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

Добавление нового триггера на фронтенде

Этот документ описывает все файлы, которые нужно создать или обновить при добавлении нового типа триггера в редактор сценариев.

Пример реализации: managed_bot_updated_trigger (Bot API 9.6).


1. Схема (shared)

shared/schema/tables/node-schema.ts

Добавить новый тип в enum type объекта nodeSchema:

type: z.enum([..., 'managed_bot_updated_trigger'])

2. Сайдбар

Новый файл: client/components/editor/sidebar/massive/triggers/{name}.ts

Определение ComponentDefinition — имя, описание, иконка, цвет, тип, defaultData.

Пример: managed-bot-updated-trigger.ts

client/components/editor/sidebar/massive/triggers/index.ts

Добавить реэкспорт нового триггера.

client/components/editor/sidebar/constants.ts

Добавить триггер в массив components категории 'Триггеры'.


3. Панель свойств

Новый файл: client/components/editor/properties/components/trigger/{Name}Configuration.tsx

Компонент панели свойств триггера. Содержит инфо-блок, поля для переменных и TriggerTargetSelector.

Пример: ManagedBotUpdatedTriggerConfiguration.tsx

client/components/editor/properties/components/main/properties-panel.tsx

Добавить блок рендера нового компонента конфигурации:

{isTriggerNode(selectedNode.type) && (selectedNode.type as any) === 'managed_bot_updated_trigger' && (
<ManagedBotUpdatedTriggerConfiguration ... />
)}

client/components/editor/properties/components/layout/properties-header.tsx

Добавить в три локальных объекта внутри файла:

  • nodeNames — отображаемое название
  • nodeIcons — иконка FontAwesome
  • nodeColors — цветовые классы Tailwind

client/components/editor/properties/utils/node-constants.ts

Добавить тип в массив TRIGGER_NODE_TYPES.

client/components/editor/properties/utils/node-defaults.ts

Добавить дефолтные данные для нового типа в объект defaults.

client/components/editor/properties/utils/node-formatters.ts

Добавить отображаемое название в объект types функции getNodeTypeLabel.

client/components/editor/properties/utils/variables-utils.ts

Добавить блок извлечения переменных из нод нового типа (аналогично callback_trigger).


4. Канвас

Новый файл: client/components/editor/canvas/canvas-node/{name}-preview.tsx

Превью узла на холсте — отображает ключевые данные ноды.

Новый файл: client/components/editor/canvas/canvas-node/{name}-header.tsx

Заголовок узла на холсте (опционально, если нужен кастомный заголовок).

client/components/editor/canvas/canvas-node/canvas-node.tsx

Четыре места:

  1. Импорт нового компонента превью
  2. Порт trigger-next — добавить тип в условие || (node.type as any) === 'managed_bot_updated_trigger'
  3. Компактный размер w-52 — добавить тип в условие
  4. Скрытие NodeHeader — добавить && (node.type as any) !== 'managed_bot_updated_trigger'
  5. Рендер превью — добавить блок {(node.type as any) === '...' && <Preview />}
  6. Скрытие футера с айди — добавить тип в условие скрытия #{node.id}

client/components/editor/canvas/canvas-node/node-header.tsx

Добавить case в switch для рендера заголовка.

client/components/editor/canvas/canvas-node/node-icons.ts

Добавить иконку: managed_bot_updated_trigger: 'fas fa-robot'

client/components/editor/canvas/canvas-node/node-colors.ts

Добавить цветовую схему для нового типа.

client/components/editor/canvas/canvas-node/connections-layer.tsx

Два места:

  1. isTrigger в функции buildSmartPath — добавить тип
  2. collectConnections пункт 4 — добавить тип для генерации trigger-next соединения

5. Переменные

client/components/editor/properties/utils/variables-utils.ts

Добавить блок извлечения переменных из нод нового типа (аналогично callback_trigger).

Если узел сохраняет результат в переменную (поле saveResultTo, httpRequestResponseVariable и т.д.) — добавить блок:

allNodes.forEach(node => {
if ((node.type as string) !== 'new_type') return;
const data = node.data as any;
if (!data.saveResultTo?.trim()) return;
const key = `new_type__${node.id}`;
if (!variablesMap.has(key)) {
variablesMap.set(key, {
name: data.saveResultTo,
nodeId: node.id,
nodeType: 'new_type' as any,
sourceTable: 'bot_users',
description: `Описание результата`,
});
}
});

client/components/editor/inline-rich/components/variable-display-utils.tsx

Два места:

1. getBadgeText() — добавить бейдж для нового типа:

const labels: Record<string, string> = {
// ...существующие...
new_type: '🔣 Метка',
};

2. getNodeInfo() — добавить блок отображения описания переменной в дропдауне:

if ((variable.nodeType as string) === 'new_type') {
return (
<div className="text-[10px] text-violet-500 dark:text-violet-400 mt-0.5 truncate">
🔣 {variable.description}
</div>
);
}

Без этих изменений переменная будет показываться с иконкой 📌 и ID узла вместо нормального описания.


6. Раскладка (layout)

client/utils/hierarchical-layout.ts

Два места:

  1. ROOT_TYPES — добавить тип чтобы триггер позиционировался как корневой узел
  2. inferConnectionsFromNodes — добавить тип в условие для trigger-next

Итого файлов

ТипКоличество
Новых файлов3–4
Редактируемых файлов14–15

Порядок реализации

  1. Схема (node-schema.ts)
  2. Определение триггера (sidebar/massive/triggers/)
  3. Регистрация в сайдбаре (index.ts, constants.ts)
  4. Панель свойств (Configuration.tsx, properties-panel.tsx, properties-header.tsx, node-constants.ts, node-defaults.ts, node-formatters.ts)
  5. Канвас (preview.tsx, header.tsx, canvas-node.tsx, node-header.tsx, node-icons.ts, node-colors.ts, connections-layer.tsx)
  6. Переменные (variables-utils.ts, variable-display-utils.tsx)
  7. Раскладка (hierarchical-layout.ts)

Добавление нового триггера в генерацию кода

Этот раздел описывает все файлы для добавления поддержки нового триггера на стороне генерации Python кода.

Пример реализации: managed_bot_updated_trigger → папка lib/templates/managed-bot-updated-trigger/.


1. Новая папка шаблона lib/templates/{name}/

Каждый триггер — отдельная папка со стандартным набором файлов:

{name}.params.ts — TypeScript интерфейсы

Определяет {Name}Entry (поля одного триггера) и {Name}TemplateParams (массив entries).

Обязательные поля Entry:

  • nodeId: string — ID узла
  • targetNodeId: string — ID целевого узла
  • targetNodeType: string — тип целевого узла

Опциональные поля — специфичные для триггера (переменные для сохранения, фильтры и т.д.).

{name}.schema.ts — Zod схема валидации

Валидирует параметры перед передачей в шаблон. Обязательные поля — z.string(), опциональные — z.string().optional().

{name}.py.jinja2 — Jinja2 шаблон Python кода

Генерирует Python обработчик. Для триггеров-сообщений:

@dp.message(lambda m: m.{event_field} is not None)
async def {name}_{nodeId}_handler(message: types.Message):
...
fake_cb = FakeCallbackQuery(user_id, message)
await handle_callback_{targetNodeId}(fake_cb)

FakeCallbackQuery должен содержать self.message = message чтобы safe_edit_or_send мог отправить ответ.

{name}.renderer.ts — функции генерации

Три функции:

  • collect{Name}Entries(nodes) — собирает триггеры из узлов
  • generate{Name}(params) — низкоуровневый API
  • generate{Name}Handlers(nodes) — высокоуровневый API

{name}.fixture.ts — тестовые данные

Фикстуры для unit-тестов: validParamsEmpty, validParamsSingle, validParamsMultiple, nodesWithTrigger, nodesWithMissingTarget, nodesWithoutTriggers, nodesWithNullAndMixed.

{name}.test.ts — unit тесты (vitest)

Блоки тестов:

  • generate{Name}() — 8–10 тестов
  • {name}ParamsSchema — 4 теста
  • collect{Name}Entries() — 5 тестов
  • generate{Name}Handlers() — 4 теста
  • Специфика триггера — 5–7 тестов
  • Производительность — 2 теста

{name}.md — документация

Описание, таблица параметров, пример входных данных, пример выходного Python кода, использование API.

index.ts — реэкспорт модуля


2. Фазовый тест lib/tests/test-phase-{name}.ts

Интеграционный тест генерации Python кода через generatePythonCode. Блоки:

  • A: Базовая генерация (10 тестов) — декоратор, имя, переменные, вызов handle_callback, logging, без autoTransitionTo, несколько триггеров, синтаксис
  • B: Целевые ноды (6 тестов) — message, forward_message, condition, с переменными
  • C: Специфика триггера (4 теста) — фильтры, условия
  • D: Взаимодействие с другими триггерами (5 тестов)
  • E: FakeCallbackQuery (4 теста) — from_user, _is_fake, self.message
  • F: Полные сценарии (3 теста) — с userDatabaseEnabled, несколько триггеров

3. Редактируемые файлы

lib/templates/node-handlers/node-handlers.dispatcher.ts

Три изменения:

  1. Добавить импорт generate{Name}Handlers
  2. Добавить блок вызова после аналогичных триггеров:
const {name}Code = generate{Name}Handlers(nodes);
if ({name}Code) {
codeLines.push('\n# Обработчики {название}');
{name}Code.split('\n').forEach(line => codeLines.push(line));
}
  1. Добавить тип в условие пропуска: || (node.type as any) === '{type_name}'

lib/templates/filters/node-predicates.ts

Добавить предикат:

export function has{Name}Nodes(nodes: Node[]): boolean {
return nodes.filter(n => n != null).some(node => (node.type as string) === '{type_name}');
}

lib/templates/keyboard-handlers/interactive-callback-handlers/interactive-callback-handlers.renderer.ts

Добавить тип в константу NODE_TYPES_WITH_DEDICATED_HANDLERS:

const NODE_TYPES_WITH_DEDICATED_HANDLERS = new Set<string>([
// ...существующие типы...
'{type_name}', // собственный обработчик генерируется шаблоном {name}.py.jinja2
]);

Это обязательно для любого узла-действия или триггера с собственным шаблоном. Без этого interactive-callback-handlers.renderer.ts создаст дублирующий пустой обработчик для узлов, на которые ведут кнопки или автопереходы.

lib/bot-generator/core/normalize-keyboard-bindings.ts

Если новый узел добавляет поля в keyboard-ноду (например shuffleButtons, customField), нужно обновить функцию buildMergedKeyboardData() чтобы эти поля пробрасывались из keyboard-ноды в host-message при привязке через keyboardNodeId.


4. Особенности для разных типов триггеров

Триггер-сообщение (ContentType)

Регистрируется через @dp.message(lambda m: m.{field} is not None). FakeCallbackQuery принимает message и хранит self.message = message.

Триггер-апдейт (Update.*)

Регистрируется через @dp.update.outer_middleware(). FakeCallbackQuery без self.message.

Триггер-команда

Регистрируется через @dp.message(Command("...")).


5. Итого файлов для генерации

ТипКоличество
Новых файлов в папке шаблона8
Новый фазовый тест1
Редактируемых файлов3
Итого12

6. Порядок реализации (генерация)

  1. {name}.params.ts — интерфейсы
  2. {name}.schema.ts — схема
  3. {name}.py.jinja2 — шаблон
  4. {name}.renderer.ts — генератор
  5. {name}.fixture.ts — фикстуры
  6. {name}.test.ts — unit тесты
  7. {name}.md — документация
  8. index.ts — реэкспорт
  9. node-handlers.dispatcher.ts — интеграция
  10. node-predicates.ts — предикат
  11. interactive-callback-handlers.renderer.ts — добавить тип в NODE_TYPES_WITH_DEDICATED_HANDLERS
  12. test-phase-{name}.ts — фазовый тест

Добавление нового узла-действия (не триггера)

Узлы-действия — это узлы которые выполняют операцию и опционально сохраняют результат в переменную. Примеры: psql_query, http_request, set_variable, get_managed_bot_token.

В отличие от триггеров, они:

  • Не регистрируются как TRIGGER_NODE_TYPES — добавляются в MANAGEMENT_NODE_TYPES
  • Не имеют порта trigger-next на холсте
  • Не скрывают NodeHeader (если нет кастомного превью)
  • Могут сохранять результат в переменную

Чеклист файлов для узла-действия

Схема

ФайлИзменение
shared/schema/tables/node-schema.tsДобавить тип в z.enum([...]), добавить поля данных

Сайдбар

ФайлИзменение
client/components/editor/sidebar/massive/{category}/{name}.tsНовый файл — ComponentDefinition
client/components/editor/sidebar/massive/{category}/index.tsРеэкспорт
client/components/editor/sidebar/constants.tsДобавить в нужную категорию

Панель свойств

ФайлИзменение
client/components/editor/properties/components/configuration/{Name}Configuration.tsxНовый файл — UI панели свойств
client/components/editor/properties/components/main/properties-panel.tsxИмпорт + блок рендера + исключить из BasicSettingsSection
client/components/editor/properties/components/layout/properties-header.tsxnodeTypeNames, nodeIcons, nodeColors
client/components/editor/properties/utils/node-constants.tsДобавить в MANAGEMENT_NODE_TYPES
client/components/editor/properties/utils/node-formatters.tsДобавить в getNodeTypeLabel()

Канвас

ФайлИзменение
client/components/editor/canvas/canvas-node/{name}-preview.tsxНовый файл — превью на холсте
client/components/editor/canvas/canvas-node/canvas-node.tsxИмпорт + рендер превью
client/components/editor/canvas/canvas-node/node-header.tsxДобавить case 'new_type' в renderTitle()
client/components/editor/canvas/canvas-node/node-icons.tsДобавить иконку
client/components/editor/canvas/canvas-node/node-colors.tsДобавить цветовую схему

Генератор

ФайлИзменение
lib/templates/{name}/Создать папку шаблона (params, schema, jinja2, renderer, fixture, test, md, index)
lib/templates/node-handlers/node-handlers.dispatcher.tsИмпорт + блок вызова + пропуск в forEach
lib/templates/filters/node-predicates.tsДобавить предикат has{Name}Nodes
lib/templates/keyboard-handlers/interactive-callback-handlers/interactive-callback-handlers.renderer.tsДобавить тип в NODE_TYPES_WITH_DEDICATED_HANDLERS

Переменные (только если узел сохраняет результат)

ФайлИзменение
client/components/editor/properties/utils/variables-utils.tsДобавить блок извлечения переменной
client/components/editor/inline-rich/components/variable-display-utils.tsxgetBadgeText() + getNodeInfo()

Порядок реализации (узел-действие)

  1. node-schema.ts — тип + поля
  2. sidebar/massive/{category}/{name}.ts + index.ts — определение в палитре
  3. sidebar/constants.ts — регистрация в категории
  4. {Name}Configuration.tsx — UI панели свойств
  5. properties-panel.tsx, properties-header.tsx, node-constants.ts, node-formatters.ts — интеграция в панель
  6. {name}-preview.tsx, canvas-node.tsx, node-header.tsx, node-icons.ts, node-colors.ts — канвас
  7. variables-utils.ts, variable-display-utils.tsx — переменные (если нужно)
  8. lib/templates/{name}/ — папка шаблона генератора
  9. node-handlers.dispatcher.ts — интеграция в диспетчер
  10. node-predicates.ts — предикат
  11. interactive-callback-handlers.renderer.ts — добавить тип в NODE_TYPES_WITH_DEDICATED_HANDLERS

Шаблоны инфраструктуры (imports, config, main)

Если новый узел требует дополнительных Python-библиотек или инициализации при старте бота, нужно обновить инфраструктурные шаблоны.

Imports (lib/templates/imports/imports.py.jinja2)

Добавить условный блок импорта:

{%- if hasNewFeatureNodes %}
from new_library import SomeClass
{%- endif %}

Также обновить:

  • lib/templates/imports/imports.schema.ts — добавить hasNewFeatureNodes: z.boolean().default(false)
  • lib/templates/imports/imports.params.ts — добавить hasNewFeatureNodes?: boolean
  • lib/templates/schemas/imports-schema.tsдублирующая схема, тоже добавить поле!

Config (lib/templates/config/config.py.jinja2)

Добавить инициализацию клиента/подключения:

{%- if hasNewFeatureNodes %}
# Инициализация нового клиента
new_client = NewClient(os.getenv("NEW_CLIENT_KEY"))
{%- endif %}

Также обновить:

  • lib/templates/config/config.schema.ts — добавить hasNewFeatureNodes: z.boolean().default(false)
  • lib/templates/schemas/config-schema.tsдублирующая схема, тоже добавить поле!

Main (lib/templates/main/main.py.jinja2)

Добавить подключение при старте и отключение в finally:

{%- if hasNewFeatureNodes %}
# Подключаем новый клиент
if new_client:
await new_client.connect()
{%- endif %}

В блоке finally:

{%- if hasNewFeatureNodes %}
if new_client:
await new_client.disconnect()
{%- endif %}

Также обновить:

  • lib/templates/main/main.schema.ts — добавить hasNewFeatureNodes: z.boolean().optional().default(false)
  • lib/templates/schemas/main-schema.tsдублирующая схема, тоже добавить поле!

Feature Flags (lib/bot-generator/core/feature-flags.ts)

Добавить вычисление флага:

hasNewFeatureNodesResult: nodes.some(
n => (n.type as string) === 'new_feature_type'
),

И добавить поле в интерфейс FeatureFlags.

Bot Generator (lib/bot-generator.ts)

Передать флаг в вызовы generateImports, generateConfig, generateMain:

hasNewFeatureNodes: flags.hasNewFeatureNodesResult,

⚠️ Важно: дублирующие схемы

В проекте есть две копии схем для imports, config и main:

  1. lib/templates/imports/imports.schema.ts — используется в тестах и прямых вызовах
  2. lib/templates/schemas/imports-schema.ts — используется в typed-renderer.ts через barrel ./schemas

Обе копии нужно обновлять синхронно! Иначе шаблон не получит новый флаг.