diff --git a/app/usage.js b/app/usage.js index 5c24bba..16778a0 100644 --- a/app/usage.js +++ b/app/usage.js @@ -211,23 +211,32 @@ function round1(n) { const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' }; +/** 是否為有限正數(排除 0、負數、NaN、Infinity),避免算出 Infinity%/NaN%/負百分比。 */ +function isFinitePositive(n) { + return Number.isFinite(Number(n)) && Number(n) > 0; +} + /** * 計算「剩餘可用百分比」,依優先序擇一: - * 1. 帳號額度(quota 有上限)→ 剩餘 credits / 上限; - * 2. 速率配額(rate limit header)→ 當前視窗剩餘 / 上限; - * 皆無法取得時回傳 { percent: null, reason }。 + * 1. 帳號額度(quota 有有限正數上限)→ 剩餘 credits / 上限; + * 2. 速率配額(rate limit header,有限正數上限)→ 當前視窗剩餘 / 上限; + * 上限或剩餘為 0/負數/NaN/Infinity 等無效值時不計算,落到 { percent: null, reason }。 */ export function resolveRemainingPercent(quota, rate) { - if (quota?.available && quota.limit != null && Number(quota.limit) > 0) { + if (quota?.available && quota.limit != null && isFinitePositive(quota.limit)) { const limit = Number(quota.limit); const remaining = quota.remaining == null ? limit - num(quota.used) : Number(quota.remaining); - return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' }; + if (Number.isFinite(remaining)) { + return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' }; + } } - if (rate?.hasData && Number(rate.limit) > 0) { + if (rate?.hasData && isFinitePositive(rate.limit)) { const limit = Number(rate.limit); const remaining = Number(rate.remaining); - const kindLabel = RATE_KIND_LABEL[rate.kind] || rate.kind; - return { percent: round1((remaining / limit) * 100), basis: `速率配額(當前視窗,${kindLabel})`, remaining, limit, unit: '' }; + if (Number.isFinite(remaining)) { + const kindLabel = RATE_KIND_LABEL[rate.kind] || rate.kind; + return { percent: round1((remaining / limit) * 100), basis: `速率配額(當前視窗,${kindLabel})`, remaining, limit, unit: '' }; + } } let reason; if (quota?.available && quota.limit == null) reason = '帳號額度無上限,無法計算百分比';