feat(diagnostic): Archiv-Modal mit Pagination fuer ARIA-Stream
- /api/agent-stream akzeptiert jetzt ?page=N&perPage=M zusaetzlich zu ?lines=N. page=1 = neueste Eintraege, hoehere Pages = aelter. Antwort enthaelt page/perPage/pagesTotal/total fuer Client-Nav. - Live-View hat neuen 📜 Archiv-Button neben Leeren/Auto-Scroll. - Modal mit PerPage-Selector (50/100/500/1000), «‹›» Navigation und reload-Button. Pagination-Buttons werden auf den Grenzen disabled. - renderArchiveLine spiegelt das Live-View-Rendering (Tool-Calls in cyan, Results in gruen, Thinking kursiv) im Modal-Container. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+27
-7
@@ -1773,22 +1773,42 @@ const server = http.createServer((req, res) => {
|
||||
return res.end(JSON.stringify({ ok: false, error: e.message }));
|
||||
}
|
||||
} else if (req.url.startsWith("/api/agent-stream") && req.method === "GET") {
|
||||
// Tail des persistierten agent_stream.jsonl. Browser-Live-View laedt das
|
||||
// beim Tab-Oeffnen damit Reload/Standby keine Events mehr wegschmeisst.
|
||||
// Tail / paginierter Slice des persistierten agent_stream.jsonl.
|
||||
// Modi:
|
||||
// ?lines=N → letzte N Zeilen (Live-View Initial-Load)
|
||||
// ?page=P&perPage=M → 1-indexed Pagination (Modal-Browser);
|
||||
// page=1 = neueste Seite, hoehere Pages = aelter
|
||||
try {
|
||||
const u = new URL(req.url, "http://localhost");
|
||||
const lines = Math.max(1, Math.min(5000, parseInt(u.searchParams.get("lines") || "200", 10) || 200));
|
||||
const linesParam = u.searchParams.get("lines");
|
||||
const pageParam = u.searchParams.get("page");
|
||||
const perPageParam = u.searchParams.get("perPage");
|
||||
const file = AGENT_STREAM_LOG;
|
||||
let raw = "";
|
||||
try { raw = fs.readFileSync(file, "utf-8"); } catch {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
return res.end(JSON.stringify({ ok: true, file, lines: [] }));
|
||||
return res.end(JSON.stringify({ ok: true, file, total: 0, lines: [] }));
|
||||
}
|
||||
const all = raw.split("\n").filter(l => l.trim());
|
||||
const tail = all.slice(-lines);
|
||||
const parsed = tail.map(l => { try { return JSON.parse(l); } catch { return { _raw: l }; } });
|
||||
let slice, page = 1, perPage = 0, pagesTotal = 1;
|
||||
if (pageParam || perPageParam) {
|
||||
perPage = Math.max(10, Math.min(5000, parseInt(perPageParam || "100", 10) || 100));
|
||||
pagesTotal = Math.max(1, Math.ceil(all.length / perPage));
|
||||
page = Math.max(1, Math.min(pagesTotal, parseInt(pageParam || "1", 10) || 1));
|
||||
// page=1 = juengste Seite → vom Ende her slicen
|
||||
const end = all.length - (page - 1) * perPage;
|
||||
const start = Math.max(0, end - perPage);
|
||||
slice = all.slice(start, end);
|
||||
} else {
|
||||
const lines = Math.max(1, Math.min(5000, parseInt(linesParam || "200", 10) || 200));
|
||||
slice = all.slice(-lines);
|
||||
}
|
||||
const parsed = slice.map(l => { try { return JSON.parse(l); } catch { return { _raw: l }; } });
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
return res.end(JSON.stringify({ ok: true, file, count: parsed.length, total: all.length, lines: parsed }));
|
||||
return res.end(JSON.stringify({
|
||||
ok: true, file, total: all.length, count: parsed.length,
|
||||
page, perPage, pagesTotal, lines: parsed,
|
||||
}));
|
||||
} catch (e) {
|
||||
res.writeHead(500, { "Content-Type": "application/json" });
|
||||
return res.end(JSON.stringify({ ok: false, error: e.message }));
|
||||
|
||||
Reference in New Issue
Block a user