225 lines · 7.8 KB · Swift
RawDownload
////  MiniMaxServiceUsage.swift//  CodexBarCore////  Created by Sisyphus on 2026-03-25.// import Foundation /// Represents the usage information for a specific MiniMax service.////// This struct encapsulates all the relevant details about how much of a particular/// MiniMax service has been used within its quota window, including reset timing/// and localized display strings.public struct MiniMaxServiceUsage: Sendable {    /// The service identifier (e.g., "text-generation", "text-to-speech", "image")    public let serviceType: String     /// The type of time window for the quota (e.g., "5 hours" or "Today")    /// This should be a localized string.    public let windowType: String     /// The specific time range for the current quota window.    /// For hourly quotas: "10:00-15:00(UTC+8)"    /// For daily quotas: full date range string    public let timeRange: String     /// The amount of quota that has been used.    public let usage: Int     /// The total quota limit for this service in the current window    public let limit: Int     /// The percentage of quota used (0-100)    public let percent: Double     /// Whether this quota window is explicitly unlimited.    public let isUnlimited: Bool     /// The timestamp when the quota will reset, if available    public let resetsAt: Date?     /// A localized description of when the quota resets (e.g., "Resets in 2 hours 30 minutes")    public let resetDescription: String     /// The remaining quota available (limit - usage)    public var remaining: Int {        max(0, self.limit - self.usage)    }     /// The display name for this service    public var displayName: String {        let normalized = self.serviceType.lowercased()        return switch normalized {        case "general":            "General"        case "video":            "Video"        case "text-generation":            "Text Generation"        case "text-to-speech":            "Text to Speech"        case "image":            "Image"        case "text generation":            "Text Generation"        case "text to speech":            "Text to Speech"        case "image generation":            "Image Generation"        case "text to video":            "Text to Video"        case "image to video":            "Image to Video"        case "music generation":            "Music Generation"        case "music generation · v2.6":            "Music Generation · v2.6"        case "music cover":            "Music Cover"        case "lyrics generation":            "Lyrics Generation"        case "image understanding":            "Image Understanding"        default:            self.serviceType        }    }     public var isPrimaryTextQuotaLane: Bool {        let normalized = self.serviceType.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()        return normalized == "general" || self.displayName == "Text Generation"    }     /// Creates a new MiniMaxServiceUsage instance.    ///    /// - Parameters:    ///   - serviceType: The service identifier    ///   - windowType: The type of time window (localized)    ///   - timeRange: The specific time range string    ///   - usage: The amount of quota used    ///   - limit: The total quota limit    ///   - percent: The percentage used (0-100)    ///   - resetsAt: Optional reset timestamp    ///   - resetDescription: Localized reset description    public init(        serviceType: String,        windowType: String,        timeRange: String,        usage: Int,        limit: Int,        percent: Double,        isUnlimited: Bool = false,        resetsAt: Date?,        resetDescription: String)    {        self.serviceType = serviceType        self.windowType = windowType        self.timeRange = timeRange        self.usage = usage        self.limit = limit        self.percent = percent        self.isUnlimited = isUnlimited        self.resetsAt = resetsAt        self.resetDescription = resetDescription    }} extension MiniMaxServiceUsage {    public static func parseWindowType(_ windowType: String) -> (windowType: String, windowMinutes: Int?) {        switch windowType.lowercased() {        case "5 hours", "5 小时":            return ("5 hours", 300)        case "today", "今日":            return ("Today", 1440)        default:            // Try to extract hours from string like "X hours"            if let hours = Int(windowType.components(separatedBy: .whitespaces).first ?? "") {                return (windowType, hours * 60)            }            return (windowType, nil)        }    }     public static func parseTimeRange(_ timeRange: String, now: Date) -> Date? {        let calendar = Calendar.current         // Handle "10:00-15:00(UTC+8)" format        if timeRange.contains("-"), timeRange.contains("("), timeRange.contains(")") {            // Extract the time part before the timezone            let components = timeRange.split(separator: "(")            guard components.count >= 1 else { return nil }            let timePart = String(components[0]).trimmingCharacters(in: .whitespaces)             // Split by "-" to get start and end times            let timeComponents = timePart.split(separator: "-")            guard timeComponents.count == 2 else { return nil }             let endTimeStr = String(timeComponents[1]).trimmingCharacters(in: .whitespaces)             // Parse end time (HH:mm format)            let timeFormatter = DateFormatter()            timeFormatter.dateFormat = "HH:mm"            timeFormatter.timeZone = TimeZone.current             guard let endTime = timeFormatter.date(from: endTimeStr) else { return nil }             // Get today's date components            let nowComponents = calendar.dateComponents([.year, .month, .day], from: now)            let endTimeComponents = calendar.dateComponents([.hour, .minute], from: endTime)             // Combine today's date with end time            var combinedComponents = DateComponents()            combinedComponents.year = nowComponents.year            combinedComponents.month = nowComponents.month            combinedComponents.day = nowComponents.day            combinedComponents.hour = endTimeComponents.hour            combinedComponents.minute = endTimeComponents.minute             guard let resultDate = calendar.date(from: combinedComponents) else { return nil }             // If the result date is in the past (before now), add one day            if resultDate < now {                return calendar.date(byAdding: .day, value: 1, to: resultDate)            }             return resultDate        }         // Handle "2026/03/25 00:00 - 2026/03/26 00:00" format        if timeRange.contains(" - ") {            let dateComponents = timeRange.split(separator: " - ")            guard dateComponents.count == 2 else { return nil }             let endDateStr = String(dateComponents[1]).trimmingCharacters(in: .whitespaces)             let dateFormatter = DateFormatter()            dateFormatter.dateFormat = "yyyy/MM/dd HH:mm"            dateFormatter.timeZone = TimeZone.current             return dateFormatter.date(from: endDateStr)        }         return nil    }     public static func generateResetDescription(resetsAt: Date, now: Date = Date()) -> String {        let calendar = Calendar.current        let components = calendar.dateComponents([.hour, .minute], from: now, to: resetsAt)         guard let hours = components.hour, let minutes = components.minute else {            return "Resets soon"        }         if hours > 0, minutes > 0 {            return "Resets in \(hours) hours \(minutes) minutes"        } else if hours > 0 {            return "Resets in \(hours) hour\(hours > 1 ? "s" : "")"        } else if minutes > 0 {            return "Resets in \(minutes) minute\(minutes > 1 ? "s" : "")"        } else {            return "Resets now"        }    }}