feat(polish): add context safeguards, layered prompts, and output validation
Use redacted cursor neighborhood and pause-aware chunks for more natural polish, validate protected terms with retry/local fallback, and structure bilingual prompts for consistency and provider prefix caching.
This commit is contained in:
@@ -9,6 +9,34 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct FieldHints: Sendable, Equatable {
|
||||
public let keyboardType: String?
|
||||
public let returnKeyType: String?
|
||||
public let isEmptyField: Bool
|
||||
public let isContextAvailable: Bool
|
||||
|
||||
public init(
|
||||
keyboardType: String? = nil,
|
||||
returnKeyType: String? = nil,
|
||||
isEmptyField: Bool = false,
|
||||
isContextAvailable: Bool = false
|
||||
) {
|
||||
self.keyboardType = keyboardType
|
||||
self.returnKeyType = returnKeyType
|
||||
self.isEmptyField = isEmptyField
|
||||
self.isContextAvailable = isContextAvailable
|
||||
}
|
||||
|
||||
public init(from context: FlowFieldContext) {
|
||||
self.init(
|
||||
keyboardType: context.keyboardType,
|
||||
returnKeyType: context.returnKeyType,
|
||||
isEmptyField: context.isEmptyField,
|
||||
isContextAvailable: context.isContextAvailable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct PolishContext: Sendable {
|
||||
/// Coarse classification of the input field. When `.unknown` the
|
||||
/// LLM is told to pick a neutral tone on its own.
|
||||
@@ -24,6 +52,12 @@ public struct PolishContext: Sendable {
|
||||
/// bias terminology choices.
|
||||
public let precedingText: String?
|
||||
|
||||
/// Optional text immediately after the insertion point.
|
||||
public let followingText: String?
|
||||
|
||||
/// Input-field signals captured by the keyboard extension.
|
||||
public let fieldHints: FieldHints?
|
||||
|
||||
/// Extra dictionary block appended after `PersonalDictionary.promptFragment()`
|
||||
/// (e.g. builtin `phrases.tsv` terms on macOS local ASR).
|
||||
public let dictionarySupplement: String?
|
||||
@@ -32,19 +66,26 @@ public struct PolishContext: Sendable {
|
||||
/// include in the prompt. The full preceding text is often
|
||||
/// hundreds of KB in a long note — we only need the tail.
|
||||
public let maxPrecedingChars: Int
|
||||
public let maxFollowingChars: Int
|
||||
|
||||
public init(
|
||||
appContext: AppContext = .unknown,
|
||||
intensity: PolishIntensity = .default,
|
||||
precedingText: String? = nil,
|
||||
followingText: String? = nil,
|
||||
fieldHints: FieldHints? = nil,
|
||||
dictionarySupplement: String? = nil,
|
||||
maxPrecedingChars: Int = 500
|
||||
maxPrecedingChars: Int = 600,
|
||||
maxFollowingChars: Int = 200
|
||||
) {
|
||||
self.appContext = appContext
|
||||
self.intensity = intensity
|
||||
self.precedingText = precedingText
|
||||
self.followingText = followingText
|
||||
self.fieldHints = fieldHints
|
||||
self.dictionarySupplement = dictionarySupplement
|
||||
self.maxPrecedingChars = maxPrecedingChars
|
||||
self.maxFollowingChars = maxFollowingChars
|
||||
}
|
||||
|
||||
/// Truncated view of `precedingText` ready for prompt injection.
|
||||
@@ -54,4 +95,10 @@ public struct PolishContext: Sendable {
|
||||
if raw.count <= maxPrecedingChars { return raw }
|
||||
return String(raw.suffix(maxPrecedingChars))
|
||||
}
|
||||
|
||||
public var followingForPrompt: String? {
|
||||
guard let raw = followingText, !raw.isEmpty else { return nil }
|
||||
if raw.count <= maxFollowingChars { return raw }
|
||||
return String(raw.prefix(maxFollowingChars))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user