docs: 修复第二轮 spec review 问题
- 分页返回类型改为 PageResult - JSON 高亮改为 highlight.js - 补充 Service 接口变更、前端类型定义、分页适配 - 修正展开行预览截断逻辑 - 明确旧接口共存策略、下拉数据来源、时间边界 - 补充 Request 存放路径、keyword 空值处理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -102,7 +102,7 @@ purpose: AI 调用日志详情查看功能设计文档
|
|||||||
|
|
||||||
### JSON 高亮方案
|
### JSON 高亮方案
|
||||||
|
|
||||||
不引入额外依赖,使用原生 `JSON.stringify(obj, null, 2)` + CSS 样式实现代码高亮。
|
JSON 格式化使用 `highlight.js` 轻量库(项目已可通过 npm 安装),对 JSON 语言进行语法高亮,无需手写解析逻辑。配合深色主题 CSS 覆盖默认配色。
|
||||||
|
|
||||||
**`JsonViewer.vue` Props 接口:**
|
**`JsonViewer.vue` Props 接口:**
|
||||||
```typescript
|
```typescript
|
||||||
@@ -141,16 +141,51 @@ interface LogQueryParams {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**时间筛选交互:** 前端筛选栏提供快捷选项(近7天、近30天、自定义范围),由前端组件将快捷选项转换为 `startTime`/`endTime` 后传给后端。自定义范围时使用 Element Plus `el-date-picker` 选择起止时间。
|
**时间筛选交互:** 前端筛选栏提供快捷选项(近7天、近30天、自定义范围),由前端组件将快捷选项转换为 `startTime`/`endTime` 后传给后端。自定义范围时使用 Element Plus `el-date-picker` 选择起止时间。
|
||||||
|
- "近7天":今天 00:00:00 往前推 6 天(含今天)
|
||||||
|
- "近30天":今天 00:00:00 往前推 29 天(含今天)
|
||||||
|
- 自定义:用户选择起止日期,开始时间为所选日期 00:00:00,结束时间为所选日期 23:59:59
|
||||||
|
|
||||||
|
**下拉数据来源:** 场景、服务商、接口下拉选项复用页面已加载的 providers / endpoints / scenes 数据(`loadAll()` 中已有),无需额外接口。
|
||||||
|
|
||||||
### 展开行预览逻辑
|
### 展开行预览逻辑
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function previewText(jsonStr: string, maxLen = 200): string {
|
function previewText(jsonStr: string, maxLen = 200): string {
|
||||||
if (!jsonStr) return '-'
|
if (!jsonStr) return '-'
|
||||||
return jsonStr.length > maxLen ? jsonStr.slice(0, maxLen) + '...' : jsonStr
|
if (jsonStr.length <= maxLen) return jsonStr
|
||||||
|
// 优先在空白字符处截断,避免截断在转义序列中间
|
||||||
|
const truncated = jsonStr.slice(0, maxLen)
|
||||||
|
const lastSpace = truncated.search(/\s+(?!.*\s)/)
|
||||||
|
return lastSpace > maxLen * 0.5 ? truncated.slice(0, lastSpace) + '...' : truncated + '...'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 类型定义
|
||||||
|
|
||||||
|
在 `web-admin/src/types/aiconfig.ts` 中新增:
|
||||||
|
```typescript
|
||||||
|
export interface LogQueryParams {
|
||||||
|
status?: string
|
||||||
|
sceneCode?: string
|
||||||
|
providerCode?: string
|
||||||
|
endpointCode?: string
|
||||||
|
startTime?: string
|
||||||
|
endTime?: string
|
||||||
|
keyword?: string
|
||||||
|
pageNum?: number
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageResult<T> {
|
||||||
|
records: T[]
|
||||||
|
total: number
|
||||||
|
pageNum: number
|
||||||
|
pageSize: number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`AiRoutingList.vue` 中的 `logs` 数据从 `AiCallLog[]` 改为分页结构:`const logs = ref<PageResult<AiCallLog>>({ records: [], total: 0, pageNum: 1, pageSize: 20 })`,表格数据源绑定 `logs.value.records`。
|
||||||
|
|
||||||
### API 变更
|
### API 变更
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -163,7 +198,7 @@ export function queryAiCallLogs(params: LogQueryParams) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**兼容性说明:** 经检查,`listAiCallLogs` 仅在 `AiRoutingList.vue` 的调用日志 Tab 中使用,无其他调用方。本次直接替换,不保留旧接口。
|
**兼容性说明:** 经检查,`listAiCallLogs` 仅在 `AiRoutingList.vue` 的调用日志 Tab 中使用,无其他调用方。新接口使用 POST,旧接口使用 GET,路径相同方法不同,技术上可共存。实现时直接替换前端调用即可,旧 GET 接口可保留或后续清理。
|
||||||
|
|
||||||
## 后端 API 设计
|
## 后端 API 设计
|
||||||
|
|
||||||
@@ -172,11 +207,13 @@ export function queryAiCallLogs(params: LogQueryParams) {
|
|||||||
```java
|
```java
|
||||||
@PostMapping("/call-logs")
|
@PostMapping("/call-logs")
|
||||||
@Operation(summary = "分页查询 AI 调用日志")
|
@Operation(summary = "分页查询 AI 调用日志")
|
||||||
public Result<Page<AiCallLog>> queryCallLogs(@RequestBody @Valid AiCallLogQueryRequest request)
|
public Result<PageResult<AiCallLog>> queryCallLogs(@RequestBody @Valid AiCallLogQueryRequest request)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Request 对象
|
### Request 对象
|
||||||
|
|
||||||
|
存放路径:`backend-single/src/main/java/com/emotion/dto/request/ai/AiCallLogQueryRequest.java`
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Schema(description = "AI 调用日志查询请求")
|
@Schema(description = "AI 调用日志查询请求")
|
||||||
public class AiCallLogQueryRequest {
|
public class AiCallLogQueryRequest {
|
||||||
@@ -214,6 +251,15 @@ public class AiCallLogQueryRequest {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Service 接口变更
|
||||||
|
|
||||||
|
在 `AiCallLogService` 接口中新增方法:
|
||||||
|
```java
|
||||||
|
PageResult<AiCallLog> query(AiCallLogQueryRequest request);
|
||||||
|
```
|
||||||
|
|
||||||
|
原 `latest(Integer limit)` 方法保留(前端旧接口调用或后台其他功能可能依赖)。
|
||||||
|
|
||||||
### Service 查询逻辑
|
### Service 查询逻辑
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@@ -231,10 +277,11 @@ public Page<AiCallLog> query(AiCallLogQueryRequest request) {
|
|||||||
.le(request.getEndTime() != null, AiCallLog::getCreateTime, request.getEndTime())
|
.le(request.getEndTime() != null, AiCallLog::getCreateTime, request.getEndTime())
|
||||||
.orderByDesc(AiCallLog::getCreateTime);
|
.orderByDesc(AiCallLog::getCreateTime);
|
||||||
|
|
||||||
|
// keyword 为空字符串或纯空格时不执行 LIKE 搜索(isNotBlank 已处理)
|
||||||
if (StringUtils.isNotBlank(request.getKeyword())) {
|
if (StringUtils.isNotBlank(request.getKeyword())) {
|
||||||
wrapper.and(w -> w.nested(i -> i.like(AiCallLog::getInputText, request.getKeyword())
|
wrapper.and(w -> w.like(AiCallLog::getInputText, request.getKeyword())
|
||||||
.or()
|
.or()
|
||||||
.like(AiCallLog::getOutputText, request.getKeyword())));
|
.like(AiCallLog::getOutputText, request.getKeyword()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return page(page, wrapper);
|
return page(page, wrapper);
|
||||||
|
|||||||
Reference in New Issue
Block a user