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:
@@ -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,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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user