937ce33f05
Add shared prompt composition, custom style management, iCloud sync, and native iOS/macOS selection interfaces so users can keep a consistent writing voice across devices.
112 lines
4.5 KiB
Swift
112 lines
4.5 KiB
Swift
// PolishIntensity.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// How aggressively the LLM should rewrite the ASR transcript.
|
|
//
|
|
// Persisted in `AppGroupStore` via `ProviderConfig` so the keyboard
|
|
// extension can honour the chosen intensity during live dictation.
|
|
|
|
import Foundation
|
|
|
|
public enum PolishIntensity: String, Codable, Sendable, CaseIterable {
|
|
/// Drop only isolated filler words (嗯 / 呃 / 那个 / 就是 / 然后)
|
|
/// and obvious duplicated fragments. Punctuation and structure
|
|
/// formatting still apply at every intensity level.
|
|
case light
|
|
|
|
/// Correction + light polish: drop fillers, fix homophone errors,
|
|
/// adjust obviously-broken word order, add punctuation. Preserves
|
|
/// the speaker's voice and intent.
|
|
case medium
|
|
|
|
/// Full structural rewrite: split long sentences, auto-number
|
|
/// enumerated items, format as paragraphs / lists. Use for
|
|
/// meeting notes, weekly reports, blog drafts.
|
|
case heavy
|
|
|
|
/// User-facing label key for the Settings picker. Localized
|
|
/// through `SharedL10n` so the same key works in the main app
|
|
/// and the keyboard extension.
|
|
public var labelKey: String {
|
|
switch self {
|
|
case .light: return "polish.intensity.light"
|
|
case .medium: return "polish.intensity.medium"
|
|
case .heavy: return "polish.intensity.heavy"
|
|
}
|
|
}
|
|
|
|
/// Short description shown under the picker. Same localization
|
|
/// story as `labelKey`.
|
|
public var descriptionKey: String {
|
|
switch self {
|
|
case .light: return "polish.intensity.light.desc"
|
|
case .medium: return "polish.intensity.medium.desc"
|
|
case .heavy: return "polish.intensity.heavy.desc"
|
|
}
|
|
}
|
|
|
|
/// Inline guideline injected into the LLM prompt. The polish
|
|
/// service appends this verbatim so the LLM has an explicit,
|
|
/// non-ambiguous constraint per call.
|
|
public var promptGuideline: String {
|
|
promptGuideline(styleID: nil)
|
|
}
|
|
|
|
/// Intensity guideline for the LLM prompt. When the active style limits
|
|
/// heavy restructuring (chat/light/dating), heavy still improves clarity
|
|
/// but must not override the style pack's length and format rules.
|
|
public func promptGuideline(styleID: String?) -> String {
|
|
let base: String
|
|
switch self {
|
|
case .light:
|
|
base = """
|
|
Light rewrite: remove isolated filler words (嗯, 呃, 那个, 就是, 然后, 对, ok, um, uh) and obvious duplicated fragments only. \
|
|
Do not rephrase otherwise-clear wording. \
|
|
Still restore punctuation and sentence breaks per the global output contract and active style pack.
|
|
"""
|
|
case .medium:
|
|
base = """
|
|
Medium rewrite: fix obvious ASR errors (homophones, missing/extra characters), remove fillers and duplicated fragments, \
|
|
adjust obviously-broken word order. Preserve the speaker's voice. \
|
|
Still restore punctuation and breaks per the global output contract and active style pack. \
|
|
Do not invent facts or change numbers/proper nouns.
|
|
"""
|
|
case .heavy:
|
|
base = """
|
|
Heavy rewrite: apply medium corrections, then you may reorganize paragraphs, split long sentences, and listify enumerated content when the active style pack allows it. \
|
|
Punctuation is mandatory at every intensity. \
|
|
Preserve every fact, number, and proper noun. Do not add information.
|
|
"""
|
|
}
|
|
|
|
guard self == .heavy,
|
|
let styleID,
|
|
PolishStylePackCatalog.limitsHeavyRestructuring(id: styleID)
|
|
else {
|
|
return base
|
|
}
|
|
|
|
return base + """
|
|
|
|
Style override: the active style pack limits heavy restructuring. Do not expand length, add paragraphs for polish only, or introduce numbered lists unless the transcript explicitly enumerates items. Keep the style pack's chat rhythm, tone, and format rules authoritative.
|
|
"""
|
|
}
|
|
|
|
/// Legacy persisted value `"off"` maps to `.medium` on read.
|
|
public static func resolve(storedRawValue raw: String) -> PolishIntensity {
|
|
if raw == legacyOffRawValue {
|
|
return .medium
|
|
}
|
|
return PolishIntensity(rawValue: raw) ?? .default
|
|
}
|
|
|
|
/// Raw value written by builds before the off tier was removed.
|
|
public static let legacyOffRawValue = "off"
|
|
}
|
|
|
|
extension PolishIntensity {
|
|
/// Default for new installs. `medium` is what Typeless and Wispr
|
|
/// Flow also use as their first-run default.
|
|
public static let `default`: PolishIntensity = .medium
|
|
}
|