import { useState, useEffect, useRef } from 'react'; import { Terminal, Activity, RefreshCw, CheckCircle2, Circle, XCircle, Clock, Loader2 } from 'lucide-react'; import apiClient from '../../api/client'; import type { WorkflowDetail, WorkflowStep } from '../../types'; interface RightPanelProps { selectedWorkflow: string | null; } function stepStatusIcon(status: string) { switch (status) { case 'completed': return ; case 'running': return ; case 'failed': return ; default: return ; } } export function RightPanel({ selectedWorkflow }: RightPanelProps) { const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const [logs, setLogs] = useState([]); const [sseConnected, setSseConnected] = useState(false); const eventSourceRef = useRef(null); const fetchDetail = async (traceId: string) => { setLoading(true); setLogs([]); try { const response = await apiClient.get(`/api/v1/workflow/${traceId}`); setDetail(response.data); } catch { setDetail(null); } finally { setLoading(false); } }; useEffect(() => { if (!selectedWorkflow) { setDetail(null); setLogs([]); return; } fetchDetail(selectedWorkflow); const protocol = window.location.protocol; const host = window.location.host; const apiBase = import.meta.env.VITE_API_BASE_URL || `${protocol}//${host}`; const es = new EventSource(`${apiBase}/api/v1/workflow/sse/${selectedWorkflow}`); eventSourceRef.current = es; es.onopen = () => { setSseConnected(true); }; es.onmessage = (event) => { setLogs(prev => [...prev, event.data]); }; es.onerror = () => { setSseConnected(false); }; const interval = setInterval(() => { fetchDetail(selectedWorkflow); }, 3000); return () => { es.close(); eventSourceRef.current = null; clearInterval(interval); }; }, [selectedWorkflow]); const isActive = detail?.status === 'llm_working' || detail?.status === 'tool_working'; if (!selectedWorkflow) { return ( No Workflow Selected Select a workflow from the left panel to view its details. ); } return ( Workflow Detail {sseConnected ? 'Live' : '--'} selectedWorkflow && fetchDetail(selectedWorkflow)} className="p-1 text-slate-400 hover:text-blue-600 rounded transition-colors" title="Refresh" > {loading && !detail ? ( Loading... ) : !detail ? ( Failed to load workflow details ) : ( <> {/* Header */} {detail.workflow_title || 'Workflow'} ID: {detail.event_id} {detail.command && ( Command: {detail.command} )} {detail.status} Step {detail.current_step}/{detail.steps.length} {/* Steps */} {detail.steps.length > 0 && ( Steps {detail.steps.map((step: WorkflowStep) => ( {stepStatusIcon(step.status)} {step.step} {step.name} {step.node} ))} )} {detail.steps.length === 0 && ( Workflow is being generated... )} {/* SSE Logs */} {logs.length > 0 && ( Live Logs {logs.map((msg, idx) => ( {msg} ))} )} {logs.length === 0 && sseConnected && isActive && ( Waiting for live events... )} > )} ); }
Select a workflow from the left panel to view its details.
ID: {detail.event_id}
Command: {detail.command}
Workflow is being generated...
{msg}