Зачем вообще это делать?
Во первых это настоящий вызов современным LLM моделям и очень интересно как разные модели будут справляться с такой задачей.
Во вторых у такого решения есть миллион способов применения во всех сферах от игровых приложений, MiniApp до бизнес задач, как например написать калькулятор стоимости на сайт.
Ну раз надо так надо, делаем )
Казалось бы что может быть проще? Берем любой чат с ИИ и говорим «напиши игру Змейка»? И нет сомнений что вы получите код страницы с рабочей версией игры.
Но.. а если вам нужно изменить в предложенном ИИ коде что-то или добавить функционала? Если вы с такими вопросами продолжите диалог с ИИ то очень быстро увидите что он начинает путаться, переписывать уже работающие блоки кода или еще хуже — выдавать вам куски кода, который нужно заменить, а остальной код не трогать.
Как же сделать так чтобы ИИ можно сам менять часть уже написанного им кода таким образом чтобы не испортить уже работающие части, а так же дописывать новый функционал к странице?
А вот как!
Давайте любой код 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 платформ. В итоге вы получаем такого бота:
Возможности бота
-
Создавать код HTML страниц с поддержкой скриптов
-
Задачи можно ставить голосом
-
Результат работы над страницей виден сразу
-
Если возможность сохранять и загружать код в GitHub
-
Можно взять код страницы по внешней ссылке и доработать его
Что еще можно создавать ботом?
-
Любые калькуляторы цен на сайт
-
Готовые игры для Телеграм MiniApp
-
Одностраничный сайт
И многое другое …
А посмотреть можно?
Да, процесс работы с ботом показан на видео: Смотреть
Итоги
Если у вас есть идеи по улучшению работы предложенной модели работы ИИ с созданием веб приложений, пишите в комментариях или мне в Телеграм.
ссылка на оригинал статьи https://habr.com/ru/articles/861770/
Добавить комментарий