# 企微声音克隆与语音发送

## 范围

客服为当前企微账号初始化一次本人录音，之后在客服工作台输入文本、自动选择受控语气，并在人工确认后生成和发送原生企微语音。第一版只允许人工触发，不接入全自动回复。试听功能禁用，避免为同一条消息额外调用一次付费合成。

## 链路

```text
本人参考录音
  -> 16kHz 单声道 WAV 档案
  -> Fmode /api/voice/indextts2
  -> 24kHz 单声道 SILK
  -> Fmode /api/qiwei/doFileApi 直传
  -> /msg/sendVoice
```

参考录音限制为 5～30 秒、最大 20MB。档案存放在 `outputs/voice/profiles/<account-key>/`，不同企微账号相互隔离。

## Payload 策略

默认参数：

```json
{
  "input": "客服回复文本",
  "emo_control_method": 0,
  "use_random": false
}
```

识别到特殊客服场景时使用文本情绪控制：

```json
{
  "input": "非常抱歉给您带来了不好的体验。",
  "emo_control_method": 3,
  "emo_alpha": 0.5,
  "emo_text": "真诚、耐心、克制，带有适度歉意，语速稍慢，不夸张",
  "use_random": false
}
```

| 语气 | 场景 | `emo_alpha` |
| --- | --- | ---: |
| 自然 | 普通说明或无法判断 | 不传 |
| 友好 | 欢迎、感谢、成功、好消息 | 0.40 |
| 真诚致歉 | 投诉、道歉、服务失误 | 0.50 |
| 温和关怀 | 遗憾、安慰、身体不适 | 0.45 |
| 明确提醒 | 截止、到期、时间安排 | 0.40 |

不启用情绪音频和八维情绪向量，不开放愤怒、厌恶、恐惧或强烈悲伤。`use_random` 固定为 `false`。

## Dashboard API

| 路由 | 用途 |
| --- | --- |
| `GET /api/agent/voice/status` | 查询服务和声音档案状态 |
| `POST /api/agent/voice/profile` | 上传 Base64 音频并初始化声音 |
| `DELETE /api/agent/voice/profile` | 删除当前账号声音档案 |
| `POST /api/agent/conversations/:id/voice-send` | 合成并真实发送企微语音；可传当前 `draftId` |
| `GET /api/agent/messages/:id/voice-audio` | 回放指定已发送语音，支持 HTTP Range |

真实发送继续执行私聊、白名单和人工确认校验，并写入工作台消息与审计表。

### 草稿与消息状态

Dashboard 从待审核草稿发送语音时，在 `voice-send` 请求中携带该草稿的 `draftId`。服务端在合成和企微发送成功后执行以下状态更新：

1. 插入 `msgType=16` 的 outbound 语音消息并保存 WAV 路径、时长和发送结果；
2. 将对应草稿内容更新为实际发送文本，状态更新为 `sent`；
3. 写入 `reviewed_at`、`reviewer=human:voice` 和 `sent_message_id`；
4. 前端刷新后不再显示「批准并发送」，恢复「让 Agent 处理」。

显式传入的 `draftId` 不存在、已处理或属于其他会话时，在合成和外发前拒绝请求。没有待审核草稿时仍允许作为人工语音发送。

### 消息展示与回放

- 已发送语音显示为带时长和播放状态的语音气泡，点击气泡播放或暂停；同一时间只播放一条语音。
- 气泡左侧提供「转文字」按钮，默认收起，点击后展开合成时保存的原文；该操作不调用 ASR。
- 播放接口只返回数据库已关联且位于 `outputs/voice/<date>/<time>-clone-*/speech.wav` 的文件，支持 HTTP Range、`audio/wav` 和私有无缓存响应。
- Dashboard 轮询检测到语音正在播放时不重绘客服工作区，避免播放被轮询中断；播放结束后恢复正常更新。

## 配置

```dotenv
QIWEI_VOICE_ENDPOINT=https://server.fmode.cn/api/voice/indextts2
QIWEI_TTS_TIMEOUT_MS=180000
```

语音合成使用 Fmode `/api/voice/indextts2`，自动复用技能包的 Fmode Token。语音媒体只使用 Fmode 网关的 `/api/qiwei/doFileApi` multipart 代理；该路由已完成真实 SILK 上传验证，可以直接返回企微发送所需的 `fileId`、`fileAesKey` 和 `fileSize`。路由不可用或上传失败时直接报错，不使用公网 URL 回源。系统会在合成前检查 Fmode 鉴权和企微账号配置，避免已知无法上传时产生合成费用。

## 安全

- 只录制和克隆当前员工本人声音；
- API Key 只保存在 `.env.local` 或安全环境变量；
- 真实发送前显示联系人、文本与语气并二次确认；
- MCP 发送工具必须显式传入 `confirmed=true`，并标记为破坏性外部操作；
- 固定关闭随机采样；
- 声音档案支持按账号删除；
- 发送成功的 WAV 会保留供工作台按消息回放；失败发送的 WAV 和所有 SILK 会立即删除，运行清单不保存完整话术；
- 记录初始化、发送、失败和删除审计。
