From 4d7912408946f5c9d14fe942138f4b6f69a66d40 Mon Sep 17 00:00:00 2001 From: AlphaCat Date: Wed, 2 Sep 2026 20:38:04 +0800 Subject: [PATCH] feat(sidebar): show last-used provider icon on recent conversation rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add shared ProviderBrandIcon component with isKnownProviderId guard (legacy rows persist "", optimistic rows may carry "pending" or a custom provider instance id — only enum hits render an icon) - Render the icon left of the row title, hidden in selection mode; native title tooltip shows the last-used model name - Compare providerId/model in areRenderedHistoryItemsEqual so rows re-render after a new turn persists a different model - Converge two byte-identical private ProviderBrandIcon copies (ComposerModelControls, modelPicker) onto the shared component --- .../src/components/ProviderBrandIcon.tsx | 36 +++++++++++++++++++ .../chat/ChatHistorySidebarRows.tsx | 12 +++++++ .../components/chat/ComposerModelControls.tsx | 15 +------- .../pages/settings/ProviderPresentation.tsx | 2 ++ .../src/pages/settings/modelPicker.tsx | 22 ++---------- 5 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 crates/agent-ui/src/components/ProviderBrandIcon.tsx diff --git a/crates/agent-ui/src/components/ProviderBrandIcon.tsx b/crates/agent-ui/src/components/ProviderBrandIcon.tsx new file mode 100644 index 000000000..3a6255c0d --- /dev/null +++ b/crates/agent-ui/src/components/ProviderBrandIcon.tsx @@ -0,0 +1,36 @@ +import { + ClaudeIcon, + DeepseekIcon, + GeminiIcon, + GrokIcon, + OpenaiChatgptIcon, +} from "@liveagent/ui/components/IconSet"; +import type { ProviderId } from "@liveagent/ui/lib/settings/types"; +import { cn } from "@liveagent/ui/lib/shared/utils"; + +const KNOWN_PROVIDER_IDS: readonly ProviderId[] = [ + "codex", + "claude_code", + "gemini", + "xai", + "deepseek", +]; + +// Sidebar rows carry providerId as a wide string: legacy rows persist "", +// GUI optimistic rows may fall back to "pending", and web optimistic rows +// store a custom provider *instance* id. Only enum hits may reach the icon — +// anything else would render as the OpenAI fallback below. +export function isKnownProviderId(value: string | undefined): value is ProviderId { + return (KNOWN_PROVIDER_IDS as readonly string[]).includes(value ?? ""); +} + +// Unmatched types (including "codex") fall through to the OpenAI icon; callers +// that need "unknown renders nothing" must guard with isKnownProviderId first. +export function ProviderBrandIcon({ type, className }: { type?: ProviderId; className?: string }) { + const cls = cn("h-4 w-4 shrink-0", className); + if (type === "claude_code") return ; + if (type === "gemini") return ; + if (type === "xai") return ; + if (type === "deepseek") return ; + return ; +} diff --git a/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx b/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx index 183af4813..91198c9a1 100644 --- a/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx +++ b/crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx @@ -56,6 +56,7 @@ import { } from "react"; import type { SidebarConversation } from "../../lib/sidebar/types"; import type { WorkspaceProjectGroup } from "../../lib/workspaceProjectTypes"; +import { isKnownProviderId, ProviderBrandIcon } from "../ProviderBrandIcon"; export type WorkspaceProjectRemoveOptions = { deleteWorktree?: boolean; @@ -126,6 +127,8 @@ function areRenderedHistoryItemsEqual(previous: SidebarConversation, next: Sideb return ( previous.id === next.id && previous.title === next.title && + previous.providerId === next.providerId && + previous.model === next.model && previous.cwd === next.cwd && previous.isPinned === next.isPinned && previous.isShared === next.isShared && @@ -700,6 +703,15 @@ export const HistoryRow = memo(function HistoryRow(props: HistoryRowProps) { {isSelected ? : null} ) : null} + {!isSelectionMode && isKnownProviderId(item.providerId) ? ( + + ) : null} {item.title} diff --git a/crates/agent-ui/src/components/chat/ComposerModelControls.tsx b/crates/agent-ui/src/components/chat/ComposerModelControls.tsx index f4c2d45c1..c57a376b8 100644 --- a/crates/agent-ui/src/components/chat/ComposerModelControls.tsx +++ b/crates/agent-ui/src/components/chat/ComposerModelControls.tsx @@ -12,20 +12,16 @@ import { ArrowDownAZ, Check, ChevronDown, - ClaudeIcon, - DeepseekIcon, - GeminiIcon, Globe, GlobeOff, - GrokIcon, Layers, Lightbulb, LightbulbOff, - OpenaiChatgptIcon, Pencil, Search, Sparkle, } from "@liveagent/ui/components/IconSet"; +import { ProviderBrandIcon } from "@liveagent/ui/components/ProviderBrandIcon"; import { Button } from "@liveagent/ui/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@liveagent/ui/components/ui/popover"; import { useLocale } from "@liveagent/ui/i18n/index"; @@ -66,15 +62,6 @@ const REASONING_COMPACT_I18N_KEYS: Record = { max: "chat.runtime.reasoningCompact.max", }; -function ProviderBrandIcon({ type, className }: { type: ProviderId; className?: string }) { - const cls = cn("h-4 w-4 shrink-0", className); - if (type === "claude_code") return ; - if (type === "gemini") return ; - if (type === "xai") return ; - if (type === "deepseek") return ; - return ; -} - function RuntimeToggleChip(props: { pressed: boolean; disabled?: boolean; diff --git a/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx b/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx index b28a11d0a..8d41f2e46 100644 --- a/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx +++ b/crates/agent-ui/src/pages/settings/ProviderPresentation.tsx @@ -118,6 +118,8 @@ export function getProviderLabel(type: ProviderId) { return PROVIDER_LABELS[type]; } +// TODO: converge with components/ProviderBrandIcon.tsx — this variant keeps the +// settings-page sizing contract (height="1em" scales with surrounding text). export function ProviderBrandIcon({ type }: { type: ProviderId }) { if (type === "claude_code") return ; if (type === "gemini") return ; diff --git a/crates/agent-ui/src/pages/settings/modelPicker.tsx b/crates/agent-ui/src/pages/settings/modelPicker.tsx index 0b3331325..936ad979a 100644 --- a/crates/agent-ui/src/pages/settings/modelPicker.tsx +++ b/crates/agent-ui/src/pages/settings/modelPicker.tsx @@ -1,15 +1,6 @@ import type { ProviderId } from "@liveagent/app/lib/settings/index"; -import { - Check, - ChevronDown, - ClaudeIcon, - DeepseekIcon, - GeminiIcon, - GrokIcon, - OpenaiChatgptIcon, - Search, - Sparkles, -} from "@liveagent/ui/components/IconSet"; +import { Check, ChevronDown, Search, Sparkles } from "@liveagent/ui/components/IconSet"; +import { ProviderBrandIcon } from "@liveagent/ui/components/ProviderBrandIcon"; import { DropdownMenu, DropdownMenuContent, @@ -34,15 +25,6 @@ export type ModelPickerOption = { providerType?: ProviderId; }; -function ProviderBrandIcon({ type, className }: { type?: ProviderId; className?: string }) { - const cls = cn("h-4 w-4 shrink-0", className); - if (type === "claude_code") return ; - if (type === "gemini") return ; - if (type === "xai") return ; - if (type === "deepseek") return ; - return ; -} - type ModelGroup = { id: string; name: string;