mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(usage): 统计表格新增输入/输出与缓存创建token细分展示
- 后端接口(admin/user_me/query)新增 output_tokens、cache_creation_tokens、total_input_context 字段返回 - 前端统计表格(模型/提供商/API格式)合并 Tokens 列为"输入/输出"+"缓存读取/创建"两行紧凑布局 - 合并"缓存Token"和"缓存命中率"为单一"命中率"列,减少表格宽度 - 修正 UsageRecordsTable 缩进及模板格式
This commit is contained in:
@@ -16,16 +16,16 @@
|
|||||||
请求数
|
请求数
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
Tokens
|
<div class="flex flex-col text-xs gap-0.5">
|
||||||
|
<span>输入/输出</span>
|
||||||
|
<span class="text-muted-foreground font-normal">缓存</span>
|
||||||
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
费用
|
费用
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
缓存Token
|
命中率
|
||||||
</TableHead>
|
|
||||||
<TableHead class="h-8 px-2 text-right">
|
|
||||||
缓存命中率
|
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
平均响应
|
平均响应
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow v-if="data.length === 0">
|
<TableRow v-if="data.length === 0">
|
||||||
<TableCell
|
<TableCell
|
||||||
:colspan="7"
|
:colspan="6"
|
||||||
class="text-center py-6 text-muted-foreground px-2"
|
class="text-center py-6 text-muted-foreground px-2"
|
||||||
>
|
>
|
||||||
暂无API格式统计数据
|
暂无API格式统计数据
|
||||||
@@ -52,7 +52,10 @@
|
|||||||
{{ item.request_count }}
|
{{ item.request_count }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
<span>{{ formatTokens(item.total_tokens) }}</span>
|
<div class="flex flex-col items-end text-xs gap-0.5">
|
||||||
|
<span>{{ formatTokens(item.total_input_context || 0) }} / {{ formatTokens(item.output_tokens || 0) }}</span>
|
||||||
|
<span class="text-muted-foreground">{{ formatTokens(item.cache_read_tokens || 0) }} / {{ formatTokens(item.cache_creation_tokens || 0) }}</span>
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
<div class="flex flex-col items-end text-xs gap-0.5">
|
<div class="flex flex-col items-end text-xs gap-0.5">
|
||||||
@@ -66,10 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
{{ formatTokens(item.cache_read_tokens || 0) }}
|
<span>{{ formatHitRate(item.cache_hit_rate) }}</span>
|
||||||
</TableCell>
|
|
||||||
<TableCell class="text-right py-2 px-2 text-muted-foreground">
|
|
||||||
{{ formatHitRate(item.cache_hit_rate) }}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right text-muted-foreground py-2 px-2">
|
<TableCell class="text-right text-muted-foreground py-2 px-2">
|
||||||
{{ item.avgResponseTime }}
|
{{ item.avgResponseTime }}
|
||||||
|
|||||||
@@ -16,16 +16,16 @@
|
|||||||
请求数
|
请求数
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
Tokens
|
<div class="flex flex-col text-xs gap-0.5">
|
||||||
|
<span>输入/输出</span>
|
||||||
|
<span class="text-muted-foreground font-normal">缓存</span>
|
||||||
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
费用
|
费用
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
缓存Token
|
命中率
|
||||||
</TableHead>
|
|
||||||
<TableHead class="h-8 px-2 text-right">
|
|
||||||
缓存命中率
|
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
效率
|
效率
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow v-if="data.length === 0">
|
<TableRow v-if="data.length === 0">
|
||||||
<TableCell
|
<TableCell
|
||||||
:colspan="7"
|
:colspan="6"
|
||||||
class="text-center py-6 text-muted-foreground px-2"
|
class="text-center py-6 text-muted-foreground px-2"
|
||||||
>
|
>
|
||||||
暂无模型统计数据
|
暂无模型统计数据
|
||||||
@@ -52,7 +52,10 @@
|
|||||||
{{ model.request_count }}
|
{{ model.request_count }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
<span>{{ formatTokens(model.total_tokens) }}</span>
|
<div class="flex flex-col items-end text-xs gap-0.5">
|
||||||
|
<span>{{ formatTokens(model.total_input_context || 0) }} / {{ formatTokens(model.output_tokens || 0) }}</span>
|
||||||
|
<span class="text-muted-foreground">{{ formatTokens(model.cache_read_tokens || 0) }} / {{ formatTokens(model.cache_creation_tokens || 0) }}</span>
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
<div class="flex flex-col items-end text-xs gap-0.5">
|
<div class="flex flex-col items-end text-xs gap-0.5">
|
||||||
@@ -66,10 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
{{ formatTokens(model.cache_read_tokens || 0) }}
|
<span>{{ formatHitRate(model.cache_hit_rate) }}</span>
|
||||||
</TableCell>
|
|
||||||
<TableCell class="text-right py-2 px-2 text-muted-foreground">
|
|
||||||
{{ formatHitRate(model.cache_hit_rate) }}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right text-muted-foreground py-2 px-2">
|
<TableCell class="text-right text-muted-foreground py-2 px-2">
|
||||||
{{ model.costPerToken }}
|
{{ model.costPerToken }}
|
||||||
|
|||||||
@@ -16,16 +16,16 @@
|
|||||||
请求数
|
请求数
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
Tokens
|
<div class="flex flex-col text-xs gap-0.5">
|
||||||
|
<span>输入/输出</span>
|
||||||
|
<span class="text-muted-foreground font-normal">缓存</span>
|
||||||
|
</div>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
费用
|
费用
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
缓存Token
|
命中率
|
||||||
</TableHead>
|
|
||||||
<TableHead class="h-8 px-2 text-right">
|
|
||||||
缓存命中率
|
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead class="h-8 px-2 text-right">
|
<TableHead class="h-8 px-2 text-right">
|
||||||
成功率
|
成功率
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow v-if="data.length === 0">
|
<TableRow v-if="data.length === 0">
|
||||||
<TableCell
|
<TableCell
|
||||||
:colspan="8"
|
:colspan="7"
|
||||||
class="text-center py-6 text-muted-foreground px-2"
|
class="text-center py-6 text-muted-foreground px-2"
|
||||||
>
|
>
|
||||||
暂无提供商统计数据
|
暂无提供商统计数据
|
||||||
@@ -55,7 +55,10 @@
|
|||||||
{{ provider.requests }}
|
{{ provider.requests }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
<span>{{ formatTokens(provider.totalTokens) }}</span>
|
<div class="flex flex-col items-end text-xs gap-0.5">
|
||||||
|
<span>{{ formatTokens(provider.totalInputContext || 0) }} / {{ formatTokens(provider.outputTokens || 0) }}</span>
|
||||||
|
<span class="text-muted-foreground">{{ formatTokens(provider.cacheReadTokens || 0) }} / {{ formatTokens(provider.cacheCreationTokens || 0) }}</span>
|
||||||
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
<div class="flex flex-col items-end text-xs gap-0.5">
|
<div class="flex flex-col items-end text-xs gap-0.5">
|
||||||
@@ -69,10 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
{{ formatTokens(provider.cacheReadTokens || 0) }}
|
<span>{{ formatHitRate(provider.cacheHitRate) }}</span>
|
||||||
</TableCell>
|
|
||||||
<TableCell class="text-right py-2 px-2 text-muted-foreground">
|
|
||||||
{{ formatHitRate(provider.cacheHitRate) }}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right py-2 px-2">
|
<TableCell class="text-right py-2 px-2">
|
||||||
<span :class="getSuccessRateClass(provider.successRate)">{{ provider.successRate }}%</span>
|
<span :class="getSuccessRateClass(provider.successRate)">{{ provider.successRate }}%</span>
|
||||||
@@ -108,5 +108,4 @@ function getSuccessRateClass(rate: number): string {
|
|||||||
if (rate < 90) return 'text-destructive'
|
if (rate < 90) return 'text-destructive'
|
||||||
return '' // 默认颜色
|
return '' // 默认颜色
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,167 +3,167 @@
|
|||||||
<template #actions>
|
<template #actions>
|
||||||
<!-- 时间范围筛选 -->
|
<!-- 时间范围筛选 -->
|
||||||
<TimeRangePicker
|
<TimeRangePicker
|
||||||
v-model="timeRangeModel"
|
v-model="timeRangeModel"
|
||||||
:show-granularity="false"
|
:show-granularity="false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
<!-- 分隔线 -->
|
||||||
<div class="hidden sm:block h-4 w-px bg-border" />
|
<div class="hidden sm:block h-4 w-px bg-border" />
|
||||||
|
|
||||||
<!-- 通用搜索 -->
|
<!-- 通用搜索 -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<Search class="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground z-10 pointer-events-none" />
|
<Search class="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground z-10 pointer-events-none" />
|
||||||
<Input
|
<Input
|
||||||
id="usage-records-search"
|
id="usage-records-search"
|
||||||
v-model="localSearch"
|
v-model="localSearch"
|
||||||
:placeholder="isAdmin ? '搜索用户/密钥' : '搜索密钥/模型'"
|
:placeholder="isAdmin ? '搜索用户/密钥' : '搜索密钥/模型'"
|
||||||
class="w-[7.5rem] sm:w-48 h-8 text-xs border-border/60 pl-8"
|
class="w-[7.5rem] sm:w-48 h-8 text-xs border-border/60 pl-8"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 用户筛选(仅管理员可见) -->
|
<!-- 用户筛选(仅管理员可见) -->
|
||||||
<Select
|
<Select
|
||||||
v-if="isAdmin && availableUsers.length > 0"
|
v-if="isAdmin && availableUsers.length > 0"
|
||||||
:model-value="filterUser"
|
:model-value="filterUser"
|
||||||
@update:model-value="$emit('update:filterUser', $event)"
|
@update:model-value="$emit('update:filterUser', $event)"
|
||||||
|
>
|
||||||
|
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-36 h-8 text-xs border-border/60">
|
||||||
|
<SelectValue placeholder="用户" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__all__">
|
||||||
|
全部用户
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem
|
||||||
|
v-for="user in availableUsers"
|
||||||
|
:key="user.id"
|
||||||
|
:value="user.id"
|
||||||
>
|
>
|
||||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-36 h-8 text-xs border-border/60">
|
{{ user.username || user.email }}
|
||||||
<SelectValue placeholder="用户" />
|
</SelectItem>
|
||||||
</SelectTrigger>
|
</SelectContent>
|
||||||
<SelectContent>
|
</Select>
|
||||||
<SelectItem value="__all__">
|
|
||||||
全部用户
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem
|
|
||||||
v-for="user in availableUsers"
|
|
||||||
:key="user.id"
|
|
||||||
:value="user.id"
|
|
||||||
>
|
|
||||||
{{ user.username || user.email }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<!-- 模型筛选 -->
|
<!-- 模型筛选 -->
|
||||||
<Select
|
<Select
|
||||||
:model-value="filterModel"
|
:model-value="filterModel"
|
||||||
@update:model-value="$emit('update:filterModel', $event)"
|
@update:model-value="$emit('update:filterModel', $event)"
|
||||||
|
>
|
||||||
|
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-40 h-8 text-xs border-border/60">
|
||||||
|
<SelectValue placeholder="模型" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__all__">
|
||||||
|
全部模型
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem
|
||||||
|
v-for="model in availableModels"
|
||||||
|
:key="model"
|
||||||
|
:value="model"
|
||||||
>
|
>
|
||||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-40 h-8 text-xs border-border/60">
|
{{ model.replace('claude-', '') }}
|
||||||
<SelectValue placeholder="模型" />
|
</SelectItem>
|
||||||
</SelectTrigger>
|
</SelectContent>
|
||||||
<SelectContent>
|
</Select>
|
||||||
<SelectItem value="__all__">
|
|
||||||
全部模型
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem
|
|
||||||
v-for="model in availableModels"
|
|
||||||
:key="model"
|
|
||||||
:value="model"
|
|
||||||
>
|
|
||||||
{{ model.replace('claude-', '') }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<!-- 提供商筛选(仅管理员可见) -->
|
<!-- 提供商筛选(仅管理员可见) -->
|
||||||
<Select
|
<Select
|
||||||
v-if="isAdmin"
|
v-if="isAdmin"
|
||||||
:model-value="filterProvider"
|
:model-value="filterProvider"
|
||||||
@update:model-value="$emit('update:filterProvider', $event)"
|
@update:model-value="$emit('update:filterProvider', $event)"
|
||||||
|
>
|
||||||
|
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-32 h-8 text-xs border-border/60">
|
||||||
|
<SelectValue placeholder="提供商" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__all__">
|
||||||
|
全部提供商
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem
|
||||||
|
v-for="provider in availableProviders"
|
||||||
|
:key="provider"
|
||||||
|
:value="provider"
|
||||||
>
|
>
|
||||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-32 h-8 text-xs border-border/60">
|
{{ provider }}
|
||||||
<SelectValue placeholder="提供商" />
|
</SelectItem>
|
||||||
</SelectTrigger>
|
</SelectContent>
|
||||||
<SelectContent>
|
</Select>
|
||||||
<SelectItem value="__all__">
|
|
||||||
全部提供商
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem
|
|
||||||
v-for="provider in availableProviders"
|
|
||||||
:key="provider"
|
|
||||||
:value="provider"
|
|
||||||
>
|
|
||||||
{{ provider }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<!-- API格式筛选 -->
|
<!-- API格式筛选 -->
|
||||||
<Select
|
<Select
|
||||||
:model-value="filterApiFormat"
|
:model-value="filterApiFormat"
|
||||||
@update:model-value="$emit('update:filterApiFormat', $event)"
|
@update:model-value="$emit('update:filterApiFormat', $event)"
|
||||||
|
>
|
||||||
|
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-32 h-8 text-xs border-border/60">
|
||||||
|
<SelectValue placeholder="格式" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__all__">
|
||||||
|
全部格式
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem
|
||||||
|
v-for="format in availableApiFormats"
|
||||||
|
:key="format.value"
|
||||||
|
:value="format.value"
|
||||||
>
|
>
|
||||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-32 h-8 text-xs border-border/60">
|
{{ format.label }}
|
||||||
<SelectValue placeholder="格式" />
|
</SelectItem>
|
||||||
</SelectTrigger>
|
</SelectContent>
|
||||||
<SelectContent>
|
</Select>
|
||||||
<SelectItem value="__all__">
|
|
||||||
全部格式
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem
|
|
||||||
v-for="format in availableApiFormats"
|
|
||||||
:key="format.value"
|
|
||||||
:value="format.value"
|
|
||||||
>
|
|
||||||
{{ format.label }}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<!-- 状态筛选 -->
|
<!-- 状态筛选 -->
|
||||||
<Select
|
<Select
|
||||||
:model-value="filterStatus"
|
:model-value="filterStatus"
|
||||||
@update:model-value="$emit('update:filterStatus', $event)"
|
@update:model-value="$emit('update:filterStatus', $event)"
|
||||||
>
|
>
|
||||||
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-28 h-8 text-xs border-border/60">
|
<SelectTrigger class="flex-1 min-w-0 sm:flex-none sm:w-28 h-8 text-xs border-border/60">
|
||||||
<SelectValue placeholder="状态" />
|
<SelectValue placeholder="状态" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="__all__">
|
<SelectItem value="__all__">
|
||||||
全部状态
|
全部状态
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="stream">
|
<SelectItem value="stream">
|
||||||
流式
|
流式
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="standard">
|
<SelectItem value="standard">
|
||||||
标准
|
标准
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="active">
|
<SelectItem value="active">
|
||||||
活跃
|
活跃
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="failed">
|
<SelectItem value="failed">
|
||||||
失败
|
失败
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="cancelled">
|
<SelectItem value="cancelled">
|
||||||
已取消
|
已取消
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="has_retry">
|
<SelectItem value="has_retry">
|
||||||
发生重试
|
发生重试
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="has_fallback">
|
<SelectItem value="has_fallback">
|
||||||
发生转移
|
发生转移
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
<!-- 分隔线 -->
|
||||||
<div class="hidden sm:block h-4 w-px bg-border" />
|
<div class="hidden sm:block h-4 w-px bg-border" />
|
||||||
|
|
||||||
<!-- 自动刷新按钮 -->
|
<!-- 自动刷新按钮 -->
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="h-8 w-8"
|
class="h-8 w-8"
|
||||||
:class="autoRefresh ? 'text-primary' : ''"
|
:class="autoRefresh ? 'text-primary' : ''"
|
||||||
:title="autoRefresh ? '点击关闭自动刷新' : '点击开启自动刷新(每3秒刷新)'"
|
:title="autoRefresh ? '点击关闭自动刷新' : '点击开启自动刷新(每3秒刷新)'"
|
||||||
@click="$emit('update:autoRefresh', !autoRefresh)"
|
@click="$emit('update:autoRefresh', !autoRefresh)"
|
||||||
>
|
>
|
||||||
<RefreshCcw
|
<RefreshCcw
|
||||||
class="w-3.5 h-3.5"
|
class="w-3.5 h-3.5"
|
||||||
:class="autoRefresh ? 'animate-spin' : ''"
|
:class="autoRefresh ? 'animate-spin' : ''"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 移动端卡片视图 -->
|
<!-- 移动端卡片视图 -->
|
||||||
|
|||||||
@@ -110,7 +110,10 @@ export function useUsageData(options: UseUsageDataOptions) {
|
|||||||
model: item.model,
|
model: item.model,
|
||||||
request_count: item.request_count || 0,
|
request_count: item.request_count || 0,
|
||||||
total_tokens: item.total_tokens || 0,
|
total_tokens: item.total_tokens || 0,
|
||||||
|
total_input_context: typeof raw.total_input_context === 'number' ? raw.total_input_context : 0,
|
||||||
|
output_tokens: typeof raw.output_tokens === 'number' ? raw.output_tokens : 0,
|
||||||
cache_read_tokens: typeof raw.cache_read_tokens === 'number' ? raw.cache_read_tokens : 0,
|
cache_read_tokens: typeof raw.cache_read_tokens === 'number' ? raw.cache_read_tokens : 0,
|
||||||
|
cache_creation_tokens: typeof raw.cache_creation_tokens === 'number' ? raw.cache_creation_tokens : 0,
|
||||||
cache_hit_rate: typeof raw.cache_hit_rate === 'number' ? raw.cache_hit_rate : 0,
|
cache_hit_rate: typeof raw.cache_hit_rate === 'number' ? raw.cache_hit_rate : 0,
|
||||||
total_cost: item.total_cost || 0,
|
total_cost: item.total_cost || 0,
|
||||||
actual_cost: typeof raw.actual_cost === 'number' ? raw.actual_cost : undefined
|
actual_cost: typeof raw.actual_cost === 'number' ? raw.actual_cost : undefined
|
||||||
@@ -121,7 +124,10 @@ export function useUsageData(options: UseUsageDataOptions) {
|
|||||||
provider: item.provider,
|
provider: item.provider,
|
||||||
requests: item.request_count,
|
requests: item.request_count,
|
||||||
totalTokens: item.total_tokens || 0,
|
totalTokens: item.total_tokens || 0,
|
||||||
|
totalInputContext: item.total_input_context || 0,
|
||||||
|
outputTokens: item.output_tokens || 0,
|
||||||
cacheReadTokens: item.cache_read_tokens || 0,
|
cacheReadTokens: item.cache_read_tokens || 0,
|
||||||
|
cacheCreationTokens: item.cache_creation_tokens || 0,
|
||||||
cacheHitRate: item.cache_hit_rate || 0,
|
cacheHitRate: item.cache_hit_rate || 0,
|
||||||
totalCost: item.total_cost,
|
totalCost: item.total_cost,
|
||||||
actualCost: item.actual_cost,
|
actualCost: item.actual_cost,
|
||||||
@@ -135,7 +141,10 @@ export function useUsageData(options: UseUsageDataOptions) {
|
|||||||
api_format: item.api_format,
|
api_format: item.api_format,
|
||||||
request_count: item.request_count || 0,
|
request_count: item.request_count || 0,
|
||||||
total_tokens: item.total_tokens || 0,
|
total_tokens: item.total_tokens || 0,
|
||||||
|
total_input_context: item.total_input_context || 0,
|
||||||
|
output_tokens: item.output_tokens || 0,
|
||||||
cache_read_tokens: item.cache_read_tokens || 0,
|
cache_read_tokens: item.cache_read_tokens || 0,
|
||||||
|
cache_creation_tokens: item.cache_creation_tokens || 0,
|
||||||
cache_hit_rate: item.cache_hit_rate || 0,
|
cache_hit_rate: item.cache_hit_rate || 0,
|
||||||
total_cost: item.total_cost || 0,
|
total_cost: item.total_cost || 0,
|
||||||
actual_cost: item.actual_cost,
|
actual_cost: item.actual_cost,
|
||||||
@@ -169,7 +178,10 @@ export function useUsageData(options: UseUsageDataOptions) {
|
|||||||
model: item.model,
|
model: item.model,
|
||||||
request_count: item.requests || 0,
|
request_count: item.requests || 0,
|
||||||
total_tokens: item.total_tokens || 0,
|
total_tokens: item.total_tokens || 0,
|
||||||
|
total_input_context: item.total_input_context || 0,
|
||||||
|
output_tokens: item.output_tokens || 0,
|
||||||
cache_read_tokens: item.cache_read_tokens || 0,
|
cache_read_tokens: item.cache_read_tokens || 0,
|
||||||
|
cache_creation_tokens: item.cache_creation_tokens || 0,
|
||||||
cache_hit_rate: item.cache_hit_rate || 0,
|
cache_hit_rate: item.cache_hit_rate || 0,
|
||||||
total_cost: item.total_cost_usd || 0,
|
total_cost: item.total_cost_usd || 0,
|
||||||
actual_cost: item.actual_total_cost_usd
|
actual_cost: item.actual_total_cost_usd
|
||||||
@@ -179,7 +191,10 @@ export function useUsageData(options: UseUsageDataOptions) {
|
|||||||
provider: item.provider,
|
provider: item.provider,
|
||||||
requests: item.requests || 0,
|
requests: item.requests || 0,
|
||||||
totalTokens: item.total_tokens || 0,
|
totalTokens: item.total_tokens || 0,
|
||||||
|
totalInputContext: item.total_input_context || 0,
|
||||||
|
outputTokens: item.output_tokens || 0,
|
||||||
cacheReadTokens: item.cache_read_tokens || 0,
|
cacheReadTokens: item.cache_read_tokens || 0,
|
||||||
|
cacheCreationTokens: item.cache_creation_tokens || 0,
|
||||||
cacheHitRate: item.cache_hit_rate || 0,
|
cacheHitRate: item.cache_hit_rate || 0,
|
||||||
totalCost: item.total_cost_usd || 0,
|
totalCost: item.total_cost_usd || 0,
|
||||||
successRate: item.success_rate || 0,
|
successRate: item.success_rate || 0,
|
||||||
@@ -209,7 +224,10 @@ export function useUsageData(options: UseUsageDataOptions) {
|
|||||||
api_format: item.api_format,
|
api_format: item.api_format,
|
||||||
request_count: item.request_count || 0,
|
request_count: item.request_count || 0,
|
||||||
total_tokens: item.total_tokens || 0,
|
total_tokens: item.total_tokens || 0,
|
||||||
|
total_input_context: item.total_input_context || 0,
|
||||||
|
output_tokens: item.output_tokens || 0,
|
||||||
cache_read_tokens: item.cache_read_tokens || 0,
|
cache_read_tokens: item.cache_read_tokens || 0,
|
||||||
|
cache_creation_tokens: item.cache_creation_tokens || 0,
|
||||||
cache_hit_rate: item.cache_hit_rate || 0,
|
cache_hit_rate: item.cache_hit_rate || 0,
|
||||||
total_cost: item.total_cost_usd || 0,
|
total_cost: item.total_cost_usd || 0,
|
||||||
avgResponseTime: (item.avg_response_time_ms ?? 0) > 0
|
avgResponseTime: (item.avg_response_time_ms ?? 0) > 0
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ export interface ModelStatsItem {
|
|||||||
model: string
|
model: string
|
||||||
request_count: number
|
request_count: number
|
||||||
total_tokens: number
|
total_tokens: number
|
||||||
|
total_input_context?: number
|
||||||
|
output_tokens?: number
|
||||||
cache_read_tokens?: number
|
cache_read_tokens?: number
|
||||||
|
cache_creation_tokens?: number
|
||||||
cache_hit_rate?: number
|
cache_hit_rate?: number
|
||||||
total_cost: number
|
total_cost: number
|
||||||
actual_cost?: number // 倍率消耗
|
actual_cost?: number // 倍率消耗
|
||||||
@@ -38,7 +41,10 @@ export interface ProviderStatsItem {
|
|||||||
provider: string
|
provider: string
|
||||||
requests: number
|
requests: number
|
||||||
totalTokens: number
|
totalTokens: number
|
||||||
|
totalInputContext?: number
|
||||||
|
outputTokens?: number
|
||||||
cacheReadTokens?: number
|
cacheReadTokens?: number
|
||||||
|
cacheCreationTokens?: number
|
||||||
cacheHitRate?: number
|
cacheHitRate?: number
|
||||||
totalCost: number
|
totalCost: number
|
||||||
actualCost?: number
|
actualCost?: number
|
||||||
@@ -51,7 +57,10 @@ export interface ApiFormatStatsItem {
|
|||||||
api_format: string
|
api_format: string
|
||||||
request_count: number
|
request_count: number
|
||||||
total_tokens: number
|
total_tokens: number
|
||||||
|
total_input_context?: number
|
||||||
|
output_tokens?: number
|
||||||
cache_read_tokens?: number
|
cache_read_tokens?: number
|
||||||
|
cache_creation_tokens?: number
|
||||||
cache_hit_rate?: number
|
cache_hit_rate?: number
|
||||||
total_cost: number
|
total_cost: number
|
||||||
actual_cost?: number
|
actual_cost?: number
|
||||||
|
|||||||
@@ -282,7 +282,10 @@
|
|||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<!-- Page-level header actions (right side) -->
|
<!-- Page-level header actions (right side) -->
|
||||||
<div id="header-actions-right" class="flex items-center" />
|
<div
|
||||||
|
id="header-actions-right"
|
||||||
|
class="flex items-center"
|
||||||
|
/>
|
||||||
<!-- Theme Toggle -->
|
<!-- Theme Toggle -->
|
||||||
<button
|
<button
|
||||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="space-y-6 pb-8">
|
<div class="space-y-6 pb-8">
|
||||||
<!-- 面包屑旁的折叠按钮 -->
|
<!-- 面包屑旁的折叠按钮 -->
|
||||||
<Teleport to="#header-actions-right" defer>
|
<Teleport
|
||||||
|
to="#header-actions-right"
|
||||||
|
defer
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||||
:title="statsExpanded ? '收起用量分析' : '展开用量分析'"
|
:title="statsExpanded ? '收起用量分析' : '展开用量分析'"
|
||||||
|
|||||||
@@ -546,6 +546,8 @@ class AdminUsageByModelAdapter(AdminApiAdapter):
|
|||||||
func.sum(Usage.actual_total_cost_usd).label("actual_cost"),
|
func.sum(Usage.actual_total_cost_usd).label("actual_cost"),
|
||||||
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
||||||
func.sum(input_context_expr()).label("total_input_context"),
|
func.sum(input_context_expr()).label("total_input_context"),
|
||||||
|
func.sum(Usage.output_tokens).label("output_tokens"),
|
||||||
|
func.sum(Usage.cache_creation_input_tokens).label("cache_creation_tokens"),
|
||||||
)
|
)
|
||||||
# 过滤掉 pending/streaming 状态的请求(尚未完成的请求不应计入统计)
|
# 过滤掉 pending/streaming 状态的请求(尚未完成的请求不应计入统计)
|
||||||
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
||||||
@@ -573,15 +575,28 @@ class AdminUsageByModelAdapter(AdminApiAdapter):
|
|||||||
"model": model,
|
"model": model,
|
||||||
"request_count": count,
|
"request_count": count,
|
||||||
"total_tokens": int(tokens or 0),
|
"total_tokens": int(tokens or 0),
|
||||||
|
"total_input_context": int(total_input_context or 0),
|
||||||
|
"output_tokens": int(output_tokens or 0),
|
||||||
"total_cost": float(cost or 0),
|
"total_cost": float(cost or 0),
|
||||||
"actual_cost": float(actual_cost or 0),
|
"actual_cost": float(actual_cost or 0),
|
||||||
"cache_read_tokens": int(cache_read_tokens or 0),
|
"cache_read_tokens": int(cache_read_tokens or 0),
|
||||||
|
"cache_creation_tokens": int(cache_creation_tokens or 0),
|
||||||
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
||||||
total_input_context=total_input_context,
|
total_input_context=total_input_context,
|
||||||
cache_read_tokens=cache_read_tokens,
|
cache_read_tokens=cache_read_tokens,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
for model, count, tokens, cost, actual_cost, cache_read_tokens, total_input_context in stats
|
for (
|
||||||
|
model,
|
||||||
|
count,
|
||||||
|
tokens,
|
||||||
|
cost,
|
||||||
|
actual_cost,
|
||||||
|
cache_read_tokens,
|
||||||
|
total_input_context,
|
||||||
|
output_tokens,
|
||||||
|
cache_creation_tokens,
|
||||||
|
) in stats
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -707,6 +722,8 @@ class AdminUsageByProviderAdapter(AdminApiAdapter):
|
|||||||
func.avg(Usage.response_time_ms).label("avg_response_time_ms"),
|
func.avg(Usage.response_time_ms).label("avg_response_time_ms"),
|
||||||
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
||||||
func.sum(input_context_expr()).label("total_input_context"),
|
func.sum(input_context_expr()).label("total_input_context"),
|
||||||
|
func.sum(Usage.output_tokens).label("output_tokens"),
|
||||||
|
func.sum(Usage.cache_creation_input_tokens).label("cache_creation_tokens"),
|
||||||
).filter(
|
).filter(
|
||||||
Usage.provider_id.isnot(None),
|
Usage.provider_id.isnot(None),
|
||||||
# 过滤掉 pending/streaming 状态的请求
|
# 过滤掉 pending/streaming 状态的请求
|
||||||
@@ -766,6 +783,10 @@ class AdminUsageByProviderAdapter(AdminApiAdapter):
|
|||||||
"provider": provider_map.get(provider_id_str, "Unknown"),
|
"provider": provider_map.get(provider_id_str, "Unknown"),
|
||||||
"request_count": attempt_count, # 尝试次数
|
"request_count": attempt_count, # 尝试次数
|
||||||
"total_tokens": int(usage_stat.total_tokens or 0) if usage_stat else 0,
|
"total_tokens": int(usage_stat.total_tokens or 0) if usage_stat else 0,
|
||||||
|
"total_input_context": (
|
||||||
|
int(usage_stat.total_input_context or 0) if usage_stat else 0
|
||||||
|
),
|
||||||
|
"output_tokens": int(usage_stat.output_tokens or 0) if usage_stat else 0,
|
||||||
"total_cost": float(usage_stat.total_cost or 0) if usage_stat else 0,
|
"total_cost": float(usage_stat.total_cost or 0) if usage_stat else 0,
|
||||||
"actual_cost": float(usage_stat.actual_cost or 0) if usage_stat else 0,
|
"actual_cost": float(usage_stat.actual_cost or 0) if usage_stat else 0,
|
||||||
"avg_response_time_ms": float(stat.avg_latency_ms or 0),
|
"avg_response_time_ms": float(stat.avg_latency_ms or 0),
|
||||||
@@ -774,6 +795,9 @@ class AdminUsageByProviderAdapter(AdminApiAdapter):
|
|||||||
"cache_read_tokens": (
|
"cache_read_tokens": (
|
||||||
int(usage_stat.cache_read_tokens or 0) if usage_stat else 0
|
int(usage_stat.cache_read_tokens or 0) if usage_stat else 0
|
||||||
),
|
),
|
||||||
|
"cache_creation_tokens": (
|
||||||
|
int(usage_stat.cache_creation_tokens or 0) if usage_stat else 0
|
||||||
|
),
|
||||||
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
||||||
total_input_context=(usage_stat.total_input_context if usage_stat else 0),
|
total_input_context=(usage_stat.total_input_context if usage_stat else 0),
|
||||||
cache_read_tokens=(usage_stat.cache_read_tokens if usage_stat else 0),
|
cache_read_tokens=(usage_stat.cache_read_tokens if usage_stat else 0),
|
||||||
@@ -811,6 +835,8 @@ class AdminUsageByApiFormatAdapter(AdminApiAdapter):
|
|||||||
func.avg(Usage.response_time_ms).label("avg_response_time_ms"),
|
func.avg(Usage.response_time_ms).label("avg_response_time_ms"),
|
||||||
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
||||||
func.sum(input_context_expr()).label("total_input_context"),
|
func.sum(input_context_expr()).label("total_input_context"),
|
||||||
|
func.sum(Usage.output_tokens).label("output_tokens"),
|
||||||
|
func.sum(Usage.cache_creation_input_tokens).label("cache_creation_tokens"),
|
||||||
)
|
)
|
||||||
# 过滤掉 pending/streaming 状态的请求
|
# 过滤掉 pending/streaming 状态的请求
|
||||||
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
query = query.filter(Usage.status.notin_(["pending", "streaming"]))
|
||||||
@@ -843,10 +869,13 @@ class AdminUsageByApiFormatAdapter(AdminApiAdapter):
|
|||||||
"api_format": api_format or "unknown",
|
"api_format": api_format or "unknown",
|
||||||
"request_count": count,
|
"request_count": count,
|
||||||
"total_tokens": int(tokens or 0),
|
"total_tokens": int(tokens or 0),
|
||||||
|
"total_input_context": int(total_input_context or 0),
|
||||||
|
"output_tokens": int(output_tokens or 0),
|
||||||
"total_cost": float(cost or 0),
|
"total_cost": float(cost or 0),
|
||||||
"actual_cost": float(actual_cost or 0),
|
"actual_cost": float(actual_cost or 0),
|
||||||
"avg_response_time_ms": float(avg_response_time or 0),
|
"avg_response_time_ms": float(avg_response_time or 0),
|
||||||
"cache_read_tokens": int(cache_read_tokens or 0),
|
"cache_read_tokens": int(cache_read_tokens or 0),
|
||||||
|
"cache_creation_tokens": int(cache_creation_tokens or 0),
|
||||||
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
||||||
total_input_context=total_input_context,
|
total_input_context=total_input_context,
|
||||||
cache_read_tokens=cache_read_tokens,
|
cache_read_tokens=cache_read_tokens,
|
||||||
@@ -861,6 +890,8 @@ class AdminUsageByApiFormatAdapter(AdminApiAdapter):
|
|||||||
avg_response_time,
|
avg_response_time,
|
||||||
cache_read_tokens,
|
cache_read_tokens,
|
||||||
total_input_context,
|
total_input_context,
|
||||||
|
output_tokens,
|
||||||
|
cache_creation_tokens,
|
||||||
) in stats
|
) in stats
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1061,6 +1061,7 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
"output_tokens": 0,
|
"output_tokens": 0,
|
||||||
"total_tokens": 0,
|
"total_tokens": 0,
|
||||||
"cache_read_tokens": 0,
|
"cache_read_tokens": 0,
|
||||||
|
"cache_creation_tokens": 0,
|
||||||
"total_input_context": 0,
|
"total_input_context": 0,
|
||||||
"cache_hit_rate": 0.0,
|
"cache_hit_rate": 0.0,
|
||||||
"total_cost_usd": 0.0,
|
"total_cost_usd": 0.0,
|
||||||
@@ -1075,6 +1076,7 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
stats["output_tokens"] += item["output_tokens"]
|
stats["output_tokens"] += item["output_tokens"]
|
||||||
stats["total_tokens"] += item["total_tokens"]
|
stats["total_tokens"] += item["total_tokens"]
|
||||||
stats["cache_read_tokens"] += int(item.get("cache_read_tokens", 0) or 0)
|
stats["cache_read_tokens"] += int(item.get("cache_read_tokens", 0) or 0)
|
||||||
|
stats["cache_creation_tokens"] += int(item.get("cache_creation_tokens", 0) or 0)
|
||||||
stats["total_input_context"] += int(item.get("total_input_context", 0) or 0)
|
stats["total_input_context"] += int(item.get("total_input_context", 0) or 0)
|
||||||
stats["total_cost_usd"] += item["total_cost_usd"]
|
stats["total_cost_usd"] += item["total_cost_usd"]
|
||||||
# 管理员可以看到真实成本
|
# 管理员可以看到真实成本
|
||||||
@@ -1086,7 +1088,9 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
"provider": provider_name,
|
"provider": provider_name,
|
||||||
"requests": 0,
|
"requests": 0,
|
||||||
"total_tokens": 0,
|
"total_tokens": 0,
|
||||||
|
"output_tokens": 0,
|
||||||
"cache_read_tokens": 0,
|
"cache_read_tokens": 0,
|
||||||
|
"cache_creation_tokens": 0,
|
||||||
"total_input_context": 0,
|
"total_input_context": 0,
|
||||||
"total_cost_usd": 0.0,
|
"total_cost_usd": 0.0,
|
||||||
"success_count": 0,
|
"success_count": 0,
|
||||||
@@ -1096,7 +1100,11 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
provider_stats = provider_summary.setdefault(provider_name, provider_base_stats)
|
provider_stats = provider_summary.setdefault(provider_name, provider_base_stats)
|
||||||
provider_stats["requests"] += item["requests"]
|
provider_stats["requests"] += item["requests"]
|
||||||
provider_stats["total_tokens"] += item["total_tokens"]
|
provider_stats["total_tokens"] += item["total_tokens"]
|
||||||
|
provider_stats["output_tokens"] += item.get("output_tokens", 0) or 0
|
||||||
provider_stats["cache_read_tokens"] += int(item.get("cache_read_tokens", 0) or 0)
|
provider_stats["cache_read_tokens"] += int(item.get("cache_read_tokens", 0) or 0)
|
||||||
|
provider_stats["cache_creation_tokens"] += int(
|
||||||
|
item.get("cache_creation_tokens", 0) or 0
|
||||||
|
)
|
||||||
provider_stats["total_input_context"] += int(item.get("total_input_context", 0) or 0)
|
provider_stats["total_input_context"] += int(item.get("total_input_context", 0) or 0)
|
||||||
provider_stats["total_cost_usd"] += item["total_cost_usd"]
|
provider_stats["total_cost_usd"] += item["total_cost_usd"]
|
||||||
provider_stats["success_count"] += int(item.get("success_count", 0) or 0)
|
provider_stats["success_count"] += int(item.get("success_count", 0) or 0)
|
||||||
@@ -1112,7 +1120,6 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
total_input_context=int(model_stats.get("total_input_context", 0) or 0),
|
total_input_context=int(model_stats.get("total_input_context", 0) or 0),
|
||||||
cache_read_tokens=int(model_stats.get("cache_read_tokens", 0) or 0),
|
cache_read_tokens=int(model_stats.get("cache_read_tokens", 0) or 0),
|
||||||
)
|
)
|
||||||
model_stats.pop("total_input_context", None)
|
|
||||||
|
|
||||||
summary_by_model = sorted(model_summary.values(), key=lambda x: x["requests"], reverse=True)
|
summary_by_model = sorted(model_summary.values(), key=lambda x: x["requests"], reverse=True)
|
||||||
summary_by_provider = []
|
summary_by_provider = []
|
||||||
@@ -1132,7 +1139,10 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
"provider": provider_stats["provider"],
|
"provider": provider_stats["provider"],
|
||||||
"requests": provider_stats["requests"],
|
"requests": provider_stats["requests"],
|
||||||
"total_tokens": provider_stats["total_tokens"],
|
"total_tokens": provider_stats["total_tokens"],
|
||||||
|
"total_input_context": provider_stats["total_input_context"],
|
||||||
|
"output_tokens": provider_stats["output_tokens"],
|
||||||
"cache_read_tokens": provider_stats["cache_read_tokens"],
|
"cache_read_tokens": provider_stats["cache_read_tokens"],
|
||||||
|
"cache_creation_tokens": provider_stats["cache_creation_tokens"],
|
||||||
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
||||||
total_input_context=int(provider_stats.get("total_input_context", 0) or 0),
|
total_input_context=int(provider_stats.get("total_input_context", 0) or 0),
|
||||||
cache_read_tokens=int(provider_stats.get("cache_read_tokens", 0) or 0),
|
cache_read_tokens=int(provider_stats.get("cache_read_tokens", 0) or 0),
|
||||||
@@ -1151,6 +1161,8 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
func.sum(Usage.total_tokens).label("total_tokens"),
|
func.sum(Usage.total_tokens).label("total_tokens"),
|
||||||
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
||||||
func.sum(input_context_expr()).label("total_input_context"),
|
func.sum(input_context_expr()).label("total_input_context"),
|
||||||
|
func.sum(Usage.output_tokens).label("output_tokens"),
|
||||||
|
func.sum(Usage.cache_creation_input_tokens).label("cache_creation_tokens"),
|
||||||
func.sum(Usage.total_cost_usd).label("total_cost_usd"),
|
func.sum(Usage.total_cost_usd).label("total_cost_usd"),
|
||||||
func.avg(Usage.response_time_ms).label("avg_response_time_ms"),
|
func.avg(Usage.response_time_ms).label("avg_response_time_ms"),
|
||||||
).filter(
|
).filter(
|
||||||
@@ -1171,7 +1183,10 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
"api_format": api_format or "unknown",
|
"api_format": api_format or "unknown",
|
||||||
"request_count": count,
|
"request_count": count,
|
||||||
"total_tokens": int(total_tokens or 0),
|
"total_tokens": int(total_tokens or 0),
|
||||||
|
"total_input_context": int(total_input_context or 0),
|
||||||
|
"output_tokens": int(output_tokens or 0),
|
||||||
"cache_read_tokens": int(cache_read_tokens or 0),
|
"cache_read_tokens": int(cache_read_tokens or 0),
|
||||||
|
"cache_creation_tokens": int(cache_creation_tokens or 0),
|
||||||
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
"cache_hit_rate": _calculate_token_cache_hit_rate(
|
||||||
total_input_context=total_input_context,
|
total_input_context=total_input_context,
|
||||||
cache_read_tokens=cache_read_tokens,
|
cache_read_tokens=cache_read_tokens,
|
||||||
@@ -1185,6 +1200,8 @@ class GetUsageAdapter(AuthenticatedApiAdapter):
|
|||||||
total_tokens,
|
total_tokens,
|
||||||
cache_read_tokens,
|
cache_read_tokens,
|
||||||
total_input_context,
|
total_input_context,
|
||||||
|
output_tokens,
|
||||||
|
cache_creation_tokens,
|
||||||
total_cost_usd,
|
total_cost_usd,
|
||||||
avg_response_time_ms,
|
avg_response_time_ms,
|
||||||
) in api_format_stats
|
) in api_format_stats
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ class UsageQueryMixin:
|
|||||||
func.sum(Usage.output_tokens).label("output_tokens"),
|
func.sum(Usage.output_tokens).label("output_tokens"),
|
||||||
func.sum(Usage.total_tokens).label("total_tokens"),
|
func.sum(Usage.total_tokens).label("total_tokens"),
|
||||||
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
func.sum(Usage.cache_read_input_tokens).label("cache_read_tokens"),
|
||||||
|
func.sum(Usage.cache_creation_input_tokens).label("cache_creation_tokens"),
|
||||||
func.sum(input_context_expr()).label("total_input_context"),
|
func.sum(input_context_expr()).label("total_input_context"),
|
||||||
func.sum(Usage.total_cost_usd).label("total_cost_usd"),
|
func.sum(Usage.total_cost_usd).label("total_cost_usd"),
|
||||||
func.sum(Usage.actual_total_cost_usd).label("actual_total_cost_usd"),
|
func.sum(Usage.actual_total_cost_usd).label("actual_total_cost_usd"),
|
||||||
@@ -307,6 +308,7 @@ class UsageQueryMixin:
|
|||||||
"output_tokens": row.output_tokens,
|
"output_tokens": row.output_tokens,
|
||||||
"total_tokens": row.total_tokens,
|
"total_tokens": row.total_tokens,
|
||||||
"cache_read_tokens": int(row.cache_read_tokens or 0),
|
"cache_read_tokens": int(row.cache_read_tokens or 0),
|
||||||
|
"cache_creation_tokens": int(row.cache_creation_tokens or 0),
|
||||||
"total_input_context": int(row.total_input_context or 0),
|
"total_input_context": int(row.total_input_context or 0),
|
||||||
"total_cost_usd": float(row.total_cost_usd or 0.0),
|
"total_cost_usd": float(row.total_cost_usd or 0.0),
|
||||||
"actual_total_cost_usd": float(row.actual_total_cost_usd or 0.0),
|
"actual_total_cost_usd": float(row.actual_total_cost_usd or 0.0),
|
||||||
|
|||||||
Reference in New Issue
Block a user