feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
855
docs/规划/流式输出视觉呈现规范.md
Normal file
855
docs/规划/流式输出视觉呈现规范.md
Normal file
@@ -0,0 +1,855 @@
|
||||
# 流式输出视觉呈现规范
|
||||
|
||||
> 版本:v1.3
|
||||
> 更新日期:2026-01-18
|
||||
> 适用范围:联系人侧边栏、智能回复等所有 AI 流式输出场景
|
||||
> 关联规范:[瑞小美AI接入规范](./瑞小美AI接入规范.md)、[技术栈标准](./瑞小美系统技术栈标准与字符标准.md)
|
||||
|
||||
---
|
||||
|
||||
## 〇、与现有架构的关系
|
||||
|
||||
### 后端:基于 AIService.chat_stream()
|
||||
|
||||
本规范的 SSE 事件协议是对现有 `AIService.chat_stream()` 的**扩展**,需后端配合改造:
|
||||
|
||||
```python
|
||||
# 现有实现(仅输出 AI 文本)
|
||||
async for chunk in ai.chat_stream(messages, prompt_name="analysis"):
|
||||
yield chunk
|
||||
|
||||
# 扩展实现(支持本规范的事件协议)
|
||||
async def enhanced_stream(messages, steps, prompt_name):
|
||||
# 1. 发送 start 事件
|
||||
yield sse_event("start", {"request_id": req_id})
|
||||
|
||||
# 2. 执行准备步骤并发送 step 事件
|
||||
for i, step in enumerate(steps):
|
||||
yield sse_event("step", {"index": i, "status": "active", **step})
|
||||
await step.execute()
|
||||
yield sse_event("step", {"index": i, "status": "done", **step})
|
||||
|
||||
# 3. 调用 AI 并发送 thinking 事件
|
||||
async for chunk in ai.chat_stream(messages, prompt_name=prompt_name):
|
||||
if is_thinking_content(chunk):
|
||||
yield sse_event("thinking", {"text": chunk})
|
||||
else:
|
||||
# 4. 解析结构化结果并发送 field 事件
|
||||
result = parse_ai_json_response(accumulated_text)
|
||||
yield sse_event("result_start", {"fields": FIELD_DEFINITIONS})
|
||||
for key, value in result.items():
|
||||
yield sse_event("field", {"key": key, "value": value})
|
||||
|
||||
# 5. 发送 complete 事件
|
||||
yield sse_event("complete", {"tokens_used": response.total_tokens})
|
||||
```
|
||||
|
||||
### 前端:扩展现有组件
|
||||
|
||||
| 现有组件 | 本规范组件 | 关系 |
|
||||
|----------|-----------|------|
|
||||
| `stream.js` | `useAIStream.ts` | 扩展,增加阶段状态管理 |
|
||||
| `useTypewriter.js` | `TypewriterList.vue` | 复用,用于列表打字效果 |
|
||||
| `AIStreamDisplay.vue` | `AIStreamContainer.vue` | 升级替换,支持三阶段 |
|
||||
|
||||
---
|
||||
|
||||
## 一、设计目标
|
||||
|
||||
1. **Agent 步骤感**:让用户清晰感知 AI 正在执行的每个步骤
|
||||
2. **思考过程透明**:展示 AI 的推理过程,增强信任感
|
||||
3. **渐进式呈现**:结果逐步填充,而非一次性展示
|
||||
4. **类 Cursor 体验**:思考内容渐隐消失,聚焦最新进展
|
||||
|
||||
---
|
||||
|
||||
## 二、展示模式
|
||||
|
||||
根据 AI 功能的使用场景,分为两种展示模式:
|
||||
|
||||
### 2.1 独立页面模式(Full Page Mode)
|
||||
|
||||
适用于功能复杂、结果丰富的 AI 分析,如:消费意向预测、客户画像、项目推荐等。
|
||||
|
||||
**特点**:
|
||||
- 占据整个页面/Tab 区域
|
||||
- 完整展示三阶段(准备→思考→渲染)
|
||||
- 支持多模块骨架屏渐进填充
|
||||
|
||||
### 2.2 内嵌卡片模式(Inline Card Mode)
|
||||
|
||||
适用于轻量级 AI 功能,嵌入在列表项、会话卡片等位置,如:AI 会话摘要、快速标签等。
|
||||
|
||||
#### 状态机
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ IDLE │ ───▶ │ LOADING │ ───▶ │ COMPLETE │
|
||||
│ 待触发状态 │ │ 加载中 │ │ 结果展示 │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
#### 必须满足的行为规范
|
||||
|
||||
| 状态 | 必须呈现的元素 | 必须支持的交互 |
|
||||
|------|---------------|---------------|
|
||||
| **IDLE** | 可点击的触发入口(如按钮/链接) | 点击后进入 LOADING |
|
||||
| **LOADING** | 加载指示器 + 提示文案 | 可选:支持取消 |
|
||||
| **COMPLETE** | AI 生成的结果内容 | 必须:提供"重新生成"入口 |
|
||||
| **ERROR** | 错误提示 | 必须:可点击重试 |
|
||||
|
||||
#### 视觉示意
|
||||
|
||||
**IDLE(待触发)**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 19:12 - 19:25 来自: 瑞小美轻医美—农农 10条对话 │
|
||||
│ │
|
||||
│ ✨生成AI摘要 | 查看聊天详情 › │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**LOADING(加载中)**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 19:12 - 19:25 来自: 瑞小美轻医美—农农 10条对话 │
|
||||
│ │
|
||||
│ ○ 正在生成摘要... │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**COMPLETE(结果展示)**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 19:12 - 19:25 来自: 瑞小美轻医美—农农 10条对话 │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ 📋 AI会话摘要 [重新生成] │ │
|
||||
│ │ │ │
|
||||
│ │ • 员工推送年底消费回馈现金券活动 │ │
|
||||
│ │ • 客户对活动内容表示疑惑 │ │
|
||||
│ │ • 客户连续追问未获及时回复 │ │
|
||||
│ │ • 客户因等待过久表达不满情绪 │ │
|
||||
│ │ • 邀约到店领券尚未得到响应 │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 查看聊天详情 › │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 缓存规则
|
||||
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| 无缓存 | 显示 IDLE 状态,等待用户触发 |
|
||||
| 有缓存 | **直接显示 COMPLETE 状态**,跳过 IDLE |
|
||||
| 重新生成 | 从 COMPLETE 进入 LOADING,完成后更新结果 |
|
||||
|
||||
#### 与独立页面模式的区别
|
||||
|
||||
| 特性 | 独立页面模式 | 内嵌卡片模式 |
|
||||
|------|-------------|-------------|
|
||||
| 空间 | 整个页面/Tab | 嵌入在卡片/列表项内 |
|
||||
| 准备阶段 | 展示步骤列表 | **省略** |
|
||||
| 思考阶段 | 展示 `<thinking>` 内容 | **省略** |
|
||||
| 渲染阶段 | 骨架屏渐进填充 | 直接展示完整结果 |
|
||||
| 初始触发 | 自动开始 | **等待用户点击** |
|
||||
|
||||
---
|
||||
|
||||
## 三、三阶段状态机(独立页面模式)
|
||||
|
||||
> 以下内容适用于**独立页面模式**
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ PREPARING │ ───▶ │ THINKING │ ───▶ │ RENDERING │ ───▶ COMPLETE
|
||||
│ 准备阶段 │ │ 思考阶段 │ │ 渲染阶段 │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
步骤逐个打勾 步骤完成+思考区域 骨架屏+渐进填充
|
||||
```
|
||||
|
||||
### 3.1 阶段定义
|
||||
|
||||
| 阶段 | 常量 | 触发条件 | 结束条件 |
|
||||
|------|------|----------|----------|
|
||||
| **准备阶段** | `PREPARING` | 请求开始 | 所有步骤完成 |
|
||||
| **思考阶段** | `THINKING` | 收到第一个 `thinking` 事件 | 收到 `result_start` 事件 |
|
||||
| **渲染阶段** | `RENDERING` | 收到 `result_start` 事件 | 收到 `complete` 事件 |
|
||||
| **完成状态** | `COMPLETE` | 收到 `complete` 事件 | - |
|
||||
| **错误状态** | `ERROR` | 收到 `error` 事件 或 请求异常 | 用户重试 |
|
||||
|
||||
---
|
||||
|
||||
## 四、视觉规范(独立页面模式)
|
||||
|
||||
### 4.1 准备阶段(PREPARING)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ 🔄 准备分析数据... │
|
||||
│ │
|
||||
│ ✅ 获取客户信息 │ ← 已完成
|
||||
│ ◐ 查询CRM数据 │ ← 进行中
|
||||
│ ○ 加载对话记录 │ ← 待完成
|
||||
│ ○ 启动AI分析 │
|
||||
│ │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**必须实现的行为**:
|
||||
|
||||
| 步骤状态 | 必须呈现的元素 |
|
||||
|---------|---------------|
|
||||
| 已完成 (done) | 勾号图标 + 完成态样式 |
|
||||
| 进行中 (active) | 加载动画 + 高亮样式 |
|
||||
| 待完成 (pending) | 空心/灰色图标 + 弱化样式 |
|
||||
|
||||
**步骤数据结构**:
|
||||
|
||||
```typescript
|
||||
interface PrepareStep {
|
||||
key: string
|
||||
text: string
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 思考阶段(THINKING)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ 🔄 准备分析数据... │
|
||||
│ │
|
||||
│ ✅ 获取客户信息 │
|
||||
│ ✅ 查询CRM数据 │
|
||||
│ ✅ 加载对话记录 │
|
||||
│ ◐ AI 思考中 │ ← 保持转圈
|
||||
│ │
|
||||
│ ───────────────────────────────────── │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ ▲ 渐隐遮罩 │ │
|
||||
│ │ ...析客户消费偏好,发现近3个月 │ │
|
||||
│ │ 主要消费集中在光电类项目... │ │
|
||||
│ │ 检查到院记录,上次到店是12月 │ │
|
||||
│ │ 15日,做的是热玛吉面部... │ │
|
||||
│ │ 结合咨询铺垫记录,客户对抗衰█ │ │ ← 闪烁光标
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 💡 AI正在分析CRM数据和对话记录... │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**必须实现的行为**:
|
||||
|
||||
| 行为 | 说明 |
|
||||
|------|------|
|
||||
| 步骤列表保留 | 前序步骤显示完成状态,最后一步保持"AI思考中" |
|
||||
| 思考内容展示 | 使用淡色字体,区别于正式结果 |
|
||||
| 自动滚动 | 新内容出现时自动滚动到底部 |
|
||||
| 渐隐效果 | 顶部内容渐隐消失,仅保留最后几行 |
|
||||
| 闪烁光标 | 末尾显示闪烁光标,表示内容仍在输出 |
|
||||
|
||||
**类 Cursor 效果的核心**:
|
||||
- 固定高度容器,`overflow: hidden`
|
||||
- 顶部渐变遮罩实现渐隐
|
||||
- 只显示最后 N 行(建议 5-8 行)
|
||||
- 内容自动滚动到底部
|
||||
|
||||
---
|
||||
|
||||
### 4.3 渲染阶段(RENDERING)
|
||||
|
||||
当收到 `result_start` 事件时,页面**平滑切换**到结果视图。
|
||||
|
||||
#### 骨架屏示意
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ 🛒 可能消费项目 │ ← 标题立即显示
|
||||
│ ┌───────┐ ┌───────┐ ┌───────┐ │
|
||||
│ │░░░░░░░│ │░░░░░░░│ │░░░░░░░│ │ ← 标签骨架
|
||||
│ └───────┘ └───────┘ └───────┘ │
|
||||
└────────────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────────────┐
|
||||
│ ✅ 触发因素 │
|
||||
│ ├── ░░░░░░░░░░░░░░░░░░░░░░░░ │ ← 列表骨架
|
||||
│ ├── ░░░░░░░░░░░░░░░░░░ │
|
||||
│ └── ░░░░░░░░░░░░░░░░░░░░░ │
|
||||
└────────────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────────────┐
|
||||
│ ⚠️ 阻碍因素 │
|
||||
│ ├── ░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ └── ░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**必须实现的行为**:
|
||||
|
||||
| 行为 | 说明 |
|
||||
|------|------|
|
||||
| 阶段切换 | 从思考阶段平滑过渡到渲染阶段,有淡入淡出动画 |
|
||||
| 骨架屏先行 | 收到 `result_start` 时立即显示所有模块的骨架屏 |
|
||||
| 模块标题可见 | 每个模块的标题和图标立即显示,内容区为骨架 |
|
||||
| 渐进填充 | 收到 `field` 事件时,对应模块骨架被实际内容替换 |
|
||||
| 内容动画 | 内容出现时有渐进动画(打字机效果 或 淡入效果) |
|
||||
|
||||
#### 字段定义结构
|
||||
|
||||
后端在 `result_start` 事件中声明字段顺序,前端据此渲染骨架:
|
||||
|
||||
```typescript
|
||||
interface FieldDefinition {
|
||||
key: string
|
||||
title: string
|
||||
type: 'tags' | 'list' | 'text'
|
||||
icon?: string
|
||||
}
|
||||
```
|
||||
|
||||
#### 字段类型与填充方式
|
||||
|
||||
| 类型 | 说明 | 建议填充效果 |
|
||||
|------|------|-------------|
|
||||
| `tags` | 标签列表 | 标签逐个淡入 |
|
||||
| `list` | 文本列表 | 逐行出现,可选打字机效果 |
|
||||
| `text` | 普通文本 | 打字机效果或直接显示 |
|
||||
|
||||
---
|
||||
|
||||
## 五、SSE 事件协议
|
||||
|
||||
### 5.1 事件类型定义(推荐方案)
|
||||
|
||||
| 事件类型 | 说明 | 触发阶段转换 |
|
||||
|----------|------|--------------|
|
||||
| `start` | 流开始 | → PREPARING |
|
||||
| `step` | 步骤状态更新 | - |
|
||||
| `thinking` | AI 思考内容(流式) | → THINKING |
|
||||
| `result_start` | 结果开始,声明字段结构 | → RENDERING |
|
||||
| `field` | 字段值更新 | - |
|
||||
| `complete` | 流结束 | → COMPLETE |
|
||||
| `error` | 错误 | → ERROR |
|
||||
|
||||
### 5.2 事件数据格式
|
||||
|
||||
#### start
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "start",
|
||||
"data": {
|
||||
"request_id": "req_123456",
|
||||
"message": "开始分析"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### step
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "step",
|
||||
"data": {
|
||||
"index": 0,
|
||||
"key": "customer",
|
||||
"text": "获取客户信息",
|
||||
"status": "done" // pending | active | done
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### thinking
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "thinking",
|
||||
"data": {
|
||||
"text": "正在分析客户消费偏好,发现近3个月..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:`thinking` 事件会多次发送,每次携带一小段文本,前端累积拼接。
|
||||
|
||||
#### result_start
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "result_start",
|
||||
"data": {
|
||||
"fields": [
|
||||
{ "key": "likely_items", "title": "可能消费项目", "type": "tags" },
|
||||
{ "key": "trigger_factors", "title": "触发因素", "type": "list" },
|
||||
{ "key": "blocking_factors", "title": "阻碍因素", "type": "list" },
|
||||
{ "key": "suggestions", "title": "跟进建议", "type": "list" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### field
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "field",
|
||||
"data": {
|
||||
"key": "likely_items",
|
||||
"value": ["光电类项目(创始人卡续费/使用)", "皮肤管理项目", "抗衰项目"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "field",
|
||||
"data": {
|
||||
"key": "trigger_factors",
|
||||
"value": [
|
||||
"客户已主动预约明天下午2点半到店",
|
||||
"客户互动意愿强烈(主动说'你也太容易放弃了吧')",
|
||||
"高净值客户(累计消费13万+,客单价1.8万+)",
|
||||
"持有创始人卡项,有消费习惯和忠诚度"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### complete
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "complete",
|
||||
"data": {
|
||||
"request_id": "req_123456",
|
||||
"tokens_used": 1250,
|
||||
"duration_ms": 3500
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### error
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "error",
|
||||
"data": {
|
||||
"code": "AI_TIMEOUT",
|
||||
"message": "AI 分析超时,请重试"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 完整事件流示例
|
||||
|
||||
```
|
||||
event: start
|
||||
data: {"request_id": "req_123", "message": "开始分析"}
|
||||
|
||||
event: step
|
||||
data: {"index": 0, "key": "customer", "text": "获取客户信息", "status": "active"}
|
||||
|
||||
event: step
|
||||
data: {"index": 0, "key": "customer", "text": "获取客户信息", "status": "done"}
|
||||
|
||||
event: step
|
||||
data: {"index": 1, "key": "crm", "text": "查询CRM数据", "status": "active"}
|
||||
|
||||
event: step
|
||||
data: {"index": 1, "key": "crm", "text": "查询CRM数据", "status": "done"}
|
||||
|
||||
event: step
|
||||
data: {"index": 2, "key": "chat", "text": "加载对话记录", "status": "active"}
|
||||
|
||||
event: step
|
||||
data: {"index": 2, "key": "chat", "text": "加载对话记录", "status": "done"}
|
||||
|
||||
event: step
|
||||
data: {"index": 3, "key": "ai", "text": "AI 思考中", "status": "active"}
|
||||
|
||||
event: thinking
|
||||
data: {"text": "正在分析客户消费偏好..."}
|
||||
|
||||
event: thinking
|
||||
data: {"text": "发现近3个月主要消费集中在光电类项目..."}
|
||||
|
||||
event: thinking
|
||||
data: {"text": "检查到院记录,上次到店是12月15日..."}
|
||||
|
||||
event: thinking
|
||||
data: {"text": "结合咨询铺垫记录,客户对抗衰项目有明确兴趣..."}
|
||||
|
||||
event: result_start
|
||||
data: {"fields": [{"key": "likely_items", "title": "可能消费项目", "type": "tags"}, ...]}
|
||||
|
||||
event: field
|
||||
data: {"key": "likely_items", "value": ["光电类项目", "皮肤管理项目", "抗衰项目"]}
|
||||
|
||||
event: field
|
||||
data: {"key": "trigger_factors", "value": ["客户已主动预约...", "客户互动意愿强烈...", ...]}
|
||||
|
||||
event: field
|
||||
data: {"key": "blocking_factors", "value": ["创始人卡项可能已过期...", "无明确咨询铺垫项目...", ...]}
|
||||
|
||||
event: field
|
||||
data: {"key": "suggestions", "value": ["建议确认创始人卡续期情况", "可推荐当季抗衰活动"]}
|
||||
|
||||
event: complete
|
||||
data: {"request_id": "req_123", "tokens_used": 1250, "duration_ms": 3500}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、前端组件架构
|
||||
|
||||
### 6.1 组件层次(建议)
|
||||
|
||||
```
|
||||
<AIStreamContainer> <!-- 流式输出容器 -->
|
||||
├── <PrepareSteps /> <!-- 准备步骤组件 -->
|
||||
├── <ThinkingArea /> <!-- 思考区域组件 -->
|
||||
└── <ResultRenderer /> <!-- 结果骨架/内容组件 -->
|
||||
```
|
||||
|
||||
### 6.2 状态管理
|
||||
|
||||
推荐封装 `useAIStream` Composable,管理以下状态:
|
||||
|
||||
```typescript
|
||||
// 核心类型定义
|
||||
type Phase = 'idle' | 'preparing' | 'thinking' | 'rendering' | 'complete' | 'error'
|
||||
type StepStatus = 'pending' | 'active' | 'done'
|
||||
|
||||
interface PrepareStep {
|
||||
key: string
|
||||
text: string
|
||||
}
|
||||
|
||||
interface FieldDefinition {
|
||||
key: string
|
||||
title: string
|
||||
type: 'tags' | 'list' | 'text'
|
||||
icon?: string
|
||||
}
|
||||
|
||||
// Composable 返回值
|
||||
interface UseAIStreamReturn {
|
||||
phase: Ref<Phase> // 当前阶段
|
||||
isLoading: ComputedRef<boolean> // 是否加载中
|
||||
prepareSteps: ComputedRef<StepWithStatus[]> // 步骤列表(含状态)
|
||||
thinkingText: Ref<string> // 思考内容
|
||||
resultFields: Ref<FieldDefinition[]> // 结果字段定义
|
||||
resultData: Ref<Record<string, unknown>> // 结果数据
|
||||
error: Ref<Error | null> // 错误信息
|
||||
start: (url: string, params?: Record<string, unknown>) => Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 SSE 事件与状态映射
|
||||
|
||||
| SSE 事件 | 状态变更 |
|
||||
|----------|----------|
|
||||
| `start` | `phase` → `preparing` |
|
||||
| `step` | 更新对应步骤状态 |
|
||||
| `thinking` | `phase` → `thinking`,追加 `thinkingText` |
|
||||
| `result_start` | `phase` → `rendering`,设置 `resultFields` |
|
||||
| `field` | 更新 `resultData[key]` |
|
||||
| `complete` | `phase` → `complete` |
|
||||
| `error` | `phase` → `error`,设置 `error` |
|
||||
|
||||
---
|
||||
|
||||
## 七、动画效果要求
|
||||
|
||||
> 具体动画参数由开发自行决定,以下为必须实现的效果:
|
||||
|
||||
### 独立页面模式
|
||||
|
||||
| 效果 | 要求 |
|
||||
|------|------|
|
||||
| 步骤完成 | 有明显的"打勾"反馈(如弹出、闪烁) |
|
||||
| 思考滚动 | 新内容出现时自动滚动到底部,旧内容渐隐上移 |
|
||||
| 阶段切换 | 有过渡动画,避免突兀跳转 |
|
||||
| 骨架屏 | 有脉冲/闪烁效果表示加载中 |
|
||||
| 内容填充 | 渐进式出现(打字机或淡入均可) |
|
||||
|
||||
### 内嵌卡片模式
|
||||
|
||||
| 效果 | 要求 |
|
||||
|------|------|
|
||||
| 加载状态 | 有旋转/跳动等动态指示器 |
|
||||
| 结果展开 | 有展开动画,避免突然出现 |
|
||||
| 列表项 | 建议逐条淡入,增强流式感 |
|
||||
|
||||
---
|
||||
|
||||
## 八、错误处理
|
||||
|
||||
### 8.1 错误类型
|
||||
|
||||
| 错误码 | 说明 | 用户提示 |
|
||||
|--------|------|----------|
|
||||
| `NETWORK_ERROR` | 网络异常 | "网络连接失败,请检查网络后重试" |
|
||||
| `AI_TIMEOUT` | AI 响应超时 | "AI 分析超时,请稍后重试" |
|
||||
| `AI_QUOTA_EXCEEDED` | API 配额不足 | "AI 服务暂时不可用,请联系管理员" |
|
||||
| `DATA_NOT_FOUND` | 数据不存在 | "未找到客户数据,请确认客户信息" |
|
||||
| `PARSE_ERROR` | 解析失败 | "数据解析失败,请重试" |
|
||||
|
||||
### 8.2 错误界面
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ⚠️ │
|
||||
│ │
|
||||
│ AI 分析超时,请稍后重试 │
|
||||
│ │
|
||||
│ [ 🔄 重试 ] │
|
||||
│ │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、缓存与重新分析规范(强制)
|
||||
|
||||
> ⚠️ **强制要求**:所有 AI 分析功能必须支持缓存机制和重新分析能力
|
||||
|
||||
### 9.1 核心原则
|
||||
|
||||
| 原则 | 要求 |
|
||||
|------|------|
|
||||
| **优先缓存** | 如有有效缓存,**必须**首先加载缓存数据,跳过流式请求 |
|
||||
| **重新分析** | 页面**必须**提供"重新分析"按钮,允许用户强制刷新 |
|
||||
| **缓存标识** | 必须展示数据来源(缓存/实时),让用户知晓数据时效 |
|
||||
|
||||
### 9.2 缓存状态定义
|
||||
|
||||
在三阶段状态机基础上,新增 `CACHED` 状态:
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ CACHED │ ◀──── 有有效缓存时直接进入
|
||||
│ 缓存加载 │
|
||||
└──────┬──────┘
|
||||
│ 用户点击"重新分析"
|
||||
▼
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ PREPARING │ ───▶ │ THINKING │ ───▶ │ RENDERING │ ───▶ COMPLETE
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
### 9.3 界面规范
|
||||
|
||||
#### 缓存数据展示
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ 🛒 消费意向分析 │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ 💡 数据来源:缓存(2小时前分析) │ │ ← 缓存标识
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 📊 消费概率:75%(高意向) │
|
||||
│ 🛍️ 可能项目:光电类、皮肤管理 │
|
||||
│ ...(完整结果内容)... │
|
||||
│ │
|
||||
│ ───────────────────────────────────── │
|
||||
│ │
|
||||
│ [ 🔄 重新分析 ] │ ← 必须有此按钮
|
||||
│ │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**必须呈现的元素**:
|
||||
|
||||
| 元素 | 要求 |
|
||||
|------|------|
|
||||
| 缓存标识 | 弱化样式,显示缓存时间(如"2小时前分析") |
|
||||
| 重新分析按钮 | 明确可点击,点击后进入流式请求流程 |
|
||||
|
||||
### 9.4 前端实现要求
|
||||
|
||||
缓存相关数据结构:
|
||||
|
||||
```typescript
|
||||
interface CachedResult {
|
||||
data: Record<string, unknown>
|
||||
cachedAt: string // ISO 8601 时间戳
|
||||
validUntil: string // 过期时间
|
||||
}
|
||||
```
|
||||
|
||||
**必须实现的逻辑**:
|
||||
1. 页面加载时优先检查缓存
|
||||
2. 有效缓存直接展示,跳过流式请求
|
||||
3. 提供"重新分析"功能,触发 `force_refresh`
|
||||
|
||||
### 9.5 后端 API 要求
|
||||
|
||||
所有 AI 分析接口必须支持 `force_refresh` 参数:
|
||||
|
||||
```
|
||||
GET /api/sidebar/purchase-intent?external_userid=xxx&force_refresh=true
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `force_refresh` | boolean | `true` 时跳过缓存,强制重新分析 |
|
||||
|
||||
**响应中包含缓存元信息**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"probability": 0.75,
|
||||
"likely_items": ["光电类", "皮肤管理"],
|
||||
"...": "..."
|
||||
},
|
||||
"meta": {
|
||||
"from_cache": true,
|
||||
"cached_at": "2026-01-18T10:30:00+08:00",
|
||||
"valid_until": "2026-01-19T10:30:00+08:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.6 缓存有效期建议
|
||||
|
||||
| 分析类型 | 建议有效期 | 说明 |
|
||||
|----------|-----------|------|
|
||||
| 消费意向预测 | 24 小时 | 数据变化不频繁 |
|
||||
| 客户画像 | 12 小时 | 对话可能更新 |
|
||||
| 项目推荐 | 24 小时 | 依赖消费历史 |
|
||||
| 接待策略 | 2 小时 | 到店场景需实时 |
|
||||
|
||||
---
|
||||
|
||||
## 十、AI 响应解析与容错
|
||||
|
||||
> 参考:[瑞小美AI接入规范](./瑞小美AI接入规范.md) - AI 响应解析规范章节
|
||||
|
||||
### 10.1 后端解析要求
|
||||
|
||||
AI 返回的 JSON 可能存在格式问题,后端**必须**使用公共解析函数:
|
||||
|
||||
```python
|
||||
from shared_backend.services.ai_service import parse_ai_json_response, safe_parse_ai_json
|
||||
|
||||
# 在发送 field 事件前解析 AI 输出
|
||||
try:
|
||||
result, thinking = parse_ai_json_response(ai_response.content)
|
||||
|
||||
# 发送 thinking 事件(如有)
|
||||
if thinking:
|
||||
yield sse_event("thinking", {"text": thinking})
|
||||
|
||||
# 发送 result_start 和 field 事件
|
||||
yield sse_event("result_start", {"fields": FIELD_DEFINITIONS})
|
||||
for key, value in result.items():
|
||||
yield sse_event("field", {"key": key, "value": value})
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
# 解析失败,发送错误事件
|
||||
yield sse_event("error", {
|
||||
"code": "PARSE_ERROR",
|
||||
"message": f"AI 输出解析失败: {str(e)}"
|
||||
})
|
||||
```
|
||||
|
||||
### 10.2 推荐:使用 json-repair 库
|
||||
|
||||
对于 JSON 格式问题较多的场景,推荐使用 `json-repair` 库(GitHub 4000+ 星):
|
||||
|
||||
```python
|
||||
from json_repair import loads as json_repair_loads
|
||||
|
||||
# 自动修复常见 JSON 格式问题
|
||||
result = json_repair_loads(ai_raw_output)
|
||||
```
|
||||
|
||||
**能修复的问题**:
|
||||
- 单引号 → 双引号
|
||||
- 未闭合括号补齐
|
||||
- `True/False/None` → `true/false/null`
|
||||
- 尾部逗号移除
|
||||
- Markdown 代码块提取
|
||||
|
||||
### 10.3 前端容错处理
|
||||
|
||||
前端收到 `field` 事件时,应做类型校验:
|
||||
|
||||
```typescript
|
||||
function onField(data: { key: string; value: unknown }): void {
|
||||
const { key, value } = data
|
||||
|
||||
// 类型校验
|
||||
const fieldDef = resultFields.value.find(f => f.key === key)
|
||||
if (!fieldDef) return
|
||||
|
||||
// 根据类型校验 value
|
||||
if (fieldDef.type === 'tags' && !Array.isArray(value)) {
|
||||
console.warn(`字段 ${key} 期望数组,实际: ${typeof value}`)
|
||||
resultData.value[key] = []
|
||||
return
|
||||
}
|
||||
|
||||
if (fieldDef.type === 'list' && !Array.isArray(value)) {
|
||||
console.warn(`字段 ${key} 期望数组,实际: ${typeof value}`)
|
||||
resultData.value[key] = []
|
||||
return
|
||||
}
|
||||
|
||||
resultData.value[key] = value
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、性能优化建议
|
||||
|
||||
1. **防抖思考文本更新**:每 50ms 批量更新一次 DOM,避免频繁重绘
|
||||
2. **虚拟滚动**:思考区域内容过长时使用虚拟滚动
|
||||
3. **骨架屏预渲染**:结果骨架使用 `v-show` 而非 `v-if`
|
||||
4. **动画硬件加速**:使用 `transform` 和 `opacity`,避免触发重排
|
||||
|
||||
---
|
||||
|
||||
## 十二、兼容性说明
|
||||
|
||||
- **SSE 支持**:所有现代浏览器均支持 `fetch` + `ReadableStream`
|
||||
- **CSS 动画**:使用标准 CSS3 动画,兼容 Chrome 60+, Safari 12+, Firefox 55+
|
||||
- **字体回退**:等宽字体使用系统回退链
|
||||
- **字符编码**:UTF-8(符合[技术栈标准](./瑞小美系统技术栈标准与字符标准.md#字符标准))
|
||||
|
||||
```css
|
||||
font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Code", "Consolas", monospace;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十三、待确认事项
|
||||
|
||||
- [ ] 后端 SSE 事件协议改造排期
|
||||
- [ ] 是否需要支持中断/取消流式请求
|
||||
- [ ] 移动端适配细节(触摸滚动、字体大小)
|
||||
- [x] ~~是否需要保存分析历史~~ → 已确认:必须支持缓存,详见第九章
|
||||
|
||||
---
|
||||
|
||||
*最后更新:2026-01-18*
|
||||
|
||||
Reference in New Issue
Block a user