Files
oai-swift/oAI/Views/Screens/CreditsView.swift
T
runeandClaude Sonnet 5 0af644456e Update commercial licensing contact URL to oai.pm
Consolidates the mac.oai.pm subdomain references (introduced in the
PolyForm Noncommercial relicense) to the root oai.pm domain, across
the LICENSE file, README, and all Swift source file headers.

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

177 lines
5.6 KiB
Swift

//
// CreditsView.swift
// oAI
//
// Account credits and balance
//
// 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 SwiftUI
struct CreditsView: View {
let provider: Settings.Provider
@Environment(\.dismiss) var dismiss
@State private var credits: Credits?
@State private var isLoading = false
@State private var errorMessage: String?
var body: some View {
NavigationStack {
VStack(spacing: 24) {
// Provider icon
Image(systemName: provider.iconName)
.font(.system(size: 60))
.foregroundColor(Color.providerColor(provider))
Text(provider.displayName)
.font(.title2)
.fontWeight(.semibold)
Divider()
// Credits info based on provider
VStack(spacing: 16) {
switch provider {
case .openrouter:
openRouterCreditsView
case .anthropic:
Text("Anthropic Balance")
.font(.headline)
Text("Check your balance at:")
.font(.caption)
.foregroundColor(.secondary)
Link("console.anthropic.com", destination: URL(string: "https://console.anthropic.com")!)
.font(.body)
case .openai:
Text("OpenAI Balance")
.font(.headline)
Text("Check your usage at:")
.font(.caption)
.foregroundColor(.secondary)
Link("platform.openai.com", destination: URL(string: "https://platform.openai.com/usage")!)
.font(.body)
case .ollama:
Text("Ollama (Local)")
.font(.headline)
Text("Running locally — no credits needed!")
.font(.body)
.foregroundColor(.secondary)
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 40))
.foregroundColor(.green)
.padding(.top)
}
}
Spacer()
}
.padding()
.navigationTitle("Credits")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") { dismiss() }
}
}
}
.task {
await fetchCredits()
}
}
// MARK: - OpenRouter Credits
@ViewBuilder
private var openRouterCreditsView: some View {
Text("OpenRouter Credits")
.font(.headline)
if isLoading {
ProgressView("Loading...")
.padding()
} else if let error = errorMessage {
Text(error)
.font(.caption)
.foregroundColor(.red)
.padding()
Button("Retry") {
Task { await fetchCredits() }
}
} else if let credits = credits {
VStack(spacing: 12) {
CreditRow(label: "Remaining", value: credits.balanceDisplay, highlight: true)
Divider()
if let limit = credits.limit {
CreditRow(label: "Total Credits", value: String(format: "$%.2f", limit))
}
if let usage = credits.usage {
CreditRow(label: "Used", value: String(format: "$%.2f", usage))
}
}
} else {
Text("No credit data available")
.font(.caption)
.foregroundColor(.secondary)
}
}
private func fetchCredits() async {
guard provider == .openrouter else { return }
guard let apiProvider = ProviderRegistry.shared.getCurrentProvider() else {
errorMessage = "No API key configured"
return
}
isLoading = true
errorMessage = nil
do {
credits = try await apiProvider.getCredits()
isLoading = false
} catch {
errorMessage = error.localizedDescription
isLoading = false
}
}
}
struct CreditRow: View {
let label: LocalizedStringKey
let value: String
var highlight: Bool = false
var body: some View {
HStack {
Text(label)
.foregroundColor(highlight ? .primary : .secondary)
.fontWeight(highlight ? .semibold : .regular)
Spacer()
Text(value)
.font(highlight ? .title2.monospacedDigit() : .body.monospacedDigit())
.fontWeight(highlight ? .bold : .medium)
.foregroundColor(highlight ? .green : .primary)
}
}
}
#Preview {
CreditsView(provider: .openrouter)
}