litellm/apps/macos-usage/Sources/LiteLLMUsageMenuBar/Models.swift

176 lines
4.9 KiB
Swift

import Foundation
struct UserInfoResponse: Decodable, Sendable {
let userID: String
let spend: Double
let maxBudget: Double?
let budgetDuration: String?
let budgetResetAt: Date?
let teams: [String]
enum CodingKeys: String, CodingKey {
case userID = "user_id"
case spend
case maxBudget = "max_budget"
case budgetDuration = "budget_duration"
case budgetResetAt = "budget_reset_at"
case teams
}
}
struct TeamInfoResponse: Decodable, Sendable {
let teamInfo: TeamBudget
enum CodingKeys: String, CodingKey {
case teamInfo = "team_info"
}
}
struct TeamBudget: Decodable, Sendable {
let spend: Double
let maxBudget: Double?
let budgetDuration: String?
let budgetResetAt: Date?
enum CodingKeys: String, CodingKey {
case spend
case maxBudget = "max_budget"
case budgetDuration = "budget_duration"
case budgetResetAt = "budget_reset_at"
}
}
struct KeyListResponse: Decodable, Sendable {
let keys: [String]
}
struct KeyInfoResponse: Decodable, Sendable {
let info: KeyBudget
enum CodingKeys: String, CodingKey {
case info
}
}
struct KeyBudget: Decodable, Sendable {
let keyAlias: String?
let keyName: String
let spend: Double
let maxBudget: Double?
let budgetDuration: String?
let budgetResetAt: Date?
let teamID: String?
enum CodingKeys: String, CodingKey {
case keyAlias = "key_alias"
case keyName = "key_name"
case spend
case maxBudget = "max_budget"
case budgetDuration = "budget_duration"
case budgetResetAt = "budget_reset_at"
case teamID = "team_id"
}
}
struct GatewayKey: Identifiable, Equatable, Sendable {
let value: String
let keyName: String
let alias: String?
var id: String { keyName }
var displayName: String { alias ?? keyName }
}
struct AuthSession: Sendable {
let token: String
let apiKey: String
let expiresAt: Date
init?(token: String) {
let segments: [Substring] = token.split(separator: ".")
guard segments.count == 3 else { return nil }
var encoded = String(segments[1]).replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
encoded += String(repeating: "=", count: (4 - encoded.count % 4) % 4)
guard let data = Data(base64Encoded: encoded),
let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let apiKey = payload["key"] as? String,
let exp = payload["exp"] as? TimeInterval else { return nil }
self.token = token
self.apiKey = apiKey
self.expiresAt = Date(timeIntervalSince1970: exp)
}
}
enum UsageState: Equatable, Sendable {
case needsKey
case loading
case ready(UsageSnapshot)
case unavailable(String)
case failure(String)
}
enum AutoRefreshInterval: Int, CaseIterable, Identifiable, Sendable {
case off = 0
case adaptive = -1
case oneMinute = 60
case fiveMinutes = 300
case fifteenMinutes = 900
case thirtyMinutes = 1800
case oneHour = 3600
var id: Self { self }
var fixedSeconds: TimeInterval? {
switch self {
case .off, .adaptive: nil
case .oneMinute, .fiveMinutes, .fifteenMinutes, .thirtyMinutes, .oneHour:
TimeInterval(rawValue)
}
}
var title: String {
switch self {
case .off: "끔"
case .adaptive: "적응형"
case .oneMinute: "1분"
case .fiveMinutes: "5분"
case .fifteenMinutes: "15분"
case .thirtyMinutes: "30분"
case .oneHour: "1시간"
}
}
}
struct UsageSnapshot: Equatable, Sendable {
let spend: Double
let limit: Double
let percentage: Double
let budgetDuration: String
let resetAt: Date?
}
enum GatewayError: LocalizedError {
case invalidResponse
case unauthorized
case forbidden
case notFound
case server(Int)
case noUserBudget
case notMonthlyBudget(String)
case invalidLoginResponse
case invalidGatewayURL
var errorDescription: String? {
switch self {
case .invalidResponse: "게이트웨이 응답을 읽을 수 없습니다"
case .unauthorized: "로그인 정보가 올바르지 않거나 세션이 만료되었습니다"
case .forbidden: "개인 사용량 조회가 거부되었습니다 (HTTP 403)"
case .notFound: "사용자를 찾을 수 없습니다"
case let .server(code): "게이트웨이 오류 (HTTP \(code))"
case .noUserBudget: "개인 예산 한도가 설정되지 않았습니다"
case let .notMonthlyBudget(duration): "개인 예산이 월 단위가 아닙니다 (\(duration))"
case .invalidLoginResponse: "로그인 응답을 읽을 수 없습니다"
case .invalidGatewayURL: "올바른 게이트웨이 URL을 입력하세요 (예: https://gateway.example.com)"
}
}
}