Skip to content

Commit 8249766

Browse files
committed
fix
1 parent a5645b1 commit 8249766

3 files changed

Lines changed: 227 additions & 17 deletions

File tree

bench_sr_agent.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ python bench_sr_agent.py \
3535

3636
python bench_sr_agent.py \
3737
--dataset lsrtransform \
38-
--exp_name bench_my_sr_agent \
38+
--exp_name bench_my_sr_agent-v4-flash \
3939
-R 2 -C 2 -L 5 -K 2 \
4040
--llm_provider openrouter \
4141
--llm_model "deepseek/deepseek-v4-flash"

src/sr_agent/web/app.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import asyncio
4+
import base64
45
import json
56
from pathlib import Path
67
from typing import Any
@@ -33,6 +34,7 @@ def list_runs():
3334
record_count, last_seq = _record_stats(run_dir / "records.jsonl")
3435
runs.append({
3536
"run_id": run_dir.name,
37+
"run_key": _run_key(app.state.log_dir, run_dir),
3638
"path": str(run_dir),
3739
"manifest": manifest,
3840
"record_count": record_count,
@@ -103,13 +105,25 @@ def _iter_run_dirs(log_dir: Path):
103105

104106

105107
def _resolve_run_dir(log_dir: Path, run_id: str) -> Path:
106-
matches = [path for path in _iter_run_dirs(log_dir) if path.name == run_id]
108+
matches = [
109+
path
110+
for path in _iter_run_dirs(log_dir)
111+
if path.name == run_id or _run_key(log_dir, path) == run_id
112+
]
107113
if not matches:
108114
raise HTTPException(status_code=404, detail=f"Run not found: {run_id}")
109115
matches.sort(key=lambda path: (path / "records.jsonl").stat().st_mtime, reverse=True)
110116
return matches[0]
111117

112118

119+
def _run_key(log_dir: Path, run_dir: Path) -> str:
120+
try:
121+
rel = run_dir.resolve().relative_to(log_dir.resolve()).as_posix()
122+
except ValueError:
123+
rel = run_dir.resolve().as_posix()
124+
return base64.urlsafe_b64encode(rel.encode("utf-8")).decode("ascii").rstrip("=")
125+
126+
113127
def _read_json(path: Path) -> dict[str, Any] | None:
114128
try:
115129
with open(path, "r", encoding="utf-8") as f:

src/sr_agent/web/static/index.html

Lines changed: 211 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,98 @@
6262
main {
6363
height: calc(100vh - 56px);
6464
display: grid;
65-
grid-template-columns: minmax(0, 1fr) 410px;
65+
grid-template-columns: 280px minmax(0, 1fr) 410px;
6666
min-height: 0;
6767
}
68+
main.run-collapsed {
69+
grid-template-columns: 58px minmax(0, 1fr) 410px;
70+
}
71+
.run-panel {
72+
min-width: 0;
73+
overflow: auto;
74+
border-right: 1px solid var(--line);
75+
background: var(--panel);
76+
padding: 12px;
77+
}
78+
.run-panel-header {
79+
display: flex;
80+
align-items: center;
81+
gap: 6px;
82+
margin-bottom: 10px;
83+
font-weight: 650;
84+
cursor: pointer;
85+
user-select: none;
86+
}
87+
.run-panel.collapsed {
88+
width: 42px;
89+
padding: 12px 8px;
90+
}
91+
.run-panel.collapsed .run-tree {
92+
display: none;
93+
}
94+
.run-panel.collapsed .run-panel-title {
95+
writing-mode: vertical-rl;
96+
transform: rotate(180deg);
97+
}
98+
.run-toggle {
99+
display: inline-block;
100+
width: 0;
101+
height: 0;
102+
border-left: 5px solid transparent;
103+
border-right: 5px solid transparent;
104+
border-top: 7px solid #334155;
105+
transition: transform 120ms ease;
106+
}
107+
.run-panel.collapsed .run-toggle {
108+
transform: rotate(-90deg);
109+
}
110+
.run-tree {
111+
font-size: 13px;
112+
line-height: 1.4;
113+
}
114+
.tree-node {
115+
margin: 2px 0;
116+
}
117+
.tree-row {
118+
display: flex;
119+
align-items: center;
120+
gap: 5px;
121+
min-height: 24px;
122+
padding: 2px 4px;
123+
border-radius: 5px;
124+
cursor: pointer;
125+
user-select: none;
126+
}
127+
.tree-row:hover {
128+
background: #eef2f7;
129+
}
130+
.tree-row.active {
131+
background: #dfeeea;
132+
color: #0f5f56;
133+
font-weight: 650;
134+
}
135+
.tree-children {
136+
margin-left: 14px;
137+
}
138+
.tree-node.collapsed > .tree-children {
139+
display: none;
140+
}
141+
.tree-caret {
142+
width: 12px;
143+
color: #64748b;
144+
flex: 0 0 12px;
145+
}
146+
.tree-name {
147+
overflow: hidden;
148+
text-overflow: ellipsis;
149+
white-space: nowrap;
150+
min-width: 0;
151+
}
152+
.tree-meta {
153+
margin-left: auto;
154+
color: var(--muted);
155+
font-size: 11px;
156+
}
68157
#graph-wrap {
69158
position: relative;
70159
min-width: 0;
@@ -355,7 +444,10 @@
355444
font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
356445
}
357446
@media (max-width: 900px) {
358-
main { grid-template-columns: 1fr; grid-template-rows: minmax(430px, 58vh) minmax(320px, 1fr); }
447+
main { grid-template-columns: 1fr; grid-template-rows: auto minmax(430px, 58vh) minmax(320px, 1fr); }
448+
.run-panel { border-right: 0; border-bottom: 1px solid var(--line); max-height: 240px; }
449+
.run-panel.collapsed { width: auto; }
450+
.run-panel.collapsed .run-panel-title { writing-mode: horizontal-tb; transform: none; }
359451
aside { border-left: 0; border-top: 1px solid var(--line); }
360452
header { flex-wrap: wrap; }
361453
.legend { position: static; width: auto; margin: 10px; }
@@ -365,7 +457,6 @@
365457
<body>
366458
<header>
367459
<h1>SR Agent Search Viewer</h1>
368-
<select id="run-select" aria-label="Run"></select>
369460
<button id="refresh">Refresh</button>
370461
<button id="layout">Auto Layout</button>
371462
<button id="weak-edges">Hide Weak Edges</button>
@@ -379,6 +470,13 @@ <h1>SR Agent Search Viewer</h1>
379470
<span id="status" class="muted"></span>
380471
</header>
381472
<main>
473+
<nav id="run-panel" class="run-panel" aria-label="Runs">
474+
<div id="run-panel-header" class="run-panel-header">
475+
<span class="run-toggle"></span>
476+
<span class="run-panel-title">Runs</span>
477+
</div>
478+
<div id="run-tree" class="run-tree"></div>
479+
</nav>
382480
<section id="graph-wrap">
383481
<div id="legend" class="legend">
384482
<strong id="legend-title"><span class="legend-toggle"></span><span>Legend</span></strong>
@@ -420,7 +518,6 @@ <h1>SR Agent Search Viewer</h1>
420518
const ROOT_PREFIX = "__root__:";
421519
const LEVEL_GAP = 118;
422520
const SIBLING_GAP = 64;
423-
const runSelect = document.getElementById("run-select");
424521
const refreshButton = document.getElementById("refresh");
425522
const layoutButton = document.getElementById("layout");
426523
const weakEdgesButton = document.getElementById("weak-edges");
@@ -429,6 +526,10 @@ <h1>SR Agent Search Viewer</h1>
429526
const exportButton = document.getElementById("export");
430527
const legend = document.getElementById("legend");
431528
const legendTitle = document.getElementById("legend-title");
529+
const main = document.querySelector("main");
530+
const runPanel = document.getElementById("run-panel");
531+
const runPanelHeader = document.getElementById("run-panel-header");
532+
const runTree = document.getElementById("run-tree");
432533
const statusEl = document.getElementById("status");
433534
const svg = document.getElementById("graph");
434535
const state = {
@@ -442,7 +543,9 @@ <h1>SR Agent Search Viewer</h1>
442543
panning: null,
443544
view: { x: 0, y: 0, scale: 1 },
444545
showWeakEdges: true,
445-
showLabels: true
546+
showLabels: true,
547+
runs: [],
548+
collapsedRunNodes: new Set()
446549
};
447550
const mathJaxReady = new Promise(resolve => {
448551
const wait = () => {
@@ -470,7 +573,10 @@ <h1>SR Agent Search Viewer</h1>
470573
});
471574
exportButton.addEventListener("click", () => exportScene(exportFormat.value));
472575
legendTitle.addEventListener("click", () => legend.classList.toggle("collapsed"));
473-
runSelect.addEventListener("change", () => selectRun(runSelect.value));
576+
runPanelHeader.addEventListener("click", () => {
577+
runPanel.classList.toggle("collapsed");
578+
main.classList.toggle("run-collapsed", runPanel.classList.contains("collapsed"));
579+
});
474580
svg.addEventListener("pointermove", onPointerMove);
475581
svg.addEventListener("pointerup", onPointerUp);
476582
svg.addEventListener("pointerleave", onPointerUp);
@@ -479,23 +585,113 @@ <h1>SR Agent Search Viewer</h1>
479585

480586
async function loadRuns() {
481587
const data = await fetchJson("/api/runs");
482-
runSelect.innerHTML = "";
483-
for (const run of data.runs) {
484-
const option = document.createElement("option");
485-
option.value = run.run_id;
486-
option.textContent = `${run.run_id} (${run.record_count})`;
487-
runSelect.appendChild(option);
488-
}
489-
if (data.runs.length && (!state.runId || !data.runs.some(run => run.run_id === state.runId))) {
490-
selectRun(data.runs[0].run_id);
588+
state.runs = data.runs || [];
589+
renderRunTree(state.runs);
590+
if (data.runs.length && (!state.runId || !data.runs.some(run => runKey(run) === state.runId))) {
591+
selectRun(runKey(data.runs[0]));
491592
}
492593
statusEl.textContent = data.runs.length ? "" : "No runs with records.jsonl found";
493594
}
494595

596+
function renderRunTree(runs) {
597+
runTree.innerHTML = "";
598+
if (!runs.length) {
599+
const empty = document.createElement("div");
600+
empty.className = "muted";
601+
empty.textContent = "No records.jsonl found.";
602+
runTree.appendChild(empty);
603+
return;
604+
}
605+
const tree = buildRunTree(runs);
606+
runTree.appendChild(renderTreeChildren(tree.children));
607+
}
608+
609+
function buildRunTree(runs) {
610+
const root = { name: "", key: "", children: new Map(), run: null };
611+
for (const run of runs) {
612+
const parts = runPathParts(run);
613+
let node = root;
614+
let pathKey = "";
615+
for (const part of parts) {
616+
pathKey = pathKey ? `${pathKey}/${part}` : part;
617+
if (!node.children.has(part)) {
618+
node.children.set(part, { name: part, key: pathKey, children: new Map(), run: null });
619+
}
620+
node = node.children.get(part);
621+
}
622+
node.run = run;
623+
}
624+
return root;
625+
}
626+
627+
function runPathParts(run) {
628+
const path = String(run.path || run.run_id || "").replaceAll("\\", "/");
629+
const marker = "/logs/";
630+
const index = path.toLowerCase().lastIndexOf(marker);
631+
const rel = index >= 0 ? path.slice(index + marker.length) : (run.run_id || path.split("/").pop());
632+
return rel.split("/").filter(Boolean);
633+
}
634+
635+
function runKey(run) {
636+
return run.run_key || run.run_id;
637+
}
638+
639+
function renderTreeChildren(children) {
640+
const container = document.createElement("div");
641+
[...children.values()]
642+
.sort((a, b) => a.name.localeCompare(b.name))
643+
.forEach(node => container.appendChild(renderTreeNode(node)));
644+
return container;
645+
}
646+
647+
function renderTreeNode(node) {
648+
const wrap = document.createElement("div");
649+
wrap.className = "tree-node";
650+
if (!node.run && state.collapsedRunNodes.has(node.key)) wrap.classList.add("collapsed");
651+
const row = document.createElement("div");
652+
row.className = "tree-row";
653+
if (node.run && runKey(node.run) === state.runId) row.classList.add("active");
654+
const caret = document.createElement("span");
655+
caret.className = "tree-caret";
656+
caret.textContent = node.children.size
657+
? (wrap.classList.contains("collapsed") ? "▸" : "▾")
658+
: "•";
659+
const name = document.createElement("span");
660+
name.className = "tree-name";
661+
name.textContent = node.name;
662+
row.append(caret, name);
663+
if (node.run) {
664+
const meta = document.createElement("span");
665+
meta.className = "tree-meta";
666+
meta.textContent = node.run.record_count;
667+
row.appendChild(meta);
668+
row.addEventListener("click", event => {
669+
event.stopPropagation();
670+
selectRun(runKey(node.run));
671+
});
672+
} else {
673+
row.addEventListener("click", event => {
674+
event.stopPropagation();
675+
wrap.classList.toggle("collapsed");
676+
if (wrap.classList.contains("collapsed")) state.collapsedRunNodes.add(node.key);
677+
else state.collapsedRunNodes.delete(node.key);
678+
caret.textContent = wrap.classList.contains("collapsed") ? "▸" : "▾";
679+
});
680+
}
681+
wrap.appendChild(row);
682+
if (node.children.size) {
683+
const childWrap = renderTreeChildren(node.children);
684+
childWrap.className = "tree-children";
685+
wrap.appendChild(childWrap);
686+
}
687+
return wrap;
688+
}
689+
495690
async function selectRun(runId) {
496691
if (!runId) return;
497692
if (state.source) state.source.close();
498693
state.runId = runId;
694+
renderRunTree(state.runs);
499695
state.records.clear();
500696
state.positions.clear();
501697
state.userMoved.clear();

0 commit comments

Comments
 (0)