feat: macOS architecture, cloud ASR/LLM providers, and 6-step iOS onboarding

- Add macOS menu-bar dictation app with local ASR models (SenseVoice/Qwen3),
  global Option hotkey, and bottom overlay
- Add cloud ASR/LLM providers (Anthropic, Volcengine, Bailian, and more) with
  provider logos, model listing, and connection checks
- Add shared 7-day usage stats UI (UsageStatsCluster / SevenDayUsageChart)
- Add iOS onboarding step 6 for polish LLM setup; hide custom-language-model
  diagnostic toggle behind DEBUG
- Unify iOS onboarding tagline with the macOS brand line ("开口即文字。")
- Rewrite README (Chinese-first, product-oriented) and refresh GitHub Pages
This commit is contained in:
Rocky
2026-07-11 19:10:20 +08:00
parent cdf833935a
commit cc8dd1070a
116 changed files with 6659 additions and 2634 deletions
+64 -11
View File
@@ -10,10 +10,14 @@ import Foundation
public enum CloudASRStrategy: String, Sendable, Equatable {
/// GLM-ASR `hotwords` + optional `prompt`.
case zhipuHotwords
/// Fun-ASR managed `vocabulary_id` + context text.
case alibabaVocabulary
/// OpenAI / MiMo / `prompt` on transcription APIs.
/// Fun-ASR Realtime DashScope inference WebSocket
case bailianStreaming
/// OpenAI / Groq / / Whisper `prompt` on transcription APIs.
case prompt
/// OpenRouter `/audio/transcriptions` JSON body + base64 WAV (not multipart).
case openRouterJson
/// SAUC ASRWebSocket + binary frame
case volcengineStreaming
/// Moonshot API 退 ASR
case localFallback
}
@@ -54,8 +58,23 @@ public enum CloudASRError: Error, LocalizedError, Sendable, Equatable {
}
public enum CloudASRModelCatalog {
/// Provider ids shown in the cloud ASR picker (explicit allowlist).
public static let selectableProviderIds: Set<String> = [
"openai",
"whisper",
"bailian",
"zhipu",
"groq",
"siliconflow",
"openrouter",
"mimo",
"volcengine",
"custom",
]
/// Sync Fun-ASR Flash base64 upload, 5 min, supports context + vocabulary.
public static let alibabaFunASRFlash = "fun-asr-flash-2026-06-15"
public static let alibabaFunASRRealtime = "fun-asr-realtime"
/// Must match the ASR model used at recognition time.
public static let alibabaVocabularyTargetModel = alibabaFunASRFlash
@@ -63,24 +82,38 @@ public enum CloudASRModelCatalog {
public static let openAITranscribe = "gpt-4o-mini-transcribe"
public static let openAIWhisper = "whisper-1"
public static let mimoASR = "mimo-v2.5-asr"
public static let groqWhisper = "whisper-large-v3-turbo"
public static let siliconflowASR = "FunAudioLLM/SenseVoiceSmall"
public static let openrouterWhisper = "openai/whisper-large-v3-turbo"
public static let volcengineDefaultResourceID = "volc.seedasr.sauc.duration"
public static let volcengineEndpoint = "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async"
public static let bailianDefaultEndpoint = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"
public static let alibabaAPIBase = "https://dashscope.aliyuncs.com/api/v1"
public static let alibabaCustomizationPath = "/services/audio/asr/customization"
public static let alibabaMultimodalPath = "/services/aigc/multimodal-generation/generation"
public static let zhipuTranscriptionPath = "/audio/transcriptions"
public static func supportsCloudASRSelection(providerId: String) -> Bool {
selectableProviderIds.contains(providerId)
}
public static func strategy(for providerId: String) -> CloudASRStrategy {
switch providerId {
case "zhipu":
return .zhipuHotwords
case "qwen":
return .alibabaVocabulary
case "bailian":
return .bailianStreaming
case "moonshot":
return .localFallback
case "openai", "mimo", "custom":
case "volcengine":
return .volcengineStreaming
case "openrouter":
return .openRouterJson
case "openai", "whisper", "mimo", "groq", "siliconflow", "custom":
return .prompt
default:
return .prompt
return .localFallback
}
}
@@ -88,16 +121,36 @@ public enum CloudASRModelCatalog {
switch providerId {
case "zhipu":
return zhipuGLMASR
case "qwen":
return alibabaFunASRFlash
case "bailian":
return alibabaFunASRRealtime
case "whisper":
return openAIWhisper
case "mimo":
return mimoASR
case "groq":
return groqWhisper
case "siliconflow":
return siliconflowASR
case "openrouter":
return openrouterWhisper
case "volcengine":
return volcengineDefaultResourceID
case "openai", "custom":
return openAITranscribe
default:
return openAITranscribe
}
}
/// Whether the ASR settings card should expose a custom endpoint field.
public static func showsASREndpointField(for providerId: String) -> Bool {
switch strategy(for: providerId) {
case .prompt, .openRouterJson, .bailianStreaming:
return true
case .zhipuHotwords, .volcengineStreaming, .localFallback:
return false
}
}
}
extension LLMProvider {
@@ -112,9 +165,9 @@ extension LLMProvider {
/// Official hotwords / vocabulary APIs during cloud ASR (not prompt-only bias).
public var supportsPersonalDictionaryCloudASR: Bool {
switch cloudASRStrategy {
case .zhipuHotwords, .alibabaVocabulary:
case .zhipuHotwords:
return true
case .prompt, .localFallback:
case .bailianStreaming, .prompt, .openRouterJson, .volcengineStreaming, .localFallback:
return false
}
}