refactor(runs): extract useRunLogs hook from RunCenter
RunCenter had three useEffects juggling log-chunk loading, tailing, and older-chunk fetches. Move the whole lifecycle into useRunLogs, which uses a single effect + AbortController to scope every IPC call to the current run and pauses polling while document.hidden.
This commit is contained in:
1 parent
0c9d8269c1
commit
933fcf27d0
2 files changed
+143
-137
No files matched your search
@@ -2,10 +2,9 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import type { AgentRecord, RunRecord, TaskRecord, WorkspaceRecord } from "@shared/types";
|
||||
import { EmptyState } from "../ui";
|
||||
import { useT } from "../../i18n";
|
||||
import { LOG_CHUNK_BYTES, initialLogState, isActiveStatus } from "./run-helpers";
|
||||
import type { RunLogState } from "./run-helpers";
|
||||
import { RunQueue } from "./RunQueue";
|
||||
import { RunDetail } from "./RunDetail";
|
||||
import { useRunLogs } from "../../hooks/useRunLogs";
|
||||
|
||||
interface RunCenterProps {
|
||||
runs: RunRecord[];
|
||||
@@ -33,8 +32,6 @@ export function RunCenter({
|
||||
const t = useT();
|
||||
const [statusFilter, setStatusFilter] = useState<"all" | RunRecord["status"]>("all");
|
||||
const [search, setSearch] = useState("");
|
||||
const [logState, setLogState] = useState<RunLogState>(initialLogState);
|
||||
const [logReloadKey, setLogReloadKey] = useState(0);
|
||||
|
||||
const agentMap = useMemo(() => new Map(agents.map((agent) => [agent.id, agent])), [agents]);
|
||||
const taskMap = useMemo(() => new Map(tasks.map((task) => [task.id, task])), [tasks]);
|
||||
@@ -69,6 +66,8 @@ export function RunCenter({
|
||||
});
|
||||
}, [agentMap, runs, search, statusFilter, taskMap, workspaceMap]);
|
||||
|
||||
const { logState, reload: reloadLogs, loadOlder: loadOlderLogs } = useRunLogs(selectedRun);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRunId && !runs.some((run) => run.id === selectedRunId)) {
|
||||
const fallbackRun = runs[0];
|
||||
@@ -78,138 +77,6 @@ export function RunCenter({
|
||||
}
|
||||
}, [onSelectRun, runs, selectedRunId]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadInitialChunk() {
|
||||
if (!selectedRun) {
|
||||
setLogState(initialLogState);
|
||||
return;
|
||||
}
|
||||
|
||||
setLogState((current) => ({ ...current, loading: true, error: null }));
|
||||
|
||||
const metaResult = await window.agentCompany.getRunLogChunk({
|
||||
companyId: selectedRun.companyId,
|
||||
runId: selectedRun.id,
|
||||
offset: 0,
|
||||
limit: 1024,
|
||||
});
|
||||
|
||||
if (!metaResult.ok) {
|
||||
if (!cancelled) {
|
||||
setLogState((current) => ({
|
||||
...current,
|
||||
loading: false,
|
||||
error: metaResult.error.message,
|
||||
}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const totalBytes = metaResult.data.totalBytes;
|
||||
const startOffset = Math.max(0, totalBytes - LOG_CHUNK_BYTES);
|
||||
const chunkResult =
|
||||
startOffset === 0 && totalBytes <= LOG_CHUNK_BYTES
|
||||
? metaResult
|
||||
: await window.agentCompany.getRunLogChunk({
|
||||
companyId: selectedRun.companyId,
|
||||
runId: selectedRun.id,
|
||||
offset: startOffset,
|
||||
limit: LOG_CHUNK_BYTES,
|
||||
});
|
||||
|
||||
if (!chunkResult.ok) {
|
||||
if (!cancelled) {
|
||||
setLogState((current) => ({
|
||||
...current,
|
||||
loading: false,
|
||||
error: chunkResult.error.message,
|
||||
}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
setLogState({
|
||||
...chunkResult.data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void loadInitialChunk();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [logReloadKey, selectedRun]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedRun || !isActiveStatus(selectedRun.status)) return;
|
||||
let cancelled = false;
|
||||
const tick = async () => {
|
||||
if (document.visibilityState === "hidden") return;
|
||||
const latest = await window.agentCompany.getRunLogChunk({
|
||||
companyId: selectedRun.companyId,
|
||||
runId: selectedRun.id,
|
||||
offset: Math.max(0, logState.totalBytes - LOG_CHUNK_BYTES),
|
||||
limit: LOG_CHUNK_BYTES,
|
||||
});
|
||||
|
||||
if (!latest.ok || cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLogState({
|
||||
...latest.data,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
};
|
||||
const intervalId = window.setInterval(tick, 2500);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearInterval(intervalId);
|
||||
};
|
||||
}, [logState.totalBytes, selectedRun]);
|
||||
|
||||
async function loadOlderLogs() {
|
||||
if (!selectedRun || logState.offset <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextOffset = Math.max(0, logState.offset - LOG_CHUNK_BYTES);
|
||||
setLogState((current) => ({ ...current, loading: true, error: null }));
|
||||
|
||||
const result = await window.agentCompany.getRunLogChunk({
|
||||
companyId: selectedRun.companyId,
|
||||
runId: selectedRun.id,
|
||||
offset: nextOffset,
|
||||
limit: logState.offset - nextOffset,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
setLogState((current) => ({
|
||||
...current,
|
||||
loading: false,
|
||||
error: result.error.message,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
setLogState({
|
||||
...result.data,
|
||||
content: `${result.data.content}${logState.content}`,
|
||||
nextOffset: logState.nextOffset,
|
||||
eof: logState.eof,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
|
||||
if (runs.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
@@ -245,7 +112,7 @@ export function RunCenter({
|
||||
onOpenConsole={onOpenConsole}
|
||||
onCancelRun={onCancelRun}
|
||||
onOpenLogPath={onOpenLogPath}
|
||||
onReloadLog={() => setLogReloadKey((current) => current + 1)}
|
||||
onReloadLog={reloadLogs}
|
||||
onLoadOlderLogs={() => void loadOlderLogs()}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user