"use client"; import { useMemo } from "react"; type ProtocolDebugEvent = { id: string; at: number; payload: Record; }; type TaskBoundaryDecision = { taskType?: string; taskMode?: string; reason?: string; confidence?: number; shouldUseCanvasContext?: boolean; shouldUseHistoryImages?: boolean; shouldUseWorkflowMemory?: boolean; shouldAllowExecution?: boolean; }; type AgentModelSlot = { slot?: string; modelId?: string; displayName?: string; providerType?: string; providerKey?: string; source?: string; reason?: string; locked?: boolean; }; type ProviderHealthSnapshot = { providerKey?: string; providerType?: string; lastEventType?: string; lastError?: string; consecutiveFailures?: number; updatedAt?: number; }; function asRecord(value: unknown): Record | null { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : null; } function toTaskBoundaryDecision(value: unknown): TaskBoundaryDecision | null { const record = asRecord(value); if (!record) return null; return { taskType: typeof record.taskType === "string" ? record.taskType : undefined, taskMode: typeof record.taskMode === "string" ? record.taskMode : undefined, reason: typeof record.reason === "string" ? record.reason : undefined, confidence: typeof record.confidence === "number" && Number.isFinite(record.confidence) ? record.confidence : undefined, shouldUseCanvasContext: typeof record.shouldUseCanvasContext === "boolean" ? record.shouldUseCanvasContext : undefined, shouldUseHistoryImages: typeof record.shouldUseHistoryImages === "boolean" ? record.shouldUseHistoryImages : undefined, shouldUseWorkflowMemory: typeof record.shouldUseWorkflowMemory === "boolean" ? record.shouldUseWorkflowMemory : undefined, shouldAllowExecution: typeof record.shouldAllowExecution === "boolean" ? record.shouldAllowExecution : undefined, }; } function toAgentModelSlots(value: unknown) { const record = asRecord(value); if (!record) return [] as AgentModelSlot[]; const slots: AgentModelSlot[] = []; for (const [slotName, slotValue] of Object.entries(record)) { const slotRecord = asRecord(slotValue); if (!slotRecord) continue; slots.push({ slot: slotName, modelId: typeof slotRecord.modelId === "string" ? slotRecord.modelId : undefined, displayName: typeof slotRecord.displayName === "string" ? slotRecord.displayName : undefined, providerType: typeof slotRecord.providerType === "string" ? slotRecord.providerType : undefined, providerKey: typeof slotRecord.providerKey === "string" ? slotRecord.providerKey : undefined, source: typeof slotRecord.source === "string" ? slotRecord.source : undefined, reason: typeof slotRecord.reason === "string" ? slotRecord.reason : undefined, locked: typeof slotRecord.locked === "boolean" ? slotRecord.locked : undefined, }); } return slots; } function toProviderHealthSnapshot(value: unknown): ProviderHealthSnapshot | null { const record = asRecord(value); if (!record) return null; return { providerKey: typeof record.providerKey === "string" ? record.providerKey : undefined, providerType: typeof record.providerType === "string" ? record.providerType : undefined, lastEventType: typeof record.lastEventType === "string" ? record.lastEventType : undefined, lastError: typeof record.lastError === "string" ? record.lastError : undefined, consecutiveFailures: typeof record.consecutiveFailures === "number" ? record.consecutiveFailures : undefined, updatedAt: typeof record.updatedAt === "number" ? record.updatedAt : undefined, }; } export function ProtocolDebugPanel({ enabled, events, onClear, }: { enabled: boolean; events: ProtocolDebugEvent[]; onClear?: () => void; }) { if (!enabled) return null; const groupedEvents = useMemo(() => { const groups = new Map(); for (const event of events) { const key = String(event.payload.phase || event.payload.type || "unknown"); const list = groups.get(key) || []; list.push(event); groups.set(key, list); } return Array.from(groups.entries()).map(([phase, items]) => ({ phase, items, })); }, [events]); const latestTaskBoundary = useMemo(() => { for (let index = events.length - 1; index >= 0; index -= 1) { const payload = events[index]?.payload; const boundary = toTaskBoundaryDecision(payload?.taskBoundary); if (boundary?.taskType) return boundary; } return null; }, [events]); const latestModelSlotSummary = useMemo(() => { for (let index = events.length - 1; index >= 0; index -= 1) { const payload = events[index]?.payload; const slots = toAgentModelSlots(payload?.modelSlots); if (slots.length > 0) { return { slots, providerHealth: toProviderHealthSnapshot(payload?.providerHealth), }; } } return null; }, [events]); return (
Protocol Debug
总事件 {events.length} 阶段 {groupedEvents.length}
{(latestTaskBoundary || latestModelSlotSummary) && (
{latestTaskBoundary && (
当前任务边界
type: {latestTaskBoundary.taskType || "unknown"} mode: {latestTaskBoundary.taskMode || "unknown"} {typeof latestTaskBoundary.confidence === "number" && ( confidence: {latestTaskBoundary.confidence} )}
{latestTaskBoundary.reason || "无原因说明"}
canvas {latestTaskBoundary.shouldUseCanvasContext ? "on" : "off"} history {latestTaskBoundary.shouldUseHistoryImages ? "on" : "off"} workflow {latestTaskBoundary.shouldUseWorkflowMemory ? "on" : "off"} execution {latestTaskBoundary.shouldAllowExecution ? "yes" : "no"}
)} {latestModelSlotSummary && (
当前模型槽位
{latestModelSlotSummary.slots.map((slot) => (
{slot.slot} {slot.displayName || slot.modelId || "unknown"} {slot.locked && ( locked )}
provider: {slot.providerType || "unknown"} · source: {slot.source || "unknown"}
{slot.reason && (
reason: {slot.reason}
)}
))} {latestModelSlotSummary.providerHealth && (
provider health: {latestModelSlotSummary.providerHealth.lastEventType || "unknown"} {typeof latestModelSlotSummary.providerHealth.consecutiveFailures === "number" && ` · failures ${latestModelSlotSummary.providerHealth.consecutiveFailures}`} {latestModelSlotSummary.providerHealth.providerType && ` · ${latestModelSlotSummary.providerHealth.providerType}`} {latestModelSlotSummary.providerHealth.lastError && (
{latestModelSlotSummary.providerHealth.lastError}
)}
)}
)}
)} {events.length === 0 ? (
暂无协议调试事件
) : ( groupedEvents.map((group) => (
{group.phase} {" · "} {group.items.length} 条
{group.items.map((event) => (
{new Date(event.at).toLocaleTimeString()}
                        {JSON.stringify(event.payload, null, 2)}
                      
))}
)) )}
); }