276d2bb6-619d-4888-ba11-8a6.../Sources/engine/HistoryStore.swift
徐翔宇 4e22618364 v0.1.0: initial 划词侠 — AI Text Actions plugin for MioIsland
Quick AI actions on clipboard or selected text. Four actions:
- 翻译 (中/英/日)
- 总结 (3-bullet Chinese)
- 改语气 (正式/口语/友好/严谨)
- 邮件回复

Powered by DeepSeek with Server-Sent Events streaming for
typewriter feedback (~600ms first byte). Two source modes:
clipboard (no permission) and selected-text via NSAccessibility
(graceful fallback to clipboard + permission banner if denied).

Five-state UI machine: idle → input → loading (cancellable) →
result → missing-config. Last 20 actions persisted to UserDefaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 20:21:17 +08:00

61 lines
1.9 KiB
Swift

//
// HistoryStore.swift
// plugin
//
// Persists last 20 actions in UserDefaults under a single JSON key.
// Cheap, durable, no external deps. Records actionLabel + input
// preview + output preview + timestamp.
//
import Foundation
struct HistoryEntry: Codable, Identifiable, Equatable {
var id: UUID
var actionLabel: String // e.g. "·" / "" / "·"
var inputPreview: String // truncated to 80 chars
var outputPreview: String // truncated to 120 chars
var timestamp: Date
init(id: UUID = UUID(),
actionLabel: String,
inputPreview: String,
outputPreview: String,
timestamp: Date = Date()) {
self.id = id
self.actionLabel = actionLabel
self.inputPreview = inputPreview
self.outputPreview = outputPreview
self.timestamp = timestamp
}
}
enum HistoryStore {
private static let key = "com.mioisland.plugin.clipper.history.v1"
private static let maxEntries = 20
static func load() -> [HistoryEntry] {
guard let data = UserDefaults.standard.data(forKey: key) else { return [] }
return (try? JSONDecoder().decode([HistoryEntry].self, from: data)) ?? []
}
static func save(_ entries: [HistoryEntry]) {
guard let data = try? JSONEncoder().encode(entries) else { return }
UserDefaults.standard.set(data, forKey: key)
}
static func append(_ entry: HistoryEntry) -> [HistoryEntry] {
var list = load()
list.insert(entry, at: 0)
if list.count > maxEntries { list = Array(list.prefix(maxEntries)) }
save(list)
return list
}
static func truncate(_ s: String, max: Int) -> String {
let collapsed = s.replacingOccurrences(of: "\n", with: " ")
if collapsed.count <= max { return collapsed }
let i = collapsed.index(collapsed.startIndex, offsetBy: max)
return String(collapsed[..<i]) + ""
}
}