feat(ai): unify reply center and refresh keyboard AI features

- Merge invitation, task, blessing, clarification, and empathy actions
  into a single Reply flow, with three fixed, clearly labeled stance
  choices whenever user intent must not be guessed.
- Refine clipboard semantic routing with bilingual schedule,
  confirmation, and follow-up models, conservative language thresholds,
  and explicit-assignment guard for complaint-only text.
- Persist Apple account refresh state, harden session recovery, and
  surface durable account diagnostics across keyboard and app.
- Derive personal-style prompts through two-stage corpus evidence and
  apply real low-confidence ASR tendencies instead of neutral templates.
- Localize the new reply center, clipboard semantics, and personal-style
  surfaces in both English and Simplified Chinese.
This commit is contained in:
Rocky
2026-08-29 11:51:21 +08:00
parent f91a8f2456
commit 3c10d73d7f
30 changed files with 2090 additions and 447 deletions
@@ -6,7 +6,7 @@
import Foundation
public struct LiveConfigurationSnapshot {
public struct LiveConfigurationSnapshot: @unchecked Sendable {
public let providerId: String
public let baseURL: String
public let apiKey: String
@@ -66,6 +66,32 @@ public struct LiveConfigurationSnapshot {
self.cloudASRPersistence = cloudASRPersistence
}
/// Capture every value exposed by an arbitrary configuration store.
/// Later source changes cannot alter provider, credential, or style
/// selection for an operation already using this snapshot.
public init(store: any ConfigurationStore) {
self.init(
providerId: store.providerId,
baseURL: store.baseURL,
apiKey: store.apiKey,
model: store.model,
asrProviderId: store.asrProviderId,
asrBaseURL: store.asrBaseURL,
asrApiKey: store.asrApiKey,
asrModel: store.asrModel,
engineMode: store.engineMode,
credentialSource: store.credentialSource,
polishIntensity: store.polishIntensity,
aiResponseLength: store.aiResponseLength,
llmThinkingEnabled: store.llmThinkingEnabled,
personalDictionary: store.personalDictionary,
polishStyleCatalog: store.polishStyleCatalog,
activePolishStyleId: store.activePolishStyleId,
detectedAppContext: store.detectedAppContext,
cloudASRPersistence: store.cloudASRPersistence
)
}
/// Build from live `ProviderConfig` plus persisted App Group extras.
public init(config: ProviderConfig, fallback: AppGroupStore) {
self.init(
@@ -101,6 +127,10 @@ public struct LiveConfigurationStore: ConfigurationStore, @unchecked Sendable {
self.snapshot = snapshot
}
public init(store: any ConfigurationStore) {
self.init(snapshot: LiveConfigurationSnapshot(store: store))
}
public init(config: ProviderConfig, fallback: AppGroupStore) {
self.init(snapshot: LiveConfigurationSnapshot(config: config, fallback: fallback))
}
@@ -131,7 +161,7 @@ public struct LiveConfigurationStore: ConfigurationStore, @unchecked Sendable {
) -> LLMClient {
if credentialSource == .managed || requestPurpose == .oobe {
return ManagedLLMClient(
capability: .polish,
capability: .resolve(taskKind: taskKind),
taskKind: taskKind,
requestPurpose: requestPurpose,
oobeFeature: oobeFeature,