mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-13 22:50:19 +08:00
style(frontend): refine badges and cycle stats
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import { afterEach, describe, expect, it } from 'vitest'
|
||||||
|
import { createApp, h, type App } from 'vue'
|
||||||
|
|
||||||
|
import Badge from '../badge.vue'
|
||||||
|
|
||||||
|
const mountedApps: Array<{ app: App, root: HTMLElement }> = []
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const { app, root } of mountedApps.splice(0)) {
|
||||||
|
app.unmount()
|
||||||
|
root.remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Badge', () => {
|
||||||
|
it('renders the transparent outline variant without the card background', () => {
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
const app = createApp({
|
||||||
|
render: () => h(Badge, { variant: 'outline-transparent' }, () => 'Fast'),
|
||||||
|
})
|
||||||
|
|
||||||
|
app.mount(root)
|
||||||
|
mountedApps.push({ app, root })
|
||||||
|
|
||||||
|
const badge = root.firstElementChild
|
||||||
|
expect(badge?.classList.contains('border-border')).toBe(true)
|
||||||
|
expect(badge?.classList.contains('bg-transparent')).toBe(true)
|
||||||
|
expect(badge?.classList.contains('bg-card/50')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -20,6 +20,7 @@ const badgeVariants = cva(
|
|||||||
destructive:
|
destructive:
|
||||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||||
outline: 'text-foreground border-border bg-card/50',
|
outline: 'text-foreground border-border bg-card/50',
|
||||||
|
'outline-transparent': 'text-foreground border-border bg-transparent',
|
||||||
success:
|
success:
|
||||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||||
warning:
|
warning:
|
||||||
@@ -35,7 +36,7 @@ const badgeVariants = cva(
|
|||||||
)
|
)
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning' | 'dark'
|
variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'outline-transparent' | 'success' | 'warning' | 'dark'
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,13 @@
|
|||||||
<div
|
<div
|
||||||
v-for="row in cycleMetricRows"
|
v-for="row in cycleMetricRows"
|
||||||
:key="`${row.key}-${variant}-cycle-row`"
|
:key="`${row.key}-${variant}-cycle-row`"
|
||||||
class="flex items-center justify-between gap-2"
|
class="truncate"
|
||||||
|
:title="`${row.label} ${row.valueText}`"
|
||||||
>
|
>
|
||||||
<span class="shrink-0 text-muted-foreground">{{ row.label }}</span>
|
|
||||||
<span
|
<span
|
||||||
class="min-w-0 truncate text-right font-medium tabular-nums text-foreground/90"
|
|
||||||
:data-testid="variant === 'desktop' ? `pool-stats-cycle-${row.key}` : undefined"
|
:data-testid="variant === 'desktop' ? `pool-stats-cycle-${row.key}` : undefined"
|
||||||
:title="row.valueText"
|
|
||||||
>
|
>
|
||||||
{{ row.valueText }}
|
{{ row.label }} {{ row.valueText }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +114,7 @@ const cycleMetricRows = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const cycleContainerClass = computed(() => [
|
const cycleContainerClass = computed(() => [
|
||||||
'mx-auto w-[132px] space-y-1 text-[9px] leading-3',
|
'mx-auto w-[132px] space-y-0.5 text-[10px] leading-4 text-foreground/90 tabular-nums',
|
||||||
props.variant === 'mobile' ? 'py-0.5' : '',
|
props.variant === 'mobile' ? 'py-0.5' : '',
|
||||||
].filter(Boolean).join(' '))
|
].filter(Boolean).join(' '))
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ describe('pool key display panels', () => {
|
|||||||
|
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')).toBeTruthy()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')).toBeTruthy()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')?.className).toContain('w-[132px]')
|
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')?.className).toContain('w-[132px]')
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('12/88')
|
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 12/88')
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-large-base"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-large-base"]')).toBeNull()
|
||||||
expect(root.textContent).not.toContain('5H')
|
expect(root.textContent).not.toContain('5H')
|
||||||
@@ -98,7 +98,7 @@ describe('pool key display panels', () => {
|
|||||||
app.use(createI18n())
|
app.use(createI18n())
|
||||||
app.mount(root)
|
app.mount(root)
|
||||||
|
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('31')
|
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 31')
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-single-marker"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-single-marker"]')).toBeNull()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-bar-request_count"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-bar-request_count"]')).toBeNull()
|
||||||
expect(root.textContent).not.toContain('月')
|
expect(root.textContent).not.toContain('月')
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
:key="badge.key"
|
:key="badge.key"
|
||||||
:data-usage-model-badge="badge.key"
|
:data-usage-model-badge="badge.key"
|
||||||
:data-request-detail-model-badge="context === 'detail' ? badge.key : undefined"
|
:data-request-detail-model-badge="context === 'detail' ? badge.key : undefined"
|
||||||
variant="outline"
|
:variant="badge.variant"
|
||||||
class="h-4 shrink-0 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4"
|
class="h-4 shrink-0 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4"
|
||||||
:class="badge.className"
|
:class="badge.className"
|
||||||
:title="badge.title"
|
:title="badge.title"
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
:key="badge.key"
|
:key="badge.key"
|
||||||
:data-usage-model-badge="badge.key"
|
:data-usage-model-badge="badge.key"
|
||||||
:data-request-detail-model-badge="context === 'detail' ? badge.key : undefined"
|
:data-request-detail-model-badge="context === 'detail' ? badge.key : undefined"
|
||||||
variant="outline"
|
:variant="badge.variant"
|
||||||
class="h-4 shrink-0 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4"
|
class="h-4 shrink-0 whitespace-nowrap rounded-full px-1.5 text-[10px] leading-4"
|
||||||
:class="badge.className"
|
:class="badge.className"
|
||||||
:title="badge.title"
|
:title="badge.title"
|
||||||
@@ -76,6 +76,7 @@ type ModelBadgeKey = 'reasoning' | 'fast' | 'cyber'
|
|||||||
interface ModelBadgePresentation {
|
interface ModelBadgePresentation {
|
||||||
key: ModelBadgeKey
|
key: ModelBadgeKey
|
||||||
label: string
|
label: string
|
||||||
|
variant: 'outline' | 'outline-transparent'
|
||||||
className: string
|
className: string
|
||||||
title: string
|
title: string
|
||||||
ariaLabel: string
|
ariaLabel: string
|
||||||
@@ -130,6 +131,7 @@ const modelBadges = computed<ModelBadgePresentation[]>(() => {
|
|||||||
badges.push({
|
badges.push({
|
||||||
key: 'reasoning',
|
key: 'reasoning',
|
||||||
label: reasoningLabel.value,
|
label: reasoningLabel.value,
|
||||||
|
variant: 'outline',
|
||||||
className: 'border-primary/30 bg-primary/5 text-primary',
|
className: 'border-primary/30 bg-primary/5 text-primary',
|
||||||
title: `Reasoning: ${reasoningLabel.value}`,
|
title: `Reasoning: ${reasoningLabel.value}`,
|
||||||
ariaLabel: `Reasoning: ${reasoningLabel.value}`,
|
ariaLabel: `Reasoning: ${reasoningLabel.value}`,
|
||||||
@@ -140,7 +142,8 @@ const modelBadges = computed<ModelBadgePresentation[]>(() => {
|
|||||||
badges.push({
|
badges.push({
|
||||||
key: 'fast',
|
key: 'fast',
|
||||||
label: 'Fast',
|
label: 'Fast',
|
||||||
className: 'border-amber-400/50 !bg-transparent text-amber-700 dark:border-amber-300/40 dark:text-amber-300',
|
variant: 'outline-transparent',
|
||||||
|
className: 'text-amber-700 dark:text-amber-300',
|
||||||
title: '上游请求档位:Fast\n计费档位:Fast',
|
title: '上游请求档位:Fast\n计费档位:Fast',
|
||||||
ariaLabel: '上游请求档位:Fast,计费档位:Fast',
|
ariaLabel: '上游请求档位:Fast,计费档位:Fast',
|
||||||
})
|
})
|
||||||
@@ -150,6 +153,7 @@ const modelBadges = computed<ModelBadgePresentation[]>(() => {
|
|||||||
badges.push({
|
badges.push({
|
||||||
key: 'cyber',
|
key: 'cyber',
|
||||||
label: 'Cyber',
|
label: 'Cyber',
|
||||||
|
variant: 'outline',
|
||||||
className: 'border-primary/30 bg-primary/5 text-rose-600 dark:text-rose-300',
|
className: 'border-primary/30 bg-primary/5 text-rose-600 dark:text-rose-300',
|
||||||
title: '上游 Cyber Policy 拒绝',
|
title: '上游 Cyber Policy 拒绝',
|
||||||
ariaLabel: '上游 Cyber Policy 拒绝',
|
ariaLabel: '上游 Cyber Policy 拒绝',
|
||||||
|
|||||||
@@ -1579,7 +1579,7 @@ function buildServiceTierBadgePresentation(
|
|||||||
const title = titleLines.join('\n')
|
const title = titleLines.join('\n')
|
||||||
return {
|
return {
|
||||||
label: 'Fast',
|
label: 'Fast',
|
||||||
className: 'border-amber-400/50 !bg-transparent text-amber-700 dark:border-amber-300/40 dark:text-amber-300',
|
className: '!bg-transparent text-amber-700 dark:text-amber-300',
|
||||||
title,
|
title,
|
||||||
ariaLabel: titleLines.join(','),
|
ariaLabel: titleLines.join(','),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,9 +339,9 @@ describe('UsageRecordsTable', () => {
|
|||||||
expect(reasoningBadge?.classList.contains('border-primary/30')).toBe(true)
|
expect(reasoningBadge?.classList.contains('border-primary/30')).toBe(true)
|
||||||
expect(reasoningBadge?.classList.contains('bg-primary/5')).toBe(true)
|
expect(reasoningBadge?.classList.contains('bg-primary/5')).toBe(true)
|
||||||
expect(reasoningBadge?.classList.contains('text-primary')).toBe(true)
|
expect(reasoningBadge?.classList.contains('text-primary')).toBe(true)
|
||||||
expect(fastBadge?.classList.contains('border-amber-400/50')).toBe(true)
|
expect(fastBadge?.getAttribute('variant')).toBe('outline-transparent')
|
||||||
expect(fastBadge?.classList.contains('!bg-transparent')).toBe(true)
|
expect(fastBadge?.classList.contains('border-amber-400/50')).toBe(false)
|
||||||
expect(fastBadge?.classList.contains('bg-transparent')).toBe(false)
|
expect(fastBadge?.classList.contains('!bg-transparent')).toBe(false)
|
||||||
expect(fastBadge?.classList.contains('bg-amber-400/10')).toBe(false)
|
expect(fastBadge?.classList.contains('bg-amber-400/10')).toBe(false)
|
||||||
expect(fastBadge?.classList.contains('text-amber-700')).toBe(true)
|
expect(fastBadge?.classList.contains('text-amber-700')).toBe(true)
|
||||||
expect(cyberBadge?.classList.contains('border-primary/30')).toBe(true)
|
expect(cyberBadge?.classList.contains('border-primary/30')).toBe(true)
|
||||||
|
|||||||
@@ -577,8 +577,8 @@ describe('PoolManagement Codex cycle stats mode', () => {
|
|||||||
|
|
||||||
expect(root.querySelector('[data-testid="pool-stats-mode-control"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-mode-control"]')).toBeNull()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')).not.toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-text"]')).not.toBeNull()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('7/12')
|
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 7/12')
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-total_tokens"]')?.textContent?.trim()).toBe('2.5K/5K')
|
expect(root.querySelector('[data-testid="pool-stats-cycle-total_tokens"]')?.textContent?.trim()).toBe('Token 2.5K/5K')
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-large-base"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-large-base"]')).toBeNull()
|
||||||
expect(endpointMocks.listPoolKeys).toHaveBeenLastCalledWith(
|
expect(endpointMocks.listPoolKeys).toHaveBeenLastCalledWith(
|
||||||
@@ -707,7 +707,7 @@ describe('PoolManagement Codex cycle stats mode', () => {
|
|||||||
expect(periodLabels).toContain('月')
|
expect(periodLabels).toContain('月')
|
||||||
expect(periodLabels).not.toContain('5H')
|
expect(periodLabels).not.toContain('5H')
|
||||||
expect(periodLabels).not.toContain('周')
|
expect(periodLabels).not.toContain('周')
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('23')
|
expect(root.querySelector('[data-testid="pool-stats-cycle-request_count"]')?.textContent?.trim()).toBe('请求 23')
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-small-overlay"]')).toBeNull()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-bar-request_count"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-bar-request_count"]')).toBeNull()
|
||||||
expect(root.querySelector('[data-testid="pool-stats-cycle-single-marker"]')).toBeNull()
|
expect(root.querySelector('[data-testid="pool-stats-cycle-single-marker"]')).toBeNull()
|
||||||
|
|||||||
Reference in New Issue
Block a user