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

11 KiB
Raw Blame History

author, created_at, purpose
author created_at purpose
Peanut 2026-05-24 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),与当前管理后台暗色风格一致。

筛选栏参数

interface LogQueryParams {
  status?: string        // running / success / failed
  sceneCode?: string
  providerCode?: string
  endpointCode?: string
  startTime?: string
  endTime?: string
  keyword?: string       // 搜索入参/出参内容
  pageNum?: number
  pageSize?: number
}

展开行预览逻辑

function previewText(jsonStr: string, maxLen = 200): string {
  if (!jsonStr) return '-'
  return jsonStr.length > maxLen ? jsonStr.slice(0, maxLen) + '...' : jsonStr
}

API 变更

// 原接口
export function listAiCallLogs(limit = 50) { ... }

// 新接口
export function queryAiCallLogs(params: LogQueryParams) {
  return request({ url: '/ai/call-logs', method: 'post', data: params })
}

后端 API 设计

接口定义

@PostMapping("/call-logs")
@Operation(summary = "分页查询 AI 调用日志")
public Result<Page<AiCallLog>> queryCallLogs(@RequestBody @Valid AiCallLogQueryRequest request)

Request 对象

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 查询逻辑

@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_textoutput_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