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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixed
- Release: prevent manual CLI artifact builds from publishing or clobbering release assets (#1154). Thanks @jskoiz!
- Cost history: route OpenAI and Mistral API spend through the shared cost-history cards, including OpenAI request counts (#1163). Thanks @LeoLin990405!
- Localization: improve Traditional Chinese wording and localize notification copy (#1158). Thanks @jack24254029!
- Localization: improve Simplified Chinese visible menu, dashboard, and usage labels (#1145). Thanks @Yuxin-Qiao!

Expand Down
44 changes: 32 additions & 12 deletions Sources/CodexBar/CostHistoryChartMenuView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ struct CostHistoryChartMenuView: View {
let date: Date
let costUSD: Double
let totalTokens: Int?
let requestCount: Int?

init(date: Date, costUSD: Double, totalTokens: Int?) {
init(date: Date, costUSD: Double, totalTokens: Int?, requestCount: Int?) {
self.date = date
self.costUSD = costUSD
self.totalTokens = totalTokens
self.requestCount = requestCount
self.id = "\(Int(date.timeIntervalSince1970))-\(costUSD)"
}
}
Expand All @@ -36,21 +38,27 @@ struct CostHistoryChartMenuView: View {
private let provider: UsageProvider
private let daily: [DailyEntry]
private let totalCostUSD: Double?
private let currencyCode: String
private let historyDays: Int
private let windowLabel: String?
private let width: CGFloat
@State private var selectedDateKey: String?

init(
provider: UsageProvider,
daily: [DailyEntry],
totalCostUSD: Double?,
currencyCode: String = "USD",
historyDays: Int = 30,
windowLabel: String? = nil,
width: CGFloat)
{
self.provider = provider
self.daily = daily
self.totalCostUSD = totalCostUSD
self.currencyCode = currencyCode
self.historyDays = max(1, min(365, historyDays))
self.windowLabel = windowLabel
self.width = width
}

Expand Down Expand Up @@ -173,8 +181,8 @@ struct CostHistoryChartMenuView: View {
if let total = self.totalCostUSD {
Text(String(
format: L("Est. total (%@): %@"),
Self.windowLabel(days: self.historyDays),
UsageFormatter.usdString(total)))
self.windowLabel ?? Self.windowLabel(days: self.historyDays),
self.costString(total)))
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
Expand Down Expand Up @@ -252,7 +260,11 @@ struct CostHistoryChartMenuView: View {
for entry in sorted {
guard let costUSD = entry.costUSD, costUSD >= 0 else { continue }
guard let date = self.dateFromDayKey(entry.date) else { continue }
let point = Point(date: date, costUSD: costUSD, totalTokens: entry.totalTokens)
let point = Point(
date: date,
costUSD: costUSD,
totalTokens: entry.totalTokens,
requestCount: entry.requestCount)
points.append(point)
pointsByKey[entry.date] = point
entriesByKey[entry.date] = entry
Expand Down Expand Up @@ -426,12 +438,15 @@ struct CostHistoryChartMenuView: View {
}

let dayLabel = date.formatted(.dateTime.month(.abbreviated).day())
let cost = UsageFormatter.usdString(point.costUSD)
let primary = if let tokens = point.totalTokens {
String(format: L("%@: %@ · %@ tokens"), dayLabel, cost, UsageFormatter.tokenCountString(tokens))
} else {
"\(dayLabel): \(cost)"
let cost = self.costString(point.costUSD)
var parts = [cost]
if let tokens = point.totalTokens {
parts.append("\(UsageFormatter.tokenCountString(tokens)) tokens")
}
if let requests = point.requestCount {
parts.append("\(UsageFormatter.tokenCountString(requests)) requests")
}
let primary = "\(dayLabel): \(parts.joined(separator: " · "))"
return DetailContent(primary: primary, rows: self.breakdownRows(key: key, model: model))
}

Expand Down Expand Up @@ -472,20 +487,21 @@ struct CostHistoryChartMenuView: View {
UsageFormatter.modelCostDetail(
item.modelName,
costUSD: item.costUSD,
totalTokens: item.totalTokens)
totalTokens: item.totalTokens,
currencyCode: self.currencyCode)
}

private func modelBreakdownModeSubtitle(_ item: CostUsageDailyReport.ModelBreakdown) -> String? {
var parts: [String] = []
if let standardCost = item.standardCostUSD {
var standardPart = "Std \(UsageFormatter.usdString(standardCost))"
var standardPart = "Std \(self.costString(standardCost))"
if let standardTokens = item.standardTokens {
standardPart += " · \(UsageFormatter.tokenCountString(standardTokens))"
}
parts.append(standardPart)
}
if let priorityCost = item.priorityCostUSD {
var priorityPart = "Fast \(UsageFormatter.usdString(priorityCost))"
var priorityPart = "Fast \(self.costString(priorityCost))"
if let priorityTokens = item.priorityTokens {
priorityPart += " · \(UsageFormatter.tokenCountString(priorityTokens))"
}
Expand All @@ -495,6 +511,10 @@ struct CostHistoryChartMenuView: View {
return parts.joined(separator: " / ")
}

private func costString(_ value: Double) -> String {
UsageFormatter.currencyString(value, currencyCode: self.currencyCode)
}

private static func breakdownAccentOpacity(for index: Int) -> Double {
let opacity = 0.75 - (Double(index) * 0.12)
return max(0.3, opacity)
Expand Down
Loading