fix(workbench): scope trace loading indicators (#797)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-24 07:24:18 +08:00
committed by GitHub
parent d45fc1890e
commit 84a4b70a26
+38 -9
View File
@@ -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<AnyRecord>({});
const [traceStepLoading, setTraceStepLoading] = useState<AnyRecord>({});
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 }),
),
),