/** * Format ISO date string to countdown format * @param {string|Date} date - ISO date string or Date object * @returns {string} Formatted countdown (e.g., "5d 12h", "2h 30m", "15m") or "-" */ export function formatResetTime(date) { if (!date) return "-"; try { const resetDate = typeof date === "string" ? new Date(date) : date; const now = new Date(); const diffMs = resetDate - now; if (diffMs <= 0) return "-"; const totalMinutes = Math.floor(diffMs / (1000 * 60)); const totalHours = Math.floor(totalMinutes / 60); const totalDays = Math.floor(totalHours / 24); const days = totalDays; const hours = totalHours % 24; const minutes = totalMinutes % 60; if (days > 0) { return `${days}d ${hours}h`; } if (hours > 0) { return `${hours}h ${minutes}m`; } return `${minutes}m`; } catch (error) { return "-"; } } /** * Get Tailwind color class based on percentage * @param {number} percentage - Remaining percentage (0-100) * @returns {string} Color name: "green" | "yellow" | "red" | "gray" */ 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"; } /** * Get status emoji based on percentage * @param {number} percentage - Remaining percentage (0-100) * @returns {string} Emoji: "🟢" | "🟡" | "🔴" | "⚫" */ export function getStatusEmoji(percentage) { if (percentage === 0 || percentage === null || percentage === undefined) { return "⚫"; } if (percentage > 70) return "🟢"; if (percentage >= 30) return "🟡"; return "🔴"; } /** * Calculate remaining percentage * @param {number} used - Used amount * @param {number} total - Total amount * @returns {number} Remaining percentage (0-100) */ export function calculatePercentage(used, total) { if (!total || total === 0) return 0; if (!used || used < 0) return 100; if (used >= total) return 0; return Math.round(((total - used) / total) * 100); } /** * Parse provider-specific quota structures into normalized array * @param {string} provider - Provider name (github, antigravity, codex, kiro, claude) * @param {Object} data - Raw quota data from provider * @returns {Array} Normalized quota objects with { name, used, total, resetAt } */ export function parseQuotaData(provider, data) { if (!data || typeof data !== "object") return []; const normalizedQuotas = []; try { switch (provider.toLowerCase()) { case "github": if (data.quotas) { Object.entries(data.quotas).forEach(([name, quota]) => { normalizedQuotas.push({ name, used: quota.used || 0, total: quota.total || 0, resetAt: quota.resetAt || null, }); }); } break; case "antigravity": if (data.quotas) { Object.entries(data.quotas).forEach(([modelName, quota]) => { normalizedQuotas.push({ name: modelName, used: quota.used || 0, total: quota.total || 0, resetAt: quota.resetAt || null, }); }); } break; case "codex": if (data.quotas) { Object.entries(data.quotas).forEach(([quotaType, quota]) => { normalizedQuotas.push({ name: quotaType, used: quota.used || 0, total: quota.total || 0, resetAt: quota.resetAt || null, }); }); } break; case "kiro": if (data.quotas) { Object.entries(data.quotas).forEach(([quotaType, quota]) => { normalizedQuotas.push({ name: quotaType, used: quota.used || 0, total: quota.total || 0, resetAt: quota.resetAt || null, }); }); } break; case "claude": if (data.message) { // Handle error message case normalizedQuotas.push({ name: "error", used: 0, total: 0, resetAt: null, message: data.message, }); } else if (data.quotas) { Object.entries(data.quotas).forEach(([name, quota]) => { normalizedQuotas.push({ name, used: quota.used || 0, total: quota.total || 0, resetAt: quota.resetAt || null, }); }); } break; default: // Generic fallback for unknown providers if (data.quotas) { Object.entries(data.quotas).forEach(([name, quota]) => { normalizedQuotas.push({ name, used: quota.used || 0, total: quota.total || 0, resetAt: quota.resetAt || null, }); }); } } } catch (error) { console.error(`Error parsing quota data for ${provider}:`, error); return []; } return normalizedQuotas; }