ruslanmv's picture
Deploy Matrix Context Console
ce45eb0 verified
Raw
History Blame Contribute Delete
9.16 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Matrix Context — Context Inspector</title>
<style>
:root { --blue:#1a73e8; --blue2:#0b3d91; --grey:#9aa0a6; --bg:#f6f8fa; --red:#d93025; }
* { box-sizing: border-box; }
body { font:14px/1.5 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
margin:0; color:#202124; background:var(--bg); }
header { background:var(--blue2); color:#fff; padding:14px 20px; }
header h1 { margin:0; font-size:18px; }
header small { opacity:.8; }
main { max-width:1000px; margin:0 auto; padding:18px; display:grid; gap:16px; }
.card { background:#fff; border:1px solid #e0e0e0; border-radius:8px; padding:14px 16px; }
.card h2 { margin:0 0 10px; font-size:14px; text-transform:uppercase; letter-spacing:.04em; color:#5f6368; }
.row { display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end; }
label { display:block; font-size:12px; color:#5f6368; margin-bottom:3px; }
input, textarea, select { font:inherit; padding:7px 9px; border:1px solid #ccc; border-radius:6px; width:100%; }
textarea { resize:vertical; min-height:46px; }
button { background:var(--blue); color:#fff; border:0; border-radius:6px; padding:8px 16px;
font-weight:600; cursor:pointer; }
button.secondary { background:#eef1f5; color:#202124; }
.chip { display:inline-block; padding:3px 9px; border-radius:12px; font-size:12px; margin:2px; }
.chip.sel { background:#e6f4ea; color:#137333; border:1px solid #137333; }
.chip.unsel { background:#f1f3f4; color:#5f6368; border:1px solid #dadce0; }
table { width:100%; border-collapse:collapse; font-size:13px; }
th, td { text-align:left; padding:6px 8px; border-bottom:1px solid #eee; vertical-align:top; }
th { color:#5f6368; font-weight:600; }
.bar { height:7px; background:#e8eaed; border-radius:4px; overflow:hidden; min-width:80px; }
.bar > span { display:block; height:100%; background:var(--blue); }
.muted { color:#5f6368; } .mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }
pre { background:#0d1117; color:#e6edf3; padding:12px; border-radius:8px; overflow:auto; white-space:pre-wrap; }
.dropped td { color:#9aa0a6; }
.reason { color:var(--red); font-size:12px; }
.pill { font-size:11px; background:#eef1f5; border-radius:10px; padding:1px 7px; margin-left:6px; }
.err { color:var(--red); }
</style>
</head>
<body>
<header>
<h1>Matrix Context — Context Inspector</h1>
<small>Run a query, see why each item was routed, kept, or dropped. <span id="ver" class="pill"></span></small>
</header>
<main>
<section class="card">
<h2>Add memory</h2>
<div class="row">
<div style="flex:3 1 320px"><label>Content</label>
<textarea id="content" placeholder="Decision: use SQLite as the default backend"></textarea></div>
<div style="flex:1 1 130px"><label>Expert</label><select id="expert"></select></div>
<div style="flex:0 1 110px"><label>Importance</label><input id="importance" type="number" min="0" max="1" step="0.1" value="0.6"></div>
<div style="flex:0 1 140px"><label>Scope</label><input id="scope" value="/"></div>
<div><button id="remember">Remember</button></div>
</div>
<div id="rememberMsg" class="muted" style="margin-top:8px"></div>
</section>
<section class="card">
<h2>Query</h2>
<div class="row">
<div style="flex:3 1 360px"><label>Query</label>
<input id="query" value="what did we decide about the backend?"></div>
<div style="flex:0 1 120px"><label>Max tokens</label><input id="maxTokens" type="number" value="120"></div>
<div style="flex:0 1 120px"><label>Top experts</label><input id="topExperts" type="number" value="2"></div>
<div><button id="inspect">Inspect</button>
<button id="loadSample" class="secondary">Load sample</button></div>
</div>
<div id="queryMsg" class="err" style="margin-top:8px"></div>
</section>
<section class="card" id="routingCard" style="display:none">
<h2>Routing <span id="routingReason" class="muted" style="text-transform:none;font-weight:400"></span></h2>
<div><strong>Selected:</strong> <span id="selected"></span></div>
<div style="margin-top:6px"><strong>Not selected:</strong> <span id="unselected"></span></div>
<table style="margin-top:10px"><thead><tr><th>Expert</th><th>Score</th><th></th></tr></thead>
<tbody id="scores"></tbody></table>
</section>
<section class="card" id="packCard" style="display:none">
<h2>Context pack <span id="packTokens" class="pill"></span></h2>
<table><thead><tr><th>Expert</th><th>Content</th><th>Score</th><th>Breakdown (rel / imp / rec / −red)</th></tr></thead>
<tbody id="kept"></tbody></table>
<div id="droppedWrap" style="margin-top:12px; display:none">
<h2 style="font-size:13px">Dropped</h2>
<table><thead><tr><th>Expert</th><th>Item</th><th>Reason</th></tr></thead>
<tbody id="dropped"></tbody></table>
</div>
</section>
<section class="card" id="promptCard" style="display:none">
<h2>Prompt-ready pack</h2>
<pre id="prompt"></pre>
</section>
</main>
<script>
const API = location.origin + "/v1";
const $ = id => document.getElementById(id);
async function call(method, path, body) {
const opt = { method, headers: { "Content-Type": "application/json" } };
if (body) opt.body = JSON.stringify(body);
const r = await fetch(API + path, opt);
let data = {}; try { data = await r.json(); } catch (e) {}
return { status: r.status, data };
}
async function init() {
const v = await call("GET", "/version");
if (v.status === 200) $("ver").textContent = "contract v" + v.data.contract_version;
const e = await call("GET", "/experts");
if (e.status === 200) $("expert").innerHTML =
e.data.experts.map(x => `<option value="${x.name}">${x.name}</option>`).join("");
}
$("remember").onclick = async () => {
const content = $("content").value.trim();
if (!content) return;
const r = await call("POST", "/remember", {
content, expert: $("expert").value, scope: $("scope").value,
importance: parseFloat($("importance").value) });
$("rememberMsg").textContent = r.status === 201
? "Remembered " + r.data.item.id + " [" + r.data.item.expert + "]"
: "Error: " + (r.data.error || r.status);
$("content").value = "";
};
$("loadSample").onclick = async () => {
const samples = [
["Decision: use SQLite as the default storage backend", "decision_memory", 0.95],
["The user prefers local-first tools over cloud services", "profile", 0.9],
["Policy: never store API keys or PII in durable memory", "policy", 0.9],
["A conflicting note claims the backend is Postgres", "session", 0.3],
["On 2026-05-02 we deferred Milvus support to v2", "episodic", 0.6]];
for (const [content, expert, importance] of samples)
await call("POST", "/remember", { content, expert, importance });
$("rememberMsg").textContent = "Loaded " + samples.length + " sample memories.";
};
function chips(el, names, cls) {
el.innerHTML = names.length
? names.map(n => `<span class="chip ${cls}">${n}</span>`).join("")
: '<span class="muted">none</span>';
}
$("inspect").onclick = async () => {
$("queryMsg").textContent = "";
const r = await call("POST", "/inspect", {
query: $("query").value, max_tokens: parseInt($("maxTokens").value),
top_experts: parseInt($("topExperts").value) });
if (r.status !== 200) { $("queryMsg").textContent = "Error: " + (r.data.error || r.status); return; }
const { routing, pack } = r.data;
$("routingCard").style.display = "";
$("routingReason").textContent = "— " + routing.reason;
chips($("selected"), routing.selected_experts, "sel");
chips($("unselected"), routing.unselected_experts, "unsel");
const max = Math.max(...Object.values(routing.scores), 0.0001);
$("scores").innerHTML = Object.entries(routing.scores)
.sort((a, b) => b[1] - a[1])
.map(([e, s]) => {
const on = routing.selected_experts.includes(e);
return `<tr><td>${on ? "● " : ""}${e}</td><td class="mono">${s.toFixed(3)}</td>
<td><div class="bar"><span style="width:${(100*s/max).toFixed(0)}%;
background:${on ? "#1a73e8" : "#9aa0a6"}"></span></div></td></tr>`;
}).join("");
$("packCard").style.display = "";
$("packTokens").textContent = pack.tokens + " / " + pack.max_tokens + " tokens";
$("kept").innerHTML = pack.items.map(p => {
const b = p.breakdown;
return `<tr><td>${p.expert}</td><td>${p.content}</td>
<td class="mono">${p.final_score}</td>
<td class="mono muted">${b.relevance} / ${b.importance} / ${b.recency} / ${b.redundancy}</td></tr>`;
}).join("");
const dropped = pack.dropped || [];
$("droppedWrap").style.display = dropped.length ? "" : "none";
$("dropped").innerHTML = dropped.map(d =>
`<tr class="dropped"><td>${d.expert}</td><td class="mono">${d.id}</td>
<td class="reason">${d.reason}</td></tr>`).join("");
$("promptCard").style.display = "";
$("prompt").textContent = pack.prompt;
};
init();
</script>
</body>
</html>