docs: AI 调用日志详情查看功能设计文档

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 11:30:34 +08:00
parent 9838e7626b
commit f98617a70a
@@ -0,0 +1,228 @@
---
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 |