Spaces:
Sleeping
Sleeping
| /* Matrix Context — live API adapter (Phase 0, Compatible Mode). | |
| * | |
| * Drop-in replacement for the design's mock `window.MC`: same method names, | |
| * real `fetch` against the same-origin `/v1` surface. This is the ONE place that | |
| * maps the backend response shapes to what the UI consumes: | |
| * - remember -> read res.item (backend returns {item:{…}}) | |
| * - inspect -> res.routing.* + res.pack.* (selected/unselected/scores/…) | |
| * - version -> contract_version / implementation_version | |
| * - scopes -> plain strings | |
| * No contract changes; works against MoC Contract v1 as-is. | |
| */ | |
| (function () { | |
| const BASE = location.origin + "/v1"; | |
| // Optional bearer token (Phase 1 servers may require it; harmless if unset). | |
| let TOKEN = (window.localStorage && localStorage.getItem("mc_token")) || ""; | |
| function setToken(t) { | |
| TOKEN = t || ""; | |
| if (window.localStorage) localStorage.setItem("mc_token", TOKEN); | |
| } | |
| async function call(method, path, body) { | |
| const headers = { "Content-Type": "application/json" }; | |
| if (TOKEN) headers["Authorization"] = "Bearer " + TOKEN; | |
| const opt = { method, headers }; | |
| if (body !== undefined) opt.body = JSON.stringify(body); | |
| let resp; | |
| try { | |
| resp = await fetch(BASE + path, opt); | |
| } catch (e) { | |
| throw new ApiError(0, "network error — is the backend running?"); | |
| } | |
| let data = {}; | |
| try { data = await resp.json(); } catch (e) { /* empty body */ } | |
| if (!resp.ok) throw new ApiError(resp.status, (data && data.error) || ("HTTP " + resp.status)); | |
| return data; | |
| } | |
| function ApiError(status, message) { this.status = status; this.message = message; } | |
| ApiError.prototype = Object.create(Error.prototype); | |
| const MC = { | |
| base: BASE, | |
| setToken, | |
| hasToken: () => !!TOKEN, | |
| ApiError, | |
| // ---- discovery ---- | |
| async health() { | |
| const h = await call("GET", "/health"); | |
| return { status: h.status, name: h.name, version: h.version, items: h.items }; | |
| }, | |
| async version() { | |
| const v = await call("GET", "/version"); | |
| return { | |
| contract: v.contract_version, | |
| implementation: v.implementation, | |
| build: v.implementation_version, | |
| name: v.name, | |
| }; | |
| }, | |
| async experts() { | |
| const r = await call("GET", "/experts"); | |
| return (r.experts || []).map((e) => ({ id: e.name, name: e.name, desc: e.description || "" })); | |
| }, | |
| async scopes() { | |
| const r = await call("GET", "/scopes"); | |
| return (r.scopes || []).map((s) => ({ id: s, label: s })); // plain strings -> objects | |
| }, | |
| // ---- items ---- | |
| async items(filter) { | |
| filter = filter || {}; | |
| const q = []; | |
| if (filter.scope) q.push("scope=" + encodeURIComponent(filter.scope)); | |
| if (filter.expert) q.push("expert=" + encodeURIComponent(filter.expert)); | |
| const r = await call("GET", "/items" + (q.length ? "?" + q.join("&") : "")); | |
| return { items: r.items || [], count: r.count || 0 }; | |
| }, | |
| async getItem(id) { | |
| const r = await call("GET", "/items/" + encodeURIComponent(id)); | |
| return r.item; | |
| }, | |
| // ---- write ---- | |
| async remember(item) { | |
| const r = await call("POST", "/remember", { | |
| content: item.content, | |
| expert: item.expert, | |
| scope: item.scope, | |
| importance: item.importance, | |
| tags: item.tags || [], | |
| ttl: item.ttl != null ? item.ttl : null, | |
| }); | |
| return r.item; // {id, content, expert, scope, importance, tags, …} | |
| }, | |
| async forget(id) { | |
| const r = await call("POST", "/forget", { id }); | |
| return !!r.deleted; | |
| }, | |
| // ---- recall / inspect ---- | |
| async inspect(query, opts) { | |
| opts = opts || {}; | |
| const r = await call("POST", "/inspect", { | |
| query, | |
| scope: opts.scope || "/", | |
| max_tokens: opts.max_tokens || 256, | |
| top_experts: opts.top_experts || 3, | |
| pin_experts: opts.pin_experts || [], | |
| }); | |
| // Normalize routing.scores (object map) -> sorted array for the UI. | |
| const scores = Object.entries((r.routing && r.routing.scores) || {}) | |
| .map(([expert, score]) => ({ expert, score })) | |
| .sort((a, b) => b.score - a.score); | |
| return { | |
| query: r.query, | |
| routing: { | |
| selected: (r.routing && r.routing.selected_experts) || [], | |
| unselected: (r.routing && r.routing.unselected_experts) || [], | |
| scores, | |
| widened: !!(r.routing && r.routing.widened), | |
| reason: (r.routing && r.routing.reason) || "", | |
| }, | |
| pack: { | |
| tokens: r.pack && r.pack.tokens, | |
| maxTokens: r.pack && r.pack.max_tokens, | |
| items: (r.pack && r.pack.items) || [], | |
| dropped: (r.pack && r.pack.dropped) || [], | |
| citations: (r.pack && r.pack.citations) || [], | |
| prompt: (r.pack && r.pack.prompt) || "", | |
| }, | |
| }; | |
| }, | |
| async routerExplain(query, opts) { | |
| opts = opts || {}; | |
| const r = await call("POST", "/router/explain", { | |
| query, scope: opts.scope || "/", top_experts: opts.top_experts || 3, | |
| }); | |
| return { | |
| selected: r.selected_experts || [], | |
| unselected: r.unselected_experts || [], | |
| scores: r.scores || [], | |
| reason: r.reason || "", | |
| widened: !!r.widened, | |
| }; | |
| }, | |
| }; | |
| window.MC = MC; | |
| })(); | |