Files
steipete--codexbar/Sources/CodexBar/PreferencesProvidersPane.swift
2026-07-13 12:22:33 +08:00

821 lines
36 KiB
Swift

import AppKit
import CodexBarCore
import SwiftUI
@MainActor
struct ProvidersPane: View {
let provider: UsageProvider
@Bindable var settings: SettingsStore
@Bindable var store: UsageStore
let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator
let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator
let codexAmbientLoginRunner: any CodexAmbientLoginRunning
let runProviderLoginFlow: @MainActor (UsageProvider) async -> Void
@State private var expandedErrors: Set<UsageProvider> = []
@State private var settingsStatusTextByID: [String: String] = [:]
@State private var settingsLastAppActiveRunAtByID: [String: Date] = [:]
@State private var activeConfirmation: ProviderSettingsConfirmationState?
@State private var codexAccountsNotice: CodexAccountsSectionNotice?
@State private var isAuthenticatingLiveCodexAccount = false
init(
provider: UsageProvider = .codex,
settings: SettingsStore,
store: UsageStore,
managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(),
codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator? = nil,
codexAmbientLoginRunner: any CodexAmbientLoginRunning = DefaultCodexAmbientLoginRunner(),
runProviderLoginFlow: @escaping @MainActor (UsageProvider) async -> Void = { _ in })
{
self.provider = provider
self.settings = settings
self.store = store
self.managedCodexAccountCoordinator = managedCodexAccountCoordinator
self.codexAccountPromotionCoordinator = codexAccountPromotionCoordinator
?? CodexAccountPromotionCoordinator(
settingsStore: settings,
usageStore: store,
managedAccountCoordinator: managedCodexAccountCoordinator)
self.codexAmbientLoginRunner = codexAmbientLoginRunner
self.runProviderLoginFlow = runProviderLoginFlow
}
var body: some View {
ProviderDetailView(
provider: self.provider,
store: self.store,
isEnabled: self.binding(for: self.provider),
subtitle: self.providerSubtitle(self.provider),
model: self.menuCardModel(for: self.provider),
openAIWebDiagnostic: self.openAIWebDiagnostic(for: self.provider),
settingsPickers: self.extraSettingsPickers(for: self.provider),
settingsToggles: self.extraSettingsToggles(for: self.provider),
settingsFields: self.extraSettingsFields(for: self.provider),
settingsActions: self.extraSettingsActions(for: self.provider),
settingsTokenAccounts: self.tokenAccountDescriptor(for: self.provider),
settingsOrganizations: self.extraSettingsOrganizations(for: self.provider),
errorDisplay: self.providerErrorDisplay(self.provider),
isErrorExpanded: self.expandedBinding(for: self.provider),
onCopyError: { text in self.copyToPasteboard(text) },
onRefresh: {
self.triggerRefresh(for: self.provider)
},
showsSupplementarySettingsContent: self.codexAccountsSectionState(for: self.provider) != nil,
supplementarySettingsContent: {
if let state = self.codexAccountsSectionState(for: self.provider) {
CodexAccountsSectionView(
state: state,
setActiveVisibleAccount: { visibleAccountID in
Task { @MainActor in
await self.selectCodexVisibleAccount(id: visibleAccountID)
}
},
reauthenticateAccount: { account in
Task { @MainActor in
await self.reauthenticateCodexAccount(account)
}
},
removeAccount: { account in
self.requestManagedCodexAccountRemoval(account)
},
requestSystemVisibleAccount: { visibleAccountID in
Task { @MainActor in
await self.requestCodexSystemVisibleAccount(id: visibleAccountID)
}
},
addAccount: {
Task { @MainActor in
await self.addManagedCodexAccount()
}
})
}
})
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
self.runSettingsDidBecomeActiveHooks()
}
.alert(
self.activeConfirmation?.title ?? "",
isPresented: Binding(
get: { self.activeConfirmation != nil },
set: { isPresented in
if !isPresented { self.activeConfirmation = nil }
}),
actions: {
if let active = self.activeConfirmation {
Button(active.confirmTitle) {
active.onConfirm()
self.activeConfirmation = nil
}
Button(L("cancel"), role: .cancel) { self.activeConfirmation = nil }
}
},
message: {
if let active = self.activeConfirmation {
Text(active.message)
}
})
}
static func filteredProviders(
_ providers: [UsageProvider],
query: String,
displayName: (UsageProvider) -> String) -> [UsageProvider]
{
let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedQuery.isEmpty else { return providers }
return providers.filter { provider in
displayName(provider).localizedCaseInsensitiveContains(trimmedQuery)
|| provider.rawValue.localizedCaseInsensitiveContains(trimmedQuery)
}
}
func moveProviders(fromOffsets: IndexSet, toOffset: Int) {
guard !self.settings.providersSortedAlphabetically else { return }
self.settings.moveProvider(fromOffsets: fromOffsets, toOffset: toOffset)
}
private func triggerRefresh(for provider: UsageProvider) {
Task { @MainActor in
await ProviderInteractionContext.$current.withValue(.userInitiated) {
if provider == .codex {
await self.store.refreshCodexAccountScopedState(allowDisabled: true)
} else {
await self.store.refreshProvider(provider, allowDisabled: true)
}
}
}
}
func binding(for provider: UsageProvider) -> Binding<Bool> {
let meta = self.store.metadata(for: provider)
return Binding(
get: { self.settings.isProviderEnabled(provider: provider, metadata: meta) },
set: { newValue in
self.settings.setProviderEnabled(provider: provider, metadata: meta, enabled: newValue)
})
}
func providerSubtitle(_ provider: UsageProvider) -> String {
let meta = self.store.metadata(for: provider)
let usageText: String
if self.store.isStale(provider: provider) {
usageText = L("last_fetch_failed")
} else if self.store.knownLimitsAvailability(for: provider)?.isUnavailable == true {
usageText = L("Limits not available")
} else if let snapshot = self.store.snapshot(for: provider) {
let relative = snapshot.updatedAt.relativeDescription()
usageText = relative
} else {
usageText = L("usage_not_fetched_yet")
}
let presentationContext = ProviderPresentationContext(
provider: provider,
settings: self.settings,
store: self.store,
metadata: meta)
let presentation = ProviderCatalog.implementation(for: provider)?
.presentation(context: presentationContext)
?? ProviderPresentation(detailLine: ProviderPresentation.standardDetailLine)
let detailLine = presentation.detailLine(presentationContext)
return "\(detailLine)\n\(usageText)"
}
func providerSidebarSubtitle(_ provider: UsageProvider) -> String {
let meta = self.store.metadata(for: provider)
let usageText: String = if self.store.isStale(provider: provider) {
L("last_fetch_failed")
} else if self.store.knownLimitsAvailability(for: provider)?.isUnavailable == true {
L("Limits not available")
} else if let snapshot = self.store.snapshot(for: provider) {
snapshot.updatedAt.relativeDescription()
} else {
L("usage_not_fetched_yet")
}
let detailLine: String = if let sourceLabel = self.store.lastSourceLabels[provider], !sourceLabel.isEmpty {
sourceLabel
} else if let version = self.store.version(for: provider), !version.isEmpty {
"\(meta.cliName) \(version)"
} else {
meta.cliName
}
return "\(detailLine)\n\(usageText)"
}
func codexAccountsSectionState(for provider: UsageProvider) -> CodexAccountsSectionState? {
guard provider == .codex else { return nil }
let projection = self.settings.codexVisibleAccountProjection
let degradedNotice: CodexAccountsSectionNotice? = if projection.hasUnreadableAddedAccountStore {
CodexAccountsSectionNotice(
text: L("managed_account_storage_unreadable"),
tone: .warning)
} else {
nil
}
return CodexAccountsSectionState(
visibleAccounts: projection.visibleAccounts,
activeVisibleAccountID: projection.activeVisibleAccountID,
liveVisibleAccountID: projection.liveVisibleAccountID,
hasUnreadableManagedAccountStore: projection.hasUnreadableAddedAccountStore,
isAuthenticatingManagedAccount: self.managedCodexAccountCoordinator.isAuthenticatingManagedAccount,
authenticatingManagedAccountID: self.managedCodexAccountCoordinator.authenticatingManagedAccountID,
isRemovingManagedAccount: self.managedCodexAccountCoordinator.isRemovingManagedAccount,
isAuthenticatingLiveAccount: self.isAuthenticatingLiveCodexAccount,
isPromotingSystemAccount: self.codexAccountPromotionCoordinator.isPromotingSystemAccount,
notice: self.codexAccountsNotice ?? degradedNotice)
}
func selectCodexVisibleAccount(id: String) async {
self.codexAccountsNotice = nil
guard self.settings.selectCodexVisibleAccount(id: id) else { return }
await self.refreshCodexProvider()
}
func requestCodexSystemVisibleAccount(id: String) async {
self.codexAccountsNotice = nil
guard let account = self.settings.codexVisibleAccountProjection.visibleAccounts.first(where: { $0.id == id }),
let managedAccountID = account.storedAccountID
else {
return
}
let result = await self.codexAccountPromotionCoordinator.promote(managedAccountID: managedAccountID)
if case let .failure(error) = result {
self.codexAccountsNotice = CodexAccountsSectionNotice(text: error.message, tone: .warning)
}
}
func addManagedCodexAccount() async {
self.codexAccountsNotice = nil
guard let state = self.codexAccountsSectionState(for: .codex), state.canAddAccount else {
return
}
do {
let account = try await self.managedCodexAccountCoordinator.authenticateManagedAccount()
self.selectCodexVisibleAccountForAuthenticatedManagedAccount(account)
await self.refreshCodexProvider()
} catch {
self.codexAccountsNotice = self.codexAccountsNotice(for: error)
}
}
func reauthenticateCodexAccount(_ account: CodexVisibleAccount) async {
self.codexAccountsNotice = nil
if let accountID = account.storedAccountID {
guard let state = self.codexAccountsSectionState(for: .codex), state.canReauthenticate(account) else {
return
}
do {
_ = try await self.managedCodexAccountCoordinator
.authenticateManagedAccount(existingAccountID: accountID)
await self.refreshCodexProvider()
} catch {
self.codexAccountsNotice = self.codexAccountsNotice(for: error)
}
return
}
guard let state = self.codexAccountsSectionState(for: .codex), state.canReauthenticate(account) else {
return
}
self.isAuthenticatingLiveCodexAccount = true
self.codexAccountPromotionCoordinator.setLiveReauthenticationInProgress(true)
defer {
self.isAuthenticatingLiveCodexAccount = false
self.codexAccountPromotionCoordinator.setLiveReauthenticationInProgress(false)
}
let result = await self.codexAmbientLoginRunner.run(timeout: 120)
if let info = CodexLoginAlertPresentation.alertInfo(for: result) {
self.presentLoginAlert(title: info.title, message: info.message)
return
}
await self.refreshCodexProvider()
}
func removeManagedCodexAccount(id: UUID) async {
self.codexAccountsNotice = nil
do {
try await self.managedCodexAccountCoordinator.removeManagedAccount(id: id)
await self.refreshCodexProvider()
} catch {
self.codexAccountsNotice = self.codexAccountsNotice(for: error)
}
}
func requestManagedCodexAccountRemoval(_ account: CodexVisibleAccount) {
guard let accountID = account.storedAccountID else { return }
self.activeConfirmation = ProviderSettingsConfirmationState(
title: L("remove_codex_account_title"),
message: String(format: L("remove_account_message"), account.email),
confirmTitle: L("remove"),
onConfirm: {
Task { @MainActor in
await self.removeManagedCodexAccount(id: accountID)
}
})
}
func providerErrorDisplay(_ provider: UsageProvider) -> ProviderErrorDisplay? {
guard let full = self.store.error(for: provider), !full.isEmpty else { return nil }
let preview = self.store.userFacingError(for: provider) ?? full
return ProviderErrorDisplay(
preview: self.truncated(preview, prefix: ""),
full: full)
}
private func extraSettingsToggles(for provider: UsageProvider) -> [ProviderSettingsToggleDescriptor] {
guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }
let context = self.makeSettingsContext(provider: provider)
return impl.settingsToggles(context: context)
.filter { $0.isVisible?() ?? true }
}
private func extraSettingsPickers(for provider: UsageProvider) -> [ProviderSettingsPickerDescriptor] {
guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }
let context = self.makeSettingsContext(provider: provider)
let providerPickers = impl.settingsPickers(context: context)
.filter { $0.isVisible?() ?? true }
if let menuBarPicker = self.menuBarMetricPicker(for: provider) {
return [menuBarPicker] + providerPickers
}
return providerPickers
}
private func extraSettingsFields(for provider: UsageProvider) -> [ProviderSettingsFieldDescriptor] {
guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }
let context = self.makeSettingsContext(provider: provider)
return impl.settingsFields(context: context)
.filter { $0.isVisible?() ?? true }
}
private func extraSettingsActions(for provider: UsageProvider) -> [ProviderSettingsActionsDescriptor] {
guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }
let context = self.makeSettingsContext(provider: provider)
return impl.settingsActions(context: context)
.filter { $0.isVisible?() ?? true }
}
private func extraSettingsOrganizations(
for provider: UsageProvider) -> ProviderSettingsOrganizationsDescriptor?
{
guard let impl = ProviderCatalog.implementation(for: provider) else { return nil }
let context = self.makeSettingsContext(provider: provider)
return impl.settingsOrganizations(context: context)
}
func tokenAccountDescriptor(for provider: UsageProvider) -> ProviderSettingsTokenAccountsDescriptor? {
guard let support = TokenAccountSupportCatalog.support(for: provider) else { return nil }
let context = self.makeSettingsContext(provider: provider)
return ProviderSettingsTokenAccountsDescriptor(
id: "token-accounts-\(provider.rawValue)",
title: support.title,
subtitle: support.subtitle,
placeholder: support.placeholder,
provider: provider,
isVisible: {
ProviderCatalog.implementation(for: provider)?
.tokenAccountsVisibility(context: context, support: support)
?? (!support.requiresManualCookieSource ||
!context.settings.tokenAccounts(for: provider).isEmpty)
},
accounts: { self.settings.tokenAccounts(for: provider) },
activeIndex: {
let data = self.settings.tokenAccountsData(for: provider)
return data?.clampedActiveIndex() ?? 0
},
setActiveIndex: { index in
self.settings.setActiveTokenAccountIndex(index, for: provider)
Task { @MainActor in
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await self.store.refreshProvider(provider, allowDisabled: true)
}
}
},
showsOrganizationField: provider == .claude,
showsTeamModeControls: provider == .zai,
addAccount: { label, token, usageScope, organizationID, workspaceID in
self.settings.addTokenAccount(
provider: provider,
label: label,
token: token,
usageScope: usageScope,
organizationID: organizationID,
workspaceID: workspaceID)
Task { @MainActor in
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await self.store.refreshProvider(provider, allowDisabled: true)
}
}
},
updateAccount: { accountID, usageScope, organizationID, workspaceID in
self.settings.updateTokenAccount(
provider: provider,
accountID: accountID,
usageScope: usageScope,
organizationID: organizationID,
workspaceID: workspaceID)
Task { @MainActor in
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await self.store.refreshProvider(provider, allowDisabled: true)
}
}
},
removeAccount: { accountID in
self.settings.removeTokenAccount(provider: provider, accountID: accountID)
Task { @MainActor in
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await self.store.refreshProvider(provider, allowDisabled: true)
}
}
},
primaryAddActionTitle: provider == .copilot ? "Add Account" : nil,
primaryAddAction: provider == .copilot ? {
await CopilotLoginFlow.run(settings: self.settings)
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await self.store.refreshProvider(provider, allowDisabled: true)
}
} : nil,
openConfigFile: {
self.settings.openTokenAccountsFile()
},
reloadFromDisk: {
self.settings.reloadTokenAccounts()
Task { @MainActor in
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await self.store.refreshProvider(provider, allowDisabled: true)
}
}
})
}
private func makeSettingsContext(provider: UsageProvider) -> ProviderSettingsContext {
ProviderSettingsContext(
provider: provider,
settings: self.settings,
store: self.store,
boolBinding: { keyPath in
Binding(
get: { self.settings[keyPath: keyPath] },
set: { self.settings[keyPath: keyPath] = $0 })
},
stringBinding: { keyPath in
Binding(
get: { self.settings[keyPath: keyPath] },
set: { self.settings[keyPath: keyPath] = $0 })
},
statusText: { id in
self.settingsStatusTextByID[id]
},
setStatusText: { id, text in
if let text {
self.settingsStatusTextByID[id] = text
} else {
self.settingsStatusTextByID.removeValue(forKey: id)
}
},
lastAppActiveRunAt: { id in
self.settingsLastAppActiveRunAtByID[id]
},
setLastAppActiveRunAt: { id, date in
if let date {
self.settingsLastAppActiveRunAtByID[id] = date
} else {
self.settingsLastAppActiveRunAtByID.removeValue(forKey: id)
}
},
requestConfirmation: { confirmation in
self.activeConfirmation = ProviderSettingsConfirmationState(confirmation: confirmation)
},
runLoginFlow: {
await self.runProviderLoginFlow(provider)
})
}
func menuBarMetricPicker(for provider: UsageProvider) -> ProviderSettingsPickerDescriptor? {
let options: [ProviderSettingsPickerOption]
if provider == .openrouter {
options = [
ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: L("automatic")),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.primary.rawValue,
title: L("primary_api_key_limit")),
]
} else if provider == .mistral {
options = [
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.automatic.rawValue,
title: L("metric_mistral_payg")),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.monthlyPlan.rawValue,
title: L("metric_mistral_monthly_plan")),
]
} else if SettingsStore.isBalanceOnlyProvider(provider) {
options = [
ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: L("Automatic")),
]
} else if provider == .mimo {
let snapshot = self.store.snapshot(for: provider)
var metricOptions = [
ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: L("automatic")),
]
if snapshot?.primary != nil, snapshot?.mimoUsage != nil {
metricOptions.append(ProviderSettingsPickerOption(
id: MenuBarMetricPreference.primary.rawValue,
title: String(format: L("metric_primary"), L("Credits"))))
metricOptions.append(ProviderSettingsPickerOption(
id: MenuBarMetricPreference.secondary.rawValue,
title: String(format: L("metric_secondary"), L("Balance"))))
}
options = metricOptions
} else if provider == .abacus {
let metadata = self.store.metadata(for: provider)
options = [
ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: L("automatic")),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.primary.rawValue,
title: String(format: L("metric_primary"), metadata.sessionLabel)),
]
} else {
let metadata = self.store.metadata(for: provider)
let snapshot = self.store.snapshot(for: provider)
let supportsAverage = self.settings.menuBarMetricSupportsAverage(for: provider)
let supportsPrimaryAndSecondary = self.settings.menuBarMetricSupportsPrimaryAndSecondary(for: provider)
let supportsTertiary = self.settings.menuBarMetricSupportsTertiary(for: provider, snapshot: snapshot)
let supportsExtraUsage = self.settings.menuBarMetricSupportsExtraUsage(for: provider, snapshot: snapshot)
var metricOptions: [ProviderSettingsPickerOption] = [
ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: L("automatic")),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.primary.rawValue,
title: String(format: L("metric_primary"), metadata.sessionLabel)),
ProviderSettingsPickerOption(
id: MenuBarMetricPreference.secondary.rawValue,
title: String(format: L("metric_secondary"), metadata.weeklyLabel)),
]
if supportsPrimaryAndSecondary {
metricOptions.append(ProviderSettingsPickerOption(
id: MenuBarMetricPreference.primaryAndSecondary.rawValue,
title: "\(L(metadata.sessionLabel)) + \(L(metadata.weeklyLabel))"))
}
if supportsTertiary {
let tertiaryTitle = metadata.opusLabel ?? MenuBarMetricPreference.tertiary.label
metricOptions.append(ProviderSettingsPickerOption(
id: MenuBarMetricPreference.tertiary.rawValue,
title: String(format: L("metric_tertiary"), tertiaryTitle)))
}
if supportsExtraUsage {
metricOptions.append(ProviderSettingsPickerOption(
id: MenuBarMetricPreference.extraUsage.rawValue,
title: MenuBarMetricPreference.extraUsage.label))
}
if supportsAverage {
metricOptions.append(ProviderSettingsPickerOption(
id: MenuBarMetricPreference.average.rawValue,
title: String(format: L("metric_average"), metadata.sessionLabel, metadata.weeklyLabel)))
}
options = metricOptions
}
return ProviderSettingsPickerDescriptor(
id: "menuBarMetric",
title: L("menu_bar_metric_title"),
subtitle: Self.menuBarMetricPickerSubtitle(for: provider),
placement: .menuBar,
binding: Binding(
get: {
self.settings
.menuBarMetricPreference(for: provider, snapshot: self.store.snapshot(for: provider))
.rawValue
},
set: { rawValue in
guard let preference = MenuBarMetricPreference(rawValue: rawValue) else { return }
self.settings.setMenuBarMetricPreference(preference, for: provider)
}),
options: options,
isVisible: { true },
onChange: nil)
}
private static func menuBarMetricPickerSubtitle(for provider: UsageProvider) -> String {
switch provider {
case .deepseek:
L("menu_bar_metric_subtitle_deepseek")
case .moonshot:
L("menu_bar_metric_subtitle_moonshot")
case .mistral:
L("menu_bar_metric_subtitle_mistral")
case .kimik2:
L("menu_bar_metric_subtitle_kimik2")
default:
L("menu_bar_metric_subtitle")
}
}
func menuCardModel(for provider: UsageProvider) -> UsageMenuCardView.Model {
let metadata = self.store.metadata(for: provider)
let snapshot = self.store.snapshot(for: provider)
let now = Date()
let codexProjection = self.store.codexConsumerProjectionIfNeeded(
for: provider,
surface: .liveCard,
now: now)
let credits: CreditsSnapshot?
let creditsError: String?
let dashboard: OpenAIDashboardSnapshot?
let dashboardError: String?
let tokenSnapshot: CostUsageTokenSnapshot?
let tokenError: String?
if let codexProjection {
credits = codexProjection.credits?.snapshot
creditsError = codexProjection.credits?.userFacingError
dashboard = nil
dashboardError = codexProjection.userFacingErrors.dashboard
tokenSnapshot = self.store.tokenSnapshot(for: provider)
tokenError = self.store.tokenError(for: provider)
} else if ProviderDescriptorRegistry.descriptor(for: provider).tokenCost.supportsTokenCost {
credits = nil
creditsError = nil
dashboard = nil
dashboardError = nil
tokenSnapshot = self.store.tokenSnapshot(for: provider)
tokenError = self.store.tokenError(for: provider)
} else {
credits = nil
creditsError = nil
dashboard = nil
dashboardError = nil
tokenSnapshot = nil
tokenError = nil
}
// Abacus uses primary for monthly credits (no secondary window)
let paceWindow = provider == .abacus ? snapshot?.primary : snapshot?.secondary
let weeklyPace = if let codexProjection,
let weekly = codexProjection.rateWindow(for: .weekly)
{
self.store.weeklyPace(provider: provider, window: weekly, now: now)
} else {
paceWindow.flatMap { window in
self.store.weeklyPace(provider: provider, window: window, now: now)
}
}
let input = UsageMenuCardView.Model.Input(
provider: provider,
metadata: metadata,
snapshot: snapshot,
codexProjection: codexProjection,
credits: credits,
creditsError: creditsError,
dashboard: dashboard,
dashboardError: dashboardError,
tokenSnapshot: tokenSnapshot,
tokenError: tokenError,
account: self.store.accountInfo(for: provider),
isRefreshing: self.store.refreshingProviders.contains(provider),
lastError: codexProjection?.userFacingErrors.usage ?? self.store.userFacingError(for: provider),
limitsAvailability: self.store.knownLimitsAvailability(for: provider),
usageBarsShowUsed: self.settings.usageBarsShowUsed,
resetTimeDisplayStyle: self.settings.resetTimeDisplayStyle,
tokenCostUsageEnabled: self.settings.isCostUsageEffectivelyEnabled(for: provider),
tokenCostInlineDashboardEnabled: self.settings.costSummaryShowsInlineDashboard(for: provider),
// Display style only controls the main menu. Provider details always expose
// available cost data in their Usage section.
tokenCostMenuSectionEnabled: self.settings.isCostUsageEffectivelyEnabled(for: provider),
showOptionalCreditsAndExtraUsage: self.settings.showOptionalCreditsAndExtraUsage,
codexSparkUsageVisible: self.settings.codexSparkUsageVisible,
copilotBudgetExtrasEnabled: self.settings.copilotBudgetExtrasEnabled,
hidePersonalInfo: self.settings.hidePersonalInfo,
weeklyPace: weeklyPace,
quotaWarningThresholds: [
.session: self.quotaWarningMarkerThresholds(provider: provider, window: .session),
.weekly: self.quotaWarningMarkerThresholds(provider: provider, window: .weekly),
],
workDaysPerWeek: self.settings.weeklyProgressWorkDays,
now: now)
return UsageMenuCardView.Model.make(input)
}
func openAIWebDiagnostic(for provider: UsageProvider) -> String? {
guard provider == .codex else { return nil }
let diagnostic = self.store.codexConsumerProjectionIfNeeded(
for: provider,
surface: .liveCard)?.userFacingErrors.dashboard
return PersonalInfoRedactor.redactEmails(in: diagnostic, isEnabled: self.settings.hidePersonalInfo)
}
private func quotaWarningMarkerThresholds(provider: UsageProvider, window: QuotaWarningWindow) -> [Int] {
guard self.settings.quotaWarningMarkersVisible else { return [] }
guard self.settings.quotaWarningEnabled(provider: provider, window: window) else { return [] }
return self.settings.resolvedQuotaWarningThresholds(provider: provider, window: window)
}
private func refreshCodexProvider() async {
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await self.store.refreshCodexAccountScopedState(allowDisabled: true)
}
}
private func selectCodexVisibleAccountForAuthenticatedManagedAccount(_ account: ManagedCodexAccount) {
self.settings.selectAuthenticatedManagedCodexAccount(account)
}
private func codexAccountsNotice(for error: Error) -> CodexAccountsSectionNotice {
if let error = error as? ManagedCodexAccountCoordinatorError,
error == .authenticationInProgress
{
return CodexAccountsSectionNotice(
text: L("managed_login_already_running"),
tone: .warning)
}
if let error = error as? ManagedCodexAccountServiceError {
return CodexAccountsSectionNotice(text: error.userFacingMessage, tone: .warning)
}
return CodexAccountsSectionNotice(
text: error.localizedDescription,
tone: .warning)
}
private func presentLoginAlert(title: String, message: String) {
let alert = NSAlert()
alert.messageText = L(title)
alert.informativeText = L(message)
alert.alertStyle = .warning
alert.runModal()
}
private func runSettingsDidBecomeActiveHooks() {
for provider in UsageProvider.allCases {
for toggle in self.extraSettingsToggles(for: provider) {
guard let hook = toggle.onAppDidBecomeActive else { continue }
Task { @MainActor in
await hook()
}
}
}
}
private func truncated(_ text: String, prefix: String, maxLength: Int = 160) -> String {
var message = text.trimmingCharacters(in: .whitespacesAndNewlines)
if message.count > maxLength {
let idx = message.index(message.startIndex, offsetBy: maxLength)
message = "\(message[..<idx])…"
}
return prefix + message
}
private func expandedBinding(for provider: UsageProvider) -> Binding<Bool> {
Binding(
get: { self.expandedErrors.contains(provider) },
set: { expanded in
if expanded {
self.expandedErrors.insert(provider)
} else {
self.expandedErrors.remove(provider)
}
})
}
private func copyToPasteboard(_ text: String) {
let pb = NSPasteboard.general
pb.clearContents()
pb.setString(text, forType: .string)
}
}
@MainActor
struct ProviderSettingsConfirmationState: Identifiable {
let id = UUID()
let title: String
let message: String
let confirmTitle: String
let onConfirm: () -> Void
init(
title: String,
message: String,
confirmTitle: String,
onConfirm: @escaping () -> Void)
{
self.title = title
self.message = message
self.confirmTitle = confirmTitle
self.onConfirm = onConfirm
}
init(confirmation: ProviderSettingsConfirmation) {
self.title = L(confirmation.title)
self.message = L(confirmation.message)
self.confirmTitle = L(confirmation.confirmTitle)
self.onConfirm = confirmation.onConfirm
}
}