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:
Gemini Nexus Bot committed 2026-04-29 01:43:48 -03:00
1 parent b1c76f3b69
commit a648f2a281
44 files changed
+8920 -14566

No files matched your search

+257 -239
View File
@@ -1,245 +1,263 @@
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,400;0,500;0,700;1,400&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
@import 'tailwindcss';
@plugin 'tailwindcss-animate';
@custom-variant dark (&:is(.dark *));
@theme {
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--font-sans: Plus Jakarta Sans, 'Microsoft YaHei', 'Heiti SC', 'WenQuanYi Micro Hei', sans-serif;
--font-mono: Geist Mono, monospace;
--font-tomorrow: Tomorrow, sans-serif;
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
}
@utility container {
margin-inline: auto;
padding-inline: 2rem;
@media (width >= --theme(--breakpoint-sm)) {
max-width: none;
}
@media (width >= 1400px) {
max-width: 1400px;
}
}
@layer base {
@font-face {
}
@font-face {
font-family: 'Geist Mono';
font-display: swap;
src: url('../assets/fonts/geist-mono/geist-mono.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 400;
font-style: normal;
src: url('../assets/fonts/tomorrow/tomorrow-regular.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 400;
font-style: italic;
src: url('../assets/fonts/tomorrow/tomorrow-italic.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 700;
font-style: normal;
src: url('../assets/fonts/tomorrow/tomorrow-bold.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 700;
font-style: italic;
src: url('../assets/fonts/tomorrow/tomorrow-bold-italic.ttf') format('truetype');
}
body {
letter-spacing: var(--tracking-normal);
}
}
@layer base {
}
@layer base {
body {
@apply overflow-hidden;
}
.draglayer {
@apply bg-background;
-webkit-app-region: drag;
}
button {
@apply cursor-pointer;
}
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
@theme inline {
--font-sans: Plus Jakarta Sans, sans-serif;
--font-mono: Geist Mono, monospace;
--font-serif: Georgia, serif;
--radius: 0.5rem;
--tracking-tighter: calc(var(--tracking-normal) - 0.05em);
--tracking-tight: calc(var(--tracking-normal) - 0.025em);
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
--tracking-widest: calc(var(--tracking-normal) + 0.1em);
--tracking-normal: var(--tracking-normal);
--shadow-2xl: var(--shadow-2xl);
--shadow-xl: var(--shadow-xl);
--shadow-lg: var(--shadow-lg);
--shadow-md: var(--shadow-md);
--shadow: var(--shadow);
--shadow-sm: var(--shadow-sm);
--shadow-xs: var(--shadow-xs);
--shadow-2xs: var(--shadow-2xs);
--spacing: var(--spacing);
--letter-spacing: var(--letter-spacing);
--shadow-offset-y: var(--shadow-offset-y);
--shadow-offset-x: var(--shadow-offset-x);
--shadow-spread: var(--shadow-spread);
--shadow-blur: var(--shadow-blur);
--shadow-opacity: var(--shadow-opacity);
--color-shadow-color: var(--shadow-color);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive-foreground: var(--destructive-foreground);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
}
@import 'tailwindcss';
@plugin 'tailwindcss-animate';
@custom-variant dark (&:is(.dark *));
/*
* Gemini Nexus Design System
* Style: Minimal Dark Professional
* Fonts: Inter (UI) + JetBrains Mono (code)
* Palette: Refined Slate + Emerald accents
*/
@theme inline {
--font-sans: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
--radius: 0.625rem;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-fade-in: fade-in 0.2s ease-out;
--animate-slide-up: slide-up 0.25s ease-out;
--animate-scale-in: scale-in 0.2s ease-out;
@keyframes accordion-down {
from { height: 0; }
to { height: var(--radix-accordion-content-height); }
}
@keyframes accordion-up {
from { height: var(--radix-accordion-content-height); }
to { height: 0; }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slide-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.97); }
to { opacity: 1; transform: scale(1); }
}
}
/* Container utility */
@utility container {
margin-inline: auto;
padding-inline: 2rem;
@media (width >= 1400px) {
max-width: 1400px;
}
}
/* Font faces — local fallbacks */
@layer base {
:root, .dark {
--background: #0F172A;
--foreground: #F8FAFC;
--card: #0F172A;
--card-foreground: #F8FAFC;
--popover: #0F172A;
--popover-foreground: #F8FAFC;
--primary: #1E293B;
--primary-foreground: #FFFFFF;
--secondary: #334155;
--secondary-foreground: #FFFFFF;
--muted: #272F42;
--muted-foreground: #94A3B8;
--accent: #22C55E;
--accent-foreground: #FFFFFF;
--destructive: #EF4444;
--destructive-foreground: #FFFFFF;
--border: #475569;
--input: #475569;
--ring: #1E293B;
--radius: 0.5rem;
@font-face {
font-family: 'Geist Mono';
font-display: swap;
src: url('../assets/fonts/geist-mono/geist-mono.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 400;
font-style: normal;
src: url('../assets/fonts/tomorrow/tomorrow-regular.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 400;
font-style: italic;
src: url('../assets/fonts/tomorrow/tomorrow-italic.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 700;
font-style: normal;
src: url('../assets/fonts/tomorrow/tomorrow-bold.ttf') format('truetype');
}
@font-face {
font-family: 'Tomorrow';
font-weight: 700;
font-style: italic;
src: url('../assets/fonts/tomorrow/tomorrow-bold-italic.ttf') format('truetype');
}
}
/*
* Refined dark color palette
* Inspired by: Linear, Vercel, Raycast
* Deep neutral darks with subtle warm undertones
*/
@layer base {
:root,
.dark {
/* Core surfaces */
--background: #0a0a0f;
--foreground: #e8eaed;
--card: #111118;
--card-foreground: #e8eaed;
--popover: #16161e;
--popover-foreground: #e8eaed;
/* Interactive elements */
--primary: #e8eaed;
--primary-foreground: #0a0a0f;
--secondary: #1c1c26;
--secondary-foreground: #c4c7cc;
--muted: #16161e;
--muted-foreground: #6b7280;
--accent: #10b981;
--accent-foreground: #ffffff;
/* State colors */
--destructive: #ef4444;
--destructive-foreground: #ffffff;
/* Boundaries */
--border: #ffffff0d;
--input: #1c1c26;
--ring: #10b981;
--radius: 0.625rem;
/* Chart palette — vibrant on dark */
--chart-1: #6366f1;
--chart-2: #10b981;
--chart-3: #f59e0b;
--chart-4: #ec4899;
--chart-5: #8b5cf6;
/* Sidebar */
--sidebar: #0a0a0f;
--sidebar-foreground: #e8eaed;
--sidebar-primary: #e8eaed;
--sidebar-primary-foreground: #0a0a0f;
--sidebar-accent: #10b981;
--sidebar-accent-foreground: #ffffff;
--sidebar-border: #ffffff0d;
--sidebar-ring: #10b981;
}
}
/* Global base styles */
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground overflow-hidden antialiased;
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.draglayer {
@apply bg-background;
-webkit-app-region: drag;
}
button {
@apply cursor-pointer;
}
/* Smooth scrollbar for dark mode */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #ffffff1a;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #ffffff33;
}
/* Selection color */
::selection {
background: #10b98133;
color: #e8eaed;
}
}
/* Utility classes for the design system */
@layer utilities {
.glass {
background: rgba(17, 17, 24, 0.6);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.glass-border {
border: 1px solid rgba(255, 255, 255, 0.06);
}
.glow-accent {
box-shadow: 0 0 20px rgba(16, 185, 129, 0.1);
}
.glow-accent-sm {
box-shadow: 0 0 10px rgba(16, 185, 129, 0.08);
}
.text-gradient {
background: linear-gradient(135deg, #e8eaed 0%, #6b7280 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.animate-in {
animation: fade-in 0.2s ease-out, slide-up 0.25s ease-out;
}
}