Перейти к основному контенту

Как построить визуальный no-code редактор воронок и AI пайплайнов на React поверх low-code ядра Metabot

image.pngПопробовать: 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, нужно понимать две вещи:

  1. Какие команды реально существуют в ядре платформы

  2. Как 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

Палитра команд платформы:

image.png

Короткое объяснение логики исполнения команд

  • Команды внутри одного скрипта (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 редакторе:

image.png

Пример настроек в новом no code редакторе:

image.png

Пример 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 редакторе:

image.png

Форма редактирования стандартной команды "Отправить текст" в low-code редакторе:

image.png

Форма редактирования новой команды "Отправить текст" в новом no-code редакторе, сравните возможности:

image.png

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-пекло от пользователя.

image.png

В нашем интерфейсе есть:

  • выбор типа логирования: step, event, tag, utter,

  • выбор пути (way),

  • параметры.

image.png

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.

image.png


В нашем редакторе — просто стрелка между узлами. 

image.png

А в 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 узел: 

image.png

И одну форму, чтобы пользователю не пришлось писать JS код — мы его будем генерить за него при импорте в low-code:

image.png

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.

image.png

Обратите внимание у узла мы добавили три выхода: успешный, ничего не найдено и ошибка.

image.png

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

Секция (папка) создаётся заново при каждом импорте, что даёт чистый, предсказуемый результат.

image.png


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 и реальными скриптами в базе данных:

  1. Mapper (JavaScript) — читает JSON, создаёт структуру карты, генерирует команды и меню.

  2. 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 из этого урока:


ЧАСТЬ 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();

Он проверяет:

  1. передан ли текущий бизнес

  2. передан ли текущий бот

  3. принадлежит ли бот действующему бизнесу

  4. корректен ли lead

  5. корректна ли топология

  6. установлен ли 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:

  1. Проверить формат и подключить нужного бота

  2. Создать секцию (папку) под воронку

  3. Создать скрипты под каждый шаг

  4. Наполнить эти скрипты командами

  5. Сгенерировать переходы (default/аналитика/кнопки)

  6. Сгенерировать и подключить JS-команды: LLM, KB-поиск, кастомный скрипт, логгер

  7. Построить меню-кнопки и обработчики

  8. Добавить все переходы между скриптами

Далее — полный разбор устройства 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 обязан:

  1. Установить бота

  2. Пройти 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

Пока заглушка, но сюда можно добавить:


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-блока мы добавляем переходы:

Логика:

  1. Если есть логгер — первым переходом идём в логгер.

  2. Потом логгер → next_step

  3. Если логгера нет — просто script → next_step

  4. Но если есть кнопки — дефолтный выход НЕ создаётся.

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

  1. Чистая модель автомата состояний

  2. Поддержка аналитики на уровне схемы

  3. Универсальные handler-скрипты для кнопок

  4. Отсутствие foreign-key зависимостей

  5. JS-код, генерируемый на лету

  6. Изоляция каждой воронки через namespace кодов

  7. Предсказуемая однопроходная структура импорта


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, доступны несколько вариантов:

  1. Развернуть выделенный сервер,

  2. Приобрести коробочную версию MetaBot,

  3. Или предложить свой функционал — пришлите описание в поддержку, и мы рассмотрим возможность включить расширения в официальный билд.
    Подробнее о тарифах: 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, а не фронтенд-разработке как таковой.

Если вы хотите разобраться глубже — есть два лучших пути:

  1. Взять исходники и спецификации редактора, загрузить архив в ChatGPT, и пошагово изучить архитектуру, структуры данных, паттерны и логику.
    Это максимально практичный способ.

  2. Взять спецификации из папки /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 ядром.

Вы научились тому, что большинство разработчиков видит только как «магия» в интерфейсе — а вы теперь умеете реализовывать её сами.

И это уже уровень архитекторов платформ, а не просто разработчиков чат-ботов.

Удачи вам в ваших продуктах — и создавайте смело.
Если нужно — мы рядом.