diff --git a/controller/subscription.go b/controller/subscription.go index c6095312b77..72eccdaec9b 100644 --- a/controller/subscription.go +++ b/controller/subscription.go @@ -239,6 +239,8 @@ func AdminUpdateSubscriptionPlan(c *gin.Context) { "upgrade_group": req.Plan.UpgradeGroup, "quota_reset_period": req.Plan.QuotaResetPeriod, "quota_reset_custom_seconds": req.Plan.QuotaResetCustomSeconds, + "model_limits_enabled": req.Plan.ModelLimitsEnabled, + "model_limits": req.Plan.ModelLimits, "updated_at": common.GetTimestamp(), } if err := tx.Model(&model.SubscriptionPlan{}).Where("id = ?", id).Updates(updateMap).Error; err != nil { diff --git a/model/main.go b/model/main.go index f37cb667cd4..d482eebd90e 100644 --- a/model/main.go +++ b/model/main.go @@ -402,6 +402,8 @@ func ensureSubscriptionPlanTableSQLite() error { ` + "`total_amount`" + ` bigint NOT NULL DEFAULT 0, ` + "`quota_reset_period`" + ` varchar(16) DEFAULT 'never', ` + "`quota_reset_custom_seconds`" + ` bigint DEFAULT 0, +` + "`model_limits_enabled`" + ` numeric DEFAULT 0, +` + "`model_limits`" + ` text DEFAULT '', ` + "`created_at`" + ` bigint, ` + "`updated_at`" + ` bigint, PRIMARY KEY (` + "`id`" + `) @@ -435,6 +437,8 @@ PRIMARY KEY (` + "`id`" + `) {Name: "total_amount", DDL: "`total_amount` bigint NOT NULL DEFAULT 0"}, {Name: "quota_reset_period", DDL: "`quota_reset_period` varchar(16) DEFAULT 'never'"}, {Name: "quota_reset_custom_seconds", DDL: "`quota_reset_custom_seconds` bigint DEFAULT 0"}, + {Name: "model_limits_enabled", DDL: "`model_limits_enabled` numeric DEFAULT 0"}, + {Name: "model_limits", DDL: "`model_limits` text DEFAULT ''"}, {Name: "created_at", DDL: "`created_at` bigint"}, {Name: "updated_at", DDL: "`updated_at` bigint"}, } diff --git a/model/subscription.go b/model/subscription.go index da8fdae9410..b6e78f2d88e 100644 --- a/model/subscription.go +++ b/model/subscription.go @@ -175,10 +175,52 @@ type SubscriptionPlan struct { QuotaResetPeriod string `json:"quota_reset_period" gorm:"type:varchar(16);default:'never'"` QuotaResetCustomSeconds int64 `json:"quota_reset_custom_seconds" gorm:"type:bigint;default:0"` + // Model limits: restrict which models can be used with this plan + ModelLimitsEnabled bool `json:"model_limits_enabled" gorm:"default:false"` + ModelLimits string `json:"model_limits" gorm:"type:text"` + CreatedAt int64 `json:"created_at" gorm:"bigint"` UpdatedAt int64 `json:"updated_at" gorm:"bigint"` } +// GetModelLimits returns the model limits as a slice. +func (p *SubscriptionPlan) GetModelLimits() []string { + if p.ModelLimits == "" { + return []string{} + } + parts := strings.Split(p.ModelLimits, ",") + result := make([]string, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + result = append(result, trimmed) + } + } + return result +} + +// GetModelLimitsMap returns the model limits as a map for fast lookup. +func (p *SubscriptionPlan) GetModelLimitsMap() map[string]bool { + limits := p.GetModelLimits() + limitsMap := make(map[string]bool, len(limits)) + for _, limit := range limits { + if limit != "" { + limitsMap[limit] = true + } + } + return limitsMap +} + +// IsModelAllowed checks if a model is allowed by this plan's model limits. +// Returns true if model limits are disabled or if the model is in the allowed list. +func (p *SubscriptionPlan) IsModelAllowed(modelName string) bool { + if !p.ModelLimitsEnabled || p.ModelLimits == "" { + return true + } + limitsMap := p.GetModelLimitsMap() + return limitsMap[modelName] +} + func (p *SubscriptionPlan) BeforeCreate(tx *gorm.DB) error { now := common.GetTimestamp() p.CreatedAt = now @@ -1013,12 +1055,20 @@ func PreConsumeUserSubscription(requestId string, userId int, modelName string, if len(subs) == 0 { return errors.New("no active subscription") } + anyPlanAllowsModel := false for _, candidate := range subs { sub := candidate plan, err := getSubscriptionPlanByIdTx(tx, sub.PlanId) if err != nil { return err } + // Check model limits: skip subscriptions whose plan doesn't allow the requested model + if modelName != "" && !plan.IsModelAllowed(modelName) { + continue + } + if modelName != "" { + anyPlanAllowsModel = true + } if err := maybeResetUserSubscriptionWithPlanTx(tx, &sub, plan, now); err != nil { return err } @@ -1062,6 +1112,9 @@ func PreConsumeUserSubscription(requestId string, userId int, modelName string, returnValue.AmountUsedAfter = sub.AmountUsed return nil } + if modelName != "" && !anyPlanAllowsModel { + return fmt.Errorf("no subscription allows model %s", modelName) + } return fmt.Errorf("subscription quota insufficient, need=%d", amount) }) if err != nil { diff --git a/web/classic/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx b/web/classic/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx index 222b05126ca..c0009593d49 100644 --- a/web/classic/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx +++ b/web/classic/src/components/table/subscriptions/SubscriptionsColumnDefs.jsx @@ -101,6 +101,17 @@ const renderPlanTitle = (text, record, t) => { {formatDuration(plan, t)} {t('重置')} {formatResetPeriod(plan, t)} + {t('模型限制')} + + {plan?.model_limits_enabled && plan?.model_limits + ? `${new Set( + plan.model_limits + .split(',') + .map((item) => item.trim()) + .filter(Boolean), + ).size} ${t('个模型')}` + : t('不限')} + ); diff --git a/web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx b/web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx index 73aef7317ad..6070963edb2 100644 --- a/web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx +++ b/web/classic/src/components/table/subscriptions/modals/AddEditSubscriptionModal.jsx @@ -17,7 +17,7 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useState, useRef, useCallback } from 'react'; import { Avatar, Button, @@ -36,10 +36,17 @@ import { IconCalendarClock, IconClose, IconCreditCard, + IconLink, IconSave, } from '@douyinfe/semi-icons'; -import { Clock, RefreshCw } from 'lucide-react'; -import { API, showError, showSuccess } from '../../../../helpers'; +import { AlertCircle, Clock, RefreshCw } from 'lucide-react'; +import { + API, + showError, + showSuccess, + getModelCategories, + selectFilter, +} from '../../../../helpers'; import { quotaToDisplayAmount, displayAmountToQuota, @@ -75,6 +82,9 @@ const AddEditSubscriptionModal = ({ const [loading, setLoading] = useState(false); const [groupOptions, setGroupOptions] = useState([]); const [groupLoading, setGroupLoading] = useState(false); + const [models, setModels] = useState([]); + const [modelsLoading, setModelsLoading] = useState(false); + const [modelsLoadError, setModelsLoadError] = useState(false); const isMobile = useIsMobile(); const formApiRef = useRef(null); const isEdit = editingPlan?.plan?.id !== undefined; @@ -97,12 +107,20 @@ const AddEditSubscriptionModal = ({ upgrade_group: '', stripe_price_id: '', creem_product_id: '', + model_limits: [], }); const buildFormValues = () => { const base = getInitValues(); if (editingPlan?.plan?.id === undefined) return base; const p = editingPlan.plan || {}; + let modelLimits = []; + if (p.model_limits_enabled && p.model_limits && p.model_limits !== '') { + modelLimits = p.model_limits + .split(',') + .map((m) => m.trim()) + .filter(Boolean); + } return { ...base, title: p.title || '', @@ -123,9 +141,48 @@ const AddEditSubscriptionModal = ({ upgrade_group: p.upgrade_group || '', stripe_price_id: p.stripe_price_id || '', creem_product_id: p.creem_product_id || '', + model_limits: modelLimits, }; }; + const loadModels = useCallback(async () => { + setModelsLoading(true); + setModelsLoadError(false); + try { + const res = await API.get('/api/channel/models_enabled'); + const { success, data } = res.data; + if (success && Array.isArray(data)) { + const categories = getModelCategories(t); + const localModelOptions = data.map((model) => { + let icon = null; + for (const [key, category] of Object.entries(categories)) { + if (key !== 'all' && category.filter({ model_name: model })) { + icon = category.icon; + break; + } + } + return { + label: ( + + {icon} + {model} + + ), + value: model, + }; + }); + setModels(localModelOptions); + } else { + setModelsLoadError(true); + } + } catch (error) { + console.error('Failed to load models:', error); + setModelsLoadError(true); + } finally { + setModelsLoading(false); + } + }, [t]); + useEffect(() => { if (!visible) return; setGroupLoading(true); @@ -139,15 +196,21 @@ const AddEditSubscriptionModal = ({ }) .catch(() => setGroupOptions([])) .finally(() => setGroupLoading(false)); - }, [visible]); + loadModels(); + }, [visible, loadModels, t]); const submit = async (values) => { + if (modelsLoadError) { + showError(t('模型列表加载失败,无法提交,请重试')); + return; + } if (!values.title || values.title.trim() === '') { showError(t('套餐标题不能为空')); return; } setLoading(true); try { + const modelLimitsStr = (values.model_limits || []).join(','); const payload = { plan: { ...values, @@ -164,6 +227,8 @@ const AddEditSubscriptionModal = ({ max_purchase_per_user: Number(values.max_purchase_per_user || 0), total_amount: displayAmountToQuota(values.total_amount), upgrade_group: values.upgrade_group || '', + model_limits_enabled: modelLimitsStr.length > 0, + model_limits: modelLimitsStr, }, }; if (editingPlan?.plan?.id) { @@ -226,6 +291,7 @@ const AddEditSubscriptionModal = ({ onClick={() => formApiRef.current?.submitForm()} icon={} loading={loading} + disabled={modelsLoading || modelsLoadError} > {t('提交')} @@ -501,6 +567,70 @@ const AddEditSubscriptionModal = ({ + {/* 模型限制 */} + +
+ + + +
+ + {t('模型限制')} + +
+ {t('限制该订阅套餐可使用的模型范围')} +
+
+
+ + + + {modelsLoadError && ( +
+
+ + + {t('模型列表加载失败,请重试')} + +
+ +
+ )} + + +
+
+ {/* 第三方支付配置 */}
diff --git a/web/classic/src/components/topup/SubscriptionPlansCard.jsx b/web/classic/src/components/topup/SubscriptionPlansCard.jsx index 9c50828372b..9d1f87aa058 100644 --- a/web/classic/src/components/topup/SubscriptionPlansCard.jsx +++ b/web/classic/src/components/topup/SubscriptionPlansCard.jsx @@ -510,6 +510,17 @@ const SubscriptionPlansCard = ({ formatSubscriptionResetPeriod(plan, t) === t('不重置') ? null : `${t('额度重置')}: ${formatSubscriptionResetPeriod(plan, t)}`; + const modelLimitNames = + plan?.model_limits_enabled && plan?.model_limits + ? plan.model_limits + .split(',') + .map((model) => model.trim()) + .filter(Boolean) + : []; + const modelLimitsLabel = + modelLimitNames.length > 0 + ? `${t('可用模型')}: ${modelLimitNames.length} ${t('个模型')}` + : null; const planBenefits = [ { label: `${t('有效期')}: ${formatSubscriptionDuration(plan, t)}`, @@ -523,6 +534,12 @@ const SubscriptionPlansCard = ({ : { label: totalLabel }, limitLabel ? { label: limitLabel } : null, upgradeLabel ? { label: upgradeLabel } : null, + modelLimitsLabel + ? { + label: modelLimitsLabel, + tooltip: modelLimitNames.join(', '), + } + : null, ].filter(Boolean); return ( diff --git a/web/classic/src/i18n/locales/en.json b/web/classic/src/i18n/locales/en.json index 829a9e4b2a3..39cb7fdd672 100644 --- a/web/classic/src/i18n/locales/en.json +++ b/web/classic/src/i18n/locales/en.json @@ -2151,6 +2151,7 @@ "模型配置": "Model Configuration", "模型重定向": "Model mapping", "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:": "The following models from the redirect have not been added to the “Models” list and requests will fail due to no available model:", + "模型限制": "Model Restrictions", "模型限制列表": "Model restrictions list", "模式": "Mode", "模板": "Template", @@ -3231,6 +3232,7 @@ "请选择至少一个部署位置": "Please select at least one deployment location", "请选择订阅套餐": "Please select a subscription plan", "请选择该令牌支持的模型,留空支持所有模型": "Select models supported by the token, leave blank to support all models", + "请选择该套餐支持的模型,留空支持所有模型": "Select models allowed by this plan, leave empty for all models", "请选择该渠道所支持的模型": "Please select the model supported by this channel", "请选择该渠道所支持的模型,留空则不更改": "Please select the models supported by the channel, leaving blank will not change", "请选择过期时间": "Please select expiration time", @@ -3388,6 +3390,7 @@ "选择同步来源": "Select sync source", "选择同步渠道": "Select synchronization channel", "选择同步语言": "Select sync language", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "When set, requests billed via this subscription can only use selected models; leave empty for no restriction", "选择容器": "Select Container", "选择您的首选界面语言,设置将自动保存并同步到所有设备": "Select your preferred interface language. Settings will be saved automatically and synced across all devices", "选择成功": "Selection successful", @@ -3563,6 +3566,7 @@ "降级": "Demote", "限制周期": "Limit period", "限制周期统一使用上方配置的“限制周期”值。": "The limit period uniformly uses the \"limit period\" value configured above.", + "限制该订阅套餐可使用的模型范围": "Restrict models available for this subscription plan", "限流": "Rate Limiting", "限购": "Limit", "隐私政策": "Privacy Policy", diff --git a/web/classic/src/i18n/locales/fr.json b/web/classic/src/i18n/locales/fr.json index e433dc8f72e..28476966475 100644 --- a/web/classic/src/i18n/locales/fr.json +++ b/web/classic/src/i18n/locales/fr.json @@ -2142,6 +2142,7 @@ "模型配置": "Configuration du modèle", "模型重定向": "Redirection de modèle", "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:": "Les modèles suivants provenant de la redirection n'ont pas été ajoutés à la liste « Modèles », l'appel échouera faute de modèle disponible :", + "模型限制": "Restrictions de modèle", "模型限制列表": "Liste des restrictions de modèle", "模式": "Mode", "模板": "Modèle", @@ -3216,6 +3217,7 @@ "请选择至少一个部署位置": "Please select at least one deployment location", "请选择订阅套餐": "Veuillez sélectionner un forfait d'abonnement", "请选择该令牌支持的模型,留空支持所有模型": "Sélectionnez les modèles pris en charge par le jeton, laissez vide pour prendre en charge tous les modèles", + "请选择该套餐支持的模型,留空支持所有模型": "Veuillez sélectionner les modèles pris en charge par ce plan, laissez vide pour tous les modèles", "请选择该渠道所支持的模型": "Veuillez sélectionner le modèle pris en charge par ce canal", "请选择该渠道所支持的模型,留空则不更改": "Veuillez sélectionner les modèles pris en charge par le canal, laisser vide ne changera rien", "请选择过期时间": "Veuillez sélectionner une date d'expiration", @@ -3368,6 +3370,7 @@ "选择同步来源": "Sélectionner la source de synchronisation", "选择同步渠道": "Sélectionner le canal de synchronisation", "选择同步语言": "Sélectionner la langue de synchronisation", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "Une fois sélectionnés, les modèles facturés via ce forfait d’abonnement ne peuvent être que ceux sélectionnés ; laissez vide pour ne pas imposer de restriction", "选择容器": "Select Container", "选择您的首选界面语言,设置将自动保存并同步到所有设备": "Sélectionnez votre langue d'interface préférée, les paramètres seront automatiquement enregistrés et synchronisés sur tous les appareils", "选择成功": "Sélection réussie", @@ -3541,6 +3544,7 @@ "降级": "Rétrograder", "限制周期": "Période de limite", "限制周期统一使用上方配置的“限制周期”值。": "La période de limite utilise uniformément la valeur \"période de limite\" configurée ci-dessus.", + "限制该订阅套餐可使用的模型范围": "Restreindre les modèles disponibles pour ce plan d'abonnement", "限流": "Limitation de débit", "限购": "Limite", "隐私政策": "Politique de confidentialité", diff --git a/web/classic/src/i18n/locales/ja.json b/web/classic/src/i18n/locales/ja.json index 5cfa0a2615f..c85e78dcaf6 100644 --- a/web/classic/src/i18n/locales/ja.json +++ b/web/classic/src/i18n/locales/ja.json @@ -2113,6 +2113,7 @@ "模型配置": "モデル設定", "模型重定向": "モデルマッピング", "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:": "The following models from the redirect have not been added to the “Models” list and requests will fail due to no available model:", + "模型限制": "モデルの制限", "模型限制列表": "モデル制限リスト", "模式": "モード", "模板": "テンプレート", @@ -3185,6 +3186,7 @@ "请选择至少一个部署位置": "Please select at least one deployment location", "请选择订阅套餐": "サブスクリプションプランを選択してください", "请选择该令牌支持的模型,留空支持所有模型": "対応モデルを選択してください。空欄の場合は全モデルに対応します。", + "请选择该套餐支持的模型,留空支持所有模型": "このプランでサポートされているモデルを選択してください。すべてのモデルをサポートする場合は空白のままにしてください", "请选择该渠道所支持的模型": "このチャネルでサポートされているモデルを選択してください", "请选择该渠道所支持的模型,留空则不更改": "このチャネルに対応しているモデルを選択してください。空欄の場合は変更されません", "请选择过期时间": "有効期限を選択してください", @@ -3337,6 +3339,7 @@ "选择同步来源": "同期ソースを選択", "选择同步渠道": "同期チャネルを選択", "选择同步语言": "同期する言語を選択", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "選択後、このサブスクリプションプランで課金されるリクエストは選択したモデルのみを使用できます。空白のままにすると制限はありません", "选择容器": "Select Container", "选择您的首选界面语言,设置将自动保存并同步到所有设备": "お好みのインターフェース言語を選択してください。設定は自動的に保存され、すべてのデバイスに同期されます", "选择成功": "選択に成功しました", @@ -3510,6 +3513,7 @@ "降级": "降格", "限制周期": "制限期間", "限制周期统一使用上方配置的“限制周期”值。": "制限期間は、一律で上記にて設定された「制限期間」の値を使用します。", + "限制该订阅套餐可使用的模型范围": "このサブスクリプションプランで利用可能なモデルを制限する", "限流": "レート制限", "限购": "購入制限", "隐私政策": "プライバシーポリシー", diff --git a/web/classic/src/i18n/locales/ru.json b/web/classic/src/i18n/locales/ru.json index f4950bfeed8..fe524d94643 100644 --- a/web/classic/src/i18n/locales/ru.json +++ b/web/classic/src/i18n/locales/ru.json @@ -2160,6 +2160,7 @@ "模型配置": "Конфигурация модели", "模型重定向": "Перенаправление модели", "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:": "Следующие модели из перенаправления ещё не добавлены в список «Модели», из-за отсутствия доступных моделей вызовы завершатся ошибкой:", + "模型限制": "Ограничения модели", "模型限制列表": "Список ограничений модели", "模式": "Режим", "模板": "Шаблон", @@ -3236,6 +3237,7 @@ "请选择至少一个部署位置": "Please select at least one deployment location", "请选择订阅套餐": "Выберите план подписки", "请选择该令牌支持的模型,留空支持所有模型": "Пожалуйста, выберите модели, поддерживаемые этим токеном, оставьте пустым для поддержки всех моделей", + "请选择该套餐支持的模型,留空支持所有模型": "Пожалуйста, выберите модели, поддерживаемые этим планом, оставьте пустым для всех моделей", "请选择该渠道所支持的模型": "Пожалуйста, выберите модели, поддерживаемые этим каналом", "请选择该渠道所支持的模型,留空则不更改": "Пожалуйста, выберите модели, поддерживаемые этим каналом, оставьте пустым для без изменений", "请选择过期时间": "Пожалуйста, выберите время истечения", @@ -3388,6 +3390,7 @@ "选择同步来源": "Выберите источник синхронизации", "选择同步渠道": "Выберите канал синхронизации", "选择同步语言": "Выберите язык синхронизации", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "После выбора запросы, оплачиваемые по этому плану подписки, могут использовать только выбранные модели; оставьте пустым для отсутствия ограничений", "选择容器": "Select Container", "选择您的首选界面语言,设置将自动保存并同步到所有设备": "Выберите предпочитаемый язык интерфейса, настройки будут автоматически сохранены и синхронизированы на всех устройствах", "选择成功": "Выбрано успешно", @@ -3561,6 +3564,7 @@ "降级": "Понизить версию", "限制周期": "Период ограничения", "限制周期统一使用上方配置的“限制周期”值。": "Период ограничения равномерно использует значение 'Период ограничения', настроенное выше.", + "限制该订阅套餐可使用的模型范围": "Ограничить модели, доступные для этого плана подписки", "限流": "Ограничение скорости", "限购": "Лимит", "隐私政策": "Политика конфиденциальности", diff --git a/web/classic/src/i18n/locales/vi.json b/web/classic/src/i18n/locales/vi.json index 1d83bf6b738..5c4d0966040 100644 --- a/web/classic/src/i18n/locales/vi.json +++ b/web/classic/src/i18n/locales/vi.json @@ -3631,6 +3631,7 @@ "请选择要导出的数据": "Vui lòng chọn dữ liệu để xuất", "请选择订阅套餐": "Vui lòng chọn gói đăng ký", "请选择该令牌支持的模型,留空支持所有模型": "Vui lòng chọn các mô hình được mã thông báo này hỗ trợ, để trống để hỗ trợ tất cả các mô hình", + "请选择该套餐支持的模型,留空支持所有模型": "Vui lòng chọn các mô hình được gói này hỗ trợ, để trống cho tất cả các mô hình", "请选择该渠道所支持的模型": "Vui lòng chọn mô hình được kênh này hỗ trợ", "请选择该渠道所支持的模型,留空则不更改": "Vui lòng chọn mô hình được kênh này hỗ trợ, để trống sẽ không thay đổi", "请选择语言": "Vui lòng chọn ngôn ngữ", @@ -3826,6 +3827,7 @@ "选择同步来源": "Chọn nguồn đồng bộ", "选择同步渠道": "Chọn kênh đồng bộ", "选择同步语言": "Chọn ngôn ngữ đồng bộ", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "Sau khi chọn, các yêu cầu được tính phí qua gói đăng ký này chỉ có thể dùng các mô hình đã chọn; để trống thì không giới hạn", "选择图片": "Chọn hình ảnh", "选择头像": "Chọn ảnh đại diện", "选择容器": "Select Container", @@ -4075,6 +4077,7 @@ "降级": "Hạ cấp", "限制周期": "Chu kỳ giới hạn", "限制周期统一使用上方配置的“限制周期”值。": "Chu kỳ giới hạn sử dụng thống nhất giá trị \"Chu kỳ giới hạn\" được cấu hình ở trên.", + "限制该订阅套餐可使用的模型范围": "Giới hạn các mô hình có sẵn cho gói đăng ký này", "限流": "Giới hạn tốc độ", "限购": "Giới hạn mua", "隐私政策": "Chính sách bảo mật", diff --git a/web/classic/src/i18n/locales/zh-CN.json b/web/classic/src/i18n/locales/zh-CN.json index 2156b46d8ee..57365a53523 100644 --- a/web/classic/src/i18n/locales/zh-CN.json +++ b/web/classic/src/i18n/locales/zh-CN.json @@ -2111,6 +2111,7 @@ "模型配置": "模型配置", "模型重定向": "模型重定向", "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:": "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:", + "模型限制": "模型限制", "模型限制列表": "模型限制列表", "模式": "模式", "模板": "模板", @@ -3187,6 +3188,7 @@ "请选择至少一个部署位置": "请选择至少一个部署位置", "请选择订阅套餐": "请选择订阅套餐", "请选择该令牌支持的模型,留空支持所有模型": "请选择该令牌支持的模型,留空支持所有模型", + "请选择该套餐支持的模型,留空支持所有模型": "请选择该套餐支持的模型,留空支持所有模型", "请选择该渠道所支持的模型": "请选择该渠道所支持的模型", "请选择该渠道所支持的模型,留空则不更改": "请选择该渠道所支持的模型,留空则不更改", "请选择过期时间": "请选择过期时间", @@ -3375,6 +3377,7 @@ "选择同步来源": "选择同步来源", "选择同步渠道": "选择同步渠道", "选择同步语言": "选择同步语言", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制", "选择容器": "选择容器", "选择您的首选界面语言,设置将自动保存并同步到所有设备": "选择您的首选界面语言,设置将自动保存并同步到所有设备", "选择成功": "选择成功", @@ -3550,6 +3553,7 @@ "降级": "降级", "限制周期": "限制周期", "限制周期统一使用上方配置的“限制周期”值。": "限制周期统一使用上方配置的“限制周期”值。", + "限制该订阅套餐可使用的模型范围": "限制该订阅套餐可使用的模型范围", "限流": "限流", "限购": "限购", "隐私政策": "隐私政策", diff --git a/web/classic/src/i18n/locales/zh-TW.json b/web/classic/src/i18n/locales/zh-TW.json index 98d8e892488..b9971f17169 100644 --- a/web/classic/src/i18n/locales/zh-TW.json +++ b/web/classic/src/i18n/locales/zh-TW.json @@ -2120,6 +2120,7 @@ "模型配置": "模型設定", "模型重定向": "模型重定向", "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:": "模型重定向裡的下列模型尚未添加到「模型」列表,調用時會因為缺少可用模型而失敗:", + "模型限制": "模型限制", "模型限制列表": "模型限制列表", "模式": "", "模板": "", @@ -3197,6 +3198,7 @@ "请选择至少一个部署位置": "請選擇至少一個部署位置", "请选择订阅套餐": "", "请选择该令牌支持的模型,留空支持所有模型": "請選擇該令牌支援的模型,留空支援所有模型", + "请选择该套餐支持的模型,留空支持所有模型": "請選擇該套餐支援的模型,留空支援所有模型", "请选择该渠道所支持的模型": "請選擇該管道所支援的模型", "请选择该渠道所支持的模型,留空则不更改": "請選擇該管道所支援的模型,留空則不更改", "请选择过期时间": "請選擇過期時間", @@ -3351,6 +3353,7 @@ "选择同步来源": "選擇同步來源", "选择同步渠道": "選擇同步管道", "选择同步语言": "選擇同步語言", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "選擇後,透過該訂閱套餐計費的請求只能使用選中的模型;留空則不限制", "选择容器": "選擇容器", "选择您的首选界面语言,设置将自动保存并同步到所有设备": "選擇您的首選界面語言,設定將自動儲存並同步到所有設備", "选择成功": "選擇成功", @@ -3525,6 +3528,7 @@ "降级": "降級", "限制周期": "限制週期", "限制周期统一使用上方配置的“限制周期”值。": "限制週期統一使用上方設定的「限制週期」值。", + "限制该订阅套餐可使用的模型范围": "限制該訂閱套餐可使用的模型範圍", "限流": "", "限购": "限購", "隐私政策": "隱私政策", diff --git a/web/classic/src/i18n/locales/zh.json b/web/classic/src/i18n/locales/zh.json index e23930f5e1d..4d340ea94f0 100644 --- a/web/classic/src/i18n/locales/zh.json +++ b/web/classic/src/i18n/locales/zh.json @@ -1469,6 +1469,7 @@ "模型配置": "模型配置", "模型重定向": "模型重定向", "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:": "模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:", + "模型限制": "模型限制", "模型限制列表": "模型限制列表", "模板示例": "模板示例", "模糊搜索模型名称": "模糊搜索模型名称", @@ -1777,6 +1778,7 @@ "站点额度展示类型及汇率": "站点额度展示类型及汇率", "端口号必须在1-65535之间": "端口号必须在1-65535之间", "端口配置详细说明": "限制外部请求只能访问指定端口。支持单个端口(80, 443)或端口范围(8000-8999)。空列表允许所有端口。默认包含常用Web端口。", + "限制该订阅套餐可使用的模型范围": "限制该订阅套餐可使用的模型范围", "端点": "端点", "端点映射": "端点映射", "端点类型": "端点类型", @@ -2237,6 +2239,7 @@ "请选择组类型": "请选择组类型", "请选择至少一个部署位置": "请选择至少一个部署位置", "请选择该令牌支持的模型,留空支持所有模型": "请选择该令牌支持的模型,留空支持所有模型", + "请选择该套餐支持的模型,留空支持所有模型": "请选择该套餐支持的模型,留空支持所有模型", "请选择该渠道所支持的模型": "请选择该渠道所支持的模型", "请选择该渠道所支持的模型,留空则不更改": "请选择该渠道所支持的模型,留空则不更改", "请选择过期时间": "请选择过期时间", @@ -2342,6 +2345,7 @@ "选择同步来源": "选择同步来源", "选择同步渠道": "选择同步渠道", "选择同步语言": "选择同步语言", + "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制": "选择后,通过该订阅套餐计费的请求只能使用选中的模型;留空则不限制", "选择容器": "选择容器", "选择成功": "选择成功", "选择支付方式": "选择支付方式",