Зачем вообще это делать?
Во первых это настоящий вызов современным 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
Одностраничный сайт
И многое другое …
А посмотреть можно?
Да, процесс работы с ботом показан на видео: Смотреть
Итоги
Если у вас есть идеи по улучшению работы предложенной модели работы ИИ с созданием веб приложений, пишите в комментариях или мне в Телеграм.