173 lines
4.7 KiB
Swift
173 lines
4.7 KiB
Swift
//
|
|
// EmailLogService.swift
|
|
// oAI
|
|
//
|
|
// Service for managing email handler activity logs
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
// Copyright (C) 2026 Rune Olsen
|
|
//
|
|
// This file is part of oAI.
|
|
//
|
|
// oAI is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// oAI is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
|
|
// Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public
|
|
// License along with oAI. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
import Foundation
|
|
import os
|
|
|
|
@Observable
|
|
final class EmailLogService {
|
|
static let shared = EmailLogService()
|
|
|
|
private let db = DatabaseService.shared
|
|
private let log = Logger(subsystem: "com.oai.oAI", category: "email-log")
|
|
|
|
private init() {}
|
|
|
|
// MARK: - Log Operations
|
|
|
|
/// Save a successful email processing log
|
|
func logSuccess(
|
|
sender: String,
|
|
subject: String,
|
|
emailContent: String,
|
|
aiResponse: String,
|
|
tokens: Int?,
|
|
cost: Double?,
|
|
responseTime: TimeInterval?,
|
|
modelId: String?
|
|
) {
|
|
let entry = EmailLog(
|
|
sender: sender,
|
|
subject: subject,
|
|
emailContent: emailContent,
|
|
aiResponse: aiResponse,
|
|
status: .success,
|
|
tokens: tokens,
|
|
cost: cost,
|
|
responseTime: responseTime,
|
|
modelId: modelId
|
|
)
|
|
|
|
db.saveEmailLog(entry)
|
|
log.info("Email log saved: \(sender) - \(subject) [SUCCESS]")
|
|
}
|
|
|
|
/// Save a failed email processing log
|
|
func logError(
|
|
sender: String,
|
|
subject: String,
|
|
emailContent: String,
|
|
errorMessage: String,
|
|
modelId: String?
|
|
) {
|
|
let entry = EmailLog(
|
|
sender: sender,
|
|
subject: subject,
|
|
emailContent: emailContent,
|
|
aiResponse: nil,
|
|
status: .error,
|
|
errorMessage: errorMessage,
|
|
modelId: modelId
|
|
)
|
|
|
|
db.saveEmailLog(entry)
|
|
log.error("Email log saved: \(sender) - \(subject) [ERROR: \(errorMessage)]")
|
|
}
|
|
|
|
/// Load recent email logs (default: 100 most recent)
|
|
func loadLogs(limit: Int = 100) -> [EmailLog] {
|
|
do {
|
|
return try db.loadEmailLogs(limit: limit)
|
|
} catch {
|
|
log.error("Failed to load email logs: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
/// Delete a specific log entry
|
|
func deleteLog(id: UUID) {
|
|
db.deleteEmailLog(id: id)
|
|
log.info("Email log deleted: \(id.uuidString)")
|
|
}
|
|
|
|
/// Clear all email logs
|
|
func clearAllLogs() {
|
|
db.clearEmailLogs()
|
|
log.info("All email logs cleared")
|
|
}
|
|
|
|
/// Get total count of email logs
|
|
func getLogCount() -> Int {
|
|
do {
|
|
return try db.getEmailLogCount()
|
|
} catch {
|
|
log.error("Failed to get email log count: \(error.localizedDescription)")
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// MARK: - Statistics
|
|
|
|
/// Get email processing statistics
|
|
func getStatistics() -> EmailStatistics {
|
|
let logs = loadLogs(limit: 1000) // Last 1000 for stats
|
|
|
|
let total = logs.count
|
|
let successful = logs.filter { $0.status == .success }.count
|
|
let errors = logs.filter { $0.status == .error }.count
|
|
|
|
let totalTokens = logs.compactMap { $0.tokens }.reduce(0, +)
|
|
let totalCost = logs.compactMap { $0.cost }.reduce(0.0, +)
|
|
|
|
let avgResponseTime: TimeInterval?
|
|
let responseTimes = logs.compactMap { $0.responseTime }
|
|
if !responseTimes.isEmpty {
|
|
avgResponseTime = responseTimes.reduce(0, +) / Double(responseTimes.count)
|
|
} else {
|
|
avgResponseTime = nil
|
|
}
|
|
|
|
return EmailStatistics(
|
|
total: total,
|
|
successful: successful,
|
|
errors: errors,
|
|
totalTokens: totalTokens,
|
|
totalCost: totalCost,
|
|
averageResponseTime: avgResponseTime
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Statistics Model
|
|
|
|
struct EmailStatistics {
|
|
let total: Int
|
|
let successful: Int
|
|
let errors: Int
|
|
let totalTokens: Int
|
|
let totalCost: Double
|
|
let averageResponseTime: TimeInterval?
|
|
|
|
var successRate: Double {
|
|
guard total > 0 else { return 0.0 }
|
|
return Double(successful) / Double(total)
|
|
}
|
|
|
|
var errorRate: Double {
|
|
guard total > 0 else { return 0.0 }
|
|
return Double(errors) / Double(total)
|
|
}
|
|
}
|