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
+48 -9
View File
@@ -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() {