diff --git a/web/src/app/home/agents/components/AgentDebugPanel.tsx b/web/src/app/home/agents/components/AgentDebugPanel.tsx index bb28e5633..f3525ce61 100644 --- a/web/src/app/home/agents/components/AgentDebugPanel.tsx +++ b/web/src/app/home/agents/components/AgentDebugPanel.tsx @@ -1,4 +1,11 @@ -import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { @@ -40,6 +47,13 @@ import { } from '@/app/home/components/event-patterns/event-pattern-groups'; import EventSelectOptionContent from '@/app/home/components/event-patterns/EventSelectOptionContent'; import AgentExecutionTrace from './AgentExecutionTrace'; +import AgentEventDataEditor from './AgentEventDataEditor'; +import { + createDebugEventData, + debugEventInputText, + invalidDebugEventField, + parseDebugEventData, +} from './debug-event-data'; import { executionSteps, type DebugExecutionEvent } from './debug-execution'; interface AgentDebugPanelProps { @@ -63,104 +77,6 @@ interface DebugEntry { finished?: boolean; } -const EVENT_PRESET_DATA: Record< - string, - { text: string; data: Record } -> = { - 'message.received': { - text: '', - data: {}, - }, - 'group.member_joined': { - text: 'A new member joined the group.', - data: { - group_id: 'debug-group', - member_id: 'debug-user', - member_name: 'Debug User', - }, - }, - 'group.member_left': { - text: 'A member left the group.', - data: { - group_id: 'debug-group', - member_id: 'debug-user', - member_name: 'Debug User', - }, - }, - 'friend.request_received': { - text: 'A user sent a friend request.', - data: { - requester_id: 'debug-user', - requester_name: 'Debug User', - request_id: 'debug-friend-request', - message: 'Hello', - }, - }, - 'feedback.received': { - text: 'The user submitted feedback.', - data: { - rating: 5, - content: 'Debug feedback', - }, - }, - 'friend.added': { - text: 'A friend was added.', - data: { user_id: 'debug-user', user_name: 'Debug User' }, - }, - 'group.member_banned': { - text: 'A member was banned.', - data: { - group_id: 'debug-group', - member_id: 'debug-user', - member_name: 'Debug User', - }, - }, - 'bot.invited_to_group': { - text: 'The bot was invited to a group.', - data: { - group_id: 'debug-group', - request_id: 'debug-group-request', - requester_id: 'debug-user', - }, - }, - 'bot.muted': { - text: 'The bot was muted.', - data: { group_id: 'debug-group', duration: 60 }, - }, - 'bot.unmuted': { - text: 'The bot was unmuted.', - data: { group_id: 'debug-group' }, - }, - 'bot.removed_from_group': { - text: 'The bot was removed from the group.', - data: { group_id: 'debug-group' }, - }, - 'message.edited': { - text: 'A message was edited.', - data: { - group_id: 'debug-group', - message_id: 'debug-message', - text: 'Edited message', - }, - }, - 'message.deleted': { - text: 'A message was deleted.', - data: { group_id: 'debug-group', message_id: 'debug-message' }, - }, - 'message.reaction': { - text: 'A reaction was added.', - data: { - group_id: 'debug-group', - message_id: 'debug-message', - reaction: '👍', - }, - }, - 'platform.specific': { - text: 'A platform-specific event occurred.', - data: { event_name: 'debug-platform-event' }, - }, -}; - function createDebugSessionId(agentId: string) { const nonce = globalThis.crypto?.randomUUID?.() ?? String(Date.now()); return `webui:${agentId}:${nonce}`; @@ -186,8 +102,22 @@ export default function AgentDebugPanel({ ); const [preset, setPreset] = useState('message.received'); const [customEventType, setCustomEventType] = useState('custom.event'); - const [inputText, setInputText] = useState(''); - const [eventDataText, setEventDataText] = useState('{}'); + const newEventData = useCallback( + (type: string) => + JSON.stringify( + createDebugEventData(type, { + user: t('agents.debugData.sampleUser'), + message: t('agents.debugData.sampleMessage'), + feedback: t('agents.debugData.sampleFeedback'), + }), + null, + 2, + ), + [t], + ); + const [eventDataText, setEventDataText] = useState(() => + newEventData('message.received'), + ); const [mockOptionsText, setMockOptionsText] = useState('{}'); const [running, setRunning] = useState(false); const [entries, setEntries] = useState([]); @@ -205,11 +135,7 @@ export default function AgentDebugPanel({ }, [entries]); const eventType = preset === 'custom' ? customEventType.trim() : preset; - const isMessageEvent = eventType.startsWith('message.'); - const supportedLabel = useMemo( - () => supportedEventPatterns.join(', '), - [supportedEventPatterns], - ); + const eventDataValid = parseDebugEventData(eventDataText) !== null; const availableEvents = useMemo(() => { const concretePatterns = supportedEventPatterns.filter( (pattern) => pattern !== '*' && !pattern.endsWith('.*'), @@ -230,6 +156,14 @@ export default function AgentDebugPanel({ (pattern) => pattern === '*' || pattern.endsWith('.*'), ); + const selectPreset = useCallback( + (value: string) => { + setPreset(value); + setEventDataText(newEventData(value)); + }, + [newEventData], + ); + useEffect(() => { if ( availableEvents.includes(preset) || @@ -238,24 +172,13 @@ export default function AgentDebugPanel({ return; } selectPreset(availableEvents[0] ?? 'custom'); - }, [availableEvents, preset, supportsCustomEvent]); - - function selectPreset(value: string) { - setPreset(value); - const nextPreset = EVENT_PRESET_DATA[value] ?? { text: '', data: {} }; - setInputText(nextPreset.text); - setEventDataText(JSON.stringify(nextPreset.data, null, 2)); - } + }, [availableEvents, preset, supportsCustomEvent, selectPreset]); async function runDebugEvent() { if (!eventType) { toast.error(t('agents.debugEventTypeRequired')); return; } - if (isMessageEvent && !inputText.trim()) { - toast.error(t('agents.debugInputRequired')); - return; - } if ( !supportedEventPatterns.some((pattern) => matchesEventPattern(pattern, eventType), @@ -265,18 +188,22 @@ export default function AgentDebugPanel({ return; } - let eventData: Record; - let mockOptions: Record; - try { - const parsed = JSON.parse(eventDataText || '{}'); - if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') { - throw new Error('payload must be an object'); - } - eventData = parsed as Record; - } catch { + const eventData = parseDebugEventData(eventDataText); + if (!eventData) { toast.error(t('agents.debugInvalidPayload')); return; } + const invalidField = invalidDebugEventField(eventType, eventData); + if (invalidField) { + toast.error( + t('agents.debugData.invalidField', { + field: t(`agents.debugData.${invalidField.label}`), + }), + ); + return; + } + const inputText = debugEventInputText(eventType, eventData); + let mockOptions: Record; try { const parsed = JSON.parse(mockOptionsText || '{}'); if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') @@ -367,8 +294,6 @@ export default function AgentDebugPanel({ }, ], ); - if (isMessageEvent) - setInputText((current) => (current === inputText ? '' : current)); } catch (error) { if (controller.signal.aborted) { if (controller.signal.reason === 'user') { @@ -541,131 +466,115 @@ export default function AgentDebugPanel({ )} -
- {supportedEventPatterns.length === 0 ? ( - - - {t('agents.debugNoEventsTitle')} - - {t('agents.debugNoEventsDescription')} - - - ) : ( - <> -
- - + + + + + {eventGroups.map((group) => ( + + + {eventGroupLabel(group.namespace, t)} + + {group.patterns.map((event) => ( + + + + ))} + + ))} + {supportsCustomEvent && ( + + + {t('agents.debugCustomEvent')} + - ))} - - ))} - {supportsCustomEvent && ( - - {t('agents.debugCustomEvent')} - - - - - )} - - -
- - {preset === 'custom' && ( -
- - setCustomEventType(event.target.value)} - placeholder="custom.event" - /> + + )} + +
- )} -
- -