feat: 调用日志补模型筛选与汇总条——可按模型收窄查询,并在当前筛选下直接读出请求次数与消费用量

This commit is contained in:
2026-10-14 08:01:58 +08:00
parent b0e9ea750b
commit 199972a2dc
17 changed files with 705 additions and 84 deletions
+4 -2
View File
@@ -67,11 +67,13 @@
## 4. 日志明细
### GET `/api/member/logs?from&to` → 200 `{items, total}`
### GET `/api/member/logs?from&to` → 200 `{items, total, summary, models}`
分页(公共约定 §6);过滤 `from/to`(必填);范围恒为名下 key;**默认全类型**(消费 + 错误,可 `type` 筛——2 / `error`)。
可选筛选(调用日志页,与管理面 `GET /api/admin/logs` 同名同义):`api_key_id` · `billing_source`(`subscription` / `wallet` / `none`)· `subscription_id` · `type`(2 / `error`)。
可选筛选(调用日志页,与管理面 `GET /api/admin/logs` 同名同义):`api_key_id` · `model_name` · `billing_source`(`subscription` / `wallet` / `none`)· `subscription_id` · `type`(2 / `error`)。
响应附带 `summary` / `models`(与管理面同构,见《API契约-管理面》§6):`summary` 为当前筛选下条数与 **type=2** 消费用量;`models` 为时间窗 + 主体筛选下去重的模型选项(不含 `model_name` / `type` 筛选)。
明细行 DTO 与管理面 `GET /api/admin/logs` **同构**(公共约定 §7——一份 DTO 类型两处复用):
+23 -2
View File
@@ -152,9 +152,30 @@
## 6. 日志明细(管理面下钻)
### GET `/api/admin/logs?from&to` → 200 `{items, total}`
### GET `/api/admin/logs?from&to` → 200 `{items, total, summary, models}`
分页(`page/page_size`,公共约定 §6)。过滤:`from/to`(必填)、`api_key_id`、`member_id`(经 `api_key_id → api_keys.member_id` 联查**当前归属**——行级归属语义)、`billing_source`、`subscription_id`、`type`。默认全类型(错误行排查入口——§10 裁定 1 的另一半)。
分页(`page/page_size`,公共约定 §6)。过滤:`from/to`(必填)、`api_key_id`、`member_id`(经 `api_key_id → api_keys.member_id` 联查**当前归属**——行级归属语义)、`model_name`、`billing_source`、`subscription_id`、`type`。默认全类型(错误行排查入口——§10 裁定 1 的另一半)。
**响应附加块**(汇总条与模型下拉的数据源;成员端 `/api/member/logs` 同构):
```json
{
"summary": {
"hit_count": 131, "usage_count": 128, "error_count": 3,
"quota": 1250000, "quota_usd": "2.50"
},
"models": ["claude-sonnet-4", "gpt-5"]
}
```
| 字段 | 口径 |
|------|------|
| `hit_count` | 当前筛选命中总数(= `total`;界面文案「请求次数」) |
| `usage_count` / `error_count` | 同筛选下 `type=2` / `type≠2` 条数 |
| `quota` / `quota_usd` | 同筛选下 **type=2** 行合计;美元按行 QPU 微分(公共约定 §5) |
| `models` | 时间窗 + 主体/来源/订阅筛选下去重 `model_name`(**不含** `model_name` / `type`),升序,供筛选下拉 |
裁定:条数跟全部筛选(含 `type` / `model_name`);用量字段恒 `type = 2`(即使请求 `type=error`)——界面标签「消费用量」。
```json
{
+3 -2
View File
@@ -83,8 +83,9 @@
### 4.1 页头与筛选
- `PageHeader`「调用日志」+ 副文案「名下 key 的模型请求流水」;右侧时间范围(同上)
- 筛选一行:Key(Select)· 类型(全部 / 消费 / 错误)· 计费来源(全部 / 订阅 / 充值 / 未关联)· 订阅 ID(可空)
- 切换任一筛选 → 回第 1 页;URL 同步 `from/to` + 筛选 + `page`(可深链)
- 筛选一行:Key(Select)· 模型(Select,可搜可清空)· 计费来源(全部 / 订阅 / 充值 / 未关联)· 订阅 ID(可空)· 类型(全部 / 消费 / 错误)
- 切换任一筛选 → 回第 1 页;URL 同步 `from/to` + 筛选(含 `model_name`)+ `page`(可深链)
- **汇总条**(筛选与表格之间,低高度单行):请求次数 + 消费用量(quota + $)——口径与《交互设计-调用日志》§2.5 同构;消费用量恒 `type = 2`
### 4.2 明细表(页面主体,表体滚动)
+18 -4
View File
@@ -4,9 +4,9 @@
| 项目 | 内容 |
|------|------|
| 状态 | 定稿(2026-10-08) |
| 关联 | 《API契约-管理面》§6(过滤与 DTO)·《交互设计-总览与共性》(时间范围、数值呈现、状态模式)·《交互设计-key管理》《交互设计-成员管理》《交互设计-全员总览》(双向跳转)· `usage_logs表字段设计.md` |
| 数据 | `usage_logs` 明细(分页;默认全类型,可筛 `type`) |
| 状态 | 定稿(2026-10-08;2026-10-13 补模型筛选与汇总条) |
| 关联 | 《实现方案-日志模型筛选与汇总条》·《API契约-管理面》§6(过滤与 DTO)·《交互设计-总览与共性》(时间范围、数值呈现、状态模式)·《交互设计-key管理》《交互设计-成员管理》《交互设计-全员总览》(双向跳转)· `usage_logs表字段设计.md` |
| 数据 | `usage_logs` 明细(分页;默认全类型,可筛 `type` / `model_name`)+ 当前筛选汇总(条数 + 消费用量) |
---
@@ -23,12 +23,26 @@
| 时间范围 | 共性组件,默认最近 30 天 | `from` / `to`(必填) |
| 成员 | Select,含「未归属」 | `member_id` |
| Key | Select(依赖已选成员可收窄;或全量) | `api_key_id` |
| 模型 | Select,可搜可清空;选项 = 时间窗 + 主体筛选下去重 `model_name` | `model_name` |
| 计费来源 | 全部 / 订阅 / 充值 / 未关联 | `billing_source` |
| 订阅 ID | 数字(可空) | `subscription_id` |
| 类型 | 全部 / 消费 / 错误 | `type`(2 / 非 2) |
- 切换任一筛选 → 回第 1 页
- URL 查询同步:`from/to` 可选省略(用默认区间);`member_id`、`api_key_id`、`billing_source`、`subscription_id`、`type`、`page`——便于总览/管理页深链
- URL 查询同步:`from/to` 可选省略(用默认区间);`member_id`、`api_key_id`、`model_name`、`billing_source`、`subscription_id`、`type`、`page`——便于总览/管理页深链
## 2.5 汇总条(筛选条与表格之间)
低高度**单行**读数条(约 32–36px),只回答「这批条件加起来多少」:
| 段 | 内容 | 口径 |
|----|------|------|
| 请求次数 | 当前筛选条数;错误数 > 0 时附注「消费 X / 错误 Y」 | 跟全部筛选(含 `type`) |
| 消费用量 | quota + $(`QuotaText`) | 恒 `type = 2` + 其余筛选 |
- 类型筛「错误」时:请求次数 = 错误条数;消费用量仍为同期消费口径(标签保持「消费用量」,不叫「花费」)
- 查询中数字骨架/淡化,不闪上一轮;空结果汇总条保留、数字归零
- 成员端同构(见《交互设计-成员视图》§4)
## 3. 日志表(页面主体)
@@ -0,0 +1,260 @@
# 实现方案 · 日志模型筛选与汇总条
> 调用日志页(管理端 + 成员端)的两项增强:**按模型名筛选**、**当前筛选条件下的用量汇总条**。交互方向已在讨论中议定——汇总条只做「条数 + 花费」、高度压低;本篇落 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`:
```json
{
"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`——字段更多,独立类型更清晰):
```ts
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. 验收口径
- [x] 管理端 / 成员端均可按模型筛选,选项来自时间窗内真实数据,可搜可清空
- [x] 筛选变更回第 1 页;URL 带 `model_name`,深链可还原
- [x] 汇总条位于筛选条与表格之间,**单行低高度**,仅「条数 + 消费用量」
- [x] 消费用量恒 type=2 + 其余筛选;类型筛错误时命中=错误数、用量仍为消费口径
- [x] `quota_usd` 按行 QPU 微分汇总(`sumUsage`)
- [x] 切换筛选时汇总加载态不闪旧数字(`is-loading` 淡化 + 占位)
- [x] typecheck / build 通过;无新依赖
---
## 10. 明确不做
- 总览页 by_model 分组卡(R5/R9 多维统计的另一落点,单议)
- 模型多选、模糊匹配(先精确单选;不够再扩)
- `model_name` 索引 / 预计算汇总表
- Tokens 段进汇总条(已议定砍掉,列内仍有)
- 明细列增减、详情弹窗改造