From fc08832ff35471468a24238d76bca620f24625cd Mon Sep 17 00:00:00 2001 From: AIStudio2API contributor Date: Wed, 4 Nov 2026 08:45:51 -0800 Subject: [PATCH] =?UTF-8?q?feat(console):=20=E5=A2=9E=E5=8A=A0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E6=80=BB=E8=A7=88=E5=92=8C=E8=B4=A6=E6=88=B7=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + README_en.md | 1 + docs/images/overview-dark.webp | Bin 0 -> 67656 bytes web/src/App.vue | 32 +- web/src/components/AccountsPanel.vue | 37 +- web/src/components/OverviewPanel.vue | 551 +++++++++++++++++++++++++++ web/src/icons.ts | 2 + web/src/locales/de.ts | 42 ++ web/src/locales/en.ts | 42 ++ web/src/locales/fr.ts | 42 ++ web/src/locales/ja.ts | 41 ++ web/src/locales/ko.ts | 41 ++ web/src/locales/zh-CN.ts | 41 ++ web/src/locales/zh-TW.ts | 41 ++ web/src/overview-metrics.test.ts | 31 ++ web/src/overview-metrics.ts | 63 +++ web/src/types.ts | 1 + 17 files changed, 997 insertions(+), 12 deletions(-) create mode 100644 docs/images/overview-dark.webp create mode 100644 web/src/components/OverviewPanel.vue create mode 100644 web/src/overview-metrics.test.ts create mode 100644 web/src/overview-metrics.ts diff --git a/README.md b/README.md index b931a46c..340f6d3e 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ - **Live 与 Robotics**: 通过 WebSocket 支持文本、音频、JPEG、媒体结束、工具调用、恢复和中断 - **反指纹检测**: 为每个账户固定 Firefox 指纹与出口,官方 WAA 在 SpiderMonkey 的 Firefox 页面环境或 Camoufox 页面中运行 - **图形界面启动器**: 通过网页管理账户、服务启停、实时日志、模型、请求、用量统计和配置,界面提供深色与浅色两种配色,以及简体中文、繁體中文、English、日本語、한국어、Français 与 Deutsch 七种语言 +- **服务总览**: 首页汇总账户健康、模型调用、冷却、最近请求和错误,按太平洋时区展示当天各账户的请求与 Token 用量;账户状态筛选显示数量 - **桌面版**: Windows、macOS 与 Linux 桌面应用在窗口中显示管理页面,关闭窗口后在系统托盘继续运行 - **在线更新**: 管理端“版本”页显示最新发行版本与更新说明,桌面版与 Linux、macOS 命令行版一键下载、校验并重启到新版本,Linux deb 安装时由系统弹出管理员认证 - **模块化架构**: Go 负责协议、调度、API 与管理端,SpiderMonkey 或 Camoufox 负责 WAA 运行时,Camoufox 负责隔离登录 diff --git a/README_en.md b/README_en.md index 87e1b7a9..455e30c1 100644 --- a/README_en.md +++ b/README_en.md @@ -93,6 +93,7 @@ The three projects connect to AI Studio, the Gemini web app and Antigravity, and - **Live and Robotics**: WebSocket text, audio, JPEG images, media end, tool calls, resumption, and interruption - **Anti-Fingerprinting**: Each account keeps a stable Firefox fingerprint and network exit, and the official WAA runs in the Firefox page environment of SpiderMonkey or in a Camoufox page - **GUI Launcher**: Manage accounts, service controls, live logs, models, requests, usage statistics, and configuration in the web UI, with dark and light color themes and seven languages: 简体中文, 繁體中文, English, 日本語, 한국어, Français, and Deutsch +- **Service Overview**: The landing page summarizes account health, model traffic, cooldowns, recent requests, and errors, with daily per-account request and token usage in Pacific time; account-state filters show counts - **Desktop App**: Windows, macOS, and Linux desktop apps show the management UI in a window and keep running in the system tray after the window is closed - **Online Update**: The Version page shows the latest release and its notes; the desktop apps and the Linux and macOS command-line builds download, verify, and restart into the new version in one click, and a Linux deb install asks for administrator authentication - **Modular Architecture**: Go handles protocols, scheduling, APIs, and management; SpiderMonkey or Camoufox hosts WAA, and Camoufox hosts isolated login diff --git a/docs/images/overview-dark.webp b/docs/images/overview-dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..0fb7f5548e801b2fdfd39e975f2e10d9be908802 GIT binary patch literal 67656 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%0D?z^UF&`L`boV|woIWGd#hpQ*-eTM>$s_L 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⁡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 BUG4w? literal 0 HcmV?d00001 diff --git a/web/src/App.vue b/web/src/App.vue index 7d09a389..e84e1319 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -47,7 +47,9 @@ function lazyPanel(loader: AsyncComponentLoader) { const UsagePanel = lazyPanel(() => import('@/components/UsagePanel.vue')) const VersionPanel = lazyPanel(() => import('@/components/VersionPanel.vue')) const AccessPanel = lazyPanel(() => import('@/components/AccessPanel.vue')) +const OverviewPanel = lazyPanel(() => import('@/components/OverviewPanel.vue')) const validTabs: TabID[] = [ + 'overview', 'logs', 'accounts', 'access', @@ -57,7 +59,7 @@ const validTabs: TabID[] = [ 'settings', 'version', ] -const { tab: currentTab, select: selectTab } = useTabParam(validTabs, 'logs') +const { tab: currentTab, select: selectTab } = useTabParam(validTabs, 'overview') const router = useRouter() const route = useRoute() // currentTab 在从试用页返回时同步目标管理页面 @@ -66,7 +68,7 @@ watch( () => { if (route.name === 'console') { const tab = route.query.tab as TabID - currentTab.value = validTabs.includes(tab) ? tab : 'logs' + currentTab.value = validTabs.includes(tab) ? tab : 'overview' } }, ) @@ -185,7 +187,15 @@ watch( { flush: 'post' }, ) +// refreshOverview 同时刷新总览使用的账户、模型、请求与冷却数据 +const overviewRefreshKey = ref(0) +async function refreshOverview(): Promise { + await Promise.all([loadAccountData(), loadRequestData()]) + overviewRefreshKey.value++ +} + const navigation: { id: TabID; label: TranslationKey; icon: IconName }[] = [ + { id: 'overview', label: 'nav.overview', icon: 'overview' }, { id: 'logs', label: 'nav.logs', icon: 'logs' }, { id: 'accounts', label: 'nav.accounts', icon: 'accounts' }, { id: 'access', label: 'nav.access', icon: 'access' }, @@ -431,9 +441,23 @@ onMounted(revealCurrentTab)

- + + [...new Set(props.accounts.map((account) => account.state))]) +// stateCounts 是每种账户状态的数量,显示在状态筛选按钮上 +const stateCounts = computed(() => { + const counts = new Map() + for (const account of props.accounts) + counts.set(account.state, (counts.get(account.state) ?? 0) + 1) + return counts +}) const tiers = computed(() => [...new Set(props.accounts.map((account) => account.benefit_tier))]) const egresses = computed(() => [...new Set(props.accounts.map((account) => account.egress))]) @@ -1051,16 +1058,30 @@ async function refreshAllAccounts(): Promise { :placeholder="t('accounts.search')" class="min-w-0 flex-[1_1_12rem] rounded-md border border-line bg-canvas px-3 py-1.5 text-sm text-white focus:border-blue-500 focus:outline-none" /> - - - - + + +import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' +import { api } from '@/api' +import { useI18n, type TranslationKey } from '@/i18n' +import type { + Account, + Cooldown, + Model, + RequestSummary, + ServiceStatus, + TabID, + UsageRecord, + UsageReport, +} from '@/types' +import { groupCooldowns, pacificDayStart } from '@/overview-metrics' +import UiIcon from './UiIcon.vue' + +const props = defineProps<{ + status: ServiceStatus | null + serviceState: 'unavailable' | 'running' | 'launching' | 'stopped' + accounts: Account[] + models: Model[] + cooldowns: Cooldown[] + requests: RequestSummary[] + refreshKey: number +}>() + +const emit = defineEmits<{ + refresh: [] + select: [tab: TabID] + start: [] + stop: [] +}>() + +const { locale, t, errorText } = useI18n() + +const accountCounts = computed(() => { + if (props.status !== null) return props.status.accounts + return props.accounts.reduce( + (counts, account) => { + counts.total++ + if (account.state === 'ready') counts.ready++ + if (account.state === 'busy') counts.busy++ + if (account.state === 'cooldown') counts.cooldown++ + if (account.state === 'auth_required') counts.auth_required++ + return counts + }, + { total: 0, ready: 0, busy: 0, cooldown: 0, auth_required: 0 }, + ) +}) + +const todayReport = ref(null) +const recentRecords = ref([]) + +const requestStats = computed(() => { + const totals = todayReport.value?.totals + if (totals === undefined) return { total: 0, success: 0, failed: 0, cancelled: 0 } + return { + total: totals.requests, + success: totals.succeeded, + failed: totals.failed, + cancelled: totals.canceled, + } +}) + +const successRate = computed(() => { + if (requestStats.value.total === 0) return '—' + return `${Math.round((requestStats.value.success / requestStats.value.total) * 100)}%` +}) + +const accountRows = computed(() => + props.accounts + .slice() + .sort((left, right) => { + const order = { + auth_required: 0, + cooldown: 1, + unavailable: 2, + busy: 3, + ready: 4, + disabled: 5, + } + return ( + (order[left.state] ?? 9) - (order[right.state] ?? 9) || + left.label.localeCompare(right.label) + ) + }) + .slice(0, 8), +) + +const recentErrors = computed(() => + recentRecords.value + .filter((record) => record.state === 'failed' || record.status >= 400) + .slice(0, 5), +) + +const modelUsage = computed(() => + (todayReport.value?.groups.model ?? []) + .filter((group) => group.key !== '' && !group.key.startsWith('__') && group.requests > 0) + .slice(0, 6) + .map((group) => [group.key, group.requests] as [string, number]), +) + +const modelCooldowns = computed(() => groupCooldowns(props.cooldowns).slice(0, 6)) + +const todayUsage = computed(() => todayReport.value?.groups.account ?? []) +const todayUsageLoading = ref(false) +const todayUsageError = ref('') +let usageController: AbortController | undefined + +// loadTodayUsage 读取太平洋时区当天的账户用量与最近请求记录 +async function loadTodayUsage(): Promise { + usageController?.abort() + const current = new AbortController() + usageController = current + todayUsageLoading.value = true + try { + const now = new Date() + const from = pacificDayStart(now).toISOString() + const to = now.toISOString() + const usageQuery = new URLSearchParams({ + from, + to, + tz: 'America/Los_Angeles', + stack: 'account', + bucket: '86400', + }) + const recordQuery = new URLSearchParams({ from, to, limit: '50' }) + const [report, records] = await Promise.all([ + api.usage(usageQuery, current.signal), + api.usageRecords(recordQuery, current.signal), + ]) + todayReport.value = report + recentRecords.value = records.items + todayUsageError.value = '' + } catch (reason) { + if (current.signal.aborted) return + todayUsageError.value = errorText(reason) + } finally { + if (usageController === current) todayUsageLoading.value = false + } +} + +onMounted(loadTodayUsage) +watch( + () => props.refreshKey, + () => void loadTodayUsage(), +) +onBeforeUnmount(() => usageController?.abort()) + +// compactNumber 用当前语言把大数值压成万/百万级显示 +function compactNumber(value: number): string { + return new Intl.NumberFormat(locale.value, { + notation: 'compact', + maximumFractionDigits: 1, + }).format(value) +} + +const stateKeys: Record = { + ready: 'state.ready', + busy: 'state.busy', + cooldown: 'state.cooldown', + auth_required: 'state.auth_required', + unavailable: 'state.unavailable', + disabled: 'state.disabled', +} + +const serviceKeys: Record = { + running: 'overview.running', + launching: 'overview.launching', + stopped: 'overview.stopped', + unavailable: 'overview.unavailable', +} + +function formatTime(value?: string): string { + if (!value) return '—' + const date = new Date(value) + if (Number.isNaN(date.valueOf())) return '—' + return new Intl.DateTimeFormat(locale.value, { hour: '2-digit', minute: '2-digit' }).format(date) +} + +function accountStateClass(state: Account['state']): string { + if (state === 'ready') return 'bg-green-400' + if (state === 'busy') return 'bg-blue-400' + if (state === 'cooldown') return 'bg-amber-400' + if (state === 'auth_required' || state === 'unavailable') return 'bg-red-400' + return 'bg-gray-500' +} + +function open(tab: TabID): void { + emit('select', tab) +} + +// recordDot 按请求记录的终态给出状态点颜色 +function recordDot(state: string): string { + if (state === 'completed' || state === 'tool_calls') return 'bg-green-400' + if (state === 'failed' || state === 'blocked') return 'bg-red-400' + if (state === 'limited') return 'bg-amber-400' + return 'bg-gray-400' +} + + + diff --git a/web/src/icons.ts b/web/src/icons.ts index 0ed237d7..8caa0cc3 100644 --- a/web/src/icons.ts +++ b/web/src/icons.ts @@ -21,6 +21,7 @@ import { Inbox, KeyRound, Languages, + LayoutDashboard, ListFilter, LoaderCircle, LogIn, @@ -72,6 +73,7 @@ import { // icons 将界面语义名映射到 Lucide 图标 export const icons = { + overview: LayoutDashboard, logs: ScrollText, accounts: KeyRound, access: Cable, diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts index 06188dde..810b14ce 100644 --- a/web/src/locales/de.ts +++ b/web/src/locales/de.ts @@ -2063,4 +2063,46 @@ export const messages: Record = { 'access.ep.media': 'In einer Antwort erzeugtes Bild oder Audio abrufen', 'access.ep.live': 'Live-WebSocket-Sitzung öffnen', 'access.ep.robotics': 'Gemini-Robotics-WebSocket-Stream öffnen', + + 'nav.overview': 'Übersicht', + 'section.overview.title': 'Dienstübersicht', + 'section.overview.description': 'Konten, Modelle, Anfragezustand und aktuelle Fehler', + 'overview.service': 'Dienststatus', + 'overview.accounts': 'Konten', + 'overview.models': 'Modelle', + 'overview.activeRequests': 'Aktive Anfragen', + 'overview.ready': 'bereit', + 'overview.cooldowns': 'Abkühlungen', + 'overview.authRequired': 'Anmeldung nötig', + 'overview.running': 'Läuft', + 'overview.launching': 'Startet', + 'overview.stopped': 'Gestoppt', + 'overview.unavailable': 'Nicht verfügbar', + 'overview.refresh': 'Übersicht aktualisieren', + 'overview.accountHealth': 'Kontostatus', + 'overview.accountHealthHint': 'Nach aktuellem Routing-Status sortiert', + 'overview.openAccounts': 'Konten anzeigen', + 'overview.modelCoverage': 'Modellverkehr', + 'overview.modelCoverageHint': 'Heutige Anfragen je Modell', + 'overview.openUsage': 'Nutzung anzeigen', + 'overview.recentErrors': 'Aktuelle Fehler', + 'overview.recentErrorsHint': 'Letzte fehlgeschlagene Anfragen heute', + 'overview.noErrors': 'Keine aktuellen Fehler', + 'overview.openLogs': 'Protokolle anzeigen', + 'overview.latestRequests': 'Letzte Anfragen', + 'overview.latestRequestsHint': 'Zuletzt gespeicherte Anfragen von heute', + 'overview.openRequests': 'Anfragen anzeigen', + 'overview.noRequests': 'Keine Anfragen', + 'overview.requests': 'Anfragen', + 'overview.requestSuccess': 'Erfolgsquote', + 'overview.requestFailed': 'Anfrage fehlgeschlagen', + 'overview.modelCooldowns': 'Modell-Abkühlungen', + 'overview.modelCooldownsHint': + 'Welche Modelle abkühlen, auf wie vielen Konten und wann sie wieder verfügbar sind', + 'overview.noCooldowns': 'Keine Abkühlungen', + 'overview.cooldownAccounts': 'Konten in Abkühlung', + 'overview.recoverAt': 'Verfügbar ab', + 'overview.todayUsage': 'Kontonutzung heute', + 'overview.todayUsageHint': 'Heutige Anfragen laut Protokoll (Pazifikzeit)', + 'overview.todayTotals': '{requests} Anfragen · {tokens} Tokens heute', } diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 2c6f458a..3978cdf9 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -2013,4 +2013,46 @@ export const messages: Record = { 'access.ep.media': 'Fetch an image or audio file generated in a response', 'access.ep.live': 'Open a Live WebSocket session', 'access.ep.robotics': 'Open a Gemini Robotics WebSocket stream', + + 'nav.overview': 'Overview', + 'section.overview.title': 'Service overview', + 'section.overview.description': 'Account pool, model coverage, request health and recent errors', + 'overview.service': 'Service', + 'overview.accounts': 'Accounts', + 'overview.models': 'Models', + 'overview.activeRequests': 'Active requests', + 'overview.ready': 'ready', + 'overview.cooldowns': 'cooldowns', + 'overview.authRequired': 'need sign-in', + 'overview.running': 'Running', + 'overview.launching': 'Starting', + 'overview.stopped': 'Stopped', + 'overview.unavailable': 'Unavailable', + 'overview.refresh': 'Refresh overview', + 'overview.accountHealth': 'Account health', + 'overview.accountHealthHint': 'Sorted by current dispatch status', + 'overview.openAccounts': 'View accounts', + 'overview.modelCoverage': 'Model traffic', + 'overview.modelCoverageHint': 'Requests per model today', + 'overview.openUsage': 'View usage', + 'overview.recentErrors': 'Recent errors', + 'overview.recentErrorsHint': 'Recent failed requests today', + 'overview.noErrors': 'No recent errors', + 'overview.openLogs': 'View logs', + 'overview.latestRequests': 'Latest requests', + 'overview.latestRequestsHint': "Today's latest persisted request records", + 'overview.openRequests': 'View requests', + 'overview.noRequests': 'No requests', + 'overview.requests': 'requests', + 'overview.requestSuccess': 'success rate', + 'overview.requestFailed': 'Request failed', + 'overview.modelCooldowns': 'Model cooldowns', + 'overview.modelCooldownsHint': + 'Which models are cooling down, on how many accounts, and when they recover', + 'overview.noCooldowns': 'No cooldowns', + 'overview.cooldownAccounts': 'accounts cooling down', + 'overview.recoverAt': 'Recovers', + 'overview.todayUsage': 'Account usage today', + 'overview.todayUsageHint': 'Today in Pacific time, from the request ledger', + 'overview.todayTotals': '{requests} requests · {tokens} tokens today', } diff --git a/web/src/locales/fr.ts b/web/src/locales/fr.ts index 1f21683e..be82e648 100644 --- a/web/src/locales/fr.ts +++ b/web/src/locales/fr.ts @@ -2071,4 +2071,46 @@ export const messages: Record = { 'access.ep.media': 'Récupérer une image ou un audio généré dans une réponse', 'access.ep.live': 'Ouvrir une session WebSocket Live', 'access.ep.robotics': 'Ouvrir un flux WebSocket Gemini Robotics', + + 'nav.overview': 'Vue d’ensemble', + 'section.overview.title': 'Vue d’ensemble du service', + 'section.overview.description': 'Comptes, modèles, santé des requêtes et erreurs récentes', + 'overview.service': 'Service', + 'overview.accounts': 'Comptes', + 'overview.models': 'Modèles', + 'overview.activeRequests': 'Requêtes actives', + 'overview.ready': 'disponibles', + 'overview.cooldowns': 'en refroidissement', + 'overview.authRequired': 'connexion requise', + 'overview.running': 'En cours', + 'overview.launching': 'Démarrage', + 'overview.stopped': 'Arrêté', + 'overview.unavailable': 'Indisponible', + 'overview.refresh': 'Actualiser', + 'overview.accountHealth': 'Santé des comptes', + 'overview.accountHealthHint': 'Triés selon l’état de routage actuel', + 'overview.openAccounts': 'Voir les comptes', + 'overview.modelCoverage': 'Trafic des modèles', + 'overview.modelCoverageHint': 'Requêtes par modèle aujourd’hui', + 'overview.openUsage': 'Voir l’utilisation', + 'overview.recentErrors': 'Erreurs récentes', + 'overview.recentErrorsHint': 'Dernières requêtes échouées du jour', + 'overview.noErrors': 'Aucune erreur récente', + 'overview.openLogs': 'Voir les journaux', + 'overview.latestRequests': 'Dernières requêtes', + 'overview.latestRequestsHint': 'Dernières requêtes enregistrées du jour', + 'overview.openRequests': 'Voir les requêtes', + 'overview.noRequests': 'Aucune requête', + 'overview.requests': 'requêtes', + 'overview.requestSuccess': 'taux de réussite', + 'overview.requestFailed': 'Échec de la requête', + 'overview.modelCooldowns': 'Modèles en refroidissement', + 'overview.modelCooldownsHint': + 'Modèles en refroidissement, nombre de comptes et reprise la plus proche', + 'overview.noCooldowns': 'Aucun refroidissement', + 'overview.cooldownAccounts': 'comptes en refroidissement', + 'overview.recoverAt': 'Reprise', + 'overview.todayUsage': 'Utilisation du jour par compte', + 'overview.todayUsageHint': 'Requêtes du jour, selon le registre (heure du Pacifique)', + 'overview.todayTotals': '{requests} requêtes · {tokens} tokens aujourd’hui', } diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts index 5ce11170..3d6915f4 100644 --- a/web/src/locales/ja.ts +++ b/web/src/locales/ja.ts @@ -2006,4 +2006,45 @@ export const messages: Record = { 'access.ep.media': '回答で生成された画像や音声を取得', 'access.ep.live': 'Live のリアルタイム会話の WebSocket を開く', 'access.ep.robotics': 'Gemini Robotics の WebSocket ストリームを開く', + + 'nav.overview': '概要', + 'section.overview.title': 'サービス概要', + 'section.overview.description': 'アカウント、モデル、リクエスト状態、最近のエラー', + 'overview.service': 'サービス状態', + 'overview.accounts': 'アカウント', + 'overview.models': 'モデル', + 'overview.activeRequests': '処理中のリクエスト', + 'overview.ready': '利用可能', + 'overview.cooldowns': 'クールダウン', + 'overview.authRequired': '再ログインが必要', + 'overview.running': '実行中', + 'overview.launching': '起動中', + 'overview.stopped': '停止', + 'overview.unavailable': '利用不可', + 'overview.refresh': '概要を更新', + 'overview.accountHealth': 'アカウント状態', + 'overview.accountHealthHint': '現在の配信状態順', + 'overview.openAccounts': 'アカウントを表示', + 'overview.modelCoverage': 'モデル利用状況', + 'overview.modelCoverageHint': '本日のモデル別リクエスト数', + 'overview.openUsage': '使用量を表示', + 'overview.recentErrors': '最近のエラー', + 'overview.recentErrorsHint': '本日の最近の失敗リクエスト', + 'overview.noErrors': 'エラーはありません', + 'overview.openLogs': 'ログを表示', + 'overview.latestRequests': '最近のリクエスト', + 'overview.latestRequestsHint': '本日の最新の保存済みリクエスト', + 'overview.openRequests': 'リクエストを表示', + 'overview.noRequests': 'リクエストはありません', + 'overview.requests': '件', + 'overview.requestSuccess': '成功率', + 'overview.requestFailed': 'リクエスト失敗', + 'overview.modelCooldowns': 'モデルのクールダウン', + 'overview.modelCooldownsHint': 'クールダウン中のモデル、アカウント数、最短の復帰時刻', + 'overview.noCooldowns': 'クールダウンなし', + 'overview.cooldownAccounts': 'アカウントがクールダウン中', + 'overview.recoverAt': '復帰', + 'overview.todayUsage': 'アカウントの本日使用量', + 'overview.todayUsageHint': '太平洋時間の本日のリクエスト台帳から集計', + 'overview.todayTotals': '本日 {requests} 件 · {tokens} tokens', } diff --git a/web/src/locales/ko.ts b/web/src/locales/ko.ts index d9b72d8e..283f47f3 100644 --- a/web/src/locales/ko.ts +++ b/web/src/locales/ko.ts @@ -1977,4 +1977,45 @@ export const messages: Record = { 'access.ep.media': '응답에서 생성된 이미지나 오디오 가져오기', 'access.ep.live': 'Live 실시간 대화 WebSocket 열기', 'access.ep.robotics': 'Gemini Robotics WebSocket 스트림 열기', + + 'nav.overview': '개요', + 'section.overview.title': '서비스 개요', + 'section.overview.description': '계정 풀, 모델 범위, 요청 상태 및 최근 오류', + 'overview.service': '서비스 상태', + 'overview.accounts': '계정', + 'overview.models': '모델', + 'overview.activeRequests': '진행 중인 요청', + 'overview.ready': '사용 가능', + 'overview.cooldowns': '쿨다운', + 'overview.authRequired': '재로그인 필요', + 'overview.running': '실행 중', + 'overview.launching': '시작 중', + 'overview.stopped': '중지됨', + 'overview.unavailable': '사용 불가', + 'overview.refresh': '개요 새로고침', + 'overview.accountHealth': '계정 상태', + 'overview.accountHealthHint': '현재 디스패치 상태순', + 'overview.openAccounts': '계정 보기', + 'overview.modelCoverage': '모델 사용량', + 'overview.modelCoverageHint': '오늘 모델별 요청 수', + 'overview.openUsage': '사용량 보기', + 'overview.recentErrors': '최근 오류', + 'overview.recentErrorsHint': '오늘 최근 실패한 요청', + 'overview.noErrors': '최근 오류 없음', + 'overview.openLogs': '로그 보기', + 'overview.latestRequests': '최근 요청', + 'overview.latestRequestsHint': '오늘 최근 저장된 요청 기록', + 'overview.openRequests': '요청 보기', + 'overview.noRequests': '요청 없음', + 'overview.requests': '개', + 'overview.requestSuccess': '성공률', + 'overview.requestFailed': '요청 실패', + 'overview.modelCooldowns': '모델 쿨다운', + 'overview.modelCooldownsHint': '쿨다운 중인 모델, 계정 수, 가장 빠른 복구 시각', + 'overview.noCooldowns': '쿨다운 없음', + 'overview.cooldownAccounts': '개 계정 쿨다운 중', + 'overview.recoverAt': '복구', + 'overview.todayUsage': '계정 오늘 사용량', + 'overview.todayUsageHint': '태평양 시간 기준 오늘의 요청 기록에서 집계', + 'overview.todayTotals': '오늘 {requests}건 · {tokens} tokens', } diff --git a/web/src/locales/zh-CN.ts b/web/src/locales/zh-CN.ts index 6060ad31..e6207b05 100644 --- a/web/src/locales/zh-CN.ts +++ b/web/src/locales/zh-CN.ts @@ -1873,6 +1873,47 @@ export const messages = { 'access.ep.media': '读取回答中生成的图片或音频', 'access.ep.live': '打开 Live 实时对话的 WebSocket', 'access.ep.robotics': '打开 Gemini Robotics 的 WebSocket 流', + + 'nav.overview': '总览', + 'section.overview.title': '服务总览', + 'section.overview.description': '账号池、模型目录、请求健康与最近错误', + 'overview.service': '服务状态', + 'overview.accounts': '账号池', + 'overview.models': '模型', + 'overview.activeRequests': '进行中请求', + 'overview.ready': '可用', + 'overview.cooldowns': '冷却中', + 'overview.authRequired': '个待重新登录', + 'overview.running': '运行中', + 'overview.launching': '启动中', + 'overview.stopped': '已停止', + 'overview.unavailable': '不可用', + 'overview.refresh': '刷新总览', + 'overview.accountHealth': '账号健康', + 'overview.accountHealthHint': '按当前可调度状态排序', + 'overview.openAccounts': '查看账号', + 'overview.modelCoverage': '模型调用分布', + 'overview.modelCoverageHint': '当天各模型的请求数', + 'overview.openUsage': '查看用量', + 'overview.recentErrors': '最近错误', + 'overview.recentErrorsHint': '当天最近的失败请求', + 'overview.noErrors': '暂无错误', + 'overview.openLogs': '查看日志', + 'overview.latestRequests': '最近请求', + 'overview.latestRequestsHint': '当天最近的持久化请求记录', + 'overview.openRequests': '查看请求', + 'overview.noRequests': '暂无请求', + 'overview.requests': '次请求', + 'overview.requestSuccess': '成功率', + 'overview.requestFailed': '请求失败', + 'overview.modelCooldowns': '模型冷却池', + 'overview.modelCooldownsHint': '哪些模型在冷却、多少账号、最早何时恢复', + 'overview.noCooldowns': '暂无冷却', + 'overview.cooldownAccounts': '个账号冷却中', + 'overview.recoverAt': '最早恢复', + 'overview.todayUsage': '账号今日用量', + 'overview.todayUsageHint': '按太平洋时区当天的请求账本统计', + 'overview.todayTotals': '今日 {requests} 次 · {tokens} tokens', } as const export type TranslationKey = keyof typeof messages diff --git a/web/src/locales/zh-TW.ts b/web/src/locales/zh-TW.ts index 9a9e79a2..fa96d848 100644 --- a/web/src/locales/zh-TW.ts +++ b/web/src/locales/zh-TW.ts @@ -1880,4 +1880,45 @@ export const messages: Record = { 'access.ep.media': '讀取回答中產生的圖片或音訊', 'access.ep.live': '開啟 Live 即時對話的 WebSocket', 'access.ep.robotics': '開啟 Gemini Robotics 的 WebSocket 串流', + + 'nav.overview': '總覽', + 'section.overview.title': '服務總覽', + 'section.overview.description': '帳號池、模型目錄、請求健康與最近錯誤', + 'overview.service': '服務狀態', + 'overview.accounts': '帳號池', + 'overview.models': '模型', + 'overview.activeRequests': '進行中請求', + 'overview.ready': '可用', + 'overview.cooldowns': '冷卻中', + 'overview.authRequired': '個待重新登入', + 'overview.running': '執行中', + 'overview.launching': '啟動中', + 'overview.stopped': '已停止', + 'overview.unavailable': '不可用', + 'overview.refresh': '重新整理總覽', + 'overview.accountHealth': '帳號健康', + 'overview.accountHealthHint': '依目前可調度狀態排序', + 'overview.openAccounts': '查看帳號', + 'overview.modelCoverage': '模型呼叫分布', + 'overview.modelCoverageHint': '當天各模型的請求數', + 'overview.openUsage': '查看用量', + 'overview.recentErrors': '最近錯誤', + 'overview.recentErrorsHint': '當天最近的失敗請求', + 'overview.noErrors': '暫無錯誤', + 'overview.openLogs': '查看日誌', + 'overview.latestRequests': '最近請求', + 'overview.latestRequestsHint': '當天最近的持久化請求記錄', + 'overview.openRequests': '查看請求', + 'overview.noRequests': '暫無請求', + 'overview.requests': '次請求', + 'overview.requestSuccess': '成功率', + 'overview.requestFailed': '請求失敗', + 'overview.modelCooldowns': '模型冷卻池', + 'overview.modelCooldownsHint': '哪些模型在冷卻、多少帳號、最早何時恢復', + 'overview.noCooldowns': '暫無冷卻', + 'overview.cooldownAccounts': '個帳號冷卻中', + 'overview.recoverAt': '最早恢復', + 'overview.todayUsage': '帳號今日用量', + 'overview.todayUsageHint': '依太平洋時區當天的請求帳本統計', + 'overview.todayTotals': '今日 {requests} 次 · {tokens} tokens', } diff --git a/web/src/overview-metrics.test.ts b/web/src/overview-metrics.test.ts new file mode 100644 index 00000000..8d6ae3c0 --- /dev/null +++ b/web/src/overview-metrics.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vite-plus/test' +import { groupCooldowns, pacificDayStart } from './overview-metrics' + +describe('overview metrics', () => { + it('groups distinct cooling accounts and their earliest recovery by model', () => { + expect( + groupCooldowns([ + { account_id: 'a', model_id: 'argon', until: '2026-11-02T08:00:00Z' }, + { account_id: 'b', model_id: 'argon', until: '2026-11-02T07:00:00Z' }, + { account_id: 'b', model_id: 'argon', until: '2026-11-02T09:00:00Z' }, + { account_id: 'a', model_id: '', until: '2026-11-02T06:00:00Z' }, + ]), + ).toEqual([ + { model: 'argon', accounts: 2, until: '2026-11-02T07:00:00Z' }, + { model: '*', accounts: 1, until: '2026-11-02T06:00:00Z' }, + ]) + expect(groupCooldowns([])).toEqual([]) + }) + + it.each([ + ['2026-11-01T06:59:59Z', '2026-10-31T07:00:00.000Z'], + ['2026-11-01T07:00:00Z', '2026-11-01T07:00:00.000Z'], + ['2026-11-01T20:00:00Z', '2026-11-01T07:00:00.000Z'], + ['2026-11-02T20:00:00Z', '2026-11-02T08:00:00.000Z'], + ['2026-03-08T07:59:59Z', '2026-03-07T08:00:00.000Z'], + ['2026-03-08T20:00:00Z', '2026-03-08T08:00:00.000Z'], + ['2026-03-09T20:00:00Z', '2026-03-09T07:00:00.000Z'], + ])('uses the Pacific midnight offset for %s', (now, expected) => { + expect(pacificDayStart(new Date(now)).toISOString()).toBe(expected) + }) +}) diff --git a/web/src/overview-metrics.ts b/web/src/overview-metrics.ts new file mode 100644 index 00000000..5d98a1f9 --- /dev/null +++ b/web/src/overview-metrics.ts @@ -0,0 +1,63 @@ +export interface CooldownMetric { + account_id: string + model_id: string + until: string +} + +export interface ModelCooldown { + model: string + accounts: number + until: string +} + +// groupCooldowns 按模型汇总冷却中的账号数与最早恢复时间,冷却账号多的模型排前面 +export function groupCooldowns(cooldowns: CooldownMetric[]): ModelCooldown[] { + const groups = new Map; until: string }>() + for (const cooldown of cooldowns) { + const model = cooldown.model_id || '*' + const group = groups.get(model) ?? { accounts: new Set(), until: cooldown.until } + group.accounts.add(cooldown.account_id) + if (Date.parse(cooldown.until) < Date.parse(group.until)) group.until = cooldown.until + groups.set(model, group) + } + return [...groups.entries()] + .map(([model, group]) => ({ model, accounts: group.accounts.size, until: group.until })) + .sort((left, right) => right.accounts - left.accounts || left.model.localeCompare(right.model)) +} + +// pacificDayStart 返回给定时刻所在的太平洋时区当天零点 +export function pacificDayStart(now: Date): Date { + const parts = new Intl.DateTimeFormat('en-CA', { + timeZone: 'America/Los_Angeles', + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).formatToParts(now) + const value = (type: string) => Number(parts.find((part) => part.type === type)?.value ?? 0) + const midnightUTC = Date.UTC(value('year'), value('month') - 1, value('day')) + return new Date(midnightUTC - zoneOffset(new Date(midnightUTC), 'America/Los_Angeles')) +} + +// zoneOffset 返回指定时刻在该时区相对 UTC 的毫秒偏移 +function zoneOffset(date: Date, timeZone: string): number { + const parts = new Intl.DateTimeFormat('en-US', { + timeZone, + hour12: false, + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }).formatToParts(date) + const value = (type: string) => Number(parts.find((part) => part.type === type)?.value ?? 0) + const asUTC = Date.UTC( + value('year'), + value('month') - 1, + value('day'), + value('hour') % 24, + value('minute'), + value('second'), + ) + return asUTC - date.getTime() +} diff --git a/web/src/types.ts b/web/src/types.ts index 360da0ad..f6bffc75 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -1,6 +1,7 @@ export type Locale = 'zh-CN' | 'zh-TW' | 'en' | 'ja' | 'ko' | 'fr' | 'de' export type TabID = + | 'overview' | 'logs' | 'accounts' | 'access'