From ca37b19cf8d3882f0b4eee1b9e49050494f30422 Mon Sep 17 00:00:00 2001 From: nqrwhal <81386789+nqrwhal@users.noreply.github.com> Date: Sat, 15 Aug 2026 05:07:53 -0700 Subject: [PATCH] fix(web): show selected model in context window tooltip (#4772) Co-authored-by: Julius Marminge Co-authored-by: codex --- apps/web/src/components/chat/ChatComposer.tsx | 28 ++++----- .../chat/ContextWindowMeter.logic.test.ts | 58 +++++++++++++++++++ .../chat/ContextWindowMeter.logic.ts | 25 ++++++++ .../components/chat/ContextWindowMeter.tsx | 7 ++- 4 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 apps/web/src/components/chat/ContextWindowMeter.logic.test.ts create mode 100644 apps/web/src/components/chat/ContextWindowMeter.logic.ts diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 293767a7390d85055e74335cfaca3f5e17243537..a92bf439ae1329cbd300f88e47ece70fff6162ab 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -103,6 +103,7 @@ import { renderProviderTraitsPicker, } from "./composerProviderState"; import { ContextWindowMeter } from "./ContextWindowMeter"; +import { resolveContextWindowModelDisplayName } from "./ContextWindowMeter.logic"; import { buildExpandedImagePreview, type ExpandedImagePreview } from "./ExpandedImagePreview"; import { basenameOfPath } from "../../pierre-icons"; import { cn, randomUUID } from "~/lib/utils"; @@ -210,7 +211,7 @@ import { XIcon, } from "lucide-react"; import { proposedPlanTitle } from "../../proposedPlan"; -import { getProviderDisplayName, getProviderInteractionModeToggle } from "../../providerModels"; +import { getProviderInteractionModeToggle } from "../../providerModels"; import { applyProviderInstanceSettings, deriveProviderInstanceEntries, @@ -225,10 +226,7 @@ import type { UnifiedSettings } from "@t3tools/contracts/settings"; import type { SessionPhase, Thread } from "../../types"; import type { PendingUserInputDraftAnswer } from "../../pendingUserInput"; import type { PendingApproval, PendingUserInput } from "../../session-logic"; -import { - deriveLatestContextWindowSnapshot, - formatProviderDisplayName, -} from "../../lib/contextWindow"; +import { deriveLatestContextWindowSnapshot } from "../../lib/contextWindow"; import { formatProviderSkillDisplayName } from "../../providerSkillPresentation"; import { searchProviderSkills } from "../../providerSkillSearch"; import { useMediaQuery } from "../../hooks/useMediaQuery"; @@ -396,7 +394,7 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop const ComposerFooterPrimaryActions = memo(function ComposerFooterPrimaryActions(props: { compact: boolean; activeContextWindow: ReturnType; - activeThreadProviderDisplayName: string | null; + activeThreadModelDisplayName: string | null; isPreparingWorktree: boolean; pendingAction: { questionIndex: number; @@ -424,7 +422,7 @@ const ComposerFooterPrimaryActions = memo(function ComposerFooterPrimaryActions( {props.activeContextWindow ? ( ) : null} {props.isPreparingWorktree ? ( @@ -930,16 +928,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) () => deriveLatestContextWindowSnapshot(activeThreadActivities ?? []), [activeThreadActivities], ); - const activeThreadProviderDisplayName = useMemo(() => { - if (!activeThreadModelSelection) return null; - const entry = providerStatuses.find( - (p) => p.instanceId === activeThreadModelSelection.instanceId, - ); - if (entry) { - return getProviderDisplayName(providerStatuses, entry.driver); - } - return formatProviderDisplayName(activeThreadModelSelection.instanceId); - }, [providerStatuses, activeThreadModelSelection]); + const activeThreadModelDisplayName = useMemo( + () => resolveContextWindowModelDisplayName(activeThreadModelSelection, modelOptionsByInstance), + [activeThreadModelSelection, modelOptionsByInstance], + ); // ------------------------------------------------------------------ // Composer-local state @@ -3222,7 +3214,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) { + it("uses the selected model from the exact provider instance", () => { + const primaryInstanceId = ProviderInstanceId.make("codex"); + const selectedInstanceId = ProviderInstanceId.make("codex-work"); + const modelOptionsByInstance = new Map([ + [ + primaryInstanceId, + [{ slug: "gpt-5.6-sol", name: "Primary profile model", shortName: "Primary" }], + ], + [selectedInstanceId, [{ slug: "gpt-5.6-sol", name: "GPT-5.6 Sol", shortName: "5.6 Sol" }]], + ]); + + expect( + resolveContextWindowModelDisplayName( + { + instanceId: selectedInstanceId, + model: "gpt-5.6-sol", + }, + modelOptionsByInstance, + ), + ).toBe("5.6 Sol"); + }); + + it("falls back to the selected model slug when model metadata is unavailable", () => { + const selectedInstanceId = ProviderInstanceId.make("codex-work"); + + expect( + resolveContextWindowModelDisplayName( + { + instanceId: selectedInstanceId, + model: "custom-model", + }, + new Map(), + ), + ).toBe("custom-model"); + }); +}); + +describe("formatContextWindowCompactionMessage", () => { + it("describes compaction in terms of the selected model", () => { + expect(formatContextWindowCompactionMessage("GPT-5.6 Sol")).toBe( + "Context for GPT-5.6 Sol compacts automatically when needed.", + ); + }); + + it("uses neutral copy when the model is unavailable", () => { + expect(formatContextWindowCompactionMessage(null)).toBe( + "Context compacts automatically when needed.", + ); + }); +}); diff --git a/apps/web/src/components/chat/ContextWindowMeter.logic.ts b/apps/web/src/components/chat/ContextWindowMeter.logic.ts new file mode 100644 index 0000000000000000000000000000000000000000..c87170ffe610d0b9e26c2c02fd85ec76de771d31 --- /dev/null +++ b/apps/web/src/components/chat/ContextWindowMeter.logic.ts @@ -0,0 +1,25 @@ +import type { ModelSelection, ProviderInstanceId } from "@t3tools/contracts"; +import { getTriggerDisplayModelName, type ModelEsque } from "./providerIconUtils"; + +export function resolveContextWindowModelDisplayName( + selection: ModelSelection | null | undefined, + modelOptionsByInstance: ReadonlyMap>, +): string | null { + if (!selection) { + return null; + } + + const selectedModel = modelOptionsByInstance + .get(selection.instanceId) + ?.find((model) => model.slug === selection.model); + + return selectedModel ? getTriggerDisplayModelName(selectedModel) : selection.model; +} + +export function formatContextWindowCompactionMessage( + modelDisplayName: string | null | undefined, +): string { + return modelDisplayName + ? `Context for ${modelDisplayName} compacts automatically when needed.` + : "Context compacts automatically when needed."; +} diff --git a/apps/web/src/components/chat/ContextWindowMeter.tsx b/apps/web/src/components/chat/ContextWindowMeter.tsx index f377c893ae2c72567a13af4803eafd0b5ac46573..6e42dcadd8b9fb857d490677b32ec6176ddf7bc7 100644 --- a/apps/web/src/components/chat/ContextWindowMeter.tsx +++ b/apps/web/src/components/chat/ContextWindowMeter.tsx @@ -1,6 +1,7 @@ import { Button } from "../ui/button"; import { type ContextWindowSnapshot, formatContextWindowTokens } from "~/lib/contextWindow"; import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; +import { formatContextWindowCompactionMessage } from "./ContextWindowMeter.logic"; function formatPercentage(value: number | null): string | null { if (value === null || !Number.isFinite(value)) { @@ -14,9 +15,9 @@ function formatPercentage(value: number | null): string | null { export function ContextWindowMeter(props: { usage: ContextWindowSnapshot; - providerDisplayName?: string | null; + modelDisplayName?: string | null; }) { - const { usage, providerDisplayName } = props; + const { usage, modelDisplayName } = props; const usedPercentage = formatPercentage(usage.usedPercentage); const normalizedPercentage = Math.max(0, Math.min(100, usage.usedPercentage ?? 0)); const radius = 9.75; @@ -127,7 +128,7 @@ export function ContextWindowMeter(props: { ) : null} {usage.compactsAutomatically ? (
- {providerDisplayName ?? "It"} automatically compacts its context when needed. + {formatContextWindowCompactionMessage(modelDisplayName)}
) : null} -- 2.54.0