返回 DeepSeek-Reasonix
completionSummary.ts
根目录 / desktop / frontend / src / lib / completionSummary.ts
1 import type { Translator } from "./i18n";
2 import type { WireCompletionSummary } from "./types";
3 import { normalizeCompletionReceipt, turnChangeText, turnCheckText, turnResultHasContent } from "./turnResult";
4
5 function count(value: number): number {
6 return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
7 }
8
9 export function normalizeCompletionSummary(summary: WireCompletionSummary): WireCompletionSummary {
10 return {
11 receipt: normalizeCompletionReceipt(summary.receipt),
12 turnId: summary.turnId,
13 checkpointTurn: Number.isSafeInteger(summary.checkpointTurn) && summary.checkpointTurn! >= 0 ? summary.checkpointTurn : undefined,
14 checking: summary.checking === true,
15 liveChecks: Array.isArray(summary.liveChecks) ? summary.liveChecks : undefined,
16 preset: String(summary.preset ?? "").trim().toLowerCase(),
17 verdict: String(summary.verdict ?? "").trim().toLowerCase(),
18 mutations: count(summary.mutations),
19 changed_files: summary.changed_files === undefined ? undefined : count(summary.changed_files),
20 checks_passed: count(summary.checks_passed),
21 checks_failed: count(summary.checks_failed),
22 checks_suppressed: count(summary.checks_suppressed),
23 review: String(summary.review ?? "").trim().toLowerCase(),
24 gap_kinds: [...new Set((Array.isArray(summary.gap_kinds) ? summary.gap_kinds : []).map((gap) => String(gap).trim().toLowerCase()).filter(Boolean))].slice(0, 8),
25 constraint_degraded: Boolean(summary.constraint_degraded),
26 floor: String(summary.floor ?? "").trim().toLowerCase(),
27 attention: Boolean(summary.attention),
28 };
29 }
30
31 export function sessionQualityFloor(meta?: { qualityFloor?: string; tokenMode?: string } | null): "standard" | "delivery" {
32 if ((meta?.qualityFloor ?? "").trim().toLowerCase() === "delivery") return "delivery";
33 if ((meta?.tokenMode ?? "").trim().toLowerCase() === "delivery") return "delivery";
34 return "standard";
35 }
36
37 export function completionSummaryNeedsAttention(
38 summary?: WireCompletionSummary,
39 _floor: "standard" | "delivery" = "standard",
40 ): boolean {
41 if (!summary) return false;
42 if (summary.receipt?.assessmentKind === "facts") {
43 return Boolean(summary.receipt.verifications?.some(check => !check.passed || check.interrupted));
44 }
45 const recordedFloor = (summary.floor ?? "").trim().toLowerCase();
46 if (recordedFloor === "standard" || recordedFloor === "delivery") return Boolean(summary.attention);
47 const verdict = summary.verdict.trim().toLowerCase();
48 const kinds = new Set((summary.gap_kinds ?? []).map((gap) => gap.trim().toLowerCase()).filter(Boolean));
49 if (verdict === "blocked" || summary.checks_failed > 0 || summary.checks_suppressed > 0) return true;
50 if (kinds.has("unbacked_claim") || kinds.has("failed_verification")) return true;
51 return false;
52 }
53
54
55 export function completionSummaryPresentation(
56 summary: WireCompletionSummary,
57 fallbackFloor: "standard" | "delivery",
58 t: Translator,
59 ): { level: "info" | "warn"; title: string; body: string } | undefined {
60 if (!turnResultHasContent(summary)) return undefined;
61 return {
62 level: completionSummaryNeedsAttention(summary, fallbackFloor) ? "warn" : "info",
63 title: t("notice.completionChangesTitle"),
64 body: `${turnChangeText(summary, t)}\n${turnCheckText(summary, t)}`,
65 };
66 }
67
67 lines TYPESCRIPT