refactor(ui): Improve usage log filter responsiveness and mobile UX

Refactor the usage log filter toolbar into a shared reusable component for common, drawing, and task logs. Optimize desktop filters with a responsive grid, move secondary filters into a mobile drawer, standardize filter typography, remove redundant filter icons, and add the missing i18n translations for the new drawer description.
This commit is contained in:
t0ng7u
2026-05-25 05:35:44 +08:00
parent b302be30e3
commit 583da45296
79 changed files with 1879 additions and 1262 deletions
@@ -311,13 +311,13 @@ export function DynamicPricingBreakdown({
<Table className='text-sm'>
<TableHeader>
<TableRow className='hover:bg-transparent'>
<TableHead className='text-muted-foreground py-2 text-[10px] font-medium tracking-wider uppercase'>
<TableHead className='text-muted-foreground py-2 font-medium'>
{t('Tier')}
</TableHead>
{visiblePriceFields.map((v) => (
<TableHead
key={v.field}
className='text-muted-foreground py-2 text-right text-[10px] font-medium tracking-wider uppercase'
className='text-muted-foreground py-2 text-right font-medium'
>
{t(v.shortLabel)}
</TableHead>
@@ -574,14 +574,10 @@ function SupportedParametersSection(props: { model: PricingModel }) {
<Table>
<TableHeader>
<TableRow className='bg-muted/30 hover:bg-muted/30'>
<TableHead className='h-9 w-44 text-xs'>
{t('Parameter')}
</TableHead>
<TableHead className='h-9 w-24 text-xs'>{t('Type')}</TableHead>
<TableHead className='h-9 w-32 text-xs'>
{t('Default / range')}
</TableHead>
<TableHead className='h-9 text-xs'>{t('Description')}</TableHead>
<TableHead className='h-9 w-44'>{t('Parameter')}</TableHead>
<TableHead className='h-9 w-24'>{t('Type')}</TableHead>
<TableHead className='h-9 w-32'>{t('Default / range')}</TableHead>
<TableHead className='h-9'>{t('Description')}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -589,13 +585,13 @@ function SupportedParametersSection(props: { model: PricingModel }) {
<TableRow key={p.name} className='hover:bg-muted/20'>
<TableCell className='py-2 align-top'>
<div className='flex items-center gap-1.5'>
<code className='font-mono text-xs font-medium'>
<code className='font-mono text-sm font-medium'>
{p.name}
</code>
{p.required && (
<Badge
variant='outline'
className='h-4 border-rose-500/40 px-1 text-[9px] text-rose-600 dark:text-rose-400'
className='h-6 border-rose-500/40 px-2 text-sm text-rose-600 dark:text-rose-400'
>
{t('required')}
</Badge>
@@ -605,7 +601,7 @@ function SupportedParametersSection(props: { model: PricingModel }) {
<TableCell className='py-2 align-top'>
<Badge
variant='secondary'
className='h-5 rounded-sm px-1.5 font-mono text-[10px] font-normal'
className='h-7 rounded-full px-2.5 font-mono text-sm font-normal'
>
{p.type}
</Badge>
@@ -613,7 +609,7 @@ function SupportedParametersSection(props: { model: PricingModel }) {
<TableCell className='py-2 align-top'>
<ParamRangeCell param={p} />
</TableCell>
<TableCell className='text-muted-foreground py-2 align-top text-xs'>
<TableCell className='text-muted-foreground py-2 align-top'>
{t(p.descriptionKey)}
</TableCell>
</TableRow>
@@ -630,21 +626,19 @@ function ParamRangeCell(props: { param: SupportedParameter }) {
if (defaultValue !== undefined) {
return (
<div className='flex flex-wrap items-center gap-1'>
<span className='text-muted-foreground text-[11px]'>=</span>
<code className='bg-muted rounded px-1 py-0.5 font-mono text-[11px]'>
<span className='text-muted-foreground text-sm'>=</span>
<code className='bg-muted rounded px-1.5 py-0.5 font-mono text-sm'>
{String(defaultValue)}
</code>
{range && (
<span className='text-muted-foreground text-[11px]'>{range}</span>
<span className='text-muted-foreground text-sm'>{range}</span>
)}
</div>
)
}
if (range) {
return (
<span className='text-muted-foreground font-mono text-[11px]'>
{range}
</span>
<span className='text-muted-foreground font-mono text-sm'>{range}</span>
)
}
if (enumValues && enumValues.length > 0) {
@@ -653,7 +647,7 @@ function ParamRangeCell(props: { param: SupportedParameter }) {
{enumValues.map((v) => (
<code
key={v}
className='bg-muted text-muted-foreground rounded px-1 py-0.5 font-mono text-[10px]'
className='bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-sm'
>
{v}
</code>
@@ -661,7 +655,7 @@ function ParamRangeCell(props: { param: SupportedParameter }) {
</div>
)
}
return <span className='text-muted-foreground/60 text-[11px]'></span>
return <span className='text-muted-foreground/60 text-sm'></span>
}
// ---------------------------------------------------------------------------
@@ -681,25 +675,23 @@ function RateLimitsSection(props: { model: PricingModel }) {
<Table>
<TableHeader>
<TableRow className='bg-muted/30 hover:bg-muted/30'>
<TableHead className='h-9 text-xs'>{t('Group')}</TableHead>
<TableHead className='h-9 text-right text-xs'>RPM</TableHead>
<TableHead className='h-9 text-right text-xs'>TPM</TableHead>
<TableHead className='h-9 text-right text-xs'>RPD</TableHead>
<TableHead className='h-9'>{t('Group')}</TableHead>
<TableHead className='h-9 text-right'>RPM</TableHead>
<TableHead className='h-9 text-right'>TPM</TableHead>
<TableHead className='h-9 text-right'>RPD</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{limits.map((l) => (
<TableRow key={l.group} className='hover:bg-muted/20'>
<TableCell className='py-2 font-mono text-xs'>
{l.group}
</TableCell>
<TableCell className='py-2 text-right font-mono text-xs'>
<TableCell className='py-2 font-mono'>{l.group}</TableCell>
<TableCell className='py-2 text-right font-mono'>
{formatRateLimit(l.rpm)}
</TableCell>
<TableCell className='py-2 text-right font-mono text-xs'>
<TableCell className='py-2 text-right font-mono'>
{formatRateLimit(l.tpm)}
</TableCell>
<TableCell className='py-2 text-right font-mono text-xs'>
<TableCell className='py-2 text-right font-mono'>
{formatRateLimit(l.rpd)}
</TableCell>
</TableRow>
@@ -199,13 +199,13 @@ export function ModelDetailsApps(props: { model: PricingModel }) {
<div className='text-sm font-medium'>
<AppLink app={app} />
</div>
<p className='text-muted-foreground line-clamp-1 text-xs'>
<p className='text-muted-foreground line-clamp-1 text-sm'>
{app.description}
</p>
</div>
</div>
</TableCell>
<TableCell className='text-muted-foreground hidden py-2.5 text-xs md:table-cell'>
<TableCell className='text-muted-foreground hidden py-2.5 md:table-cell'>
{app.category}
</TableCell>
<TableCell className='py-2.5 text-right font-mono tabular-nums'>
@@ -42,6 +42,7 @@ import {
} from '@/components/ui/table'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { CopyButton } from '@/components/copy-button'
import { sideDrawerContentClassName } from '@/components/drawer-layout'
import { GroupBadge } from '@/components/group-badge'
import { PublicLayout } from '@/components/layout'
import { getPerfMetrics } from '@/features/performance-metrics/api'
@@ -735,7 +736,7 @@ function GroupPricingSection(props: {
return (
<TableRow key={`${group}-${tier.label || tierIndex}`}>
<TableCell className='text-muted-foreground py-2.5 text-xs'>
<TableCell className='text-muted-foreground py-2.5'>
{tier.label || t('Default')}
</TableCell>
{priceFields.map((fieldEntry) => {
@@ -808,7 +809,7 @@ function GroupPricingSection(props: {
<TableCell className='py-2.5'>
<GroupBadge group={group} size='sm' />
</TableCell>
<TableCell className='text-muted-foreground py-2.5 font-mono text-xs'>
<TableCell className='text-muted-foreground py-2.5 font-mono'>
{ratio}x
</TableCell>
{isTokenBased ? (
@@ -1006,7 +1007,9 @@ export function ModelDetailsDrawer(props: ModelDetailsDrawerProps) {
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent
side='right'
className='flex h-dvh w-full overflow-hidden p-0 sm:max-w-2xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-5xl'
className={sideDrawerContentClassName(
'sm:max-w-2xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-5xl'
)}
>
<SheetHeader className='sr-only'>
<SheetTitle>{props.model.model_name}</SheetTitle>
@@ -27,6 +27,7 @@ import {
} from '@/components/ui/tooltip'
import { DataTableColumnHeader } from '@/components/data-table/column-header'
import { GroupBadge } from '@/components/group-badge'
import { StatusBadge, StatusBadgeList } from '@/components/status-badge'
import { DEFAULT_TOKEN_UNIT, QUOTA_TYPE_VALUES } from '../constants'
import {
getDynamicDisplayGroupRatio,
@@ -56,19 +57,15 @@ function renderLimitedTags(
items: string[],
maxDisplay: number = 3
): React.ReactNode {
if (items.length === 0)
return <span className='text-muted-foreground/50 text-xs'></span>
const displayed = items.slice(0, maxDisplay)
const remaining = items.length - maxDisplay
return (
<span className='text-muted-foreground text-xs'>
{displayed.join(', ')}
{remaining > 0 && (
<span className='text-muted-foreground/50'> +{remaining}</span>
<StatusBadgeList
items={items}
max={maxDisplay}
getKey={(item) => item}
renderItem={(item) => (
<StatusBadge label={item} autoColor={item} size='sm' copyable={false} />
)}
</span>
/>
)
}
@@ -76,21 +73,13 @@ function renderLimitedGroupBadges(
groups: string[],
maxDisplay: number = 2
): React.ReactNode {
if (groups.length === 0)
return <span className='text-muted-foreground/50 text-xs'></span>
const displayed = groups.slice(0, maxDisplay)
const remaining = groups.length - maxDisplay
return (
<div className='flex max-w-full items-center gap-1 overflow-hidden'>
{displayed.map((group) => (
<GroupBadge key={group} group={group} size='sm' />
))}
{remaining > 0 && (
<span className='text-muted-foreground/50 text-xs'>+{remaining}</span>
)}
</div>
<StatusBadgeList
items={groups}
max={maxDisplay}
getKey={(group) => group}
renderItem={(group) => <GroupBadge group={group} size='sm' />}
/>
)
}
@@ -141,9 +130,11 @@ export function usePricingColumns(
cell: ({ row }) => {
const isTokenBased = row.original.quota_type === QUOTA_TYPE_VALUES.TOKEN
return (
<span className='text-muted-foreground text-xs font-medium tracking-wider uppercase'>
{isTokenBased ? t('Token') : t('Request')}
</span>
<StatusBadge
label={isTokenBased ? t('Token') : t('Request')}
variant={isTokenBased ? 'info' : 'neutral'}
copyable={false}
/>
)
},
size: 80,
@@ -365,9 +356,14 @@ export function usePricingColumns(
? getLobeIcon(model.vendor_icon, 12)
: null
return (
<span className='text-muted-foreground flex items-center gap-1.5 text-xs'>
<span className='flex items-center gap-1.5'>
{vendorIcon}
{model.vendor_name}
<StatusBadge
label={model.vendor_name}
autoColor={model.vendor_name}
size='sm'
copyable={false}
/>
</span>
)
},
@@ -102,7 +102,7 @@ export function PricingTable(props: PricingTableProps) {
<TableHead
key={header.id}
style={{ width: header.getSize() }}
className='text-muted-foreground text-[10px] font-medium tracking-wider uppercase'
className='text-muted-foreground font-medium'
>
{header.isPlaceholder
? null
@@ -40,6 +40,11 @@ import {
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import {
sideDrawerContentClassName,
sideDrawerFormClassName,
sideDrawerHeaderClassName,
} from '@/components/drawer-layout'
import {
VIEW_MODES,
getSortLabels,
@@ -269,15 +274,15 @@ export function PricingToolbar(props: PricingToolbarProps) {
<Sheet open={mobileFiltersOpen} onOpenChange={setMobileFiltersOpen}>
<SheetContent
side='right'
className='flex h-dvh w-full flex-col overflow-hidden p-0 sm:max-w-md'
className={sideDrawerContentClassName('sm:max-w-md')}
>
<SheetHeader className='border-b px-4 py-3 sm:px-6 sm:py-4'>
<SheetHeader className={sideDrawerHeaderClassName()}>
<SheetTitle>{t('Filter')}</SheetTitle>
<SheetDescription>
{t('Filter models by provider, group, type, endpoint, and tags.')}
</SheetDescription>
</SheetHeader>
<div className='flex-1 overflow-y-auto p-3 sm:p-4'>
<div className={sideDrawerFormClassName('gap-0')}>
<PricingSidebar
quotaTypeFilter={props.quotaTypeFilter}
endpointTypeFilter={props.endpointTypeFilter}