1409 lines · 58.5 KB · Swift
RawDownload
import AppKitimport CodexBarCoreimport Observationimport QuartzCoreimport SwiftUI // MARK: - NSMenu construction extension StatusItemController {    private static let menuCardBaseWidth: CGFloat = 310    private static let maxOverviewProviders = SettingsStore.mergedOverviewProviderLimit    private static let overviewRowIdentifierPrefix = "overviewRow-"    private static let menuOpenRefreshDelay: Duration = .seconds(1.2)    static let usageBreakdownChartID = "usageBreakdownChart"    static let creditsHistoryChartID = "creditsHistoryChart"    static let costHistoryChartID = "costHistoryChart"    static let usageHistoryChartID = "usageHistoryChart"     private func menuCardWidth(for providers: [UsageProvider], menu: NSMenu? = nil) -> CGFloat {        _ = menu        return Self.menuCardBaseWidth    }     func makeMenu() -> NSMenu {        guard self.shouldMergeIcons else {            return self.makeMenu(for: nil)        }        let menu = NSMenu()        menu.autoenablesItems = false        menu.delegate = self        return menu    }     func menuWillOpen(_ menu: NSMenu) {        if self.isHostedSubviewMenu(menu) {            self.hydrateHostedSubviewMenuIfNeeded(menu)            self.refreshHostedSubviewHeights(in: menu)            if Self.menuRefreshEnabled, self.isOpenAIWebSubviewMenu(menu) {                self.store.requestOpenAIDashboardRefreshIfStale(reason: "submenu open")            }            self.openMenus[ObjectIdentifier(menu)] = menu            // Removed redundant async refresh - single pass is sufficient after initial layout            return        }         var provider: UsageProvider?        if self.shouldMergeIcons {            let resolvedProvider = self.resolvedMenuProvider()            self.lastMenuProvider = resolvedProvider ?? .codex            provider = resolvedProvider        } else {            if let menuProvider = self.menuProviders[ObjectIdentifier(menu)] {                self.lastMenuProvider = menuProvider                provider = menuProvider            } else if menu === self.fallbackMenu {                self.lastMenuProvider = self.store.enabledProvidersForDisplay().first ?? .codex                provider = nil            } else {                let resolved = self.store.enabledProvidersForDisplay().first ?? .codex                self.lastMenuProvider = resolved                provider = resolved            }        }         let didRefresh = self.menuNeedsRefresh(menu)        if didRefresh {            self.populateMenu(menu, provider: provider)            self.markMenuFresh(menu)            // Heights are already set during populateMenu, no need to remeasure        }        self.openMenus[ObjectIdentifier(menu)] = menu        // Only schedule refresh after menu is registered as open - refreshNow is called async        if Self.menuRefreshEnabled {            self.scheduleOpenMenuRefresh(for: menu)        }    }     func menuDidClose(_ menu: NSMenu) {        let key = ObjectIdentifier(menu)         self.openMenus.removeValue(forKey: key)        self.menuRefreshTasks.removeValue(forKey: key)?.cancel()         let isPersistentMenu = menu === self.mergedMenu ||            menu === self.fallbackMenu ||            self.providerMenus.values.contains { $0 === menu }        if !isPersistentMenu {            self.menuProviders.removeValue(forKey: key)            self.menuVersions.removeValue(forKey: key)        }        for menuItem in menu.items {            (menuItem.view as? MenuCardHighlighting)?.setHighlighted(false)        }    }     func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {        for menuItem in menu.items {            let highlighted = menuItem == item && menuItem.isEnabled            (menuItem.view as? MenuCardHighlighting)?.setHighlighted(highlighted)        }    }     private func populateMenu(_ menu: NSMenu, provider: UsageProvider?) {        let enabledProviders = self.store.enabledProvidersForDisplay()        let includesOverview = self.includesOverviewTab(enabledProviders: enabledProviders)        let switcherSelection = self.shouldMergeIcons && enabledProviders.count > 1            ? self.resolvedSwitcherSelection(                enabledProviders: enabledProviders,                includesOverview: includesOverview)            : nil        let isOverviewSelected = switcherSelection == .overview        let selectedProvider = if isOverviewSelected {            self.resolvedMenuProvider(enabledProviders: enabledProviders)        } else {            switcherSelection?.provider ?? provider        }        let menuWidth = self.menuCardWidth(for: enabledProviders, menu: menu)        let currentProvider = selectedProvider ?? enabledProviders.first ?? .codex        let codexAccountDisplay = isOverviewSelected ? nil : self.codexAccountMenuDisplay(for: currentProvider)        let tokenAccountDisplay = isOverviewSelected ? nil : self.tokenAccountMenuDisplay(for: currentProvider)        let showAllTokenAccounts = tokenAccountDisplay?.showAll ?? false        let openAIContext = self.openAIWebContext(            currentProvider: currentProvider,            showAllTokenAccounts: showAllTokenAccounts)         let hasTokenSwitcher = menu.items.contains { $0.view is TokenAccountSwitcherView }        let hasCodexSwitcher = menu.items.contains { $0.view is CodexAccountSwitcherView }        let switcherProvidersMatch = enabledProviders == self.lastSwitcherProviders        let switcherUsageBarsShowUsedMatch = self.settings.usageBarsShowUsed == self.lastSwitcherUsageBarsShowUsed        let switcherSelectionMatches = switcherSelection == self.lastMergedSwitcherSelection        let switcherOverviewAvailabilityMatches = includesOverview == self.lastSwitcherIncludesOverview        let tokenSwitcherCompatible = tokenAccountDisplay == nil && !hasTokenSwitcher        let codexSwitcherCompatible = codexAccountDisplay == self.lastCodexAccountMenuDisplay &&            ((codexAccountDisplay == nil && !hasCodexSwitcher) || (codexAccountDisplay != nil && hasCodexSwitcher))        let canSmartUpdate = self.shouldMergeIcons &&            enabledProviders.count > 1 &&            !isOverviewSelected &&            switcherProvidersMatch &&            switcherUsageBarsShowUsedMatch &&            switcherSelectionMatches &&            switcherOverviewAvailabilityMatches &&            tokenSwitcherCompatible &&            codexSwitcherCompatible &&            !menu.items.isEmpty &&            menu.items.first?.view is ProviderSwitcherView         if canSmartUpdate {            self.updateMenuContent(                menu,                provider: selectedProvider,                currentProvider: currentProvider,                menuWidth: menuWidth,                openAIContext: openAIContext)            return        }         menu.removeAllItems()         let descriptor = MenuDescriptor.build(            provider: selectedProvider,            store: self.store,            settings: self.settings,            account: self.account,            managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,            codexAccountPromotionCoordinator: self.codexAccountPromotionCoordinator,            updateReady: self.updater.updateStatus.isUpdateReady,            includeContextualActions: !isOverviewSelected)         self.addProviderSwitcherIfNeeded(            to: menu,            enabledProviders: enabledProviders,            includesOverview: includesOverview,            selection: switcherSelection ?? .provider(currentProvider))        // Track which providers the switcher was built with for smart update detection        if self.shouldMergeIcons, enabledProviders.count > 1 {            self.lastSwitcherProviders = enabledProviders            self.lastSwitcherUsageBarsShowUsed = self.settings.usageBarsShowUsed            self.lastMergedSwitcherSelection = switcherSelection            self.lastSwitcherIncludesOverview = includesOverview        }        self.addCodexAccountSwitcherIfNeeded(to: menu, display: codexAccountDisplay)        self.lastCodexAccountMenuDisplay = codexAccountDisplay        self.addTokenAccountSwitcherIfNeeded(to: menu, display: tokenAccountDisplay)        let menuContext = MenuCardContext(            currentProvider: currentProvider,            selectedProvider: selectedProvider,            menuWidth: menuWidth,            tokenAccountDisplay: tokenAccountDisplay,            openAIContext: openAIContext)        if isOverviewSelected {            if self.addOverviewRows(                to: menu,                enabledProviders: enabledProviders,                menuWidth: menuWidth)            {                menu.addItem(.separator())            } else {                self.addOverviewEmptyState(to: menu, enabledProviders: enabledProviders)                menu.addItem(.separator())            }        } else {            let addedOpenAIWebItems = self.addMenuCards(to: menu, context: menuContext)            self.addOpenAIWebItemsIfNeeded(                to: menu,                currentProvider: currentProvider,                context: openAIContext,                addedOpenAIWebItems: addedOpenAIWebItems)            if self.addUsageHistoryMenuItemIfNeeded(to: menu, provider: currentProvider) {                menu.addItem(.separator())            }        }        self.addActionableSections(descriptor.sections, to: menu, width: menuWidth)    }     /// Smart update: only rebuild content sections when switching providers (keep the switcher intact).    private func updateMenuContent(        _ menu: NSMenu,        provider: UsageProvider?,        currentProvider: UsageProvider,        menuWidth: CGFloat,        openAIContext: OpenAIWebContext)    {        // Batch menu updates to prevent visual flickering during provider switch.        CATransaction.begin()        CATransaction.setDisableActions(true)        defer { CATransaction.commit() }         var contentStartIndex = 0        if menu.items.first?.view is ProviderSwitcherView {            contentStartIndex = 2        }        if menu.items.count > contentStartIndex,           menu.items[contentStartIndex].view is CodexAccountSwitcherView        {            contentStartIndex += 2        }        if menu.items.count > contentStartIndex,           menu.items[contentStartIndex].view is TokenAccountSwitcherView        {            contentStartIndex += 2        }        while menu.items.count > contentStartIndex {            menu.removeItem(at: contentStartIndex)        }         let descriptor = MenuDescriptor.build(            provider: provider,            store: self.store,            settings: self.settings,            account: self.account,            managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,            codexAccountPromotionCoordinator: self.codexAccountPromotionCoordinator,            updateReady: self.updater.updateStatus.isUpdateReady)         let menuContext = MenuCardContext(            currentProvider: currentProvider,            selectedProvider: provider,            menuWidth: menuWidth,            tokenAccountDisplay: nil,            openAIContext: openAIContext)        let addedOpenAIWebItems = self.addMenuCards(to: menu, context: menuContext)        self.addOpenAIWebItemsIfNeeded(            to: menu,            currentProvider: currentProvider,            context: openAIContext,            addedOpenAIWebItems: addedOpenAIWebItems)        if self.addUsageHistoryMenuItemIfNeeded(to: menu, provider: currentProvider) {            menu.addItem(.separator())        }        self.addActionableSections(descriptor.sections, to: menu, width: menuWidth)    }     private struct OpenAIWebContext {        let hasUsageBreakdown: Bool        let hasCreditsHistory: Bool        let hasCostHistory: Bool        let canShowBuyCredits: Bool        let hasOpenAIWebMenuItems: Bool    }     private struct MenuCardContext {        let currentProvider: UsageProvider        let selectedProvider: UsageProvider?        let menuWidth: CGFloat        let tokenAccountDisplay: TokenAccountMenuDisplay?        let openAIContext: OpenAIWebContext    }     private func openAIWebContext(        currentProvider: UsageProvider,        showAllTokenAccounts: Bool) -> OpenAIWebContext    {        let codexProjection = self.store.codexConsumerProjectionIfNeeded(            for: currentProvider,            surface: .liveCard)        let hasCreditsHistory = codexProjection?.hasCreditsHistory == true        let hasUsageBreakdown = codexProjection?.hasUsageBreakdown == true        let hasCostHistory = self.settings.isCostUsageEffectivelyEnabled(for: currentProvider) &&            (self.store.tokenSnapshot(for: currentProvider)?.daily.isEmpty == false)        let canShowBuyCredits = self.settings.showOptionalCreditsAndExtraUsage &&            codexProjection?.canShowBuyCredits == true        let hasOpenAIWebMenuItems = !showAllTokenAccounts &&            (hasCreditsHistory || hasUsageBreakdown || hasCostHistory)        return OpenAIWebContext(            hasUsageBreakdown: hasUsageBreakdown,            hasCreditsHistory: hasCreditsHistory,            hasCostHistory: hasCostHistory,            canShowBuyCredits: canShowBuyCredits,            hasOpenAIWebMenuItems: hasOpenAIWebMenuItems)    }     private func addProviderSwitcherIfNeeded(        to menu: NSMenu,        enabledProviders: [UsageProvider],        includesOverview: Bool,        selection: ProviderSwitcherSelection)    {        guard self.shouldMergeIcons, enabledProviders.count > 1 else { return }        let switcherItem = self.makeProviderSwitcherItem(            providers: enabledProviders,            includesOverview: includesOverview,            selected: selection,            menu: menu)        menu.addItem(switcherItem)        menu.addItem(.separator())    }     private func addTokenAccountSwitcherIfNeeded(to menu: NSMenu, display: TokenAccountMenuDisplay?) {        guard let display, display.showSwitcher else { return }        let switcherItem = self.makeTokenAccountSwitcherItem(display: display, menu: menu)        menu.addItem(switcherItem)        menu.addItem(.separator())    }     private func addCodexAccountSwitcherIfNeeded(to menu: NSMenu, display: CodexAccountMenuDisplay?) {        guard let display else { return }        let switcherItem = self.makeCodexAccountSwitcherItem(display: display, menu: menu)        menu.addItem(switcherItem)        menu.addItem(.separator())    }     @discardableResult    private func addOverviewRows(        to menu: NSMenu,        enabledProviders: [UsageProvider],        menuWidth: CGFloat) -> Bool    {        let overviewProviders = self.settings.reconcileMergedOverviewSelectedProviders(            activeProviders: enabledProviders)        let rows: [(provider: UsageProvider, model: UsageMenuCardView.Model)] = overviewProviders            .compactMap { provider in                guard let model = self.menuCardModel(for: provider) else { return nil }                return (provider: provider, model: model)            }        guard !rows.isEmpty else { return false }         for (index, row) in rows.enumerated() {            let identifier = "\(Self.overviewRowIdentifierPrefix)\(row.provider.rawValue)"            let item = self.makeMenuCardItem(                OverviewMenuCardRowView(model: row.model, width: menuWidth),                id: identifier,                width: menuWidth,                onClick: { [weak self, weak menu] in                    guard let self, let menu else { return }                    self.selectOverviewProvider(row.provider, menu: menu)                })            // Keep menu item action wired for keyboard activation and accessibility action paths.            item.target = self            item.action = #selector(self.selectOverviewProvider(_:))            menu.addItem(item)            if index < rows.count - 1 {                menu.addItem(.separator())            }        }        return true    }     private func addOverviewEmptyState(to menu: NSMenu, enabledProviders: [UsageProvider]) {        let resolvedProviders = self.settings.resolvedMergedOverviewProviders(            activeProviders: enabledProviders,            maxVisibleProviders: Self.maxOverviewProviders)        let message = if resolvedProviders.isEmpty {            "No providers selected for Overview."        } else {            "No overview data available."        }        let item = NSMenuItem(title: message, action: nil, keyEquivalent: "")        item.isEnabled = false        item.representedObject = "overviewEmptyState"        menu.addItem(item)    }     private func addMenuCards(to menu: NSMenu, context: MenuCardContext) -> Bool {        if let tokenAccountDisplay = context.tokenAccountDisplay, tokenAccountDisplay.showAll {            let accountSnapshots = tokenAccountDisplay.snapshots            let cards = accountSnapshots.isEmpty                ? []                : accountSnapshots.compactMap { accountSnapshot in                    self.menuCardModel(                        for: context.currentProvider,                        snapshotOverride: accountSnapshot.snapshot,                        errorOverride: accountSnapshot.error)                }            if cards.isEmpty, let model = self.menuCardModel(for: context.selectedProvider) {                menu.addItem(self.makeMenuCardItem(                    UsageMenuCardView(model: model, width: context.menuWidth),                    id: "menuCard",                    width: context.menuWidth))                menu.addItem(.separator())            } else {                for (index, model) in cards.enumerated() {                    menu.addItem(self.makeMenuCardItem(                        UsageMenuCardView(model: model, width: context.menuWidth),                        id: "menuCard-\(index)",                        width: context.menuWidth))                    if index < cards.count - 1 {                        menu.addItem(.separator())                    }                }                if !cards.isEmpty {                    menu.addItem(.separator())                }            }            return false        }         guard let model = self.menuCardModel(for: context.selectedProvider) else { return false }        if context.openAIContext.hasOpenAIWebMenuItems {            let webItems = OpenAIWebMenuItems(                hasUsageBreakdown: context.openAIContext.hasUsageBreakdown,                hasCreditsHistory: context.openAIContext.hasCreditsHistory,                hasCostHistory: context.openAIContext.hasCostHistory,                canShowBuyCredits: context.openAIContext.canShowBuyCredits)            self.addMenuCardSections(                to: menu,                model: model,                provider: context.currentProvider,                width: context.menuWidth,                webItems: webItems)            return true        }         menu.addItem(self.makeMenuCardItem(            UsageMenuCardView(model: model, width: context.menuWidth),            id: "menuCard",            width: context.menuWidth))        if context.openAIContext.canShowBuyCredits {            menu.addItem(self.makeBuyCreditsItem())        }        menu.addItem(.separator())        return false    }     private func addOpenAIWebItemsIfNeeded(        to menu: NSMenu,        currentProvider: UsageProvider,        context: OpenAIWebContext,        addedOpenAIWebItems: Bool)    {        guard context.hasOpenAIWebMenuItems else { return }        if !addedOpenAIWebItems {            // Only show these when we actually have additional data.            if context.hasUsageBreakdown {                _ = self.addUsageBreakdownSubmenu(to: menu)            }            if context.hasCreditsHistory {                _ = self.addCreditsHistorySubmenu(to: menu)            }            if context.hasCostHistory {                _ = self.addCostHistorySubmenu(to: menu, provider: currentProvider)            }        }        menu.addItem(.separator())    }     private func addActionableSections(_ sections: [MenuDescriptor.Section], to menu: NSMenu, width: CGFloat) {        let actionableSections = sections.filter { section in            section.entries.contains { entry in                if case .action = entry { return true }                if case .submenu = entry { return true }                return false            }        }        for (index, section) in actionableSections.enumerated() {            for entry in section.entries {                switch entry {                case let .text(text, style):                    if style == .secondary {                        menu.addItem(self.makeWrappedSecondaryTextItem(text: text, width: width))                        continue                    }                    let item = NSMenuItem(title: text, action: nil, keyEquivalent: "")                    item.isEnabled = false                    if style == .headline {                        let font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)                        item.attributedTitle = NSAttributedString(string: text, attributes: [.font: font])                    } else if style == .secondary {                        let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)                        item.attributedTitle = NSAttributedString(                            string: text,

Lines 1–500 of 1409.