Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.
Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
162
apps/web/src/lib/api.ts
Normal file
162
apps/web/src/lib/api.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Client for the Entropyk REST API (Axum server in demo/).
|
||||
*
|
||||
* In development, requests go through the Next.js rewrite proxy
|
||||
* (/api/entropyk/* → http://localhost:3030/api/* by default; see next.config.mjs).
|
||||
*/
|
||||
|
||||
import type { ComponentMeta } from "./componentMeta";
|
||||
|
||||
const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_URL || "/api/entropyk";
|
||||
|
||||
export interface IterationInfo {
|
||||
iteration: number;
|
||||
residual_norm: number;
|
||||
delta_norm: number;
|
||||
alpha?: number | null;
|
||||
max_residual_index?: number | null;
|
||||
max_residual?: number;
|
||||
}
|
||||
|
||||
export interface SimulationResult {
|
||||
input: string;
|
||||
status: "converged" | "failed" | "error" | string;
|
||||
convergence?: {
|
||||
iterations?: number;
|
||||
final_residual?: number;
|
||||
tolerance?: number;
|
||||
strategy?: string;
|
||||
iteration_history?: IterationInfo[];
|
||||
converged?: boolean;
|
||||
status?: string;
|
||||
};
|
||||
iterations?: number;
|
||||
state?: Array<{
|
||||
edge?: number;
|
||||
edge_id?: number;
|
||||
pressure_bar?: number;
|
||||
pressure_pa?: number;
|
||||
enthalpy_kj_kg?: number;
|
||||
enthalpy_j_kg?: number;
|
||||
mass_flow_kg_s?: number;
|
||||
source?: string;
|
||||
target?: string;
|
||||
source_port?: string;
|
||||
target_port?: string;
|
||||
temperature_c?: number;
|
||||
saturation_temperature_c?: number;
|
||||
}>;
|
||||
performance?: {
|
||||
q_cooling_kw?: number | null;
|
||||
q_heating_kw?: number | null;
|
||||
compressor_power_kw?: number | null;
|
||||
cop?: number | null;
|
||||
cooling_capacity_w?: number | null;
|
||||
heating_capacity_w?: number | null;
|
||||
compressor_power_w?: number | null;
|
||||
pump_power_w?: number | null;
|
||||
cop_cooling?: number | null;
|
||||
cop_heating?: number | null;
|
||||
};
|
||||
components?: Array<{
|
||||
name: string;
|
||||
component_type: string;
|
||||
circuit: number;
|
||||
inlet?: { pressure_pa: number; enthalpy_j_kg: number };
|
||||
outlet?: { pressure_pa: number; enthalpy_j_kg: number };
|
||||
energy?: { heat_transfer_w: number; work_w: number };
|
||||
}>;
|
||||
error?: string | null;
|
||||
failure_diagnostics?: {
|
||||
final_residual_norm?: number;
|
||||
last_residual_norm?: number;
|
||||
dominant_residual_index?: number;
|
||||
dominant_residual_value?: number;
|
||||
} | null;
|
||||
/** Degrees-of-freedom summary after topology finalize (CLI hard gate). */
|
||||
dof?: DofSummary | null;
|
||||
}
|
||||
|
||||
/** Degrees-of-freedom summary returned by the CLI after finalize. */
|
||||
export interface DofSummary {
|
||||
n_equations: number;
|
||||
n_unknowns: number;
|
||||
balance: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface SimulateResponse {
|
||||
ok: boolean;
|
||||
result?: SimulationResult;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface MollierPoint {
|
||||
t_k: number;
|
||||
p_bar: number;
|
||||
h_liq_kj_kg: number;
|
||||
h_vap_kj_kg: number;
|
||||
}
|
||||
|
||||
export interface MollierResponse {
|
||||
ok: boolean;
|
||||
fluid: string;
|
||||
saturation: MollierPoint[];
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export async function fetchComponents(): Promise<ComponentMeta[]> {
|
||||
const res = await fetch(`${API_BASE}/components`, { cache: "no-store" });
|
||||
if (!res.ok) throw new Error(`Failed to fetch components: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function simulate(config: unknown): Promise<SimulateResponse> {
|
||||
const healthy = await checkHealth();
|
||||
if (!healthy) {
|
||||
throw new Error(
|
||||
`Entropyk API is down (${API_BASE}/health). Start ui-server: cargo run -p entropyk-demo --bin ui-server`,
|
||||
);
|
||||
}
|
||||
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(`${API_BASE}/simulate`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
} catch (e) {
|
||||
const detail = e instanceof Error ? e.message : String(e);
|
||||
throw new Error(
|
||||
`Cannot reach Entropyk API (${API_BASE}). ui-server may have crashed mid-solve (CoolProp). Restart it on :3030. ${detail}`,
|
||||
);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const body = (await res.text().catch(() => "")).trim();
|
||||
const hint =
|
||||
res.status === 500 || res.status === 502 || res.status === 504
|
||||
? " — ui-server likely crashed (CoolProp race) or was restarting. Restart: cargo run -p entropyk-demo --bin ui-server"
|
||||
: "";
|
||||
throw new Error(
|
||||
`Simulate request failed: ${res.status}${hint}${body ? `\n${body.slice(0, 400)}` : ""}`,
|
||||
);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchMollier(fluid: string): Promise<MollierResponse> {
|
||||
const res = await fetch(`${API_BASE}/mollier?fluid=${encodeURIComponent(fluid)}`, { cache: "no-store" });
|
||||
if (!res.ok) throw new Error(`Failed to fetch Mollier data: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function checkHealth(): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/health`, { cache: "no-store" });
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user