mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-03 13:01:00 +00:00
refactor(web): inline retry chain and polish model mapping popover in usage logs
Show the retry chain as compact grey inline text next to the channel badge (middle-collapsed beyond three hops, full chain in the tooltip) instead of hiding it behind a popover icon. Move the model-mapping route marker inside the badge's trailing icon slot, size the mapping popover to its content, and make both model names copyable. Group ratio now hides together with the group name when sensitive info is masked.
This commit is contained in:
+26
-39
@@ -24,11 +24,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { GroupBadge } from '@/components/group-badge'
|
||||
import { CopyableStatusBadge, StatusBadge } from '@/components/status-badge'
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -347,6 +342,17 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
|
||||
const channelChain = hasRetryChain
|
||||
? useChannel.join(' → ')
|
||||
: undefined
|
||||
// Inline variant is compact (no spaces) so three hops fit the
|
||||
// 160px cell; longer chains collapse the middle, keeping the
|
||||
// first hops and the final channel. Full chain stays in the
|
||||
// tooltip below.
|
||||
let channelChainInline: string | undefined
|
||||
if (hasRetryChain) {
|
||||
channelChainInline =
|
||||
useChannel.length > 3
|
||||
? `${useChannel[0]}→${useChannel[1]}→…→${useChannel.at(-1)}`
|
||||
: useChannel.join('→')
|
||||
}
|
||||
const channelDisplay = log.channel_name
|
||||
? `${log.channel_name} #${log.channel}`
|
||||
: `#${log.channel}`
|
||||
@@ -366,7 +372,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
|
||||
<div className='flex max-w-[160px] flex-col gap-0.5' />
|
||||
}
|
||||
>
|
||||
<div className='relative inline-flex w-fit items-center gap-1'>
|
||||
<div className='relative inline-flex w-fit max-w-full items-center gap-1'>
|
||||
<CopyableStatusBadge
|
||||
value={String(log.channel)}
|
||||
variant='neutral'
|
||||
@@ -389,35 +395,15 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
|
||||
</StatusBadge>
|
||||
)}
|
||||
{hasRetryChain && (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<button
|
||||
type='button'
|
||||
className='text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex size-5 shrink-0 items-center justify-center rounded-full transition-colors focus-visible:ring-2 focus-visible:outline-none'
|
||||
aria-label={t('Retry Chain')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<GitBranch
|
||||
className='text-warning size-3.5'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side='top'
|
||||
align='start'
|
||||
className='w-64 text-xs'
|
||||
>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<p className='font-medium'>{t('Retry Chain')}</p>
|
||||
<p className='text-muted-foreground font-mono break-all'>
|
||||
{channelChain}
|
||||
</p>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<span className='text-subtle-foreground inline-flex min-w-0 items-center gap-0.5 text-xs'>
|
||||
<GitBranch
|
||||
className='size-3 shrink-0'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
<span className='truncate font-mono tabular-nums'>
|
||||
{channelChainInline}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{affinity && (
|
||||
<button
|
||||
@@ -568,10 +554,11 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
|
||||
let group = log.group
|
||||
if (!group) group = other?.group || ''
|
||||
|
||||
const groupRatioText = getGroupRatioText(
|
||||
other,
|
||||
group ? groupRatios[group] : undefined
|
||||
)
|
||||
// The ratio reveals the group's pricing, so it hides together with
|
||||
// the group name when sensitive info is masked.
|
||||
const groupRatioText = sensitiveVisible
|
||||
? getGroupRatioText(other, group ? groupRatios[group] : undefined)
|
||||
: null
|
||||
const tokenBadgeClassName =
|
||||
'max-w-full min-w-0 overflow-hidden [&>[data-slot=status-badge-label]]:max-w-full [&>[data-slot=status-badge-label]]:min-w-0 [&>[data-slot=status-badge-label]]:overflow-hidden [&>[data-slot=status-badge-label]]:text-ellipsis'
|
||||
|
||||
|
||||
+22
-14
@@ -126,6 +126,7 @@ function resolveModelProvider(modelName: string): ModelProvider | null {
|
||||
function ModelBadgeContent(
|
||||
props: ModelBadgeProps & {
|
||||
staticOnly: boolean
|
||||
mapped?: boolean
|
||||
}
|
||||
) {
|
||||
const provider = resolveModelProvider(props.modelName)
|
||||
@@ -144,6 +145,16 @@ function ModelBadgeContent(
|
||||
<span key='model' className='whitespace-nowrap'>
|
||||
{props.modelName}
|
||||
</span>,
|
||||
// Mapped models carry the route marker inside the pill (trailing icon
|
||||
// slot) so the indicator reads as part of the badge, not a stray glyph.
|
||||
props.mapped ? (
|
||||
<Route
|
||||
key='mapped'
|
||||
data-icon='inline-end'
|
||||
className='opacity-70'
|
||||
aria-hidden='true'
|
||||
/>
|
||||
) : null,
|
||||
]
|
||||
|
||||
if (props.staticOnly) {
|
||||
@@ -190,33 +201,30 @@ export function ModelBadge(props: ModelBadgeProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<button type='button' className='inline-flex items-center gap-1' />
|
||||
}
|
||||
render={<button type='button' className='inline-flex items-center' />}
|
||||
>
|
||||
<ModelBadgeContent {...props} staticOnly />
|
||||
<Route className='text-muted-foreground size-3 shrink-0' />
|
||||
<ModelBadgeContent {...props} staticOnly mapped />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className='w-72'>
|
||||
<PopoverContent className='w-fit max-w-80'>
|
||||
<div className='space-y-2'>
|
||||
<div className='flex items-start justify-between gap-3'>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<span className='text-muted-foreground shrink-0 text-xs'>
|
||||
{t('Request Model:')}
|
||||
</span>
|
||||
<ModelBadgeContent
|
||||
modelName={props.modelName}
|
||||
staticOnly
|
||||
className='max-w-[11rem]'
|
||||
staticOnly={false}
|
||||
className='max-w-56'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-start justify-between gap-3'>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<span className='text-muted-foreground shrink-0 text-xs'>
|
||||
{t('Actual Model:')}
|
||||
</span>
|
||||
<ModelBadgeContent
|
||||
modelName={props.actualModel}
|
||||
staticOnly
|
||||
className='max-w-[11rem]'
|
||||
staticOnly={false}
|
||||
className='max-w-56'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user