# 研发工单 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)。