From 9ad8e64e85458a0154c1cac7bac2e3f082f9c7ac Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Fri, 20 Feb 2026 22:54:03 +0700 Subject: [PATCH] refactor(ui): centralize provider metadata and fallbacks - move logo, label, and device-code copy into provider-config - handle unknown or non-string providers without UI crashes - replace duplicated hardcoded mappings in cliproxy table/logo --- ui/src/components/cliproxy/cliproxy-table.tsx | 13 +- ui/src/components/cliproxy/provider-logo.tsx | 49 ++------ .../components/shared/device-code-dialog.tsx | 27 ++-- ui/src/hooks/use-device-code.ts | 25 ++-- ui/src/lib/provider-config.ts | 116 ++++++++++++++++-- 5 files changed, 145 insertions(+), 85 deletions(-) diff --git a/ui/src/components/cliproxy/cliproxy-table.tsx b/ui/src/components/cliproxy/cliproxy-table.tsx index e3f5996e..15de4b23 100644 --- a/ui/src/components/cliproxy/cliproxy-table.tsx +++ b/ui/src/components/cliproxy/cliproxy-table.tsx @@ -27,21 +27,12 @@ import { MoreHorizontal, Trash2, User, Pencil } from 'lucide-react'; import { useDeleteVariant } from '@/hooks/use-cliproxy'; import { CliproxyEditDialog } from './cliproxy-edit-dialog'; import type { Variant } from '@/lib/api-client'; +import { getProviderDisplayName } from '@/lib/provider-config'; interface CliproxyTableProps { data: Variant[]; } -const providerLabels: Record = { - gemini: 'Google Gemini', - codex: 'OpenAI Codex', - agy: 'Antigravity', - qwen: 'Alibaba Qwen', - iflow: 'iFlow', - kiro: 'Kiro (AWS)', - ghcp: 'GitHub Copilot (OAuth)', -}; - export function CliproxyTable({ data }: CliproxyTableProps) { const deleteMutation = useDeleteVariant(); const [editingVariant, setEditingVariant] = useState(null); @@ -59,7 +50,7 @@ export function CliproxyTable({ data }: CliproxyTableProps) { if (row.original.type === 'composite') { return composite; } - return providerLabels[row.original.provider] || row.original.provider; + return getProviderDisplayName(row.original.provider); }, }, { diff --git a/ui/src/components/cliproxy/provider-logo.tsx b/ui/src/components/cliproxy/provider-logo.tsx index e2b8528b..14c35b7b 100644 --- a/ui/src/components/cliproxy/provider-logo.tsx +++ b/ui/src/components/cliproxy/provider-logo.tsx @@ -4,6 +4,11 @@ */ import { cn } from '@/lib/utils'; +import { + getProviderFallbackVisual, + getProviderLogoAsset, + providerNeedsDarkLogoBackground, +} from '@/lib/provider-config'; interface ProviderLogoProps { provider: string; @@ -11,35 +16,6 @@ interface ProviderLogoProps { size?: 'sm' | 'md' | 'lg'; } -/** Provider image assets mapping */ -const PROVIDER_IMAGES: Record = { - gemini: '/assets/providers/gemini-color.svg', - codex: '/assets/providers/openai.svg', - agy: '/assets/providers/agy.png', - qwen: '/assets/providers/qwen-color.svg', - iflow: '/assets/providers/iflow.png', - kiro: '/assets/providers/kiro.png', - ghcp: '/assets/providers/copilot.svg', - claude: '/assets/providers/claude.svg', - kimi: '/assets/providers/kimi.svg', -}; - -/** Provider color configuration (for fallback only - no background for image logos) */ -const PROVIDER_CONFIG: Record = { - gemini: { text: 'text-blue-600', letter: 'G' }, - claude: { text: 'text-orange-600', letter: 'C' }, - codex: { text: 'text-emerald-600', letter: 'X' }, - agy: { text: 'text-violet-600', letter: 'A' }, - qwen: { text: 'text-cyan-600', letter: 'Q' }, - iflow: { text: 'text-indigo-600', letter: 'i' }, - kiro: { text: 'text-teal-600', letter: 'K' }, - ghcp: { text: 'text-green-600', letter: 'C' }, - kimi: { text: 'text-orange-500', letter: 'K' }, -}; - -/** Providers whose logos require a dark background */ -const DARK_BG_PROVIDERS = new Set(['kimi']); - /** Size configuration */ const SIZE_CONFIG = { sm: { container: 'w-6 h-6', icon: 'w-4 h-4', text: 'text-xs' }, @@ -48,19 +24,16 @@ const SIZE_CONFIG = { }; export function ProviderLogo({ provider, className, size = 'md' }: ProviderLogoProps) { - const providerKey = provider.toLowerCase(); - const config = PROVIDER_CONFIG[providerKey] || { - text: 'text-gray-600', - letter: provider[0]?.toUpperCase() || '?', - }; + const fallback = getProviderFallbackVisual(provider); const sizeConfig = SIZE_CONFIG[size]; - const imageSrc = PROVIDER_IMAGES[providerKey]; + const imageSrc = getProviderLogoAsset(provider); return (
) : ( - {config.letter} + + {fallback.letter} + )}
); diff --git a/ui/src/components/shared/device-code-dialog.tsx b/ui/src/components/shared/device-code-dialog.tsx index 5f21cd50..809f4a43 100644 --- a/ui/src/components/shared/device-code-dialog.tsx +++ b/ui/src/components/shared/device-code-dialog.tsx @@ -17,6 +17,10 @@ import { import { Button } from '@/components/ui/button'; import { ExternalLink, Copy, Check, Loader2, KeyRound } from 'lucide-react'; import { toast } from 'sonner'; +import { + getDeviceCodeProviderDisplayName, + getDeviceCodeProviderInstruction, +} from '@/lib/provider-config'; interface DeviceCodeDialogProps { open: boolean; @@ -28,18 +32,6 @@ interface DeviceCodeDialogProps { expiresAt: number; } -/** Provider display names */ -const PROVIDER_DISPLAY_NAMES: Record = { - ghcp: 'GitHub Copilot', - qwen: 'Qwen Code', -}; - -/** Provider specific instructions */ -const PROVIDER_INSTRUCTIONS: Record = { - ghcp: 'Sign in with your GitHub account that has Copilot access.', - qwen: 'Sign in with your Qwen account to authorize access.', -}; - export function DeviceCodeDialog({ open, onClose, @@ -91,9 +83,12 @@ export function DeviceCodeDialog({ window.open(verificationUrl, '_blank', 'noopener,noreferrer'); }, [verificationUrl]); - const providerDisplay = PROVIDER_DISPLAY_NAMES[provider] || provider; - const instructions = - PROVIDER_INSTRUCTIONS[provider] || 'Complete the authorization in your browser.'; + const providerDisplay = getDeviceCodeProviderDisplayName(provider); + const instructions = getDeviceCodeProviderInstruction(provider); + const openActionLabel = + providerDisplay === 'Unknown provider' + ? 'Open verification page' + : `Open ${providerDisplay.split(' ')[0]}`; // Format remaining time const formatTime = (seconds: number): string => { @@ -155,7 +150,7 @@ export function DeviceCodeDialog({