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
+257
-239
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user