feat(admin): add metric toggle to usage charts

This commit is contained in:
Peter
2026-03-11 16:05:27 +08:00
parent 7455476c60
commit 0ddaef3c9a
8 changed files with 547 additions and 24 deletions

View File

@@ -1,12 +1,39 @@
<template>
<div class="card p-4">
<h3 class="mb-4 text-sm font-semibold text-gray-900 dark:text-white">
{{ t('admin.dashboard.modelDistribution') }}
</h3>
<div class="mb-4 flex items-center justify-between gap-3">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
{{ t('admin.dashboard.modelDistribution') }}
</h3>
<div
v-if="showMetricToggle"
class="inline-flex rounded-lg border border-gray-200 bg-gray-50 p-0.5 dark:border-gray-700 dark:bg-dark-800"
>
<button
type="button"
class="rounded-md px-2.5 py-1 text-xs font-medium transition-colors"
:class="metric === 'tokens'
? 'bg-white text-gray-900 shadow-sm dark:bg-dark-700 dark:text-white'
: 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'"
@click="emit('update:metric', 'tokens')"
>
{{ t('admin.dashboard.metricTokens') }}
</button>
<button
type="button"
class="rounded-md px-2.5 py-1 text-xs font-medium transition-colors"
:class="metric === 'actual_cost'
? 'bg-white text-gray-900 shadow-sm dark:bg-dark-700 dark:text-white'
: 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'"
@click="emit('update:metric', 'actual_cost')"
>
{{ t('admin.dashboard.metricActualCost') }}
</button>
</div>
</div>
<div v-if="loading" class="flex h-48 items-center justify-center">
<LoadingSpinner />
</div>
<div v-else-if="modelStats.length > 0 && chartData" class="flex items-center gap-6">
<div v-else-if="displayModelStats.length > 0 && chartData" class="flex items-center gap-6">
<div class="h-48 w-48">
<Doughnut :data="chartData" :options="doughnutOptions" />
</div>
@@ -23,7 +50,7 @@
</thead>
<tbody>
<tr
v-for="model in modelStats"
v-for="model in displayModelStats"
:key="model.model"
class="border-t border-gray-100 dark:border-gray-700"
>
@@ -71,9 +98,21 @@ ChartJS.register(ArcElement, Tooltip, Legend)
const { t } = useI18n()
const props = defineProps<{
type DistributionMetric = 'tokens' | 'actual_cost'
const props = withDefaults(defineProps<{
modelStats: ModelStat[]
loading?: boolean
metric?: DistributionMetric
showMetricToggle?: boolean
}>(), {
loading: false,
metric: 'tokens',
showMetricToggle: false,
})
const emit = defineEmits<{
'update:metric': [value: DistributionMetric]
}>()
const chartColors = [
@@ -89,15 +128,22 @@ const chartColors = [
'#84cc16'
]
const displayModelStats = computed(() => {
if (!props.modelStats?.length) return []
const metricKey = props.metric === 'actual_cost' ? 'actual_cost' : 'total_tokens'
return [...props.modelStats].sort((a, b) => b[metricKey] - a[metricKey])
})
const chartData = computed(() => {
if (!props.modelStats?.length) return null
return {
labels: props.modelStats.map((m) => m.model),
labels: displayModelStats.value.map((m) => m.model),
datasets: [
{
data: props.modelStats.map((m) => m.total_tokens),
backgroundColor: chartColors.slice(0, props.modelStats.length),
data: displayModelStats.value.map((m) => props.metric === 'actual_cost' ? m.actual_cost : m.total_tokens),
backgroundColor: chartColors.slice(0, displayModelStats.value.length),
borderWidth: 0
}
]
@@ -116,8 +162,11 @@ const doughnutOptions = computed(() => ({
label: (context: any) => {
const value = context.raw as number
const total = context.dataset.data.reduce((a: number, b: number) => a + b, 0)
const percentage = ((value / total) * 100).toFixed(1)
return `${context.label}: ${formatTokens(value)} (${percentage}%)`
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0'
const formattedValue = props.metric === 'actual_cost'
? `$${formatCost(value)}`
: formatTokens(value)
return `${context.label}: ${formattedValue} (${percentage}%)`
}
}
}