返回 DeepSeek-Reasonix
ContextBudgetCard.tsx
根目录 / desktop / frontend / src / components / ContextBudgetCard.tsx
1 import type { Translator } from "../lib/i18n";
2 import type { ContextBudgetInfo } from "../lib/contextMaintenanceTypes";
3 import type { ContextInfo, ContextPanelInfo } from "../lib/types";
4 import { formatTokens } from "../lib/format";
5
6 export function resolveContextBudget(context?: ContextInfo | null, info?: ContextPanelInfo | null): ContextBudgetInfo | undefined {
7 return info?.contextBudget ?? context?.contextBudget ?? context?.maintenance?.contextBudget;
8 }
9
10 export function contextBudgetSourceKey(source?: string): "context.budgetSourceExplicit" | "context.budgetSourceOfficial" | "context.budgetSourceOpencode" | "context.budgetSourceLearned" | "context.budgetSourceUnknown" {
11 switch (source) {
12 case "explicit":
13 return "context.budgetSourceExplicit";
14 case "official":
15 return "context.budgetSourceOfficial";
16 case "opencode":
17 return "context.budgetSourceOpencode";
18 case "learned":
19 return "context.budgetSourceLearned";
20 default:
21 return "context.budgetSourceUnknown";
22 }
23 }
24
25 export function contextBudgetRecoveryKey(kind?: string): "context.budgetRecoveryClip" | "context.budgetRecoveryRetry" | "context.budgetRecoveryCompacted" | "context.budgetRecoveryFailed" | "" {
26 switch (kind) {
27 case "proactive_clip":
28 return "context.budgetRecoveryClip";
29 case "learned_retry":
30 return "context.budgetRecoveryRetry";
31 case "compacted":
32 return "context.budgetRecoveryCompacted";
33 case "failed":
34 return "context.budgetRecoveryFailed";
35 default:
36 return "";
37 }
38 }
39
40 export function sharedContextPhysicalRemaining(budget?: ContextBudgetInfo): number | undefined {
41 if (!budget || budget.windowMode !== "shared") return undefined;
42 return Math.max(0, budget.physicalRemaining ?? 0);
43 }
44
45 export function showsSharedContextOverflowRisk(budget?: ContextBudgetInfo): boolean {
46 return budget?.windowMode === "shared" && ((budget.physicalRemaining ?? 0) <= 0 || Boolean(budget.clipped));
47 }
48
49 export function ContextBudgetCard({
50 budget,
51 t,
52 }: {
53 budget?: ContextBudgetInfo;
54 t: Translator;
55 }) {
56 if (!budget || ((budget.windowTokens ?? 0) <= 0 && (budget.promptTokens ?? 0) <= 0)) {
57 return null;
58 }
59 const prompt = budget.promptTokens ?? 0;
60 const reserved = budget.effectiveOutputTokens || budget.requestedOutputTokens || budget.autoOutputTokens || 0;
61 const remaining = sharedContextPhysicalRemaining(budget);
62 const recovery = contextBudgetRecoveryKey(budget.lastRecovery);
63 const overflowRisk = showsSharedContextOverflowRisk(budget);
64 return (
65 <section className="context-panel__section context-panel__budget">
66 <div className="context-panel__section-head">
67 <span className="context-panel__section-title">{t("context.budgetTitle")}</span>
68 </div>
69 <div className="context-panel__budget-grid">
70 <div>
71 <span>{t("context.prompt")}</span>
72 <strong>{formatTokens(prompt)}</strong>
73 </div>
74 <div>
75 <span>{t("context.budgetOutputReserve")}</span>
76 <strong>{formatTokens(reserved)}</strong>
77 </div>
78 <div>
79 <span>{t("context.budgetPhysicalRemaining")}</span>
80 <strong>{remaining === undefined ? "-" : formatTokens(remaining)}</strong>
81 </div>
82 </div>
83 <p className="context-panel__budget-source">{t("context.budgetOutputSource")}: {t(contextBudgetSourceKey(budget.source))}</p>
84 {overflowRisk && <p className="context-panel__budget-note">{t("context.budgetWhyOverflow")}</p>}
85 {recovery && <p className="context-panel__budget-recovery">{t(recovery)}</p>}
86 </section>
87 );
88 }
89
89 lines Plain Text