Track usage independent of conversation save state; fix Analytics toolbar button
Conversation text is still only persisted when explicitly saved (⌘S), but tokens/cost/model/provider are now logged to a new usage_events table for every completed AI response regardless — the Analytics view now reads from this table instead of messages, so it reflects real usage even for conversations that were never saved. Adds a By Provider chart mode alongside Over Time/By Model. Also fixes the Analytics entry point: ToolbarItem(placement: .navigation) silently doesn't render in a plain .sheet-presented NavigationStack on macOS. Moved the button inline next to the segmented picker, matching the codebase's existing convention (e.g. the model-favorites star filter).
This commit is contained in:
@@ -1153,6 +1153,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
let cost = Self.resolveCost(usage: usage, pricing: model.pricing)
|
||||
messages[index].cost = cost
|
||||
sessionStats.addMessage(inputTokens: usage.promptTokens, outputTokens: usage.completionTokens, cost: cost)
|
||||
logUsageEvent(modelId: modelId, promptTokens: usage.promptTokens, completionTokens: usage.completionTokens, cost: cost)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1216,6 +1217,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
let cost = Self.resolveCost(usage: usage, pricing: model.pricing)
|
||||
messages[index].cost = cost
|
||||
sessionStats.addMessage(inputTokens: usage.promptTokens, outputTokens: usage.completionTokens, cost: cost)
|
||||
logUsageEvent(modelId: modelId, promptTokens: usage.promptTokens, completionTokens: usage.completionTokens, cost: cost)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1572,6 +1574,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
let cost = usage.rawCostUSD
|
||||
messages[index].cost = cost
|
||||
sessionStats.addMessage(inputTokens: usage.promptTokens, outputTokens: usage.completionTokens, cost: cost)
|
||||
logUsageEvent(modelId: modelId, promptTokens: usage.promptTokens, completionTokens: usage.completionTokens, cost: cost)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -1863,6 +1866,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
outputTokens: usage.completionTokens,
|
||||
cost: cost
|
||||
)
|
||||
logUsageEvent(modelId: modelId, promptTokens: usage.promptTokens, completionTokens: usage.completionTokens, cost: cost)
|
||||
}
|
||||
} else {
|
||||
let assistantMessage = Message(
|
||||
@@ -1890,6 +1894,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
outputTokens: usage.completionTokens,
|
||||
cost: cost
|
||||
)
|
||||
logUsageEvent(modelId: modelId, promptTokens: usage.promptTokens, completionTokens: usage.completionTokens, cost: cost)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2610,6 +2615,24 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
return calculateCost(usage: usage, pricing: pricing)
|
||||
}
|
||||
|
||||
/// Records usage independent of whether this conversation ever gets explicitly saved (⌘S) —
|
||||
/// conversation *text* is opt-in, but tokens/cost/model/provider are tracked for every
|
||||
/// completed generation regardless, so the Analytics view reflects real usage either way.
|
||||
private func logUsageEvent(modelId: String, promptTokens: Int?, completionTokens: Int?, cost: Double?) {
|
||||
do {
|
||||
try DatabaseService.shared.logUsageEvent(
|
||||
provider: currentProvider.rawValue,
|
||||
modelId: modelId,
|
||||
promptTokens: promptTokens,
|
||||
completionTokens: completionTokens,
|
||||
cost: cost,
|
||||
conversationId: currentConversationId
|
||||
)
|
||||
} catch {
|
||||
Log.db.error("Failed to log usage event: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Summarize a chunk of messages into a concise summary
|
||||
private func summarizeMessageChunk(_ messages: [Message]) async -> String? {
|
||||
guard let provider = providerRegistry.getProvider(for: currentProvider),
|
||||
|
||||
Reference in New Issue
Block a user