<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Navi — Context Debug</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: ui-monospace, "Cascadia Code", "Fira Code", monospace;
font-size: 13px;
background: #0e0e0e;
color: #d4d4d4;
min-height: 100vh;
}
/* ── Header ── */
header {
position: sticky;
top: 0;
z-index: 10;
background: #161616;
border-bottom: 1px solid #2a2a2a;
padding: 10px 16px;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
header h1 { font-size: 13px; color: #888; font-weight: 400; white-space: nowrap; }
#input-wrap { display: flex; gap: 6px; flex: 1; min-width: 260px; }
#session-input {
flex: 1;
background: #1e1e1e;
border: 1px solid #333;
border-radius: 4px;
color: #d4d4d4;
font-family: inherit;
font-size: 12px;
padding: 4px 8px;
outline: none;
}
#session-input:focus { border-color: #555; }
button {
background: #2a2a2a;
border: 1px solid #444;
border-radius: 4px;
color: #d4d4d4;
cursor: pointer;
font-family: inherit;
font-size: 12px;
padding: 4px 10px;
}
button:hover { background: #333; }
#stats {
font-size: 11px;
color: #666;
white-space: nowrap;
}
#stats span { color: #999; }
/* ── Main ── */
main { max-width: 900px; margin: 0 auto; padding: 16px; }
#placeholder {
text-align: center;
color: #444;
padding: 60px 0;
}
/* ── Message card ── */
.msg {
display: grid;
grid-template-columns: 90px 1fr;
gap: 0 12px;
margin-bottom: 12px;
border: 1px solid #222;
border-radius: 6px;
overflow: hidden;
}
.msg-role {
background: #1a1a1a;
border-right: 1px solid #222;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding: 10px 6px;
gap: 4px;
text-align: center;
}
.role-badge {
font-size: 10px;
font-weight: 700;
letter-spacing: .04em;
text-transform: uppercase;
padding: 2px 6px;
border-radius: 3px;
}
.role-system { background: #1e3a2f; color: #4ec9b0; }
.role-user { background: #1e2d40; color: #569cd6; }
.role-assistant{ background: #2d1e3a; color: #c586c0; }
.role-tool { background: #3a2d1e; color: #ce9178; }
.badge-summary { background: #3a3a1e; color: #dcdcaa; font-size: 9px; padding: 1px 5px; border-radius: 3px; }
.badge-images { background: #1e3a3a; color: #4ec9b0; font-size: 9px; padding: 1px 5px; border-radius: 3px; }
.badge-tools { background: #3a1e2d; color: #f48771; font-size: 9px; padding: 1px 5px; border-radius: 3px; }
.msg-idx {
font-size: 10px;
color: #444;
}
.msg-body { padding: 10px 12px; overflow: hidden; }
.msg-content {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.55;
color: #d4d4d4;
}
.msg-content.dim { color: #888; }
.section-label {
font-size: 10px;
color: #555;
text-transform: uppercase;
letter-spacing: .06em;
margin: 8px 0 4px;
}
.tool-calls { margin-top: 8px; }
.tool-call-block {
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 4px;
margin-bottom: 6px;
overflow: hidden;
}
.tool-call-name {
background: #222;
color: #ce9178;
padding: 3px 8px;
font-size: 11px;
font-weight: 600;
}
.tool-call-args {
padding: 6px 8px;
white-space: pre-wrap;
word-break: break-word;
color: #9cdcfe;
font-size: 11px;
}
.tool-id { font-size: 10px; color: #555; margin-top: 4px; }
.img-strip { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.img-thumb { width: 80px; height: 80px; object-fit: cover; border-radius: 3px; border: 1px solid #333; }
.char-count { font-size: 10px; color: #444; margin-top: 6px; }
#error { color: #f48771; padding: 20px; text-align: center; }
</style>
</head>
<body>
<header>
<h1>context debug</h1>
<div id="input-wrap">
<input id="session-input" type="text" placeholder="session id…" spellcheck="false">
<button id="btn-load">Load</button>
</div>
<div id="stats"></div>
</header>
<main>
<div id="placeholder">Enter a session ID to inspect what the model sees.</div>
<div id="error" hidden></div>
<div id="messages"></div>
</main>
<script>
const input = document.getElementById('session-input');
const btnLoad = document.getElementById('btn-load');
const statsEl = document.getElementById('stats');
const msgsEl = document.getElementById('messages');
const errEl = document.getElementById('error');
const phEl = document.getElementById('placeholder');
// Pre-fill from URL hash
if (location.hash) {
input.value = location.hash.slice(1);
}
btnLoad.addEventListener('click', load);
input.addEventListener('keydown', e => { if (e.key === 'Enter') load(); });
async function load() {
const id = input.value.trim();
if (!id) return;
location.hash = id;
msgsEl.innerHTML = '';
errEl.hidden = true;
phEl.hidden = true;
statsEl.textContent = 'loading…';
try {
const res = await fetch(`/sessions/${encodeURIComponent(id)}/context`);
if (!res.ok) {
const j = await res.json().catch(() => ({}));
throw new Error(j.detail || `HTTP ${res.status}`);
}
const data = await res.json();
renderContext(data);
} catch (e) {
errEl.textContent = e.message;
errEl.hidden = false;
statsEl.textContent = '';
}
}
function renderContext(data) {
const estTokens = Math.round(data.total_chars / 4);
statsEl.innerHTML =
`<span>${data.profile_id}</span> · ` +
`<span>${data.message_count}</span> messages · ` +
`<span>${data.total_chars.toLocaleString()}</span> chars · ` +
`~<span>${estTokens.toLocaleString()}</span> tokens`;
data.context.forEach((msg, idx) => {
msgsEl.appendChild(renderMessage(msg, idx));
});
}
function renderMessage(msg, idx) {
const el = document.createElement('div');
el.className = 'msg';
// ── Left column: role + badges ──
const roleCol = document.createElement('div');
roleCol.className = 'msg-role';
const badge = document.createElement('span');
badge.className = `role-badge role-${msg.role}`;
badge.textContent = msg.role;
roleCol.appendChild(badge);
if (msg.is_summary) {
const b = document.createElement('span');
b.className = 'badge-summary';
b.textContent = 'summary';
roleCol.appendChild(b);
}
if (msg.images?.length) {
const b = document.createElement('span');
b.className = 'badge-images';
b.textContent = `${msg.images.length} img`;
roleCol.appendChild(b);
}
if (msg.tool_calls?.length) {
const b = document.createElement('span');
b.className = 'badge-tools';
b.textContent = `${msg.tool_calls.length} call${msg.tool_calls.length > 1 ? 's' : ''}`;
roleCol.appendChild(b);
}
const idxEl = document.createElement('span');
idxEl.className = 'msg-idx';
idxEl.textContent = `#${idx}`;
roleCol.appendChild(idxEl);
// ── Right column: body ──
const body = document.createElement('div');
body.className = 'msg-body';
// Content
if (msg.content) {
const pre = document.createElement('pre');
pre.className = 'msg-content' + (msg.role === 'tool' ? ' dim' : '');
pre.textContent = msg.content;
body.appendChild(pre);
}
// Tool calls (assistant planning)
if (msg.tool_calls?.length) {
const wrap = document.createElement('div');
wrap.className = 'tool-calls';
const lbl = document.createElement('div');
lbl.className = 'section-label';
lbl.textContent = 'tool calls';
wrap.appendChild(lbl);
for (const tc of msg.tool_calls) {
const block = document.createElement('div');
block.className = 'tool-call-block';
const name = document.createElement('div');
name.className = 'tool-call-name';
name.textContent = tc.name || tc.function?.name || '?';
block.appendChild(name);
const args = document.createElement('pre');
args.className = 'tool-call-args';
const rawArgs = tc.arguments ?? tc.function?.arguments ?? {};
args.textContent = JSON.stringify(rawArgs, null, 2);
block.appendChild(args);
wrap.appendChild(block);
}
body.appendChild(wrap);
}
// Tool call ID (for tool result messages)
if (msg.tool_call_id) {
const tid = document.createElement('div');
tid.className = 'tool-id';
tid.textContent = `call_id: ${msg.tool_call_id}`;
body.appendChild(tid);
}
// Images
if (msg.images?.length) {
const lbl = document.createElement('div');
lbl.className = 'section-label';
lbl.textContent = 'images';
body.appendChild(lbl);
const strip = document.createElement('div');
strip.className = 'img-strip';
for (const b64 of msg.images) {
const img = document.createElement('img');
img.className = 'img-thumb';
img.src = b64.startsWith('data:') ? b64 : `data:image/jpeg;base64,${b64}`;
strip.appendChild(img);
}
body.appendChild(strip);
}
// Char count
const chars = (msg.content || '').length;
if (chars > 0) {
const cc = document.createElement('div');
cc.className = 'char-count';
cc.textContent = `${chars.toLocaleString()} chars · ~${Math.round(chars / 4).toLocaleString()} tokens`;
body.appendChild(cc);
}
el.appendChild(roleCol);
el.appendChild(body);
return el;
}
// Auto-load if hash present
if (input.value) load();
</script>
</body>
</html>