mirror of
https://github.com/carey314/mio-plugin-clipper.git
synced 2026-08-10 07:44:32 +00:00
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>
61 lines
1.9 KiB
Swift
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]) + "…"
|
|
}
|
|
}
|