Add all-time usage statistics tab
Extends the existing Stats sheet (⌘⇧S) with a Session/All-Time segmented picker. All-Time aggregates tokens, cost, and message counts across every saved conversation, broken down by model and by conversation, using the modelId already stored per message — no schema change needed.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// UsageStats.swift
|
||||
// oAI
|
||||
//
|
||||
// All-time usage statistics (aggregated from the messages table)
|
||||
//
|
||||
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
||||
// Copyright (C) 2026 Rune Olsen
|
||||
//
|
||||
// This file is part of oAI.
|
||||
//
|
||||
// oAI is licensed under the PolyForm Noncommercial License 1.0.0.
|
||||
// You may use, study, modify, and share it for any noncommercial
|
||||
// purpose. Commercial use — including selling oAI or any part of
|
||||
// it, standalone or bundled into another product or service —
|
||||
// requires a separate commercial license from the copyright holder.
|
||||
//
|
||||
// See the LICENSE file or
|
||||
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
|
||||
// the full license text. For commercial licensing, contact Rune
|
||||
// Olsen via <https://oai.pm>.
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
struct UsageStats: Sendable {
|
||||
var totalMessages: Int = 0
|
||||
var totalTokens: Int = 0
|
||||
var totalCost: Double = 0.0
|
||||
var hasCostData: Bool = false
|
||||
var firstMessageDate: Date?
|
||||
var lastMessageDate: Date?
|
||||
|
||||
var totalTokensDisplay: String {
|
||||
if totalTokens >= 1_000_000 {
|
||||
return String(format: "%.1fM", Double(totalTokens) / 1_000_000)
|
||||
} else if totalTokens >= 1000 {
|
||||
return String(format: "%.1fK", Double(totalTokens) / 1000)
|
||||
} else {
|
||||
return "\(totalTokens)"
|
||||
}
|
||||
}
|
||||
|
||||
var totalCostDisplay: String {
|
||||
hasCostData ? String(format: "$%.4f", totalCost) : "N/A"
|
||||
}
|
||||
}
|
||||
|
||||
struct ModelUsageStat: Identifiable, Sendable {
|
||||
var id: String { modelId }
|
||||
let modelId: String
|
||||
var messageCount: Int
|
||||
var totalTokens: Int
|
||||
var totalCost: Double
|
||||
var hasCostData: Bool
|
||||
var lastUsed: Date
|
||||
|
||||
var totalTokensDisplay: String {
|
||||
if totalTokens >= 1_000_000 {
|
||||
return String(format: "%.1fM", Double(totalTokens) / 1_000_000)
|
||||
} else if totalTokens >= 1000 {
|
||||
return String(format: "%.1fK", Double(totalTokens) / 1000)
|
||||
} else {
|
||||
return "\(totalTokens)"
|
||||
}
|
||||
}
|
||||
|
||||
var totalCostDisplay: String {
|
||||
hasCostData ? String(format: "$%.4f", totalCost) : "N/A"
|
||||
}
|
||||
}
|
||||
|
||||
struct ConversationUsageStat: Identifiable, Sendable {
|
||||
let conversationId: UUID
|
||||
var id: UUID { conversationId }
|
||||
var name: String
|
||||
var messageCount: Int
|
||||
var totalTokens: Int
|
||||
var totalCost: Double
|
||||
var hasCostData: Bool
|
||||
|
||||
var totalTokensDisplay: String {
|
||||
if totalTokens >= 1_000_000 {
|
||||
return String(format: "%.1fM", Double(totalTokens) / 1_000_000)
|
||||
} else if totalTokens >= 1000 {
|
||||
return String(format: "%.1fK", Double(totalTokens) / 1000)
|
||||
} else {
|
||||
return "\(totalTokens)"
|
||||
}
|
||||
}
|
||||
|
||||
var totalCostDisplay: String {
|
||||
hasCostData ? String(format: "$%.4f", totalCost) : "N/A"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user