@barivia/barsom-mcp 0.7.3 → 0.7.12
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 +58 -26
- package/dist/index.js +1 -1
- package/dist/shared.js +107 -33
- package/dist/tools/account.js +9 -1
- package/dist/tools/datasets.js +3 -4
- package/dist/tools/explore_map.js +1 -2
- package/dist/tools/feedback.js +0 -1
- package/dist/tools/guide_barsom.js +14 -42
- package/dist/tools/inference.js +0 -1
- package/dist/tools/jobs.js +35 -11
- package/dist/tools/results.js +0 -1
- package/dist/tools/training_guidance.js +1 -2
- package/dist/tools/training_monitor.js +43 -0
- package/dist/tools/training_prep.js +3 -1
- package/dist/tools/training_review_store.js +0 -1
- package/dist/views/src/views/results-explorer/index.html +1 -1
- package/dist/views/src/views/training-monitor/index.html +12 -12
- package/dist/viz-server.js +0 -1
- package/package.json +10 -4
- package/dist/index.d.ts +0 -25
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/shared.d.ts +0 -176
- package/dist/shared.d.ts.map +0 -1
- package/dist/shared.js.map +0 -1
- package/dist/tools/account.d.ts +0 -3
- package/dist/tools/account.d.ts.map +0 -1
- package/dist/tools/account.js.map +0 -1
- package/dist/tools/datasets.d.ts +0 -11
- package/dist/tools/datasets.d.ts.map +0 -1
- package/dist/tools/datasets.js.map +0 -1
- package/dist/tools/explore_map.d.ts +0 -5
- package/dist/tools/explore_map.d.ts.map +0 -1
- package/dist/tools/explore_map.js.map +0 -1
- package/dist/tools/feedback.d.ts +0 -3
- package/dist/tools/feedback.d.ts.map +0 -1
- package/dist/tools/feedback.js.map +0 -1
- package/dist/tools/guide_barsom.d.ts +0 -3
- package/dist/tools/guide_barsom.d.ts.map +0 -1
- package/dist/tools/guide_barsom.js.map +0 -1
- package/dist/tools/inference.d.ts +0 -3
- package/dist/tools/inference.d.ts.map +0 -1
- package/dist/tools/inference.js.map +0 -1
- package/dist/tools/jobs.d.ts +0 -61
- package/dist/tools/jobs.d.ts.map +0 -1
- package/dist/tools/jobs.js.map +0 -1
- package/dist/tools/results.d.ts +0 -11
- package/dist/tools/results.d.ts.map +0 -1
- package/dist/tools/results.js.map +0 -1
- package/dist/tools/training_guidance.d.ts +0 -3
- package/dist/tools/training_guidance.d.ts.map +0 -1
- package/dist/tools/training_guidance.js.map +0 -1
- package/dist/tools/training_prep.d.ts +0 -4
- package/dist/tools/training_prep.d.ts.map +0 -1
- package/dist/tools/training_prep.js.map +0 -1
- package/dist/tools/training_review_store.d.ts +0 -17
- package/dist/tools/training_review_store.d.ts.map +0 -1
- package/dist/tools/training_review_store.js.map +0 -1
- package/dist/views/src/views/data-preview/index.html +0 -148
- package/dist/views/src/views/map-explorer/index.html +0 -288
- package/dist/views/src/views/som-explorer/index.html +0 -288
- package/dist/viz-server.d.ts +0 -16
- package/dist/viz-server.d.ts.map +0 -1
- package/dist/viz-server.js.map +0 -1
|
@@ -1,47 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
The workflow explains the exact sequence of tool calls needed: Upload → Preprocess → Train → Wait → Analyze.
|
|
5
|
-
For parameter hints (grid, epochs, model, etc.), call the training_guidance tool or use the prepare_training prompt before train_map.`, {}, async () => {
|
|
6
|
-
return {
|
|
7
|
-
content: [
|
|
8
|
-
{
|
|
9
|
-
type: "text",
|
|
10
|
-
text: `Mapping Analysis Standard Operating Procedure (SOP)
|
|
11
|
-
|
|
12
|
-
Step 1: Upload Data
|
|
13
|
-
- Use \`datasets(action=upload)\` with \`file_path\` to your CSV (server reads from workspace root; token-efficient). Use \`csv_data\` only for small inline pastes.
|
|
14
|
-
- BEFORE UPLOADING: Clean the dataset to remove NaNs or malformed data.
|
|
15
|
-
- Capture the \`dataset_id\` returned.
|
|
16
|
-
|
|
17
|
-
Step 2: Preview & Preprocess
|
|
18
|
-
- Use \`datasets(action=preview)\` to inspect columns, ranges, and types.
|
|
19
|
-
- Check for skewed columns requiring 'log' or 'sqrt' transforms.
|
|
20
|
-
- Check for cyclical or temporal features (hours, days) requiring \`cyclic_features\` or \`temporal_features\` during training.
|
|
21
|
-
- Raw categorical/text columns usually stay out of the first map. If one really matters, use explicit baseline \`categorical_features\` encoding rather than assuming advanced categorical embeddings.
|
|
1
|
+
import { fetchWorkflowGuideFromApi } from "../shared.js";
|
|
2
|
+
/** Minimal orientation when the API is unreachable; full SOP lives on GET /v1/docs/workflow. */
|
|
3
|
+
const OFFLINE_STUB = `## Offline / API unavailable
|
|
22
4
|
|
|
23
|
-
|
|
24
|
-
- Call \`jobs(action=train_map, dataset_id=...)\` with the \`dataset_id\`.
|
|
25
|
-
- Carefully select columns to include (start with 5-10).
|
|
26
|
-
- Assign \`feature_weights\` when some numeric or encoded features should matter more than others.
|
|
27
|
-
- Wait for the returned \`job_id\`.
|
|
28
|
-
- For projection and inference, use the same job_id from train_som (or the new job_id returned by recolor/project when applicable).
|
|
5
|
+
Configure \`BARIVIA_API_KEY\` and optional \`BARIVIA_API_URL\`, then call **guide_barsom_workflow** again. Full tool map, async rules, training modes, and step-by-step SOP are loaded from the Barivia API (authenticated) and scoped to your plan.
|
|
29
6
|
|
|
30
|
-
|
|
31
|
-
- Use \`jobs(action=status, job_id=...)\` every 10-15 seconds.
|
|
32
|
-
- Wait until status is "completed". DO NOT assume failure before 3 minutes (or longer for large grids).
|
|
33
|
-
- If it fails, read the error message and adjust parameters (e.g., reduce grid size, fix column names).
|
|
7
|
+
**Core tools:** \`datasets\`, \`jobs\` (train_map, train_siom_map, train_floop_siom where entitled), \`results\`, \`inference\`, \`account\`, \`training_guidance\`, \`guide_barsom_workflow\`.
|
|
34
8
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
9
|
+
**Parameter hints:** call \`training_guidance\` (also API-scoped). **Async:** poll \`jobs(action=status)\` every 10–15s after submit.`;
|
|
10
|
+
export function registerGuideBarsomTool(server) {
|
|
11
|
+
server.tool("guide_barsom_workflow", "Ground-truth orientation for this MCP: proxy model, tool categories, async rules, training modes, and step-by-step SOP — loaded from the Barivia API when online (tier-scoped to allowed_job_types). Call at the start of mapping work. Offline: returns a short stub. For field-level training parameters, use training_guidance or prepare_training.", {}, async () => {
|
|
12
|
+
const md = await fetchWorkflowGuideFromApi();
|
|
13
|
+
if (md) {
|
|
14
|
+
const text = "Source: Barivia API (`GET /v1/docs/workflow`). Text below is scoped to your API key / plan.\n\n" + md;
|
|
15
|
+
return { content: [{ type: "text", text }] };
|
|
16
|
+
}
|
|
17
|
+
return { content: [{ type: "text", text: OFFLINE_STUB }] };
|
|
45
18
|
});
|
|
46
19
|
}
|
|
47
|
-
//# sourceMappingURL=guide_barsom.js.map
|
package/dist/tools/inference.js
CHANGED
package/dist/tools/jobs.js
CHANGED
|
@@ -9,7 +9,8 @@ export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs.
|
|
|
9
9
|
| compare | Picking the best training run from a set of completed jobs |
|
|
10
10
|
| train_map | Submitting a new map training job — returns job_id for polling |
|
|
11
11
|
| train_siom_map | Submitting a self-interacting map training job — same map flow with SIOM coverage control |
|
|
12
|
-
|
|
|
12
|
+
| train_floop_siom | Submitting a FLooP-SIOM job (growing manifold; default topology=free / CHL) — requires Premium or Enterprise plan (all_algorithms) |
|
|
13
|
+
| train_floop_chain | Deprecated alias for train_floop_siom — same behavior; prefer train_floop_siom |
|
|
13
14
|
| cancel | Stopping a running or pending job to free the worker |
|
|
14
15
|
| delete | Permanently removing a job and all its S3 result files |
|
|
15
16
|
|
|
@@ -34,7 +35,9 @@ action=train_map / train_siom_map: Submits a grid-map training job. Returns job_
|
|
|
34
35
|
Presets refined/high_res may use GPU. On CPU-only hosts pass backend=cpu. API expects strings "cpu" | "gpu" | "gpu_graphs" (no colon). Future backends (e.g. non-CUDA) may be added under the same contract.
|
|
35
36
|
normalize: "auto" (default) = scale only non-cyclic features; "all" = scale every feature. Use "auto" when using cyclic_features.
|
|
36
37
|
categorical_features: optional baseline categorical support using explicit weighted one-hot encoding. Provide the raw column name, allowed categories, and a weight. Advanced categorical embeddings are intentionally outside this default tool surface.
|
|
37
|
-
|
|
38
|
+
train_siom_map only: siom_feature_geometry l2 (default) | mixed | auto — torus distance on cyclic (cos,sin) pairs when mixed or auto with cyclic encodings. siom_qe_backend cpu|cuda|auto and siom_qe_batch_size align siom_qe with training geometry when mixed.
|
|
39
|
+
action=train_floop_siom (preferred) or train_floop_chain (deprecated alias): Submits FLooP-SIOM — a growing node-budget manifold instead of a fixed hex grid. **Only if the API key’s plan includes all_algorithms** (Premium or Enterprise); otherwise the API returns a clear upgrade message. Default topology is free (CHL dynamic graph); topology=chain is optional for a strict 1D linked-list backbone.
|
|
40
|
+
Key params: topology (default free), max_nodes, effort, gamma, siom_decay.
|
|
38
41
|
max_nodes is a total node budget, not a grid width or area. If omitted, the backend uses a dataset-size heuristic (roughly 2*sqrt(n_samples), capped for stability).
|
|
39
42
|
effort controls passes only: quick≈15, standard≈30, thorough≈60. If coverage collapses, reduce max_nodes before increasing effort.
|
|
40
43
|
action=compare: Returns a metrics table (QE, TE, explained variance, silhouette) for 2+ jobs.
|
|
@@ -45,7 +48,7 @@ export async function fetchTrainingPresets() {
|
|
|
45
48
|
return configData?.presets || {};
|
|
46
49
|
}
|
|
47
50
|
export function buildTrainMapParams(args, presets) {
|
|
48
|
-
const { preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features, temporal_features, feature_weights, transforms, auto_log_transforms, time_delay_embeddings, categorical_features, normalize, sigma_f, learning_rate, batch_size, quality_metrics, backend, output_format, output_dpi, colormap, row_range, } = args;
|
|
51
|
+
const { preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features, temporal_features, feature_weights, transforms, auto_log_transforms, time_delay_embeddings, categorical_features, normalize, sigma_f, learning_rate, batch_size, quality_metrics, backend, output_format, output_dpi, colormap, row_range, siom_feature_geometry, siom_qe_backend, siom_qe_batch_size, } = args;
|
|
49
52
|
const p = preset ? presets[preset] : undefined;
|
|
50
53
|
const params = {
|
|
51
54
|
model: model ?? "SOM",
|
|
@@ -98,6 +101,12 @@ export function buildTrainMapParams(args, presets) {
|
|
|
98
101
|
params.colormap = colormap;
|
|
99
102
|
if (row_range && row_range.length >= 2 && row_range[0] <= row_range[1])
|
|
100
103
|
params.row_range = row_range;
|
|
104
|
+
if (siom_feature_geometry !== undefined)
|
|
105
|
+
params.siom_feature_geometry = siom_feature_geometry;
|
|
106
|
+
if (siom_qe_backend !== undefined)
|
|
107
|
+
params.siom_qe_backend = siom_qe_backend;
|
|
108
|
+
if (siom_qe_batch_size !== undefined)
|
|
109
|
+
params.siom_qe_batch_size = siom_qe_batch_size;
|
|
101
110
|
const effectiveGrid = params.grid;
|
|
102
111
|
const effectiveEpochs = params.epochs;
|
|
103
112
|
const effectiveBatch = params.batch_size;
|
|
@@ -115,8 +124,8 @@ export function buildTrainMapParams(args, presets) {
|
|
|
115
124
|
export function registerJobsTool(server, description) {
|
|
116
125
|
server.tool("jobs", description, {
|
|
117
126
|
action: z
|
|
118
|
-
.enum(["status", "list", "compare", "cancel", "delete", "train_map", "train_siom_map", "train_floop_chain", "batch_predict", "run_baseline_study"])
|
|
119
|
-
.describe("status: check progress; list: see all jobs; compare: metrics table; cancel: stop job; delete: remove job + files; train_map: submit standard map training; train_siom_map: submit SIOM map training;
|
|
127
|
+
.enum(["status", "list", "compare", "cancel", "delete", "train_map", "train_siom_map", "train_floop_siom", "train_floop_chain", "batch_predict", "run_baseline_study"])
|
|
128
|
+
.describe("status: check progress; list: see all jobs; compare: metrics table; cancel: stop job; delete: remove job + files; train_map: submit standard map training; train_siom_map: submit SIOM map training; train_floop_siom: submit FLooP-SIOM (preferred); train_floop_chain: deprecated alias for train_floop_siom; batch_predict: submit multiple predict jobs at once; run_baseline_study: auto-configure and train a baseline SOM"),
|
|
120
129
|
job_id: z
|
|
121
130
|
.string()
|
|
122
131
|
.optional()
|
|
@@ -197,6 +206,9 @@ export function registerJobsTool(server, description) {
|
|
|
197
206
|
siom_penalty: z.enum(["linear", "log", "exp"]).optional(),
|
|
198
207
|
penalty_alpha: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
199
208
|
reset_per_epoch: z.boolean().optional(),
|
|
209
|
+
siom_feature_geometry: z.enum(["l2", "mixed", "auto"]).optional().describe("train_siom_map: l2 (default) = legacy L2 on cos/sin columns; mixed = cyclic torus distance when cyclic pairs exist; auto = mixed only when cyclic encodings are present"),
|
|
210
|
+
siom_qe_backend: z.enum(["cpu", "cuda", "auto"]).optional().describe("train_siom_map: backend for metric-aligned siom_qe when using mixed geometry; auto uses GPU when available"),
|
|
211
|
+
siom_qe_batch_size: z.number().int().min(1).max(1_000_000).optional().describe("train_siom_map: batch size for GPU SIOM QE (default 256 on worker)"),
|
|
200
212
|
topology: z.enum(["free", "chain"]).optional().default("free"),
|
|
201
213
|
max_nodes: z.number().int().min(2).optional(),
|
|
202
214
|
effort: z.enum(["quick", "standard", "thorough"]).optional().default("standard"),
|
|
@@ -240,10 +252,11 @@ export function registerJobsTool(server, description) {
|
|
|
240
252
|
colormap: "coolwarm"
|
|
241
253
|
};
|
|
242
254
|
const data = (await apiCall("POST", "/v1/jobs", { dataset_id, params }));
|
|
243
|
-
|
|
255
|
+
const jid = String(data.id ?? "");
|
|
256
|
+
return { content: [{ type: "text", text: `Baseline study submitted. Job ID: ${jid}\nGrid size: ${side}x${side}\nNormalization: MAD\n\nPoll with jobs(action=status, job_id="${jid}") until complete, then retrieve with results(action=get, job_id="${jid}"). Optional: training_monitor(job_id="${jid}") for a visual panel—not required.` }] };
|
|
244
257
|
}
|
|
245
258
|
if (action === "train_map" || action === "train_siom_map") {
|
|
246
|
-
const { preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features, temporal_features, feature_weights, transforms, auto_log_transforms, time_delay_embeddings, categorical_features, normalize, sigma_f, learning_rate, batch_size, quality_metrics, backend, output_format, output_dpi, colormap, row_range, gamma, gamma_f, siom_decay, siom_penalty, penalty_alpha, reset_per_epoch, } = args;
|
|
259
|
+
const { preset, grid_x, grid_y, epochs, model, periodic, columns, cyclic_features, temporal_features, feature_weights, transforms, auto_log_transforms, time_delay_embeddings, categorical_features, normalize, sigma_f, learning_rate, batch_size, quality_metrics, backend, output_format, output_dpi, colormap, row_range, gamma, gamma_f, siom_decay, siom_penalty, penalty_alpha, reset_per_epoch, siom_feature_geometry, siom_qe_backend, siom_qe_batch_size, } = args;
|
|
247
260
|
let PRESETS = {};
|
|
248
261
|
try {
|
|
249
262
|
PRESETS = await fetchTrainingPresets();
|
|
@@ -278,6 +291,12 @@ export function registerJobsTool(server, description) {
|
|
|
278
291
|
params.penalty_alpha = penalty_alpha;
|
|
279
292
|
if (reset_per_epoch !== undefined)
|
|
280
293
|
params.reset_per_epoch = reset_per_epoch;
|
|
294
|
+
if (siom_feature_geometry !== undefined)
|
|
295
|
+
params.siom_feature_geometry = siom_feature_geometry;
|
|
296
|
+
if (siom_qe_backend !== undefined)
|
|
297
|
+
params.siom_qe_backend = siom_qe_backend;
|
|
298
|
+
if (siom_qe_batch_size !== undefined)
|
|
299
|
+
params.siom_qe_batch_size = siom_qe_batch_size;
|
|
281
300
|
}
|
|
282
301
|
let totalRows = 0;
|
|
283
302
|
try {
|
|
@@ -299,6 +318,7 @@ export function registerJobsTool(server, description) {
|
|
|
299
318
|
if (waitMinutes > 1)
|
|
300
319
|
msg += `You are #${pending + 1} in queue. Estimated wait: ~${waitMinutes} min. `;
|
|
301
320
|
msg += `Poll with jobs(action=status, job_id="${newJobId}"). When status is completed, use results(action=get, job_id="${newJobId}") to view the map and metrics.`;
|
|
321
|
+
msg += ` Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
302
322
|
if (effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75) {
|
|
303
323
|
msg += ` Note: Grid may be large for ${totalRows} rows (consider grid=auto for fewer dead nodes).`;
|
|
304
324
|
}
|
|
@@ -306,6 +326,7 @@ export function registerJobsTool(server, description) {
|
|
|
306
326
|
}
|
|
307
327
|
catch {
|
|
308
328
|
let msg = `Job submitted (${variantPrefix}, ${paramSummary}). Poll with jobs(action=status, job_id="${newJobId}"). When status is completed, use results(action=get, job_id="${newJobId}") to view the map and metrics.`;
|
|
329
|
+
msg += ` Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
|
|
309
330
|
if (effectiveGrid && totalRows > 0 && effectiveGrid[0] * effectiveGrid[1] > totalRows * 0.75) {
|
|
310
331
|
msg += ` Note: Grid may be large for ${totalRows} rows (consider grid=auto for fewer dead nodes).`;
|
|
311
332
|
}
|
|
@@ -313,10 +334,10 @@ export function registerJobsTool(server, description) {
|
|
|
313
334
|
}
|
|
314
335
|
return textResult(data);
|
|
315
336
|
}
|
|
316
|
-
if (action === "train_floop_chain") {
|
|
337
|
+
if (action === "train_floop_siom" || action === "train_floop_chain") {
|
|
317
338
|
const { columns, cyclic_features, temporal_features, feature_weights, transforms, auto_log_transforms, normalize, row_range, output_format, output_dpi, colormap, topology, max_nodes, effort, n_initial, n_passes, growth_interval, neighborhood_size, edge_max_age, max_degree, gamma, siom_decay, siom_penalty, penalty_alpha, error_threshold, error_decay, ring_close_threshold, sigma_0, sigma_f, eta_0, eta_f, elastic_lambda, elastic_mu, elastic_anchor, anchor_percentile, } = args;
|
|
318
339
|
if (!dataset_id)
|
|
319
|
-
throw new Error("jobs(
|
|
340
|
+
throw new Error("jobs(train_floop_siom) requires dataset_id");
|
|
320
341
|
const params = {
|
|
321
342
|
_job_type: "train_floop_siom",
|
|
322
343
|
topology,
|
|
@@ -400,7 +421,11 @@ export function registerJobsTool(server, description) {
|
|
|
400
421
|
data.effective_params = paramSummary;
|
|
401
422
|
data.message =
|
|
402
423
|
`Job submitted (${paramSummary}). Poll with jobs(action=status, job_id="${newJobId}"). ` +
|
|
403
|
-
`When status is completed, use results(action=get, job_id="${newJobId}") to view
|
|
424
|
+
`When status is completed, use results(action=get, job_id="${newJobId}") to view FLooP-SIOM figures and metrics. ` +
|
|
425
|
+
`Optional: training_monitor(job_id="${newJobId}") for charts—not required.` +
|
|
426
|
+
(action === "train_floop_chain"
|
|
427
|
+
? " Note: action=train_floop_chain is deprecated; prefer train_floop_siom."
|
|
428
|
+
: "");
|
|
404
429
|
return textResult(data);
|
|
405
430
|
}
|
|
406
431
|
if (action === "status") {
|
|
@@ -487,4 +512,3 @@ export function registerJobsTool(server, description) {
|
|
|
487
512
|
throw new Error("Invalid action");
|
|
488
513
|
});
|
|
489
514
|
}
|
|
490
|
-
//# sourceMappingURL=jobs.js.map
|
package/dist/tools/results.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { fetchTrainingGuidanceFromApi } from "../shared.js";
|
|
2
2
|
export function registerTrainingGuidanceTool(server) {
|
|
3
|
-
server.tool("training_guidance", "Returns parameter guidance for jobs(
|
|
3
|
+
server.tool("training_guidance", "Returns parameter guidance for training jobs (grid, epochs, batch, model, periodic, SIOM/FLooP fields where your plan allows, categorical baselines). Domain knowledge is served from GET /v1/training/config and filtered to your allowed_job_types; requires valid API key. For full proxy orientation and SOP, call guide_barsom_workflow (loads GET /v1/docs/workflow). Optional visual workflows: training_prep (prep), results_explorer (postprocess)—not required; jobs + results are sufficient.", {}, async () => {
|
|
4
4
|
const text = await fetchTrainingGuidanceFromApi();
|
|
5
5
|
return { content: [{ type: "text", text }] };
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
-
//# sourceMappingURL=training_guidance.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
3
|
+
import { apiCall, getClientSupportsMcpApps, getVizPort, structuredTextResult, } from "../shared.js";
|
|
4
|
+
export const TRAINING_MONITOR_URI = "ui://barsom/training-monitor";
|
|
5
|
+
function buildStructuredContent(job_id, data) {
|
|
6
|
+
const id = String(data.id ?? job_id);
|
|
7
|
+
return {
|
|
8
|
+
...data,
|
|
9
|
+
type: "training-monitor",
|
|
10
|
+
jobId: id,
|
|
11
|
+
id,
|
|
12
|
+
job_id: id,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function registerTrainingMonitorTool(server) {
|
|
16
|
+
registerAppTool(server, "training_monitor", {
|
|
17
|
+
title: "Training Monitor",
|
|
18
|
+
description: "Optional embedded or standalone view of training progress for a job_id. Same information is available via jobs(action=status, job_id=...)—use this when the user wants charts or a live panel; not required to complete training or read results.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
job_id: z.string().describe("Training job ID to monitor"),
|
|
21
|
+
},
|
|
22
|
+
_meta: { ui: { resourceUri: TRAINING_MONITOR_URI } },
|
|
23
|
+
}, async ({ job_id }) => {
|
|
24
|
+
const data = (await apiCall("GET", `/v1/jobs/${job_id}`));
|
|
25
|
+
const structuredContent = buildStructuredContent(job_id, data);
|
|
26
|
+
const status = String(data.status ?? "unknown");
|
|
27
|
+
const progress = (data.progress ?? 0) * 100;
|
|
28
|
+
const text = `Training monitor snapshot: job ${job_id} — ${status} (${progress.toFixed(1)}%). ` +
|
|
29
|
+
`Optional UI; polling jobs(action=status) is enough to finish the workflow.`;
|
|
30
|
+
const content = [{ type: "text", text }];
|
|
31
|
+
const port = getVizPort();
|
|
32
|
+
const standaloneUrl = port
|
|
33
|
+
? `http://localhost:${port}/viz/training-monitor?mode=standalone&job_id=${encodeURIComponent(job_id)}`
|
|
34
|
+
: undefined;
|
|
35
|
+
if (!getClientSupportsMcpApps() && standaloneUrl) {
|
|
36
|
+
content.push({
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `Open in browser: ${standaloneUrl}`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return structuredTextResult(structuredContent, text, content);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -315,6 +315,9 @@ export function registerTrainingPrepTools(server) {
|
|
|
315
315
|
backend: z.enum(["auto", "cpu", "gpu", "gpu_graphs"]).optional().default("auto"),
|
|
316
316
|
output_format: z.enum(["png", "pdf", "svg"]).optional().default("png"),
|
|
317
317
|
row_range: z.tuple([z.number().int().min(1), z.number().int().min(1)]).optional(),
|
|
318
|
+
siom_feature_geometry: z.enum(["l2", "mixed", "auto"]).optional(),
|
|
319
|
+
siom_qe_backend: z.enum(["cpu", "cuda", "auto"]).optional(),
|
|
320
|
+
siom_qe_batch_size: z.number().int().min(1).max(1_000_000).optional(),
|
|
318
321
|
};
|
|
319
322
|
registerAppTool(server, "training_prep", {
|
|
320
323
|
title: "Training Preparation",
|
|
@@ -350,4 +353,3 @@ export function registerTrainingPrepTools(server) {
|
|
|
350
353
|
}, `Training job submitted from reviewed prep. Job ID: ${jobId}\nPoll with jobs(action=status, job_id="${jobId}") until complete, then use results(action=get, job_id="${jobId}").`);
|
|
351
354
|
});
|
|
352
355
|
}
|
|
353
|
-
//# sourceMappingURL=training_prep.js.map
|
|
@@ -308,7 +308,7 @@ Note: This type uses \`Record<K, string | undefined>\` rather than \`Partial<Rec
|
|
|
308
308
|
for compatibility with Zod schema generation. Both are functionally equivalent for validation.`);c.object({method:c.literal("ui/open-link"),params:c.object({url:c.string().describe("URL to open in the host's browser")})});var rI=c.object({isError:c.boolean().optional().describe("True if the host failed to open the URL (e.g., due to security policy).")}).passthrough(),aI=c.object({isError:c.boolean().optional().describe("True if the host rejected or failed to deliver the message.")}).passthrough();c.object({method:c.literal("ui/notifications/sandbox-proxy-ready"),params:c.object({})});var io=c.object({connectDomains:c.array(c.string()).optional().describe("Origins for network requests (fetch/XHR/WebSocket)."),resourceDomains:c.array(c.string()).optional().describe("Origins for static resources (scripts, images, styles, fonts)."),frameDomains:c.array(c.string()).optional().describe("Origins for nested iframes (frame-src directive)."),baseUriDomains:c.array(c.string()).optional().describe("Allowed base URIs for the document (base-uri directive).")}),no=c.object({camera:c.object({}).optional().describe("Request camera access (Permission Policy `camera` feature)."),microphone:c.object({}).optional().describe("Request microphone access (Permission Policy `microphone` feature)."),geolocation:c.object({}).optional().describe("Request geolocation access (Permission Policy `geolocation` feature)."),clipboardWrite:c.object({}).optional().describe("Request clipboard write access (Permission Policy `clipboard-write` feature).")});c.object({method:c.literal("ui/notifications/size-changed"),params:c.object({width:c.number().optional().describe("New width in pixels."),height:c.number().optional().describe("New height in pixels.")})});var oI=c.object({method:c.literal("ui/notifications/tool-input"),params:c.object({arguments:c.record(c.string(),c.unknown().describe("Complete tool call arguments as key-value pairs.")).optional().describe("Complete tool call arguments as key-value pairs.")})}),sI=c.object({method:c.literal("ui/notifications/tool-input-partial"),params:c.object({arguments:c.record(c.string(),c.unknown().describe("Partial tool call arguments (incomplete, may change).")).optional().describe("Partial tool call arguments (incomplete, may change).")})}),uI=c.object({method:c.literal("ui/notifications/tool-cancelled"),params:c.object({reason:c.string().optional().describe('Optional reason for the cancellation (e.g., "user action", "timeout").')})}),lI=c.object({fonts:c.string().optional()}),cI=c.object({variables:nI.optional().describe("CSS variables for theming the app."),css:lI.optional().describe("CSS blocks that apps can inject.")}),dI=c.object({method:c.literal("ui/resource-teardown"),params:c.object({})});c.record(c.string(),c.unknown());var Vo=c.object({text:c.object({}).optional().describe("Host supports text content blocks."),image:c.object({}).optional().describe("Host supports image content blocks."),audio:c.object({}).optional().describe("Host supports audio content blocks."),resource:c.object({}).optional().describe("Host supports resource content blocks."),resourceLink:c.object({}).optional().describe("Host supports resource link content blocks."),structuredContent:c.object({}).optional().describe("Host supports structured content.")}),mI=c.object({experimental:c.object({}).optional().describe("Experimental features (structure TBD)."),openLinks:c.object({}).optional().describe("Host supports opening external URLs."),serverTools:c.object({listChanged:c.boolean().optional().describe("Host supports tools/list_changed notifications.")}).optional().describe("Host can proxy tool calls to the MCP server."),serverResources:c.object({listChanged:c.boolean().optional().describe("Host supports resources/list_changed notifications.")}).optional().describe("Host can proxy resource reads to the MCP server."),logging:c.object({}).optional().describe("Host accepts log messages."),sandbox:c.object({permissions:no.optional().describe("Permissions granted by the host (camera, microphone, geolocation)."),csp:io.optional().describe("CSP domains approved by the host.")}).optional().describe("Sandbox configuration applied by the host."),updateModelContext:Vo.optional().describe("Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns."),message:Vo.optional().describe("Host supports receiving content messages (ui/message) from the view.")}),fI=c.object({experimental:c.object({}).optional().describe("Experimental features (structure TBD)."),tools:c.object({listChanged:c.boolean().optional().describe("App supports tools/list_changed notifications.")}).optional().describe("App exposes MCP-style tools that the host can call."),availableDisplayModes:c.array(gt).optional().describe("Display modes the app supports.")});c.object({method:c.literal("ui/notifications/initialized"),params:c.object({}).optional()});c.object({csp:io.optional().describe("Content Security Policy configuration."),permissions:no.optional().describe("Sandbox permissions requested by the UI."),domain:c.string().optional().describe("Dedicated origin for view sandbox."),prefersBorder:c.boolean().optional().describe("Visual boundary preference - true if UI prefers a visible border.")});c.object({method:c.literal("ui/request-display-mode"),params:c.object({mode:gt.describe("The display mode being requested.")})});var pI=c.object({mode:gt.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),vI=c.union([c.literal("model"),c.literal("app")]).describe("Tool visibility scope - who can access the tool.");c.object({resourceUri:c.string().optional(),visibility:c.array(vI).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
309
309
|
- "model": Tool visible to and callable by the agent
|
|
310
310
|
- "app": Tool callable by the app from this server only`)});c.object({mimeTypes:c.array(c.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')});c.object({method:c.literal("ui/message"),params:c.object({role:c.literal("user").describe('Message role, currently only "user" is supported.'),content:c.array(zt).describe("Message content blocks (text, image, etc.).")})});c.object({method:c.literal("ui/notifications/sandbox-resource-ready"),params:c.object({html:c.string().describe("HTML content to load into the inner iframe."),sandbox:c.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:io.optional().describe("CSP configuration from resource metadata."),permissions:no.optional().describe("Sandbox permissions from resource metadata.")})});var hI=c.object({method:c.literal("ui/notifications/tool-result"),params:Zi.describe("Standard MCP tool execution result.")}),zf=c.object({toolInfo:c.object({id:yt.optional().describe("JSON-RPC id of the tools/call request."),tool:or.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:tI.optional().describe("Current color theme preference."),styles:cI.optional().describe("Style configuration for theming the app."),displayMode:gt.optional().describe("How the UI is currently displayed."),availableDisplayModes:c.array(gt).optional().describe("Display modes the host supports."),containerDimensions:c.union([c.object({height:c.number().describe("Fixed container height in pixels.")}),c.object({maxHeight:c.union([c.number(),c.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(c.union([c.object({width:c.number().describe("Fixed container width in pixels.")}),c.object({maxWidth:c.union([c.number(),c.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
311
|
-
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:c.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:c.string().optional().describe("User's timezone in IANA format."),userAgent:c.string().optional().describe("Host application identifier."),platform:c.union([c.literal("web"),c.literal("desktop"),c.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:c.object({touch:c.boolean().optional().describe("Whether the device supports touch input."),hover:c.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:c.object({top:c.number().describe("Top safe area inset in pixels."),right:c.number().describe("Right safe area inset in pixels."),bottom:c.number().describe("Bottom safe area inset in pixels."),left:c.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),gI=c.object({method:c.literal("ui/notifications/host-context-changed"),params:zf.describe("Partial context update containing only changed fields.")});c.object({method:c.literal("ui/update-model-context"),params:c.object({content:c.array(zt).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:c.record(c.string(),c.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});c.object({method:c.literal("ui/initialize"),params:c.object({appInfo:xi.describe("App identification (name and version)."),appCapabilities:fI.describe("Features and capabilities this app provides."),protocolVersion:c.string().describe("Protocol version this app supports.")})});var _I=c.object({protocolVersion:c.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:xi.describe("Host application identification and version."),hostCapabilities:mI.describe("Features and capabilities provided by the host."),hostContext:zf.describe("Rich context about the host environment.")}).passthrough();class $I extends b${_appInfo;_capabilities;options;_hostCapabilities;_hostInfo;_hostContext;constructor(t,n={},a={autoResize:!0}){super(a),this._appInfo=t,this._capabilities=n,this.options=a,this.setRequestHandler(zi,i=>(console.log("Received ping:",i.params),{})),this.onhostcontextchanged=()=>{}}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}set ontoolinput(t){this.setNotificationHandler(oI,n=>t(n.params))}set ontoolinputpartial(t){this.setNotificationHandler(sI,n=>t(n.params))}set ontoolresult(t){this.setNotificationHandler(hI,n=>t(n.params))}set ontoolcancelled(t){this.setNotificationHandler(uI,n=>t(n.params))}set onhostcontextchanged(t){this.setNotificationHandler(gI,n=>{this._hostContext={...this._hostContext,...n.params},t(n.params)})}set onteardown(t){this.setRequestHandler(dI,(n,a)=>t(n.params,a))}set oncalltool(t){this.setRequestHandler(Rs,(n,a)=>t(n.params,a))}set onlisttools(t){this.setRequestHandler(Ds,(n,a)=>t(n.params,a))}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${t} registered`)}}assertNotificationCapability(t){}assertTaskCapability(t){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(t){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(t,n){return await this.request({method:"tools/call",params:t},Zi,n)}sendMessage(t,n){return this.request({method:"ui/message",params:t},aI,n)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,n){return this.request({method:"ui/update-model-context",params:t},Vn,n)}openLink(t,n){return this.request({method:"ui/open-link",params:t},rI,n)}sendOpenLink=this.openLink;requestDisplayMode(t,n){return this.request({method:"ui/request-display-mode",params:t},pI,n)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,n=0,a=0,i=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let o=document.documentElement,s=o.style.width,u=o.style.height;o.style.width="fit-content",o.style.height="fit-content";let l=o.getBoundingClientRect();o.style.width=s,o.style.height=u;let d=window.innerWidth-o.clientWidth,f=Math.ceil(l.width+d),p=Math.ceil(l.height);(f!==n||p!==a)&&(n=f,a=p,this.sendSizeChanged({width:f,height:p}))}))};i();let r=new ResizeObserver(i);return r.observe(document.documentElement),r.observe(document.body),()=>r.disconnect()}async connect(t=new k$(window.parent,window.parent),n){await super.connect(t);try{let a=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:I$}},_I,n);if(a===void 0)throw Error(`Server sent invalid initialize result: ${a}`);this._hostCapabilities=a.hostCapabilities,this._hostInfo=a.hostInfo,this._hostContext=a.hostContext,await this.notification({method:"ui/notifications/initialized"}),this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(a){throw this.close(),a}}}let Ee=null,it=null,yi=null,Pn=null;const Wo=new Map,jn=document.getElementById("loading"),bI=document.getElementById("app"),yI=document.getElementById("title"),kI=document.getElementById("subtitle"),II=document.getElementById("metrics"),wI=document.getElementById("highlights"),Bo=document.getElementById("figures"),SI=document.getElementById("figure-title"),xI=document.getElementById("figure-file"),zI=document.getElementById("figure-caption"),Be=document.getElementById("main-image"),Ho=document.getElementById("open-standalone"),ZI=document.getElementById("download-current");function UI(e){const t=String(e.job_type??"train_som"),n=e.grid??[],a=e.quantization_error!=null?Number(e.quantization_error).toFixed(4):"N/A",i=e.topographic_error!=null?Number(e.topographic_error).toFixed(4):"N/A",r=String(e.n_samples??"N/A"),o=String(e.n_features??"N/A"),s=e.coverage??{},u=l=>l!=null?`${(Number(l)*100).toFixed(1)}%`:"N/A";return t==="train_floop_siom"?[{label:"Topology",value:String(e.topology??"
|
|
311
|
+
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:c.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:c.string().optional().describe("User's timezone in IANA format."),userAgent:c.string().optional().describe("Host application identifier."),platform:c.union([c.literal("web"),c.literal("desktop"),c.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:c.object({touch:c.boolean().optional().describe("Whether the device supports touch input."),hover:c.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:c.object({top:c.number().describe("Top safe area inset in pixels."),right:c.number().describe("Right safe area inset in pixels."),bottom:c.number().describe("Bottom safe area inset in pixels."),left:c.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),gI=c.object({method:c.literal("ui/notifications/host-context-changed"),params:zf.describe("Partial context update containing only changed fields.")});c.object({method:c.literal("ui/update-model-context"),params:c.object({content:c.array(zt).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:c.record(c.string(),c.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});c.object({method:c.literal("ui/initialize"),params:c.object({appInfo:xi.describe("App identification (name and version)."),appCapabilities:fI.describe("Features and capabilities this app provides."),protocolVersion:c.string().describe("Protocol version this app supports.")})});var _I=c.object({protocolVersion:c.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:xi.describe("Host application identification and version."),hostCapabilities:mI.describe("Features and capabilities provided by the host."),hostContext:zf.describe("Rich context about the host environment.")}).passthrough();class $I extends b${_appInfo;_capabilities;options;_hostCapabilities;_hostInfo;_hostContext;constructor(t,n={},a={autoResize:!0}){super(a),this._appInfo=t,this._capabilities=n,this.options=a,this.setRequestHandler(zi,i=>(console.log("Received ping:",i.params),{})),this.onhostcontextchanged=()=>{}}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}set ontoolinput(t){this.setNotificationHandler(oI,n=>t(n.params))}set ontoolinputpartial(t){this.setNotificationHandler(sI,n=>t(n.params))}set ontoolresult(t){this.setNotificationHandler(hI,n=>t(n.params))}set ontoolcancelled(t){this.setNotificationHandler(uI,n=>t(n.params))}set onhostcontextchanged(t){this.setNotificationHandler(gI,n=>{this._hostContext={...this._hostContext,...n.params},t(n.params)})}set onteardown(t){this.setRequestHandler(dI,(n,a)=>t(n.params,a))}set oncalltool(t){this.setRequestHandler(Rs,(n,a)=>t(n.params,a))}set onlisttools(t){this.setRequestHandler(Ds,(n,a)=>t(n.params,a))}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${t} registered`)}}assertNotificationCapability(t){}assertTaskCapability(t){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(t){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(t,n){return await this.request({method:"tools/call",params:t},Zi,n)}sendMessage(t,n){return this.request({method:"ui/message",params:t},aI,n)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,n){return this.request({method:"ui/update-model-context",params:t},Vn,n)}openLink(t,n){return this.request({method:"ui/open-link",params:t},rI,n)}sendOpenLink=this.openLink;requestDisplayMode(t,n){return this.request({method:"ui/request-display-mode",params:t},pI,n)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,n=0,a=0,i=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let o=document.documentElement,s=o.style.width,u=o.style.height;o.style.width="fit-content",o.style.height="fit-content";let l=o.getBoundingClientRect();o.style.width=s,o.style.height=u;let d=window.innerWidth-o.clientWidth,f=Math.ceil(l.width+d),p=Math.ceil(l.height);(f!==n||p!==a)&&(n=f,a=p,this.sendSizeChanged({width:f,height:p}))}))};i();let r=new ResizeObserver(i);return r.observe(document.documentElement),r.observe(document.body),()=>r.disconnect()}async connect(t=new k$(window.parent,window.parent),n){await super.connect(t);try{let a=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:I$}},_I,n);if(a===void 0)throw Error(`Server sent invalid initialize result: ${a}`);this._hostCapabilities=a.hostCapabilities,this._hostInfo=a.hostInfo,this._hostContext=a.hostContext,await this.notification({method:"ui/notifications/initialized"}),this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(a){throw this.close(),a}}}let Ee=null,it=null,yi=null,Pn=null;const Wo=new Map,jn=document.getElementById("loading"),bI=document.getElementById("app"),yI=document.getElementById("title"),kI=document.getElementById("subtitle"),II=document.getElementById("metrics"),wI=document.getElementById("highlights"),Bo=document.getElementById("figures"),SI=document.getElementById("figure-title"),xI=document.getElementById("figure-file"),zI=document.getElementById("figure-caption"),Be=document.getElementById("main-image"),Ho=document.getElementById("open-standalone"),ZI=document.getElementById("download-current");function UI(e){const t=String(e.job_type??"train_som"),n=e.grid??[],a=e.quantization_error!=null?Number(e.quantization_error).toFixed(4):"N/A",i=e.topographic_error!=null?Number(e.topographic_error).toFixed(4):"N/A",r=String(e.n_samples??"N/A"),o=String(e.n_features??"N/A"),s=e.coverage??{},u=l=>l!=null?`${(Number(l)*100).toFixed(1)}%`:"N/A";return t==="train_floop_siom"?[{label:"Topology",value:String(e.topology??"free")},{label:"Nodes",value:String(e.occupied_nodes??e.final_length??"N/A")},{label:"QE",value:a},{label:"TE",value:i},{label:"Utilization",value:u(s.utilization)},{label:"Dead",value:u(s.dead_fraction)},{label:"Coverage",value:String(s.status??"N/A")},{label:"Samples",value:r},{label:"Features",value:o}]:t==="train_siom"?[{label:"Grid",value:n.length>=2?`${n[0]}×${n[1]}`:"N/A"},{label:"Model",value:"SIOM"},{label:"QE",value:a},{label:"TE",value:i},{label:"Utilization",value:u(s.utilization)},{label:"Dead",value:u(s.dead_fraction)},{label:"Coverage",value:String(s.status??"N/A")},{label:"Samples",value:r},{label:"Features",value:o}]:[{label:"Grid",value:n.length>=2?`${n[0]}×${n[1]}`:"N/A"},{label:"Model",value:String(e.model??"SOM")},{label:"QE",value:a},{label:"TE",value:i},{label:"Samples",value:r},{label:"Features",value:o}]}function OI(e,t){const n=e.summary??{},a=(n.files??[]).filter(s=>/\.(png|pdf|svg)$/i.test(s)),i=String(n.output_format??"png"),o=(a.length>0?a:[`combined.${i}`]).map(s=>{const u=s.replace(/\.(png|pdf|svg)$/i,""),l=u.startsWith("component_")?"component":u==="combined"||u==="coverage_overview"?"summary":"diagnostic";return{key:u,label:u.replace(/^component_\d+_/,"Component: ").replace(/_/g," "),filename:s,kind:l,caption:/\.(png|jpe?g|gif|webp)$/i.test(s)?void 0:"This figure is available for download, but it is not inline-displayable in this first iteration.",url:/\.(png|jpe?g|gif|webp)$/i.test(s)?`/api/results/${t}/image/${s}`:void 0}});return{type:"results-explorer",jobId:t,title:"Results Explorer",subtitle:String(e.label??""),metrics:UI(n),highlights:[n.training_duration_seconds!=null?`Training duration: ${n.training_duration_seconds}s`:"",n.selected_columns?`Variables: ${n.selected_columns.join(", ")}`:""].filter(Boolean),availableFigures:o,defaultFigureKey:o.find(s=>s.key==="combined"&&s.url)?.key??o.find(s=>!!s.url)?.key??o.find(s=>s.key==="combined")?.key??o[0]?.key}}function TI(e){II.innerHTML=e.map(t=>`<div class="metric"><span class="metric-label">${t.label}</span><span class="metric-value">${t.value}</span></div>`).join("")}function NI(e){wI.innerHTML=e.length>0?e.map(t=>`<div class="highlight">${t}</div>`).join(""):'<div class="highlight">No extra highlights for this result yet.</div>'}const PI={summary:"Summary",diagnostic:"Diagnostics",component:"Components",artifact:"Other"};function Zf(e){const t=new Map;for(const i of e){const r=i.kind??"diagnostic",o=t.get(r)??[];o.push(i),t.set(r,o)}const n=["summary","diagnostic","component","artifact"];let a="";for(const i of n){const r=t.get(i);if(!(!r||r.length===0)){t.size>1&&(a+=`<div class="figure-group-label">${PI[i]??i}</div>`);for(const o of r)a+=`
|
|
312
312
|
<button class="figure-button${o.key===it?.key?" active":""}" type="button" data-figure-key="${o.key}">
|
|
313
313
|
${o.label}
|
|
314
314
|
<small>${o.filename}</small>
|