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:
t0ng7u
2026-07-12 15:53:15 +08:00
parent 135628a5cb
commit 7c3e2cc66f
2 changed files with 48 additions and 53 deletions
@@ -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'
@@ -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>