'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; import { CheckCircle2, Circle, ArrowRight, Loader2 } from 'lucide-react'; interface DiscoveredTool { name: string; method: string; description: string; paramCount: number; enabled: boolean; } interface AnalysisStreamProps { /** The analysis ID or spec input to stream */ analysisInput: { type: 'url' | 'raw'; value: string }; /** Called when analysis completes with selected tools */ onComplete: (tools: DiscoveredTool[]) => void; /** Called if user clicks Continue */ onContinue: (tools: DiscoveredTool[]) => void; } const METHOD_COLORS: Record = { GET: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30', POST: 'bg-blue-500/20 text-blue-400 border-blue-500/30', PUT: 'bg-amber-500/20 text-amber-400 border-amber-500/30', PATCH: 'bg-orange-500/20 text-orange-400 border-orange-500/30', DELETE: 'bg-red-500/20 text-red-400 border-red-500/30', }; export function AnalysisStream({ analysisInput, onComplete, onContinue, }: AnalysisStreamProps) { const [tools, setTools] = useState([]); const [progress, setProgress] = useState(0); const [step, setStep] = useState('Initializing...'); const [done, setDone] = useState(false); const [error, setError] = useState(null); const eventSourceRef = useRef(null); const containerRef = useRef(null); useEffect(() => { const params = new URLSearchParams(); params.set('type', analysisInput.type); params.set('value', analysisInput.value); const es = new EventSource(`/api/analyze?${params.toString()}`); eventSourceRef.current = es; es.addEventListener('progress', (e) => { try { const data = JSON.parse(e.data); setProgress(data.percent || 0); setStep(data.step || ''); } catch {} }); es.addEventListener('tool_found', (e) => { try { const data = JSON.parse(e.data); const tool: DiscoveredTool = { name: data.name || 'Unknown', method: data.method || 'GET', description: data.description || '', paramCount: data.paramCount || Object.keys(data.inputSchema?.properties || {}).length || 0, enabled: true, }; setTools((prev) => [...prev, tool]); } catch {} }); es.addEventListener('complete', (e) => { try { const data = JSON.parse(e.data); setProgress(100); setStep('Analysis complete'); setDone(true); // If complete event includes all tools, sync them if (data.tools && Array.isArray(data.tools)) { const allTools: DiscoveredTool[] = data.tools.map((t: any) => ({ name: t.name || 'Unknown', method: t.method || 'GET', description: t.description || '', paramCount: t.paramCount || Object.keys(t.inputSchema?.properties || {}).length || 0, enabled: true, })); setTools(allTools); onComplete(allTools); } } catch {} es.close(); }); es.addEventListener('error', (e) => { try { const data = JSON.parse((e as any).data || '{}'); setError(data.message || 'Analysis failed'); } catch { setError('Connection lost. Analysis may have failed.'); } es.close(); }); es.onerror = () => { if (!done) { setError('Connection lost'); es.close(); } }; return () => { es.close(); }; }, [analysisInput, done, onComplete]); // Auto-scroll as tools appear useEffect(() => { if (containerRef.current) { containerRef.current.scrollTop = containerRef.current.scrollHeight; } }, [tools]); const toggleTool = useCallback((index: number) => { setTools((prev) => prev.map((t, i) => (i === index ? { ...t, enabled: !t.enabled } : t)), ); }, []); const enabledCount = tools.filter((t) => t.enabled).length; return (
{/* Progress bar */}
{step} {Math.round(progress)}%
{/* Error state */} {error && (
{error}
)} {/* Tool list */}
{tools.map((tool, i) => (
{/* Checkbox */} {/* Method badge */} {tool.method} {/* Tool info */}
{tool.name}

{tool.description}

{/* Param count */} {tool.paramCount}p
))} {/* Loading indicator while streaming */} {!done && !error && (
Discovering tools...
)}
{/* Summary + Continue */} {done && tools.length > 0 && (
{enabledCount} of {tools.length} tools selected
)}
); }