Expand onboarding and adaptive keyboard intelligence

Add resilient usage analytics, OOBE gateway flows, clipboard semantic ranking, purchase recovery, style learning, and managed current-information search.
This commit is contained in:
Rocky
2026-08-22 16:33:18 +08:00
parent ac374631ae
commit e5a83843db
162 changed files with 23574 additions and 1013 deletions
@@ -262,6 +262,9 @@ enum MacDictationPipeline {
}
return MacDictationResult(
text: polished,
prePolishText: postASR,
polishStyleID: outcome.polishStyleID,
polishStylePrompt: outcome.polishStylePrompt,
polishWarning: outcome.qualityDegraded
? MacL10n.string("flow.warning.polishDegradedQuality")
: nil,
@@ -276,6 +279,9 @@ enum MacDictationPipeline {
)
return MacDictationResult(
text: delivery.text,
prePolishText: postASR,
polishStyleID: nil,
polishStylePrompt: nil,
polishWarning: delivery.polishWarning,
chunkWarning: nil
)
+5
View File
@@ -5,6 +5,11 @@ import Foundation
struct MacDictationResult: Sendable, Equatable {
let text: String
/// Corrected ASR transcript before LLM polishing; never shown in History.
let prePolishText: String
/// Exact style metadata returned by the successful polish request.
let polishStyleID: String?
let polishStylePrompt: String?
/// Shown when DeepSeek / cloud polish failed but raw ASR was delivered.
let polishWarning: String?
/// Non-fatal per-chunk ASR issues from long utterance chunking.
+9 -1
View File
@@ -423,7 +423,15 @@ final class MacDictationViewModel: ObservableObject {
targetApplication: targetApplication
)
self.recordUsage(for: result.text)
self.speechHistory.append(text: result.text)
self.speechHistory.append(
text: result.text,
prePolishText: result.prePolishText,
wasTranslation: store.isTranslationEffective,
polishStyleID: result.polishStyleID
?? store.activePolishStyleId,
polishStylePrompt: result.polishStylePrompt,
engineMode: store.engineMode
)
self.appendToOverview(result.text)
self.statusMessage = self.statusAfterDelivery(
pasted: pasted,
+132
View File
@@ -8,11 +8,13 @@ import SwiftUI
struct MacPolishStylesView: View {
@ObservedObject var viewModel: MacDictationViewModel
@ObservedObject private var history = SpeechHistoryStore.shared
@Environment(\.themePalette) private var palette
@State private var editingPack: PolishStylePack?
@State private var viewingPack: PolishStylePack?
@State private var errorMessage: String?
@State private var isGeneratingLearnedStyle = false
private var lang: AppUILanguage { viewModel.config.uiLanguage }
private var store: AppGroupStore { AppGroupStore(defaults: viewModel.defaults) }
@@ -59,6 +61,7 @@ struct MacPolishStylesView: View {
ScrollView {
LazyVStack(alignment: .leading, spacing: Spacing.xl) {
styleLearningCard
styleSection(
title: MacL10n.string("mac.styles.builtin", language: lang),
packs: PolishStylePackCatalog.BuiltinStyleGroup.practical.packs
@@ -114,6 +117,103 @@ struct MacPolishStylesView: View {
}
}
private var styleLearningCorpus: PolishStyleLearningCorpus {
PolishStyleLearningCorpusBuilder.build(from: history.snapshot())
}
private var styleLearningCard: some View {
let corpus = styleLearningCorpus
let required = PolishStyleLearningCorpusBuilder.requiredEffectiveCharacterCount
let reachedLimit = catalog.entries.count >= PolishStyleLimits.maximumUserPacks
return VStack(alignment: .leading, spacing: Spacing.md) {
HStack(alignment: .top, spacing: Spacing.md) {
Image(systemName: "waveform.badge.sparkles")
.font(.system(size: 20, weight: .semibold))
.foregroundStyle(palette.accent)
.frame(width: 42, height: 42)
.background(
palette.accentMuted,
in: RoundedRectangle(cornerRadius: Radius.medium, style: .continuous)
)
VStack(alignment: .leading, spacing: 3) {
Text(MacL10n.string("mac.styles.learn.title", language: lang))
.font(TypeStyle.bodyEmph)
.foregroundStyle(palette.textPrimary)
Text(MacL10n.string("mac.styles.learn.body", language: lang))
.font(TypeStyle.caption2)
.foregroundStyle(palette.textSecondary)
}
}
ProgressView(
value: Double(min(corpus.effectiveCharacterCount, required)),
total: Double(required)
)
.tint(palette.accent)
HStack {
Text(
MacL10n.format(
"mac.styles.learn.progress",
language: lang,
Int64(corpus.effectiveCharacterCount),
Int64(required)
)
)
.font(TypeStyle.caption2)
.foregroundStyle(palette.textTertiary)
Spacer()
Text(
corpus.isReady
? MacL10n.string("mac.styles.learn.ready", language: lang)
: MacL10n.format(
"mac.styles.learn.remaining",
language: lang,
Int64(corpus.remainingCharacterCount)
)
)
.font(TypeStyle.caption2)
.foregroundStyle(corpus.isReady ? palette.accent : palette.textTertiary)
}
HStack {
Text(
MacL10n.string(
reachedLimit
? "mac.styles.learn.limit"
: "mac.styles.learn.privacy",
language: lang
)
)
.font(TypeStyle.caption2)
.foregroundStyle(palette.textTertiary)
Spacer()
Button {
generateLearnedStyle(from: corpus)
} label: {
if isGeneratingLearnedStyle {
ProgressView()
.controlSize(.small)
}
Text(
MacL10n.string(
isGeneratingLearnedStyle
? "mac.styles.learn.generating"
: "mac.styles.learn.action",
language: lang
)
)
}
.buttonStyle(.borderedProminent)
.tint(palette.accent)
.disabled(!corpus.isReady || isGeneratingLearnedStyle || reachedLimit)
}
}
.padding(Spacing.lg)
.surfaceCard()
}
private func styleSection(title: String, packs: [PolishStylePack]) -> some View {
VStack(alignment: .leading, spacing: Spacing.sm) {
Text(title)
@@ -160,6 +260,38 @@ struct MacPolishStylesView: View {
)
}
private func generateLearnedStyle(from corpus: PolishStyleLearningCorpus) {
guard corpus.isReady, !isGeneratingLearnedStyle else { return }
isGeneratingLearnedStyle = true
Task {
defer { isGeneratingLearnedStyle = false }
do {
let generated = try await PolishStyleLearningService(store: store)
.generateStyle(from: corpus, outputLanguage: lang)
// Review is mandatory before the learned prompt enters sync or
// becomes the active dictation personality.
editingPack = generated
} catch {
errorMessage = localizedLearningError(error)
}
}
}
private func localizedLearningError(_ error: Error) -> String {
switch error as? PolishStyleLearningError {
case .insufficientCorpus:
return MacL10n.string("mac.styles.learn.error.insufficient", language: lang)
case .invalidResponse:
return MacL10n.string("mac.styles.learn.error.invalidResponse", language: lang)
case .promptTooLong:
return MacL10n.string("mac.styles.learn.error.promptTooLong", language: lang)
case .requestTooLarge:
return MacL10n.string("mac.styles.learn.error.requestTooLarge", language: lang)
case nil:
return MacL10n.string("mac.styles.learn.error.request", language: lang)
}
}
private func subtitle(for pack: PolishStylePack) -> String {
if pack.kind == .user {
return MacL10n.string("mac.styles.customDescription", language: lang)