Files
oai-swift/oAI/Views/Screens/StatsView.swift
T
runeandClaude Sonnet 5 cf3f4ebfe4 Relicense oAI from AGPL-3.0-or-later to PolyForm Noncommercial 1.0.0
Switches the project from AGPL to a source-available license that
restricts commercial use — selling oAI or any part of it, standalone
or bundled into another product/service, now requires a separate
commercial license from the copyright holder. Noncommercial use,
study, modification, and sharing remain fully permitted.

Updates: LICENSE (canonical PolyForm Noncommercial 1.0.0 text +
commercial licensing contact note), SPDX headers and file-header
boilerplate across all Swift source files, the in-app About dialog's
license link (+ its localization catalog entry), README.md and
DEVELOPMENT.md license sections.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:16:09 +02:00

165 lines
5.7 KiB
Swift

//
// StatsView.swift
// oAI
//
// Session statistics screen
//
// 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://mac.oai.pm>.
import SwiftUI
struct StatsView: View {
let stats: SessionStats
let model: ModelInfo?
let provider: Settings.Provider
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationStack {
List {
Section("Session Info") {
StatRow(label: "Provider", value: provider.displayName)
StatRow(label: "Model", value: model?.name ?? "None selected")
StatRow(label: "Messages", value: "\(stats.messageCount)")
}
Section("Token Usage") {
StatRow(label: "Input Tokens", value: stats.totalInputTokens.formatted())
StatRow(label: "Output Tokens", value: stats.totalOutputTokens.formatted())
StatRow(label: "Total Tokens", value: stats.totalTokens.formatted())
if stats.totalTokens > 0 {
HStack {
Text("Token Distribution")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
GeometryReader { geo in
HStack(spacing: 0) {
Rectangle()
.fill(Color.blue)
.frame(width: geo.size.width * CGFloat(stats.totalInputTokens) / CGFloat(stats.totalTokens))
Rectangle()
.fill(Color.green)
.frame(width: geo.size.width * CGFloat(stats.totalOutputTokens) / CGFloat(stats.totalTokens))
}
}
.frame(height: 20)
.cornerRadius(4)
}
}
}
Section("Costs") {
StatRow(label: "Total Cost", value: stats.totalCostDisplay)
if stats.messageCount > 0 {
StatRow(label: "Avg per Message", value: stats.averageCostDisplay)
}
}
if let model = model {
Section("Model Details") {
StatRow(label: "Context Length", value: model.contextLengthDisplay)
StatRow(label: "Prompt Price", value: model.promptPriceDisplay + "/1M tokens")
StatRow(label: "Completion Price", value: model.completionPriceDisplay + "/1M tokens")
HStack {
Text("Capabilities")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
HStack(spacing: 8) {
if model.capabilities.vision {
CapabilityBadge(icon: "👁️", label: "Vision")
}
if model.capabilities.tools {
CapabilityBadge(icon: "🔧", label: "Tools")
}
if model.capabilities.online {
CapabilityBadge(icon: "🌐", label: "Online")
}
}
}
}
}
}
#if os(iOS)
.listStyle(.insetGrouped)
#else
.listStyle(.sidebar)
#endif
.navigationTitle("Statistics")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
dismiss()
}
}
}
.frame(minWidth: 500, idealWidth: 550, minHeight: 450, idealHeight: 500)
}
}
}
struct StatRow: View {
let label: LocalizedStringKey
let value: String
var body: some View {
HStack {
Text(label)
.font(.body)
Spacer()
Text(value)
.font(.body.monospacedDigit())
.foregroundColor(.secondary)
}
}
}
struct CapabilityBadge: View {
let icon: String
let label: LocalizedStringKey
var body: some View {
HStack(spacing: 2) {
Text(icon)
Text(label)
}
.font(.caption2)
.padding(.horizontal, 6)
.padding(.vertical, 3)
.background(Color.blue.opacity(0.1))
.cornerRadius(4)
}
}
#Preview {
StatsView(
stats: SessionStats(
totalInputTokens: 1250,
totalOutputTokens: 3420,
totalCost: 0.0152,
messageCount: 12
),
model: ModelInfo.mockModels.first,
provider: .openrouter
)
}