Как построить визуальный no-code редактор воронок и AI пайплайнов на React поверх low-code ядра Metabot
Попробовать: https://cjm.metabot24.ru/cjm-designer
Этот урок — про то, как поверх Metabot (который сам по себе полный low-code/full-code backend для чат-ботов и ассистентов) построить новый продукт: визуальный конструктор чат-ботов.
То есть вы создадите реальный продуктовый интерфейс — свой ManyChat / BotHelp, но умнее и современнее.
Главная идея:
Metabot — это low-code/full-code ядро.
Пройдя этот урок, вы научитесь строить целые продукты поверх ядра, создавая собственный фронт, визуальный редактор, AI-функции и конструкторы.
Часть 1. Что будет в этом уроке — и чего здесь не будет
Чтобы вы сразу понимали рамки: этот урок — это фундамент, первая версия конструктора, на котором вы сможете дальше строить полноценный продукт. Здесь мы создаём работающий скелет визуального редактора и настраиваем импорт в Metabot.
А теперь — по пунктам.
Что будет в уроке
1. Создадим собственный JSON-формат воронки
Вы:
-
придумаете свой формат,
-
определите структуру шагов воронки,
-
создадите свои типы команд,
-
добавите свои параметры и опции.
То есть вы спроектируете свой DSL (domain-specific language) — язык описания воронок вашего продукта.
2. Разберётесь, как работает Mapper
Мы подробно посмотрим, как:
-
входящий JSON обрабатывается,
-
шаги проверяются,
-
неподдерживаемые типы фильтруются,
-
нужные команды собираются,
-
создаются переходы, кнопки, логгирование, LLM-вызовы и так далее.
То есть вы поймёте логику трансформации вашего формата → формат Metabot.
Вы получите исходный код Mapper'а.
3. Разберётесь, как работает Builder
Мы детально пройдём по механике:
-
создания секции/папки скриптов в Metabot,
-
создания скриптов,
-
добавления low-code команд в скрипт,
-
создания переходов и menu-кнопок,
-
удаления старых версий воронки.
И главное — вы увидите, как Builder напрямую манипулирует базой данных Metabot (PostgreSQL) через PHP/V8.
Это даёт понимание того, как работает ядро платформы и как можно строить свои надстройки.
Вы получите исходный код Builder'а.
4. Научитесь подключать Metabot как backend
Вы увидите, как:
-
передавать JSON в Metabot через API,
-
запускать импорт,
-
пересобирать воронку целиком,
-
структурировать код для своего продукта.
То есть Metabot превращается для вас в backend-as-a-platform, который вы можете использовать под любые проекты.
5. Сделаем визуальный редактор на React (с помощью V0 + Cursor)
Мы:
-
создадим canvas и интерфейс узлов,
-
свяжем их с JSON,
-
добавим импорт и экспорт в/из файла,
-
добавим базовые настройки шагов и валидации,
-
подготовим front-end архитектуру.
Вы получите:
-
исходный код редактора,
-
архитектуру компонентов,
-
промпты, с помощью которых редактор собирался (это важно — это обучает самостоятельному созданию продуктов).
6. Настроим рабочий цикл разработки
Первые итерации будут такие:
1) создаём JSON вручную →
2) отправляем через Postman →
3) смотрим, как Metabot создал воронку →
4) фиксируем ошибки →
5) делаем свои команды →
6) строим визуальный редактор, который уже генерит нужный JSON →
7) импортируем в Metabot одной кнопкой
Это честный, практичный, продуктовый процесс.
7. Вы получите всё, чтобы создать свой продукт
В итоге у вас будет:
-
работающий визуальный редактор,
-
свой JSON-формат,
- свой набор команд,
- сохранение в файл и загрузка из файла,
-
исходники маппера/билдера,
-
связка React → JSON → Metabot.
На этом можно строить:
-
no-code конструктор,
-
CJM-редактор,
-
AI-конструктор цепочек,
-
бизнесовый продукт поверх Metabot.
🚫 Чего в этом уроке НЕ будет
Чтобы не вводить в заблуждение — сразу фиксируем:
1. Не будет двусторонней синхронизации
Мы делаем только импорт из вашего конструктора → Metabot.
То есть:
-
если воронка существует — она удаляется и создаётся заново,
-
мы не читаем текущую структуру из Metabot,
-
мы не отображаем её обратно в редактор.
Это осознанное упрощение.
Вторую сторону можно сделать, но это отдельный блок задач:
читать данные Metabot → конвертировать → отображать → синхронизировать версии.
Мы это оставляем для следующего урока, если будет запрос.
2. Не будет полноценного менеджмента трафика
Это очень важный момент.
В реальной жизни по ранее импортированной воронке уже могут идти живые пользователи.
Если вы меняете воронку, возникают вопросы:
-
что делать с активными пользователями?
-
надо ли их перенаправлять в новую версию?
-
надо ли блокировать обновления?
-
нужно ли создавать копии воронок?
-
нужно ли делать миграции шагов?
-
как правильно писать стратегию версионирования?
Это целая философия, и у каждого продукта — свой подход.
Мы эту сложность сознательно не включаем в этот урок.
Но если понадобится понадобится помощь — дайте знать, попробуем помочь.
3. Не будет полноценной "платформенности"
В этом уроке:
- нет авторизации и регистрации — импорт по API токену пользователя, привязанному к боту.
- нет совместной работы.
Мы делаем первую версию конструктора, но она уже рабочая и расширяемая.
Принял. Делаю первую итерацию — без фанатизма, но уже структурно, лаконично, красиво, чтобы можно было вложить в документацию и развивать дальше.
Ты дал огромный объём информации, поэтому я сейчас фиксирую каркас главы 2, + делаю первый блок: спецификация команд Metabot + JSON-формат, + простую таблицу команд, красиво оформленную, + мини-описание модели данных (sentences, phrases, references).
В следующей итерации я смогу:
-
добить оставшиеся разделы,
-
оформить финальный CJM JSON на 20 шагов,
-
добавить продвинутую постановку задач.
Глава 2. JSON-формат и фундаментальная модель данных Metabot
Чтобы создать собственный визуальный конструктор поверх Metabot, нужно понимать две вещи:
-
Какие команды реально существуют в ядре платформы
-
Как Metabot хранит эти команды в базе данных
Эта глава даёт разработчику язык конструктора — JSON-формат шага, таблицу команд и модель хранения внутри Metabot.
2.1. Базовая модель данных Metabot
Метабот — low-code движок. Его диалоговая логика физически живёт в трёх таблицах:
sentences — это скрипты
Каждый шаг в нашем редакторе превратится в отдельный скрипт Metabot.
| Поле | Что значит |
|---|---|
id |
ID скрипта |
code |
Уникальный код (мы сами создаём) |
name |
Читабельное имя |
bot_id |
Бот, которому принадлежит скрипт |
section_id |
Папка/категория (наша “воронка”) |
| ... | Остальные поля нам пока не нужны |
Пример:
id: 85681
code: "test-script"
name: "TestScript"
bot_id: 2370
phrases — это команды внутри скрипта
Одна запись = одна команда.
Каждый шаг в нашем редакторе может порождать от 1 до 10 фраз.
| Поле | Описание |
|---|---|
id |
ID команды |
type |
Тип команды (send_text, run_javascript, …) |
sentence_id |
Скрипт, которому команда принадлежит |
content |
Текст или JSON-контент команды |
sort_order |
Порядок выполнения |
alias |
Уникальный ID команды |
Пример вывода send_text:
type: "send_text"
sentence_id: 85681
content: "Hello, World!"
sort_order: 0
Пример ввода пользователя value_input:
id: 231462
type: value_input
sentence_id: 85681
content: {"attribute_key":"var","prompt":"Введите число",...}
sort_order: 1
references — это кнопки (меню)
Меню — это не фраза, а отдельная таблица (так исторически, да). Каждая запись — это кнопка.
| Поле | Значение |
|---|---|
sentence_id |
Скрипт, которому меню принадлежит |
caption |
Текст кнопки |
code |
Код/значение кнопки |
jump_sentence_id |
Куда переходить |
sort_order |
Порядок кнопок |
line_num |
Для многострочных клавиатур |
condition_script_code |
Условие отображения |
Пример:
sentence_id: 85681
caption: "Start Over"
code: "1"
jump_sentence_id: 85680
📌 Все 21 команды Метабота
На момент написания этого урока в Metabot есть 20 команд в скриптах + одна неявная команда (меню).
| № | Команда | Что делает | Где хранится | Пример content в БД |
|---|---|---|---|---|
| 1 | send_text | отправляет текстовое сообщение | phrases | Hello, World!!! |
| 2 | value_input | запрашивает ввод и валидирует | phrases | {"attribute_key":"abc","prompt":"Введите число", ...} |
| 3 | run_javascript | выполняет JS на сервере | phrases | lead.setAttr('abc', 123); |
| 4 | send_image | отправляет изображение | phrases | {"path":"bot/.../logo.png","name":"logo.png"} |
| 5 | send_file | отправляет файл (любые документы) | phrases | {"path":"bot/...","name":"file.pdf"} |
| 6 | run_sentence | выполняет другой скрипт, останавливает текущий | phrases | 85681 |
| 7 | send_email | отправляет email | phrases | {"recipient":"user@mail","subject":"...","body":"..."} |
| 8 | run_trigger | запускает триггер | phrases | {"trigger_id":"7222","run_at":"2025-06-11","run_after_sec":300} |
| 9 | set_lead_status | меняет статус лида | phrases | 7783 |
| 10 | add_lead_tags | добавляет теги | phrases | "tag" |
| 11 | remove_lead_tags | удаляет теги | phrases | "tag" |
| 12 | nlp_detect_intent | определяет интент | phrases | {"text_to_detect":"intent"} |
| 13 | run_js_callback | делает JS-callback | phrases | {"prompt":"...","error_message":"...","js_code":"..."} |
| 14 | async_api_call | async вызов API (например, обращение к LLM) | phrases | {"js_code":"...","is_immediate_call":1} |
| 15 | add_lead_contexts | добавляет контекст | phrases | "context" |
| 16 | remove_lead_contexts | удаляет контекст | phrases | "context" |
| 17 | forward_to_operator | переводит на оператора | phrases | 7784 |
| 18 | return_to_bot | возвращает в бота | phrases | 7783 |
| 19 | repeat_sentence | повторяет текущий скрипт | phrases | {} |
| 20 | stop | полностью останавливает выполнение | phrases | {} |
| 21 | menu (reference) | кнопки меню, переходы, inline actions | references | caption="Start", jump_sentence_id=85680 |
Палитра команд платформы:
Короткое объяснение логики исполнения команд
-
Команды внутри одного скрипта (
sentence) выполняются сверху вниз поsort_order. -
Любая команда типа
run_sentenceилиrun_javascriptможет мгновенно передать управление в другой скрипт. -
Кнопки (
references) — это отдельная сущность: они не лежат вphrases, но участвуют в сценарии. В интерфейсе меню расположено внизу скрипта, после списка команд.
«Зачем нам свой конструктор и свои команды»
После того как мы разобрались, какие команды реально существуют внутри Metabot и посмотрели, как они физически хранятся в базе (sentences, phrases, references), мы можем перейти к ключевой идее нашего урока:
Мы не будем создавать интерфейс, который один-в-один повторяет Metabot.
Мы создадим свой собственный визуальный конструктор, и в нём у нас будут свои команды — более удобные, более компактные и намного более выразительные.
Почему так?
Почему в Metabot всё разбито на 21 команду
(и почему это отлично для движка, но неудобно для дизайнера чат-ботов)
Metabot — это low-code ядро. Каждая команда — это минимальная атомарная операция:
-
отправить текст,
-
отправить картинку,
-
записать тег,
-
выполнить JS,
-
перейти в скрипт,
- дождаться ввода
и так далее.
Это правильно для движка:
движок должен быть предсказуемым и детерминированным.
Но для человека, который рисует сценарий, это слишком низкий уровень.
Чтобы собрать один «узел» диалога, дизайнеру нужно вручную собрать:
-
1–3
send_text-
условия и JS
-
настройки под разные каналы
-
кнопки меню (в
references)
-
обработчики кликов (ещё несколько
run_sentence+ JS)
-
JS для логирования для аналитики
-
триггеры для отслеживания ссылок
-
переходы к следующему шагу
-
В итоге один визуальный узел превращается в:
→ 6–12 низкоуровневых фраз в базе
→ 2–4 скрипта (sentences)
→ и пачку записей в references
Именно поэтому мы не хотим заставлять пользователя руками разбираться в 21 команде ядра. Мы хотим сократить этот слой.
Теперь проектируем наш собственный редактор
Мы создаём свою систему узлов — такую, в которой:
-
один узел = одна логическая операция сценария,
-
пользователь видит высокоуровневую настройку,
-
а всё сложное — «распаковывается» автоматически внутрь Metabot при импорте.
Это и есть смысл маппера и билдера.
Какие наши команды мы сделаем первыми
1. Start / Entry Node
Стартовый узел воронки.
Даёт визуальную точку входа + будущую связь с диплинками.
Вход в воронку возможен как из других воронок / сценариев, так и снаружи при переходе в мессенджер из приложений или из контента в Интернет.
Пример шага в новом no code редакторе:
Пример настроек в новом no code редакторе:
Пример JSON-кода шага:
{
"code": "entry_point_main",
"type": "entry_point",
"name": "Старт",
"next_step": "log_action_2ec8cbbe",
"deep_links": [
{
"code": "waylogger",
"title": "New Deep Link",
"active": true,
"parameters": [
{
"key": "ref_source",
"value": "vk_ads"
},
{
"key": "source",
"value": "cpc"
},
{
"key": "campaign",
"value": "waylogger"
},
{
"key": "content",
"value": "1"
}
]
}
],
"coordinates": {
"x": 650,
"y": 728
}
}
2. Send Text (расширенная)
Это не просто send_text из Metabot.
Наша версия включает:
-
💬 универсальное сообщение
-
💬 кастомизация сообщения под каналы (Telegram, WhatsApp, VK, WebChat и так далее)
- 🔘 поддержка кнопок
- ⚙️ обработчики событий нажатия на кнопку
-
📌 запись в аналитику прямо внутри узла
- 🔗 поддержка ссылок (с автогенерацией триггеров)
В Metabot для этого потребовалось бы от 6 до 12 низкоуровневых команд.
У нас — один визуальный блок.
Пример шага в новом no code редакторе:
Форма редактирования стандартной команды "Отправить текст" в low-code редакторе:
Форма редактирования новой команды "Отправить текст" в новом no-code редакторе, сравните возможности:
JSON:
{
"code": "send_text_27607f82",
"type": "send_text",
"content": "3. Кто вы?",
"next_step": "log_action_a47418f3",
"buttons": [
{
"title": "Владелец проекта",
"next_step": null,
"input_code": "1",
"js_condition": "",
"value": "Владелец проекта",
"add_tags": [],
"remove_tags": []
},
{
"title": "Маркетолог",
"next_step": null,
"input_code": "2",
"js_condition": "",
"value": "Маркетолог",
"add_tags": [],
"remove_tags": []
},
{
"title": "Разработчик",
"next_step": null,
"input_code": "3",
"js_condition": "",
"value": "Разработчик",
"add_tags": [],
"remove_tags": []
},
{
"title": "Другое",
"next_step": null,
"input_code": "4",
"js_condition": "",
"value": "Другое",
"add_tags": [],
"remove_tags": []
}
],
"buttons_value_target": {
"scope": "lead",
"key": "role"
},
"log_way_steps": [
{
"type": "step",
"way": "waylogger",
"step": "role_selected",
"event": "",
"tag": "",
"tag_action": "add",
"utter": ""
}
],
"coordinates": {
"x": 197,
"y": 1044
}
}
3. Записать в аналитику
Под "капотом" это будет метаботовская команда run_javascript в которой будет вызов плагина аналитики WayLogger.
Мы прячем всё JS-пекло от пользователя.
В нашем интерфейсе есть:
-
выбор типа логирования:
step,event,tag,utter, -
выбор пути (way),
-
параметры.
JSON:
{
"code": "log_action_a47418f3",
"type": "log_action",
"log_type": "step",
"way": "waylogger",
"step": "finished_quiz",
"event": "",
"tag": "",
"tag_action": "add",
"utter": "",
"next_step": "send_pdf",
"coordinates": {
"x": 474,
"y": 1142
}
}
4. Переход к следующему шагу
В Metabot это команда run_sentence.
В нашем редакторе — просто стрелка между узлами.
А в JSON это параметр шага, кнопки и так далее, например, next_step.
5. Обращение к LLM (call_llm)
В Metabot — большой JS код с настройками в команде async_api_call. Например:
const LLMClient = require("Common.MetabotAI.LLMClient")
const llm = new LLMClient("UserDialog")
llm.setProvider("OpenAI")
llm.setModel("gpt-3.5-turbo")
llm.setErrorScript("SupportBot:ErrorFallback")
llm.setPromptTable("gpt_prompts", "SupportBot")
if (isFirstImmediateCall) {
llm.addSystemPrompt(`Ты должен ответить пользователю, используя найденную информацию из базы знаний.`)
llm.addSystemPrompt(
`Если в базе знаний будут найдены ссылки на медиа контент, `+
`определи релевантность контента, проанализировав их описание, и включи релевантные ссылки в формате markdown. ` +
`Сам ссылки не придумывай!`)
llm.addSystemPrompt(`Информация из базы: {{$kb_chunks}}`)
llm.addSystemPrompt(`Распознаннный интент: {{$user_intent}}`)
llm.addHistoryToPrompts() // Добавляем историю
llm.addUserPrompt(`{{$user_input}}`)
llm.addSystemPrompt(`$UserDialog:final`) // Промпт агента из таблицы
llm.addSystemPrompt(`@общий`) // Общий промпт из таблицы
llm.prepareRequest()
return llm.sendRequest()
}
llm.handleResponse()
llm.sendFormattedResponse()
return true
Это не самый свежий пример кода, но рабочий. Здесь мы делаем:
-
выбор провайдера и модели
- error / fallback узел
- устанавливаем агента и подключаем таблицу промптов
- system / user prompts
-
способ отображения ответа
-
параметры истории
- отправляем запрос, принимаем, выводим
Всё это — внутри одного команды и требует базового владения JS кодом.
В новом редакторе упакуем все это в один no code узел:
И одну форму, чтобы пользователю не пришлось писать JS код — мы его будем генерить за него при импорте в low-code:
JSON:
{
"code": "call_llm_ccbfc9c0",
"type": "call_llm",
"title": "Формируем ответ LLM",
"agent_name": "SupportBot",
"provider": "OpenAI",
"model": "gpt-3.5-turbo",
"prompt_table": "gpt_prompts",
"system_prompts": {
"start": [
"Ты должен ответить пользователю, используя найденную информацию из базы знаний.",
"Если в базе знаний будут найдены ссылки на медиа контент, \nопредели релевантность контента, проанализировав их описание, и включи релевантные ссылки в формате markdown. \nСам ссылки не придумывай!",
"Информация из базы: {{$kb_chunks}}",
"Распознаннный интент: {{$user_intent}}"
],
"final": [
"$UserDialog:final",
"@общий"
]
},
"history": {
"enabled": true,
"save_to_attr": "chat_history_str",
"max_length": 4,
"add_to_prompts": true
},
"user_query": {
"enabled": true,
"attr": "user_input",
"add_to_prompts": true
},
"response": {
"enabled": true,
"save_to_attr": "user_intent",
"display_to_user": true,
"format": "markdown"
},
"trace_enabled": false,
"next_step": "search_kb_394b4ae2",
"error_step": "send_text_47451106",
"coordinates": {
"x": -336.29289321881345,
"y": 2063.166664123535
}
}
6. Поиск по базе знаний (search_knowledgebase)
Аналогично — в Metabot при импорте создаем JS команду и необходимые связки.
let userIntent = lead.getAttr('user_intent')
// Если интент есть — ищем по базе знаний
if (userIntent) {
const KnowbaseSearch = require('Common.MetabotAI.KnowbaseSearch')
const kbSearch = new KnowbaseSearch()
const chunks = kbSearch.findBestChunks("DefaultKB", userIntent, "default")
// Сохраняем все, что нашли в kb_chunks
lead.setJsonAttr("kb_chunks", chunks)
bot.sendMessage(`chunks: ${JSON.stringify(chunks)}`)
} else {
lead.setJsonAttr("kb_chunks", [])
}
А в no code — один узел, один JSON.
Обратите внимание у узла мы добавили три выхода: успешный, ничего не найдено и ошибка.
JSON:
{
"code": "search_kb_394b4ae2",
"type": "search_knowledgebase",
"knowbase_name": "DefaultKB",
"query_attr": "user_intent",
"domain": "default",
"save_results_to_attr": "kb_chunks",
"trace_enabled": false,
"next_step": "call_llm_417fc51a",
"not_found_step": "send_text_455c4afe",
"error_step": "send_text_47451106",
"coordinates": {
"x": -337,
"y": 2417
}
}
Почему наши команды будут «упакованными»
(концепция compact high-level commands)
Каждая наша команда — это высокоуровневый «комбинированный» блок.
Когда пользователь его настраивает, мы:
-
собираем данные в простой JSON (наш формат),
-
передаём этот JSON мапперу,
-
маппер превращает один наш узел в:
-
1 или несколько
sentences -
десятки
phrases -
references -
JS-код
-
триггеры
- диплинки
-
переходы
-
аналитику
- и так далее
-
То есть пользователь видит простую, компактную визуальную воронку, а под капотом строится настоящая сложная система Metabot с триггерами, скриптами, условиями, логированием и разветвлёнными сценариями.
Экономический эффект / ROI
Чтобы собрать один логический узел вручную, в Metabot приходится склеивать десятки микродействий: несколько сообщений, кнопки, переходы, теги, аналитику, JS-обработчики, триггеры, условия. Каждое из этих действий занимает секунды или минуты. А в реальном проекте — это часы.
Чем сложнее сценарий, чем массивнее продукт — тем больше времени уходит не на смысловую работу, а на рутину: перенести текст, добавить теги, прописать переходы, подправить сортировку, создать скрипт, связать кнопки. Всё это множится в геометрической прогрессии.
И вот здесь становится очевиден смысл no-code-редактора.
Один визуальный узел в нашем редакторе может вместить всю эту низкоуровневую сложность: аналитику, кнопки, ссылки, условия, LLM-вызовы, JS-код, обработку ввода, переходы — всё внутри одного компактного блока с настройками.
Пользователь просто выбирает тип узла, заполняет пару полей — и весь сложный набор действий раскладывается движком автоматически.
Вместо десятка операций — одна.
Вместо нескольких минут — несколько секунд.
Вместо часов — десятки минут.
На дистанции это превращается в огромную экономию ресурсов.
За неделю — это часы.
За месяц — дни.
За год — целые недели чистого рабочего времени, которые раньше уходили на рутину.
И это уже прямой экономический эффект:
команды создают продукты быстрее, дешевле, рентабельнее;
клиенты получают большую ценность за те же деньги;
а вы можете делать более сложные решения теми же силами.
При этом мы сохраняем лучшее из двух миров:
-
Скорость и простоту no-code для типовых сценариев
-
Гибкость low-code/full-code ядра Metabot для любых нестандартных кейсов
Нужна свобода — подключили разработчика и дописали кастомный JS или сложную логику.
Нужна скорость — делаем всё в визуальном редакторе.
И это не все: наш JSON-формат позволяет генерировать воронки автоматически.
Вы можете написать в ChatGPT: «Сгенерируй мне воронку для такого-то кейса» — и импортировать её одним кликом.
Та самая воронка на скриншоте в начале этого урока, состоящая из восьми шагов, создаётся в 5–10 раз быстрее, чем через классический low-code.
Это и есть настоящая эффективность:
быстрее создавать — быстрее тестировать — быстрее зарабатывать.
JSON-формат нашего визуального конструктора
После того, как мы разобрались с тем, как устроены низкоуровневые команды Metabot и почему нам нужен собственный слой абстракции, самое время поговорить о JSON-формате, который станет языком нашего визуального редактора.
По сути, мы создаём свой диалоговый DSL/язык доменной области — простой, человекочитаемый, легко генерируемый (вплоть до того, что его можно создавать через ChatGPT). Этот формат затем преобразуется в структуры Metabot: скрипты, команды, переходы, кнопки, аналитику, LLM-вызовы.
Чтобы понимать, как его проектировать, нужно осознать два ключевых отличия от классического low-code формата Metabot.
1. В чём принципиальное отличие от формата Metabot
✔ В Metabot каждая логика — это скрипт, который содержит много команд
Скрипты связаны переходами (run_sentence), а внутри одного скрипта команды выполняются последовательно — как патроны в обойме.
Это отличный формат для движка, но он низкоуровневый и слишком детализированный для проектирования диалогов.
✔ Наш новый формат — это машина состояний
Мы мыслим не скриптами, а шага́ми.
Каждый шаг — это одно состояние, из которого есть переходы в другие состояния.
Похожая логика используется в BPMN, state-machine диаграммах, автоматах Мили/Мура.
Внутри шага могут происходить десятки внутренних действий, но в JSON это будет один объект.
Таким образом:
-
Metabot = «список команд, выполняющихся последовательно»
-
Новый формат = «автомат состояний»:
step → step → step
Этот подход радикально упрощает визуальное проектирование и делает структуру понятной.
2. Общая структура JSON-формата
Мы храним всю воронку в одном JSON-документе, который содержит:
1) Параметры проекта (bot id, версия, формат и т.д.)
2) Массив steps — список шагов машины состояний
Вот упрощённая структура:
{
"script_request_params": {
"format": "cjm",
"version": "1.0",
"bot_id": 2370,
"code": "quiz_lead_magnet_tags",
"title": "Воронка WayLogger",
"channels": {
"telegram_bot_name": "metabot",
"whatsapp_phone_number": "79150465850",
"vk_group_name": "metabot_platform",
"use_chat_widget": true
},
"steps": [ ... ]
}
}
Обращаем внимание на обёртку script_request_params без которой платформа Metabot "не услышит" передаваемые данные.
3. Зачем нужен «format» и «version»
Мы сознательно включаем поля:
-
format: имя формата (например,cjm) -
version: версия схемы
Это позволяет:
✔ поддерживать совместимость старых воронок
Если формат JSON изменится (а он будет меняться), старые импорты не сломаются.
✔ нескольким продуктам использовать разные форматы
Один редактор → один формат.
Другой редактор → другой формат.
Все они могут жить в одной экосистеме Metabot.
✔ при импорте определять, как парсить данные
Mapper видит format + version и понимает, какой алгоритм применять.
4. Название и код воронки
Поля:
-
code— машинное имя воронки -
title— человекочитаемое название
В метаботе мы формируем имя секции:
cjm:1.0:quiz_lead_magnet_tags:Воронка WayLogger
Секция (папка) создаётся заново при каждом импорте, что даёт чистый, предсказуемый результат.
5. Массив шагов steps — сердце машины состояний
Каждый шаг описывается JSON-объектом:
-
уникальный
code(имя узла) -
тип шага (
send_text,analytics,llm_call, …) -
переходы в другие шаги
-
кнопки
-
аналитика
-
делегируемые действия (напр. отправить файл, записать теги)
-
координаты (для визуального редактора)
-
любые дополнительные поля
Идея проста:
Один наш шаг = целый набор низкоуровневых команд Metabot.
Визуально шаг выглядит как блок на диаграмме (см. скриншот), а в импорте он превращается в цепочку скриптов и команд.
6. Как мы создаём скрипты и команды при импорте
Во время импорта Metabot получает:
-
format -
version -
code(воронки) -
steps(шаги)
Mapper делает следующее:
✔ 1. Находит и удаляет старую cекцию/папку со всеми скриптами
(имя: format:version:code:title)
✔ 2. Создаёт новую секцию
Внутрь неё будут помещаться все скрипты.
✔ 3. Для каждого шага создаёт один или несколько скриптов
Количество зависит от сложности шага:
-
send_text→ 1 скрипт -
send_text + buttons + analytics→ 1–3 скрипта -
и так далее.
✔ 4. Формирует уникальные коды скриптов
Это ключевой момент.
Мы склеиваем:
<format>:<version>:<сjm_funnel_code>:<step_code>
Например:
cjm:1.0:quiz_lead_magnet_tags:entry_point_main
cjm:1.0:quiz_lead_magnet_tags:hello_intro
cjm:1.0:quiz_lead_magnet_tags:send_pdf
cjm:1.0:quiz_lead_magnet_tags:log_action_2ec8cbbe
Так обеспечивается:
-
уникальность внутри всего бота
-
читаемость
-
возможность переимпорта
-
возможность отладки
✔ 5. Внутрь каждого скрипта складывается набор команд (phrases)
Например, шаг «Send Text» превратится в:
-
send_text run_sentence(переход)-
кнопки в
references -
еще один скрипт с
run_javascript(аналитика)
7. Почему шагам обязательно нужен уникальный code
Каждый узел должен иметь уникальный идентификатор.
Без него невозможно:
-
связать узлы между собой
-
построить диаграмму
-
обновлять узлы
-
делать автогенерацию
-
однозначно создать скрипты в Metabot
Пользователь может менять коды шагов — но внутри маппера этот код всегда станет частью полного имени скрипта.
8. Что происходит со старой версией воронки
В текущей версии урока мы применяем упрощённый алгоритм:
✔ удалить старую секцию полностью
✔ создать новую с тем же именем
✔ импортировать шаги заново
Это быстрый, предсказуемый, безопасный метод.
Если нужно:
-
версионность → меняем
version -
несколько версий воронки → клонируем формат
-
продвинутый sync → пишем собственный маппер (за пределами этого урока)
9. Что нужно, чтобы импортировать воронку
Чтобы импортировать JSON:
1. Создаём пользователя с API-доступом
TODO: как это сделать — в отдельном мануале)
2. Создаём endpoint в боте
В разделе Internal API создаём endpoint, например, с таким алиасом:
cjm/import
3. Кладём внутрь endpoint JS-код:
const { getSuccessResponse, getErrorResponse } = require('Common.Utils.Response')
const { Mapper } = require('Common.CJM.Mapper')
const {
id = null,
format,
version,
bot_id,
code,
title,
steps = []
} = request.json || {}
if (!format || !version || !bot_id || !code || !title) {
return getErrorResponse("Missing required fields: format, version, bot_id, code, title")
}
try {
const mapper = new Mapper()
result = mapper.runImport(bot_id, format, version, code, title, steps)
return getSuccessResponse({result})
} catch (error) {
return getErrorResponse(`Error saving CJM: ${error.message}`)
}
Всё. Теперь можно импортировать воронки из:
-
Postman
-
вашего React/Vue редактора
-
ChatGPT (сгенерировали JSON → вставили → импортировали)
🔷 Глава 2 завершена.
Следующая — Глава 3. Import: Mapper и Builder
В ней мы разберём:
-
как работает маппер
-
как он создаёт скрипты
-
как билдер создаёт фразы
-
как связываются узлы
-
как формируется final graph в Metabot
Готов продолжать?














