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>
163 lines
4.5 KiB
TypeScript
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;
|
|
}
|
|
}
|