irregular6612 Claude Opus 4.8 (1M context) commited on
Commit
087e169
·
1 Parent(s): ca0ff77

feat(web): step-through memory replay grid in the memory panel

Browse files

Co-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