246 lines
9.0 KiB
Swift
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)
|
|
}
|
|
}
|