Files
oai-swift/oAI/Models/Message.swift

137 lines
3.8 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
var modelId: String? // Model ID that generated this message (e.g., "gpt-4", "claude-3-sonnet")
// 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,
modelId: String? = nil,
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.modelId = modelId
self.isStreaming = isStreaming
self.generatedImages = generatedImages
}
enum CodingKeys: String, CodingKey {
case id, role, content, tokens, cost, timestamp, attachments, responseTime, wasInterrupted, modelId
}
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"
}
}
}