# 研发工单 v1.3.0 - 02 语音控制与 AI 辅助
---
## 一、目标与范围
### 1.1 目标
实现语音控制 Todo 待办项能力与 AI 辅助任务拆分功能,通过平台原生 STT/TTS 实现语音输入输出,通过 LLM 意图解析处理自然语言指令,离线降级到规则匹配,通过 LLM 提供 AI 拆分建议。
### 1.2 范围
**包含**:
- 语音输入(STT):平台原生语音识别 → 文字
- 语音播报(TTS):执行结果语音反馈
- 语音指令解析与执行(CRUD + 子任务操作)
- 指令歧义处理(目标不唯一时返回候选列表)
- AI 辅助任务拆分(LLM 生成子任务建议,用户确认后批量创建)
**不包含**:
- 语音通话功能(场景不明确,本期不做)
- 前端语音 UI 设计(仅提供能力层与 API)
- 第三方语音服务集成(使用平台原生能力)
---
## 二、前置条件
| 条件 | 说明 |
|---|---|
| Hua.Todo v1.2.0 | 已完成,提供基础业务能力 |
| 各平台原生 STT/TTS | Windows(`Windows.Media.SpeechRecognition`/`SpeechSynthesis`)、Android(`SpeechRecognizer`/`TextToSpeech`)、iOS/macOS(`SFSpeechRecognizer`/`AVSpeechSynthesizer`)、Linux(WebKitGTK Web Speech API / `vosk` 离线模型) |
| LLM API | 在线模式意图解析 + AI 拆分共用;API Key 在 Host 端管理 |
| 工单 01(可选) | MCP 服务就绪后,语音指令与 AI 拆分也可通过 MCP 暴露 |
---
## 三、架构设计
### 3.1 整体链路
```
平台 STT(语音→文字)
↓
IVoiceInputService(Core 接口,各平台实现)
↓ 回调文字到前端
WebView → POST /api/voice/command { text: "帮我把那个开会的删了吧" }
↓
Host API → IVoiceIntentParser(双策略)
├─ 在线:LlmIntentParser(调 LLM,输出结构化意图+参数)
└─ 离线:RuleIntentParser(关键词规则匹配,覆盖高频指令)
↓
意图 + 参数 → 判断歧义
├─ 无歧义 → 调用 TaskService 执行 → TTS 播报结果
├─ 有歧义 → 返回候选列表 → 前端展示 → 用户确认 → 再执行
└─ UNKNOWN → TTS 播报"没听懂,请再说一次"
```
### 3.2 STT/TTS 分层策略
遵循与全局快捷键相同的平台分离模式(接口+平台目录):
| 层 | 职责 | 位置 |
|---|---|---|
| `IVoiceInputService` / `IVoiceOutputService` | 接口定义 | `src/Hua.Todo.Core/Services/` |
| MAUI 平台实现 | 各平台原生 STT/TTS | `src/Hua.Todo.Maui/Platforms/{Windows,Android,macOS,iOS}/VoiceInputService.cs` |
| Avalonia 平台实现 | Linux 桌面 STT/TTS | `src/Hua.Todo.Avalonia/Services/Platforms/VoiceInputService.cs` |
| Web 降级方案 | 浏览器 Web Speech API | 前端 `voiceInput.ts` |
### 3.3 意图解析双策略(A+C 方案)
采用 **LLM 主解析 + 规则降级** 的混合方案:
#### 策略 A:在线 — LLM 意图解析(LlmIntentParser)
在线模式下,将用户原始文字发送给 LLM,通过 system prompt 约束输出为结构化 JSON:
```
System Prompt(精简版):
你是 Hua.Todo 的语音指令解析器。根据用户输入,输出以下 JSON 格式:
{
"intent": "CREATE|UPDATE|DELETE|COMPLETE|UNCOMPLETE|QUERY|ADD_SUBTASK|AI_BREAKDOWN|UNKNOWN",
"params": { ... },
"confidence": 0.0-1.0
}
意图说明:
- CREATE: 创建任务,params: { title, priority? }
- UPDATE: 修改任务,params: { targetTitle, newTitle }
- DELETE: 删除任务,params: { targetTitle }
- COMPLETE: 完成任务,params: { targetTitle }
- UNCOMPLETE: 取消完成,params: { targetTitle }
- QUERY: 查询任务,params: { filter? }
- ADD_SUBTASK: 添加子任务,params: { parentTitle, subTitle }
- AI_BREAKDOWN: AI辅助拆分,params: { targetTitle }
- UNKNOWN: 无法识别
只输出 JSON,不要解释。
```
**示例调用**:
```
输入:"帮我把那个开会的任务删了吧"
LLM 输出:
{
"intent": "DELETE",
"params": { "targetTitle": "开会" },
"confidence": 0.95
}
```
```
输入:"这个项目太大了,帮我拆一下"
LLM 输出:
{
"intent": "AI_BREAKDOWN",
"params": { "targetTitle": "这个项目" },
"confidence": 0.88
}
```
**优势**:
- 无需维护同义词表,LLM 天然理解各种自然语言表述
- 扩展新意图只需修改 prompt,不改代码
- 歧义场景 LLM 也能判断(如 confidence < 阈值时触发确认)
#### 策略 C:离线 — 规则匹配降级(RuleIntentParser)
离线/嵌入式模式下,降级到关键词规则匹配,只覆盖高频指令:
| 意图 | 匹配规则 | 示例 |
|---|---|---|
| `CREATE` | 正则:`/^(创建|新增|添加|加)\s*(任务|待办)?\s*(.+)/` | "创建任务 开会" |
| `DELETE` | 正则:`/^(删除|删掉|移除)\s*(任务|待办)?\s*(.+)/` | "删除任务 开会" |
| `COMPLETE` | 正则:`/^(完成|做完)\s*(任务|待办)?\s*(.+)/` | "完成任务 开会" |
| `UNCOMPLETE` | 正则:`/^(取消完成|重新打开|恢复)\s*(.+)/` | "取消完成 开会" |
| `UPDATE` | 正则:`/^(修改|更新|编辑)\s*(.+?)\s*(改为|改成|改成)\s*(.+)/` | "修改任务 开会 改为 团队会议" |
| `QUERY` | 正则:`/^(查询|查看|列出|显示)\s*(.+)/` | "查询未完成任务" |
| `ADD_SUBTASK` | 正则:`/^(给|为)\s*(.+?)\s*(添加|加)\s*(子任务|子项)?\s*(.+)/` | "给任务开会添加子任务 准备PPT" |
| `AI_BREAKDOWN` | 正则:`/^(帮我拆分|AI拆分|智能拆分)\s*(.+)/` | "帮我拆分 开会" |
**降级策略**:
- 离线模式下 `AI_BREAKDOWN` 意图不可用(需要 LLM),TTS 播报"离线模式不支持 AI 拆分"
- 规则匹配失败时返回 `UNKNOWN`
#### 策略切换逻辑
```csharp
/// 语音意图解析器接口
public interface IVoiceIntentParser
{
/// 解析语音指令文本,返回意图与参数
Task ParseAsync(string text, CancellationToken ct = default);
}
/// 双策略解析器:在线走 LLM,离线走规则
public class HybridVoiceIntentParser : IVoiceIntentParser
{
private readonly LlmIntentParser _llmParser;
private readonly RuleIntentParser _ruleParser;
private readonly IConnectivityService _connectivity;
public async Task ParseAsync(string text, CancellationToken ct)
{
if (_connectivity.IsOnline)
{
try
{
var result = await _llmParser.ParseAsync(text, ct);
if (result.Intent != VoiceIntent.UNKNOWN)
return result;
}
catch (Exception)
{
// LLM 调用失败,降级到规则
}
}
return _ruleParser.Parse(text);
}
}
```
### 3.4 语音指令处理完整流程
```
原始文字 → HybridVoiceIntentParser
├─ 在线:LlmIntentParser(LLM 结构化输出)
└─ 离线/降级:RuleIntentParser(关键词正则匹配)
↓
意图 + 参数 + confidence
├─ confidence >= 阈值 且 目标唯一 → 执行 → TTS 播报
├─ 目标匹配多个 → 返回候选列表 → 用户确认 → 执行
├─ confidence < 阈值 → TTS "您是要...吗?" → 用户确认
└─ UNKNOWN → TTS "没听懂,请再说一次"
```
---
## 四、需求规格
### 4.1 语音输入(STT)
各平台通过原生 API 将语音转为文字,通过 `IVoiceInputService` 接口统一暴露:
```csharp
/// 语音输入服务接口,各平台实现原生 STT
public interface IVoiceInputService
{
/// 是否正在监听
bool IsListening { get; }
/// 开始语音识别,识别结果通过回调返回
Task StartListeningAsync(Action onResult, Action? onError = null);
/// 停止语音识别
Task StopListeningAsync();
}
```
### 4.2 语音播报(TTS)
```csharp
/// 语音输出服务接口,各平台实现原生 TTS
public interface IVoiceOutputService
{
/// 是否正在播报
bool IsSpeaking { get; }
/// 播报文本
Task SpeakAsync(string text);
/// 停止播报
Task StopSpeakingAsync();
}
```
### 4.3 语音指令解析
#### 意图定义
| 意图 (Intent) | 参数 | 说明 |
|---|---|---|
| `CREATE` | `title`(必填)、`priority`(可选:High/Medium/Low) | 创建任务 |
| `UPDATE` | `targetTitle`(必填)、`newTitle`(必填) | 修改任务标题 |
| `DELETE` | `targetTitle`(必填) | 删除任务 |
| `COMPLETE` | `targetTitle`(必填) | 标记完成 |
| `UNCOMPLETE` | `targetTitle`(必填) | 取消完成 |
| `QUERY` | `filter`(可选:如"未完成"、"高优先级") | 查询任务 |
| `ADD_SUBTASK` | `parentTitle`(必填)、`subTitle`(必填) | 添加子任务 |
| `AI_BREAKDOWN` | `targetTitle`(必填) | AI 辅助拆分(仅在线) |
| `UNKNOWN` | 无 | 无法识别 |
#### 歧义处理规则
当指令中 `targetTitle` 匹配到多个 Todo 待办项时:
1. **不直接执行**,返回歧义结果(包含匹配的候选列表)
2. 前端展示候选项,用户点选或语音确认后再执行
3. 匹配规则:标题完全匹配优先,包含匹配次之
#### 置信度阈值
- LLM 返回 `confidence >= 0.8`:直接执行
- `0.5 <= confidence < 0.8`:确认后执行(TTS "您是要...吗?")
- `confidence < 0.5`:按 UNKNOWN 处理
### 4.4 语音指令 API
```
POST /api/voice/command
请求体:
{
"text": "帮我把那个开会的删了吧" // STT 识别后的原始文字
}
响应体:
{
"intent": "DELETE", // 解析出的意图
"params": { // 提取的参数
"targetTitle": "开会"
},
"confidence": 0.95, // 置信度
"result": { // 执行结果
"success": true,
"message": "已删除任务:开会",
"data": null
}
}
```
歧义响应:
```json
{
"intent": "COMPLETE",
"params": { "targetTitle": "开会" },
"confidence": 0.92,
"result": {
"success": false,
"message": "找到多个匹配的任务,请确认",
"ambiguity": true,
"candidates": [
{ "id": 1, "title": "开会" },
{ "id": 5, "title": "开会讨论方案" }
]
}
}
```
用户确认后二次请求:
```
POST /api/voice/command/confirm
请求体:
{
"intent": "COMPLETE",
"targetId": 5 // 用户选择的候选 ID
}
```
### 4.5 AI 辅助任务拆分
```
POST /api/voice/ai-breakdown
请求体:
{
"taskId": 5 // 要拆分的目标任务 ID
}
响应体:
{
"suggestions": [ // LLM 生成的子任务建议
{ "title": "准备会议议程", "priority": "Medium" },
{ "title": "发送会议邀请", "priority": "High" },
{ "title": "预定会议室", "priority": "Low" }
]
}
```
用户确认后批量创建:
```
POST /api/voice/ai-breakdown/confirm
请求体:
{
"parentTaskId": 5,
"subTasks": [ // 用户勾选后的子任务列表
{ "title": "准备会议议程", "priority": "Medium" },
{ "title": "发送会议邀请", "priority": "High" }
]
}
```
**关键约束**:
- LLM 调用在 Host 端执行,API Key 不暴露到客户端
- 意图解析与 AI 拆分共用同一 LLM 基础设施
- 建议≠直接执行,必须用户确认后才创建子任务
- 离线模式下 AI 拆分不可用,TTS 播报"离线模式不支持 AI 拆分"
- MCP 服务就绪后,`aiBreakdown` 可同步暴露为 MCP 工具
---
## 五、验收标准
| 验收项 | 验证方法 | 预期结果 |
|---|---|---|
| STT 语音输入 | 说话后检查回调文字 | 文字识别正确 |
| TTS 语音播报 | 执行指令后检查播报 | 播报内容与执行结果一致 |
| 创建任务(在线) | "帮我把开会的任务加上" | LLM 解析为 CREATE,成功创建 |
| 创建任务(离线降级) | "创建任务 测试" | 规则匹配为 CREATE,成功创建 |
| 创建带优先级任务 | "建一个高优先级任务 紧急" | 成功创建高优先级任务 |
| 完成任务指令 | "把那个开会的事标完成" | LLM 解析为 COMPLETE,执行成功 |
| 取消完成指令 | "取消完成 测试" | 任务状态恢复为未完成 |
| 删除任务指令 | "帮我把测试删了" | LLM 解析为 DELETE,删除成功 |
| 更新任务指令 | "把测试改成验收" | 任务标题更新 |
| 查询任务指令 | "有哪些没做完的" | LLM 解析为 QUERY,返回列表 |
| 添加子任务指令 | "给开会加个子任务 准备PPT" | 子任务创建成功 |
| 歧义处理 | 多个任务含"开会"时说"完成开会" | 返回候选列表,不直接执行 |
| AI 拆分建议 | "帮我拆分 开会" | 返回子任务建议列表 |
| AI 拆分确认 | 用户勾选后确认 | 仅创建勾选的子任务 |
| 离线 AI 拆分 | 离线时说"帮我拆分" | 播报"离线模式不支持 AI 拆分" |
| 无法识别指令 | 说出无关内容 | 播报"没听懂,请再说一次" |
| LLM 降级 | 断网时使用语音 | 自动降级到规则匹配,基本 CRUD 可用 |
---
## 六、Touch List
| 文件路径 | 修改类型 | 说明 |
|---|---|---|
| `src/Hua.Todo.Core/Services/IVoiceInputService.cs` | 新增 | STT 接口定义 |
| `src/Hua.Todo.Core/Services/IVoiceOutputService.cs` | 新增 | TTS 接口定义 |
| `src/Hua.Todo.Core/Services/IVoiceIntentParser.cs` | 新增 | 意图解析器接口 |
| `src/Hua.Todo.Application/Voice/LlmIntentParser.cs` | 新增 | LLM 意图解析器(在线策略) |
| `src/Hua.Todo.Application/Voice/RuleIntentParser.cs` | 新增 | 规则匹配解析器(离线降级策略) |
| `src/Hua.Todo.Application/Voice/HybridVoiceIntentParser.cs` | 新增 | 双策略解析器(在线走 LLM,离线走规则) |
| `src/Hua.Todo.Application/Voice/VoiceCommandIntent.cs` | 新增 | 意图枚举定义 |
| `src/Hua.Todo.Application/Voice/VoiceCommandExecutor.cs` | 新增 | 意图执行器(分发到 TaskService) |
| `src/Hua.Todo.Application/Voice/VoiceController.cs` | 新增 | 语音指令 API 端点(`/api/voice/command`、`/api/voice/ai-breakdown`) |
| `src/Hua.Todo.Application/Voice/AiBreakdownService.cs` | 新增 | AI 辅助拆分服务(调用 LLM 生成建议) |
| `src/Hua.Todo.Application/Voice/LlmClientService.cs` | 新增 | LLM 调用封装(意图解析 + AI 拆分共用) |
| `src/Hua.Todo.Application/Voice/Models/` | 新增 | 语音相关 DTO(VoiceIntentResult、VoiceCommandRequest 等) |
| `src/Hua.Todo.Application/Voice/VoiceServiceCollectionExtensions.cs` | 新增 | 语音服务 DI 注册 |
| `src/Hua.Todo.Maui/Platforms/Windows/VoiceInputService.cs` | 新增 | Windows STT 实现 |
| `src/Hua.Todo.Maui/Platforms/Windows/VoiceOutputService.cs` | 新增 | Windows TTS 实现 |
| `src/Hua.Todo.Maui/Platforms/Android/VoiceInputService.cs` | 新增 | Android STT 实现 |
| `src/Hua.Todo.Maui/Platforms/Android/VoiceOutputService.cs` | 新增 | Android TTS 实现 |
| `src/Hua.Todo.Maui/Platforms/MacCatalyst/VoiceInputService.cs` | 新增 | macOS STT 实现 |
| `src/Hua.Todo.Maui/Platforms/MacCatalyst/VoiceOutputService.cs` | 新增 | macOS TTS 实现 |
| `src/Hua.Todo.Maui/Platforms/iOS/VoiceInputService.cs` | 新增 | iOS STT 实现 |
| `src/Hua.Todo.Maui/Platforms/iOS/VoiceOutputService.cs` | 新增 | iOS TTS 实现 |
| `src/Hua.Todo.Avalonia/Services/Platforms/VoiceInputService.cs` | 新增 | Linux STT 实现 |
| `src/Hua.Todo.Avalonia/Services/Platforms/VoiceOutputService.cs` | 新增 | Linux TTS 实现 |
| `src/Hua.Todo.Web/src/api/voice.ts` | 新增 | 前端语音 API 模块 |
| `src/Hua.Todo.Web/src/composables/useVoiceInput.ts` | 新增 | 前端语音输入组合式函数(含 Web Speech API 降级) |
---
**工单编号**:02
**标题**:语音控制与 AI 辅助
**版本**:v1.3.0
**修订日期**:2026-06-16
---
## 七、与其他工单的语音入口衔接
### 7.1 背景
Hua.Todo 目前有两个功能入口:**UI 入口**(WebView / 前端界面)和**语音控制入口**(本工单产出)。后续每项新增功能在需求阶段都必须确认这两个入口的覆盖情况。
### 7.2 工单 03(会议任务拆分)的语音入口
工单 03 的会议功能需要通过语音控制入口提供以下指令:
| 语音指令 | 意图 | 参数 | 说明 |
|---|---|---|---|
| "新建会议 项目评审" | `CREATE_MEETING` | `title`(必填)、`priority`(可选) | 创建会议类型任务(TaskType.Meeting) |
| "记录会议内容" | `RECORD_MEETING` | `targetTitle`(必填) | 开始录制当前会议 |
| "停止录制" | `STOP_RECORDING` | 无 | 停止录制,触发转写 |
| "帮我拆分这个会议" | `AI_BREAKDOWN` | `targetTitle`(必填,目标为会议类型) | 对会议内容进行 AI 拆分(复用 AI_BREAKDOWN 意图,参数携带 TaskType=Meeting) |
| "查看会议待办项" | `QUERY` | `filter="会议"` | 查询所有会议类型任务 |
**关键点**:
- `AI_BREAKDOWN` 意图需扩展以区分普通任务拆分和会议拆分(通过 `TaskType` 字段),会议拆分的 prompt 侧重"提取行动项"
- `CREATE_MEETING` 是 `CREATE` 意图的子类型,创建时字段 `taskType = Meeting`,需要 LLM intent parser 的 prompt 增加说明
- 录音控制(`RECORD_MEETING` / `STOP_RECORDING`)是新意图,对应前端 MediaRecorder 的启动/停止
### 7.3 语音入口覆盖检查表
后续每个新增功能/工单,智能体必须在需求讨论阶段输出以下检查表:
| 检查项 | 状态 | 备注 |
|---|---|---|
| UI 入口是否已规划 | ✅ / ❌ | 描述 UI 入口 |
| 语音控制入口是否已规划 | ✅ / ❌ | 描述对应的语音指令与意图 |
| 当前不可覆盖的入口 | 说明原因 | 如:语音控制暂不支持 XX 操作(选项过多/需可视化交互) |
此规则已固化为项目规范,详见 [.trae/rules/项目/05-多入口功能同步规范.md](../../../.trae/rules/项目/05-多入口功能同步规范.md)。