mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-25 13:56:08 +00:00
Feat: bot message routing (#2100)
* refactor: pipeline routing rules - add routed_by_rule bypass and diagnostic logging - Add routing rules editor (RoutingRulesEditor component) - Add routed_by_rule bypass logic in response rules - Add diagnostic logging for pipeline routing - Database migration for bot pipeline routing rules - Extract RoutingRulesEditor component from BotForm - Revert log levels to debug * feat: add message_has_element routing rule type Support routing by message element type (Image, Voice, File, Forward, Face, At, AtAll, Quote) with eq/neq operators. * test: add unit tests for pipeline routing rules 20 tests covering _match_operator (eq/neq/contains/not_contains/ starts_with/regex/invalid) and resolve_pipeline_uuid (launcher_type/ launcher_id/message_content/message_has_element/first-match-wins/ skip-invalid/default-operator). * fix(web): add missing 'message_has_element' to routing rule type validation The Zod schema and TypeScript type for PipelineRoutingRule.type were missing the 'message_has_element' variant, causing silent form validation failure when saving routing rules with this type. * feat: add pipeline discard functionality and localization support * feat(web): improve drag-and-drop with DragOverlay, add discard monitoring and pipeline icons - Add DragOverlay for smooth cursor-following drag in routing rules editor - Remove transition to eliminate redundant swap animation on drop - Record discarded messages in monitoring system via _record_discarded_message - Display pipeline name (Workflow icon) and runner name (Play icon) on session monitor messages - Show discard badge on discarded messages in session monitor - Add i18n translations for discarded/userMessage/botMessage * fix: ensure discarded messages appear in session monitor and improve icons - Create/update monitoring session for discarded messages so they show in the bot session monitor (was only inserting message rows, not sessions) - Use human-readable 'Discarded' as pipeline_name instead of '__discard__' - Change runner icon from Play to Bot for better AI Agent semantics * fix: merge discarded messages into same session and remove session-level pipeline name - Use LauncherTypes enum for session_id in discarded messages to match the format used by monitoring_helper (fixes duplicate sessions) - Don't overwrite session pipeline info on discard — a session can have messages from multiple pipelines - Remove pipeline_name from session list and chat header since it's now shown per-message and a session is no longer single-pipeline * fix(web): only show save button on config tab in bot detail page * fix(web): scroll to bottom after messages render in session monitor --------- Co-authored-by: RockChinQ <rockchinq@gmail.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Copy, Check } from 'lucide-react';
|
||||
import { Ban, Bot, Copy, Check, Workflow } from 'lucide-react';
|
||||
import {
|
||||
MessageChainComponent,
|
||||
Plain,
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Quote,
|
||||
Voice,
|
||||
} from '@/app/infra/entities/message';
|
||||
import { PIPELINE_DISCARD } from '@/app/home/bots/components/bot-form/RoutingRulesEditor';
|
||||
|
||||
interface SessionInfo {
|
||||
session_id: string;
|
||||
@@ -145,14 +146,18 @@ const BotSessionMonitor = forwardRef<
|
||||
}, [selectedSessionId, loadMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = messagesContainerRef.current;
|
||||
if (container) {
|
||||
const viewport = container.querySelector(
|
||||
'[data-radix-scroll-area-viewport]',
|
||||
);
|
||||
const scrollTarget = viewport || container;
|
||||
scrollTarget.scrollTop = scrollTarget.scrollHeight;
|
||||
}
|
||||
if (messages.length === 0) return;
|
||||
// Wait for DOM to render the new messages before scrolling
|
||||
requestAnimationFrame(() => {
|
||||
const container = messagesContainerRef.current;
|
||||
if (container) {
|
||||
const viewport = container.querySelector(
|
||||
'[data-radix-scroll-area-viewport]',
|
||||
);
|
||||
const scrollTarget = viewport || container;
|
||||
scrollTarget.scrollTop = scrollTarget.scrollHeight;
|
||||
}
|
||||
});
|
||||
}, [messages]);
|
||||
|
||||
const parseMessageChain = (content: string): MessageChainComponent[] => {
|
||||
@@ -391,7 +396,6 @@ const BotSessionMonitor = forwardRef<
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500 inline-block" />
|
||||
</span>
|
||||
)}
|
||||
<span className="truncate">{session.pipeline_name}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -447,12 +451,6 @@ const BotSessionMonitor = forwardRef<
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{selectedSession?.pipeline_name && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span>{selectedSession.pipeline_name}</span>
|
||||
</>
|
||||
)}
|
||||
{selectedSession?.is_active && (
|
||||
<>
|
||||
<span>·</span>
|
||||
@@ -483,6 +481,9 @@ const BotSessionMonitor = forwardRef<
|
||||
) : (
|
||||
messages.map((msg) => {
|
||||
const isUser = isUserMessage(msg);
|
||||
const isDiscarded =
|
||||
msg.status === 'discarded' ||
|
||||
msg.pipeline_id === PIPELINE_DISCARD;
|
||||
return (
|
||||
<div
|
||||
key={msg.id}
|
||||
@@ -498,10 +499,11 @@ const BotSessionMonitor = forwardRef<
|
||||
? 'bg-primary/10 rounded-br-sm'
|
||||
: 'bg-muted rounded-bl-sm',
|
||||
msg.status === 'error' && 'ring-1 ring-red-400/50',
|
||||
isDiscarded && 'opacity-60',
|
||||
)}
|
||||
>
|
||||
{renderMessageContent(msg)}
|
||||
{/* Role label + timestamp */}
|
||||
{/* Role label + pipeline + timestamp */}
|
||||
<div
|
||||
className={cn(
|
||||
'text-[11px] mt-1.5 flex items-center gap-1.5 text-muted-foreground',
|
||||
@@ -519,11 +521,25 @@ const BotSessionMonitor = forwardRef<
|
||||
<span className="tabular-nums">
|
||||
{formatTime(msg.timestamp)}
|
||||
</span>
|
||||
{isDiscarded ? (
|
||||
<span className="inline-flex items-center gap-0.5 text-destructive">
|
||||
<Ban className="w-3 h-3" />
|
||||
{t('bots.sessionMonitor.discarded', {
|
||||
defaultValue: 'Discarded',
|
||||
})}
|
||||
</span>
|
||||
) : msg.pipeline_name ? (
|
||||
<span className="inline-flex items-center gap-0.5 opacity-70">
|
||||
<Workflow className="w-3 h-3" />
|
||||
{msg.pipeline_name}
|
||||
</span>
|
||||
) : null}
|
||||
{msg.status === 'error' && (
|
||||
<span className="text-red-500">error</span>
|
||||
)}
|
||||
{msg.runner_name && (
|
||||
<span className="opacity-70">
|
||||
<span className="inline-flex items-center gap-0.5 opacity-70">
|
||||
<Bot className="w-3 h-3" />
|
||||
{msg.runner_name}
|
||||
</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user