From c534b520af6cbbe504acf57cb423bcfb5299714c Mon Sep 17 00:00:00 2001 From: zhangsiqiang Date: Fri, 11 Sep 2026 15:38:39 +0800 Subject: [PATCH 1/2] feat(chat): let the context ring lead with used or remaining capacity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Composer 工具栏的上下文用量环此前固定显示剩余容量。新增设置项 AppSettings.contextUsageDisplay(remaining 默认 / used),让环弧长、 触发器百分比与令牌标签、弹层标题、tooltip 与 aria-label 跟随所选数值。 颜色分级(remaining ≤ 25% warning、≤ 10% critical)仍按剩余容量判定, 避免「已用 90%」显示为安全色。设置入口位于 AI → 默认项卡片。 --- .../src/components/ContextUsageInspector.tsx | 47 +++++++++++------ apps/desktop/src/lib/context-usage.ts | 44 ++++++++++++++++ apps/desktop/src/lib/settings-search.ts | 3 ++ apps/desktop/src/pages/SettingsPage.tsx | 52 +++++++++++++++++++ apps/desktop/test/context-usage.test.mjs | 27 ++++++++++ apps/desktop/test/settings-general.test.mjs | 17 ++++++ packages/i18n/src/locales/de/index.ts | 10 +++- packages/i18n/src/locales/en/index.ts | 10 +++- packages/i18n/src/locales/es/index.ts | 10 +++- packages/i18n/src/locales/fr/index.ts | 10 +++- packages/i18n/src/locales/ko/index.ts | 10 +++- packages/i18n/src/locales/tr/index.ts | 10 +++- packages/i18n/src/locales/zh-CN/index.ts | 10 +++- packages/i18n/src/locales/zh-TW/index.ts | 10 +++- packages/shared/src/types.ts | 9 ++++ 15 files changed, 256 insertions(+), 23 deletions(-) diff --git a/apps/desktop/src/components/ContextUsageInspector.tsx b/apps/desktop/src/components/ContextUsageInspector.tsx index 893958d95..44a107e54 100644 --- a/apps/desktop/src/components/ContextUsageInspector.tsx +++ b/apps/desktop/src/components/ContextUsageInspector.tsx @@ -17,6 +17,8 @@ import { calculateContextUsage, calculateTokenRate, contextOccupancyTokens, + contextUsageView, + resolveContextUsageDisplay, } from "../lib/context-usage"; import { placeContextInspector, @@ -65,6 +67,12 @@ export function ContextUsageInspector({ const [popoverPosition, setPopoverPosition] = useState(null); const context = calculateContextUsage(usage, contextWindow); + // The display preference flips the leading figure only; capacity colors + // still follow remaining space so the warning state keeps one meaning. + const usageDisplay = useAppStore((state) => + resolveContextUsageDisplay(state.settings?.contextUsageDisplay), + ); + const display = contextUsageView(context, usageDisplay); // Occupancy, turn total, and provider cache/input/output are the last // model request. Summing every tool-loop call inflates cache read past // the window (OpenCode last-message accounting). @@ -87,7 +95,18 @@ export function ContextUsageInspector({ ? "critical" : context.remainingPercent <= 25 ? "warning" - : "comfortable"; + : "comfortable"; + // One accessible sentence serves both display modes: the localized `state` + // phrase carries "remaining"/"used", so the key stays literal for the + // tooltip contract while `percent`/`count` stay numeric. + const ariaArguments = { + percent: display.percent, + count: formatTokenCount(display.tokens), + state: + display.display === "used" + ? t("chat.usageContextAriaUsed") + : t("chat.usageContextAriaRemaining"), + }; const closeInspector = useCallback(() => { setOpen(false); @@ -229,12 +248,16 @@ export function ContextUsageInspector({ >
- {t("chat.usageContextLeft", { - count: formatTokenCount(context.remainingTokens), - })} + {display.display === "used" + ? t("chat.usageContextSpent", { + count: formatTokenCount(display.tokens), + }) + : t("chat.usageContextLeft", { + count: formatTokenCount(display.tokens), + })} - {context.remainingPercent}% + {display.percent}%
@@ -336,14 +359,8 @@ export function ContextUsageInspector({ ref={triggerRef} type="button" className="context-inspector-trigger" - tooltip={t("chat.usageContextAria", { - percent: context.remainingPercent, - remaining: formatTokenCount(context.remainingTokens), - })} - ariaLabel={t("chat.usageContextAria", { - percent: context.remainingPercent, - remaining: formatTokenCount(context.remainingTokens), - })} + tooltip={t("chat.usageContextAria", ariaArguments)} + ariaLabel={t("chat.usageContextAria", ariaArguments)} aria-haspopup="dialog" aria-expanded={open} aria-controls={open ? panelId : undefined} @@ -367,12 +384,12 @@ export function ContextUsageInspector({ r={CONTEXT_RING_RADIUS} strokeDasharray={CONTEXT_RING_CIRCUMFERENCE} strokeDashoffset={ - CONTEXT_RING_CIRCUMFERENCE * (1 - context.remainingRatio) + CONTEXT_RING_CIRCUMFERENCE * (1 - display.ratio) } /> - {context.remainingPercent}% + {display.percent}% {popover && typeof document !== "undefined" diff --git a/apps/desktop/src/lib/context-usage.ts b/apps/desktop/src/lib/context-usage.ts index 5e72bcf34..7dd6607fd 100644 --- a/apps/desktop/src/lib/context-usage.ts +++ b/apps/desktop/src/lib/context-usage.ts @@ -1,6 +1,7 @@ import { effectiveContextWindow, modelIdsMatch, + type ContextUsageDisplay, type MessageUsage, type ModelInfo, type ProviderPublic, @@ -120,6 +121,49 @@ export function calculateContextUsage( }; } +/** + * Which figure the composer ring and its summary lead with (D398). Absent or + * unrecognised values keep the remaining-capacity default, so a persisted + * typo never blanks the trigger. + */ +export function resolveContextUsageDisplay(value: unknown): ContextUsageDisplay { + return value === "used" ? "used" : "remaining"; +} + +export type ContextUsageView = { + display: ContextUsageDisplay; + /** Percentage the trigger, heading, and popover lead with. */ + percent: number; + /** Token count matching `percent`. */ + tokens: number; + /** Ring arc fill, 0–1, matching `percent`. */ + ratio: number; +}; + +/** + * Pick the leading percentage/token pair for the configured display mode. + * Capacity colors stay on `ContextUsage.remainingPercent` in both modes, so + * "used 78%" still warns when only 22% is left. + */ +export function contextUsageView( + usage: ContextUsage, + display: ContextUsageDisplay, +): ContextUsageView { + return display === "used" + ? { + display, + percent: usage.usedPercent, + tokens: usage.usedTokens, + ratio: usage.usedRatio, + } + : { + display, + percent: usage.remainingPercent, + tokens: usage.remainingTokens, + ratio: usage.remainingRatio, + }; +} + function serializedLength(value: unknown): number { if (typeof value === "string") return value.length; try { diff --git a/apps/desktop/src/lib/settings-search.ts b/apps/desktop/src/lib/settings-search.ts index a23b4cef7..44cd57847 100644 --- a/apps/desktop/src/lib/settings-search.ts +++ b/apps/desktop/src/lib/settings-search.ts @@ -83,6 +83,9 @@ export const SETTINGS_NAV: SettingsNavEntry[] = [ "settings.commandShell", "settings.linkOpenTarget", "settings.enterToSend", + "settings.contextUsageDisplay", + "settings.contextUsageDisplayRemaining", + "settings.contextUsageDisplayUsed", "settings.largePasteThreshold", ], }, diff --git a/apps/desktop/src/pages/SettingsPage.tsx b/apps/desktop/src/pages/SettingsPage.tsx index ad20e2d7d..51fd3f33d 100644 --- a/apps/desktop/src/pages/SettingsPage.tsx +++ b/apps/desktop/src/pages/SettingsPage.tsx @@ -30,6 +30,7 @@ import { SETTINGS_NAV_GROUP_LABELS, type SettingsNavGroupId, } from "../lib/settings-search"; +import { resolveContextUsageDisplay } from "../lib/context-usage"; import { IconArchive, IconBookOpen, @@ -283,6 +284,53 @@ function LinkOpenTargetRow({ ); } +/** + * Which figure the composer context ring leads with (D398). Color thresholds + * stay on remaining capacity in both modes, so "used" never repaints the + * warning state. + */ +function ContextUsageDisplayRow({ + settings, + saveSettings, +}: { + settings: AppSettings; + saveSettings: (patch: Partial) => Promise; +}) { + const { t } = useTranslation(); + const current = resolveContextUsageDisplay(settings.contextUsageDisplay); + return ( + +
+ {([ + ["remaining", "settings.contextUsageDisplayRemaining"], + ["used", "settings.contextUsageDisplayUsed"], + ] as const).map(([value, labelKey]) => ( + + ))} +
+
+ ); +} + function LargePasteThresholdRow({ settings, saveSettings, @@ -1425,6 +1473,10 @@ export function SettingsPage() { + { assert.equal(context.remainingRatio, 28 / 128); }); +test("context usage display preference picks the ring's leading figure", () => { + const context = calculateContextUsage( + { inputTokens: 80, outputTokens: 20, totalTokens: 100 }, + 128, + ); + + const remaining = contextUsageView(context, "remaining"); + assert.equal(remaining.percent, 22); + assert.equal(remaining.tokens, 28); + assert.equal(remaining.ratio, 28 / 128); + + const used = contextUsageView(context, "used"); + assert.equal(used.percent, 78); + assert.equal(used.tokens, 100); + assert.equal(used.ratio, 100 / 128); +}); + +test("an absent or unrecognised display value keeps the remaining default", () => { + assert.equal(resolveContextUsageDisplay(undefined), "remaining"); + assert.equal(resolveContextUsageDisplay("used"), "used"); + assert.equal(resolveContextUsageDisplay("remaining"), "remaining"); + assert.equal(resolveContextUsageDisplay("bogus"), "remaining"); + assert.equal(resolveContextUsageDisplay(null), "remaining"); +}); + test("context window prefers the selected model catalog over provider fallback", () => { const providerModels = { provider: [ diff --git a/apps/desktop/test/settings-general.test.mjs b/apps/desktop/test/settings-general.test.mjs index cb624a72a..77ff237f8 100644 --- a/apps/desktop/test/settings-general.test.mjs +++ b/apps/desktop/test/settings-general.test.mjs @@ -115,6 +115,23 @@ test("Basics and AI tabs expose their respective app and AI controls", () => { assert.match(aiSource, /CommandShellRow/); assert.match(aiSource, /enterToSend: !settings\.enterToSend/); assert.match(aiSource, /LargePasteThresholdRow/); + assert.match(aiSource, /ContextUsageDisplayRow/); + assert.match( + settingsPageSource, + /saveSettings\(\{ contextUsageDisplay: value \}\)/, + ); + for (const key of [ + "settings.contextUsageDisplay", + "settings.contextUsageDisplayRemaining", + "settings.contextUsageDisplayUsed", + ]) { + assert.match(settingsSearchSource, new RegExp(key.replaceAll(".", "\\."))); + assert.match(enLocaleSource, new RegExp(`${key.split(".").at(-1)}:`)); + assert.match(zhLocaleSource, new RegExp(`${key.split(".").at(-1)}:`)); + assert.match(trLocaleSource, new RegExp(`${key.split(".").at(-1)}:`)); + } + assert.match(sharedTypesSource, /contextUsageDisplay\?: ContextUsageDisplay/); + assert.match(sharedTypesSource, /ContextUsageDisplay = "remaining" \| "used"/); assert.match(settingsPageSource, /largePasteThreshold/); assert.match(settingsPageSource, /saveSettings\(\{ largePasteThreshold: next \}\)/); assert.doesNotMatch(settingsPageSource, /commandShellConfigured/); diff --git a/packages/i18n/src/locales/de/index.ts b/packages/i18n/src/locales/de/index.ts index 24df1b1f1..d64907cc7 100644 --- a/packages/i18n/src/locales/de/index.ts +++ b/packages/i18n/src/locales/de/index.ts @@ -273,11 +273,14 @@ export const de = { "revisionPager": "{{current}} / {{total}}", "usageContextLabel": "Kontext", "usageContextLeft": "{{count}} Token übrig", + "usageContextSpent": "{{count}} Token verwendet", "usageContextTokens": "{{used}} / {{window}} Token", "usageContextWindow": "Kontextfenster", "usageContextUsed": "Verwendet", "usageContextRemaining": "Verbleibend", - "usageContextAria": "{{percent}}% Kontext verbleibend, {{remaining}} Token übrig", + "usageContextAria": "Kontext zu {{percent}}% {{state}}, {{count}} Token", + "usageContextAriaRemaining": "verbleibend", + "usageContextAriaUsed": "verwendet", "usageTurnTotal": "Diese Runde", "usageThroughputLabel": "Generierungsgeschwindigkeit", "usageThroughput": "{{count}} Tokens/s", @@ -747,6 +750,11 @@ export const de = { "linkOpenTargetDesc": "Wählen Sie, wo Links in Chat-Nachrichten standardmäßig geöffnet werden.", "linkOpenTargetWorkpanel": "Arbeitsbereich-Browser", "linkOpenTargetExternal": "Standard-Betriebssystem-Browser", + "contextUsageDisplay": "Kontextnutzung-Anzeige", + "contextUsageDisplayDesc": + "Wählen Sie, ob der Kontextring im Composer den verbleibenden Platz oder die genutzte Menge zählt.", + "contextUsageDisplayRemaining": "Verbleibend", + "contextUsageDisplayUsed": "Verwendet", "linkContextMenuOpenExternal": "Im Standard-Browser öffnen", "linkContextMenuOpenWorkpanel": "Im Arbeitsbereich öffnen", "linkContextMenuCopy": "Link-Adresse kopieren", diff --git a/packages/i18n/src/locales/en/index.ts b/packages/i18n/src/locales/en/index.ts index 472455e8f..d3f417663 100644 --- a/packages/i18n/src/locales/en/index.ts +++ b/packages/i18n/src/locales/en/index.ts @@ -280,11 +280,14 @@ export const en = { revisionPager: "{{current}} / {{total}}", usageContextLabel: "Context", usageContextLeft: "{{count}} tokens left", + usageContextSpent: "{{count}} tokens used", usageContextTokens: "{{used}} / {{window}} tokens", usageContextWindow: "Context window", usageContextUsed: "Used", usageContextRemaining: "Remaining", - usageContextAria: "{{percent}}% context remaining, {{remaining}} tokens left", + usageContextAria: "{{percent}}% context {{state}}, {{count}} tokens", + usageContextAriaRemaining: "remaining", + usageContextAriaUsed: "used", usageTurnTotal: "This turn", usageThroughputLabel: "Generation speed", usageThroughput: "{{count}} tokens/s", @@ -757,6 +760,11 @@ export const en = { linkOpenTargetDesc: "Choose where links in chat messages open by default.", linkOpenTargetWorkpanel: "Work panel browser", linkOpenTargetExternal: "Default OS browser", + contextUsageDisplay: "Context usage readout", + contextUsageDisplayDesc: + "Choose whether the composer context ring counts down the space left or up what this turn used.", + contextUsageDisplayRemaining: "Remaining", + contextUsageDisplayUsed: "Used", linkContextMenuOpenExternal: "Open in default browser", linkContextMenuOpenWorkpanel: "Open in work panel", linkContextMenuCopy: "Copy link address", diff --git a/packages/i18n/src/locales/es/index.ts b/packages/i18n/src/locales/es/index.ts index ac185d92f..0705f18e3 100644 --- a/packages/i18n/src/locales/es/index.ts +++ b/packages/i18n/src/locales/es/index.ts @@ -273,11 +273,14 @@ export const es = { "revisionPager": "{{current}} / {{total}}", "usageContextLabel": "Contexto", "usageContextLeft": "Quedan {{count}} tokens", + "usageContextSpent": "{{count}} tokens usados", "usageContextTokens": "{{used}} / {{window}} tokens", "usageContextWindow": "Ventana de contexto", "usageContextUsed": "Usado", "usageContextRemaining": "Restante", - "usageContextAria": "{{percent}}% de contexto restante, quedan {{remaining}} tokens", + "usageContextAria": "Contexto {{state}} al {{percent}}%, {{count}} tokens", + "usageContextAriaRemaining": "restante", + "usageContextAriaUsed": "usado", "usageTurnTotal": "Este turno", "usageThroughputLabel": "Velocidad de generación", "usageThroughput": "{{count}} tokens/s", @@ -747,6 +750,11 @@ export const es = { "linkOpenTargetDesc": "Elija dónde se abren de forma predeterminada los enlaces en los mensajes.", "linkOpenTargetWorkpanel": "Navegador del panel de trabajo", "linkOpenTargetExternal": "Navegador predeterminado del sistema", + "contextUsageDisplay": "Lectura del uso del contexto", + "contextUsageDisplayDesc": + "Elija si el anillo de contexto del compositor cuenta el espacio restante o lo usado en este turno.", + "contextUsageDisplayRemaining": "Restante", + "contextUsageDisplayUsed": "Usado", "linkContextMenuOpenExternal": "Abrir en el navegador predeterminado", "linkContextMenuOpenWorkpanel": "Abrir en el panel de trabajo", "linkContextMenuCopy": "Copiar dirección del enlace", diff --git a/packages/i18n/src/locales/fr/index.ts b/packages/i18n/src/locales/fr/index.ts index 565cd818d..ed8cad853 100644 --- a/packages/i18n/src/locales/fr/index.ts +++ b/packages/i18n/src/locales/fr/index.ts @@ -273,11 +273,14 @@ export const fr = { "revisionPager": "{{current}} / {{total}}", "usageContextLabel": "Contexte", "usageContextLeft": "{{count}} jetons restants", + "usageContextSpent": "{{count}} jetons utilisés", "usageContextTokens": "{{used}} / {{window}} jetons", "usageContextWindow": "Fenêtre de contexte", "usageContextUsed": "Utilisé", "usageContextRemaining": "Restant", - "usageContextAria": "{{percent}}% de contexte restant, {{remaining}} jetons restants", + "usageContextAria": "Contexte {{state}} à {{percent}} %, {{count}} jetons", + "usageContextAriaRemaining": "restant", + "usageContextAriaUsed": "utilisé", "usageTurnTotal": "Ce tour", "usageThroughputLabel": "Vitesse de génération", "usageThroughput": "{{count}} jetons/s", @@ -747,6 +750,11 @@ export const fr = { "linkOpenTargetDesc": "Choisissez où les liens des messages s'ouvrent par défaut.", "linkOpenTargetWorkpanel": "Navigateur du panneau de travail", "linkOpenTargetExternal": "Navigateur par défaut du système", + "contextUsageDisplay": "Affichage de l'usage du contexte", + "contextUsageDisplayDesc": + "Choisissez si l'anneau de contexte du composeur compte l'espace restant ou la quantité utilisée.", + "contextUsageDisplayRemaining": "Restant", + "contextUsageDisplayUsed": "Utilisé", "linkContextMenuOpenExternal": "Ouvrir dans le navigateur par défaut", "linkContextMenuOpenWorkpanel": "Ouvrir dans le panneau de travail", "linkContextMenuCopy": "Copier l'adresse du lien", diff --git a/packages/i18n/src/locales/ko/index.ts b/packages/i18n/src/locales/ko/index.ts index 18c03f362..fe3724ea3 100644 --- a/packages/i18n/src/locales/ko/index.ts +++ b/packages/i18n/src/locales/ko/index.ts @@ -282,11 +282,14 @@ export const ko = { revisionPager: "{{current}} / {{total}}", usageContextLabel: "컨텍스트", usageContextLeft: "토큰 {{count}}개 남음", + usageContextSpent: "토큰 {{count}}개 사용됨", usageContextTokens: "{{used}} / {{window}} 토큰", usageContextWindow: "컨텍스트 창", usageContextUsed: "사용됨", usageContextRemaining: "남음", - usageContextAria: "컨텍스트 {{percent}}% 남음, 토큰 {{remaining}}개", + usageContextAria: "컨텍스트 {{percent}}% {{state}}, 토큰 {{count}}개", + usageContextAriaRemaining: "남음", + usageContextAriaUsed: "사용됨", usageTurnTotal: "이번 턴", usageThroughputLabel: "생성 속도", usageThroughput: "{{count}} tokens/s", @@ -759,6 +762,11 @@ export const ko = { linkOpenTargetDesc: "채팅 메시지의 링크를 기본적으로 어디에서 열지 선택하세요.", linkOpenTargetWorkpanel: "작업 패널 브라우저", linkOpenTargetExternal: "시스템 기본 브라우저", + contextUsageDisplay: "컨텍스트 사용량 표시", + contextUsageDisplayDesc: + "작성기 컨텍스트 링이 남은 공간을 셀지, 이번 턴에 사용한 양을 셀지 선택하세요.", + contextUsageDisplayRemaining: "남음", + contextUsageDisplayUsed: "사용됨", linkContextMenuOpenExternal: "기본 브라우저에서 열기", linkContextMenuOpenWorkpanel: "작업 패널에서 열기", linkContextMenuCopy: "링크 주소 복사", diff --git a/packages/i18n/src/locales/tr/index.ts b/packages/i18n/src/locales/tr/index.ts index de9f18d34..500e6b24c 100644 --- a/packages/i18n/src/locales/tr/index.ts +++ b/packages/i18n/src/locales/tr/index.ts @@ -282,11 +282,14 @@ export const tr = { revisionPager: "{{current}} / {{total}}", usageContextLabel: "Bağlam", usageContextLeft: "{{count}} token kaldı", + usageContextSpent: "{{count}} token kullanıldı", usageContextTokens: "{{used}} / {{window}} token", usageContextWindow: "Bağlam penceresi", usageContextUsed: "Kullanılan", usageContextRemaining: "Kalan", - usageContextAria: "Bağlamın %{{percent}} kadarı kaldı, {{remaining}} token", + usageContextAria: "Bağlam %{{percent}} {{state}}, {{count}} token", + usageContextAriaRemaining: "kaldı", + usageContextAriaUsed: "kullanıldı", usageTurnTotal: "Bu tur", usageThroughputLabel: "Üretim hızı", usageThroughput: "{{count}} token/sn", @@ -759,6 +762,11 @@ export const tr = { linkOpenTargetDesc: "Sohbet mesajlarındaki bağlantıların varsayılan olarak nerede açılacağını seçin.", linkOpenTargetWorkpanel: "Çalışma paneli tarayıcısı", linkOpenTargetExternal: "Varsayılan sistem tarayıcısı", + contextUsageDisplay: "Bağlam kullanımı göstergesi", + contextUsageDisplayDesc: + "Composer bağlam halkasının kalan alanı mı yoksa bu turda kullanılanı mı sayacağını seçin.", + contextUsageDisplayRemaining: "Kalan", + contextUsageDisplayUsed: "Kullanılan", linkContextMenuOpenExternal: "Varsayılan tarayıcıda aç", linkContextMenuOpenWorkpanel: "Çalışma panelinde aç", linkContextMenuCopy: "Bağlantı adresini kopyala", diff --git a/packages/i18n/src/locales/zh-CN/index.ts b/packages/i18n/src/locales/zh-CN/index.ts index a85cabd2d..27cd5cef9 100644 --- a/packages/i18n/src/locales/zh-CN/index.ts +++ b/packages/i18n/src/locales/zh-CN/index.ts @@ -275,11 +275,14 @@ export const zhCN = { revisionPager: "{{current}} / {{total}}", usageContextLabel: "上下文", usageContextLeft: "剩余 {{count}} tokens", + usageContextSpent: "已用 {{count}} tokens", usageContextTokens: "{{used}} / {{window}} tokens", usageContextWindow: "上下文窗口", usageContextUsed: "已用", usageContextRemaining: "剩余", - usageContextAria: "上下文剩余 {{percent}}%,还剩 {{remaining}} tokens", + usageContextAria: "上下文{{state}} {{percent}}%,共 {{count}} tokens", + usageContextAriaRemaining: "剩余", + usageContextAriaUsed: "已用", usageTurnTotal: "本轮合计", usageThroughputLabel: "生成速度", usageThroughput: "{{count}} tokens/s", @@ -755,6 +758,11 @@ export const zhCN = { linkOpenTargetDesc: "选择对话中超链接的默认打开方式。", linkOpenTargetWorkpanel: "工作区浏览器", linkOpenTargetExternal: "系统默认浏览器", + contextUsageDisplay: "上下文用量读数", + contextUsageDisplayDesc: + "选择输入框上下文环显示剩余空间,还是本轮已用用量。", + contextUsageDisplayRemaining: "剩余", + contextUsageDisplayUsed: "已用", linkContextMenuOpenExternal: "在系统默认浏览器中打开", linkContextMenuOpenWorkpanel: "在工作区浏览器中打开", linkContextMenuCopy: "复制链接地址", diff --git a/packages/i18n/src/locales/zh-TW/index.ts b/packages/i18n/src/locales/zh-TW/index.ts index 2d06a265f..4fffc6606 100644 --- a/packages/i18n/src/locales/zh-TW/index.ts +++ b/packages/i18n/src/locales/zh-TW/index.ts @@ -275,11 +275,14 @@ export const zhTW = { revisionPager: "{{current}} / {{total}}", usageContextLabel: "上下文", usageContextLeft: "剩餘 {{count}} tokens", + usageContextSpent: "已用 {{count}} tokens", usageContextTokens: "{{used}} / {{window}} tokens", usageContextWindow: "上下文視窗", usageContextUsed: "已用", usageContextRemaining: "剩餘", - usageContextAria: "上下文剩餘 {{percent}}%,還剩 {{remaining}} tokens", + usageContextAria: "上下文{{state}} {{percent}}%,共 {{count}} tokens", + usageContextAriaRemaining: "剩餘", + usageContextAriaUsed: "已用", usageTurnTotal: "本輪合計", usageThroughputLabel: "生成速度", usageThroughput: "{{count}} tokens/s", @@ -755,6 +758,11 @@ export const zhTW = { linkOpenTargetDesc: "選擇對話中超連結的預設開啟方式。", linkOpenTargetWorkpanel: "工作區瀏覽器", linkOpenTargetExternal: "系統預設瀏覽器", + contextUsageDisplay: "上下文用量讀數", + contextUsageDisplayDesc: + "選擇輸入框上下文環顯示剩餘空間,還是本輪已用用量。", + contextUsageDisplayRemaining: "剩餘", + contextUsageDisplayUsed: "已用", linkContextMenuOpenExternal: "在系統預設瀏覽器中開啟", linkContextMenuOpenWorkpanel: "在工作區瀏覽器中開啟", linkContextMenuCopy: "複製連結位址", diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 6da8e064e..3c2d1c1ba 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -1212,11 +1212,20 @@ export type AppSettings = { * `external`: Open directly in the system's default web browser. */ linkOpenTarget?: LinkOpenTarget; + /** + * Which context figure the composer ring and its summary lead with (D398). + * `remaining` (default, absent) counts down from 100%; `used` counts up. + * Color thresholds always follow remaining capacity, so the warning state + * does not change meaning with this preference. + */ + contextUsageDisplay?: ContextUsageDisplay; onboardingDismissed: boolean; }; export type LinkOpenTarget = "workpanel" | "external"; +export type ContextUsageDisplay = "remaining" | "used"; + export type PluginMarketSource = "official" | "mirror" | "custom"; export type PluginUpdateInfo = { From a7f803264a4052171e8522b7ae092f23ba70cdb1 Mon Sep 17 00:00:00 2001 From: zhangsiqiang Date: Fri, 11 Sep 2026 15:38:40 +0800 Subject: [PATCH 2/2] docs(spec): record the context usage display preference (D398 / ADR 0223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 ADR 0223 与决策 D398,同步设置信息架构、组件规格与 E2E-250 场景(含中英 spec 对),并把 E2E-250 登记进 §8 追溯矩阵。 说明默认 remaining、颜色阈值仍按剩余容量,以及本次为纯渲染器改动、 无协议与存储迁移。 --- .../0223-context-usage-display-preference.md | 65 +++++++++++++++++++ docs/adr/README.md | 1 + docs/spec/04-ux/06-settings-ia.md | 15 +++-- docs/spec/04-ux/08-component-spec.md | 16 +++-- docs/spec/06-delivery/04-e2e-test-plan.md | 34 +++++++++- docs/spec/08-meta/decisions-log.md | 16 +++++ docs/zh-CN/spec/04-ux/06-settings-ia.md | 8 ++- docs/zh-CN/spec/04-ux/08-component-spec.md | 12 ++-- .../spec/06-delivery/04-e2e-test-plan.md | 21 +++++- docs/zh-CN/spec/08-meta/decisions-log.md | 9 +++ 10 files changed, 174 insertions(+), 23 deletions(-) create mode 100644 docs/adr/0223-context-usage-display-preference.md diff --git a/docs/adr/0223-context-usage-display-preference.md b/docs/adr/0223-context-usage-display-preference.md new file mode 100644 index 000000000..710e7727b --- /dev/null +++ b/docs/adr/0223-context-usage-display-preference.md @@ -0,0 +1,65 @@ +# ADR 0223: Context Usage Display Preference + +- Status: Accepted +- Date: 2026-09-11 +- Deciders: PI-Desktop desktop UI maintainers +- Amends: 0184 +- Related: [04-ux/06-settings-ia](../spec/04-ux/06-settings-ia.md) · + [04-ux/08-component-spec](../spec/04-ux/08-component-spec.md) · + [08-meta/decisions-log](../spec/08-meta/decisions-log.md) (D398) · + E2E-250 + +## Context + +The composer toolbar's context usage inspector (ADR 0184 / D347) always leads +with the remaining-capacity figure: the trigger ring, popover heading, +tooltip, and `aria-label` all show the remaining token count and percentage. +Some users find the used-capacity figure more intuitive — especially when +context is lightly loaded and the remaining number is close to the total +window, which provides little signal at a glance. + +## Decision + +1. A new setting `AppSettings.contextUsageDisplay` (`ContextUsageDisplay = + "remaining" | "used"`) lets the user choose which figure the context + inspector leads with. The default (and fallback for absent or + unrecognised values) is `"remaining"`, preserving the existing behaviour. +2. When `contextUsageDisplay` is `"used"`, the composer toolbar ring's + arc length (`strokeDashoffset`), the trigger percentage and token label, + the popover heading, the tooltip, and the `aria-label` all switch to + the used-capacity pair instead of the remaining pair. The ring fills + proportionally to `usedRatio` rather than `remainingRatio`. +3. Warning and critical color thresholds remain based on **remaining** + capacity (remaining ≤ 25 % → warning, ≤ 10 % → critical) regardless + of the display mode. A display reading "used 78 %" still turns warning + colour because only 22 % remains. +4. Settings → AI → Defaults gains a `ContextUsageDisplayRow` (segmented + control: Remaining / Used) placed after the Link open destination row + and before the Enter-to-send row. +5. The change is renderer-only: no protocol, storage schema, host-side + migration, or IPC change. The host-core settings merge preserves + unknown keys, so persisted `contextUsageDisplay` values survive across + upgrades without a schema bump. + +## Consequences + +- Users who prefer a "how much have I spent" mental model get a consistent + display; users who prefer the original "how much is left" model see no + change by default. +- The ring arc direction flips visually when switching to `"used"`, which + is the correct correspondence: a fuller ring means more context consumed. +- Color semantics stay stable across modes, so the warning/critical signal + is never ambiguous regardless of the chosen display direction. +- No host or storage change means no migration risk and no protocol version + bump. + +## Rejected alternatives + +- **Boolean toggle (show-used: true/false):** a two-value segmented control + reads clearer than a checkbox for mutually exclusive display modes, and + the `ContextUsageDisplay` union type leaves room for future modes without + a type rename. +- **Color thresholds also follow display mode:** rejected; it would make a + "used 90 %" ring green despite only 10 % remaining, which is dangerously + misleading. Remaining capacity is the safety signal and must stay + authoritative for color. diff --git a/docs/adr/README.md b/docs/adr/README.md index 3fc8c4445..1bef3bc7d 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -240,3 +240,4 @@ Each ADR includes: | 0220 | Keep Windows work-panel chrome single-purpose | Accepted (amends D154 / D357 / ADR 0195) | | 0221 | Render canonical thinking-level values without translation | Accepted (amends D369 / ADR 0202) | | 0222 | Native file and folder drops in the Composer | Accepted (amends ADR 0101 / D397) | +| 0223 | Context Usage Display Preference | Accepted (amends 0184) | diff --git a/docs/spec/04-ux/06-settings-ia.md b/docs/spec/04-ux/06-settings-ia.md index 6f5df2e9a..54255d19c 100644 --- a/docs/spec/04-ux/06-settings-ia.md +++ b/docs/spec/04-ux/06-settings-ia.md @@ -109,12 +109,15 @@ Settings is a **full-window page** that replaces the app sidebar + main chrome ( - **Permissions** card: the global permission-mode control (ask / accept-edits / auto) that governs how autonomously the agent acts. - **Defaults** card: the host-backed default operating mode (Agent / Plan / Goal), - command shell selection, Link open destination, Enter-to-send control, and the - large text paste threshold. Link open destination uses the Work panel browser - by default and can route plain HTTP(S) link clicks to the system browser. - The threshold controls when a text-only paste becomes a temporary - session-scratch file; it defaults to 600 characters and accepts integer values - from 1 through 1,000,000. + command shell selection, Link open destination, context usage display + (remaining or used), Enter-to-send control, and the large text paste + threshold. Link open destination uses the Work panel browser by default + and can route plain HTTP(S) link clicks to the system browser. Context + usage display controls whether the composer toolbar context ring and its + popover lead with the remaining or the used capacity figure; the default + is remaining. The threshold controls when a text-only paste becomes a + temporary session-scratch file; it defaults to 600 characters and accepts + integer values from 1 through 1,000,000. - The **Command shell** row in Defaults uses the host-discovered catalog of native PowerShell 5.1, PowerShell 7, cmd, Git Bash, and Bash with IDs `windows-powershell`, `windows-pwsh`, `cmd`, `git-bash`, and diff --git a/docs/spec/04-ux/08-component-spec.md b/docs/spec/04-ux/08-component-spec.md index bd3b6c61c..16a216fd1 100644 --- a/docs/spec/04-ux/08-component-spec.md +++ b/docs/spec/04-ux/08-component-spec.md @@ -1376,11 +1376,17 @@ Single message render — either user (plaintext) or assistant (markdown streami assistant message (the last model request), using `input + output + reasoning + cacheRead + cacheWrite` (D355). They are not the sum of every model call in the visual tool-loop. It is hidden until that - usage exists. The trigger keeps a small remaining-capacity ring beside the - percentage and omits the redundant `Context` label; low capacity changes - the semantic color without making color the only signal. Clicking the - trigger (or activating it from the keyboard) toggles a non-modal panel with - a remaining-token-plus-percentage heading, used/window counts, and two + usage exists. The trigger keeps a small capacity ring beside the + percentage and omits the redundant `Context` label; the leading figure + (ring arc, percentage, token label, popover heading, tooltip, and + `aria-label`) follows `settings.contextUsageDisplay` — `"remaining"` + (default) or `"used"` — so the ring fills by `remainingRatio` or + `usedRatio` accordingly. Low capacity changes the semantic color based + on remaining capacity (remaining ≤ 25 % warning, ≤ 10 % critical) + regardless of display mode, without making color the only signal. + Clicking the trigger (or activating it from the keyboard) toggles a + non-modal panel whose heading follows the same display-mode figure, + followed by used/window counts and two unboxed turn/speed summary values. Model usage is compressed into one inline summary row that retains exact last-request input/output/cache/reasoning values diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index d0ddb90b6..a23335708 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -6287,14 +6287,14 @@ Each scenario is documented in this format: |---|---| | A — App startup | E2E-001, E2E-002, E2E-003, E2E-004, E2E-067, E2E-076, E2E-079, E2E-092, E2E-097, E2E-143, E2E-150, E2E-168, E2E-204 | | B — Model config | E2E-005, E2E-006, E2E-007, E2E-038, E2E-050, E2E-052, E2E-055, E2E-066, E2E-080, E2E-082, E2E-102c, E2E-102d, E2E-102e, E2E-151, E2E-154, E2E-163, E2E-166, E2E-172, E2E-174, E2E-197, E2E-005G, E2E-005J, E2E-199, E2E-201, E2E-202, E2E-203, E2E-205, E2E-206, E2E-209 | -| C — Conversation & stream | E2E-008, E2E-008a, E2E-009, E2E-010, E2E-011, E2E-011a, E2E-011b, E2E-011d, E2E-011e, E2E-011g, E2E-031, E2E-040, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-052, E2E-053, E2E-054, E2E-055, E2E-059, E2E-059a, E2E-060c, E2E-060d, E2E-061, E2E-061a, E2E-062, E2E-064, E2E-065, E2E-068, E2E-071, E2E-073, E2E-074, E2E-075, E2E-081, E2E-083, E2E-084, E2E-086, E2E-087, E2E-088, E2E-088b, E2E-089, E2E-090, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102g, E2E-102i, E2E-106, E2E-109, E2E-111, E2E-114, E2E-116, E2E-117, E2E-118, E2E-119, E2E-120, E2E-121, E2E-218, E2E-219, E2E-AGENTS-001, E2E-142, E2E-144, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-151, E2E-154, E2E-155, E2E-158, E2E-159, E2E-161, E2E-162, E2E-166, E2E-172, E2E-173, E2E-174, E2E-177, E2E-178, E2E-179, E2E-180, E2E-182, E2E-183, E2E-187, E2E-198, E2E-199, E2E-202, E2E-203, E2E-207, E2E-208 | +| C — Conversation & stream | E2E-008, E2E-008a, E2E-009, E2E-010, E2E-011, E2E-011a, E2E-011b, E2E-011d, E2E-011e, E2E-011g, E2E-031, E2E-040, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-052, E2E-053, E2E-054, E2E-055, E2E-059, E2E-059a, E2E-060c, E2E-060d, E2E-061, E2E-061a, E2E-062, E2E-064, E2E-065, E2E-068, E2E-071, E2E-073, E2E-074, E2E-075, E2E-081, E2E-083, E2E-084, E2E-086, E2E-087, E2E-088, E2E-088b, E2E-089, E2E-090, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102g, E2E-106, E2E-109, E2E-111, E2E-114, E2E-116, E2E-117, E2E-118, E2E-119, E2E-120, E2E-121, E2E-218, E2E-219, E2E-AGENTS-001, E2E-142, E2E-144, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-151, E2E-154, E2E-155, E2E-158, E2E-159, E2E-161, E2E-162, E2E-166, E2E-172, E2E-173, E2E-174, E2E-177, E2E-178, E2E-179, E2E-180, E2E-182, E2E-183, E2E-187, E2E-198, E2E-199, E2E-202, E2E-203, E2E-207, E2E-208, E2E-250, E2E-102i | | D — Workspace | E2E-012, E2E-013, E2E-022B, E2E-024I, E2E-047, E2E-049, E2E-057, E2E-058, E2E-060, E2E-068, E2E-075, E2E-078, E2E-153, E2E-158, E2E-182, E2E-187 | | E — Tools & permissions | E2E-008a, E2E-014, E2E-015, E2E-016, E2E-017, E2E-018, E2E-019, E2E-024I, E2E-024K, E2E-040, E2E-049, E2E-074, E2E-093, E2E-097, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102d, E2E-102e, E2E-102g, E2E-103, E2E-105, E2E-106, E2E-107, E2E-111, E2E-112, E2E-113, E2E-114, E2E-115, E2E-116, E2E-119, E2E-121, E2E-122, E2E-142, E2E-145, E2E-147, E2E-155, E2E-158, E2E-166, E2E-181 | | F — Persistence | E2E-020, E2E-021, E2E-021a, E2E-036, E2E-037, E2E-038, E2E-040, E2E-042, E2E-047, E2E-048, E2E-051, E2E-054, E2E-056, E2E-061, E2E-062, E2E-064, E2E-066, E2E-068, E2E-071, E2E-072, E2E-073, E2E-082, E2E-084, E2E-096, E2E-098, E2E-102, E2E-102b, E2E-102c, E2E-102d, E2E-102g, E2E-102i, E2E-103, E2E-AGENTS-001, E2E-061a, E2E-073a, E2E-104, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-112, E2E-118, E2E-119, E2E-120, E2E-121, E2E-123, E2E-142, E2E-146, E2E-146a, E2E-148, E2E-151, E2E-158, E2E-160, E2E-168, E2E-171, E2E-177, E2E-178, E2E-183, E2E-186, E2E-005J | | G — Plugins | E2E-022, E2E-022A, E2E-022B, E2E-022C, E2E-023, E2E-024, E2E-024B, E2E-024C, E2E-024D, E2E-024E, E2E-024W, E2E-024F, E2E-024G, E2E-024H, E2E-024I, E2E-024J, E2E-024K, E2E-024L, E2E-024M, E2E-024N, E2E-024O, E2E-024P, E2E-025, E2E-026, E2E-105, E2E-117, E2E-120, E2E-122, E2E-123, E2E-024Q, E2E-148, E2E-152, E2E-153 | | H — Diagnostics | E2E-027, E2E-031, E2E-034, E2E-042, E2E-096, E2E-098, E2E-104, E2E-107, E2E-108, E2E-109, E2E-110, E2E-113, E2E-115, E2E-116, E2E-118, E2E-121, E2E-146, E2E-146a, E2E-155, E2E-159, E2E-176, E2E-194, E2E-195 | | Security | E2E-028, E2E-029, E2E-030, E2E-024J, E2E-024K, E2E-024M, E2E-049, E2E-068, E2E-086, E2E-102c, E2E-102d, E2E-102e, E2E-105, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-112, E2E-113, E2E-115, E2E-116, E2E-117, E2E-119, E2E-121, E2E-122, E2E-123, E2E-142, E2E-148, E2E-151, E2E-153, E2E-158, E2E-187, E2E-196c, E2E-196b, E2E-196 | -| Quality | E2E-032, E2E-033, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-053, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102e, E2E-102i, E2E-103, E2E-AGENTS-001, E2E-021a, E2E-024N, E2E-059a, E2E-060b, E2E-060c, E2E-060d, E2E-061a, E2E-073a, E2E-111, E2E-114, E2E-117, E2E-118, E2E-119, E2E-120, E2E-122, E2E-123, E2E-142, E2E-143, E2E-144, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-148, E2E-150, E2E-151, E2E-153, E2E-155, E2E-158, E2E-159, E2E-160, E2E-161, E2E-162, E2E-163, E2E-168, E2E-172, E2E-173, E2E-174, E2E-011g, E2E-176, E2E-177, E2E-178, E2E-179, E2E-180, E2E-181, E2E-182, E2E-183, E2E-186, E2E-187, E2E-194, E2E-195, E2E-196a, E2E-196b, E2E-196c, E2E-198, E2E-199, E2E-200, E2E-196, E2E-201, E2E-204, E2E-202, E2E-203, E2E-205, E2E-206, E2E-207, E2E-208, E2E-209, E2E-210, E2E-218, E2E-219 | +| Quality | E2E-032, E2E-033, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-053, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-094, E2E-095, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102e, E2E-103, E2E-AGENTS-001, E2E-021a, E2E-024N, E2E-059a, E2E-060b, E2E-060c, E2E-060d, E2E-061a, E2E-073a, E2E-111, E2E-114, E2E-117, E2E-118, E2E-119, E2E-120, E2E-122, E2E-123, E2E-142, E2E-143, E2E-144, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-148, E2E-150, E2E-151, E2E-153, E2E-155, E2E-158, E2E-159, E2E-160, E2E-161, E2E-162, E2E-163, E2E-168, E2E-172, E2E-173, E2E-174, E2E-011g, E2E-176, E2E-177, E2E-178, E2E-179, E2E-180, E2E-181, E2E-182, E2E-183, E2E-186, E2E-187, E2E-194, E2E-195, E2E-196a, E2E-196b, E2E-196c, E2E-198, E2E-199, E2E-200, E2E-196, E2E-201, E2E-204, E2E-202, E2E-203, E2E-205, E2E-206, E2E-207, E2E-208, E2E-209, E2E-210, E2E-218, E2E-219, E2E-250, E2E-102i | | Milestone | Scenarios | |---|---| @@ -6302,7 +6302,7 @@ Each scenario is documented in this format: | M2 | E2E-004, E2E-005, E2E-006, E2E-007, E2E-008, E2E-009, E2E-010, E2E-011, E2E-011a, E2E-011b, E2E-011d, E2E-011e, E2E-011g, E2E-020, E2E-021, E2E-021a, E2E-027, E2E-031, E2E-036, E2E-037, E2E-042, E2E-087, E2E-088, E2E-088b, E2E-089, E2E-090, E2E-144, E2E-005J, E2E-201, E2E-202, E2E-207, E2E-206 | | M3 | E2E-012, E2E-013, E2E-014, E2E-015, E2E-016, E2E-017, E2E-018, E2E-019, E2E-040 | | M4 | E2E-022, E2E-023, E2E-024, E2E-025, E2E-026, E2E-030, E2E-038 | -| M5 | E2E-008a, E2E-032, E2E-033, E2E-034, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-051, E2E-052, E2E-053, E2E-054, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102e, E2E-102i, E2E-AGENTS-001, E2E-059a, E2E-060b, E2E-060c, E2E-061a, E2E-073a, E2E-094, E2E-095, E2E-143, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-177, E2E-178, E2E-180, E2E-181, E2E-182, E2E-183, E2E-186, E2E-187, E2E-194, E2E-195, E2E-204, E2E-208 | +| M5 | E2E-008a, E2E-032, E2E-033, E2E-034, E2E-039, E2E-043, E2E-044, E2E-045, E2E-046, E2E-047, E2E-048, E2E-048A, E2E-049, E2E-050, E2E-051, E2E-052, E2E-053, E2E-054, E2E-055, E2E-056, E2E-057, E2E-058, E2E-059, E2E-060, E2E-061, E2E-062, E2E-063, E2E-064, E2E-065, E2E-066, E2E-067, E2E-068, E2E-069, E2E-070, E2E-071, E2E-072, E2E-073, E2E-074, E2E-075, E2E-076, E2E-077, E2E-078, E2E-079, E2E-080, E2E-081, E2E-082, E2E-083, E2E-084, E2E-085, E2E-086, E2E-092, E2E-093, E2E-096, E2E-097, E2E-098, E2E-099, E2E-100, E2E-101, E2E-102, E2E-102a, E2E-102b, E2E-102c, E2E-102d, E2E-102e, E2E-AGENTS-001, E2E-059a, E2E-060b, E2E-060c, E2E-061a, E2E-073a, E2E-094, E2E-095, E2E-143, E2E-145, E2E-146, E2E-146a, E2E-147, E2E-177, E2E-178, E2E-180, E2E-181, E2E-182, E2E-183, E2E-186, E2E-187, E2E-194, E2E-195, E2E-204, E2E-208, E2E-250, E2E-102i | | M6 | E2E-104, E2E-105, E2E-106, E2E-107, E2E-108, E2E-109, E2E-110, E2E-111, E2E-112, E2E-113, E2E-114, E2E-115, E2E-116, E2E-117, E2E-118, E2E-119, E2E-120, E2E-103, E2E-172 | | M6+ | E2E-121, E2E-122, E2E-148, E2E-150, E2E-151, E2E-154, E2E-155, E2E-158, E2E-159, E2E-160, E2E-161, E2E-162, E2E-163, E2E-166, E2E-168, E2E-173, E2E-174, E2E-176, E2E-179, E2E-196a, E2E-196b, E2E-196c, E2E-198, E2E-199, E2E-200, E2E-202, E2E-203, E2E-205, E2E-209, E2E-210, E2E-212, E2E-213, E2E-214, E2E-215, E2E-216, E2E-217, E2E-218, E2E-219 | | Post-MVP | E2E-022A, E2E-022B, E2E-022C, E2E-024I, E2E-024J, E2E-024K, E2E-024L, E2E-024M (plugin roadmap R2/R3/R6) | @@ -9996,3 +9996,31 @@ sample extensions under `apps/desktop/test/fixtures/pi-extensions/`. declaration, `mode-prompts.test.ts` updated wording); desktop journey is Draft (do not run E2E locally unless explicitly requested) + +#### E2E-250: Context usage display preference switches the inspector's leading figure + +- **Preconditions**: An Agent session has completed at least one turn that + reported token usage. Settings → AI → Defaults is reachable. +- **Steps**: + 1. Confirm the composer toolbar context ring shows remaining capacity + (ring nearly full, percentage ≈ remaining %, tooltip and aria-label + use remaining vocabulary). + 2. Open Settings → AI → Defaults and switch the Context usage display + segmented control from Remaining to Used. + 3. Return to the chat and inspect the context ring: the ring arc now + fills by `usedRatio` (nearly empty at low occupancy), the percentage + shows ≈ used %, the popover heading shows used tokens + percentage, + and the tooltip/aria-label use used-capacity vocabulary. + 4. Confirm warning/critical ring colors still follow remaining capacity: + at remaining > 25 % the ring stays neutral even when used % is high. + 5. Switch back to Remaining and confirm the original display returns. +- **Expected**: The display mode flips the ring arc, percentage, token + count, heading, tooltip, and aria-label consistently. Color thresholds + remain based on remaining capacity in both modes. The default for a + fresh profile is Remaining. +- **Specs linked**: `04-ux/06-settings-ia.md`, `04-ux/08-component-spec.md`, + ADR 0223, `08-meta/decisions-log.md` (D398) +- **Acceptance**: C (conversation & stream), Quality (preference) +- **Milestone**: M5 +- **Status**: Unit-covered (`context-usage.test.mjs`, + `settings-general.test.mjs`); full scenario Draft diff --git a/docs/spec/08-meta/decisions-log.md b/docs/spec/08-meta/decisions-log.md index c00fd0bcc..522199831 100644 --- a/docs/spec/08-meta/decisions-log.md +++ b/docs/spec/08-meta/decisions-log.md @@ -70,6 +70,7 @@ This log freezes previously open questions into concrete decisions. | D393 | User-invoked Skills in the composer | **Amend D123 / D174 / ADR 0024 / ADR 0039: active built-in, plugin, and user Skills appear in a separate `Skills` group at the end of the composer slash menu. Selecting one inserts its exact id; Electron main revalidates the active project scope at send time and asks the model to call the local `Skill` tool, preserving on-demand body loading and existing permissions. Existing command names win collisions; inactive Skills remain literal slash text. See ADR 0219 and E2E-088b.** | D174's model-invoked catalog remains the body-loading and security contract, while a final explicit entry makes known workflows discoverable without moving Skill bodies into the renderer, prompt, or host protocol. | | D394 | Windows work-panel chrome keeps one resource action cluster | **Amend D154 / D357 / ADR 0195: the open work-panel header keeps one compact resource switcher; resource close is owned by the existing keyboard-operable context-menu rows, the viewport-fixed toggle remains the only panel collapse control, and subagent detail returns with a back chevron. Windows/Linux native controls remain fixed at the window edge. Renderer-only; no panel state, window geometry, IPC, protocol, or storage change. See ADR 0220 and E2E-067.** | The header resource `X`, viewport-fixed toggle, and Windows native close cluster read as duplicate close actions and became cramped at narrow panel widths. | | D396 | Renderer and plugin-panel scrollbars share one compact contract | **Amend D300: every renderer scroll container uses one 6px, trackless, transparent-at-rest scrollbar with the same hover, focus-within, scroll-reveal, and dragged-thumb states. Remove the sidebar-specific width and opacity override. The plugin-panel preload applies the same contract and 300ms reveal mark to docked and detached plugin documents, including the bundled Files view. External pages loaded inside the Browser guest remain page-owned. Presentation-only; no protocol, storage, host runtime, or external-page behavior change. See E2E-157.** | Windows' classic scrollbar made the right-side work-panel Files view visibly heavier than the conversation, while the sidebar retained a second scrollbar treatment. | +| D398 | Context usage display preference | **Amend D347 / ADR 0184: the context usage inspector's leading figure — the trigger ring arc, percentage, token label, popover heading, tooltip, and `aria-label` — is configurable via `AppSettings.contextUsageDisplay` (`"remaining"` or `"used"`). Default and fallback for absent/unrecognised values is `"remaining"`. When `"used"`, the ring fills by `usedRatio`, and text shows the used-capacity pair. Warning and critical color thresholds (remaining ≤ 25 % / ≤ 10 %) stay based on remaining capacity regardless of display mode. Settings → AI → Defaults adds a segmented control (Remaining / Used) after Link open destination and before Enter-to-send. Renderer only; no protocol, storage, host, or migration change. See ADR 0222 and E2E-250.** | The remaining-only display gave weak signal at low occupancy and did not match users who reason in terms of "how much have I spent". Color must stay on remaining to avoid a misleading green ring at 90 % used. | | D244 | Compact context usage summary | **Amend D103 / D184 / ADR 0047: keep the context inspector's remaining-capacity trigger, used/window counts, turn total, completed-turn speed, exact provider values, aggregate tool types/calls/tokens, and checkpoint summary, but render them as a short summary. Remove the per-tool rows, share bars, source badges, explanatory estimate paragraph, and used-capacity meter from the default panel. No protocol, storage, runtime accounting, or model metadata changes.** *(Amended by D347: the trigger moves to the composer toolbar.)* | The prior diagnostic layout made a routine capacity check tall and visually dense. Keeping the aggregate signal while removing drill-down chrome makes the default status surface scannable without changing the underlying usage data. See ADR 0103 and E2E-060d / US-UI-61. | @@ -956,6 +957,21 @@ section mirrors only marketplace/catalog items still blocking nothing. - Decision D397 amends ADR 0101's previous drag/drop scope. See ADR 0222 and E2E-102i. +## 2026-09-11 — Context usage display preference (D398) + +- The context usage inspector's leading figure — the trigger ring arc + (`strokeDashoffset`), percentage, token label, popover heading, tooltip, + and `aria-label` — is configurable via `AppSettings.contextUsageDisplay` + (`"remaining"` or `"used"`). Default and fallback for absent or + unrecognised values is `"remaining"`. +- When `"used"`, the ring fills by `usedRatio`, and text shows the + used-capacity pair instead of the remaining pair. +- Warning and critical color thresholds (remaining ≤ 25 % / ≤ 10 %) stay + based on remaining capacity regardless of display mode. +- Settings → AI → Defaults adds a segmented control (Remaining / Used) after + Link open destination and before Enter-to-send. +- Decision D398 amends D347 / ADR 0184. See ADR 0223 and E2E-250. + ## 2026-07-31 — Plugin themes ship CSS files - `contributes.themes` declares `{ id, label, path, base? }` and requires diff --git a/docs/zh-CN/spec/04-ux/06-settings-ia.md b/docs/zh-CN/spec/04-ux/06-settings-ia.md index 1caf30f29..1c02c461b 100644 --- a/docs/zh-CN/spec/04-ux/06-settings-ia.md +++ b/docs/zh-CN/spec/04-ux/06-settings-ia.md @@ -69,8 +69,12 @@ - **权限**卡:全局权限模式控制 (询问/接受编辑/自动)控制代理如何自主行动。 - **默认项**卡:主机支持的默认运行模式(Agent / Plan / Goal)、 - 命令 Shell 选择、回车发送控制和大段文本粘贴阈值。该阈值决定纯文本粘贴何时 - 转为会话临时文件,默认值为 600 个字符,接受 1 至 1,000,000 的整数。 + 命令 Shell 选择、链接打开目标、上下文用量显示(剩余或已用)、 + 回车发送控制和大段文本粘贴阈值。链接打开目标默认使用工作面板浏览器, + 可将纯 HTTP(S) 链接点击路由到系统浏览器。上下文用量显示控制输入框 + 工具栏上下文环及其弹层是以剩余容量还是已用容量为引导数值;默认为剩余。 + 该阈值决定纯文本粘贴何时转为会话临时文件,默认值为 600 个字符, + 接受 1 至 1,000,000 的整数。 - **默认项**卡中的**命令 Shell**行:主机发现的本机 PowerShell 5.1、PowerShell 7、 cmd、Git Bash 和 ID 为 `windows-powershell`、`windows-pwsh`、`cmd`、`git-bash` 的 Bash 和 diff --git a/docs/zh-CN/spec/04-ux/08-component-spec.md b/docs/zh-CN/spec/04-ux/08-component-spec.md index 6b63b6a32..c57df5f89 100644 --- a/docs/zh-CN/spec/04-ux/08-component-spec.md +++ b/docs/zh-CN/spec/04-ux/08-component-spec.md @@ -1121,10 +1121,14 @@ tail 不得将该寻呼机从用户气泡中移动或分离。寻呼机是 以及模型 input/output/cache/reasoning/命中率取该回合最新一条已报告用量 的助手消息(最后一次模型请求),占用为 `input + output + reasoning + cacheRead + cacheWrite`(D355);它们不是 - 视觉工具循环里每一次请求的加总。没有用量时不显示。触发器保留剩余容量 - 圆环和百分比,去掉重复的 `Context` 文字;低容量只改变语义颜色,不让 - 颜色成为唯一信号。点击触发器(或使用键盘激活)会打开非模态摘要面板, - 标题为剩余令牌 + 百分比,并展示已用/窗口计数以及两个无卡片的本轮/ + 视觉工具循环里每一次请求的加总。没有用量时不显示。触发器保留容量 + 圆环和百分比,去掉重复的 `Context` 文字;引导数值(圆弧、百分比、令牌 + 标签、弹层标题、tooltip 与 `aria-label`)跟随 + `settings.contextUsageDisplay`——`"remaining"`(默认)或 `"used"`—— + 因此圆环按 `remainingRatio` 或 `usedRatio` 填充。低容量时根据剩余容量 + 改变语义颜色(剩余 ≤ 25 % 警告、≤ 10 % 临界),不随显示模式变化, + 不让颜色成为唯一信号。点击触发器(或使用键盘激活)会打开非模态摘要 + 面板,标题跟随同一显示模式,并展示已用/窗口计数以及两个无卡片的本轮/ 速度数值。模型用量压缩为一条内联摘要,保留精确的最后一次请求 input/output/cache/reasoning 数值和可用的缓存命中率。工具用量压缩为 一条聚合摘要,展示工具种类、调用次数和带 `~` 的估算令牌数;默认视图 diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index bfbfb01f1..820cef6b3 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -4546,14 +4546,14 @@ IPC 请求无法关闭。 |---|---| | A — 应用程序启动 | E2E-001、E2E-002、E2E-003、E2E-004、E2E-067、E2E-076、E2E-079、E2E-092、E2E-097、E2E-143、E2E-150、E2E-168、E2E-204、E2E-217 | | B——模型配置 | E2E-005、E2E-005G、E2E-006、E2E-007、E2E-038、E2E-050、E2E-052、E2E-055、E2E-066、E2E-080、E2E-082、E2E-151、E2E-005J、E2E-199、E2E-201、E2E-202、E2E-203、E2E-209 | -| C — 对话和直播 | E2E-008、E2E-008a、E2E-009、E2E-010、E2E-011、E2E-011a、E2E-011b、E2E-031、E2E-040、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-052、 E2E-053、E2E-054、E2E-055、E2E-059、E2E-059a、E2E-060c、E2E-060d、E2E-061、E2E-061a、E2E-062、E2E-064、E2E-065、E2E-068、E2E-071、 E2E-073、E2E-074、E2E-075、E2E-081、E2E-083、E2E-084、E2E-086、E2E-087、E2E-088、E2E-088b、E2E-089、E2E-090、E2E-094、E2E-095、E2E-096、 E2E-097、E2E-098、E2E-099、E2E-102、E2E-102a、E2E-102b、E2E-106、E2E-109、E2E-111、E2E-114、E2E-116、E2E-117、E2E-118、E2E-119、 E2E-120、E2E-121、E2E-代理-001、E2E-142、E2E-144、E2E-145、E2E-146、E2E-147、E2E-151、E2E-199 | +| C — 对话和直播 | E2E-008、E2E-008a、E2E-009、E2E-010、E2E-011、E2E-011a、E2E-011b、E2E-031、E2E-040、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-052、 E2E-053、E2E-054、E2E-055、E2E-059、E2E-059a、E2E-060c、E2E-060d、E2E-061、E2E-061a、E2E-062、E2E-064、E2E-065、E2E-068、E2E-071、 E2E-073、E2E-074、E2E-075、E2E-081、E2E-083、E2E-084、E2E-086、E2E-087、E2E-088、E2E-088b、E2E-089、E2E-090、E2E-094、E2E-095、E2E-096、 E2E-097、E2E-098、E2E-099、E2E-102、E2E-102a、E2E-102b、E2E-106、E2E-109、E2E-111、E2E-114、E2E-116、E2E-117、E2E-118、E2E-119、 E2E-120、E2E-121、E2E-代理-001、E2E-142、E2E-144、E2E-145、E2E-146、E2E-147、E2E-151、E2E-199、E2E-250 | | D——工作区 | E2E-012、E2E-013、E2E-022B、E2E-024I、E2E-047、E2E-049、E2E-057、E2E-058、E2E-060、E2E-068、E2E-075、E2E-078、E2E-153 | | E——工具和权限 | E2E-008a、E2E-014、E2E-015、E2E-016、E2E-017、E2E-018、E2E-019、E2E-024I、E2E-024K、E2E-040、E2E-049、E2E-074、E2E-093、E2E-097、 E2E-099、E2E-100、E2E-101、E2E-102、E2E-103、E2E-105、E2E-106、E2E-107、E2E-111、E2E-112、E2E-113、E2E-114、E2E-115、E2E-116、 E2E-119、E2E-121、E2E-122、E2E-123、E2E-142、E2E-145、E2E-147 | | F——坚持 | E2E-020、E2E-021、E2E-036、E2E-037、E2E-038、E2E-040、E2E-042、E2E-047、E2E-048、E2E-051、E2E-054、E2E-056、E2E-061、E2E-062、 E2E-064、E2E-066、E2E-068、E2E-071、E2E-072、E2E-073、E2E-082、E2E-084、E2E-096、E2E-098、E2E-102、E2E-102b、E2E-103、E2E-代理-001、 E2E-061a、E2E-073a、E2E-104、E2E-106、E2E-107、E2E-108、E2E-109、E2E-110、E2E-112、E2E-118、E2E-119、E2E-120、E2E-121、E2E-123、E2E-142、E2E-146、E2E-148、E2E-151、E2E-171、E2E-005J | | G——插件 | E2E-022、E2E-022A、E2E-022B、E2E-022C、E2E-023、E2E-024、E2E-024B、E2E-024C、E2E-024D、E2E-024E、E2E-024W、E2E-024F、E2E-024G、E2E-024H、 E2E-024I、E2E-024J、E2E-024K、E2E-024L、E2E-024M、E2E-024N、E2E-024O、E2E-024P、E2E-025、E2E-026、E2E-105、E2E-117、E2E-120、E2E-122、E2E-123、E2E-148、E2E-153 | | H——诊断 | E2E-027、E2E-031、E2E-034、E2E-042、E2E-096、E2E-098、E2E-104、E2E-107、E2E-108、E2E-109、E2E-110、E2E-113、E2E-115、E2E-116、 E2E-118、E2E-121、E2E-146、E2E-194、E2E-195 | | 安全性 | E2E-028、E2E-029、E2E-030、E2E-024J、E2E-024K、E2E-024M、E2E-049、E2E-068、E2E-086、E2E-105、E2E-106、E2E-107、E2E-108、E2E-109、 E2E-110、E2E-112、E2E-113、E2E-115、E2E-116、E2E-117、E2E-119、E2E-121、E2E-122、E2E-123、E2E-142、E2E-148、E2E-151、E2E-153 | -| 品质 | E2E-032、E2E-033、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、E2E-053、E2E-055、 E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、 E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、 E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-094、E2E-095、E2E-096、E2E-097、E2E-098、E2E-099、E2E-100、E2E-101、E2E-102、 E2E-102a、E2E-102b、E2E-103、E2E-AGENTS-001、E2E-024N、E2E-024O、E2E-059a、E2E-060b、E2E-060c、E2E-060d、E2E-061a、E2E-073a、E2E-111、 E2E-114、E2E-117、E2E-118、E2E-119、E2E-120、E2E-122、E2E-123、E2E-142、E2E-143、E2E-144、E2E-145、E2E-146、E2E-147、E2E-148、E2E-150、E2E-151、E2E-153、E2E-194、E2E-195、E2E-199、E2E-200、E2E-201、E2E-202、E2E-203、E2E-204、E2E-209、E2E-210 | +| 品质 | E2E-032、E2E-033、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、E2E-053、E2E-055、 E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、 E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、 E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-094、E2E-095、E2E-096、E2E-097、E2E-098、E2E-099、E2E-100、E2E-101、E2E-102、 E2E-102a、E2E-102b、E2E-103、E2E-AGENTS-001、E2E-024N、E2E-024O、E2E-059a、E2E-060b、E2E-060c、E2E-060d、E2E-061a、E2E-073a、E2E-111、 E2E-114、E2E-117、E2E-118、E2E-119、E2E-120、E2E-122、E2E-123、E2E-142、E2E-143、E2E-144、E2E-145、E2E-146、E2E-147、E2E-148、E2E-150、E2E-151、E2E-153、E2E-194、E2E-195、E2E-199、E2E-200、E2E-201、E2E-202、E2E-203、E2E-204、E2E-209、E2E-210、E2E-250 | | 里程碑 | 应用场景 | |---|---| @@ -4561,7 +4561,7 @@ IPC 请求无法关闭。 | M2 | E2E-004、E2E-005、E2E-006、E2E-007、E2E-008、E2E-009、E2E-010、E2E-011、E2E-011a、E2E-011b、E2E-020、E2E-021、E2E-027、E2E-031、 E2E-036、E2E-037、E2E-042、E2E-087、E2E-088、E2E-088b、E2E-089、E2E-090、E2E-144、E2E-005J、E2E-201 | | M3 | E2E-012、E2E-013、E2E-014、E2E-015、E2E-016、E2E-017、E2E-018、E2E-019、E2E-040 | | M4 | E2E-022、E2E-023、E2E-024、E2E-025、E2E-026、E2E-030、E2E-038 | -| M5 | E2E-008a、E2E-032、E2E-033、E2E-034、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、 E2E-051、E2E-052、E2E-053、E2E-054、E2E-055、E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、 E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、 E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-096、E2E-097、E2E-098、E2E-099、 E2E-100、E2E-101、E2E-102、E2E-102a、E2E-102b、E2E-AGENTS-001、E2E-059a、E2E-060b、E2E-060c、E2E-061a、E2E-073a、E2E-094、E2E-095、E2E-143、E2E-145、E2E-146、E2E-147、E2E-194、E2E-195、E2E-204 | +| M5 | E2E-008a、E2E-032、E2E-033、E2E-034、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、 E2E-051、E2E-052、E2E-053、E2E-054、E2E-055、E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、 E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、 E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-096、E2E-097、E2E-098、E2E-099、 E2E-100、E2E-101、E2E-102、E2E-102a、E2E-102b、E2E-AGENTS-001、E2E-059a、E2E-060b、E2E-060c、E2E-061a、E2E-073a、E2E-094、E2E-095、E2E-143、E2E-145、E2E-146、E2E-147、E2E-194、E2E-195、E2E-204、E2E-250 | | M6 | E2E-104、E2E-105、E2E-106、E2E-107、E2E-108、E2E-109、E2E-110、E2E-111、E2E-112、E2E-113、E2E-114、E2E-115、E2E-116、E2E-117、 E2E-118、E2E-119、E2E-120、E2E-103 | | M6+ | E2E-121、E2E-122、E2E-123、E2E-142、E2E-148、E2E-150、E2E-151、E2E-168、E2E-199、E2E-200、E2E-202、E2E-203、E2E-209、E2E-211、E2E-212、E2E-213、E2E-214、E2E-215、E2E-216、E2E-217 | | 后MVP | E2E-022A、E2E-022B、E2E-022C、E2E-024I、E2E-024J、E2E-024K、E2E-024L、E2E-024M(插件路线图 R2/R3/R6) | @@ -6477,3 +6477,18 @@ IPC 请求无法关闭。 - **里程碑**:M6 - **状态**:由单元测试与源码契约覆盖(2026-09-11):`start_does_not_keep_the_stdout_channel_open`、`peek_jsonrpc_id_reads_a_string_id_from_a_truncated_prefix`、`apps/desktop/test/windows-host-runtime.test.mjs`(弱引用)、`apps/desktop/test/rpc-lifecycle-contract.test.mjs`(客户端预检)、`packages/shared/src/rpc-limits.test.ts`。桌面行程仍为草稿。 + +#### E2E-250:上下文用量显示偏好切换检查器引导数值 + +- **前提条件**:一个 Agent 会话已完成至少一个报告了令牌用量的回合。可打开设置 → 全局 AI → 默认项。 +- **步骤**: + 1. 确认输入框工具栏上下文环显示剩余容量(环近乎满圆,百分比 ≈ 剩余 %,tooltip 与 aria-label 使用剩余词汇)。 + 2. 打开设置 → 全局 AI → 默认项,将上下文用量显示分段控件从「剩余」切到「已用」。 + 3. 回到聊天,检查上下文环:圆弧按 `usedRatio` 填充(低占用时近乎空环),百分比显示 ≈ 已用 %,弹层标题显示已用令牌 + 百分比,tooltip/aria-label 使用已用容量词汇。 + 4. 确认警告/临界颜色仍按剩余容量判定:剩余 > 25 % 时环保持中性色,即使已用 % 较高。 + 5. 切回「剩余」,确认恢复原始显示。 +- **预期**:显示模式一致翻转圆弧、百分比、令牌数、标题、tooltip 与 aria-label。两种模式下颜色阈值始终基于剩余容量。新配置文件的默认值为「剩余」。 +- **链接规格**:`04-ux/06-settings-ia.md`、`04-ux/08-component-spec.md`、ADR 0223、`08-meta/decisions-log.md`(D398) +- **验收**:C(会话与流式)、Quality(偏好) +- **里程碑**:M5 +- **状态**:单元测试覆盖(`context-usage.test.mjs`、`settings-general.test.mjs`);完整场景为草稿 diff --git a/docs/zh-CN/spec/08-meta/decisions-log.md b/docs/zh-CN/spec/08-meta/decisions-log.md index f06157780..8280b50e9 100644 --- a/docs/zh-CN/spec/08-meta/decisions-log.md +++ b/docs/zh-CN/spec/08-meta/decisions-log.md @@ -73,6 +73,7 @@ | D393 | Composer 中用户调用的 Skills | **修订 D123 / D174 / ADR 0024 / ADR 0039:激活的内置、插件和用户 Skills 出现在 composer slash 菜单末尾独立的 `Skills` 分组中。选择后插入其精确 id;Electron main 在发送时重新验证当前项目范围,并要求模型调用本地 `Skill` 工具,同时保留按需加载正文和现有权限。现有命令名优先解决冲突;未激活的 Skills 保持为字面 slash 文本。见 ADR 0219 和 E2E-088b。** | D174 的模型调用目录仍是 Skill 正文加载和安全契约,但拒绝面向用户的 slash 条目使已经知道工作流的用户难以发现活跃 Skills。 | | D394 | Windows 工作面板 chrome 保持单一资源操作组 | **修订 D154 / D357 / ADR 0195:打开的工作面板标题栏只保留一个紧凑资源切换器;资源关闭由现有可键盘操作的上下文菜单行负责,视口固定开关仍是唯一的面板折叠控件,子代理详情使用返回箭头。Windows/Linux 原生控件仍固定在窗口边缘。仅渲染器变更;不改面板状态、窗口几何、IPC、协议或存储。见 ADR 0220 与 E2E-067。** | 标题栏资源 `X`、视口固定开关和 Windows 原生关闭按钮在窄面板中看起来像重复的关闭操作,并且过于拥挤。 | | D396 | 渲染器与插件面板滚动条统一为紧凑规则 | **修订 D300:渲染器中的每个滚动容器统一使用 6px、无轨道、静止时透明的滚动条,以及相同的悬停、focus-within、滚动显示和拖动状态。移除侧边栏专用的宽度和透明度覆盖。插件面板 preload 给停靠和独立插件文档(包括内置 Files 视图)注入同一规则和 300ms 的滚动显示标记。Browser 内部加载的外部网页仍由网页自己管理。仅表现层变更;不改变协议、存储、主机运行时或外部网页行为。见 E2E-157。** | Windows 的经典滚动条让右侧工作面板的 Files 视图明显比对话区更粗,而侧边栏还保留了第二套滚动条样式。 | +| D398 | 上下文用量显示偏好 | **修订 D347 / ADR 0184:上下文用量检查器的引导数值——触发器圆弧(`strokeDashoffset`)、百分比、令牌标签、弹层标题、tooltip 与 `aria-label`——可通过 `AppSettings.contextUsageDisplay`(`"remaining"` 或 `"used"`)配置。缺省及非法值回退为 `"remaining"`。当设为 `"used"` 时,圆环按 `usedRatio` 填充,文字展示已用容量对。警告与临界颜色阈值(剩余 ≤ 25 % / ≤ 10 %)仍按剩余容量判定,不随显示模式变化。设置 → 全局 AI → 默认项新增分段控件(剩余 / 已用),位于链接打开目标之后、回车发送之前。仅渲染器改动;无协议、存储、宿主或迁移变更。见 ADR 0222 与 E2E-250。** | 仅显示剩余时在低占用下信号微弱,且不符合习惯用「已用多少」思考的用户。颜色必须始终按剩余判定,否则已用 90 % 仍为绿色会产生误导。 | | D244 | 紧凑的上下文用量摘要 | **修订 D103 / D184 / ADR 0047:保留上下文检查器的剩余容量触发器、已用/窗口计数、回合合计、已完成回合速度、精确的提供商数值、聚合的工具类型/调用数/令牌数以及检查点摘要,但把它们渲染为一段简短摘要。从默认面板中移除逐工具行、占比条、来源徽章、解释性估算段落和已用容量计量条。不改动协议、存储、运行时计费或模型元数据。** *(由 D347 修订:触发器移到输入框工具栏。)* | 之前的诊断式布局让一次例行的容量检查变得又高又密。保留聚合信号、移除下钻装饰,使默认状态界面可以快速浏览,同时不改变底层用量数据。参见 ADR 0103 与 E2E-060d / US-UI-61。 | | D347 | 输入框工具栏中的上下文用量检查器 | **修订 D103 / D184 / D244 / ADR 0047 / ADR 0103:紧凑上下文检查器放在输入框右侧工具栏、模型 × 推理芯片左侧,始终对应当前最新一条已报告用量的助手回合。触发器保留剩余容量圆环和百分比,去掉重复的 Context 文字。弹层标题为剩余 tokens + 百分比;下方行用同一套左标签/右数值节奏,只用留白分隔,不画内部分隔线(D297)。答案下方的助理元只保留模型徽章。仅渲染器改动。** | 挂在最新答案下方的检查器会随记录滚出视野。输入框只保留一个入口作为最新快照的权威位置;标题双线通过去掉多余说明文字解决,而不是加分隔线。参见 ADR 0184 与 E2E-060d / US-UI-61。 | | D355 | 上下文检查器按最后一次请求计算占用 | **修订 D103 / D184 / D244 / D347 / ADR 0047 / ADR 0103 / ADR 0184:剩余容量、已用/窗口计数、本轮合计,以及模型 input/output/cache/reasoning/命中率,都取最新一条已报告用量的助手消息(最后一次模型请求)。占用为该消息的 `input + output + reasoning + cacheRead + cacheWrite`。它们不是视觉工具循环里每一次请求的加总。已完成回合速度和聚合工具行仍描述该视觉回合。仅渲染器改动;宿主回合汇总和 Token Insights 仍做账单累加。** | 把工具循环里的缓存读取加总后,367k 缓存读取会紧挨着 55k 窗口。OpenCode 的上下文组件只用最后一条助手消息。参见 ADR 0193 与 E2E-060d。 | @@ -3818,3 +3819,11 @@ D193 和 D194。 的滚动显示标记。Browser 内部加载的外部网页仍由网页自己管理样式。 - 这是仅表现层的变更,不改变协议、存储、主机运行时或外部网页行为。见 `04-ux/07-ui-design-system.md`、`04-ux/08-component-spec.md` 与 E2E-157。 + +## 2026-09-11 —— 上下文用量显示偏好(D398) + +- 上下文用量检查器的引导数值——触发器圆弧(`strokeDashoffset`)、百分比、令牌标签、弹层标题、tooltip 与 `aria-label`——可通过 `AppSettings.contextUsageDisplay`(`"remaining"` 或 `"used"`)配置。缺省及非法值回退为 `"remaining"`。 +- 当设为 `"used"` 时,圆环按 `usedRatio` 填充,文字展示已用容量对。 +- 警告与临界颜色阈值(剩余 ≤ 25 % / ≤ 10 %)仍按剩余容量判定,不随显示模式变化。 +- 设置 → 全局 AI → 默认项新增分段控件(剩余 / 已用),位于链接打开目标之后、回车发送之前。 +- 决策 D398 修订 D347 / ADR 0184。见 ADR 0223 与 E2E-250。