ИП Горелов Максим Николаевич 8 (915) 093-74-75
Автор:
Андрей Тиунов
Во-первых, это настоящий вызов современным 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 код )
Сначала напишем функцию, которая в нашем словаре внесет правки предложенные ИИ:
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
Игра Змейка, созданная ИИ за несколько запросов
Очень хороший вопрос! Ответ - сделаем Телеграм бота, который может делать все что описано выше и может получать от вас инструкции голосом.
Бота соберем на No-code платформе ProTalk, так как там вся описанная в статье механика сделана мной в виде готового плагина/функции к боту.
Создать бота в Телеграм вы можете по этому видео-примеру: Тык
Обзор платформы в целом тут: Тык
В итоге вы получаем такого бота:
Бот, созданный на платформе ProTalk
И многое другое …
Да, процесс работы с ботом показан на видео: Смотреть
У вас есть статья о ProTalk? Поделитесь с нами - support@pro-talk.ru мы опубликуем её с вашим контактом!
© ProTalk 2023-2025 ИП Горелов Максим Николаевич ИНН 500104951533 ОГРН 309500106900065
Новая, 8, 9 Реутов, Московская область 143964 Россия