Spaces:
Sleeping
Sleeping
File size: 3,687 Bytes
f2e28ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | // api.js
// ----------------------------------------------------------------------------
// Thin browser client for the FastAPI server in `server/visualizer_server.py`.
//
// - `getModes()` — list available inference modes (LLM + baselines).
// - `getConfig()` — sectors/baselines/etc. for the empty-state shell.
// - `startRun(opts)` — POST /api/runs and return the new {run_id, ...}.
// - `streamRun(runId, on)` — open an EventSource and dispatch typed events.
// - `getSnapshot(runId)` — fetch the full episode-so-far (used on reload).
//
// The visualizer assumes Vite's dev proxy maps /api/* to the backend (see
// `vite.config.js`). In production builds the same path works as long as the
// FastAPI server is reverse-proxied under the same origin as the static UI.
// ----------------------------------------------------------------------------
const BASE = '/api';
async function jsonOrThrow(res) {
if (!res.ok) {
let message = `${res.status} ${res.statusText}`;
try {
const body = await res.json();
if (body?.detail) message = typeof body.detail === 'string' ? body.detail : JSON.stringify(body.detail);
} catch {
/* empty body — fall back to status */
}
throw new Error(message);
}
return res.json();
}
export async function getHealth() {
return jsonOrThrow(await fetch(`${BASE}/health`));
}
export async function getConfig() {
return jsonOrThrow(await fetch(`${BASE}/config`));
}
export async function getModes() {
return jsonOrThrow(await fetch(`${BASE}/modes`));
}
export async function listRuns() {
return jsonOrThrow(await fetch(`${BASE}/runs`));
}
export async function startRun({
mode,
modelId = null,
seed = 42,
maxRounds = 20,
temperature = 0.2,
}) {
const res = await fetch(`${BASE}/runs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mode,
model_id: modelId,
seed,
max_rounds: maxRounds,
temperature,
}),
});
return jsonOrThrow(res);
}
export async function getSnapshot(runId) {
return jsonOrThrow(await fetch(`${BASE}/runs/${runId}/snapshot`));
}
/**
* Open an SSE connection to a live run.
*
* `handlers` may contain any subset of:
* { onStart, onRound, onActivity, onSummary, onError, onDone, onConnectionError }
*
* `onActivity(data)` fires for each within-round action:
* data.kind = 'round_start' | 'debate' | 'proposal' | 'vote'
*
* Returns an object with a `close()` method to tear the connection down.
*/
export function streamRun(runId, handlers = {}) {
const url = `${BASE}/runs/${runId}/stream`;
const source = new EventSource(url);
const route = (type, ev) => {
let data = {};
try {
data = ev.data ? JSON.parse(ev.data) : {};
} catch (err) {
handlers.onConnectionError?.(err);
return;
}
switch (type) {
case 'start':
handlers.onStart?.(data);
break;
case 'activity':
handlers.onActivity?.(data);
break;
case 'round':
handlers.onRound?.(data);
break;
case 'summary':
handlers.onSummary?.(data);
break;
case 'error':
handlers.onError?.(data);
break;
case 'done':
handlers.onDone?.(data);
source.close();
break;
default:
break;
}
};
['start', 'activity', 'round', 'summary', 'error', 'done'].forEach(type => {
source.addEventListener(type, e => route(type, e));
});
source.onerror = err => {
handlers.onConnectionError?.(err);
};
return {
close: () => source.close(),
raw: source,
};
}
|