feat(macos): add macOS menu-bar app and harden cross-device iCloud sync
Introduce a standalone macOS menu-bar app (OSGKeyboardMac) that reuses the platform-agnostic OSGKeyboardShared core: record -> cloud/local ASR -> polish -> insert. Local mode uses Qwen3-ASR via mlx-swift-asr (macOS 15+, Apple Silicon); iOS targets stay zero-SPM. Harden iCloud sync for multi-device correctness: - Per-field settings merge (appSettings.v2) so concurrent edits no longer clobber each other's unrelated fields. - Per-device usage statistics (G-Counter) that sum instead of max(). - Tombstoned dictionary/history merge so deletes propagate and entries can't resurrect. - API keys replicate via iCloud Keychain, never iCloud KVS JSON; pulling a legacy blob without key fields no longer wipes local Keychain entries. - Add a low-risk "Sync Now" action in Settings. Fix Flow keyboard mic state: stay orange until the host publishes a real ready contract, share a single MicVoiceAvailability gate, and self-heal stale cross-process heartbeat jitter instead of getting stuck. Extract shared storage (SpeechHistoryStore/UsageStatisticsStore, ConfigurationStore) into OSGKeyboardShared and add tests for the new sync/merge logic.
This commit is contained in:
+102
@@ -26,6 +26,14 @@ options:
|
||||
# Carthage (matches the long-standing "zero dependencies" promise in the
|
||||
# README). Optional post-ASR cloud polish routes through DeepSeek (or
|
||||
# any OpenAI-compatible endpoint) using the existing `LLMClient`.
|
||||
#
|
||||
# macOS only: `mlx-swift-asr` (MLXASR) for Qwen3-ASR-1.7B on-device ASR.
|
||||
# iOS targets remain zero-SPM.
|
||||
|
||||
packages:
|
||||
MLXSwiftASR:
|
||||
url: https://github.com/ontypehq/mlx-swift-asr
|
||||
branch: main
|
||||
|
||||
settings:
|
||||
base:
|
||||
@@ -375,6 +383,91 @@ targets:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.osgkeyboard.ios.ext.tests
|
||||
TARGETED_DEVICE_FAMILY: "1"
|
||||
|
||||
# =========================================================
|
||||
# macOS 菜单栏 App (Phase 1 · 云端 MVP)
|
||||
# =========================================================
|
||||
# Standalone menu-bar utility: record → cloud ASR → polish → clipboard.
|
||||
# Reuses the platform-agnostic core files from OSGKeyboardShared at the
|
||||
# source level (no framework), excluding the iOS-only files that import
|
||||
# SpeechAnalyzer / AVAudioSession / UIKit / SwiftUI views. Local mode uses
|
||||
# Qwen3-ASR MLX via mlx-swift-asr (requires macOS 15+, Apple Silicon).
|
||||
OSGKeyboardMac:
|
||||
type: application
|
||||
platform: macOS
|
||||
deploymentTarget: "15.0"
|
||||
sources:
|
||||
# Same Icon Composer asset as the iOS app — do not ship a separate PNG
|
||||
# appiconset; actool crashes if both coexist.
|
||||
- path: OSGKeyboard/AppIcon.icon
|
||||
buildPhase: resources
|
||||
# Reuse the iOS asset catalog for provider logos + brand mark. Exclude
|
||||
# the PNG appiconset so it doesn't clash with AppIcon.icon above.
|
||||
- path: OSGKeyboard/Assets.xcassets
|
||||
excludes:
|
||||
- "AppIcon.appiconset"
|
||||
- path: OSGKeyboardMac
|
||||
- path: OSGKeyboardShared
|
||||
excludes:
|
||||
- "en.lproj"
|
||||
- "zh-Hans.lproj"
|
||||
- "Info.plist"
|
||||
- "**/*.swift.example"
|
||||
- "DesignSystem/WaveformView.swift"
|
||||
- "DesignSystem/TranslationChip.swift"
|
||||
- "DesignSystem/RecordButton.swift"
|
||||
- "Services/ASRService.swift"
|
||||
- "Services/CloudASR/CloudASRService.swift"
|
||||
- "Services/ChunkedUtterancePipeline.swift"
|
||||
- "Services/FlowContinuousCapture.swift"
|
||||
- "Services/LiveDictationController.swift"
|
||||
- "Services/KeyboardState.swift"
|
||||
- "Services/CursorNavigation.swift"
|
||||
- "Services/CustomLanguageModelManager.swift"
|
||||
- "Models/MicVoiceAvailability+Keyboard.swift"
|
||||
- "Utilities/ProgressiveDictationTranscriptAccumulator.swift"
|
||||
- path: OSGKeyboardShared/en.lproj/Shared.strings
|
||||
buildPhase: resources
|
||||
- path: OSGKeyboardShared/zh-Hans.lproj/Shared.strings
|
||||
buildPhase: resources
|
||||
entitlements:
|
||||
path: OSGKeyboardMac/OSGKeyboardMac.entitlements
|
||||
properties:
|
||||
com.apple.security.app-sandbox: true
|
||||
com.apple.security.device.audio-input: true
|
||||
com.apple.security.network.client: true
|
||||
com.apple.developer.ubiquity-kvstore-identifier: $(TeamIdentifierPrefix)com.osgkeyboard.ios
|
||||
keychain-access-groups:
|
||||
- $(AppIdentifierPrefix)com.osgkeyboard.shared
|
||||
info:
|
||||
path: OSGKeyboardMac/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OSGKeyboard
|
||||
CFBundleShortVersionString: "$(MARKETING_VERSION)"
|
||||
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
|
||||
CFBundleDevelopmentRegion: en
|
||||
CFBundleLocalizations:
|
||||
- en
|
||||
- zh-Hans
|
||||
LSApplicationCategoryType: public.app-category.utilities
|
||||
NSMicrophoneUsageDescription: "OSGKeyboard uses the microphone to transcribe your voice via your configured cloud speech provider."
|
||||
NSSpeechRecognitionUsageDescription: "OSGKeyboard uses on-device speech recognition for local dictation mode."
|
||||
NSHumanReadableCopyright: "OSGKeyboard"
|
||||
ITSAppUsesNonExemptEncryption: false
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.osgkeyboard.mac
|
||||
MACOSX_DEPLOYMENT_TARGET: "15.0"
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
||||
# macOS app is not an app extension; override the framework-wide flag.
|
||||
APPLICATION_EXTENSION_API_ONLY: NO
|
||||
CODE_SIGN_STYLE: Automatic
|
||||
DEVELOPMENT_TEAM: X329MZU23S
|
||||
dependencies:
|
||||
- sdk: Speech.framework
|
||||
- sdk: AVFoundation.framework
|
||||
- package: MLXSwiftASR
|
||||
product: MLXASR
|
||||
|
||||
schemes:
|
||||
OSGKeyboard:
|
||||
build:
|
||||
@@ -391,3 +484,12 @@ schemes:
|
||||
- OSGKeyboardExtTests
|
||||
archive:
|
||||
config: Release
|
||||
|
||||
OSGKeyboardMac:
|
||||
build:
|
||||
targets:
|
||||
OSGKeyboardMac: all
|
||||
run:
|
||||
config: Debug
|
||||
archive:
|
||||
config: Release
|
||||
|
||||
Reference in New Issue
Block a user