e5a83843db
Add resilient usage analytics, OOBE gateway flows, clipboard semantic ranking, purchase recovery, style learning, and managed current-information search.
91 lines
3.9 KiB
Swift
91 lines
3.9 KiB
Swift
// SpeechHistoryEntry.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// A single voice transcription in the cross-device history log.
|
|
|
|
import Foundation
|
|
|
|
public struct SpeechHistoryEntry: Codable, Identifiable, Equatable, Sendable {
|
|
public enum Source: String, Codable, Equatable, Sendable {
|
|
case dictation
|
|
case ai
|
|
}
|
|
|
|
public let id: UUID
|
|
/// Final text delivered to the host app and shown in history.
|
|
public let text: String
|
|
/// ASR transcript after deterministic correction but before LLM polishing.
|
|
/// Stored for future speaking-style learning and intentionally not shown.
|
|
public let prePolishText: String?
|
|
/// Translation outputs must not be treated as same-language style examples.
|
|
public let wasTranslation: Bool
|
|
/// Style active when the final text was produced; nil for legacy or AI rows.
|
|
public let polishStyleID: String?
|
|
/// SHA-256 key into `SyncedSpeechHistory.polishStylePromptSnapshots`.
|
|
/// Deduplication preserves the exact prompt without repeating it per row.
|
|
public let polishStylePromptFingerprint: String?
|
|
public let createdAt: Date
|
|
/// Last content mutation. Legacy rows default to `createdAt`.
|
|
public let modifiedAt: Date
|
|
/// Monotonic per-entry revision used to merge edits across devices.
|
|
public let revision: Int64
|
|
/// iOS Flow engine mode; nil on macOS captures.
|
|
public let engineMode: String?
|
|
/// Origin of the inserted text. Legacy rows decode as normal dictation.
|
|
public let source: Source
|
|
|
|
public init(
|
|
id: UUID = UUID(),
|
|
text: String,
|
|
prePolishText: String? = nil,
|
|
wasTranslation: Bool = false,
|
|
polishStyleID: String? = nil,
|
|
polishStylePromptFingerprint: String? = nil,
|
|
createdAt: Date = Date(),
|
|
modifiedAt: Date? = nil,
|
|
revision: Int64 = 0,
|
|
engineMode: String? = nil,
|
|
source: Source = .dictation
|
|
) {
|
|
self.id = id
|
|
self.text = text
|
|
let trimmedPrePolishText = prePolishText?
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
self.prePolishText = trimmedPrePolishText?.isEmpty == false
|
|
? trimmedPrePolishText
|
|
: nil
|
|
self.wasTranslation = wasTranslation
|
|
self.polishStyleID = polishStyleID
|
|
self.polishStylePromptFingerprint = polishStylePromptFingerprint
|
|
self.createdAt = createdAt
|
|
self.modifiedAt = modifiedAt ?? createdAt
|
|
self.revision = revision
|
|
self.engineMode = engineMode
|
|
self.source = source
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try container.decode(UUID.self, forKey: .id)
|
|
text = try container.decode(String.self, forKey: .text)
|
|
prePolishText = try container.decodeIfPresent(String.self, forKey: .prePolishText)
|
|
wasTranslation = try container.decodeIfPresent(Bool.self, forKey: .wasTranslation) ?? false
|
|
polishStyleID = try container.decodeIfPresent(String.self, forKey: .polishStyleID)
|
|
polishStylePromptFingerprint = try container.decodeIfPresent(
|
|
String.self,
|
|
forKey: .polishStylePromptFingerprint
|
|
)
|
|
createdAt = try container.decode(Date.self, forKey: .createdAt)
|
|
modifiedAt = try container.decodeIfPresent(Date.self, forKey: .modifiedAt) ?? createdAt
|
|
revision = try container.decodeIfPresent(Int64.self, forKey: .revision) ?? 0
|
|
engineMode = try container.decodeIfPresent(String.self, forKey: .engineMode)
|
|
source = try container.decodeIfPresent(Source.self, forKey: .source) ?? .dictation
|
|
}
|
|
|
|
/// First-line preview for compact list rows (macOS history sidebar).
|
|
public var previewTitle: String {
|
|
let firstLine = text.split(separator: "\n").first.map(String.init) ?? text
|
|
return firstLine.count > 36 ? String(firstLine.prefix(36)) + "…" : firstLine
|
|
}
|
|
}
|