Files
steipete--codexbar/Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageSnapshot.swift
T
2026-07-13 12:22:33 +08:00

246 lines
9.0 KiB
Swift

import Foundation
public struct MiniMaxUsageSnapshot: Sendable {
public let planName: String?
public let availablePrompts: Int?
public let currentPrompts: Int?
public let remainingPrompts: Int?
public let windowMinutes: Int?
public let usedPercent: Double?
public let resetsAt: Date?
public let updatedAt: Date
public let services: [MiniMaxServiceUsage]?
public let billingSummary: MiniMaxBillingSummary?
public let pointsBalance: Double?
public let subscriptionExpiresAt: Date?
public let subscriptionRenewsAt: Date?
public var primaryService: MiniMaxServiceUsage? {
self.orderedQuotaServices.first
}
public var secondaryService: MiniMaxServiceUsage? {
let services = self.orderedQuotaServices
guard services.count >= 2 else { return nil }
return services[1]
}
public var tertiaryService: MiniMaxServiceUsage? {
let services = self.orderedQuotaServices
guard services.count >= 3 else { return nil }
return services[2]
}
public var orderedQuotaServices: [MiniMaxServiceUsage] {
guard let services, !services.isEmpty else { return [] }
return services.enumerated().sorted { lhs, rhs in
let lhsRank = self.quotaServiceRank(lhs.element, originalIndex: lhs.offset)
let rhsRank = self.quotaServiceRank(rhs.element, originalIndex: rhs.offset)
if lhsRank.primary != rhsRank.primary {
return lhsRank.primary < rhsRank.primary
}
if lhsRank.window != rhsRank.window {
return lhsRank.window < rhsRank.window
}
return lhsRank.originalIndex < rhsRank.originalIndex
}.map(\.element)
}
private func quotaServiceRank(
_ service: MiniMaxServiceUsage,
originalIndex: Int) -> (primary: Int, window: Int, originalIndex: Int)
{
(
primary: service.isPrimaryTextQuotaLane ? 0 : 1,
window: self.quotaWindowRank(service),
originalIndex: originalIndex)
}
private func quotaWindowRank(_ service: MiniMaxServiceUsage) -> Int {
let window = service.windowType.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
if window == "weekly" {
return 1
}
return 0
}
public init(
planName: String?,
availablePrompts: Int?,
currentPrompts: Int?,
remainingPrompts: Int?,
windowMinutes: Int?,
usedPercent: Double?,
resetsAt: Date?,
updatedAt: Date,
services: [MiniMaxServiceUsage]? = nil,
billingSummary: MiniMaxBillingSummary? = nil,
pointsBalance: Double? = nil,
subscriptionExpiresAt: Date? = nil,
subscriptionRenewsAt: Date? = nil)
{
self.planName = planName
self.availablePrompts = availablePrompts
self.currentPrompts = currentPrompts
self.remainingPrompts = remainingPrompts
self.windowMinutes = windowMinutes
self.usedPercent = usedPercent
self.resetsAt = resetsAt
self.updatedAt = updatedAt
self.services = services
self.billingSummary = billingSummary
self.pointsBalance = pointsBalance
self.subscriptionExpiresAt = subscriptionExpiresAt
self.subscriptionRenewsAt = subscriptionRenewsAt
}
public func withBillingSummary(_ billingSummary: MiniMaxBillingSummary?) -> MiniMaxUsageSnapshot {
MiniMaxUsageSnapshot(
planName: self.planName,
availablePrompts: self.availablePrompts,
currentPrompts: self.currentPrompts,
remainingPrompts: self.remainingPrompts,
windowMinutes: self.windowMinutes,
usedPercent: self.usedPercent,
resetsAt: self.resetsAt,
updatedAt: self.updatedAt,
services: self.services,
billingSummary: billingSummary,
pointsBalance: self.pointsBalance,
subscriptionExpiresAt: self.subscriptionExpiresAt,
subscriptionRenewsAt: self.subscriptionRenewsAt)
}
}
extension MiniMaxUsageSnapshot {
public func toUsageSnapshot() -> UsageSnapshot {
// If we have services array, use that for multi-service support
if let services = self.services, !services.isEmpty {
let primaryWindow = self.rateWindow(for: self.primaryService)
let secondaryWindow = self.rateWindow(for: self.secondaryService)
let tertiaryWindow = self.rateWindow(for: self.tertiaryService)
let planName = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines)
let loginMethod = (planName?.isEmpty ?? true) ? nil : planName
let identity = ProviderIdentitySnapshot(
providerID: .minimax,
accountEmail: nil,
accountOrganization: nil,
loginMethod: loginMethod)
return UsageSnapshot(
primary: primaryWindow,
secondary: secondaryWindow,
tertiary: tertiaryWindow,
providerCost: self.pointsBalanceSnapshot(),
minimaxUsage: self,
subscriptionExpiresAt: self.subscriptionExpiresAt,
subscriptionRenewsAt: self.subscriptionRenewsAt,
updatedAt: self.updatedAt,
identity: identity)
}
// Fallback to single-service mode for backward compatibility
let used = max(0, min(100, self.usedPercent ?? 0))
let resetDescription = self.limitDescription()
let primary = RateWindow(
usedPercent: used,
windowMinutes: self.windowMinutes,
resetsAt: self.resetsAt,
resetDescription: resetDescription)
let planName = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines)
let loginMethod = (planName?.isEmpty ?? true) ? nil : planName
let identity = ProviderIdentitySnapshot(
providerID: .minimax,
accountEmail: nil,
accountOrganization: nil,
loginMethod: loginMethod)
return UsageSnapshot(
primary: primary,
secondary: nil,
tertiary: nil,
providerCost: self.pointsBalanceSnapshot(),
minimaxUsage: self,
subscriptionExpiresAt: self.subscriptionExpiresAt,
subscriptionRenewsAt: self.subscriptionRenewsAt,
updatedAt: self.updatedAt,
identity: identity)
}
private func rateWindow(for service: MiniMaxServiceUsage?) -> RateWindow? {
guard let service else { return nil }
let windowMinutes = self.windowMinutes(for: service)
return RateWindow(
usedPercent: max(0, min(100, service.percent)),
windowMinutes: windowMinutes,
resetsAt: service.resetsAt,
resetDescription: service.resetDescription)
}
private func limitDescription() -> String? {
guard let availablePrompts, availablePrompts > 0 else {
return self.windowDescription()
}
if let windowDescription = self.windowDescription() {
return "\(availablePrompts) prompts / \(windowDescription)"
}
return "\(availablePrompts) prompts"
}
private func windowDescription() -> String? {
guard let windowMinutes, windowMinutes > 0 else { return nil }
if windowMinutes % (24 * 60) == 0 {
let days = windowMinutes / (24 * 60)
return "\(days) \(days == 1 ? "day" : "days")"
}
if windowMinutes % 60 == 0 {
let hours = windowMinutes / 60
return "\(hours) \(hours == 1 ? "hour" : "hours")"
}
return "\(windowMinutes) \(windowMinutes == 1 ? "minute" : "minutes")"
}
private func windowMinutes(for service: MiniMaxServiceUsage) -> Int? {
let windowType = service.windowType.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
// Handle "Today" case - 24 hours = 1440 minutes
if windowType == "today" {
return 24 * 60
}
if windowType == "weekly" {
return 7 * 24 * 60
}
// Handle time duration formats like "5 hours", "30 minutes", etc.
let components = windowType.split(separator: " ")
guard components.count >= 2 else { return nil }
guard let value = Int(components[0]) else { return nil }
let unit = components[1].lowercased()
switch unit {
case "hour", "hours", "h", "hr", "hrs":
return value * 60
case "minute", "minutes", "min", "mins", "m":
return value
case "day", "days", "d":
return value * 24 * 60
default:
return nil
}
}
private func pointsBalanceSnapshot() -> ProviderCostSnapshot? {
guard let pointsBalance, pointsBalance >= 0 else { return nil }
return ProviderCostSnapshot(
used: pointsBalance,
limit: 0,
currencyCode: "Points",
period: "MiniMax points balance",
updatedAt: self.updatedAt)
}
}