diff --git a/web/.prettierrc.json b/web/.prettierrc.json index 77bffdbb..533feb9a 100644 --- a/web/.prettierrc.json +++ b/web/.prettierrc.json @@ -1,10 +1,14 @@ { - "semi": false, + "semi": true, "singleQuote": true, "tabWidth": 2, "useTabs": false, - "trailingComma": "es5", - "printWidth": 80, - "arrowParens": "avoid", - "endOfLine": "lf" + "trailingComma": "all", + "printWidth": 100, + "arrowParens": "always", + "endOfLine": "lf", + "bracketSpacing": true, + "jsxSingleQuote": false, + "proseWrap": "preserve", + "bracketSameLine": false } diff --git a/web/src/components/cooldown-details-dialog.tsx b/web/src/components/cooldown-details-dialog.tsx index ce498522..f411534c 100644 --- a/web/src/components/cooldown-details-dialog.tsx +++ b/web/src/components/cooldown-details-dialog.tsx @@ -1,10 +1,7 @@ -import { useEffect, useState, useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import type { TFunction } from 'i18next' -import { - Dialog, - DialogContent, -} from '@/components/ui/dialog' +import { useEffect, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { TFunction } from 'i18next'; +import { Dialog, DialogContent } from '@/components/ui/dialog'; import { Snowflake, Clock, @@ -18,32 +15,38 @@ import { Thermometer, Calendar, Activity, -} from 'lucide-react' -import type { Cooldown } from '@/lib/transport/types' -import { useCooldowns } from '@/hooks/use-cooldowns' +} from 'lucide-react'; +import type { Cooldown } from '@/lib/transport/types'; +import { useCooldowns } from '@/hooks/use-cooldowns'; interface CooldownDetailsDialogProps { - cooldown: Cooldown | null - open: boolean - onOpenChange: (open: boolean) => void - onClear: () => void - isClearing: boolean - onDisable: () => void - isDisabling: boolean + cooldown: Cooldown | null; + open: boolean; + onOpenChange: (open: boolean) => void; + onClear: () => void; + isClearing: boolean; + onDisable: () => void; + isDisabling: boolean; } // Reason 信息和图标 - 使用翻译 const getReasonInfo = (t: TFunction) => ({ server_error: { label: t('provider.reasons.serverError'), - description: t('provider.reasons.serverErrorDesc', '上游服务器返回 5xx 错误,系统自动进入冷却保护'), + description: t( + 'provider.reasons.serverErrorDesc', + '上游服务器返回 5xx 错误,系统自动进入冷却保护', + ), icon: Server, color: 'text-red-400', bgColor: 'bg-red-400/10 border-red-400/20', }, network_error: { label: t('provider.reasons.networkError'), - description: t('provider.reasons.networkErrorDesc', '无法连接到上游服务器,可能是网络故障或服务器宕机'), + description: t( + 'provider.reasons.networkErrorDesc', + '无法连接到上游服务器,可能是网络故障或服务器宕机', + ), icon: Wifi, color: 'text-amber-400', bgColor: 'bg-amber-400/10 border-amber-400/20', @@ -76,7 +79,7 @@ const getReasonInfo = (t: TFunction) => ({ color: 'text-muted-foreground', bgColor: 'bg-muted border-border', }, -}) +}); export function CooldownDetailsDialog({ cooldown, @@ -87,38 +90,38 @@ export function CooldownDetailsDialog({ onDisable, isDisabling, }: CooldownDetailsDialogProps) { - const { t, i18n } = useTranslation() - const REASON_INFO = getReasonInfo(t) + const { t, i18n } = useTranslation(); + const REASON_INFO = getReasonInfo(t); // 获取 formatRemaining 函数用于实时倒计时 - const { formatRemaining } = useCooldowns() + const { formatRemaining } = useCooldowns(); // 计算初始倒计时值 const getInitialCountdown = useCallback(() => { - return cooldown ? formatRemaining(cooldown) : '' - }, [cooldown, formatRemaining]) + return cooldown ? formatRemaining(cooldown) : ''; + }, [cooldown, formatRemaining]); // 实时倒计时状态 - const [liveCountdown, setLiveCountdown] = useState(getInitialCountdown) + const [liveCountdown, setLiveCountdown] = useState(getInitialCountdown); // 每秒更新倒计时 useEffect(() => { - if (!cooldown) return + if (!cooldown) return; // 每秒更新 const interval = setInterval(() => { - setLiveCountdown(formatRemaining(cooldown)) - }, 1000) + setLiveCountdown(formatRemaining(cooldown)); + }, 1000); - return () => clearInterval(interval) - }, [cooldown, formatRemaining]) + return () => clearInterval(interval); + }, [cooldown, formatRemaining]); - if (!cooldown) return null + if (!cooldown) return null; - const reasonInfo = REASON_INFO[cooldown.reason] || REASON_INFO.unknown - const Icon = reasonInfo.icon + const reasonInfo = REASON_INFO[cooldown.reason] || REASON_INFO.unknown; + const Icon = reasonInfo.icon; const formatUntilTime = (until: string) => { - const date = new Date(until) + const date = new Date(until); return date.toLocaleString(i18n.resolvedLanguage ?? i18n.language, { month: '2-digit', day: '2-digit', @@ -126,11 +129,11 @@ export function CooldownDetailsDialog({ minute: '2-digit', second: '2-digit', hour12: false, - }) - } + }); + }; - const untilDateStr = formatUntilTime(cooldown.untilTime) - const [datePart, timePart] = untilDateStr.split(' ') + const untilDateStr = formatUntilTime(cooldown.untilTime); + const [datePart, timePart] = untilDateStr.split(' '); return ( @@ -149,15 +152,10 @@ export function CooldownDetailsDialog({
- +
-

- {t('cooldown.title')} -

+

{t('cooldown.title')}

Frozen Protocol Active

@@ -193,9 +191,7 @@ export function CooldownDetailsDialog({
-

- {reasonInfo.label} -

+

{reasonInfo.label}

{reasonInfo.description}

@@ -210,9 +206,7 @@ export function CooldownDetailsDialog({
- - Remaining - + Remaining
{liveCountdown} @@ -224,18 +218,14 @@ export function CooldownDetailsDialog({ Resume -
- {timePart} -
+
{timePart}
Date -
- {datePart} -
+
{datePart}
@@ -251,9 +241,7 @@ export function CooldownDetailsDialog({ {isClearing ? ( <>
- - Thawing... - + Thawing... ) : ( <> @@ -290,5 +278,5 @@ export function CooldownDetailsDialog({
- ) + ); } diff --git a/web/src/components/force-project-dialog.tsx b/web/src/components/force-project-dialog.tsx index ef0a1cae..c92b818e 100644 --- a/web/src/components/force-project-dialog.tsx +++ b/web/src/components/force-project-dialog.tsx @@ -4,10 +4,7 @@ */ import { useEffect, useState } from 'react'; -import { - Dialog, - DialogContent, -} from '@/components/ui/dialog'; +import { Dialog, DialogContent } from '@/components/ui/dialog'; import { FolderOpen, AlertCircle, Loader2, Clock, X } from 'lucide-react'; import { useProjects, useUpdateSessionProject, useRejectSession } from '@/hooks/queries'; import type { NewSessionPendingEvent } from '@/lib/transport/types'; @@ -16,85 +13,81 @@ import { getClientName, getClientColor } from '@/components/icons/client-icons'; import { useTranslation } from 'react-i18next'; interface ForceProjectDialogProps { - event: NewSessionPendingEvent | null - onClose: () => void - timeoutSeconds: number + event: NewSessionPendingEvent | null; + onClose: () => void; + timeoutSeconds: number; } -export function ForceProjectDialog({ - event, - onClose, - timeoutSeconds, -}: ForceProjectDialogProps) { - const { t } = useTranslation() - const { data: projects, isLoading } = useProjects() - const updateSessionProject = useUpdateSessionProject() - const rejectSession = useRejectSession() - const [selectedProjectId, setSelectedProjectId] = useState(0) - const [remainingTime, setRemainingTime] = useState(timeoutSeconds) - const [eventId, setEventId] = useState(null) +export function ForceProjectDialog({ event, onClose, timeoutSeconds }: ForceProjectDialogProps) { + const { t } = useTranslation(); + const { data: projects, isLoading } = useProjects(); + const updateSessionProject = useUpdateSessionProject(); + const rejectSession = useRejectSession(); + const [selectedProjectId, setSelectedProjectId] = useState(0); + const [remainingTime, setRemainingTime] = useState(timeoutSeconds); + const [eventId, setEventId] = useState(null); // Reset state when event changes useEffect(() => { if (event && event.sessionID !== eventId) { - setEventId(event.sessionID) - setSelectedProjectId(0) - setRemainingTime(timeoutSeconds) + setEventId(event.sessionID); + setSelectedProjectId(0); + setRemainingTime(timeoutSeconds); } - }, [event, eventId, timeoutSeconds]) + }, [event, eventId, timeoutSeconds]); // Countdown timer useEffect(() => { - if (!event) return + if (!event) return; const interval = setInterval(() => { - setRemainingTime(prev => { + setRemainingTime((prev) => { if (prev <= 1) { - clearInterval(interval) - return 0 + clearInterval(interval); + return 0; } - return prev - 1 - }) - }, 1000) + return prev - 1; + }); + }, 1000); - return () => clearInterval(interval) - }, [event]) + return () => clearInterval(interval); + }, [event]); // 超时后关闭弹窗 useEffect(() => { if (remainingTime === 0 && event) { - onClose() + onClose(); } - }, [remainingTime, event, onClose]) + }, [remainingTime, event, onClose]); const handleConfirm = async () => { - if (!event || selectedProjectId === 0) return + if (!event || selectedProjectId === 0) return; try { await updateSessionProject.mutateAsync({ sessionID: event.sessionID, projectID: selectedProjectId, - }) - onClose() + }); + onClose(); } catch (error) { - console.error('Failed to bind project:', error) + console.error('Failed to bind project:', error); } - } + }; const handleReject = async () => { - if (!event) return + if (!event) return; try { - await rejectSession.mutateAsync(event.sessionID) - onClose() + await rejectSession.mutateAsync(event.sessionID); + onClose(); } catch (error) { - console.error('Failed to reject session:', error) + console.error('Failed to reject session:', error); } - } + }; - if (!event) return null + if (!event) return null; - const clientColor = getClientColor(event.clientType) + const clientColor = getClientColor(event.clientType); return ( !open && onClose()}> @@ -148,19 +141,19 @@ export function ForceProjectDialog({ 'relative overflow-hidden rounded-xl border p-5 flex flex-col items-center justify-center group', remainingTime <= 10 ? 'bg-linear-to-br from-red-950/30 to-transparent border-red-500/20' - : 'bg-linear-to-br from-amber-950/30 to-transparent border-amber-500/20' + : 'bg-linear-to-br from-amber-950/30 to-transparent border-amber-500/20', )} >
@@ -173,7 +166,7 @@ export function ForceProjectDialog({ 'relative font-mono text-4xl font-bold tracking-widest tabular-nums', remainingTime <= 10 ? 'text-red-400 drop-shadow-[0_0_8px_rgba(248,113,113,0.3)]' - : 'text-amber-400 drop-shadow-[0_0_8px_rgba(251,191,36,0.3)]' + : 'text-amber-400 drop-shadow-[0_0_8px_rgba(251,191,36,0.3)]', )} > {remainingTime}s @@ -192,7 +185,7 @@ export function ForceProjectDialog({ {projects && projects.length > 0 ? (
- {projects.map(project => ( + {projects.map((project) => ( - ) + ); } diff --git a/web/src/components/layout/index.ts b/web/src/components/layout/index.ts index 93ba7284..cf6b938c 100644 --- a/web/src/components/layout/index.ts +++ b/web/src/components/layout/index.ts @@ -1,7 +1,7 @@ export { AppLayout } from './app-layout'; -export { SidebarNav } from './sidebar-nav'; +export { SidebarNav } from './app-sidebar'; export { PageHeader } from './page-header'; -export { NavMain } from './nav-main'; -export { NavRoutes } from './nav-routes'; -export { NavManagement } from './nav-management'; export { NavProxyStatus } from './nav-proxy-status'; +export { SidebarRenderer } from './app-sidebar/sidebar-renderer'; +export { RequestsNavItem } from './app-sidebar/requests-nav-item'; +export { ClientRoutesItems } from './app-sidebar/client-routes-items'; diff --git a/web/src/components/layout/nav-main.tsx b/web/src/components/layout/nav-main.tsx deleted file mode 100644 index 277b40d2..00000000 --- a/web/src/components/layout/nav-main.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { NavLink, useLocation } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import type { LucideIcon } from 'lucide-react' -import { - SidebarGroup, - SidebarGroupContent, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, -} from '@/components/ui/sidebar' - -interface NavItem { - to: string - icon: LucideIcon - labelKey: string -} - -interface NavMainProps { - items: NavItem[] - children?: React.ReactNode -} - -export function NavMain({ items, children }: NavMainProps) { - const location = useLocation() - const { t } = useTranslation() - - return ( - - - - {items.map(item => { - const Icon = item.icon - const isActive = - item.to === '/' - ? location.pathname === '/' - : location.pathname.startsWith(item.to) - - return ( - - } - isActive={isActive} - tooltip={t(item.labelKey)} - > - - {t(item.labelKey)} - - - ) - })} - {children} - - - - ) -} diff --git a/web/src/components/layout/nav-management.tsx b/web/src/components/layout/nav-management.tsx deleted file mode 100644 index eb3f140c..00000000 --- a/web/src/components/layout/nav-management.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { NavLink, useLocation } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import type { LucideIcon } from 'lucide-react' -import { - SidebarGroup, - SidebarGroupContent, - SidebarGroupLabel, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, -} from '@/components/ui/sidebar' - -interface NavItem { - to: string - icon: LucideIcon - labelKey: string -} - -interface NavManagementProps { - items: NavItem[] - title?: string -} - -export function NavManagement({ items, title }: NavManagementProps) { - const location = useLocation() - const { t } = useTranslation() - - return ( - - {title && {title}} - - - {items.map(item => { - const Icon = item.icon - const isActive = location.pathname.startsWith(item.to) - - return ( - - } - isActive={isActive} - tooltip={t(item.labelKey)} - > - - {t(item.labelKey)} - - - ) - })} - - - - ) -} diff --git a/web/src/components/layout/nav-proxy-status.tsx b/web/src/components/layout/nav-proxy-status.tsx index 1a563cfd..ac025ed3 100644 --- a/web/src/components/layout/nav-proxy-status.tsx +++ b/web/src/components/layout/nav-proxy-status.tsx @@ -1,34 +1,30 @@ -import { useState } from 'react' -import { Radio, Check, Copy } from 'lucide-react' -import { useProxyStatus } from '@/hooks/queries' -import { useSidebar } from '@/components/ui/sidebar' -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from '@/components/ui/tooltip' -import { Button } from '../ui' -import { useTranslation } from 'react-i18next' +import { useState } from 'react'; +import { Radio, Check, Copy } from 'lucide-react'; +import { useProxyStatus } from '@/hooks/queries'; +import { useSidebar } from '@/components/ui/sidebar'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; +import { Button } from '../ui'; +import { useTranslation } from 'react-i18next'; export function NavProxyStatus() { - const { t } = useTranslation() - const { data: proxyStatus } = useProxyStatus() - const { state } = useSidebar() - const [copied, setCopied] = useState(false) + const { t } = useTranslation(); + const { data: proxyStatus } = useProxyStatus(); + const { state } = useSidebar(); + const [copied, setCopied] = useState(false); - const proxyAddress = proxyStatus?.address ?? '...' - const fullUrl = `http://${proxyAddress}` - const isCollapsed = state === 'collapsed' + const proxyAddress = proxyStatus?.address ?? '...'; + const fullUrl = `http://${proxyAddress}`; + const isCollapsed = state === 'collapsed'; const handleCopy = async () => { try { - await navigator.clipboard.writeText(fullUrl) - setCopied(true) - setTimeout(() => setCopied(false), 2000) + await navigator.clipboard.writeText(fullUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); } catch (err) { - console.error('Failed to copy:', err) + console.error('Failed to copy:', err); } - } + }; if (isCollapsed) { return ( @@ -60,7 +56,7 @@ export function NavProxyStatus() {
- ) + ); } return ( @@ -75,17 +71,13 @@ export function NavProxyStatus() {
{t('proxy.listeningOn')} - - {proxyAddress} - + {proxyAddress}
- ) + ); } diff --git a/web/src/components/layout/nav-routes.tsx b/web/src/components/layout/nav-routes.tsx deleted file mode 100644 index 15b9015b..00000000 --- a/web/src/components/layout/nav-routes.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { NavLink, useLocation } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import { - ClientIcon, - allClientTypes, - getClientName, - getClientColor, -} from '@/components/icons/client-icons' -import { StreamingBadge } from '@/components/ui/streaming-badge' -import { useStreamingRequests } from '@/hooks/use-streaming' -import type { ClientType } from '@/lib/transport' -import { - SidebarGroup, - SidebarGroupContent, - SidebarGroupLabel, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - SidebarMenuBadge, -} from '@/components/ui/sidebar' - -function ClientNavItem({ clientType }: { clientType: ClientType }) { - const location = useLocation() - const { countsByClient } = useStreamingRequests() - const streamingCount = countsByClient.get(clientType) || 0 - const color = getClientColor(clientType) - const clientName = getClientName(clientType) - const isActive = location.pathname === `/routes/${clientType}` - - return ( - - } - isActive={isActive} - tooltip={clientName} - className="relative overflow-hidden" - > - {/* Marquee 背景动画 (仅在有 streaming 请求且未激活时显示) */} - {streamingCount > 0 && !isActive && ( -
- )} - - {clientName} - - {streamingCount > 0 && ( - - - - )} - - ) -} - -export function NavRoutes() { - const { t } = useTranslation() - - return ( - - {t('nav.routes')} - - - {allClientTypes.map(clientType => ( - - ))} - - - - ) -} diff --git a/web/src/components/layout/page-header.tsx b/web/src/components/layout/page-header.tsx index 16f49155..fae508fd 100644 --- a/web/src/components/layout/page-header.tsx +++ b/web/src/components/layout/page-header.tsx @@ -1,13 +1,13 @@ -import type { LucideIcon } from 'lucide-react' -import type { ReactNode } from 'react' +import type { LucideIcon } from 'lucide-react'; +import type { ReactNode } from 'react'; interface PageHeaderProps { - icon?: LucideIcon - iconClassName?: string - title: string - description?: string - actions?: ReactNode - children?: ReactNode + icon?: LucideIcon; + iconClassName?: string; + title: string; + description?: string; + actions?: ReactNode; + children?: ReactNode; } export function PageHeader({ @@ -27,12 +27,8 @@ export function PageHeader({
)}
-

- {title} -

- {description && ( -

{description}

- )} +

{title}

+ {description &&

{description}

}
{(actions || children) && ( @@ -42,5 +38,5 @@ export function PageHeader({ )} - ) + ); } diff --git a/web/src/components/layout/sidebar-nav.tsx b/web/src/components/layout/sidebar-nav.tsx deleted file mode 100644 index d14c87d7..00000000 --- a/web/src/components/layout/sidebar-nav.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { NavLink, useLocation } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import { - LayoutDashboard, - Activity, - Server, - FolderKanban, - Users, - RefreshCw, - Terminal, - Settings, - Key, - Zap, - BarChart3, -} from 'lucide-react' -import { StreamingBadge } from '@/components/ui/streaming-badge' -import { useStreamingRequests } from '@/hooks/use-streaming' -import { useProxyStatus } from '@/hooks/queries' -import { - Sidebar, - SidebarContent, - SidebarFooter, - SidebarHeader, - SidebarMenuBadge, - SidebarMenuButton, - SidebarMenuItem, - SidebarTrigger, -} from '@/components/ui/sidebar' -import { NavMain } from './nav-main' -import { NavRoutes } from './nav-routes' -import { NavManagement } from './nav-management' -import { NavProxyStatus } from './nav-proxy-status' -import { ThemeToggle } from '@/components/theme-toggle' - -const mainNavItems = [ - { to: '/', icon: LayoutDashboard, labelKey: 'nav.dashboard' }, - { to: '/console', icon: Terminal, labelKey: 'nav.console' }, - { to: '/stats', icon: BarChart3, labelKey: 'nav.stats' }, -] - -const managementItems = [ - { to: '/providers', icon: Server, labelKey: 'nav.providers' }, - { to: '/projects', icon: FolderKanban, labelKey: 'nav.projects' }, - { to: '/sessions', icon: Users, labelKey: 'nav.sessions' }, - { to: '/api-tokens', icon: Key, labelKey: 'nav.apiTokens' }, -] - -const configItems = [ - { to: '/model-mappings', icon: Zap, labelKey: 'nav.modelMappings' }, - { to: '/retry-configs', icon: RefreshCw, labelKey: 'nav.retryConfigs' }, - { to: '/settings', icon: Settings, labelKey: 'nav.settings' }, -] - -/** - * Requests 导航项 - 带 Streaming Badge - */ -function RequestsNavItem() { - const location = useLocation() - const { total } = useStreamingRequests() - const { t } = useTranslation() - const isActive = location.pathname.startsWith('/requests') - const color = 'var(--color-success)' // emerald-500 - - return ( - - } - isActive={isActive} - tooltip={t('requests.title')} - className="relative" - > - {/* Marquee 背景动画 (仅在有 streaming 请求且未激活时显示) */} - {total > 0 && !isActive && ( -
- )} - - {t('requests.title')} - - {total > 0 && ( - - - - )} - - ) -} - -export function SidebarNav() { - const { t } = useTranslation() - const { data: proxyStatus } = useProxyStatus() - const versionDisplay = proxyStatus?.version ?? '...' - return ( - - - - - - - - - - - - - - - -

- {versionDisplay} -

-
- - -
-
-
- ) -} diff --git a/web/src/components/provider-details-dialog.tsx b/web/src/components/provider-details-dialog.tsx index 76ed1b3a..c7de551e 100644 --- a/web/src/components/provider-details-dialog.tsx +++ b/web/src/components/provider-details-dialog.tsx @@ -1,6 +1,6 @@ -import { useEffect, useState, useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import type { TFunction } from 'i18next' +import { useEffect, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { TFunction } from 'i18next'; import { Snowflake, Clock, @@ -20,59 +20,58 @@ import { CheckCircle2, XCircle, Trash2, -} from 'lucide-react' -import type { - Cooldown, - ProviderStats, - ClientType, -} from '@/lib/transport/types' -import type { ProviderConfigItem } from '@/pages/client-routes/types' -import { useCooldowns } from '@/hooks/use-cooldowns' -import { Button, Switch } from '@/components/ui' -import { getProviderColor, type ProviderType } from '@/lib/theme' -import { cn } from '@/lib/utils' -import { Dialog, DialogContent } from '@/components/ui/dialog' +} from 'lucide-react'; +import type { Cooldown, ProviderStats, ClientType } from '@/lib/transport/types'; +import type { ProviderConfigItem } from '@/pages/client-routes/types'; +import { useCooldowns } from '@/hooks/use-cooldowns'; +import { Button, Switch } from '@/components/ui'; +import { getProviderColor, type ProviderType } from '@/lib/theme'; +import { cn } from '@/lib/utils'; +import { Dialog, DialogContent } from '@/components/ui/dialog'; interface ProviderDetailsDialogProps { - item: ProviderConfigItem | null - clientType: ClientType - open: boolean - onOpenChange: (open: boolean) => void - stats?: ProviderStats - cooldown?: Cooldown | null - streamingCount: number - onToggle: () => void - isToggling: boolean - onDelete?: () => void - onClearCooldown?: () => void - isClearingCooldown?: boolean + item: ProviderConfigItem | null; + clientType: ClientType; + open: boolean; + onOpenChange: (open: boolean) => void; + stats?: ProviderStats; + cooldown?: Cooldown | null; + streamingCount: number; + onToggle: () => void; + isToggling: boolean; + onDelete?: () => void; + onClearCooldown?: () => void; + isClearingCooldown?: boolean; } // Reason 信息和图标 - 使用翻译 const getReasonInfo = (t: TFunction) => ({ server_error: { label: t('provider.reasons.serverError'), - description: t('provider.reasons.serverErrorDesc', '上游服务器返回 5xx 错误,系统自动进入冷却保护'), + description: t( + 'provider.reasons.serverErrorDesc', + '上游服务器返回 5xx 错误,系统自动进入冷却保护', + ), icon: Server, color: 'text-rose-500 dark:text-rose-400', - bgColor: - 'bg-rose-500/10 dark:bg-rose-500/15 border-rose-500/30 dark:border-rose-500/25', + bgColor: 'bg-rose-500/10 dark:bg-rose-500/15 border-rose-500/30 dark:border-rose-500/25', }, network_error: { label: t('provider.reasons.networkError'), - description: t('provider.reasons.networkErrorDesc', '无法连接到上游服务器,可能是网络故障或服务器宕机'), + description: t( + 'provider.reasons.networkErrorDesc', + '无法连接到上游服务器,可能是网络故障或服务器宕机', + ), icon: Wifi, color: 'text-amber-600 dark:text-amber-400', - bgColor: - 'bg-amber-500/10 dark:bg-amber-500/15 border-amber-500/30 dark:border-amber-500/25', + bgColor: 'bg-amber-500/10 dark:bg-amber-500/15 border-amber-500/30 dark:border-amber-500/25', }, quota_exhausted: { label: t('provider.reasons.quotaExhausted'), description: t('provider.reasons.quotaExhaustedDesc', 'API 配额已用完,等待配额重置'), icon: AlertCircle, color: 'text-rose-500 dark:text-rose-400', - bgColor: - 'bg-rose-500/10 dark:bg-rose-500/15 border-rose-500/30 dark:border-rose-500/25', + bgColor: 'bg-rose-500/10 dark:bg-rose-500/15 border-rose-500/30 dark:border-rose-500/25', }, rate_limit_exceeded: { label: t('provider.reasons.rateLimitExceeded'), @@ -97,37 +96,37 @@ const getReasonInfo = (t: TFunction) => ({ color: 'text-muted-foreground', bgColor: 'bg-muted/50 border-border', }, -}) +}); // 格式化 Token 数量 function formatTokens(count: number): string { if (count >= 1_000_000) { - return `${(count / 1_000_000).toFixed(1)}M` + return `${(count / 1_000_000).toFixed(1)}M`; } if (count >= 1_000) { - return `${(count / 1_000).toFixed(1)}K` + return `${(count / 1_000).toFixed(1)}K`; } - return count.toString() + return count.toString(); } // 格式化成本 (微美元 → 美元) function formatCost(microUsd: number): string { - const usd = microUsd / 1_000_000 + const usd = microUsd / 1_000_000; if (usd >= 1) { - return `$${usd.toFixed(2)}` + return `$${usd.toFixed(2)}`; } if (usd >= 0.01) { - return `$${usd.toFixed(3)}` + return `$${usd.toFixed(3)}`; } - return `$${usd.toFixed(4)}` + return `$${usd.toFixed(4)}`; } // 计算缓存利用率 function calcCacheRate(stats: ProviderStats): number { - const cacheTotal = stats.totalCacheRead + stats.totalCacheWrite - const total = stats.totalInputTokens + stats.totalOutputTokens + cacheTotal - if (total === 0) return 0 - return (cacheTotal / total) * 100 + const cacheTotal = stats.totalCacheRead + stats.totalCacheWrite; + const total = stats.totalInputTokens + stats.totalOutputTokens + cacheTotal; + if (total === 0) return 0; + return (cacheTotal / total) * 100; } export function ProviderDetailsDialog({ @@ -144,36 +143,36 @@ export function ProviderDetailsDialog({ onClearCooldown, isClearingCooldown, }: ProviderDetailsDialogProps) { - const { t, i18n } = useTranslation() - const REASON_INFO = getReasonInfo(t) - const { formatRemaining } = useCooldowns() + const { t, i18n } = useTranslation(); + const REASON_INFO = getReasonInfo(t); + const { formatRemaining } = useCooldowns(); // 计算初始倒计时值 const getInitialCountdown = useCallback(() => { - return cooldown ? formatRemaining(cooldown) : '' - }, [cooldown, formatRemaining]) + return cooldown ? formatRemaining(cooldown) : ''; + }, [cooldown, formatRemaining]); - const [liveCountdown, setLiveCountdown] = useState(getInitialCountdown) + const [liveCountdown, setLiveCountdown] = useState(getInitialCountdown); // 每秒更新倒计时 useEffect(() => { - if (!cooldown) return + if (!cooldown) return; const interval = setInterval(() => { - setLiveCountdown(formatRemaining(cooldown)) - }, 1000) + setLiveCountdown(formatRemaining(cooldown)); + }, 1000); - return () => clearInterval(interval) - }, [cooldown, formatRemaining]) + return () => clearInterval(interval); + }, [cooldown, formatRemaining]); - if (!item) return null + if (!item) return null; - const { provider, enabled, route, isNative } = item - const color = getProviderColor(provider.type as ProviderType) - const isInCooldown = !!cooldown + const { provider, enabled, route, isNative } = item; + const color = getProviderColor(provider.type as ProviderType); + const isInCooldown = !!cooldown; const formatUntilTime = (until: string) => { - const date = new Date(until) + const date = new Date(until); return date.toLocaleString(i18n.resolvedLanguage ?? i18n.language, { month: '2-digit', day: '2-digit', @@ -181,14 +180,14 @@ export function ProviderDetailsDialog({ minute: '2-digit', second: '2-digit', hour12: false, - }) - } + }); + }; const endpoint = provider.config?.custom?.clientBaseURL?.[clientType] || provider.config?.custom?.baseURL || provider.config?.antigravity?.endpoint || - t('provider.defaultEndpoint') + t('provider.defaultEndpoint'); return ( @@ -210,18 +209,12 @@ export function ProviderDetailsDialog({ {enabled ? t('provider.status.on') : t('provider.status.off')} - +
{route && (
@@ -399,17 +390,15 @@ export function ProviderDetailsDialog({ > {(() => { const Icon = - REASON_INFO[cooldown.reason]?.icon || - REASON_INFO.unknown.icon - return + REASON_INFO[cooldown.reason]?.icon || REASON_INFO.unknown.icon; + return ; })()}

- {REASON_INFO[cooldown.reason]?.label || - REASON_INFO.unknown.label} + {REASON_INFO[cooldown.reason]?.label || REASON_INFO.unknown.label}

{REASON_INFO[cooldown.reason]?.description || @@ -432,13 +421,13 @@ export function ProviderDetailsDialog({ {liveCountdown}

{(() => { - const untilDateStr = formatUntilTime(cooldown.untilTime) + const untilDateStr = formatUntilTime(cooldown.untilTime); return (
{untilDateStr}
- ) + ); })()} @@ -449,9 +438,7 @@ export function ProviderDetailsDialog({
- - Statistics - + Statistics
{stats && stats.totalRequests > 0 ? ( @@ -459,19 +446,14 @@ export function ProviderDetailsDialog({ {/* Requests */}
- + Requests
- - Total - + Total {stats.totalRequests} @@ -498,10 +480,7 @@ export function ProviderDetailsDialog({ {/* Success Rate */}
- + Success Rate @@ -514,7 +493,7 @@ export function ProviderDetailsDialog({ ? 'text-emerald-600 dark:text-emerald-400' : stats.successRate >= 90 ? 'text-blue-600 dark:text-blue-400' - : 'text-amber-600 dark:text-amber-400' + : 'text-amber-600 dark:text-amber-400', )} > {Math.round(stats.successRate)}% @@ -525,39 +504,28 @@ export function ProviderDetailsDialog({ {/* Tokens */}
- + Tokens
- - In - + In {formatTokens(stats.totalInputTokens)}
- - Out - + Out {formatTokens(stats.totalOutputTokens)}
- - Cache - + Cache - {formatTokens( - stats.totalCacheRead + stats.totalCacheWrite - )} + {formatTokens(stats.totalCacheRead + stats.totalCacheWrite)}
@@ -566,10 +534,7 @@ export function ProviderDetailsDialog({ {/* Cost */}
- + Cost @@ -598,5 +563,5 @@ export function ProviderDetailsDialog({
- ) + ); } diff --git a/web/src/components/routes/ClientTypeRoutesContent.tsx b/web/src/components/routes/ClientTypeRoutesContent.tsx index 91d5bb69..473170a7 100644 --- a/web/src/components/routes/ClientTypeRoutesContent.tsx +++ b/web/src/components/routes/ClientTypeRoutesContent.tsx @@ -3,8 +3,8 @@ * Used by both global routes and project routes */ -import { useState, useMemo } from 'react' -import { Plus, RefreshCw, Zap } from 'lucide-react' +import { useState, useMemo } from 'react'; +import { Plus, RefreshCw, Zap } from 'lucide-react'; import { DndContext, closestCenter, @@ -15,13 +15,13 @@ import { type DragEndEvent, type DragStartEvent, DragOverlay, -} from '@dnd-kit/core' +} from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, -} from '@dnd-kit/sortable' +} from '@dnd-kit/sortable'; import { useRoutes, useProviders, @@ -31,31 +31,28 @@ import { useUpdateRoutePositions, useProviderStats, routeKeys, -} from '@/hooks/queries' -import { useQueryClient } from '@tanstack/react-query' -import { useStreamingRequests } from '@/hooks/use-streaming' -import { getClientName, getClientColor } from '@/components/icons/client-icons' -import { getProviderColor, type ProviderType } from '@/lib/theme' -import type { ClientType, Provider } from '@/lib/transport' +} from '@/hooks/queries'; +import { useQueryClient } from '@tanstack/react-query'; +import { useStreamingRequests } from '@/hooks/use-streaming'; +import { getClientName, getClientColor } from '@/components/icons/client-icons'; +import { getProviderColor, type ProviderType } from '@/lib/theme'; +import type { ClientType, Provider } from '@/lib/transport'; import { SortableProviderRow, ProviderRowContent, -} from '@/pages/client-routes/components/provider-row' -import type { ProviderConfigItem } from '@/pages/client-routes/types' -import { Button } from '../ui' +} from '@/pages/client-routes/components/provider-row'; +import type { ProviderConfigItem } from '@/pages/client-routes/types'; +import { Button } from '../ui'; interface ClientTypeRoutesContentProps { - clientType: ClientType - projectID: number // 0 for global routes + clientType: ClientType; + projectID: number; // 0 for global routes } -export function ClientTypeRoutesContent({ - clientType, - projectID, -}: ClientTypeRoutesContentProps) { - const [activeId, setActiveId] = useState(null) - const { data: providerStats = {} } = useProviderStats(clientType, projectID) - const queryClient = useQueryClient() +export function ClientTypeRoutesContent({ clientType, projectID }: ClientTypeRoutesContentProps) { + const [activeId, setActiveId] = useState(null); + const { data: providerStats = {} } = useProviderStats(clientType, projectID); + const queryClient = useQueryClient(); const sensors = useSensors( useSensor(PointerSensor, { @@ -65,75 +62,65 @@ export function ClientTypeRoutesContent({ }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, - }) - ) + }), + ); - const { data: allRoutes, isLoading: routesLoading } = useRoutes() - const { data: providers = [], isLoading: providersLoading } = useProviders() - const { countsByProviderAndClient } = useStreamingRequests() + const { data: allRoutes, isLoading: routesLoading } = useRoutes(); + const { data: providers = [], isLoading: providersLoading } = useProviders(); + const { countsByProviderAndClient } = useStreamingRequests(); - const createRoute = useCreateRoute() - const toggleRoute = useToggleRoute() - const deleteRoute = useDeleteRoute() - const updatePositions = useUpdateRoutePositions() + const createRoute = useCreateRoute(); + const toggleRoute = useToggleRoute(); + const deleteRoute = useDeleteRoute(); + const updatePositions = useUpdateRoutePositions(); - const loading = routesLoading || providersLoading + const loading = routesLoading || providersLoading; // Get routes for this clientType and projectID const clientRoutes = useMemo(() => { - return ( - allRoutes?.filter( - r => r.clientType === clientType && r.projectID === projectID - ) || [] - ) - }, [allRoutes, clientType, projectID]) + return allRoutes?.filter((r) => r.clientType === clientType && r.projectID === projectID) || []; + }, [allRoutes, clientType, projectID]); // Build provider config items const items = useMemo((): ProviderConfigItem[] => { - const allItems = providers.map(provider => { - const route = - clientRoutes.find(r => Number(r.providerID) === Number(provider.id)) || - null - const isNative = (provider.supportedClientTypes || []).includes( - clientType - ) + const allItems = providers.map((provider) => { + const route = clientRoutes.find((r) => Number(r.providerID) === Number(provider.id)) || null; + const isNative = (provider.supportedClientTypes || []).includes(clientType); return { id: `${clientType}-provider-${provider.id}`, provider, route, enabled: route?.isEnabled ?? false, isNative, - } - }) + }; + }); // Only show providers that have routes - const filteredItems = allItems.filter(item => item.route) + const filteredItems = allItems.filter((item) => item.route); return filteredItems.sort((a, b) => { - if (a.route && b.route) return a.route.position - b.route.position - if (a.route && !b.route) return -1 - if (!a.route && b.route) return 1 - if (a.isNative && !b.isNative) return -1 - if (!a.isNative && b.isNative) return 1 - return a.provider.name.localeCompare(b.provider.name) - }) - }, [providers, clientRoutes, clientType]) + if (a.route && b.route) return a.route.position - b.route.position; + if (a.route && !b.route) return -1; + if (!a.route && b.route) return 1; + if (a.isNative && !b.isNative) return -1; + if (!a.isNative && b.isNative) return 1; + return a.provider.name.localeCompare(b.provider.name); + }); + }, [providers, clientRoutes, clientType]); // Get available providers (without routes yet) const availableProviders = useMemo((): Provider[] => { - return providers.filter(p => { - const hasRoute = clientRoutes.some( - r => Number(r.providerID) === Number(p.id) - ) - return !hasRoute - }) - }, [providers, clientRoutes]) + return providers.filter((p) => { + const hasRoute = clientRoutes.some((r) => Number(r.providerID) === Number(p.id)); + return !hasRoute; + }); + }, [providers, clientRoutes]); - const activeItem = activeId ? items.find(item => item.id === activeId) : null + const activeItem = activeId ? items.find((item) => item.id === activeId) : null; const handleToggle = (item: ProviderConfigItem) => { if (item.route) { - toggleRoute.mutate(item.route.id) + toggleRoute.mutate(item.route.id); } else { createRoute.mutate({ isEnabled: true, @@ -143,9 +130,9 @@ export function ClientTypeRoutesContent({ providerID: item.provider.id, position: items.length + 1, retryConfigID: 0, - }) + }); } - } + }; const handleAddRoute = (provider: Provider, isNative: boolean) => { createRoute.mutate({ @@ -156,69 +143,69 @@ export function ClientTypeRoutesContent({ providerID: provider.id, position: items.length + 1, retryConfigID: 0, - }) - } + }); + }; const handleDeleteRoute = (routeId: number) => { - deleteRoute.mutate(routeId) - } + deleteRoute.mutate(routeId); + }; const handleDragStart = (event: DragStartEvent) => { - setActiveId(event.active.id as string) - } + setActiveId(event.active.id as string); + }; const handleDragEnd = (event: DragEndEvent) => { - const { active, over } = event - setActiveId(null) + const { active, over } = event; + setActiveId(null); - if (!over || active.id === over.id) return + if (!over || active.id === over.id) return; - const oldIndex = items.findIndex(item => item.id === active.id) - const newIndex = items.findIndex(item => item.id === over.id) + const oldIndex = items.findIndex((item) => item.id === active.id); + const newIndex = items.findIndex((item) => item.id === over.id); - if (oldIndex === -1 || newIndex === -1) return + if (oldIndex === -1 || newIndex === -1) return; - const newItems = arrayMove(items, oldIndex, newIndex) + const newItems = arrayMove(items, oldIndex, newIndex); // Update positions for all items - const updates: Record = {} + const updates: Record = {}; newItems.forEach((item, i) => { if (item.route) { - updates[item.route.id] = i + 1 + updates[item.route.id] = i + 1; } - }) + }); if (Object.keys(updates).length > 0) { // 乐观更新:立即更新本地缓存 queryClient.setQueryData(routeKeys.list(), (oldRoutes: typeof allRoutes) => { - if (!oldRoutes) return oldRoutes - return oldRoutes.map(route => { - const newPosition = updates[route.id] + if (!oldRoutes) return oldRoutes; + return oldRoutes.map((route) => { + const newPosition = updates[route.id]; if (newPosition !== undefined) { - return { ...route, position: newPosition } + return { ...route, position: newPosition }; } - return route - }) - }) + return route; + }); + }); // 发送 API 请求 updatePositions.mutate(updates, { onError: () => { // 失败时回滚:重新获取服务器数据 - queryClient.invalidateQueries({ queryKey: routeKeys.list() }) + queryClient.invalidateQueries({ queryKey: routeKeys.list() }); }, - }) + }); } - } + }; - const color = getClientColor(clientType) + const color = getClientColor(clientType); if (loading) { return (
Loading...
- ) + ); } return ( @@ -234,7 +221,7 @@ export function ClientTypeRoutesContent({ onDragEnd={handleDragEnd} > item.id)} + items={items.map((item) => item.id)} strategy={verticalListSortingStrategy} >
@@ -245,20 +232,12 @@ export function ClientTypeRoutesContent({ index={index} clientType={clientType} streamingCount={ - countsByProviderAndClient.get( - `${item.provider.id}:${clientType}` - ) || 0 + countsByProviderAndClient.get(`${item.provider.id}:${clientType}`) || 0 } stats={providerStats[item.provider.id]} - isToggling={ - toggleRoute.isPending || createRoute.isPending - } + isToggling={toggleRoute.isPending || createRoute.isPending} onToggle={() => handleToggle(item)} - onDelete={ - item.route - ? () => handleDeleteRoute(item.route!.id) - : undefined - } + onDelete={item.route ? () => handleDeleteRoute(item.route!.id) : undefined} /> ))}
@@ -268,12 +247,10 @@ export function ClientTypeRoutesContent({ {activeItem && ( i.id === activeItem.id)} + index={items.findIndex((i) => i.id === activeItem.id)} clientType={clientType} streamingCount={ - countsByProviderAndClient.get( - `${activeItem.provider.id}:${clientType}` - ) || 0 + countsByProviderAndClient.get(`${activeItem.provider.id}:${clientType}`) || 0 } stats={providerStats[activeItem.provider.id]} isToggling={false} @@ -285,12 +262,8 @@ export function ClientTypeRoutesContent({ ) : (
-

- No routes configured for {getClientName(clientType)} -

-

- Add a route below to get started -

+

No routes configured for {getClientName(clientType)}

+

Add a route below to get started

)} @@ -304,13 +277,9 @@ export function ClientTypeRoutesContent({
- {availableProviders.map(provider => { - const isNative = ( - provider.supportedClientTypes || [] - ).includes(clientType) - const providerColor = getProviderColor( - provider.type as ProviderType - ) + {availableProviders.map((provider) => { + const isNative = (provider.supportedClientTypes || []).includes(clientType); + const providerColor = getProviderColor(provider.type as ProviderType); return ( - ) + ); })}
@@ -373,5 +339,5 @@ export function ClientTypeRoutesContent({ - ) + ); } diff --git a/web/src/components/theme-provider.tsx b/web/src/components/theme-provider.tsx index f60ba51f..c2c13993 100644 --- a/web/src/components/theme-provider.tsx +++ b/web/src/components/theme-provider.tsx @@ -1,26 +1,26 @@ -import { createContext, useContext, useEffect, useState } from 'react' +import { createContext, useContext, useEffect, useState } from 'react'; -type Theme = 'dark' | 'light' | 'system' +type Theme = 'dark' | 'light' | 'system'; type ThemeProviderProps = { - children: React.ReactNode - defaultTheme?: Theme - storageKey?: string -} + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; type ThemeProviderState = { - theme: Theme - setTheme: (theme: Theme) => void - toggleTheme: () => void -} + theme: Theme; + setTheme: (theme: Theme) => void; + toggleTheme: () => void; +}; const initialState: ThemeProviderState = { theme: 'system', setTheme: () => null, toggleTheme: () => null, -} +}; -const ThemeProviderContext = createContext(initialState) +const ThemeProviderContext = createContext(initialState); export function ThemeProvider({ children, @@ -29,52 +29,50 @@ export function ThemeProvider({ ...props }: ThemeProviderProps) { const [theme, setTheme] = useState( - () => (localStorage.getItem(storageKey) as Theme) || defaultTheme - ) + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, + ); useEffect(() => { - const root = window.document.documentElement + const root = window.document.documentElement; - root.classList.remove('light', 'dark') + root.classList.remove('light', 'dark'); if (theme === 'system') { - const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') - .matches + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' - : 'light' + : 'light'; - root.classList.add(systemTheme) - return + root.classList.add(systemTheme); + return; } - root.classList.add(theme) - }, [theme]) + root.classList.add(theme); + }, [theme]); const value = { theme, setTheme: (theme: Theme) => { - localStorage.setItem(storageKey, theme) - setTheme(theme) + localStorage.setItem(storageKey, theme); + setTheme(theme); }, toggleTheme: () => { - const newTheme = theme === 'dark' ? 'light' : 'dark' - localStorage.setItem(storageKey, newTheme) - setTheme(newTheme) + const newTheme = theme === 'dark' ? 'light' : 'dark'; + localStorage.setItem(storageKey, newTheme); + setTheme(newTheme); }, - } + }; return ( {children} - ) + ); } export const useTheme = () => { - const context = useContext(ThemeProviderContext) + const context = useContext(ThemeProviderContext); - if (context === undefined) - throw new Error('useTheme must be used within a ThemeProvider') + if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider'); - return context -} + return context; +}; diff --git a/web/src/components/theme-toggle.tsx b/web/src/components/theme-toggle.tsx index 36f87be1..f42fcfcb 100644 --- a/web/src/components/theme-toggle.tsx +++ b/web/src/components/theme-toggle.tsx @@ -1,15 +1,15 @@ -import { Moon, Sun } from 'lucide-react' -import { useTheme } from '@/components/theme-provider' -import { Button } from '@/components/ui/button' +import { Moon, Sun } from 'lucide-react'; +import { useTheme } from '@/components/theme-provider'; +import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' +} from '@/components/ui/dropdown-menu'; export function ThemeToggle() { - const { setTheme } = useTheme() + const { setTheme } = useTheme(); return ( @@ -23,16 +23,10 @@ export function ThemeToggle() { } > - setTheme('light')}> - Light - - setTheme('dark')}> - Dark - - setTheme('system')}> - System - + setTheme('light')}>Light + setTheme('dark')}>Dark + setTheme('system')}>System - ) + ); } diff --git a/web/src/components/ui/alert-dialog.tsx b/web/src/components/ui/alert-dialog.tsx index 99741a68..b6ba7fcf 100644 --- a/web/src/components/ui/alert-dialog.tsx +++ b/web/src/components/ui/alert-dialog.tsx @@ -1,49 +1,42 @@ -"use client" +'use client'; -import * as React from "react" -import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog" +import * as React from 'react'; +import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'; -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) { - return + return ; } function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) { - return ( - - ) + return ; } function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) { - return ( - - ) + return ; } -function AlertDialogOverlay({ - className, - ...props -}: AlertDialogPrimitive.Backdrop.Props) { +function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) { return ( - ) + ); } function AlertDialogContent({ className, - size = "default", + size = 'default', ...props }: AlertDialogPrimitive.Popup.Props & { - size?: "default" | "sm" + size?: 'default' | 'sm'; }) { return ( @@ -52,55 +45,52 @@ function AlertDialogContent({ data-slot="alert-dialog-content" data-size={size} className={cn( - "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none", - className + 'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none', + className, )} {...props} /> - ) + ); } -function AlertDialogHeader({ - className, - ...props -}: React.ComponentProps<"div">) { +function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) { return (
- ) + ); } -function AlertDialogFooter({ - className, - ...props -}: React.ComponentProps<"div">) { +function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) { return (
- ) + ); } -function AlertDialogMedia({ - className, - ...props -}: React.ComponentProps<"div">) { +function AlertDialogMedia({ className, ...props }: React.ComponentProps<'div'>) { return (
- ) + ); } function AlertDialogTitle({ @@ -110,10 +100,13 @@ function AlertDialogTitle({ return ( - ) + ); } function AlertDialogDescription({ @@ -123,32 +116,26 @@ function AlertDialogDescription({ return ( - ) + ); } -function AlertDialogAction({ - className, - ...props -}: React.ComponentProps) { - return ( -