'use client'; import { useSearchParams } from 'next/navigation'; import { useState, useEffect, useCallback, Suspense } from 'react'; import PayPageLayout from '@/components/PayPageLayout'; import { resolveLocale, type Locale } from '@/lib/locale'; import { PlatformBadge } from '@/lib/platform-style'; /* ---------- types ---------- */ interface SubscriptionPlan { id: string; name: string; description: string | null; price: number; originalPrice: number | null; validDays: number; validityUnit: 'day' | 'week' | 'month'; features: string[]; groupId: string | null; groupName: string | null; sortOrder: number; enabled: boolean; groupExists: boolean; groupPlatform: string | null; groupRateMultiplier: number | null; groupDailyLimit: number | null; groupWeeklyLimit: number | null; groupMonthlyLimit: number | null; groupModelScopes: string[] | null; productName: string | null; groupAllowMessagesDispatch: boolean; groupDefaultMappedModel: string | null; } interface Sub2ApiGroup { id: string; name: string; subscription_type: string; daily_limit_usd: number | null; weekly_limit_usd: number | null; monthly_limit_usd: number | null; platform: string | null; rate_multiplier: number | null; allow_messages_dispatch: boolean; default_mapped_model: string | null; } interface Sub2ApiSubscription { id: number; user_id: number; group_id: number; starts_at: string; expires_at: string; status: string; daily_usage_usd: number; weekly_usage_usd: number; monthly_usage_usd: number; daily_window_start: string | null; weekly_window_start: string | null; monthly_window_start: string | null; notes: string | null; } interface SubsUserInfo { id: number; username: string; email: string; } /* ---------- i18n ---------- */ function buildText(locale: Locale) { return locale === 'en' ? { missingToken: 'Missing admin token', missingTokenHint: 'Please access the admin page from the Sub2API platform.', invalidToken: 'Invalid admin token', requestFailed: 'Request failed', title: 'Subscription Management', subtitle: 'Manage subscription plans and user subscriptions', orders: 'Order Management', dashboard: 'Dashboard', refresh: 'Refresh', loading: 'Loading...', tabPlans: 'Plan Configuration', tabSubs: 'User Subscriptions', newPlan: 'New Plan', editPlan: 'Edit Plan', deletePlan: 'Delete Plan', deleteConfirm: 'Delete this plan?', save: 'Save', cancel: 'Cancel', fieldGroup: 'Sub2API Group', fieldGroupPlaceholder: 'Select a group', fieldName: 'Plan Name', fieldDescription: 'Description', fieldPrice: 'Price (CNY)', fieldOriginalPrice: 'Original Price (CNY)', fieldValidDays: 'Validity', fieldValidUnit: 'Unit', unitDay: 'Day(s)', unitWeek: 'Week(s)', unitMonth: 'Month(s)', fieldFeatures: 'Features (one per line)', fieldSortOrder: 'Sort Order', fieldEnabled: 'For Sale', colName: 'Name', colGroup: 'Group ID', colPrice: 'Price', colOriginalPrice: 'Original Price', colValidDays: 'Validity', colEnabled: 'For Sale', colGroupStatus: 'Sub2API Status', colActions: 'Actions', edit: 'Edit', delete: 'Delete', enabled: 'Yes', disabled: 'No', groupExists: 'Exists', groupMissing: 'Missing', noPlans: 'No plans configured', searchUserId: 'Email / Username / Notes / API Key', search: 'Search', noSubs: 'No subscription records found', enterUserId: 'Enter a keyword to search users', fieldProductName: 'Payment Product Name', fieldProductNamePlaceholder: 'Leave empty for default', saveFailed: 'Failed to save plan', deleteFailed: 'Failed to delete plan', loadFailed: 'Failed to load data', days: 'days', user: 'User', group: 'Group', usage: 'Usage', expiresAt: 'Expires At', status: 'Status', active: 'Active', expired: 'Expired', suspended: 'Suspended', daily: 'Daily', weekly: 'Weekly', monthly: 'Monthly', remaining: 'remaining', unlimited: 'Unlimited', resetIn: 'Reset in', noGroup: 'Unknown Group', groupInfo: 'Sub2API Group Info', groupInfoReadonly: '(read-only, from Sub2API)', platform: 'Platform', rateMultiplier: 'Rate', dailyLimit: 'Daily Limit', weeklyLimit: 'Weekly Limit', monthlyLimit: 'Monthly Limit', modelScopes: 'Models', } : { missingToken: '缺少管理员凭证', missingTokenHint: '请从 Sub2API 平台正确访问管理页面', invalidToken: '管理员凭证无效', requestFailed: '请求失败', title: '订阅管理', subtitle: '管理订阅套餐与用户订阅', orders: '订单管理', dashboard: '数据概览', refresh: '刷新', loading: '加载中...', tabPlans: '套餐配置', tabSubs: '用户订阅', newPlan: '新建套餐', editPlan: '编辑套餐', deletePlan: '删除套餐', deleteConfirm: '确认删除该套餐?', save: '保存', cancel: '取消', fieldGroup: 'Sub2API 分组', fieldGroupPlaceholder: '请选择分组', fieldName: '套餐名称', fieldDescription: '描述', fieldPrice: '价格(元)', fieldOriginalPrice: '原价(元)', fieldValidDays: '有效期', fieldValidUnit: '单位', unitDay: '天', unitWeek: '周', unitMonth: '月', fieldFeatures: '特性描述(每行一个)', fieldSortOrder: '排序', fieldEnabled: '启用售卖', colName: '名称', colGroup: '分组 ID', colPrice: '价格', colOriginalPrice: '原价', colValidDays: '有效期', colEnabled: '启用售卖', colGroupStatus: 'Sub2API 状态', colActions: '操作', edit: '编辑', delete: '删除', enabled: '是', disabled: '否', groupExists: '存在', groupMissing: '缺失', noPlans: '暂无套餐配置', searchUserId: '邮箱/用户名/备注/API Key', search: '搜索', noSubs: '未找到订阅记录', enterUserId: '输入关键词搜索用户', fieldProductName: '支付商品名称', fieldProductNamePlaceholder: '留空使用默认名称', saveFailed: '保存套餐失败', deleteFailed: '删除套餐失败', loadFailed: '加载数据失败', days: '天', user: '用户', group: '分组', usage: '用量', expiresAt: '到期时间', status: '状态', active: '生效中', expired: '已过期', suspended: '已暂停', daily: '日用量', weekly: '周用量', monthly: '月用量', remaining: '剩余', unlimited: '无限制', resetIn: '重置于', noGroup: '未知分组', groupInfo: 'Sub2API 分组信息', groupInfoReadonly: '(只读,来自 Sub2API)', platform: '平台', rateMultiplier: '倍率', dailyLimit: '日限额', weeklyLimit: '周限额', monthlyLimit: '月限额', modelScopes: '模型', }; } /* ---------- helpers ---------- */ function formatDate(dateStr: string | null): string { if (!dateStr) return '-'; const d = new Date(dateStr); return d.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); } function daysRemaining(expiresAt: string | null): number | null { if (!expiresAt) return null; const now = new Date(); const exp = new Date(expiresAt); const diff = exp.getTime() - now.getTime(); return Math.ceil(diff / (1000 * 60 * 60 * 24)); } function resetCountdown(windowStart: string | null, periodDays: number): string | null { if (!windowStart) return null; const start = new Date(windowStart); const resetAt = new Date(start.getTime() + periodDays * 24 * 60 * 60 * 1000); const now = new Date(); const diffMs = resetAt.getTime() - now.getTime(); if (diffMs <= 0) return null; const hours = Math.floor(diffMs / (1000 * 60 * 60)); const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); if (hours >= 24) { const d = Math.floor(hours / 24); const h = hours % 24; return `${d}d ${h}h`; } return `${hours}h ${minutes}m`; } /* ---------- UsageBar component ---------- */ function UsageBar({ label, usage, limit, resetText, isDark, }: { label: string; usage: number; limit: number | null; resetText: string | null; isDark: boolean; }) { const pct = limit && limit > 0 ? Math.min((usage / limit) * 100, 100) : 0; const barColor = pct > 80 ? 'bg-red-500' : pct > 50 ? 'bg-yellow-500' : 'bg-green-500'; return (
{label} ${usage.toFixed(2)} {limit != null ? `/ $${limit.toFixed(2)}` : ''}
{limit != null && limit > 0 ? (
) : null} {resetText && (
{resetText}
)}
); } /* ---------- main content ---------- */ function SubscriptionsContent() { const searchParams = useSearchParams(); const token = searchParams.get('token') || ''; const theme = searchParams.get('theme') === 'dark' ? 'dark' : 'light'; const locale = resolveLocale(searchParams.get('lang')); const isDark = theme === 'dark'; const uiMode = searchParams.get('ui_mode') || 'standalone'; const isEmbedded = uiMode === 'embedded'; const t = buildText(locale); /* --- shared state --- */ const [activeTab, setActiveTab] = useState<'plans' | 'subs'>('plans'); const [error, setError] = useState(''); /* --- plans state --- */ const [plans, setPlans] = useState([]); const [groups, setGroups] = useState([]); const [plansLoading, setPlansLoading] = useState(true); const [modalOpen, setModalOpen] = useState(false); const [editingPlan, setEditingPlan] = useState(null); /* form state */ const [formGroupId, setFormGroupId] = useState(''); const [formName, setFormName] = useState(''); const [formDescription, setFormDescription] = useState(''); const [formPrice, setFormPrice] = useState(''); const [formOriginalPrice, setFormOriginalPrice] = useState(''); const [formValidDays, setFormValidDays] = useState('30'); const [formValidUnit, setFormValidUnit] = useState<'day' | 'week' | 'month'>('day'); const [formFeatures, setFormFeatures] = useState(''); const [formSortOrder, setFormSortOrder] = useState('0'); const [formEnabled, setFormEnabled] = useState(true); const [formProductName, setFormProductName] = useState(''); const [saving, setSaving] = useState(false); /* --- subs state --- */ const [subsUserId, setSubsUserId] = useState(''); const [subsKeyword, setSubsKeyword] = useState(''); const [searchResults, setSearchResults] = useState<{ id: number; email: string; username: string; notes?: string }[]>( [], ); const [searchDropdownOpen, setSearchDropdownOpen] = useState(false); const [searchTimer, setSearchTimer] = useState | null>(null); const [subs, setSubs] = useState([]); const [subsUser, setSubsUser] = useState(null); const [subsLoading, setSubsLoading] = useState(false); const [subsSearched, setSubsSearched] = useState(false); /* --- fetch plans --- */ const fetchPlans = useCallback(async () => { if (!token) return; setPlansLoading(true); try { const res = await fetch(`/api/admin/subscription-plans?token=${encodeURIComponent(token)}`); if (!res.ok) { if (res.status === 401) { setError(t.invalidToken); return; } throw new Error(t.requestFailed); } const data = await res.json(); setPlans(Array.isArray(data) ? data : (data.plans ?? [])); } catch { setError(t.loadFailed); } finally { setPlansLoading(false); } }, [token]); /* --- fetch groups --- */ const fetchGroups = useCallback(async () => { if (!token) return; try { const res = await fetch(`/api/admin/sub2api/groups?token=${encodeURIComponent(token)}`); if (res.ok) { const data = await res.json(); setGroups(Array.isArray(data) ? data : (data.groups ?? [])); } } catch { /* ignore */ } }, [token]); useEffect(() => { fetchPlans(); fetchGroups(); }, [fetchPlans, fetchGroups]); /* auto-fetch subs when switching to subs tab */ useEffect(() => { if (activeTab === 'subs' && !subsSearched) { fetchSubs(); } }, [activeTab]); /* --- modal helpers --- */ const openCreate = () => { setEditingPlan(null); setFormGroupId(''); setFormName(''); setFormDescription(''); setFormPrice(''); setFormOriginalPrice(''); setFormValidDays('1'); setFormValidUnit('month'); setFormFeatures(''); setFormSortOrder('0'); setFormEnabled(true); setFormProductName(''); setModalOpen(true); }; const openEdit = (plan: SubscriptionPlan) => { setEditingPlan(plan); setFormGroupId(plan.groupId ?? ''); setFormName(plan.name); setFormDescription(plan.description ?? ''); setFormPrice(String(plan.price)); setFormOriginalPrice(plan.originalPrice != null ? String(plan.originalPrice) : ''); setFormValidDays(String(plan.validDays)); setFormValidUnit(plan.validityUnit ?? 'day'); setFormFeatures((plan.features ?? []).join('\n')); setFormSortOrder(String(plan.sortOrder)); setFormEnabled(plan.enabled); setFormProductName(plan.productName ?? ''); setModalOpen(true); }; const closeModal = () => { setModalOpen(false); setEditingPlan(null); }; /* --- save plan (snake_case for backend) --- */ const handleSave = async () => { if (!formName.trim() || !formPrice || !formGroupId) return; setSaving(true); setError(''); const body = { group_id: Number(formGroupId), name: formName.trim(), description: formDescription.trim() || null, price: parseFloat(formPrice), original_price: formOriginalPrice ? parseFloat(formOriginalPrice) : null, validity_days: parseInt(formValidDays, 10) || 30, validity_unit: formValidUnit, features: formFeatures .split('\n') .map((l) => l.trim()) .filter(Boolean), sort_order: parseInt(formSortOrder, 10) || 0, for_sale: formEnabled, product_name: formProductName.trim() || null, }; try { const url = editingPlan ? `/api/admin/subscription-plans/${editingPlan.id}` : '/api/admin/subscription-plans'; const method = editingPlan ? 'PUT' : 'POST'; const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify(body), }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || t.saveFailed); } closeModal(); fetchPlans(); } catch (e) { // 分组被删除等错误:刷新列表使前端状态同步 setError(e instanceof Error ? e.message : t.saveFailed); fetchPlans(); } finally { setSaving(false); } }; /* --- delete plan --- */ const handleDelete = async (plan: SubscriptionPlan) => { if (!confirm(t.deleteConfirm)) return; try { const res = await fetch(`/api/admin/subscription-plans/${plan.id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || t.deleteFailed); } fetchPlans(); } catch (e) { setError(e instanceof Error ? e.message : t.deleteFailed); } }; /* --- toggle plan enabled --- */ const handleToggleEnabled = async (plan: SubscriptionPlan) => { try { const res = await fetch(`/api/admin/subscription-plans/${plan.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ for_sale: !plan.enabled }), }); if (res.ok) { setPlans((prev) => prev.map((p) => (p.id === plan.id ? { ...p, enabled: !p.enabled } : p))); } } catch { /* ignore */ } }; /* --- search users (R1) --- */ const handleKeywordChange = (value: string) => { setSubsKeyword(value); if (searchTimer) clearTimeout(searchTimer); if (!value.trim()) { setSubsUserId(''); setSearchResults([]); setSearchDropdownOpen(false); return; } const timer = setTimeout(async () => { try { const res = await fetch( `/api/admin/sub2api/search-users?token=${encodeURIComponent(token)}&keyword=${encodeURIComponent(value.trim())}`, ); if (res.ok) { const data = await res.json(); setSearchResults(data.users ?? []); setSearchDropdownOpen(true); } } catch { /* ignore */ } }, 300); setSearchTimer(timer); }; const selectUser = (user: { id: number; email: string; username: string }) => { setSubsUserId(String(user.id)); setSubsKeyword(`${user.email} #${user.id}`); setSearchDropdownOpen(false); setSearchResults([]); }; /* --- fetch user subs --- */ const fetchSubs = async () => { if (!token) return; setSubsLoading(true); setSubsSearched(true); setSubsUser(null); try { const qs = new URLSearchParams({ token }); if (subsUserId.trim()) qs.set('user_id', subsUserId.trim()); const res = await fetch(`/api/admin/subscriptions?${qs}`); if (!res.ok) { if (res.status === 401) { setError(t.invalidToken); return; } throw new Error(t.requestFailed); } const data = await res.json(); setSubs(data.subscriptions ?? []); setSubsUser(data.user ?? null); } catch { setError(t.loadFailed); } finally { setSubsLoading(false); } }; /* --- no token guard --- */ if (!token) { return (

{t.missingToken}

{t.missingTokenHint}

); } /* --- nav params --- */ const navParams = new URLSearchParams(); navParams.set('token', token); if (locale === 'en') navParams.set('lang', 'en'); if (isDark) navParams.set('theme', 'dark'); if (isEmbedded) navParams.set('ui_mode', 'embedded'); const btnBase = [ 'inline-flex items-center rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors', isDark ? 'border-slate-600 text-slate-200 hover:bg-slate-800' : 'border-slate-300 text-slate-700 hover:bg-slate-100', ].join(' '); /* available groups for the form: only subscription type, exclude already used */ const subscriptionGroups = groups.filter((g) => g.subscription_type === 'subscription'); const usedGroupIds = new Set( plans.filter((p) => p.id !== editingPlan?.id && p.groupId != null).map((p) => p.groupId!), ); const availableGroups = subscriptionGroups.filter((g) => !usedGroupIds.has(String(g.id))); /* group id → name map (all groups, for subscription display) */ const groupNameMap = new Map(groups.map((g) => [String(g.id), g.name])); /* --- tab classes --- */ const tabCls = (active: boolean) => [ 'flex-1 rounded-lg py-2 text-center text-sm font-medium transition-colors cursor-pointer', active ? isDark ? 'bg-indigo-500/30 text-indigo-200 ring-1 ring-indigo-400/40' : 'bg-blue-600 text-white' : isDark ? 'text-slate-400 hover:text-slate-200' : 'text-slate-600 hover:text-slate-800', ].join(' '); /* --- table cell style --- */ const thCls = [ 'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider', isDark ? 'text-slate-400' : 'text-slate-500', ].join(' '); const tdCls = ['px-4 py-3 text-sm', isDark ? 'text-slate-300' : 'text-slate-700'].join(' '); const tableWrapCls = [ 'overflow-x-auto rounded-xl border', isDark ? 'border-slate-700 bg-slate-800/70' : 'border-slate-200 bg-white shadow-sm', ].join(' '); const rowBorderCls = isDark ? 'border-slate-700/50' : 'border-slate-100'; /* --- input classes --- */ const inputCls = [ 'w-full rounded-lg border px-3 py-2 text-sm outline-none transition-colors', isDark ? 'border-slate-600 bg-slate-700 text-slate-200 focus:border-indigo-400' : 'border-slate-300 bg-white text-slate-800 focus:border-blue-500', ].join(' '); const labelCls = ['block text-sm font-medium mb-1', isDark ? 'text-slate-300' : 'text-slate-700'].join(' '); /* --- status badge --- */ const statusBadge = (status: string) => { const map: Record = { active: { label: t.active, cls: isDark ? 'bg-green-500/20 text-green-300' : 'bg-green-50 text-green-700', }, expired: { label: t.expired, cls: isDark ? 'bg-red-500/20 text-red-300' : 'bg-red-50 text-red-600', }, suspended: { label: t.suspended, cls: isDark ? 'bg-yellow-500/20 text-yellow-300' : 'bg-yellow-50 text-yellow-700', }, }; const info = map[status] ?? { label: status, cls: isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-100 text-gray-500', }; return ( {info.label} ); }; return ( {t.orders} {t.dashboard} } > {/* Error banner */} {error && (
{error}
)} {/* Tab switcher */}
{/* ====== Tab: Plan Configuration ====== */} {activeTab === 'plans' && ( <> {/* New plan button */}
{/* Plans cards */} {plansLoading ? (
{t.loading}
) : plans.length === 0 ? (
{t.noPlans}
) : (
{plans.map((plan) => (
{/* ── 套餐配置(上半部分) ── */}

{plan.name}

{plan.groupExists ? t.groupExists : t.groupMissing}
{/* Toggle */}
{t.colEnabled}
{/* Actions */}
{/* Plan fields grid */}
{t.colGroup}
{plan.groupId ? ( <> {plan.groupId} {plan.groupName && ( ({plan.groupName}) )} ) : ( {locale === 'en' ? 'Unbound' : '未绑定'} )}
{t.colPrice}
¥{plan.price.toFixed(2)} {plan.originalPrice != null && ( ¥{plan.originalPrice.toFixed(2)} )}
{t.colValidDays}
{plan.validDays}{' '} {plan.validityUnit === 'month' ? t.unitMonth : plan.validityUnit === 'week' ? t.unitWeek : t.unitDay}
{t.fieldSortOrder}
{plan.sortOrder}
{/* ── Sub2API 分组信息(嵌套只读区域) ── */} {plan.groupExists && (
{t.groupInfo} {t.groupInfoReadonly}
{plan.groupPlatform && (
{t.platform}
)} {plan.groupRateMultiplier != null && (
{t.rateMultiplier}
{plan.groupRateMultiplier}x
)}
{t.dailyLimit}
{plan.groupDailyLimit != null ? `$${plan.groupDailyLimit}` : t.unlimited}
{t.weeklyLimit}
{plan.groupWeeklyLimit != null ? `$${plan.groupWeeklyLimit}` : t.unlimited}
{t.monthlyLimit}
{plan.groupMonthlyLimit != null ? `$${plan.groupMonthlyLimit}` : t.unlimited}
{plan.groupPlatform?.toLowerCase() === 'openai' && ( <>
/v1/messages 调度
{plan.groupAllowMessagesDispatch ? '已启用' : '未启用'}
{plan.groupDefaultMappedModel && (
默认模型
{plan.groupDefaultMappedModel}
)} )}
)}
))}
)} )} {/* ====== Tab: User Subscriptions ====== */} {activeTab === 'subs' && ( <> {/* Search bar (R1: fuzzy search) */}
handleKeywordChange(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { setSearchDropdownOpen(false); fetchSubs(); } }} onFocus={() => { if (searchResults.length > 0) setSearchDropdownOpen(true); }} placeholder={t.searchUserId} className={inputCls} /> {/* Dropdown */} {searchDropdownOpen && searchResults.length > 0 && (
{searchResults.map((u) => ( ))}
)}
{/* User info card */} {subsUser && (
{(subsUser.email?.[0] ?? subsUser.username?.[0] ?? '?').toUpperCase()}
{subsUser.username}
{subsUser.email}
ID: {subsUser.id}
)} {/* Subs list */}
{subsLoading ? (
{t.loading}
) : !subsSearched ? (
{t.loading}
) : subs.length === 0 ? (
{t.noSubs}
) : ( {subs.map((sub) => { const gName = groupNameMap.get(String(sub.group_id)) ?? t.noGroup; const remaining = daysRemaining(sub.expires_at); const group = groups.find((g) => String(g.id) === String(sub.group_id)); const dailyLimit = group?.daily_limit_usd ?? null; const weeklyLimit = group?.weekly_limit_usd ?? null; const monthlyLimit = group?.monthly_limit_usd ?? null; return ( {/* Group */} {/* Status */} {/* Usage */} {/* Expires */} ); })}
{t.group} {t.status} {t.usage} {t.expiresAt}
{gName}
ID: {sub.group_id}
{statusBadge(sub.status)}
{formatDate(sub.expires_at)}
{remaining != null && (
{remaining > 0 ? `${remaining} ${t.days} ${t.remaining}` : t.expired}
)}
)}
)} {/* ====== Edit / Create Modal ====== */} {modalOpen && (

{editingPlan ? t.editPlan : t.newPlan}

{/* Group */}
{/* Selected group info card (read-only) */} {(() => { const selectedGroup = groups.find((g) => String(g.id) === formGroupId); if (!selectedGroup) return null; return (
{t.groupInfo} {t.groupInfoReadonly}
{selectedGroup.platform && (
{t.platform}
)} {selectedGroup.rate_multiplier != null && (
{t.rateMultiplier}
{selectedGroup.rate_multiplier}x
)}
{t.dailyLimit}
{selectedGroup.daily_limit_usd != null ? `$${selectedGroup.daily_limit_usd}` : t.unlimited}
{t.weeklyLimit}
{selectedGroup.weekly_limit_usd != null ? `$${selectedGroup.weekly_limit_usd}` : t.unlimited}
{t.monthlyLimit}
{selectedGroup.monthly_limit_usd != null ? `$${selectedGroup.monthly_limit_usd}` : t.unlimited}
{selectedGroup.platform?.toLowerCase() === 'openai' && (
/v1/messages 调度
{selectedGroup.allow_messages_dispatch ? '已启用' : '未启用'}
)}
); })()} {/* Name */}
setFormName(e.target.value)} className={inputCls} required />
{/* Description */}