From e8aa3e21fe9ce447451429009304858eb922074b Mon Sep 17 00:00:00 2001 From: decolua Date: Fri, 6 Feb 2026 11:23:08 +0700 Subject: [PATCH] - Added new model "Claude Opus 4.6" to the provider models. --- open-sse/config/constants.js | 2 +- open-sse/config/providerModels.js | 3 +- open-sse/services/usage.js | 72 +++++++-- .../ProviderLimits/QuotaProgressBar.js | 10 +- .../components/ProviderLimits/QuotaTable.js | 45 +++--- .../usage/components/ProviderLimits/index.js | 139 ++++++++---------- .../usage/components/ProviderLimits/utils.js | 16 +- src/shared/components/Sidebar.js | 9 +- src/shared/constants/config.js | 4 +- src/shared/constants/providers.js | 2 +- 10 files changed, 159 insertions(+), 143 deletions(-) diff --git a/open-sse/config/constants.js b/open-sse/config/constants.js index a07af40e..900b9740 100644 --- a/open-sse/config/constants.js +++ b/open-sse/config/constants.js @@ -222,7 +222,7 @@ export const BACKOFF_CONFIG = { export const COOLDOWN_MS = { unauthorized: 2 * 60 * 1000, // 401 → 30 min paymentRequired: 2 * 60 * 1000, // 402/403 → 30 min - notFound: 2 * 60 * 60 * 1000, // 404 → 12 hours + notFound: 2 * 60 * 1000, // 404 → 2 minutes transient: 30 * 1000, // 408/500/502/503/504 → 1 min requestNotAllowed: 5 * 1000, // "Request not allowed" → 5 sec // Legacy aliases for backward compatibility diff --git a/open-sse/config/providerModels.js b/open-sse/config/providerModels.js index c1456db9..95e11216 100644 --- a/open-sse/config/providerModels.js +++ b/open-sse/config/providerModels.js @@ -5,6 +5,7 @@ export const PROVIDER_MODELS = { // OAuth Providers (using alias) cc: [ // Claude Code + { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, { id: "claude-opus-4-5-20251101", name: "Claude 4.5 Opus" }, { id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" }, { id: "claude-haiku-4-5-20251001", name: "Claude 4.5 Haiku" }, @@ -52,8 +53,8 @@ export const PROVIDER_MODELS = { { id: "glm-4.7", name: "GLM 4.7" }, ], ag: [ // Antigravity - special case: models call different backends + // { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, { id: "claude-opus-4-5-thinking", name: "Claude Opus 4.5 Thinking" }, - { id: "claude-opus-4-5", name: "Claude Opus 4.5" }, { id: "claude-sonnet-4-5-thinking", name: "Claude Sonnet 4.5 Thinking" }, { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" }, { id: "gemini-3-pro-high", name: "Gemini 3 Pro High" }, diff --git a/open-sse/services/usage.js b/open-sse/services/usage.js index 7d5f8e11..f62851e8 100644 --- a/open-sse/services/usage.js +++ b/open-sse/services/usage.js @@ -59,6 +59,36 @@ export async function getUsageForProvider(connection) { } } +/** + * Parse reset date/time to ISO string + * Handles multiple formats: Unix timestamp (ms), ISO date string, etc. + */ +function parseResetTime(resetValue) { + if (!resetValue) return null; + + try { + // If it's already a Date object + if (resetValue instanceof Date) { + return resetValue.toISOString(); + } + + // If it's a number (Unix timestamp in milliseconds) + if (typeof resetValue === 'number') { + return new Date(resetValue).toISOString(); + } + + // If it's a string (ISO date or any parseable date string) + if (typeof resetValue === 'string') { + return new Date(resetValue).toISOString(); + } + + return null; + } catch (error) { + console.warn(`Failed to parse reset time: ${resetValue}`, error); + return null; + } +} + /** * GitHub Copilot Usage * Uses GitHub accessToken (not copilotToken) to call copilot_internal/user API @@ -92,19 +122,22 @@ async function getGitHubUsage(accessToken, providerSpecificData) { if (data.quota_snapshots) { // Paid plan format const snapshots = data.quota_snapshots; + const resetAt = parseResetTime(data.quota_reset_date); + return { plan: data.copilot_plan, resetDate: data.quota_reset_date, quotas: { - chat: formatGitHubQuotaSnapshot(snapshots.chat), - completions: formatGitHubQuotaSnapshot(snapshots.completions), - premium_interactions: formatGitHubQuotaSnapshot(snapshots.premium_interactions), + chat: { ...formatGitHubQuotaSnapshot(snapshots.chat), resetAt }, + completions: { ...formatGitHubQuotaSnapshot(snapshots.completions), resetAt }, + premium_interactions: { ...formatGitHubQuotaSnapshot(snapshots.premium_interactions), resetAt }, }, }; } else if (data.monthly_quotas || data.limited_user_quotas) { // Free/limited plan format const monthlyQuotas = data.monthly_quotas || {}; const usedQuotas = data.limited_user_quotas || {}; + const resetAt = parseResetTime(data.limited_user_reset_date); return { plan: data.copilot_plan || data.access_type_sku, @@ -114,11 +147,13 @@ async function getGitHubUsage(accessToken, providerSpecificData) { used: usedQuotas.chat || 0, total: monthlyQuotas.chat || 0, unlimited: false, + resetAt, }, completions: { used: usedQuotas.completions || 0, total: monthlyQuotas.completions || 0, unlimited: false, + resetAt, }, }, }; @@ -236,7 +271,7 @@ async function getAntigravityUsage(accessToken, providerSpecificData) { quotas[modelKey] = { used, total, - resetAt: info.quotaInfo.resetTime || null, + resetAt: parseResetTime(info.quotaInfo.resetTime), remainingPercentage, unlimited: false, displayName: info.displayName || modelKey, @@ -372,13 +407,9 @@ async function getCodexUsage(accessToken) { const primaryWindow = rateLimit.primary_window || {}; const secondaryWindow = rateLimit.secondary_window || {}; - // Calculate reset dates - const sessionResetAt = primaryWindow.reset_at - ? new Date(primaryWindow.reset_at * 1000).toISOString() - : null; - const weeklyResetAt = secondaryWindow.reset_at - ? new Date(secondaryWindow.reset_at * 1000).toISOString() - : null; + // Parse reset dates (reset_at is Unix timestamp in seconds, multiply by 1000 for ms) + const sessionResetAt = parseResetTime(primaryWindow.reset_at ? primaryWindow.reset_at * 1000 : null); + const weeklyResetAt = parseResetTime(secondaryWindow.reset_at ? secondaryWindow.reset_at * 1000 : null); return { plan: data.plan_type || "unknown", @@ -388,14 +419,14 @@ async function getCodexUsage(accessToken) { used: primaryWindow.used_percent || 0, total: 100, remaining: 100 - (primaryWindow.used_percent || 0), - resetTime: sessionResetAt, + resetAt: sessionResetAt, unlimited: false, }, weekly: { used: secondaryWindow.used_percent || 0, total: 100, remaining: 100 - (secondaryWindow.used_percent || 0), - resetTime: weeklyResetAt, + resetAt: weeklyResetAt, unlimited: false, }, }, @@ -439,10 +470,21 @@ async function getKiroUsage(accessToken, providerSpecificData) { } const data = await response.json(); + + console.log("[Kiro Usage] API Response:", JSON.stringify(data, null, 2)); // Parse usage data from usageBreakdownList const usageList = data.usageBreakdownList || []; const quotaInfo = {}; + + // Parse reset time - supports multiple formats (nextDateReset, resetDate, etc.) + const resetAt = parseResetTime(data.nextDateReset || data.resetDate); + + console.log("[Kiro Usage] Reset time:", { + nextDateReset: data.nextDateReset, + resetDate: data.resetDate, + parsedResetAt: resetAt + }); usageList.forEach((breakdown) => { const resourceType = breakdown.resourceType?.toLowerCase() || "unknown"; @@ -453,7 +495,7 @@ async function getKiroUsage(accessToken, providerSpecificData) { used, total, remaining: total - used, - resetTime: data.nextDateReset ? new Date(data.nextDateReset).toISOString() : null, + resetAt, unlimited: false, }; @@ -466,7 +508,7 @@ async function getKiroUsage(accessToken, providerSpecificData) { used: freeUsed, total: freeTotal, remaining: freeTotal - freeUsed, - resetTime: data.nextDateReset ? new Date(data.nextDateReset).toISOString() : null, + resetAt, unlimited: false, }; } diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js index 52c3c853..fc0bdc1d 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js @@ -5,15 +5,6 @@ import { formatResetTime } from "./utils"; // Calculate color based on remaining percentage const getColorClasses = (remainingPercentage) => { - if (remainingPercentage === 0) { - return { - text: "text-gray-400", - bg: "bg-gray-400", - bgLight: "bg-gray-400/10", - emoji: "⚫" - }; - } - if (remainingPercentage > 70) { return { text: "text-green-500", @@ -32,6 +23,7 @@ const getColorClasses = (remainingPercentage) => { }; } + // 0-29% including 0% (out of quota) - show red return { text: "text-red-500", bg: "bg-red-500", diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js index 55fb6e3c..e15d5ddc 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js @@ -40,15 +40,6 @@ function formatResetTimeDisplay(resetTime) { * Get color classes based on remaining percentage */ function getColorClasses(remainingPercentage) { - if (remainingPercentage === 0) { - return { - text: "text-text-muted", - bg: "bg-bg-muted", - bgLight: "bg-bg-muted/20", - emoji: "⚫" - }; - } - if (remainingPercentage > 70) { return { text: "text-green-600 dark:text-green-400", @@ -67,6 +58,7 @@ function getColorClasses(remainingPercentage) { }; } + // 0-29% including 0% (out of quota) - show red return { text: "text-red-600 dark:text-red-400", bg: "bg-red-500", @@ -85,7 +77,12 @@ export default function QuotaTable({ quotas = [] }) { return (
- +
+ + {/* Model Name */} + {/* Limit Progress */} + {/* Reset Time */} + {quotas.map((quota, index) => { const remaining = quota.remainingPercentage !== undefined @@ -136,18 +133,22 @@ export default function QuotaTable({ quotas = [] }) { {/* Reset Time */} ); diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js index 0b5297f1..d90261d5 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js @@ -358,85 +358,68 @@ export default function ProviderLimits() { const isLoading = loading[conn.id]; const error = errors[conn.id]; - // Use table layout for Antigravity and Kiro, card layout for others - if (conn.provider === "antigravity" || conn.provider === "kiro") { - return ( - -
-
-
-
- -
-
-

- {conn.provider} -

- {conn.name && ( -

{conn.name}

- )} -
-
- - -
-
- -
- {isLoading ? ( -
- - progress_activity - -
- ) : error ? ( -
- - error - -

{error}

-
- ) : quota?.message ? ( -
-

{quota.message}

-
- ) : ( - - )} -
-
- ); - } - - // Use card layout for other providers + // Use table layout for all providers return ( - refreshProvider(conn.id, conn.provider)} - /> + +
+
+
+
+ +
+
+

+ {conn.provider} +

+ {conn.name && ( +

{conn.name}

+ )} +
+
+ + +
+
+ +
+ {isLoading ? ( +
+ + progress_activity + +
+ ) : error ? ( +
+ + error + +

{error}

+
+ ) : quota?.message ? ( +
+

{quota.message}

+
+ ) : ( + + )} +
+
); })} diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js index 0fcc1864..8cf3f600 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js @@ -42,31 +42,23 @@ export function formatResetTime(date) { /** * Get Tailwind color class based on percentage * @param {number} percentage - Remaining percentage (0-100) - * @returns {string} Color name: "green" | "yellow" | "red" | "gray" + * @returns {string} Color name: "green" | "yellow" | "red" */ export function getStatusColor(percentage) { - if (percentage === 0 || percentage === null || percentage === undefined) { - return "gray"; - } - if (percentage > 70) return "green"; if (percentage >= 30) return "yellow"; - return "red"; + return "red"; // 0-29% including 0% (out of quota) - show red } /** * Get status emoji based on percentage * @param {number} percentage - Remaining percentage (0-100) - * @returns {string} Emoji: "🟢" | "🟡" | "🔴" | "⚫" + * @returns {string} Emoji: "🟢" | "🟡" | "🔴" */ export function getStatusEmoji(percentage) { - if (percentage === 0 || percentage === null || percentage === undefined) { - return "⚫"; - } - if (percentage > 70) return "🟢"; if (percentage >= 30) return "🟡"; - return "🔴"; + return "🔴"; // 0-29% including 0% (out of quota) - show red } /** diff --git a/src/shared/components/Sidebar.js b/src/shared/components/Sidebar.js index 4f19fe3e..a1dce0b6 100644 --- a/src/shared/components/Sidebar.js +++ b/src/shared/components/Sidebar.js @@ -76,9 +76,12 @@ export default function Sidebar({ onClose }) {
hub
-

- {APP_CONFIG.name} -

+
+

+ {APP_CONFIG.name} +

+ v{APP_CONFIG.version} +
diff --git a/src/shared/constants/config.js b/src/shared/constants/config.js index a4381512..bdc2f08c 100644 --- a/src/shared/constants/config.js +++ b/src/shared/constants/config.js @@ -1,8 +1,10 @@ +import pkg from "../../../../package.json" with { type: "json" }; + // App configuration export const APP_CONFIG = { name: "Endpoint Proxy", description: "AI Infrastructure Management", - version: "1.0.0", + version: pkg.version, }; // Theme configuration diff --git a/src/shared/constants/providers.js b/src/shared/constants/providers.js index 58d4d15c..ed72c65f 100644 --- a/src/shared/constants/providers.js +++ b/src/shared/constants/providers.js @@ -78,4 +78,4 @@ export const ID_TO_ALIAS = Object.values(AI_PROVIDERS).reduce((acc, p) => { }, {}); // Providers that support usage/quota API -export const USAGE_SUPPORTED_PROVIDERS = ["antigravity", "kiro", "github", "claude"]; +export const USAGE_SUPPORTED_PROVIDERS = ["antigravity", "kiro", "github"];
-
- {countdown !== "-" && ( -
- in {countdown} -
- )} - {resetDisplay && ( -
- {resetDisplay} -
- )} -
+ {countdown !== "-" || resetDisplay ? ( +
+ {countdown !== "-" && ( +
+ in {countdown} +
+ )} + {resetDisplay && ( +
+ {resetDisplay} +
+ )} +
+ ) : ( +
N/A
+ )}