Refactor dashboard layout, quota logic, and connection status pill
This commit is contained in:
@@ -178,13 +178,17 @@ window.DashboardCharts.updateCharts = function (component) {
|
||||
// Calculate average health from quotaInfo (each entry has { pct })
|
||||
// Health = average of all account quotas for this model
|
||||
const quotaInfo = row.quotaInfo || [];
|
||||
let avgHealth = 0;
|
||||
|
||||
if (quotaInfo.length > 0) {
|
||||
const avgHealth = quotaInfo.reduce((sum, q) => sum + (q.pct || 0), 0) / quotaInfo.length;
|
||||
healthByFamily[family].total++;
|
||||
healthByFamily[family].weighted += avgHealth;
|
||||
totalHealthSum += avgHealth;
|
||||
totalModelCount++;
|
||||
avgHealth = quotaInfo.reduce((sum, q) => sum + (q.pct || 0), 0) / quotaInfo.length;
|
||||
}
|
||||
// If quotaInfo is empty, avgHealth remains 0 (depleted/unknown)
|
||||
|
||||
healthByFamily[family].total++;
|
||||
healthByFamily[family].weighted += avgHealth;
|
||||
totalHealthSum += avgHealth;
|
||||
totalModelCount++;
|
||||
});
|
||||
|
||||
// Update overall health for dashboard display
|
||||
@@ -193,9 +197,9 @@ window.DashboardCharts.updateCharts = function (component) {
|
||||
: 0;
|
||||
|
||||
const familyColors = {
|
||||
claude: getThemeColor("--color-neon-purple"),
|
||||
gemini: getThemeColor("--color-neon-green"),
|
||||
unknown: getThemeColor("--color-neon-cyan"),
|
||||
claude: getThemeColor("--color-neon-purple") || "#a855f7",
|
||||
gemini: getThemeColor("--color-neon-green") || "#22c55e",
|
||||
unknown: getThemeColor("--color-neon-cyan") || "#06b6d4",
|
||||
};
|
||||
|
||||
const data = [];
|
||||
@@ -240,7 +244,9 @@ window.DashboardCharts.updateCharts = function (component) {
|
||||
|
||||
// Inactive segment
|
||||
data.push(inactiveVal);
|
||||
colors.push(window.DashboardCharts.hexToRgba(familyColor, 0.1));
|
||||
// Use higher opacity (0.6) to ensure the ring color matches the legend more closely
|
||||
// while still differentiating "depleted" from "active" (1.0 opacity)
|
||||
colors.push(window.DashboardCharts.hexToRgba(familyColor, 0.6));
|
||||
labels.push(depletedLabel);
|
||||
});
|
||||
|
||||
|
||||
@@ -21,9 +21,10 @@ window.DashboardStats = window.DashboardStats || {};
|
||||
*
|
||||
* 统计逻辑:
|
||||
* 1. 仅统计启用的账号(enabled !== false)
|
||||
* 2. 优先统计核心模型(Sonnet/Opus/Pro/Flash)的配额
|
||||
* 3. 配额 > 5% 视为 active,否则为 limited
|
||||
* 4. 状态非 'ok' 的账号归为 limited
|
||||
* 2. 检查账号下所有追踪模型的配额
|
||||
* 3. 如果任一追踪模型配额 <= 5%,则标记为 limited (Rate Limited Cooldown)
|
||||
* 4. 如果所有追踪模型配额 > 5%,则标记为 active
|
||||
* 5. 状态非 'ok' 的账号归为 limited
|
||||
*
|
||||
* @param {object} component - Dashboard 组件实例(Alpine.js 上下文)
|
||||
* @param {object} component.stats - 统计数据对象(会被修改)
|
||||
@@ -37,24 +38,32 @@ window.DashboardStats.updateStats = function(component) {
|
||||
const accounts = Alpine.store('data').accounts;
|
||||
let active = 0, limited = 0;
|
||||
|
||||
const isCore = (id) => /sonnet|opus|pro|flash/i.test(id);
|
||||
|
||||
// Only count enabled accounts in statistics
|
||||
const enabledAccounts = accounts.filter(acc => acc.enabled !== false);
|
||||
|
||||
enabledAccounts.forEach(acc => {
|
||||
if (acc.status === 'ok') {
|
||||
const limits = Object.entries(acc.limits || {});
|
||||
let hasActiveCore = limits.some(([id, l]) => l && l.remainingFraction > 0.05 && isCore(id));
|
||||
|
||||
if (!hasActiveCore) {
|
||||
const hasAnyCore = limits.some(([id]) => isCore(id));
|
||||
if (!hasAnyCore) {
|
||||
hasActiveCore = limits.some(([_, l]) => l && l.remainingFraction > 0.05);
|
||||
}
|
||||
if (limits.length === 0) {
|
||||
// No limit data available, consider limited to be safe
|
||||
limited++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasActiveCore) active++; else limited++;
|
||||
// Check if ANY tracked model is rate limited (<= 5%)
|
||||
// We consider all models in the limits object as "tracked"
|
||||
const hasRateLimitedModel = limits.some(([_, l]) => {
|
||||
// Treat null/undefined fraction as 0 (limited)
|
||||
if (!l || l.remainingFraction === null || l.remainingFraction === undefined) return true;
|
||||
return l.remainingFraction <= 0.05;
|
||||
});
|
||||
|
||||
if (hasRateLimitedModel) {
|
||||
limited++;
|
||||
} else {
|
||||
active++;
|
||||
}
|
||||
} else {
|
||||
limited++;
|
||||
}
|
||||
@@ -66,6 +75,25 @@ window.DashboardStats.updateStats = function(component) {
|
||||
component.stats.active = active;
|
||||
component.stats.limited = limited;
|
||||
|
||||
// Calculate model usage for rate limit details
|
||||
let totalLimitedModels = 0;
|
||||
let totalTrackedModels = 0;
|
||||
|
||||
enabledAccounts.forEach(acc => {
|
||||
const limits = Object.entries(acc.limits || {});
|
||||
limits.forEach(([id, l]) => {
|
||||
totalTrackedModels++;
|
||||
if (!l || l.remainingFraction <= 0.05) {
|
||||
totalLimitedModels++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
component.stats.modelUsage = {
|
||||
limited: totalLimitedModels,
|
||||
total: totalTrackedModels
|
||||
};
|
||||
|
||||
// Calculate subscription tier distribution
|
||||
const subscription = { ultra: 0, pro: 0, free: 0 };
|
||||
enabledAccounts.forEach(acc => {
|
||||
|
||||
@@ -227,7 +227,10 @@ document.addEventListener('alpine:init', () => {
|
||||
quotaInfo.push({ pct });
|
||||
});
|
||||
|
||||
if (quotaInfo.length === 0) return;
|
||||
if (quotaInfo.length === 0) {
|
||||
// Include model even if no quota info is available (treat as 0% or unknown)
|
||||
// This ensures the family appears in the charts
|
||||
}
|
||||
|
||||
rows.push({ modelId, family, quotaInfo });
|
||||
});
|
||||
|
||||
@@ -25,6 +25,9 @@ document.addEventListener('alpine:init', () => {
|
||||
active: "ACTIVE",
|
||||
operational: "Operational",
|
||||
rateLimited: "RATE LIMITED",
|
||||
quotasDepleted: "{count}/{total} Quotas Depleted",
|
||||
quotasDepletedTitle: "QUOTAS DEPLETED",
|
||||
outOfTracked: "Out of {total} Tracked",
|
||||
cooldown: "Cooldown",
|
||||
searchPlaceholder: "Search models...",
|
||||
allAccounts: "All Accounts",
|
||||
@@ -273,6 +276,9 @@ document.addEventListener('alpine:init', () => {
|
||||
active: "活跃状态",
|
||||
operational: "运行中",
|
||||
rateLimited: "受限状态",
|
||||
quotasDepleted: "{count}/{total} 配额耗尽",
|
||||
quotasDepletedTitle: "配额耗尽数",
|
||||
outOfTracked: "共追踪 {total} 个",
|
||||
cooldown: "冷却中",
|
||||
searchPlaceholder: "搜索模型...",
|
||||
allAccounts: "所有账号",
|
||||
|
||||
Reference in New Issue
Block a user