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
dc245ae7
authored
May 26, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(web): improve channel and usage log UI
Fixes #5121
parent
f8add4ca
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
177 additions
and
116 deletions
+177
-116
web/default/src/components/status-badge.tsx
+1
-1
web/default/src/components/ui/select.tsx
+31
-24
web/default/src/components/ui/sidebar.tsx
+13
-10
web/default/src/features/channels/components/channels-columns.tsx
+23
-13
web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
+5
-13
web/default/src/features/dashboard/components/overview/overview-dashboard.tsx
+50
-35
web/default/src/features/usage-logs/components/columns/column-helpers.tsx
+1
-1
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
+29
-11
web/default/src/features/usage-logs/components/columns/drawing-logs-columns.tsx
+2
-2
web/default/src/features/usage-logs/components/columns/task-logs-columns.tsx
+3
-2
web/default/src/features/usage-logs/components/model-badge.tsx
+3
-3
web/default/src/features/usage-logs/components/usage-logs-mobile-card.tsx
+0
-0
web/default/src/features/usage-logs/components/usage-logs-table.tsx
+8
-0
web/default/src/i18n/locales/en.json
+1
-0
web/default/src/i18n/locales/fr.json
+1
-0
web/default/src/i18n/locales/ja.json
+1
-0
web/default/src/i18n/locales/ru.json
+1
-0
web/default/src/i18n/locales/vi.json
+1
-0
web/default/src/i18n/locales/zh.json
+1
-0
web/default/src/i18n/static-keys.ts
+1
-0
web/default/src/lib/currency.ts
+1
-1
No files found.
web/default/src/components/status-badge.tsx
View file @
dc245ae7
...
...
@@ -131,7 +131,7 @@ export function StatusBadge({
return
(
<
span
className=
{
cn
(
'inline-flex w-fit max-w-full shrink-0 items-center rounded-
ful
l font-medium tracking-normal whitespace-nowrap transition-colors'
,
'inline-flex w-fit max-w-full shrink-0 items-center rounded-
4x
l font-medium tracking-normal whitespace-nowrap transition-colors'
,
sizeMap
[
size
??
'sm'
],
textColorMap
[
computedVariant
],
pulse
&&
'animate-pulse'
,
...
...
web/default/src/components/ui/select.tsx
View file @
dc245ae7
...
...
@@ -19,6 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
'use client'
import
*
as
React
from
'react'
import
{
useMediaQuery
}
from
'@/hooks'
import
{
Select
as
SelectPrimitive
}
from
'@base-ui/react/select'
import
{
UnfoldMoreIcon
,
...
...
@@ -97,32 +98,38 @@ function SelectContent({
SelectPrimitive
.
Positioner
.
Props
,
'align'
|
'alignOffset'
|
'side'
|
'sideOffset'
|
'alignItemWithTrigger'
>
)
{
return
(
<
SelectPrimitive
.
Portal
>
<
SelectPrimitive
.
Positioner
side=
{
side
}
sideOffset=
{
sideOffset
}
align=
{
align
}
alignOffset=
{
alignOffset
}
alignItemWithTrigger=
{
alignItemWithTrigger
}
className=
'isolate z-50'
const
isMobile
=
useMediaQuery
(
'(max-width: 640px)'
)
const
content
=
(
<
SelectPrimitive
.
Positioner
side=
{
side
}
sideOffset=
{
sideOffset
}
align=
{
align
}
alignOffset=
{
alignOffset
}
alignItemWithTrigger=
{
alignItemWithTrigger
}
className=
'isolate z-50'
>
<
SelectPrimitive
.
Popup
data
-
slot=
'select-content'
data
-
align
-
trigger=
{
alignItemWithTrigger
}
className=
{
cn
(
'bg-popover text-popover-foreground ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none'
,
className
)
}
{
...
props
}
>
<
SelectPrimitive
.
Popup
data
-
slot=
'select-content'
data
-
align
-
trigger=
{
alignItemWithTrigger
}
className=
{
cn
(
'bg-popover text-popover-foreground ring-foreground/10 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-md ring-1 duration-100 data-[align-trigger=true]:animate-none'
,
className
)
}
{
...
props
}
>
<
SelectScrollUpButton
/>
<
SelectPrimitive
.
List
>
{
children
}
</
SelectPrimitive
.
List
>
<
SelectScrollDownButton
/>
</
SelectPrimitive
.
Popup
>
</
SelectPrimitive
.
Positioner
>
</
SelectPrimitive
.
Portal
>
<
SelectScrollUpButton
/>
<
SelectPrimitive
.
List
>
{
children
}
</
SelectPrimitive
.
List
>
<
SelectScrollDownButton
/>
</
SelectPrimitive
.
Popup
>
</
SelectPrimitive
.
Positioner
>
)
if
(
isMobile
)
{
return
content
}
return
<
SelectPrimitive
.
Portal
>
{
content
}
</
SelectPrimitive
.
Portal
>
}
function
SelectLabel
({
...
...
web/default/src/components/ui/sidebar.tsx
View file @
dc245ae7
...
...
@@ -40,6 +40,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import
{
Tooltip
,
TooltipContent
,
TooltipProvider
,
TooltipTrigger
,
}
from
'@/components/ui/tooltip'
...
...
@@ -418,7 +419,7 @@ function SidebarGroupLabel({
props
:
mergeProps
<
'div'
>
(
{
className
:
cn
(
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0'
,
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:
pointer-events-none group-data-[collapsible=icon]:
-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0'
,
className
),
},
...
...
@@ -556,15 +557,17 @@ function SidebarMenuButton({
}
return
(
<
Tooltip
>
{
comp
}
<
TooltipContent
side=
'right'
align=
'center'
hidden=
{
state
!==
'collapsed'
||
isMobile
}
{
...
tooltip
}
/>
</
Tooltip
>
<
TooltipProvider
delay=
{
0
}
>
<
Tooltip
>
{
comp
}
<
TooltipContent
side=
'right'
align=
'center'
hidden=
{
state
!==
'collapsed'
||
isMobile
}
{
...
tooltip
}
/>
</
Tooltip
>
</
TooltipProvider
>
)
}
...
...
web/default/src/features/channels/components/channels-columns.tsx
View file @
dc245ae7
...
...
@@ -26,6 +26,7 @@ import {
ChevronRight
,
ListOrdered
,
Shuffle
,
SlidersHorizontal
,
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
toast
}
from
'sonner'
...
...
@@ -301,15 +302,18 @@ function BalanceCell({ channel }: { channel: Channel }) {
const
usedDisplay
=
withSuffix
(
formatQuotaValue
(
usedQuota
))
const
remainingDisplay
=
withSuffix
(
formatBalance
(
balance
))
const
usedLabel
=
`
${
t
(
'Used:'
)}
${
usedDisplay
}
`
const
remainingLabel
=
`
${
t
(
'Remaining:'
)}
${
remainingDisplay
}
`
// Tag row: only show cumulative used quota
if
(
isTagRow
)
{
return
(
<
StatusBadge
label=
{
`Used: ${usedDisplay}`
}
label=
{
usedLabel
}
variant=
'neutral'
size=
'sm'
copyable=
{
false
}
showDot=
{
false
}
/>
)
}
...
...
@@ -354,14 +358,13 @@ function BalanceCell({ channel }: { channel: Channel }) {
variant=
'neutral'
size=
'sm'
copyable=
{
false
}
showDot=
{
false
}
className=
'cursor-help'
/>
}
/>
<
TooltipContent
>
<
p
>
{
t
(
'Used:'
)
}
{
usedDisplay
}
</
p
>
<
p
>
{
usedLabel
}
</
p
>
</
TooltipContent
>
</
Tooltip
>
<
Tooltip
>
...
...
@@ -384,6 +387,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
}
size=
'sm'
copyable=
{
false
}
showDot=
{
false
}
className=
'cursor-pointer'
onClick=
{
handleClickUpdate
}
/>
...
...
@@ -393,7 +397,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
<
p
>
{
channel
.
type
===
57
?
t
(
'Click to view Codex usage'
)
:
`${t('Remaining:')} ${remainingDisplay}`
}
:
remainingLabel
}
</
p
>
{
channel
.
type
!==
57
&&
<
p
>
{
t
(
'Click to update balance'
)
}
</
p
>
}
</
TooltipContent
>
...
...
@@ -494,7 +498,6 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
const
isTagRow
=
isTagAggregateRow
(
row
.
original
)
const
name
=
row
.
getValue
(
'name'
)
as
string
const
channel
=
row
.
original
const
isMultiKey
=
isMultiKeyChannel
(
channel
)
// Tag row with expand/collapse
if
(
isTagRow
)
{
...
...
@@ -531,6 +534,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
// Regular channel row
const
settings
=
parseChannelSettings
(
channel
.
setting
)
const
isPassThrough
=
settings
.
pass_through_body_enabled
===
true
const
hasParamOverride
=
Boolean
(
channel
.
param_override
?.
trim
())
return
(
<
div
className=
'flex items-center gap-2'
>
...
...
@@ -557,13 +561,19 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
</
Tooltip
>
</
TooltipProvider
>
)
}
{
isMultiKey
&&
(
<
StatusBadge
label=
{
`${channel.channel_info.multi_key_size} keys`
}
variant=
'purple'
size=
'sm'
copyable=
{
false
}
/>
{
hasParamOverride
&&
(
<
TooltipProvider
delay=
{
100
}
>
<
Tooltip
>
<
TooltipTrigger
render=
{
<
SlidersHorizontal
className=
'text-info h-3.5 w-3.5 flex-shrink-0'
/>
}
></
TooltipTrigger
>
<
TooltipContent
side=
'top'
>
{
t
(
'Override request parameters'
)
}
</
TooltipContent
>
</
Tooltip
>
</
TooltipProvider
>
)
}
<
UpstreamUpdateTags
channel=
{
channel
}
/>
</
div
>
...
...
web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
View file @
dc245ae7
...
...
@@ -2754,23 +2754,15 @@ export function ChannelMutateDrawer({
</
div
>
</
div
>
<
FormControl
>
<
JsonEditor
<
Textarea
value=
{
field
.
value
||
''
}
onChange=
{
field
.
onChange
}
disabled=
{
isSubmitting
}
keyPlaceholder=
'temperature'
valuePlaceholder=
'0.7'
keyLabel=
'Parameter'
valueLabel=
'Value'
emptyMessage=
{
t
(
'No parameter overrides configured.'
rows=
{
8
}
placeholder=
{
t
(
'Override request parameters. Cannot override stream parameter.'
)
}
template=
{
{
temperature
:
0.7
,
max_tokens
:
2000
,
top_p
:
1
,
}
}
valueType=
'any'
className=
'max-h-72 min-h-40 resize-y overflow-auto font-mono text-xs'
/>
</
FormControl
>
<
FormMessage
/>
...
...
web/default/src/features/dashboard/components/overview/overview-dashboard.tsx
View file @
dc245ae7
...
...
@@ -26,6 +26,7 @@ import {
ChevronDown
,
ChevronUp
,
Circle
,
Copy
,
CreditCard
,
FileText
,
KeyRound
,
...
...
@@ -38,13 +39,14 @@ import {
}
from
'lucide-react'
import
{
motion
,
useReducedMotion
}
from
'motion/react'
import
{
useTranslation
}
from
'react-i18next'
import
{
toast
}
from
'sonner'
import
{
useAuthStore
}
from
'@/stores/auth-store'
import
{
getUserModels
}
from
'@/lib/api'
import
{
MOTION_TRANSITION
}
from
'@/lib/motion'
import
{
ROLE
}
from
'@/lib/roles'
import
{
cn
}
from
'@/lib/utils'
import
{
useCopyToClipboard
}
from
'@/hooks/use-copy-to-clipboard'
import
{
Button
}
from
'@/components/ui/button'
import
{
CopyButton
}
from
'@/components/copy-button'
import
{
CardStaggerContainer
,
CardStaggerItem
,
...
...
@@ -104,8 +106,8 @@ interface RequestExample {
endpoint
:
string
model
:
string
keyName
:
string
keyId
?:
number
displayKey
:
string
curl
:
string
ready
:
boolean
}
...
...
@@ -179,7 +181,7 @@ function SetupGuideBackdrop(props: { compact?: boolean }) {
<>
<
div
className=
{
cn
(
'pointer-events-none absolute inset-0 bg-[
linear-gradient(112deg,oklch(0.97_0.04_250/.92)_0%,oklch(0.95_0.08_315/.82)_38%,oklch(0.96_0.12_92/.78)_74%,oklch(0.94_0.1_132/.62)_100%)] dark:opacity-2
5'
,
'pointer-events-none absolute inset-0 bg-[
radial-gradient(ellipse_48%_120%_at_78%_0%,color-mix(in_oklch,var(--primary)_8%,transparent)_0%,transparent_62%),linear-gradient(112deg,color-mix(in_oklch,var(--card)_98%,var(--primary)_2%)_0%,color-mix(in_oklch,var(--card)_94%,var(--muted)_6%)_48%,color-mix(in_oklch,var(--background)_92%,var(--accent)_8%)_100%)] dark:opacity-6
5'
,
props
.
compact
?
'[mask-image:linear-gradient(90deg,black_0%,black_48%,transparent_74%)] opacity-55'
:
'opacity-85'
...
...
@@ -188,7 +190,7 @@ function SetupGuideBackdrop(props: { compact?: boolean }) {
/>
<
div
className=
{
cn
(
'
pointer-events-none absolute inset-y-0 right-0 hidden overflow-hidden font-mono text-lime-100/75 sm:block dark:text-lime-200/25
'
,
'
text-foreground/5 pointer-events-none absolute inset-y-0 right-0 hidden overflow-hidden font-mono sm:block dark:text-foreground/8
'
,
props
.
compact
?
'w-1/2 opacity-45'
:
'w-[58%] opacity-75'
)
}
aria
-
hidden=
'true'
...
...
@@ -275,12 +277,41 @@ function RequestPreview(props: {
})
{
const
{
t
}
=
useTranslation
()
const
shouldReduceMotion
=
useReducedMotion
()
const
previewLines
=
props
.
example
.
curl
.
split
(
'\n'
).
map
((
line
)
=>
{
if
(
line
.
includes
(
'Authorization: Bearer'
))
{
return
` -H "Authorization: Bearer
${
props
.
example
.
displayKey
}
" \\`
}
return
line
const
[
isCopying
,
setIsCopying
]
=
useState
(
false
)
const
{
copyToClipboard
}
=
useCopyToClipboard
({
notify
:
false
})
const
previewCurl
=
buildCurlCommand
({
endpoint
:
props
.
example
.
endpoint
,
apiKey
:
props
.
example
.
displayKey
,
model
:
props
.
example
.
model
,
})
const
previewLines
=
previewCurl
.
split
(
'\n'
)
const
handleCopyRequest
=
async
()
=>
{
if
(
!
props
.
example
.
keyId
||
isCopying
)
return
setIsCopying
(
true
)
try
{
const
result
=
await
fetchTokenKey
(
props
.
example
.
keyId
)
const
key
=
result
.
success
&&
result
.
data
?.
key
?
result
.
data
.
key
:
''
if
(
!
key
)
{
toast
.
error
(
result
.
message
||
t
(
'Failed to copy to clipboard'
))
return
}
const
realCurl
=
buildCurlCommand
({
endpoint
:
props
.
example
.
endpoint
,
apiKey
:
`sk-
${
key
}
`
,
model
:
props
.
example
.
model
,
})
const
copied
=
await
copyToClipboard
(
realCurl
)
if
(
copied
)
{
toast
.
success
(
t
(
'Copied to clipboard'
))
}
else
{
toast
.
error
(
t
(
'Failed to copy to clipboard'
))
}
}
finally
{
setIsCopying
(
false
)
}
}
return
(
<
motion
.
div
...
...
@@ -315,17 +346,17 @@ function RequestPreview(props: {
</
div
>
</
div
>
{
props
.
example
.
ready
?
(
<
CopyButton
value=
{
props
.
example
.
curl
}
<
Button
variant=
'outline'
size=
'sm'
className=
'h-7 gap-1.5 px-2 text-xs'
tooltip=
{
t
(
'Copy ready-to-run curl'
)
}
successTooltip=
{
t
(
'Copied!'
)
}
disabled=
{
isCopying
}
onClick=
{
handleCopyRequest
}
aria
-
label=
{
t
(
'Copy ready-to-run curl'
)
}
>
{
t
(
'Copy'
)
}
</
CopyButton
>
<
Copy
data
-
icon=
'inline-start'
/>
{
isCopying
?
t
(
'Loading'
)
:
t
(
'Copy'
)
}
</
Button
>
)
:
(
<
Button
size=
'sm'
variant=
'outline'
render=
{
<
Link
to=
'/keys'
/>
}
>
{
t
(
'Create API Key'
)
}
...
...
@@ -463,17 +494,6 @@ export function OverviewDashboard() {
[
apiKeysQuery
.
data
]
)
const
realKeyQuery
=
useQuery
({
queryKey
:
[
'dashboard'
,
'overview'
,
'token-key'
,
preferredKey
?.
id
],
queryFn
:
async
()
=>
{
if
(
!
preferredKey
?.
id
)
return
''
const
result
=
await
fetchTokenKey
(
preferredKey
.
id
)
return
result
.
success
&&
result
.
data
?.
key
?
`sk-
${
result
.
data
.
key
}
`
:
''
},
enabled
:
Boolean
(
preferredKey
?.
id
),
staleTime
:
5
*
60
*
1000
,
})
const
startSteps
=
useMemo
<
StartStep
[]
>
(
()
=>
[
{
...
...
@@ -561,23 +581,18 @@ export function OverviewDashboard() {
const
requestExample
=
useMemo
<
RequestExample
>
(()
=>
{
const
endpoint
=
normalizeEndpoint
(
apiInfoItems
[
0
]?.
url
)
const
model
=
modelsQuery
.
data
?.[
0
]
??
'gpt-4o-mini'
const
apiKey
=
realKeyQuery
.
data
??
''
const
keyName
=
preferredKey
?.
name
??
t
(
'No API key yet'
)
const
ready
=
Boolean
(
apiKey
&&
model
)
const
ready
=
Boolean
(
preferredKey
?.
id
&&
model
)
return
{
endpoint
,
model
,
keyName
,
displayKey
:
formatDisplayKey
(
apiKey
),
keyId
:
preferredKey
?.
id
,
displayKey
:
preferredKey
?
formatDisplayKey
(
`sk-
${
preferredKey
.
key
}
`
)
:
'sk-...'
,
ready
,
curl
:
buildCurlCommand
({
endpoint
,
apiKey
:
apiKey
||
'sk-...'
,
model
,
}),
}
},
[
apiInfoItems
,
modelsQuery
.
data
,
preferredKey
,
realKeyQuery
.
data
,
t
])
},
[
apiInfoItems
,
modelsQuery
.
data
,
preferredKey
,
t
])
const
completedStepCount
=
startSteps
.
filter
((
step
)
=>
step
.
completed
).
length
const
setupComplete
=
completedStepCount
===
startSteps
.
length
...
...
web/default/src/features/usage-logs/components/columns/column-helpers.tsx
View file @
dc245ae7
...
...
@@ -150,7 +150,7 @@ export function createDurationColumn<T>(config: {
variant=
{
variant
}
size=
'sm'
copyable=
{
false
}
className=
{
cn
(
'font-mono'
,
durationBgMap
[
variant
])
}
className=
{
cn
(
'
rounded-md
font-mono'
,
durationBgMap
[
variant
])
}
/>
)
},
...
...
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
View file @
dc245ae7
...
...
@@ -90,6 +90,12 @@ function getGroupRatioText(other: LogOtherData | null): string | null {
return
null
}
function
splitQuotaDisplay
(
value
:
string
):
{
prefix
:
string
;
amount
:
string
}
{
const
match
=
value
.
match
(
/^
([^
0-9+
\-
.,
\s]
+
)(
.+
)
$/
)
if
(
!
match
)
return
{
prefix
:
''
,
amount
:
value
}
return
{
prefix
:
match
[
1
],
amount
:
match
[
2
]
}
}
function
buildDetailSegments
(
log
:
UsageLog
,
other
:
LogOtherData
|
null
,
...
...
@@ -277,6 +283,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
variant={config.color as StatusBadgeProps['variant']}
size='sm'
copyable={false}
className='!text-xs [&_span]:!text-xs'
/>
</div>
)
...
...
@@ -295,6 +302,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
columns.push(
{
id: 'channel',
accessorFn: (row) => row.channel,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Channel')} />
),
...
...
@@ -395,10 +403,11 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
</TooltipProvider>
)
},
meta: { label: t('Channel')
, mobileHidden: true
},
meta: { label: t('Channel') },
},
{
id: 'user',
accessorFn: (row) => row.username,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('User')} />
),
...
...
@@ -419,7 +428,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
setUserInfoDialogOpen(true)
}}
>
<Avatar className='ring-border/60 size-6 ring-1'>
<Avatar className='ring-border/60 size-6 ring-1
max-sm:hidden
'>
<AvatarFallback
className={cn(
'text-[11px] font-semibold',
...
...
@@ -451,7 +460,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
</button>
)
},
meta: { label: t('User')
, mobileHidden: true
},
meta: { label: t('User') },
}
)
}
...
...
@@ -555,7 +564,9 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
? log.completion_tokens / useTime
: null
const timeVariant = getResponseTimeColor(useTime, log.completion_tokens)
const frtVariant = frt ? getFirstResponseTimeColor(frt / 1000) : 'neutral'
const frtVariant = frt
? getFirstResponseTimeColor(frt / 1000)
: 'neutral'
const timingBgMap: Record<string, string> = {
success:
...
...
@@ -576,7 +587,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
variant={timeVariant as StatusBadgeProps['variant']}
size='sm'
copyable={false}
className={cn('font-mono', timingBgMap[timeVariant])}
className={cn('
rounded-md
font-mono', timingBgMap[timeVariant])}
/>
{log.is_stream &&
(frt != null && frt > 0 ? (
...
...
@@ -586,7 +597,10 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
size='sm'
showDot={false}
copyable={false}
className={cn('font-mono', timingBgMap[frtVariant])}
className={cn(
'rounded-md font-mono',
timingBgMap[frtVariant]
)}
/>
) : (
<StatusBadge
...
...
@@ -595,7 +609,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
size='sm'
showDot={false}
copyable={false}
className={
timingBgMap.neutral
}
className={
cn('rounded-md font-mono', timingBgMap.neutral)
}
/>
))}
</div>
...
...
@@ -641,7 +655,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
</div>
)
},
meta: { label: t('Timing')
, mobileHidden: true
},
meta: { label: t('Timing') },
},
{
...
...
@@ -692,7 +706,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
</div>
)
},
meta: { label: 'Tokens'
, mobileHidden: true
},
meta: { label: 'Tokens' },
},
{
...
...
@@ -734,11 +748,15 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
}
const quotaStr = formatLogQuota(quota)
const quotaDisplay = splitQuotaDisplay(quotaStr)
return (
<div className='flex flex-col gap-0.5'>
<span className='border-border/80 bg-muted/60 inline-flex w-fit items-center rounded-md border px-1.5 py-0.5 [font-family:var(--font-body)] font-semibold tabular-nums'>
{quotaStr}
<span className='border-border/80 bg-muted/60 inline-flex h-6 w-fit items-center rounded-md border px-2 text-sm leading-none [font-family:var(--font-body)] font-semibold tabular-nums'>
{quotaDisplay.prefix && (
<span className='mr-1'>{quotaDisplay.prefix}</span>
)}
<span>{quotaDisplay.amount}</span>
</span>
</div>
)
...
...
web/default/src/features/usage-logs/components/columns/drawing-logs-columns.tsx
View file @
dc245ae7
...
...
@@ -231,7 +231,7 @@ export function useDrawingLogsColumns(
</>
)
},
meta
:
{
label
:
t
(
'Image'
)
,
mobileHidden
:
true
},
meta
:
{
label
:
t
(
'Image'
)
},
},
{
accessorKey
:
'prompt'
,
...
...
@@ -268,7 +268,7 @@ export function useDrawingLogsColumns(
</>
)
},
meta
:
{
label
:
t
(
'Prompt'
)
,
mobileHidden
:
true
},
meta
:
{
label
:
t
(
'Prompt'
)
},
size
:
200
,
maxSize
:
220
,
},
...
...
web/default/src/features/usage-logs/components/columns/task-logs-columns.tsx
View file @
dc245ae7
...
...
@@ -123,6 +123,7 @@ export function useTaskLogsColumns(isAdmin: boolean): ColumnDef<TaskLog>[] {
if
(
isAdmin
)
{
columns
.
push
(
createChannelColumn
<
TaskLog
>
({
headerLabel
:
t
(
'Channel'
)
}),
{
id
:
'user'
,
accessorFn
:
(
row
)
=>
row
.
username
||
row
.
user_id
,
header
:
({
column
})
=>
(
<
DataTableColumnHeader
column=
{
column
}
title=
{
t
(
'User'
)
}
/>
),
...
...
@@ -142,7 +143,7 @@ export function useTaskLogsColumns(isAdmin: boolean): ColumnDef<TaskLog>[] {
setUserInfoDialogOpen
(
true
)
}
}
>
<
Avatar
className=
'ring-border/60 size-6 ring-1'
>
<
Avatar
className=
'ring-border/60 size-6 ring-1
max-sm:hidden
'
>
<
AvatarFallback
className=
{
cn
(
'text-[11px] font-semibold'
,
...
...
@@ -161,7 +162,7 @@ export function useTaskLogsColumns(isAdmin: boolean): ColumnDef<TaskLog>[] {
</
button
>
)
},
meta
:
{
label
:
t
(
'User'
)
,
mobileHidden
:
true
},
meta
:
{
label
:
t
(
'User'
)
},
})
}
...
...
web/default/src/features/usage-logs/components/model-badge.tsx
View file @
dc245ae7
...
...
@@ -101,12 +101,12 @@ function ModelBadgeContent(props: ModelBadgeProps) {
showDot=
{
!
provider
}
autoColor=
{
provider
?
undefined
:
props
.
modelName
}
className=
{
cn
(
'border-border/60 bg-muted/30 h-
auto min-h-6 gap-1.5 rounded-md border px-2 py-0.5 [font-family:var(--font-body)] break-all whitespace-normal
'
,
'border-border/60 bg-muted/30 h-
6 max-w-full gap-1.5 rounded-md border px-2 [font-family:var(--font-body)]
'
,
provider
&&
'text-foreground'
,
props
.
className
)
}
>
<
span
className=
'flex min-w-0 items-center gap-1.5'
>
<
span
className=
'flex m
ax-w-full m
in-w-0 items-center gap-1.5'
>
{
provider
&&
(
<
span
className=
'flex size-3.5 shrink-0 items-center justify-center'
...
...
@@ -116,7 +116,7 @@ function ModelBadgeContent(props: ModelBadgeProps) {
{
getLobeIcon
(
provider
.
icon
,
14
)
}
</
span
>
)
}
<
span
>
{
props
.
modelName
}
</
span
>
<
span
className=
'truncate'
>
{
props
.
modelName
}
</
span
>
</
span
>
</
StatusBadge
>
)
...
...
web/default/src/features/usage-logs/components/usage-logs-mobile-card.tsx
0 → 100644
View file @
dc245ae7
This diff is collapsed.
Click to expand it.
web/default/src/features/usage-logs/components/usage-logs-table.tsx
View file @
dc245ae7
...
...
@@ -47,6 +47,7 @@ import { fetchLogsByCategory } from '../lib/utils'
import
type
{
LogCategory
}
from
'../types'
import
{
CommonLogsFilterBar
}
from
'./common-logs-filter-bar'
import
{
TaskLogsFilterBar
}
from
'./task-logs-filter-bar'
import
{
UsageLogsMobileList
}
from
'./usage-logs-mobile-card'
const
route
=
getRouteApi
(
'/_authenticated/usage-logs/$section'
)
...
...
@@ -190,6 +191,13 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
'[&_[data-slot=table]]:text-[13px] [&_[data-slot=table]_td]:text-[13px] [&_[data-slot=table]_td_*]:text-[13px] [&_[data-slot=table]_th]:text-[13px] [&_[data-slot=table]_th_*]:text-[13px]'
)
}
tableHeaderClassName=
'bg-muted/30 sticky top-0 z-10'
mobile=
{
<
UsageLogsMobileList
table=
{
table
}
isLoading=
{
isLoadingData
}
logCategory=
{
logCategory
}
/>
}
toolbar=
{
isCommon
?
(
<
CommonLogsFilterBar
table=
{
table
}
/>
...
...
web/default/src/i18n/locales/en.json
View file @
dc245ae7
...
...
@@ -2778,6 +2778,7 @@
"Override auto-discovered endpoint"
:
"Override auto-discovered endpoint"
,
"Override request headers"
:
"Override request headers"
,
"Override request headers (JSON format)"
:
"Override request headers (JSON format)"
,
"Override request parameters"
:
"Override request parameters"
,
"Override request parameters (JSON format)"
:
"Override request parameters (JSON format)"
,
"Override request parameters. Cannot override"
:
"Override request parameters. Cannot override"
,
"Override request parameters. Cannot override stream parameter."
:
"Override request parameters. Cannot override stream parameter."
,
...
...
web/default/src/i18n/locales/fr.json
View file @
dc245ae7
...
...
@@ -2778,6 +2778,7 @@
"Override auto-discovered endpoint"
:
"Remplacer le point de terminaison auto-découvert"
,
"Override request headers"
:
"Remplacer les en-têtes de requête"
,
"Override request headers (JSON format)"
:
"Surcharge des en-têtes de requête (format JSON)"
,
"Override request parameters"
:
"Remplacer les paramètres de requête"
,
"Override request parameters (JSON format)"
:
"Remplacer les paramètres de requête (format JSON)"
,
"Override request parameters. Cannot override"
:
"Remplacer les paramètres de requête. Impossible de remplacer"
,
"Override request parameters. Cannot override stream parameter."
:
"Remplace les paramètres de requête. Impossible de remplacer le paramètre stream."
,
...
...
web/default/src/i18n/locales/ja.json
View file @
dc245ae7
...
...
@@ -2778,6 +2778,7 @@
"Override auto-discovered endpoint"
:
"自動検出されたエンドポイントを上書きする"
,
"Override request headers"
:
"リクエストヘッダーを上書きする"
,
"Override request headers (JSON format)"
:
"リクエストヘッダーのオーバーライド (JSON 形式)"
,
"Override request parameters"
:
"リクエストパラメータを上書き"
,
"Override request parameters (JSON format)"
:
"リクエストパラメータの上書き (JSON形式)"
,
"Override request parameters. Cannot override"
:
"リクエストパラメーターを上書きします。上書きできません"
,
"Override request parameters. Cannot override stream parameter."
:
"リクエストパラメータを上書きします。stream パラメータは上書きできません。"
,
...
...
web/default/src/i18n/locales/ru.json
View file @
dc245ae7
...
...
@@ -2778,6 +2778,7 @@
"Override auto-discovered endpoint"
:
"Переопределить автоматически обнаруженную конечную точку"
,
"Override request headers"
:
"Переопределить заголовки запроса"
,
"Override request headers (JSON format)"
:
"Переопределение заголовков запроса (формат JSON)"
,
"Override request parameters"
:
"Переопределить параметры запроса"
,
"Override request parameters (JSON format)"
:
"Переопределить параметры запроса (формат JSON)"
,
"Override request parameters. Cannot override"
:
"Переопределить параметры запроса. Невозможно переопределить"
,
"Override request parameters. Cannot override stream parameter."
:
"Переопределяет параметры запроса. Параметр stream переопределить нельзя."
,
...
...
web/default/src/i18n/locales/vi.json
View file @
dc245ae7
...
...
@@ -2778,6 +2778,7 @@
"Override auto-discovered endpoint"
:
"Ghi đè điểm cuối tự động phát hiện"
,
"Override request headers"
:
"Ghi đè tiêu đề yêu cầu"
,
"Override request headers (JSON format)"
:
"Ghi đè tiêu đề yêu cầu (định dạng JSON)"
,
"Override request parameters"
:
"Ghi đè tham số yêu cầu"
,
"Override request parameters (JSON format)"
:
"Ghi đè tham số yêu cầu (định dạng JSON)"
,
"Override request parameters. Cannot override"
:
"Ghi đè tham số yêu cầu. Không thể ghi đè"
,
"Override request parameters. Cannot override stream parameter."
:
"Ghi đè tham số yêu cầu. Không thể ghi đè tham số stream."
,
...
...
web/default/src/i18n/locales/zh.json
View file @
dc245ae7
...
...
@@ -2778,6 +2778,7 @@
"Override auto-discovered endpoint"
:
"覆盖自动发现的端点"
,
"Override request headers"
:
"覆盖请求标头"
,
"Override request headers (JSON format)"
:
"覆盖请求头(JSON 格式)"
,
"Override request parameters"
:
"覆盖请求参数"
,
"Override request parameters (JSON format)"
:
"覆盖请求参数 (JSON 格式)"
,
"Override request parameters. Cannot override"
:
"覆盖请求参数。无法覆盖"
,
"Override request parameters. Cannot override stream parameter."
:
"覆盖请求参数。无法覆盖 stream 参数。"
,
...
...
web/default/src/i18n/static-keys.ts
View file @
dc245ae7
...
...
@@ -315,6 +315,7 @@ export const STATIC_I18N_KEYS = [
'Regex Replace'
,
'Return Error'
,
'Param Override'
,
'Override request parameters'
,
// Profile / 2FA
'Backed up'
,
...
...
web/default/src/lib/currency.ts
View file @
dc245ae7
...
...
@@ -292,7 +292,7 @@ function formatCurrencyValue(
maximumFractionDigits
:
digits
,
}).
format
(
adjustedValue
)
return
`
${
meta
.
symbol
}${
decimal
}
`
return
`
${
meta
.
symbol
}
${
decimal
}
`
}
/**
...
...
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