feat: chat integration, tenant admin spec, backend chat service, miniprogram updates, DEMO moved to tmp, XCX-TEST removed, migrations & docs
This commit is contained in:
@@ -528,7 +528,7 @@ interface CoachTask {
|
||||
// ─── 最亲密助教 ───
|
||||
|
||||
interface FavoriteCoach {
|
||||
/** 亲密度 emoji,如 "💖"、"💛" */
|
||||
/** 亲密度 emoji,如 "💖"、"🧡"、"💛"、"💙" */
|
||||
emoji: string
|
||||
/** 助教姓名 */
|
||||
name: string
|
||||
@@ -699,7 +699,7 @@ interface CustomerNote {
|
||||
|
||||
| 字段 | 类型 | 说明 | 数据源 |
|
||||
|------|------|------|--------|
|
||||
| `emoji` | string | 亲密度 emoji(💖 = 高亲密度 ≥ 0.7,💛 = 中亲密度 < 0.7) | 后端根据 `relationIndex` 映射 |
|
||||
| `emoji` | string | 亲密度 emoji(💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5),后端 compute_heart_icon() 根据 rs_display 映射) | 后端根据 `relationIndex` 映射 |
|
||||
| `name` | string | 助教姓名 | `biz.users.nickname` |
|
||||
| `relationIndex` | string | 关系指数,如 `"0.92"` | 后端根据服务频率/消费金额综合计算 |
|
||||
| `indexColor` | string | 关系指数对应颜色 | 后端根据 `relationIndex` 阈值映射 |
|
||||
@@ -759,7 +759,7 @@ interface CustomerNote {
|
||||
| `settle_type` 过滤 | 消费记录取正向交易 `settle_type IN (1, 3)`,本表无 `is_delete` 字段 |
|
||||
| 废单排除 | 使用 `dwd_assistant_service_log_ex.is_trash` 排除废单,`dwd_assistant_trash_event` 已废弃 |
|
||||
| `type` 枚举映射 | `table`(台桌消费)/ `shop`(酒水/商品消费)/ `recharge`(充值),后端根据 `settle_type` 和业务逻辑映射 |
|
||||
| 亲密度 emoji | 💖 = 高亲密度(关系指数 ≥ 0.7),💛 = 中亲密度(关系指数 < 0.7) |
|
||||
| 亲密度 emoji | 💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5) |
|
||||
|
||||
#### 数据源映射表
|
||||
|
||||
@@ -867,8 +867,8 @@ interface CoachBoardItem {
|
||||
skills: Array<{ text: string; cls: string }>
|
||||
/**
|
||||
* Top 客户列表(含亲密度 emoji 前缀)
|
||||
* 示例:['💖 王先生', '💖 李女士', '💛 赵总']
|
||||
* 💖 = 高亲密度,💛 = 中亲密度
|
||||
* 示例:['💖 王先生', '🧡 李女士', '💛 赵总', '💙 新客户']
|
||||
* 💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5)
|
||||
*/
|
||||
topCustomers: string[]
|
||||
|
||||
@@ -922,7 +922,7 @@ interface CoachBoardItem {
|
||||
| `avatarGradient` | string | 头像渐变色标识(`blue`/`green`/`pink`/`amber`/`violet`/`cyan`) | 后端根据 ID 哈希分配 |
|
||||
| `level` | string | 等级英文 key:`star`/`senior`/`middle`/`junior` | `fdw_etl.v_dws_assistant_salary_calc.coach_level` |
|
||||
| `skills` | `Array<{ text: string, cls: string }>` | 技能标签,`text` 为 emoji 或文字,`cls` 为前端样式类 | `biz.coach_skills` 或配置表 |
|
||||
| `topCustomers` | `string[]` | Top 客户列表,含亲密度 emoji 前缀(💖/💛) | 后端按亲密度排序取 Top 3,拼接 emoji + 姓名 |
|
||||
| `topCustomers` | `string[]` | Top 客户列表,含亲密度 emoji 前缀(💖/🧡/💛/💙) | 后端按亲密度排序取 Top 3,拼接 emoji + 姓名 |
|
||||
|
||||
##### perf 维度专属字段(4 个)
|
||||
|
||||
@@ -978,7 +978,7 @@ interface CoachBoardItem {
|
||||
| 助教费用拆分(DWD-DOC 规则 2) | 工资计算中基础课 = `assistant_pd_money`,激励课 = `assistant_cx_money` |
|
||||
| `levelClass` 前端计算 | 后端仅返回 `level`(英文 key),前端通过 `LEVEL_CLASS` 映射生成样式类 |
|
||||
| `perfGap` 后端计算 | 后端根据档位阈值计算差距描述字符串,已达标时不返回该字段 |
|
||||
| `topCustomers` emoji 规则 | 💖 = 高亲密度(关系指数 ≥ 0.7),💛 = 中亲密度(关系指数 < 0.7) |
|
||||
| `topCustomers` emoji 规则 | 💖(>8.5) / 🧡(>7) / 💛(>5) / 💙(≤5),基于 rs_display 值域 0-10 |
|
||||
|
||||
### BOARD-2: 客户看板
|
||||
|
||||
@@ -1856,10 +1856,12 @@ interface NoteItem {
|
||||
|
||||
---
|
||||
|
||||
## 八、对话模块
|
||||
## 八、对话模块(已实现 ✅ — RNS1.4)
|
||||
|
||||
> 数据源:`zqyy_app.chat_sessions`(对话会话)、`zqyy_app.chat_messages`(消息)
|
||||
> 路由前缀:`/api/xcx/chat`(RNS1.4 从 `/api/ai/*` 迁移,旧路径已移除)
|
||||
> 数据源:`zqyy_app.biz.ai_conversations`(对话会话)、`zqyy_app.biz.ai_messages`(消息)
|
||||
> 时间字段统一使用 `createdAt`(camelCase),替代前端 `timestamp` 和旧契约 `created_at`
|
||||
> 所有端点需 JWT(approved 状态),使用 `require_approved()` 权限检查
|
||||
|
||||
### CHAT-1: 对话历史列表
|
||||
|
||||
@@ -1867,15 +1869,22 @@ interface NoteItem {
|
||||
GET /api/xcx/chat/history?page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `page` | number (query) | 否 | 页码,默认 1 |
|
||||
| `pageSize` | number (query) | 否 | 每页条数,默认 20,最大 100 |
|
||||
|
||||
#### 响应结构
|
||||
|
||||
```typescript
|
||||
interface ChatHistoryResponse {
|
||||
items: Array<{
|
||||
id: string // 对话 ID
|
||||
title: string // 对话标题(新增,需求 5.8.2)
|
||||
customerName?: string // 关联客户姓名(可选)
|
||||
lastMessage: string // 最后一条消息摘要
|
||||
id: number // 对话 ID(即 chatId)
|
||||
title: string // 对话标题(自定义 > 客户姓名 > 首条消息前20字)
|
||||
customerName?: string // 关联客户姓名(仅 contextType=customer 时有值)
|
||||
lastMessage?: string // 最后一条消息摘要
|
||||
timestamp: string // 最后消息时间(ISO 8601)
|
||||
unreadCount: number // 未读消息数
|
||||
}>
|
||||
@@ -1885,33 +1894,55 @@ interface ChatHistoryResponse {
|
||||
}
|
||||
```
|
||||
|
||||
### CHAT-2: 对话消息
|
||||
#### 排序规则
|
||||
|
||||
> 支持 `customerId` 查询参数(需求 5.8.5):后端根据 `customerId` 自动查找或创建对话,返回对应的 `chatId` 和消息列表
|
||||
按 `last_message_at` 倒序(最新对话在前)。
|
||||
|
||||
### CHAT-2a: 通过 chatId 查询消息
|
||||
|
||||
```
|
||||
GET /api/xcx/chat/{chatId}/messages?page=1&pageSize=50
|
||||
GET /api/xcx/chat/messages?customerId={customerId}&page=1&pageSize=50
|
||||
```
|
||||
|
||||
#### 响应结构
|
||||
#### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `chatId` | number (path) | 是 | 对话 ID |
|
||||
| `page` | number (query) | 否 | 页码,默认 1 |
|
||||
| `pageSize` | number (query) | 否 | 每页条数,默认 50,最大 100 |
|
||||
|
||||
### CHAT-2b: 通过上下文查询消息
|
||||
|
||||
> 后端根据 `contextType` + `contextId` 自动查找或创建对话,返回对应的 `chatId` 和消息列表。
|
||||
> 对话复用规则:`task` 入口同一 taskId 始终复用(无时限);`customer`/`coach` 入口 ≤ 3 天复用、> 3 天新建;`general` 入口始终新建。
|
||||
|
||||
```
|
||||
GET /api/xcx/chat/messages?contextType={type}&contextId={id}&page=1&pageSize=50
|
||||
```
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `contextType` | string (query) | 是 | 上下文类型:`task` / `customer` / `coach` / `general` |
|
||||
| `contextId` | string (query) | 是 | 上下文 ID(taskId / customerId / coachId,general 时为空) |
|
||||
| `page` | number (query) | 否 | 页码,默认 1 |
|
||||
| `pageSize` | number (query) | 否 | 每页条数,默认 50,最大 100 |
|
||||
|
||||
#### 响应结构(CHAT-2a / CHAT-2b 共用)
|
||||
|
||||
```typescript
|
||||
interface ChatMessagesResponse {
|
||||
chatId: string // 对话 ID(customerId 入口时由后端返回)
|
||||
chatId: number // 对话 ID(上下文入口时由后端返回,供后续发送消息使用)
|
||||
items: Array<{
|
||||
id: string
|
||||
id: number
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
createdAt: string // 统一时间字段名(需求 5.8.1),ISO 8601
|
||||
createdAt: string // 统一时间字段名,ISO 8601
|
||||
|
||||
// 引用卡片(可选,需求 5.8.3)
|
||||
referenceCard?: {
|
||||
type: 'customer' | 'record' // 引用类型
|
||||
title: string // 卡片标题,如 '张伟 — 消费概览'
|
||||
summary: string // 摘要文字
|
||||
data: Record<string, string> // 键值对详情,如 { '近30天消费': '¥2,380', '到店次数': '8次' }
|
||||
}
|
||||
// 引用卡片(可选,AI 回复涉及特定客户时附带)
|
||||
referenceCard?: ReferenceCard
|
||||
}>
|
||||
total: number
|
||||
page: number
|
||||
@@ -1919,42 +1950,89 @@ interface ChatMessagesResponse {
|
||||
}
|
||||
```
|
||||
|
||||
### CHAT-3: 发送消息
|
||||
#### 排序规则
|
||||
|
||||
消息按 `created_at` 正序(最早的消息在前)。
|
||||
|
||||
#### referenceCard 结构定义
|
||||
|
||||
```typescript
|
||||
interface ReferenceCard {
|
||||
type: 'customer' | 'record' // 引用类型
|
||||
title: string // 卡片标题,如 '张伟 — 消费概览'
|
||||
summary: string // 摘要文字,如 '余额 ¥5,200,近30天消费 ¥2,380'
|
||||
data: Record<string, string> // 键值对详情
|
||||
}
|
||||
```
|
||||
|
||||
示例:
|
||||
```json
|
||||
{
|
||||
"type": "customer",
|
||||
"title": "张伟 — 消费概览",
|
||||
"summary": "余额 ¥5,200,近30天消费 ¥2,380",
|
||||
"data": {
|
||||
"余额": "¥5,200",
|
||||
"近30天消费": "¥2,380",
|
||||
"到店次数": "8次",
|
||||
"最近到店": "3天前"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> referenceCard 中金额使用 `items_sum` 口径(DWD-DOC 强制规则 1),会员信息通过 `member_id` JOIN `dim_member` 获取(DWD-DOC 规则 DQ-6)。
|
||||
|
||||
### CHAT-3: 发送消息(同步回复)
|
||||
|
||||
```
|
||||
POST /api/xcx/chat/{chatId}/messages
|
||||
Content-Type: application/json
|
||||
Body: { content: string }
|
||||
```
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `chatId` | number (path) | 是 | 对话 ID(归属验证:不属于当前用户返回 403) |
|
||||
| `content` | string (body) | 是 | 消息内容(不能为空) |
|
||||
|
||||
#### 响应结构
|
||||
|
||||
```typescript
|
||||
interface SendMessageResponse {
|
||||
userMessage: {
|
||||
id: string
|
||||
id: number
|
||||
content: string
|
||||
createdAt: string
|
||||
createdAt: string // ISO 8601
|
||||
}
|
||||
aiReply: {
|
||||
id: string
|
||||
id: number
|
||||
content: string
|
||||
createdAt: string
|
||||
createdAt: string // ISO 8601
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CHAT-4: SSE 流式端点(需求 5.8.4)
|
||||
#### AI 失败降级
|
||||
|
||||
> 用于 AI 流式回复,前端通过 SSE 接收逐 token 输出
|
||||
AI 服务调用失败时,用户消息仍保存,`aiReply.content` 返回错误提示消息(如 `"抱歉,AI 助手暂时无法回复,请稍后重试"`),HTTP 状态码保持 200。
|
||||
|
||||
### CHAT-4: SSE 流式端点
|
||||
|
||||
> 用于 AI 流式回复,前端通过 SSE 接收逐 token 输出。
|
||||
> chatId 归属验证在 SSE 流开始前完成,失败时返回普通 HTTP 403(非 SSE)。
|
||||
|
||||
```
|
||||
POST /api/xcx/chat/stream
|
||||
Content-Type: application/json
|
||||
Body: { chatId: string, content: string }
|
||||
Body: { chatId: number, content: string }
|
||||
Response: text/event-stream
|
||||
```
|
||||
|
||||
#### SSE 事件格式
|
||||
#### SSE 事件类型定义
|
||||
|
||||
三种事件类型:`message`(逐 token)、`done`(完成)、`error`(错误)。
|
||||
|
||||
```
|
||||
event: message
|
||||
@@ -1964,24 +2042,40 @@ event: message
|
||||
data: {"token": "数据分析"}
|
||||
|
||||
event: done
|
||||
data: {"messageId": "msg-xxx", "createdAt": "2026-03-05T14:30:00+08:00"}
|
||||
data: {"messageId": 123, "createdAt": "2026-03-20T14:30:00+08:00"}
|
||||
|
||||
event: error
|
||||
data: {"message": "AI 服务暂时不可用"}
|
||||
```
|
||||
|
||||
- `event: message` — 逐 token 输出,`data.token` 为文本片段
|
||||
- `event: done` — 流结束,`data.messageId` 为完整消息 ID
|
||||
- `event: error` — 错误,`data.message` 为错误描述
|
||||
| 事件类型 | data 结构 | 说明 |
|
||||
|----------|----------|------|
|
||||
| `message` | `{ "token": string }` | 逐 token 输出,`token` 为文本片段 |
|
||||
| `done` | `{ "messageId": number, "createdAt": string }` | 流结束,返回完整消息 ID 和创建时间 |
|
||||
| `error` | `{ "message": string }` | 错误,返回错误描述 |
|
||||
|
||||
> 注意:SSE 端点的响应不经过 ResponseWrapperMiddleware 包装(`text/event-stream` 自动跳过)
|
||||
> 注意:SSE 端点的响应不经过 ResponseWrapperMiddleware 包装(`text/event-stream` 自动跳过,RNS1.0 已实现)。
|
||||
|
||||
#### 对话模块错误码
|
||||
|
||||
| HTTP 状态码 | 场景 | 响应示例 |
|
||||
|:-----------:|------|---------|
|
||||
| 403 | 用户未通过审核 | `{ "code": 403, "message": "用户未通过审核,无法访问此资源" }` |
|
||||
| 403 | chatId 不属于当前用户 | `{ "code": 403, "message": "无权访问此对话" }` |
|
||||
| 404 | 对话不存在 | `{ "code": 404, "message": "对话不存在" }` |
|
||||
| 422 | 消息内容为空 | `{ "code": 422, "message": "消息内容不能为空" }` |
|
||||
|
||||
#### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `CHAT-1.items[].title` | string | 对话标题,新增字段 |
|
||||
| `CHAT-1.items[].title` | string | 对话标题(自定义 > 客户姓名 > 首条消息前20字) |
|
||||
| `CHAT-1.items[].timestamp` | string | 最后消息时间(ISO 8601) |
|
||||
| `CHAT-2.items[].createdAt` | string | 统一时间字段名,替代 `created_at` 和 `timestamp` |
|
||||
| `CHAT-2.items[].referenceCard` | object? | 引用卡片,从其他页面跳转时附带的上下文信息 |
|
||||
| `CHAT-2` `customerId` 参数 | query | 支持通过客户 ID 查找/创建对话 |
|
||||
| `CHAT-4` | SSE | 流式端点,`text/event-stream` 格式,不经过响应包装 |
|
||||
| `CHAT-2.items[].referenceCard` | ReferenceCard? | 引用卡片,AI 回复涉及客户时附带结构化上下文数据 |
|
||||
| `CHAT-2b` `contextType` 参数 | query | 上下文类型:`task` / `customer` / `coach` / `general` |
|
||||
| `CHAT-2b` `contextId` 参数 | query | 上下文 ID(taskId / customerId / coachId) |
|
||||
| `CHAT-4` SSE | text/event-stream | 三种事件:`message`(token)/ `done`(完成)/ `error`(错误) |
|
||||
|
||||
---
|
||||
|
||||
@@ -2017,6 +2111,6 @@ Response: {
|
||||
| board-coach | BOARD-1, CONFIG-1 | P2 |
|
||||
| board-customer | BOARD-2 | P2 |
|
||||
| board-finance | BOARD-3 | P2 |
|
||||
| chat-history | CHAT-1 | P2 |
|
||||
| chat | CHAT-2, CHAT-3 | P2 |
|
||||
| chat-history | CHAT-1 | ✅ 后端已实现 |
|
||||
| chat | CHAT-2a, CHAT-2b, CHAT-3, CHAT-4 | ✅ 后端已实现 |
|
||||
| my-profile | AUTH-3 | ✅ 已实现(读 globalData) |
|
||||
|
||||
Reference in New Issue
Block a user