mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 16:56:16 +08:00
feat(usage): add official subscription quota template with unified tier rendering
Changes: - Add official_subscription template type for Claude/Codex/Gemini - Replace implicit 'category=official auto-query' with explicit opt-in template - Default disabled; users enable via usage script modal with configurable interval - Unify tier→label mapping across subscription and script paths via labeled_tier_parts() - Fix tray rendering: week aliases (seven_day/opus/sonnet) now use highest utilization - Add depth guard: official_subscription checks enabled flag in query_provider_usage_inner - Add cache invalidation symmetry: invalidate_subscription() for disabled providers - i18n: add templateOfficialSubscription + hint in zh/en/ja/zh-TW Backend (Rust): - provider.rs: add TEMPLATE_TYPE_OFFICIAL_SUBSCRIPTION branch, flatten SubscriptionQuota→UsageData - tray.rs: extract labeled_tier_parts() shared by both summary functions, use max_by for multi-alias groups - usage_cache.rs: add invalidate_subscription() method - Test coverage: add week-alias highest-utilization tests for both paths Frontend (TypeScript): - UsageScriptModal: add official_subscription to templates, auto-detect for official providers - ProviderCard: gate useUsageQuery with !isOfficialSubscriptionUsage, pass autoQueryInterval to footer - SubscriptionQuotaFooter: accept autoQueryInterval prop, default 0 (disabled) - constants.ts: add TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION Fixes tier rendering regression where: - Claude/Codex: seven_day was missed (only weekly_limit matched) → lost 7-day window in tray - Gemini: gemini_pro/flash/flash_lite fell through to fallback → leaked machine names - Multi-window (opus+sonnet): find() took first, not worst → underestimated utilization and emoji color All tests pass (cargo test + cargo clippy clean).
This commit is contained in:
@@ -9,6 +9,7 @@ interface SubscriptionQuotaFooterProps {
|
||||
appId: AppId;
|
||||
inline?: boolean;
|
||||
isCurrent?: boolean;
|
||||
autoQueryInterval?: number;
|
||||
}
|
||||
|
||||
interface SubscriptionQuotaViewProps {
|
||||
@@ -397,12 +398,18 @@ const SubscriptionQuotaFooter: React.FC<SubscriptionQuotaFooterProps> = ({
|
||||
appId,
|
||||
inline = false,
|
||||
isCurrent = false,
|
||||
autoQueryInterval = 5,
|
||||
}) => {
|
||||
const {
|
||||
data: quota,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useSubscriptionQuota(appId, isCurrent, isCurrent);
|
||||
} = useSubscriptionQuota(
|
||||
appId,
|
||||
isCurrent,
|
||||
isCurrent && autoQueryInterval > 0,
|
||||
autoQueryInterval,
|
||||
);
|
||||
|
||||
if (!isCurrent) return null;
|
||||
|
||||
|
||||
@@ -110,6 +110,9 @@ const generatePresetTemplates = (
|
||||
|
||||
// 官方余额查询模板不需要脚本,使用专用 Rust 查询
|
||||
[TEMPLATE_TYPES.BALANCE]: "",
|
||||
|
||||
// 官方订阅额度查询不需要脚本,使用 CLI/OAuth 凭据调用官方 API
|
||||
[TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION]: "",
|
||||
});
|
||||
|
||||
// 模板名称国际化键映射
|
||||
@@ -120,6 +123,8 @@ const TEMPLATE_NAME_KEYS: Record<string, string> = {
|
||||
[TEMPLATE_TYPES.GITHUB_COPILOT]: "usageScript.templateCopilot",
|
||||
[TEMPLATE_TYPES.TOKEN_PLAN]: "usageScript.templateTokenPlan",
|
||||
[TEMPLATE_TYPES.BALANCE]: "usageScript.templateBalance",
|
||||
[TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION]:
|
||||
"usageScript.templateOfficialSubscription",
|
||||
};
|
||||
|
||||
/** 官方余额查询供应商检测 */
|
||||
@@ -141,6 +146,45 @@ function detectBalanceProvider(baseUrl: string | undefined): boolean {
|
||||
return BALANCE_PROVIDERS.some((bp) => bp.pattern.test(baseUrl));
|
||||
}
|
||||
|
||||
function isOfficialSubscriptionProvider(provider: Provider, appId: AppId) {
|
||||
if (!["claude", "codex", "gemini"].includes(appId)) return false;
|
||||
if (provider.category === "official") return true;
|
||||
|
||||
const config = provider.settingsConfig as Record<string, any>;
|
||||
if (appId === "claude") {
|
||||
const baseUrl = config?.env?.ANTHROPIC_BASE_URL;
|
||||
return !baseUrl || (typeof baseUrl === "string" && baseUrl.trim() === "");
|
||||
}
|
||||
if (appId === "codex") {
|
||||
const apiKey = config?.auth?.OPENAI_API_KEY;
|
||||
const bearerToken =
|
||||
typeof config?.config === "string"
|
||||
? extractCodexExperimentalBearerToken(config.config)
|
||||
: undefined;
|
||||
return (
|
||||
!bearerToken &&
|
||||
(!apiKey || (typeof apiKey === "string" && apiKey.trim() === ""))
|
||||
);
|
||||
}
|
||||
if (appId === "gemini") {
|
||||
const env = config?.env || {};
|
||||
const apiKey = env.GEMINI_API_KEY;
|
||||
const baseUrl = env.GOOGLE_GEMINI_BASE_URL;
|
||||
return (
|
||||
(!apiKey || (typeof apiKey === "string" && apiKey.trim() === "")) &&
|
||||
(!baseUrl || (typeof baseUrl === "string" && baseUrl.trim() === ""))
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const NATIVE_USAGE_TEMPLATES = new Set<string>([
|
||||
TEMPLATE_TYPES.GITHUB_COPILOT,
|
||||
TEMPLATE_TYPES.TOKEN_PLAN,
|
||||
TEMPLATE_TYPES.BALANCE,
|
||||
TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION,
|
||||
]);
|
||||
|
||||
const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
provider,
|
||||
appId,
|
||||
@@ -238,22 +282,33 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
};
|
||||
|
||||
const providerCredentials = getProviderCredentials();
|
||||
const isOfficialSubscription = isOfficialSubscriptionProvider(
|
||||
provider,
|
||||
appId,
|
||||
);
|
||||
|
||||
const [script, setScript] = useState<UsageScript>(() => {
|
||||
const savedScript = provider.meta?.usage_script;
|
||||
if (savedScript) {
|
||||
const normalizedScript = createUsageScript(savedScript);
|
||||
if (
|
||||
isOfficialSubscription &&
|
||||
normalizedScript.templateType !== TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION
|
||||
) {
|
||||
return createUsageScript();
|
||||
}
|
||||
// 已有配置:如果是 coding_plan 但没有 codingPlanProvider,自动检测填充
|
||||
if (
|
||||
savedScript.templateType === TEMPLATE_TYPES.TOKEN_PLAN &&
|
||||
!savedScript.codingPlanProvider
|
||||
normalizedScript.templateType === TEMPLATE_TYPES.TOKEN_PLAN &&
|
||||
!normalizedScript.codingPlanProvider
|
||||
) {
|
||||
return {
|
||||
...savedScript,
|
||||
...normalizedScript,
|
||||
codingPlanProvider:
|
||||
detectCodingPlanProvider(providerCredentials.baseUrl) || "kimi",
|
||||
};
|
||||
}
|
||||
return savedScript;
|
||||
return normalizedScript;
|
||||
}
|
||||
|
||||
const autoDetected = detectCodingPlanProvider(providerCredentials.baseUrl);
|
||||
@@ -265,6 +320,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
return createUsageScript();
|
||||
}
|
||||
|
||||
if (isOfficialSubscription) {
|
||||
return createUsageScript();
|
||||
}
|
||||
|
||||
return createUsageScript({
|
||||
code: PRESET_TEMPLATES[TEMPLATE_TYPES.GENERAL],
|
||||
});
|
||||
@@ -327,9 +386,17 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
return TEMPLATE_TYPES.GITHUB_COPILOT;
|
||||
}
|
||||
// 优先使用保存的 templateType
|
||||
if (existingScript?.templateType) {
|
||||
if (
|
||||
existingScript?.templateType &&
|
||||
(!isOfficialSubscription ||
|
||||
existingScript.templateType === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION)
|
||||
) {
|
||||
return existingScript.templateType as string;
|
||||
}
|
||||
// 官方 CLI/OAuth 供应商默认使用官方订阅额度模板,但开关默认关闭
|
||||
if (isOfficialSubscription) {
|
||||
return TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION;
|
||||
}
|
||||
// 向后兼容:根据字段推断模板类型
|
||||
// 检测 NEW_API 模板(有 accessToken 或 userId)
|
||||
if (existingScript?.accessToken || existingScript?.userId) {
|
||||
@@ -378,12 +445,8 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
// Copilot、Coding Plan、Balance 模板不需要脚本验证
|
||||
if (
|
||||
selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT &&
|
||||
selectedTemplate !== TEMPLATE_TYPES.TOKEN_PLAN &&
|
||||
selectedTemplate !== TEMPLATE_TYPES.BALANCE
|
||||
) {
|
||||
// 专用模板不需要脚本验证
|
||||
if (!NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "")) {
|
||||
if (script.enabled && !script.code.trim()) {
|
||||
toast.error(t("usageScript.scriptEmpty"));
|
||||
return;
|
||||
@@ -403,6 +466,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
| "github_copilot"
|
||||
| "token_plan"
|
||||
| "balance"
|
||||
| "official_subscription"
|
||||
| undefined,
|
||||
};
|
||||
onSave(scriptWithTemplate);
|
||||
@@ -412,6 +476,28 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
const handleTest = async () => {
|
||||
setTesting(true);
|
||||
try {
|
||||
// 官方订阅额度模板使用 CLI/OAuth 凭据和官方 API
|
||||
if (selectedTemplate === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION) {
|
||||
const { subscriptionApi } = await import("@/lib/api/subscription");
|
||||
const quota = await subscriptionApi.getQuota(appId);
|
||||
if (quota.success && quota.tiers.length > 0) {
|
||||
const summary = quota.tiers
|
||||
.map((tier) => `${tier.name}: ${Math.round(tier.utilization)}%`)
|
||||
.join(", ");
|
||||
toast.success(`${t("usageScript.testSuccess")}${summary}`, {
|
||||
duration: 3000,
|
||||
closeButton: true,
|
||||
});
|
||||
queryClient.setQueryData(["subscription", "quota", appId], quota);
|
||||
} else {
|
||||
toast.error(
|
||||
`${t("usageScript.testFailed")}: ${quota.error || t("endpointTest.noResult")}`,
|
||||
{ duration: 5000 },
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 官方余额查询模板使用专用 API
|
||||
if (selectedTemplate === TEMPLATE_TYPES.BALANCE) {
|
||||
const baseUrl = providerCredentials.baseUrl ?? "";
|
||||
@@ -654,6 +740,16 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
accessToken: undefined,
|
||||
userId: undefined,
|
||||
});
|
||||
} else if (presetName === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION) {
|
||||
// 官方订阅额度查询不需要脚本,使用 CLI/OAuth 凭据
|
||||
setScript({
|
||||
...script,
|
||||
code: "",
|
||||
apiKey: undefined,
|
||||
baseUrl: undefined,
|
||||
accessToken: undefined,
|
||||
userId: undefined,
|
||||
});
|
||||
}
|
||||
setSelectedTemplate(presetName);
|
||||
}
|
||||
@@ -681,7 +777,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleFormat}
|
||||
disabled={!script.enabled}
|
||||
disabled={
|
||||
!script.enabled ||
|
||||
NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "")
|
||||
}
|
||||
title={t("usageScript.format")}
|
||||
>
|
||||
<Wand2 size={14} className="mr-1" />
|
||||
@@ -742,8 +841,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
if (isCopilotProvider) {
|
||||
return name === TEMPLATE_TYPES.GITHUB_COPILOT;
|
||||
}
|
||||
// 官方 CLI/OAuth 供应商只显示官方订阅额度模板
|
||||
if (isOfficialSubscription) {
|
||||
return name === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION;
|
||||
}
|
||||
// 非 Copilot 供应商不显示 copilot 模板
|
||||
return name !== TEMPLATE_TYPES.GITHUB_COPILOT;
|
||||
return (
|
||||
name !== TEMPLATE_TYPES.GITHUB_COPILOT &&
|
||||
name !== TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION
|
||||
);
|
||||
})
|
||||
.map((name) => {
|
||||
const isSelected = selectedTemplate === name;
|
||||
@@ -865,6 +971,15 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 官方订阅额度模式:自动提示 */}
|
||||
{selectedTemplate === TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION && (
|
||||
<div className="space-y-2 border-t border-white/10 pt-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("usageScript.officialSubscriptionHint")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Coding Plan 模式:供应商选择 */}
|
||||
{selectedTemplate === TEMPLATE_TYPES.TOKEN_PLAN && (
|
||||
<div className="space-y-3 border-t border-white/10 pt-3">
|
||||
@@ -1191,43 +1306,41 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 提取器代码 - Copilot 模板不需要 */}
|
||||
{selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT &&
|
||||
selectedTemplate !== TEMPLATE_TYPES.TOKEN_PLAN && (
|
||||
<div className="space-y-4 glass rounded-xl border border-white/10 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-base font-medium">
|
||||
{t("usageScript.extractorCode")}
|
||||
</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("usageScript.extractorHint")}
|
||||
</div>
|
||||
{/* 提取器代码 - 专用模板不需要 */}
|
||||
{!NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "") && (
|
||||
<div className="space-y-4 glass rounded-xl border border-white/10 p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-base font-medium">
|
||||
{t("usageScript.extractorCode")}
|
||||
</Label>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("usageScript.extractorHint")}
|
||||
</div>
|
||||
<JsonEditor
|
||||
id="usage-code"
|
||||
value={script.code || ""}
|
||||
onChange={(value) =>
|
||||
setScript((prev) => ({ ...prev, code: value }))
|
||||
}
|
||||
height={480}
|
||||
language="javascript"
|
||||
showMinimap={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<JsonEditor
|
||||
id="usage-code"
|
||||
value={script.code || ""}
|
||||
onChange={(value) =>
|
||||
setScript((prev) => ({ ...prev, code: value }))
|
||||
}
|
||||
height={480}
|
||||
language="javascript"
|
||||
showMinimap={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 帮助信息 - Copilot 模板不需要 */}
|
||||
{selectedTemplate !== TEMPLATE_TYPES.GITHUB_COPILOT &&
|
||||
selectedTemplate !== TEMPLATE_TYPES.TOKEN_PLAN && (
|
||||
<div className="glass rounded-xl border border-white/10 p-6 text-sm text-foreground/90">
|
||||
<h4 className="font-medium mb-2">
|
||||
{t("usageScript.scriptHelp")}
|
||||
</h4>
|
||||
<div className="space-y-3 text-xs">
|
||||
<div>
|
||||
<strong>{t("usageScript.configFormat")}</strong>
|
||||
<pre className="mt-1 p-2 bg-black/20 text-foreground rounded border border-white/10 text-[10px] overflow-x-auto">
|
||||
{`({
|
||||
{/* 帮助信息 - 专用模板不需要 */}
|
||||
{!NATIVE_USAGE_TEMPLATES.has(selectedTemplate || "") && (
|
||||
<div className="glass rounded-xl border border-white/10 p-6 text-sm text-foreground/90">
|
||||
<h4 className="font-medium mb-2">
|
||||
{t("usageScript.scriptHelp")}
|
||||
</h4>
|
||||
<div className="space-y-3 text-xs">
|
||||
<div>
|
||||
<strong>{t("usageScript.configFormat")}</strong>
|
||||
<pre className="mt-1 p-2 bg-black/20 text-foreground rounded border border-white/10 text-[10px] overflow-x-auto">
|
||||
{`({
|
||||
request: {
|
||||
url: "{{baseUrl}}/api/usage",
|
||||
method: "POST",
|
||||
@@ -1244,39 +1357,39 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
};
|
||||
}
|
||||
})`}
|
||||
</pre>
|
||||
</div>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>{t("usageScript.extractorFormat")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>{t("usageScript.fieldIsValid")}</li>
|
||||
<li>{t("usageScript.fieldInvalidMessage")}</li>
|
||||
<li>{t("usageScript.fieldRemaining")}</li>
|
||||
<li>{t("usageScript.fieldUnit")}</li>
|
||||
<li>{t("usageScript.fieldPlanName")}</li>
|
||||
<li>{t("usageScript.fieldTotal")}</li>
|
||||
<li>{t("usageScript.fieldUsed")}</li>
|
||||
<li>{t("usageScript.fieldExtra")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{t("usageScript.extractorFormat")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>{t("usageScript.fieldIsValid")}</li>
|
||||
<li>{t("usageScript.fieldInvalidMessage")}</li>
|
||||
<li>{t("usageScript.fieldRemaining")}</li>
|
||||
<li>{t("usageScript.fieldUnit")}</li>
|
||||
<li>{t("usageScript.fieldPlanName")}</li>
|
||||
<li>{t("usageScript.fieldTotal")}</li>
|
||||
<li>{t("usageScript.fieldUsed")}</li>
|
||||
<li>{t("usageScript.fieldExtra")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground">
|
||||
<strong>{t("usageScript.tips")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>
|
||||
{t("usageScript.tip1", {
|
||||
apiKey: "{{apiKey}}",
|
||||
baseUrl: "{{baseUrl}}",
|
||||
})}
|
||||
</li>
|
||||
<li>{t("usageScript.tip2")}</li>
|
||||
<li>{t("usageScript.tip3")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
<strong>{t("usageScript.tips")}</strong>
|
||||
<ul className="mt-1 space-y-0.5 ml-2">
|
||||
<li>
|
||||
{t("usageScript.tip1", {
|
||||
apiKey: "{{apiKey}}",
|
||||
baseUrl: "{{baseUrl}}",
|
||||
})}
|
||||
</li>
|
||||
<li>{t("usageScript.tip2")}</li>
|
||||
<li>{t("usageScript.tip3")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import UsageFooter from "@/components/UsageFooter";
|
||||
import SubscriptionQuotaFooter from "@/components/SubscriptionQuotaFooter";
|
||||
import CopilotQuotaFooter from "@/components/CopilotQuotaFooter";
|
||||
import CodexOauthQuotaFooter from "@/components/CodexOauthQuotaFooter";
|
||||
import { PROVIDER_TYPES } from "@/config/constants";
|
||||
import { PROVIDER_TYPES, TEMPLATE_TYPES } from "@/config/constants";
|
||||
import { isHermesReadOnlyProvider } from "@/config/hermesProviderPresets";
|
||||
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
|
||||
import { FailoverPriorityBadge } from "@/components/providers/FailoverPriorityBadge";
|
||||
@@ -192,6 +192,13 @@ export function ProviderCard({
|
||||
|
||||
const usageEnabled = provider.meta?.usage_script?.enabled ?? false;
|
||||
const isOfficial = isOfficialProvider(provider, appId);
|
||||
const supportsOfficialSubscription =
|
||||
isOfficial && ["claude", "codex", "gemini"].includes(appId);
|
||||
const isOfficialSubscriptionUsage =
|
||||
provider.meta?.usage_script?.templateType ===
|
||||
TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION;
|
||||
const officialSubscriptionEnabled =
|
||||
supportsOfficialSubscription && usageEnabled && isOfficialSubscriptionUsage;
|
||||
const isOfficialBlockedByProxy =
|
||||
isProxyTakeover && (provider.category === "official" || isOfficial);
|
||||
const isCopilot =
|
||||
@@ -231,7 +238,7 @@ export function ProviderCard({
|
||||
: 0;
|
||||
|
||||
const { data: usage } = useUsageQuery(provider.id, appId, {
|
||||
enabled: usageEnabled,
|
||||
enabled: usageEnabled && !isOfficial && !isOfficialSubscriptionUsage,
|
||||
autoQueryInterval,
|
||||
});
|
||||
|
||||
@@ -469,11 +476,16 @@ export function ProviderCard({
|
||||
isCurrent={isCurrent}
|
||||
/>
|
||||
) : isOfficial ? (
|
||||
<SubscriptionQuotaFooter
|
||||
appId={appId}
|
||||
inline={true}
|
||||
isCurrent={isCurrent}
|
||||
/>
|
||||
officialSubscriptionEnabled ? (
|
||||
<SubscriptionQuotaFooter
|
||||
appId={appId}
|
||||
inline={true}
|
||||
isCurrent={isCurrent}
|
||||
autoQueryInterval={
|
||||
provider.meta?.usage_script?.autoQueryInterval ?? 0
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
) : hasMultiplePlans ? (
|
||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
||||
<span className="font-medium">
|
||||
@@ -540,7 +552,9 @@ export function ProviderCard({
|
||||
: undefined
|
||||
}
|
||||
onConfigureUsage={
|
||||
isOfficial || isCopilot || isCodexOauth
|
||||
(isOfficial && !supportsOfficialSubscription) ||
|
||||
isCopilot ||
|
||||
isCodexOauth
|
||||
? undefined
|
||||
: () => onConfigureUsage(provider)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user