Commit 5d943281 by RedwindA Committed by GitHub

feat(system-settings): add user token limit configuration section (#5678)

parent 0b2cf43e
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { zodResolver } from '@hookform/resolvers/zod'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import * as z from 'zod'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { SettingsForm } from '../components/settings-form-layout'
import { SettingsPageFormActions } from '../components/settings-page-context'
import { SettingsSection } from '../components/settings-section'
import { useUpdateOption } from '../hooks/use-update-option'
const tokenLimitSchema = z.object({
token_setting: z.object({
max_user_tokens: z.number().min(1),
}),
})
type TokenLimitFormValues = z.output<typeof tokenLimitSchema>
type TokenLimitFormInput = z.input<typeof tokenLimitSchema>
type NormalizedTokenLimitValues = {
'token_setting.max_user_tokens': number
}
type TokenLimitSectionProps = {
defaultValues: NormalizedTokenLimitValues
}
const buildFormDefaults = (
defaults: TokenLimitSectionProps['defaultValues']
): TokenLimitFormInput => ({
token_setting: {
max_user_tokens: defaults['token_setting.max_user_tokens'],
},
})
const normalizeFormValues = (
values: TokenLimitFormValues
): NormalizedTokenLimitValues => ({
'token_setting.max_user_tokens': values.token_setting.max_user_tokens,
})
export function TokenLimitSection({ defaultValues }: TokenLimitSectionProps) {
const { t } = useTranslation()
const updateOption = useUpdateOption()
const form = useForm<TokenLimitFormInput, unknown, TokenLimitFormValues>({
resolver: zodResolver(tokenLimitSchema),
mode: 'onChange',
defaultValues: buildFormDefaults(defaultValues),
})
useEffect(() => {
form.reset(buildFormDefaults(defaultValues))
}, [defaultValues, form])
const onSubmit = async (values: TokenLimitFormValues) => {
const key = 'token_setting.max_user_tokens' as const
const normalized = normalizeFormValues(values)
const value = normalized[key]
if (value !== defaultValues[key]) {
await updateOption.mutateAsync({ key, value })
}
}
return (
<SettingsSection title={t('Token Limits')}>
<Form {...form}>
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
<SettingsPageFormActions
onSave={form.handleSubmit(onSubmit)}
isSaving={updateOption.isPending}
saveLabel='Save token limits'
/>
<FormField
control={form.control}
name='token_setting.max_user_tokens'
render={({ field }) => (
<FormItem>
<FormLabel>{t('Maximum tokens per user')}</FormLabel>
<FormControl>
<Input
type='number'
min={1}
step={1}
{...field}
onChange={(e) =>
field.onChange(Number.parseInt(e.target.value) || 1)
}
/>
</FormControl>
<FormDescription>
{t(
'Maximum number of tokens each user can create. Default 1000. Setting too large may affect performance.'
)}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</SettingsForm>
</Form>
</SettingsSection>
)
}
......@@ -41,6 +41,7 @@ const defaultSecuritySettings: SecuritySettings = {
'fetch_setting.ip_list': [],
'fetch_setting.allowed_ports': [],
'fetch_setting.apply_ip_filter_for_domain': false,
'token_setting.max_user_tokens': 1000,
}
export function SecuritySettings() {
......
......@@ -19,6 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
import { RateLimitSection } from '../request-limits/rate-limit-section'
import { SensitiveWordsSection } from '../request-limits/sensitive-words-section'
import { SSRFSection } from '../request-limits/ssrf-section'
import { TokenLimitSection } from '../request-limits/token-limit-section'
import type { SecuritySettings } from '../types'
import { createSectionRegistry } from '../utils/section-registry'
......@@ -77,6 +78,18 @@ const SECURITY_SECTIONS = [
/>
),
},
{
id: 'token-limits',
titleKey: 'Token Limits',
build: (settings: SecuritySettings) => (
<TokenLimitSection
defaultValues={{
'token_setting.max_user_tokens':
settings['token_setting.max_user_tokens'],
}}
/>
),
},
] as const
export type SecuritySectionId = (typeof SECURITY_SECTIONS)[number]['id']
......
......@@ -378,6 +378,7 @@ export type SecuritySettings = {
'fetch_setting.ip_list': string[]
'fetch_setting.allowed_ports': number[]
'fetch_setting.apply_ip_filter_for_domain': boolean
'token_setting.max_user_tokens': number
}
export type UpstreamChannel = {
......
......@@ -370,6 +370,7 @@
"API Key disabled successfully": "API Key disabled successfully",
"API Key enabled successfully": "API Key enabled successfully",
"API key from the provider": "API key from the provider",
"API key is loading, please try again in a moment": "API key is loading, please try again in a moment",
"API key is required": "API key is required",
"API Key mode (does not support batch creation)": "API Key mode (does not support batch creation)",
"API Key mode: use APIKey|Region": "API Key mode: use APIKey|Region",
......@@ -1701,6 +1702,7 @@
"Failed to copy keys": "Failed to copy keys",
"Failed to copy model names": "Failed to copy model names",
"Failed to copy to clipboard": "Failed to copy to clipboard",
"Failed to create account": "Failed to create account",
"Failed to create API key": "Failed to create API key",
"Failed to create channel": "Failed to create channel",
"Failed to create deployment": "Failed to create deployment",
......@@ -1753,6 +1755,8 @@
"Failed to load key status": "Failed to load key status",
"Failed to load logs": "Failed to load logs",
"Failed to load Passkey status": "Failed to load Passkey status",
"Failed to load playground groups": "Failed to load playground groups",
"Failed to load playground models": "Failed to load playground models",
"Failed to load profile": "Failed to load profile",
"Failed to load redemption codes": "Failed to load redemption codes",
"Failed to load setup data": "Failed to load setup data",
......@@ -1779,7 +1783,9 @@
"Failed to search API keys": "Failed to search API keys",
"Failed to search redemption codes": "Failed to search redemption codes",
"Failed to search users": "Failed to search users",
"Failed to send reset email": "Failed to send reset email",
"Failed to send verification code": "Failed to send verification code",
"Failed to send verification email": "Failed to send verification email",
"Failed to set tag": "Failed to set tag",
"Failed to setup 2FA": "Failed to setup 2FA",
"Failed to start {{provider}} login": "Failed to start {{provider}} login",
......@@ -1822,6 +1828,7 @@
"Fee": "Fee",
"Fee Amount": "Fee Amount",
"Fetch available models for:": "Fetch available models for:",
"Fetch available models from upstream": "Fetch available models from upstream",
"Fetch from Upstream": "Fetch from Upstream",
"Fetch Models": "Fetch Models",
"Fetched {{count}} model(s) from upstream": "Fetched {{count}} model(s) from upstream",
......@@ -2445,10 +2452,12 @@
"Maximum 500 characters. Supports Markdown and HTML.": "Maximum 500 characters. Supports Markdown and HTML.",
"Maximum check-in quota": "Maximum check-in quota",
"Maximum input window": "Maximum input window",
"Maximum number of tokens each user can create. Default 1000. Setting too large may affect performance.": "Maximum number of tokens each user can create. Default 1000. Setting too large may affect performance.",
"Maximum number of tokens in the response": "Maximum number of tokens in the response",
"Maximum quota amount awarded for check-in": "Maximum quota amount awarded for check-in",
"Maximum tokens including hidden reasoning tokens": "Maximum tokens including hidden reasoning tokens",
"Maximum tokens per response": "Maximum tokens per response",
"Maximum tokens per user": "Maximum tokens per user",
"maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647",
"May be used for training by upstream provider": "May be used for training by upstream provider",
"Media pricing": "Media pricing",
......@@ -2517,7 +2526,9 @@
"Model Mapping (JSON)": "Model Mapping (JSON)",
"Model Mapping must be a JSON object like": "Model Mapping must be a JSON object like",
"Model mapping must be a JSON object with string values": "Model mapping must be a JSON object with string values",
"Model mapping must be a valid JSON object": "Model mapping must be a valid JSON object",
"Model mapping must be valid JSON": "Model mapping must be valid JSON",
"Model mapping must be valid JSON format": "Model mapping must be valid JSON format",
"Model mapping values must be strings": "Model mapping values must be strings",
"Model name": "Model name",
"Model Name": "Model Name",
......@@ -2748,6 +2759,7 @@
"No missing models found.": "No missing models found.",
"No model found.": "No model found.",
"No model mappings configured. Click \"Add Mapping\" to get started.": "No model mappings configured. Click \"Add Mapping\" to get started.",
"No model price changes to save": "No model price changes to save",
"No models available": "No models available",
"No models available in this category": "No models available in this category",
"No models available. Create your first model to get started.": "No models available. Create your first model to get started.",
......@@ -2760,6 +2772,7 @@
"No models match the selected filters": "No models match the selected filters",
"No models match your current filters.": "No models match your current filters.",
"No models match your search": "No models match your search",
"No models matched your search.": "No models matched your search.",
"No models selected": "No models selected",
"No models to add": "No models to add",
"No models to copy": "No models to copy",
......@@ -3072,6 +3085,7 @@
"Password Login": "Password Login",
"Password must be at least 8 characters": "Password must be at least 8 characters",
"Password must be at least 8 characters long": "Password must be at least 8 characters long",
"Password must be between 8 and 20 characters": "Password must be between 8 and 20 characters",
"Password Registration": "Password Registration",
"Password reset and copied to clipboard: {{password}}": "Password reset and copied to clipboard: {{password}}",
"Password reset: {{password}}": "Password reset: {{password}}",
......@@ -3323,6 +3337,7 @@
"Prompt Details": "Prompt Details",
"Prompt price ($/1M tokens)": "Prompt price ($/1M tokens)",
"Proprietary": "Proprietary",
"Protect login and registration with Cloudflare Turnstile": "Protect login and registration with Cloudflare Turnstile",
"Provide a JSON object where each key maps to an endpoint definition.": "Provide a JSON object where each key maps to an endpoint definition.",
"Provide a valid URL starting with http:// or https://": "Provide a valid URL starting with http:// or https://",
"Provide Markdown, HTML, or an external URL for the privacy policy": "Provide Markdown, HTML, or an external URL for the privacy policy",
......@@ -3860,6 +3875,7 @@
"Send a request": "Send a request",
"Send code": "Send code",
"Send email alerts when a user falls below this quota": "Send email alerts when a user falls below this quota",
"Send reset email": "Send reset email",
"Sending...": "Sending...",
"Sensitive Words": "Sensitive Words",
"Sent the API key to FluentRead.": "Sent the API key to FluentRead.",
......@@ -4240,6 +4256,7 @@
"This action cannot be undone.": "This action cannot be undone.",
"This action cannot be undone. This will permanently delete your account and remove all your data from our servers.": "This action cannot be undone. This will permanently delete your account and remove all your data from our servers.",
"This action will permanently remove 2FA protection from your account.": "This action will permanently remove 2FA protection from your account.",
"This channel has no configured models.": "This channel has no configured models.",
"This channel is not an Ollama channel.": "This channel is not an Ollama channel.",
"This channel type does not support fetching models": "This channel type does not support fetching models",
"This channel type requires additional configuration": "This channel type requires additional configuration",
......@@ -4326,6 +4343,7 @@
"Token Endpoint (Optional)": "Token Endpoint (Optional)",
"Token estimator": "Token estimator",
"Token group": "Token group",
"Token Limits": "Token Limits",
"Token management": "Token management",
"Token Management": "Token Management",
"Token Mgmt": "Token Mgmt",
......@@ -4524,6 +4542,7 @@
"Updated system setting {{key}}": "Updated system setting {{key}}",
"Updated user {{username}} (ID: {{id}})": "Updated user {{username}} (ID: {{id}})",
"Updating all channel balances. This may take a while. Please refresh to see results.": "Updating all channel balances. This may take a while. Please refresh to see results.",
"Updating...": "Updating...",
"Upgrade Group": "Upgrade Group",
"Upgrade plaintext SMTP connection with STARTTLS before authentication": "Upgrade plaintext SMTP connection with STARTTLS before authentication",
"Upload": "Upload",
......@@ -4728,6 +4747,7 @@
"Visual Parameter Override": "Visual Parameter Override",
"VolcEngine": "VolcEngine",
"vs. previous": "vs. previous",
"Waffo": "Waffo",
"Waffo Aggregator Gateway": "Waffo Aggregator Gateway",
"Waffo Pancake Dashboard": "Waffo Pancake Dashboard",
"Waffo Pancake MoR": "Waffo Pancake MoR",
......
......@@ -370,6 +370,7 @@
"API Key disabled successfully": "API 密钥禁用成功",
"API Key enabled successfully": "API 密钥启用成功",
"API key from the provider": "来自提供商的 API 密钥",
"API key is loading, please try again in a moment": "API 密钥正在加载,请稍后再试",
"API key is required": "需要 API 密钥",
"API Key mode (does not support batch creation)": "API Key 模式(不支持批量创建)",
"API Key mode: use APIKey|Region": "API Key 模式:使用 APIKey|Region",
......@@ -1701,6 +1702,7 @@
"Failed to copy keys": "复制密钥失败",
"Failed to copy model names": "复制模型名称失败",
"Failed to copy to clipboard": "复制到剪贴板失败",
"Failed to create account": "创建账户失败",
"Failed to create API key": "创建API密钥失败",
"Failed to create channel": "创建渠道失败",
"Failed to create deployment": "创建部署失败",
......@@ -1753,6 +1755,8 @@
"Failed to load key status": "加载密钥状态失败",
"Failed to load logs": "加载日志失败",
"Failed to load Passkey status": "加载 Passkey 状态失败",
"Failed to load playground groups": "加载 playground 分组失败",
"Failed to load playground models": "加载 playground 模型失败",
"Failed to load profile": "加载个人资料失败",
"Failed to load redemption codes": "加载兑换码失败",
"Failed to load setup data": "无法加载设置数据",
......@@ -1779,7 +1783,9 @@
"Failed to search API keys": "搜索 API 密钥失败",
"Failed to search redemption codes": "搜索兑换码失败",
"Failed to search users": "搜索用户失败",
"Failed to send reset email": "发送重置邮件失败",
"Failed to send verification code": "发送验证码失败",
"Failed to send verification email": "发送验证邮件失败",
"Failed to set tag": "设置标签失败",
"Failed to setup 2FA": "设置 2FA 失败",
"Failed to start {{provider}} login": "启动 {{provider}} 登录失败",
......@@ -1822,6 +1828,7 @@
"Fee": "扣费",
"Fee Amount": "扣费金额",
"Fetch available models for:": "获取可用模型:",
"Fetch available models from upstream": "从上游获取可用模型",
"Fetch from Upstream": "从上游获取",
"Fetch Models": "获取模型",
"Fetched {{count}} model(s) from upstream": "从上游获取了 {{count}} 个模型",
......@@ -2445,10 +2452,12 @@
"Maximum 500 characters. Supports Markdown and HTML.": "最多 500 个字符。支持 Markdown 和 HTML。",
"Maximum check-in quota": "签到最大额度",
"Maximum input window": "最大输入窗口",
"Maximum number of tokens each user can create. Default 1000. Setting too large may affect performance.": "每个用户可创建的最大令牌数量。默认 1000。设置过大可能会影响性能。",
"Maximum number of tokens in the response": "响应中最大 token 数",
"Maximum quota amount awarded for check-in": "签到奖励的最大额度",
"Maximum tokens including hidden reasoning tokens": "最大 token 数(含隐藏的推理 token)",
"Maximum tokens per response": "单次响应最大 token 数",
"Maximum tokens per user": "每个用户的最大令牌数",
"maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1,两者均 ≤ 2,147,483,647",
"May be used for training by upstream provider": "可能被上游提供商用于训练",
"Media pricing": "媒体定价",
......@@ -2517,7 +2526,9 @@
"Model Mapping (JSON)": "模型映射 (JSON)",
"Model Mapping must be a JSON object like": "模型映射必须是如下所示的 JSON 对象",
"Model mapping must be a JSON object with string values": "模型映射必须是值为字符串的 JSON 对象",
"Model mapping must be a valid JSON object": "模型映射必须是有效的 JSON 对象",
"Model mapping must be valid JSON": "模型映射必须是有效的 JSON",
"Model mapping must be valid JSON format": "模型映射必须是有效的 JSON 格式",
"Model mapping values must be strings": "模型映射的值必须是字符串",
"Model name": "模型名称",
"Model Name": "模型名称",
......@@ -2748,6 +2759,7 @@
"No missing models found.": "未找到缺失的模型。",
"No model found.": "未找到模型。",
"No model mappings configured. Click \"Add Mapping\" to get started.": "未配置模型映射。点击“添加映射”即可开始使用。",
"No model price changes to save": "没有模型价格变更需要保存",
"No models available": "没有可用的模型",
"No models available in this category": "该分类下没有可用模型",
"No models available. Create your first model to get started.": "没有可用的模型。创建您的第一个模型即可开始使用。",
......@@ -2760,6 +2772,7 @@
"No models match the selected filters": "没有匹配筛选条件的模型",
"No models match your current filters.": "没有模型匹配您当前的筛选条件。",
"No models match your search": "没有匹配的模型",
"No models matched your search.": "没有匹配搜索的模型",
"No models selected": "未选择模型",
"No models to add": "无待新增模型",
"No models to copy": "没有模型可复制",
......@@ -3072,6 +3085,7 @@
"Password Login": "密码登录",
"Password must be at least 8 characters": "密码必须至少 8 个字符",
"Password must be at least 8 characters long": "密码必须至少 8 个字符长",
"Password must be between 8 and 20 characters": "密码长度必须在 8 到 20 个字符之间",
"Password Registration": "密码注册",
"Password reset and copied to clipboard: {{password}}": "密码已重置并复制到剪贴板:{{password}}",
"Password reset: {{password}}": "密码已重置:{{password}}",
......@@ -3323,6 +3337,7 @@
"Prompt Details": "提示词详情",
"Prompt price ($/1M tokens)": "提示词价格(美元/100 万 token)",
"Proprietary": "商业闭源",
"Protect login and registration with Cloudflare Turnstile": "使用 Cloudflare Turnstile 保护登录和注册",
"Provide a JSON object where each key maps to an endpoint definition.": "提供一个 JSON 对象,其中每个键映射到一个端点定义。",
"Provide a valid URL starting with http:// or https://": "请提供以 http:// 或 https:// 开头的有效 URL",
"Provide Markdown, HTML, or an external URL for the privacy policy": "提供 Markdown、HTML 或外部 URL 作为隐私政策",
......@@ -3860,6 +3875,7 @@
"Send a request": "发送请求",
"Send code": "发送验证码",
"Send email alerts when a user falls below this quota": "当用户低于此配额时发送电子邮件警报",
"Send reset email": "发送重置邮件",
"Sending...": "发送中...",
"Sensitive Words": "敏感词",
"Sent the API key to FluentRead.": "API 密钥已发送至 FluentRead。",
......@@ -4240,6 +4256,7 @@
"This action cannot be undone.": "此操作无法撤消。",
"This action cannot be undone. This will permanently delete your account and remove all your data from our servers.": "此操作无法撤消。这将永久删除您的账户并从我们的服务器中移除您的所有数据。",
"This action will permanently remove 2FA protection from your account.": "此操作将永久移除您账户的 2FA 保护。",
"This channel has no configured models.": "该渠道没有配置模型。",
"This channel is not an Ollama channel.": "该渠道不是 Ollama 渠道。",
"This channel type does not support fetching models": "此渠道类型不支持获取模型",
"This channel type requires additional configuration": "此渠道类型需要填写额外配置",
......@@ -4326,6 +4343,7 @@
"Token Endpoint (Optional)": "Token 端点(可选)",
"Token estimator": "Token 估算器",
"Token group": "令牌分组",
"Token Limits": "令牌限制",
"Token management": "令牌管理",
"Token Management": "令牌管理",
"Token Mgmt": "令牌管理",
......@@ -4524,6 +4542,7 @@
"Updated system setting {{key}}": "修改系统设置 {{key}}",
"Updated user {{username}} (ID: {{id}})": "更新用户 {{username}}(ID: {{id}})",
"Updating all channel balances. This may take a while. Please refresh to see results.": "正在更新所有渠道余额。这可能需要一段时间。请刷新以查看结果。",
"Updating...": "更新中...",
"Upgrade Group": "升级分组",
"Upgrade plaintext SMTP connection with STARTTLS before authentication": "在身份验证前使用 STARTTLS 升级明文 SMTP 连接",
"Upload": "上传",
......@@ -4728,6 +4747,7 @@
"Visual Parameter Override": "可视化参数覆盖",
"VolcEngine": "火山方舟",
"vs. previous": "相较上期",
"Waffo": "Waffo",
"Waffo Aggregator Gateway": "Waffo 聚合网关",
"Waffo Pancake Dashboard": "Waffo Pancake 控制台",
"Waffo Pancake MoR": "Waffo Pancake MoR",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment