feat: /admin/usage页面增加模型分布情况显示
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('usage.totalCost') }}</p>
|
||||
<p class="text-xl font-bold text-green-600 dark:text-green-400">${{ (usageStats?.total_actual_cost || 0).toFixed(4) }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('usage.actualCost') }} / <span class="line-through">${{ (usageStats?.total_cost || 0).toFixed(4) }}</span> {{ t('usage.standardCost') }}
|
||||
<span class="line-through">${{ (usageStats?.total_cost || 0).toFixed(4) }}</span> {{ t('usage.standardCost') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,6 +70,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Section -->
|
||||
<div class="space-y-4">
|
||||
<!-- Chart Controls -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.dashboard.granularity') }}:</span>
|
||||
<div class="w-28">
|
||||
<Select
|
||||
v-model="granularity"
|
||||
:options="granularityOptions"
|
||||
@change="onGranularityChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Grid -->
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<ModelDistributionChart
|
||||
:model-stats="modelStats"
|
||||
:loading="chartsLoading"
|
||||
/>
|
||||
<TokenUsageTrend
|
||||
:trend-data="trendData"
|
||||
:loading="chartsLoading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card">
|
||||
<div class="px-6 py-4">
|
||||
@@ -324,7 +353,9 @@ import Pagination from '@/components/common/Pagination.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import DateRangePicker from '@/components/common/DateRangePicker.vue'
|
||||
import type { UsageLog } from '@/types'
|
||||
import ModelDistributionChart from '@/components/charts/ModelDistributionChart.vue'
|
||||
import TokenUsageTrend from '@/components/charts/TokenUsageTrend.vue'
|
||||
import type { UsageLog, TrendDataPoint, ModelStat } from '@/types'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import type { SimpleUser, SimpleApiKey, AdminUsageStatsResponse, AdminUsageQueryParams } from '@/api/admin/usage'
|
||||
|
||||
@@ -334,6 +365,18 @@ const appStore = useAppStore()
|
||||
// Usage stats from API
|
||||
const usageStats = ref<AdminUsageStatsResponse | null>(null)
|
||||
|
||||
// Chart data
|
||||
const trendData = ref<TrendDataPoint[]>([])
|
||||
const modelStats = ref<ModelStat[]>([])
|
||||
const chartsLoading = ref(false)
|
||||
const granularity = ref<'day' | 'hour'>('day')
|
||||
|
||||
// Granularity options for Select component
|
||||
const granularityOptions = computed(() => [
|
||||
{ value: 'day', label: t('admin.dashboard.day') },
|
||||
{ value: 'hour', label: t('admin.dashboard.hour') },
|
||||
])
|
||||
|
||||
const columns = computed<Column[]>(() => [
|
||||
{ key: 'user', label: t('admin.usage.user'), sortable: false },
|
||||
{ key: 'api_key', label: t('usage.apiKeyFilter'), sortable: false },
|
||||
@@ -535,10 +578,45 @@ const loadUsageStats = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadChartData = async () => {
|
||||
chartsLoading.value = true
|
||||
try {
|
||||
const params = {
|
||||
start_date: filters.value.start_date || startDate.value,
|
||||
end_date: filters.value.end_date || endDate.value,
|
||||
granularity: granularity.value,
|
||||
user_id: filters.value.user_id,
|
||||
api_key_id: filters.value.api_key_id ? Number(filters.value.api_key_id) : undefined,
|
||||
}
|
||||
|
||||
const [trendResponse, modelResponse] = await Promise.all([
|
||||
adminAPI.dashboard.getUsageTrend(params),
|
||||
adminAPI.dashboard.getModelStats({
|
||||
start_date: params.start_date,
|
||||
end_date: params.end_date,
|
||||
user_id: params.user_id,
|
||||
api_key_id: params.api_key_id,
|
||||
}),
|
||||
])
|
||||
|
||||
trendData.value = trendResponse.trend || []
|
||||
modelStats.value = modelResponse.models || []
|
||||
} catch (error) {
|
||||
console.error('Failed to load chart data:', error)
|
||||
} finally {
|
||||
chartsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onGranularityChange = () => {
|
||||
loadChartData()
|
||||
}
|
||||
|
||||
const applyFilters = () => {
|
||||
pagination.value.page = 1
|
||||
loadUsageLogs()
|
||||
loadUsageStats()
|
||||
loadChartData()
|
||||
}
|
||||
|
||||
const resetFilters = () => {
|
||||
@@ -552,11 +630,13 @@ const resetFilters = () => {
|
||||
start_date: undefined,
|
||||
end_date: undefined
|
||||
}
|
||||
granularity.value = 'day'
|
||||
// Reset date range to default (last 7 days)
|
||||
initializeDateRange()
|
||||
pagination.value.page = 1
|
||||
loadUsageLogs()
|
||||
loadUsageStats()
|
||||
loadChartData()
|
||||
}
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
@@ -614,6 +694,7 @@ onMounted(() => {
|
||||
initializeDateRange()
|
||||
loadUsageLogs()
|
||||
loadUsageStats()
|
||||
loadChartData()
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user