Как построить визуальный 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 → вставили → импортировали)
Глава 3. Import: Mapper и Builder — ядро нашего no-code импорта
Теперь, когда мы разобрали сам формат JSON-схемы, можно перейти к механике её импорта в Metabot. На платформе в Common.CJM доступны два ключевых плагина, которые вместе образуют мост между вашим JSON и реальными скриптами в базе данных:
-
Mapper (JavaScript) — читает JSON, создаёт структуру карты, генерирует команды и меню.
-
Builder (PHP) — низкоуровневый слой, который напрямую создаёт секции, скрипты, команды и переходы в базе.
Эти плагины доступны всем пользователям платформы, но важно понимать их статус:
-
Общий Mapper и Builder — это обучающие версии, не полноценный коммерческий конструктор.
Они упрощены, без множества проверок и UI-логики. Их задача — показать принципы, на которых строится no-code. -
Исходники мы даём прямо в статье — вы можете взять их за основу и создать свой собственный конструктор.
-
На общем сервере Metabot нельзя ставить новые PHP-плагины (по соображениям безопасности), но на выделенном сервере или коробочной установке — можно.
Как работает связка Mapper → Builder
Чтобы показать структуру максимально практично, мы дальше будем двигаться блок за блоком:
показываем фрагмент кода → объясняем, что он делает → даём рекомендации, где можно улучшить.
Но вначале — краткая логика работы всей цепочки.
1. Устанавливаем контекст: выбор бота и проверка доступа
Mapper — это JS-плагин, но он работает через Builder, а Builder — PHP-уровень, который:
-
проверяет принадлежность бота к бизнесу;
-
отслеживает попытки добавления скриптов в чужой бот;
-
обеспечивает гарантии целостности данных.
Поэтому первым шагом Mapper вызывает:
setBotById(botId)
Если бот не относится к текущему бизнесу — импорт прерывается.
Это фундаментальный слой безопасности, и Builder строго следит, чтобы вы не могли повредить данные другого бота.
2. Создаём секцию под карту
При импорте мы:
-
формируем префикс (
format:version:mapCode), -
удаляем предыдущую секцию целиком,
-
создаём свежую секцию с чистой структурой.
Это важно, потому что карта имеет жёсткую целостность:
скрипты, переходы, обработчики кнопок, логирование — всё должно меняться согласованно.
3. Почему импорт идёт в три слоя, а не линейно?
Вот здесь та часть, которую многим разработчикам сложно понять.
Нельзя просто:
-
взять шаг,
-
создать под него скрипт,
-
добавить команды,
-
добавить кнопки,
-
создать переходы.
Почему?
Потому что кнопки и переходы требуют ID-скриптов, а ID знает только Builder, и только после создания всей секции.
Поэтому алгоритм у нас такой:
Шаг А — создаём скрипты.
Это даёт нам ID каждого шага.
Шаг Б — наполняем команды.
Включая аналитику, custom script, call_llm, search_kb.
Шаг В — собираем меню (кнопки).
Каждая кнопка — это либо прямой переход, либо создание отдельного скрипта-обработчика.
Шаг Г — собираем переходы.
Все переходы хранятся временно в массиве transitions,
и только в самом конце добавляются в базу.
Именно это обеспечивает корректную топологию карты.
Этот подход можно усложнить и оптимизировать,
но важно понимать, что «слоистость» — не прихоть, а необходимость, если мы хотим:
-
избежать циклических зависимостей;
-
избежать ситуаций, когда ссылка создаётся раньше объекта;
-
сохранять атомарность структуры карты.
Исходный код Маппера и Билдера
Полный код Mapper и Builder из этого урока:
- Common.CJM.Builder (PHP)
- Common.CJM.Mapper (JS)
ЧАСТЬ 3.1 — Builder (PHP): низкоуровневое ядро конструктора
Builder — это низкоуровневый плагин, который напрямую работает с таблицами Metabot:
-
sentences— скрипты; -
phrases— команды внутри скриптов; -
script_sections— секции (папки); -
references— меню (кнопки переходов); -
botlog— логи.
Mapper на JavaScript работает «поверх» Builder’а, но именно Builder делает грязную работу:
✔ создаёт реальные записи в БД
✔ удаляет секции и весь вложенный контент
✔ проверяет политику доступа
✔ обеспечивает целостность ссылок
✔ создаёт команды и меню
✔ соединяет скрипты между собой
Builder — это ваш «микро ORM + сервис создания бота».
Mapper — лишь удобная оболочка.
Зачем Builder существует вообще?
Потому что:
-
V8-движок Metabot может выполнять JavaScript, но не имеет прямого доступа к БД;
-
PHP — серверное ядро платформы, которое защищено, логирует действия и выполняет транзакции;
-
Builder — это bridge между JS и PHP, через V8 Wrapper.
Mapper вызывает Builder так:
const Builder = require('Common.CJM.Builder')
Методы Builder становятся доступными JS-коду.
Архитектура Builder
Builder хранит три ключевых контекста:
protected ?Business $_business = null;
protected ?Bot $_runFromBot = null;
protected ?Bot $_bot = null;
protected int $_botId;
protected ?Lead $_lead = null;
Зачем?
Потому что Builder не должен позволять JS-коду выкрутить руки бизнес-логике:
-
вы не можете создавать скрипты в чужом боте;
-
вы не можете добавлять команды в скрипт другого бизнеса;
-
вы не можете обращаться к несуществующему боту;
-
удаление секции может произойти только в рамках бизнеса, которому она принадлежит.
Это критично.
🔐 CheckPolicy(): сердце безопасности
Каждый метод Builder начинает с этого:
$this->checkPolicy();
Он проверяет:
-
передан ли текущий бизнес
-
передан ли текущий бот
-
принадлежит ли бот действующему бизнесу
-
корректен ли lead
-
корректна ли топология
-
установлен ли botId
Без этого Builder вообще не работает.
Методы Builder: полный разбор
Теперь — весь API Builder, который нам нужен для маппера и для создания собственного конструктора.
1) setBotById(botId)
Устанавливает текущий бот для всех дальнейших операций.
Почему нельзя делать это в JS напрямую?
Потому что выбор бота — потенциальная точка атаки.
Метод делает:
-
ищет Bot
-
проверяет, что bot.business_id принадлежит текущему бизнесу
-
сохраняет
_botи_botId
Возвращает:
['success' => true, 'bot_id' => X]
2) createSection(sectionTitle)
Создаёт папку (ScriptSection) для новой карты:
-
bot_id -
name(полное имя"format:version:mapCode:mapTitle") -
сортировка = 0
Builder не создаёт вложенность, всё плоское.
Вложенность симулируется именованием.
3) deleteSectionByCodeDeep(sectionCode)
Самый опасный и самый важный метод.
Удаляет всю секцию по коду и все её вложенные элементы.
Пример: для карты с кодом testmap удалит всё, что содержит "testmap".
Делает:
-
ищет section
-
удаляет все скрипты в этой секции
-
удаляет все фразы этих скриптов
-
удаляет все меню (reference)
-
удаляет все переходы в run_sentence, которые ссылались на эти скрипты
-
удаляет логи
-
удаляет саму секцию
Это глубокая очистка, без которой импорт JSON невозможен.
Логи приходится удалять, потому что иначе Metabot не даст удалить скрипты из-за целостности БД. Это не очень хорошая практика т.к. по воронке могли пройти лиды и логи нужны. Это к вопросу, который мы поднимали выше, что простого одностороннего импорта, удаляющего старую версию, в полноценном продукте не достаточно. Для урока мы сознательно упрощаем.
4) createScript(sectionId, code, name)
Создаёт пустой скрипт в конкретной секции:
-
sentence.bot_id = this->_botId -
sentence.section_id = sectionId -
sentence.code = code -
sentence.name = name
Возвращает:
['id' => sentence.id, 'code' => sentence.code]
Это строительный блок №1 для Mapper.
5) findScript(code)
Возвращает скрипт или null.
Mapper использует это для проверок.
6) deleteScript(code)
Удаляет скрипт (предложение) + все фразы.
7) createCommand(scriptCode, type, content, commandCode, sort_order)
Создаёт команду (фразу) внутри скрипта.
Поля:
-
sentence_id— скрипт -
type— тип команды (send_text, run_javascript, run_sentence…) -
content— JSON или строка -
alias— идентификатор команды (UUID либо переданный) -
sort_order— порядок
Это фундаментальная часть, потому что Mapper весь low-code строит только на этих операциях.
8) existsScript / existsCommand
Проверяют существование.
Mapper использует для валидаций.
9) deleteCommand(scriptCode, commandCode)
Удаляет фразу с нужным alias.
Обратите внимание, что в low-code интерфейсе конструктора у команд нет алиасов, а в БД есть.
10) createMenuItem()
Создаёт кнопку в меню:
Поля:
-
sentence_id— скрипт, где кнопка находится -
jump_sentence_id— скрипт, куда ведёт -
caption— текст кнопки -
code— иногда это input_code -
sort_order— порядок кнопки -
line_num— расположение ряда (для Telegram клавиатур) -
js_condition— условие показа
Mapper формирует их в конце, когда все ID известны.
11) findSectionByCode / findSectionByTitle
Используются для поиска секций перед удалением.
12) deleteSectionByIdDeep
Глубокое удаление секции (аналогично deleteSectionByCodeDeep).
Именно этот метод делает большую часть работы:
-
удаляет скрипты, фразы, меню, логи
-
удаляет переходы между скриптами
-
очищает всё до пустого состояния
В сумме Builder умеет:
1) Управлять секциями
создавать, искать, удалять с зависимостями
2) Управлять скриптами
создавать, искать, удалять, проверять существование
3) Управлять командами
создавать, удалять, находить
4) Управлять меню
создавать кнопки и переходы
5) Очищать полностью старую карту
deep delete секции
6) Гарантировать безопасность через CheckPolicy
никаких операций вне текущего бизнеса
Итого о Билдере
Builder — это низкоуровневый API конструктора Metabot, на котором можно собрать:
-
no-code редактор
-
low-code редактор
-
визуальный CJM editor
-
auto-generator воронок из JSON, GPT, CRM
-
свой собственный mini-n8n или BotHelp-лайк движок
Mapper лишь использует Builder, но Builder — это фундамент.
🔎 Примечание о расширении Builder
Если вам понадобится добавить в Builder новый функционал — вы можете это сделать, но важно учесть следующее:
-
PHP-плагины запрещены на общем облачном сервере Metabot в целях безопасности.
Поэтому собственные версии Builder можно устанавливать только:-
на выделенном сервере, или
-
в коробочной (on-premise) версии платформы.
-
-
Если вам требуется расширить Builder именно под свой продукт, вы можете создать собственный плагин Builder и развивать его так, как вам нужно. Это полностью поддерживается на выделенных/коробочных установках.
-
Если же вы хотите предложить улучшение или новый метод в общий Builder, вы можете прислать ваш вариант реализации или концепцию в поддержку Metabot.
Мы рассмотрим предложение и, если оно подходит под архитектуру платформы, подумаем о включении в будущие версии общего плагина.
Подробнее о возможностях выделенных серверов и коробочных лицензий смотрите здесь:
https://metabot24.ru/price/tarify-na-platformu/
3.2 — Маппер: подробный разбор архитектуры и алгоритма импорта
Маппер — это верхнеуровневый JS-плагин, который принимает JSON-описание вашей схемы (воронки) и преобразует её в реальную структуру скриптов Metabot через Builder. Mapper не работает с базой напрямую — он опирается на PHP-плагин Builder, а сам выполняет только логику трансформации структуры шагов, генерацию JavaScript для команд и построение связей.
Маппер вы можете разместить в плагины вашего бизнеса и использовать на любом сервере, включая общий облачный. Либо можете использовать нашу последнюю версию из Common.CJM.Mapper.
Основная ответственность Mapper:
-
Проверить формат и подключить нужного бота
-
Создать секцию (папку) под воронку
-
Создать скрипты под каждый шаг
-
Наполнить эти скрипты командами
-
Сгенерировать переходы (default/аналитика/кнопки)
-
Сгенерировать и подключить JS-команды: LLM, KB-поиск, кастомный скрипт, логгер
-
Построить меню-кнопки и обработчики
-
Добавить все переходы между скриптами
Далее — полный разбор устройства Mapper с примерами кода и объяснением, зачем всё так устроено.
3.2.1. Подключение Builder и базовая инициализация
Mapper работает в JS, но Builder — это PHP-плагин. Их соединяет V8-wrapper:
const Builder = require('Common.CJM.Builder')
class Mapper {
constructor() {
this.builder = Builder
}
Builder подгружается как JS-объект, но внутри вызывает PHP-код.
Перед началом импорта Mapper обязан:
-
Установить бота
-
Пройти CheckPolicy внутри Builder (безопасность)
setBotById(botId) {
const result = this.builder.setBotById(botId)
if (result.success) {
this.botId = botId
}
return result
}
Если бот не принадлежит бизнесу, Builder возвращает ошибку. Это важно: так мы не позволяем создать скрипт в чужом боте.
3.2.2. Главный метод: runImport()
Это сердце Mapper. Он получает JSON из внешнего API:
runImport(botId, importFormat, importVersion, mapCode, mapTitle, steps = [])
Шаг 1. Установка бота
Если бот не найден или доступ запрещён — бросаем ошибку:
const { success, message } = this.setBotById(botId)
if (!success) throw new Error(message)
Шаг 2. Генерация префикса и названия папки
Маппер работает по строгой системе кодирования:
{format}:{version}:{mapCode}:{stepCode}
Папка:
cjm:1.0:quiz_lead_magnet_tags:Воронка WayLogger
Это ключевой принцип: каждый объект (скрипт, команда и др.) в Metabot получает уникальный код, который невозможно перепутать между воронками.
const mapPrefix = `${importFormat}:${importVersion}:${mapCode}`;
const sectionTitle = `${mapPrefix}:${mapTitle}`
Шаг 3. Удаление старой секции
Mapper не обновляет кусочно — он делает полную пересборку:
this.builder.deleteSectionByCodeDeep(mapCode)
Если вам нужна версия 1.0 и 1.1 одновременно — просто укажите другую version перед импортом.
Шаг 4. Создание новой секции
const section = this.builder.createSection(sectionTitle)
const sectionId = section.id
3.2.3. Фаза 1 — Создание скриптов (без наполнения)
Это важнейший архитектурный момент:
Мы не можем создавать команды или кнопки, пока не созданы ВСЕ скрипты.
Почему?
Потому что кнопка указывает на next_step, а Builder требует ИД скрипта — а его нет, если скрипт ещё не создан.
Поэтому первая фаза:
for (const step of steps) {
if (!supportedStepTypes.includes(step.type)) continue
const scriptCode = `${mapPrefix}:${step.code}`
const script = this.builder.createScript(sectionId, scriptCode, step.name || step.code)
scriptIds[scriptCode] = script.id
validSteps.push(step)
}
В результате у нас есть:
-
полный список скриптов
-
их ID
-
steps, которые мы точно умеем обработать
3.2.4. Фаза 2 — Наполнение скриптов командами
Теперь, когда все скрипты созданы — начинаем обрабатывать каждый шаг.
Главные переменные:
const nextScriptCode = step.next_step ? `${mapPrefix}:${step.next_step}` : false
let addDefaultExit = true
let loggerScriptCode = false
1. Работа с аналитикой (log_way_steps)
Если у шага есть log_way_steps, мы создаём отдельный логгер-скрипт:
if (Array.isArray(step.log_way_steps)) {
loggerScriptCode = `${scriptCode}_logger`
const loggerScript = this.builder.createScript(sectionId, loggerScriptCode, `${step.code}_logger`)
Затем в него добавляется JS-команда:
const js = this._buildLoggerContent(logStep)
this.builder.createCommand(loggerScriptCode, 'run_javascript', js)
Почему логгер вынесен в отдельный скрипт?
-
чтобы шаг не раздувать
-
чтобы возможные ошибки в основной команде не мешали зафиксировать факт достижения шага
-
чтобы легко реиспользовать логику логирования (у нас может быть несколько переходов из узла, например, кнопки)
2. switch-case по типам команд
Ключевой блок:
switch(step.type) {
case 'send_text':
case 'entry_point':
case 'log_action':
case 'run_custom_script':
case 'call_llm':
case 'search_knowledgebase':
...
}
Разберём каждый.
send_text
this.builder.createCommand(
scriptCode,
'send_text',
step.content,
step.code,
0
)
Если есть кнопки — мы НЕ создаём дефолтный выход:
if (step.buttons.length > 0) addDefaultExit = false
Кнопки в этот момент НЕ создаются. Они добавляются позже (см. Phase 3).
entry_point
Пока заглушка, но сюда можно добавить:
-
генерацию deep links
-
автогенерацию триггеров
-
multi-entry routing
log_action
Это упрощённая команда аналитики:
this.builder.createCommand(scriptCode, 'run_javascript', this._buildLoggerContent(step))
run_custom_script
Здесь мы вручную генерим JS, который переходит в другой скрипт:
const js = this._buildCustomScriptJs(step)
this.builder.createCommand(scriptCode, 'run_javascript', js)
Почему не используем нативную команду run_sentence?
Потому что она привязана к зависимостям БД, и при удалении секций может нарушать целостность. JavaScript не проверяет foreign keys — это гибче.
call_llm
Здесь начинается генерация JS-кода для LLM-вызова:
this.builder.createCommand(
scriptCode,
'run_js_callback',
this._buildCallLLMJs(step, mapPrefix)
)
Mapper анализирует:
-
system_prompts (start/final)
-
provider/model
-
prompt_table
-
error_step / next_step
-
history
-
response formatting
И превращает всё это в JS-«рецепт», который выполняется в Metabot.
search_knowledgebase
Похожий алгоритм:
this.builder.createCommand(
scriptCode,
'run_js_callback',
this._buildSearchKbJs(step, mapPrefix)
)
Здесь генерится код:
-
вызов поиска через KnowbaseSearch
-
сохранение результата в lead.attr
-
опциональный Telegram-debug
-
переход в success/not_found/error шаги
3.2.5. Фаза 3 — Default Exit и Transition Collection
После switch-блока мы добавляем переходы:
Логика:
-
Если есть логгер — первым переходом идём в логгер.
-
Потом логгер → next_step
-
Если логгера нет — просто script → next_step
-
Но если есть кнопки — дефолтный выход НЕ создаётся.
Mapper пока не создаёт команду перехода — только сохраняет их:
transitions.push({ from: scriptCode, to: nextScriptCode })
Это тоже важно: мы не можем создавать переходы, пока не созданы ВСЕ скрипты.
3.2.6. Фаза 4 — Обработка кнопок и создание handler-скриптов
Блок обработки кнопок — наиболее сложный и многослойный участок маппера.
Причина простая: каждая кнопка — это мини-сценарий внутри сценария, и внутренняя логика кнопки может требовать построения отдельных скриптов:
1) Скрипт-обработчик кнопки (handler script)
Создаётся, если:
-
нужно выставить значение в lead/bot
-
нужно добавить теги
-
нужно удалить теги
-
есть кастомная логика кнопки
2) Скрипт логирования (logger script)
Подключается только если в шаге, к которому относится кнопка, включена аналитика.
3) Целевой скрипт (next_step)
Это обычный переход — либо от самой кнопки (button.next_step), либо от шага (step.next_step).
Таким образом, нажатие кнопки может порождать цепочку:
Handler Script → Logger Script → Next Step
Или:
Logger Script → Next Step
Или:
Handler Script → Next Step
Или просто:
Next Step
Все четыре варианта должны корректно работать. Отсюда и вся сложность логики.
3.2.7. Фаза 5 — Финальное объединение скриптов (TransitionMap)
Теперь, когда:
-
все скрипты созданы
-
команды добавлены
-
кнопки созданы
-
handler-скрипты созданы
— наконец можно создавать команды-переходы run_sentence — рёбра между объектами.
Mapper строит карту:
for (const { from, to } of transitionMap.values()) {
const toScriptId = scriptIds[to]
this.builder.createCommand(from, 'run_sentence', String(toScriptId), null, 777)
}
Почему sort_order = 777?
Чтобы команда перехода гарантированно была последней.
3.2.8. Генераторы JS-кода (LLM, KB, CustomScript, Logger)
Mapper содержит важные приватные функции:
-
_buildLoggerContent(step) -
_buildCustomScriptJs(step) -
_buildCallLLMJs(step) -
_buildSearchKbJs(step)
Они генерируют JavaScript прямо на лету, подставляя параметры шага.
Это архитектурно мощно, потому что:
-
позволяет описывать сложные команды чистым JSON
-
Mapper превращает JSON → JS → low-code конструкцию
-
можно легко добавлять новые типы шагов
Однако, лучше это вынести в отдельный модуль или Snippet, чтобы код маппера был чище.
3.2.9. Итог импорта
Mapper возвращает:
return {
success: true,
section_id: sectionId,
created_scripts: createdScriptsCnt
}
3.2.10. Архитектурные преимущества Mapper
-
Чистая модель автомата состояний
-
Поддержка аналитики на уровне схемы
-
Универсальные handler-скрипты для кнопок
-
Отсутствие foreign-key зависимостей
-
JS-код, генерируемый на лету
-
Изоляция каждой воронки через namespace кодов
-
Предсказуемая однопроходная структура импорта
3.2.11 — Псевдокод полного алгоритма импорта
function runImport(config) {
validate(config)
setBot(config.bot_id)
prefix = `${format}:${version}:${mapCode}`
sectionTitle = `${prefix}:${title}`
deleteOldSection(mapCode)
sectionId = createSection(sectionTitle)
scriptIds = {}
validSteps = []
// ------------------------
// PHASE 1 — Create scripts
// ------------------------
for step in config.steps:
if (!supported(step.type)) continue
scriptCode = `${prefix}:${step.code}`
scriptId = createScript(sectionId, scriptCode)
scriptIds[scriptCode] = scriptId
validSteps.push(step)
transitions = []
menuButtons = []
// ------------------------
// PHASE 2 — Fill scripts
// ------------------------
for step in validSteps:
scriptCode = prefix + ":" + step.code
nextScript = prefix + ":" + step.next_step
if step.log_way_steps:
loggerCode = createLoggerScript()
transitions.push( scriptCode → loggerCode )
switch(step.type):
case send_text:
addSendText(scriptCode)
if (step.buttons) menuButtons.push(...)
else transitions.push(scriptCode → nextScript)
case run_custom_script:
addJS(scriptCode, buildCustomJS(step))
transitions.push(scriptCode → nextScript)
case call_llm:
addJS(scriptCode, buildLLM(step))
transitions.push(scriptCode → nextScript)
case search_knowledgebase:
addJS(scriptCode, buildKB(step))
transitions.push(scriptCode → nextScript)
case log_action:
addJS(scriptCode, buildLogger(step))
transitions.push(scriptCode → nextScript)
// -------------------------
// PHASE 3 — Buttons logic
// -------------------------
for btn in menuButtons:
if btn.hasHandler:
handlerCode = createHandlerScript()
addTagLogic(handlerCode)
addAttrLogic(handlerCode)
addTransition(handlerCode → logger → next)
createMenuItem(scriptId, handlerCode)
else:
jumpScript = resolveJump()
createMenuItem(scriptId, jumpScript)
// -------------------------
// PHASE 4 — Final transitions
// -------------------------
mergeDuplicates(transitions)
for t in transitions:
addCommand(t.from, "run_sentence", scriptIds[t.to])
return success(sectionId)
}
✅ Завершение части про JSON-формат, Mapper и Builder
Мы разобрали JSON-формат, логику импорта, работу Mapper и низкоуровневого Builder.
Теперь у вас есть полный набор кирпичиков, чтобы:
-
проектировать свои форматы воронок,
-
писать собственные команды,
-
упаковывать сложные многошаговые кейсы в компактные no-code узлы,
-
импортировать схемы из внешнего редактора,
-
и автоматически разворачивать их в полнофункциональные low-code скрипты MetaBot.
Важно подчеркнуть:
Mapper — полностью JavaScript-плагин.
Его можно размещать прямо в бизнесе, на любом боте, в том числе на общем облачном сервере.
Вы можете свободно модифицировать его под себя, расширять функционал, менять формат, добавлять новые типы шагов и логику.
Builder — PHP-плагин.
Он даёт прямой доступ к базе Metabot и обеспечивает всю работу с «железом» конструктора (создание скриптов, команд, секций, меню и т.д.).
Он уже содержит достаточный набор функций, чтобы реализовать практически любой no-code → low-code транслятор.
Напомним:
Если вам требуются новые возможности в Builder или вы хотите собрать свой собственный Builder, доступны несколько вариантов:
Развернуть выделенный сервер,
Приобрести коробочную версию MetaBot,
Или предложить свой функционал — пришлите описание в поддержку, и мы рассмотрим возможность включить расширения в официальный билд.
Подробнее о тарифах: https://metabot24.ru/price/tarify-na-platformu/
На этом завершаем блок работы с JSON-схемой, импортом и серверной частью конструктора.
Часть 4. Визуальный редактор воронок на React/Next.js с помощью V0 или Cursor
Теперь, когда мы полностью разобрали JSON-формат, Mapper, Builder и серверную архитектуру импорта, мы можем перейти к самому вдохновляющему этапу — созданию визуального no-code редактора воронок, работающего поверх MetaBot.
Этот редактор позволяет:
-
рисовать схему так же, как в Miro;
-
редактировать каждый узел через popup-панель;
-
добавлять шаги, кнопки и связи;
-
импортировать/экспортировать JSON;
-
отправлять схему в Metabot;
-
подключать промпты и базу знаний КРОМЕ схем (расширение для продвинутых).
4.1. Общая концепция редактора
Наша реализация состоит из:
-
React/Next.js фронтенда
-
визуального канваса на React Flow
-
pop-up редактора узлов
-
панели инструментов
-
API-клиента, отправляющего JSON в Metabot
-
блока для редактирования промптов и базы знаний (не входит в урок, но есть в репозитории)
4.2. Ссылки на спецификации и исходники
Спецификации редактора
Здесь представлены основные промпты, которые удалось восстановить из реального проекта:
GitHub: https://github.com/art-yg/metabot-cjm-designer-specs
GitVerse: https://gitverse.ru/metabot/metabot-cjm-designer-specs
Основная версия интерфейса редактора была изначально создана в V0 — интерфейс-генераторе, разработанном командой создателей Next.js.
Это важный момент: V0 обладает огромной обучающей базой готовых дизайнов, использует лучшие паттерны современного React/Next.js-подхода и умеет генерировать чистый, аккуратный, минималистичный интерфейс, который отлично подходит для:
-
прототипирования: первые версии можно получить за минуты;
-
быстрой эволюции: внесли правку → обновили компонент;
-
единообразия UI: всё выглядит цельно, в единой системе;
-
лучшего старта проекта: он создаёт базовый стиль, который проще поддерживать.
Именно поэтому первые две версии визуального редактора были сделаны полностью в V0.
После того как каркас был собран, мы переехали в Cursor.
В Cursor уже:
-
улучшали дизайн,
-
корректировали стиль,
-
дорабатывали интерфейс,
-
добавляли сложные компоненты,
-
интегрировали API,
-
формировали уже продакшен-ориентированную архитектуру.
Финальный вариант, который вы видите в репозитории, — это результат совместной работы V0 и Cursor, где:
-
V0 дал идеальный стартовый дизайн и структуру,
-
Cursor дал возможность довести проект до рабочего качества.
Что касается спецификаций:
-
В репозитории сохранены почти все спецификации V0, поскольку V0 работал строго по промптам и генерировал воспроизводимый код.
-
Спецификации Cursor восстановить не удалось: там происходило множество микро-итераций, локальных улучшений, полировки компонентов, дополнительных правок, устранения несовпадений между UI и реальным поведением.
-
Мы намеренно исключили из набора спецификаций пункт «исправления ошибок», потому что такие правки — это компетенция разработчика и не представляют особой обучающей пользы.
Таким образом:
V0 дал foundation — Cursor дал refinement.
В итоге вы получаете хороший прототип, который можно расширять, углублять, менять и развивать под коммерческий продукт.
💻 Исходный код фронтенда (React/Next.js)
GitHub: https://github.com/art-yg/metabot-cjm-designer
GitVerse: https://gitverse.ru/metabot/metabot-cjm-designer
Исходный код фронтенда — это не эталон и не финальный продукт.
Это рабочий прототип, созданный для демонстрации концепции no-code редактора, на котором вы можете:
- изучить архитектуру,
- форкнуть и доработать под себя,
- переписать полностью,
- использовать как основу.
В реальных продуктах вы можете — и вполне вероятно будете — улучшать структуру, оптимизацию, убирать лишнее, переносить логику, оптимизировать производительность.
Пример JSON импорта
Две воронки (онбординг в продукт + работа с LLM) в одном файле:
GitHub: https://github.com/art-yg/metabot-cjm-designer-specs/blob/main/json/cjm-schema-example1.json
GitVerse: https://gitverse.ru/metabot/metabot-cjm-designer-specs/content/main/json/cjm-schema-example1.json
4.3. Принципы UI/UX, на которых строится редактор
Мы сознательно выбрали подход «минимум интерфейса — максимум пространства», вдохновившись инструментами вроде Miro.
Основные моменты:
1) Большая канва, минимум панелей
Ничего не перекрывает рабочее пространство.
Узлы и связи всегда в центре внимания.
2) Popup-редактирование вместо боковых панелей
Первые версии имели боковую панель, но она отнимала пространство.
Popup-окно лучше работает для:
-
редактирования узла,
-
переключения вкладок,
-
изменения параметров.
3) React Flow для прототипа
React Flow — идеальная библиотека для быстрого прототипирования и небольших проектов.
Но не идеальна для:
-
больших схем (лэйаут начинает тормозить),
-
100+ нод,
-
сложных графов.
Для полноценных продуктов рекомендуем подобрать более оптимизированную библиотеку.
Перед тем как завершить
Мы не углубляемся в детали разработки приложений на React/Next.js — это отдельная большая тема, и этот урок посвящён именно тому, как построить no-code редактор поверх ядра Metabot, а не фронтенд-разработке как таковой.
Если вы хотите разобраться глубже — есть два лучших пути:
-
Взять исходники и спецификации редактора, загрузить архив в ChatGPT, и пошагово изучить архитектуру, структуры данных, паттерны и логику.
Это максимально практичный способ. -
Взять спецификации из папки
/specsи попробовать воссоздать редактор самостоятельно — используя V0, Cursor или любой другой генератор UI.
Спеки в этом проекте очень детализированные и позволяют полностью повторить дизайнер с нуля.
Эти два подхода дадут вам гораздо больше, чем любой абстрактный учебник по React — потому что вы будете изучать реальный рабочий продукт, который уже интегрирован с Metabot.
🎉 Поздравляем — урок завершён!
Мы подошли к финалу большого и важного пути.
Теперь у вас есть все инструменты, чтобы создавать собственные no-code редакторы поверх ядра Metabot, точно так же, как это делают разработчики самой платформы.
Давайте коротко зафиксируем, что именно вы теперь умеете.
✅ Что вы освоили в этом уроке
1. Поняли внутреннюю механику Metabot
Вы разобрались:
-
как Metabot хранит скрипты, команды и меню;
-
как работает низкоуровневый PHP-Builder;
-
почему Builder — это фундамент конструктора любого уровня сложности.
Теперь вы умеете работать с двигателем платформы, а не только с UI.
2. Создали собственный JSON-формат воронок
Вы:
-
спроектировали свою FSM-модель (автомат состояний),
-
добавили поля формата и версии,
-
определили свои типы шагов,
-
научились описывать сценарии как человекочитаемый DSL.
Этот JSON — основа любого no-code редактора.
3. Полностью разобрали Mapper
Вы увидели, как работает реальный JS-транслятор:
-
проверка формата и бота,
-
создание секции,
-
создание скриптов,
-
построение переходов,
-
генерация JS-кода для LLM, поиска по базе знаний и логгера,
-
обработка кнопок,
-
сборка карты как topology graph.
То есть вы освоили алгоритм импорта в Metabot от начала до конца.
Mapper — это ваш мост между визуальным редактором и low-code ядром.
4. Научились импортировать воронки через API
Вы теперь умеете:
-
создавать endpoint в Metabot,
-
передавать JSON,
-
полностью пересобирать карту,
-
реализовывать версионирование и namespaces.
Это полноценный backend-конвейер для вашего конструктора.
5. Построили визуальный редактор на React
Вы узнали:
-
почему мы начинали с V0 (создатели Next.js → чистый UI),
-
как V0 даёт быстрый skeleton редактора,
-
как затем довести продукт до ума в Cursor,
-
как устроены popup-формы, канва, панель инструментов, связи,
-
как связать редактор с JSON и Metabot API.
А самое главное — вы получили исходники редактора и спецификации, на которых его можно воспроизвести полностью.
6. Получили материалы для самостоятельного развития
В этом уроке вы получили:
-
JSON-формат и полное его описание;
-
Mapper.js — рабочий пример транслятора;
-
Builder.php — низкоуровневый API;
-
Спецификации редактора (/specs), созданные в V0;
-
Исходный код фронтенда (React + Next.js);
-
Полную схему связей «JSON → Mapper → Builder → Metabot DB».
Это достаточный набор, чтобы:
-
сделать свой ManyChat,
-
свой BotHelp,
-
свой AI-конструктор,
-
свой CJM-редактор,
-
свой бизнесовый продукт поверх Metabot.
🔧 Примечание
Если вам понадобится углублять функциональность Builder, создавать собственные PHP-плагины или расширять серверную логику конструктора — это можно делать:
-
на выделенном сервере,
-
или в коробочной версии Metabot.
На общем облаке установка PHP-плагинов отключена по безопасности.
Если хотите предложить улучшение Builder — напишите нам, мы рассмотрим и, возможно, включим в официальный билд.
🚀 Напоследок
Мы сознательно не углублялись в React/Next.js как учебник — это не цель урока.
Но у вас есть всё необходимое, чтобы:
-
загрузить архив со спецификациями в ChatGPT,
-
изучить архитектуру,
-
воспроизвести или улучшить редактор,
-
построить собственный инструмент с нуля.
Это лучший способ освоить реальный продуктовый подход.
🎉 Ещё раз поздравляем!
Теперь вы умеете создавать no-code редакторы поверх Metabot, понимаете, как писать свои команды, как превращать JSON в скрипты, как строить визуальные пайплайны и как надстраивать интерфейсы над low-code ядром.
Вы научились тому, что большинство разработчиков видит только как «магия» в интерфейсе — а вы теперь умеете реализовывать её сами.
И это уже уровень архитекторов платформ, а не просто разработчиков чат-ботов.
Удачи вам в ваших продуктах — и создавайте смело.
Если нужно — мы рядом.















Нет комментариев