diff --git a/public/app.js b/public/app.js index 013816c..e240dd1 100644 --- a/public/app.js +++ b/public/app.js @@ -9,6 +9,7 @@ document.addEventListener('alpine:init', () => { // Register Components (loaded from separate files via window.Components) Alpine.data('dashboard', window.Components.dashboard); + Alpine.data('models', window.Components.models); Alpine.data('accountManager', window.Components.accountManager); Alpine.data('claudeConfig', window.Components.claudeConfig); Alpine.data('logsViewer', window.Components.logsViewer); diff --git a/public/index.html b/public/index.html index e6c65c1..12d3d50 100644 --- a/public/index.html +++ b/public/index.html @@ -114,8 +114,10 @@ class="w-8 h-8 rounded bg-gradient-to-br from-neon-purple to-blue-600 flex items-center justify-center text-white font-bold shadow-[0_0_15px_rgba(168,85,247,0.4)]"> AG
- ANTIGRAVITY - CLAUDE PROXY SYSTEM + ANTIGRAVITY + CLAUDE PROXY SYSTEM
@@ -151,7 +153,8 @@
-
Main
+
Main
-
System
+
System
+ +
+
@@ -241,21 +260,24 @@

Add New Account

-

Connect a Google Workspace account to increase your API quota limit. +

Connect a Google + Workspace account to increase your API quota limit. The account will be used to proxy Claude requests via Antigravity.

- @@ -264,7 +286,8 @@
-
+
Use CLI Command
@@ -295,6 +318,7 @@ + diff --git a/public/js/components/dashboard.js b/public/js/components/dashboard.js index 6f71e5d..3ef00df 100644 --- a/public/js/components/dashboard.js +++ b/public/js/components/dashboard.js @@ -439,7 +439,7 @@ window.Components.dashboard = () => ({ padding: 10, displayColors: true, callbacks: { - label: function(context) { + label: function (context) { return context.dataset.label + ': ' + context.parsed.y; } } @@ -531,7 +531,8 @@ window.Components.dashboard = () => ({ this.charts.quotaDistribution.destroy(); } - const rows = Alpine.store('data').quotaRows; + // Use UNFILTERED data for global health chart + const rows = Alpine.store('data').getUnfilteredQuotaData(); // Dynamic family aggregation (supports any model family) const familyStats = {}; @@ -580,12 +581,12 @@ window.Components.dashboard = () => ({ // Labels using translations if possible const activeLabel = family === 'claude' ? store.t('claudeActive') : - family === 'gemini' ? store.t('geminiActive') : - `${familyName} ${store.t('activeSuffix')}`; + family === 'gemini' ? store.t('geminiActive') : + `${familyName} ${store.t('activeSuffix')}`; const depletedLabel = family === 'claude' ? store.t('claudeEmpty') : - family === 'gemini' ? store.t('geminiEmpty') : - `${familyName} ${store.t('depleted')}`; + family === 'gemini' ? store.t('geminiEmpty') : + `${familyName} ${store.t('depleted')}`; // Active segment data.push(activeVal); diff --git a/public/js/components/model-manager.js b/public/js/components/model-manager.js index 69554f4..3ec866a 100644 --- a/public/js/components/model-manager.js +++ b/public/js/components/model-manager.js @@ -6,10 +6,36 @@ window.Components = window.Components || {}; window.Components.modelManager = () => ({ + // Track which model is currently being edited (null = none) + editingModelId: null, + init() { // Component is ready }, + /** + * Start editing a model's mapping + * @param {string} modelId - The model to edit + */ + startEditing(modelId) { + this.editingModelId = modelId; + }, + + /** + * Stop editing + */ + stopEditing() { + this.editingModelId = null; + }, + + /** + * Check if a model is being edited + * @param {string} modelId - The model to check + */ + isEditing(modelId) { + return this.editingModelId === modelId; + }, + /** * Update model configuration with authentication * @param {string} modelId - The model ID to update diff --git a/public/js/components/models.js b/public/js/components/models.js new file mode 100644 index 0000000..84d4581 --- /dev/null +++ b/public/js/components/models.js @@ -0,0 +1,58 @@ +/** + * Models Component + * Displays model quota/status list + * Registers itself to window.Components for Alpine.js to consume + */ +window.Components = window.Components || {}; + +window.Components.models = () => ({ + init() { + // Ensure data is fetched when this tab becomes active + this.$watch('$store.global.activeTab', (val) => { + if (val === 'models') { + // Trigger recompute to ensure filters are applied + this.$nextTick(() => { + Alpine.store('data').computeQuotaRows(); + }); + } + }); + + // Initial compute if already on models tab + if (this.$store.global.activeTab === 'models') { + this.$nextTick(() => { + Alpine.store('data').computeQuotaRows(); + }); + } + }, + + /** + * Update model configuration (Pin/Hide quick actions) + * @param {string} modelId - The model ID to update + * @param {object} configUpdates - Configuration updates (pinned, hidden) + */ + async updateModelConfig(modelId, configUpdates) { + const store = Alpine.store('global'); + try { + const { response, newPassword } = await window.utils.request('/api/models/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ modelId, config: configUpdates }) + }, store.webuiPassword); + + if (newPassword) store.webuiPassword = newPassword; + + if (!response.ok) { + throw new Error('Failed to update model config'); + } + + // Optimistic update + Alpine.store('data').modelConfig[modelId] = { + ...Alpine.store('data').modelConfig[modelId], + ...configUpdates + }; + Alpine.store('data').computeQuotaRows(); + } catch (e) { + store.showToast('Failed to update: ' + e.message, 'error'); + } + } +}); diff --git a/public/js/data-store.js b/public/js/data-store.js index 867c937..36414de 100644 --- a/public/js/data-store.js +++ b/public/js/data-store.js @@ -69,34 +69,32 @@ document.addEventListener('alpine:init', () => { computeQuotaRows() { const models = this.models || []; const rows = []; - const showExhausted = Alpine.store('settings')?.showExhausted ?? true; // Need settings store - // Temporary debug flag or settings flag to show hidden models - const showHidden = Alpine.store('settings')?.showHiddenModels ?? false; + const showExhausted = Alpine.store('settings')?.showExhausted ?? true; models.forEach(modelId => { // Config const config = this.modelConfig[modelId] || {}; const family = this.getModelFamily(modelId); - // Smart Visibility Logic: - // 1. If explicit config exists, use it. - // 2. If no config, default 'unknown' families to HIDDEN to prevent clutter. - // 3. Known families (Claude/Gemini) default to VISIBLE. + // Visibility Logic for Models Tab (quotaRows): + // 1. If explicitly hidden via config, always hide + // 2. If no config, default 'unknown' families to HIDDEN + // 3. Known families (Claude/Gemini) default to VISIBLE + // Note: showHiddenModels toggle is for Settings page only, NOT here let isHidden = config.hidden; if (isHidden === undefined) { isHidden = (family === 'other' || family === 'unknown'); } - // Skip hidden models unless "Show Hidden" is enabled - if (isHidden && !showHidden) return; + // Models Tab: ALWAYS hide hidden models (no toggle check) + if (isHidden) return; // Filters if (this.filters.family !== 'all' && this.filters.family !== family) return; if (this.filters.search) { const searchLower = this.filters.search.toLowerCase(); - const aliasMatch = config.alias && config.alias.toLowerCase().includes(searchLower); const idMatch = modelId.toLowerCase().includes(searchLower); - if (!aliasMatch && !idMatch) return; + if (!idMatch) return; } // Data Collection @@ -138,7 +136,7 @@ document.addEventListener('alpine:init', () => { rows.push({ modelId, - displayName: config.alias || modelId, // Use alias if available + displayName: modelId, // Simplified: no longer using alias family, minQuota, avgQuota, // Added Average Quota @@ -165,6 +163,43 @@ document.addEventListener('alpine:init', () => { if (lower.includes('claude')) return 'claude'; if (lower.includes('gemini')) return 'gemini'; return 'other'; + }, + + /** + * Get quota data without filters applied (for Dashboard global charts) + * Returns array of { modelId, family, quotaInfo: [{pct}] } + */ + getUnfilteredQuotaData() { + const models = this.models || []; + const rows = []; + const showHidden = Alpine.store('settings')?.showHiddenModels ?? false; + + models.forEach(modelId => { + const config = this.modelConfig[modelId] || {}; + const family = this.getModelFamily(modelId); + + // Smart visibility (same logic as computeQuotaRows) + let isHidden = config.hidden; + if (isHidden === undefined) { + isHidden = (family === 'other' || family === 'unknown'); + } + if (isHidden && !showHidden) return; + + const quotaInfo = []; + // Use ALL accounts (no account filter) + this.accounts.forEach(acc => { + const limit = acc.limits?.[modelId]; + if (!limit) return; + const pct = limit.remainingFraction !== null ? Math.round(limit.remainingFraction * 100) : 0; + quotaInfo.push({ pct }); + }); + + if (quotaInfo.length === 0) return; + + rows.push({ modelId, family, quotaInfo }); + }); + + return rows; } }); }); diff --git a/public/js/store.js b/public/js/store.js index 81f008c..24a99a1 100644 --- a/public/js/store.js +++ b/public/js/store.js @@ -15,6 +15,7 @@ document.addEventListener('alpine:init', () => { translations: { en: { dashboard: "Dashboard", + models: "Models", accounts: "Accounts", logs: "Logs", settings: "Settings", @@ -77,14 +78,16 @@ document.addEventListener('alpine:init', () => { noSignal: "NO SIGNAL DETECTED", establishingUplink: "ESTABLISHING UPLINK...", // Settings - Models - modelsDesc: "Manage visibility and ordering of models in the dashboard.", + modelsDesc: "Configure model visibility, pinning, and request redirection.", + modelsPageDesc: "Real-time quota and status for all available models.", showHidden: "Show Hidden Models", modelId: "Model ID", - alias: "Alias", actions: "Actions", pinToTop: "Pin to top", toggleVisibility: "Toggle Visibility", noModels: "NO MODELS DETECTED", + modelMappingHint: "Server-side model redirection. Claude Code users: see 'Claude CLI' tab for easier setup.", + modelMapping: "Mapping (Target Model)", // Settings - Claude proxyConnection: "Proxy Connection", modelSelection: "Model Selection", @@ -219,6 +222,7 @@ document.addEventListener('alpine:init', () => { }, zh: { dashboard: "仪表盘", + models: "模型列表", accounts: "账号管理", logs: "运行日志", settings: "系统设置", @@ -282,14 +286,16 @@ document.addEventListener('alpine:init', () => { noSignal: "无信号连接", establishingUplink: "正在建立上行链路...", // Settings - Models - modelsDesc: "管理仪表盘中模型的可见性和排序。", + modelsDesc: "配置模型的可见性、置顶和请求重定向。", + modelsPageDesc: "所有可用模型的实时配额和状态。", showHidden: "显示隐藏模型", modelId: "模型 ID", - alias: "别名", actions: "操作", pinToTop: "置顶", toggleVisibility: "切换可见性", noModels: "未检测到模型", + modelMappingHint: "服务端模型重定向功能。Claude Code 用户请使用 'Claude CLI' 标签页以便捷配置。", + modelMapping: "映射 (目标模型)", // Settings - Claude proxyConnection: "代理连接", modelSelection: "模型选择", diff --git a/public/views/dashboard.html b/public/views/dashboard.html index 24601ff..6522062 100644 --- a/public/views/dashboard.html +++ b/public/views/dashboard.html @@ -5,8 +5,7 @@ class="stat bg-space-900/40 border border-space-border/30 rounded-lg p-4 hover:border-space-border/60 transition-colors group relative">
- + @@ -16,15 +15,15 @@
-
Registered +
+ Registered Nodes
- + @@ -32,14 +31,14 @@
-
+
+
- + @@ -72,14 +71,16 @@
-
+
Claude
-
+
Gemini
@@ -157,8 +158,9 @@ @@ -249,7 +253,8 @@ @@ -288,140 +293,4 @@
- - -
-
- -
- -
- - - -
-
- - -
- - - -
-
- - -
-
- - - -
- -
-
- - -
- - - - - - - - - - - - - - - - - - - - - -
StatModel IdentityGlobal QuotaNext ResetAccount - Distribution
-
- - ESTABLISHING - UPLINK... -
-
- NO SIGNAL DETECTED -
-
\ No newline at end of file diff --git a/public/views/models.html b/public/views/models.html new file mode 100644 index 0000000..9bfd4e4 --- /dev/null +++ b/public/views/models.html @@ -0,0 +1,193 @@ +
+ +
+
+

Models

+

Real-time quota and status for + all available models.

+
+
+ + +
+
+ +
+ +
+ + + +
+
+ + +
+ + + +
+
+ + +
+
+ + + +
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + +
StatModel IdentityGlobal + QuotaNext Reset + Account + DistributionActions +
+
+ + ESTABLISHING + UPLINK... +
+
+ NO SIGNAL DETECTED +
+
+
\ No newline at end of file diff --git a/public/views/settings.html b/public/views/settings.html index 723cf1d..b534e17 100644 --- a/public/views/settings.html +++ b/public/views/settings.html @@ -5,379 +5,443 @@
-

- - - - - System Configuration -

-
- -
- - - - -
-
- - -
- - -
-
- -
- -
- - -
-
- - -
- - -
- 10s - 300s -
-
- - -
- - -
- 500 - 5000 -
-
+

+ + + + + System Configuration +

-
- -
-
-
-
- Show Exhausted Models - Display models even if they have 0% remaining quota. -
- -
-
- -
-
-
- Compact Mode - Reduce padding in tables for higher information density. -
- -
-
-
-
- - -
-
- - Settings below directly modify ~/.claude/settings.json. Restart Claude CLI to apply. -
- - -
- -
-
-
ANTHROPIC_BASE_URL
- -
-
-
ANTHROPIC_AUTH_TOKEN
- -
-
-
- - -
- - -
- -
- -
- -
- - -
- ANTHROPIC_MODEL -
- - -
- -
- -
- - -
- CLAUDE_CODE_SUBAGENT_MODEL -
-
- -
ALIAS OVERRIDES
- - -
- -
- -
- -
- -
-
- -
- -
- -
- -
-
- -
- -
- -
- -
-
-
-
- -
- + + +
- -
-
-
Manage visibility and ordering of models in the dashboard.
-
- Show Hidden Models - + +
+ + +
+
+ +
+ +
+ + +
+
+ + +
+ + +
+ 10s + 300s +
+
+ + +
+ + +
+ 500 + 5000 +
+
+
+ +
+ +
+
+
+
+ Show Exhausted Models + Display models even if they have 0% + remaining quota. +
+ +
+
+ +
+
+
+ Compact Mode + Reduce + padding in tables for higher information density. +
+ +
+
- -
- - - - - - - - - - - - - - - -
Model IDAliasMapping (Target Model ID)Actions
- NO MODELS DETECTED -
-
-
- - -
- -
-
-
-
- - - -
-
-

WebUI Password

-

Authentication for accessing this dashboard

-
-
- + +
- -
-
- Common Settings -
-
- - -
+ +
+ +
-
- Debug Mode - Detailed logging in the Logs tab -
- -
-
- - -
-
-
- Persist Token Cache - Save OAuth tokens to disk for faster restarts -
- -
-
-
- - -
-
-
-
- - - -
-
- Advanced Tuning - Experienced users only -
-
- - - -
- -
-
- - -
-
- Network Retry Settings -
- -
- - -
- -
-
- - +
+
+ + +
-
- - +
+

WebUI Password

+

+ Authentication for accessing this dashboard

-
- - -
-
- Rate Limiting & Timeouts -
- -
- - -
- -
- - -

Maximum time to wait for a sticky account to reset before switching.

-
-
-
-
- - -
- - - - All changes are saved automatically. Some settings may require server restart. -
- - -
-
-

Change WebUI Password

- -
-
- - -
- -
- - -
- -
- - -
-
- -
- -
-
-
-
-
\ No newline at end of file + +
+
+ Common Settings +
+
+ + +
+
+
+ Debug Mode + Detailed + logging in the Logs tab +
+ +
+
+ + +
+
+
+ Persist Token Cache + Save OAuth tokens to disk for faster + restarts +
+ +
+
+
+ + +
+
+
+
+ + + +
+
+ Advanced Tuning + Experienced users only +
+
+ + + +
+ +
+
+ + +
+
+ Network Retry Settings +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ Rate Limiting & Timeouts +
+ +
+ + +
+ +
+ + +

Maximum time to wait for a sticky account to + reset before switching.

+
+
+
+
+ + +
+ + + + All + changes are saved automatically. Some settings may require server restart. +
+ + +
+
+

Change + WebUI Password

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+
+
+ +
+
\ No newline at end of file