Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
db48108d
authored
Apr 28, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(logs): enhance usage logs table with log type indicators and improve UI elements
parent
22ef5b2f
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
289 additions
and
185 deletions
+289
-185
web/default/src/components/data-table/column-header.tsx
+1
-1
web/default/src/components/status-badge.tsx
+13
-11
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
+151
-97
web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
+19
-9
web/default/src/features/usage-logs/components/common-logs-stats.tsx
+6
-6
web/default/src/features/usage-logs/components/usage-logs-table.tsx
+54
-15
web/default/src/lib/format.ts
+0
-1
web/default/src/styles/theme.css
+45
-45
No files found.
web/default/src/components/data-table/column-header.tsx
View file @
db48108d
...
...
@@ -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
>
({
...
...
web/default/src/components/status-badge.tsx
View file @
db48108d
...
...
@@ -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
>
)
...
...
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
View file @
db48108d
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 (
<>
<
B
utton
variant='ghost
'
className='
h-auto max-w-[220px] justify-start p-0 text-left text-xs font-normal hover:underline
'
<
b
utton
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>
)}
</
B
utton>
</
b
utton>
<DetailsDialog
log={log}
isAdmin={isAdmin}
...
...
@@ -748,8 +802,8 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
)
},
meta: { label: t('Details'), mobileHidden: true },
size:
20
0,
maxSize: 2
2
0,
size:
18
0,
maxSize: 2
0
0,
}
)
...
...
web/default/src/features/usage-logs/components/common-logs-filter-bar.tsx
View file @
db48108d
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
}
...
...
web/default/src/features/usage-logs/components/common-logs-stats.tsx
View file @
db48108d
...
...
@@ -42,19 +42,19 @@ export function CommonLogsStats() {
if
(
isLoading
)
{
return
(
<
div
className=
'flex items-center gap-
1.5
'
>
<
Skeleton
className=
'h-
6 w-[126
px] rounded-md'
/>
<
Skeleton
className=
'h-
6 w-[76
px] rounded-md'
/>
<
Skeleton
className=
'h-
6 w-[92
px] rounded-md'
/>
<
div
className=
'flex items-center gap-
2
'
>
<
Skeleton
className=
'h-
7 w-[150
px] rounded-md'
/>
<
Skeleton
className=
'h-
7 w-[100
px] rounded-md'
/>
<
Skeleton
className=
'h-
7 w-[120
px] 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
,
...
...
web/default/src/features/usage-logs/components/usage-logs-table.tsx
View file @
db48108d
...
...
@@ -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
>
...
...
web/default/src/lib/format.ts
View file @
db48108d
...
...
@@ -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
...
...
web/default/src/styles/theme.css
View file @
db48108d
@custom-variant
dark
(
&
:
is
(.
dark
*
));
:root
{
--background
:
oklch
(
1
0
0
);
--foreground
:
oklch
(
0.1
29
0.042
264.695
);
--card
:
oklch
(
1
0
0
);
--card-foreground
:
oklch
(
0.1
29
0.042
264.695
);
--popover
:
oklch
(
1
0
0
);
--popover-foreground
:
oklch
(
0.1
29
0.042
264.695
);
--primary
:
oklch
(
0.2
08
0.042
265.755
);
--primary-foreground
:
oklch
(
0.98
4
0.003
247.858
);
--secondary
:
oklch
(
0.9
68
0.007
247.896
);
--secondary-foreground
:
oklch
(
0.2
08
0.042
265.755
);
--muted
:
oklch
(
0.9
68
0.007
247.896
);
--muted-foreground
:
oklch
(
0.
554
0.046
257.417
);
--accent
:
oklch
(
0.9
68
0.007
247.896
);
--accent-foreground
:
oklch
(
0.2
08
0.042
265.755
);
--background
:
oklch
(
0.994
0.002
247.858
);
--foreground
:
oklch
(
0.1
8
0.035
264.695
);
--card
:
oklch
(
0.997
0.002
247.858
);
--card-foreground
:
oklch
(
0.1
8
0.035
264.695
);
--popover
:
oklch
(
0.997
0.002
247.858
);
--popover-foreground
:
oklch
(
0.1
8
0.035
264.695
);
--primary
:
oklch
(
0.2
55
0.042
265.755
);
--primary-foreground
:
oklch
(
0.98
5
0.004
247.858
);
--secondary
:
oklch
(
0.9
74
0.004
247.896
);
--secondary-foreground
:
oklch
(
0.2
55
0.042
265.755
);
--muted
:
oklch
(
0.9
72
0.004
247.896
);
--muted-foreground
:
oklch
(
0.
49
0.04
257.417
);
--accent
:
oklch
(
0.9
72
0.004
247.896
);
--accent-foreground
:
oklch
(
0.2
55
0.042
265.755
);
--destructive
:
oklch
(
0.577
0.245
27.325
);
--border
:
oklch
(
0.92
9
0.013
255.508
);
--input
:
oklch
(
0.92
9
0.013
255.508
);
--ring
:
oklch
(
0.
704
0.04
256.788
);
--border
:
oklch
(
0.92
5
0.01
255.508
);
--input
:
oklch
(
0.92
5
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.94
3
0.003
264
);
--skeleton-highlight
:
oklch
(
0.98
5
0.001
264
);
--skeleton-base
:
oklch
(
0.94
8
0.004
264
);
--skeleton-highlight
:
oklch
(
0.98
8
0.002
264
);
}
.dark
{
--background
:
oklch
(
0.1
1
0
0
);
--foreground
:
oklch
(
0.9
84
0.003
247.858
);
--card
:
oklch
(
0.
13
0
0
);
--card-foreground
:
oklch
(
0.9
84
0.003
247.858
);
--popover
:
oklch
(
0.
15
0
0
);
--popover-foreground
:
oklch
(
0.9
84
0.003
247.858
);
--primary
:
oklch
(
0.
929
0.013
255.508
);
--primary-foreground
:
oklch
(
0.2
08
0.042
265.755
);
--secondary
:
oklch
(
0.2
0
0
);
--secondary-foreground
:
oklch
(
0.9
84
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.9
84
0.003
247.858
);
--background
:
oklch
(
0.1
65
0.012
258
);
--foreground
:
oklch
(
0.9
2
0.008
247.858
);
--card
:
oklch
(
0.
205
0.012
258
);
--card-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--popover
:
oklch
(
0.
225
0.014
258
);
--popover-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--primary
:
oklch
(
0.
87
0.018
255.508
);
--primary-foreground
:
oklch
(
0.2
35
0.042
265.755
);
--secondary
:
oklch
(
0.2
55
0.012
258
);
--secondary-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--muted
:
oklch
(
0.2
45
0.012
258
);
--muted-foreground
:
oklch
(
0.
68
0.014
257.417
);
--accent
:
oklch
(
0.2
65
0.014
258
);
--accent-foreground
:
oklch
(
0.9
2
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.9
85
0
0
);
--sidebar
:
oklch
(
0.
185
0.012
258
);
--sidebar-foreground
:
oklch
(
0.9
2
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.2
69
0
0
);
--sidebar-accent-foreground
:
oklch
(
0.9
85
0
0
);
--sidebar-border
:
oklch
(
1
0
0
/
10%
);
--sidebar-ring
:
oklch
(
0.5
56
0
0
);
--skeleton-base
:
oklch
(
0.
18
0
0
);
--skeleton-highlight
:
oklch
(
0.
26
0
0
);
--sidebar-accent
:
oklch
(
0.2
55
0.012
258
);
--sidebar-accent-foreground
:
oklch
(
0.9
2
0.008
247.858
);
--sidebar-border
:
oklch
(
0.3
0.014
258
);
--sidebar-ring
:
oklch
(
0.5
2
0.02
256.788
);
--skeleton-base
:
oklch
(
0.
245
0.01
258
);
--skeleton-highlight
:
oklch
(
0.
32
0.014
258
);
}
@theme
inline
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment