# PRD-增量-人工按钮与术语统一

> **文档版本**: v1.0
> **创建日期**: 2026-06-11
> **产品经理**: 许清楚 (Xu) · 宋献
> **状态**: 待评审
> **对应任务**: 人工按钮需求定义 + 术语/图标统一规范

---

## 摘要

本文档包含两个增量需求：

1. **"人工"按钮需求文档** — 定义H5用户端呼叫坐席按钮的完整产品规范
2. **术语/图标统一规范** — 全局统一"人工"与"摇人"概念，取消"举手"概念

---

## 一、"人工"按钮需求文档

### 1.1 需求背景

当前H5用户端存在以下问题：
- 按钮文案不统一：代码中为"呼叫"，产品语言中应为"人工"
- 按钮图标使用了摇铃铛 🔔（手摇铃），与产品定义不符
- 按钮启用条件未明确定义
- 坐席端对应状态图标未同步

### 1.2 产品定义

**"人工"** = 用户（员工）主动呼叫IT坐席，请求人工服务。

| 属性 | 定义 |
|------|------|
| 触发者 | H5用户端员工 |
| 接收者 | IT坐席（主责坐席或系统分配坐席） |
| 图标 | 传菜铃（桌面拍铃）—— 前台/厨房放在桌面、拍一下发出"叮"声的圆形金属铃铛 |
| 区别于 | "摇人" = 坐席呼叫其他坐席（详见第二节） |

> **图标说明：传菜铃 vs 摇铃**
>
> - ✅ **传菜铃**（正确）：圆形金属铃铛，放在桌面上，从上方拍打铃面发出"叮"声。代表"我已提出问题，请人工来看一下"。
> - ❌ **摇铃** 🔔（错误）：有把手的手摇铃，摇动发出声响。当前代码中使用的是此图标，需替换。
> - 传菜铃的SVG/Unicode表示：无直接对应的Unicode字符，建议使用自定义SVG图标（圆形底座+金属铃面）

### 1.3 按钮设计

#### 1.3.1 H5用户端按钮

**位置**：H5聊天界面标题栏右侧（当前"呼叫"按钮位置）

**形态**：

```
┌────────────────────────────────────┐
│  智能IT支持服务台          [🔔 人工]   │  ← 启用状态（橙色）
│                     [▓▓ 人工]   │  ← 禁用状态（灰色）
└────────────────────────────────────┘
```

**文案**：`人工`（按钮文字）

**图标规范**：
- 启用状态：传菜铃SVG图标 + 橙色系配色 `#FF9800`
- 禁用状态：传菜铃SVG图标 + 灰色 `#9E9E9E`，按钮整体 `opacity: 0.5; cursor: not-allowed`
- 图标尺寸：16×16px（标题栏）、24×24px（弹窗内）

**传菜铃SVG参考**：

```svg
<!-- 传菜铃（桌面拍铃）SVG示意 -->
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <!-- 铃身（圆形，从上方拍打） -->
  <circle cx="12" cy="14" r="7" fill="#FFD54F" stroke="#FFA000" stroke-width="1.5"/>
  <!-- 铃底金属边框 -->
  <ellipse cx="12" cy="20" rx="5" ry="1.5" fill="#FFA000"/>
  <!-- 拍铃动画指示（从上方向下拍） -->
  <line x1="12" y1="4" x2="12" y2="7" stroke="#FF9800" stroke-width="2" stroke-linecap="round"/>
  <circle cx="12" cy="3" r="2" fill="#FF9800"/>
</svg>
```

#### 1.3.2 启用条件

按钮在以下任一条件满足时**启用**（橙色，可点击）：

| # | 条件 | 说明 |
|---|------|------|
| 1 | **问题已确定** | AI已识别问题类型（`problem_confirmed = true`），用户可能需要人工进一步处理 |
| 2 | **会话交互达3次** | 用户与AI的对话轮次 ≥ 3（不含：直接呼叫人工的消息、问候语如"你好""hi"） |

**禁用状态**（灰色，不可点击）：
- 以上条件均未满足时
- 显示 `title="请先描述您的问题，AI将为您匹配最佳坐席"`

**计数规则**：

```
计入交互轮次：
  ✅ 用户发送有效问题消息
  ✅ AI回复消息
  ❌ 用户发送"你好"、"hi"等纯问候语（不计入）
  ❌ 用户直接点击呼叫（不计入，但触发呼叫流程）
  ❌ 系统消息（不计入）
```

#### 1.3.3 点击流程

```
用户点击"人工"按钮
    │
    ▼
  CallAgentModal 弹窗
    │
    ├── 显示传菜铃动画（替换当前的摇铃铛动画）
    ├── 话术："叮！IT坐席马上来～"
    └── 同时发送 shake/fetch 请求到后端
          │
          ▼
      成功 → 显示"已通知坐席，请稍候"
      失败 → 提示"当前坐席繁忙，请稍后再试"
```

#### 1.3.4 坐席端联动

坐席端（Agent工作台）对应位置的按钮/状态指示也使用**传菜铃**图标：

| 位置 | 当前状态 | 目标状态 |
|------|---------|---------|
| 会话列表 - 标签 | "举手"黄色标签 | "人工"橙色标签 + 传菜铃图标 |
| 会话列表 - 排序权重 | `hand_raise` 标记 | `human_requested` 标记 |
| 聊天区 - 系统消息 | "员工摇人请求人工" | "员工请求人工服务🔔" |

#### 1.3.5 坐席离线状态

当无坐席在线时：
- H5标题栏坐席状态显示：**灰色圆点 + "坐席离线"**
- "人工"按钮：保持可见但**禁用**（灰色），`title="当前无在线坐席，请稍后再试或继续与AI对话"`
- 不隐藏按钮（保持用户认知一致性）

---

## 二、术语/图标统一规范

### 2.1 核心概念定义

| 术语 | 定义 | 触发者 | 接收者 | 图标 | 使用范围 |
|------|------|--------|--------|------|----------|
| **人工** | 用户呼叫坐席，请求人工服务 | H5用户端员工 | IT坐席 | 传菜铃（桌面拍铃）SVG | 用户端H5 + 坐席端（状态指示） |
| **摇人** | 主责坐席呼叫其他坐席进入群聊协助 | IT坐席 | 其他IT坐席 | 👋 招手 | 仅坐席端 |
| ~~**举手**~~ | <span style="color:red">**已取消**</span> | — | — | — | 全局移除 |

### 2.2 "人工"详细规范

**含义**：用户（员工）需要人工坐席介入处理问题。

**触发方式**：
1. 用户点击H5标题栏"🔔 人工"按钮
2. 用户输入关键词（"转人工"、"人工"、"人工服务"等）—— 关键词触发仍有效

**后端标记字段**（重构方向）：

```python
# backend/app/schemas/conversation.py

class ConversationTags(BaseModel):
    # 移除: hand_raise: bool = False
    # 新增:
    human_requested: bool = Field(default=False, description="用户请求人工服务")
    human_requested_at: datetime | None = Field(default=None, description="用户请求人工的时间")
```

**前端显示**：
- H5端：标题栏按钮"🔔 人工"
- 坐席端会话列表：橙色"人工"标签 + 传菜铃图标

### 2.3 "摇人"详细规范

**含义**：主责坐席在处理会话时，邀请其他坐席进入当前会话协助。

**触发方式**：
- 坐席点击聊天区"🤝 摇人"按钮 → 弹出 InviteDialog.vue

**与"人工"的区别**：

| 维度 | 人工 | 摇人 |
|------|------|------|
| 触发者 | 用户（员工） | 主责坐席 |
| 接收者 | IT坐席 | 其他IT坐席 |
| 目的 | 请求人工服务 | 协作处理复杂问题 |
| 图标 | 传菜铃 SVG | 👋 招手 |
| 对应代码 | `human_requested` | `collaborating_agent_ids` |

**InviteDialog.vue 改动**：
- 标题改为：`🤝 摇人 — 邀请坐席协作`（当前为"🤝 摇人 — 邀请坐席协作"，已正确）
- 确保图标使用 👋 而非传菜铃

### 2.4 全局取消"举手"概念

**涉及范围**：

| 类别 | 涉及文件/位置 | 改动说明 |
|------|-------------|---------|
| **代码变量名** | `hand_raise`, `hand_raise_keywords`, `detect_hand_raise()` | 标记为 `@deprecated`，下个迭代重构为 `human_requested`, `human_request_keywords`, `detect_human_request()` |
| **数据库字段** | `tags` JSON中的 `hand_raise: true` | 保持兼容，新增 `human_requested: true`，逐步迁移 |
| **API响应** | `ConversationResponse.tags.hand_raise` | 新增 `ConversationResponse.tags.human_requested`，旧字段保留一个迭代周期 |
| **前端状态** | `store.tags.hand_raise` | 改为 `store.tags.human_requested` |
| **UI文案** | "举手标签"、"举手标记" | 改为"人工标签"、"人工请求标记" |
| **文档** | PRD.md、ARCHITECTURE.md 等 | 全局替换"举手"为"人工（用户呼叫）" |
| **测试用例** | `test_scoring_service.py`、`test_h5_shake.py` | 变量名重构时同步更新 |

**兼容策略**（避免一次性破坏）：

```
阶段1（本迭代）：
  - 新增 human_requested 字段
  - 旧 hand_raise 字段保留，标记为 @deprecated
  - UI全局切换为"人工"
  - 后端同时写入 hand_raise 和 human_requested（兼容旧逻辑）

阶段2（下个迭代）：
  - 重构变量名：hand_raise → human_requested
  - 移除 hand_raise 写入逻辑
  - 数据库迁移：将 tags 中的 hand_raise 合并到 human_requested
```

### 2.5 图标汇总表

| 场景 | 图标 | 说明 |
|------|------|------|
| H5用户端 - "人工"按钮 | 传菜铃 SVG | 橙色，启用状态 |
| H5用户端 - "人工"按钮（禁用） | 传菜铃 SVG | 灰色，`opacity: 0.5` |
| 坐席端 - 会话列表"人工"标签 | 传菜铃 SVG + "人工"文字 | 橙色标签 |
| 坐席端 - "摇人"按钮 | 👋 | 招手emoji |
| 坐席端 - InviteDialog 标题 | 🤝 + 👋 | "摇人 — 邀请坐席协作" |
| 坐席离线状态 | ⚪ 灰色圆点 | CSS: `background-color: #9E9E9E` |

---

## 三、改动清单

### 3.1 前端改动

| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `frontend-h5/src/components/chat/ChatPanel.vue` | 修改 | 按钮文案"呼叫"→"人工"，图标🔔→传菜铃SVG，启用条件逻辑 |
| `frontend-h5/src/components/chat/CallAgentModal.vue` | 修改 | 弹窗标题"摇铃呼叫人工坐席"→"呼叫人工坐席"，动画场景图标更新 |
| `frontend-h5/src/stores/conversation.ts` | 修改 | `canCallAgent` 逻辑（问题确定或交互≥3轮） |
| `frontend-agent/src/components/conversation/ConversationItem.vue` | 修改 | "举手"标签→"人工"标签+传菜铃图标 |
| `frontend-agent/src/stores/conversation.ts` | 修改 | 排序逻辑中 `hand_raise` → `human_requested` |
| `frontend-agent/src/components/conversation/InviteDialog.vue` | 修改 | 确认图标/文案为"摇人"=👋 |

### 3.2 后端改动

| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `backend/app/schemas/conversation.py` | 修改 | 新增 `human_requested` 字段，标记 `hand_raise` 为 `@deprecated` |
| `backend/app/services/scoring_service.py` | 修改 | `detect_hand_raise()` → `detect_human_request()`，关键词配置 key 不变（兼容） |
| `backend/app/services/message_router.py` | 修改 | 标记检测：设置 `human_requested = True` |
| `backend/app/api/h5.py` | 修改 | shake 端点响应文案更新 |
| `backend/app/models/conversation.py` | 修改 | `tags` 字段兼容 `human_requested` |

### 3.3 文档改动

| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `docs/PRD.md` | 修改 | 全局"举手"→"人工（用户呼叫）"，更新术语表 |
| `docs/ARCHITECTURE.md` | 修改 | 更新排序规则描述，替换"举手"相关描述 |
| `docs/01-项目总览与部署手册.md` | 修改 | 替换"举手"标签说明 |
| `docs/团队沟通文档-架构消息知识库.md` | 修改 | 更新标记体系说明 |

---

## 四、验收标准

### 4.1 "人工"按钮

| # | 验收标准 | 验证方式 |
|---|---------|---------|
| 1 | H5标题栏显示"🔔 人工"按钮（传菜铃图标） | 视觉验收 |
| 2 | 问题未确定且交互<3轮时，按钮为灰色禁用状态 | 功能测试 |
| 3 | 问题确定或交互≥3轮时，按钮为橙色启用状态 | 功能测试 |
| 4 | 点击按钮弹出CallAgentModal，动画使用传菜铃场景 | 视觉+功能测试 |
| 5 | 坐席离线时，按钮禁用，title提示"当前无在线坐席" | 功能测试 |
| 6 | 坐席端会话列表显示"人工"标签+传菜铃图标 | 视觉验收 |

### 4.2 术语统一

| # | 验收标准 | 验证方式 |
|---|---------|---------|
| 1 | 所有UI、代码注释中无"举手"一词（待重构变量除外，需标记@deprecated） | 全文搜索 |
| 2 | "人工"概念在用户端和坐席端统一 | 功能测试 |
| 3 | "摇人"概念仅在坐席端出现，图标为👋 | 视觉验收 |
| 4 | 后端同时写入 `hand_raise`（兼容）和 `human_requested`（新） | 单元测试 |

---

## 五、开发优先级

| 优先级 | 内容 | 预计工时 |
|--------|------|---------|
| P0 | H5"人工"按钮UI改动（文案+图标+启用条件） | 0.5天 |
| P0 | 坐席端"人工"标签显示 | 0.5天 |
| P1 | CallAgentModal动画更新（传菜铃） | 0.5天 |
| P1 | 后端新增 `human_requested` 字段 | 0.5天 |
| P2 | 文档全局更新（PRD/ARCHITECTURE等） | 0.5天 |
| P3 | 代码变量名重构（`hand_raise` → `human_requested`） | 1天（下个迭代） |

---

## 附录 A：传菜铃图标注说明

由于Unicode中无直接对应的传菜铃字符，使用自定义SVG图标。

**推荐SVG（简洁版）**：

```svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
  <!-- 铃身 -->
  <circle cx="12" cy="14" r="7" fill="#FFD54F" stroke="#FFA000" stroke-width="1.5"/>
  <!-- 金属高光 -->
  <ellipse cx="10" cy="12" rx="2.5" ry="1.5" fill="#FFF9C4" opacity="0.6"/>
  <!-- 铃底 -->
  <ellipse cx="12" cy="20" rx="5" ry="1.5" fill="#FFA000"/>
  <!-- 拍击指示（小圆点代表"拍"的动作） -->
  <circle cx="12" cy="5" r="1.8" fill="#FF9800"/>
  <line x1="12" y1="5" x2="12" y2="8" stroke="#FF9800" stroke-width="1.5"/>
</svg>
```

**启用/禁用状态CSS**：

```css
.bell-counter-icon { filter: none; }
.bell-counter-icon--disabled { filter: grayscale(100%); opacity: 0.5; }
```
