feat: Add WebSocket live updates and convergence visualization

Phase 4 - Live Updates:
- Create useOptimizationStream hook for real-time trial updates
- Replace polling with WebSocket subscription in SpecRenderer
- Auto-report errors to ErrorPanel via panel store
- Add progress tracking (FEA count, NN count, best trial)

Phase 5 - Convergence Visualization:
- Add ConvergenceSparkline component for mini line charts
- Add ProgressRing component for circular progress indicator
- Update ObjectiveNode to show convergence trend sparkline
- Add history field to ObjectiveNodeData schema
- Add live progress indicator centered on canvas when running

Bug fixes:
- Fix TypeScript errors in FloatingIntrospectionPanel (type casts)
- Fix ValidationPanel using wrong store method (selectNode vs setSelectedNodeId)
- Fix NodeConfigPanelV2 unused state variable
- Fix specValidator source.extractor_id path
- Clean up unused imports across components
This commit is contained in:
2026-01-21 21:48:35 -05:00
parent c224b16ac3
commit 2cb8dccc3a
10 changed files with 764 additions and 167 deletions

View File

@@ -43,7 +43,6 @@ export function NodeConfigPanelV2({ onClose }: NodeConfigPanelV2Props) {
const { updateNode, removeNode, clearSelection } = useSpecStore();
const [showFileBrowser, setShowFileBrowser] = useState(false);
const [showIntrospection, setShowIntrospection] = useState(false);
const [isUpdating, setIsUpdating] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -249,16 +248,7 @@ export function NodeConfigPanelV2({ onClose }: NodeConfigPanelV2Props) {
fileTypes={['.sim', '.prt', '.fem', '.afem']}
/>
{/* Introspection Panel */}
{showIntrospection && spec.model.sim?.path && (
<div className="fixed top-20 right-96 z-40">
<IntrospectionPanel
filePath={spec.model.sim.path}
studyId={useSpecStore.getState().studyId || undefined}
onClose={() => setShowIntrospection(false)}
/>
</div>
)}
{/* Introspection is now handled by FloatingIntrospectionPanel via usePanelStore */}
</div>
);
}
@@ -280,6 +270,7 @@ function ModelNodeConfig({ spec }: SpecConfigProps) {
filePath: spec.model.sim?.path || '',
studyId: useSpecStore.getState().studyId || undefined,
});
openPanel('introspection');
};
return (