✨ style(web): unify design system across default frontend
🔤 Typography - Fix --font-sans stack: 'Public Sans Variable' was never applied (wrong family name), add CJK fallbacks (PingFang SC / Microsoft YaHei UI / Noto Sans SC) - Sync <html lang> with i18n language for correct CJK glyph selection - Remove dead fake font system (font-provider.tsx, config/fonts.ts, --font-inter/--font-manrope) - Typography contract: 3-tier headings, arbitrary text-[10px]/[11px] → text-xs (46 files), no uppercase table headers, font-bold → font-semibold - Numbers use tabular-nums only; font-mono reserved for keys/IDs/code (29 files) 🎨 Color discipline - Retire StatusBadge string-hash rainbow (autoColor → neutral); all variants resolve to 5 semantic voices - Add tintedBadgeClassMap as the single tinted-pill recipe - Replace hardcoded emerald/amber/rose/sky classes with semantic tokens across ~30 files (timing badges, progress bars, system panels, pricing, rankings, log row tints) - Drop all !important text overrides and [font-family:var(--font-body)] hacks 🧘 Motion restraint - Remove admin entrance animations: table row stagger, card hover lift/shadow, button press scale, dashboard shimmer line - Neuter page-transition stagger components into plain wrappers; page transitions reduced to pure opacity fade - Decorative motion stays landing-only 📐 Shape - Single radius source: --radius 1rem → 0.625rem - Sparklines use solid chart tokens instead of decorative gradients - PanelWrapper: border-only (no shadow), align radius with Card 📏 Guardrails - Add "Design System Discipline" section (3.10.1) to web/default/AGENTS.md ✅ Verified: tsgo typecheck clean, rsbuild production build passes
This commit is contained in:
@@ -199,7 +199,7 @@ export function DynamicPricingBreakdown({
|
||||
<section className={cn('min-w-0', !compact && 'py-4')}>
|
||||
{!compact && (
|
||||
<div className='mb-3 flex items-center gap-2'>
|
||||
<span className='inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<span className='bg-warning/15 text-warning inline-flex size-6 items-center justify-center rounded-lg'>
|
||||
<TagIcon className='size-3.5' />
|
||||
</span>
|
||||
<div>
|
||||
@@ -212,7 +212,7 @@ export function DynamicPricingBreakdown({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground mb-1 text-xs font-medium tracking-wider uppercase'>
|
||||
{t('Raw expression')}
|
||||
</div>
|
||||
<code className='text-muted-foreground block text-xs break-all'>
|
||||
@@ -234,7 +234,7 @@ export function DynamicPricingBreakdown({
|
||||
<section className={cn('min-w-0', !compact && 'py-3 sm:py-4')}>
|
||||
{!compact && (
|
||||
<div className='mb-3 flex items-start gap-2 sm:mb-4'>
|
||||
<span className='mt-0.5 inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<span className='bg-warning/15 text-warning mt-0.5 inline-flex size-6 items-center justify-center rounded-lg'>
|
||||
<TagIcon className='size-3.5' />
|
||||
</span>
|
||||
<div>
|
||||
@@ -271,7 +271,7 @@ export function DynamicPricingBreakdown({
|
||||
key={`tier-mobile-${i}`}
|
||||
className={cn(
|
||||
'rounded-md border p-2',
|
||||
isMatched && 'border-emerald-500/40 bg-emerald-500/10'
|
||||
isMatched && 'border-success/40 bg-success/10'
|
||||
)}
|
||||
>
|
||||
<div className='mb-1.5 flex flex-wrap items-center gap-1.5'>
|
||||
@@ -284,7 +284,7 @@ export function DynamicPricingBreakdown({
|
||||
{isMatched && (
|
||||
<Badge
|
||||
variant='secondary'
|
||||
className='bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300'
|
||||
className='bg-success/10 text-success'
|
||||
>
|
||||
{t('Matched')}
|
||||
</Badge>
|
||||
@@ -302,7 +302,7 @@ export function DynamicPricingBreakdown({
|
||||
)
|
||||
return (
|
||||
<div key={v.field} className='min-w-0'>
|
||||
<div className='text-muted-foreground truncate text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground truncate text-xs font-medium tracking-wider uppercase'>
|
||||
{t(v.shortLabel)}
|
||||
</div>
|
||||
<div
|
||||
@@ -337,10 +337,7 @@ export function DynamicPricingBreakdown({
|
||||
const isMatched =
|
||||
normalizedMatchedTierLabel !== '' &&
|
||||
normalizeTierLabel(tier.label) === normalizedMatchedTierLabel
|
||||
return cn(
|
||||
isMatched &&
|
||||
'bg-emerald-50/70 hover:bg-emerald-50/70 dark:bg-emerald-500/10 dark:hover:bg-emerald-500/10'
|
||||
)
|
||||
return cn(isMatched && 'bg-success/10 hover:bg-success/10')
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
@@ -369,7 +366,7 @@ export function DynamicPricingBreakdown({
|
||||
{isMatched && (
|
||||
<Badge
|
||||
variant='secondary'
|
||||
className='bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300'
|
||||
className='bg-success/10 text-success'
|
||||
>
|
||||
{t('Matched')}
|
||||
</Badge>
|
||||
|
||||
@@ -96,10 +96,10 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
||||
if (dynamicSummary.isSpecialExpression) {
|
||||
priceSummary = (
|
||||
<span className='min-w-0'>
|
||||
<span className='text-amber-700 dark:text-amber-300'>
|
||||
<span className='text-warning'>
|
||||
{t('Special billing expression')}
|
||||
</span>
|
||||
<code className='text-muted-foreground/70 mt-0.5 line-clamp-1 block font-mono text-[11px] break-all'>
|
||||
<code className='text-muted-foreground/70 mt-0.5 line-clamp-1 block font-mono text-xs break-all'>
|
||||
{dynamicSummary.rawExpression}
|
||||
</code>
|
||||
</span>
|
||||
|
||||
@@ -539,7 +539,7 @@ function CodeSamplesSection(props: {
|
||||
|
||||
<p className='text-muted-foreground mt-2 text-xs'>
|
||||
{t('Replace')}{' '}
|
||||
<code className='bg-muted rounded px-1 py-0.5 font-mono text-[11px]'>
|
||||
<code className='bg-muted rounded px-1 py-0.5 font-mono text-xs'>
|
||||
{'<YOUR_API_KEY>'}
|
||||
</code>{' '}
|
||||
{t('with the API key from your token settings.')}
|
||||
@@ -582,7 +582,7 @@ function SupportedParametersSection(props: { model: PricingModel }) {
|
||||
{p.required && (
|
||||
<Badge
|
||||
variant='outline'
|
||||
className='h-6 border-rose-500/40 px-2 text-sm text-rose-600 dark:text-rose-400'
|
||||
className='border-destructive/40 text-destructive h-6 px-2 text-sm'
|
||||
>
|
||||
{t('required')}
|
||||
</Badge>
|
||||
@@ -711,7 +711,7 @@ function RateLimitsSection(props: { model: PricingModel }) {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<p className='text-muted-foreground mt-2 text-[11px] leading-relaxed'>
|
||||
<p className='text-muted-foreground mt-2 text-xs leading-relaxed'>
|
||||
{t(
|
||||
'RPM = requests per minute, TPM = tokens per minute, RPD = requests per day. Limits apply per token group.'
|
||||
)}
|
||||
@@ -734,11 +734,11 @@ function AuthSection() {
|
||||
<div className='space-y-1.5 text-xs leading-relaxed'>
|
||||
<p>
|
||||
{t('All requests must include')}{' '}
|
||||
<code className='bg-muted rounded px-1 py-0.5 font-mono text-[11px]'>
|
||||
<code className='bg-muted rounded px-1 py-0.5 font-mono text-xs'>
|
||||
Authorization: Bearer <TOKEN>
|
||||
</code>{' '}
|
||||
{t('header. Anthropic-formatted endpoints accept the')}{' '}
|
||||
<code className='bg-muted rounded px-1 py-0.5 font-mono text-[11px]'>
|
||||
<code className='bg-muted rounded px-1 py-0.5 font-mono text-xs'>
|
||||
x-api-key
|
||||
</code>{' '}
|
||||
{t('header instead.')}
|
||||
|
||||
@@ -48,16 +48,14 @@ function RankBadge(props: { rank: number }) {
|
||||
const isPodium = rank <= 3
|
||||
const palette =
|
||||
rank === 1
|
||||
? 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300'
|
||||
: rank === 2
|
||||
? 'bg-slate-100 text-slate-700 dark:bg-slate-500/20 dark:text-slate-300'
|
||||
: rank === 3
|
||||
? 'bg-orange-100 text-orange-700 dark:bg-orange-500/20 dark:text-orange-300'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
? 'bg-warning/15 text-warning'
|
||||
: rank <= 3
|
||||
? 'bg-muted text-foreground'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex size-7 shrink-0 items-center justify-center rounded-md font-mono text-xs font-bold tabular-nums',
|
||||
'inline-flex size-7 shrink-0 items-center justify-center rounded-md text-xs font-bold tabular-nums',
|
||||
palette
|
||||
)}
|
||||
>
|
||||
@@ -71,16 +69,16 @@ function GrowthChip(props: { value: number }) {
|
||||
const isUp = value > 0
|
||||
const isDown = value < 0
|
||||
const palette = isUp
|
||||
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300'
|
||||
? 'bg-success/10 text-success'
|
||||
: isDown
|
||||
? 'bg-rose-100 text-rose-700 dark:bg-rose-500/20 dark:text-rose-300'
|
||||
? 'bg-destructive/10 text-destructive'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
const Icon = isUp ? ArrowUpRight : isDown ? ArrowDownRight : null
|
||||
const formatted = `${value > 0 ? '+' : ''}${value.toFixed(1)}%`
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center gap-0.5 rounded-md px-1.5 py-0.5 font-mono text-[11px] font-semibold tabular-nums',
|
||||
'inline-flex items-center gap-0.5 rounded-md px-1.5 py-0.5 text-xs font-semibold tabular-nums',
|
||||
palette
|
||||
)}
|
||||
>
|
||||
@@ -125,35 +123,35 @@ export function ModelDetailsApps(props: { model: PricingModel }) {
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className='grid grid-cols-1 gap-2 sm:grid-cols-3'>
|
||||
<div className='bg-muted/20 rounded-lg border p-3'>
|
||||
<div className='text-muted-foreground text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground text-xs font-medium tracking-wider uppercase'>
|
||||
{t('Tracked apps')}
|
||||
</div>
|
||||
<div className='text-foreground mt-1 font-mono text-lg font-semibold tabular-nums'>
|
||||
<div className='text-foreground mt-1 text-lg font-semibold tabular-nums'>
|
||||
{apps.length}
|
||||
</div>
|
||||
<p className='text-muted-foreground/70 text-[11px]'>
|
||||
<p className='text-muted-foreground/70 text-xs'>
|
||||
{t('Top integrations using this model')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='bg-muted/20 rounded-lg border p-3'>
|
||||
<div className='text-muted-foreground text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground text-xs font-medium tracking-wider uppercase'>
|
||||
{t('Monthly tokens')}
|
||||
</div>
|
||||
<div className='text-foreground mt-1 font-mono text-lg font-semibold tabular-nums'>
|
||||
<div className='text-foreground mt-1 text-lg font-semibold tabular-nums'>
|
||||
{COMPACT_NUMBER.format(totalMonthlyTokens)}
|
||||
</div>
|
||||
<p className='text-muted-foreground/70 text-[11px]'>
|
||||
<p className='text-muted-foreground/70 text-xs'>
|
||||
{t('Aggregated across the apps below')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='bg-muted/20 rounded-lg border p-3'>
|
||||
<div className='text-muted-foreground text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground text-xs font-medium tracking-wider uppercase'>
|
||||
{t('#1 by usage')}
|
||||
</div>
|
||||
<div className='text-foreground mt-1 truncate text-base font-semibold'>
|
||||
{top.name}
|
||||
</div>
|
||||
<p className='text-muted-foreground/70 truncate text-[11px]'>
|
||||
<p className='text-muted-foreground/70 truncate text-xs'>
|
||||
{top.category} · {formatTokenVolume(top.monthly_tokens)}{' '}
|
||||
{t('tokens / mo')}
|
||||
</p>
|
||||
@@ -225,7 +223,7 @@ export function ModelDetailsApps(props: { model: PricingModel }) {
|
||||
]}
|
||||
/>
|
||||
|
||||
<p className='text-muted-foreground/60 text-[11px] leading-relaxed'>
|
||||
<p className='text-muted-foreground/60 text-xs leading-relaxed'>
|
||||
{t(
|
||||
'App rankings shown here are simulated for preview purposes and will be replaced with live usage data once the backend integration is complete.'
|
||||
)}
|
||||
|
||||
@@ -51,22 +51,20 @@ function StatCard(props: {
|
||||
const Icon = props.icon
|
||||
return (
|
||||
<div className='bg-background flex flex-col gap-1 rounded-lg border p-3'>
|
||||
<span className='text-muted-foreground inline-flex items-center gap-1.5 text-[10px] font-medium tracking-wider uppercase'>
|
||||
<span className='text-muted-foreground inline-flex items-center gap-1.5 text-xs font-medium tracking-wider uppercase'>
|
||||
<Icon className='size-3' />
|
||||
{props.label}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'text-foreground font-mono text-lg font-semibold tabular-nums',
|
||||
'text-foreground text-lg font-semibold tabular-nums',
|
||||
props.valueClassName
|
||||
)}
|
||||
>
|
||||
{props.value}
|
||||
</span>
|
||||
{props.hint && (
|
||||
<span className='text-muted-foreground/70 text-[11px]'>
|
||||
{props.hint}
|
||||
</span>
|
||||
<span className='text-muted-foreground/70 text-xs'>{props.hint}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -330,7 +328,7 @@ export function ModelDetailsPerformance(props: { model: PricingModel }) {
|
||||
}
|
||||
accent={
|
||||
incidentCount > 0 ? (
|
||||
<span className='inline-flex items-center gap-1 text-amber-600 dark:text-amber-400'>
|
||||
<span className='text-warning inline-flex items-center gap-1'>
|
||||
<AlertTriangle className='size-3.5' />
|
||||
{t('{{count}} incidents', {
|
||||
count: incidentCount,
|
||||
|
||||
+6
-8
@@ -128,7 +128,7 @@ export function UptimeSparkline(props: UptimeSparklineProps) {
|
||||
{showOverall && (
|
||||
<span
|
||||
className={cn(
|
||||
'font-mono text-sm font-semibold tabular-nums',
|
||||
'text-sm font-semibold tabular-nums',
|
||||
getSuccessRateTextClass(overall)
|
||||
)}
|
||||
>
|
||||
@@ -164,13 +164,11 @@ export function UptimeStatusRow(props: {
|
||||
: AlertCircle
|
||||
|
||||
const statusColour =
|
||||
status === 'operational'
|
||||
? 'text-emerald-600 dark:text-emerald-400'
|
||||
: status === 'minor'
|
||||
? 'text-emerald-600 dark:text-emerald-400'
|
||||
: status === 'degraded'
|
||||
? 'text-amber-600 dark:text-amber-400'
|
||||
: 'text-rose-600 dark:text-rose-400'
|
||||
status === 'operational' || status === 'minor'
|
||||
? 'text-success'
|
||||
: status === 'degraded'
|
||||
? 'text-warning'
|
||||
: 'text-destructive'
|
||||
|
||||
const statusLabel =
|
||||
status === 'operational'
|
||||
|
||||
+20
-20
@@ -156,12 +156,12 @@ function OverviewMetric(props: {
|
||||
<div className='flex min-w-0 items-center gap-2 px-3 py-2'>
|
||||
<Icon className='text-muted-foreground/70 size-3.5 shrink-0' />
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div className='text-muted-foreground truncate text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground truncate text-xs font-medium tracking-wider uppercase'>
|
||||
{props.label}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'text-foreground truncate font-mono text-sm font-semibold tabular-nums',
|
||||
'text-foreground truncate text-sm font-semibold tabular-nums',
|
||||
props.valueClassName
|
||||
)}
|
||||
>
|
||||
@@ -254,7 +254,7 @@ function CatalogTextValue(props: { children: React.ReactNode }) {
|
||||
function CatalogInfoCell(props: { label: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className='bg-card flex min-w-0 flex-col gap-1 px-3 py-2.5'>
|
||||
<span className='text-muted-foreground text-[10px] font-medium tracking-wider uppercase'>
|
||||
<span className='text-muted-foreground text-xs font-medium tracking-wider uppercase'>
|
||||
{props.label}
|
||||
</span>
|
||||
{props.children}
|
||||
@@ -361,7 +361,7 @@ function ModelBackendQuickStats(props: { model: PricingModel }) {
|
||||
key={stat.key}
|
||||
className='bg-background flex min-w-0 flex-col gap-0.5 px-3 py-2.5'
|
||||
>
|
||||
<span className='text-muted-foreground inline-flex min-w-0 items-center gap-1 text-[10px] font-medium tracking-wider uppercase'>
|
||||
<span className='text-muted-foreground inline-flex min-w-0 items-center gap-1 text-xs font-medium tracking-wider uppercase'>
|
||||
<Icon className='size-3 shrink-0' />
|
||||
<span className='truncate'>{stat.label}</span>
|
||||
</span>
|
||||
@@ -369,7 +369,7 @@ function ModelBackendQuickStats(props: { model: PricingModel }) {
|
||||
{stat.value}
|
||||
</span>
|
||||
{stat.hint && (
|
||||
<span className='text-muted-foreground/60 truncate text-[10px]'>
|
||||
<span className='text-muted-foreground/60 truncate text-xs'>
|
||||
{stat.hint}
|
||||
</span>
|
||||
)}
|
||||
@@ -565,7 +565,7 @@ function ModelHeader(props: { model: PricingModel }) {
|
||||
{model.billing_mode === 'tiered_expr' && model.billing_expr && (
|
||||
<>
|
||||
<span className='text-muted-foreground/30'>·</span>
|
||||
<span className='rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-700 dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<span className='bg-warning/15 text-warning rounded px-1.5 py-0.5 text-xs font-medium'>
|
||||
{isSpecialExpression
|
||||
? t('Special billing expression')
|
||||
: t('Dynamic Pricing')}
|
||||
@@ -649,15 +649,15 @@ function PriceSection(props: {
|
||||
return (
|
||||
<section>
|
||||
<SectionTitle>{t('Base Price')}</SectionTitle>
|
||||
<div className='rounded-lg border border-amber-200/70 bg-amber-50/70 p-3 dark:border-amber-500/20 dark:bg-amber-500/10'>
|
||||
<div className='text-sm font-medium text-amber-800 dark:text-amber-200'>
|
||||
<div className='border-warning/30 bg-warning/10 rounded-lg border p-3'>
|
||||
<div className='text-warning text-sm font-medium'>
|
||||
{t('Special billing expression')}
|
||||
</div>
|
||||
<p className='text-muted-foreground mt-1 text-xs'>
|
||||
{t('Unable to parse structured pricing')}
|
||||
</p>
|
||||
<div className='mt-3'>
|
||||
<div className='text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground mb-1 text-xs font-medium tracking-wider uppercase'>
|
||||
{t('Raw expression')}
|
||||
</div>
|
||||
<code className='text-muted-foreground bg-background/80 block max-h-28 overflow-auto rounded-md border px-2 py-1.5 font-mono text-xs break-all'>
|
||||
@@ -682,7 +682,7 @@ function PriceSection(props: {
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{t(entry.shortLabel)}
|
||||
</div>
|
||||
<div className='text-foreground mt-1 font-mono text-base font-semibold tabular-nums'>
|
||||
<div className='text-foreground mt-1 text-base font-semibold tabular-nums'>
|
||||
{entry.formatted}
|
||||
<span className='text-muted-foreground/40 ml-1 text-xs font-normal'>
|
||||
/ {tokenUnitLabel}
|
||||
@@ -707,7 +707,7 @@ function PriceSection(props: {
|
||||
<span className='text-muted-foreground/70 text-sm'>
|
||||
{t(entry.shortLabel)}
|
||||
</span>
|
||||
<span className='text-muted-foreground font-mono text-sm tabular-nums'>
|
||||
<span className='text-muted-foreground text-sm tabular-nums'>
|
||||
{entry.formatted}
|
||||
<span className='text-muted-foreground/40 ml-1 text-xs font-normal'>
|
||||
/ {tokenUnitLabel}
|
||||
@@ -730,7 +730,7 @@ function PriceSection(props: {
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Per request')}
|
||||
</span>
|
||||
<span className='text-foreground font-mono text-sm font-semibold tabular-nums'>
|
||||
<span className='text-foreground text-sm font-semibold tabular-nums'>
|
||||
{formatFixedPrice(
|
||||
props.model,
|
||||
baseGroupKey,
|
||||
@@ -771,7 +771,7 @@ function PriceSection(props: {
|
||||
{primaryPriceTypes.map((item) => (
|
||||
<div key={item.type} className='bg-muted/20 rounded-lg border p-3'>
|
||||
<div className='text-muted-foreground text-xs'>{item.label}</div>
|
||||
<div className='text-foreground mt-1 font-mono text-base font-semibold tabular-nums'>
|
||||
<div className='text-foreground mt-1 text-base font-semibold tabular-nums'>
|
||||
{renderPrice(item.type)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -788,7 +788,7 @@ function PriceSection(props: {
|
||||
<span className='text-muted-foreground/70 text-sm'>
|
||||
{item.label}
|
||||
</span>
|
||||
<span className='text-muted-foreground font-mono text-sm tabular-nums'>
|
||||
<span className='text-muted-foreground text-sm tabular-nums'>
|
||||
{renderPrice(item.type)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -923,7 +923,7 @@ function GroupPricingSection(props: {
|
||||
}
|
||||
|
||||
const thClass =
|
||||
'text-muted-foreground py-2 text-[10px] font-medium tracking-wider uppercase'
|
||||
'text-muted-foreground py-2 text-xs font-medium tracking-wider uppercase'
|
||||
|
||||
if (isDynamicPricingModel(props.model)) {
|
||||
const dynamicTiers = getDynamicPricingTiers(props.model)
|
||||
@@ -933,8 +933,8 @@ function GroupPricingSection(props: {
|
||||
<section>
|
||||
<SectionTitle>{t('Pricing by Group')}</SectionTitle>
|
||||
<AutoGroupChain model={props.model} autoGroups={props.autoGroups} />
|
||||
<div className='rounded-lg border border-amber-200/70 bg-amber-50/70 p-3 dark:border-amber-500/20 dark:bg-amber-500/10'>
|
||||
<div className='text-sm font-medium text-amber-800 dark:text-amber-200'>
|
||||
<div className='border-warning/30 bg-warning/10 rounded-lg border p-3'>
|
||||
<div className='text-warning text-sm font-medium'>
|
||||
{t('Special billing expression')}
|
||||
</div>
|
||||
<p className='text-muted-foreground mt-1 text-xs'>
|
||||
@@ -943,7 +943,7 @@ function GroupPricingSection(props: {
|
||||
)}
|
||||
</p>
|
||||
<div className='mt-3'>
|
||||
<div className='text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase'>
|
||||
<div className='text-muted-foreground mb-1 text-xs font-medium tracking-wider uppercase'>
|
||||
{t('Raw expression')}
|
||||
</div>
|
||||
<code className='text-muted-foreground bg-background/80 block max-h-28 overflow-auto rounded-md border px-2 py-1.5 font-mono text-xs break-all'>
|
||||
@@ -1028,7 +1028,7 @@ function GroupPricingSection(props: {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<p className='text-muted-foreground/40 mt-1.5 text-[10px]'>
|
||||
<p className='text-muted-foreground/40 mt-1.5 text-xs'>
|
||||
{t('Prices shown per')} {tokenUnitLabel} tokens
|
||||
</p>
|
||||
</div>
|
||||
@@ -1119,7 +1119,7 @@ function GroupPricingSection(props: {
|
||||
/>
|
||||
<div className='-mx-4 sm:mx-0'>
|
||||
{isTokenBased && (
|
||||
<p className='text-muted-foreground/40 mt-1.5 px-4 text-[10px] sm:px-0'>
|
||||
<p className='text-muted-foreground/40 mt-1.5 px-4 text-xs sm:px-0'>
|
||||
{t('Prices shown per')} {tokenUnitLabel} tokens
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const ModelPerfBadge = memo(function ModelPerfBadge(
|
||||
)}
|
||||
>
|
||||
<div title={t('Average latency')} className='min-w-0'>
|
||||
<div className='text-muted-foreground/55 text-[10px] leading-4'>
|
||||
<div className='text-muted-foreground/55 text-xs leading-4'>
|
||||
{t('Latency short')}
|
||||
</div>
|
||||
<div className='text-muted-foreground/80 font-mono text-xs leading-4 whitespace-nowrap'>
|
||||
@@ -87,7 +87,7 @@ export const ModelPerfBadge = memo(function ModelPerfBadge(
|
||||
</div>
|
||||
</div>
|
||||
<div title={t('Throughput')} className='min-w-0'>
|
||||
<div className='text-muted-foreground/55 truncate text-[10px] leading-4'>
|
||||
<div className='text-muted-foreground/55 truncate text-xs leading-4'>
|
||||
{t('Throughput short')}
|
||||
</div>
|
||||
<div className='text-muted-foreground/80 font-mono text-xs leading-4 whitespace-nowrap'>
|
||||
@@ -98,7 +98,7 @@ export const ModelPerfBadge = memo(function ModelPerfBadge(
|
||||
title={`${t('Success rate')}: ${success_rate.toFixed(1)}%`}
|
||||
className='min-w-0'
|
||||
>
|
||||
<div className='text-muted-foreground/55 truncate text-[10px] leading-4'>
|
||||
<div className='text-muted-foreground/55 truncate text-xs leading-4'>
|
||||
{t('Status short')}
|
||||
</div>
|
||||
<div className='flex h-4 items-center justify-end gap-0.5'>
|
||||
|
||||
+13
-15
@@ -135,13 +135,13 @@ export function usePricingColumns(
|
||||
if (dynamicSummary.isSpecialExpression) {
|
||||
return (
|
||||
<div className='max-w-full min-w-0'>
|
||||
<div className='text-xs font-medium text-amber-700 dark:text-amber-300'>
|
||||
<div className='text-warning text-xs font-medium'>
|
||||
{t('Special billing expression')}
|
||||
</div>
|
||||
<div className='text-muted-foreground text-[11px]'>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{t('Unable to parse structured pricing')}
|
||||
</div>
|
||||
<code className='text-muted-foreground/70 mt-1 line-clamp-2 block font-mono text-[10px] leading-relaxed break-all'>
|
||||
<code className='text-muted-foreground/70 mt-1 line-clamp-2 block font-mono text-xs leading-relaxed break-all'>
|
||||
{dynamicSummary.rawExpression}
|
||||
</code>
|
||||
</div>
|
||||
@@ -159,7 +159,7 @@ export function usePricingColumns(
|
||||
|
||||
return (
|
||||
<div className='max-w-full min-w-0'>
|
||||
<span className='font-mono text-sm tabular-nums'>
|
||||
<span className='text-sm tabular-nums'>
|
||||
{primaryEntries.map((entry, index) => (
|
||||
<span key={entry.key}>
|
||||
{index > 0 && (
|
||||
@@ -169,7 +169,7 @@ export function usePricingColumns(
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
<div className='text-muted-foreground/50 text-[10px]'>
|
||||
<div className='text-muted-foreground/50 text-xs'>
|
||||
/ {tokenUnitLabel} tokens
|
||||
{dynamicSummary.tierCount > 1 &&
|
||||
` · ${t('{{count}} tiers', {
|
||||
@@ -208,12 +208,12 @@ export function usePricingColumns(
|
||||
|
||||
return (
|
||||
<div className='max-w-full min-w-0'>
|
||||
<span className='font-mono text-sm tabular-nums'>
|
||||
<span className='text-sm tabular-nums'>
|
||||
{inputPrice}
|
||||
<span className='text-muted-foreground/40 mx-1'>/</span>
|
||||
{outputPrice}
|
||||
</span>
|
||||
<div className='text-muted-foreground/50 text-[10px]'>
|
||||
<div className='text-muted-foreground/50 text-xs'>
|
||||
/ {tokenUnitLabel} tokens
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,8 +232,8 @@ export function usePricingColumns(
|
||||
|
||||
return (
|
||||
<div className='max-w-full min-w-0'>
|
||||
<span className='font-mono text-sm tabular-nums'>{price}</span>
|
||||
<div className='text-muted-foreground/50 text-[10px]'>
|
||||
<span className='text-sm tabular-nums'>{price}</span>
|
||||
<div className='text-muted-foreground/50 text-xs'>
|
||||
/ {t('request')}
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,10 +278,10 @@ export function usePricingColumns(
|
||||
|
||||
return (
|
||||
<div className='max-w-full min-w-0'>
|
||||
<span className='font-mono text-sm tabular-nums'>
|
||||
<span className='text-sm tabular-nums'>
|
||||
{stripTrailingZeros(cacheEntry.formatted)}
|
||||
</span>
|
||||
<div className='text-muted-foreground/50 text-[10px]'>
|
||||
<div className='text-muted-foreground/50 text-xs'>
|
||||
/ {tokenUnitLabel}
|
||||
</div>
|
||||
</div>
|
||||
@@ -308,10 +308,8 @@ export function usePricingColumns(
|
||||
|
||||
return (
|
||||
<div className='max-w-full min-w-0'>
|
||||
<span className='font-mono text-sm tabular-nums'>
|
||||
{cachedPrice}
|
||||
</span>
|
||||
<div className='text-muted-foreground/50 text-[10px]'>
|
||||
<span className='text-sm tabular-nums'>{cachedPrice}</span>
|
||||
<div className='text-muted-foreground/50 text-xs'>
|
||||
/ {tokenUnitLabel}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,7 +115,7 @@ function FilterChip(props: {
|
||||
{(props.option.suffix || props.option.count != null) && (
|
||||
<span
|
||||
className={cn(
|
||||
'rounded-md px-1.5 py-0.5 text-[10px]',
|
||||
'rounded-md px-1.5 py-0.5 text-xs',
|
||||
props.active
|
||||
? 'bg-background text-foreground'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
|
||||
@@ -179,7 +179,7 @@ export function PricingToolbar(props: PricingToolbarProps) {
|
||||
<Filter className='size-4' />
|
||||
{t('Filter')}
|
||||
{props.activeFilterCount > 0 && (
|
||||
<Badge className='ml-0.5 size-5 justify-center p-0 text-[10px]'>
|
||||
<Badge className='ml-0.5 size-5 justify-center p-0 text-xs'>
|
||||
{props.activeFilterCount}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function SearchBar(props: SearchBarProps) {
|
||||
<X className='size-4' />
|
||||
</Button>
|
||||
) : (
|
||||
<kbd className='bg-muted text-muted-foreground pointer-events-none hidden rounded border px-1.5 py-0.5 font-mono text-[10px] sm:inline-block'>
|
||||
<kbd className='bg-muted text-muted-foreground pointer-events-none hidden rounded border px-1.5 py-0.5 font-mono text-xs sm:inline-block'>
|
||||
⌘K
|
||||
</kbd>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user