import { useState, useEffect, useRef } from 'react'; import { fetchEventSource } from '@microsoft/fetch-event-source'; import { ActivityFeed } from '../components/ActivityFeed'; import { StageProgressBar } from '../components/StageProgressBar'; import { ApprovalCard } from '../components/ApprovalCard'; import { api, API_BASE } from '../api'; import '../hitl.css'; const PIPELINE_STAGES = [ 'INIT', 'SPEC', 'SPEC_VALIDATE', 'HIERARCHY_EXPAND', 'FEASIBILITY_CHECK', 'CDC_ANALYZE', 'VERIFICATION_PLAN', 'RTL_GEN', 'RTL_FIX', 'VERIFICATION', 'FORMAL_VERIFY', 'COVERAGE_CHECK', 'REGRESSION', 'SDC_GEN', 'FLOORPLAN', 'HARDENING', 'CONVERGENCE_REVIEW', 'ECO_PATCH', 'SIGNOFF', ]; const TOTAL = PIPELINE_STAGES.length; // Contextual messages for the bottom status bar — what the agent is doing right now const STAGE_ENCOURAGEMENTS: Record = { INIT: 'Setting up your build environment…', SPEC: 'Translating your description into a chip specification…', SPEC_VALIDATE: 'Validating spec — classifying design, checking completeness, generating assertions…', HIERARCHY_EXPAND: 'Expanding complex submodules into nested specifications…', FEASIBILITY_CHECK: 'Evaluating Sky130 physical design feasibility…', CDC_ANALYZE: 'Analyzing clock domain crossings…', VERIFICATION_PLAN: 'Building verification plan & SVA properties…', RTL_GEN: 'Writing Verilog — your chip is taking shape…', RTL_FIX: 'Fixing any RTL issues automatically…', VERIFICATION: 'Running simulation — making sure your logic is correct…', FORMAL_VERIFY: 'Proving your chip is correct with formal methods…', COVERAGE_CHECK: 'Measuring how thoroughly the tests cover your design…', REGRESSION: 'Running the full regression suite…', SDC_GEN: 'Generating timing constraints for physical design…', FLOORPLAN: 'Planning the physical layout of your chip…', HARDENING: 'Running place-and-route — turning RTL into real silicon…', CONVERGENCE_REVIEW:'Checking that timing is met across all corners…', ECO_PATCH: 'Applying final tweaks for clean sign-off…', SIGNOFF: 'Almost there — running final LVS/DRC checks…', }; // Milestone stages that deserve a special celebration toast const MILESTONE_TOASTS: Record = { RTL_GEN: { title: 'RTL Complete', msg: 'Your chip can now run instructions. Verilog is ready.' }, VERIFICATION: { title: 'Verification Passed', msg: 'All simulation tests passed. Design is logically correct.' }, HARDENING: { title: 'Silicon Layout Done', msg: 'Place-and-route complete. Your chip has a physical form.' }, SIGNOFF: { title: 'Chip Signed Off', msg: 'All checks passed. Ready for tape-out.' }, }; // Human-readable stage names const STAGE_LABELS: Record = { INIT: 'Initialization', SPEC: 'Specification', SPEC_VALIDATE: 'Spec Validation', HIERARCHY_EXPAND: 'Hierarchy Expansion', FEASIBILITY_CHECK: 'Feasibility Check', CDC_ANALYZE: 'CDC Analysis', VERIFICATION_PLAN: 'Verification Plan', RTL_GEN: 'RTL Generation', RTL_FIX: 'RTL Fix', VERIFICATION: 'Verification', FORMAL_VERIFY: 'Formal Verification', COVERAGE_CHECK: 'Coverage Check', REGRESSION: 'Regression', SDC_GEN: 'SDC Generation', FLOORPLAN: 'Floorplan', HARDENING: 'Hardening', CONVERGENCE_REVIEW: 'Convergence', ECO_PATCH: 'ECO Patch', SIGNOFF: 'Signoff', FAIL: 'Failed', }; // Mandatory stages (cannot be skipped) const MANDATORY_STAGES = new Set([ 'INIT', 'SPEC', 'SPEC_VALIDATE', 'HIERARCHY_EXPAND', 'FEASIBILITY_CHECK', 'CDC_ANALYZE', 'VERIFICATION_PLAN', 'RTL_GEN', 'RTL_FIX', 'VERIFICATION', 'HARDENING', 'SIGNOFF', ]); // Build mode presets type BuildMode = 'quick' | 'verified' | 'full'; const BUILD_MODE_SKIPS: Record = { quick: ['FORMAL_VERIFY', 'COVERAGE_CHECK', 'REGRESSION', 'SDC_GEN', 'FLOORPLAN', 'HARDENING', 'CONVERGENCE_REVIEW', 'ECO_PATCH', 'SIGNOFF'], verified: ['REGRESSION', 'ECO_PATCH', 'CONVERGENCE_REVIEW'], full: [], }; interface BuildEvent { type: string; state: string; message: string; step: number; total_steps: number; timestamp: number | string; status?: string; // agent_thought fields agent_name?: string; thought_type?: string; content?: string; // stage_complete fields stage_name?: string; summary?: string; artifacts?: Array<{ name: string; path: string; description: string }>; decisions?: string[]; warnings?: string[]; next_stage_name?: string; next_stage_preview?: string; } interface StageCompleteData { stage_name: string; summary: string; artifacts: Array<{ name: string; path: string; description: string }>; decisions: string[]; warnings: string[]; next_stage_name: string; next_stage_preview: string; } function slugify(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9\s_]/g, '') .trim() .split(/\s+/) .slice(0, 4) .join('_') .substring(0, 48); } type Phase = 'prompt' | 'building' | 'done'; export const HumanInLoopBuild = () => { const [phase, setPhase] = useState('prompt'); const [prompt, setPrompt] = useState(''); const [designName, setDesignName] = useState(''); const [jobId, setJobId] = useState(''); const [events, setEvents] = useState([]); const [jobStatus, setJobStatus] = useState('queued'); const [result, setResult] = useState(null); const [error, setError] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const abortCtrlRef = useRef(null); // Build options const [skipOpenlane, setSkipOpenlane] = useState(false); const [skipCoverage, setSkipCoverage] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false); const [maxRetries, setMaxRetries] = useState(5); const [showThinking, setShowThinking] = useState(false); const [minCoverage, setMinCoverage] = useState(80.0); const [strictGates, setStrictGates] = useState(false); const [pdkProfile, setPdkProfile] = useState("sky130"); // Build mode & stage skipping (Improvement 2) const [buildMode, setBuildMode] = useState('verified'); const [skipStages, setSkipStages] = useState>(new Set(BUILD_MODE_SKIPS.verified)); const [showStageToggles, setShowStageToggles] = useState(false); // Stage tracking const [currentStage, setCurrentStage] = useState('INIT'); const [completedStages, setCompletedStages] = useState>(new Set()); const [failedStage, setFailedStage] = useState(); const [waitingForApproval, setWaitingForApproval] = useState(false); const [approvalData, setApprovalData] = useState(null); // Fetch partial artifacts on build failure const [partialArtifacts, setPartialArtifacts] = useState>([]); const [showFullLog, setShowFullLog] = useState(false); // Thinking indicator (Improvement 1) const [thinkingData, setThinkingData] = useState<{ agent_name: string; message: string } | null>(null); // Stall detection — shown when LLM is silent for 5+ minutes const [stallWarning, setStallWarning] = useState(null); // Milestone toast: shown briefly when a key stage completes const [milestoneToast, setMilestoneToast] = useState<{ title: string; msg: string } | null>(null); const milestoneTimerRef = useRef | null>(null); useEffect(() => { if (prompt.length > 8) { setDesignName(slugify(prompt)); } }, [prompt]); const handleLaunch = async () => { if (!prompt.trim()) return; setError(''); // Quick RTL mode implies skip_openlane const effectiveSkipOpenlane = buildMode === 'quick' || skipOpenlane; const effectiveSkipCoverage = skipCoverage || skipStages.has('COVERAGE_CHECK'); try { const res = await api.post(`/build`, { design_name: designName || slugify(prompt), description: prompt, skip_openlane: effectiveSkipOpenlane, skip_coverage: effectiveSkipCoverage, max_retries: maxRetries, show_thinking: showThinking, min_coverage: minCoverage, strict_gates: strictGates, pdk_profile: pdkProfile, human_in_loop: true, skip_stages: Array.from(skipStages), }); const { job_id, design_name: dn } = res.data; setJobId(job_id); if (dn) setDesignName(dn); setPhase('building'); startStreaming(job_id); } catch (e: any) { if (e?.code === 'ERR_NETWORK' || !e?.response) { setError('Backend is offline. Start the server with: uvicorn server.api:app --port 7860'); } else { setError(e?.response?.data?.detail || 'Build failed. Check the backend logs.'); } } }; const startStreaming = (jid: string) => { if (abortCtrlRef.current) abortCtrlRef.current.abort(); const ctrl = new AbortController(); abortCtrlRef.current = ctrl; setEvents([]); fetchEventSource(`${API_BASE}/build/stream/${jid}`, { method: 'GET', headers: { 'ngrok-skip-browser-warning': 'true', 'Accept': 'text/event-stream', }, signal: ctrl.signal, onmessage(evt) { try { const data: BuildEvent = JSON.parse(evt.data); if (data.type === 'ping') return; if (data.type === 'stream_end') { ctrl.abort(); fetchResult(jid, data.status as any); return; } // Handle stall warning: LLM silent for 5+ minutes if (data.type === 'stall_warning') { setStallWarning(data.message || '⚠️ No activity for 5 minutes — the LLM may be stuck. You can cancel and retry.'); return; } // Any real event clears the stall warning and thinking indicator if (data.type === 'log' || data.type === 'checkpoint' || data.type === 'transition') { setStallWarning(null); } // Handle agent_thinking: show pulsing indicator if (data.type === 'agent_thinking') { setThinkingData({ agent_name: data.agent_name || '', message: data.message || '' }); return; // Don't add to event feed } // Any real event clears the thinking indicator if (data.type !== 'agent_thinking') { setThinkingData(null); } // Handle stage_complete: show approval card if (data.type === 'stage_complete') { setApprovalData({ stage_name: data.stage_name || data.state || '', summary: data.summary || '', artifacts: data.artifacts || [], decisions: data.decisions || [], warnings: data.warnings || [], next_stage_name: data.next_stage_name || '', next_stage_preview: data.next_stage_preview || '', }); setWaitingForApproval(true); setCurrentStage(data.stage_name || data.state || ''); } // Track stage progression if (data.type === 'transition' || data.state) { const newState = data.state; if (newState && newState !== 'UNKNOWN') { setCurrentStage(prev => { if (prev !== newState && prev !== 'INIT') { setCompletedStages(cs => { const next = new Set(cs); next.add(prev); return next; }); } return newState; }); } if (newState === 'FAIL') { setFailedStage(newState); } } // Add event to feed setEvents(prev => { const last = prev[prev.length - 1]; if (last && last.message === data.message && last.type === data.type) { return prev; } return [...prev, data]; }); if (data.type === 'error') { setJobStatus('failed'); } else if (data.type !== 'stage_complete') { setJobStatus('running'); } } catch { /* ignore parse errors */ } }, onerror(err) { ctrl.abort(); throw err; } }); }; const fetchResult = async (jid: string, status: string) => { setJobStatus(status === 'done' ? 'done' : 'failed'); try { const res = await api.get(`/build/result/${jid}`); setResult(res.data.result); } catch { /* */ } // On failure, fetch partial artifacts from disk if (status !== 'done' && designName) { try { const artRes = await api.get(`/build/artifacts/${designName}`); setPartialArtifacts(artRes.data.artifacts || []); } catch { /* */ } } setPhase('done'); }; const handleApprove = async () => { if (!approvalData || isSubmitting) return; setIsSubmitting(true); try { await api.post(`/approve`, { stage: approvalData.stage_name, design_name: designName, }); // Add user action to feed setEvents(prev => [...prev, { type: 'user_action', state: approvalData.stage_name, message: `👤 Approved: ${approvalData.stage_name}`, step: 0, total_steps: 15, timestamp: new Date().toISOString(), agent_name: 'User', thought_type: 'user_action', content: `Approved: ${approvalData.stage_name}`, }]); setApprovalData(null); setWaitingForApproval(false); setCompletedStages(prev => { const next = new Set(prev); next.add(approvalData.stage_name); return next; }); // Fire milestone toast if this is a key stage const toast = MILESTONE_TOASTS[approvalData.stage_name]; if (toast) { if (milestoneTimerRef.current) clearTimeout(milestoneTimerRef.current); setMilestoneToast(toast); milestoneTimerRef.current = setTimeout(() => setMilestoneToast(null), 5000); } } catch (e: any) { setError(e?.response?.data?.detail || 'Failed to approve'); } setIsSubmitting(false); }; const handleReject = async (feedback: string) => { if (!approvalData || isSubmitting) return; setIsSubmitting(true); try { await api.post(`/reject`, { stage: approvalData.stage_name, design_name: designName, feedback: feedback || undefined, }); const feedbackMsg = feedback ? ` — Feedback: ${feedback}` : ''; setEvents(prev => [...prev, { type: 'user_action', state: approvalData.stage_name, message: `👤 Rejected: ${approvalData.stage_name}${feedbackMsg}`, step: 0, total_steps: 15, timestamp: new Date().toISOString(), agent_name: 'User', thought_type: 'user_action', content: `Rejected: ${approvalData.stage_name}${feedbackMsg}`, }, { type: 'agent_thought', state: approvalData.stage_name, message: 'Retrying stage with your feedback...', step: 0, total_steps: 15, timestamp: new Date().toISOString(), agent_name: 'Orchestrator', thought_type: 'observation', content: 'Retrying stage with your feedback...', }]); setApprovalData(null); setWaitingForApproval(false); } catch (e: any) { setError(e?.response?.data?.detail || 'Failed to reject'); } setIsSubmitting(false); }; const handleReset = () => { abortCtrlRef.current?.abort(); setPhase('prompt'); setEvents([]); setResult(null); setJobId(''); setJobStatus('queued'); setError(''); setPrompt(''); setCurrentStage('INIT'); setCompletedStages(new Set()); setFailedStage(undefined); setWaitingForApproval(false); setApprovalData(null); setThinkingData(null); setStallWarning(null); setBuildMode('verified'); setSkipStages(new Set(BUILD_MODE_SKIPS.verified)); setSkipCoverage(false); setShowStageToggles(false); setPartialArtifacts([]); setShowFullLog(false); }; const handleCancel = async () => { if (abortCtrlRef.current) abortCtrlRef.current.abort(); if (jobId) { try { await api.post(`/build/cancel/${jobId}`); } catch { /* */ } } handleReset(); }; // Computed values for bottom bar const stepNum = Math.max(1, PIPELINE_STAGES.indexOf(currentStage) + 1); const pct = Math.round((completedStages.size / TOTAL) * 100); const remaining = Math.max(0, TOTAL - completedStages.size); const estMinutes = remaining * 2; // Request notification permission useEffect(() => { if ('Notification' in window && Notification.permission === 'default') { Notification.requestPermission(); } return () => abortCtrlRef.current?.abort(); }, []); return (
{/* ── PROMPT PHASE ── */} {phase === 'prompt' && (

Design Your Chip

Human-in-the-Loop — review and approve every stage of the autonomous pipeline.

{[ '8-bit RISC CPU with Harvard architecture', 'AXI4 DMA engine with 4 channels', 'UART controller at 115200 baud', ].map(ex => ( ))}