Spaces:
Configuration error
Configuration error
| import React, { useState, useEffect } from 'react'; | |
| import { Mail, Search, Sparkles, Database, ShieldCheck, Terminal, AlertCircle } from 'lucide-react'; | |
| import { API_BASE_URL } from '@/app/config'; | |
| interface AgentLogItem { | |
| step: number; | |
| thought: string; | |
| action: string; | |
| result: string; | |
| timestamp: string; | |
| } | |
| interface AgentRunTimelineProps { | |
| runId: string; | |
| } | |
| export default function AgentRunTimeline({ runId }: AgentRunTimelineProps) { | |
| const [logs, setLogs] = useState<AgentLogItem[]>([]); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [error, setError] = useState(false); | |
| useEffect(() => { | |
| const fetchLogs = async () => { | |
| setIsLoading(true); | |
| setError(false); | |
| const token = localStorage.getItem('token') || ''; | |
| try { | |
| const response = await fetch(`${API_BASE_URL}/api/agent/runs/${runId}/logs`, { | |
| headers: { | |
| 'Authorization': `Bearer ${token}`, | |
| }, | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch run logs'); | |
| } | |
| const data = await response.json(); | |
| setLogs(data); | |
| } catch (err) { | |
| console.error(err); | |
| setError(true); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| if (runId) { | |
| fetchLogs(); | |
| // Poll every 3 seconds while active | |
| const interval = setInterval(fetchLogs, 3000); | |
| return () => clearInterval(interval); | |
| } | |
| }, [runId]); | |
| const getActionIcon = (action: string) => { | |
| switch (action.toUpperCase()) { | |
| case 'READ_EMAIL': | |
| return <Mail size={14} className="text-cyan-400" />; | |
| case 'RETRIEVE_CONTEXT': | |
| return <Database size={14} className="text-indigo-400" />; | |
| case 'LLM_TRIAGE': | |
| return <Sparkles size={14} className="text-purple-400" />; | |
| case 'COMMIT_CHANGES': | |
| return <ShieldCheck size={14} className="text-emerald-400" />; | |
| default: | |
| return <Terminal size={14} className="text-slate-400" />; | |
| } | |
| }; | |
| const getActionColor = (action: string) => { | |
| switch (action.toUpperCase()) { | |
| case 'READ_EMAIL': | |
| return 'border-cyan-500/30 bg-cyan-950/20'; | |
| case 'RETRIEVE_CONTEXT': | |
| return 'border-indigo-500/30 bg-indigo-950/20'; | |
| case 'LLM_TRIAGE': | |
| return 'border-purple-500/30 bg-purple-950/20'; | |
| case 'COMMIT_CHANGES': | |
| return 'border-emerald-500/30 bg-emerald-950/20'; | |
| default: | |
| return 'border-slate-800 bg-slate-950/40'; | |
| } | |
| }; | |
| if (isLoading && logs.length === 0) { | |
| return ( | |
| <div className="flex items-center justify-center p-8 text-slate-500"> | |
| <div className="flex items-center gap-2"> | |
| <div className="h-4 w-4 animate-spin rounded-full border border-purple-500 border-t-transparent" /> | |
| <span className="text-xs">Loading audit trail...</span> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| if (error && logs.length === 0) { | |
| return ( | |
| <div className="flex items-center justify-center p-6 text-red-400 gap-2 border border-red-950/30 bg-red-950/10 rounded-xl"> | |
| <AlertCircle size={16} /> | |
| <span className="text-xs">Could not fetch logs for this agent execution.</span> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="flex flex-col gap-4"> | |
| <h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2 flex items-center gap-1.5"> | |
| <Terminal size={14} /> | |
| <span>Agent Thinking Audit Trail</span> | |
| </h4> | |
| {logs.length === 0 ? ( | |
| <p className="text-xs text-slate-500 italic">No agent log steps found for this execution run.</p> | |
| ) : ( | |
| <div className="relative border-l border-slate-800 ml-3.5 pl-6 flex flex-col gap-6.5"> | |
| {logs.map((log) => ( | |
| <div key={log.step} className="relative group"> | |
| {/* Vertical line connector bullet */} | |
| <div className={`absolute -left-[35px] top-0 h-7 w-7 rounded-full border flex items-center justify-center transition-transform group-hover:scale-110 ${getActionColor(log.action)}`}> | |
| {getActionIcon(log.action)} | |
| </div> | |
| {/* Node Contents */} | |
| <div> | |
| <div className="flex items-center gap-2.5 mb-1"> | |
| <span className="text-xs font-semibold text-slate-200"> | |
| Step {log.step}: {log.thought} | |
| </span> | |
| <span className="text-[10px] text-slate-500 font-mono"> | |
| {new Date(log.timestamp).toLocaleTimeString()} | |
| </span> | |
| </div> | |
| <div className="bg-slate-950/35 border border-slate-900/60 p-3 rounded-xl text-[11px] text-slate-400 font-mono leading-relaxed max-w-full overflow-x-auto"> | |
| <span className="text-slate-500 block mb-1">Action: {log.action}</span> | |
| <span className="text-slate-300 block">{log.result}</span> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |