From a5d35321bd620400dc03ddf7a42ee2672b225ac3 Mon Sep 17 00:00:00 2001 From: Vitaly Iegorov Date: Sat, 15 Aug 2026 14:06:50 +0200 Subject: [PATCH] fix(web): show provider account accent badge in sidebar rows and hover card (#5980) Co-authored-by: Claude Fable 5 --- apps/web/src/components/Sidebar.tsx | 55 +++++++++++++++---- .../components/chat/ModelPickerSidebar.tsx | 19 +++---- .../components/chat/ProviderModelPicker.tsx | 8 +-- apps/web/src/providerInstances.ts | 17 ++++++ 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index a7a5b638c0eb98322fad271abb1c01d74f7a5ad6..016a4c682754181ebed8d73c80ec081a999f8e1d 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -158,7 +158,11 @@ import { import { ProjectFavicon } from "./ProjectFavicon"; import { ProviderInstanceIcon } from "./chat/ProviderInstanceIcon"; import { getTriggerDisplayModelLabel } from "./chat/providerIconUtils"; -import { deriveProviderInstanceEntries, type ProviderInstanceEntry } from "../providerInstances"; +import { + deriveProviderInstanceEntries, + shouldShowInstanceBadge, + type ProviderInstanceEntry, +} from "../providerInstances"; import { primaryServerProvidersAtom } from "../state/server"; import { useThreadRunningTerminalIds } from "../state/terminalSessions"; import { stackedThreadToast, toastManager } from "./ui/toast"; @@ -247,7 +251,8 @@ function SidebarThreadTooltip({ projectCwd, projectFaviconPath, environmentLabel, - driverKind, + providerEntry, + showInstanceBadge, modelInstanceId, modelLabel, branchMismatch, @@ -259,7 +264,8 @@ function SidebarThreadTooltip({ projectCwd: string | null; projectFaviconPath: string | null; environmentLabel: string | null; - driverKind: ProviderInstanceEntry["driverKind"] | null; + providerEntry: ProviderInstanceEntry | null; + showInstanceBadge: boolean; modelInstanceId: string; modelLabel: string; branchMismatch: { @@ -269,6 +275,7 @@ function SidebarThreadTooltip({ terminalStatus: TerminalStatusIndicator | null; terminalProcessCount: number; }) { + const driverKind = providerEntry?.driverKind ?? null; return ( -
{modelLabel}
+
+ {showInstanceBadge && providerEntry + ? `${modelLabel} ยท ${providerEntry.displayName}` + : modelLabel} +
) : null} {terminalStatus ? ( @@ -867,6 +885,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { const modelInstanceId = thread.session?.providerInstanceId ?? thread.modelSelection.instanceId; const providerEntry = props.providerEntryByInstanceId.get(modelInstanceId) ?? null; const driverKind = providerEntry?.driverKind ?? null; + const showInstanceBadge = + providerEntry !== null && + shouldShowInstanceBadge(providerEntry, props.providerEntryByInstanceId.values()); const selectedModel = providerEntry?.models.find( (model) => model.slug === thread.modelSelection.model, ); @@ -884,7 +905,8 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { projectCwd={props.projectCwd} projectFaviconPath={props.projectFaviconPath} environmentLabel={props.environmentLabel} - driverKind={driverKind} + providerEntry={providerEntry} + showInstanceBadge={showInstanceBadge} modelInstanceId={modelInstanceId} modelLabel={modelLabel} branchMismatch={branchMismatch} @@ -1481,11 +1503,19 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { ) : null} {driverKind ? ( - + ) : null} @@ -1542,7 +1572,9 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { }); const modelInstanceId = thread.session?.providerInstanceId ?? thread.modelSelection.instanceId; const providerEntry = props.providerEntryByInstanceId.get(modelInstanceId) ?? null; - const driverKind = providerEntry?.driverKind ?? null; + const showInstanceBadge = + providerEntry !== null && + shouldShowInstanceBadge(providerEntry, props.providerEntryByInstanceId.values()); const selectedModel = providerEntry?.models.find( (model) => model.slug === thread.modelSelection.model, ); @@ -1600,7 +1632,8 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { projectCwd={props.projectCwd} projectFaviconPath={props.projectFaviconPath} environmentLabel={props.environmentLabel} - driverKind={driverKind} + providerEntry={providerEntry} + showInstanceBadge={showInstanceBadge} modelInstanceId={modelInstanceId} modelLabel={modelLabel} branchMismatch={branchMismatch} diff --git a/apps/web/src/components/chat/ModelPickerSidebar.tsx b/apps/web/src/components/chat/ModelPickerSidebar.tsx index 05b44dcb732761cba47f4b578655f139a0352dbf..df35cbd90e544abecb4092a3b97f2a4718cf03ec 100644 --- a/apps/web/src/components/chat/ModelPickerSidebar.tsx +++ b/apps/web/src/components/chat/ModelPickerSidebar.tsx @@ -1,10 +1,14 @@ import { type ProviderInstanceId } from "@t3tools/contracts"; -import { memo, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { memo, useLayoutEffect, useRef, useState } from "react"; import { SparklesIcon, StarIcon } from "lucide-react"; import { ProviderInstanceIcon } from "./ProviderInstanceIcon"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { cn } from "~/lib/utils"; -import { isProviderInstancePickerReady, type ProviderInstanceEntry } from "../../providerInstances"; +import { + isProviderInstancePickerReady, + shouldShowInstanceBadge, + type ProviderInstanceEntry, +} from "../../providerInstances"; /** * Build the hover tooltip for an instance button. Mirrors the old @@ -65,14 +69,6 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { const [hoveredInstanceId, setHoveredInstanceId] = useState(null); const sidebarContentRef = useRef(null); const [selectedIndicatorTop, setSelectedIndicatorTop] = useState(null); - const duplicateDriverCounts = useMemo(() => { - const counts = new Map(); - for (const entry of props.instanceEntries) { - counts.set(entry.driverKind, (counts.get(entry.driverKind) ?? 0) + 1); - } - return counts; - }, [props.instanceEntries]); - useLayoutEffect(() => { const content = sidebarContentRef.current; if (!content) { @@ -143,8 +139,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { const isSelected = props.selectedInstanceId === entry.instanceId; const isHovered = hoveredInstanceId === entry.instanceId; const showNewBadge = props.newBadgeInstanceIds?.has(entry.instanceId) ?? false; - const showInstanceBadge = - Boolean(entry.accentColor) || (duplicateDriverCounts.get(entry.driverKind) ?? 0) > 1; + const showInstanceBadge = shouldShowInstanceBadge(entry, props.instanceEntries); const tooltip = isUnavailable ? describeUnavailableInstance(entry) diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index a9b3a398115bec1d24494850a8cf05f8dbe9938e..bd374a0fd6f5a05b2609c3ad6bea0e908359cefe 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -16,7 +16,7 @@ import { getTriggerDisplayModelLabel, getTriggerDisplayModelName, } from "./providerIconUtils"; -import type { ProviderInstanceEntry } from "../../providerInstances"; +import { shouldShowInstanceBadge, type ProviderInstanceEntry } from "../../providerInstances"; import { ComposerControl, ComposerControlChevron } from "./ComposerControl"; export const ProviderModelPicker = memo(function ProviderModelPicker(props: { @@ -67,10 +67,8 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { selectedInstanceOptions[0]; const triggerTitle = selectedModel ? getTriggerDisplayModelName(selectedModel) : props.model; const triggerLabel = selectedModel ? getTriggerDisplayModelLabel(selectedModel) : props.model; - const duplicateDriverCount = props.instanceEntries.filter( - (entry) => activeEntry !== null && entry.driverKind === activeEntry.driverKind, - ).length; - const showInstanceBadge = Boolean(activeEntry?.accentColor) || duplicateDriverCount > 1; + const showInstanceBadge = + activeEntry !== null && shouldShowInstanceBadge(activeEntry, props.instanceEntries); const setIsMenuOpen = (open: boolean) => { props.onOpenChange?.(open); diff --git a/apps/web/src/providerInstances.ts b/apps/web/src/providerInstances.ts index 337e68d44d0af017043d70c972476d6125cb3e57..fd4ca7da92da77fb1c5133e0059bbc58452d6265 100644 --- a/apps/web/src/providerInstances.ts +++ b/apps/web/src/providerInstances.ts @@ -109,6 +109,23 @@ function driverKindLabel(driverKind: ProviderDriverKind): string { return PROVIDER_DISPLAY_NAMES[driverKind] ?? formatProviderDriverKindLabel(driverKind); } +/** + * Whether an instance's icon carries the account badge: accent color set, or + * several instances sharing a driver so the brand glyph alone is ambiguous. + * Shared by the composer trigger, the picker rail, and sidebar rows. + */ +export function shouldShowInstanceBadge( + entry: ProviderInstanceEntry, + entries: Iterable, +): boolean { + if (entry.accentColor) return true; + let sharedDriverCount = 0; + for (const candidate of entries) { + if (candidate.driverKind === entry.driverKind && ++sharedDriverCount > 1) return true; + } + return false; +} + export function normalizeProviderAccentColor(value: string | undefined): string | undefined { const trimmed = value?.trim(); if (!trimmed) return undefined; -- 2.54.0