134 lines
3.6 KiB
Swift
134 lines
3.6 KiB
Swift
//
|
|
// Message.swift
|
|
// oAI
|
|
//
|
|
// Core message model for chat conversations
|
|
//
|
|
|
|
import Foundation
|
|
|
|
enum MessageRole: String, Codable {
|
|
case user
|
|
case assistant
|
|
case system
|
|
}
|
|
|
|
struct Message: Identifiable, Codable, Equatable {
|
|
let id: UUID
|
|
let role: MessageRole
|
|
var content: String
|
|
var tokens: Int?
|
|
var cost: Double?
|
|
let timestamp: Date
|
|
let attachments: [FileAttachment]?
|
|
var responseTime: TimeInterval? // Time taken to generate response in seconds
|
|
var wasInterrupted: Bool = false // Whether generation was cancelled
|
|
|
|
// Streaming state (not persisted)
|
|
var isStreaming: Bool = false
|
|
|
|
// Generated images from image-output models (base64-decoded PNG/JPEG data)
|
|
var generatedImages: [Data]? = nil
|
|
|
|
init(
|
|
id: UUID = UUID(),
|
|
role: MessageRole,
|
|
content: String,
|
|
tokens: Int? = nil,
|
|
cost: Double? = nil,
|
|
timestamp: Date = Date(),
|
|
attachments: [FileAttachment]? = nil,
|
|
responseTime: TimeInterval? = nil,
|
|
wasInterrupted: Bool = false,
|
|
isStreaming: Bool = false,
|
|
generatedImages: [Data]? = nil
|
|
) {
|
|
self.id = id
|
|
self.role = role
|
|
self.content = content
|
|
self.tokens = tokens
|
|
self.cost = cost
|
|
self.timestamp = timestamp
|
|
self.attachments = attachments
|
|
self.responseTime = responseTime
|
|
self.wasInterrupted = wasInterrupted
|
|
self.isStreaming = isStreaming
|
|
self.generatedImages = generatedImages
|
|
}
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id, role, content, tokens, cost, timestamp, attachments, responseTime, wasInterrupted
|
|
}
|
|
|
|
static func == (lhs: Message, rhs: Message) -> Bool {
|
|
lhs.id == rhs.id &&
|
|
lhs.content == rhs.content &&
|
|
lhs.tokens == rhs.tokens &&
|
|
lhs.cost == rhs.cost &&
|
|
lhs.responseTime == rhs.responseTime &&
|
|
lhs.wasInterrupted == rhs.wasInterrupted &&
|
|
lhs.isStreaming == rhs.isStreaming &&
|
|
lhs.generatedImages == rhs.generatedImages
|
|
}
|
|
}
|
|
|
|
struct FileAttachment: Codable, Equatable {
|
|
let path: String
|
|
let type: AttachmentType
|
|
let data: Data? // file contents: raw bytes for images/PDFs, UTF-8 for text
|
|
|
|
enum AttachmentType: String, Codable {
|
|
case image
|
|
case pdf
|
|
case text
|
|
}
|
|
|
|
/// Detect attachment type from file extension
|
|
static func typeFromExtension(_ path: String) -> AttachmentType {
|
|
let ext = (path as NSString).pathExtension.lowercased()
|
|
switch ext {
|
|
case "png", "jpg", "jpeg", "gif", "webp", "bmp", "svg":
|
|
return .image
|
|
case "pdf":
|
|
return .pdf
|
|
default:
|
|
return .text
|
|
}
|
|
}
|
|
|
|
/// MIME type string for the file (used in base64 data URLs)
|
|
var mimeType: String {
|
|
let ext = (path as NSString).pathExtension.lowercased()
|
|
switch ext {
|
|
case "png": return "image/png"
|
|
case "jpg", "jpeg": return "image/jpeg"
|
|
case "gif": return "image/gif"
|
|
case "webp": return "image/webp"
|
|
case "bmp": return "image/bmp"
|
|
case "svg": return "image/svg+xml"
|
|
case "pdf": return "application/pdf"
|
|
default: return "text/plain"
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Display Helpers
|
|
|
|
extension MessageRole {
|
|
var displayName: String {
|
|
switch self {
|
|
case .user: return "You"
|
|
case .assistant: return "Assistant"
|
|
case .system: return "System"
|
|
}
|
|
}
|
|
|
|
var iconName: String {
|
|
switch self {
|
|
case .user: return "person.circle.fill"
|
|
case .assistant: return "cpu"
|
|
case .system: return "info.circle.fill"
|
|
}
|
|
}
|
|
}
|