Добавление нового триггера на фронтенде
Этот документ описывает все файлы, которые нужно создать или обновить при добавлении нового типа триггера в редактор сценариев.
Пример реализации: 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— иконка FontAwesomenodeColors— цветовые классы 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
Четыре места:
- Импорт нового компонента превью
- Порт
trigger-next— добавить тип в условие|| (node.type as any) === 'managed_bot_updated_trigger' - Компактный размер
w-52— добавить тип в условие - Скрытие
NodeHeader— добавить&& (node.type as any) !== 'managed_bot_updated_trigger' - Рендер превью — добавить блок
{(node.type as any) === '...' && <Preview />} - Скрытие футера с айди — добавить тип в условие скрытия
#{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
Два места:
isTriggerв функцииbuildSmartPath— добавить тип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
Два места:
ROOT_TYPES— добавить тип чтобы триггер позиционировался как корневой узелinferConnectionsFromNodes— добавить тип в условие дляtrigger-next
Итого файлов
| Тип | Количество |
|---|---|
| Новых файлов | 3–4 |
| Редактируемых файлов | 14–15 |
Порядок реализации
- Схема (
node-schema.ts) - Определение триггера (
sidebar/massive/triggers/) - Регистрация в сайдбаре (
index.ts,constants.ts) - Панель свойств (
Configuration.tsx,properties-panel.tsx,properties-header.tsx,node-constants.ts,node-defaults.ts,node-formatters.ts) - Канвас (
preview.tsx,header.tsx,canvas-node.tsx,node-header.tsx,node-icons.ts,node-colors.ts,connections-layer.tsx) - Переменные (
variables-utils.ts,variable-display-utils.tsx) - Раскладка (
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)— низкоуровневый APIgenerate{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
Три изменения:
- Добавить импорт
generate{Name}Handlers - Добавить блок вызова после аналогичных триггеров:
const {name}Code = generate{Name}Handlers(nodes);
if ({name}Code) {
codeLines.push('\n# Обработчики {название}');
{name}Code.split('\n').forEach(line => codeLines.push(line));
}
- Добавить тип в условие пропуска:
|| (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. Порядок реализации (генерация)
{name}.params.ts— интерфейсы{name}.schema.ts— схема{name}.py.jinja2— шаблон{name}.renderer.ts— генератор{name}.fixture.ts— фикстуры{name}.test.ts— unit тесты{name}.md— документацияindex.ts— реэкспортnode-handlers.dispatcher.ts— интеграцияnode-predicates.ts— предикатinteractive-callback-handlers.renderer.ts— добавить тип вNODE_TYPES_WITH_DEDICATED_HANDLERStest-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.tsx | nodeTypeNames, 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.tsx | getBadgeText() + getNodeInfo() |
Порядок реализации (узел-действие)
node-schema.ts— тип + поляsidebar/massive/{category}/{name}.ts+index.ts— определение в палитреsidebar/constants.ts— регистрация в категории{Name}Configuration.tsx— UI панели свойствproperties-panel.tsx,properties-header.tsx,node-constants.ts,node-formatters.ts— интеграция в панель{name}-preview.tsx,canvas-node.tsx,node-header.tsx,node-icons.ts,node-colors.ts— канвасvariables-utils.ts,variable-display-utils.tsx— переменные (если нужно)lib/templates/{name}/— папка шаблона генератораnode-handlers.dispatcher.ts— интеграция в диспетчерnode-predicates.ts— предикат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?: booleanlib/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:
lib/templates/imports/imports.schema.ts— используется в тестах и прямых вызовахlib/templates/schemas/imports-schema.ts— используется вtyped-renderer.tsчерез barrel./schemas
Обе копии нужно обновлять синхронно! Иначе шаблон не получит новый флаг.