diff --git a/src/components/frontend/src/code-queue.tsx b/src/components/frontend/src/code-queue.tsx index 1387b363..599ff265 100644 --- a/src/components/frontend/src/code-queue.tsx +++ b/src/components/frontend/src/code-queue.tsx @@ -1196,6 +1196,15 @@ function taskTraceStepsLoaded(task: any, attemptIndex: any = null): boolean { return Boolean(task?._traceStepsLoaded); } +function traceStepsLoadingKey(taskId: string, attemptIndex: any = null): string { + const attemptKey = attemptIndex === null || attemptIndex === undefined || String(attemptIndex).length === 0 ? "all" : String(attemptIndex); + return `${taskId}:${attemptKey}`; +} + +function traceStepLoadingKey(taskId: string, seq: any): string { + return `${taskId}:${String(seq ?? "")}`; +} + function taskTraceStepDetails(task: any): AnyRecord { return task?._traceStepDetails && typeof task._traceStepDetails === "object" && !Array.isArray(task._traceStepDetails) ? task._traceStepDetails : {}; } @@ -2158,12 +2167,14 @@ function ProgressivePromptBlock({ task, loading, onLoadPromptPart, testId = "cod ); } -function ProgressiveExecutionSummary({ task, attempt, attemptIndex, loading, onLoadSteps, onLoadStep, testId = "codex-execution-summary" }: AnyRecord) { +function ProgressiveExecutionSummary({ task, attempt, attemptIndex, traceStepsLoading, traceStepLoading, onLoadSteps, onLoadStep, testId = "codex-execution-summary" }: AnyRecord) { const steps = coalesceTraceSteps(taskTraceSteps(task, attemptIndex)); const execution = canonicalExecutionSummary(attemptExecutionSummary(task, attempt)); const stats = attempt ? attemptOaTraceStats(task, attempt) : taskOaTraceStats(task); const stepDetails = taskTraceStepDetails(task); const stepsLoaded = taskTraceStepsLoaded(task, attemptIndex); + const taskId = String(task?.id || ""); + const stepsLoading = Boolean(traceStepsLoading?.[traceStepsLoadingKey(taskId, attemptIndex)]); const errorCount = statCount(stats?.errorCount); const fallbackStepCount = rawTraceStepCountFallback( steps.length > 0 ? steps.length : null, @@ -2226,10 +2237,11 @@ function ProgressiveExecutionSummary({ task, attempt, attemptIndex, loading, onL h("span", null, `执行命令:${listPreview(commands, 4)}`), ), steps.length === 0 - ? h("div", { className: "codex-output-empty" }, loading ? "正在按需拉取步骤 summary..." : "展开后将只请求执行步骤 summary,不拉取单步骤全量。") + ? h("div", { className: "codex-output-empty" }, stepsLoading ? "正在按需拉取步骤 summary..." : "展开后将只请求执行步骤 summary,不拉取单步骤全量。") : h("div", { className: "codex-trace-step-list" }, steps.map((step: any) => { const seq = String(step?.seq ?? ""); const detail = stepDetails[seq]; + const detailLoading = Boolean(traceStepLoading?.[traceStepLoadingKey(taskId, seq)]); const summaryLines = Array.isArray(step?.summaryLines) ? step.summaryLines.slice(0, 4) : []; const inlineSummary = summaryLines.find((line: any) => String(line || "").trim().length > 0); return h("details", { @@ -2261,7 +2273,7 @@ function ProgressiveExecutionSummary({ task, attempt, attemptIndex, loading, onL className: "codex-transcript codex-step-detail-transcript", collapseTools: false, }) - : h("div", { className: "codex-output-empty" }, loading ? "正在按需拉取这个步骤的全量数据..." : "展开后将只请求这个单步骤的全量数据。"), + : h("div", { className: "codex-output-empty" }, detailLoading ? "正在按需拉取这个步骤的全量数据..." : "展开后将只请求这个单步骤的全量数据。"), ); })), ); @@ -2345,7 +2357,7 @@ function ProgressiveJudgeFeedbackPrompt({ task, attempt, attemptIndex, loading, ); } -function ProgressiveAttemptCycle({ task, attempt, position, loading, onLoadPromptPart, onLoadSteps, onLoadStep }: AnyRecord) { +function ProgressiveAttemptCycle({ task, attempt, position, loading, traceStepsLoading, traceStepLoading, onLoadPromptPart, onLoadSteps, onLoadStep }: AnyRecord) { const attemptIndex = attemptSegmentIndex(attempt, position); const current = attemptIsActiveCurrent(task, attemptIndex); const first = position === 0 && !current; @@ -2362,7 +2374,8 @@ function ProgressiveAttemptCycle({ task, attempt, position, loading, onLoadPromp task, attempt, attemptIndex, - loading, + traceStepsLoading, + traceStepLoading, onLoadSteps, onLoadStep, testId: current ? "codex-execution-summary-current" : first ? "codex-execution-summary" : `codex-execution-summary-attempt-${attemptIndex}`, @@ -2390,16 +2403,16 @@ function ProgressiveAttemptCycle({ task, attempt, position, loading, onLoadPromp ); } -function ProgressiveTrace({ task, loading, onLoadPromptPart, onLoadSteps, onLoadStep }: AnyRecord) { +function ProgressiveTrace({ task, loading, traceStepsLoading, traceStepLoading, onLoadPromptPart, onLoadSteps, onLoadStep }: AnyRecord) { if (!task) return h(EmptyState, { title: "未选择任务", text: "从左侧队列选择任务,或提交新 Codex 任务。" }); const attempts = taskProgressiveAttempts(task); return h("div", { className: "codex-transcript codex-progressive-trace", "data-testid": "codex-output" }, loading && !taskTraceSummary(task) ? h("div", { className: "codex-output-empty" }, "正在加载 Trace Summary...") : null, h(ProgressivePromptBlock, { task, loading, onLoadPromptPart }), attempts.length > 0 - ? attempts.map((attempt: any, index: number) => h(ProgressiveAttemptCycle, { key: `${attempt?.index || index + 1}-${attempt?.startedAt || index}`, task, attempt, position: index, loading, onLoadPromptPart, onLoadSteps, onLoadStep })) + ? attempts.map((attempt: any, index: number) => h(ProgressiveAttemptCycle, { key: `${attempt?.index || index + 1}-${attempt?.startedAt || index}`, task, attempt, position: index, loading, traceStepsLoading, traceStepLoading, onLoadPromptPart, onLoadSteps, onLoadStep })) : [ - h(ProgressiveExecutionSummary, { key: "execution", task, loading, onLoadSteps, onLoadStep }), + h(ProgressiveExecutionSummary, { key: "execution", task, traceStepsLoading, traceStepLoading, onLoadSteps, onLoadStep }), h(ProgressiveFinalResponse, { key: "final", task }), h(ProgressiveJudge, { key: "judge", task }), ], @@ -2492,6 +2505,8 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi const [selectedId, setSelectedId] = useState(initialSelectedId); const [selectedTask, setSelectedTask] = useState(initialSelectedTask); const [selectedDetailLoading, setSelectedDetailLoading] = useState(false); + const [traceStepsLoading, setTraceStepsLoading] = useState({}); + const [traceStepLoading, setTraceStepLoading] = useState({}); const [taskSearchQuery, setTaskSearchQuery] = useState(""); const [searchTasksData, setSearchTasksData] = useState(null); const [searchLoading, setSearchLoading] = useState(false); @@ -3015,6 +3030,8 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi const existing = traceStepsInFlightRef.current.get(key); if (existing) return existing; const token = detailLoadTokenRef.current; + const loadingKey = traceStepsLoadingKey(taskId, attemptKey || null); + setTraceStepsLoading((prev: AnyRecord) => ({ ...prev, [loadingKey]: true })); if (selectedIdRef.current === taskId) setSelectedDetailLoading(true); const promise = (async () => { try { @@ -3042,6 +3059,11 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi } } finally { traceStepsInFlightRef.current.delete(key); + setTraceStepsLoading((prev: AnyRecord) => { + const next = { ...prev }; + delete next[loadingKey]; + return next; + }); if (token === detailLoadTokenRef.current && selectedIdRef.current === taskId) setSelectedDetailLoading(false); } })(); @@ -3060,6 +3082,8 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi const existing = traceStepInFlightRef.current.get(key); if (existing) return existing; const token = detailLoadTokenRef.current; + const loadingKey = traceStepLoadingKey(taskId, seqKey); + setTraceStepLoading((prev: AnyRecord) => ({ ...prev, [loadingKey]: true })); if (selectedIdRef.current === taskId) setSelectedDetailLoading(true); const promise = (async () => { try { @@ -3072,6 +3096,11 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi }, token); } finally { traceStepInFlightRef.current.delete(key); + setTraceStepLoading((prev: AnyRecord) => { + const next = { ...prev }; + delete next[loadingKey]; + return next; + }); if (token === detailLoadTokenRef.current && selectedIdRef.current === taskId) setSelectedDetailLoading(false); } })(); @@ -4241,7 +4270,7 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi ) : null, h("div", { className: "codex-session-main" }, h("div", { className: "codex-output-stack" }, - h(ProgressiveTrace, { task: selectedTask, loading: selectedDetailLoading, onLoadPromptPart: ensurePromptPart, onLoadSteps: ensureTraceSteps, onLoadStep: ensureTraceStep }), + h(ProgressiveTrace, { task: selectedTask, loading: selectedDetailLoading, traceStepsLoading, traceStepLoading, onLoadPromptPart: ensurePromptPart, onLoadSteps: ensureTraceSteps, onLoadStep: ensureTraceStep }), h(RawTranscript, { task: selectedTask }), ), ),