feat: harden Flow cold-start/force-quit and polish macOS dictation UX

Fix cold-start overlay recursion that overflowed the main-thread stack when
recording began while the ready overlay was still up; also remove temporary
on-screen Flow DEBUG panels after the orange-mic investigation, and land the
macOS overlay/catalog/layout polish plus related Flow recovery hardening.
This commit is contained in:
Rocky
2026-07-10 12:39:41 +08:00
parent dcb66a9849
commit cdf833935a
104 changed files with 5794 additions and 853 deletions
@@ -17,31 +17,35 @@ public protocol CloudASRTranscribing: Sendable {
public enum CloudASRClientFactory {
public static func make(store: any ConfigurationStore, session: URLSession = .shared) -> CloudASRTranscribing {
let strategy = CloudASRModelCatalog.strategy(for: store.providerId)
let providerId = store.asrProviderId
let strategy = CloudASRModelCatalog.strategy(for: providerId)
let asrModel = store.asrModel.isEmpty
? CloudASRModelCatalog.defaultModel(for: providerId)
: store.asrModel
switch strategy {
case .zhipuHotwords:
return ZhipuCloudASRClient(
apiKey: store.apiKey,
model: CloudASRModelCatalog.defaultModel(for: store.providerId),
apiKey: store.asrApiKey,
model: asrModel,
session: session
)
case .alibabaVocabulary:
return AlibabaFunASRClient(
apiKey: store.apiKey,
model: CloudASRModelCatalog.defaultModel(for: store.providerId),
apiKey: store.asrApiKey,
model: asrModel,
persistence: store.cloudASRPersistence,
session: session
)
case .prompt:
return PromptCloudASRClient(
providerId: store.providerId,
baseURL: store.baseURL,
apiKey: store.apiKey,
model: CloudASRModelCatalog.defaultModel(for: store.providerId),
providerId: providerId,
baseURL: store.asrBaseURL,
apiKey: store.asrApiKey,
model: asrModel,
session: session
)
case .localFallback:
return UnsupportedCloudASRClient(providerId: store.providerId)
return UnsupportedCloudASRClient(providerId: providerId)
}
}
}
@@ -133,7 +133,7 @@ public final class CloudASRService: ASRService, @unchecked Sendable {
}
private func bindClientIfNeeded() {
let providerId = store.providerId
let providerId = store.asrProviderId
let strategy = CloudASRModelCatalog.strategy(for: providerId)
lock.withLock {
guard boundProviderId != providerId else { return }