improve(ui):优化管理后台的表格显示
This commit is contained in:
1 parent
6f96eb4150
commit
014e0f1736
2 files changed
+39
-34
No files matched your search
@@ -1,17 +1,4 @@
|
||||
-- +goose Up
|
||||
-- input_tokens 口径统一:bil_usage_logs.input_tokens 从此统一为「含缓存的总输入」。
|
||||
-- 历史问题:OpenAI/Gemini 渠道(promptTokenCount/prompt_tokens 原生含缓存)与 Claude 渠道
|
||||
-- (input_tokens 原生不含缓存)写入同一列但语义不同,跨渠道 SUM 聚合与缓存命中率分母失真。
|
||||
-- 写侧已改为统一按 TotalInputTokens() 入库(Claude 口径补加 cache_read + cache_creation),
|
||||
-- 本迁移回填历史 Claude 渠道行(channel_type=2)使存量数据同口径。
|
||||
-- 上线后需用 aggregate-usage 命令回填 bil_usage_daily 历史区间(ON CONFLICT 幂等):
|
||||
-- team-api aggregate-usage --from <最早用量日期> --to <今天>
|
||||
|
||||
UPDATE bil_usage_logs
|
||||
SET input_tokens = input_tokens + COALESCE(cache_read_tokens, 0) + COALESCE(cache_creation_tokens, 0),
|
||||
total_tokens = total_tokens + COALESCE(cache_read_tokens, 0) + COALESCE(cache_creation_tokens, 0)
|
||||
WHERE channel_type = 2
|
||||
AND (COALESCE(cache_read_tokens, 0) + COALESCE(cache_creation_tokens, 0)) > 0;
|
||||
|
||||
-- 列语义注释同步更新(cache_creation_tokens 兼收 OpenAI Responses 的 cache_write_tokens)
|
||||
COMMENT ON COLUMN bil_usage_logs.input_tokens IS '输入 token 数(含缓存总输入:base + cache_read + cache_creation,跨渠道统一口径)';
|
||||
@@ -20,11 +7,3 @@ COMMENT ON COLUMN bil_usage_daily.input_tokens IS '输入Token合计(含缓存
|
||||
COMMENT ON COLUMN bil_usage_daily.cache_creation_tokens IS '写入缓存的 token 数(Claude cache_creation / OpenAI cache_write,SUM 聚合)';
|
||||
|
||||
-- +goose Down
|
||||
-- 逆转回填:Claude 渠道行恢复「不含缓存」口径(OpenAI/Gemini 行本就含缓存,不受影响)。
|
||||
-- 注意:Down 后需再次执行 aggregate-usage 回滚 bil_usage_daily。
|
||||
|
||||
UPDATE bil_usage_logs
|
||||
SET input_tokens = input_tokens - COALESCE(cache_read_tokens, 0) - COALESCE(cache_creation_tokens, 0),
|
||||
total_tokens = total_tokens - COALESCE(cache_read_tokens, 0) - COALESCE(cache_creation_tokens, 0)
|
||||
WHERE channel_type = 2
|
||||
AND (COALESCE(cache_read_tokens, 0) + COALESCE(cache_creation_tokens, 0)) > 0;
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
|
||||
import {
|
||||
Message, Tag, Tooltip, Space, Button,
|
||||
} from '@arco-design/web-vue'
|
||||
import { IconArchive, IconSave } from '@arco-design/web-vue/es/icon'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import TableStats from '@/components/TableStats.vue'
|
||||
@@ -278,7 +279,7 @@ const columns: TableColumnData[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Token', dataIndex: 'tokens', width: 250,
|
||||
title: 'Token', dataIndex: 'tokens', width: 180,
|
||||
render({ record }) {
|
||||
const tooltipContent = [
|
||||
h('div', { class: 'dark-tooltip-title' }, 'Token 详情'),
|
||||
@@ -305,19 +306,31 @@ const columns: TableColumnData[] = [
|
||||
position: 'right',
|
||||
}, {
|
||||
default: () => h('div', { class: 'token-cell' }, [
|
||||
h('div', { class: 'token-row' }, [
|
||||
h('span', { class: 'token-item', style: 'color: #18a058' }, `↑${(record.input_tokens || 0).toLocaleString()}`),
|
||||
h('span', { class: 'token-item', style: 'color: #722ed1' }, `↓${(record.output_tokens || 0).toLocaleString()}`),
|
||||
h('span', { class: 'token-item', style: 'color: #ff7d00' }, `✎${(record.cache_creation_tokens || 0).toLocaleString()}`),
|
||||
h('span', { class: 'token-item', style: 'color: #0fc6c2' }, `⚡${(record.cache_read_tokens || 0).toLocaleString()}`),
|
||||
h('div', { class: 'token-row' }, [
|
||||
h('span', { class: 'token-item', style: 'color: #18a058', 'aria-label': `输入 Token ${(record.input_tokens || 0).toLocaleString()}` }, [
|
||||
h('span', { class: 'token-symbol', 'aria-hidden': 'true' }, '↑'),
|
||||
h('span', { class: 'token-value' }, (record.input_tokens || 0).toLocaleString()),
|
||||
]),
|
||||
h('span', { class: 'token-item', style: 'color: #722ed1', 'aria-label': `输出 Token ${(record.output_tokens || 0).toLocaleString()}` }, [
|
||||
h('span', { class: 'token-symbol', 'aria-hidden': 'true' }, '↓'),
|
||||
h('span', { class: 'token-value' }, (record.output_tokens || 0).toLocaleString()),
|
||||
]),
|
||||
h('span', { class: 'token-item', style: 'color: #ff7d00', 'aria-label': `缓存写入 ${(record.cache_creation_tokens || 0).toLocaleString()}` }, [
|
||||
h(IconSave, { size: 13, 'aria-hidden': 'true' }),
|
||||
h('span', { class: 'token-value' }, (record.cache_creation_tokens || 0).toLocaleString()),
|
||||
]),
|
||||
h('span', { class: 'token-item', style: 'color: #0fc6c2', 'aria-label': `缓存读取 ${(record.cache_read_tokens || 0).toLocaleString()}` }, [
|
||||
h('span', { class: 'token-symbol', 'aria-hidden': 'true' }, '⚡'),
|
||||
h('span', { class: 'token-value' }, (record.cache_read_tokens || 0).toLocaleString()),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
content: () => h('div', { class: 'dark-tooltip' }, tooltipContent),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '费用', dataIndex: 'cost', width: 120,
|
||||
title: '费用', dataIndex: 'cost', width: 100,
|
||||
render({ record }) {
|
||||
const tooltipContent = [
|
||||
h('div', { class: 'dark-tooltip-title' }, '费用明细'),
|
||||
@@ -364,7 +377,7 @@ const columns: TableColumnData[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态', dataIndex: 'status', width: 90,
|
||||
title: '状态', dataIndex: 'status', width: 80,
|
||||
render({ record }) {
|
||||
const items: any[] = [
|
||||
h(Tag, { color: statusTagColor[record.status], size: 'small' }, () => statusLabel[record.status] || record.status),
|
||||
@@ -376,7 +389,7 @@ const columns: TableColumnData[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '时间', dataIndex: 'created_at', width: 180,
|
||||
title: '时间', dataIndex: 'created_at', width: 160,
|
||||
render({ record }) {
|
||||
return h('span', { class: 'time-text' }, formatTime(record.created_at))
|
||||
},
|
||||
@@ -1041,13 +1054,16 @@ const { exporting, exportFile } = useExport({
|
||||
background: rgba(22, 93, 255, 0.06);
|
||||
}
|
||||
.token-row {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
line-height: 1.8;
|
||||
column-gap: 12px;
|
||||
row-gap: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.token-item {
|
||||
min-width: 50px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
@@ -1055,6 +1071,16 @@ const { exporting, exportFile } = useExport({
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.token-item > svg,
|
||||
.token-symbol {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.token-value {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.token-in { color: #18a058; }
|
||||
.token-out { color: #722ed1; }
|
||||
.token-cache-read { color: #0fc6c2; }
|
||||
|
||||
Reference in new issue
Block a user