Просмотр исходного кода

feat(logs): enhance usage logs table with log type indicators and improve UI elements

CaIon 1 месяц назад
Родитель
Сommit
db48108d21

+ 1 - 1
web/default/src/components/data-table/column-header.tsx

@@ -19,7 +19,7 @@ import {
 type DataTableColumnHeaderProps<TData, TValue> =
   React.HTMLAttributes<HTMLDivElement> & {
     column: Column<TData, TValue>
-    title: string
+    title: React.ReactNode
   }
 
 export function DataTableColumnHeader<TData, TValue>({

+ 13 - 11
web/default/src/components/status-badge.tsx

@@ -69,7 +69,7 @@ export interface StatusBadgeProps extends Omit<
   children?: React.ReactNode
   icon?: LucideIcon
   pulse?: boolean
-  /** @deprecated Dot is always shown in flat design */
+  /** When false, hides the leading dot */
   showDot?: boolean
   variant?: StatusVariant | null
   size?: 'sm' | 'md' | 'lg' | null
@@ -87,7 +87,7 @@ export function StatusBadge({
   variant,
   size = 'sm',
   pulse = false,
-  showDot: _showDot,
+  showDot = true,
   rounded: _rounded,
   copyable = true,
   copyText,
@@ -110,7 +110,7 @@ export function StatusBadge({
     onClick?.(e)
   }
 
-  const content = children ?? (label ? <span>{label}</span> : null)
+  const content = children ?? (label ? <span className='truncate'>{label}</span> : null)
 
   return (
     <span
@@ -127,14 +127,16 @@ export function StatusBadge({
       title={copyable ? `Click to copy: ${copyText || label || ''}` : undefined}
       {...props}
     >
-      <span
-        className={cn(
-          'inline-block size-1.5 shrink-0 rounded-full',
-          dotColorMap[computedVariant]
-        )}
-        aria-hidden='true'
-      />
-      {Icon && <Icon className='h-3 w-3' />}
+      {showDot && (
+        <span
+          className={cn(
+            'inline-block size-1.5 shrink-0 rounded-full',
+            dotColorMap[computedVariant]
+          )}
+          aria-hidden='true'
+        />
+      )}
+      {Icon && <Icon className='size-3 shrink-0' />}
       {content}
     </span>
   )

+ 151 - 97
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx

@@ -1,6 +1,6 @@
 import { useState } from 'react'
 import { type ColumnDef } from '@tanstack/react-table'
-import { Route, CircleAlert, Sparkles } from 'lucide-react'
+import { Route, CircleAlert, Sparkles, KeyRound } from 'lucide-react'
 import { useTranslation } from 'react-i18next'
 import { formatBillingCurrencyFromUSD } from '@/lib/currency'
 import {
@@ -10,7 +10,6 @@ import {
 } from '@/lib/format'
 import { getAvatarColorClass } from '@/lib/colors'
 import { cn } from '@/lib/utils'
-import { Button } from '@/components/ui/button'
 import {
   Popover,
   PopoverContent,
@@ -371,7 +370,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
           } = useUsageLogsContext()
           const log = row.original
 
-          if (!isDisplayableLogType(log.type) || !log.username) return null
+          if (!log.username) return null
 
           return (
             <button
@@ -385,7 +384,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
             >
               <span
                 className={cn(
-                  'flex size-5 items-center justify-center rounded-full text-[11px] font-bold',
+                  'flex size-6 items-center justify-center rounded-full text-xs font-bold ring-1 ring-border/60 saturate-[1.2] brightness-95 dark:brightness-110',
                   sensitiveVisible
                     ? getAvatarColorClass(log.username)
                     : 'bg-muted text-muted-foreground'
@@ -404,6 +403,39 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
     )
   }
 
+  columns.push({
+    accessorKey: 'token_name',
+    header: ({ column }) => (
+      <DataTableColumnHeader column={column} title={t('Token')} />
+    ),
+    cell: function TokenNameCell({ row }) {
+      const { sensitiveVisible } = useUsageLogsContext()
+      const log = row.original
+      if (!isDisplayableLogType(log.type)) return null
+
+      const tokenName = log.token_name
+      if (!tokenName) return null
+
+      const displayName = sensitiveVisible ? tokenName : '••••'
+
+      return (
+        <div className='max-w-[120px]'>
+          <StatusBadge
+            label={displayName}
+            icon={KeyRound}
+            autoColor={tokenName}
+            copyText={sensitiveVisible ? tokenName : undefined}
+            size='sm'
+            showDot={false}
+            className='max-w-full overflow-hidden rounded-md border border-border/60 bg-muted/30 px-1.5 py-0.5 font-mono'
+          />
+        </div>
+      )
+    },
+    meta: { label: t('Token') },
+    size: 130,
+  })
+
   columns.push(
     {
       accessorKey: 'model_name',
@@ -416,30 +448,29 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
         if (!isDisplayableLogType(log.type)) return null
 
         const modelInfo = formatModelName(log)
-        const tokenName = log.token_name
         const other = parseLogOther(log.other)
         let group = log.group
         if (!group) group = other?.group || ''
 
+        const badgeClass =
+          'truncate rounded-md border border-border/60 bg-muted/30 px-1.5 py-0.5 font-mono'
+
         const modelBadge = modelInfo.isMapped ? (
           <Popover>
             <PopoverTrigger asChild>
-              <Button
-                variant='ghost'
-                size='sm'
-                className='h-auto p-0 hover:bg-transparent'
+              <button
+                type='button'
+                className='inline-flex items-center gap-1'
               >
-                <span className='flex items-center gap-1'>
-                  <StatusBadge
-                    label={modelInfo.name}
-                    autoColor={modelInfo.name}
-                    copyText={modelInfo.name}
-                    size='sm'
-                    className='truncate font-mono'
-                  />
-                  <Route className='text-muted-foreground size-3 shrink-0' />
-                </span>
-              </Button>
+                <StatusBadge
+                  label={modelInfo.name}
+                  autoColor={modelInfo.name}
+                  copyText={modelInfo.name}
+                  size='sm'
+                  className={badgeClass}
+                />
+                <Route className='text-muted-foreground size-3 shrink-0' />
+              </button>
             </PopoverTrigger>
             <PopoverContent className='w-72'>
               <div className='space-y-2'>
@@ -468,12 +499,11 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
             autoColor={modelInfo.name}
             copyText={modelInfo.name}
             size='sm'
-            className='truncate font-mono'
+            className={badgeClass}
           />
         )
 
         const metaParts: string[] = []
-        if (tokenName) metaParts.push(sensitiveVisible ? tokenName : '••••')
         if (group) metaParts.push(sensitiveVisible ? group : '••••')
 
         return (
@@ -505,41 +535,65 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
         const timeVariant = getTimeColor(useTime)
         const frtVariant = frt ? getTimeColor(frt / 1000) : null
 
+        const pillBg: Record<string, string> = {
+          success:
+            'border border-emerald-200/60 bg-emerald-50/50 dark:border-emerald-800/50 dark:bg-emerald-950/20',
+          info: 'border border-sky-200/60 bg-sky-50/50 dark:border-sky-800/50 dark:bg-sky-950/20',
+          warning:
+            'border border-amber-200/60 bg-amber-50/50 dark:border-amber-800/50 dark:bg-amber-950/20',
+        }
+
         return (
-          <div className='flex flex-col gap-0.5'>
-            <div className='flex items-center gap-1 text-xs'>
-              <span
-                className={cn(
-                  'size-1.5 shrink-0 rounded-full',
-                  dotColorMap[timeVariant]
-                )}
-                aria-hidden='true'
-              />
+          <div className='flex flex-col gap-1'>
+            <div className='flex items-center gap-1.5'>
               <span
                 className={cn(
-                  'font-mono font-medium',
+                  'inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 font-mono text-xs font-medium',
+                  pillBg[timeVariant],
                   textColorMap[timeVariant]
                 )}
               >
+                <span
+                  className={cn(
+                    'size-1.5 shrink-0 rounded-full',
+                    dotColorMap[timeVariant]
+                  )}
+                  aria-hidden='true'
+                />
                 {formatUseTime(useTime)}
               </span>
-              {log.is_stream && frt != null && frt > 0 && (
-                <>
-                  <span className='text-muted-foreground/30'>·</span>
-                  <span
-                    className={cn(
-                      'font-mono font-medium',
-                      textColorMap[frtVariant!]
-                    )}
-                  >
-                    {formatUseTime(frt / 1000)}
-                  </span>
-                </>
-              )}
+              {log.is_stream && (frt != null && frt > 0 ? (
+                <span
+                  className={cn(
+                    'inline-flex items-center rounded-md px-1.5 py-0.5 font-mono text-xs font-medium',
+                    pillBg[frtVariant!],
+                    textColorMap[frtVariant!]
+                  )}
+                >
+                  {formatUseTime(frt / 1000)}
+                </span>
+              ) : (
+                <span className='inline-flex items-center rounded-md border border-border/60 px-1.5 py-0.5 text-[11px] text-muted-foreground/50'>
+                  N/A
+                </span>
+              ))}
             </div>
             <div className='flex items-center gap-1 text-[11px]'>
               <span className='text-muted-foreground/60'>
                 {log.is_stream ? t('Stream') : t('Non-stream')}
+                {useTime > 0 && (log.prompt_tokens + log.completion_tokens) > 0 && (
+                  <>
+                    {' · '}
+                    <span className='font-mono tabular-nums'>
+                      {Math.round(
+                        (log.is_stream
+                          ? log.completion_tokens
+                          : log.prompt_tokens + log.completion_tokens) / useTime
+                      )}
+                    </span>
+                    {' t/s'}
+                  </>
+                )}
               </span>
               {log.is_stream &&
                 other?.stream_status &&
@@ -583,9 +637,6 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
         if (!isDisplayableLogType(log.type)) return null
 
         const other = parseLogOther(log.other)
-        if (isPerCallBilling(other?.model_price)) {
-          return <span className='text-muted-foreground text-xs'>-</span>
-        }
 
         const promptTokens = log.prompt_tokens || 0
         const completionTokens = log.completion_tokens || 0
@@ -600,24 +651,25 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
         const cacheWriteTokens = hasSplitCache
           ? cacheWrite5m + cacheWrite1h
           : other?.cache_creation_tokens || 0
-        const cacheSegments = [
-          cacheReadTokens > 0
-            ? `${t('Cache')}读 ${cacheReadTokens.toLocaleString()}`
-            : null,
-          cacheWriteTokens > 0
-            ? `写 ${cacheWriteTokens.toLocaleString()}`
-            : null,
-        ].filter(Boolean)
 
         return (
           <div className='flex flex-col gap-0.5'>
-            <span className='font-mono text-xs font-medium'>
+            <span className='font-mono text-xs font-medium tabular-nums'>
               {promptTokens.toLocaleString()} / {completionTokens.toLocaleString()}
             </span>
-            {cacheSegments.length > 0 && (
-              <span className='text-muted-foreground/60 text-[11px]'>
-                {cacheSegments.join(' · ')}
-              </span>
+            {(cacheReadTokens > 0 || cacheWriteTokens > 0) && (
+              <div className='flex items-center gap-1 text-[11px]'>
+                {cacheReadTokens > 0 && (
+                  <span className='text-muted-foreground/60'>
+                    {t('Cache')}↓ {cacheReadTokens.toLocaleString()}
+                  </span>
+                )}
+                {cacheWriteTokens > 0 && (
+                  <span className='text-muted-foreground/60'>
+                    ↑ {cacheWriteTokens.toLocaleString()}
+                  </span>
+                )}
+              </div>
             )}
           </div>
         )
@@ -643,14 +695,10 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
             <TooltipProvider>
               <Tooltip>
                 <TooltipTrigger asChild>
-                  <div>
-                    <StatusBadge
-                      label={t('Subscription')}
-                      variant='green'
-                      size='sm'
-                      copyable={false}
-                    />
-                  </div>
+                  <span className='inline-flex items-center gap-1 rounded-md border border-emerald-200 bg-emerald-50 px-1.5 py-0.5 text-xs font-medium text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300'>
+                    <span className='size-1.5 rounded-full bg-emerald-500' aria-hidden='true' />
+                    {t('Subscription')}
+                  </span>
                 </TooltipTrigger>
                 <TooltipContent>
                   <span>
@@ -662,10 +710,12 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
           )
         }
 
+        const quotaStr = formatLogQuota(quota)
+
         return (
-          <div className='flex flex-col'>
-            <span className='font-mono text-xs font-medium tabular-nums'>
-              {formatLogQuota(quota)}
+          <div className='flex flex-col gap-0.5'>
+            <span className='border-border/80 inline-flex w-fit items-center rounded-md border bg-muted/60 px-1.5 py-0.5 font-mono text-xs font-semibold tabular-nums'>
+              {quotaStr}
             </span>
             {(() => {
               const userGroupRatio = other?.user_group_ratio
@@ -705,39 +755,43 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
         const other = parseLogOther(log.other)
 
         const segments = buildDetailSegments(log, other, t)
+        const primary = segments[0]
+        const hasMore = segments.length > 1
 
         return (
           <>
-            <Button
-              variant='ghost'
-              className='h-auto max-w-[220px] justify-start p-0 text-left text-xs font-normal hover:underline'
+            <button
+              type='button'
+              className='group flex max-w-[200px] items-center gap-1 text-left text-xs'
               onClick={() => setDialogOpen(true)}
               title={t('Click to view full details')}
             >
-              {segments.length > 0 ? (
-                <div className='flex flex-col gap-px'>
-                  {segments.map((seg, i) => (
-                    <span
-                      key={i}
-                      className={cn(
-                        'leading-snug',
-                        seg.muted
-                          ? 'text-muted-foreground/60'
-                          : seg.danger
-                            ? 'text-red-600 dark:text-red-400'
-                            : 'text-foreground'
-                      )}
-                    >
-                      {seg.text}
+              {primary ? (
+                <span
+                  className={cn(
+                    'truncate leading-snug group-hover:underline',
+                    primary.muted
+                      ? 'text-muted-foreground/60'
+                      : primary.danger
+                        ? 'text-red-600 dark:text-red-400'
+                        : 'text-foreground'
+                  )}
+                >
+                  {primary.text}
+                  {hasMore && (
+                    <span className='text-muted-foreground/40 ml-0.5'>
+                      +{segments.length - 1}
                     </span>
-                  ))}
-                </div>
+                  )}
+                </span>
               ) : log.content ? (
-                <span className='line-clamp-2'>{log.content}</span>
+                <span className='text-muted-foreground truncate group-hover:underline'>
+                  {log.content}
+                </span>
               ) : (
-                <span className='text-muted-foreground'>-</span>
+                <span className='text-muted-foreground/40'>—</span>
               )}
-            </Button>
+            </button>
             <DetailsDialog
               log={log}
               isAdmin={isAdmin}
@@ -748,8 +802,8 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
         )
       },
       meta: { label: t('Details'), mobileHidden: true },
-      size: 200,
-      maxSize: 220,
+      size: 180,
+      maxSize: 200,
     }
   )
 

+ 19 - 9
web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx

@@ -1,6 +1,7 @@
 import { useState, useEffect, useCallback, type ReactNode } from 'react'
 import { useNavigate, getRouteApi } from '@tanstack/react-router'
-import { ChevronDown, Eye, EyeOff, RotateCcw, Search } from 'lucide-react'
+import { useQueryClient, useIsFetching } from '@tanstack/react-query'
+import { ChevronDown, Eye, EyeOff, Loader2, RotateCcw, Search } from 'lucide-react'
 import { useTranslation } from 'react-i18next'
 import { cn } from '@/lib/utils'
 import { useIsAdmin } from '@/hooks/use-admin'
@@ -33,9 +34,11 @@ export function CommonLogsFilterBar({
 }: CommonLogsFilterBarProps) {
   const { t } = useTranslation()
   const navigate = useNavigate()
+  const queryClient = useQueryClient()
   const searchParams = route.useSearch()
   const isAdmin = useIsAdmin()
   const { sensitiveVisible, setSensitiveVisible } = useUsageLogsContext()
+  const fetchingLogs = useIsFetching({ queryKey: ['logs'] })
 
   const [expanded, setExpanded] = useState(false)
   const [filters, setFilters] = useState<CommonLogFilters>(() => {
@@ -88,14 +91,15 @@ export function CommonLogsFilterBar({
     navigate({
       to: '/usage-logs/$section',
       params: { section: 'common' },
-      search: (prev: Record<string, unknown>) => ({
-        ...prev,
+      search: {
         ...filterParams,
-        ...(logType ? { type: [logType] } : { type: undefined }),
+        ...(logType ? { type: [logType] } : {}),
         page: 1,
-      }),
+      },
     })
-  }, [filters, logType, navigate])
+    queryClient.invalidateQueries({ queryKey: ['logs'] })
+    queryClient.invalidateQueries({ queryKey: ['usage-logs-stats'] })
+  }, [filters, logType, navigate, queryClient])
 
   const handleReset = useCallback(() => {
     const { start, end } = getDefaultTimeRange()
@@ -112,7 +116,9 @@ export function CommonLogsFilterBar({
         endTime: end.getTime(),
       },
     })
-  }, [navigate])
+    queryClient.invalidateQueries({ queryKey: ['logs'] })
+    queryClient.invalidateQueries({ queryKey: ['usage-logs-stats'] })
+  }, [navigate, queryClient])
 
   const handleKeyDown = useCallback(
     (e: React.KeyboardEvent) => {
@@ -267,8 +273,12 @@ export function CommonLogsFilterBar({
             <RotateCcw className='size-3.5' />
             {t('Reset')}
           </Button>
-          <Button size='sm' className='h-8' onClick={handleApply}>
-            <Search className='size-3.5' />
+          <Button size='sm' className='h-8' onClick={handleApply} disabled={fetchingLogs > 0}>
+            {fetchingLogs > 0 ? (
+              <Loader2 className='size-3.5 animate-spin' />
+            ) : (
+              <Search className='size-3.5' />
+            )}
             {t('Search')}
           </Button>
           {viewOptions}

+ 6 - 6
web/default/src/features/usage-logs/components/common-logs-stats.tsx

@@ -42,19 +42,19 @@ export function CommonLogsStats() {
 
   if (isLoading) {
     return (
-      <div className='flex items-center gap-1.5'>
-        <Skeleton className='h-6 w-[126px] rounded-md' />
-        <Skeleton className='h-6 w-[76px] rounded-md' />
-        <Skeleton className='h-6 w-[92px] rounded-md' />
+      <div className='flex items-center gap-2'>
+        <Skeleton className='h-7 w-[150px] rounded-md' />
+        <Skeleton className='h-7 w-[100px] rounded-md' />
+        <Skeleton className='h-7 w-[120px] rounded-md' />
       </div>
     )
   }
 
   const tagClass =
-    'inline-flex h-6 items-center rounded-md border px-2.5 text-xs font-medium shadow-xs'
+    'inline-flex h-7 items-center rounded-md border px-3 py-1 text-xs font-medium shadow-xs'
 
   return (
-    <div className='flex flex-wrap items-center gap-1.5'>
+    <div className='flex flex-wrap items-center gap-2'>
       <span
         className={cn(
           tagClass,

+ 54 - 15
web/default/src/features/usage-logs/components/usage-logs-table.tsx

@@ -34,7 +34,7 @@ import {
   MobileCardList,
 } from '@/components/data-table'
 import { PageFooterPortal } from '@/components/layout'
-import { DEFAULT_LOGS_DATA } from '../constants'
+import { DEFAULT_LOGS_DATA, LOG_TYPE_ENUM } from '../constants'
 import { useColumnsByCategory } from '../lib/columns'
 import { fetchLogsByCategory } from '../lib/utils'
 import type { LogCategory } from '../types'
@@ -43,6 +43,20 @@ import { CommonLogsStats } from './common-logs-stats'
 
 const route = getRouteApi('/_authenticated/usage-logs/$section')
 
+const logTypeBorderColor: Record<number, string> = {
+  [LOG_TYPE_ENUM.TOPUP]: 'border-l-cyan-400 dark:border-l-cyan-500',
+  [LOG_TYPE_ENUM.CONSUME]: 'border-l-emerald-400 dark:border-l-emerald-500',
+  [LOG_TYPE_ENUM.MANAGE]: 'border-l-orange-400 dark:border-l-orange-500',
+  [LOG_TYPE_ENUM.SYSTEM]: 'border-l-purple-400 dark:border-l-purple-500',
+  [LOG_TYPE_ENUM.ERROR]: 'border-l-rose-400 dark:border-l-rose-500',
+  [LOG_TYPE_ENUM.REFUND]: 'border-l-blue-400 dark:border-l-blue-500',
+}
+
+const logTypeRowTint: Record<number, string> = {
+  [LOG_TYPE_ENUM.ERROR]: 'bg-rose-50/40 dark:bg-rose-950/20',
+  [LOG_TYPE_ENUM.REFUND]: 'bg-blue-50/30 dark:bg-blue-950/15',
+}
+
 interface UsageLogsTableProps {
   logCategory: LogCategory
 }
@@ -150,6 +164,42 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
     ensurePageInRange(pageCount)
   }, [pageCount, ensurePageInRange])
 
+  const isCommon = logCategory === 'common'
+
+  const renderRows = () => {
+    const rows = table.getRowModel().rows
+    if (rows.length === 0) return null
+
+    return rows.map((row) => {
+      const logType = (row.original as Record<string, unknown>).type as
+        | number
+        | undefined
+      const borderClass =
+        isCommon && logType != null
+          ? logTypeBorderColor[logType] ?? 'border-l-transparent'
+          : ''
+      const tintClass =
+        isCommon && logType != null ? (logTypeRowTint[logType] ?? '') : ''
+
+      return (
+        <TableRow
+          key={row.id}
+          className={cn(
+            '!border-l-[3px] transition-colors',
+            borderClass,
+            tintClass
+          )}
+        >
+          {row.getVisibleCells().map((cell) => (
+            <TableCell key={cell.id} className='py-2'>
+              {flexRender(cell.column.columnDef.cell, cell.getContext())}
+            </TableCell>
+          ))}
+        </TableRow>
+      )
+    })
+  }
+
   return (
     <>
       <div className='space-y-4'>
@@ -184,9 +234,9 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
             )}
           >
             <Table>
-              <TableHeader>
+              <TableHeader className='bg-muted/30 sticky top-0 z-10'>
                 {table.getHeaderGroups().map((headerGroup) => (
-                  <TableRow key={headerGroup.id}>
+                  <TableRow key={headerGroup.id} className='border-l-[3px] border-l-transparent'>
                     {headerGroup.headers.map((header) => (
                       <TableHead key={header.id} colSpan={header.colSpan}>
                         {header.isPlaceholder
@@ -212,18 +262,7 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
                     )}
                   />
                 ) : (
-                  table.getRowModel().rows.map((row) => (
-                    <TableRow key={row.id}>
-                      {row.getVisibleCells().map((cell) => (
-                        <TableCell key={cell.id} className='py-2'>
-                          {flexRender(
-                            cell.column.columnDef.cell,
-                            cell.getContext()
-                          )}
-                        </TableCell>
-                      ))}
-                    </TableRow>
-                  ))
+                  renderRows()
                 )}
               </TableBody>
             </Table>

+ 0 - 1
web/default/src/lib/format.ts

@@ -162,7 +162,6 @@ export function formatTokens(tokens: number): string {
  * Format use time in seconds with appropriate unit
  */
 export function formatUseTime(seconds: number): string {
-  if (seconds < 1) return `${(seconds * 1000).toFixed(0)}ms`
   if (seconds < 60) return `${seconds.toFixed(1)}s`
   const minutes = Math.floor(seconds / 60)
   const remainingSeconds = seconds % 60

+ 45 - 45
web/default/src/styles/theme.css

@@ -1,31 +1,31 @@
 @custom-variant dark (&:is(.dark *));
 
 :root {
-  --background: oklch(1 0 0);
-  --foreground: oklch(0.129 0.042 264.695);
-  --card: oklch(1 0 0);
-  --card-foreground: oklch(0.129 0.042 264.695);
-  --popover: oklch(1 0 0);
-  --popover-foreground: oklch(0.129 0.042 264.695);
-  --primary: oklch(0.208 0.042 265.755);
-  --primary-foreground: oklch(0.984 0.003 247.858);
-  --secondary: oklch(0.968 0.007 247.896);
-  --secondary-foreground: oklch(0.208 0.042 265.755);
-  --muted: oklch(0.968 0.007 247.896);
-  --muted-foreground: oklch(0.554 0.046 257.417);
-  --accent: oklch(0.968 0.007 247.896);
-  --accent-foreground: oklch(0.208 0.042 265.755);
+  --background: oklch(0.994 0.002 247.858);
+  --foreground: oklch(0.18 0.035 264.695);
+  --card: oklch(0.997 0.002 247.858);
+  --card-foreground: oklch(0.18 0.035 264.695);
+  --popover: oklch(0.997 0.002 247.858);
+  --popover-foreground: oklch(0.18 0.035 264.695);
+  --primary: oklch(0.255 0.042 265.755);
+  --primary-foreground: oklch(0.985 0.004 247.858);
+  --secondary: oklch(0.974 0.004 247.896);
+  --secondary-foreground: oklch(0.255 0.042 265.755);
+  --muted: oklch(0.972 0.004 247.896);
+  --muted-foreground: oklch(0.49 0.04 257.417);
+  --accent: oklch(0.972 0.004 247.896);
+  --accent-foreground: oklch(0.255 0.042 265.755);
   --destructive: oklch(0.577 0.245 27.325);
-  --border: oklch(0.929 0.013 255.508);
-  --input: oklch(0.929 0.013 255.508);
-  --ring: oklch(0.704 0.04 256.788);
+  --border: oklch(0.925 0.01 255.508);
+  --input: oklch(0.925 0.01 255.508);
+  --ring: oklch(0.64 0.055 256.788);
   --chart-1: oklch(0.646 0.222 41.116);
   --chart-2: oklch(0.6 0.118 184.704);
   --chart-3: oklch(0.398 0.07 227.392);
   --chart-4: oklch(0.828 0.189 84.429);
   --chart-5: oklch(0.769 0.188 70.08);
   --radius: 0.625rem;
-  --sidebar: var(--background);
+  --sidebar: oklch(0.991 0.002 247.858);
   --sidebar-foreground: var(--foreground);
   --sidebar-primary: var(--primary);
   --sidebar-primary-foreground: var(--primary-foreground);
@@ -33,44 +33,44 @@
   --sidebar-accent-foreground: var(--accent-foreground);
   --sidebar-border: var(--border);
   --sidebar-ring: var(--ring);
-  --skeleton-base: oklch(0.943 0.003 264);
-  --skeleton-highlight: oklch(0.985 0.001 264);
+  --skeleton-base: oklch(0.948 0.004 264);
+  --skeleton-highlight: oklch(0.988 0.002 264);
 }
 
 .dark {
-  --background: oklch(0.11 0 0);
-  --foreground: oklch(0.984 0.003 247.858);
-  --card: oklch(0.13 0 0);
-  --card-foreground: oklch(0.984 0.003 247.858);
-  --popover: oklch(0.15 0 0);
-  --popover-foreground: oklch(0.984 0.003 247.858);
-  --primary: oklch(0.929 0.013 255.508);
-  --primary-foreground: oklch(0.208 0.042 265.755);
-  --secondary: oklch(0.2 0 0);
-  --secondary-foreground: oklch(0.984 0.003 247.858);
-  --muted: oklch(0.2 0 0);
-  --muted-foreground: oklch(0.704 0 0);
-  --accent: oklch(0.2 0 0);
-  --accent-foreground: oklch(0.984 0.003 247.858);
+  --background: oklch(0.165 0.012 258);
+  --foreground: oklch(0.92 0.008 247.858);
+  --card: oklch(0.205 0.012 258);
+  --card-foreground: oklch(0.92 0.008 247.858);
+  --popover: oklch(0.225 0.014 258);
+  --popover-foreground: oklch(0.92 0.008 247.858);
+  --primary: oklch(0.87 0.018 255.508);
+  --primary-foreground: oklch(0.235 0.042 265.755);
+  --secondary: oklch(0.255 0.012 258);
+  --secondary-foreground: oklch(0.92 0.008 247.858);
+  --muted: oklch(0.245 0.012 258);
+  --muted-foreground: oklch(0.68 0.014 257.417);
+  --accent: oklch(0.265 0.014 258);
+  --accent-foreground: oklch(0.92 0.008 247.858);
   --destructive: oklch(0.704 0.191 22.216);
-  --border: oklch(1 0 0 / 12%);
-  --input: oklch(1 0 0 / 16%);
-  --ring: oklch(0.65 0 0);
+  --border: oklch(0.31 0.014 258);
+  --input: oklch(0.34 0.014 258);
+  --ring: oklch(0.58 0.025 256.788);
   --chart-1: oklch(0.488 0.243 264.376);
   --chart-2: oklch(0.696 0.17 162.48);
   --chart-3: oklch(0.769 0.188 70.08);
   --chart-4: oklch(0.627 0.265 303.9);
   --chart-5: oklch(0.645 0.246 16.439);
-  --sidebar: oklch(0.205 0 0);
-  --sidebar-foreground: oklch(0.985 0 0);
+  --sidebar: oklch(0.185 0.012 258);
+  --sidebar-foreground: oklch(0.92 0.008 247.858);
   --sidebar-primary: oklch(0.75 0.14 233);
   --sidebar-primary-foreground: oklch(0.29 0.06 243);
-  --sidebar-accent: oklch(0.269 0 0);
-  --sidebar-accent-foreground: oklch(0.985 0 0);
-  --sidebar-border: oklch(1 0 0 / 10%);
-  --sidebar-ring: oklch(0.556 0 0);
-  --skeleton-base: oklch(0.18 0 0);
-  --skeleton-highlight: oklch(0.26 0 0);
+  --sidebar-accent: oklch(0.255 0.012 258);
+  --sidebar-accent-foreground: oklch(0.92 0.008 247.858);
+  --sidebar-border: oklch(0.3 0.014 258);
+  --sidebar-ring: oklch(0.52 0.02 256.788);
+  --skeleton-base: oklch(0.245 0.01 258);
+  --skeleton-highlight: oklch(0.32 0.014 258);
 }
 
 @theme inline {