161 lines
5.0 KiB
Swift
161 lines
5.0 KiB
Swift
//
|
|
// CreditsView.swift
|
|
// oAI
|
|
//
|
|
// Account credits and balance
|
|
//
|
|
|
|
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: String
|
|
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)
|
|
}
|