返回 DeepSeek-Reasonix
turnResult.ts
根目录 / desktop / frontend / src / lib / turnResult.ts
1 import type { Translator } from "./i18n";
2 import type { TurnChanges, WireCompletionReceipt, WireCompletionSummary } from "./types";
3
4 const validCount = (value: unknown): value is number => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
5
6 export function normalizeTurnChanges(value?: TurnChanges): TurnChanges | undefined {
7 if (!value) return undefined;
8 const valid = Array.isArray(value.files) && validCount(value.added) && validCount(value.removed) && validCount(value.turn);
9 const files = (Array.isArray(value.files) ? value.files : []).filter(file => file && typeof file.path === "string" && validCount(file.added) && validCount(file.removed));
10 return {
11 id: typeof value.id === "string" ? value.id : undefined,
12 turn: validCount(value.turn) ? value.turn : 0,
13 coverage: valid && files.length === value.files.length && (value.coverage === "complete" || value.coverage === "partial") ? value.coverage : "unknown",
14 files,
15 added: validCount(value.added) ? value.added : 0,
16 removed: validCount(value.removed) ? value.removed : 0,
17 reasons: (Array.isArray(value.reasons) ? value.reasons : []).filter((v): v is string => typeof v === "string"),
18 };
19 }
20
21 export function normalizeCompletionReceipt(value?: WireCompletionReceipt): WireCompletionReceipt | undefined {
22 if (!value) return undefined;
23 return {
24 verdict: String(value.verdict ?? "unknown"),
25 assessmentKind: value.assessmentKind === "facts" ? "facts" : undefined,
26 diff: normalizeTurnChanges(value.diff),
27 interrupted: value.interrupted === true,
28 changes: Array.isArray(value.changes) ? value.changes : [],
29 verifications: (Array.isArray(value.verifications) ? value.verifications : []).filter(v => v && typeof v.command === "string").map(v => ({
30 ...v, passed: v.passed === true && (v.exitCode === undefined || v.exitCode === 0), stale: v.stale === true,
31 })),
32 gaps: (Array.isArray(value.gaps) ? value.gaps : []).filter(g => g && typeof g.kind === "string"),
33 risks: (Array.isArray(value.risks) ? value.risks : []).filter((v): v is string => typeof v === "string"),
34 };
35 }
36
37 export function mergeTurnResult(
38 summary?: WireCompletionSummary,
39 receipt?: WireCompletionReceipt,
40 turnId?: string,
41 checkpointTurn?: number,
42 ): WireCompletionSummary {
43 const normalized = normalizeCompletionReceipt(receipt);
44 return {
45 preset: "", verdict: normalized?.verdict ?? "unknown", mutations: 0,
46 checks_passed: 0, checks_failed: 0, checks_suppressed: 0, review: "none", constraint_degraded: false,
47 ...summary,
48 receipt: normalized ?? summary?.receipt,
49 turnId: turnId ?? summary?.turnId,
50 checkpointTurn: validCount(checkpointTurn) ? checkpointTurn : summary?.checkpointTurn,
51 };
52 }
53
54 export function turnResultHasContent(summary: WireCompletionSummary): boolean {
55 const receipt = summary.receipt;
56 return Boolean(summary.checking || summary.mutations > 0 || summary.checks_failed > 0 || summary.checks_passed > 0 || summary.checks_suppressed > 0 || summary.attention
57 || receipt?.diff?.files.length || receipt?.diff?.reasons.length || receipt?.changes?.length || receipt?.verifications?.length || receipt?.gaps?.length || receipt?.risks?.length);
58 }
59
60 export function turnChangeText(summary: WireCompletionSummary, t: Translator): string {
61 const diff = summary.receipt?.diff;
62 if (!diff || diff.coverage === "unknown") return t("completion.changesUnknown");
63 if (diff.files.length === 0) return t(diff.coverage === "complete" ? "completion.noNetChanges" : "completion.changesIncomplete");
64 const files = t(diff.coverage === "complete" ? "completion.filesChanged" : "completion.filesCounted", { count: diff.files.length });
65 const suffix = diff.coverage === "partial" ? ` · ${t("completion.partialStats")}` : "";
66 return `${files} · +${diff.added} −${diff.removed}${suffix}`;
67 }
68
69 export type TurnCheckStatus = "running" | "failed" | "interrupted" | "stale" | "passed" | "none" | "unknown";
70
71 export function turnCheckState(summary: WireCompletionSummary): { status: TurnCheckStatus; count: number; stale: boolean } {
72 const checks = summary.receipt?.verifications ?? [];
73 const stale = checks.some(v => v.stale) || (summary.gap_kinds ?? []).some(kind => kind === "stale_check" || kind === "stale_verification");
74 const failed = summary.receipt ? checks.filter(v => !v.passed && !v.interrupted).length : summary.checks_failed;
75 const interrupted = checks.some(v => v.interrupted) || Boolean(summary.receipt?.interrupted && summary.liveChecks?.length);
76 const status: TurnCheckStatus = summary.checking ? "running"
77 : failed > 0 ? "failed"
78 : interrupted ? "interrupted"
79 : stale ? "stale"
80 : !summary.receipt ? "unknown"
81 : checks.length > 0 ? "passed" : "none";
82 return { status, count: status === "failed" ? failed : checks.length, stale };
83 }
84
85 export function turnCheckText(summary: WireCompletionSummary, t: Translator): string {
86 const { status, count, stale } = turnCheckState(summary);
87 const labels = {
88 running: "completion.checkRunning", failed: "completion.checkFailed", interrupted: "completion.checkInterrupted",
89 stale: "completion.checkStale", passed: "completion.checkPassed", none: "completion.checkNone", unknown: "completion.checkUnknown",
90 } as const;
91 const text = t(labels[status], { count });
92 return status === "failed" && stale ? `${text} · ${t("completion.someStale")}` : text;
93 }
94
94 lines TYPESCRIPT