diff --git a/src/langbot/pkg/pipeline/pipelinemgr.py b/src/langbot/pkg/pipeline/pipelinemgr.py index e59a5f15..c9c5def2 100644 --- a/src/langbot/pkg/pipeline/pipelinemgr.py +++ b/src/langbot/pkg/pipeline/pipelinemgr.py @@ -247,8 +247,8 @@ class RuntimePipeline: await self._check_output(query, result) if result.result_type == pipeline_entities.ResultType.INTERRUPT: - self.ap.logger.warning( - f'Stage {stage_container.inst_name} interrupted query {query.query_id}, message: {str(query.message_chain)[:100]}' + self.ap.logger.debug( + f'Stage {stage_container.inst_name} interrupted query {query.query_id}' ) break elif result.result_type == pipeline_entities.ResultType.CONTINUE: @@ -263,8 +263,8 @@ class RuntimePipeline: await self._check_output(query, sub_result) if sub_result.result_type == pipeline_entities.ResultType.INTERRUPT: - self.ap.logger.warning( - f'Stage {stage_container.inst_name} interrupted query {query.query_id}, message: {str(query.message_chain)[:100]}' + self.ap.logger.debug( + f'Stage {stage_container.inst_name} interrupted query {query.query_id}' ) break elif sub_result.result_type == pipeline_entities.ResultType.CONTINUE: @@ -327,8 +327,8 @@ class RuntimePipeline: event_ctx = await self.ap.plugin_connector.emit_event(event_obj, bound_plugins) if event_ctx.is_prevented_default(): - self.ap.logger.warning( - f'MessageReceived event prevented default for query {query.query_id}, pipeline={pipeline_name}, message: {str(query.message_chain)[:100]}' + self.ap.logger.debug( + f'MessageReceived event prevented default for query {query.query_id}, pipeline={pipeline_name}' ) return diff --git a/src/langbot/pkg/pipeline/process/handlers/chat.py b/src/langbot/pkg/pipeline/process/handlers/chat.py index 0f2e6a90..203a3612 100644 --- a/src/langbot/pkg/pipeline/process/handlers/chat.py +++ b/src/langbot/pkg/pipeline/process/handlers/chat.py @@ -61,8 +61,8 @@ class ChatMessageHandler(handler.MessageHandler): yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query) else: - self.ap.logger.warning( - f'NormalMessageReceived event prevented default for query {query.query_id} without reply, message: {str(query.message_chain)[:100]}' + self.ap.logger.debug( + f'NormalMessageReceived event prevented default for query {query.query_id} without reply' ) yield entities.StageProcessResult(result_type=entities.ResultType.INTERRUPT, new_query=query) else: diff --git a/web/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx index aa803d24..611a58bd 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -13,13 +13,10 @@ import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic'; import { UUID } from 'uuidjs'; import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent'; import { httpClient } from '@/app/infra/http/HttpClient'; -import { - Bot, - PipelineRoutingRule, - RoutingRuleOperator, -} from '@/app/infra/entities/api'; +import { Bot } from '@/app/infra/entities/api'; import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs'; -import { ExternalLink, Plus, Trash2 } from 'lucide-react'; +import { ExternalLink } from 'lucide-react'; +import RoutingRulesEditor from './RoutingRulesEditor'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; @@ -492,303 +489,10 @@ export default function BotForm({ /> {/* Pipeline Routing Rules */} -
-
-
- {t('bots.routingRules')} -

- {t('bots.routingRulesDescription')} -

-
- -
- - {(form.watch('pipeline_routing_rules') || []).map( - (rule, index) => { - // Determine which operators are available for the current type - const operatorsForType: { - value: RoutingRuleOperator; - labelKey: string; - }[] = - rule.type === 'launcher_type' - ? [ - { value: 'eq', labelKey: 'bots.operatorEq' }, - { value: 'neq', labelKey: 'bots.operatorNeq' }, - ] - : rule.type === 'launcher_id' - ? [ - { value: 'eq', labelKey: 'bots.operatorEq' }, - { value: 'neq', labelKey: 'bots.operatorNeq' }, - { - value: 'contains', - labelKey: 'bots.operatorContains', - }, - { - value: 'not_contains', - labelKey: 'bots.operatorNotContains', - }, - { - value: 'regex', - labelKey: 'bots.operatorRegex', - }, - ] - : [ - { value: 'eq', labelKey: 'bots.operatorEq' }, - { value: 'neq', labelKey: 'bots.operatorNeq' }, - { - value: 'contains', - labelKey: 'bots.operatorContains', - }, - { - value: 'not_contains', - labelKey: 'bots.operatorNotContains', - }, - { - value: 'starts_with', - labelKey: 'bots.operatorStartsWith', - }, - { - value: 'regex', - labelKey: 'bots.operatorRegex', - }, - ]; - - return ( -
- {/* Field selector */} - - - {/* Operator selector */} - - - {/* Value input */} - {rule.type === 'launcher_type' ? ( - - ) : ( - { - const rules = [ - ...(form.getValues('pipeline_routing_rules') || - []), - ]; - rules[index] = { - ...rules[index], - value: e.target.value, - }; - form.setValue('pipeline_routing_rules', rules, { - shouldDirty: true, - }); - }} - /> - )} - - - → - - - {/* Pipeline selector */} - - - -
- ); - }, - )} -
+ )} diff --git a/web/src/app/home/bots/components/bot-form/RoutingRulesEditor.tsx b/web/src/app/home/bots/components/bot-form/RoutingRulesEditor.tsx new file mode 100644 index 00000000..7d2ada5d --- /dev/null +++ b/web/src/app/home/bots/components/bot-form/RoutingRulesEditor.tsx @@ -0,0 +1,258 @@ +'use client'; + +import { useTranslation } from 'react-i18next'; +import { UseFormReturn } from 'react-hook-form'; +import { + PipelineRoutingRule, + RoutingRuleOperator, +} from '@/app/infra/entities/api'; +import { Plus, Trash2 } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { FormLabel } from '@/components/ui/form'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; + +interface PipelineOption { + value: string; + label: string; + emoji?: string; +} + +interface RoutingRulesEditorProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + form: UseFormReturn; + pipelineNameList: PipelineOption[]; +} + +const OPERATORS_BY_TYPE: Record< + PipelineRoutingRule['type'], + { value: RoutingRuleOperator; labelKey: string }[] +> = { + launcher_type: [ + { value: 'eq', labelKey: 'bots.operatorEq' }, + { value: 'neq', labelKey: 'bots.operatorNeq' }, + ], + launcher_id: [ + { value: 'eq', labelKey: 'bots.operatorEq' }, + { value: 'neq', labelKey: 'bots.operatorNeq' }, + { value: 'contains', labelKey: 'bots.operatorContains' }, + { value: 'not_contains', labelKey: 'bots.operatorNotContains' }, + { value: 'regex', labelKey: 'bots.operatorRegex' }, + ], + message_content: [ + { value: 'eq', labelKey: 'bots.operatorEq' }, + { value: 'neq', labelKey: 'bots.operatorNeq' }, + { value: 'contains', labelKey: 'bots.operatorContains' }, + { value: 'not_contains', labelKey: 'bots.operatorNotContains' }, + { value: 'starts_with', labelKey: 'bots.operatorStartsWith' }, + { value: 'regex', labelKey: 'bots.operatorRegex' }, + ], +}; + +function getValuePlaceholder( + t: (key: string) => string, + rule: PipelineRoutingRule, +): string { + if (rule.type === 'launcher_id') return t('bots.ruleValueLauncherIdPlaceholder'); + if (rule.operator === 'regex') return t('bots.ruleValueRegexpPlaceholder'); + return t('bots.ruleValueMessagePlaceholder'); +} + +export default function RoutingRulesEditor({ + form, + pipelineNameList, +}: RoutingRulesEditorProps) { + const { t } = useTranslation(); + + const rules: PipelineRoutingRule[] = + form.watch('pipeline_routing_rules') || []; + + const updateRules = (newRules: PipelineRoutingRule[]) => { + form.setValue('pipeline_routing_rules', newRules, { shouldDirty: true }); + }; + + const addRule = () => { + updateRules([ + ...rules, + { + type: 'launcher_type', + operator: 'eq', + value: '', + pipeline_uuid: '', + }, + ]); + }; + + const updateRule = (index: number, patch: Partial) => { + const updated = [...rules]; + updated[index] = { ...updated[index], ...patch }; + updateRules(updated); + }; + + const removeRule = (index: number) => { + const updated = [...rules]; + updated.splice(index, 1); + updateRules(updated); + }; + + return ( +
+
+
+ {t('bots.routingRules')} +

+ {t('bots.routingRulesDescription')} +

+
+ +
+ + {rules.map((rule, index) => { + const operatorsForType = OPERATORS_BY_TYPE[rule.type] || OPERATORS_BY_TYPE.message_content; + + return ( +
+ {/* Field selector */} + + + {/* Operator selector */} + + + {/* Value input */} + {rule.type === 'launcher_type' ? ( + + ) : ( + updateRule(index, { value: e.target.value })} + /> + )} + + + + {/* Pipeline selector */} + + + +
+ ); + })} +
+ ); +}