Spaces:
Runtime error
Runtime error
Commit ·
087e169
1
Parent(s): ca0ff77
feat(web): step-through memory replay grid in the memory panel
Browse filesCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
proteus/web/static/index.html
CHANGED
|
@@ -87,6 +87,16 @@
|
|
| 87 |
<div id="memInfo" style="color:var(--muted);font-size:12px"></div>
|
| 88 |
<details id="memoryPanel" class="hidden" open>
|
| 89 |
<summary id="memorySummary">memory shown to the model</summary>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
<pre id="memoryBlock"></pre>
|
| 91 |
</details>
|
| 92 |
<div id="grid"></div>
|
|
@@ -140,8 +150,8 @@ async function jpost(url, body) {
|
|
| 140 |
|
| 141 |
function hex6(h) { return h.length >= 7 ? h.slice(0,7) : h; } // drop alpha for CSS
|
| 142 |
|
| 143 |
-
function drawGrid(grid) {
|
| 144 |
-
const g = $("grid");
|
| 145 |
const h = grid.length, w = grid[0].length;
|
| 146 |
// Auto-fit the whole grid into a comfortable box: shrink cells for large
|
| 147 |
// grids (so a 64x64 field fits and stays centered) and cap at 30px so small
|
|
@@ -246,11 +256,38 @@ function selectProvider(p) {
|
|
| 246 |
}
|
| 247 |
function currentModel() { return `${PROVIDER}:${$("modelId").value.trim()}`; }
|
| 248 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
function showMem(m) {
|
| 250 |
const panel = $("memoryPanel");
|
| 251 |
if (!m || !m.attached) {
|
| 252 |
$("memInfo").textContent = m ? "memory: none (no pre-roll before the task)" : "";
|
| 253 |
panel.classList.add("hidden");
|
|
|
|
| 254 |
return;
|
| 255 |
}
|
| 256 |
$("memInfo").textContent =
|
|
@@ -260,6 +297,12 @@ function showMem(m) {
|
|
| 260 |
$("memoryBlock").textContent = m.block || "";
|
| 261 |
$("memorySummary").textContent =
|
| 262 |
`memory shown to the model — ${m.source} · ${m.turns} turns (click to collapse)`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
panel.open = true;
|
| 264 |
panel.classList.remove("hidden");
|
| 265 |
}
|
|
@@ -366,6 +409,9 @@ async function init() {
|
|
| 366 |
showModelInput();
|
| 367 |
SPEED = Number($("speed").value);
|
| 368 |
$("speed").addEventListener("change", () => { SPEED = Number($("speed").value); });
|
|
|
|
|
|
|
|
|
|
| 369 |
$("pauseBtn").addEventListener("click", () => {
|
| 370 |
PAUSED = !PAUSED; $("pauseBtn").textContent = PAUSED ? "Resume" : "Pause";
|
| 371 |
if (!PAUSED) spectateLoop();
|
|
|
|
| 87 |
<div id="memInfo" style="color:var(--muted);font-size:12px"></div>
|
| 88 |
<details id="memoryPanel" class="hidden" open>
|
| 89 |
<summary id="memorySummary">memory shown to the model</summary>
|
| 90 |
+
<div id="memReplay" class="hidden" style="padding:6px 10px">
|
| 91 |
+
<div style="margin-bottom:6px">
|
| 92 |
+
<button id="memReplayPrev" type="button">◀ prev</button>
|
| 93 |
+
<button id="memReplayNext" type="button">next ▶</button>
|
| 94 |
+
<button id="memReplayPlay" type="button">▶ play</button>
|
| 95 |
+
<span id="memReplayCap" style="color:var(--muted);margin-left:8px"></span>
|
| 96 |
+
</div>
|
| 97 |
+
<div id="memReplayGrid" style="display:grid;gap:1px;width:max-content;
|
| 98 |
+
background:var(--line);padding:1px;border-radius:4px"></div>
|
| 99 |
+
</div>
|
| 100 |
<pre id="memoryBlock"></pre>
|
| 101 |
</details>
|
| 102 |
<div id="grid"></div>
|
|
|
|
| 150 |
|
| 151 |
function hex6(h) { return h.length >= 7 ? h.slice(0,7) : h; } // drop alpha for CSS
|
| 152 |
|
| 153 |
+
function drawGrid(grid, target) {
|
| 154 |
+
const g = target || $("grid");
|
| 155 |
const h = grid.length, w = grid[0].length;
|
| 156 |
// Auto-fit the whole grid into a comfortable box: shrink cells for large
|
| 157 |
// grids (so a 64x64 field fits and stays centered) and cap at 30px so small
|
|
|
|
| 256 |
}
|
| 257 |
function currentModel() { return `${PROVIDER}:${$("modelId").value.trim()}`; }
|
| 258 |
|
| 259 |
+
// --- memory step-through replay -------------------------------------------- //
|
| 260 |
+
let MEM_FRAMES = [], MEM_I = 0, MEM_PLAY = false;
|
| 261 |
+
function renderMemFrame() {
|
| 262 |
+
if (!MEM_FRAMES.length) return;
|
| 263 |
+
const f = MEM_FRAMES[MEM_I];
|
| 264 |
+
drawGrid(f.grid, $("memReplayGrid"));
|
| 265 |
+
$("memReplayCap").textContent =
|
| 266 |
+
`Memory ${f.turn_idx} / ${MEM_FRAMES.length} — you chose: ${f.action}`;
|
| 267 |
+
}
|
| 268 |
+
function memStep(d) {
|
| 269 |
+
if (!MEM_FRAMES.length) return;
|
| 270 |
+
MEM_PLAY = false; $("memReplayPlay").textContent = "▶ play";
|
| 271 |
+
MEM_I = (MEM_I + d + MEM_FRAMES.length) % MEM_FRAMES.length;
|
| 272 |
+
renderMemFrame();
|
| 273 |
+
}
|
| 274 |
+
async function memPlay() {
|
| 275 |
+
if (MEM_PLAY) { MEM_PLAY = false; $("memReplayPlay").textContent = "▶ play"; return; }
|
| 276 |
+
if (!MEM_FRAMES.length) return;
|
| 277 |
+
MEM_PLAY = true; $("memReplayPlay").textContent = "⏸ pause";
|
| 278 |
+
for (let k = 0; k < MEM_FRAMES.length && MEM_PLAY; k++) {
|
| 279 |
+
MEM_I = k; renderMemFrame();
|
| 280 |
+
await new Promise(r => setTimeout(r, SPEED));
|
| 281 |
+
}
|
| 282 |
+
MEM_PLAY = false; $("memReplayPlay").textContent = "▶ play";
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
function showMem(m) {
|
| 286 |
const panel = $("memoryPanel");
|
| 287 |
if (!m || !m.attached) {
|
| 288 |
$("memInfo").textContent = m ? "memory: none (no pre-roll before the task)" : "";
|
| 289 |
panel.classList.add("hidden");
|
| 290 |
+
MEM_FRAMES = []; MEM_PLAY = false; $("memReplay").classList.add("hidden");
|
| 291 |
return;
|
| 292 |
}
|
| 293 |
$("memInfo").textContent =
|
|
|
|
| 297 |
$("memoryBlock").textContent = m.block || "";
|
| 298 |
$("memorySummary").textContent =
|
| 299 |
`memory shown to the model — ${m.source} · ${m.turns} turns (click to collapse)`;
|
| 300 |
+
// Replay of how the model actually moved (alongside the text block).
|
| 301 |
+
MEM_FRAMES = (m && m.frames) || []; MEM_I = 0; MEM_PLAY = false;
|
| 302 |
+
$("memReplayPlay").textContent = "▶ play";
|
| 303 |
+
const rep = $("memReplay");
|
| 304 |
+
if (MEM_FRAMES.length) { rep.classList.remove("hidden"); renderMemFrame(); }
|
| 305 |
+
else rep.classList.add("hidden");
|
| 306 |
panel.open = true;
|
| 307 |
panel.classList.remove("hidden");
|
| 308 |
}
|
|
|
|
| 409 |
showModelInput();
|
| 410 |
SPEED = Number($("speed").value);
|
| 411 |
$("speed").addEventListener("change", () => { SPEED = Number($("speed").value); });
|
| 412 |
+
$("memReplayPrev").addEventListener("click", () => memStep(-1));
|
| 413 |
+
$("memReplayNext").addEventListener("click", () => memStep(1));
|
| 414 |
+
$("memReplayPlay").addEventListener("click", memPlay);
|
| 415 |
$("pauseBtn").addEventListener("click", () => {
|
| 416 |
PAUSED = !PAUSED; $("pauseBtn").textContent = PAUSED ? "Resume" : "Pause";
|
| 417 |
if (!PAUSED) spectateLoop();
|
tests/web/test_memory_modes.py
CHANGED
|
@@ -104,3 +104,10 @@ def test_memory_frames_empty_when_no_memory():
|
|
| 104 |
_reg())
|
| 105 |
assert payload["memory"]["attached"] is False
|
| 106 |
assert payload["memory"]["frames"] == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
_reg())
|
| 105 |
assert payload["memory"]["attached"] is False
|
| 106 |
assert payload["memory"]["frames"] == []
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def test_index_html_has_replay_controls():
|
| 110 |
+
status, body, ctype = server.handle_request("GET", "/", None, _reg())
|
| 111 |
+
html = body.decode() if isinstance(body, (bytes, bytearray)) else body
|
| 112 |
+
assert "memReplayGrid" in html
|
| 113 |
+
assert "memReplayPrev" in html and "memReplayNext" in html and "memReplayPlay" in html
|