@barivia/barmesh-mcp 0.8.0 → 0.8.3
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 +21 -13
- package/dist/audit.js +3 -1
- package/dist/figure_sections.js +11 -3
- package/dist/logger.js +2 -1
- package/dist/shared.js +12 -2
- package/dist/tools/barmesh_results_explorer.js +6 -0
- package/dist/tools/training_monitor.js +1 -1
- package/dist/training_monitor_curve.js +42 -0
- package/dist/views/src/views/barmesh-results-explorer/index.html +1 -1
- package/dist/views/src/views/barmesh-training-monitor/index.html +28 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,16 +13,16 @@ form on a shared self-organizing map (SOM)**:
|
|
|
13
13
|
- **`barmesh_mesh_convergence`** — trains one SOM on all meshes (joint-normalized), projects
|
|
14
14
|
each mesh to a volume-weighted fingerprint, and computes **symmetric KL** and
|
|
15
15
|
**Wasserstein-1 (EMD)** distances stepwise and against a reference mesh, with publication
|
|
16
|
-
figures and an advisory convergence reading.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
enqueues **`prepare_training_matrix`** on worker-io when
|
|
21
|
-
auto-polls `prepare_job_id
|
|
22
|
-
**`cfd_finalize
|
|
23
|
-
when figures are deferred.
|
|
16
|
+
figures and an advisory convergence reading. Default **`mesh_order`** sorts by ascending
|
|
17
|
+
cell count and **interleaves U/G (or similar) families** when counts tie, so stepwise
|
|
18
|
+
distances do not jump across topology families. The SOM features use the same staged
|
|
19
|
+
pipeline as barsom training; optional `transforms`, `normalize`, `normalization_methods`,
|
|
20
|
+
and `row_range` apply. Submit enqueues **`prepare_training_matrix`** on worker-io when
|
|
21
|
+
staged; the proxy auto-polls `prepare_job_id`. Default **`defer_figures=true`** →
|
|
22
|
+
**`cfd_finalize`**; **`barmesh_jobs(status)`** auto-polls **`finalize_job_id`**.
|
|
24
23
|
- **`barmesh_richardson`** — classical three-level Richardson extrapolation / Grid
|
|
25
|
-
Convergence Index (GCI) on scalar quantities of interest.
|
|
24
|
+
Convergence Index (GCI) on scalar quantities of interest. Prefer **one topology family**
|
|
25
|
+
(all U or all G); mixed families return a **`topology_warning`** in the result summary.
|
|
26
26
|
|
|
27
27
|
These complement, and do not replace, conventional numerical uncertainty analysis.
|
|
28
28
|
|
|
@@ -56,9 +56,9 @@ API key; otherwise the analysis calls return HTTP 403. Contact Barivia to enable
|
|
|
56
56
|
| `barmesh_mesh_convergence` | SOM fingerprint distances (async job). |
|
|
57
57
|
| `barmesh_richardson` | Richardson/GCI on scalar QoIs (async job). |
|
|
58
58
|
| `barmesh_jobs` | Poll job status / block until terminal (`action=monitor`) / list jobs. Auto-polls CFD prepare + finalize when applicable. Reports phase, epoch/total, elapsed, ETA, and QE live during the SOM training. |
|
|
59
|
-
| `barmesh_training_monitor` |
|
|
60
|
-
| `barmesh_results` | Distances, convergence reading, and figures. `action=get` inlines headline PNGs; `action=download` saves artifacts to disk; `action=render` produces publication PDFs on demand. |
|
|
61
|
-
| `barmesh_results_explorer` | Interactive MCP App with a **figure dropdown above the plot
|
|
59
|
+
| `barmesh_training_monitor` | Live MCP App (QE + panel TE) and/or headless monitor; always returns a standalone localhost URL + `ui_delivery`. |
|
|
60
|
+
| `barmesh_results` | Distances, convergence reading, and figures. `action=get` inlines headline PNGs; `action=download` saves artifacts to disk; `action=render` produces publication PDFs on demand; `action=feature_divergences` refreshes feature-plane KL/W1 (distinct from mesh SKL/EMD). |
|
|
61
|
+
| `barmesh_results_explorer` | Interactive MCP App with a **figure dropdown above the plot**; always leads with a standalone localhost URL + `ui_delivery`. |
|
|
62
62
|
| `barmesh_send_feedback` | Send a short note or bug report to the Barivia team. |
|
|
63
63
|
|
|
64
64
|
### Figures and progress (0.5.2)
|
|
@@ -69,6 +69,11 @@ API key; otherwise the analysis calls return HTTP 403. Contact Barivia to enable
|
|
|
69
69
|
`plot_vol_all_meshes.png` shows volume fingerprints for every mesh in mesh_order.
|
|
70
70
|
- **Learning curve:** every job produces `learning_curve.png` (QE by epoch) for training
|
|
71
71
|
quality inspection; listed in the results explorer dropdown.
|
|
72
|
+
- **Asymmetric mesh KL (`KL_asymmetric.png`):** pairwise ``D_{KL}(P\\|Q)`` heatmap —
|
|
73
|
+
above-diagonal coarse→fine, below-diagonal fine→coarse (meshes ordered coarse→fine).
|
|
74
|
+
Distinct from feature-plane `divergence_kl` (component association).
|
|
75
|
+
- **Live ΔKL in training monitor:** panel SKL→ref mean/max curve (TE-panel sample) beside
|
|
76
|
+
the hit-grid; SOM batch-step axis labels (not FLooP) for regular mesh_convergence jobs.
|
|
72
77
|
- **PDFs on demand:** publication vector PDFs are NOT generated by default. Render them
|
|
73
78
|
after completion with `barmesh_results(action=render, format=pdf)`, then download with
|
|
74
79
|
`barmesh_results(action=download, folder=...)` or `action=image`.
|
|
@@ -85,6 +90,8 @@ API key; otherwise the analysis calls return HTTP 403. Contact Barivia to enable
|
|
|
85
90
|
|
|
86
91
|
### Migration notes
|
|
87
92
|
|
|
93
|
+
- **Feature-plane divergences (0.8.1):** Mesh jobs write `divergence_kl` / `feature_divergences.json` / `weights.json` (barsom-parallel). These measure association between SOM **component planes** — not mesh-vs-mesh fingerprint SKL/EMD (`KL_ref`, `EMD_*`). Refresh or add Wasserstein via `barmesh_results(action=feature_divergences)`.
|
|
94
|
+
- **UI delivery + mesh_order (0.7.3+ / 0.8.0):** App tools emit structured `ui_delivery` and **always put the standalone viz URL first** (hosts may advertise MCP Apps without mounting widgets). Env: `BARIVIA_UI_DELIVERY` (`auto` \| `apps` \| `localhost` \| `inline`), `BARIVIA_VIZ_PORT`. Default mesh_order interleaves U/G at tied cell counts; Richardson warns on mixed topology families.
|
|
88
95
|
- **Fixed-panel live TE (0.6.3 / barsom 0.20.4):** mid-training TE uses a fixed evaluation panel (`te_panel_size`; `te_inner_samples` alias). Curves stay on panel TE; monitors show **Panel TE** and **Map TE** separately — no snap-to-map on the curve tail.
|
|
89
96
|
- **`barmesh_training_monitor` (0.5.3):** server-side blocking monitor with throttled snapshots — preferred after job submit instead of manual `barmesh_jobs(status)` loops. Equivalent to `barmesh_jobs(action=monitor)`.
|
|
90
97
|
- **`send_feedback` → `barmesh_send_feedback` (0.3.0):** the feedback tool was renamed so it no longer collides with the `@barivia/barsom-mcp` tool of the same name when both servers are enabled in one client. Update any direct call sites; the behavior is unchanged.
|
|
@@ -110,4 +117,5 @@ Parquet staging is supported by the API but not yet exposed as an MCP upload for
|
|
|
110
117
|
| `BARIVIA_FETCH_TIMEOUT_MS` | `60000` | Per-request timeout (raise for large uploads). |
|
|
111
118
|
| `BARIVIA_WORKSPACE_ROOT` | workspace/cwd | Root for resolving relative `file_path` uploads. |
|
|
112
119
|
| `BARIVIA_ENFORCE_WORKSPACE_SANDBOX` | `1` | Restrict uploads to the workspace; set `0` to allow absolute paths. |
|
|
113
|
-
| `BARIVIA_VIZ_PORT` | ephemeral | Fixed localhost port for
|
|
120
|
+
| `BARIVIA_VIZ_PORT` | ephemeral | Fixed localhost port for standalone App pages (otherwise OS-assigned per session). |
|
|
121
|
+
| `BARIVIA_UI_DELIVERY` | `auto` | Client-capability override for App tools: `auto` \| `apps` \| `localhost` \| `inline`. Responses always lead with a standalone localhost URL. |
|
package/dist/audit.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MCP tool audit wrapper — tool name, action, latency, outcome; no secrets.
|
|
3
3
|
*/
|
|
4
4
|
import { logAudit } from "./logger.js";
|
|
5
|
-
import { runWithTrace } from "./shared.js";
|
|
5
|
+
import { currentTraceId, runWithTrace } from "./shared.js";
|
|
6
6
|
const AUDIT_PARAM_KEYS = new Set([
|
|
7
7
|
"action",
|
|
8
8
|
"preset",
|
|
@@ -69,6 +69,7 @@ export async function runMcpToolAudit(tool, action, args, handler) {
|
|
|
69
69
|
action,
|
|
70
70
|
duration_ms: Date.now() - t0,
|
|
71
71
|
outcome: "ok",
|
|
72
|
+
trace_id: currentTraceId(),
|
|
72
73
|
...ids,
|
|
73
74
|
...(Object.keys(params).length > 0 ? { params } : {}),
|
|
74
75
|
});
|
|
@@ -81,6 +82,7 @@ export async function runMcpToolAudit(tool, action, args, handler) {
|
|
|
81
82
|
duration_ms: Date.now() - t0,
|
|
82
83
|
outcome: "error",
|
|
83
84
|
error_code: errorCodeFrom(err),
|
|
85
|
+
trace_id: currentTraceId(),
|
|
84
86
|
...(Object.keys(params).length > 0 ? { params } : {}),
|
|
85
87
|
});
|
|
86
88
|
throw err;
|
package/dist/figure_sections.js
CHANGED
|
@@ -24,6 +24,9 @@ export function figureSection(key) {
|
|
|
24
24
|
}
|
|
25
25
|
if (key === "learning_curve" || key.startsWith("plot_vol"))
|
|
26
26
|
return "diagnostics";
|
|
27
|
+
// Feature-plane association heatmaps (barsom-parallel) — not mesh fingerprint KL/EMD.
|
|
28
|
+
if (key.startsWith("divergence_"))
|
|
29
|
+
return "component";
|
|
27
30
|
if (key.startsWith("plot_"))
|
|
28
31
|
return "component";
|
|
29
32
|
return "diagnostics";
|
|
@@ -33,17 +36,22 @@ export const FIGURE_SORT_RANK = {
|
|
|
33
36
|
overview_distances: 1,
|
|
34
37
|
KL_ref: 10,
|
|
35
38
|
KL_stepwise: 11,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
KL_asymmetric: 12,
|
|
40
|
+
EMD_ref: 13,
|
|
41
|
+
EMD_stepwise: 14,
|
|
42
|
+
learning_curve: 15,
|
|
39
43
|
plot_vol_all_meshes: 19,
|
|
40
44
|
plot_vol_coarse_fine: 20,
|
|
45
|
+
divergence_kl: 28,
|
|
46
|
+
divergence_w1: 29,
|
|
41
47
|
};
|
|
42
48
|
export function sortRank(key) {
|
|
43
49
|
if (key in FIGURE_SORT_RANK)
|
|
44
50
|
return FIGURE_SORT_RANK[key];
|
|
45
51
|
if (key.startsWith("plot_vol"))
|
|
46
52
|
return 25;
|
|
53
|
+
if (key.startsWith("divergence_"))
|
|
54
|
+
return 28;
|
|
47
55
|
if (key.startsWith("plot_"))
|
|
48
56
|
return 30;
|
|
49
57
|
return 50;
|
package/dist/logger.js
CHANGED
|
@@ -32,6 +32,7 @@ export function logInfo(msg, fields = {}) {
|
|
|
32
32
|
export function logWarn(msg, fields = {}) {
|
|
33
33
|
emit({ ...fields, level: "warn", msg });
|
|
34
34
|
}
|
|
35
|
+
/** Audit line: stable event name is `msg=mcp_tool_call` (OBSERVABILITY taxonomy). */
|
|
35
36
|
export function logAudit(fields) {
|
|
36
|
-
emit({ ...fields,
|
|
37
|
+
emit({ ...fields, level: "info", msg: "mcp_tool_call" });
|
|
37
38
|
}
|
package/dist/shared.js
CHANGED
|
@@ -35,7 +35,7 @@ function newRequestId() {
|
|
|
35
35
|
return randomUUID();
|
|
36
36
|
}
|
|
37
37
|
/** Single source of truth for the proxy version. Keep in sync with package.json on bump. */
|
|
38
|
-
export const CLIENT_VERSION = "0.8.
|
|
38
|
+
export const CLIENT_VERSION = "0.8.3";
|
|
39
39
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
40
40
|
/** Large per-cell CSV uploads may exceed the default fetch timeout. */
|
|
41
41
|
export const UPLOAD_DATASET_TIMEOUT_MS = 180_000;
|
|
@@ -532,8 +532,12 @@ function _newSpanId() {
|
|
|
532
532
|
export function runWithTrace(fn) {
|
|
533
533
|
return _traceStore.run(_newTraceId(), fn);
|
|
534
534
|
}
|
|
535
|
+
/** Current ALS trace id, or a fresh id if called outside `runWithTrace`. */
|
|
536
|
+
export function currentTraceId() {
|
|
537
|
+
return _traceStore.getStore() ?? _newTraceId();
|
|
538
|
+
}
|
|
535
539
|
function _traceparentHeader() {
|
|
536
|
-
return `00-${
|
|
540
|
+
return `00-${currentTraceId()}-${_newSpanId()}-01`;
|
|
537
541
|
}
|
|
538
542
|
export async function apiCall(method, pathPart, body, extraHeaders, requestTimeoutMs) {
|
|
539
543
|
const url = `${API_URL}${pathPart}`;
|
|
@@ -722,6 +726,12 @@ export function getCaptionForImage(filename) {
|
|
|
722
726
|
return "Wasserstein-1 (EMD) distance from each mesh to the reference. A monotone decrease toward the finest mesh indicates field-level convergence.";
|
|
723
727
|
if (base === "EMD_stepwise")
|
|
724
728
|
return "Stepwise EMD between consecutive meshes. Small, plateauing values on the finest pairs indicate the fingerprint has stopped changing.";
|
|
729
|
+
if (base === "KL_asymmetric")
|
|
730
|
+
return "Asymmetric pairwise mesh-fingerprint KL D_KL(P‖Q): above-diagonal cells are coarse→fine, below-diagonal are fine→coarse (meshes ordered coarse→fine). Use when SKL line plots hide directional disagreement.";
|
|
731
|
+
if (base === "divergence_kl")
|
|
732
|
+
return "Feature-plane symmetric KL heatmap (min-shift sum-normalized physical component planes). Measures association between SOM features — not mesh-vs-mesh fingerprint KL (see KL_ref / KL_asymmetric).";
|
|
733
|
+
if (base === "divergence_w1")
|
|
734
|
+
return "Feature-plane 1-Wasserstein heatmap on the same physical component-plane masses. Opt-in via barmesh_results(action=feature_divergences, metrics=[\"wasserstein\"]).";
|
|
725
735
|
if (base === "plot_vol_all_meshes")
|
|
726
736
|
return "Volume-weighted SOM fingerprints P_vol for every mesh in mesh_order (shared color scale).";
|
|
727
737
|
if (base === "plot_vol_coarse_fine")
|
|
@@ -40,6 +40,12 @@ function labelForFigure(filename) {
|
|
|
40
40
|
return "Wasserstein (EMD) vs reference";
|
|
41
41
|
if (base === "EMD_stepwise")
|
|
42
42
|
return "Wasserstein (EMD) stepwise";
|
|
43
|
+
if (base === "KL_asymmetric")
|
|
44
|
+
return "Asymmetric mesh KL matrix (coarse↔fine)";
|
|
45
|
+
if (base === "divergence_kl")
|
|
46
|
+
return "Feature-plane symmetric KL (not mesh KL)";
|
|
47
|
+
if (base === "divergence_w1")
|
|
48
|
+
return "Feature-plane Wasserstein-1";
|
|
43
49
|
if (base === "plot_vol_all_meshes")
|
|
44
50
|
return "Volume fingerprint: all meshes";
|
|
45
51
|
if (base === "plot_vol_coarse_fine")
|
|
@@ -24,7 +24,7 @@ function buildStructuredContent(job_id, data, port) {
|
|
|
24
24
|
export function registerTrainingMonitorTool(server) {
|
|
25
25
|
registerAppTool(server, "barmesh_training_monitor", {
|
|
26
26
|
title: "Mesh Convergence Training Monitor",
|
|
27
|
-
description: "Default visual monitor for mesh_convergence jobs after submit. Embedded MCP App auto-refreshes every 5s: epoch progress bar, phase, ETA, live QE/TE curves (uniformly subsampled to ≤1000 batch samples per phase), and a per-epoch hit-grid heatmap when available. On already-completed jobs, replays training-log curves in review mode (same entry point). Also exposes a standalone localhost URL — copy it if window.open is blocked in your MCP host. Headless fallback: barmesh_jobs(action=monitor) blocks with compact text snapshots (live or post-hoc review). barmesh_jobs(action=status) remains a one-shot poll. After completion, use barmesh_results_explorer for figures.",
|
|
27
|
+
description: "Default visual monitor for mesh_convergence jobs after submit. Embedded MCP App auto-refreshes every 5s: epoch progress bar, phase, ETA, live QE/TE curves (uniformly subsampled to ≤1000 batch samples per phase; SOM batch-step labels — not FLooP unless job_type is FLooP), panel fingerprint ΔKL curve (mean/max SKL→ref on the TE panel), and a per-epoch hit-grid heatmap when available. On already-completed jobs, replays training-log curves in review mode (same entry point). Also exposes a standalone localhost URL — copy it if window.open is blocked in your MCP host. In MCP App context, use Copy results explorer link when Open results explorer cannot navigate. Headless fallback: barmesh_jobs(action=monitor) blocks with compact text snapshots (live or post-hoc review). barmesh_jobs(action=status) remains a one-shot poll. After completion, use barmesh_results_explorer for figures.",
|
|
28
28
|
inputSchema: {
|
|
29
29
|
job_id: z.string().describe("Job ID from barmesh_mesh_convergence or barmesh_richardson"),
|
|
30
30
|
fetch_training_log: z
|
|
@@ -16,6 +16,48 @@ export function isKernelTrainingComplete(data, status) {
|
|
|
16
16
|
return true;
|
|
17
17
|
return status === "completed";
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* True only for actual FLooP / FLOOP_SIOM jobs.
|
|
21
|
+
* Do NOT infer from "ordering-only" curve shape — mesh_convergence generic/pitz
|
|
22
|
+
* presets use epochs=[N,0] (regular SOM ordering, no convergence phase).
|
|
23
|
+
*/
|
|
24
|
+
export function isFloopJob(data) {
|
|
25
|
+
const phase = String(data.training_progress_phase ?? data.progress_phase ?? "").toLowerCase();
|
|
26
|
+
if (phase === "floop")
|
|
27
|
+
return true;
|
|
28
|
+
const jt = String(data.job_type ?? data._job_type ?? "").toLowerCase();
|
|
29
|
+
if (jt.includes("floop"))
|
|
30
|
+
return true;
|
|
31
|
+
const rc = data.run_config;
|
|
32
|
+
if (rc && typeof rc === "object") {
|
|
33
|
+
const model = String(rc.model ?? "").toLowerCase();
|
|
34
|
+
if (model.includes("floop"))
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const model = String(data.model ?? "").toLowerCase();
|
|
38
|
+
if (model.includes("floop"))
|
|
39
|
+
return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/** Axis / series labels keyed by actual training type (SOM batch steps vs FLooP). */
|
|
43
|
+
export function curveAxisLabel(data) {
|
|
44
|
+
return isFloopJob(data) ? "FLooP step" : "Batch sample";
|
|
45
|
+
}
|
|
46
|
+
export function qeSeriesLabel(data, phase) {
|
|
47
|
+
if (phase === "floop" || isFloopJob(data))
|
|
48
|
+
return "QE (FLooP steps)";
|
|
49
|
+
if (phase === "convergence")
|
|
50
|
+
return "QE convergence";
|
|
51
|
+
return "QE ordering";
|
|
52
|
+
}
|
|
53
|
+
export function teSeriesLabel(data, phase) {
|
|
54
|
+
const base = phase === "floop" || isFloopJob(data)
|
|
55
|
+
? "TE (FLooP steps)"
|
|
56
|
+
: phase === "convergence"
|
|
57
|
+
? "TE convergence"
|
|
58
|
+
: "TE ordering";
|
|
59
|
+
return teCurveLabel(base);
|
|
60
|
+
}
|
|
19
61
|
export function teCurveLabel(base) {
|
|
20
62
|
return `${base} (panel)`;
|
|
21
63
|
}
|
|
@@ -136,7 +136,7 @@ Boolean requesting whether a visible border and background is provided by the ho
|
|
|
136
136
|
- omitted: host decides border`)});p({method:c("ui/request-display-mode"),params:p({mode:Le.describe("The display mode being requested.")})});var Hf=p({mode:Le.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),Jf=T([c("model"),c("app")]).describe("Tool visibility scope - who can access the tool.");p({resourceUri:l().optional(),visibility:I(Jf).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
137
137
|
- "model": Tool visible to and callable by the agent
|
|
138
138
|
- "app": Tool callable by the app from this server only`),csp:_e().optional(),permissions:_e().optional()});p({mimeTypes:I(l()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')});p({method:c("ui/download-file"),params:p({contents:I(T([Wu,Ku])).describe("Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})});p({method:c("ui/message"),params:p({role:c("user").describe('Message role, currently only "user" is supported.'),content:I(ut).describe("Message content blocks (text, image, etc.).")})});p({method:c("ui/notifications/sandbox-resource-ready"),params:p({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:Gi.optional().describe("CSP configuration from resource metadata."),permissions:Qi.optional().describe("Sandbox permissions from resource metadata.")})});var Bf=p({method:c("ui/notifications/tool-result"),params:Un.describe("Standard MCP tool execution result.")}),tl=p({toolInfo:p({id:rt.optional().describe("JSON-RPC id of the tools/call request."),tool:Ki.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:Tf.optional().describe("Current color theme preference."),styles:Mf.optional().describe("Style configuration for theming the app."),displayMode:Le.optional().describe("How the UI is currently displayed."),availableDisplayModes:I(Le).optional().describe("Display modes the host supports."),containerDimensions:T([p({height:x().describe("Fixed container height in pixels.")}),p({maxHeight:T([x(),Ce()]).optional().describe("Maximum container height in pixels.")})]).and(T([p({width:x().describe("Fixed container width in pixels.")}),p({maxWidth:T([x(),Ce()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
139
|
-
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:p({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:p({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(),Vf=p({method:c("ui/notifications/host-context-changed"),params:tl.describe("Partial context update containing only changed fields.")});p({method:c("ui/update-model-context"),params:p({content:I(ut).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.")})});p({method:c("ui/initialize"),params:p({appInfo:Nn.describe("App identification (name and version)."),appCapabilities:Ff.describe("Features and capabilities this app provides."),protocolVersion:l().describe("Protocol version this app supports.")})});var Wf=p({protocolVersion:l().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Nn.describe("Host application identification and version."),hostCapabilities:qf.describe("Features and capabilities provided by the host."),hostContext:tl.describe("Rich context about the host environment.")}).passthrough(),Kf={target:"draft-2020-12"};async function wo(t,n){let r=t["~standard"];if(r.jsonSchema)return r.jsonSchema[n](Kf);if(r.vendor==="zod"){let{z:o}=await dl(()=>Promise.resolve().then(()=>Rm),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 So(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 Yi extends zf{_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:Rf,toolinputpartial:Zf,toolresult:Bf,toolcancelled:Cf,hostcontextchanged:Vf};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(n){if(!Yi.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(Tn,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=If(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(m){Object.assign(this,m),i()},remove(){e._registeredTools[n]===s&&(delete e._registeredTools[n],i())},handler:async(m,g)=>{if(!s.enabled)throw Error(`Tool ${n} is disabled`);let h;if(a){let d=s.inputSchema,b=d?await So(d,m??{},`Invalid input for tool ${n}: `):m??{};h=await o(b,g)}else h=await o(g);return s.outputSchema&&!h.isError&&(h.structuredContent=await So(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 wo(e.inputSchema,"input"):{type:"object",properties:{}}};return e.outputSchema&&(i.outputSchema=await wo(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(Lf,(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(Qu,(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(Gu,(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},Un,{onprogress:()=>{},resetTimeoutOnProgress:!0,...r})}async readServerResource(n,r){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:n},Vu,r)}async listServerResources(n,r){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:n},Bu,r)}async createSamplingMessage(n,r){this._assertInitialized("createSamplingMessage");let o=n.tools?el:Xu;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},Df,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},Ei,r)}openLink(n,r){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:n},Pf,r)}sendOpenLink=this.openLink;downloadFile(n,r){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:n},Ef,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},Hf,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 m=Math.ceil(a.getBoundingClientRect().height);a.style.height=s;let g=Math.ceil(window.innerWidth);(g!==r||m!==o)&&(r=g,o=m,this.sendSizeChanged({width:g,height:m}))}))};e();let i=new ResizeObserver(e);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(n=new Nf(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:xf}},Wf,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}}}const Zn=["overview","distances","diagnostics","component"],Gf={overview:"Overview",distances:"Distances",diagnostics:"Diagnostics",component:"Component planes"};function zt(t){return t==="combined"?"overview":t==="overview_distances"||t.startsWith("KL_")||t.startsWith("EMD_")?"distances":t==="learning_curve"||t.startsWith("plot_vol")?"diagnostics":t.startsWith("plot_")?"component":"diagnostics"}const Io={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 zo(t){return t in Io?Io[t]:t.startsWith("plot_vol")?25:t.startsWith("plot_")?30:50}let ae=null,ue=null,re=null,Cn=null;const xo=new Map,vt=document.getElementById("loading"),Qf=document.getElementById("app"),nl=document.getElementById("title"),rl=document.getElementById("subtitle"),ft=document.getElementById("metadata-strip"),Yf=document.getElementById("metrics"),qe=document.getElementById("figure-select"),Xf=document.getElementById("figure-title"),eh=document.getElementById("figure-file"),th=document.getElementById("figure-caption"),ne=document.getElementById("main-image"),nh=document.getElementById("figure-empty"),ht=document.getElementById("open-standalone"),On=document.getElementById("open-monitor-btn"),ce=document.getElementById("copy-url"),jo=document.getElementById("related-monitor"),il=document.getElementById("download-current");function rh(t){return[...t].sort((n,r)=>Zn.indexOf(n.section??zt(n.key))-Zn.indexOf(r.section??zt(r.key))||zo(n.key)-zo(r.key)||n.label.localeCompare(r.label))}function ih(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 oh(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 ah(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 rh([...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`,m=/\.(png|svg)$/i.test(a),g=i.filter(h=>h==="pdf"||h==="svg");return{key:o,label:oh(a),filename:a,downloadFilename:s,formats:i,section:zt(o),caption:g.length>0?`High-quality ${g.join("/").toUpperCase()} available to download.`:void 0,url:m?`/api/results/${n}/image/${a}`:void 0,downloadUrl:`/api/results/${n}/image/${s}`}}))}function sh(t,n){const r=t.summary??{},o=t.label!=null?String(t.label):null,e=n.length>8?`${n.slice(0,8)}…`:n,i=r.grid??[],a=r.epochs??[],s=[o?`label ${o}`:null,`job ${e}`,String(r.job_type??"cfd_mesh_convergence")].filter(Boolean).join(" · "),m=[r.n_train_total!=null?`n_train ${r.n_train_total}`:null,r.reference_mesh!=null?`ref ${r.reference_mesh}`:null,i.length>=2?`grid ${i[0]}×${i[1]}`:null,a.length>=2?`epochs [${a[0]},${a[1]}]`:null,r.quantization_error!=null?`QE ${Number(r.quantization_error).toFixed(4)}`:null].filter(Boolean).join(" · ");return[s,m].filter(g=>g.length>0)}function ch(t,n){const r=t.summary??{},o=(r.files??[]).filter(d=>/\.(png|pdf|svg)$/i.test(d)),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",m=r.epochs??[],g=[{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:"Epochs",value:m.length>=2?`${m[0]}+${m[1]}`:"N/A"},{label:"QE",value:a},{label:"TE",value:s}],h=ah(o,n);return{type:"barmesh-results-explorer",jobId:n,title:"Mesh Convergence Results",subtitle:String(t.label??""),metadataStrip:sh(t,n),metrics:g,availableFigures:h,defaultFigureKey:ih(h),trainingMonitorUrl:`/viz/barmesh-training-monitor?mode=standalone&job_id=${encodeURIComponent(n)}`,standaloneUrl:`${window.location.origin}/viz/barmesh-results-explorer?mode=standalone&job_id=${encodeURIComponent(n)}`,relatedUrls:{trainingMonitor:`${window.location.origin}/viz/barmesh-training-monitor?mode=standalone&job_id=${encodeURIComponent(n)}`,resultsExplorer:`${window.location.origin}/viz/barmesh-results-explorer?mode=standalone&job_id=${encodeURIComponent(n)}`}}}function uh(t){if(!t||t.length===0){ft.style.display="none",ft.innerHTML="";return}ft.style.display="block",ft.innerHTML=t.map(n=>`<div class="metadata-line">${n}</div>`).join("")}function lh(t){Yf.innerHTML=t.map(n=>`<div class="metric"><span class="metric-label">${n.label}</span><span class="metric-value">${n.value}</span></div>`).join("")}function ol(t){const n=new Map;for(const r of t){const o=r.section??zt(r.key),e=n.get(o)??[];e.push(r),n.set(o,e)}qe.innerHTML="";for(const r of Zn){const o=n.get(r);if(!o||o.length===0)continue;const e=document.createElement("optgroup");e.label=Gf[r];for(const i of o){const a=document.createElement("option");a.value=i.key,a.textContent=`${i.label} (${i.filename})`,i.key===ue?.key&&(a.selected=!0),e.appendChild(a)}qe.appendChild(e)}}qe.addEventListener("change",()=>{const t=ae?.availableFigures.find(n=>n.key===qe.value);t&&al(t)});async function dh(t,n){const r=`${t}/${n}`,o=xo.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 xo.set(r,a),a}}catch{}return null}function mh(t){!re||!ae||re.updateModelContext({content:[{type:"text",text:`User is viewing the "${t.label}" figure (${t.filename}) of mesh convergence job ${ae.jobId}.`}]}).catch(()=>{})}function ph(t){return t.formats.length>1?` · formats: ${t.formats.join(", ")}`:""}function fh(t){const n=/\.(pdf|svg)$/i.test(t.downloadFilename);il.textContent=n?re?"Download PNG (preview)":`Download ${t.downloadFilename.split(".").pop()?.toUpperCase()} (high quality)`:"Download figure"}function hh(t){ne.style.display=t?"block":"none",nh.style.display=t?"none":"block"}async function al(t){ue=t,Xf.textContent=t.label,eh.textContent=`${t.filename}${ph(t)}`,th.textContent=t.caption??"",fh(t),mh(t),qe.value=t.key;let n=!1;if(t.url)ne.src=t.url,n=!0;else if(re&&ae){if(t.key===ae.defaultFigureKey&&Cn)ne.src=Cn,n=!0;else if(/\.(png|svg|jpe?g|webp)$/i.test(t.filename)){ne.removeAttribute("src");const r=await dh(ae.jobId,t.filename);r&&ue?.key===t.key&&(ne.src=r,n=!0)}}else/\.(png|svg|jpe?g|webp)$/i.test(t.filename)&&ne.removeAttribute("src");hh(n),ae&&ol(ae.availableFigures)}function gh(t){return t.standaloneUrl??window.location.href}function vh(t){const n=gh(t);t.standaloneUrl?(ht.href=t.standaloneUrl,ht.style.display="inline-flex"):cl&&(ht.href=window.location.href,ht.style.display="inline-flex");const r=t.relatedUrls?.trainingMonitor??t.trainingMonitorUrl??`/viz/barmesh-training-monitor?mode=standalone&job_id=${encodeURIComponent(t.jobId)}`;On.href=r,On.style.display="inline-flex",On.title="View live or completed training curves for this job",ce.style.display="inline-flex",ce.dataset.url=n;const o=t.relatedUrls?.trainingMonitor??t.trainingMonitorUrl;o&&(jo.href=o,jo.style.display="inline-flex")}function No(t){ae=t,nl.textContent=t.title,rl.textContent=t.subtitle?t.subtitle:`Job ${t.jobId}`,uh(t.metadataStrip),lh(t.metrics),vh(t);const n=t.availableFigures.find(r=>r.key===t.defaultFigureKey)??t.availableFigures[0]??null;ue=n,ol(t.availableFigures),n&&al(n),vt.style.display="none",Qf.style.display="block"}il.addEventListener("click",()=>{if(!ue)return;const t=document.createElement("a");if(ue.downloadUrl&&!re)t.href=ue.downloadUrl,t.download=ue.downloadFilename;else{const n=ne.src;if(!n)return;t.href=n,t.download=ue.filename}t.click()});ce.addEventListener("click",async()=>{const t=ce.dataset.url||ae?.standaloneUrl||window.location.href;try{await navigator.clipboard.writeText(t);const n=ce.textContent;ce.textContent="Copied!",setTimeout(()=>{ce.textContent=n??"Copy explorer URL"},1500)}catch{ce.textContent="Copy failed",setTimeout(()=>{ce.textContent="Copy explorer URL"},1500)}});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 sl=new URLSearchParams(window.location.search),cl=sl.get("mode")==="standalone",gt=sl.get("job_id");if(cl&>){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/${gt}`);if(o.status===404){vt.textContent=`Results for job ${gt} 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();No(ch(e,gt));return}catch(o){if(r<3){await new Promise(e=>setTimeout(e,1e3*r));continue}vt.textContent=`Could not load results from the local viz server (${o instanceof Error?o.message:"error"}). ${t}`}})()}else{const t=new Yi({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&&(nl.textContent="Mesh Convergence Results",rl.textContent=`Loading job ${o}...`)},t.ontoolresult=n=>{const r=n.content?.find(e=>e.type==="image");r&&(Cn=`data:${r.mimeType};base64,${r.data}`);const o=n.structuredContent;if(!o||typeof o!="object"){vt.textContent="The results explorer did not receive structured data.";return}No(o)},t.connect()}</script>
|
|
139
|
+
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:p({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:p({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(),Vf=p({method:c("ui/notifications/host-context-changed"),params:tl.describe("Partial context update containing only changed fields.")});p({method:c("ui/update-model-context"),params:p({content:I(ut).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.")})});p({method:c("ui/initialize"),params:p({appInfo:Nn.describe("App identification (name and version)."),appCapabilities:Ff.describe("Features and capabilities this app provides."),protocolVersion:l().describe("Protocol version this app supports.")})});var Wf=p({protocolVersion:l().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Nn.describe("Host application identification and version."),hostCapabilities:qf.describe("Features and capabilities provided by the host."),hostContext:tl.describe("Rich context about the host environment.")}).passthrough(),Kf={target:"draft-2020-12"};async function wo(t,n){let r=t["~standard"];if(r.jsonSchema)return r.jsonSchema[n](Kf);if(r.vendor==="zod"){let{z:o}=await dl(()=>Promise.resolve().then(()=>Rm),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 So(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 Yi extends zf{_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:Rf,toolinputpartial:Zf,toolresult:Bf,toolcancelled:Cf,hostcontextchanged:Vf};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(n){if(!Yi.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(Tn,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=If(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(m){Object.assign(this,m),i()},remove(){e._registeredTools[n]===s&&(delete e._registeredTools[n],i())},handler:async(m,g)=>{if(!s.enabled)throw Error(`Tool ${n} is disabled`);let h;if(a){let d=s.inputSchema,b=d?await So(d,m??{},`Invalid input for tool ${n}: `):m??{};h=await o(b,g)}else h=await o(g);return s.outputSchema&&!h.isError&&(h.structuredContent=await So(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 wo(e.inputSchema,"input"):{type:"object",properties:{}}};return e.outputSchema&&(i.outputSchema=await wo(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(Lf,(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(Qu,(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(Gu,(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},Un,{onprogress:()=>{},resetTimeoutOnProgress:!0,...r})}async readServerResource(n,r){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:n},Vu,r)}async listServerResources(n,r){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:n},Bu,r)}async createSamplingMessage(n,r){this._assertInitialized("createSamplingMessage");let o=n.tools?el:Xu;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},Df,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},Ei,r)}openLink(n,r){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:n},Pf,r)}sendOpenLink=this.openLink;downloadFile(n,r){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:n},Ef,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},Hf,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 m=Math.ceil(a.getBoundingClientRect().height);a.style.height=s;let g=Math.ceil(window.innerWidth);(g!==r||m!==o)&&(r=g,o=m,this.sendSizeChanged({width:g,height:m}))}))};e();let i=new ResizeObserver(e);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(n=new Nf(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:xf}},Wf,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}}}const Zn=["overview","distances","diagnostics","component"],Gf={overview:"Overview",distances:"Distances",diagnostics:"Diagnostics",component:"Component planes"};function zt(t){return t==="combined"?"overview":t==="overview_distances"||t.startsWith("KL_")||t.startsWith("EMD_")?"distances":t==="learning_curve"||t.startsWith("plot_vol")?"diagnostics":t.startsWith("divergence_")||t.startsWith("plot_")?"component":"diagnostics"}const Io={combined:0,overview_distances:1,KL_ref:10,KL_stepwise:11,KL_asymmetric:12,EMD_ref:13,EMD_stepwise:14,learning_curve:15,plot_vol_all_meshes:19,plot_vol_coarse_fine:20,divergence_kl:28,divergence_w1:29};function zo(t){return t in Io?Io[t]:t.startsWith("plot_vol")?25:t.startsWith("divergence_")?28:t.startsWith("plot_")?30:50}let ae=null,ue=null,re=null,Cn=null;const xo=new Map,vt=document.getElementById("loading"),Qf=document.getElementById("app"),nl=document.getElementById("title"),rl=document.getElementById("subtitle"),ft=document.getElementById("metadata-strip"),Yf=document.getElementById("metrics"),qe=document.getElementById("figure-select"),Xf=document.getElementById("figure-title"),eh=document.getElementById("figure-file"),th=document.getElementById("figure-caption"),ne=document.getElementById("main-image"),nh=document.getElementById("figure-empty"),ht=document.getElementById("open-standalone"),On=document.getElementById("open-monitor-btn"),ce=document.getElementById("copy-url"),jo=document.getElementById("related-monitor"),il=document.getElementById("download-current");function rh(t){return[...t].sort((n,r)=>Zn.indexOf(n.section??zt(n.key))-Zn.indexOf(r.section??zt(r.key))||zo(n.key)-zo(r.key)||n.label.localeCompare(r.label))}function ih(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 oh(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==="KL_asymmetric"?"Asymmetric mesh KL matrix (coarse↔fine)":n==="divergence_kl"?"Feature-plane symmetric KL (not mesh KL)":n==="divergence_w1"?"Feature-plane Wasserstein-1":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 ah(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 rh([...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`,m=/\.(png|svg)$/i.test(a),g=i.filter(h=>h==="pdf"||h==="svg");return{key:o,label:oh(a),filename:a,downloadFilename:s,formats:i,section:zt(o),caption:g.length>0?`High-quality ${g.join("/").toUpperCase()} available to download.`:void 0,url:m?`/api/results/${n}/image/${a}`:void 0,downloadUrl:`/api/results/${n}/image/${s}`}}))}function sh(t,n){const r=t.summary??{},o=t.label!=null?String(t.label):null,e=n.length>8?`${n.slice(0,8)}…`:n,i=r.grid??[],a=r.epochs??[],s=[o?`label ${o}`:null,`job ${e}`,String(r.job_type??"cfd_mesh_convergence")].filter(Boolean).join(" · "),m=[r.n_train_total!=null?`n_train ${r.n_train_total}`:null,r.reference_mesh!=null?`ref ${r.reference_mesh}`:null,i.length>=2?`grid ${i[0]}×${i[1]}`:null,a.length>=2?`epochs [${a[0]},${a[1]}]`:null,r.quantization_error!=null?`QE ${Number(r.quantization_error).toFixed(4)}`:null].filter(Boolean).join(" · ");return[s,m].filter(g=>g.length>0)}function ch(t,n){const r=t.summary??{},o=(r.files??[]).filter(d=>/\.(png|pdf|svg)$/i.test(d)),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",m=r.epochs??[],g=[{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:"Epochs",value:m.length>=2?`${m[0]}+${m[1]}`:"N/A"},{label:"QE",value:a},{label:"TE",value:s}],h=ah(o,n);return{type:"barmesh-results-explorer",jobId:n,title:"Mesh Convergence Results",subtitle:String(t.label??""),metadataStrip:sh(t,n),metrics:g,availableFigures:h,defaultFigureKey:ih(h),trainingMonitorUrl:`/viz/barmesh-training-monitor?mode=standalone&job_id=${encodeURIComponent(n)}`,standaloneUrl:`${window.location.origin}/viz/barmesh-results-explorer?mode=standalone&job_id=${encodeURIComponent(n)}`,relatedUrls:{trainingMonitor:`${window.location.origin}/viz/barmesh-training-monitor?mode=standalone&job_id=${encodeURIComponent(n)}`,resultsExplorer:`${window.location.origin}/viz/barmesh-results-explorer?mode=standalone&job_id=${encodeURIComponent(n)}`}}}function uh(t){if(!t||t.length===0){ft.style.display="none",ft.innerHTML="";return}ft.style.display="block",ft.innerHTML=t.map(n=>`<div class="metadata-line">${n}</div>`).join("")}function lh(t){Yf.innerHTML=t.map(n=>`<div class="metric"><span class="metric-label">${n.label}</span><span class="metric-value">${n.value}</span></div>`).join("")}function ol(t){const n=new Map;for(const r of t){const o=r.section??zt(r.key),e=n.get(o)??[];e.push(r),n.set(o,e)}qe.innerHTML="";for(const r of Zn){const o=n.get(r);if(!o||o.length===0)continue;const e=document.createElement("optgroup");e.label=Gf[r];for(const i of o){const a=document.createElement("option");a.value=i.key,a.textContent=`${i.label} (${i.filename})`,i.key===ue?.key&&(a.selected=!0),e.appendChild(a)}qe.appendChild(e)}}qe.addEventListener("change",()=>{const t=ae?.availableFigures.find(n=>n.key===qe.value);t&&al(t)});async function dh(t,n){const r=`${t}/${n}`,o=xo.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 xo.set(r,a),a}}catch{}return null}function mh(t){!re||!ae||re.updateModelContext({content:[{type:"text",text:`User is viewing the "${t.label}" figure (${t.filename}) of mesh convergence job ${ae.jobId}.`}]}).catch(()=>{})}function ph(t){return t.formats.length>1?` · formats: ${t.formats.join(", ")}`:""}function fh(t){const n=/\.(pdf|svg)$/i.test(t.downloadFilename);il.textContent=n?re?"Download PNG (preview)":`Download ${t.downloadFilename.split(".").pop()?.toUpperCase()} (high quality)`:"Download figure"}function hh(t){ne.style.display=t?"block":"none",nh.style.display=t?"none":"block"}async function al(t){ue=t,Xf.textContent=t.label,eh.textContent=`${t.filename}${ph(t)}`,th.textContent=t.caption??"",fh(t),mh(t),qe.value=t.key;let n=!1;if(t.url)ne.src=t.url,n=!0;else if(re&&ae){if(t.key===ae.defaultFigureKey&&Cn)ne.src=Cn,n=!0;else if(/\.(png|svg|jpe?g|webp)$/i.test(t.filename)){ne.removeAttribute("src");const r=await dh(ae.jobId,t.filename);r&&ue?.key===t.key&&(ne.src=r,n=!0)}}else/\.(png|svg|jpe?g|webp)$/i.test(t.filename)&&ne.removeAttribute("src");hh(n),ae&&ol(ae.availableFigures)}function gh(t){return t.standaloneUrl??window.location.href}function vh(t){const n=gh(t);t.standaloneUrl?(ht.href=t.standaloneUrl,ht.style.display="inline-flex"):cl&&(ht.href=window.location.href,ht.style.display="inline-flex");const r=t.relatedUrls?.trainingMonitor??t.trainingMonitorUrl??`/viz/barmesh-training-monitor?mode=standalone&job_id=${encodeURIComponent(t.jobId)}`;On.href=r,On.style.display="inline-flex",On.title="View live or completed training curves for this job",ce.style.display="inline-flex",ce.dataset.url=n;const o=t.relatedUrls?.trainingMonitor??t.trainingMonitorUrl;o&&(jo.href=o,jo.style.display="inline-flex")}function No(t){ae=t,nl.textContent=t.title,rl.textContent=t.subtitle?t.subtitle:`Job ${t.jobId}`,uh(t.metadataStrip),lh(t.metrics),vh(t);const n=t.availableFigures.find(r=>r.key===t.defaultFigureKey)??t.availableFigures[0]??null;ue=n,ol(t.availableFigures),n&&al(n),vt.style.display="none",Qf.style.display="block"}il.addEventListener("click",()=>{if(!ue)return;const t=document.createElement("a");if(ue.downloadUrl&&!re)t.href=ue.downloadUrl,t.download=ue.downloadFilename;else{const n=ne.src;if(!n)return;t.href=n,t.download=ue.filename}t.click()});ce.addEventListener("click",async()=>{const t=ce.dataset.url||ae?.standaloneUrl||window.location.href;try{await navigator.clipboard.writeText(t);const n=ce.textContent;ce.textContent="Copied!",setTimeout(()=>{ce.textContent=n??"Copy explorer URL"},1500)}catch{ce.textContent="Copy failed",setTimeout(()=>{ce.textContent="Copy explorer URL"},1500)}});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 sl=new URLSearchParams(window.location.search),cl=sl.get("mode")==="standalone",gt=sl.get("job_id");if(cl&>){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/${gt}`);if(o.status===404){vt.textContent=`Results for job ${gt} 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();No(ch(e,gt));return}catch(o){if(r<3){await new Promise(e=>setTimeout(e,1e3*r));continue}vt.textContent=`Could not load results from the local viz server (${o instanceof Error?o.message:"error"}). ${t}`}})()}else{const t=new Yi({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&&(nl.textContent="Mesh Convergence Results",rl.textContent=`Loading job ${o}...`)},t.ontoolresult=n=>{const r=n.content?.find(e=>e.type==="image");r&&(Cn=`data:${r.mimeType};base64,${r.data}`);const o=n.structuredContent;if(!o||typeof o!="object"){vt.textContent="The results explorer did not receive structured data.";return}No(o)},t.connect()}</script>
|
|
140
140
|
</head>
|
|
141
141
|
<body>
|
|
142
142
|
<div id="loading">Loading mesh convergence results...</div>
|