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
fc377dae
authored
Apr 28, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
style: optimize UI
parent
df14a0bf
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
137 additions
and
341 deletions
+137
-341
web/default/src/features/pricing/components/dynamic-pricing-breakdown.tsx
+3
-3
web/default/src/features/profile/components/available-models-card.tsx
+0
-234
web/default/src/features/profile/index.tsx
+0
-5
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
+111
-83
web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
+23
-16
No files found.
web/default/src/features/pricing/components/dynamic-pricing-breakdown.tsx
View file @
fc377dae
...
...
@@ -176,7 +176,7 @@ export function DynamicPricingBreakdown({
if
(
!
hasTiers
&&
!
hasRules
)
{
return
(
<
section
className=
'py-4'
>
<
section
className=
'
min-w-0
py-4'
>
<
div
className=
'mb-3 flex items-center gap-2'
>
<
span
className=
'inline-flex size-6 items-center justify-center rounded-full bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'
>
<
TagIcon
className=
'size-3.5'
/>
...
...
@@ -201,7 +201,7 @@ export function DynamicPricingBreakdown({
})
return
(
<
section
className=
'py-4'
>
<
section
className=
'
min-w-0
py-4'
>
<
div
className=
'mb-4 flex items-start gap-2'
>
<
span
className=
'mt-0.5 inline-flex size-6 items-center justify-center rounded-full bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'
>
<
TagIcon
className=
'size-3.5'
/>
...
...
@@ -221,7 +221,7 @@ export function DynamicPricingBreakdown({
<
div
className=
'text-foreground mb-2 text-sm font-semibold'
>
{
t
(
'Tiered price table'
)
}
</
div
>
<
div
className=
'-mx-4
sm:mx-0
'
>
<
div
className=
'-mx-4
max-w-[calc(100%+2rem)] overflow-x-auto sm:mx-0 sm:max-w-full
'
>
<
Table
className=
'text-sm'
>
<
TableHeader
>
<
TableRow
className=
'hover:bg-transparent'
>
...
...
web/default/src/features/profile/components/available-models-card.tsx
deleted
100644 → 0
View file @
df14a0bf
import
{
useMemo
,
useState
}
from
'react'
import
{
useQuery
}
from
'@tanstack/react-query'
import
{
ChevronDown
,
ChevronUp
,
Copy
,
Settings
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
toast
}
from
'sonner'
import
{
api
}
from
'@/lib/api'
import
{
Button
}
from
'@/components/ui/button'
import
{
Card
,
CardContent
,
CardDescription
,
CardHeader
,
CardTitle
,
}
from
'@/components/ui/card'
import
{
Skeleton
}
from
'@/components/ui/skeleton'
import
{
Tabs
,
TabsList
,
TabsTrigger
}
from
'@/components/ui/tabs'
import
{
StatusBadge
}
from
'@/components/status-badge'
const
MODEL_CATEGORIES
=
[
{
key
:
'all'
,
label
:
'All'
,
filter
:
()
=>
true
},
{
key
:
'gpt'
,
label
:
'GPT'
,
filter
:
(
m
:
string
)
=>
/^
(
gpt|o
[
0-9
]
|chatgpt
)
/i
.
test
(
m
),
},
{
key
:
'claude'
,
label
:
'Claude'
,
filter
:
(
m
:
string
)
=>
/claude/i
.
test
(
m
)
},
{
key
:
'gemini'
,
label
:
'Gemini'
,
filter
:
(
m
:
string
)
=>
/gemini|gemma/i
.
test
(
m
),
},
{
key
:
'llama'
,
label
:
'Llama'
,
filter
:
(
m
:
string
)
=>
/llama/i
.
test
(
m
)
},
{
key
:
'mistral'
,
label
:
'Mistral'
,
filter
:
(
m
:
string
)
=>
/mistral|mixtral/i
.
test
(
m
),
},
{
key
:
'deepseek'
,
label
:
'DeepSeek'
,
filter
:
(
m
:
string
)
=>
/deepseek/i
.
test
(
m
),
},
{
key
:
'qwen'
,
label
:
'Qwen'
,
filter
:
(
m
:
string
)
=>
/qwen/i
.
test
(
m
)
},
{
key
:
'embedding'
,
label
:
'Embedding'
,
filter
:
(
m
:
string
)
=>
/embed/i
.
test
(
m
),
},
{
key
:
'image'
,
label
:
'Image'
,
filter
:
(
m
:
string
)
=>
/dall-e|stable-diffusion|midjourney|sd
[
x3
]
|flux|imagen/i
.
test
(
m
),
},
{
key
:
'tts'
,
label
:
'TTS'
,
filter
:
(
m
:
string
)
=>
/tts|whisper|speech/i
.
test
(
m
),
},
]
as
const
const
MODELS_DISPLAY_COUNT
=
25
export
function
AvailableModelsCard
()
{
const
{
t
}
=
useTranslation
()
const
[
activeCategory
,
setActiveCategory
]
=
useState
(
'all'
)
const
[
isExpanded
,
setIsExpanded
]
=
useState
(()
=>
{
try
{
return
JSON
.
parse
(
localStorage
.
getItem
(
'modelsExpanded'
)
??
'false'
)
}
catch
{
return
false
}
})
const
{
data
:
models
=
[],
isLoading
}
=
useQuery
({
queryKey
:
[
'user-available-models'
],
queryFn
:
async
()
=>
{
const
res
=
await
api
.
get
(
'/api/user/models'
)
if
(
!
res
.
data
.
success
||
!
Array
.
isArray
(
res
.
data
.
data
))
return
[]
return
res
.
data
.
data
as
string
[]
},
staleTime
:
5
*
60
*
1000
,
})
const
toggleExpand
=
(
val
:
boolean
)
=>
{
setIsExpanded
(
val
)
localStorage
.
setItem
(
'modelsExpanded'
,
JSON
.
stringify
(
val
))
}
const
copyModel
=
(
model
:
string
)
=>
{
navigator
.
clipboard
.
writeText
(
model
)
toast
.
success
(
t
(
'Copied: {{model}}'
,
{
model
}))
}
const
categoriesWithCounts
=
useMemo
(
()
=>
MODEL_CATEGORIES
.
map
((
cat
)
=>
({
...
cat
,
count
:
cat
.
key
===
'all'
?
models
.
length
:
models
.
filter
(
cat
.
filter
).
length
,
})).
filter
((
cat
)
=>
cat
.
key
===
'all'
||
cat
.
count
>
0
),
[
models
]
)
const
filteredModels
=
useMemo
(()
=>
{
const
cat
=
MODEL_CATEGORIES
.
find
((
c
)
=>
c
.
key
===
activeCategory
)
if
(
!
cat
||
cat
.
key
===
'all'
)
return
models
return
models
.
filter
(
cat
.
filter
)
},
[
models
,
activeCategory
])
if
(
isLoading
)
{
return
(
<
Card
>
<
CardHeader
>
<
CardTitle
className=
'flex items-center gap-2'
>
<
Settings
className=
'h-4 w-4'
/>
{
t
(
'Available Models'
)
}
</
CardTitle
>
<
CardDescription
>
{
t
(
'View all currently available models'
)
}
</
CardDescription
>
</
CardHeader
>
<
CardContent
>
<
div
className=
'flex flex-wrap gap-2'
>
{
Array
.
from
({
length
:
12
}).
map
((
_
,
i
)
=>
(
<
Skeleton
key=
{
i
}
className=
'h-7 w-24 rounded-full'
/>
))
}
</
div
>
</
CardContent
>
</
Card
>
)
}
if
(
models
.
length
===
0
)
{
return
(
<
Card
>
<
CardHeader
>
<
CardTitle
className=
'flex items-center gap-2'
>
<
Settings
className=
'h-4 w-4'
/>
{
t
(
'Available Models'
)
}
</
CardTitle
>
</
CardHeader
>
<
CardContent
>
<
p
className=
'text-muted-foreground text-sm'
>
{
t
(
'No available models'
)
}
</
p
>
</
CardContent
>
</
Card
>
)
}
const
needsExpand
=
filteredModels
.
length
>
MODELS_DISPLAY_COUNT
const
displayModels
=
needsExpand
&&
!
isExpanded
?
filteredModels
.
slice
(
0
,
MODELS_DISPLAY_COUNT
)
:
filteredModels
return
(
<
Card
>
<
CardHeader
>
<
CardTitle
className=
'flex items-center gap-2'
>
<
Settings
className=
'h-4 w-4'
/>
{
t
(
'Available Models'
)
}
</
CardTitle
>
<
CardDescription
>
{
t
(
'View all currently available models'
)
}
·
{
models
.
length
}{
' '
}
{
t
(
'models'
)
}
</
CardDescription
>
</
CardHeader
>
<
CardContent
className=
'space-y-4'
>
<
Tabs
value=
{
activeCategory
}
onValueChange=
{
setActiveCategory
}
>
<
TabsList
className=
'h-auto flex-wrap'
>
{
categoriesWithCounts
.
map
((
cat
)
=>
(
<
TabsTrigger
key=
{
cat
.
key
}
value=
{
cat
.
key
}
className=
'text-xs'
>
{
cat
.
label
}
<
StatusBadge
label=
{
String
(
cat
.
count
)
}
variant=
{
activeCategory
===
cat
.
key
?
'info'
:
'neutral'
}
className=
'ml-1'
copyable=
{
false
}
/>
</
TabsTrigger
>
))
}
</
TabsList
>
</
Tabs
>
<
div
className=
'flex flex-wrap gap-1.5'
>
{
displayModels
.
map
((
model
)
=>
(
<
StatusBadge
key=
{
model
}
variant=
'neutral'
className=
'cursor-pointer font-normal transition-opacity hover:opacity-70'
onClick=
{
()
=>
copyModel
(
model
)
}
copyable=
{
false
}
>
<
Copy
className=
'h-2.5 w-2.5 opacity-50'
/>
{
model
}
</
StatusBadge
>
))
}
{
needsExpand
&&
!
isExpanded
&&
(
<
Button
variant=
'ghost'
size=
'sm'
className=
'h-6 gap-1 text-xs'
onClick=
{
()
=>
toggleExpand
(
true
)
}
>
<
ChevronDown
className=
'h-3 w-3'
/>
{
t
(
'More'
)
}
{
filteredModels
.
length
-
MODELS_DISPLAY_COUNT
}{
' '
}
{
t
(
'models'
)
}
</
Button
>
)
}
{
needsExpand
&&
isExpanded
&&
(
<
Button
variant=
'ghost'
size=
'sm'
className=
'h-6 gap-1 text-xs'
onClick=
{
()
=>
toggleExpand
(
false
)
}
>
<
ChevronUp
className=
'h-3 w-3'
/>
{
t
(
'Collapse'
)
}
</
Button
>
)
}
</
div
>
{
filteredModels
.
length
===
0
&&
(
<
p
className=
'text-muted-foreground py-4 text-center text-sm'
>
{
t
(
'No models available in this category'
)
}
</
p
>
)
}
</
CardContent
>
</
Card
>
)
}
web/default/src/features/profile/index.tsx
View file @
fc377dae
...
...
@@ -5,7 +5,6 @@ import {
CardStaggerContainer
,
CardStaggerItem
,
}
from
'@/components/page-transition'
import
{
AvailableModelsCard
}
from
'./components/available-models-card'
import
{
CheckinCalendarCard
}
from
'./components/checkin-calendar-card'
import
{
PasskeyCard
}
from
'./components/passkey-card'
import
{
ProfileHeader
}
from
'./components/profile-header'
...
...
@@ -38,10 +37,6 @@ export function Profile() {
</
CardStaggerItem
>
<
CardStaggerItem
>
<
AvailableModelsCard
/>
</
CardStaggerItem
>
<
CardStaggerItem
>
<
div
className=
'grid gap-6 lg:grid-cols-2 lg:items-start'
>
<
div
className=
'space-y-6'
>
<
ProfileSecurityCard
profile=
{
profile
}
loading=
{
loading
}
/>
...
...
web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
View file @
fc377dae
...
...
@@ -33,6 +33,7 @@ import {
getTimeColor
,
formatModelName
,
getTieredBillingSummary
,
hasAnyCacheTokens
,
parseLogOther
,
isViolationFeeLog
,
}
from
'../../lib/format'
...
...
@@ -45,7 +46,6 @@ import {
import
type
{
LogOtherData
}
from
'../../types'
import
{
DetailsDialog
}
from
'../dialogs/details-dialog'
import
{
useUsageLogsContext
}
from
'../usage-logs-provider'
import
{
CacheTooltip
}
from
'./column-helpers'
interface
DetailSegment
{
text
:
string
...
...
@@ -90,33 +90,60 @@ function buildDetailSegments(
const segments: DetailSegment[] = []
const userGroupRatio = other.user_group_ratio
const groupRatio = other.group_ratio
const isUserGroup =
userGroupRatio != null &&
Number.isFinite(userGroupRatio) &&
userGroupRatio !== -1
const effectiveRatio = isUserGroup ? userGroupRatio : groupRatio
const ratioLabel = isUserGroup ? t('User Exclusive Ratio') : t('Group Ratio')
if (effectiveRatio != null && Number.isFinite(effectiveRatio)) {
segments.push({
text: `
$
{
ratioLabel
}
$
{
formatRatioCompact
(
effectiveRatio
)}
x
`,
})
}
const priceOpts = { digitsLarge: 4, digitsSmall: 6, abbreviate: false }
const formatPrice = (price: number) =>
`
$
{
formatBillingCurrencyFromUSD
(
price
,
priceOpts
)}
/M
`
const
formatPriceCompact
=
(
price
:
number
)
=>
formatBillingCurrencyFromUSD
(
price
,
priceOpts
)
const
formatPriceList
=
(
prices
:
string
[],
showUnit
:
boolean
)
=>
{
const
text
=
prices
.
join
(
' / '
)
return
showUnit
?
`
${
text
}
/M`
:
text
}
const
tieredSummary
=
getTieredBillingSummary
(
other
)
if
(
tieredSummary
)
{
if (tieredSummary.tier.label) {
const
baseEntries
=
tieredSummary
.
priceEntries
.
filter
((
entry
)
=>
[
'inputPrice'
,
'outputPrice'
].
includes
(
entry
.
field
))
.
map
((
entry
)
=>
formatPriceCompact
(
entry
.
price
))
if
(
baseEntries
.
length
>
0
)
{
const
tierLabel
=
tieredSummary
.
tier
.
label
||
t
(
'Default'
)
segments
.
push
({
text: `
$
{
t
(
'Tier'
)}
$
{
tieredSummary
.
tier
.
label
}
`,
text
:
`
${
tierLabel
}
·
${
formatPriceList
(
baseEntries
,
true
)}
`
,
})
}
const cacheEntries = tieredSummary.priceEntries
.filter((entry) =>
[
'cacheReadPrice',
'cacheCreatePrice',
'cacheCreate1hPrice',
].includes(entry.field)
)
.map((entry) => {
return formatPriceCompact(entry.price)
})
if (cacheEntries.length > 0) {
segments.push({
text: `
$
{
t
(
'Cache'
)}
$
{
formatPriceList
(
cacheEntries
,
false
)}
`,
muted: true,
})
}
for (const entry of tieredSummary.priceEntries) {
const otherEntries = tieredSummary.priceEntries
.filter(
(entry) =>
![
'inputPrice',
'outputPrice',
'cacheReadPrice',
'cacheCreatePrice',
'cacheCreate1hPrice',
].includes(entry.field)
)
.map((entry) => `
$
{
t
(
entry
.
shortLabel
)}
$
{
formatPrice
(
entry
.
price
)}
`)
if (otherEntries.length > 0) {
segments.push({
text:
`
$
{
t
(
entry
.
shortLabel
)}
$
{
formatBillingCurrencyFromUSD
(
entry
.
price
,
priceOpts
)}
/M`
,
text:
otherEntries.join(' · ')
,
muted: true,
})
}
...
...
@@ -124,15 +151,59 @@ function buildDetailSegments(
const isPerCall = isPerCallBilling(other.model_price)
if (isPerCall) {
segments.push({
text
:
`
${
t
(
'Model Price'
)}
${
formatBillingCurrencyFromUSD
(
other
.
model_price
!
,
priceOpts
)}
`
,
muted
:
true
,
text: `
$
{
t
(
'Per-call'
)}
·
$
{
formatBillingCurrencyFromUSD
(
other
.
model_price
!
,
priceOpts
)}
`,
})
} else if (other.model_ratio != null) {
const inputPriceUSD = other.model_ratio * 2.0
const baseEntries = [formatPriceCompact(inputPriceUSD)]
if (other.completion_ratio != null) {
baseEntries.push(
formatPriceCompact(inputPriceUSD * other.completion_ratio)
)
}
segments.push({
text
:
`
${
t
(
'Input'
)}
${
formatBillingCurrencyFromUSD
(
inputPriceUSD
,
priceOpts
)}
/M`
,
muted
:
true
,
text: `
$
{
t
(
'Standard'
)}
·
$
{
formatPriceList
(
baseEntries
,
true
)}
`,
})
if (hasAnyCacheTokens(other)) {
const cacheEntries = [
other.cache_ratio != null && other.cache_ratio !== 1
? formatPriceCompact(inputPriceUSD * other.cache_ratio)
: null,
other.cache_creation_ratio != null &&
other.cache_creation_ratio !== 1
? formatPriceCompact(inputPriceUSD * other.cache_creation_ratio)
: null,
other.cache_creation_ratio_1h != null &&
other.cache_creation_ratio_1h !== 0
? formatPriceCompact(inputPriceUSD * other.cache_creation_ratio_1h)
: null,
].filter(Boolean) as string[]
if (cacheEntries.length > 0) {
segments.push({
text: `
$
{
t
(
'Cache'
)}
$
{
formatPriceList
(
cacheEntries
,
false
)}
`,
muted: true,
})
}
}
} else {
const userGroupRatio = other.user_group_ratio
const groupRatio = other.group_ratio
const isUserGroup =
userGroupRatio != null &&
Number.isFinite(userGroupRatio) &&
userGroupRatio !== -1
const effectiveRatio = isUserGroup ? userGroupRatio : groupRatio
const ratioLabel = isUserGroup
? t('User Exclusive Ratio')
: t('Group Ratio')
if (effectiveRatio != null && Number.isFinite(effectiveRatio)) {
segments.push({
text: `
$
{
ratioLabel
}
$
{
formatRatioCompact
(
effectiveRatio
)}
x
`,
})
}
}
}
...
...
@@ -482,7 +553,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
{
accessorKey: 'prompt_tokens',
header: ({ column }) => (
<
DataTableColumnHeader
column=
{
column
}
title=
{
t
(
'Input'
)
}
/>
<DataTableColumnHeader column={column} title=
'Tokens'
/>
),
cell: ({ row }) => {
const log = row.original
...
...
@@ -494,84 +565,41 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
}
const promptTokens = log.prompt_tokens || 0
if
(
promptTokens
===
0
)
{
return
<
span
className=
'text-muted-foreground text-xs'
>
-
</
span
>
}
const
cacheReadTokens
=
other
?.
cache_tokens
||
0
return
(
<
div
className=
'flex flex-col gap-0.5'
>
<
span
className=
'font-mono text-xs font-medium'
>
{
promptTokens
.
toLocaleString
()
}
</
span
>
{
cacheReadTokens
>
0
&&
(
<
span
className=
'flex items-center gap-1 text-[11px]'
>
<
CacheTooltip
tokens=
{
cacheReadTokens
}
label=
{
t
(
'Cache Read'
)
}
color=
'fill-amber-500 text-amber-500'
/>
<
span
className=
'text-muted-foreground/60'
>
{
t
(
'Cache Read'
)
}
{
cacheReadTokens
.
toLocaleString
()
}
</
span
>
</
span
>
)
}
</
div
>
)
},
meta
:
{
label
:
t
(
'Input'
),
mobileHidden
:
true
},
},
{
accessorKey
:
'completion_tokens'
,
header
:
({
column
})
=>
(
<
DataTableColumnHeader
column=
{
column
}
title=
{
t
(
'Output'
)
}
/>
),
cell
:
({
row
})
=>
{
const
log
=
row
.
original
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 completionTokens = log.completion_tokens || 0
if
(
completionTokens
===
0
)
{
if (
promptTokens === 0 &&
completionTokens === 0) {
return <span className='text-muted-foreground text-xs'>-</span>
}
const cacheReadTokens = other?.cache_tokens || 0
const cacheWrite5m = other?.cache_creation_tokens_5m || 0
const cacheWrite1h = other?.cache_creation_tokens_1h || 0
const hasSplitCache = cacheWrite5m > 0 || cacheWrite1h > 0
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'>
{
completionTokens
.
toLocaleString
()
}
{
promptTokens.toLocaleString()} / {
completionTokens.toLocaleString()}
</span>
{
cacheWriteTokens
>
0
&&
(
<
span
className=
'flex items-center gap-1 text-[11px]'
>
<
CacheTooltip
tokens=
{
cacheWriteTokens
}
label=
{
t
(
'Cache Write'
)
}
color=
'fill-blue-500 text-blue-500'
/>
<
span
className=
'text-muted-foreground/60'
>
{
hasSplitCache
?
`${t('Cache Write')} ${cacheWrite5m.toLocaleString()}/${cacheWrite1h.toLocaleString()}`
:
`${t('Cache Write')} ${cacheWriteTokens.toLocaleString()}`
}
</
span
>
{cacheSegments.length > 0 && (
<span className='text-muted-foreground/60 text-[11px]'>
{cacheSegments.join(' · ')}
</span>
)}
</div>
)
},
meta
:
{
label
:
t
(
'Output'
)
,
mobileHidden
:
true
},
meta: { label:
'Tokens'
, mobileHidden: true },
},
{
...
...
web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
View file @
fc377dae
...
...
@@ -175,7 +175,7 @@ function BillingBreakdown(props: {
})
}
if
(
!
tieredSummary
&&
isClaude
)
{
if
(
!
tieredSummary
&&
isClaude
&&
hasAnyCacheTokens
(
other
)
)
{
if
(
other
.
cache_ratio
!=
null
&&
other
.
cache_ratio
!==
1
)
{
rows
.
push
({
label
:
t
(
'Cache Read'
),
...
...
@@ -376,6 +376,11 @@ export function DetailsDialog(props: DetailsDialogProps) {
const
isTopup
=
props
.
log
.
type
===
1
const
isManage
=
props
.
log
.
type
===
3
const
isSubscription
=
other
?.
billing_source
===
'subscription'
const
isTieredBilling
=
isConsume
&&
!
isViolation
&&
other
?.
billing_mode
===
'tiered_expr'
&&
!!
other
?.
expr_b64
const
hasAudioTokens
=
other
?.
ws
||
other
?.
audio
const
showTiming
=
isTimingLogType
(
props
.
log
.
type
)
const
showAdminIp
=
...
...
@@ -446,7 +451,12 @@ export function DetailsDialog(props: DetailsDialogProps) {
return
(
<
Dialog
open=
{
props
.
open
}
onOpenChange=
{
props
.
onOpenChange
}
>
<
DialogContent
className=
'sm:max-w-lg'
>
<
DialogContent
className=
{
cn
(
'min-w-0'
,
isTieredBilling
?
'sm:max-w-4xl lg:max-w-5xl'
:
'sm:max-w-lg'
)
}
>
<
DialogHeader
>
<
DialogTitle
className=
'flex items-center gap-2 text-base'
>
{
t
(
'Log Details'
)
}
...
...
@@ -462,8 +472,8 @@ export function DetailsDialog(props: DetailsDialogProps) {
</
DialogDescription
>
</
DialogHeader
>
<
ScrollArea
className=
'max-h-[70vh] pr-4'
>
<
div
className=
'space-y-3 py-1'
>
<
ScrollArea
className=
'max-h-[70vh]
min-w-0
pr-4'
>
<
div
className=
'
min-w-0
space-y-3 py-1'
>
{
/* Overview section - key identifiers */
}
<
div
className=
'space-y-1.5'
>
{
props
.
log
.
request_id
&&
(
...
...
@@ -797,18 +807,15 @@ export function DetailsDialog(props: DetailsDialogProps) {
)
}
{
/* Tiered pricing breakdown (when billing_mode is tiered_expr) */
}
{
isConsume
&&
!
isViolation
&&
other
?.
billing_mode
===
'tiered_expr'
&&
other
?.
expr_b64
&&
(
<
div
className=
'bg-muted/30 rounded-md border px-3'
>
<
DynamicPricingBreakdown
billingExpr=
{
decodeBillingExprB64
(
other
.
expr_b64
)
}
matchedTierLabel=
{
other
.
matched_tier
}
hideCacheColumns=
{
!
hasAnyCacheTokens
(
other
)
}
/>
</
div
>
)
}
{
isTieredBilling
&&
other
?.
expr_b64
&&
(
<
div
className=
'bg-muted/30 min-w-0 rounded-md border px-3'
>
<
DynamicPricingBreakdown
billingExpr=
{
decodeBillingExprB64
(
other
.
expr_b64
)
}
matchedTierLabel=
{
other
.
matched_tier
}
hideCacheColumns=
{
!
hasAnyCacheTokens
(
other
)
}
/>
</
div
>
)
}
{
/* Admin billing mode indicator for non-consume */
}
{
props
.
isAdmin
&&
...
...
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