返回 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 // A live snapshot's missing budget also matters (for example after a model
8 // rebuild); do not resurrect the old request from the private panel snapshot.
9 return context ? context.contextBudget ?? context.maintenance?.contextBudget : info?.contextBudget;
10 }
11
12 export function contextBudgetSourceKey(source?: string): "context.budgetSourceExplicit" | "context.budgetSourceOfficial" | "context.budgetSourceOpencode" | "context.budgetSourceLearned" | "context.budgetSourceUnknown" {
13 switch (source) {
14 case "explicit":
15 return "context.budgetSourceExplicit";
16 case "official":
17 return "context.budgetSourceOfficial";
18 case "opencode":
19 return "context.budgetSourceOpencode";
20 case "learned":
21 return "context.budgetSourceLearned";
22 default:
23 return "context.budgetSourceUnknown";
24 }
25 }
26
27 export function contextBudgetRecoveryKey(kind?: string): "context.budgetRecoveryClip" | "context.budgetRecoveryRetry" | "context.budgetRecoveryCompacted" | "context.budgetRecoveryFailed" | "" {
28 switch (kind) {
29 case "proactive_clip":
30 return "context.budgetRecoveryClip";
31 case "learned_retry":
32 return "context.budgetRecoveryRetry";
33 case "compacted":
34 return "context.budgetRecoveryCompacted";
35 case "failed":
36 return "context.budgetRecoveryFailed";
37 default:
38 return "";
39 }
40 }
41
42 export function sharedContextPhysicalRemaining(budget?: ContextBudgetInfo): number | undefined {
43 if (!budget || budget.windowMode !== "shared") return undefined;
44 return Math.max(0, budget.physicalRemaining ?? 0);
45 }
46
47 export function showsSharedContextOverflowRisk(budget?: ContextBudgetInfo): boolean {
48 return budget?.windowMode === "shared" && ((budget.physicalRemaining ?? 0) <= 0 || Boolean(budget.clipped));
49 }
50
51 export function ContextBudgetCard({
52 budget,
53 t,
54 }: {
55 budget?: ContextBudgetInfo;
56 t: Translator;
57 }) {
58 if (!budget || ((budget.windowTokens ?? 0) <= 0 && (budget.promptTokens ?? 0) <= 0)) {
59 return null;
60 }
61 const prompt = budget.promptTokens ?? 0;
62 const reserved = budget.effectiveOutputTokens || budget.requestedOutputTokens || budget.autoOutputTokens || 0;
63 const remaining = sharedContextPhysicalRemaining(budget);
64 const recovery = contextBudgetRecoveryKey(budget.lastRecovery);
65 const overflowRisk = showsSharedContextOverflowRisk(budget);
66 return (
67 <section className="context-panel__section context-panel__budget">
68 <div className="context-panel__section-head">
69 <span className="context-panel__section-title">{t("context.budgetTitle")}</span>
70 </div>
71 <div className="context-panel__budget-grid">
72 <div>
73 <span>{t("context.prompt")}</span>
74 <strong>{formatTokens(prompt)}</strong>
75 </div>
76 <div>
77 <span>{t("context.budgetOutputReserve")}</span>
78 <strong>{formatTokens(reserved)}</strong>
79 </div>
80 <div>
81 <span>{t("context.budgetPhysicalRemaining")}</span>
82 <strong>{remaining === undefined ? "-" : formatTokens(remaining)}</strong>
83 </div>
84 </div>
85 <p className="context-panel__budget-source">{t("context.budgetOutputSource")}: {t(contextBudgetSourceKey(budget.source))}</p>
86 {overflowRisk && <p className="context-panel__budget-note">{t("context.budgetWhyOverflow")}</p>}
87 {recovery && <p className="context-panel__budget-recovery">{t(recovery)}</p>}
88 </section>
89 );
90 }
91
91 lines Plain Text