File size: 4,528 Bytes
4977db2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | import Foundation
public enum ImportProvider: String, CaseIterable, Codable, Identifiable, Sendable {
case claude
case codex
case hermes
case chatGPT
case gemini
public var id: String { rawValue }
public var displayName: String {
switch self {
case .claude: "Claude"
case .codex: "Codex"
case .hermes: "Hermes"
case .chatGPT: "ChatGPT"
case .gemini: "Gemini"
}
}
public var formats: [ImportFormat] {
ImportFormat.allCases.filter { $0.provider == self }
}
}
public enum ImportFormat: String, CaseIterable, Codable, Identifiable, Sendable {
case claudeJSONL
case codexHistoryJSONL
case hermesStateDatabase
case chatGPTAnalysisJSON
case chatGPTRawConversationsJSON
case geminiMarkdown
case geminiJSONL
public var id: String { rawValue }
public var provider: ImportProvider {
switch self {
case .claudeJSONL: .claude
case .codexHistoryJSONL: .codex
case .hermesStateDatabase: .hermes
case .chatGPTAnalysisJSON, .chatGPTRawConversationsJSON: .chatGPT
case .geminiMarkdown, .geminiJSONL: .gemini
}
}
public var displayName: String {
switch self {
case .claudeJSONL: "Claude Code JSONL"
case .codexHistoryJSONL: "Codex history.jsonl"
case .hermesStateDatabase: "Hermes state.db"
case .chatGPTAnalysisJSON: "ChatGPT Analysis JSON"
case .chatGPTRawConversationsJSON: "ChatGPT Conversations JSON"
case .geminiMarkdown: "Gemini Markdown"
case .geminiJSONL: "Gemini JSONL"
}
}
public var allowsDirectory: Bool {
switch self {
case .hermesStateDatabase: true
default: true
}
}
}
public struct ImportCandidate: Hashable, Sendable {
public let title: String
public let content: String
public let source: String
public let tags: String?
public let model: String?
public let promptedAt: String?
public init(
title: String,
content: String,
source: String,
tags: String?,
model: String?,
promptedAt: String? = nil
) {
self.title = title
self.content = content
self.source = source
self.tags = tags
self.model = model
self.promptedAt = promptedAt
}
}
public struct ImportStatistics: Codable, Equatable, Sendable {
public var scanned = 0
public var imported = 0
public var skippedShort = 0
public var skippedLong = 0
public var skippedDuplicate = 0
public var skippedAgent = 0
public var skippedSystem = 0
public var skippedMalformed = 0
public init() {}
}
public struct ImportPreview: Sendable {
public let format: ImportFormat
public let sourceURL: URL
public let candidates: [ImportCandidate]
public let statistics: ImportStatistics
public init(
format: ImportFormat,
sourceURL: URL,
candidates: [ImportCandidate],
statistics: ImportStatistics
) {
self.format = format
self.sourceURL = sourceURL
self.candidates = candidates
self.statistics = statistics
}
}
public struct ImportReport: Codable, Sendable {
public let provider: String
public let format: String
public let sourceName: String
public let completedAt: Date
public let statistics: ImportStatistics
public init(preview: ImportPreview, statistics: ImportStatistics, completedAt: Date = Date()) {
provider = preview.format.provider.rawValue
format = preview.format.rawValue
sourceName = preview.sourceURL.lastPathComponent
self.completedAt = completedAt
self.statistics = statistics
}
}
public struct DedupIndex: Sendable {
public var hashes: Set<String>
public let existingContents: [String]
public init(hashes: Set<String>, existingContents: [String]) {
// Legacy imports encode their 12-character content hash in `source`.
// Manual and other pre-native rows may not. Hash every exact stored
// content as well so preview and commit use the same dedup identity
// without rewriting any existing row.
self.hashes = hashes.union(existingContents.map {
ImportText.contentHash(ImportText.trimmed($0))
})
self.existingContents = existingContents
}
public func contains(_ hash: String) -> Bool {
hashes.contains(hash)
}
}
|