VoiceInput/AGENTS.md
JOJO deae1fbc06 feat: RecordingPanel 宽度自适应 + 打包 DMG
- updateText 移除文本宽度硬编码下限 160,真正根据内容自适应
- showRefining/showThinking 简化为调用 updateText,复用自适应逻辑
- 面板最小宽度由 minPanelWidth(180pt) 兜底
- 打包 VoiceInput-20260425.dmg
2026-04-25 16:34:33 +08:00

146 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# VoiceInput — AI Agent 项目指南
## 项目概述
VoiceInput 是一款 **macOS 菜单栏语音输入应用**。按住 Fn / Globe 键录音,松开后通过剪贴板 + Cmd+V 自动将识别文本注入光标位置。支持 Apple 原生语音识别实时转录、LLMDeepSeek纠错优化、多语言切换以及双击 Control 切换 Input / Assist 模式。
- **最低系统**macOS 14+
- **语言**Swift 5.9
- **构建系统**Swift Package Manager`Package.swift`+ Makefile
## 常用命令
所有命令在项目根目录执行。
| 命令 | 说明 |
|------|------|
| `make build` / `make debug` | Debug 构建并生成 `.app` bundle |
| `make release` | Release 构建(优化) |
| `make run` | Debug 构建 + 启动应用 |
| `make clean` | 清理构建产物 |
| `make install` | Release 构建 + 安装到 `/Applications` |
| `make sign` | Ad-hoc 签名Release 后用) |
| `make dmg` | 签名 + 创建 DMG 分发包 |
构建产物路径:`.build/release/VoiceInput.app`Release`.build/debug/VoiceInput.app`Debug
`.app` bundle 由 `scripts/create_app_bundle.sh` 生成,负责将二进制 + 资源封装为标准 macOS 应用包。
## 项目结构
```
Sources/
├── main.swift # 入口:创建 NSApplication(.accessory) + AppDelegate
├── App/
│ ├── AppDelegate.swift # 主控制器:权限引导、录音/停止、模式切换、LLM 调度
│ └── MenuBarController.swift # 菜单栏图标与菜单语言、LLM 配置、模式切换)
├── Core/
│ ├── KeyMonitor.swift # CGEvent 全局热键Fn 键长按录音、Control 键双击切换模式
│ ├── SpeechRecognizer.swift # SFSpeechRecognizer 封装:流式识别、语言切换
│ ├── AudioLevelMonitor.swift # AVAudioEngine 音频电平RMS 音量驱动波形
│ ├── TextInjector.swift # 剪贴板 + Cmd+V 注入;自动切换 CJK 输入法
│ ├── InputSourceManager.swift # 输入法检测与切换(中文/英文等)
│ └── LLMRefiner.swift # DeepSeek API 调用:语音纠错(refine) / AI 对话(chat)
├── UI/
│ ├── RecordingPanel.swift # 胶囊浮动窗:无边框、底部居中、弹簧动画
│ ├── WaveformView.swift # 5 条球形波动画RMS 驱动实时跳动
│ └── SettingsWindow.swift # LLM 设置面板API Key / Base URL / Model / Test
├── Utils/
│ └── Config.swift # UserDefaults 持久化语言、LLM 配置、触发模式
└── Resources/
├── AppIcon.icns # 应用图标
├── AppIcon-macOS.png # 图标源文件
└── Info.plist # Bundle 配置
```
## 架构模式
### 应用生命周期
`main.swift` 创建 `NSApplication` 并设置为 `.accessory`(无 Dock 图标,仅菜单栏)。`AppDelegate` 通过 `static retainedInstance` 保持强引用(因为 `NSApplication.delegate` 是 weak
### 权限引导流程(顺序执行)
```
辅助功能权限 → 输入监控权限 → 启动 KeyMonitor
运行时按需请求:
麦克风权限 + 语音识别权限
```
`beginPermissionFlow()` 使用 `CGPreflightListenEventAccess()``CGEvent.tapCreate` 做权限预检。未授权时弹出对话框 + 轮询 Timer。
### 录音流程
```
Fn Down长按 > 200ms
→ AppDelegate.startRecording()
→ AudioLevelMonitor.start() // 音频电平
→ SpeechRecognizer.start() // 语音识别
→ RecordingPanel.showAnimated() // 显示胶囊窗
Fn Up
→ AppDelegate.stopRecording()
→ 如果启用 LLM 纠错 → LLMRefiner.refine()
→ TextInjector.inject() // 注入文本
→ RecordingPanel.hideAnimated()
```
### Fn 键交互(长按录音)
`KeyMonitor` 通过 `CGEvent.tapCreate(.cghidEventTap)` 监听 `flagsChanged` 事件。
- Fn 键 mask 为 `0x00800000`**长按 > 200ms**:触发录音(`onFnDown` → `onFnUp`
- Control 键 mask 为 `0x00040000`**双击(两次按下间隔 < 400ms**切换模式`onControlDoubleClick`
- Fn 短按< 200ms无操作留作扩展
### LLM 集成
`LLMRefiner` 通过 HTTP 调用 DeepSeek Chat Completions API两种模式
- **Input 模式**`refine`纠错优化修正同音字/拼写/术语
- **Assist 模式**`chat`AI 对话结果注入光标
配置通过 `Config.shared.inputLLM` / `Config.shared.assistLLM` 独立管理
## 代码约定
| 约定 | 说明 |
|------|------|
| `final class` | 所有业务类默认使用除非设计上需要继承 |
| `weak self` | 闭包/异步回调中必须使用防止循环引用 |
| `@MainActor` | 所有 UI 操作在主线程异步任务用 `Task { @MainActor in }` |
| `// MARK: -` | 按功能分区保持 `AppDelegate` 等大文件可读性 |
| Print 日志 | 使用 `print("[模块名] 内容")` 格式 `[KeyMonitor]`、`[App]` |
| 权限处理 | 所有权限失败必须给用户明确提示NSAlert并引导至系统设置 |
| 异步 | 语音识别和 LLM 调用使用 Swift Concurrency`async/await` |
## 关键依赖
所有依赖通过系统框架链接无第三方包
```
Speech → 语音识别
AVFAudio → 音频采集与电平
Carbon → 全局热键(已逐步迁移至 CoreGraphics
CoreGraphics → CGEvent API热键监听、文本注入
AppKit → UI 组件
Foundation → 基础类型与网络
```
Entitlements 文件 `Sources/Resources/VoiceInput.entitlements` 包含
- `com.apple.security.device.audio-input` 麦克风
- `com.apple.security.cs.disable-library-validation` 允许加载系统插件
## 注意事项
1. **ARC 陷阱**`NSApplication.delegate` weak 引用`main.swift` 中的 `AppDelegate` 会被 ARC 释放解决方案`AppDelegate` 内部使用 `static retainedInstance` 自持 + `main.swift` 使用 `withExtendedLifetime` 双重保险
2. **HID Event Tap 回退**`KeyMonitor` 优先创建 `.cghidEventTap`失败时回退到 `.cgSessionEventTap`前者需要输入监控权限后者权限要求较低但可能丢失部分事件
3. **语音识别间歇性报错**`SFSpeechRecognizer` 在环境安静时可能报 `kAFAssistantErrorDomain 1110`无语音输入`AppDelegate` 中已忽略此错误
4. **输入法切换**注入文本前 `TextInjector` 会通过 `InputSourceManager` 检测并切换到合适的 CJK 输入法确保粘贴中文时不出现乱码
5. **DMG 构建**`make dmg` 依赖 `make sign` `make release`需要 `hdiutil`系统自带)。