853 lines · 33.2 KB · Swift
RawDownload
import Chartsimport CodexBarCoreimport SwiftUI @MainActorstruct PlanUtilizationHistoryChartMenuView: View {    private enum Layout {        static let chartHeight: CGFloat = 130        static let detailHeight: CGFloat = 16        static let emptyStateHeight: CGFloat = chartHeight + detailHeight        static let maxPoints = 30        static let maxAxisLabels = 4        static let barWidth: CGFloat = 6    }     private struct SeriesSelection: Hashable {        let name: PlanUtilizationSeriesName        let windowMinutes: Int         var id: String {            "\(self.name.rawValue):\(self.windowMinutes)"        }    }     private struct VisibleSeries: Identifiable, Equatable {        let selection: SeriesSelection        let title: String        let history: PlanUtilizationSeriesHistory         var id: String {            self.selection.id        }    }     private struct EntryPointAccumulator {        let effectiveBoundaryDate: Date        let displayBoundaryDate: Date        let observedAt: Date        let usedPercent: Double        let hasObservedResetBoundary: Bool    }     private struct ResetBoundaryLattice {        let referenceBoundaryDate: Date        let windowInterval: TimeInterval    }     private struct Point: Identifiable {        let id: Date        let index: Int        let date: Date        let usedPercent: Double        let isObserved: Bool    }     private struct Model {        let points: [Point]        let axisIndexes: [Double]        let xDomain: ClosedRange<Double>?        let pointsByID: [Date: Point]        let pointsByIndex: [Int: Point]        let barColor: Color        let trackColor: Color    }     private let provider: UsageProvider    private let visibleSeries: [VisibleSeries]    private let modelsBySeriesID: [String: Model]    private let emptyModel: Model    private let width: CGFloat     @State private var selectedSeriesID: String?    @State private var selectedPointID: Date?     init(        provider: UsageProvider,        histories: [PlanUtilizationSeriesHistory],        snapshot: UsageSnapshot? = nil,        width: CGFloat)    {        self.provider = provider        let visibleSeries = Self.visibleSeries(            histories: histories,            provider: provider,            snapshot: snapshot)        let referenceDate = Date()        self.visibleSeries = visibleSeries        self.modelsBySeriesID = Dictionary(uniqueKeysWithValues: visibleSeries.map {            ($0.id, Self.makeModel(history: $0.history, provider: provider, referenceDate: referenceDate))        })        self.emptyModel = Self.emptyModel(provider: provider)        self.width = width    }     var body: some View {        let effectiveSelectedSeries = self.visibleSeries.first(where: { $0.id == self.selectedSeriesID })            ?? self.visibleSeries.first        let model = effectiveSelectedSeries.flatMap { self.modelsBySeriesID[$0.id] } ?? self.emptyModel         VStack(alignment: .leading, spacing: 10) {            if self.visibleSeries.count > 1 {                Picker(selection: Binding(                    get: { effectiveSelectedSeries?.id ?? "" },                    set: { newValue in                        self.selectedSeriesID = newValue                        self.selectedPointID = nil                    })) {                        ForEach(self.visibleSeries) { series in                            Text(series.title).tag(series.id)                        }                    } label: {                        EmptyView()                    }                    .labelsHidden()                        .pickerStyle(.segmented)            }             if model.points.isEmpty {                ZStack {                    Text(Self.emptyStateText(title: effectiveSelectedSeries?.title))                        .font(.footnote)                        .foregroundStyle(.secondary)                }                .frame(maxWidth: .infinity)                .frame(height: Layout.emptyStateHeight)            } else {                self.utilizationChart(model: model)                    .chartYAxis(.hidden)                    .chartYScale(domain: 0...100)                    .chartXAxis {                        AxisMarks(values: model.axisIndexes) { value in                            AxisGridLine().foregroundStyle(Color.clear)                            AxisTick().foregroundStyle(Color.clear)                            AxisValueLabel {                                if let raw = value.as(Double.self) {                                    let index = Int(raw.rounded())                                    if let point = model.pointsByIndex[index] {                                        let isTrailingFullChartLabel = index == model.points.last?.index                                            && model.points.count == Layout.maxPoints                                        Self.axisLabel(                                            for: point,                                            windowMinutes: effectiveSelectedSeries?.history.windowMinutes ?? 0,                                            isTrailingFullChartLabel: isTrailingFullChartLabel)                                    }                                }                            }                        }                    }                    .chartLegend(.hidden)                    .frame(height: Layout.chartHeight)                    .accessibilityLabel(L("Plan utilization chart"))                    .accessibilityValue(                        model.points.isEmpty                            ? L("No data")                            : String(format: L("%d utilization samples"), model.points.count))                    .chartOverlay { proxy in                        GeometryReader { geo in                            MouseLocationReader { location in                                self.updateSelection(location: location, model: model, proxy: proxy, geo: geo)                            }                            .frame(maxWidth: .infinity, maxHeight: .infinity)                            .contentShape(Rectangle())                        }                    }                 Text(self.detailLine(model: model, windowMinutes: effectiveSelectedSeries?.history.windowMinutes ?? 0))                    .font(.caption)                    .foregroundStyle(.secondary)                    .lineLimit(1)                    .truncationMode(.tail)                    .frame(height: Layout.detailHeight, alignment: .leading)            }        }        .padding(.horizontal, 16)        .padding(.vertical, 10)        .frame(minWidth: self.width, maxWidth: .infinity, alignment: .topLeading)        .task(id: self.visibleSeries.map(\.id).joined(separator: ",")) {            guard let firstVisibleSeries = self.visibleSeries.first else { return }            guard !self.visibleSeries.contains(where: { $0.id == self.selectedSeriesID }) else { return }            self.selectedSeriesID = firstVisibleSeries.id            self.selectedPointID = nil        }    }     private nonisolated static func visibleSeries(        histories: [PlanUtilizationSeriesHistory],        provider: UsageProvider,        snapshot: UsageSnapshot?) -> [VisibleSeries]    {        let metadata = ProviderDescriptorRegistry.metadata[provider]        let allowedNames = self.visibleSeriesNames(provider: provider, snapshot: snapshot)        var historiesBySelection: [SeriesSelection: PlanUtilizationSeriesHistory] = [:]        for history in histories {            guard !history.entries.isEmpty else { continue }            guard history.windowMinutes > 0 else { continue }            guard allowedNames?.contains(history.name) ?? true else { continue }             let canonicalWindowMinutes = history.name.canonicalWindowMinutes(history.windowMinutes)            let selection = SeriesSelection(name: history.name, windowMinutes: canonicalWindowMinutes)            if let existingHistory = historiesBySelection[selection] {                historiesBySelection[selection] = PlanUtilizationSeriesHistory(                    name: history.name,                    windowMinutes: canonicalWindowMinutes,                    entries: Self.mergedEntries(existingHistory.entries + history.entries))            } else {                historiesBySelection[selection] = PlanUtilizationSeriesHistory(                    name: history.name,                    windowMinutes: canonicalWindowMinutes,                    entries: history.entries)            }        }         return historiesBySelection.values            .sorted { lhs, rhs in                let lhsOrder = self.seriesSortOrder(lhs.name)                let rhsOrder = self.seriesSortOrder(rhs.name)                if lhsOrder != rhsOrder {                    return lhsOrder < rhsOrder                }                if lhs.windowMinutes != rhs.windowMinutes {                    return lhs.windowMinutes < rhs.windowMinutes                }                return lhs.name.rawValue < rhs.name.rawValue            }            .map { history in                VisibleSeries(                    selection: SeriesSelection(name: history.name, windowMinutes: history.windowMinutes),                    title: self.seriesTitle(name: history.name, metadata: metadata),                    history: history)            }    }     nonisolated static func mergedEntries(        _ entries: [PlanUtilizationHistoryEntry]) -> [PlanUtilizationHistoryEntry]    {        var seen: Set<PlanUtilizationHistoryEntry> = []        return entries.filter { entry in            seen.insert(entry).inserted        }    }     private nonisolated static func visibleSeriesNames(        provider: UsageProvider,        snapshot: UsageSnapshot?) -> Set<PlanUtilizationSeriesName>?    {        guard let snapshot else { return nil }         var names: Set<PlanUtilizationSeriesName> = []        switch provider {        case .codex:            if snapshot.primary != nil { names.insert(.session) }            if snapshot.secondary != nil { names.insert(.weekly) }        case .claude:            if snapshot.primary != nil { names.insert(.session) }            if snapshot.secondary != nil { names.insert(.weekly) }            if snapshot.tertiary != nil,               ProviderDescriptorRegistry.metadata[provider]?.supportsOpus == true            {                names.insert(.opus)            }        default:            let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self)                + (snapshot.extraRateWindows?.filter(\.usageKnown).map(\.window) ?? [])            guard windows.contains(where: { $0.windowMinutes == 7 * 24 * 60 }) else { return nil }            names.insert(.weekly)        }         return names    }     private nonisolated static func makeModel(        history: PlanUtilizationSeriesHistory?,        provider: UsageProvider,        referenceDate: Date) -> Model    {        guard let history else {            return self.emptyModel(provider: provider)        }         var points = self.seriesPoints(history: history, referenceDate: referenceDate)        if points.count > Layout.maxPoints {            points = Array(points.suffix(Layout.maxPoints))        }         points = points.enumerated().map { offset, point in            Point(                id: point.id,                index: offset,                date: point.date,                usedPercent: point.usedPercent,                isObserved: point.isObserved)        }         let pointsByID = Dictionary(uniqueKeysWithValues: points.map { ($0.id, $0) })        let pointsByIndex = Dictionary(uniqueKeysWithValues: points.map { ($0.index, $0) })        let color = ProviderDescriptorRegistry.descriptor(for: provider).branding.color        let barColor = Color(red: color.red, green: color.green, blue: color.blue)        let trackColor = MenuHighlightStyle.progressTrack(false)         return Model(            points: points,            axisIndexes: self.axisIndexes(points: points, windowMinutes: history.windowMinutes),            xDomain: self.xDomain(points: points),            pointsByID: pointsByID,            pointsByIndex: pointsByIndex,            barColor: barColor,            trackColor: trackColor)    }     private nonisolated static func emptyModel(provider: UsageProvider) -> Model {        let color = ProviderDescriptorRegistry.descriptor(for: provider).branding.color        let barColor = Color(red: color.red, green: color.green, blue: color.blue)        let trackColor = MenuHighlightStyle.progressTrack(false)        return Model(            points: [],            axisIndexes: [],            xDomain: nil,            pointsByID: [:],            pointsByIndex: [:],            barColor: barColor,            trackColor: trackColor)    }     private nonisolated static func seriesPoints(        history: PlanUtilizationSeriesHistory,        referenceDate: Date) -> [Point]    {        guard history.windowMinutes > 0 else { return [] }        let windowInterval = Double(history.windowMinutes) * 60        let resetBoundaryLattice = self.resetBoundaryLattice(            entries: history.entries,            windowMinutes: history.windowMinutes)        var strongestObservedPointByPeriod: [Date: EntryPointAccumulator] = [:]         for entry in history.entries {            let candidate = self.observedPointCandidate(                for: entry,                windowMinutes: history.windowMinutes,                resetBoundaryLattice: resetBoundaryLattice)             if let existing = strongestObservedPointByPeriod[candidate.effectiveBoundaryDate],               !self.shouldPreferObservedPoint(candidate, over: existing)            {                continue            }            strongestObservedPointByPeriod[candidate.effectiveBoundaryDate] = candidate        }         guard !strongestObservedPointByPeriod.isEmpty else { return [] }         let sortedPeriodBoundaryDates = strongestObservedPointByPeriod.keys.sorted()        var points: [Point] = []        var previousPeriodBoundaryDate: Date?         for periodBoundaryDate in sortedPeriodBoundaryDates {            if let previousPeriodBoundaryDate {                var cursor = previousPeriodBoundaryDate.addingTimeInterval(windowInterval)                while cursor < periodBoundaryDate {                    points.append(Point(                        id: cursor,                        index: 0,                        date: cursor,                        usedPercent: 0,                        isObserved: false))                    cursor = cursor.addingTimeInterval(windowInterval)                }            }             if let bucket = strongestObservedPointByPeriod[periodBoundaryDate] {                points.append(Point(                    id: bucket.effectiveBoundaryDate,                    index: 0,                    date: bucket.displayBoundaryDate,                    usedPercent: bucket.usedPercent,                    isObserved: true))            }            previousPeriodBoundaryDate = periodBoundaryDate        }         if let lastObservedPeriodBoundaryDate = sortedPeriodBoundaryDates.last {            let currentPeriodBoundaryDate = self.currentPeriodBoundaryDate(                for: referenceDate,                windowMinutes: history.windowMinutes,                resetBoundaryLattice: resetBoundaryLattice)             if currentPeriodBoundaryDate > lastObservedPeriodBoundaryDate {                var cursor = lastObservedPeriodBoundaryDate.addingTimeInterval(windowInterval)                while cursor <= currentPeriodBoundaryDate {                    points.append(Point(                        id: cursor,                        index: 0,                        date: cursor,                        usedPercent: 0,                        isObserved: false))                    cursor = cursor.addingTimeInterval(windowInterval)                }            }        }         return points    }     private nonisolated static func observedPointCandidate(        for entry: PlanUtilizationHistoryEntry,        windowMinutes: Int,        resetBoundaryLattice: ResetBoundaryLattice?) -> EntryPointAccumulator    {        let rawResetBoundaryDate = entry.resetsAt.map(self.normalizedBoundaryDate)        let effectiveBoundaryDate = self.effectivePeriodBoundaryDate(            for: entry,            windowMinutes: windowMinutes,            rawResetBoundaryDate: rawResetBoundaryDate,            resetBoundaryLattice: resetBoundaryLattice)        return EntryPointAccumulator(            effectiveBoundaryDate: effectiveBoundaryDate,            displayBoundaryDate: rawResetBoundaryDate ?? effectiveBoundaryDate,            observedAt: entry.capturedAt,            usedPercent: max(0, min(100, entry.usedPercent)),            hasObservedResetBoundary: rawResetBoundaryDate != nil)    }     private nonisolated static func resetBoundaryLattice(        entries: [PlanUtilizationHistoryEntry],        windowMinutes: Int) -> ResetBoundaryLattice?    {        guard let latestObservedResetBoundaryDate = entries            .compactMap(\.resetsAt)            .map(self.normalizedBoundaryDate)            .max()        else {            return nil        }        return ResetBoundaryLattice(            referenceBoundaryDate: latestObservedResetBoundaryDate,            windowInterval: Double(windowMinutes) * 60)    }     private nonisolated static func normalizedBoundaryDate(_ date: Date) -> Date {        Date(timeIntervalSince1970: floor(date.timeIntervalSince1970))    }     private nonisolated static func effectivePeriodBoundaryDate(        for entry: PlanUtilizationHistoryEntry,        windowMinutes: Int,        rawResetBoundaryDate: Date?,        resetBoundaryLattice: ResetBoundaryLattice?) -> Date    {        if let rawResetBoundaryDate {            if let resetBoundaryLattice {                return self.closestPeriodBoundaryDate(                    to: rawResetBoundaryDate,                    resetBoundaryLattice: resetBoundaryLattice)            }            return rawResetBoundaryDate        }        if let resetBoundaryLattice {            return self.periodBoundaryDate(                containing: entry.capturedAt,                resetBoundaryLattice: resetBoundaryLattice)        }        return self.syntheticBoundaryDate(for: entry.capturedAt, windowMinutes: windowMinutes)    }     private nonisolated static func shouldPreferObservedPoint(        _ candidate: EntryPointAccumulator,        over existing: EntryPointAccumulator) -> Bool    {        if candidate.usedPercent != existing.usedPercent {            return candidate.usedPercent > existing.usedPercent        }        if candidate.hasObservedResetBoundary != existing.hasObservedResetBoundary {            return candidate.hasObservedResetBoundary        }        if candidate.displayBoundaryDate != existing.displayBoundaryDate {            return candidate.displayBoundaryDate > existing.displayBoundaryDate        }        return candidate.observedAt >= existing.observedAt    }     private nonisolated static func currentPeriodBoundaryDate(        for referenceDate: Date,        windowMinutes: Int,        resetBoundaryLattice: ResetBoundaryLattice?) -> Date    {        if let resetBoundaryLattice {            return self.periodBoundaryDate(                containing: referenceDate,                resetBoundaryLattice: resetBoundaryLattice)        }        return self.syntheticBoundaryDate(for: referenceDate, windowMinutes: windowMinutes)    }     private nonisolated static func closestPeriodBoundaryDate(        to rawBoundaryDate: Date,        resetBoundaryLattice: ResetBoundaryLattice) -> Date    {        let offset = rawBoundaryDate.timeIntervalSince(resetBoundaryLattice.referenceBoundaryDate)        let periodOffset = (offset / resetBoundaryLattice.windowInterval).rounded()        return resetBoundaryLattice.referenceBoundaryDate            .addingTimeInterval(periodOffset * resetBoundaryLattice.windowInterval)

Lines 1–500 of 853.