From 8f52a08177fb1ff22de4e5aa4d1be5d80697c4d1 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Tue, 15 Sep 2026 02:33:27 +0800 Subject: [PATCH] feat(usage): show 30-minute trend ticks and restore light theme --- index.html | 2 +- src-tauri/src/usage.rs | 35 ++- src/App.tsx | 46 +-- src/i18n/ja.ts | 3 +- src/i18n/locales/en.ts | 3 +- src/i18n/locales/zh-CN.ts | 3 +- src/pages/ConfigPanel.tsx | 2 - src/pages/UsageRecordsPage.tsx | 98 +++--- src/services/usageTrend.ts | 68 ++-- src/styles.css | 555 +++++++++++++++++---------------- src/theme.ts | 2 +- tests/usageTrend.test.ts | 52 +-- 12 files changed, 466 insertions(+), 403 deletions(-) diff --git a/index.html b/index.html index 075dd72..08b40cd 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@ : window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; document.documentElement.dataset.theme = theme; document.documentElement.style.colorScheme = theme; - document.documentElement.style.backgroundColor = theme === 'dark' ? '#111412' : '#f6f7f5'; + document.documentElement.style.backgroundColor = theme === 'dark' ? '#0b0d11' : '#f6f7f5'; })(); EasyCLIProxyAPI diff --git a/src-tauri/src/usage.rs b/src-tauri/src/usage.rs index 085fd17..263a039 100644 --- a/src-tauri/src/usage.rs +++ b/src-tauri/src/usage.rs @@ -2649,7 +2649,16 @@ fn load_usage_overview( let timeline_sql = format!( r#" SELECT - local_hour, + CASE + WHEN timestamp_ms > 0 THEN + strftime('%Y-%m-%d-%H-', datetime(timestamp_ms / 1000, 'unixepoch', 'localtime')) + || CASE + WHEN CAST(strftime('%M', datetime(timestamp_ms / 1000, 'unixepoch', 'localtime')) AS INTEGER) < 30 + THEN '00' + ELSE '30' + END + ELSE local_hour || '-00' + END, COALESCE(NULLIF(TRIM(model), ''), 'unknown'), COUNT(*), COALESCE(SUM(CASE WHEN failed = 0 THEN 1 ELSE 0 END), 0), @@ -2657,8 +2666,8 @@ fn load_usage_overview( COALESCE(SUM(CASE WHEN canceled != 0 THEN 1 ELSE 0 END), 0), COALESCE(SUM(total_tokens), 0) FROM usage_events{} - GROUP BY local_hour, 2 - ORDER BY local_hour ASC, 2 ASC + GROUP BY 1, 2 + ORDER BY 1 ASC, 2 ASC "#, filter.clause ); @@ -5541,6 +5550,26 @@ mod tests { fs::remove_dir_all(root).unwrap(); } + #[test] + fn timeline_groups_requests_into_half_hour_buckets() { + let root = test_root("sqlite-half-hour"); + let mut connection = open_test_database(&root); + let first = sample_record("half-1", "2026-07-17T20:10:00+08:00", "gpt-a"); + let second = sample_record("half-2", "2026-07-17T20:40:00+08:00", "gpt-a"); + insert_usage_records(&mut connection, &[first, second]).unwrap(); + + let overview = load_usage_overview(&connection, &UsageQuery::default()).unwrap(); + assert_eq!(overview.timeline.len(), 2); + assert_ne!(overview.timeline[0].hour, overview.timeline[1].hour); + assert!(overview + .timeline + .iter() + .all(|point| point.hour.ends_with("-00") || point.hour.ends_with("-30"))); + assert_eq!(overview.timeline.iter().map(|point| point.requests).sum::(), 2); + drop(connection); + fs::remove_dir_all(root).unwrap(); + } + #[test] fn overview_tps_uses_weighted_total_latency_without_requiring_ttft() { let root = test_root("tps-overview"); diff --git a/src/App.tsx b/src/App.tsx index 034d2a6..16d32a3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,13 +10,13 @@ import { History, House, Languages, + Lock, LogIn, MessageCircle, Network, PackageOpen, ServerCog, Settings, - Sparkles, X, } from 'lucide-react'; import appLogo from './assets/logo.jpg'; @@ -42,7 +42,7 @@ const pages = [ { id: 'easy', labelKey: 'app.nav.easy', - icon: Sparkles, + icon: House, component: HomePage, }, { @@ -52,16 +52,10 @@ const pages = [ component: HomePage, }, { - id: 'versions', - labelKey: 'app.nav.versions', - icon: PackageOpen, - component: VersionManagementPageWrapper, - }, - { - id: 'config', - labelKey: 'app.nav.config', - icon: Settings, - component: ConfigPanelPage, + id: 'api', + labelKey: 'app.nav.api', + icon: Network, + component: ApiAccessPage, }, { id: 'oauth', @@ -70,10 +64,10 @@ const pages = [ component: OAuthManagementPage, }, { - id: 'api', - labelKey: 'app.nav.api', - icon: Network, - component: ApiAccessPage, + id: 'agents', + labelKey: 'app.nav.agents', + icon: Bot, + component: AgentsPage, }, { id: 'usage-records', @@ -82,10 +76,16 @@ const pages = [ component: UsageRecordsPage, }, { - id: 'agents', - labelKey: 'app.nav.agents', - icon: Bot, - component: AgentsPage, + id: 'config', + labelKey: 'app.nav.config', + icon: Settings, + component: ConfigPanelPage, + }, + { + id: 'versions', + labelKey: 'app.nav.versions', + icon: PackageOpen, + component: VersionManagementPageWrapper, }, ] as const; @@ -309,12 +309,14 @@ function AppContent() { .filter(Boolean) .join(' ')} disabled={locked} - title={locked ? t('app.coreRequired.title') : undefined} + title={locked ? t('app.nav.lockedHint') : undefined} onClick={() => select(page.id)} >