Files
happy-life-star/docs/superpowers/specs/2026-05-24-ai-call-log-detail-design.md
T
2026-05-24 11:30:34 +08:00

229 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
author: Peanut
created_at: 2026-05-24
purpose: AI 调用日志详情查看功能设计文档
---
# AI 调用日志详情查看功能设计
## 背景
后台管理 `web-admin` 中 AI 配置管理的「调用日志」页签目前仅展示基础列表信息(调用时间、场景、服务商、接口、状态、片段数、首字耗时、总耗时、错误码)。入参(`inputText`)、出参(`outputText`)、错误详情(`errorMessage`)等字段已有存储但完全隐藏,不利于后期统计和问题排查。
## 目标
1. 在调用日志列表中支持展开行快速预览入参/出参
2. 提供详情弹窗,完整展示所有字段,入参和出参需 JSON 格式化高亮显示
3. 支持一键复制入参/出参 JSON 内容
4. 后端支持分页、多条件筛选、关键词搜索入参出参内容
## 交互设计
### 列表页(调用日志 Tab
```
┌─────────────────────────────────────────────────────────────────────┐
│ [筛选栏] 状态[全部▼] 场景[全部▼] 时间[近7天▼] 关键词[____] [搜索] │
├─────────────────────────────────────────────────────────────────────┤
│ ▼ 调用时间 场景 服务商 接口 状态 ... │
│ ───────────────────────────────────────────────────────────────── │
│ ▶ 2025-05-24 script_gen dify dify.xxx 成功 ... │
│ ▼ 2025-05-24 emotion coze coze.summary 失败 ... │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 入参预览: {"messages":[{"role":"user","content":"..."}]} │ │
│ │ 出参预览: AI 分析完成,用户情绪状态为... │ │
│ │ [查看完整详情] │ │
│ └────────────────────────────────────────────────────────────┘ │
│ ▶ 2025-05-23 short_story dify dify.story 成功 ... │
├─────────────────────────────────────────────────────────────────────┤
│ 共 156 条 [1][2][3]...[16] 每页 10/20/50 条 │
└─────────────────────────────────────────────────────────────────────┘
```
**展开行交互:**
- 点击行首展开/收起图标切换展开状态
- 展开区域显示入参和出参的前 200 字符预览(超长截断 + `...`
- 提供「查看完整详情」按钮,点击打开详情弹窗
### 详情弹窗
```
┌────────────────────────────────────────────────────────────┐
│ 调用详情 [×] │
├────────────────────────────────────────────────────────────┤
│ 基本信息 │
│ ┌────────────┬────────────┬────────────┬────────────┐ │
│ │ 调用时间 │ 场景 │ 服务商 │ 接口 │ │
│ │ 状态 │ 首字耗时 │ 总耗时 │ 片段数 │ │
│ └────────────┴────────────┴────────────┴────────────┘ │
│ │
│ 入参 [📋 复制] │
│ ┌────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "messages": [ │ │
│ │ { "role": "user", "content": "我最近很焦虑..." }│ │
│ │ ] │ │
│ │ } │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 出参 [📋 复制] │
│ ┌────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "response": "我理解你的感受..." │ │
│ │ } │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 错误信息(仅失败时显示) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ AI_STREAM_INTERRUPTED: 连接超时 │ │
│ └────────────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────────────┤
│ [关闭] │
└────────────────────────────────────────────────────────────┘
```
**弹窗特性:**
- 入参/出参使用代码高亮 + 等宽字体,支持纵向滚动
- 每个代码块右上角有「复制」按钮,点击复制原始 JSON 到剪贴板
- 错误信息区域仅在 `status === 'failed'` 时显示
- 弹窗宽度 800px
## 前端组件设计
### 文件变更
| 文件 | 说明 |
|------|------|
| `web-admin/src/views/aiconfig/AiRoutingList.vue` | 修改:增加展开行、筛选栏、分页逻辑 |
| `web-admin/src/views/aiconfig/components/AiCallLogDetailDialog.vue` | 新增:详情弹窗 |
| `web-admin/src/components/JsonViewer.vue` | 新增:通用 JSON 格式化展示组件 |
### JSON 高亮方案
不引入额外依赖,使用原生 `JSON.stringify(obj, null, 2)` + CSS 样式实现代码高亮:
- 字符串值 → `#a5d6a7`(浅绿)
- 数字/布尔 → `#90caf9`(浅蓝)
- key → `#ce93d8`(浅紫)
- 标点符号 → `#b0bec5`(灰)
背景使用深色主题(`#1e1e1e`),与当前管理后台暗色风格一致。
### 筛选栏参数
```typescript
interface LogQueryParams {
status?: string // running / success / failed
sceneCode?: string
providerCode?: string
endpointCode?: string
startTime?: string
endTime?: string
keyword?: string // 搜索入参/出参内容
pageNum?: number
pageSize?: number
}
```
### 展开行预览逻辑
```typescript
function previewText(jsonStr: string, maxLen = 200): string {
if (!jsonStr) return '-'
return jsonStr.length > maxLen ? jsonStr.slice(0, maxLen) + '...' : jsonStr
}
```
### API 变更
```typescript
// 原接口
export function listAiCallLogs(limit = 50) { ... }
// 新接口
export function queryAiCallLogs(params: LogQueryParams) {
return request({ url: '/ai/call-logs', method: 'post', data: params })
}
```
## 后端 API 设计
### 接口定义
```java
@PostMapping("/call-logs")
@Operation(summary = "分页查询 AI 调用日志")
public Result<Page<AiCallLog>> queryCallLogs(@RequestBody @Valid AiCallLogQueryRequest request)
```
### Request 对象
```java
public class AiCallLogQueryRequest {
private String status;
private String sceneCode;
private String providerCode;
private String endpointCode;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String keyword;
private Integer pageNum = 1;
private Integer pageSize = 20;
}
```
### Service 查询逻辑
```java
@Override
public Page<AiCallLog> query(AiCallLogQueryRequest request) {
Page<AiCallLog> page = new Page<>(request.getPageNum(), request.getPageSize());
LambdaQueryWrapper<AiCallLog> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AiCallLog::getIsDeleted, 0)
.eq(StringUtils.isNotBlank(request.getStatus()), AiCallLog::getStatus, request.getStatus())
.eq(StringUtils.isNotBlank(request.getSceneCode()), AiCallLog::getSceneCode, request.getSceneCode())
.eq(StringUtils.isNotBlank(request.getProviderCode()), AiCallLog::getProviderCode, request.getProviderCode())
.eq(StringUtils.isNotBlank(request.getEndpointCode()), AiCallLog::getEndpointCode, request.getEndpointCode())
.ge(request.getStartTime() != null, AiCallLog::getCreateTime, request.getStartTime())
.le(request.getEndTime() != null, AiCallLog::getCreateTime, request.getEndTime())
.orderByDesc(AiCallLog::getCreateTime);
if (StringUtils.isNotBlank(request.getKeyword())) {
wrapper.and(w -> w.like(AiCallLog::getInputText, request.getKeyword())
.or()
.like(AiCallLog::getOutputText, request.getKeyword()));
}
return page(page, wrapper);
}
```
## 性能考虑
- `input_text``output_text` 可能存储大 JSON(聊天记录),LIKE 搜索在数据量极大时会变慢
- 当前日志量不大,先使用 LIKE。后续如果数据量增长,可升级为:
- MySQL 全文索引(FULLTEXT
- 或限制 keyword 搜索只查询近 N 天数据
## 数据模型
复用现有 `AiCallLog` 实体,无需新增表或字段。详情弹窗展示字段:
| 字段 | 说明 |
|------|------|
| createTime | 调用时间 |
| sceneCode | 场景编码 |
| providerCode | 服务商编码 |
| endpointCode | 接口编码 |
| status | 状态 |
| firstTokenMs | 首字耗时 |
| durationMs | 总耗时 |
| streamChunks | 片段数 |
| inputText | 入参(JSON 格式化) |
| outputText | 出参(JSON 格式化) |
| errorCode | 错误码 |
| errorMessage | 错误信息 |
| userId | 用户 ID |
| requestId | 请求 ID |