Привет, друзья!
В праздники я провел небольшое исследование представленных сегодня на рынке бесплатных инструментов с участием «искусственного интеллекта» для помощи в веб-разработке, результатами которого хочу с вами поделиться, авось кому-нибудь пригодится.
Обратите внимание, что я акцентировал внимание на бесплатных решениях для написания кода клиентской части веб-приложений.
Список протестированных инструментов:
Далее я подробнее расскажу о первых трех (Codeium, Devv, V0) и немного о двух следующих за ними (ChatGPT, GPT4All) решениях из представленных в списке, как наиболее «выдающихся» с моей точки зрения. Остальные сервисы (начиная с cursor и ниже) в той или иной степени похожи на codeium и `devv`, но показывают более плохие результаты генерации кода по запросу, исправления ошибок и документирования кода, а также менее удобны в использовании и иногда требуют дополнительных настроек, например, указания ключа OpenAI, установки лишних инструментов (тулкитов), являются условно бесплатными и т.п.
❯ Сodeium
Сodeium — это набор инструментов для помощи в написании нового и улучшении существующего кода, документации к нему и т.д. Работает в контексте кодовой базы, т.е. предложения по дополнению/модификации основаны на коде проекта, его стиле (используемых подходах и принципах), форматировании и т.п. Поставляется в виде расширения для VSCode, плагина для JetBrains и других редакторов. Поддерживает 70+ языков программирования. Полностью бесплатный.
В настоящее время команда codeium разрабатывает собственный редактор кода — Windsurf, где разработчик и ИИ занимаются парным программированием (затея сомнительная, но окэй :))
Преимущества использования
Контекстуальные предложения и дополнения кода
codeiumанализирует кодовую базу целиком, включая стиль, форматирование и используемые подходы. Это обеспечивает согласованность и единообразие во всех компонентах и функциях- автоматически предлагает код, который соответствует существующим стандартам, что упрощает внедрение новых компонентов и сокращает время на ревью
Поддержка документации
- Хорошая кодовая база должна включать не только код, но и качественную документацию: примеры использования компонентов, их ограничения и API
codeiumпомогает как в генерации новой, так и в улучшении существующей документации
Масштабируемость для больших проектов
- Код проекта обычно разрастается со временем, включает десятки и сотни компонентов
codeiumподдерживает работу с крупными проектами и эффективно обрабатывает большие кодовые базы
Поддержка 70+ языков программирования
- При разработке приложений используются различные технологии:
- Javascript/Typescript для логики компонентов
- CSS/TailwindCSS/CSS-in-JS для стилизации
- Markdown/MDX для документации
codeiumподдерживает все эти «языки» и позволяет гибко переключаться между разными частями проекта
Интеграция с популярными IDE
- Плагины для VSCode, JetBrains и других редакторов делают
codeiumудобным инструментом для команд, использующих разные инструменты разработки
Резюме
codeiumупрощает повторное использование кода, что критично при создании универсальных компонентов- улучшает согласованность кода за счет анализа стиля и предоставления адаптированных рекомендаций
- поддержка документации позволяет облегчить онбординг разработчиков, которые будут поддерживать или расширять проект
Попросим codeium написать TS-функцию для извлечения свойств объекта по ключам:
Я бы вместо T extends object использовал T extends Record<string, unknown>, в остальном к сгенерированному коду вопросов нет, что просили, то и получили.
Специально для адептов функционального программирования ;):
const extractProps = < T extends Record<string, unknown>, K extends keyof T, >( props: T, ...keys: K[] ) => Object.fromEntries( keys.filter((key) => key in props).map((key) => [key, props[key]]), ) as Pick<T, K>
❯ Devv_
Devv_ — это «поисковик следующего поколения для разработчиков». Показывает отличные результаты в генерации кода по запросу, в частности на JavaScript/TypeScript/React. Дает развернутые и понятные комментарии к сгенерированному коду. Полностью бесплатный.
Преимущества использования
Высокое качество генерируемого кода
devv_очень эффективен при работе с JavaScript/TypeScript/React, которые являются основой многих веб-приложений- может генерировать качественные, чистые и современные компоненты, соответствующие актуальным практикам и технологиям
Развернутые комментарии
- При разработке проекта важно не только писать код, но и объяснять сложную логику для последующей поддержки и развития другими разработчиками
devv_генерирует понятные и развернутые комментарии, что улучшает читаемость кода и упрощает развитие проекта
Решение узких задач и разработка небольших компонентов
- Для создания отдельных компонентов (например, кнопок, карточек или модальных окон)
devv_позволяет быстро генерировать код по конкретному запросу - это сокращает время на разработку и позволяет сосредоточиться на более высокоуровневых задачах (например, архитектуре системы)
Доступность
devv_работает бесплатно в браузере, не требует дополнительных настроек и может быть использован на любом этапе разработки
Резюме
devv_хорошо подходит для быстрого прототипирования компонентов и генерации примеров использования- объяснение кода помогает создавать самодокументирующуюся кодовую базу, что облегчает работу других разработчиков
- удобный веб-интерфейс делает
devv_отличным инструментом для небольших и средних команд, которые могут использовать его для проверки идей и разработки MVP
Попросим devv_ реализовать алгоритм Кнута-Морриса-Пратта (здесь про алгоритм можно почитать подробнее и найти пример его реализации):
/** * The Knuth-Morris-Pratt (KMP) algorithm is a string-searching algorithm that searches for occurrences of a "word" W within a main "text string" S. * It utilizes a pre-computed table (LPS array - Longest Proper Prefix Suffix) to optimize the search process. * * @param text The main text string to search within. * @param pattern The word (pattern) to search for in the text. * @returns An array of indices representing the starting positions of all occurrences of the pattern within the text. */ function knuthMorrisPratt(text: string, pattern: string): number[] { const textLength = text.length; const patternLength = pattern.length; // 1. Build LPS (Longest Proper Prefix Suffix) array const lps = buildLPS(pattern); // 2. Search for pattern in text const occurrences: number[] = []; let textIndex = 0; let patternIndex = 0; while (textIndex < textLength) { if (text[textIndex] === pattern[patternIndex]) { textIndex++; patternIndex++; if (patternIndex === patternLength) { // Pattern found! occurrences.push(textIndex - patternLength); patternIndex = lps[patternIndex - 1]; // Reset patternIndex using LPS } } else { if (patternIndex !== 0) { patternIndex = lps[patternIndex - 1]; // Shift pattern based on LPS } else { textIndex++; // Move to the next character in the text } } } return occurrences; } /** * Builds the LPS (Longest Proper Prefix Suffix) array for a given pattern. * * @param pattern The pattern string. * @returns The LPS array. */ function buildLPS(pattern: string): number[] { const patternLength = pattern.length; const lps: number[] = new Array(patternLength).fill(0); let length = 0; // Length of the previous longest prefix suffix lps[0] = 0; // lps[0] is always 0 let i = 1; while (i < patternLength) { if (pattern[i] === pattern[length]) { length++; lps[i] = length; i++; } else { if (length !== 0) { length = lps[length - 1]; // Fallback to previous LPS value } else { lps[i] = 0; i++; } } } return lps; } // Example usage: const text = "ABC ABCDAB ABCDABCDABDE"; const pattern = "ABCDABD"; const indices = knuthMorrisPratt(text, pattern); console.log(`Pattern found at indices: ${indices}`); // Output: Pattern found at indices: 15 const text2 = "ABABDABACDABABCABAB"; const pattern2 = "ABABCABAB"; const indices2 = knuthMorrisPratt(text2, pattern2); console.log(`Pattern found at indices: ${indices2}`); // Output: Pattern found at indices: 10 const text3 = "AAAAABAAABA"; const pattern3 = "AAAA"; const indices3 = knuthMorrisPratt(text3, pattern3); console.log(`Pattern found at indices: ${indices3}`); // Output: Pattern found at indices: 0,1 const text4 = "THIS IS A TEST TEXT"; const pattern4 = "TEST"; const indices4 = knuthMorrisPratt(text4, pattern4); console.log(`Pattern found at indices: ${indices4}`); // Output: Pattern found at indices: 10
Сгенерированный код работает примерно так, как и должен.
Попросим devv_ реализовать хук React/TS для извлечения ширины и высоты элемента при изменении его размеров:
import { useState, useRef, useLayoutEffect, useCallback } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; interface Size { width: number | undefined; height: number | undefined; } type UseResizeObserverOptions = { box?: 'border-box' | 'content-box' | 'device-pixel-content-box'; }; function useResizeObserver<T extends HTMLElement = HTMLElement>( options: UseResizeObserverOptions = {} ): [React.RefObject<T>, Size] { const { box = 'content-box' } = options; const ref = useRef<T>(null); const [size, setSize] = useState<Size>({ width: undefined, height: undefined }); const handleResize = useCallback( (entries: ResizeObserverEntry[]) => { if (!Array.isArray(entries)) { return; } const entry = entries[0]; const newWidth = entry.contentRect.width; const newHeight = entry.contentRect.height; setSize({ width: newWidth, height: newHeight }); }, [] ); useLayoutEffect(() => { if (!ref.current) return; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(ref.current, { box }); return () => { resizeObserver.disconnect(); }; }, [ref.current, box, handleResize]); return [ref, size]; } export default useResizeObserver;
import React from 'react'; import useResizeObserver from './useResizeObserver'; const MyComponent: React.FC = () => { const [ref, size] = useResizeObserver(); return ( <div ref={ref} style={{ width: '50%', height: '200px', border: '1px solid black' }}> {size.width && size.height && ( <p>Width: {size.width}px, Height: {size.height}px</p> )} </div> ); }; export default MyComponent;
К этому коду есть несколько вопросов, например:
- на сегодняшний день нам не нужен полифил
ref.currentв зависимостях хукаuseLayoutEffect()не нужен- вместо
useLayoutEffect()я бы использовалuseEffect()и т.д.
Но, в целом, код довольно качественный и с ним можно работать.
❯ V0
v0 — AI-помощник, специализирующийся на веб-разработке. Показывает отличные результаты в написании кода на JavaScript/TypeScript по запросу. Генерирует компоненты React с использованием современных подходов и технологий. Дает развернутые и понятные комментарии к написанному коду. Полностью бесплатный.
Преимущества использования
Фокус на веб-разработке
v0специально заточен под React и TypeScript, которые сегодня фактически являются стандартом разработки веб-приложений- генерирует компоненты с использованием современных подходов (например, хуков React, TailwindCSS или стилизованных компонентов (Styled Components)).
Автоматизация создания компонентов
- Позволяет быстро и эффективно генерировать модульные и переиспользуемые компоненты на основе запроса
- подходит для создания как автономных компонентов (например, кнопок, иконок, выпадающих списков), так и составных компонентов (например, форм или карточек)
Развернутые комментарии и объяснения
v0генерирует развернутые пояснения к сгенерированному коду. Это помогает документировать кодовую базу и обеспечивает прозрачность логики компонентов
Быстрая интеграция в проекты
- Созданные компоненты легко интегрируются в существующие проекты
- позволяет следовать принципам DRY (Don’t Repeat Yourself) и атомарному подходу к разработке компонентов
Простота использования
v0работает бесплатно в браузере, не требует установки дополнительных инструментов и предоставляет простой, понятный и удобный интерфейс
Резюме
v0отлично подходит для генерации и оптимизации компонентов, что ускоряет разработку веб-приложений- фокус на React и современных технологиях делает его особенно полезным при разработке библиотек компонентов (дизайн-систем)
- объяснения и комментарии помогают команде быстро разбираться в коде и легко расширять проект
Попросим v0 сгенерировать код компонента React «Модальное окно» с помощью элемента HTML dialog:

// Предполагается использование серверных компонентов Next.js 'use client' import React, { useRef, useEffect, ReactNode } from 'react' import { X } from 'lucide-react' import { Button } from '@/components/ui/button' interface ModalProps { isOpen: boolean onClose: () => void children: ReactNode title: string } export function Modal({ isOpen, onClose, children, title }: ModalProps) { const dialogRef = useRef<HTMLDialogElement>(null) useEffect(() => { const dialogElement = dialogRef.current if (!dialogElement) return if (isOpen) { dialogElement.showModal() } else { dialogElement.close() } const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { onClose() } } if (isOpen) { document.addEventListener('keydown', handleKeyDown) } return () => { document.removeEventListener('keydown', handleKeyDown) } }, [isOpen, onClose]) const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => { if (e.target === dialogRef.current) { onClose() } } return ( {/* dialog, как мы и просили */} <dialog ref={dialogRef} // Предполагается использование tailwind className="p-0 bg-background rounded-lg shadow-lg backdrop:bg-black/50" onClick={handleBackdropClick} > <div className="min-w-[300px] max-w-md"> <div className="flex justify-between items-center p-4 border-b"> <h2 className="text-lg font-semibold">{title}</h2> <Button variant="ghost" size="icon" onClick={onClose} aria-label="Close"> <X className="h-4 w-4" /> </Button> </div> <div className="p-4">{children}</div> </div> </dialog> ) }
'use client' import { useState } from 'react' import { Modal } from '@/components/Modal' import { Button } from '@/components/ui/button' export default function Home() { const [isModalOpen, setIsModalOpen] = useState(false) return ( <div className="flex flex-col items-center justify-center min-h-screen p-4"> <h1 className="text-2xl font-bold mb-4">Modal Demo</h1> <Button onClick={() => setIsModalOpen(true)}>Open Modal</Button> <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} title="Example Modal" > <p>This is the content of the modal. You can put any React components or HTML here.</p> <div className="mt-4 flex justify-end"> <Button onClick={() => setIsModalOpen(false)}>Close</Button> </div> </Modal> </div> ) }
I’ll create a React modal component using the HTML <dialog> element. This approach provides a native, accessible modal solution with minimal JavaScript. Let’s build this component step by step.
This implementation creates a reusable Modal component that uses the HTML <dialog> element. Here’s a breakdown of the key features:
- The
Modalcomponent acceptsisOpen,onClose,children, andtitleas props. - It uses the
useRefhook to get a reference to the<dialog>element. - The
useEffecthook is used to control the opening and closing of the modal based on theisOpenprop. - It implements a click handler on the backdrop to close the modal when clicking outside of it.
- The component is styled using Tailwind CSS classes for a clean, modern look.
- It uses the
Buttoncomponent from the UI library for consistency. - The modal is accessible, supporting closing via the Escape key and using semantic HTML.
In the Home component, we demonstrate how to use the Modal:
- We use the
useStatehook to manage the open state of the modal. - A button is provided to open the modal.
- The
Modalcomponent is rendered with the necessary props, including the content as children.
This implementation provides a flexible, accessible, and easy-to-use modal component that leverages the native <dialog> element for better performance and built-in accessibility features.
Не то, чтобы к сгенерированному коду не было вопросов, но в качестве отправной точки вполне подойдет.
❯ ChatGPT и GPT4All
ChatGPT — это написанное на Rust десктопное приложение для Mac, Windows и Linux. О ChatGPT написано млн статей, так что, с вашего позволения, я сразу перейду к примеру использования приложения.
Скормим ChatGPT скриншот кода, сгенерированного codeium и попросим его улучшить:
Yes, I can improve this code snippet. I’ll refine it to make it more concise and efficient while adhering to best practices. Here’s the updated code:
Improved Code
function extractPropertiesByKeys<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { return keys.reduce((result, key) => { if (key in obj) { result[key] = obj[key]; } return result; }, {} as Pick<T, K>); }
Improvements Made
- Replaced the for…of Loop with reduce:
Using Array.prototype.reduce is more functional and concise for building an object iteratively.
- Removed the Need for Type Assertion in the Return Statement:
The result is initialized as {} as Pick<T, K> to ensure proper typing without needing assertions later.
- Avoided the Partial Utility Type:
The Pick type is sufficient for ensuring that only the specified keys are included in the resulting object. Using Partial was unnecessary and could lead to confusion.
- Better Readability and Clarity:
This version is shorter and aligns with modern JavaScript/TypeScript functional programming practices.
Example Usage
interface Person { name: string; age: number; address: string; } const person: Person = { name: "Alice", age: 30, address: "123 Main St" }; const selectedProperties = extractPropertiesByKeys(person, ["name", "age"]); console.log(selectedProperties); // Output: { name: "Alice", age: 30 }
Let me know if you’d like further refinements or additional explanations!
Попросим ChatGPT придать коду более функциональный вид:
Где-то такой вариант утилиты мы уже видели 🙂
Certainly! A functional programming approach emphasizes immutability and the use of higher-order functions. Here’s the rewritten code using a functional programming paradigm:
Functional Programming Version
function extractPropertiesByKeys<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { return Object.fromEntries( keys .filter((key) => key in obj) .map((key) => [key, obj[key]]) ) as Pick<T, K>; }
Explanation of Functional Approach
-
filter:
Filters the keys array to include only keys that exist in the object. -
map:
Transforms each valid key into a [key, value] pair (tuples) suitable for creating an object. -
Object.fromEntries:
Converts the array of key-value pairs into an object.
Example Usage
interface Person { name: string; age: number; address: string; } const person: Person = { name: "Alice", age: 30, address: "123 Main St" }; const selectedProperties = extractPropertiesByKeys(person, ["name", "age"]); console.log(selectedProperties); // Output: { name: "Alice", age: 30 }
Why This Is Functional
- Immutability: No variables are mutated; each function call produces a new result.
- Higher-Order Functions: filter and map are used for processing collections in a declarative way.
- Declarative Readability: The code describes what to do rather than how to do it.
Let me know if you’d like more refinements or explanations!
В завершение кратко рассмотрим еще одно интересное решение — GPT4All.
GPT4All — это десктопное приложение для работы с локальными (!) большими языковыми моделями. Показывает хорошие результаты в написании кода по запросу, в частности на JavaScript/TypeScript/React. Дает развернутые и понятные комментарии к сгенерированному коду. Бесплатный, но для работы требуется БЯМ (например, модель Llama 3 весит 4,34 ГБ и требует 8 ГБ оперативной памяти, минимум). Работает как онлайн, так и оффлайн.
Это единственное готовое бесплатное решение, которое я обнаружил, позволяющее использовать всю мощь ИИ без доступа к Интернету, что может быть важно в некоторых ситуациях, например, когда вы опасаетесь, что ваши данные без вашего ведома могут оказаться в распоряжении сервиса, предоставляющего услуги.
В заключение отмечу, что в своей работе я часто использую codeium и ChatGPT и иногда прибегаю к помощи v0.
Делитесь вашими находками в комментариях.
Happy coding!
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
ссылка на оригинал статьи https://habr.com/ru/articles/873430/

Добавить комментарий