feat(macos): local ASR model manager, menu-bar polish, and release 0.5.2

Adds a bundled local ASR model catalog for the macOS app with one-click
Sherpa Qwen3 / SenseVoice downloads (pause/resume, inline actions) and a
shared model storage directory used by MLX Qwen3. Fixes the light-mode
sidebar material and makes the menu-bar icon follow the system appearance
with a refreshed status mark. Renames the built product to OSGKeyboard.app.

Bumps version to 0.5.2 (build 19).
This commit is contained in:
Rocky
2026-07-09 08:55:37 +08:00
parent c2f07bd8d2
commit 200265fbd6
50 changed files with 4666 additions and 266 deletions
@@ -0,0 +1,82 @@
// LocalASRCapabilities.swift
// OSGKeyboard · Shared
//
// Declares what each on-device ASR backend can accept for vocabulary bias.
// Callers must consult capabilities before building a `LocalASRBiasPayload`.
import Foundation
/// How a backend accepts vocabulary hints (honest matrix not every model
/// supports hard hotwords).
public enum LocalASRHotwordMode: String, Sendable, Codable, Equatable {
case none
case promptOnly
case perRequest
case recognizerScoped
case cloudVocabulary
}
/// Cost of refreshing hotwords on a backend (e.g. Sherpa Qwen3 reloads recognizer).
public enum LocalASRHotwordReloadCost: String, Sendable, Codable, Equatable {
case none
case recognizerReload
case modelReload
}
public struct LocalASRCapabilities: Sendable, Equatable {
public let hotwordMode: LocalASRHotwordMode
public let maxHotwordCount: Int
public let maxPromptCharacters: Int
public let supportsStreaming: Bool
public let hotwordReloadCost: LocalASRHotwordReloadCost
public init(
hotwordMode: LocalASRHotwordMode,
maxHotwordCount: Int,
maxPromptCharacters: Int,
supportsStreaming: Bool,
hotwordReloadCost: LocalASRHotwordReloadCost
) {
self.hotwordMode = hotwordMode
self.maxHotwordCount = maxHotwordCount
self.maxPromptCharacters = maxPromptCharacters
self.supportsStreaming = supportsStreaming
self.hotwordReloadCost = hotwordReloadCost
}
/// Qwen3 MLX via mlx-swift-asr `context` soft prompt on `transcribe`.
public static let qwen3MLX = LocalASRCapabilities(
hotwordMode: .promptOnly,
maxHotwordCount: 0,
maxPromptCharacters: 800,
supportsStreaming: false,
hotwordReloadCost: .none
)
/// Apple Speech on macOS no project-controlled hotword API today.
public static let appleSpeech = LocalASRCapabilities(
hotwordMode: .none,
maxHotwordCount: 0,
maxPromptCharacters: 0,
supportsStreaming: false,
hotwordReloadCost: .none
)
/// Sherpa Qwen3 hard hotwords via `--qwen3-asr-hotwords`.
public static let sherpaQwen3 = LocalASRCapabilities(
hotwordMode: .recognizerScoped,
maxHotwordCount: 100,
maxPromptCharacters: 0,
supportsStreaming: false,
hotwordReloadCost: .recognizerReload
)
/// Sherpa SenseVoice fast Chinese baseline without hotwords.
public static let sherpaSenseVoice = LocalASRCapabilities(
hotwordMode: .none,
maxHotwordCount: 0,
maxPromptCharacters: 0,
supportsStreaming: false,
hotwordReloadCost: .none
)
}