fix: correct MiniMax quota calculation and improve Token Plan display

- Fix MiniMax usage_count being treated as remaining (was inverted)
- Add MiniMax weekly quota tier extraction
- Remove Zhipu TIME_LIMIT (tools usage), keep only TOKENS_LIMIT
- Improve Kimi parsing with extract_reset_time and parse_f64 helpers
- Reuse TierBadge for Token Plan inline rendering
- Clean up unused i18n keys and debug println
This commit is contained in:
Jason
2026-04-05 13:35:54 +08:00
parent bfdac2a22a
commit ca6a187745
7 changed files with 146 additions and 64 deletions
+7 -5
View File
@@ -10,8 +10,8 @@ interface SubscriptionQuotaFooterProps {
inline?: boolean;
}
/** 已知 tier 名称的显示映射 */
const TIER_I18N_KEYS: Record<string, string> = {
/** 已知 tier 名称的显示映射(官方订阅 + Token Plan 共用) */
export const TIER_I18N_KEYS: Record<string, string> = {
five_hour: "subscription.fiveHour",
seven_day: "subscription.sevenDay",
seven_day_opus: "subscription.sevenDayOpus",
@@ -20,17 +20,19 @@ const TIER_I18N_KEYS: Record<string, string> = {
gemini_pro: "subscription.geminiPro",
gemini_flash: "subscription.geminiFlash",
gemini_flash_lite: "subscription.geminiFlashLite",
// Token Planfive_hour 已在上方官方映射中)
weekly_limit: "subscription.weeklyLimit",
};
/** 根据使用百分比返回颜色 class */
function utilizationColor(utilization: number): string {
export function utilizationColor(utilization: number): string {
if (utilization >= 90) return "text-red-500 dark:text-red-400";
if (utilization >= 70) return "text-orange-500 dark:text-orange-400";
return "text-green-600 dark:text-green-400";
}
/** 计算倒计时的纯时间字符串,如 "2h30m"、"3d12h" */
function countdownStr(resetsAt: string | null): string | null {
export function countdownStr(resetsAt: string | null): string | null {
if (!resetsAt) return null;
const diffMs = new Date(resetsAt).getTime() - Date.now();
if (diffMs <= 0) return null;
@@ -278,7 +280,7 @@ const SubscriptionQuotaFooter: React.FC<SubscriptionQuotaFooterProps> = ({
};
/** inline 模式下的单个 tier 显示 */
const TierBadge: React.FC<{
export const TierBadge: React.FC<{
tier: QuotaTier;
t: (key: string, options?: Record<string, unknown>) => string;
}> = ({ tier, t }) => {
+50 -1
View File
@@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next";
import { type AppId } from "@/lib/api";
import { useUsageQuery } from "@/lib/query/queries";
import { UsageData, Provider } from "@/types";
import { TierBadge } from "@/components/SubscriptionQuotaFooter";
import type { QuotaTier } from "@/types/subscription";
interface UsageFooterProps {
provider: Provider;
@@ -15,6 +17,15 @@ interface UsageFooterProps {
inline?: boolean; // 是否内联显示(在按钮左侧)
}
/** UsageData → QuotaTier 转换(Token Plan 使用) */
function toQuotaTier(data: UsageData): QuotaTier {
return {
name: data.planName || "",
utilization: data.used || 0,
resetsAt: data.extra || null,
};
}
const UsageFooter: React.FC<UsageFooterProps> = ({
provider,
providerId,
@@ -25,6 +36,8 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
inline = false,
}) => {
const { t } = useTranslation();
const isTokenPlan =
provider.meta?.usage_script?.templateType === "token_plan";
// 统一的用量查询(自动查询仅对当前激活的供应商启用)
// OpenCode(累加模式):使用 isInConfig 代替 isCurrent
@@ -108,7 +121,41 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
// 无数据时不显示
if (usageDataList.length === 0) return null;
// 内联模式:仅显示第一个套餐的核心数据(分上下两行)
// ── Token Plan:订阅风格内联渲染(百分比徽章 + 倒计时) ──
if (isTokenPlan && inline) {
return (
<div className="flex flex-col items-end gap-1 text-xs whitespace-nowrap flex-shrink-0">
{/* 第一行:查询时间 + 刷新 */}
<div className="flex items-center gap-2 justify-end">
<span className="text-[10px] text-muted-foreground/70 flex items-center gap-1">
<Clock size={10} />
{lastQueriedAt
? formatRelativeTime(lastQueriedAt, now, t)
: t("usage.never", { defaultValue: "从未更新" })}
</span>
<button
onClick={(e) => {
e.stopPropagation();
refetch();
}}
disabled={loading}
className="p-1 rounded hover:bg-muted transition-colors disabled:opacity-50 flex-shrink-0 text-muted-foreground"
title={t("usage.refreshUsage")}
>
<RefreshCw size={12} className={loading ? "animate-spin" : ""} />
</button>
</div>
{/* 第二行:tier 徽章(复用官方订阅的 TierBadge) */}
<div className="flex items-center gap-2">
{usageDataList.map((data, index) => (
<TierBadge key={index} tier={toQuotaTier(data)} t={t} />
))}
</div>
</div>
);
}
// ── 通用用量:内联模式(原有逻辑) ──
if (inline) {
const firstUsage = usageDataList[0];
const isExpired = firstUsage.isValid === false;
@@ -231,6 +278,8 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
);
};
// ── 通用用量组件 ────────────────────────────────────────────
// 单个套餐数据展示组件
const UsagePlanItem: React.FC<{ data: UsageData }> = ({ data }) => {
const { t } = useTranslation();
+3 -1
View File
@@ -186,8 +186,10 @@ export function ProviderCard({
autoQueryInterval,
});
const isTokenPlan =
provider.meta?.usage_script?.templateType === "token_plan";
const hasMultiplePlans =
usage?.success && usage.data && usage.data.length > 1;
usage?.success && usage.data && usage.data.length > 1 && !isTokenPlan;
const [isExpanded, setIsExpanded] = useState(false);
+1
View File
@@ -2288,6 +2288,7 @@
"geminiPro": "Pro",
"geminiFlash": "Flash",
"geminiFlashLite": "Flash Lite",
"weeklyLimit": "Weekly",
"utilization": "{{value}}%",
"resetsIn": "Resets in {{time}}",
"extraUsage": "Extra Usage",
+1
View File
@@ -2288,6 +2288,7 @@
"geminiPro": "Pro",
"geminiFlash": "Flash",
"geminiFlashLite": "Flash Lite",
"weeklyLimit": "週間",
"utilization": "{{value}}%",
"resetsIn": "{{time}}後にリセット",
"extraUsage": "超過使用量",
+1
View File
@@ -2288,6 +2288,7 @@
"geminiPro": "Pro",
"geminiFlash": "Flash",
"geminiFlashLite": "Flash Lite",
"weeklyLimit": "每周",
"utilization": "{{value}}%",
"resetsIn": "{{time}}后重置",
"extraUsage": "超额用量",