Привет, друзья!
В праздники я провел небольшое исследование представленных сегодня на рынке бесплатных инструментов с участием "искусственного интеллекта" для помощи в веб-разработке, результатами которого хочу с вами поделиться, авось кому-нибудь пригодится.
Обратите внимание, что я акцентировал внимание на бесплатных решениях для написания кода клиентской части веб-приложений.
Список протестированных инструментов:
Далее я подробнее расскажу о первых трех (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
Modal
component acceptsisOpen
,onClose
,children
, andtitle
as props. - It uses the
useRef
hook to get a reference to the<dialog>
element. - The
useEffect
hook is used to control the opening and closing of the modal based on theisOpen
prop. - 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
Button
component 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
useState
hook to manage the open state of the modal. - A button is provided to open the modal.
- The
Modal
component 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-канале ↩
Комментарии (3)
Alexandroppolus
22.01.2025 09:51По поводу
useResizeObserver
, сотворенного devv_: там действительно не нужен ref.current в зависимостях эффекта, он ведь меняется после рендера. Более того, этот хук сможет работать, только если элемент, помещаемый в реф, создается в момент монтирования компонента с хуком и живет всё время, пока жив родитель. Чтобы работало во всех случаях, надо вообще убрать зависимости из эффекта, и уже вручную в функции эффекта проверять, поменялся ли box или ref.current. Дополнительно понадобится ещё эффект для очистки при размонтировании. Как-то так:Код
const prevRef = useRef(ref.current); const prevBoxRef = useRef(box); const refObserver = useRef<ResizeObserver>(null); useLayoutEffect(() => { // проверяем, что по факту депсы не поменялись if (ref.current === prevRef.current && box === prevBoxRef.current) { return; } // дисконнект предыдущего refObserver.current?.disconnect(); refObserver.current = null; if (!ref.current) return; refObserver.current = new ResizeObserver(handleResize); refObserver.current.observe(ref.current, { box }); }); // размонтирование компонента useLayoutEffect(() => () => refObserver.current?.disconnect(), []);
Может ли devv_ (или кто-либо иной из списка) выдать что-то подобное, если ему объяснить проблему?
Alexandroppolus
22.01.2025 09:51забыл добавить обновления
prevRef
иprevBoxRef
, сразу после проверки... prevBoxRef.current = ref.current; prevBoxRef.current = box; ...
positroid
Во всех перечисленных инструментах из списка вы тестировали только режим чата? У всех или почти всех под капотом одни и те же модели (claude sonnet 3.5 или openai gpt-4o-mini), в рамках сравнения чата логичнее было тогда уж сравнивать именно результаты, которые выдают сами модели напрямую (а заодно фичи чатов вроде артефактов у claude или canvas у openai).
Для инструментов чат скорее вторичен - их нужно сравнивать уже по другому набору функций - качество подсказок, понимание и размер контекста, фичи, которые предоставляет инструмент для разработки (агенты, комментарии к коммитам, поиск по кодовой базе, возможность выполнять команды на устройстве).
Copilot с недавних пор бесплатен с определенными лимитами, но наиболее функционален в рамках VS Code. Cursor имеет бесплатный период в 2 недели, но может в агентном режиме сам читать и писать код непосредственно в проекте, без ручного копирования между чатами.