Files
new-api-key-usage/docs/design/实现方案-日志模型筛选与汇总条.md
T

11 KiB
Raw Blame History

实现方案 · 日志模型筛选与汇总条

调用日志页(管理端 + 成员端)的两项增强:按模型名筛选、当前筛选条件下的用量汇总条。交互方向已在讨论中议定——汇总条只做「条数 + 花费」、高度压低;本篇落 API / 数据层 / 前端改造清单与验收口径。

项目 内容
状态 已实现(2026-10-13)
关联 《交互设计-调用日志》·《交互设计-成员视图》§4 · 《API契约-管理面》§6 · 《API契约-成员视图》· 《API契约-公共约定》(分页、quota 双单位、聚合口径)· usage_logs表字段设计.md · 需求清单 R5 / R9(多维统计含模型)
数据 usage_logs.model_name(已入库)· 明细分页筛选与消费汇总(派生查询)

1. 问题与目标

日志表已展示 model_name,筛选区却没有模型维度;总览有三维聚合,日志页却回答不了「这批条件加起来花了多少」。本方案补上:

  1. 模型筛选——筛选条第六格,与时间 / 成员 / Key / 来源 / 订阅 / 类型并列
  2. 汇总条——筛选条与表格之间一条低高度读数条,只展示请求次数 + 消费用量(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 筛选)

口径备注(写入契约裁定区):

  1. summary.usage_* / quota* 不含 type 筛选与 model_name 筛选以外的 type 限制——即 model_name 与主体/时间/来源/订阅参与,type 不参与用量字段;hit_count / error_count / usage_count 的条数则跟全部筛选。
  2. 更精确说:
    • 条数字段(hit_count / usage_count / error_count)→ 全部筛选(含 type、model_name)
    • 用量字段(quota / quota_usd)→ 同等筛选但 固定 type=2(即使请求 type=error)
  3. 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:

  • logPage schema:增加 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. 实现顺序

  1. ✅ packages/core:LogPageSummary + LogPage + logPage schema 扩展
  2. ✅ packages/db:buildLogConds、model_name 筛选、summarize / summarizeMember、listModels / listMemberModels
  3. ✅ apps/server:两端 /logs 接线(summary + models)
  4. ✅ apps/web:API 类型 → logs-summary-bar → admin / member logs-view
  5. ✅ 设计文档修订 + README 文档地图
  6. ✅ 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 段进汇总条(已议定砍掉,列内仍有)
  • 明细列增减、详情弹窗改造