import React, { useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; import { api } from '../api'; const STATES_DISPLAY: Record = { INIT: { label: 'Initializing Workspace', icon: 'πŸ”§' }, SPEC: { label: 'Architectural Planning', icon: 'πŸ“' }, SPEC_VALIDATE: { label: 'Specification Validation', icon: 'πŸ”' }, HIERARCHY_EXPAND: { label: 'Hierarchy Expansion', icon: '🌲' }, FEASIBILITY_CHECK: { label: 'Feasibility Check', icon: 'βš–οΈ' }, CDC_ANALYZE: { label: 'CDC Analysis', icon: 'πŸ”€' }, VERIFICATION_PLAN: { label: 'Verification Planning', icon: 'πŸ“‹' }, RTL_GEN: { label: 'RTL Generation', icon: 'πŸ’»' }, RTL_FIX: { label: 'RTL Syntax Fixing', icon: 'πŸ”¨' }, VERIFICATION: { label: 'Verification & Testbench', icon: 'πŸ§ͺ' }, FORMAL_VERIFY: { label: 'Formal Verification', icon: 'πŸ“Š' }, COVERAGE_CHECK: { label: 'Coverage Analysis', icon: 'πŸ“ˆ' }, REGRESSION: { label: 'Regression Testing', icon: 'πŸ”' }, SDC_GEN: { label: 'SDC Generation', icon: 'πŸ•’' }, FLOORPLAN: { label: 'Floorplanning', icon: 'πŸ—ΊοΈ' }, HARDENING: { label: 'GDSII Hardening', icon: 'πŸ—οΈ' }, CONVERGENCE_REVIEW: { label: 'Convergence Review', icon: '🎯' }, ECO_PATCH: { label: 'ECO Patch', icon: '🩹' }, SIGNOFF: { label: 'DRC/LVS Signoff', icon: 'βœ…' }, SUCCESS: { label: 'Build Complete', icon: 'πŸŽ‰' }, }; interface BuildEvent { type: string; state: string; message: string; step: number; total_steps: number; timestamp: number; } interface StageSchemaItem { state: string; label: string; icon: string; } interface Props { designName: string; jobId: string; events: BuildEvent[]; jobStatus: string; stageSchema?: StageSchemaItem[]; } export const BuildMonitor: React.FC = ({ designName, jobId, events, jobStatus, stageSchema }) => { const logsRef = useRef(null); const [cancelling, setCancelling] = React.useState(false); const mergedDisplay: Record = React.useMemo(() => { if (!stageSchema || stageSchema.length === 0) return STATES_DISPLAY; const map: Record = {}; for (const stage of stageSchema) { map[stage.state] = { label: stage.label, icon: stage.icon }; } if (!map.SUCCESS) map.SUCCESS = STATES_DISPLAY.SUCCESS; return map; }, [stageSchema]); const stateOrder = React.useMemo(() => Object.keys(mergedDisplay), [mergedDisplay]); const reachedStates = new Set(events.map(e => e.state)); const currentState = events.length > 0 ? events[events.length - 1].state : 'INIT'; const currentStateIndex = stateOrder.indexOf(currentState); const furthestReachedIndex = Math.max( 0, ...events .map(e => stateOrder.indexOf(e.state)) .filter(idx => idx >= 0) ); const currentStep = Math.max(1, (currentStateIndex >= 0 ? currentStateIndex : furthestReachedIndex) + 1); const logEvents = events.filter(e => e.message && e.message.trim().length > 0); const isDone = ['done', 'failed', 'cancelled', 'cancelling'].includes(jobStatus); const selfHeal = { stageExceptions: events.filter(e => /stage .* exception/i.test(e.message || '')).length, formalRegens: events.filter(e => /regenerating sva/i.test(e.message || '')).length, coverageRestores: events.filter(e => /restoring best testbench/i.test(e.message || '')).length, coverageRejects: events.filter(e => /regressed coverage/i.test(e.message || '')).length, deterministicFallbacks: events.filter(e => /deterministic tb fallback/i.test(e.message || '')).length, }; useEffect(() => { if (logsRef.current) { logsRef.current.scrollTop = logsRef.current.scrollHeight; } }, [events]); const handleCancel = async () => { if (!jobId || cancelling) return; setCancelling(true); try { await api.post(`/build/cancel/${jobId}`); } catch { setCancelling(false); } }; return (
{/* Header */}
{designName}
{!isDone ? ( <> Step {currentStep} / {stateOrder.length} ) : ( {jobStatus === 'done' ? 'βœ“ Complete' : jobStatus === 'cancelled' ? '⊘ Cancelled' : 'βœ• Failed'} )}
{/* Body */}
{/* Checkpoint Timeline */}
Build Pipeline
{stateOrder.map((stateKey, idx) => { const info = mergedDisplay[stateKey] || { label: stateKey, icon: 'β€’' }; const isPassed = stateOrder.indexOf(stateKey) < stateOrder.indexOf(currentState); const isCurrent = currentState === stateKey && !isDone; const isSuccess = stateKey === 'SUCCESS' && jobStatus === 'done'; return (
{isPassed || isSuccess ? ( βœ“ ) : isCurrent ? ( ⟳ ) : ( )} {idx < stateOrder.length - 1 && (
)}
{info.icon} {info.label}
); })}
{/* Live Terminal */}
Live Log
Self-Healing Stage guards: {selfHeal.stageExceptions} Formal regens: {selfHeal.formalRegens} TB regressions blocked: {selfHeal.coverageRejects} Best TB restores: {selfHeal.coverageRestores} TB fallbacks: {selfHeal.deterministicFallbacks}
{logEvents.length === 0 ? ( Waiting for AgentIC to start… ) : ( logEvents.map((evt, i) => ( {new Date(evt.timestamp * 1000).toLocaleTimeString()} [{evt.state}] {evt.message} )) )}
{/* Progress */}
{Math.round((currentStep / stateOrder.length) * 100)}% complete
{/* Footer */} {!isDone && (
AgentIC is building your chip autonomously. This takes 10–30 min. You'll get a browser notification when done β€” you can leave this tab open.
)}
); };