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

Как построить визуальный 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 → вставили → импортировали)


🔷 Глава 2 завершена.

Следующая — Глава 3. Import: Mapper и Builder

В ней мы разберём:

  • как работает маппер

  • как он создаёт скрипты

  • как билдер создаёт фразы

  • как связываются узлы

  • как формируется final graph в Metabot

Готов продолжать?