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
11 changes: 11 additions & 0 deletions Sources/CodexBar/CostHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,13 @@ struct CostHistoryChartMenuView: View {
}

if let total = self.totalCostUSD {
if let disclaimer = Self.estimateDisclaimer(provider: self.provider) {
Text(disclaimer)
.font(.caption2)
.foregroundStyle(Color(nsColor: .tertiaryLabelColor))
.lineLimit(1)
.truncationMode(.tail)
}
Text(String(
format: L("Est. total (%@): %@"),
self.windowLabel ?? Self.windowLabel(days: self.historyDays),
Expand Down Expand Up @@ -283,6 +290,10 @@ struct CostHistoryChartMenuView: View {
.frame(minWidth: self.width, maxWidth: .infinity, alignment: .top)
}

static func estimateDisclaimer(provider: UsageProvider) -> String? {
provider == .codex ? L("codex_api_estimate_not_billed") : nil
}

private struct Model {
let points: [Point]
let pointsByDateKey: [String: Point]
Expand Down
28 changes: 23 additions & 5 deletions Sources/CodexBar/InlineUsageDashboardContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,21 @@ extension UsageMenuCardView.Model {
comparisonPeriodsEnabled: Bool) -> InlineUsageDashboardModel
{
let historyDays = max(1, min(365, snapshot.historyDays))
let historyTitle = snapshot.historyLabel
let defaultHistoryTitle = snapshot.historyLabel
?? (historyDays == 1
? L("Today")
: historyDays == 30
? L("30d cost")
: "\(String(format: L("Last %d days"), historyDays)) \(L("Cost"))")
let codexHistoryPeriod = snapshot.historyLabel
?? (historyDays == 1
? L("Today")
: historyDays == 30
? "30d"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Localize the Codex 30-day inline label

When the app is running in a non-English locale and the Codex inline dashboard uses the default 30-day window, this literal bypasses the existing localized 30d key, so the second KPI becomes mixed-language like 30d · API-equivalent estimate even though the previous 30-day cost label was localized through L("30d cost"). Use L("30d") for this period before appending the Codex-specific header.

Useful? React with 👍 / 👎.

: String(format: L("Last %d days"), historyDays))
let historyTitle = provider == .codex
? "\(codexHistoryPeriod) · \(L("codex_api_estimate_header"))"
: defaultHistoryTitle
let tokenHistoryTitle = snapshot.historyLabel.map { "\($0) \(L("tokens"))" }
?? (historyDays == 1
? L("Today tokens")
Expand Down Expand Up @@ -388,19 +397,28 @@ extension UsageMenuCardView.Model {
details
.append("\(requestHistoryTitle): \(UsageFormatter.tokenCountString(requestCount)) \(L("requests"))")
}
if let hint = Self.tokenUsageHint(provider: provider) {
details.append(hint)
let hintLines = Self.tokenUsageHintLines(provider: provider)
if hintLines.isEmpty == false {
details.append(contentsOf: hintLines)
} else {
details.append(L("cost_estimate_hint"))
}
}
let providerName = ProviderDefaults.metadata[provider]?.displayName ?? provider.rawValue
let codexEstimateHeader = L("codex_api_estimate_header")
let accessibilityLabel = if provider == .codex {
"\(providerName) \(periodLabel) \(codexEstimateHeader) trend"
} else {
"\(providerName) \(periodLabel) cost trend"
}
var model = InlineUsageDashboardModel(
accessibilityLabel: "\(providerName) \(periodLabel) cost trend",
accessibilityLabel: accessibilityLabel,
valueStyle: Self.costValueStyle(currencyCode: snapshot.currencyCode),
kpis: [
.init(
title: usesLatestPrimary ? L("Latest") : L("Today"),
title: provider == .codex
? "\(L("Today")) · \(L("codex_api_estimate_header"))"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Relabel Codex inline chart accessibility

When provider == .codex and the inline dashboard is rendered, this branch relabels the visible KPI as an API-equivalent estimate, but the same model still sets the mini chart accessibility label to a string ending in cost trend, which MiniUsageBars exposes via .accessibilityLabel(...). VoiceOver users therefore still hear the Codex dollar chart described as a cost trend even though the visible UI says it is not a bill; make the accessibility label provider-specific too.

Useful? React with 👍 / 👎.

: usesLatestPrimary ? L("Latest") : L("Today"),
value: primaryCostUSD.map { Self.costString($0, currencyCode: snapshot.currencyCode) } ?? "—",
emphasis: true),
.init(
Expand Down
27 changes: 20 additions & 7 deletions Sources/CodexBar/MenuCardView+Costs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,21 +169,34 @@ extension UsageMenuCardView.Model {
}

static func tokenUsageHint(provider: UsageProvider) -> String? {
let lines = Self.tokenUsageHintLines(provider: provider)
return lines.isEmpty ? nil : lines.joined(separator: "\n")
}

static func tokenUsageHeader(provider: UsageProvider) -> String {
provider == .codex ? L("codex_api_estimate_header") : L("cost_header_estimated")
Comment on lines +176 to +177

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the Codex estimate title in provider details

This adds a provider-specific token-usage header, but the Settings provider detail still renders Codex token USD as ProviderMetricInlineTextRow(title: L("Cost"), value: tokenUsage.sessionLine) and does not show tokenUsage.hintLine, so Settings continues to show the local Codex API-rate total as Cost with no not-billed disclosure. Please route that provider-detail row through the same provider-specific title/disclaimer path so the in-app Settings surface does not contradict the new Codex wording.

Useful? React with 👍 / 👎.

}

static func tokenUsageHintLines(provider: UsageProvider) -> [String] {
switch provider {
case .codex:
L("Estimated from local Codex logs for the selected account.")
[
L("Estimated from local Codex logs for the selected account."),
L("codex_api_estimate_not_billed"),
L("codex_api_estimate_hint"),
]
case .claude:
UsageFormatter.costEstimateHint(provider: provider)
[UsageFormatter.costEstimateHint(provider: provider)]
case .vertexai:
L("cost_estimate_hint")
[L("cost_estimate_hint")]
case .bedrock:
L("AWS Cost Explorer billing can lag.")
[L("AWS Cost Explorer billing can lag.")]
case .openai:
L("Reported by OpenAI Admin API organization usage.")
[L("Reported by OpenAI Admin API organization usage.")]
case .mistral:
L("Reported by Mistral billing usage.")
[L("Reported by Mistral billing usage.")]
default:
nil
[]
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ struct UsageMenuCardView: View {
}
if let tokenUsage = liveModel.tokenUsage {
VStack(alignment: .leading, spacing: 6) {
Text(L("cost_header_estimated"))
Text(UsageMenuCardView.Model.tokenUsageHeader(provider: liveModel.provider))
.font(.body)
.fontWeight(.medium)
Text(tokenUsage.sessionLine)
Expand Down Expand Up @@ -758,7 +758,7 @@ struct UsageMenuCardCostSectionView: View {
VStack(alignment: .leading, spacing: 10) {
if let tokenUsage = liveModel.tokenUsage {
VStack(alignment: .leading, spacing: 6) {
Text(L("cost_header_estimated"))
Text(UsageMenuCardView.Model.tokenUsageHeader(provider: liveModel.provider))
.font(.body)
.fontWeight(.medium)
Text(tokenUsage.sessionLine)
Expand Down
8 changes: 7 additions & 1 deletion Sources/CodexBar/PreferencesProviderDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,14 @@ struct ProviderMetricsInlineView: View {
}

if let tokenUsage = self.model.tokenUsage {
ProviderMetricInlineTextRow(title: L("Cost"), value: tokenUsage.sessionLine)
let isCodexEstimate = self.model.provider == .codex
ProviderMetricInlineTextRow(
title: isCodexEstimate ? L("codex_api_estimate_header") : L("Cost"),
value: tokenUsage.sessionLine)
ProviderMetricInlineTextRow(title: "", value: tokenUsage.monthLine)
if isCodexEstimate, let hint = tokenUsage.hintLine, !hint.isEmpty {
ProviderMetricInlineTextRow(title: "", value: hint)
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@
/* Cost estimation */
"cost_header_estimated" = "التكلفة (تقديرية)";
"cost_estimate_hint" = "تقديرات من سجلات محلية · قد تختلف عن فاتورتك";
"codex_api_estimate_header" = "تقدير مكافئ لواجهة API";
"codex_api_estimate_hint" = "الاستخدام المحلي × أسعار API العامة · ليس فاتورة اشتراك أو قيمة خطة";
"codex_api_estimate_not_billed" = "ليس فاتورة اشتراك أو قيمة خطة";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "لم يتم اكتشاف أي JetBrains IDE مع مساعدة الذكاء الاصطناعي. قم بتثبيت JetBrains IDE وفعل AI Assistant.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "OpenRouter API الرمز غير مكون. حدد OPENROUTER_API_KEY متغير البيئة أو قم بالتكوين في الإعدادات.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "z.ai API الرمز لم يعثر عليه. اضبط apiKey في ~/.codexbar/config.json أو Z_AI_API_KEY.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,9 @@
/* Cost estimation */
"cost_header_estimated" = "Cost (estimat)";
"cost_estimate_hint" = "Estimat a partir de registres locals · pot diferir de la vostra factura";
"codex_api_estimate_header" = "Estimació equivalent a l'API";
"codex_api_estimate_hint" = "Ús local × preus públics de l'API · no és una factura de subscripció ni el valor d'un pla";
"codex_api_estimate_not_billed" = "no és una factura de subscripció ni el valor d'un pla";

/* Popup panels */
"No usage configured." = "No hi ha cap ús configurat.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,9 @@
/* Cost estimation */
"cost_header_estimated" = "Kosten (geschätzt)";
"cost_estimate_hint" = "Schätzung aus lokalen Protokollen · kann von Ihrer Rechnung abweichen";
"codex_api_estimate_header" = "API-äquivalente Schätzung";
"codex_api_estimate_hint" = "Lokale Nutzung × öffentliche API-Preise · keine Abonnementrechnung oder Planwert";
"codex_api_estimate_not_billed" = "keine Abonnementrechnung oder Planwert";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "Keine JetBrains-IDE mit AI Assistant erkannt. Installieren Sie eine JetBrains-IDE und aktivieren Sie AI Assistant.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "OpenRouter-API-Token nicht konfiguriert. Legen Sie die Umgebungsvariable OPENROUTER_API_KEY fest oder konfigurieren Sie sie in den Einstellungen.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "z.ai-API-Token nicht gefunden. Legen Sie apiKey in ~/.codexbar/config.json oder Z_AI_API_KEY fest.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@
/* Cost estimation */
"cost_header_estimated" = "Cost (estimated)";
"cost_estimate_hint" = "Estimated from local logs · may differ from your bill";
"codex_api_estimate_header" = "API-equivalent estimate";
"codex_api_estimate_hint" = "Local usage × public API prices · not a subscription bill or plan value";
"codex_api_estimate_not_billed" = "not a subscription bill or plan value";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,9 @@
/* Cost estimation */
"cost_header_estimated" = "Coste (estimado)";
"cost_estimate_hint" = "Estimado a partir de registros locales · puede diferir de tu factura";
"codex_api_estimate_header" = "Estimación equivalente a la API";
"codex_api_estimate_hint" = "Uso local × precios públicos de la API · no es una factura de suscripción ni el valor de un plan";
"codex_api_estimate_not_billed" = "no es una factura de suscripción ni el valor de un plan";

/* Popup panels */
"No usage configured." = "No hay uso configurado.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/fa.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@
/* Cost estimation */
"cost_header_estimated" = "هزینه (تخمینی)";
"cost_estimate_hint" = "برآورد شده از چوب های محلی · ممکن است با صورتحساب شما متفاوت باشد";
"codex_api_estimate_header" = "برآورد معادل API";
"codex_api_estimate_hint" = "استفاده محلی × قیمت‌های عمومی API · نه صورتحساب اشتراک است و نه ارزش طرح";
"codex_api_estimate_not_billed" = "نه صورتحساب اشتراک است و نه ارزش طرح";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "هیچ JetBrains IDE ای با AI Assistant شناسایی نشد. یک JetBrains IDE نصب کنید و AI Assistant را فعال کنید.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "OpenRouter API توکن پیکربندی نشده است. متغیر محیطی OPENROUTER_API_KEY تنظیم کنید یا در تنظیمات پیکربندی کنید.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "z.ai API توکن پیدا نشد. apiKey را در ~/.codexbar/config.json یا Z_AI_API_KEY تنظیم کنید.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,9 @@
/* Cost estimation */
"cost_header_estimated" = "Coût (estimé)";
"cost_estimate_hint" = "Estimé à partir des journaux locaux · peut différer de votre facture";
"codex_api_estimate_header" = "Estimation équivalente à l’API";
"codex_api_estimate_hint" = "Utilisation locale × tarifs API publics · ce n’est ni une facture d’abonnement ni la valeur d’un forfait";
"codex_api_estimate_not_billed" = "ce n’est ni une facture d’abonnement ni la valeur d’un forfait";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "Aucun IDE JetBrains avec AI Assistant détecté. Installez un IDE JetBrains et activez AI Assistant.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "Jeton API OpenRouter non configuré. Définissez la variable d'environnement OPENROUTER_API_KEY ou configurez-la dans Paramètres.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "Jeton API z.ai introuvable. Définissez apiKey dans ~/.codexbar/config.json ou Z_AI_API_KEY.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/gl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,9 @@
/* Cost estimation */
"cost_header_estimated" = "Custo (estimado)";
"cost_estimate_hint" = "Estimado a partir de rexistros locais · pode diferir da túa factura";
"codex_api_estimate_header" = "Estimación equivalente á API";
"codex_api_estimate_hint" = "Uso local × prezos públicos da API · non é unha factura de subscrición nin o valor dun plan";
"codex_api_estimate_not_billed" = "non é unha factura de subscrición nin o valor dun plan";

/* Popup panels */
"No usage configured." = "Non hai ningún uso configurado.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/id.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,9 @@
/* Cost estimation */
"cost_header_estimated" = "Biaya (perkiraan)";
"cost_estimate_hint" = "Diperkirakan dari log lokal · mungkin berbeda dari tagihan Anda";
"codex_api_estimate_header" = "Estimasi setara API";
"codex_api_estimate_hint" = "Penggunaan lokal × harga API publik · bukan tagihan langganan atau nilai paket";
"codex_api_estimate_not_billed" = "bukan tagihan langganan atau nilai paket";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "Tidak ada JetBrains IDE dengan AI Assistant terdeteksi. Pasang JetBrains IDE dan aktifkan AI Assistant.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "Token API OpenRouter belum dikonfigurasi. Atur variabel lingkungan OPENROUTER_API_KEY atau konfigurasi di Pengaturan.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "Token API z.ai tidak ditemukan. Atur apiKey di ~/.codexbar/config.json atau Z_AI_API_KEY.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/it.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,9 @@
/* Cost estimation */
"cost_header_estimated" = "Costo (stimato)";
"cost_estimate_hint" = "Stimato dai log locali · può differire dalla fattura";
"codex_api_estimate_header" = "Stima equivalente API";
"codex_api_estimate_hint" = "Utilizzo locale × prezzi API pubblici · non è una fattura di abbonamento né il valore di un piano";
"codex_api_estimate_not_billed" = "non è una fattura di abbonamento né il valore di un piano";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "Nessun IDE JetBrains con AI Assistant rilevato. Installa un IDE JetBrains e abilita AI Assistant.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "Token API OpenRouter non configurato. Imposta la variabile d'ambiente OPENROUTER_API_KEY oppure configuralo nelle Impostazioni.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "Token API z.ai non trovato. Imposta apiKey in ~/.codexbar/config.json o Z_AI_API_KEY.";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,9 @@
/* Cost estimation */
"cost_header_estimated" = "コスト(推定)";
"cost_estimate_hint" = "ローカルログからの推定値 · 請求額と異なる場合があります";
"codex_api_estimate_header" = "API相当の見積もり";
"codex_api_estimate_hint" = "ローカル使用量 × 公開API価格 · サブスクリプションの請求額やプラン価値ではありません";
"codex_api_estimate_not_billed" = "サブスクリプションの請求額やプラン価値ではありません";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "AI Assistant 対応の JetBrains IDE が検出されませんでした。JetBrains IDE をインストールし、AI Assistant を有効にしてください。";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "OpenRouter API トークンが設定されていません。環境変数 OPENROUTER_API_KEY を設定するか、設定で構成してください。";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "z.ai API トークンが見つかりません。~/.codexbar/config.json の apiKey または Z_AI_API_KEY を設定してください。";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ko.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,9 @@
"not_found" = "찾을 수 없음";
"cost_header_estimated" = "비용(추정)";
"cost_estimate_hint" = "로컬 로그에서 추정 · 실제 청구액과 다를 수 있음";
"codex_api_estimate_header" = "API 등가 추정치";
"codex_api_estimate_hint" = "로컬 사용량 × 공개 API 가격 · 구독 청구서나 플랜 가치가 아닙니다";
"codex_api_estimate_not_billed" = "구독 청구서나 플랜 가치가 아닙니다";
"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant." = "AI Assistant가 있는 JetBrains IDE를 찾지 못했습니다. JetBrains IDE를 설치하고 AI Assistant를 사용하세요.";
"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings." = "OpenRouter API 토큰이 구성되지 않았습니다. OPENROUTER_API_KEY 환경 변수를 설정하거나 설정에서 구성하세요.";
"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY." = "z.ai API 토큰을 찾을 수 없습니다. ~/.codexbar/config.json에 apiKey를 설정하거나 Z_AI_API_KEY를 설정하세요.";
Expand Down
Loading