Files
Entropyk/apps/web/src/lib/api.ts
sepehr 3358b74342 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>
2026-07-17 22:46:46 +02:00

163 lines
4.5 KiB
TypeScript

/**
* 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;
}
}