c07cf4db9f
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.
92 lines
3.2 KiB
Swift
92 lines
3.2 KiB
Swift
// LLMProvider.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// Provider preset: a known cloud LLM with sensible defaults.
|
|
// User picks one of these on first launch, or defines a Custom one.
|
|
|
|
import Foundation
|
|
|
|
public struct LLMProvider: Identifiable, Codable, Hashable, Sendable {
|
|
public let id: String
|
|
public let name: String
|
|
public let defaultBaseURL: String
|
|
public let defaultModel: String
|
|
public let apiKeyURL: URL?
|
|
/// Optional short blurb shown under the provider name in the picker.
|
|
public let blurb: String?
|
|
|
|
public init(
|
|
id: String,
|
|
name: String,
|
|
defaultBaseURL: String,
|
|
defaultModel: String,
|
|
apiKeyURL: URL? = nil,
|
|
blurb: String? = nil
|
|
) {
|
|
self.id = id
|
|
self.name = name
|
|
self.defaultBaseURL = defaultBaseURL
|
|
self.defaultModel = defaultModel
|
|
self.apiKeyURL = apiKeyURL
|
|
self.blurb = blurb
|
|
}
|
|
|
|
public static let presets: [LLMProvider] = [
|
|
.init(
|
|
id: "openai",
|
|
name: "OpenAI",
|
|
defaultBaseURL: "https://api.openai.com/v1",
|
|
defaultModel: "gpt-4o-mini",
|
|
apiKeyURL: URL(string: "https://platform.openai.com/api-keys"),
|
|
blurb: "GPT-4o mini · 多语言 · Multilingual"
|
|
),
|
|
.init(
|
|
id: "deepseek",
|
|
name: "DeepSeek",
|
|
defaultBaseURL: "https://api.deepseek.com/v1",
|
|
// v0.2.0: bumped default to `deepseek-v4-flash` for the
|
|
// local-mode cloud-polish toggle. `deepseek-chat` is
|
|
// retained as a valid user-overridable model name; only
|
|
// the default is updated.
|
|
defaultModel: "deepseek-v4-flash",
|
|
apiKeyURL: URL(string: "https://platform.deepseek.com/api_keys"),
|
|
blurb: "deepseek-v4-flash · 默认 · 快速且中文友好"
|
|
),
|
|
.init(
|
|
id: "qwen",
|
|
name: "Qwen (DashScope)",
|
|
defaultBaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
defaultModel: "qwen-plus",
|
|
apiKeyURL: URL(string: "https://dashscope.console.aliyun.com/apiKey"),
|
|
blurb: "通义千问 · OpenAI 兼容 · OpenAI-compatible"
|
|
),
|
|
.init(
|
|
id: "zhipu",
|
|
name: "智谱 GLM · Zhipu",
|
|
defaultBaseURL: "https://open.bigmodel.cn/api/paas/v4",
|
|
defaultModel: "glm-4-flash",
|
|
apiKeyURL: URL(string: "https://bigmodel.cn/usercenter/apikeys"),
|
|
blurb: "GLM-4-Flash · 中文优化 · Chinese-optimized"
|
|
),
|
|
.init(
|
|
id: "moonshot",
|
|
name: "月之暗面 Moonshot",
|
|
defaultBaseURL: "https://api.moonshot.cn/v1",
|
|
defaultModel: "moonshot-v1-8k",
|
|
apiKeyURL: URL(string: "https://platform.moonshot.cn/console/api-keys"),
|
|
blurb: "Kimi · 长上下文 · Long context"
|
|
),
|
|
.init(
|
|
id: "custom",
|
|
name: "Custom · 自定义",
|
|
defaultBaseURL: "",
|
|
defaultModel: "",
|
|
blurb: "自建 / 任意 OpenAI 兼容端点 · Any OpenAI-compatible endpoint"
|
|
)
|
|
]
|
|
|
|
public static func provider(id: String) -> LLMProvider {
|
|
presets.first(where: { $0.id == id }) ?? .presets[0]
|
|
}
|
|
}
|