Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sources/CodexBar/PreferencesProviderDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ private struct ProviderDetailInfoGrid: View {
if self.store.refreshingProviders.contains(self.provider) {
return "Refreshing"
}
if self.store.unavailableMessage(for: self.provider) != nil {
return "Unavailable"
}
return "Not fetched yet"
}
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/CodexBar/ProviderRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ struct ProviderRegistry {
// Mac's Codex sessions, not as account-owned remote state. If we later want
// account-scoped token history in the UI, that needs an explicit product decision and
// presentation change so the two concepts are not conflated.
if provider == .codex, let managedHomePath = settings.activeManagedCodexRemoteHomePath {
if provider == .codex,
case .managedAccount = settings.codexActiveSource,
let managedHomePath = settings.activeManagedCodexRemoteHomePath
{
env = CodexHomeScope.scopedEnvironment(base: env, codexHome: managedHomePath)
}
return env
Expand Down
83 changes: 79 additions & 4 deletions Sources/CodexBar/StatusItemController+Animation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ extension StatusItemController {
return false
}

func applyIcon(phase: Double?) {
guard let button = self.statusItem.button else { return }
// swiftlint:disable function_body_length
@discardableResult
func applyIcon(phase: Double?) -> Bool {
guard let button = self.statusItem.button else { return false }

let style = self.store.iconStyle
let showUsed = self.settings.usageBarsShowUsed
Expand Down Expand Up @@ -299,31 +301,92 @@ extension StatusItemController {
}
return .none
}()
let debugDouble: (Double?) -> String = { value in
guard let value else { return "nil" }
return String(format: "%.3f", value)
}

if showBrandPercent,
let brand = ProviderBrandIcon.image(for: primaryProvider)
{
let displayText = self.menuBarDisplayText(for: primaryProvider, snapshot: snapshot)
let signature = [
"mode=brandPercent",
"provider=\(primaryProvider.rawValue)",
"style=\(String(describing: style))",
"primary=\(debugDouble(primary))",
"weekly=\(debugDouble(weekly))",
"credits=\(debugDouble(credits))",
"stale=\(stale ? "1" : "0")",
"status=\(statusIndicator.rawValue)",
"text=\(displayText ?? "nil")",
"anim=\(needsAnimation ? "1" : "0")",
].joined(separator: "|")
if self.shouldSkipMergedIconRender(signature) {
return true
}
self.setButtonImage(brand, for: button)
self.setButtonTitle(displayText, for: button)
return
return false
}

if Self.shouldUseOpenRouterBrandFallback(provider: primaryProvider, snapshot: snapshot),
let brand = ProviderBrandIcon.image(for: primaryProvider)
{
let signature = [
"mode=openRouterFallback",
"provider=\(primaryProvider.rawValue)",
"style=\(String(describing: style))",
"primary=\(debugDouble(primary))",
"weekly=\(debugDouble(weekly))",
"credits=\(debugDouble(credits))",
"stale=\(stale ? "1" : "0")",
"status=\(statusIndicator.rawValue)",
"anim=\(needsAnimation ? "1" : "0")",
].joined(separator: "|")
if self.shouldSkipMergedIconRender(signature) {
return true
}
self.setButtonTitle(nil, for: button)
self.setButtonImage(
Self.brandImageWithStatusOverlay(brand: brand, statusIndicator: statusIndicator),
for: button)
return
return false
}

self.setButtonTitle(nil, for: button)
if let morphProgress {
let signature = [
"mode=morph",
"provider=\(primaryProvider.rawValue)",
"style=\(String(describing: style))",
"morph=\(debugDouble(morphProgress))",
"status=\(statusIndicator.rawValue)",
"anim=\(needsAnimation ? "1" : "0")",
].joined(separator: "|")
if self.shouldSkipMergedIconRender(signature) {
return true
}
let image = IconRenderer.makeMorphIcon(progress: morphProgress, style: style)
self.setButtonImage(image, for: button)
} else {
let signature = [
"mode=icon",
"provider=\(primaryProvider.rawValue)",
"style=\(String(describing: style))",
"primary=\(debugDouble(primary))",
"weekly=\(debugDouble(weekly))",
"credits=\(debugDouble(credits))",
"stale=\(stale ? "1" : "0")",
"status=\(statusIndicator.rawValue)",
"blink=\(debugDouble(Double(blink)))",
"wiggle=\(debugDouble(Double(wiggle)))",
"tilt=\(debugDouble(Double(tilt)))",
"anim=\(needsAnimation ? "1" : "0")",
].joined(separator: "|")
if self.shouldSkipMergedIconRender(signature) {
return true
}
let image = IconRenderer.makeIcon(
primaryRemaining: primary,
weeklyRemaining: weekly,
Expand All @@ -336,6 +399,18 @@ extension StatusItemController {
statusIndicator: statusIndicator)
self.setButtonImage(image, for: button)
}
return false
}

// swiftlint:enable function_body_length

private func shouldSkipMergedIconRender(_ signature: String) -> Bool {
guard self.shouldMergeIcons else { return false }
if self.lastAppliedMergedIconRenderSignature == signature {
return true
}
self.lastAppliedMergedIconRenderSignature = signature
return false
}

func applyIcon(for provider: UsageProvider, phase: Double?) {
Expand Down
153 changes: 153 additions & 0 deletions Sources/CodexBar/StatusItemController+HostedSubmenus.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import AppKit
import CodexBarCore
import SwiftUI

extension StatusItemController {
private static let hostedSubviewWidth: CGFloat = 310

func makeHostedSubviewPlaceholderMenu(chartID: String, provider: UsageProvider? = nil) -> NSMenu {
let submenu = NSMenu()
submenu.delegate = self
let chartItem = NSMenuItem()
chartItem.isEnabled = false
chartItem.representedObject = chartID
chartItem.toolTip = provider?.rawValue
submenu.addItem(chartItem)
return submenu
}

func hydrateHostedSubviewMenuIfNeeded(_ menu: NSMenu) {
guard let placeholder = menu.items.first,
menu.items.count == 1,
placeholder.view == nil,
let chartID = placeholder.representedObject as? String
else {
return
}

let width = Self.hostedSubviewWidth
menu.removeAllItems()

let didHydrate: Bool = switch chartID {
case Self.usageBreakdownChartID:
self.appendUsageBreakdownChartItem(to: menu, width: width)
case Self.creditsHistoryChartID:
self.appendCreditsHistoryChartItem(to: menu, width: width)
case Self.costHistoryChartID:
if let providerRawValue = placeholder.toolTip,
let provider = UsageProvider(rawValue: providerRawValue)
{
self.appendCostHistoryChartItem(to: menu, provider: provider, width: width)
} else {
false
}
case Self.usageHistoryChartID:
if let providerRawValue = placeholder.toolTip,
let provider = UsageProvider(rawValue: providerRawValue)
{
self.appendUsageHistoryChartItem(to: menu, provider: provider, width: width)
} else {
false
}
default:
false
}

guard !didHydrate else { return }

let unavailableItem = NSMenuItem(title: "No data available", action: nil, keyEquivalent: "")
unavailableItem.isEnabled = false
unavailableItem.representedObject = chartID
unavailableItem.toolTip = placeholder.toolTip
menu.addItem(unavailableItem)
Comment thread
ratulsarna marked this conversation as resolved.
}

@discardableResult
func appendUsageBreakdownChartItem(to submenu: NSMenu, width: CGFloat) -> Bool {
let breakdown = self.store.openAIDashboard?.usageBreakdown ?? []
guard !breakdown.isEmpty else { return false }

if !Self.menuCardRenderingEnabled {
let chartItem = NSMenuItem()
chartItem.isEnabled = false
chartItem.representedObject = Self.usageBreakdownChartID
submenu.addItem(chartItem)
return true
}

let chartView = UsageBreakdownChartMenuView(breakdown: breakdown, width: width)
let hosting = MenuHostingView(rootView: chartView)
let controller = NSHostingController(rootView: chartView)
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: size.height))

let chartItem = NSMenuItem()
chartItem.view = hosting
chartItem.isEnabled = false
chartItem.representedObject = Self.usageBreakdownChartID
submenu.addItem(chartItem)
return true
}

@discardableResult
func appendCreditsHistoryChartItem(to submenu: NSMenu, width: CGFloat) -> Bool {
let breakdown = self.store.openAIDashboard?.dailyBreakdown ?? []
guard !breakdown.isEmpty else { return false }

if !Self.menuCardRenderingEnabled {
let chartItem = NSMenuItem()
chartItem.isEnabled = false
chartItem.representedObject = Self.creditsHistoryChartID
submenu.addItem(chartItem)
return true
}

let chartView = CreditsHistoryChartMenuView(breakdown: breakdown, width: width)
let hosting = MenuHostingView(rootView: chartView)
let controller = NSHostingController(rootView: chartView)
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: size.height))

let chartItem = NSMenuItem()
chartItem.view = hosting
chartItem.isEnabled = false
chartItem.representedObject = Self.creditsHistoryChartID
submenu.addItem(chartItem)
return true
}

@discardableResult
func appendCostHistoryChartItem(
to submenu: NSMenu,
provider: UsageProvider,
width: CGFloat) -> Bool
{
guard let tokenSnapshot = self.store.tokenSnapshot(for: provider) else { return false }
guard !tokenSnapshot.daily.isEmpty else { return false }

if !Self.menuCardRenderingEnabled {
let chartItem = NSMenuItem()
chartItem.isEnabled = false
chartItem.representedObject = Self.costHistoryChartID
submenu.addItem(chartItem)
return true
}

let chartView = CostHistoryChartMenuView(
provider: provider,
daily: tokenSnapshot.daily,
totalCostUSD: tokenSnapshot.last30DaysCostUSD,
width: width)
let hosting = MenuHostingView(rootView: chartView)
let controller = NSHostingController(rootView: chartView)
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: size.height))

let chartItem = NSMenuItem()
chartItem.view = hosting
chartItem.isEnabled = false
chartItem.representedObject = Self.costHistoryChartID
submenu.addItem(chartItem)
return true
}
}
Loading