perf: optimize overview/tasks/polling/microservice-cache and improve frontend progressive loading
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -49,6 +49,30 @@ const fastCodexQueueService = {
|
||||
},
|
||||
};
|
||||
|
||||
function isDocumentVisible(): boolean {
|
||||
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
||||
}
|
||||
|
||||
function shellRefreshIntervalMs(moduleId: string, tabId: string): number {
|
||||
if (moduleId === "ops" && tabId === "status") return 5_000;
|
||||
if (moduleId === "nodes" && tabId === "monitor") return 5_000;
|
||||
if (moduleId === "tasks" && (tabId === "dispatch" || tabId === "pending")) return 5_000;
|
||||
if (moduleId === "nodes" || moduleId === "ops") return 10_000;
|
||||
if (moduleId === "apps") return 15_000;
|
||||
if (moduleId === "tasks") return 15_000;
|
||||
return 30_000;
|
||||
}
|
||||
|
||||
async function loadTaskRawData(task: any): Promise<any> {
|
||||
if (!task?._summaryOnly || !task?.id) return task;
|
||||
const result = await requestJson(`${cfg.apiBaseUrl}/tasks/${encodeURIComponent(String(task.id))}`);
|
||||
return result?.task || task;
|
||||
}
|
||||
|
||||
function taskRawButtonData(task: any): any {
|
||||
return task?._summaryOnly ? { ...task, _loadRaw: () => loadTaskRawData(task) } : task;
|
||||
}
|
||||
|
||||
function fmtDate(value: any): string {
|
||||
if (!value) return "--";
|
||||
const date = new Date(value);
|
||||
@@ -262,7 +286,7 @@ function taskUpgradeSource(task: any): string {
|
||||
function taskUpgradePolicy(task: any): string {
|
||||
const result = taskResult(task);
|
||||
const plan = result.plan && typeof result.plan === "object" && !Array.isArray(result.plan) ? result.plan as AnyRecord : {};
|
||||
const policy = plan.policy;
|
||||
const policy = result.policy ?? plan.policy;
|
||||
return typeof policy === "string" && policy.length > 0 ? policy : "--";
|
||||
}
|
||||
|
||||
@@ -349,12 +373,29 @@ function Panel({ title, eyebrow, actions, children, className }: AnyRecord) {
|
||||
}
|
||||
|
||||
function RawButton({ title, data, onOpen, testId }: AnyRecord) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const loadData = data && typeof data === "object" && typeof data._loadRaw === "function" ? data._loadRaw : null;
|
||||
async function open(): Promise<void> {
|
||||
if (!loadData) {
|
||||
onOpen(title, data);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
onOpen(title, await loadData());
|
||||
} catch (err) {
|
||||
onOpen(title, { ok: false, error: errorMessage(err, "读取原始 JSON 失败"), fallback: data });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
return h("button", {
|
||||
type: "button",
|
||||
className: "ghost-btn",
|
||||
"data-testid": testId,
|
||||
onClick: () => onOpen(title, data),
|
||||
}, "查看原始JSON");
|
||||
disabled: loading,
|
||||
onClick: () => void open(),
|
||||
}, loading ? "读取中" : "查看原始JSON");
|
||||
}
|
||||
|
||||
function RawDialog({ raw, onClose }: AnyRecord) {
|
||||
@@ -1227,7 +1268,7 @@ function UpgradeRecordsTable({ records, onRaw, compact = false }: AnyRecord) {
|
||||
h("td", null, h("span", { className: "version-chip" }, taskUpgradeVersion(task))),
|
||||
h("td", null, h("span", { className: `upgrade-outcome ${String(task.status || "").toLowerCase()}` }, taskUpgradeOutcome(task))),
|
||||
h("td", null, fmtDate(task.updatedAt)),
|
||||
h("td", null, h(RawButton, { title: `Provider Upgrade Task ${task.id}`, data: task, onOpen: onRaw })),
|
||||
h("td", null, h(RawButton, { title: `Provider Upgrade Task ${task.id}`, data: taskRawButtonData(task), onOpen: onRaw })),
|
||||
))),
|
||||
));
|
||||
}
|
||||
@@ -1622,7 +1663,7 @@ function TaskCompactRow({ task, onRaw }: AnyRecord) {
|
||||
h(StatusBadge, { status: task.status }),
|
||||
h("div", null, h("strong", null, task.command), h("code", null, task.id)),
|
||||
h("span", null, isPendingTask(task) ? `已等待 ${fmtRelativeAge(task.updatedAt)}` : `耗时 ${fmtDuration(taskElapsedSeconds(task) ?? 0)}`),
|
||||
h(RawButton, { title: `Task ${task.id}`, data: task, onOpen: onRaw }),
|
||||
h(RawButton, { title: `Task ${task.id}`, data: taskRawButtonData(task), onOpen: onRaw }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1676,7 +1717,7 @@ function TaskPendingPage({ tasks, onRaw }: AnyRecord) {
|
||||
h("td", null, h("code", null, task.providerId)),
|
||||
h("td", null, fmtRelativeAge(task.updatedAt)),
|
||||
h("td", null, h(DataSummary, { data: task.payload })),
|
||||
h("td", null, h(RawButton, { title: `Pending Task ${task.id}`, data: task, onOpen: onRaw })),
|
||||
h("td", null, h(RawButton, { title: `Pending Task ${task.id}`, data: taskRawButtonData(task), onOpen: onRaw })),
|
||||
))),
|
||||
)),
|
||||
),
|
||||
@@ -1697,7 +1738,7 @@ function TaskHistoryPage({ tasks, onRaw }: AnyRecord) {
|
||||
h("td", null, h(DataSummary, { data: task.payload })),
|
||||
h("td", null, h(TaskDiagnosticCell, { task })),
|
||||
h("td", null, fmtDate(task.updatedAt)),
|
||||
h("td", null, h(RawButton, { title: `Task ${task.id}`, data: task, onOpen: onRaw })),
|
||||
h("td", null, h(RawButton, { title: `Task ${task.id}`, data: taskRawButtonData(task), onOpen: onRaw })),
|
||||
))),
|
||||
)),
|
||||
),
|
||||
@@ -1712,7 +1753,7 @@ function TaskResultsPage({ tasks, onRaw }: AnyRecord) {
|
||||
h("div", { className: "node-card-head" }, h("strong", null, task.command), h(StatusBadge, { status: task.status })),
|
||||
h("code", null, task.id),
|
||||
h(DataSummary, { data: task.result, empty: "无执行输出" }),
|
||||
h(RawButton, { title: `Task Result ${task.id}`, data: task, onOpen: onRaw }),
|
||||
h(RawButton, { title: `Task Result ${task.id}`, data: taskRawButtonData(task), onOpen: onRaw }),
|
||||
))),
|
||||
);
|
||||
}
|
||||
@@ -1802,6 +1843,7 @@ function Shell({ session, onLogout }: AnyRecord) {
|
||||
const [clock, setClock] = useState(new Date());
|
||||
const [raw, setRaw] = useState(null);
|
||||
const [railCollapsed, setRailCollapsed] = useState(false);
|
||||
const refreshInFlightRef = React.useRef(false);
|
||||
|
||||
const module = ROUTE_REGISTRY.moduleById[activeModule] || ROUTE_REGISTRY.modules[0];
|
||||
const activeTab = activeTabs[activeModule] || DEFAULT_ACTIVE_TABS[activeModule] || module.tabs[0].id;
|
||||
@@ -1824,28 +1866,30 @@ function Shell({ session, onLogout }: AnyRecord) {
|
||||
}] : [];
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
if (refreshInFlightRef.current) return;
|
||||
refreshInFlightRef.current = true;
|
||||
try {
|
||||
const requests: Array<[string, Promise<any>]> = [];
|
||||
const add = (key: string, path: string): void => {
|
||||
requests.push([key, requestJson(path)]);
|
||||
};
|
||||
const isOverview = activeModule === "ops" && activeTab === "status";
|
||||
const needsOverviewSummary = activeModule !== "apps";
|
||||
const needsOverviewSummary = isOverview || (activeModule === "config" && activeTab === "topology");
|
||||
const needsNodes = isOverview || activeModule === "nodes" || (activeModule === "tasks" && activeTab === "dispatch");
|
||||
const needsMicroservices = activeModule === "apps" && activeTab !== "codex-queue";
|
||||
if (needsOverviewSummary) add("overview", `${cfg.apiBaseUrl}/overview`);
|
||||
if (needsNodes) add("nodes", `${cfg.apiBaseUrl}/nodes`);
|
||||
if (activeModule === "nodes" && activeTab === "monitor") {
|
||||
add("systemStatuses", `${cfg.apiBaseUrl}/nodes/system-status?limit=60`);
|
||||
add("tasks", `${cfg.apiBaseUrl}/tasks?limit=120`);
|
||||
add("tasks", `${cfg.apiBaseUrl}/tasks?limit=120&summary=1`);
|
||||
} else if (activeModule === "nodes" && activeTab === "docker") {
|
||||
add("dockerStatuses", `${cfg.apiBaseUrl}/nodes/docker-status`);
|
||||
} else if (activeModule === "nodes" && activeTab === "gateway") {
|
||||
add("tasks", `${cfg.apiBaseUrl}/tasks?limit=300`);
|
||||
add("tasks", `${cfg.apiBaseUrl}/tasks?limit=300&summary=1`);
|
||||
} else if (activeModule === "tasks" && activeTab === "pending") {
|
||||
add("pendingTasks", `${cfg.apiBaseUrl}/tasks?status=pending&limit=100`);
|
||||
add("pendingTasks", `${cfg.apiBaseUrl}/tasks?status=pending&limit=100&summary=1`);
|
||||
} else if (activeModule === "tasks" && (activeTab === "history" || activeTab === "results")) {
|
||||
add("tasks", `${cfg.apiBaseUrl}/tasks?limit=300`);
|
||||
add("tasks", `${cfg.apiBaseUrl}/tasks?limit=300&summary=1`);
|
||||
} else if (isOverview) {
|
||||
add("tasks", `${cfg.apiBaseUrl}/tasks?limit=8&lite=1`);
|
||||
add("pendingTasks", `${cfg.apiBaseUrl}/tasks?status=pending&limit=20&lite=1`);
|
||||
@@ -1873,13 +1917,26 @@ function Shell({ session, onLogout }: AnyRecord) {
|
||||
} catch (err) {
|
||||
setConnection({ ok: false, text: errorMessage(err, "连接失败") });
|
||||
if ((err as { status?: number }).status === 401) onLogout(false);
|
||||
} finally {
|
||||
refreshInFlightRef.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
const timer = setInterval(refresh, 5000);
|
||||
return () => clearInterval(timer);
|
||||
const tick = (): void => {
|
||||
if (!isDocumentVisible()) return;
|
||||
void refresh();
|
||||
};
|
||||
tick();
|
||||
const timer = setInterval(tick, shellRefreshIntervalMs(activeModule, activeTab));
|
||||
const onVisible = (): void => {
|
||||
if (isDocumentVisible()) tick();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisible);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
document.removeEventListener("visibilitychange", onVisible);
|
||||
};
|
||||
}, [activeModule, activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -11,6 +11,16 @@ const useState: any = React.useState;
|
||||
const codexTranscriptChunkLimit = 120;
|
||||
const codexInitialTaskLimit = 24;
|
||||
const codexMoreTaskLimit = 48;
|
||||
const codexLocalReadRetention = 300;
|
||||
const queueErrorPreviewLength = 1200;
|
||||
|
||||
function isDocumentVisible(): boolean {
|
||||
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
||||
}
|
||||
|
||||
function errorText(error: unknown, fallback = "操作失败"): string {
|
||||
return errorMessage(error, fallback);
|
||||
}
|
||||
|
||||
function fmtDate(value: any): string {
|
||||
if (!value) return "--";
|
||||
@@ -53,6 +63,7 @@ async function requestJson(path: string, options: AnyRecord = {}): Promise<any>
|
||||
retryInvalidJson: 1,
|
||||
invalidJsonPrefix: "Codex Queue 返回了无效 JSON",
|
||||
invalidJsonPreview: true,
|
||||
responsePreviewLength: queueErrorPreviewLength,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
@@ -678,19 +689,33 @@ function loadLocalReadAt(): AnyRecord {
|
||||
if (typeof window === "undefined") return {};
|
||||
try {
|
||||
const parsed = JSON.parse(window.localStorage.getItem(codexReadAtStorageKey) || "{}");
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? trimLocalReadAt(parsed) : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function saveLocalReadAt(readAtByTask: AnyRecord): void {
|
||||
if (typeof window === "undefined") return;
|
||||
function trimLocalReadAt(readAtByTask: AnyRecord): AnyRecord {
|
||||
const entries = Object.entries(readAtByTask || {})
|
||||
.filter(([, value]) => typeof value === "string" && value.length > 0)
|
||||
.sort((left, right) => {
|
||||
const leftTime = Date.parse(String(left[1] || ""));
|
||||
const rightTime = Date.parse(String(right[1] || ""));
|
||||
return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
|
||||
})
|
||||
.slice(0, codexLocalReadRetention);
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
function saveLocalReadAt(readAtByTask: AnyRecord): AnyRecord {
|
||||
const retained = trimLocalReadAt(readAtByTask);
|
||||
if (typeof window === "undefined") return retained;
|
||||
try {
|
||||
window.localStorage.setItem(codexReadAtStorageKey, JSON.stringify(readAtByTask));
|
||||
window.localStorage.setItem(codexReadAtStorageKey, JSON.stringify(retained));
|
||||
} catch {
|
||||
// Best-effort fallback only; backend readAt remains authoritative when deployed.
|
||||
}
|
||||
return retained;
|
||||
}
|
||||
|
||||
function applyLocalReadState(task: any, readAtByTask: AnyRecord): any {
|
||||
@@ -1459,8 +1484,7 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
setLocalReadAt((previous: AnyRecord) => {
|
||||
const next = { ...(previous || {}) };
|
||||
for (const id of ids) next[id] = readAt;
|
||||
saveLocalReadAt(next);
|
||||
return next;
|
||||
return saveLocalReadAt(next);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1854,7 +1878,7 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
startedAt: new Date(Date.now() - queueMs),
|
||||
});
|
||||
}
|
||||
if (nextId) void ensureTraceSummary(nextId, true, trackLoad ? startedAt : undefined, trackLoad ? queueMs : undefined).catch((err) => setError(errorMessage(err, "加载 Codex Trace Summary 失败")));
|
||||
if (nextId) void ensureTraceSummary(nextId, true, trackLoad ? startedAt : undefined, trackLoad ? queueMs : undefined).catch((err) => setError(errorText(err, "加载 Codex Trace Summary 失败")));
|
||||
else {
|
||||
detailLoadTokenRef.current += 1;
|
||||
setSelectedTask(null);
|
||||
@@ -1902,7 +1926,7 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
setError(errorMessage(err, "加载更早 Codex tasks 失败"));
|
||||
setError(errorText(err, "加载更早 Codex tasks 失败"));
|
||||
} finally {
|
||||
loadMoreInFlightRef.current = false;
|
||||
setLoadingMoreTasks(false);
|
||||
@@ -1922,7 +1946,7 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
try {
|
||||
await action();
|
||||
} catch (err) {
|
||||
setError(errorMessage(err, message));
|
||||
setError(errorText(err, message));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
@@ -1955,7 +1979,7 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
setNotice(`已复制任务 ID:${taskId}`);
|
||||
window.setTimeout(() => setCopiedTaskId((value: string) => value === taskId ? "" : value), 1600);
|
||||
} catch (err) {
|
||||
setError(`复制任务 ID 失败:${errorMessage(err)}`);
|
||||
setError(`复制任务 ID 失败:${errorText(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2177,7 +2201,7 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
if (row) setSelectedTask(row);
|
||||
else setSelectedTask(null);
|
||||
}
|
||||
void load(taskId).catch((err) => setError(errorMessage(err, "切换 Codex session 失败")));
|
||||
void load(taskId).catch((err) => setError(errorText(err, "切换 Codex session 失败")));
|
||||
}
|
||||
|
||||
function selectTaskFromSidebar(taskId: string): void {
|
||||
@@ -2195,10 +2219,21 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
|
||||
useEffect(() => {
|
||||
if (!service) return undefined;
|
||||
const tick = (): void => {
|
||||
if (!isDocumentVisible()) return;
|
||||
void load(selectedIdRef.current, false).catch((err) => setError(errorText(err, "Codex Queue 轮询失败")));
|
||||
};
|
||||
const timer = window.setInterval(() => {
|
||||
void load(selectedIdRef.current, false).catch((err) => setError(errorMessage(err, "Codex Queue 轮询失败")));
|
||||
tick();
|
||||
}, 1500);
|
||||
return () => window.clearInterval(timer);
|
||||
const onVisible = (): void => {
|
||||
if (isDocumentVisible()) tick();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisible);
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
document.removeEventListener("visibilitychange", onVisible);
|
||||
};
|
||||
}, [service?.id, selectedQueueId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -2211,7 +2246,7 @@ export function CodexQueuePage({ microservices, onRaw, apiBaseUrl = "/api", init
|
||||
const key = `${taskId}:${updatedAt || "unknown"}`;
|
||||
if (autoTraceLoadKeysRef.current.has(key)) return;
|
||||
autoTraceLoadKeysRef.current.add(key);
|
||||
void ensureTraceSummary(taskId, true).catch((err) => setError(errorMessage(err, "自动加载 Trace Summary 失败")));
|
||||
void ensureTraceSummary(taskId, true).catch((err) => setError(errorText(err, "自动加载 Trace Summary 失败")));
|
||||
}, [service?.id, selectedTask?.id, selectedTask?.updatedAt, selectedTask?._traceSummaryUpdatedAt, selectedTask?._traceSummaryLoaded, selectedDetailLoading]);
|
||||
|
||||
const taskListContent = tasks.length === 0 ? h(EmptyState, { title: "队列为空", text: "提交一个任务后,Codex 会串行执行并保存输出。" }) : [
|
||||
|
||||
@@ -57,6 +57,7 @@ const clientConfig = JSON.stringify({
|
||||
const indexHtmlTemplate = readFileSync(join(publicDir, "index.html"), "utf8");
|
||||
const indexHtmlRootMarker = '<div id="root" data-config="__UNIDESK_CONFIG__"></div>';
|
||||
const codexQueueOverviewCache = new Map<string, { at: number; payload: JsonValue; text: string }>();
|
||||
const codexQueueOverviewRefreshes = new Map<string, Promise<JsonValue | null>>();
|
||||
const codexQueueOverviewCacheTtlMs = 10_000;
|
||||
const defaultCodexQueueOverviewPath = "/api/tasks/overview?limit=24&transcriptLimit=3&compact=1&afterSeq=0&preferId=";
|
||||
|
||||
@@ -71,6 +72,17 @@ function cachedCodexQueueOverview(pathWithQuery: string, maxAgeMs = codexQueueOv
|
||||
}
|
||||
|
||||
async function refreshCodexQueueOverview(pathWithQuery: string, timeoutMs = 800): Promise<JsonValue | null> {
|
||||
const existing = codexQueueOverviewRefreshes.get(pathWithQuery);
|
||||
if (existing !== undefined) return existing;
|
||||
const refresh = refreshCodexQueueOverviewUncached(pathWithQuery, timeoutMs)
|
||||
.finally(() => {
|
||||
codexQueueOverviewRefreshes.delete(pathWithQuery);
|
||||
});
|
||||
codexQueueOverviewRefreshes.set(pathWithQuery, refresh);
|
||||
return refresh;
|
||||
}
|
||||
|
||||
async function refreshCodexQueueOverviewUncached(pathWithQuery: string, timeoutMs = 800): Promise<JsonValue | null> {
|
||||
const started = performance.now();
|
||||
try {
|
||||
const response = await fetch(`http://codex-queue:4222${pathWithQuery}`, {
|
||||
@@ -123,9 +135,6 @@ async function spaShellHtml(req: Request, pathname: string): Promise<string> {
|
||||
}
|
||||
|
||||
refreshCodexQueueOverview(defaultCodexQueueOverviewPath, 2_000).catch(() => undefined);
|
||||
setInterval(() => {
|
||||
refreshCodexQueueOverview(defaultCodexQueueOverviewPath, 2_000).catch(() => undefined);
|
||||
}, 5_000);
|
||||
const requestPerformanceSamples: RequestPerformanceSample[] = [];
|
||||
const operationPerformanceSamples: OperationPerformanceSample[] = [];
|
||||
const maxPerformanceSamples = 3000;
|
||||
|
||||
@@ -35,6 +35,10 @@ const pipelineGanttNodeColumnWidth = 72;
|
||||
const pipelineGanttHeaderHeight = 64;
|
||||
const pipelineGanttArrowTipInsetPx = 12;
|
||||
|
||||
function isDocumentVisible(): boolean {
|
||||
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
||||
}
|
||||
|
||||
function pipelinePercent(value: any, fallback: number): number {
|
||||
const number = Number.parseFloat(String(value || ""));
|
||||
return Number.isFinite(number) ? number / 100 : fallback;
|
||||
@@ -3448,12 +3452,12 @@ export function PipelinePage({ microservices, onRaw, apiBaseUrl = "/api" }: AnyR
|
||||
loadRequestRef.current = requestId;
|
||||
if (!silent) setState((prev: any) => ({ ...prev, loading: true, error: "" }));
|
||||
try {
|
||||
const snapshotQuery = `__unideskArrayLimit=registry.components:80,runs:${pipelineSnapshotRunLimit}&_=${Date.now()}`;
|
||||
const snapshotQuery = `__unideskArrayLimit=registry.components:80,runs:${pipelineSnapshotRunLimit}`;
|
||||
const [snapshot, oaDiagnostics, minimaxQuota] = await Promise.all([
|
||||
requestJson(`${apiBaseUrl}/microservices/pipeline/proxy/api/snapshot?${snapshotQuery}`, { cache: "no-store" }),
|
||||
requestJson(`${apiBaseUrl}/microservices/pipeline/proxy/api/oa-event-flow/diagnostics?_=${Date.now()}`, { cache: "no-store" })
|
||||
requestJson(`${apiBaseUrl}/microservices/pipeline/proxy/api/oa-event-flow/diagnostics`, { cache: "no-store" })
|
||||
.catch((error: unknown) => ({ ok: false, error: errorMessage(error, "OA event flow diagnostics failed") })),
|
||||
requestJson(`${apiBaseUrl}/microservices/pipeline/proxy/api/model-quota/minimax?_=${Date.now()}`, { cache: "no-store" })
|
||||
requestJson(`${apiBaseUrl}/microservices/pipeline/proxy/api/model-quota/minimax`, { cache: "no-store" })
|
||||
.catch((error: unknown) => ({ ok: false, error: errorMessage(error, "MiniMax quota failed") })),
|
||||
]);
|
||||
if (requestId !== loadRequestRef.current) return;
|
||||
@@ -3470,10 +3474,20 @@ export function PipelinePage({ microservices, onRaw, apiBaseUrl = "/api" }: AnyR
|
||||
useEffect(() => {
|
||||
load();
|
||||
if (!service) return undefined;
|
||||
const tick = (): void => {
|
||||
if (isDocumentVisible()) load({ silent: true });
|
||||
};
|
||||
const timer = window.setInterval(() => {
|
||||
load({ silent: true });
|
||||
tick();
|
||||
}, pipelineAutoRefreshMs);
|
||||
return () => window.clearInterval(timer);
|
||||
const onVisible = (): void => {
|
||||
if (isDocumentVisible()) tick();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisible);
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
document.removeEventListener("visibilitychange", onVisible);
|
||||
};
|
||||
}, [service?.id, service?.runtime?.providerStatus, apiBaseUrl]);
|
||||
|
||||
const runtime = microserviceRuntime(service);
|
||||
@@ -3587,8 +3601,8 @@ export function PipelinePage({ microservices, onRaw, apiBaseUrl = "/api" }: AnyR
|
||||
}));
|
||||
try {
|
||||
const [details, runSummary] = await Promise.all([
|
||||
requestJson(`${pipelineProxyPath(apiBaseUrl, `/api/node-control/runs/${encodeURIComponent(runId)}?tail=160&view=timeline`)}&_=${Date.now()}`, { cache: "no-store", strictJson: true }),
|
||||
requestJson(`${pipelineProxyPath(apiBaseUrl, `/api/runs/${encodeURIComponent(runId)}`)}?_=${Date.now()}`, { cache: "no-store" }).catch((error: unknown) => ({ ok: false, runSummaryError: errorMessage(error, "抓取评分失败") })),
|
||||
requestJson(pipelineProxyPath(apiBaseUrl, `/api/node-control/runs/${encodeURIComponent(runId)}?tail=160&view=timeline`), { cache: "no-store", strictJson: true }),
|
||||
requestJson(pipelineProxyPath(apiBaseUrl, `/api/runs/${encodeURIComponent(runId)}`), { cache: "no-store" }).catch((error: unknown) => ({ ok: false, runSummaryError: errorMessage(error, "抓取评分失败") })),
|
||||
]);
|
||||
if (requestId !== runDetailsRequestRef.current) return;
|
||||
setRunDetails({ runId, scale, loading: false, error: "", details: { ...details, run: isRecord(runSummary?.run) ? runSummary.run : undefined, runSummaryError: runSummary?.runSummaryError }, fetchedAt: new Date() });
|
||||
@@ -3653,10 +3667,20 @@ export function PipelinePage({ microservices, onRaw, apiBaseUrl = "/api" }: AnyR
|
||||
return undefined;
|
||||
}
|
||||
void fetchRunDetails(activeRunId);
|
||||
const tick = (): void => {
|
||||
if (isDocumentVisible()) void fetchRunDetails(activeRunId, { silent: true });
|
||||
};
|
||||
const timer = window.setInterval(() => {
|
||||
void fetchRunDetails(activeRunId, { silent: true });
|
||||
tick();
|
||||
}, pipelineAutoRefreshMs);
|
||||
return () => window.clearInterval(timer);
|
||||
const onVisible = (): void => {
|
||||
if (isDocumentVisible()) tick();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisible);
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
document.removeEventListener("visibilitychange", onVisible);
|
||||
};
|
||||
}, [activeRunId, apiBaseUrl]);
|
||||
|
||||
async function fetchNodeDetails(runId = activeRunId, nodeId = selectedNodeId): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user