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
25 changes: 10 additions & 15 deletions apps/web/src/components/ProviderUpdatePrimaryNotification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useNavigate } from "@tanstack/react-router";
import { useAtomValue } from "@effect/atom-react";
import { DownloadIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { type ProviderDriverKind, type ProviderInstanceId } from "@t3tools/contracts";
import { type ServerProvider, type ProviderInstanceId } from "@t3tools/contracts";

import { primaryServerProvidersAtom, serverEnvironment } from "../state/server";
import { usePrimaryEnvironment } from "../state/environments";
import { useDismissedProviderUpdateNotificationKeys } from "../providerUpdateDismissal";
import { PROVIDER_ICON_BY_PROVIDER } from "./chat/providerIconUtils";
import { ProviderInstanceIcon } from "./chat/ProviderInstanceIcon";
import {
canOneClickUpdateProviderCandidate,
collectProviderUpdateCandidates,
Expand Down Expand Up @@ -35,20 +35,15 @@ type ActiveProviderUpdateToast =
readonly providerCount: number;
};

function ProviderUpdateToastIcon({ provider }: { provider: ProviderDriverKind }) {
const ProviderIcon = PROVIDER_ICON_BY_PROVIDER[provider];

if (!ProviderIcon) {
return (
<span className="relative inline-flex size-4 shrink-0 items-center justify-center">
<DownloadIcon aria-hidden="true" className="size-4 text-success" strokeWidth={2.5} />
</span>
);
}

function ProviderUpdateToastIcon({ provider }: { provider: ServerProvider }) {
return (
<span className="relative inline-flex size-4 shrink-0 items-center justify-center">
<ProviderIcon aria-hidden="true" className="size-4" />
<ProviderInstanceIcon
driverKind={provider.driver}
displayName={provider.displayName ?? provider.driver}
acpRegistryIconUrl={provider.iconUrl}
iconClassName="size-4"
/>
<span className="absolute -right-1 -bottom-1 inline-flex size-3 items-center justify-center rounded-full bg-popover">
<DownloadIcon aria-hidden="true" className="size-2.5 text-success" strokeWidth={2.5} />
</span>
Expand Down Expand Up @@ -285,7 +280,7 @@ export function ProviderUpdatePrimaryNotification() {
data: {
leadingIcon:
updateProviders.length === 1 ? (
<ProviderUpdateToastIcon provider={updateProviders[0]!.driver} />
<ProviderUpdateToastIcon provider={updateProviders[0]!} />
) : undefined,
hideCopyButton: true,
onClose: dismissPrompt,
Expand Down
22 changes: 8 additions & 14 deletions apps/web/src/components/chat/ModelListRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
getDisplayModelName,
getTriggerDisplayModelLabel,
type ModelEsque,
PROVIDER_ICON_BY_PROVIDER,
} from "./providerIconUtils";
import { ComboboxItem } from "../ui/combobox";
import { Button } from "../ui/button";
Expand Down Expand Up @@ -43,7 +42,6 @@ export const ModelListRow = memo(function ModelListRow(props: {
disabledReason?: string | null;
onToggleFavorite: () => void;
}) {
const ProviderIcon = PROVIDER_ICON_BY_PROVIDER[props.driverKind] ?? null;
const providerLabel = props.model.subProvider
? `${props.providerDisplayName} · ${props.model.subProvider}`
: props.providerDisplayName;
Expand Down Expand Up @@ -88,18 +86,14 @@ export const ModelListRow = memo(function ModelListRow(props: {
</div>
{props.showProvider && (
<div className="mt-1 flex items-center gap-1.5">
{props.driverKind === "acpRegistry" ? (
<ProviderInstanceIcon
driverKind={props.driverKind}
displayName={props.providerDisplayName}
acpRegistryAgentId={props.acpRegistryAgentId}
acpRegistryIconUrl={props.acpRegistryIconUrl}
className="size-3"
iconClassName="size-3"
/>
) : ProviderIcon ? (
<ProviderIcon className="size-3 shrink-0" />
) : null}
<ProviderInstanceIcon
driverKind={props.driverKind}
displayName={props.providerDisplayName}
acpRegistryAgentId={props.acpRegistryAgentId}
acpRegistryIconUrl={props.acpRegistryIconUrl}
className="size-3"
iconClassName="size-3"
/>
<span className="truncate text-xs font-normal leading-snug text-muted-foreground/70">
{providerLabel}
</span>
Expand Down
27 changes: 23 additions & 4 deletions apps/web/src/components/chat/ProviderInstanceIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
import { type CSSProperties, memo } from "react";
import { type ProviderDriverKind } from "@t3tools/contracts";

import { providerInstanceInitials } from "@t3tools/client-runtime/state/provider-instance-display";

import { PROVIDER_ICON_BY_PROVIDER } from "./providerIconUtils";
import { ProviderDriverKind } from "@t3tools/contracts";
import {
AntigravityIcon,
ClaudeAI,
CursorIcon,
GrokIcon,
Icon,
OpenAI,
OpenCodeIcon,
PiAgentIcon,
} from "../Icons";

import { cn } from "~/lib/utils";
import {
AcpRegistryAgentIcon,
officialAcpRegistryIconUrlForAgentId,
resolveOfficialAcpRegistryIconUrl,
} from "../settings/AcpRegistryIcon";

const PROVIDER_ICON_BY_PROVIDER: Partial<Record<ProviderDriverKind, Icon>> = {
[ProviderDriverKind.make("codex")]: OpenAI,
[ProviderDriverKind.make("claudeAgent")]: ClaudeAI,
[ProviderDriverKind.make("opencode")]: OpenCodeIcon,
[ProviderDriverKind.make("cursor")]: CursorIcon,
[ProviderDriverKind.make("grok")]: GrokIcon,
[ProviderDriverKind.make("antigravity")]: AntigravityIcon,
[ProviderDriverKind.make("pi")]: PiAgentIcon,
};

export function resolveProviderInstanceAcpRegistryIconUrl(input: {
readonly driverKind: ProviderDriverKind;
readonly agentId?: string | undefined;
Expand All @@ -22,8 +43,6 @@ export function resolveProviderInstanceAcpRegistryIconUrl(input: {
);
}

export { providerInstanceInitials };

export const ProviderInstanceIcon = memo(function ProviderInstanceIcon(props: {
driverKind: ProviderDriverKind;
displayName: string;
Expand Down
28 changes: 16 additions & 12 deletions apps/web/src/components/chat/V2LifecycleRow.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Fragment } from "react";
import type {
OrchestrationV2Run,
OrchestrationV2TurnItem,
ProviderInstanceId,
ServerProvider,
ThreadId,
import {
ProviderDriverKind,
type OrchestrationV2Run,
type OrchestrationV2TurnItem,
type ProviderInstanceId,
type ServerProvider,
type ThreadId,
} from "@t3tools/contracts";
import type { TimestampFormat } from "@t3tools/contracts/settings";
import {
Expand All @@ -21,7 +22,8 @@ import { Tooltip, TooltipTrigger, TooltipPopup } from "../ui/tooltip";
import { cn } from "../../lib/utils";
import { getProviderInstanceEntry } from "../../providerInstances";
import { formatShortTimestamp } from "../../timestampFormat";
import { PROVIDER_ICON_BY_PROVIDER, getTriggerDisplayModelName } from "./providerIconUtils";
import { getTriggerDisplayModelName } from "./providerIconUtils";
import { ProviderInstanceIcon } from "./ProviderInstanceIcon";
import { TimelineSystemDivider } from "./TimelineSystemDivider";
import { T3Wordmark } from "../T3Wordmark";

Expand Down Expand Up @@ -345,10 +347,6 @@ function HandoffEndpoint(props: {
readonly model?: string | undefined;
}) {
const entry = getProviderInstanceEntry(props.providers, props.instanceId);
const Icon =
Object.entries(PROVIDER_ICON_BY_PROVIDER).find(
([driver]) => driver === (entry?.driverKind ?? props.instanceId),
)?.[1] ?? BotIcon;
const model = props.model?.trim();
const providerModel =
model === undefined || model.length === 0
Expand All @@ -370,7 +368,13 @@ function HandoffEndpoint(props: {
aria-label={label}
className="inline-flex shrink-0 items-center justify-center rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<Icon aria-hidden="true" className="size-3 shrink-0" />
<ProviderInstanceIcon
driverKind={entry?.driverKind ?? ProviderDriverKind.make(props.instanceId)}
displayName={entry?.displayName ?? props.instanceId}
acpRegistryAgentId={entry?.acpRegistryAgentId}
acpRegistryIconUrl={entry?.acpRegistryIconUrl}
iconClassName="size-3"
/>
</span>
}
/>
Expand Down
22 changes: 0 additions & 22 deletions apps/web/src/components/chat/providerIconUtils.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
import { ProviderDriverKind } from "@t3tools/contracts";
import {
AntigravityIcon,
ClaudeAI,
CursorIcon,
GrokIcon,
Icon,
OpenAI,
OpenCodeIcon,
PiAgentIcon,
} from "../Icons";

export const PROVIDER_ICON_BY_PROVIDER: Partial<Record<ProviderDriverKind, Icon>> = {
[ProviderDriverKind.make("codex")]: OpenAI,
[ProviderDriverKind.make("claudeAgent")]: ClaudeAI,
[ProviderDriverKind.make("opencode")]: OpenCodeIcon,
[ProviderDriverKind.make("cursor")]: CursorIcon,
[ProviderDriverKind.make("grok")]: GrokIcon,
[ProviderDriverKind.make("antigravity")]: AntigravityIcon,
[ProviderDriverKind.make("pi")]: PiAgentIcon,
};

export type ModelEsque = {
slug: string;
name: string;
Expand Down
29 changes: 22 additions & 7 deletions apps/web/src/components/onboarding/WelcomeWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { getProviderSummary } from "../settings/providerStatus";
import { getDriverOption } from "../settings/providerDriverMeta";
import { TerminalViewport } from "../ThreadTerminalDrawer";
import { CloudEnvironmentConnectRows } from "../cloud/CloudEnvironmentConnectList";
import { ClaudeAI, OpenAI } from "../Icons";
import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";
import { T3Wordmark } from "../T3Wordmark";
import { Button } from "../ui/button";
import { Checkbox } from "../ui/checkbox";
Expand Down Expand Up @@ -751,16 +751,17 @@ function AgentCard({
readonly onOpenTerminal: () => void;
}) {
const meta = getDriverOption(ProviderDriverKind.make(driver));
const Icon = meta?.icon;
const displayName = driver === "claudeAgent" ? "Claude Code" : (meta?.label ?? driver);
const summary = getProviderSummary(provider);
const providerState = getOnboardingProviderState(provider);

return (
<div className="flex items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5">
{Icon ? (
<Icon className={cn("size-5 shrink-0", driver !== "claudeAgent" && "fill-foreground")} />
) : null}
<ProviderInstanceIcon
driverKind={ProviderDriverKind.make(driver)}
displayName={displayName}
iconClassName="size-5"
/>
<div className="min-w-0 flex-1">
<span className="block text-sm font-medium text-foreground">{displayName}</span>
<p className="mt-0.5 text-xs leading-relaxed break-words whitespace-pre-wrap text-muted-foreground">
Expand Down Expand Up @@ -1491,11 +1492,25 @@ function ImportRowMeta({
<span className="ml-auto grid shrink-0 grid-cols-[1rem_1rem_2.5rem_2.25rem] items-center gap-x-1 text-xs text-muted-foreground tabular-nums">
<span className="flex size-4 items-center justify-center">
{sources?.includes("claudeAgent") ? (
<ClaudeAI className="size-3" aria-label="Claude Code" />
<span role="img" aria-label="Claude Code">
<ProviderInstanceIcon
driverKind={ProviderDriverKind.make("claudeAgent")}
displayName="Claude Code"
iconClassName="size-3"
/>
</span>
) : null}
</span>
<span className="flex size-4 items-center justify-center">
{sources?.includes("codex") ? <OpenAI className="size-3" aria-label="Codex" /> : null}
{sources?.includes("codex") ? (
<span role="img" aria-label="Codex">
<ProviderInstanceIcon
driverKind={ProviderDriverKind.make("codex")}
displayName="Codex"
iconClassName="size-3"
/>
</span>
) : null}
</span>
<span className="text-right">{threadCount}</span>
<span className="text-right whitespace-nowrap">{age}</span>
Expand Down
11 changes: 9 additions & 2 deletions apps/web/src/components/settings/AcpRegistrySearchStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group"
import { ScrollArea } from "../ui/scroll-area";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { isConfiguredAcpRegistryAgent } from "./AddProviderInstanceDialog.logic";
import { AcpRegistryAgentIcon } from "./AcpRegistryIcon";
import { ProviderDriverKind } from "@t3tools/contracts";
import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";

const SUGGESTED_SEARCHES = ["Codex", "Copilot", "Kimi"] as const;
function errorMessage(error: unknown): string {
Expand Down Expand Up @@ -240,7 +241,13 @@ export function AcpRegistrySearchStep({
<article className="grid min-w-0 gap-2 py-3 first:pt-2 last:pb-2" key={agent.id}>
<div className="flex min-w-0 items-start justify-between gap-3">
<div className="flex min-w-0 flex-1 items-start gap-3">
<AcpRegistryAgentIcon icon={agent.icon} />
<ProviderInstanceIcon
driverKind={ProviderDriverKind.make("acpRegistry")}
displayName={agent.name}
acpRegistryAgentId={agent.id}
acpRegistryIconUrl={agent.icon ?? undefined}
iconClassName="size-8 rounded-lg bg-muted text-muted-foreground"
/>
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-baseline gap-2">
<h4 className="min-w-0 truncate text-sm font-medium text-foreground">
Expand Down
11 changes: 8 additions & 3 deletions apps/web/src/components/settings/AddProviderInstanceDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";

import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";

import { Radio as RadioPrimitive } from "@base-ui/react/radio";
import { CheckIcon } from "lucide-react";
import { useMemo, useState } from "react";
Expand All @@ -19,7 +21,7 @@ import {
import { cn } from "../../lib/utils";
import { normalizeProviderAccentColor } from "../../providerInstances";
import { Button } from "../ui/button";
import { Gemini, GithubCopilotIcon, PiAgentIcon, type Icon } from "../Icons";
import { Gemini, GithubCopilotIcon, type Icon } from "../Icons";
import { Dialog } from "../ui/dialog";
import { Badge } from "../ui/badge";
import { Input } from "../ui/input";
Expand Down Expand Up @@ -357,14 +359,17 @@ export function AddProviderInstanceDialog({
className="grid grid-cols-1 gap-2 sm:grid-cols-2"
>
{DRIVER_OPTIONS.map((option) => {
const IconComponent = option.icon;
return (
<RadioPrimitive.Root
key={option.value}
value={option.value}
className="relative flex cursor-pointer items-center gap-3 rounded-lg bg-card px-3 py-3 text-left text-muted-foreground outline-none ring-1 ring-black/5 hover:bg-zinc-50 focus-visible:ring-2 focus-visible:ring-ring data-checked:bg-primary/8 data-checked:text-foreground data-checked:ring-2 data-checked:ring-primary data-checked:hover:bg-primary/8 dark:bg-white/3 dark:ring-white/5 dark:hover:bg-white/5 dark:data-checked:bg-primary/15 dark:data-checked:ring-primary dark:data-checked:hover:bg-primary/15"
>
<IconComponent className="size-4 shrink-0" aria-hidden />
<ProviderInstanceIcon
driverKind={option.value}
displayName={option.label}
iconClassName="size-4"
/>
<span className="min-w-0 flex-1 truncate text-sm font-medium text-foreground">
{option.label}
</span>
Expand Down
18 changes: 3 additions & 15 deletions apps/web/src/components/settings/ProviderInstanceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import type { DriverOption, ProviderEnvironmentFieldDefinition } from "./providerDriverMeta";
import { ProviderSettingsForm } from "./ProviderSettingsForm";
import { ProviderModelsSection } from "./ProviderModelsSection";
import { ProviderInstanceIcon, providerInstanceInitials } from "../chat/ProviderInstanceIcon";
import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";
import { ProviderAccentColorPicker } from "./ProviderAccentColorPicker";
import { RedactedSensitiveText } from "./RedactedSensitiveText";
import { SettingsRow, SettingsSection } from "./settingsLayout";
Expand Down Expand Up @@ -565,7 +565,6 @@ export function ProviderInstanceCard({
const versionAdvisory = getProviderVersionAdvisoryPresentation(liveProvider?.versionAdvisory);
const urlAuthAction = liveProvider?.auth.action;
const updateCommand = versionAdvisory?.updateCommand ?? null;
const FallbackIconComponent = driverOption?.icon;
const displayName =
instance.displayName?.trim() || driverOption?.label || String(instance.driver);
const accentColor = normalizeProviderAccentColor(instance.accentColor);
Expand Down Expand Up @@ -679,9 +678,9 @@ export function ProviderInstanceCard({
updateEnvironment(providerEnvironmentWithoutNames(instance.environment, new Set([field.name])));
};

const titleIconNode = driverKind ? (
const titleIconNode = (
<ProviderInstanceIcon
driverKind={driverKind}
driverKind={driverKind ?? instance.driver}
displayName={displayName}
accentColor={accentColor}
acpRegistryAgentId={readConfigString(instance.config, "agentId") ?? undefined}
Expand All @@ -691,17 +690,6 @@ export function ProviderInstanceCard({
iconClassName="size-4 text-foreground/80"
badgeClassName="right-[-0.125rem] bottom-[-0.125rem] h-3 min-w-3 px-0.5 text-[7px]"
/>
) : FallbackIconComponent ? (
<span className="inline-flex size-5 shrink-0 items-center justify-center">
<FallbackIconComponent className="size-4 text-foreground/80" aria-hidden />
</span>
) : (
<span
className="inline-flex size-5 shrink-0 items-center justify-center text-[10px] font-semibold leading-none text-foreground/80"
aria-hidden
>
{providerInstanceInitials(displayName)}
</span>
);

const titleTailNode = headerAction ? (
Expand Down
Loading
Loading