Skip to content

Commit d5c456f

Browse files
committed
docs(readme): simplify overview and rename icon source
1 parent e14a464 commit d5c456f

3 files changed

Lines changed: 41 additions & 288 deletions

File tree

README.md

Lines changed: 39 additions & 286 deletions
Original file line numberDiff line numberDiff line change
@@ -1,320 +1,73 @@
11
# Typoless
22

3-
Typoless 是一个面向 macOS 的语音 + AI 输入助手项目。
3+
![Typoless](./typoless.svg)
44

5-
首版产品形态不是系统级输入法,而是 `菜单栏常驻应用`。用户通过全局快捷键触发录音(按一次开始,再按一次结束),录音结束后自动完成:
5+
Typoless 是一个面向 macOS 的菜单栏语音 + AI 输入助手。
66

7-
`录音 -> RNNoise 本地降噪 -> FunASR 离线识别 -> OpenAI 兼容 LLM 润色 -> 写回当前焦点应用`
7+
当前主链路:
88

9-
## 项目目标
9+
`录音 -> RNNoise 本地降噪 -> FunASR / 腾讯云 ASR -> OpenAI 兼容 LLM 润色 -> 写回当前焦点应用`
1010

11-
- 在 macOS 上提供全局可用的中文语音输入能力
12-
- 使用本地 `FunASR` 离线 ASR 完成短语音识别,无需配置云端 ASR 服务
13-
- 使用 RNNoise 本地降噪和个人词典提升中文短语音识别质量
14-
- 支持用户接入自有 `OpenAI 兼容` 大模型服务
15-
- 将口语输入整理为更适合直接发送或写入的文本
16-
- 在常见桌面应用中稳定注入文本
11+
## 当前范围
1712

18-
## 首版范围
19-
20-
### 已实现
21-
22-
- 菜单栏常驻应用(MenuBarExtra)
23-
- 设置页(LLM / 通用 / 权限 / 诊断)
24-
- 全局快捷键(Carbon Event API)
25-
- 按一次开始录音,再按一次结束录音
26-
- FunASR 本地离线 ASR(默认链路,通过内置 Python sidecar 运行 paraformer-zh + fsmn-vad)
27-
- RNNoise 本地降噪(录音后、ASR 前自动执行)
28-
- OpenAI 兼容 LLM 润色(增强版固定 Prompt,纠错 + 同音词 + 去赘词 + 轻度书面化 + 补标点 + 专有名词保护)
29-
- 个人词典(ASR hotwords + LLM 术语参考,`~/.typoless/dictionary.json`
30-
- 文本注入(AX API 主策略 + 键盘事件回退)
13+
- 菜单栏常驻应用,不是系统级输入法
14+
- 单一全局快捷键,按一次开始录音,再按一次结束录音
15+
- 本地 `FunASR` 离线识别,模型外置到 `~/.typoless/models/funasr/`
16+
- 腾讯云一句话识别,用户手动切换
17+
- `OpenAI Chat Completions` 兼容接口
18+
- 默认输出 LLM 润色结果
19+
- 文本注入优先走 `Accessibility API`,失败后回退键盘事件
3120
- 麦克风与辅助功能权限引导
32-
- 注入失败文本临时复制入口(菜单栏内显示截断预览,点击复制到剪贴板,仅当前运行期有效)
33-
- 状态机驱动菜单栏反馈(空闲 / 录音中 / 识别中 / 润色中 / 注入中 / 完成 / 失败 / 已取消)
34-
- 用户可理解的错误分类与展示
35-
- 处理中可取消(识别中 / 润色中)
36-
- 诊断页(最近错误摘要 + 会话状态 + 版本信息)
37-
- 诊断日志(per-session 耗时、Debug ASR/LLM 明文对照、Release 脱敏)
38-
- 60 秒录音上限 + 500ms 短录音静默取消
39-
- 本地 FunASR 模型外置到用户目录,设置页通过模型状态区引导下载与展示就绪状态
40-
- 腾讯云一句话识别(用户手动选择,直接调用 Cloud API)
41-
- 构建前与录音前资源校验
42-
43-
### 不包含
44-
45-
- macOS 系统级输入法
46-
- 多种 LLM 协议
47-
- 自定义 Prompt
48-
- 风格模式切换
49-
- temperature / max tokens 等高级参数
50-
- ASR 高级运行参数
51-
- 音频历史保存
52-
- Agent 工作流
21+
- 注入失败文本仅保留在当前运行期,可从菜单栏复制
5322

54-
## 产品行为
23+
## 技术栈
5524

56-
- 应用形态:菜单栏常驻应用
57-
- 交互方式:单一全局快捷键,按一次开始录音,再按一次结束录音
58-
- 单次录音上限:`60 秒`
59-
- 低于 `500ms` 的录音视为误触,静默取消,不进入识别和 LLM
60-
- ASR 平台:用户手动选择 `本地 FunASR``腾讯云一句话识别`
61-
- 本地模型:存储于 `~/.typoless/models/funasr/`,设置页仅展示模型状态,不暴露路径或版本
62-
- 音频预处理:默认 RNNoise 本地降噪
63-
- 默认输出:`LLM 润色版`
64-
- LLM 启用条件:`Base URL``API Key``Model` 三项完整
65-
- LLM 失败处理:直接报错,不注入任何文本
66-
- 注入失败策略:不自动写剪贴板,菜单栏显示失败文本截断预览,点击可复制到剪贴板,仅当前运行期有效
25+
- `Swift 6`
26+
- `SwiftUI + AppKit`
27+
- `MVVM + Service Layer`
28+
- `RNNoise`
29+
- `FunASR` Python sidecar
30+
- `OpenAI Chat Completions` 兼容接口
6731

68-
## 技术架构
32+
## 仓库结构
6933

70-
### 技术栈
71-
72-
- 客户端:`Swift 6.0 + SwiftUI + AppKit`
73-
- 架构:`MVVM + Service Layer`
74-
- 语音识别:`FunASR` 本地离线 ASR / `腾讯云一句话识别`(用户手动切换)
75-
- 音频降噪:`RNNoise`
76-
- 大模型接入:`OpenAI Chat Completions` 兼容接口
77-
- 音频格式:`PCM/WAV 16k mono`
78-
- 文本注入:优先 `Accessibility API`,失败后回退键盘事件输入
79-
- 配置存储:`~/.typoless/config.json`(UTF-8 JSON)
80-
81-
### 分层结构
82-
83-
```
84-
app/Typoless/
85-
├── App/ # 应用入口(TypolessApp)
86-
├── Domain/
87-
│ ├── Coordinators/ # AppCoordinator, SessionCoordinator
88-
│ ├── Models/ # SessionState, TypolessError, AppConfig 等
89-
│ └── Services/ # DiagnosticsLogger, ResourceValidator
90-
├── Persistence/ # ConfigStore, KeychainHelper, PersonalDictionaryStore
91-
├── Platform/ # AudioRecorder, AudioPreprocessor, HotkeyManager, PermissionsManager, TextInjector
92-
├── Providers/ # ASRProvider, FunASRProvider, ASRRuntimeManager, StreamingASRProvider, WhisperProvider, LLMProvider
93-
├── Resources/ # 资源文件(正式包默认包含 rnnoise, funasr)
94-
└── UI/
95-
├── MenuBar/ # MenuBarView
96-
└── Settings/ # 设置页各 Tab 视图
34+
```text
35+
app/ macOS 客户端与 XcodeGen 工程定义
36+
docs/ PRD、TDD、验证与设计文档
37+
scripts/ 资源准备、构建、签名相关脚本
9738
```
9839

99-
### 核心对象
100-
101-
| 对象 | 职责 |
102-
| --- | --- |
103-
| `AppCoordinator` | 应用生命周期、菜单栏入口、设置页导航 |
104-
| `SessionCoordinator` | 主链路状态机编排(录音→识别→润色→注入) |
105-
| `AudioRecorder` | 音频采集与 PCM/WAV 标准化 |
106-
| `AudioPreprocessor` | RNNoise 本地降噪 |
107-
| `ASRProvider` | 统一 ASR 识别协议 |
108-
| `FunASRProvider` | FunASR 离线识别(默认链路),通过 sidecar 通信 |
109-
| `ASRRuntimeManager` | Python sidecar 生命周期管理、warmup、健康检查 |
110-
| `StreamingASRProvider` | sherpa-onnx 流式识别(旧链路) |
111-
| `WhisperProvider` | 本地 Whisper 子进程调用(旧链路) |
112-
| `LLMProvider` | OpenAI Chat Completions 调用 |
113-
| `TextInjector` | AX API 文本注入 + 键盘事件回退 |
114-
| `PermissionsManager` | 麦克风与辅助功能权限管理 |
115-
| `HotkeyManager` | Carbon Event 全局快捷键 |
116-
| `ConfigStore` | `~/.typoless/config.json` 配置读写 |
117-
| `PersonalDictionaryStore` | 个人词典管理(`~/.typoless/dictionary.json`|
118-
| `DiagnosticsLogger` | 会话耗时与 ASR/LLM 对照日志 |
119-
| `ResourceValidator` | 运行时资源完整性校验 |
120-
121-
## 核心流程
122-
123-
### 首次配置
124-
125-
1. 启动应用
126-
2. 应用内置降噪、FunASR runtime 与 worker;若使用本地 FunASR,先在设置页下载模型
127-
3. 配置 LLM `Base URL / API Key / Model`
128-
4. 设置全局快捷键
129-
5. 授予麦克风权限
130-
6. 授予辅助功能权限
131-
132-
### 日常使用
133-
134-
1. 在任意应用中聚焦输入区域
135-
2. 按下快捷键开始录音
136-
3. 再次按下快捷键结束录音(或达到 60 秒自动结束)
137-
4. 对音频进行本地降噪
138-
5. 使用 FunASR 进行本地离线识别,获取转写文本
139-
6. 调用 LLM 做纠错与轻度书面化
140-
7. 将最终文本一次性注入当前焦点应用
141-
142-
## 状态机
143-
144-
主状态流转:
145-
146-
`idle -> recording -> transcribing -> polishing -> injecting -> done`
147-
148-
异常状态:
149-
150-
- `error`
151-
- `cancelled`
152-
153-
约束:
154-
155-
- 同一时间只允许一个 session
156-
- 菜单栏允许取消处理中任务(识别中 / 润色中)
157-
- 取消后不得继续注入文本
158-
- 状态变化实时反映到菜单栏图标
159-
160-
## 错误处理
161-
162-
| 错误类型 | 用户提示 |
163-
| --- | --- |
164-
| 麦克风权限缺失 | 麦克风权限未开启,无法录音 |
165-
| 辅助功能权限缺失 | 辅助功能权限未开启,无法注入文本 |
166-
| 录音数据为空 | 录音数据为空,请重试 |
167-
| 本地识别引擎未就绪 | 本地识别引擎未就绪,请重新安装应用 |
168-
| LLM 配置无效 | LLM 配置无效:具体原因 |
169-
| LLM 网络失败 | LLM 网络连接失败,请检查网络 |
170-
| LLM 空结果 | LLM 返回空结果,请检查模型或网关配置 |
171-
| 文本注入失败 | 文本注入失败:具体原因 |
172-
173-
## LLM 处理边界
174-
175-
首版 LLM 只做以下事情:
176-
177-
- 修正 ASR 识别错误
178-
- 修正常见同音词和错别字
179-
- 去掉明显口语赘词
180-
- 轻度书面化表达
181-
- 自动补自然中文标点
182-
- 保留个人词典中的专有名词
183-
184-
首版 LLM 不做以下事情:
185-
186-
- 大幅改写句子结构
187-
- 扩写用户没说出的内容
188-
- 擅自改变语义、语气、事实
189-
190-
## 配置项
40+
## 本地开发
19141

192-
| 配置字段 | 存储位置 |
193-
| --- | --- |
194-
| `openai_base_url` | `~/.typoless/config.json` |
195-
| `openai_model` | `~/.typoless/config.json` |
196-
| `global_hotkey` | `~/.typoless/config.json` |
197-
| `pasteboard_injection_bundle_ids` | `~/.typoless/config.json` |
198-
| `openai_api_key` | `~/.typoless/config.json` |
199-
| 个人词典 | `~/.typoless/dictionary.json` |
42+
依赖:
20043

201-
## 测试策略
44+
- macOS 14+
45+
- Xcode 16+
46+
- `xcodegen`
20247

203-
- 单元测试重点覆盖 `Provider`(FunASR/sherpa/Whisper)、`ASRRuntimeManager``AudioPreprocessor``PersonalDictionaryStore``Session Coordinator`
204-
- 端到端以手工验收主链路为主
205-
- 重点验证权限缺失、配置错误、LLM 回退、注入失败
206-
207-
### 手工验收清单
208-
209-
- [ ] 首次启动自动打开设置页
210-
- [ ] 配置 LLM 并保存成功
211-
- [ ] 设置全局快捷键并生效
212-
- [ ] 麦克风权限授权后可录音
213-
- [ ] 低于 500ms 的短录音静默取消,不触发 ASR / LLM / 注入
214-
- [ ] 辅助功能权限授权后可注入文本
215-
- [ ] 完整链路:录音 → 降噪 → FunASR → LLM → 注入(浏览器输入框、备忘录、聊天应用)
216-
- [ ] RNNoise 降噪链路正常
217-
- [ ] FunASR 离线识别链路正常
218-
- [ ] FunASR sidecar 首次惰性启动成功
219-
- [ ] Debug 日志可查看耗时和 ASR/LLM 明文对照
220-
- [ ] Release 日志不泄露 ASR/LLM 明文
221-
- [ ] 个人词典可改善专有名词识别与润色
222-
- [ ] LLM 配置不完整时,录音流程直接报错且不注入文本
223-
- [ ] LLM 失败或空结果时直接报错且不注入文本
224-
- [ ] 注入失败后菜单栏显示失败文本预览,点击可复制
225-
- [ ] 成功注入后失败文本预览消失
226-
- [ ] 菜单栏状态随主链路正确刷新
227-
- [ ] 识别中 / 润色中可从菜单取消
228-
- [ ] 诊断页展示最近错误摘要
229-
- [ ] 权限缺失场景提示清晰
230-
- [ ] 本地识别失败场景提示清晰
231-
- [ ] 应用重启后配置正常恢复,无历史文本残留
232-
233-
## 目录说明
234-
235-
- [PRD.md](./docs/PRD.md): 已更新的产品需求文档
236-
- [TDD.md](./docs/TDD.md): 已更新的技术设计文档
237-
- [EPICS_AND_STORIES.md](./docs/EPICS_AND_STORIES.md): Epic 和 Story 拆分
238-
- `app/`: macOS 客户端代码(Swift + SwiftUI + AppKit)
239-
- `app/project.yml`: XcodeGen 项目配置
240-
241-
## 开发环境
242-
243-
### 依赖
244-
245-
- macOS 14.0+
246-
- Xcode 16.0+
247-
- [XcodeGen](https://github.com/yonaskolb/XcodeGen)
248-
249-
### 准备本地语音资源
250-
251-
正式包默认仅内置 `FunASR + RNNoise` 资源。开发环境至少需要准备 RNNoise;旧链路脚本只保留给历史调试用途:
48+
准备资源:
25249

25350
```bash
254-
# 准备 RNNoise 降噪库
25551
./scripts/setup-rnnoise.sh
256-
257-
# 可选:准备 sherpa-onnx runtime 与中文 streaming 模型(旧链路调试)
258-
# ./scripts/setup-sherpa.sh
259-
260-
# 可选:准备 Whisper 资源(旧链路调试)
261-
# ./scripts/setup-whisper.sh
26252
```
26353

264-
脚本支持通过环境变量指定资源路径,详见各脚本顶部注释。
265-
266-
### 生成 Xcode 工程
54+
如需本地 FunASR runtime 打包或签名,使用:
26755

26856
```bash
269-
cd app
270-
xcodegen generate
271-
open Typoless.xcodeproj
57+
./scripts/bundle-funasr-runtime.sh
58+
./scripts/sign-funasr-runtime.sh
27259
```
27360

274-
### 构建与运行
61+
生成工程并构建:
27562

27663
```bash
27764
cd app
27865
xcodegen generate
27966
xcodebuild build -project Typoless.xcodeproj -scheme Typoless -destination 'platform=macOS'
28067
```
28168

282-
或在 Xcode 中打开 `Typoless.xcodeproj` 后按 `⌘R` 运行。
283-
284-
## CI / CD
285-
286-
项目使用 GitHub Actions 进行持续集成和构建产物生成。
287-
288-
### CI(`.github/workflows/ci.yml`
289-
290-
- **触发条件**`main` 分支 push、PR 提交
291-
- **执行内容**`xcodegen generate``xcodebuild build``xcodebuild test`
292-
- **失败诊断**:自动上传 `xcresult` 和构建日志
293-
294-
### Release Build(`.github/workflows/release-build.yml`
295-
296-
- **触发条件**:手动触发(`workflow_dispatch`)、`v*` tag push
297-
- **执行内容**`xcodegen generate``xcodebuild archive`(未签名)→ 打包 `.app` zip
298-
- **产物**:可从 Actions 下载 `Typoless-unsigned.zip``.xcarchive`
299-
- **不依赖**:Developer ID 签名、Apple notarization、GitHub Secrets
300-
301-
### 构建准备脚本
302-
303-
```bash
304-
# CI 和本地均可使用
305-
./scripts/ci/prepare-macos-build.sh
306-
```
307-
308-
脚本自动完成:安装 `xcodegen`、准备 RNNoise 库、生成 Xcode 工程。
309-
310-
## 当前状态
311-
312-
- `PRD`: 已更新至 v1.2(FunASR 收敛)
313-
- `TDD`: 已更新至 v1.2(FunASR sidecar 架构)
314-
- `代码实现`: FunASR 默认链路实现中(DiagnosticsLogger、RNNoise、个人词典、Prompt 优化、资源校验已完成;FunASR Provider 与 sidecar 集成进行中)
315-
316-
## 参考
69+
## 文档入口
31770

318-
- 产品需求文档:[PRD.md](./docs/PRD.md)
319-
- Epic 和 Story 拆分:[EPICS_AND_STORIES.md](./docs/EPICS_AND_STORIES.md)
320-
- 技术设计文档:[TDD.md](./docs/TDD.md)
71+
- [PRD](./docs/PRD.md)
72+
- [TDD](./docs/TDD.md)
73+
- [EPICS_AND_STORIES](./docs/EPICS_AND_STORIES.md)

scripts/generate_app_icon.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#!/usr/bin/env swift
22
// generate_app_icon.swift
33
//
4-
// 基于项目根目录 svg.svg 生成 macOS App Icon 全套 PNG 资源。
4+
// 基于项目根目录 typoless.svg 生成 macOS App Icon 全套 PNG 资源。
55
// 风格:白底卡片 + 圆角底板 + 居中 SVG 图形 + 内边距
66
//
77
// 用法:swift scripts/generate_app_icon.swift <svg_path> <output_dir>
8-
// 示例:swift scripts/generate_app_icon.swift svg.svg app/Typoless/Resources/Assets.xcassets/AppIcon.appiconset
8+
// 示例:swift scripts/generate_app_icon.swift typoless.svg app/Typoless/Resources/Assets.xcassets/AppIcon.appiconset
99

1010
import AppKit
1111
import Foundation
File renamed without changes.

0 commit comments

Comments
 (0)