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:
@@ -138,21 +138,43 @@ final class FlowSessionManager: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
reconcilePersistedFlowStateBeforeStart()
|
||||
|
||||
// Healthy / busy sessions must not flash the cold-start overlay when a
|
||||
// spurious `startflow` arrives (e.g. keyboard finalize race).
|
||||
if coldStart, isActive {
|
||||
extendSession(duration: duration)
|
||||
refreshHostReady()
|
||||
let decision = FlowHandoffPolicy.coldStartOverlayDecision(
|
||||
sessionIsActive: true,
|
||||
hostIsReady: FlowSessionBridge.isHostReady(),
|
||||
isUtteranceBusy: isUtteranceRecording || isUtteranceProcessing
|
||||
)
|
||||
switch decision {
|
||||
case .silence:
|
||||
traceState(
|
||||
"startSession.coldStart.silenced",
|
||||
extra: FlowSessionBridge.isHostReady() ? "reason=alreadyReady" : "reason=busy"
|
||||
)
|
||||
return
|
||||
case .present:
|
||||
isColdStartHandoff = true
|
||||
showColdStartPreparing()
|
||||
Task { @MainActor [weak self] in
|
||||
await self?.prepareExistingSessionForColdStartReturn()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if coldStart {
|
||||
isColdStartHandoff = true
|
||||
showColdStartPreparing()
|
||||
}
|
||||
|
||||
reconcilePersistedFlowStateBeforeStart()
|
||||
|
||||
if isActive {
|
||||
extendSession(duration: duration)
|
||||
refreshHostReady()
|
||||
if coldStart {
|
||||
Task { @MainActor [weak self] in
|
||||
await self?.prepareExistingSessionForColdStartReturn()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1354,8 +1376,9 @@ final class FlowSessionManager: ObservableObject {
|
||||
if Task.isCancelled {
|
||||
throw CancellationError()
|
||||
}
|
||||
let polished = try await polisher.polish(
|
||||
text,
|
||||
let polished = try await Self.polishWithHostTimeout(
|
||||
polisher: polisher,
|
||||
text: text,
|
||||
mode: polishMode,
|
||||
providerIdOverride: pipelineStore.polishProviderIdOverride
|
||||
)
|
||||
@@ -1542,6 +1565,22 @@ final class FlowSessionManager: ObservableObject {
|
||||
return FlowSessionKeys.cloudASRWaitTimeout
|
||||
}
|
||||
|
||||
/// Host-level polish cap — does not wait for a cancelled LLM task to unwind.
|
||||
private static func polishWithHostTimeout(
|
||||
polisher: PolishingService,
|
||||
text: String,
|
||||
mode: PolishingService.PolishMode,
|
||||
providerIdOverride: String?
|
||||
) async throws -> String {
|
||||
try await HardTimeout.run(seconds: FlowSessionKeys.maxPolishTimeout) {
|
||||
try await polisher.polish(
|
||||
text,
|
||||
mode: mode,
|
||||
providerIdOverride: providerIdOverride
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Level publishing (main thread only)
|
||||
|
||||
private func startLevelPublishing() {
|
||||
|
||||
Reference in New Issue
Block a user