@barivia/barmesh-mcp 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -65,7 +65,8 @@ API key; otherwise the analysis calls return HTTP 403. Contact Barivia to enable
|
|
|
65
65
|
|
|
66
66
|
- **Combined overview (`combined.png`):** barsom-style **grid of all component planes**
|
|
67
67
|
on the trained SOM — the primary headline artifact. A separate
|
|
68
|
-
`overview_distances.png` holds the KL/EMD
|
|
68
|
+
`overview_distances.png` holds the KL/EMD diagnostic mosaic (all four distance panels).
|
|
69
|
+
`plot_vol_all_meshes.png` shows volume fingerprints for every mesh in mesh_order.
|
|
69
70
|
- **Learning curve:** every job produces `learning_curve.png` (QE by epoch) for training
|
|
70
71
|
quality inspection; listed in the results explorer dropdown.
|
|
71
72
|
- **PDFs on demand:** publication vector PDFs are NOT generated by default. Render them
|
package/dist/shared.js
CHANGED
|
@@ -22,7 +22,7 @@ export const FETCH_TIMEOUT_MS = parseInt(process.env.BARIVIA_FETCH_TIMEOUT_MS ??
|
|
|
22
22
|
export const MAX_RETRIES = 2;
|
|
23
23
|
export const RETRYABLE_STATUS = new Set([502, 503, 504]);
|
|
24
24
|
/** Single source of truth for the proxy version. Keep in sync with package.json on bump. */
|
|
25
|
-
export const CLIENT_VERSION = "0.5.
|
|
25
|
+
export const CLIENT_VERSION = "0.5.4";
|
|
26
26
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
27
27
|
/** Large per-cell CSV uploads may exceed the default fetch timeout. */
|
|
28
28
|
export const UPLOAD_DATASET_TIMEOUT_MS = 180_000;
|
|
@@ -389,7 +389,7 @@ export function getCaptionForImage(filename) {
|
|
|
389
389
|
if (base === "combined")
|
|
390
390
|
return "All feature component planes on the trained SOM in one grid — primary comparison artifact (barsom-style combined view).";
|
|
391
391
|
if (base === "overview_distances")
|
|
392
|
-
return "Diagnostic mosaic:
|
|
392
|
+
return "Diagnostic mosaic: KL and EMD vs reference and stepwise (all distance panels).";
|
|
393
393
|
if (base === "learning_curve")
|
|
394
394
|
return "Quantization error by epoch: ordering phase (steel blue) then convergence (coral).";
|
|
395
395
|
if (base === "KL_ref")
|
|
@@ -400,6 +400,8 @@ export function getCaptionForImage(filename) {
|
|
|
400
400
|
return "Wasserstein-1 (EMD) distance from each mesh to the reference. A monotone decrease toward the finest mesh indicates field-level convergence.";
|
|
401
401
|
if (base === "EMD_stepwise")
|
|
402
402
|
return "Stepwise EMD between consecutive meshes. Small, plateauing values on the finest pairs indicate the fingerprint has stopped changing.";
|
|
403
|
+
if (base === "plot_vol_all_meshes")
|
|
404
|
+
return "Volume-weighted SOM fingerprints P_vol for every mesh in mesh_order (shared color scale).";
|
|
403
405
|
if (base === "plot_vol_coarse_fine")
|
|
404
406
|
return "Volume-weighted SOM fingerprints P_vol for the coarsest and reference meshes (shared color scale).";
|
|
405
407
|
if (base.startsWith("plot_vol_steps") || base.startsWith("plot_vol_seven_steps"))
|
|
@@ -428,6 +430,12 @@ export async function loadViewHtml(viewName) {
|
|
|
428
430
|
}
|
|
429
431
|
return null;
|
|
430
432
|
}
|
|
433
|
+
export const MAX_INLINE_BYTES = parseInt(process.env.BARIVIA_MAX_INLINE_BYTES ?? "8000000", 10);
|
|
434
|
+
let _inlineAttachBytesUsed = 0;
|
|
435
|
+
/** Reset inline image byte budget (call at the start of each MCP tool response). */
|
|
436
|
+
export function resetInlineAttachBudget() {
|
|
437
|
+
_inlineAttachBytesUsed = 0;
|
|
438
|
+
}
|
|
431
439
|
export async function tryAttachImage(content, jobId, filename) {
|
|
432
440
|
if (filename.endsWith(".pdf") || filename.endsWith(".svg")) {
|
|
433
441
|
content.push({
|
|
@@ -441,6 +449,14 @@ export async function tryAttachImage(content, jobId, filename) {
|
|
|
441
449
|
content.push({ type: "text", text: `${filename}: ${caption}` });
|
|
442
450
|
try {
|
|
443
451
|
const { data: imgBuf } = await apiRawCall(`/v1/results/${jobId}/image/${filename}`);
|
|
452
|
+
if (_inlineAttachBytesUsed + imgBuf.length > MAX_INLINE_BYTES) {
|
|
453
|
+
content.push({
|
|
454
|
+
type: "text",
|
|
455
|
+
text: `(${filename} omitted — inline image budget exceeded; use barmesh_results(action=image, job_id="${jobId}", filename="${filename}") or action=download)`,
|
|
456
|
+
});
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
_inlineAttachBytesUsed += imgBuf.length;
|
|
444
460
|
content.push({
|
|
445
461
|
type: "image",
|
|
446
462
|
data: imgBuf.toString("base64"),
|
|
@@ -11,6 +11,7 @@ const FIGURE_SORT_RANK = {
|
|
|
11
11
|
EMD_ref: 12,
|
|
12
12
|
EMD_stepwise: 13,
|
|
13
13
|
learning_curve: 14,
|
|
14
|
+
plot_vol_all_meshes: 19,
|
|
14
15
|
plot_vol_coarse_fine: 20,
|
|
15
16
|
};
|
|
16
17
|
function sortRank(key) {
|
|
@@ -52,6 +53,8 @@ function labelForFigure(filename) {
|
|
|
52
53
|
return "Wasserstein (EMD) vs reference";
|
|
53
54
|
if (base === "EMD_stepwise")
|
|
54
55
|
return "Wasserstein (EMD) stepwise";
|
|
56
|
+
if (base === "plot_vol_all_meshes")
|
|
57
|
+
return "Volume fingerprint: all meshes";
|
|
55
58
|
if (base === "plot_vol_coarse_fine")
|
|
56
59
|
return "Volume fingerprint: coarse vs reference";
|
|
57
60
|
if (base.startsWith("plot_vol_"))
|
package/dist/tools/results.js
CHANGED
|
@@ -2,8 +2,46 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { registerAuditedTool } from "../audit.js";
|
|
5
|
-
import { apiCall, apiRawCall, getWorkspaceRootAsync, sandboxPath, textResult, tryAttachImage, pollUntilComplete, POLL_STAGE_MAX_MS, } from "../shared.js";
|
|
6
|
-
const MESH_DEFAULT_FIGURES = ["combined", "overview_distances", "
|
|
5
|
+
import { apiCall, apiRawCall, getWorkspaceRootAsync, sandboxPath, textResult, tryAttachImage, resetInlineAttachBudget, pollUntilComplete, POLL_STAGE_MAX_MS, } from "../shared.js";
|
|
6
|
+
const MESH_DEFAULT_FIGURES = ["combined", "overview_distances", "plot_vol_all_meshes", "plot_vol_steps", "learning_curve"];
|
|
7
|
+
function formatMeshResultsSummary(jobId, data, summary) {
|
|
8
|
+
const label = data.label != null && data.label !== "" ? String(data.label) : null;
|
|
9
|
+
const header = label ? `Mesh convergence results for ${label} (job_id: ${jobId})` : `Mesh convergence results for job_id: ${jobId}`;
|
|
10
|
+
const grid = summary.grid ?? [];
|
|
11
|
+
const meshes = summary.meshes ?? [];
|
|
12
|
+
const stepwise = summary.stepwise ?? [];
|
|
13
|
+
const fmt = (v) => (typeof v === "number" && Number.isFinite(v) ? v.toFixed(4) : String(v ?? "N/A"));
|
|
14
|
+
const meshLines = meshes.map((m) => ` ${String(m.mesh ?? "?")} (n=${m.n_cells ?? "?"}): SKL→ref=${fmt(m.skl_to_ref)} EMD→ref=${fmt(m.emd_to_ref)}`);
|
|
15
|
+
const stepLines = stepwise.map((s) => ` ${String(s.pair ?? "?")}: EMD=${fmt(s.emd)} KL↓=${fmt(s.kl_coarsen)} KL↑=${fmt(s.kl_refine)}`);
|
|
16
|
+
const convergence = summary.convergence;
|
|
17
|
+
const emd = convergence?.emd;
|
|
18
|
+
const skl = convergence?.skl;
|
|
19
|
+
const reading = [];
|
|
20
|
+
if (emd) {
|
|
21
|
+
const plateau = emd.plateau === true ? "plateauing" : "not yet plateauing";
|
|
22
|
+
const mono = emd.monotonic_decrease === true ? "monotonic decrease toward reference" : "non-monotonic vs reference";
|
|
23
|
+
reading.push(`EMD vs ${String(summary.reference_mesh ?? "reference")}: ${mono}, ${plateau}.`);
|
|
24
|
+
}
|
|
25
|
+
if (skl) {
|
|
26
|
+
reading.push(`Symmetric KL vs reference: ${skl.plateau === true ? "plateauing" : "not yet plateauing"}.`);
|
|
27
|
+
}
|
|
28
|
+
const ord = summary.ordering_errors;
|
|
29
|
+
const conv = summary.convergence_errors;
|
|
30
|
+
const curveNote = (ord?.length ?? 0) > 0 || (conv?.length ?? 0) > 0
|
|
31
|
+
? `Training curves: ${ord?.length ?? 0} ordering + ${conv?.length ?? 0} convergence batch samples (≤1000 each in API/MCP responses).`
|
|
32
|
+
: "";
|
|
33
|
+
return [
|
|
34
|
+
header,
|
|
35
|
+
`Preset: ${String(summary.preset ?? "generic")} | Grid: ${grid.length >= 2 ? `${grid[0]}×${grid[1]}` : "N/A"} | Reference: ${String(summary.reference_mesh ?? "N/A")}`,
|
|
36
|
+
`QE: ${fmt(summary.quantization_error)} | TE: ${fmt(summary.topographic_error)} | EMD method: ${String(summary.emd_method ?? "exact")}`,
|
|
37
|
+
meshes.length > 0 ? `\nDistances vs reference:\n${meshLines.join("\n")}` : "",
|
|
38
|
+
stepLines.length > 0 ? `\nStepwise:\n${stepLines.join("\n")}` : "",
|
|
39
|
+
reading.length > 0 ? `\nConvergence reading:\n${reading.map((l) => ` ${l}`).join("\n")}` : "",
|
|
40
|
+
curveNote,
|
|
41
|
+
"\nAdvisory: SOM distances complement, not replace, numerical uncertainty analysis (use barmesh_richardson for classical GCI).",
|
|
42
|
+
"For every figure: barmesh_results_explorer(job_id) or barmesh_results(action=download). Use figures=\"none\" to skip inline images.",
|
|
43
|
+
].filter(Boolean).join("\n");
|
|
44
|
+
}
|
|
7
45
|
const TEXT_ARTIFACTS = [
|
|
8
46
|
"summary.json",
|
|
9
47
|
"distances.csv",
|
|
@@ -23,7 +61,7 @@ export function registerResultsTool(server) {
|
|
|
23
61
|
| download | Save figures and metrics to a local folder (headless / agent path). |
|
|
24
62
|
|
|
25
63
|
BEST FOR: After barmesh_jobs(action=status) shows completed (and finalize finished if defer_figures was used).
|
|
26
|
-
FIGURES: Default
|
|
64
|
+
FIGURES: Default bundle is combined.png, overview_distances.png (all KL/EMD panels), plot_vol_all_meshes.png, plot_vol_steps.png, and learning_curve.png — not redundant per-feature PNGs. action=get inlines the headline set; pass figures="all" for every artifact listed in summary.files, figures="none" for metrics only (recommended for agents).
|
|
27
65
|
PDFs ON DEMAND: vector PDFs are not produced by default. Use action=render (format=pdf) once, then action=image or action=download.
|
|
28
66
|
If GET /v1/results returns 404 shortly after status=completed, cfd_finalize may still be rendering — poll barmesh_jobs(status) until finalize_job_id completes.
|
|
29
67
|
NOT FOR: Submitting jobs.`, {
|
|
@@ -96,9 +134,18 @@ NOT FOR: Submitting jobs.`, {
|
|
|
96
134
|
const files = summary.files ?? [];
|
|
97
135
|
const isImage = (f) => /\.(png|svg|pdf)$/i.test(f);
|
|
98
136
|
let toDownload;
|
|
99
|
-
if (figures === "all"
|
|
137
|
+
if (figures === "all") {
|
|
100
138
|
toDownload = include_json ? files : files.filter(isImage);
|
|
101
139
|
}
|
|
140
|
+
else if (figures === undefined) {
|
|
141
|
+
toDownload = MESH_DEFAULT_FIGURES.flatMap((b) => files.filter((f) => f.replace(/\.[^.]+$/, "") === b || f.startsWith(`${b}.`)));
|
|
142
|
+
if (include_json) {
|
|
143
|
+
for (const t of TEXT_ARTIFACTS) {
|
|
144
|
+
if (files.includes(t) && !toDownload.includes(t))
|
|
145
|
+
toDownload.push(t);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
102
149
|
else if (Array.isArray(figures)) {
|
|
103
150
|
toDownload = figures.flatMap((key) => {
|
|
104
151
|
if (/\.(png|pdf|svg|csv|txt|json)$/i.test(key))
|
|
@@ -139,9 +186,10 @@ NOT FOR: Submitting jobs.`, {
|
|
|
139
186
|
? `Saved ${saved.length} file(s) to ${savedDir}: ${saved.join(", ")}`
|
|
140
187
|
: `No files saved. Check job_id and that the job (and cfd_finalize, if any) is completed.`);
|
|
141
188
|
}
|
|
142
|
-
|
|
189
|
+
resetInlineAttachBudget();
|
|
190
|
+
let data;
|
|
143
191
|
try {
|
|
144
|
-
|
|
192
|
+
data = (await apiCall("GET", `/v1/results/${job_id}`));
|
|
145
193
|
}
|
|
146
194
|
catch (err) {
|
|
147
195
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -154,7 +202,8 @@ NOT FOR: Submitting jobs.`, {
|
|
|
154
202
|
}
|
|
155
203
|
throw err;
|
|
156
204
|
}
|
|
157
|
-
const
|
|
205
|
+
const summary = (data.summary ?? {});
|
|
206
|
+
const content = [{ type: "text", text: formatMeshResultsSummary(job_id, data, summary) }];
|
|
158
207
|
if (figures === "none")
|
|
159
208
|
return { content };
|
|
160
209
|
const allFiles = summary.files ?? [];
|
|
@@ -133,7 +133,7 @@ Boolean requesting whether a visible border and background is provided by the ho
|
|
|
133
133
|
- omitted: host decides border`)});m({method:c("ui/request-display-mode"),params:m({mode:Me.describe("The display mode being requested.")})});var Zf=m({mode:Me.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),Cf=T([c("model"),c("app")]).describe("Tool visibility scope - who can access the tool.");m({resourceUri:l().optional(),visibility:I(Cf).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
134
134
|
- "model": Tool visible to and callable by the agent
|
|
135
135
|
- "app": Tool callable by the app from this server only`),csp:ve().optional(),permissions:ve().optional()});m({mimeTypes:I(l()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')});m({method:c("ui/download-file"),params:m({contents:I(T([qu,Fu])).describe("Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})});m({method:c("ui/message"),params:m({role:c("user").describe('Message role, currently only "user" is supported.'),content:I(ct).describe("Message content blocks (text, image, etc.).")})});m({method:c("ui/notifications/sandbox-resource-ready"),params:m({html:l().describe("HTML content to load into the inner iframe."),sandbox:l().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:Hi.optional().describe("CSP configuration from resource metadata."),permissions:Ji.optional().describe("Sandbox permissions from resource metadata.")})});var Af=m({method:c("ui/notifications/tool-result"),params:xn.describe("Standard MCP tool execution result.")}),Ku=m({toolInfo:m({id:nt.optional().describe("JSON-RPC id of the tools/call request."),tool:Fi.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:wf.optional().describe("Current color theme preference."),styles:Pf.optional().describe("Style configuration for theming the app."),displayMode:Me.optional().describe("How the UI is currently displayed."),availableDisplayModes:I(Me).optional().describe("Display modes the host supports."),containerDimensions:T([m({height:x().describe("Fixed container height in pixels.")}),m({maxHeight:T([x(),Ze()]).optional().describe("Maximum container height in pixels.")})]).and(T([m({width:x().describe("Fixed container width in pixels.")}),m({maxWidth:T([x(),Ze()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
136
|
-
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:l().optional().describe("User's language and region preference in BCP 47 format."),timeZone:l().optional().describe("User's timezone in IANA format."),userAgent:l().optional().describe("Host application identifier."),platform:T([c("web"),c("desktop"),c("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:m({touch:Z().optional().describe("Whether the device supports touch input."),hover:Z().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:m({top:x().describe("Top safe area inset in pixels."),right:x().describe("Right safe area inset in pixels."),bottom:x().describe("Bottom safe area inset in pixels."),left:x().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Mf=m({method:c("ui/notifications/host-context-changed"),params:Ku.describe("Partial context update containing only changed fields.")});m({method:c("ui/update-model-context"),params:m({content:I(ct).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:P(l(),C().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});m({method:c("ui/initialize"),params:m({appInfo:In.describe("App identification (name and version)."),appCapabilities:Rf.describe("Features and capabilities this app provides."),protocolVersion:l().describe("Protocol version this app supports.")})});var Lf=m({protocolVersion:l().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:In.describe("Host application identification and version."),hostCapabilities:Df.describe("Features and capabilities provided by the host."),hostContext:Ku.describe("Rich context about the host environment.")}).passthrough(),qf={target:"draft-2020-12"};async function vo(t,n){let r=t["~standard"];if(r.jsonSchema)return r.jsonSchema[n](qf);if(r.vendor==="zod"){let{z:o}=await il(()=>Promise.resolve().then(()=>Nm),void 0,import.meta.url);return o.toJSONSchema(t,{io:n})}throw Error(`Schema (vendor: ${r.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function _o(t,n,r=""){let o=await t["~standard"].validate(n);if(o.issues){let e=o.issues.map(i=>{let a=i.path?.map(s=>typeof s=="object"?s.key:s).join(".");return a?`${a}: ${i.message}`:i.message}).join("; ");throw Error(r+e)}return o.value}class Vi extends bf{_appInfo;_capabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_assertInitialized(n){if(this._initializedSent)return;let r=`[ext-apps] App.${n}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(r);console.warn(`${r}. This will throw in a future release.`)}eventSchemas={toolinput:Nf,toolinputpartial:Tf,toolresult:Af,toolcancelled:Of,hostcontextchanged:Mf};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(n){if(!Vi.ONE_SHOT_EVENTS.has(n)||this._everHadListener.has(n)||(this._everHadListener.add(n),!this._initializedSent))return;let r=`[ext-apps] "${String(n)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(r);console.warn(r)}setEventHandler(n,r){r&&this._assertHandlerTiming(n),super.setEventHandler(n,r)}addEventListener(n,r){this._assertHandlerTiming(n),super.addEventListener(n,r)}onEventDispatch(n,r){n==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...r})}constructor(n,r={},o={autoResize:!0}){super(o),this._appInfo=n,this._capabilities=r,this.options=o,o.allowUnsafeEval||J({jitless:!0}),this.setRequestHandler(zn,e=>(console.log("Received ping:",e.params),{})),this.setEventHandler("hostcontextchanged",void 0)}registerCapabilities(n){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._capabilities=_f(this._capabilities,n)}registerTool(n,r,o){if(this._registeredTools[n])throw Error(`Tool ${n} is already registered`);let e=this,i=()=>{e._initializedSent&&e._capabilities.tools?.listChanged&&e.sendToolListChanged()},a=r.inputSchema!==void 0,s={title:r.title,description:r.description,inputSchema:r.inputSchema,outputSchema:r.outputSchema,annotations:r.annotations,_meta:r._meta,enabled:!0,enable(){this.enabled=!0,i()},disable(){this.enabled=!1,i()},update(p){Object.assign(this,p),i()},remove(){e._registeredTools[n]===s&&(delete e._registeredTools[n],i())},handler:async(p,g)=>{if(!s.enabled)throw Error(`Tool ${n} is disabled`);let h;if(a){let d=s.inputSchema,b=d?await _o(d,p??{},`Invalid input for tool ${n}: `):p??{};h=await o(b,g)}else h=await o(g);return s.outputSchema&&!h.isError&&(h.structuredContent=await _o(s.outputSchema,h.structuredContent,`Invalid output for tool ${n}: `)),h}};return this._registeredTools[n]=s,!this._capabilities.tools&&!this.transport&&this.registerCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),i(),s}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(n,r)=>{let o=this._registeredTools[n.name];if(!o)throw Error(`Tool ${n.name} not found`);return o.handler(n.arguments,r)},this.onlisttools=async(n,r)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([o,e])=>e.enabled).map(async([o,e])=>{let i={name:o,title:e.title,description:e.description,inputSchema:e.inputSchema?await vo(e.inputSchema,"input"):{type:"object",properties:{}}};return e.outputSchema&&(i.outputSchema=await vo(e.outputSchema,"output")),e.annotations&&(i.annotations=e.annotations),e._meta&&(i._meta=e._meta),i}))}))}async sendToolListChanged(n={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:n})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(n){this.setEventHandler("toolinput",n)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(n){this.setEventHandler("toolinputpartial",n)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(n){this.setEventHandler("toolresult",n)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(n){this.setEventHandler("toolcancelled",n)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(n){this.setEventHandler("hostcontextchanged",n)}_onteardown;get onteardown(){return this._onteardown}set onteardown(n){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,n),this._onteardown=n,this.replaceRequestHandler(Ef,(r,o)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(r.params,o)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(n){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,n),this._oncalltool=n,this.replaceRequestHandler(Ju,(r,o)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(r.params,o)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(n){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,n),this._onlisttools=n,this.replaceRequestHandler(Hu,(r,o)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(r.params,o)})}assertCapabilityForMethod(n){switch(n){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${n})`);break}}assertRequestHandlerCapability(n){switch(n){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${n})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${n} registered`)}}assertNotificationCapability(n){}assertTaskCapability(n){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(n){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(n,r){if(this._assertInitialized("callServerTool"),typeof n=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${n}"). Did you mean: callServerTool({ name: "${n}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:n},xn,{onprogress:()=>{},resetTimeoutOnProgress:!0,...r})}async readServerResource(n,r){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:n},Lu,r)}async listServerResources(n,r){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:n},Mu,r)}async createSamplingMessage(n,r){this._assertInitialized("createSamplingMessage");let o=n.tools?Wu:Bu;return await this.request({method:"sampling/createMessage",params:n},o,r)}sendMessage(n,r){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:n},jf,r)}sendLog(n){return this.notification({method:"notifications/message",params:n})}updateModelContext(n,r){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:n},ji,r)}openLink(n,r){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:n},zf,r)}sendOpenLink=this.openLink;downloadFile(n,r){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:n},xf,r)}requestTeardown(n={}){return this.notification({method:"ui/notifications/request-teardown",params:n})}requestDisplayMode(n,r){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:n},Zf,r)}sendSizeChanged(n){return this.notification({method:"ui/notifications/size-changed",params:n})}setupSizeChangedNotifications(){let n=!1,r=0,o=0,e=()=>{n||(n=!0,requestAnimationFrame(()=>{n=!1;let a=document.documentElement,s=a.style.height;a.style.height="max-content";let p=Math.ceil(a.getBoundingClientRect().height);a.style.height=s;let g=Math.ceil(window.innerWidth);(g!==r||p!==o)&&(r=g,o=p,this.sendSizeChanged({width:g,height:p}))}))};e();let i=new ResizeObserver(e);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(n=new kf(window.parent,window.parent),r){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1,await super.connect(n);try{let o=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:$f}},Lf,r);if(o===void 0)throw Error(`Server sent invalid initialize result: ${o}`);this._hostCapabilities=o.hostCapabilities,this._hostInfo=o.hostInfo,this._hostContext=o.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(o){throw this.close(),o}}}let se=null,ce=null,re=null,Un=null;const bo=new Map,ft=document.getElementById("loading"),Ff=document.getElementById("app"),Gu=document.getElementById("title"),Qu=document.getElementById("subtitle"),Hf=document.getElementById("metrics"),Jf=document.getElementById("highlights"),Le=document.getElementById("figure-select"),Vf=document.getElementById("figure-title"),Bf=document.getElementById("figure-file"),Wf=document.getElementById("figure-caption"),ne=document.getElementById("main-image"),Kf=document.getElementById("figure-empty"),$o=document.getElementById("open-standalone"),Yu=document.getElementById("download-current"),yo={combined:0,overview_distances:1,KL_ref:10,KL_stepwise:11,EMD_ref:12,EMD_stepwise:13,learning_curve:14,plot_vol_coarse_fine:20};function ko(t){return t in yo?yo[t]:t.startsWith("plot_vol")?25:t.startsWith("plot_")?30:50}function Gf(t){return[...t].sort((n,r)=>ko(n.key)-ko(r.key)||n.label.localeCompare(r.label))}function Qf(t){if(t.find(r=>r.key==="combined"))return"combined";const n=t.find(r=>r.url||/\.(png|svg)$/i.test(r.filename));return n?n.key:t.find(r=>r.key==="KL_ref")?"KL_ref":t[0]?.key}function Yf(t){const n=t.replace(/\.(png|pdf|svg)$/i,"");return n==="combined"?"Component planes (overview)":n==="overview_distances"?"Distances & volume overview":n==="learning_curve"?"Learning curve (QE by epoch)":n==="KL_ref"?"KL divergence vs reference":n==="KL_stepwise"?"KL divergence stepwise":n==="EMD_ref"?"Wasserstein (EMD) vs reference":n==="EMD_stepwise"?"Wasserstein (EMD) stepwise":n==="plot_vol_coarse_fine"?"Volume: coarse vs reference":n.startsWith("plot_vol_")?"Volume: stepwise":n.startsWith("plot_")?`Component: ${n.replace(/^plot_/,"").replace(/_/g," ")}`:n.replace(/_/g," ")}function Xf(t,n){const r=new Map;for(const o of t){const e=o.match(/^(.*)\.(png|pdf|svg)$/i);if(!e)continue;const i=e[1],a=r.get(i)??new Set;a.add(e[2].toLowerCase()),r.set(i,a)}return Gf([...r.entries()].map(([o,e])=>{const i=[...e],a=e.has("png")?`${o}.png`:e.has("svg")?`${o}.svg`:`${o}.${i[0]}`,s=e.has("svg")?`${o}.svg`:e.has("pdf")?`${o}.pdf`:`${o}.png`,p=/\.(png|svg)$/i.test(a),g=i.filter(h=>h==="pdf"||h==="svg");return{key:o,label:Yf(a),filename:a,downloadFilename:s,formats:i,kind:o==="combined"?"summary":o.startsWith("plot_")&&!o.startsWith("plot_vol_")?"component":"diagnostic",caption:g.length>0?`High-quality ${g.join("/").toUpperCase()} available to download.`:void 0,url:p?`/api/results/${n}/image/${a}`:void 0,downloadUrl:`/api/results/${n}/image/${s}`}}))}function eh(t,n){const r=t.summary??{},o=(r.files??[]).filter(h=>/\.(png|pdf|svg)$/i.test(h)),e=r.grid??[],i=r.meshes??[],a=r.quantization_error!=null?Number(r.quantization_error).toFixed(4):"N/A",s=r.topographic_error!=null?Number(r.topographic_error).toFixed(4):"N/A",p=[{label:"Grid",value:e.length>=2?`${e[0]}×${e[1]}`:"N/A"},{label:"Preset",value:String(r.preset??"generic")},{label:"Reference",value:String(r.reference_mesh??"N/A")},{label:"Meshes",value:String(i.length||"N/A")},{label:"QE",value:a},{label:"TE",value:s}],g=Xf(o,n);return{type:"barmesh-results-explorer",jobId:n,title:"Mesh Convergence Results",subtitle:String(t.label??""),metrics:p,highlights:["SOM distances complement, not replace, numerical uncertainty analysis."],availableFigures:g,defaultFigureKey:Qf(g)}}function th(t){Hf.innerHTML=t.map(n=>`<div class="metric"><span class="metric-label">${n.label}</span><span class="metric-value">${n.value}</span></div>`).join("")}function nh(t){Jf.innerHTML=t.length>0?t.map(n=>`<div class="highlight">${n}</div>`).join(""):'<div class="highlight">No convergence reading available.</div>'}const rh={summary:"Overview",diagnostic:"Distances & diagnostics",component:"Component planes",artifact:"Other"};function Xu(t){const n=new Map;for(const o of t){const e=o.kind??"diagnostic",i=n.get(e)??[];i.push(o),n.set(e,i)}const r=["summary","diagnostic","component","artifact"];Le.innerHTML="";for(const o of r){const e=n.get(o);if(!e||e.length===0)continue;const i=document.createElement("optgroup");i.label=rh[o]??o;for(const a of e){const s=document.createElement("option");s.value=a.key,s.textContent=`${a.label} (${a.filename})`,a.key===ce?.key&&(s.selected=!0),i.appendChild(s)}Le.appendChild(i)}}Le.addEventListener("change",()=>{const t=se?.availableFigures.find(n=>n.key===Le.value);t&&el(t)});async function ih(t,n){const r=`${t}/${n}`,o=bo.get(r);if(o)return o;if(!re)return null;try{const i=(await re.callServerTool({name:"_barmesh_fetch_figure",arguments:{job_id:t,filename:n}})).content?.find(a=>a.type==="image");if(i){const a=`data:${i.mimeType};base64,${i.data}`;return bo.set(r,a),a}}catch{}return null}function oh(t){!re||!se||re.updateModelContext({content:[{type:"text",text:`User is viewing the "${t.label}" figure (${t.filename}) of mesh convergence job ${se.jobId}.`}]}).catch(()=>{})}function ah(t){return t.formats.length>1?` · formats: ${t.formats.join(", ")}`:""}function sh(t){const n=/\.(pdf|svg)$/i.test(t.downloadFilename);Yu.textContent=n?re?"Download PNG (preview)":`Download ${t.downloadFilename.split(".").pop()?.toUpperCase()} (high quality)`:"Download figure"}function ch(t){ne.style.display=t?"block":"none",Kf.style.display=t?"none":"block"}async function el(t){ce=t,Vf.textContent=t.label,Bf.textContent=`${t.filename}${ah(t)}`,Wf.textContent=t.caption??"",sh(t),oh(t),Le.value=t.key;let n=!1;if(t.url)ne.src=t.url,n=!0;else if(re&&se){if(t.key===se.defaultFigureKey&&Un)ne.src=Un,n=!0;else if(/\.(png|svg|jpe?g|webp)$/i.test(t.filename)){ne.removeAttribute("src");const r=await ih(se.jobId,t.filename);r&&ce?.key===t.key&&(ne.src=r,n=!0)}}else/\.(png|svg|jpe?g|webp)$/i.test(t.filename)&&ne.removeAttribute("src");ch(n),se&&Xu(se.availableFigures)}function wo(t){se=t,Gu.textContent=t.title,Qu.textContent=t.subtitle?t.subtitle:`Job ${t.jobId}`,th(t.metrics),nh(t.highlights),t.standaloneUrl&&($o.href=t.standaloneUrl,$o.style.display="inline-flex");const n=t.availableFigures.find(r=>r.key===t.defaultFigureKey)??t.availableFigures[0]??null;ce=n,Xu(t.availableFigures),n&&el(n),ft.style.display="none",Ff.style.display="block"}Yu.addEventListener("click",()=>{if(!ce)return;const t=document.createElement("a");if(ce.downloadUrl&&!re)t.href=ce.downloadUrl,t.download=ce.downloadFilename;else{const n=ne.src;if(!n)return;t.href=n,t.download=ce.filename}t.click()});ne.style.cursor="zoom-in";ne.addEventListener("click",()=>{if(!re||!ne.src)return;const t=re.getHostContext?.(),n=t?.availableDisplayModes;if(!Array.isArray(n)||!n.includes("fullscreen"))return;const r=t?.displayMode==="fullscreen"?"inline":"fullscreen";re.requestDisplayMode({mode:r}).then(o=>{ne.style.cursor=o.mode==="fullscreen"?"zoom-out":"zoom-in"}).catch(()=>{})});const tl=new URLSearchParams(window.location.search),uh=tl.get("mode")==="standalone",pt=tl.get("job_id");if(uh&&pt){const t="The local viz port is assigned per MCP session and changes when the proxy restarts. Re-run barmesh_results_explorer for a fresh URL, or set BARIVIA_VIZ_PORT for a persistent port.";(async()=>{for(let r=1;r<=3;r++)try{const o=await fetch(`/api/results/${pt}`);if(o.status===404){ft.textContent=`Results for job ${pt} were not found (HTTP 404). Compute may have finished but cfd_finalize is still rendering figures — poll barmesh_jobs(status) until finalize completes. ${t}`;return}if(!o.ok)throw new Error(`HTTP ${o.status}`);const e=await o.json();wo(eh(e,pt));return}catch(o){if(r<3){await new Promise(e=>setTimeout(e,1e3*r));continue}ft.textContent=`Could not load results from the local viz server (${o instanceof Error?o.message:"error"}). ${t}`}})()}else{const t=new Vi({name:"Mesh Convergence Results",version:"1.0.0"},{},{autoResize:!0});re=t,t.ontoolinput=n=>{const r=n?.arguments??{},o=r.job_id!=null?String(r.job_id):"";o&&(Gu.textContent="Mesh Convergence Results",Qu.textContent=`Loading job ${o}...`)},t.ontoolresult=n=>{const r=n.content?.find(e=>e.type==="image");r&&(Un=`data:${r.mimeType};base64,${r.data}`);const o=n.structuredContent;if(!o||typeof o!="object"){ft.textContent="The results explorer did not receive structured data.";return}wo(o)},t.connect()}</script>
|
|
136
|
+
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:l().optional().describe("User's language and region preference in BCP 47 format."),timeZone:l().optional().describe("User's timezone in IANA format."),userAgent:l().optional().describe("Host application identifier."),platform:T([c("web"),c("desktop"),c("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:m({touch:Z().optional().describe("Whether the device supports touch input."),hover:Z().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:m({top:x().describe("Top safe area inset in pixels."),right:x().describe("Right safe area inset in pixels."),bottom:x().describe("Bottom safe area inset in pixels."),left:x().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Mf=m({method:c("ui/notifications/host-context-changed"),params:Ku.describe("Partial context update containing only changed fields.")});m({method:c("ui/update-model-context"),params:m({content:I(ct).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:P(l(),C().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});m({method:c("ui/initialize"),params:m({appInfo:In.describe("App identification (name and version)."),appCapabilities:Rf.describe("Features and capabilities this app provides."),protocolVersion:l().describe("Protocol version this app supports.")})});var Lf=m({protocolVersion:l().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:In.describe("Host application identification and version."),hostCapabilities:Df.describe("Features and capabilities provided by the host."),hostContext:Ku.describe("Rich context about the host environment.")}).passthrough(),qf={target:"draft-2020-12"};async function vo(t,n){let r=t["~standard"];if(r.jsonSchema)return r.jsonSchema[n](qf);if(r.vendor==="zod"){let{z:o}=await il(()=>Promise.resolve().then(()=>Nm),void 0,import.meta.url);return o.toJSONSchema(t,{io:n})}throw Error(`Schema (vendor: ${r.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function _o(t,n,r=""){let o=await t["~standard"].validate(n);if(o.issues){let e=o.issues.map(i=>{let a=i.path?.map(s=>typeof s=="object"?s.key:s).join(".");return a?`${a}: ${i.message}`:i.message}).join("; ");throw Error(r+e)}return o.value}class Vi extends bf{_appInfo;_capabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_assertInitialized(n){if(this._initializedSent)return;let r=`[ext-apps] App.${n}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(r);console.warn(`${r}. This will throw in a future release.`)}eventSchemas={toolinput:Nf,toolinputpartial:Tf,toolresult:Af,toolcancelled:Of,hostcontextchanged:Mf};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(n){if(!Vi.ONE_SHOT_EVENTS.has(n)||this._everHadListener.has(n)||(this._everHadListener.add(n),!this._initializedSent))return;let r=`[ext-apps] "${String(n)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(r);console.warn(r)}setEventHandler(n,r){r&&this._assertHandlerTiming(n),super.setEventHandler(n,r)}addEventListener(n,r){this._assertHandlerTiming(n),super.addEventListener(n,r)}onEventDispatch(n,r){n==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...r})}constructor(n,r={},o={autoResize:!0}){super(o),this._appInfo=n,this._capabilities=r,this.options=o,o.allowUnsafeEval||J({jitless:!0}),this.setRequestHandler(zn,e=>(console.log("Received ping:",e.params),{})),this.setEventHandler("hostcontextchanged",void 0)}registerCapabilities(n){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._capabilities=_f(this._capabilities,n)}registerTool(n,r,o){if(this._registeredTools[n])throw Error(`Tool ${n} is already registered`);let e=this,i=()=>{e._initializedSent&&e._capabilities.tools?.listChanged&&e.sendToolListChanged()},a=r.inputSchema!==void 0,s={title:r.title,description:r.description,inputSchema:r.inputSchema,outputSchema:r.outputSchema,annotations:r.annotations,_meta:r._meta,enabled:!0,enable(){this.enabled=!0,i()},disable(){this.enabled=!1,i()},update(p){Object.assign(this,p),i()},remove(){e._registeredTools[n]===s&&(delete e._registeredTools[n],i())},handler:async(p,g)=>{if(!s.enabled)throw Error(`Tool ${n} is disabled`);let h;if(a){let d=s.inputSchema,b=d?await _o(d,p??{},`Invalid input for tool ${n}: `):p??{};h=await o(b,g)}else h=await o(g);return s.outputSchema&&!h.isError&&(h.structuredContent=await _o(s.outputSchema,h.structuredContent,`Invalid output for tool ${n}: `)),h}};return this._registeredTools[n]=s,!this._capabilities.tools&&!this.transport&&this.registerCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),i(),s}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(n,r)=>{let o=this._registeredTools[n.name];if(!o)throw Error(`Tool ${n.name} not found`);return o.handler(n.arguments,r)},this.onlisttools=async(n,r)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([o,e])=>e.enabled).map(async([o,e])=>{let i={name:o,title:e.title,description:e.description,inputSchema:e.inputSchema?await vo(e.inputSchema,"input"):{type:"object",properties:{}}};return e.outputSchema&&(i.outputSchema=await vo(e.outputSchema,"output")),e.annotations&&(i.annotations=e.annotations),e._meta&&(i._meta=e._meta),i}))}))}async sendToolListChanged(n={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:n})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(n){this.setEventHandler("toolinput",n)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(n){this.setEventHandler("toolinputpartial",n)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(n){this.setEventHandler("toolresult",n)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(n){this.setEventHandler("toolcancelled",n)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(n){this.setEventHandler("hostcontextchanged",n)}_onteardown;get onteardown(){return this._onteardown}set onteardown(n){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,n),this._onteardown=n,this.replaceRequestHandler(Ef,(r,o)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(r.params,o)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(n){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,n),this._oncalltool=n,this.replaceRequestHandler(Ju,(r,o)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(r.params,o)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(n){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,n),this._onlisttools=n,this.replaceRequestHandler(Hu,(r,o)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(r.params,o)})}assertCapabilityForMethod(n){switch(n){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${n})`);break}}assertRequestHandlerCapability(n){switch(n){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${n})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${n} registered`)}}assertNotificationCapability(n){}assertTaskCapability(n){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(n){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(n,r){if(this._assertInitialized("callServerTool"),typeof n=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${n}"). Did you mean: callServerTool({ name: "${n}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:n},xn,{onprogress:()=>{},resetTimeoutOnProgress:!0,...r})}async readServerResource(n,r){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:n},Lu,r)}async listServerResources(n,r){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:n},Mu,r)}async createSamplingMessage(n,r){this._assertInitialized("createSamplingMessage");let o=n.tools?Wu:Bu;return await this.request({method:"sampling/createMessage",params:n},o,r)}sendMessage(n,r){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:n},jf,r)}sendLog(n){return this.notification({method:"notifications/message",params:n})}updateModelContext(n,r){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:n},ji,r)}openLink(n,r){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:n},zf,r)}sendOpenLink=this.openLink;downloadFile(n,r){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:n},xf,r)}requestTeardown(n={}){return this.notification({method:"ui/notifications/request-teardown",params:n})}requestDisplayMode(n,r){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:n},Zf,r)}sendSizeChanged(n){return this.notification({method:"ui/notifications/size-changed",params:n})}setupSizeChangedNotifications(){let n=!1,r=0,o=0,e=()=>{n||(n=!0,requestAnimationFrame(()=>{n=!1;let a=document.documentElement,s=a.style.height;a.style.height="max-content";let p=Math.ceil(a.getBoundingClientRect().height);a.style.height=s;let g=Math.ceil(window.innerWidth);(g!==r||p!==o)&&(r=g,o=p,this.sendSizeChanged({width:g,height:p}))}))};e();let i=new ResizeObserver(e);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(n=new kf(window.parent,window.parent),r){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1,await super.connect(n);try{let o=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:$f}},Lf,r);if(o===void 0)throw Error(`Server sent invalid initialize result: ${o}`);this._hostCapabilities=o.hostCapabilities,this._hostInfo=o.hostInfo,this._hostContext=o.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(o){throw this.close(),o}}}let se=null,ce=null,re=null,Un=null;const bo=new Map,ft=document.getElementById("loading"),Ff=document.getElementById("app"),Gu=document.getElementById("title"),Qu=document.getElementById("subtitle"),Hf=document.getElementById("metrics"),Jf=document.getElementById("highlights"),Le=document.getElementById("figure-select"),Vf=document.getElementById("figure-title"),Bf=document.getElementById("figure-file"),Wf=document.getElementById("figure-caption"),ne=document.getElementById("main-image"),Kf=document.getElementById("figure-empty"),$o=document.getElementById("open-standalone"),Yu=document.getElementById("download-current"),yo={combined:0,overview_distances:1,KL_ref:10,KL_stepwise:11,EMD_ref:12,EMD_stepwise:13,learning_curve:14,plot_vol_all_meshes:19,plot_vol_coarse_fine:20};function ko(t){return t in yo?yo[t]:t.startsWith("plot_vol")?25:t.startsWith("plot_")?30:50}function Gf(t){return[...t].sort((n,r)=>ko(n.key)-ko(r.key)||n.label.localeCompare(r.label))}function Qf(t){if(t.find(r=>r.key==="combined"))return"combined";const n=t.find(r=>r.url||/\.(png|svg)$/i.test(r.filename));return n?n.key:t.find(r=>r.key==="KL_ref")?"KL_ref":t[0]?.key}function Yf(t){const n=t.replace(/\.(png|pdf|svg)$/i,"");return n==="combined"?"Component planes (overview)":n==="overview_distances"?"Distances overview (KL + EMD)":n==="learning_curve"?"Learning curve (QE by epoch)":n==="KL_ref"?"KL divergence vs reference":n==="KL_stepwise"?"KL divergence stepwise":n==="EMD_ref"?"Wasserstein (EMD) vs reference":n==="EMD_stepwise"?"Wasserstein (EMD) stepwise":n==="plot_vol_all_meshes"?"Volume: all meshes":n==="plot_vol_coarse_fine"?"Volume: coarse vs reference":n.startsWith("plot_vol_")?"Volume: stepwise":n.startsWith("plot_")?`Component: ${n.replace(/^plot_/,"").replace(/_/g," ")}`:n.replace(/_/g," ")}function Xf(t,n){const r=new Map;for(const o of t){const e=o.match(/^(.*)\.(png|pdf|svg)$/i);if(!e)continue;const i=e[1],a=r.get(i)??new Set;a.add(e[2].toLowerCase()),r.set(i,a)}return Gf([...r.entries()].map(([o,e])=>{const i=[...e],a=e.has("png")?`${o}.png`:e.has("svg")?`${o}.svg`:`${o}.${i[0]}`,s=e.has("svg")?`${o}.svg`:e.has("pdf")?`${o}.pdf`:`${o}.png`,p=/\.(png|svg)$/i.test(a),g=i.filter(h=>h==="pdf"||h==="svg");return{key:o,label:Yf(a),filename:a,downloadFilename:s,formats:i,kind:o==="combined"?"summary":o.startsWith("plot_")&&!o.startsWith("plot_vol_")?"component":"diagnostic",caption:g.length>0?`High-quality ${g.join("/").toUpperCase()} available to download.`:void 0,url:p?`/api/results/${n}/image/${a}`:void 0,downloadUrl:`/api/results/${n}/image/${s}`}}))}function eh(t,n){const r=t.summary??{},o=(r.files??[]).filter(h=>/\.(png|pdf|svg)$/i.test(h)),e=r.grid??[],i=r.meshes??[],a=r.quantization_error!=null?Number(r.quantization_error).toFixed(4):"N/A",s=r.topographic_error!=null?Number(r.topographic_error).toFixed(4):"N/A",p=[{label:"Grid",value:e.length>=2?`${e[0]}×${e[1]}`:"N/A"},{label:"Preset",value:String(r.preset??"generic")},{label:"Reference",value:String(r.reference_mesh??"N/A")},{label:"Meshes",value:String(i.length||"N/A")},{label:"QE",value:a},{label:"TE",value:s}],g=Xf(o,n);return{type:"barmesh-results-explorer",jobId:n,title:"Mesh Convergence Results",subtitle:String(t.label??""),metrics:p,highlights:["SOM distances complement, not replace, numerical uncertainty analysis."],availableFigures:g,defaultFigureKey:Qf(g)}}function th(t){Hf.innerHTML=t.map(n=>`<div class="metric"><span class="metric-label">${n.label}</span><span class="metric-value">${n.value}</span></div>`).join("")}function nh(t){Jf.innerHTML=t.length>0?t.map(n=>`<div class="highlight">${n}</div>`).join(""):'<div class="highlight">No convergence reading available.</div>'}const rh={summary:"Overview",diagnostic:"Distances & diagnostics",component:"Component planes",artifact:"Other"};function Xu(t){const n=new Map;for(const o of t){const e=o.kind??"diagnostic",i=n.get(e)??[];i.push(o),n.set(e,i)}const r=["summary","diagnostic","component","artifact"];Le.innerHTML="";for(const o of r){const e=n.get(o);if(!e||e.length===0)continue;const i=document.createElement("optgroup");i.label=rh[o]??o;for(const a of e){const s=document.createElement("option");s.value=a.key,s.textContent=`${a.label} (${a.filename})`,a.key===ce?.key&&(s.selected=!0),i.appendChild(s)}Le.appendChild(i)}}Le.addEventListener("change",()=>{const t=se?.availableFigures.find(n=>n.key===Le.value);t&&el(t)});async function ih(t,n){const r=`${t}/${n}`,o=bo.get(r);if(o)return o;if(!re)return null;try{const i=(await re.callServerTool({name:"_barmesh_fetch_figure",arguments:{job_id:t,filename:n}})).content?.find(a=>a.type==="image");if(i){const a=`data:${i.mimeType};base64,${i.data}`;return bo.set(r,a),a}}catch{}return null}function oh(t){!re||!se||re.updateModelContext({content:[{type:"text",text:`User is viewing the "${t.label}" figure (${t.filename}) of mesh convergence job ${se.jobId}.`}]}).catch(()=>{})}function ah(t){return t.formats.length>1?` · formats: ${t.formats.join(", ")}`:""}function sh(t){const n=/\.(pdf|svg)$/i.test(t.downloadFilename);Yu.textContent=n?re?"Download PNG (preview)":`Download ${t.downloadFilename.split(".").pop()?.toUpperCase()} (high quality)`:"Download figure"}function ch(t){ne.style.display=t?"block":"none",Kf.style.display=t?"none":"block"}async function el(t){ce=t,Vf.textContent=t.label,Bf.textContent=`${t.filename}${ah(t)}`,Wf.textContent=t.caption??"",sh(t),oh(t),Le.value=t.key;let n=!1;if(t.url)ne.src=t.url,n=!0;else if(re&&se){if(t.key===se.defaultFigureKey&&Un)ne.src=Un,n=!0;else if(/\.(png|svg|jpe?g|webp)$/i.test(t.filename)){ne.removeAttribute("src");const r=await ih(se.jobId,t.filename);r&&ce?.key===t.key&&(ne.src=r,n=!0)}}else/\.(png|svg|jpe?g|webp)$/i.test(t.filename)&&ne.removeAttribute("src");ch(n),se&&Xu(se.availableFigures)}function wo(t){se=t,Gu.textContent=t.title,Qu.textContent=t.subtitle?t.subtitle:`Job ${t.jobId}`,th(t.metrics),nh(t.highlights),t.standaloneUrl&&($o.href=t.standaloneUrl,$o.style.display="inline-flex");const n=t.availableFigures.find(r=>r.key===t.defaultFigureKey)??t.availableFigures[0]??null;ce=n,Xu(t.availableFigures),n&&el(n),ft.style.display="none",Ff.style.display="block"}Yu.addEventListener("click",()=>{if(!ce)return;const t=document.createElement("a");if(ce.downloadUrl&&!re)t.href=ce.downloadUrl,t.download=ce.downloadFilename;else{const n=ne.src;if(!n)return;t.href=n,t.download=ce.filename}t.click()});ne.style.cursor="zoom-in";ne.addEventListener("click",()=>{if(!re||!ne.src)return;const t=re.getHostContext?.(),n=t?.availableDisplayModes;if(!Array.isArray(n)||!n.includes("fullscreen"))return;const r=t?.displayMode==="fullscreen"?"inline":"fullscreen";re.requestDisplayMode({mode:r}).then(o=>{ne.style.cursor=o.mode==="fullscreen"?"zoom-out":"zoom-in"}).catch(()=>{})});const tl=new URLSearchParams(window.location.search),uh=tl.get("mode")==="standalone",pt=tl.get("job_id");if(uh&&pt){const t="The local viz port is assigned per MCP session and changes when the proxy restarts. Re-run barmesh_results_explorer for a fresh URL, or set BARIVIA_VIZ_PORT for a persistent port.";(async()=>{for(let r=1;r<=3;r++)try{const o=await fetch(`/api/results/${pt}`);if(o.status===404){ft.textContent=`Results for job ${pt} were not found (HTTP 404). Compute may have finished but cfd_finalize is still rendering figures — poll barmesh_jobs(status) until finalize completes. ${t}`;return}if(!o.ok)throw new Error(`HTTP ${o.status}`);const e=await o.json();wo(eh(e,pt));return}catch(o){if(r<3){await new Promise(e=>setTimeout(e,1e3*r));continue}ft.textContent=`Could not load results from the local viz server (${o instanceof Error?o.message:"error"}). ${t}`}})()}else{const t=new Vi({name:"Mesh Convergence Results",version:"1.0.0"},{},{autoResize:!0});re=t,t.ontoolinput=n=>{const r=n?.arguments??{},o=r.job_id!=null?String(r.job_id):"";o&&(Gu.textContent="Mesh Convergence Results",Qu.textContent=`Loading job ${o}...`)},t.ontoolresult=n=>{const r=n.content?.find(e=>e.type==="image");r&&(Un=`data:${r.mimeType};base64,${r.data}`);const o=n.structuredContent;if(!o||typeof o!="object"){ft.textContent="The results explorer did not receive structured data.";return}wo(o)},t.connect()}</script>
|
|
137
137
|
</head>
|
|
138
138
|
<body>
|
|
139
139
|
<div id="loading">Loading mesh convergence results...</div>
|