4fec0da7f04b7457f0cd2b71693f4923791c8ad1
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9b759281cc | feat(translation-v2): refactor translationEnabled into computed + onboarding row visibility | ||
|
|
deddb49d56 |
feat(translation): add post-ASR translation mode for cloud engine
Adds an opt-in translation pipeline that reuses the existing PolishingService + LLMClient + AppGroupStore chain. Translation is implemented as a new PolishMode (.translate(targetLocaleId:)); all existing call sites are unchanged. Settings: - New TranslationPickerRow in the language tab (Toggle + 10-locale picker: en/zh-Hans/zh-Hant/ja/ko/fr/de/es/ru/pt), persisted to the App Group so the keyboard extension can read it during live dictation. - 5 new strings per language (en + zh-Hans). Keyboard: - New TranslationChip on the top bar to the right of LocaleChip; same Menu pattern, lets users toggle or quickly switch target language without leaving the keyboard. - PolishingService dispatches .translate with a parameterised prompt (en/zh variants selected by provider id); PolishingService.error gains a translationNotAvailable case so local-engine users get a clear inline warning when the toggle is on but cloud is off. - 6 new strings per language (en + zh-Hans) for the chip + banner. Local engine policy: - Translation is cloud-only by design (local engine stays ASR-only to honour the no-roundtrip promise). Chip shows a 'cloud required' state and raw transcript still inserts on failure — no data loss. Build: - OSGKeyboardShared adds TranslationLanguage enum (10 locales) and TranslationPrompt factory. - 4 new files, 9 modified. xcodebuild scheme=OSGKeyboard config=Debug destination=iPhone 17 Simulator: BUILD SUCCEEDED (0 warning, 0 error). Also pins DEVELOPMENT_TEAM in project.yml for TestFlight uploads (3 targets; Team X329MZU23S). |
||
|
|
c07cf4db9f |
refactor: drop Qwen3 CoreML ASR, add local-engine cloud polish toggle
Rolls back the v0.2.0 Qwen3 CoreML on-device ASR stack and replaces the
'local engine' UX with iOS 26 SpeechAnalyzer + DictationTranscriber only.
The 'Cloud polish after ASR' toggle (ProviderConfig.localModeCloudPolishEnabled)
lets users opt into a post-ASR DeepSeek round-trip from the local engine.
Defaults to off so the local engine stays genuinely local. New PolishError.missingAPIError
surfaces an inline 'fill in your key' warning when the toggle is on but the
Keychain is empty. DeepSeek preset default model bumped to deepseek-v4-flash.
Deleted:
- OSGKeyboard/ThirdParty/Qwen3Speech/ (74 files, ~16k LoC)
- OSGKeyboard/Services/ModelManager.swift (492)
- OSGKeyboard/Services/OnDeviceModelWarmup.swift (197)
- OSGKeyboard/Services/Qwen3ASRService.swift (257)
- OSGKeyboard/Services/ModelDownloadSourcePicker.swift (126)
- OSGKeyboard/Views/OnDeviceModelsView.swift (184)
- OSGKeyboard/Views/DownloadConfirmSheet.swift (96)
- OSGKeyboardShared/Models/OnDeviceModel.swift (140)
- OSGKeyboardShared/Services/OnDeviceModelStatus.swift (104)
- Qwen3ASRServiceProvider registration in OSGKeyboardApp
- Qwen3Speech package declaration in project.yml
- 5 .qwen3ASR enum / branch reference sites in HomeView, OnboardingView,
LocalEngineSettingsRows, FlowSessionManager, ASRService, EngineServiceLabel
- Two pre-existing Swift 6 strict-concurrency errors in
LiveDictationController + FlowSessionManager (the weak [weak self] in
detached-task MainActor.run blocks) that were blocking clean builds
Added:
- LocalModelsGroup: 'Built-in iOS SpeechAnalyzer' badge + 'Cloud polish
after ASR' Switch toggle
- PolishingService: honour localModeCloudPolishEnabled; new .missingAPIKey
error case with localised warning
- AppGroupStore.localModeCloudPolishEnabled (mirrored into App Group
so the keyboard extension honours the toggle during live dictation)
- SettingsView: show provider/api sections when local-mode cloud polish
is on so the user can paste a DeepSeek key
- FlowSessionManager: route through PolishingService for local + polish-on
flow; translate missingAPIKey into a polished warning
- KeyboardViewController: handle PolishingService.PolishError.missingAPIKey
in the keyboard-side live polish path
- CHANGELOG v0.2.1: documents the rollback + new toggle
- README.md / README.zh.md: engine matrix section, data flow note
Verified: xcodebuild -scheme OSGKeyboard -destination 'generic/platform=iOS Simulator'
build succeeds under SWIFT_STRICT_CONCURRENCY=complete.
|
||
|
|
df1c5ff32c |
feat: migrate on-device Qwen3 ASR to CoreML for background Flow dictation
Replace MLX GPU inference with CoreML bundles so transcription continues while the host app is backgrounded. Adds model download and warm-up, vendored Qwen3Speech, and updates onboarding, settings, and copy for the ~1.6 GB CoreML package (iOS 18+). |
||
|
|
7f059dbd45 |
feat: TypeWhisper Flow sessions, Phase 4 UX, and GitHub Pages privacy site
Migrate keyboard dictation to continuous Flow sessions with auto-start, tap-to-toggle recording, 60s countdown, five-step onboarding, and App Group IPC. Add docs/ GitHub Pages site with en/zh privacy policy for App Store compliance. |
||
|
|
81581f0e5f |
chore: drop iOS 18–25 / non-iPhone support, require iOS 26
Two related cleanups the user asked for in one shot:
1. iPhone-only is now enforced at every target — Mac Catalyst and
visionOS were never configured in `project.yml`, but the
`OSGKeyboardShared` framework and the two test bundles were
still defaulting to `TARGETED_DEVICE_FAMILY = "1,2"` (iPhone +
iPad). All four targets now explicitly set `"1"`. SDK is
`iphoneos` for everyone, no `xros` / `macosx`.
2. Deployment target bumped from iOS 18.0 to iOS 26.0 across the
board (`project.yml` + the ext's per-target setting). With
iOS 26 as the floor, the iOS 18–25 SFSpeechRecognizer path
became dead code and several `#available` checks became
always-true. Removed:
- `AppleSpeechASR` (the entire SFSpeechRecognizer-based ASR
backend) and the `#available(iOS 26.0, *)` factory branch.
`ASRServiceFactory.make()` now returns `SpeechAnalyzerASR()`
directly. SpeechAnalyzer is always fully on-device, which
also made the `requiresOnDevice` flag meaningless.
- `requiresOnDevice` from the `ASRService.transcribe` protocol
signature, from `ProviderConfig`, `AppGroupStore`,
`KeyboardState`, `AppGroupPersistor`, and the ext's
`KeyboardViewController` (`state.requiresOnDevice`,
`state.setRequiresOnDevice`, `persistRequiresOnDevice`).
- `#available(iOS 17.0, *)` branch in
`PreviewASRController.requestMicrophonePermission` and the
ext's `PermissionManager.requestMicPermission` — both now
just call the iOS 17+ `AVAudioApplication` API directly.
- The `else` (iOS 18–25) branch in `SettingsView.asrEngineRow`
— the on-device-only toggle is gone, the row is a static
"SpeechAnalyzer active" badge. Same for the `else` branch
in `EnginePickerSection.localSubtitle`.
- `makeMicAuthHandler` (the iOS < 17 mic permission callback
wrapper) from `PreviewASRController`.
No `#available` / `@available` checks remain in the codebase
except for the SpeechAnalyzer class itself (now unnecessary
too, but kept for clarity — `AVAudioApplication` and
`SpeechAnalyzer` are both iOS 17+ / iOS 26+ respectively,
and the deployment target of 26 makes the explicit
`@available` redundant; I left the SpeechAnalyzer class
un-`@available` and removed the `@available(iOS 26.0, *)`
decoration since it's no longer needed).
The Keyboard ext's existing ASRService usage
(`asr.transcribe(stream:locale:)`) is unchanged at the
call-site level — just the third argument is gone.
3. Updated `info.plist` UISupportedInterfaceOrientations is
already `[UIInterfaceOrientationPortrait]` only, which is
correct for an iPhone-only app; no change needed.
Build: BUILD SUCCEEDED.
Tests: 22/22 pass.
Verified: `TARGETED_DEVICE_FAMILY = 1` on all four targets,
`SDKROOT = iphoneos` on all four.
🤖 Generated with Claude Code
|
||
|
|
b1635d5d35 |
feat: iOS-style localization + onboarding engine picker + UX fixes
Five user-flagged issues addressed in this commit. Some of the
uncommitted files belong to a prior agent pass and are included as-is
so this is a clean working tree.
1. Remove globe (nextKeyboard) button from keyboard bottom bar.
iOS already provides a globe key in the system keyboard strip
for next-keyboard switching, so the in-extension one was
redundant. The bar is now: ⌫ (delete) [space] ↩ (return).
Affected: OSGKeyboardExt/Views/KeyboardRootView.swift and
OSGKeyboard/Views/KeyboardPreviewStub.swift (preview mirrors).
2. Fix TabView with .page style auto-jumping on TextField focus.
SwiftUI's `.tabViewStyle(.page(...))` wraps content in a
UIPageViewController, which has a long-standing iOS 18 bug
where the keyboard-showing layout reflow on a TextField focus
is misread as a horizontal swipe — the page jumps back to
step 1 the moment the user starts typing. Replaced the
TabView with a ZStack + conditional view + transition. We give
up swipe-to-page, but Back/Next buttons + page dots are the
canonical onboarding affordance and the user is one tap from
the next page anyway.
3. Onboarding APISetupPage now offers Engine choice (Local vs
Cloud), matching the in-app Settings page. First-run users can
pick the on-device engine and skip the API-key setup entirely.
Extracted the engine section from SettingsView into a shared
`EnginePickerSection` component used by both. Cloud path
keeps the provider + API fields; Local path shows a
"no API key needed" confirmation card.
4. APISettingsCard test-connection error messages are now built
with NSLocalizedString + String.localizedStringWithFormat (for
the interpolated HTTP status / reply preview). Status badge
labels are also NSLocalizedString-backed.
5. iOS-standard localization.
- New `en.lproj/Localizable.strings` and `zh-Hans.lproj/
Localizable.strings` (in both the main app and the keyboard
extension bundles — each .appex has its own bundle).
- `project.yml` sets `CFBundleDevelopmentRegion: en` and
`CFBundleLocalizations: [en, zh-Hans]`; the two .lproj dirs
are added as resources.
- Every `Text("...")` / `Button("...")` / `Label("...")` /
`accessibilityLabel(Text("..."))` in user-facing views was
rewritten to use `Text("key")` (SwiftUI auto-resolves
string-literal `LocalizedStringKey`s against the strings
file) or `NSLocalizedString("key", comment: "")` for
interpolated / dynamic values. The hardcoded
"中 · EN" / "EN · 中" pattern is gone.
- Two helper signatures that took `String` for the title/body
of a row (`footnoteRow`, `sectionHeader`) are now
`LocalizedStringKey` so the row labels are looked up.
- `Label("key", systemImage: "...")` and
`.confirmationDialog(LocalizedStringKey("key"), ...)` and
`.navigationTitle(LocalizedStringKey("key"))` are used where
`String` would just print the key.
- Preview-onboarding `APISetupPage` updated to use the same
engine picker as Settings (issue 3), with a section that
only renders the provider + API fields when the user picks
Cloud.
Verified on iOS 26 simulator with system language set to both
zh-Hans (default) and en: the same screen renders "按住说话,松开
即得润色文字。" / "下一步" in Chinese, and "Hold to talk. Release
for polished text, in any app." / "Next" in English — no
"中 · EN" doubling, no missing keys.
Build: BUILD SUCCEEDED.
Tests: 21/21 pass.
🤖 Generated with Claude Code
|
||
|
|
79be7384dd |
[JJC-20260618-005-D] P1/P2 cleanup: structured errors, privacy audit, timeout SSOT, view-model tests
13 items, 555-line diff, build + 15/15 tests green.
ARCH-A3: Phase.error now carries ErrorKind (micDenied/speechDenied/asr/llm/
appGroupUnavailable/unknown) so the UI can pick icons/copy without parsing
free-form strings. Phase.ErrorKind, Phase, LLMError all Equatable.
ARCH-A4: Every TextField in APISettingsCard gets .keyboardType(.asciiCapable)
to defeat SwiftUI's iOS 18 system-keyboard hand-off that auto-suggests
Chinese/emoji and corrupts API keys / URLs / model names.
ARCH-A5 + DOC-3: PrivacyInfo.xcprivacy audited for honesty. Removed three
declared-but-unused APIs (FileTimestamp / DiskSpace / SystemBootTime) and
added ActiveKeyboards (DDA9.1) to the extension (it actually calls
advanceToNextInputMode in the tap path). Main App now declares only
UserDefaults (CA92.1). CHANGELOG updated.
ARCH-A6: Extracted PermissionManager (mic+speech permission flow, iOS 17
branching) and AppGroupPersistor (App Group load/persist) from the God
Object. KeyboardViewController drops 515 → 459 lines. KeyboardPipelineController
left in-place per risk plan — pressBegan state machine is too race-sensitive
to refactor in this pass.
RED-2: Deleted unused Theme enum (no call sites).
RED-3: Deleted unused cardStyle() alias (no call sites).
RED-7: Single source of truth for LLM timeout — LLMClient.requestTimeout +
LLMClientFactory.defaultRequestTimeout; PolishingService derives timeout from
defaultRequestTimeout+1 instead of hardcoding 15.
RED-8: ASRService.transcribe now emits .capability(onDeviceSupported:) as
first event per session; StatusBadge shows REC ⚠️ when the locale fell
back to cloud. New @Published var onDeviceSupported on State.
TEST-1: testPolishThrowsOnTransportTimeout now actually exercises
cancellation: StubURLProtocol delays response 5s, client.polish is
cancelled via Task.cancel(), test asserts the client throws .cancelled /
.transport / .decoding (was: silently passed).
TEST-2: New testPolisherSkipsNetworkWhenModeOff — PolishingService now
short-circuits when modeId == 'off' and returns trimmed input without
invoking LLMClient (proved via injected CountingLLMClient). Service was
moved to OSGKeyboardShared to be reachable from the test target.
TEST-3: KeyboardState (formerly KeyboardViewController.State) extracted
into OSGKeyboardShared so tests can @testable-import it. 5 phase/mode
tests in new KeyboardStateTests. Typealias preserves the old name.
TEST-4: New OSGKeyboardExtTests target with 6 tests covering State
initial values, phase transitions, structured-error round-trip, mode
switching, and InputMode rawValue round-trip.
|