11 KiB
实现方案 · 日志模型筛选与汇总条
调用日志页(管理端 + 成员端)的两项增强:按模型名筛选、当前筛选条件下的用量汇总条。交互方向已在讨论中议定——汇总条只做「条数 + 花费」、高度压低;本篇落 API / 数据层 / 前端改造清单与验收口径。
| 项目 | 内容 |
|---|---|
| 状态 | 已实现(2026-10-13) |
| 关联 | 《交互设计-调用日志》·《交互设计-成员视图》§4 · 《API契约-管理面》§6 · 《API契约-成员视图》· 《API契约-公共约定》(分页、quota 双单位、聚合口径)· usage_logs表字段设计.md · 需求清单 R5 / R9(多维统计含模型) |
| 数据 | usage_logs.model_name(已入库)· 明细分页筛选与消费汇总(派生查询) |
1. 问题与目标
日志表已展示 model_name,筛选区却没有模型维度;总览有三维聚合,日志页却回答不了「这批条件加起来花了多少」。本方案补上:
- 模型筛选——筛选条第六格,与时间 / 成员 / Key / 来源 / 订阅 / 类型并列
- 汇总条——筛选条与表格之间一条低高度读数条,只展示请求次数 + 消费用量(quota + $)
范围仅两端调用日志页及其数据链路;不改总览 by_member / by_key / by_source,不加 by_model 分组卡(R5/R9 的「按模型」分组统计另议)。
2. 交互(已议定,实施以此为准)
2.1 页面骨架(两端同构)
PageHeader(标题 + 时间范围)
筛选条(成员/Key | 模型 | 计费来源 | 订阅 ID | 类型) ← 模型新增
汇总条(请求次数 · 消费用量) ← 新增,低高度单行
日志表(既有列不变)
分页
成员端无「成员」筛选,其余同构。
2.2 模型筛选
| 项 | 约定 |
|---|---|
| 位置 | 筛选条内,Key 与计费来源之间(谁 → 调了什么 → 记在哪) |
| 形态 | Select,showSearch + allowClear;空值 = 全部模型 |
| 选项 | 当前 from/to 与主体筛选(成员/Key)下、时间窗内去重的 model_name;不含模型自身与 type 筛选,避免「选了只剩自己」 |
| 变更 | 回第 1 页;URL 同步 model(与 member_id 等同级) |
| 宽度 | 与相邻筛选格一致(约 120–140px),可搜避免长模型名截断误选 |
2.3 汇总条
| 项 | 约定 |
|---|---|
| 位置 | 筛选条下方、表格上方——改条件 → 扫总量 → 下钻明细 |
| 高度 | 单行,约 32–36px;浅底或细分隔,不做成大卡片 |
| 内容 | 两段:请求次数(当前筛选下总条数)· 消费用量(quota + $,QuotaText) |
| 口径 | 请求次数跟全部筛选(含 type);消费用量恒 type = 2 + 其余筛选(既有裁决:聚合仅消费行) |
| 类型筛「错误」时 | 请求次数 = 错误条数;消费用量仍显示同期同条件的消费口径,标签保持「消费用量」不叫「花费」,避免误读为错误行开销 |
| 加载 | 查询中数字区骨架/淡化,不闪上一轮数字 |
| 空结果 | 汇总条保留,数字归零/—;表格空态不变 |
文案示例:
- 类型全部:
请求次数 131 条(消费 128 / 错误 3)+消费用量 1,250,000 quota ≈ $2.50 - 类型=错误:
请求次数 3 条+消费用量 …(消费口径不变) - 零结果:
请求次数 0 条+消费用量 0 quota ≈ $0.00
「消费 128 / 错误 3」在错误数为 0 时可只显示 请求次数 128 条,保持条更短。
3. API 契约变更
两端 GET /api/admin/logs / GET /api/member/logs 同名同义扩展;DTO 继续一份复用。
3.1 请求
新增可选查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
model_name |
string | 精确匹配 usage_logs.model_name;空/缺省 = 不筛 |
其余参数不变。from/to 仍必填。
3.2 响应
在现有 { items, total } 上增加 summary 与 models:
{
"items": [ /* 不变 */ ],
"total": 131,
"summary": {
"hit_count": 131,
"usage_count": 128,
"error_count": 3,
"quota": 1250000,
"quota_usd": "2.50"
},
"models": ["claude-sonnet-4", "gpt-5"]
}
| 字段 | 语义 |
|---|---|
total |
既有:当前筛选下命中总数(分页用) |
summary.hit_count |
与 total 同值(汇总条「请求次数」;保留字段便于前端不依赖分页语义) |
summary.usage_count |
当前筛选下 type=2 条数 |
summary.error_count |
当前筛选下 type≠2 条数(hit_count = usage_count + error_count) |
summary.quota / quota_usd |
当前筛选下 type=2 行的 quota 合计与美元(公共约定 §5:微分累加、一次取整) |
models |
时间窗 + 主体筛选下去重的 model_name 升序列表(选项源;不含 model_name / type 筛选) |
口径备注(写入契约裁定区):
summary.usage_*/quota*不含 type 筛选与model_name筛选以外的 type 限制——即 model_name 与主体/时间/来源/订阅参与,type 不参与用量字段;hit_count/error_count/usage_count的条数则跟全部筛选。- 更精确说:
- 条数字段(
hit_count/usage_count/error_count)→ 全部筛选(含type、model_name) - 用量字段(
quota/quota_usd)→ 同等筛选但 固定 type=2(即使请求type=error)
- 条数字段(
quota_usd换算沿「按日志时刻取 QPU」——汇总不得用单一当前汇率乘总 quota。
models 轻量查询:DISTINCT model_name,条件 = 时间窗 + 主体/来源/订阅(与列表一致),刻意不套 model_name 与 type。办公室规模(模型十来个)直接全量返回,不分页。
3.3 契约文档落点
- 《API契约-管理面》§6:过滤表加
model_name;响应从{items,total}扩为含summary/models - 《API契约-成员视图》日志节:同名同义同步
- 《API契约-公共约定》:若需,补一句「列表响应可附带与筛选同源的汇总块」——或在各自契约内写死,避免总纲膨胀
4. 数据层(packages/db)
4.1 筛选
repos.logs.listPage / listMemberPage 参数增加 model_name?: string,条件 eq(usageLogs.model_name, model_name)(非空时)。
4.2 汇总
新增 repos.logs.summarize / summarizeMember(或 list 方法返回体扩展,推荐独立方法以免分页热路径多跑聚合):
- 条数:SQL
count(),条件与列表筛选一致(含 type) - 用量:
count() filter (type=2)+sum(quota) filter (type=2);quota_usd需按行 QPU——取 type=2 行的(quota, created_at)交给sumUsage(与总览聚合同一套微分规则) - 选项:
select distinct model_name,条件同列表但去掉model_name与type
办公室查询频率低、命中行数量级可控;不做预计算表(需求清单「统计 = 派生视图」)。
时间范围仍走 created_at 索引;model_name 无索引,精确等值 + 时间窗过滤后残余行少,暂不加索引(表设计不为此改;若日后慢查询再评估)。
4.3 类型共享
packages/core:
logPageschema:增加summary(见下)与models: z.array(z.string())- 新导出
LogPageSummary(或复用/扩展UsageTotals——字段更多,独立类型更清晰):
interface LogPageSummary {
hit_count: number;
usage_count: number;
error_count: number;
quota: Quota;
quota_usd: QuotaUsd;
}
5. 服务端路由
| 文件 | 改动 |
|---|---|
apps/server/src/routes/admin-usage.ts |
GET /logs query 增 model_name;调用 list + summarize + distinct models;组 summary(sumUsage → quota_usd);响应扩字段 |
apps/server/src/routes/member-view.ts |
同上(listMemberPage / member 侧汇总与选项) |
apps/server/src/api-schemas.ts |
logPageSchema 随 core 更新 |
OpenAPI 描述字符串同步(summary 文案提到模型筛选与汇总块)。
6. 前端(apps/web)
6.1 API 层
api/admin/logs.ts:AdminLogFilters加model_name?: string;返回类型改为带summary/models的分页结构api/member/me.ts:MemberLogQuery同上
6.2 汇总条组件
新建共享组件(两端复用,避免双份样式漂移):
- 路径建议:
apps/web/src/components/common/logs-summary-bar.vue - Props:
hitCount·usageCount·errorCount·quota·quotaUsd·loading - 布局:单行 flex,左「请求次数…」右「消费用量…」;
loading时数字占位 - 花费数字复用
QuotaText - 样式对齐共性:弱标签 + 主数字,高度 32–36px,间距遵循页面既有
gap
6.3 两端 logs-view
| 项 | 改动 |
|---|---|
| 状态 | modelFilter: ref<string | null> |
| params | 带上 model_name |
| 模型选项 | 读响应 models(或独立 query 缓存按 from/to+主体 键) |
| 模板 | 筛选条插入模型 Select;筛选条与 a-table 之间插入 LogsSummaryBar |
| URL | syncFromRoute / 写回增加 model |
| 筛选变更 | 既有「回第 1 页」逻辑覆盖 model |
列结构、详情弹窗、跳转链路不动。
6.4 样式
筛选条若因第六格换行,允许两行(共性已允许一行或两行);汇总条始终单行不换行,过窄时隐藏次要括注(如「/ 错误 3」)。
7. 设计文档同步
实现时同步修订(不另开版本叙事):
| 文档 | 修订点 |
|---|---|
| 《交互设计-调用日志》 | §2 筛选表加「模型」;§3 前插入汇总条小节(位置、两段、口径);URL 同步参数补 model |
| 《交互设计-成员视图》§4 | 同构引用或摘要 + 成员端差异 |
| 《API契约-管理面》§6 | 过滤 + 响应扩展 + 口径裁定 |
| 《API契约-成员视图》 | 同名同义 |
| README 文档地图 | 本篇条目 |
状态流转:本篇「待审阅」→ 实现完成后「已实现(日期)」。
8. 实现顺序
- ✅
packages/core:LogPageSummary+LogPage+logPageschema 扩展 - ✅
packages/db:buildLogConds、model_name筛选、summarize/summarizeMember、listModels/listMemberModels - ✅
apps/server:两端/logs接线(summary + models) - ✅
apps/web:API 类型 →logs-summary-bar→ admin / member logs-view - ✅ 设计文档修订 + README 文档地图
- ✅
bun run typecheck+bun run build:web通过
9. 验收口径
- 管理端 / 成员端均可按模型筛选,选项来自时间窗内真实数据,可搜可清空
- 筛选变更回第 1 页;URL 带
model_name,深链可还原 - 汇总条位于筛选条与表格之间,单行低高度,仅「条数 + 消费用量」
- 消费用量恒 type=2 + 其余筛选;类型筛错误时命中=错误数、用量仍为消费口径
quota_usd按行 QPU 微分汇总(sumUsage)- 切换筛选时汇总加载态不闪旧数字(
is-loading淡化 + 占位) - typecheck / build 通过;无新依赖
10. 明确不做
- 总览页 by_model 分组卡(R5/R9 多维统计的另一落点,单议)
- 模型多选、模糊匹配(先精确单选;不够再扩)
model_name索引 / 预计算汇总表- Tokens 段进汇总条(已议定砍掉,列内仍有)
- 明细列增减、详情弹窗改造