From fba26df7808b066ee6f13cf499bc395650e6a967 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 24 Apr 2026 09:50:38 -0500 Subject: [PATCH] Page persisted run logs incrementally Co-Authored-By: Paperclip --- ui/src/pages/AgentDetail.tsx | 65 ++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index 70d383dc61f..daf293e3b5b 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -110,6 +110,8 @@ const runStatusIcons: Record(null); const [logOffset, setLogOffset] = useState(0); + const [hasMoreLog, setHasMoreLog] = useState(false); + const [loadingMoreLog, setLoadingMoreLog] = useState(false); const [isFollowing, setIsFollowing] = useState(false); const [isStreamingConnected, setIsStreamingConnected] = useState(false); const [transcriptMode, setTranscriptMode] = useState("nice"); @@ -3627,6 +3631,8 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin pendingLogLineRef.current = ""; setLogLines([]); setLogOffset(0); + setHasMoreLog(false); + setLoadingMoreLog(false); setLogError(null); if (!run.logRef && !isLive) { @@ -3637,25 +3643,14 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin } setLogLoading(true); - const firstLimit = - typeof run.logBytes === "number" && run.logBytes > 0 - ? Math.min(Math.max(run.logBytes + 1024, 256_000), 2_000_000) - : 256_000; - const load = async () => { try { - let offset = 0; - let first = true; - while (!cancelled) { - const result = await heartbeatsApi.log(run.id, offset, first ? firstLimit : 256_000); - if (cancelled) break; - appendLogContent(result.content, result.nextOffset === undefined); - const next = result.nextOffset ?? offset + result.content.length; - setLogOffset(next); - offset = next; - first = false; - if (result.nextOffset === undefined || isLive) break; - } + const result = await heartbeatsApi.log(run.id, 0, RUN_LOG_PAGE_BYTES); + if (cancelled) return; + appendLogContent(result.content, result.nextOffset === undefined); + const next = result.nextOffset ?? result.content.length; + setLogOffset(next); + setHasMoreLog(!isLive && result.nextOffset !== undefined); } catch (err) { if (!cancelled) { if (isLive && isRunLogUnavailable(err)) { @@ -3675,6 +3670,23 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin }; }, [run.id, run.logRef, run.logBytes, isLive]); + async function loadMorePersistedLog() { + if (loadingMoreLog || !hasMoreLog) return; + setLoadingMoreLog(true); + setLogError(null); + try { + const result = await heartbeatsApi.log(run.id, logOffset, RUN_LOG_PAGE_BYTES); + appendLogContent(result.content, result.nextOffset === undefined); + const next = result.nextOffset ?? logOffset + result.content.length; + setLogOffset(next); + setHasMoreLog(result.nextOffset !== undefined); + } catch (err) { + setLogError(err instanceof Error ? err.message : "Failed to load more run log"); + } finally { + setLoadingMoreLog(false); + } + } + // Poll for live updates useEffect(() => { if (!isLive || isStreamingConnected) return; @@ -3941,6 +3953,25 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin streaming={isLive} emptyMessage={run.logRef ? "Waiting for transcript..." : "No persisted transcript for this run."} /> + {hasMoreLog && ( +
+ + + Showing the first {Math.round(logOffset / 1024).toLocaleString("en-US")} KB + {typeof run.logBytes === "number" && run.logBytes > 0 + ? ` of ${Math.round(run.logBytes / 1024).toLocaleString("en-US")} KB` + : ""} + +
+ )} {logError && (
{logError}