Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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