Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions crates/agent-ui/src/components/ProviderBrandIcon.tsx
Original file line number Diff line number Diff line change
@@ -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 <ClaudeIcon className={cls} />;
if (type === "gemini") return <GeminiIcon className={cls} />;
if (type === "xai") return <GrokIcon className={cls} />;
if (type === "deepseek") return <DeepseekIcon className={cls} />;
return <OpenaiChatgptIcon className={cn(cls, "fill-current dark:text-white")} />;
}
12 changes: 12 additions & 0 deletions crates/agent-ui/src/components/chat/ChatHistorySidebarRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -700,6 +703,15 @@ export const HistoryRow = memo(function HistoryRow(props: HistoryRowProps) {
{isSelected ? <Check className="h-3 w-3" /> : null}
</span>
) : null}
{!isSelectionMode && isKnownProviderId(item.providerId) ? (
<span
aria-hidden="true"
title={item.model || undefined}
className="flex h-4 w-4 shrink-0 items-center justify-center"
>
<ProviderBrandIcon type={item.providerId} />
</span>
) : null}
<span className="sidebar-project-name-fade min-w-0 flex-1 overflow-hidden whitespace-nowrap text-[calc(14px*var(--zone-font-scale,1))] font-normal leading-5">
{item.title}
</span>
Expand Down
15 changes: 1 addition & 14 deletions crates/agent-ui/src/components/chat/ComposerModelControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -66,15 +62,6 @@ const REASONING_COMPACT_I18N_KEYS: Record<ReasoningLevel, string> = {
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 <ClaudeIcon className={cls} />;
if (type === "gemini") return <GeminiIcon className={cls} />;
if (type === "xai") return <GrokIcon className={cls} />;
if (type === "deepseek") return <DeepseekIcon className={cls} />;
return <OpenaiChatgptIcon className={cn(cls, "fill-current dark:text-white")} />;
}

function RuntimeToggleChip(props: {
pressed: boolean;
disabled?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions crates/agent-ui/src/pages/settings/ProviderPresentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ClaudeIcon height="1em" />;
if (type === "gemini") return <GeminiIcon height="1em" />;
Expand Down
22 changes: 2 additions & 20 deletions crates/agent-ui/src/pages/settings/modelPicker.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 <ClaudeIcon className={cls} />;
if (type === "gemini") return <GeminiIcon className={cls} />;
if (type === "xai") return <GrokIcon className={cls} />;
if (type === "deepseek") return <DeepseekIcon className={cls} />;
return <OpenaiChatgptIcon className={cn(cls, "fill-current dark:text-white")} />;
}

type ModelGroup = {
id: string;
name: string;
Expand Down
Loading