"use client"; import { useRef, useState } from "react"; import { Play, Loader2, FileDown, FileUp, Eraser, Boxes, Grid3x3, RotateCw, Layers, FlipHorizontal2, FlipVertical2, } from "lucide-react"; import { useDiagramStore } from "@/store/diagramStore"; import { buildScenarioConfig, validateConfig } from "@/lib/configBuilder"; import { computeDofLedger } from "@/lib/dofLedger"; import { simulate } from "@/lib/api"; import type { EntropykNodeData } from "@/lib/configBuilder"; import type { Node } from "@xyflow/react"; import MultiRunPanel from "@/components/panels/MultiRunPanel"; const FLUIDS = ["R410A", "R134a", "R290", "R744", "R32", "R1234yf", "Water", "Air"]; const BACKENDS = ["CoolProp", "Test"]; const SOLVERS = [ { value: "newton", label: "Newton" }, { value: "picard", label: "Picard" }, ]; const EXAMPLES = [ { file: "hx_air_water_4port.json", label: "HX air–water" }, { file: "chiller_flooded_4port_watercooled.json", label: "Chiller flooded" }, { file: "chiller_watercooled_r410a.json", label: "Chiller R410A" }, { file: "chiller_aircooled_r134a.json", label: "Chiller air-cooled" }, { file: "bphx_evaporator_condenser.json", label: "BPHX cycle" }, { file: "heatpump_r410a_reversing_valve.json", label: "Heat pump 4-way" }, { file: "chiller_r134a_exv_orifice.json", label: "EXV orifice" }, { file: "chiller_r410a_full_physics.json", label: "R410A full physics" }, { file: "capillary_tube_r134a.json", label: "Capillary smoke" }, ]; export default function Toolbar() { const { nodes, edges, fluid, fluidBackend, solverStrategy, maxIterations, tolerance, snapToGrid, selectedNodeId, setFluid, setFluidBackend, setSolverStrategy, setLastConfig, toggleSnapToGrid, rotateNode, flipNodeH, flipNodeV, setResult, setSimulating, simulating, loadFromConfig, clear, } = useDiagramStore(); const [issues, setIssues] = useState([]); const [multiOpen, setMultiOpen] = useState(false); const [exampleFile, setExampleFile] = useState(EXAMPLES[0].file); const fileInputRef = useRef(null); const onSimulate = async () => { const problems = validateConfig(nodes, edges); const ledger = computeDofLedger(nodes as Node[], edges); // Hard block only when over-constrained (no free lunch). Under-constrained // diagrams are common while editing — surface as a soft issue after simulate // via the status bar; the CLI still hard-fails on non-square systems. if (ledger.balance === "over-constrained") { problems.push( `DoF over-constrained: ${ledger.nEquations} equations > ${ledger.nUnknowns} unknowns. ` + `Remove a FIX (quality control, extra outlet closure) or FREE an actuator.`, ); } setIssues(problems); if (problems.length > 0) return; const config = buildScenarioConfig(nodes, edges, { fluid, fluidBackend, solverStrategy, maxIterations, tolerance, }); setSimulating(true); setLastConfig(config); setResult(null, null); try { const resp = await simulate(config); if (resp.ok && resp.result) { setResult(resp.result, null); if (resp.result.dof && resp.result.dof.n_equations !== resp.result.dof.n_unknowns) { setIssues([ `Server DoF: ${resp.result.dof.n_equations} eqs vs ${resp.result.dof.n_unknowns} unk (${resp.result.dof.balance})`, ]); } } else setResult(null, resp.error || "Simulation failed"); } catch (e) { setResult(null, e instanceof Error ? e.message : String(e)); } finally { setSimulating(false); } }; const onLoadExample = async () => { try { const res = await fetch(`/examples/${exampleFile}`); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); loadFromConfig(await res.json()); setIssues([]); } catch (e) { setIssues([`Could not load example: ${e instanceof Error ? e.message : e}`]); } }; const onImportJsonFile = async (file: File | undefined) => { if (!file) return; try { const text = await file.text(); loadFromConfig(JSON.parse(text)); setIssues([]); } catch (e) { setIssues([`Could not import JSON: ${e instanceof Error ? e.message : e}`]); } finally { if (fileInputRef.current) fileInputRef.current.value = ""; } }; return (
ENTROPYK
{/* Orientation — icon group, works on the selected part */}
{issues.length > 0 && (
⚠ {issues.length} · {issues[0]}
)} void onImportJsonFile(event.target.files?.[0])} /> {multiOpen && setMultiOpen(false)} />}
); } function BarField({ label, children }: { label: string; children: React.ReactNode }) { return ( ); }