fix: token tracking pipeline + premium usage dashboard redesign
Backend fixes: - Fix totalTokens defaulting to 0 when API omits totalTokenCount (now computes prompt+completion) - Fix streaming metadata extraction: unwrap response wrapper in Anthropic/Gemini handlers - Fix Zod schemas: handle SQL SUM(null) with nullable().transform() - Add is_estimated column migration to token_usage table Frontend redesign: - Rewrite Usage page with area charts (nivo/line), horizontal bar model ranking - StatCard: add trend indicators (% vs previous period) and inline SVG sparklines - Premium color palette, prompt/completion ratio bar, top models mini-list - Live indicator with 15s auto-refresh Design system migration: - Standardize all components on Gemini Nexus tokens (rounded-xl, border-white/[0.06]) - Refine UI components, proxy tabs, cloud account list, provider groups
This commit is contained in:
1 parent
b1c76f3b69
commit
a648f2a281
44 files changed
+8920
-14566
No files matched your search
@@ -77,12 +77,15 @@ export class ProxyService {
|
||||
if (!usage) {
|
||||
return;
|
||||
}
|
||||
const prompt = usage.promptTokenCount ?? 0;
|
||||
const completion = usage.candidatesTokenCount ?? 0;
|
||||
const total = usage.totalTokenCount ?? (prompt + completion);
|
||||
this.tokenUsageService.recordUsage({
|
||||
accountId,
|
||||
model,
|
||||
promptTokens: usage.promptTokenCount ?? 0,
|
||||
completionTokens: usage.candidatesTokenCount ?? 0,
|
||||
totalTokens: usage.totalTokenCount ?? 0,
|
||||
promptTokens: prompt,
|
||||
completionTokens: completion,
|
||||
totalTokens: total,
|
||||
timestamp: Date.now(),
|
||||
requestType,
|
||||
isEstimated,
|
||||
@@ -319,14 +322,15 @@ export class ProxyService {
|
||||
if (startMsg) subscriber.next(startMsg);
|
||||
}
|
||||
|
||||
const candidate = json.candidates?.[0];
|
||||
const responsePayload = json.response ?? json;
|
||||
const candidate = responsePayload.candidates?.[0];
|
||||
const part = candidate?.content?.parts?.[0];
|
||||
|
||||
if (candidate?.finishReason) {
|
||||
lastFinishReason = candidate.finishReason;
|
||||
}
|
||||
if (json.usageMetadata) {
|
||||
lastUsageMetadata = json.usageMetadata;
|
||||
if (responsePayload.usageMetadata) {
|
||||
lastUsageMetadata = responsePayload.usageMetadata;
|
||||
}
|
||||
|
||||
if (this.isGeminiPart(part)) {
|
||||
@@ -605,10 +609,11 @@ export class ProxyService {
|
||||
if (dataStr === '[DONE]') continue;
|
||||
try {
|
||||
const parsed = JSON.parse(dataStr);
|
||||
if (parsed?.usageMetadata) {
|
||||
lastUsageMetadata = parsed.usageMetadata;
|
||||
const responsePayload = parsed?.response ?? parsed;
|
||||
if (responsePayload?.usageMetadata) {
|
||||
lastUsageMetadata = responsePayload.usageMetadata;
|
||||
}
|
||||
const text = parsed?.candidates?.[0]?.content?.parts?.[0]?.text;
|
||||
const text = responsePayload?.candidates?.[0]?.content?.parts?.[0]?.text;
|
||||
if (typeof text === 'string') {
|
||||
accumulatedText += text;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user