Как заставить ИИ на базе LLM писать полноценные приложения на HTML + CSS + JavaScript

Как заставить ИИ на базе LLM писать полноценные приложения на HTML + CSS + JavaScript

Зачем вообще это делать?

Во-первых, это настоящий вызов современным LLM моделям, и очень интересно как разные модели будут справляться с такой задачей.

Во вторых у такого решения есть миллион способов применения во всех сферах от игровых приложений, MiniApp до бизнес задач, как например написать калькулятор стоимости на сайт.

Ну раз надо так надо, делаем )

Казалось бы что может быть проще? Берем любой чат с ИИ и говорим "напиши игру Змейка"? И нет сомнений что вы получите код страницы с рабочей версией игры.

Как заставить ИИ на базе LLM писать полноценные приложения на HTML + CSS + JavaScript

Но.. а если вам нужно изменить в предложенном ИИ коде что-то или добавить функционала? Если вы с такими вопросами продолжите диалог с ИИ то очень быстро увидите что он начинает путаться, переписывать уже работающие блоки кода или еще хуже - выдавать вам куски кода, который нужно заменить, а остальной код не трогать.

Как же сделать так чтобы ИИ можно сам менять часть уже написанного им кода таким образом чтобы не испортить уже работающие части, а так же дописывать новый функционал к странице?

А вот как!

Давайте любой код HTML разобьем на блоки/кирпичики/слоты и заставим ИИ писать код используя такие блоки, а самое главное потом заменять/удалять/добавлять такие блоки САМОСТОЯТЕЛЬНО.

Вот как будет выглядеть тогда пустая страница, разбитая на такие виртуальные блоки:

<html> <head> <!-- HEAD_BLOCK_1 --><meta charset="UTF-8"><title>My Page</title><!-- /HEAD_BLOCK_1 --> <!-- HEAD_BLOCK_2 --><style>body {color:#333;background:#ccc;}</style><!-- /HEAD_BLOCK_2 --> <!-- HEAD_BLOCK_3 --><script>alert('Hello World!')</script><!-- /HEAD_BLOCK_3 --> </head> <body> <!-- BODY_BLOCK_1 --><header><h1>Welcome</h1></header><!-- /BODY_BLOCK_1 --> <!-- BODY_BLOCK_2 --><main><p>Main content here</p></main><!-- /BODY_BLOCK_2 --> <!-- BODY_BLOCK_3 --><footer>Footer content</footer><!-- /BODY_BLOCK_3 --> </body> </html>

Главный фокус

И сразу разоблачение: мы НИКОГДА не будем показывать ИИ сам код HTML!

Мы подготовим словарь, содержащий блоки и показывать будем только его.

Вот код функции, которая создает словарь в Python по нашей структуре HTML документа:

import re def html_to_dict(html): structure = { 'html': { 'head': {'blocks': []}, 'body': {'blocks': []} } } head_pattern = r'<!-- HEAD_BLOCK_\d+ -->(.*?)<!-- /HEAD_BLOCK_\d+ -->' body_pattern = r'<!-- BODY_BLOCK_\d+ -->(.*?)<!-- /BODY_BLOCK_\d+ -->' head_blocks = re.findall(head_pattern, html, re.DOTALL) body_blocks = re.findall(body_pattern, html, re.DOTALL) structure['html']['head']['blocks'] = head_blocks structure['html']['body']['blocks'] = body_blocks return structure

В результате наш код пустой странице после выполнения этого кода будет выглядеть так:

{ "html": { "body": { "blocks": [ "<header><h1>Welcome</h1></header>", "<main><p>Main content here</p></main>", "<footer>Footer content</footer>" ] }, "head": { "blocks": [ "<meta charset=\"UTF-8\"><title>My Page</title>", "<style>body {color:#333;background:#ccc;}</style>", "<script>alert('Hello World!')</script>" ] } } }

И вот именно в этом виде ИИ будет работать с кодом.

Теперь немного промптинга, куда уж без него )

Роль для ИИ может выглядеть так:

Твоя задача - создавать и редактировать HTML/CSS/JavaScript код. При этом код страницы соответствует такому шаблону: ``` <html> <head> <!-- HEAD_BLOCK_1 --><meta charset="UTF-8"><title>My Page</title><!-- /HEAD_BLOCK_1 --> <!-- HEAD_BLOCK_2 --><style>body {color:#333;background:#ccc;}</style><!-- /HEAD_BLOCK_2 --> <!-- HEAD_BLOCK_3 --><script>alert('Hello World!')</script><!-- /HEAD_BLOCK_3 --> </head> <body> <!-- BODY_BLOCK_1 --><header><h1>Welcome</h1></header><!-- /BODY_BLOCK_1 --> <!-- BODY_BLOCK_2 --><main><p>Main content here</p></main><!-- /BODY_BLOCK_2 --> <!-- BODY_BLOCK_3 --><footer>Footer content</footer><!-- /BODY_BLOCK_3 --> </body> </html> ``` Твой ответ должен быть всегда только в виде строк с описанием изменений: ``` head:::1:::@@@<meta charset="UTF-8"><title>My Website</title>@@@&&& head:::2:::@@@<style>body {color:#000;}</style>@@@&&& head:::3:::@@@<meta name="viewport" content="width=device-width, initial-scale=1.0">@@@&&& head:::4:::@@@<script>alert('Hello');</script>@@@&&& body:::1:::@@@<header><h1>Welcome to our site</h1></header>@@@&&& body:::2:::@@@<nav><ul><li>Home</li><li>About</li><li>Contact</li></ul></nav>@@@&&& body:::3:::@@@<main><article><h2>Latest News</h2><p>Content here</p></article></main>@@@&&& body:::4:::@@@<aside><div class="widget">Sidebar content</div></aside>@@@&&& body:::5:::@@@<footer><p>© 2024 My Company</p></footer>@@@&&& ``` Важно чтобы стили и скрипты были описаны внутри HTML, исключения могут быть только ссылки на библиотеки стилей (например bootstrap) или JavaScript (jquery) должны быть только в виде внешних ссылок. Если ты добавляешь код, которого еще не было, то добавляй его в новый номер блока. Если ты хочешь изменить код в блоке, который уже есть, то обязательно ответь полным кодом этого блока.

Это не идеальный но уже вполне рабочий промпт, который слушается большинство LLM моделей.

Теперь посмотрим как выглядит запрос к ИИ:

# html_dict - словарь, полученный из HTML на предыдущем шаге # task - Задача, например: "Создай игру Змейка" api_key = 'sk-XXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' client = openai.OpenAI(api_key=api_key) response = client.chat.completions.create( model="gpt-4o-mini", #"o1-mini", messages=[ { "role": "user", "content": role # - это наш промпт, приведенный выше }, { "role": "user", "content": f"Текущий код страницы: ```{html_dict}``` и вот новая задача: ```{task}```" } ] ) ai_reply = response.choices[0].message.content

И вот какой ответ мы получим от ИИ при пустой странице и запросе "создай игру Змейка" (на модели gpt-4o-mini):

head:::1:::@@@<meta charset="UTF-8"><title>Snake Game</title>@@@&&& head:::2:::@@@<style>body {color:#000; background:#f0f0f0; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; } canvas { border: 1px solid #000; }</style>@@@&&& head:::3:::@@@<script>let canvas, ctx, snake, food, score, direction; function init() { canvas = document.createElement('canvas'); ctx = canvas.getContext('2d'); document.body.appendChild(canvas); canvas.width = 400; canvas.height = 400; snake = [{ x: 10, y: 10 }]; food = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) }; score = 0; direction = { x: 0, y: 0 }; window.addEventListener('keydown', changeDirection); gameLoop(); } function gameLoop() { update(); draw(); setTimeout(gameLoop, 100); } function update() { const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y }; snake.unshift(head); if (head.x === food.x && head.y === food.y) { score++; food = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) }; } else { snake.pop(); } } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let segment of snake) { ctx.fillStyle = 'green'; ctx.fillRect(segment.x * 20, segment.y * 20, 20, 20); } ctx.fillStyle = 'red'; ctx.fillRect(food.x * 20, food.y * 20, 20, 20); ctx.fillStyle = 'black'; ctx.fillText('Score: ' + score, 10, 20); } function changeDirection(event) { if (event.key === 'ArrowUp') { direction = { x: 0, y: -1 }; } else if (event.key === 'ArrowDown') { direction = { x: 0, y: 1 }; } else if (event.key === 'ArrowLeft') { direction = { x: -1, y: 0 }; } else if (event.key === 'ArrowRight') { direction = { x: 1, y: 0 }; } } window.onload = init;</script>@@@&&& body:::1:::@@@<header><h1>Snake Game</h1></header>@@@&&& body:::2:::@@@<main><p>Use arrow keys to move the snake and eat the food!</p></main>@@@&&& body:::3:::@@@<footer><p>© 2024 Snake Game</p></footer>@@@&&&

Назад в HTML!

Теперь наша задача из этой абракадабры обратно собрать HTML код )

Сначала напишем функцию, которая в нашем словаре внесет правки предлложенные ИИ:

import copy def merge_changes(current_structure, changes): new_structure = { 'html': { 'head': {'blocks': copy.deepcopy(current_structure['html']['head']['blocks'])}, 'body': {'blocks': copy.deepcopy(current_structure['html']['body']['blocks'])} } } for change in changes: if change and ':::' in change: section, number, content = change.split(':::', 2) section = section.replace('\n', '').replace(' ', '') content = content.split('@@@')[1].replace('\n', '').replace('\r', '') block_index = int(number) - 1 if section == 'head': while len(new_structure['html']['head']['blocks']) <= block_index: new_structure['html']['head']['blocks'].append('') new_structure['html']['head']['blocks'][block_index] = content elif section == 'body': while len(new_structure['html']['body']['blocks']) <= block_index: new_structure['html']['body']['blocks'].append('') new_structure['html']['body']['blocks'][block_index] = content return new_structure # changes - это ответ от ИИ с предложенным списком изменений new_structure = merge_changes(current_structure, changes.split('&&&\n'))

Итогом работы этой функции будет обновленный словарь вида:

{ "html": { "body": { "blocks": [ "<header><h1>Snake Game</h1></header>", "<main><p>Use arrow keys to move the snake and eat the food!</p></main>", "<footer><p>\u00a9 2024 Snake Game</p></footer>" ] }, "head": { "blocks": [ "<meta charset=\"UTF-8\"><title>Snake Game</title>", "<style>body {color:#000; background:#f0f0f0; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; } canvas { border: 1px solid #000; }</style>", "<script>let canvas, ctx, snake, food, score, direction; function init() { canvas = document.createElement('canvas'); ctx = canvas.getContext('2d'); document.body.appendChild(canvas); canvas.width = 400; canvas.height = 400; snake = [{ x: 10, y: 10 }]; food = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) }; score = 0; direction = { x: 0, y: 0 }; window.addEventListener('keydown', changeDirection); gameLoop(); } function gameLoop() { update(); draw(); setTimeout(gameLoop, 100); } function update() { const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y }; snake.unshift(head); if (head.x === food.x && head.y === food.y) { score++; food = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) }; } else { snake.pop(); } } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let segment of snake) { ctx.fillStyle = 'green'; ctx.fillRect(segment.x * 20, segment.y * 20, 20, 20); } ctx.fillStyle = 'red'; ctx.fillRect(food.x * 20, food.y * 20, 20, 20); ctx.fillStyle = 'black'; ctx.fillText('Score: ' + score, 10, 20); } function changeDirection(event) { if (event.key === 'ArrowUp') { direction = { x: 0, y: -1 }; } else if (event.key === 'ArrowDown') { direction = { x: 0, y: 1 }; } else if (event.key === 'ArrowLeft') { direction = { x: -1, y: 0 }; } else if (event.key === 'ArrowRight') { direction = { x: 1, y: 0 }; } } window.onload = init;</script>" ] } } }

Ну и наконец-то мы теперь можем собрать обратно HTML код вот такой функцией:

def dict_to_html(structure): html = ['<html>', '<head>'] for i, block in enumerate(structure['html']['head']['blocks'], 1): if block: # Проверяем, что блок не пустой html.append(f' <!-- HEAD_BLOCK_{i} -->{block}<!-- /HEAD_BLOCK_{i} -->') html.append('</head>') html.append('<body>') for i, block in enumerate(structure['html']['body']['blocks'], 1): if block: # Проверяем, что блок не пустой html.append(f' <!-- BODY_BLOCK_{i} -->{block}<!-- /BODY_BLOCK_{i} -->') html.append('</body>') html.append('</html>') return '\n'.join(html)

На выходе получим HTML код работающей игры Змейка:

<html> <head> <!-- HEAD_BLOCK_1 --><meta charset="UTF-8"><title>Snake Game</title><!-- /HEAD_BLOCK_1 --> <!-- HEAD_BLOCK_2 --><style>body {color:#000; background:#f0f0f0; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; } canvas { border: 1px solid #000; }</style><!-- /HEAD_BLOCK_2 --> <!-- HEAD_BLOCK_3 --><script>let canvas, ctx, snake, food, score, direction; function init() { canvas = document.createElement('canvas'); ctx = canvas.getContext('2d'); document.body.appendChild(canvas); canvas.width = 400; canvas.height = 400; snake = [{ x: 10, y: 10 }]; food = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) }; score = 0; direction = { x: 0, y: 0 }; window.addEventListener('keydown', changeDirection); gameLoop(); } function gameLoop() { update(); draw(); setTimeout(gameLoop, 100); } function update() { const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y }; snake.unshift(head); if (head.x === food.x && head.y === food.y) { score++; food = { x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) }; } else { snake.pop(); } } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let segment of snake) { ctx.fillStyle = 'green'; ctx.fillRect(segment.x * 20, segment.y * 20, 20, 20); } ctx.fillStyle = 'red'; ctx.fillRect(food.x * 20, food.y * 20, 20, 20); ctx.fillStyle = 'black'; ctx.fillText('Score: ' + score, 10, 20); } function changeDirection(event) { if (event.key === 'ArrowUp') { direction = { x: 0, y: -1 }; } else if (event.key === 'ArrowDown') { direction = { x: 0, y: 1 }; } else if (event.key === 'ArrowLeft') { direction = { x: -1, y: 0 }; } else if (event.key === 'ArrowRight') { direction = { x: 1, y: 0 }; } } window.onload = init;</script><!-- /HEAD_BLOCK_3 --> </head> <body> <!-- BODY_BLOCK_1 --><header><h1>Snake Game</h1></header><!-- /BODY_BLOCK_1 --> <!-- BODY_BLOCK_2 --><main><p>Use arrow keys to move the snake and eat the food!</p></main><!-- /BODY_BLOCK_2 --> <!-- BODY_BLOCK_3 --><footer><p>© 2024 Snake Game</p></footer><!-- /BODY_BLOCK_3 --> </body> </html>

Игра Змейка

Вот тут можете поиграть в начальную версию игру, описанную в статье: Змейка 1.0

А вот версия игры после нескольких последовательных запросов на улучшение кода: Змейка 3.0

Игра Змейка, созданная ИИ за несколько запросов
Игра Змейка, созданная ИИ за несколько запросов

Ну и как этим пользоваться если я не знаю HTML?

Очень хороший вопрос! Ответ - сделаем Телеграм бота, который может делать все что описано выше и может получать от вас инструкции голосом.

Бота соберем на No-code платформе ProTalk, так как там вся описанная в статье механика сделана мной в виде готового плагина/функции к боту.

Создать бота в Телеграм вы можете по этому видео-примеру: Тык

Обзор платформы в целом тут: Тык

В итоге вы получаем такого бота:

Бот, созданный на платформе ProTalk
Бот, созданный на платформе ProTalk

Возможности бота

  • Создавать код HTML страниц с поддержкой скриптов
  • Задачи можно ставить голосом
  • Результат работы над страницей виден сразу
  • Если возможность сохранять и загружать код в GitHub
  • Можно взять код страницы по внешней ссылке и доработать его

Что еще можно создавать ботом?

  • Любые калькуляторы цен на сайт
  • Готовые игры для Телеграм MiniApp
  • Одностраничный сайт

И многое другое …

А посмотреть можно?

Да, процесс работы с ботом показан на видео: Смотреть

Итоги

Если у вас есть идеи по улучшению работы предложенной модели работы ИИ с созданием веб приложений, пишите в комментариях или мне в Телеграм.

22
1 комментарий

Это про платформу ProTalk речь идет, если нужен такой бот? Или еще где-то можно собрать?

1