docs: refresh README and GitHub Pages for v0.2.1

Sync project documentation with the current v0.2.1 implementation
(Tap-to-toggle recording, 5-step onboarding, Flow session model,
SpeechAnalyzer-only local engine, deepseek-v4-flash default).

README.md / README.zh.md
- Replace 'press-and-hold / 按住说话' with the v0.2.0+ tap-to-toggle
  interaction; add the 60-second per-take cap to feature bullets.
- Update 3-step onboarding to the real 5-step flow.
- Correct architecture diagram: AudioCaptureService in the extension
  is legacy/unused, PolishingService lives in OSGKeyboardShared, and
  the FlowSession* / LiveDictation* services are now in the tree.
- Replace the legacy single-pipeline data flow with the actual Flow
  session data flow (keyboard -> App Group -> host app -> chunked
  ASR -> LLM polish -> App Group -> insertText).
- Move the stale 'main -> 0.2 branch rename' banner into a project
  status section under the new 'v0.2.1' badge.
- Expand 'Known limitations' with 60s/3min caps, 60MB sandbox note,
  URL scheme caveat, and the v0.2.0->v0.2.1 on-device LLM rollback.
- Add a 'Development' section pointing to tests, CI, logging policy.
- Add version badge, license link, privacy policy link.

docs/index.html (GitHub Pages landing)
- Mirror the same copy fixes in both EN and ZH i18n tables
  (verified: 50 keys each, no missing translations).
- Grow feature grid from 6 to 8 cards (add 'Flow session' and
  'Local + cloud polish'), grow steps from 3 to 5.
- Bump footer to 'v0.2.1 · source available, non-commercial',
  add License link.

docs/privacy.html + docs/privacy/index.html
- Update 'Last updated' to July 3, 2026, tag v0.2.1.
- Add explicit iOS 26+ requirement, Flow session explanation,
  children's privacy section, policy-changes section, license
  reference, and rocky.hk@gmail.com contact.
- Make the 'no raw audio upload to any server' claim explicit.

Verification
- grep confirms no leftover 'press and hold' / '按住' / 'press to'.
- HTML structure validated (well-formed on all three pages).
- EN/ZH i18n keys are symmetric (50 each).
This commit is contained in:
Mavis
2026-07-03 05:25:55 +00:00
parent dc9697bf3d
commit 2e6e70247a
5 changed files with 427 additions and 339 deletions
+103 -118
View File
@@ -1,93 +1,43 @@
> ⚠️ **2026-06-24**: Default branch renamed from `main` to `0.2`. Old `main` is now `0.1`; `refactor/drop-qwen3-coreml` is now `0.2`. See CHANGELOG for details.
## Build Setup
This project uses [XcodeGen](https://github.com/yonaskolb/XcodeGen) — `project.yml`
is the source of truth, and `OSGKeyboard.xcodeproj` is **regenerated, not committed**
(it's in `.gitignore`). New clones must generate the Xcode project before opening
in Xcode, otherwise Xcode will show an empty project with no source files.
### Prerequisites
- macOS with **Xcode 26** (matches `project.yml` deployment target iOS 26)
- [Homebrew](https://brew.sh)
### One-time setup
```bash
brew install xcodegen
```
### Generate `OSGKeyboard.xcodeproj`
From the repository root:
```bash
./Scripts/generate-xcodeproj.sh
```
This script:
1. Runs `xcodegen generate` to produce `OSGKeyboard.xcodeproj` from `project.yml`.
2. Calls `Scripts/patch-icon-composer.sh` to patch the generated project for
Xcode 26 Icon Composer bundles — `OSGKeyboard/AppIcon.icon` is treated as
a single `folder.iconcomposer.icon` reference (see `project.yml` note on
XcodeGen `fileTypes.icon`).
Then open the project:
```bash
open OSGKeyboard.xcodeproj
```
> **Troubleshooting**: If Xcode shows an empty project, re-run
> `./Scripts/generate-xcodeproj.sh`. If the build fails on icon assets,
> make sure `OSGKeyboard/AppIcon.icon/` exists with `Assets/` and `icon.json`
> inside (do not expand the folder manually).
### Re-running
Run `./Scripts/generate-xcodeproj.sh` any time `project.yml` changes
(e.g. after `git pull`).
---
# OSGKeyboard
> Hold a key, speak, release — AI-polished text appears at your cursor in any app.
> Tap to talk, tap to stop — AI-polished text appears at your cursor in any app.
> A source-available, custom-keyboard-based voice input tool for iOS 26+, inspired by [Typeless](https://typeless.com) and [OpenLess](https://github.com/Open-Less/openless).
![Platform](https://img.shields.io/badge/platform-iOS%2026%2B-0078D4?logo=apple)
![Swift](https://img.shields.io/badge/Swift-6.0-FA7343?logo=swift)
![License](https://img.shields.io/badge/license-Source%20Available-blue)
![CI](https://github.com/hkgood/OSGKeyboard/actions/workflows/ci.yml/badge.svg)
![Version](https://img.shields.io/badge/version-0.2.1-3aa05a)
[中文 README](./README.zh.md)
[中文 README](./README.zh.md) · [Privacy Policy](https://hkgood.github.io/OSGKeyboard/privacy/)
---
## What is it?
OSGKeyboard is a free, source-available alternative to commercial voice-input tools. It runs as a **Custom Keyboard Extension** on iOS, so you can use it in **any app** — Messages, Notes, Mail, ChatGPT, Claude, Cursor, you name it.
OSGKeyboard is a free, source-available alternative to commercial voice-input tools. It runs as a **Custom Keyboard Extension** on iOS, so you can use it in **any app** — Messages, Notes, Mail, WeChat, ChatGPT, Claude, Cursor, you name it.
1. Press and hold the mic key
2. Speak naturally
3. Release — the AI polishes your words into clean text and inserts it at the cursor
1. Tap the mic to start recording
2. Speak naturally (up to 60 seconds per take)
3. Tap again to stop — the AI polishes your words into clean text and inserts at the cursor
The audio stays on-device (transcribed by Apple's on-device `SpeechAnalyzer` + `DictationTranscriber` on iOS 26+). Only the **polished transcript** is sent to your chosen cloud LLM. **No audio ever leaves your phone.**
Audio is transcribed **on-device** by Apple's `SpeechAnalyzer` + `DictationTranscriber` (iOS 26+). Only the **polished transcript** is sent to your chosen cloud LLM. **No audio ever leaves your phone.**
Under the hood, OSGKeyboard uses a **Flow session model**: a long-lived audio session runs in the host app, the keyboard extension writes tiny "start / stop" signals to the App Group, and the polished text is delivered back to the keyboard for insertion. You do not need to jump back to the host app between recordings.
---
## Features
- 🎙 **Push-to-talk** with a Typeless-style circular mic button
- 🎙 **Tap-to-toggle recording** with a Typeless-style circular mic button, 60-second per-take cap with live countdown
- 🧠 **On-device ASR** (`SpeechAnalyzer` + `DictationTranscriber`, iOS 26+)
- ✍️ **AI polishing** — adds structure, punctuation, fixes grammar, optionally produces lists
- 🧩 **Local + cloud polish toggle** — local engine is ASR-only by default; opt into a post-ASR cloud polish step (DeepSeek by default) when the iOS speech recognition isn't strong enough for your environment (noisy far-field audio, strong accents, etc.)
- 🔌 **Bring-your-own API** — works with any OpenAI-compatible endpoint (OpenAI, DeepSeek, Qwen DashScope, your own self-hosted server, …)
- 🔒 **Privacy first** — audio never leaves your device; transcripts only sent to the LLM you choose
- 🎨 **Native SwiftUI** — dark theme, frosted glass, ~2000 lines of Swift
- 🔌 **Bring-your-own API** — works with any OpenAI-compatible endpoint (OpenAI, DeepSeek, Qwen DashScope, Moonshot, Zhipu, your own self-hosted server, …)
- 🔒 **Privacy first** — audio never leaves your device; only the final transcript is sent to the LLM you choose
- 🎨 **Native SwiftUI** — dark theme, frosted glass, pure Swift 6, ~3,600 lines of code
- 🪶 **Zero dependencies** — no SwiftPM packages, no CocoaPods, no Carthage
- 🔁 **Flow session** — keep recording across multiple takes without bouncing back to the host app
---
@@ -95,30 +45,39 @@ The audio stays on-device (transcribed by Apple's on-device `SpeechAnalyzer` + `
### Requirements
- macOS with **Xcode 16+** (Xcode 26 recommended)
- macOS with **Xcode 26** (matches `project.yml` deployment target iOS 26)
- iPhone running **iOS 26.0+**
- [XcodeGen](https://github.com/yonaskolb/XcodeGen): `brew install xcodegen`
- An OpenAI-compatible API key (e.g. from [OpenAI](https://platform.openai.com/api-keys), [DeepSeek](https://platform.deepseek.com/api_keys), or [Qwen DashScope](https://dashscope.console.aliyun.com/apiKey))
- An OpenAI-compatible API key (e.g. from [OpenAI](https://platform.openai.com/api-keys), [DeepSeek](https://platform.deepseek.com/api_keys), or [Qwen DashScope](https://dashscope.console.aliyun.com/apiKey)). Not needed if you stay on the "local ASR only" engine.
### Build & run
```bash
git clone https://github.com/hkgood/OSGKeyboard.git
cd OSGKeyboard
xcodegen generate # produces OSGKeyboard.xcodeproj
./Scripts/generate-xcodeproj.sh # generates OSGKeyboard.xcodeproj via XcodeGen
open OSGKeyboard.xcodeproj # or build via CLI:
xcodebuild -project OSGKeyboard.xcodeproj -scheme OSGKeyboard \
-destination 'generic/platform=iOS Simulator' build
```
> The `OSGKeyboard.xcodeproj` is **not** committed — it is regenerated from
> `project.yml` by `Scripts/generate-xcodeproj.sh`. Always re-run the script
> after `git pull` if `project.yml` has changed.
### Enable the keyboard in iOS
1. Run the app on your device or simulator.
2. Follow the 3-step onboarding: **enable the keyboard** in iOS Settings, then **allow Full Access** (required for the mic and LLM calls), then **paste your API key**.
3. In any text field, tap 🌐 to switch to **OSGKeyboard**.
4. Press and hold the mic, speak, release. ✨
The host app walks you through a **5-step onboarding**:
> **"Allow Full Access" is required.** Without it, iOS blocks the keyboard from using the microphone and from making network requests. We never log, store, or transmit your keystrokes — see [`PrivacyInfo.xcprivacy`](./OSGKeyboard/PrivacyInfo.xcprivacy).
1. **Welcome** — intro to OSGKeyboard
2. **Microphone** — request mic access
3. **Speech recognition** — request on-device speech recognition access
4. **Enable keyboard + Full Access** — open iOS Settings to add OSGKeyboard and allow Full Access
5. **Engine + API** — pick the local or cloud engine, then paste your API key (cloud / cloud-polish only)
After onboarding, in any text field, tap 🌐 to switch to **OSGKeyboard**, then tap the circular mic to start, speak, and tap again to stop.
> **"Allow Full Access" is required.** Without it, iOS blocks the keyboard from using the microphone and from making network requests. We never log, store, or transmit your keystrokes — see [`PrivacyInfo.xcprivacy`](./OSGKeyboard/PrivacyInfo.xcprivacy) and our [Privacy Policy](https://hkgood.github.io/OSGKeyboard/privacy/).
---
@@ -126,54 +85,60 @@ xcodebuild -project OSGKeyboard.xcodeproj -scheme OSGKeyboard \
```
OSGKeyboard/
├── OSGKeyboard/ # Main iOS app (settings, onboarding)
│ ├── Views/ # SwiftUI screens
│ ├── OSGKeyboardApp.swift # @main entry
├── OSGKeyboard/ # Main iOS app (host of the Flow session)
│ ├── Services/ # FlowSessionManager, AppPermissions, SpeechHistoryStore, …
│ ├── Views/ # SwiftUI: OnboardingView, HomeView, SettingsView, HistoryView, …
│ ├── OSGKeyboardApp.swift # @main entry, owns the FlowSessionManager
│ ├── PrivacyInfo.xcprivacy # Required privacy manifest
│ └── OSGKeyboard.entitlements # App Group declaration
│ └── OSGKeyboard.entitlements # App Group + Keychain Group
├── OSGKeyboardExt/ # Custom Keyboard Extension
│ ├── KeyboardViewController.swift # Principal class
│ ├── Services/
│ ├── AudioCaptureService.swift # AVAudioEngine → 16 kHz PCM
│ ├── ASRService.swift # iOS 26 SpeechAnalyzer ASR
│ │ └── PolishingService.swift # LLM call with timeout
── Views/ # RecordButton, Waveform, KeyboardRootView
├── OSGKeyboardShared/ # Framework shared by app + extension
│ ├── Models/ # ProviderConfig, LLMRequest, LLMProvider
│ ├── Services/ # LLMClient (OpenAI-compatible)
│ ├── KeyboardViewController.swift # Principal class (drives SwiftUI)
│ ├── Services/ # AppGroupPersistor, HostAppLauncher, AudioCaptureService (legacy, unused)
│ ├── Views/ # KeyboardRootView, RecordButton, WaveformView
└── PrivacyInfo.xcprivacy
├── OSGKeyboardShared/ # Framework shared by app + extension (APPLICATION_EXTENSION_API_ONLY=YES)
── Services/ # FlowSessionBridge, FlowSessionDarwin, LLMClient, PolishingService, ASRService, Keychain, AppGroupStore, …
│ ├── Models/ # LLMProvider, ProviderConfig, TranscriptionDelivery, AudioBufferSnapshot, …
│ ├── DesignSystem/ # Theme, ThemedRoot
│ └── Constants/ # AppGroup identifier
├── OSGKeyboardTests/ # XCTest unit tests
├── project.yml # XcodeGen project definition
├── OSGKeyboardTests/ # XCTest unit tests (LLM, Keychain, ASR, Flow bridge, …)
├── OSGKeyboardExtTests/ # Keyboard-extension-side unit tests
├── Scripts/ # generate-xcodeproj.sh, patch-icon-composer.sh
├── docs/ # GitHub Pages site (privacy policy + landing)
├── project.yml # XcodeGen project definition (source of truth)
└── .github/workflows/ci.yml # Lint + build CI
```
### Data flow
### Data flow — Flow session model
```
[Long-press mic] → AudioCaptureService → AudioBufferSnapshot (16 kHz mono)
ASRService.transcribe()
ASREvent.final(rawTranscript)
PolishingService.polish()
LLMClient (OpenAI-compatible)
textDocumentProxy.insertText(polished)
[Tap mic in keyboard]
└─► KeyboardViewController.pressBegan
└─► FlowSessionBridge.setRecordingState(.recording) [App Group UserDefaults]
└─► Darwin notification: "recordingState changed"
└─► FlowSessionManager (host app) sees the signal
└─► FlowContinuousCapture feeds 16 kHz PCM into ChunkedUtterancePipeline
└─► ASRService.transcribe (iOS 26 SpeechAnalyzer)
└─► ASREvent.partial / .final
└─► UtteranceTranscriptStitcher stitches the chunks
└─► PolishingService (LLMClient) [optional, configurable]
└─► FlowSessionBridge.storeTranscriptionResult
[Keyboard polls + Darwin notif]
└─► KeyboardViewController sees the result
└─► textDocumentProxy.insertText(polished)
```
**Engine modes:**
- `cloud` (default): ASR runs on-device via `SpeechAnalyzer`, the transcript is
sent to your configured LLM for polish.
- `local`: ASR runs on-device via `SpeechAnalyzer`. The transcript is inserted
as-is — no cloud round-trip.
- `local` + "Cloud polish after ASR" toggle (Settings → On-device models):
same as `cloud` in spirit, but routed via the local-engine pipeline so the
keyboard extension can still use the on-device ASR; the transcript is sent
to the configured LLM before insertion. Requires a DeepSeek (or other
OpenAI-compatible) API key in the Keychain.
- `cloud` (default) on-device ASR via `SpeechAnalyzer`, transcript is sent to your configured LLM for polish.
- `local` — on-device ASR via `SpeechAnalyzer` only; transcript is inserted as-is. No network round-trip.
- `local` + "Cloud polish after ASR" toggle (Settings → Engine) — same on-device ASR, but the transcript is routed through your configured LLM before insertion. Useful when iOS speech recognition isn't accurate enough in your environment.
**Cross-process plumbing (host app ↔ keyboard extension):**
- **App Group `group.com.osgkeyboard.shared`** — `UserDefaults` for the live Flow session state, recording state, audio levels, transcription delivery, and most preferences.
- **Shared Keychain group `com.osgkeyboard.shared`** — the LLM API key is written by the host app's Settings, read by both processes before every LLM call.
- **Darwin notifications (`CFNotificationCenter`)** — light-weight "something changed" pings; payloads still travel through the App Group.
---
@@ -191,15 +156,39 @@ LLMProvider(
)
```
That's it. No other code changes required.
That's it — no other code changes required.
To set it as the new default for first-time users, also bump the `defaultProviderId` constant used by `ProviderConfig`.
---
## Limitations
## Known limitations
- iOS sandboxes keyboard extensions: ~60 MB memory cap, Full Access required.
- The keyboard does **not** work in password fields or some `WKWebView` textareas (iOS limitation).
- iOS 26+ only. Earlier iOS versions are not supported.
- **iOS 26+ only.** Earlier iOS versions are not supported. We dropped the pre-26 SFSpeechRecognizer / AVAudioSession branching so the entire ASR path can use the iOS 26 `SpeechAnalyzer` API exclusively.
- **~60 MB memory cap** for the keyboard extension (iOS sandbox). The Flow session is hosted in the main app, so audio buffers and ASR models live there, not in the extension.
- **"Allow Full Access" required.** Without it, the keyboard can't reach the microphone or make network requests for cloud polish.
- **Password fields and some `WKWebView` textareas** are blocked by iOS itself — not something we can work around.
- **60-second per-take cap.** A long take is automatically stopped and dispatched for transcription; a new take can be started immediately.
- **3-minute per-utterance ASR cap.** If you exceed it, the pipeline gracefully splits into multiple stitched chunks.
- **No on-device LLM polish.** The local engine is ASR-only; "AI polish" is always cloud-based and configurable. On-device model support was explored in v0.2.0 and rolled back in v0.2.1 to keep the dependency surface at zero SPM packages.
- **URL scheme `osgkeyboard://`** can be opened by any app on the device. We don't trust it for anything beyond "wake the host app and (re)start the Flow session"; it never carries your API key or other secrets.
---
## Development
- **Build setup** — see the [Build Setup](#build-setup) section at the top of this file. Run `./Scripts/generate-xcodeproj.sh` after any `project.yml` change.
- **Tests** — `xcodebuild test -project OSGKeyboard.xcodeproj -scheme OSGKeyboard -destination 'platform=iOS Simulator,name=iPhone 17'` runs both `OSGKeyboardTests` and `OSGKeyboardExtTests` targets.
- **CI** — `.github/workflows/ci.yml` runs SwiftLint, a clean Debug build, and the test suite on every push to `0.1` / `0.2` and PRs.
- **Logging** — `print` is debug-only; release builds use `NSLog` for the few cross-process status messages.
---
## Project status
- **Current release: v0.2.1** (2026-06-24)
- **Default branch: `0.2`** (renamed from `main` on 2026-06-24; the previous `main` is preserved as `0.1`).
- See [`CHANGELOG.md`](./CHANGELOG.md) for the full release history and [`TYPEWHISPER_FLOW_MIGRATION_TRACKER.md`](./TYPEWHISPER_FLOW_MIGRATION_TRACKER.md) for the architecture-decision log behind the Flow session model.
---
@@ -214,7 +203,3 @@ That's it. No other code changes required.
- Inspired by [Typeless](https://typeless.com) and the desktop open-source [OpenLess](https://github.com/Open-Less/openless)
- Built with [XcodeGen](https://github.com/yonaskolb/XcodeGen)
- Powered by Apple's [SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer) and [SFSpeechRecognizer](https://developer.apple.com/documentation/speech/sfspeechrecognizer)
---
**Note:** the project is published at [`hkgood/OSGKeyboard`](https://github.com/hkgood/OSGKeyboard); badges and git clone URLs already point there.
+99 -110
View File
@@ -1,67 +1,14 @@
# OSGKeyboard
> 住说话,松开即得 AI 润色文字,插入任意 App 的光标处。
> 一下开始,再按一下结束 —— AI 润色文字直接出现在任意 App 的光标处。
> 一款源码可见的 iOS 自定义键盘语音输入工具,灵感来自 [Typeless](https://typeless.com) 和 [OpenLess](https://github.com/Open-Less/openless)。
![Platform](https://img.shields.io/badge/platform-iOS%2026%2B-0078D4?logo=apple)
![Swift](https://img.shields.io/badge/Swift-6.0-FA7343?logo=swift)
![License](https://img.shields.io/badge/license-Source%20Available-blue)
![Version](https://img.shields.io/badge/version-0.2.1-3aa05a)
[English README](./README.md)
---
> ⚠️ **2026-06-24**: Default branch renamed from `main` to `0.2`. Old `main` is now `0.1`; `refactor/drop-qwen3-coreml` is now `0.2`. See CHANGELOG for details.
## Build Setup
This project uses [XcodeGen](https://github.com/yonaskolb/XcodeGen) — `project.yml`
is the source of truth, and `OSGKeyboard.xcodeproj` is **regenerated, not committed**
(it's in `.gitignore`). New clones must generate the Xcode project before opening
in Xcode, otherwise Xcode will show an empty project with no source files.
### Prerequisites
- macOS with **Xcode 26** (matches `project.yml` deployment target iOS 26)
- [Homebrew](https://brew.sh)
### One-time setup
```bash
brew install xcodegen
```
### Generate `OSGKeyboard.xcodeproj`
From the repository root:
```bash
./Scripts/generate-xcodeproj.sh
```
This script:
1. Runs `xcodegen generate` to produce `OSGKeyboard.xcodeproj` from `project.yml`.
2. Calls `Scripts/patch-icon-composer.sh` to patch the generated project for
Xcode 26 Icon Composer bundles — `OSGKeyboard/AppIcon.icon` is treated as
a single `folder.iconcomposer.icon` reference (see `project.yml` note on
XcodeGen `fileTypes.icon`).
Then open the project:
```bash
open OSGKeyboard.xcodeproj
```
> **Troubleshooting**: If Xcode shows an empty project, re-run
> `./Scripts/generate-xcodeproj.sh`. If the build fails on icon assets,
> make sure `OSGKeyboard/AppIcon.icon/` exists with `Assets/` and `icon.json`
> inside (do not expand the folder manually).
### Re-running
Run `./Scripts/generate-xcodeproj.sh` any time `project.yml` changes
(e.g. after `git pull`).
[English README](./README.md) · [隐私政策](https://hkgood.github.io/OSGKeyboard/privacy/)
---
@@ -69,24 +16,27 @@ Run `./Scripts/generate-xcodeproj.sh` any time `project.yml` changes
OSGKeyboard 是商业语音输入工具的免费、源码可见替代方案。它以 **iOS 自定义键盘扩展** 的形式运行,所以你可以在 **任何 App** 里使用 —— 微信、备忘录、邮件、ChatGPT、Claude、Cursor,无所不能。
1. 按麦克风键
2. 自由说话
3. 松开 —— AI 帮你整理成干净的文字,自动插入光标
1.麦克风键开始录音
2. 自由说话(单次上限 60 秒)
3. 再按一下结束 —— AI 自动整理成干净的文字插入光标
**音频始终在设备本地转写**iOS 26+ 使用 `SpeechAnalyzer` + `DictationTranscriber`),**只有润色后的文本** 会发到你选择的云端 LLM。**音频永不离开你的手机。**
**音频始终在设备本地转写**iOS 26+ `SpeechAnalyzer` + `DictationTranscriber`),**只有润色后的文本** 会发到你选择的云端 LLM。**音频永不离开你的手机。**
项目内部采用 **Flow 会话模型**:主 App 维护一个长生命周期的音频会话,键盘扩展只通过 App Group 写入"开始 / 停止"等轻量信号,润色后的文本再由主 App 回传给键盘插入。**多次录音之间无需反复跳回主 App**。
---
## 特性
- 🎙 **按住说话**Typeless 风格的圆形麦克风按钮
- 🎙 **点按录音** —— Typeless 风格的圆形麦克风按钮,单次上限 60 秒并实时倒计时
- 🧠 **端侧 ASR**iOS 26+ `SpeechAnalyzer` + `DictationTranscriber`
- ✍️ **AI 润色** —— 自动加结构、补标点、修正语法、可生成列表
- 🧩 **本地 + 云端润色开关** —— 本地模式默认仅在设备上识别;若 iOS 语音识别效果不理想(远场、噪声、方言),可开启「识别后云端润色」,默认走 DeepSeek
- 🔌 **自带 API 接入** —— 兼容任何 OpenAI 兼容协议端点(OpenAI / DeepSeek / Qwen DashScope / 自建服务器 ……)
- 🧩 **本地 + 云端润色开关** —— 本地模式默认仅在设备上识别;若 iOS 语音识别效果不理想远场、噪声、方言,可开启「识别后云端润色」,默认走 DeepSeek
- 🔌 **自带 API 接入** —— 兼容任何 OpenAI 兼容协议端点(OpenAI / DeepSeek / Qwen DashScope / Moonshot / 智谱 / 自建服务器 ……)
- 🔒 **隐私优先** —— 音频不离开设备;只有润色文本会发给你选择的 LLM
- 🎨 **原生 SwiftUI** —— 暗色主题、毛玻璃、约 2000 行 Swift
- 🎨 **原生 SwiftUI** —— 暗色主题、毛玻璃、纯 Swift 6 实现,约 3,600 行代码
- 🪶 **零依赖** —— 无 SwiftPM 包、无 CocoaPods、无 Carthage
- 🔁 **Flow 会话** —— 多次录音无需跳回主 App,会话自动维持心跳与续期
---
@@ -94,30 +44,37 @@ OSGKeyboard 是商业语音输入工具的免费、源码可见替代方案。
### 环境要求
- macOS + **Xcode 16+**推荐 Xcode 26
- macOS + **Xcode 26**`project.yml` 中 iOS 26 部署目标对齐
- iPhone 运行 **iOS 26.0+**
- [XcodeGen](https://github.com/yonaskolb/XcodeGen)`brew install xcodegen`
- 一个 OpenAI 兼容 API Key[OpenAI](https://platform.openai.com/api-keys) / [DeepSeek](https://platform.deepseek.com/api_keys) / [Qwen DashScope](https://dashscope.console.aliyun.com/apiKey) 任一)
- 一个 OpenAI 兼容 API Key[OpenAI](https://platform.openai.com/api-keys) / [DeepSeek](https://platform.deepseek.com/api_keys) / [Qwen DashScope](https://dashscope.console.aliyun.com/apiKey) 任一)。如果一直使用「纯本地 ASR」引擎则无需 Key。
### 编译与运行
```bash
git clone https://github.com/hkgood/OSGKeyboard.git
cd OSGKeyboard
xcodegen generate # 生成 OSGKeyboard.xcodeproj
./Scripts/generate-xcodeproj.sh # 通过 XcodeGen 生成 OSGKeyboard.xcodeproj
open OSGKeyboard.xcodeproj # 或命令行编译:
xcodebuild -project OSGKeyboard.xcodeproj -scheme OSGKeyboard \
-destination 'generic/platform=iOS Simulator' build
```
> `OSGKeyboard.xcodeproj` **不进入版本库**,由 `Scripts/generate-xcodeproj.sh` 从 `project.yml` 生成。每次 `git pull` 后若 `project.yml` 有变更,请重新执行该脚本。
### 在 iOS 中启用键盘
1. 在真机/模拟器上运行 App。
2. 按 3 步引导:**启用键盘** → **允许完全访问**(麦克风 + LLM 调用必须) → **粘贴 API Key**
3. 在任意输入框,点 🌐 切换到 **OSGKeyboard**
4. 长按麦克风键 → 说话 → 松开。✨
主 App 会引导你走完 **5 步**
> **"允许完全访问"是必须的。** 没有它,iOS 会阻止键盘使用麦克风与网络。我们**绝不记录、存储或上传你的击键** —— 见 [`PrivacyInfo.xcprivacy`](./OSGKeyboard/PrivacyInfo.xcprivacy)。
1. **欢迎** —— 介绍 OSGKeyboard
2. **麦克风** —— 申请麦克风权限
3. **语音识别** —— 申请端侧语音识别权限
4. **启用键盘 + 完全访问** —— 跳转 iOS 设置添加 OSGKeyboard 并允许完全访问
5. **引擎 + API** —— 选择本地或云端引擎,粘贴 API Key(仅云端 / 识别后云端润色需要)
完成后,在任意输入框点 🌐 切换到 **OSGKeyboard**,再点圆形麦克风键开始说话,再次点击结束。
> **"允许完全访问"是必须的。** 没有它,iOS 会阻止键盘使用麦克风与网络。我们**绝不记录、存储或上传你的击键** —— 见 [`PrivacyInfo.xcprivacy`](./OSGKeyboard/PrivacyInfo.xcprivacy) 与 [隐私政策](https://hkgood.github.io/OSGKeyboard/privacy/)。
---
@@ -125,48 +82,60 @@ xcodebuild -project OSGKeyboard.xcodeproj -scheme OSGKeyboard \
```
OSGKeyboard/
├── OSGKeyboard/ # 主 iOS App设置、Onboarding
│ ├── Views/ # SwiftUI 屏幕
│ ├── OSGKeyboardApp.swift # @main 入口
├── OSGKeyboard/ # 主 iOS AppFlow 会话宿主
│ ├── Services/ # FlowSessionManager、AppPermissions、SpeechHistoryStore、…
│ ├── Views/ # SwiftUIOnboardingView、HomeView、SettingsView、HistoryView、…
│ ├── OSGKeyboardApp.swift # @main 入口,持有 FlowSessionManager
│ ├── PrivacyInfo.xcprivacy # 隐私清单
│ └── OSGKeyboard.entitlements # App Group 声明
│ └── OSGKeyboard.entitlements # App Group + Keychain Group
├── OSGKeyboardExt/ # 自定义键盘扩展
│ ├── KeyboardViewController.swift # 主体类
│ ├── Services/
│ ├── AudioCaptureService.swift # AVAudioEngine → 16kHz PCM
│ ├── ASRService.swift # iOS 26 SpeechAnalyzer ASR
│ │ └── PolishingService.swift # LLM 调用(带超时
── Views/ # 录音按钮、波形、键盘主视图
├── OSGKeyboardShared/ # 主 App + 键盘共享 framework
│ ├── Models/ # ProviderConfig、LLMRequest、LLMProvider
│ ├── Services/ # LLMClientOpenAI 兼容)
│ ├── KeyboardViewController.swift # 主体类(驱动 SwiftUI
│ ├── Services/ # AppGroupPersistor、HostAppLauncher、AudioCaptureService(旧版,未使用)
│ ├── Views/ # KeyboardRootView、RecordButton、WaveformView
└── PrivacyInfo.xcprivacy
├── OSGKeyboardShared/ # 主 App + 键盘共享 frameworkAPPLICATION_EXTENSION_API_ONLY=YES
── Services/ # FlowSessionBridge、FlowSessionDarwin、LLMClient、PolishingService、ASRService、Keychain、AppGroupStore、…
│ ├── Models/ # LLMProvider、ProviderConfig、TranscriptionDelivery、AudioBufferSnapshot、…
│ ├── DesignSystem/ # Theme、ThemedRoot
│ └── Constants/ # AppGroup ID
├── OSGKeyboardTests/ # XCTest 单元测试
├── project.yml # XcodeGen 工程定义
├── OSGKeyboardTests/ # XCTest 单元测试LLM、Keychain、ASR、Flow bridge、…)
├── OSGKeyboardExtTests/ # 键盘扩展侧单元测试
├── Scripts/ # generate-xcodeproj.sh、patch-icon-composer.sh
├── docs/ # GitHub Pages(隐私政策 + 落地页)
├── project.yml # XcodeGen 工程定义(唯一源)
└── .github/workflows/ci.yml # Lint + 编译 CI
```
### 数据流
### 数据流 —— Flow 会话模型
```
[按麦克风] → AudioCaptureService → AudioBufferSnapshot (16kHz mono)
ASRService.transcribe()
ASREvent.final(原始转写文本)
PolishingService.polish()
LLMClientOpenAI 兼容协议)
textDocumentProxy.insertText(润色后文本)
[键盘点按麦克风]
└─► KeyboardViewController.pressBegan
└─► FlowSessionBridge.setRecordingState(.recording) [App Group UserDefaults]
└─► Darwin 通知:"recordingState changed"
└─► 主 App 的 FlowSessionManager 收到信号
└─► FlowContinuousCapture 持续把 16kHz PCM 喂给 ChunkedUtterancePipeline
└─► ASRService.transcribeiOS 26 SpeechAnalyzer
└─► ASREvent.partial / .final
└─► UtteranceTranscriptStitcher 拼接
└─► PolishingServiceLLMClient [可选,由引擎模式决定]
└─► FlowSessionBridge.storeTranscriptionResult
[键盘轮询 + Darwin 通知]
└─► KeyboardViewController 拿到结果
└─► textDocumentProxy.insertText(润色后文本)
```
**引擎模式:**
- `cloud`(默认):本地 `SpeechAnalyzer` 识别,文本发到云端 LLM 润色。
- `local`:本地 `SpeechAnalyzer` 识别,原始文本直接插入,不联网。
- `local` + 「识别后云端润色」开关(设置 → 本地模型):与 `cloud` 类似,但使用本地引擎流程,识别完成后送 LLM 润色再插入。需在 Keychain 中提前填好 DeepSeek(或任意 OpenAI 兼容提供方)API Key
- `cloud`(默认)—— 端侧 `SpeechAnalyzer` 识别,文本发到云端 LLM 润色。
- `local` —— 仅端侧 `SpeechAnalyzer` 识别,原始文本直接插入,不联网。
- `local` + 「识别后云端润色」开关(设置 → 引擎)—— 同样走端侧 ASR,但识别完成后送 LLM 润色再插入。适用于 iOS 识别效果不理想的场景
**跨进程管道(主 App ↔ 键盘扩展):**
- **App Group `group.com.osgkeyboard.shared`** —— `UserDefaults` 存放 Flow 会话状态、录音状态、音量、转写投递、绝大部分偏好。
- **共享 Keychain 组 `com.osgkeyboard.shared`** —— LLM API Key 由主 App「设置」写入,键盘扩展在每次 LLM 调用前读取。
- **Darwin 通知(`CFNotificationCenter`)** —— 轻量级"有变化"信号;具体负载仍走 App Group。
---
@@ -186,13 +155,37 @@ LLMProvider(
仅此而已,**无需改动其他代码**。
如需设为新用户的默认值,还需同步调整 `ProviderConfig` 中的 `defaultProviderId` 常量。
---
## 限制
## 已知限制
- iOS 沙盒:键盘扩展 ~60 MB 内存上限,必须开完全访问
- 密码框与部分 `WKWebView` 输入框不可用(iOS 限制)
- 仅支持 iOS 26+;更早的 iOS 版本不在支持范围内
- **仅支持 iOS 26+。** 我们已移除 26 以下 `SFSpeechRecognizer` / `AVAudioSession` 的兼容分支,让 ASR 路径全部走 iOS 26 `SpeechAnalyzer`
- **键盘扩展约 60 MB 内存上限**(iOS 沙盒)。Flow 会话由主 App 承载,音频缓冲与 ASR 模型都在主 App 侧,不占用扩展内存。
- **必须「允许完全访问」**。否则键盘无法使用麦克风,也无法发起云端润色请求
- **密码框与部分 `WKWebView` 输入框不可用**(iOS 系统限制,无法绕过)。
- **单次录音上限 60 秒**。到点自动停止并提交识别,下次可立即开始新的录音。
- **单次 utterance ASR 上限 3 分钟**。超出后会拆成多个 chunk 拼接识别。
- **不做端侧 LLM 润色**。本地引擎仅做 ASR"AI 润色"始终走云端、可配置。v0.2.0 曾尝试引入端侧模型,v0.2.1 回滚以保持零 SPM 依赖。
- **URL Scheme `osgkeyboard://`** 任何 App 都可调用。OSGKeyboard 只把它用于"唤醒主 App / 续期 Flow 会话"**不** 传递 API Key 等敏感信息。
---
## 开发指南
- **构建** —— 见本文开头的 [编译与运行](#编译与运行) 节。`project.yml` 变更后请重新执行 `./Scripts/generate-xcodeproj.sh`
- **测试** —— `xcodebuild test -project OSGKeyboard.xcodeproj -scheme OSGKeyboard -destination 'platform=iOS Simulator,name=iPhone 17'` 会同时跑 `OSGKeyboardTests``OSGKeyboardExtTests` 两个 target。
- **CI** —— `.github/workflows/ci.yml` 在每次 push 到 `0.1` / `0.2` 分支及 PR 时跑 SwiftLint、Debug 干净构建和测试套件。
- **日志** —— `print` 仅在 Debug 启用;Release 仅保留少量跨进程状态相关的 `NSLog`
---
## 项目状态
- **当前版本:v0.2.1**2026-06-24
- **默认分支:`0.2`**2026-06-24 从 `main` 改名;旧 `main` 保留为 `0.1`)。
- 完整发布记录见 [`CHANGELOG.md`](./CHANGELOG.md);Flow 会话模型的架构决策日志见 [`TYPEWHISPER_FLOW_MIGRATION_TRACKER.md`](./TYPEWHISPER_FLOW_MIGRATION_TRACKER.md)。
---
@@ -207,7 +200,3 @@ LLMProvider(
- 灵感来源:[Typeless](https://typeless.com) 与桌面端开源版 [OpenLess](https://github.com/Open-Less/openless)
- 工程脚手架:[XcodeGen](https://github.com/yonaskolb/XcodeGen)
- 端侧 ASRApple [SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer) / [SFSpeechRecognizer](https://developer.apple.com/documentation/speech/sfspeechrecognizer)
---
**说明**:本项目发布仓库为 [`hkgood/OSGKeyboard`](https://github.com/hkgood/OSGKeyboard)。
+114 -52
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OSGKeyboard — Voice dictation keyboard for iOS</title>
<meta name="description" content="OSGKeyboard — hold to talk, release to insert AI-polished text in any app. On-device speech recognition, bring-your-own LLM.">
<meta name="description" content="OSGKeyboard — tap to start, tap to stop. AI-polished text in any app. On-device speech recognition (iOS 26+), bring-your-own LLM, source available.">
<meta name="theme-color" content="#0A0A0B">
<link rel="icon" href="assets/app-icon.png" type="image/png">
<link rel="apple-touch-icon" href="assets/app-icon.png">
@@ -399,6 +399,7 @@
background: radial-gradient(circle at 30% 30%, #5fd080, var(--accent) 55%, #2a7a45);
box-shadow: 0 0 0 10px var(--accent-muted), 0 12px 28px var(--accent-glow);
animation: pulse 2.4s ease-in-out infinite;
position: relative;
}
@keyframes pulse {
@@ -406,6 +407,22 @@
50% { transform: scale(1.04); }
}
/* Pulsing recording rings overlay for the keyboard mock */
.kb-mic::after {
content: "";
position: absolute;
inset: -6px;
border-radius: 50%;
border: 2px solid var(--accent);
opacity: 0;
animation: ring 2.4s ease-out infinite;
}
@keyframes ring {
0% { transform: scale(0.9); opacity: 0.55; }
100% { transform: scale(1.6); opacity: 0; }
}
.phone-glow {
position: absolute;
inset: 10% 5% auto;
@@ -483,12 +500,16 @@
.steps {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
grid-template-columns: repeat(5, 1fr);
gap: 0.8rem;
counter-reset: step;
}
@media (max-width: 760px) {
@media (max-width: 1080px) {
.steps { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
.steps { grid-template-columns: 1fr; }
}
@@ -597,10 +618,10 @@
<section class="hero">
<div class="section-inner hero-grid">
<div>
<div class="eyebrow"><span class="eyebrow-dot"></span><span data-i18n="hero.eyebrow">Open source · iOS 26+</span></div>
<h1 data-i18n="hero.title">Hold to talk.<br>Polished text in any app.</h1>
<div class="eyebrow"><span class="eyebrow-dot"></span><span data-i18n="hero.eyebrow">Source available · iOS 26+</span></div>
<h1 data-i18n="hero.title">Tap to talk.<br>Polished text in any app.</h1>
<p class="hero-lead" data-i18n="hero.lead">
A custom keyboard that transcribes your voice on-device and optionally polishes the result with the LLM you configure WeChat, Notes, Mail, ChatGPT, and everywhere else you type.
A custom keyboard that transcribes your voice on-device and optionally polishes the result with the LLM you configure. Works in WeChat, Notes, Mail, ChatGPT everywhere you type. Up to 60 seconds per take, with a long-lived Flow session so you never bounce back to the host app.
</p>
<div class="hero-cta">
<a class="btn btn-primary" href="https://github.com/hkgood/OSGKeyboard" data-i18n="hero.cta.primary">View on GitHub</a>
@@ -610,6 +631,7 @@
<span class="badge" data-i18n="hero.badge1">On-device ASR</span>
<span class="badge" data-i18n="hero.badge2">Bring your own API</span>
<span class="badge" data-i18n="hero.badge3">Zero dependencies</span>
<span class="badge" data-i18n="hero.badge4">Flow session · 60s / take</span>
</div>
</div>
@@ -649,8 +671,8 @@
<div class="features">
<article class="feature-card">
<div class="feature-icon" aria-hidden="true">🎙</div>
<h3 data-i18n="features.f1.title">Push-to-talk</h3>
<p data-i18n="features.f1.body">Press and hold the mic key, speak naturally, release — polished text lands at your cursor.</p>
<h3 data-i18n="features.f1.title">Tap-to-toggle, 60s cap</h3>
<p data-i18n="features.f1.body">Tap the mic to start, speak naturally, tap again to stop. A live countdown keeps you honest at 60 seconds per take.</p>
</article>
<article class="feature-card">
<div class="feature-icon" aria-hidden="true">🧠</div>
@@ -677,6 +699,16 @@
<h3 data-i18n="features.f6.title">Lean & native</h3>
<p data-i18n="features.f6.body">Pure Swift 6 and SwiftUI — no CocoaPods, no Carthage, no third-party packages.</p>
</article>
<article class="feature-card">
<div class="feature-icon" aria-hidden="true">🔁</div>
<h3 data-i18n="features.f7.title">Flow session</h3>
<p data-i18n="features.f7.body">A long-lived host-app session keeps the recorder warm. Record many takes without bouncing back to the app.</p>
</article>
<article class="feature-card">
<div class="feature-icon" aria-hidden="true">🧩</div>
<h3 data-i18n="features.f8.title">Local + cloud polish</h3>
<p data-i18n="features.f8.body">Local mode is ASR-only. Enable cloud polish to route the transcript through your chosen LLM when iOS speech recognition isn't strong enough.</p>
</article>
</div>
</div>
</section>
@@ -689,16 +721,24 @@
</div>
<div class="steps">
<article class="step-card">
<h3 data-i18n="steps.s1.title">Enable the keyboard</h3>
<p data-i18n="steps.s1.body">Install from source, turn on OSGKeyboard in iOS Settings, and allow Full Access for mic + your API.</p>
<h3 data-i18n="steps.s1.title">Open the app</h3>
<p data-i18n="steps.s1.body">Build from source and launch. The 5-step onboarding starts the Flow session automatically once permissions are granted.</p>
</article>
<article class="step-card">
<h3 data-i18n="steps.s2.title">Hold the mic</h3>
<p data-i18n="steps.s2.body">Switch to OSGKeyboard in any text field, press and hold the circular record button, and speak.</p>
<h3 data-i18n="steps.s2.title">Allow mic & speech</h3>
<p data-i18n="steps.s2.body">Grant microphone and on-device speech recognition access when prompted. Both stay on-device.</p>
</article>
<article class="step-card">
<h3 data-i18n="steps.s3.title">Release to insert</h3>
<p data-i18n="steps.s3.body">On-device transcription runs instantly; optional LLM polish returns clean text right where you type.</p>
<h3 data-i18n="steps.s3.title">Enable the keyboard</h3>
<p data-i18n="steps.s3.body">In iOS Settings, add OSGKeyboard and allow Full Access so it can use the mic and your API.</p>
</article>
<article class="step-card">
<h3 data-i18n="steps.s4.title">Pick engine + API</h3>
<p data-i18n="steps.s4.body">Choose local ASR or cloud. For cloud polish, paste any OpenAI-compatible key (DeepSeek works out of the box).</p>
</article>
<article class="step-card">
<h3 data-i18n="steps.s5.title">Tap to talk</h3>
<p data-i18n="steps.s5.body">Switch to OSGKeyboard in any text field, tap the mic, speak (up to 60s), tap again. Polished text lands at your cursor.</p>
</article>
</div>
</div>
@@ -719,11 +759,13 @@
<footer class="footer">
<div class="footer-inner">
<span data-i18n="footer.copy">© OSGKeyboard source available</span>
<span data-i18n="footer.copy">© OSGKeyboard · v0.2.1 · source available, non-commercial</span>
<span>
<a href="https://github.com/hkgood/OSGKeyboard" data-i18n="footer.source">Source code</a>
·
<a href="privacy/" data-i18n="footer.privacy">Privacy</a>
·
<a href="https://github.com/hkgood/OSGKeyboard/blob/main/LICENSE" data-i18n="footer.license">License</a>
</span>
</div>
</footer>
@@ -733,86 +775,106 @@
en: {
"nav.github": "GitHub",
"hero.eyebrow": "Source available · iOS 26+",
"hero.title": "Hold to talk.<br>Polished text in any app.",
"hero.lead": "A custom keyboard that transcribes your voice on-device and optionally polishes the result with the LLM you configure WeChat, Notes, Mail, ChatGPT, and everywhere else you type.",
"hero.title": "Tap to talk.<br>Polished text in any app.",
"hero.lead": "A custom keyboard that transcribes your voice on-device and optionally polishes the result with the LLM you configure. Works in WeChat, Notes, Mail, ChatGPT everywhere you type. Up to 60 seconds per take, with a long-lived Flow session so you never bounce back to the host app.",
"hero.cta.primary": "View on GitHub",
"hero.cta.secondary": "Privacy Policy",
"hero.badge1": "On-device ASR",
"hero.badge2": "Bring your own API",
"hero.badge3": "Zero dependencies",
"hero.badge4": "Flow session · 60s / take",
"mock.title": "Notes",
"mock.sub": "OSGKeyboard active",
"mock.sub": "OSGKeyboard active · Flow session",
"mock.text": "Tomorrow at 3 PM, sync with the design team on the new onboarding flow. Send the draft before lunch.",
"features.title": "Built for real typing workflows",
"features.subtitle": "Native SwiftUI, privacy-first architecture, and a Typeless-style push-to-talk experience.",
"features.f1.title": "Push-to-talk",
"features.f1.body": "Press and hold the mic key, speak naturally, release — polished text lands at your cursor.",
"features.subtitle": "Native SwiftUI, privacy-first architecture, and a tap-to-toggle Flow session model.",
"features.f1.title": "Tap-to-toggle, 60s cap",
"features.f1.body": "Tap the mic to start, speak naturally, tap again to stop. A live countdown keeps you honest at 60 seconds per take.",
"features.f2.title": "On-device ASR",
"features.f2.body": "Speech stays on your iPhone. Apple's on-device speech stack handles transcription — audio never leaves the device.",
"features.f2.body": "Speech stays on your iPhone. Apple's iOS 26 SpeechAnalyzer + DictationTranscriber handle transcription — audio never leaves the device.",
"features.f3.title": "AI polish",
"features.f3.body": "Optional cloud pass adds punctuation, structure, and clarity. Only the transcript is sent — not your voice.",
"features.f4.title": "Bring your own API",
"features.f4.body": "Works with any OpenAI-compatible endpoint: OpenAI, DeepSeek, Qwen DashScope, or your own server.",
"features.f4.body": "Works with any OpenAI-compatible endpoint: OpenAI, DeepSeek, Qwen DashScope, Moonshot, Zhipu, or your own server.",
"features.f5.title": "Privacy first",
"features.f5.body": "No ordinary keystroke logging. You choose when cloud polish runs and which provider receives text.",
"features.f6.title": "Lean & native",
"features.f6.body": "Pure Swift 6 and SwiftUI — no CocoaPods, no Carthage, no third-party packages.",
"steps.title": "How it works",
"steps.subtitle": "Three gestures between thought and sent message.",
"steps.s1.title": "Enable the keyboard",
"steps.s1.body": "Install from source, turn on OSGKeyboard in iOS Settings, and allow Full Access for mic + your API.",
"steps.s2.title": "Hold the mic",
"steps.s2.body": "Switch to OSGKeyboard in any text field, press and hold the circular record button, and speak.",
"steps.s3.title": "Release to insert",
"steps.s3.body": "On-device transcription runs instantly; optional LLM polish returns clean text right where you type.",
"features.f7.title": "Flow session",
"features.f7.body": "A long-lived host-app session keeps the recorder warm. Record many takes without bouncing back to the app.",
"features.f8.title": "Local + cloud polish",
"features.f8.body": "Local mode is ASR-only. Enable cloud polish to route the transcript through your chosen LLM when iOS speech recognition isn't strong enough.",
"steps.title": "Get started in five steps",
"steps.subtitle": "From clone to first polished message — about three minutes.",
"steps.s1.title": "Open the app",
"steps.s1.body": "Build from source and launch. The 5-step onboarding starts the Flow session automatically once permissions are granted.",
"steps.s2.title": "Allow mic & speech",
"steps.s2.body": "Grant microphone and on-device speech recognition access when prompted. Both stay on-device.",
"steps.s3.title": "Enable the keyboard",
"steps.s3.body": "In iOS Settings, add OSGKeyboard and allow Full Access so it can use the mic and your API.",
"steps.s4.title": "Pick engine + API",
"steps.s4.body": "Choose local ASR or cloud. For cloud polish, paste any OpenAI-compatible key (DeepSeek works out of the box).",
"steps.s5.title": "Tap to talk",
"steps.s5.body": "Switch to OSGKeyboard in any text field, tap the mic, speak (up to 60s), tap again. Polished text lands at your cursor.",
"privacy.title": "Privacy at a glance",
"privacy.body": "Audio is processed on your device. We do not log ordinary keystrokes. In cloud polish mode, only transcribed text — never audio — goes to the API provider you configure.",
"privacy.cta": "Read full policy",
"footer.copy": "© OSGKeyboard source available",
"footer.copy": "© OSGKeyboard · v0.2.1 · source available, non-commercial",
"footer.source": "Source code",
"footer.privacy": "Privacy"
"footer.privacy": "Privacy",
"footer.license": "License"
},
zh: {
"nav.github": "GitHub",
"hero.eyebrow": "源码可见 · 需要 iOS 26+",
"hero.title": "按说话,<br>任意 App 里获得润色文字。",
"hero.lead": "自定义键盘扩展:在设备端转写语音,并可选通过你配置的 LLM 润色结果。适用于微信、备忘录、邮件、ChatGPT 等所有出现键盘的场景。",
"hero.title": "按说话,<br>任意 App 里获得润色文字。",
"hero.lead": "自定义键盘扩展:在设备端转写语音,并可选通过你配置的 LLM 润色结果。适用于微信、备忘录、邮件、ChatGPT 等所有出现键盘的场景。单次录音上限 60 秒,配合长生命周期的 Flow 会话,多次录音无需反复跳回主 App。",
"hero.cta.primary": "GitHub 仓库",
"hero.cta.secondary": "隐私政策",
"hero.badge1": "设备端语音识别",
"hero.badge2": "自带 API",
"hero.badge3": "零第三方依赖",
"hero.badge4": "Flow 会话 · 单次 60s",
"mock.title": "备忘录",
"mock.sub": "OSGKeyboard 已启用",
"mock.sub": "OSGKeyboard 已启用 · Flow 会话",
"mock.text": "明天下午三点和设计团队同步新引导流程,午饭前把草稿发过去。",
"features.title": "为真实输入场景设计",
"features.subtitle": "原生 SwiftUI、隐私优先架构,以及 Typeless 风格的按住说话体验。",
"features.f1.title": "按住说话",
"features.f1.body": "按住麦克风键自然说话,松手后润色文字直接插入光标位置。",
"features.subtitle": "原生 SwiftUI、隐私优先架构,以及点按切换的 Flow 会话模型。",
"features.f1.title": "点按录音 · 单次 60 秒",
"features.f1.body": "点一下麦克风开始说话,再点一下结束。60 秒倒计时实时显示,长按也不会被卡住。",
"features.f2.title": "设备端转写",
"features.f2.body": "语音留在 iPhone 本地处理,由系统端侧语音能力完成转写,音频不会上传。",
"features.f2.body": "语音留在 iPhone 本地处理,由 iOS 26 SpeechAnalyzer + DictationTranscriber 完成转写,音频不会上传。",
"features.f3.title": "AI 润色",
"features.f3.body": "可选云端润色,自动补全标点与结构。仅发送转写文字,不发送录音。",
"features.f4.title": "自带 API",
"features.f4.body": "兼容 OpenAI 风格接口:OpenAI、DeepSeek、通义 DashScope 或自建服务均可。",
"features.f4.body": "兼容 OpenAI 风格接口:OpenAI、DeepSeek、通义 DashScope、Moonshot、智谱 或自建服务均可。",
"features.f5.title": "隐私优先",
"features.f5.body": "不记录普通击键。是否启用云端润色、发给哪家服务商,完全由你决定。",
"features.f6.title": "轻量原生",
"features.f6.body": "纯 Swift 6 + SwiftUI 实现,无 CocoaPods、无 Carthage、无第三方包。",
"steps.title": "如何使用",
"steps.subtitle": "三个步骤,从想法到可发送的文字。",
"steps.s1.title": "启用键盘",
"steps.s1.body": "从源码安装后,在 iOS 设置中启用 OSGKeyboard,并允许完全访问(麦克风与 API 所需)。",
"steps.s2.title": "按住麦克风",
"steps.s2.body": "在任意输入框切换到 OSGKeyboard,按住圆形录音键开始说话。",
"steps.s3.title": "松手插入",
"steps.s3.body": "设备端即时转写;若开启润色,LLM 返回整洁文字并插入当前光标处。",
"features.f7.title": "Flow 会话",
"features.f7.body": "主 App 维持长生命周期录音会话,多次录音无需跳回主 App,体验更连贯。",
"features.f8.title": "本地 + 云端润色",
"features.f8.body": "本地模式仅做 ASR;当 iOS 识别效果不理想时,可一键开启云端润色走你配置的 LLM。",
"steps.title": "五步上手",
"steps.subtitle": "从克隆到发出第一条润色消息,约 3 分钟。",
"steps.s1.title": "打开 App",
"steps.s1.body": "从源码编译运行 App,权限齐全后 5 步引导会自动启动 Flow 会话。",
"steps.s2.title": "授权麦克风与语音",
"steps.s2.body": "按提示授予麦克风与端侧语音识别权限,二者均在设备端处理。",
"steps.s3.title": "启用键盘",
"steps.s3.body": "在 iOS 设置中添加 OSGKeyboard,并允许完全访问(麦克风与 API 所需)。",
"steps.s4.title": "选择引擎与 API",
"steps.s4.body": "选择本地或云端引擎;云端润色需粘贴 OpenAI 兼容 KeyDeepSeek 开箱即用)。",
"steps.s5.title": "点按说话",
"steps.s5.body": "在任意输入框切换到 OSGKeyboard,点麦克风开始(上限 60 秒),再点结束,文字自动插入光标。",
"privacy.title": "隐私摘要",
"privacy.body": "音频在设备端处理。我们不记录普通击键。云端润色模式下,仅转写文字(非音频)会发送到你配置的 API 服务商。",
"privacy.cta": "查看完整政策",
"footer.copy": "© OSGKeyboard · 源码可见",
"footer.copy": "© OSGKeyboard · v0.2.1 · 源码可见,禁止商用",
"footer.source": "源代码",
"footer.privacy": "隐私"
"footer.privacy": "隐私",
"footer.license": "许可协议"
}
};
+54 -28
View File
@@ -15,76 +15,102 @@
<body>
<p class="lang"><a href="#zh">中文</a></p>
<h1>OSGKeyboard Privacy Policy</h1>
<p><strong>Last updated:</strong> June 19, 2026</p>
<p>OSGKeyboard is a custom iOS keyboard that turns your voice into text. This policy explains what data the app processes and how it is used.</p>
<p><strong>Last updated:</strong> July 3, 2026 · <em>v0.2.1</em></p>
<p>OSGKeyboard is a custom iOS keyboard that turns your voice into text. It runs as a Custom Keyboard Extension on iOS 26 and later, and uses Apple's on-device <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code> for transcription. This policy explains what data the app processes and how it is used.</p>
<h2>What we collect</h2>
<ul>
<li><strong>Voice audio</strong> — captured only while you actively record. On-device mode transcribes locally with Apples speech APIs; raw audio is not uploaded by OSGKeyboard.</li>
<li><strong>Transcribed text</strong> — in Cloud polish mode, the final text (not audio) may be sent to the LLM provider you configure (e.g. OpenAI) for punctuation and formatting.</li>
<li><strong>API credentials</strong> — stored in the iOS Keychain on your device and shared only between the main app and keyboard extension via an App Group.</li>
<li><strong>App preferences</strong> — engine mode, language, and keyboard settings stored in App Group UserDefaults on your device.</li>
<li><strong>Voice audio</strong> — captured only while you actively record. In the local engine, audio is transcribed on-device with Apple's <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code>; raw audio is not uploaded by OSGKeyboard.</li>
<li><strong>Transcribed text</strong> — in Cloud polish mode, the final text (not audio) is sent to the LLM provider you configure (e.g. DeepSeek) for punctuation and formatting. The local engine is ASR-only by default; you must explicitly opt in to cloud polish.</li>
<li><strong>API credentials</strong> your LLM API key is stored in the iOS Keychain on your device and is read by the keyboard extension only at the moment it makes an LLM request. It is shared with the main app through a shared Keychain group, never through UserDefaults.</li>
<li><strong>App preferences</strong> — engine mode, language, on-device model choices, and keyboard settings are stored in App Group <code>UserDefaults</code> on your device so the main app and keyboard extension can stay in sync.</li>
<li><strong>Voice history</strong> — the host app may keep a local list of recent successful transcripts in its History tab, on-device only, capped at 500 entries.</li>
</ul>
<h2>What we do not collect</h2>
<ul>
<li>We do <strong>not</strong> log or upload ordinary keystrokes you type with the keyboard.</li>
<li>We do <strong>not</strong> operate analytics or advertising SDKs.</li>
<li>We do <strong>not</strong> operate analytics, crash reporting, or advertising SDKs.</li>
<li>We do <strong>not</strong> upload raw audio to any server, including the LLM provider.</li>
<li>We do <strong>not</strong> sell personal data.</li>
</ul>
<h2>How the keyboard extension talks to the host app</h2>
<p>OSGKeyboard uses a long-lived "Flow session" hosted in the main app. The keyboard extension writes tiny "start / stop" signals into an App Group, the main app processes the audio and writes the polished transcript back. Nothing leaves your device unless you've explicitly enabled cloud polish, in which case only the transcript (never the audio) is sent to the LLM provider you chose.</p>
<h2>Permissions</h2>
<ul>
<li><strong>Microphone</strong> — required for voice input and background voice sessions.</li>
<li><strong>Speech recognition</strong> — required for on-device transcription.</li>
<li><strong>Full Access</strong> — required so the keyboard can reach the microphone, read your API key, and communicate with the main app. Full Access does not grant us access to everything you type; we do not exfiltrate keystrokes.</li>
<li><strong>Microphone</strong> (<code>NSMicrophoneUsageDescription</code>) — required for voice input and the long-lived voice session.</li>
<li><strong>Speech recognition</strong> (<code>NSSpeechRecognitionUsageDescription</code>) — required for on-device transcription via Apple's APIs.</li>
<li><strong>Full Access</strong> — required so the keyboard can reach the microphone, read your API key from the shared Keychain, and request the polished transcript from the host app. Full Access does <em>not</em> grant us access to everything you type; we do not exfiltrate keystrokes.</li>
</ul>
<h2>Third parties</h2>
<p>When you choose Cloud polish mode, transcribed text is sent to the API endpoint you configure. That providers privacy policy applies to those requests.</p>
<p>When you enable cloud polish, transcribed text is sent to the OpenAI-compatible API endpoint you configured in Settings (e.g. OpenAI, DeepSeek, Qwen DashScope, Moonshot, Zhipu, or your own self-hosted server). That provider's privacy policy applies to those requests. OSGKeyboard does not proxy, log, or aggregate your requests.</p>
<h2>Data retention</h2>
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard.</p>
<p><strong>Voice history</strong> — successful transcripts may be saved locally in the main apps History tab for your convenience. This history stays on your device only, is never uploaded, and can be cleared at any time from History or by resetting settings.</p>
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard. Voice history is kept locally (up to 500 entries) and is never uploaded; you can clear it at any time from the History tab or by resetting settings.</p>
<h2>Children's privacy</h2>
<p>OSGKeyboard is not directed to children under 13 and does not knowingly collect personal data from children.</p>
<h2>Changes to this policy</h2>
<p>We may update this policy as the app evolves. The "Last updated" date at the top will always reflect the current version. Material changes will be called out in the project <a href="https://github.com/hkgood/OSGKeyboard/blob/main/CHANGELOG.md">CHANGELOG</a>.</p>
<h2>License</h2>
<p>OSGKeyboard is distributed under the <a href="https://github.com/hkgood/OSGKeyboard/blob/main/LICENSE">OSGKeyboard Source Available License</a> — personal learning and non-commercial local use only. No commercial use, redistribution, or public forks without permission.</p>
<h2>Contact</h2>
<p>Questions: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>.</p>
<p>Questions or privacy requests: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>, or email <a href="mailto:rocky.hk@gmail.com">rocky.hk@gmail.com</a>.</p>
<hr id="zh">
<h1>OSGKeyboard 隐私政策</h1>
<p><strong>更新日期:</strong>2026 年 619 日</p>
<p>OSGKeyboard 是一款 iOS 自定义键盘,可将语音转为文字。本政策说明应用处理哪些数据及用途。</p>
<p><strong>更新日期:</strong>2026 年 73 日 · <em>v0.2.1</em></p>
<p>OSGKeyboard 是一款 iOS 自定义键盘,可将语音转为文字。它以自定义键盘扩展的形式运行,需要 iOS 26 及以上系统,转写全程使用 Apple 端侧的 <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code>本政策说明应用处理哪些数据及用途。</p>
<h2>我们处理的数据</h2>
<ul>
<li><strong>语音音频</strong> — 仅在你主动录音时采集。本地模式在设备端通过 Apple 语音识别转写,OSGKeyboard 不会上传原始录音。</li>
<li><strong>转写文字</strong> — 云端润色模式下,最终文字(非音频)可能发送到你配置的 LLM 服务商以整理标点和格式</li>
<li><strong>API 凭证</strong> — 保存在设备 Keychain仅通过 App Group 在主 App 与键盘扩展间共享</li>
<li><strong>应用偏好</strong> — 引擎、语言设置保存在设备 App Group </li>
<li><strong>语音音频</strong> — 仅在你主动录音时采集。本地模式下音频在设备端通过 <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code> 转写,OSGKeyboard 不会上传原始录音。</li>
<li><strong>转写文字</strong> — 云端润色模式下,最终文字(非音频)发送到你配置的 LLM 服务商(默认 DeepSeek)以整理标点和格式。本地引擎默认仅做 ASR,云端润色需显式开启</li>
<li><strong>API 凭证</strong> LLM API Key 保存在设备 Keychain键盘扩展仅在每次发起 LLM 请求时读取;通过共享 Keychain 组与主 App 共享,<strong>不会</strong>写入 <code>UserDefaults</code></li>
<li><strong>应用偏好</strong> — 引擎模式、语言、端侧模型选项、键盘设置保存在 App Group <code>UserDefaults</code>,仅用于主 App 与键盘扩展之间的状态同步</li>
<li><strong>语音历史</strong> — 主 App 可在「历史」页保留近期成功转写,最多 500 条,仅本机保存,不会上传。</li>
</ul>
<h2>我们不收集的内容</h2>
<ul>
<li>我们<strong>不会</strong>记录或上传你平时在键盘上的击键内容。</li>
<li>我们<strong>不会</strong>集成广告或第三方分析 SDK。</li>
<li>我们<strong>不会</strong>集成分析、崩溃上报或广告 SDK。</li>
<li>我们<strong>不会</strong>将原始录音上传至任何服务器,包括你配置的 LLM 服务商。</li>
<li>我们<strong>不会</strong>出售个人数据。</li>
</ul>
<h2>键盘扩展与主 App 的通信方式</h2>
<p>OSGKeyboard 采用主 App 维护的「Flow 会话」机制:键盘扩展在 App Group 中写入轻量的「开始 / 停止」信号,主 App 负责处理音频,润色完成后将文字回写到 App Group,键盘扩展再读取并插入。整条链路不会让数据离开你的设备,除非你显式开启云端润色 —— 此时发送的也只是转写文字,不包含录音。</p>
<h2>权限说明</h2>
<ul>
<li><strong>麦克风</strong> — 语音输入与后台语音会话所需。</li>
<li><strong>语音识别</strong> — 端侧转写所需。</li>
<li><strong>完全访问</strong> — 使键盘能使用麦克风、读取 API Key 并与主 App 通信。完全访问不代表我们会收集全部击键内容</li>
<li><strong>麦克风</strong><code>NSMicrophoneUsageDescription</code>— 语音输入与长生命周期语音会话所需。</li>
<li><strong>语音识别</strong><code>NSSpeechRecognitionUsageDescription</code>)— 调用 Apple 端侧语音能力进行转写所需。</li>
<li><strong>完全访问</strong> — 使键盘能使用麦克风、从共享 Keychain 读取 API Key,并从主 App 获取润色结果。完全访问<strong></strong>代表我们会获取你输入的所有内容,绝不外传击键</li>
</ul>
<h2>第三方</h2>
<p>选择云端润色时,转写文字会发往你配置的 API,该服务商的隐私政策适用于相关请求。</p>
<p>开启云端润色时,转写文字会发往你在设置中配置的 OpenAI 兼容 API 端点(OpenAI / DeepSeek / 通义 DashScope / Moonshot / 智谱 / 自建服务 等),该服务商的隐私政策适用于相关请求。OSGKeyboard 不代理、不记录、不聚合这些请求。</p>
<h2>数据保留</h2>
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。</p>
<p><strong>语音历史</strong> — 成功的转写可能保存在主 App「历史」页,仅供本机查看,不会上传,可随时在历史页清空或通过重置设置清除。</p>
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。语音历史最多 500 条本机保存,不会上传,可随时在「历史」页清空或通过重置设置清除。</p>
<h2>儿童隐私</h2>
<p>OSGKeyboard 不面向 13 岁以下儿童,亦不会明知地从儿童处收集个人信息。</p>
<h2>政策变更</h2>
<p>本政策可能随应用演进而更新,更新日期始终显示在文首。重大变更会在项目 <a href="https://github.com/hkgood/OSGKeyboard/blob/main/CHANGELOG.md">CHANGELOG</a> 中标注。</p>
<h2>许可协议</h2>
<p>OSGKeyboard 基于 <a href="https://github.com/hkgood/OSGKeyboard/blob/main/LICENSE">OSGKeyboard 源码可见许可协议</a> 发布 —— 仅限个人学习与非商用本地使用;禁止商用、再分发及公开 fork。</p>
<h2>联系</h2>
<p>问题反馈:<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a></p>
<p>问题反馈或隐私相关请求<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a> 提 Issue,或邮件 <a href="mailto:rocky.hk@gmail.com">rocky.hk@gmail.com</a></p>
</body>
</html>
+54 -28
View File
@@ -16,76 +16,102 @@
<body>
<p class="lang"><a href="#zh">中文</a></p>
<h1>OSGKeyboard Privacy Policy</h1>
<p><strong>Last updated:</strong> June 19, 2026</p>
<p>OSGKeyboard is a custom iOS keyboard that turns your voice into text. This policy explains what data the app processes and how it is used.</p>
<p><strong>Last updated:</strong> July 3, 2026 · <em>v0.2.1</em></p>
<p>OSGKeyboard is a custom iOS keyboard that turns your voice into text. It runs as a Custom Keyboard Extension on iOS 26 and later, and uses Apple's on-device <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code> for transcription. This policy explains what data the app processes and how it is used.</p>
<h2>What we collect</h2>
<ul>
<li><strong>Voice audio</strong> — captured only while you actively record. On-device mode transcribes locally with Apples speech APIs; raw audio is not uploaded by OSGKeyboard.</li>
<li><strong>Transcribed text</strong> — in Cloud polish mode, the final text (not audio) may be sent to the LLM provider you configure (e.g. OpenAI) for punctuation and formatting.</li>
<li><strong>API credentials</strong> — stored in the iOS Keychain on your device and shared only between the main app and keyboard extension via an App Group.</li>
<li><strong>App preferences</strong> — engine mode, language, and keyboard settings stored in App Group UserDefaults on your device.</li>
<li><strong>Voice audio</strong> — captured only while you actively record. In the local engine, audio is transcribed on-device with Apple's <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code>; raw audio is not uploaded by OSGKeyboard.</li>
<li><strong>Transcribed text</strong> — in Cloud polish mode, the final text (not audio) is sent to the LLM provider you configure (e.g. DeepSeek) for punctuation and formatting. The local engine is ASR-only by default; you must explicitly opt in to cloud polish.</li>
<li><strong>API credentials</strong> your LLM API key is stored in the iOS Keychain on your device and is read by the keyboard extension only at the moment it makes an LLM request. It is shared with the main app through a shared Keychain group, never through UserDefaults.</li>
<li><strong>App preferences</strong> — engine mode, language, on-device model choices, and keyboard settings are stored in App Group <code>UserDefaults</code> on your device so the main app and keyboard extension can stay in sync.</li>
<li><strong>Voice history</strong> — the host app may keep a local list of recent successful transcripts in its History tab, on-device only, capped at 500 entries.</li>
</ul>
<h2>What we do not collect</h2>
<ul>
<li>We do <strong>not</strong> log or upload ordinary keystrokes you type with the keyboard.</li>
<li>We do <strong>not</strong> operate analytics or advertising SDKs.</li>
<li>We do <strong>not</strong> operate analytics, crash reporting, or advertising SDKs.</li>
<li>We do <strong>not</strong> upload raw audio to any server, including the LLM provider.</li>
<li>We do <strong>not</strong> sell personal data.</li>
</ul>
<h2>How the keyboard extension talks to the host app</h2>
<p>OSGKeyboard uses a long-lived "Flow session" hosted in the main app. The keyboard extension writes tiny "start / stop" signals into an App Group, the main app processes the audio and writes the polished transcript back. Nothing leaves your device unless you've explicitly enabled cloud polish, in which case only the transcript (never the audio) is sent to the LLM provider you chose.</p>
<h2>Permissions</h2>
<ul>
<li><strong>Microphone</strong> — required for voice input and background voice sessions.</li>
<li><strong>Speech recognition</strong> — required for on-device transcription.</li>
<li><strong>Full Access</strong> — required so the keyboard can reach the microphone, read your API key, and communicate with the main app. Full Access does not grant us access to everything you type; we do not exfiltrate keystrokes.</li>
<li><strong>Microphone</strong> (<code>NSMicrophoneUsageDescription</code>) — required for voice input and the long-lived voice session.</li>
<li><strong>Speech recognition</strong> (<code>NSSpeechRecognitionUsageDescription</code>) — required for on-device transcription via Apple's APIs.</li>
<li><strong>Full Access</strong> — required so the keyboard can reach the microphone, read your API key from the shared Keychain, and request the polished transcript from the host app. Full Access does <em>not</em> grant us access to everything you type; we do not exfiltrate keystrokes.</li>
</ul>
<h2>Third parties</h2>
<p>When you choose Cloud polish mode, transcribed text is sent to the API endpoint you configure. That providers privacy policy applies to those requests.</p>
<p>When you enable cloud polish, transcribed text is sent to the OpenAI-compatible API endpoint you configured in Settings (e.g. OpenAI, DeepSeek, Qwen DashScope, Moonshot, Zhipu, or your own self-hosted server). That provider's privacy policy applies to those requests. OSGKeyboard does not proxy, log, or aggregate your requests.</p>
<h2>Data retention</h2>
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard.</p>
<p><strong>Voice history</strong> — successful transcripts may be saved locally in the main apps History tab for your convenience. This history stays on your device only, is never uploaded, and can be cleared at any time from History or by resetting settings.</p>
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard. Voice history is kept locally (up to 500 entries) and is never uploaded; you can clear it at any time from the History tab or by resetting settings.</p>
<h2>Children's privacy</h2>
<p>OSGKeyboard is not directed to children under 13 and does not knowingly collect personal data from children.</p>
<h2>Changes to this policy</h2>
<p>We may update this policy as the app evolves. The "Last updated" date at the top will always reflect the current version. Material changes will be called out in the project <a href="https://github.com/hkgood/OSGKeyboard/blob/main/CHANGELOG.md">CHANGELOG</a>.</p>
<h2>License</h2>
<p>OSGKeyboard is distributed under the <a href="https://github.com/hkgood/OSGKeyboard/blob/main/LICENSE">OSGKeyboard Source Available License</a> — personal learning and non-commercial local use only. No commercial use, redistribution, or public forks without permission.</p>
<h2>Contact</h2>
<p>Questions: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>.</p>
<p>Questions or privacy requests: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>, or email <a href="mailto:rocky.hk@gmail.com">rocky.hk@gmail.com</a>.</p>
<hr id="zh">
<h1>OSGKeyboard 隐私政策</h1>
<p><strong>更新日期:</strong>2026 年 619 日</p>
<p>OSGKeyboard 是一款 iOS 自定义键盘,可将语音转为文字。本政策说明应用处理哪些数据及用途。</p>
<p><strong>更新日期:</strong>2026 年 73 日 · <em>v0.2.1</em></p>
<p>OSGKeyboard 是一款 iOS 自定义键盘,可将语音转为文字。它以自定义键盘扩展的形式运行,需要 iOS 26 及以上系统,转写全程使用 Apple 端侧的 <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code>本政策说明应用处理哪些数据及用途。</p>
<h2>我们处理的数据</h2>
<ul>
<li><strong>语音音频</strong> — 仅在你主动录音时采集。本地模式在设备端通过 Apple 语音识别转写,OSGKeyboard 不会上传原始录音。</li>
<li><strong>转写文字</strong> — 云端润色模式下,最终文字(非音频)可能发送到你配置的 LLM 服务商以整理标点和格式</li>
<li><strong>API 凭证</strong> — 保存在设备 Keychain仅通过 App Group 在主 App 与键盘扩展间共享</li>
<li><strong>应用偏好</strong> — 引擎、语言设置保存在设备 App Group </li>
<li><strong>语音音频</strong> — 仅在你主动录音时采集。本地模式下音频在设备端通过 <code>SpeechAnalyzer</code> + <code>DictationTranscriber</code> 转写,OSGKeyboard 不会上传原始录音。</li>
<li><strong>转写文字</strong> — 云端润色模式下,最终文字(非音频)发送到你配置的 LLM 服务商(默认 DeepSeek)以整理标点和格式。本地引擎默认仅做 ASR,云端润色需显式开启</li>
<li><strong>API 凭证</strong> LLM API Key 保存在设备 Keychain键盘扩展仅在每次发起 LLM 请求时读取;通过共享 Keychain 组与主 App 共享,<strong>不会</strong>写入 <code>UserDefaults</code></li>
<li><strong>应用偏好</strong> — 引擎模式、语言、端侧模型选项、键盘设置保存在 App Group <code>UserDefaults</code>,仅用于主 App 与键盘扩展之间的状态同步</li>
<li><strong>语音历史</strong> — 主 App 可在「历史」页保留近期成功转写,最多 500 条,仅本机保存,不会上传。</li>
</ul>
<h2>我们不收集的内容</h2>
<ul>
<li>我们<strong>不会</strong>记录或上传你平时在键盘上的击键内容。</li>
<li>我们<strong>不会</strong>集成广告或第三方分析 SDK。</li>
<li>我们<strong>不会</strong>集成分析、崩溃上报或广告 SDK。</li>
<li>我们<strong>不会</strong>将原始录音上传至任何服务器,包括你配置的 LLM 服务商。</li>
<li>我们<strong>不会</strong>出售个人数据。</li>
</ul>
<h2>键盘扩展与主 App 的通信方式</h2>
<p>OSGKeyboard 采用主 App 维护的「Flow 会话」机制:键盘扩展在 App Group 中写入轻量的「开始 / 停止」信号,主 App 负责处理音频,润色完成后将文字回写到 App Group,键盘扩展再读取并插入。整条链路不会让数据离开你的设备,除非你显式开启云端润色 —— 此时发送的也只是转写文字,不包含录音。</p>
<h2>权限说明</h2>
<ul>
<li><strong>麦克风</strong> — 语音输入与后台语音会话所需。</li>
<li><strong>语音识别</strong> — 端侧转写所需。</li>
<li><strong>完全访问</strong> — 使键盘能使用麦克风、读取 API Key 并与主 App 通信。完全访问不代表我们会收集全部击键内容</li>
<li><strong>麦克风</strong><code>NSMicrophoneUsageDescription</code>— 语音输入与长生命周期语音会话所需。</li>
<li><strong>语音识别</strong><code>NSSpeechRecognitionUsageDescription</code>)— 调用 Apple 端侧语音能力进行转写所需。</li>
<li><strong>完全访问</strong> — 使键盘能使用麦克风、从共享 Keychain 读取 API Key,并从主 App 获取润色结果。完全访问<strong></strong>代表我们会获取你输入的所有内容,绝不外传击键</li>
</ul>
<h2>第三方</h2>
<p>选择云端润色时,转写文字会发往你配置的 API,该服务商的隐私政策适用于相关请求。</p>
<p>开启云端润色时,转写文字会发往你在设置中配置的 OpenAI 兼容 API 端点(OpenAI / DeepSeek / 通义 DashScope / Moonshot / 智谱 / 自建服务 等),该服务商的隐私政策适用于相关请求。OSGKeyboard 不代理、不记录、不聚合这些请求。</p>
<h2>数据保留</h2>
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。</p>
<p><strong>语音历史</strong> — 成功的转写可能保存在主 App「历史」页,仅供本机查看,不会上传,可随时在历史页清空或通过重置设置清除。</p>
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。语音历史最多 500 条本机保存,不会上传,可随时在「历史」页清空或通过重置设置清除。</p>
<h2>儿童隐私</h2>
<p>OSGKeyboard 不面向 13 岁以下儿童,亦不会明知地从儿童处收集个人信息。</p>
<h2>政策变更</h2>
<p>本政策可能随应用演进而更新,更新日期始终显示在文首。重大变更会在项目 <a href="https://github.com/hkgood/OSGKeyboard/blob/main/CHANGELOG.md">CHANGELOG</a> 中标注。</p>
<h2>许可协议</h2>
<p>OSGKeyboard 基于 <a href="https://github.com/hkgood/OSGKeyboard/blob/main/LICENSE">OSGKeyboard 源码可见许可协议</a> 发布 —— 仅限个人学习与非商用本地使用;禁止商用、再分发及公开 fork。</p>
<h2>联系</h2>
<p>问题反馈:<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a></p>
<p>问题反馈或隐私相关请求<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a> 提 Issue,或邮件 <a href="mailto:rocky.hk@gmail.com">rocky.hk@gmail.com</a></p>
</body>
</html>