diff --git a/apps/web/src/components/chat/ModelPickerContent.tsx b/apps/web/src/components/chat/ModelPickerContent.tsx index 211bbd59dbd98..ca6e34141cdf3 100644 --- a/apps/web/src/components/chat/ModelPickerContent.tsx +++ b/apps/web/src/components/chat/ModelPickerContent.tsx @@ -834,6 +834,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { {showSidebar && ( void; onFocusSearch: () => void; instanceEntries: ReadonlyArray; @@ -131,6 +133,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { const isDisabled = (isUnavailable && !unavailableSelectionIsReachable) || isContextDisabled; const isSelected = props.selectedInstanceId === entry.instanceId; + const isActive = props.activeInstanceId === entry.instanceId; const isHovered = hoveredInstanceId === entry.instanceId; const showInstanceBadge = shouldShowInstanceBadge(entry, props.instanceEntries); @@ -138,7 +141,9 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { ? describeUnavailableInstance(entry) : isContextDisabled ? (props.getDisabledInstanceTooltip?.(entry) ?? entry.displayName) - : entry.displayName; + : isActive + ? `${entry.displayName} ยท Active` + : entry.displayName; const button = ( @@ -180,6 +186,13 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { } {...(entry.accentColor ? { badgeClassName: "h-3 min-w-3 px-0.5 text-5xs" } : {})} /> + {isActive ? ( + + ) : null} ); diff --git a/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx b/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx index b6dd1a38e86cf..252164bcb60cd 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx @@ -79,6 +79,12 @@ it("keeps each registry image when adding a model from another ACP agent", async await vi.waitFor(() => expect(imageWidths()).toEqual([11])); await screen.getByRole("button", { name: "First model", exact: true }).click(); await page.getByRole("button", { name: "Second agent", exact: true }).click(); + await expect + .element(page.getByRole("button", { name: "First agent", exact: true })) + .toHaveAttribute("aria-current", "true"); + await expect + .element(page.getByRole("button", { name: "Second agent", exact: true })) + .not.toHaveAttribute("aria-current"); await userEvent.keyboard("{Shift>}"); try { await page.getByRole("option", { name: /Second model/ }).click(); diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index 2e47b4c2fe3b4..de528feabdf37 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -7,9 +7,11 @@ import { } from "@t3tools/contracts"; import { createContext, memo, type ReactNode, useRef, useState } from "react"; import { Badge } from "../ui/badge"; +import { Kbd } from "../ui/kbd"; import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { cn } from "~/lib/utils"; +import { ThreadHoverCard, ThreadHoverCardPopup } from "../ThreadHoverCard"; import { ModelPickerContent, resolveModelPickerSelectedModel } from "./ModelPickerContent"; import { ChatGptSharingControl } from "./ChatGptSharingControl"; import { ProviderInstanceIcon } from "./ProviderInstanceIcon"; @@ -228,6 +230,9 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { const allModelNames = selectedEntries ? selectedEntries.map((selection) => selection.fullLabel).join(", ") || "Choose models" : undefined; + // Only when same-driver instances could be confused, e.g. one Claude instance per account. + const activeInstanceLabel = + showInstanceBadge && !selectedEntries ? activeEntry?.displayName : undefined; const triggerTooltipContent = [ props.triggerLabel ?? allModelNames ?? triggerLabel, props.triggerDetail, @@ -309,6 +314,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { aria-label={[ props.triggerAriaLabel ?? props.triggerLabel ?? allModelNames ?? triggerLabel, props.triggerDetail, + activeInstanceLabel, ] .filter(Boolean) .join(", ")} @@ -387,12 +393,42 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { /> ) : null} - {props.isComposerOwned ? ( + {props.isComposerOwned && !activeInstanceLabel ? ( {triggerText} ) : ( }>{triggerText} - {triggerTooltipContent} + {activeInstanceLabel && activeEntry ? ( + // Same card as the agents panel, so the thread's instance reads the same way. + + +
+ + + {activeInstanceLabel} + +
+ {props.triggerDetail || shortcutLabel ? ( +
+ {props.triggerDetail} + {shortcutLabel ? {shortcutLabel} : null} +
+ ) : null} +
+
+ ) : ( + {triggerTooltipContent} + )}
)} {props.triggerSuffix}