chore: rebrand copyright from QuantumNous to modelstoken
Docker Build / Build and Push Docker Image (push) Failing after 1m2s

This commit is contained in:
2026-06-12 01:05:46 +08:00
parent 6db071543c
commit 97ddfeab59
1313 changed files with 6166 additions and 6855 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
new-api Notices new-api Notices
new-api new-api
Copyright (c) QuantumNous and contributors. Copyright (c) modelstoken and contributors.
This project is licensed under the GNU Affero General Public License v3.0. This project is licensed under the GNU Affero General Public License v3.0.
See LICENSE for the full project license terms. See LICENSE for the full project license terms.
@@ -19,7 +19,7 @@ Modified versions that present a user interface must also preserve a visible
link to the original project in a prominent about, legal, footer, or link to the original project in a prominent about, legal, footer, or
attribution location: attribution location:
https://github.com/QuantumNous/new-api https://git.viaeon.com/admin/new-api
Modified versions must not misrepresent the origin of the software and must Modified versions must not misrepresent the origin of the software and must
mark their changes in accordance with AGPLv3 Section 7(c). mark their changes in accordance with AGPLv3 Section 7(c).
+1 -1
View File
@@ -14,7 +14,7 @@ import (
var StartTime = time.Now().Unix() // unit: second var StartTime = time.Now().Unix() // unit: second
var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change
var SystemName = "New API" var SystemName = "ModelsToken"
var Footer = "" var Footer = ""
var Logo = "" var Logo = ""
var TopUpLink = "" var TopUpLink = ""
+2 -2
View File
@@ -18,10 +18,10 @@
"openai", "openai",
"claude" "claude"
], ],
"author": "QuantumNous", "author": "modelstoken",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/QuantumNous/new-api" "url": "https://git.viaeon.com/admin/new-api"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
+1 -1
View File
@@ -164,7 +164,7 @@ func main() {
common.SysLog(fmt.Sprintf("panic detected: %v", err)) common.SysLog(fmt.Sprintf("panic detected: %v", err))
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{ "error": gin.H{
"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err), "message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://git.viaeon.com/admin/new-api/issues", err),
"type": "new_api_panic", "type": "new_api_panic",
}, },
}) })
+1 -1
View File
@@ -17,7 +17,7 @@ func RelayPanicRecover() gin.HandlerFunc {
common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack()))) common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack())))
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{ "error": gin.H{
"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err), "message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://git.viaeon.com/admin/new-api/issues", err),
"type": "new_api_panic", "type": "new_api_panic",
}, },
}) })
+1 -1
View File
@@ -221,7 +221,7 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, header *http.Header, info *
header.Set("HTTP-Referer", "https://www.newapi.ai") header.Set("HTTP-Referer", "https://www.newapi.ai")
} }
if header.Get("X-OpenRouter-Title") == "" { if header.Get("X-OpenRouter-Title") == "" {
header.Set("X-OpenRouter-Title", "New API") header.Set("X-OpenRouter-Title", "ModelsToken")
} }
} }
return nil return nil
+1 -1
View File
@@ -16,7 +16,7 @@
content="A unified AI model hub for aggregation & distribution. It supports cross-converting various LLMs into OpenAI-compatible, Claude-compatible, or Gemini-compatible formats. A centralized gateway for personal and enterprise model management." content="A unified AI model hub for aggregation & distribution. It supports cross-converting various LLMs into OpenAI-compatible, Claude-compatible, or Gemini-compatible formats. A centralized gateway for personal and enterprise model management."
/> />
<meta name="generator" content="new-api" /> <meta name="generator" content="new-api" />
<title>New API</title> <title>ModelsToken</title>
<!--umami--> <!--umami-->
<!--Google Analytics--> <!--Google Analytics-->
</head> </head>
+6 -10
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { lazy, Suspense, useContext, useMemo } from 'react'; import React, { lazy, Suspense, useContext, useMemo } from 'react';
@@ -72,20 +72,16 @@ function App() {
try { try {
const modules = JSON.parse(headerNavModulesConfig); const modules = JSON.parse(headerNavModulesConfig);
// 处理向后兼容性:如果pricingboolean,默认不需要登录 // 处ç†å‘åŽå…¼å®¹æ€§ï¼šå¦‚æžœpricing是boolean,默认ä¸éœ€è¦ç™»å½? if (typeof modules.pricing === 'boolean') {
if (typeof modules.pricing === 'boolean') { return false; // 默认ä¸éœ€è¦ç™»å½•鉴æ? }
return false; // 默认不需要登录鉴权
}
// 如果是对象格å¼ï¼Œä½¿ç”¨requireAuthé…ç½® // 如果是对象格å¼ï¼Œä½¿ç”¨requireAuthé…ç½®
return modules.pricing?.requireAuth === true; return modules.pricing?.requireAuth === true;
} catch (error) { } catch (error) {
console.error('è§£æžé¡¶æ æ¨¡å—é…置失败:', error); console.error('è§£æžé¡¶æ æ¨¡å—é…置失败:', error);
return false; // 默认不需要登录 return false; // 默认ä¸éœ€è¦ç™»å½? }
}
} }
return false; // 默认不需要登录 return false; // 默认ä¸éœ€è¦ç™»å½? }, [statusState?.status?.HeaderNavModules]);
}, [statusState?.status?.HeaderNavModules]);
return ( return (
<SetupCheck> <SetupCheck>
+34 -41
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
@@ -73,7 +73,7 @@ const LoginForm = () => {
const githubButtonTextKeyByState = { const githubButtonTextKeyByState = {
idle: '雿輻鍂 GitHub 蝏抒賒', idle: '雿輻鍂 GitHub 蝏抒賒',
redirecting: '甇銁頝唾蓮 GitHub...', redirecting: '甇銁頝唾蓮 GitHub...',
timeout: '请求超时,请刷新页面后重新发起 GitHub 登录', timeout: '霂瑟𧒄嚗諹窈瑟鰵憿菟𢒰𡡞韏?GitHub ',
}; };
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
username: '', username: '',
@@ -149,8 +149,7 @@ const LoginForm = () => {
setTurnstileSiteKey(status.turnstile_site_key); setTurnstileSiteKey(status.turnstile_site_key);
} }
// 从 status 获取用户协议和隐私政策的启用状态 // 隞?status 讛悅錇蝑𣇉舐鍂? setHasUserAgreement(status?.user_agreement_enabled || false);
setHasUserAgreement(status?.user_agreement_enabled || false);
setHasPrivacyPolicy(status?.privacy_policy_enabled || false); setHasPrivacyPolicy(status?.privacy_policy_enabled || false);
}, [status]); }, [status]);
@@ -168,7 +167,7 @@ const LoginForm = () => {
useEffect(() => { useEffect(() => {
if (searchParams.get('expired')) { if (searchParams.get('expired')) {
showError(t('未登录或登录已过期,请重新登录')); showError(t('芰蒈敶閙撌脰霂琿啁蒈敶?));
} }
}, []); }, []);
@@ -199,7 +198,7 @@ const LoginForm = () => {
setUserData(data); setUserData(data);
updateAPI(); updateAPI();
navigate('/'); navigate('/');
showSuccess('登录成功!'); showSuccess('𣂼?);
setShowWeChatLoginModal(false); setShowWeChatLoginModal(false);
} else { } else {
showError(message); showError(message);
@@ -237,7 +236,7 @@ const LoginForm = () => {
); );
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
// 检查是否需要2FA验证 // 交糓閬?FA撉諹
if (data && data.require_2fa) { if (data && data.require_2fa) {
setShowTwoFA(true); setShowTwoFA(true);
setLoginLoading(false); setLoginLoading(false);
@@ -247,7 +246,7 @@ const LoginForm = () => {
userDispatch({ type: 'login', payload: data }); userDispatch({ type: 'login', payload: data });
setUserData(data); setUserData(data);
updateAPI(); updateAPI();
showSuccess('登录成功!'); showSuccess('𣂼嚗?);
if (username === 'root' && password === '123456') { if (username === 'root' && password === '123456') {
Modal.error({ Modal.error({
title: '冽迤其蝙霈文', title: '冽迤其蝙霈文',
@@ -297,7 +296,7 @@ const LoginForm = () => {
if (success) { if (success) {
userDispatch({ type: 'login', payload: data }); userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data)); localStorage.setItem('user', JSON.stringify(data));
showSuccess('登录成功!'); showSuccess('𣂼?);
setUserData(data); setUserData(data);
updateAPI(); updateAPI();
navigate('/'); navigate('/');
@@ -332,8 +331,7 @@ const LoginForm = () => {
try { try {
onGitHubOAuthClicked(status.github_client_id, { shouldLogout: true }); onGitHubOAuthClicked(status.github_client_id, { shouldLogout: true });
} finally { } finally {
// 由于重定向,这里不会执行到,但为了完整性添加 // 𡢅餈䠷銝滢蛹鈭扳溶? setTimeout(() => setGithubLoading(false), 3000);
setTimeout(() => setGithubLoading(false), 3000);
} }
}; };
@@ -347,8 +345,7 @@ const LoginForm = () => {
try { try {
onDiscordOAuthClicked(status.discord_client_id, { shouldLogout: true }); onDiscordOAuthClicked(status.discord_client_id, { shouldLogout: true });
} finally { } finally {
// 由于重定向,这里不会执行到,但为了完整性添加 // 𡢅餈䠷銝滢蛹鈭扳溶? setTimeout(() => setDiscordLoading(false), 3000);
setTimeout(() => setDiscordLoading(false), 3000);
} }
}; };
@@ -367,8 +364,7 @@ const LoginForm = () => {
{ shouldLogout: true }, { shouldLogout: true },
); );
} finally { } finally {
// 由于重定向,这里不会执行到,但为了完整性添加 // 𡢅餈䠷銝滢蛹鈭扳溶? setTimeout(() => setOidcLoading(false), 3000);
setTimeout(() => setOidcLoading(false), 3000);
} }
}; };
@@ -382,8 +378,7 @@ const LoginForm = () => {
try { try {
onLinuxDOOAuthClicked(status.linuxdo_client_id, { shouldLogout: true }); onLinuxDOOAuthClicked(status.linuxdo_client_id, { shouldLogout: true });
} finally { } finally {
// 由于重定向,这里不会执行到,但为了完整性添加 // 𡢅餈䠷銝滢蛹鈭扳溶? setTimeout(() => setLinuxdoLoading(false), 3000);
setTimeout(() => setLinuxdoLoading(false), 3000);
} }
}; };
@@ -397,8 +392,7 @@ const LoginForm = () => {
try { try {
onCustomOAuthClicked(provider, { shouldLogout: true }); onCustomOAuthClicked(provider, { shouldLogout: true });
} finally { } finally {
// 由于重定向,这里不会执行到,但为了完整性添加 // 𡢅餈䠷銝滢蛹鈭扳溶? setTimeout(() => {
setTimeout(() => {
setCustomOAuthLoading((prev) => ({ ...prev, [provider.slug]: false })); setCustomOAuthLoading((prev) => ({ ...prev, [provider.slug]: false }));
}, 3000); }, 3000);
} }
@@ -455,14 +449,14 @@ const LoginForm = () => {
userDispatch({ type: 'login', payload: finish.data }); userDispatch({ type: 'login', payload: finish.data });
setUserData(finish.data); setUserData(finish.data);
updateAPI(); updateAPI();
showSuccess('登录成功!'); showSuccess('𣂼嚗?);
navigate('/console'); navigate('/console');
} else { } else {
showError(finish.message || 'Passkey 憭梯揖嚗諹窈'); showError(finish.message || 'Passkey 憭梯揖嚗諹窈');
} }
} catch (error) { } catch (error) {
if (error?.name === 'AbortError') { if (error?.name === 'AbortError') {
showInfo('已取消 Passkey 登录'); showInfo('撌脣?Passkey ');
} else { } else {
showError('Passkey 憭梯揖嚗諹窈'); showError('Passkey 憭梯揖嚗諹窈');
} }
@@ -471,8 +465,7 @@ const LoginForm = () => {
} }
}; };
// 包装的重置密码点击处理 // 蝵桀? const handleResetPasswordClick = () => {
const handleResetPasswordClick = () => {
setResetPasswordLoading(true); setResetPasswordLoading(true);
navigate('/reset'); navigate('/reset');
setResetPasswordLoading(false); setResetPasswordLoading(false);
@@ -490,7 +483,7 @@ const LoginForm = () => {
userDispatch({ type: 'login', payload: data }); userDispatch({ type: 'login', payload: data });
setUserData(data); setUserData(data);
updateAPI(); updateAPI();
showSuccess('登录成功!'); showSuccess('𣂼?);
navigate('/console'); navigate('/console');
}; };
@@ -514,7 +507,7 @@ const LoginForm = () => {
<Card className='border-0 !rounded-2xl overflow-hidden'> <Card className='border-0 !rounded-2xl overflow-hidden'>
<div className='flex justify-center pt-6 pb-2'> <div className='flex justify-center pt-6 pb-2'>
<Title heading={3} className='text-gray-800 dark:text-gray-200'> <Title heading={3} className='text-gray-800 dark:text-gray-200'>
{t('登 录')} {t('?敶?)}
</Title> </Title>
</div> </div>
<div className='px-2 py-8'> <div className='px-2 py-8'>
@@ -643,7 +636,7 @@ const LoginForm = () => {
)} )}
<Divider margin='12px' align='center'> <Divider margin='12px' align='center'>
{t('或')} {t('?)}
</Divider> </Divider>
<Button <Button
@@ -665,7 +658,7 @@ const LoginForm = () => {
onChange={(e) => setAgreedToTerms(e.target.checked)} onChange={(e) => setAgreedToTerms(e.target.checked)}
> >
<Text size='small' className='text-gray-600'> <Text size='small' className='text-gray-600'>
{t('我已阅读并同意')} {t('穃歇粉撟嗅?)}
{hasUserAgreement && ( {hasUserAgreement && (
<> <>
<a <a
@@ -678,7 +671,7 @@ const LoginForm = () => {
</a> </a>
</> </>
)} )}
{hasUserAgreement && hasPrivacyPolicy && t('和')} {hasUserAgreement && hasPrivacyPolicy && t('?)}
{hasPrivacyPolicy && ( {hasPrivacyPolicy && (
<> <>
<a <a
@@ -699,7 +692,7 @@ const LoginForm = () => {
{!status.self_use_mode_enabled && ( {!status.self_use_mode_enabled && (
<div className='mt-6 text-center text-sm'> <div className='mt-6 text-center text-sm'>
<Text> <Text>
{t('没有账户?')}{' '} {t('瘝⊥韐行嚗?)}{' '}
<Link <Link
to='/register' to='/register'
className='text-blue-600 hover:text-blue-800 font-medium' className='text-blue-600 hover:text-blue-800 font-medium'
@@ -728,7 +721,7 @@ const LoginForm = () => {
<Card className='border-0 !rounded-2xl overflow-hidden'> <Card className='border-0 !rounded-2xl overflow-hidden'>
<div className='flex justify-center pt-6 pb-2'> <div className='flex justify-center pt-6 pb-2'>
<Title heading={3} className='text-gray-800 dark:text-gray-200'> <Title heading={3} className='text-gray-800 dark:text-gray-200'>
{t('登 录')} {t('??)}
</Title> </Title>
</div> </div>
<div className='px-2 py-8'> <div className='px-2 py-8'>
@@ -757,7 +750,7 @@ const LoginForm = () => {
<Form.Input <Form.Input
field='password' field='password'
label={t('撖')} label={t('撖')}
placeholder={t('请输入您的密码')} placeholder={t('霂瑁?)}
name='password' name='password'
mode='password' mode='password'
onChange={(value) => handleChange('password', value)} onChange={(value) => handleChange('password', value)}
@@ -771,7 +764,7 @@ const LoginForm = () => {
onChange={(e) => setAgreedToTerms(e.target.checked)} onChange={(e) => setAgreedToTerms(e.target.checked)}
> >
<Text size='small' className='text-gray-600'> <Text size='small' className='text-gray-600'>
{t('我已阅读并同意')} {t('穃歇粉撟嗅?)}
{hasUserAgreement && ( {hasUserAgreement && (
<> <>
<a <a
@@ -784,7 +777,7 @@ const LoginForm = () => {
</a> </a>
</> </>
)} )}
{hasUserAgreement && hasPrivacyPolicy && t('和')} {hasUserAgreement && hasPrivacyPolicy && t('?)}
{hasPrivacyPolicy && ( {hasPrivacyPolicy && (
<> <>
<a <a
@@ -824,7 +817,7 @@ const LoginForm = () => {
onClick={handleResetPasswordClick} onClick={handleResetPasswordClick}
loading={resetPasswordLoading} loading={resetPasswordLoading}
> >
{t('忘记密码?')} {t('敹䁅扇撖?)}
</Button> </Button>
</div> </div>
</Form> </Form>
@@ -832,7 +825,7 @@ const LoginForm = () => {
{hasOAuthLoginOptions && ( {hasOAuthLoginOptions && (
<> <>
<Divider margin='12px' align='center'> <Divider margin='12px' align='center'>
{t('或')} {t('?)}
</Divider> </Divider>
<div className='mt-4 text-center'> <div className='mt-4 text-center'>
@@ -852,7 +845,7 @@ const LoginForm = () => {
{!status.self_use_mode_enabled && ( {!status.self_use_mode_enabled && (
<div className='mt-6 text-center text-sm'> <div className='mt-6 text-center text-sm'>
<Text> <Text>
{t('没有账户?')}{' '} {t('韐行?)}{' '}
<Link <Link
to='/register' to='/register'
className='text-blue-600 hover:text-blue-800 font-medium' className='text-blue-600 hover:text-blue-800 font-medium'
@@ -885,7 +878,7 @@ const LoginForm = () => {
}} }}
> >
<div className='flex flex-col items-center'> <div className='flex flex-col items-center'>
<img src={status.wechat_qrcode} alt='微信二维码' className='mb-4' /> <img src={status.wechat_qrcode} alt='敺桐縑鈭𣬚輕? className='mb-4' />
</div> </div>
<div className='text-center mb-4'> <div className='text-center mb-4'>
@@ -897,8 +890,8 @@ const LoginForm = () => {
<Form> <Form>
<Form.Input <Form.Input
field='wechat_verification_code' field='wechat_verification_code'
placeholder={t('验证码')} placeholder={t('撉諹?)}
label={t('验证码')} label={t('撉諹?)}
value={inputs.wechat_verification_code} value={inputs.wechat_verification_code}
onChange={(value) => onChange={(value) =>
handleChange('wechat_verification_code', value) handleChange('wechat_verification_code', value)
@@ -948,7 +941,7 @@ const LoginForm = () => {
return ( return (
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'> <div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
{/* 背景模糊晕染球 */} {/* 峕艶璅∠?*/}
<div <div
className='blur-ball blur-ball-indigo' className='blur-ball blur-ball-indigo'
style={{ top: '-80px', right: '-80px', transform: 'none' }} style={{ top: '-80px', right: '-80px', transform: 'none' }}
+9 -13
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useContext, useEffect, useRef } from 'react'; import React, { useContext, useEffect, useRef } from 'react';
@@ -36,11 +36,9 @@ const OAuth2Callback = (props) => {
const [, userDispatch] = useContext(UserContext); const [, userDispatch] = useContext(UserContext);
const navigate = useNavigate(); const navigate = useNavigate();
// 防止 React 18 Strict Mode 下重复执行 // 防止 React 18 Strict Mode 下é‡å¤æ‰§è¡? const hasExecuted = useRef(false);
const hasExecuted = useRef(false);
// 最大重试次数 // 最大é‡è¯•次æ•? const MAX_RETRIES = 3;
const MAX_RETRIES = 3;
const sendCode = async (code, state, retry = 0) => { const sendCode = async (code, state, retry = 0) => {
try { try {
@@ -57,21 +55,20 @@ const OAuth2Callback = (props) => {
} }
if (data?.action === 'bind') { if (data?.action === 'bind') {
showSuccess(t('绑定成功!')); showSuccess(t('绑定æˆåŠŸï¼?));
navigate('/console/personal'); navigate('/console/personal');
} else { } else {
userDispatch({ type: 'login', payload: data }); userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data)); localStorage.setItem('user', JSON.stringify(data));
setUserData(data); setUserData(data);
updateAPI(); updateAPI();
showSuccess(t('登录成功!')); showSuccess(t('ç»å½æˆåŠŸï¼?));
navigate('/console/token'); navigate('/console/token');
} }
} catch (error) { } catch (error) {
// 网络错误等å¯é‡è¯• // 网络错误等å¯é‡è¯•
if (retry < MAX_RETRIES) { if (retry < MAX_RETRIES) {
// 递增的退避等待 // 递增的退é¿ç­‰å¾? await new Promise((resolve) => setTimeout(resolve, (retry + 1) * 2000));
await new Promise((resolve) => setTimeout(resolve, (retry + 1) * 2000));
return sendCode(code, state, retry + 1); return sendCode(code, state, retry + 1);
} }
@@ -82,8 +79,7 @@ const OAuth2Callback = (props) => {
}; };
useEffect(() => { useEffect(() => {
// 防止 React 18 Strict Mode 下重复执行 // 防止 React 18 Strict Mode 下é‡å¤æ‰§è¡? if (hasExecuted.current) {
if (hasExecuted.current) {
return; return;
} }
hasExecuted.current = true; hasExecuted.current = true;
@@ -93,7 +89,7 @@ const OAuth2Callback = (props) => {
// 傿•°ç¼ºå¤±ç›´æŽ¥è¿”回 // 傿•°ç¼ºå¤±ç›´æŽ¥è¿”回
if (!code) { if (!code) {
showError(t('未获取到授权码')); showError(t('未获å–到授æƒç ?));
navigate('/console/personal'); navigate('/console/personal');
return; return;
} }
+6 -6
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -82,7 +82,7 @@ const PasswordResetConfirm = () => {
async function handleSubmit(e) { async function handleSubmit(e) {
if (!email || !token) { if (!email || !token) {
showError(t('无效的重置链接,请重新发起密码重置请求')); showError(t('无效的é‡ç½®é“¾æŽ¥ï¼Œè¯·é‡æ–°å‘起密ç é‡ç½®è¯·æ±?));
return; return;
} }
setDisableButton(true); setDisableButton(true);
@@ -105,7 +105,7 @@ const PasswordResetConfirm = () => {
return ( return (
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'> <div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
{/* 背景模糊晕染球 */} {/* 背景模糊晕染ç?*/}
<div <div
className='blur-ball blur-ball-indigo' className='blur-ball blur-ball-indigo'
style={{ top: '-80px', right: '-80px', transform: 'none' }} style={{ top: '-80px', right: '-80px', transform: 'none' }}
@@ -134,7 +134,7 @@ const PasswordResetConfirm = () => {
{!isValidResetLink && ( {!isValidResetLink && (
<Banner <Banner
type='danger' type='danger'
description={t('无效的重置链接,请重新发起密码重置请求')} description={t('æ æˆçšéç½®é¾æŽ¥ï¼Œè¯·éæ°åèµ·å¯ç é置请æ±?)}
className='mb-4 !rounded-lg' className='mb-4 !rounded-lg'
closeIcon={null} closeIcon={null}
/> />
@@ -159,7 +159,7 @@ const PasswordResetConfirm = () => {
{newPassword && ( {newPassword && (
<Form.Input <Form.Input
field='newPassword' field='newPassword'
label={t('新密码')} label={t('新密ç ?)}
name='newPassword' name='newPassword'
disabled={true} disabled={true}
prefix={<IconLock />} prefix={<IconLock />}
+4 -4
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -105,7 +105,7 @@ const PasswordResetForm = () => {
return ( return (
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'> <div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
{/* 背景模糊晕染球 */} {/* 背景模糊晕染ç?*/}
<div <div
className='blur-ball blur-ball-indigo' className='blur-ball blur-ball-indigo'
style={{ top: '-80px', right: '-80px', transform: 'none' }} style={{ top: '-80px', right: '-80px', transform: 'none' }}
@@ -161,7 +161,7 @@ const PasswordResetForm = () => {
<div className='mt-6 text-center text-sm'> <div className='mt-6 text-center text-sm'>
<Text> <Text>
{t('想起来了?')}{' '} {t('想起æ¥äº†ï¼?)}{' '}
<Link <Link
to='/login' to='/login'
className='text-blue-600 hover:text-blue-800 font-medium' className='text-blue-600 hover:text-blue-800 font-medium'
+28 -30
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
@@ -71,7 +71,7 @@ const RegisterForm = () => {
const githubButtonTextKeyByState = { const githubButtonTextKeyByState = {
idle: '雿輻鍂 GitHub 蝏抒賒', idle: '雿輻鍂 GitHub 蝏抒賒',
redirecting: '甇銁頝唾蓮 GitHub...', redirecting: '甇銁頝唾蓮 GitHub...',
timeout: '请求超时,请刷新页面后重新发起 GitHub 登录', timeout: '霂瑟𧒄嚗諹窈瑟鰵憿菟𢒰𡡞韏?GitHub ',
}; };
const [inputs, setInputs] = useState({ const [inputs, setInputs] = useState({
username: '', username: '',
@@ -150,8 +150,7 @@ const RegisterForm = () => {
setTurnstileSiteKey(status.turnstile_site_key); setTurnstileSiteKey(status.turnstile_site_key);
} }
// 从 status 获取用户协议和隐私政策的启用状态 // 隞?status 讛悅錇蝑𣇉舐鍂? setHasUserAgreement(status?.user_agreement_enabled || false);
setHasUserAgreement(status?.user_agreement_enabled || false);
setHasPrivacyPolicy(status?.privacy_policy_enabled || false); setHasPrivacyPolicy(status?.privacy_policy_enabled || false);
}, [status]); }, [status]);
@@ -199,7 +198,7 @@ const RegisterForm = () => {
setUserData(data); setUserData(data);
updateAPI(); updateAPI();
navigate('/'); navigate('/');
showSuccess('登录成功!'); showSuccess('𣂼嚗?);
setShowWeChatLoginModal(false); setShowWeChatLoginModal(false);
} else { } else {
showError(message); showError(message);
@@ -221,7 +220,7 @@ const RegisterForm = () => {
return; return;
} }
if (password !== password2) { if (password !== password2) {
showInfo('两次输入的密码不一致'); showInfo('銝斗活颲枏?);
return; return;
} }
if (username && password) { if (username && password) {
@@ -242,7 +241,7 @@ const RegisterForm = () => {
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
navigate('/login'); navigate('/login');
showSuccess('注册成功!'); showSuccess('瘜典𣂼嚗?);
} else { } else {
showError(message); showError(message);
} }
@@ -268,8 +267,7 @@ const RegisterForm = () => {
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
showSuccess('撉諹霂瑟蝞梧'); showSuccess('撉諹霂瑟蝞梧');
setDisableButton(true); // 发送成功后禁用按钮,开始倒计时 setDisableButton(true); // 厰僼嚗憪见坿恣? } else {
} else {
showError(message); showError(message);
} }
} catch (error) { } catch (error) {
@@ -379,7 +377,7 @@ const RegisterForm = () => {
if (success) { if (success) {
userDispatch({ type: 'login', payload: data }); userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data)); localStorage.setItem('user', JSON.stringify(data));
showSuccess('登录成功!'); showSuccess('𣂼?);
setUserData(data); setUserData(data);
updateAPI(); updateAPI();
navigate('/'); navigate('/');
@@ -405,7 +403,7 @@ const RegisterForm = () => {
<Card className='border-0 !rounded-2xl overflow-hidden'> <Card className='border-0 !rounded-2xl overflow-hidden'>
<div className='flex justify-center pt-6 pb-2'> <div className='flex justify-center pt-6 pb-2'>
<Title heading={3} className='text-gray-800 dark:text-gray-200'> <Title heading={3} className='text-gray-800 dark:text-gray-200'>
{t('注 册')} {t('瘜??)}
</Title> </Title>
</div> </div>
<div className='px-2 py-8'> <div className='px-2 py-8'>
@@ -521,7 +519,7 @@ const RegisterForm = () => {
)} )}
<Divider margin='12px' align='center'> <Divider margin='12px' align='center'>
{t('或')} {t('?)}
</Divider> </Divider>
<Button <Button
@@ -532,13 +530,13 @@ const RegisterForm = () => {
onClick={handleEmailRegisterClick} onClick={handleEmailRegisterClick}
loading={emailRegisterLoading} loading={emailRegisterLoading}
> >
<span className='ml-3'>{t('使用 用户名 注册')}</span> <span className='ml-3'>{t('雿輻鍂 ?瘜典')}</span>
</Button> </Button>
</div> </div>
<div className='mt-6 text-center text-sm'> <div className='mt-6 text-center text-sm'>
<Text> <Text>
{t('已有账户?')}{' '} {t('撌脫韐行嚗?)}{' '}
<Link <Link
to='/login' to='/login'
className='text-blue-600 hover:text-blue-800 font-medium' className='text-blue-600 hover:text-blue-800 font-medium'
@@ -568,14 +566,14 @@ const RegisterForm = () => {
<Card className='border-0 !rounded-2xl overflow-hidden'> <Card className='border-0 !rounded-2xl overflow-hidden'>
<div className='flex justify-center pt-6 pb-2'> <div className='flex justify-center pt-6 pb-2'>
<Title heading={3} className='text-gray-800 dark:text-gray-200'> <Title heading={3} className='text-gray-800 dark:text-gray-200'>
{t('注 册')} {t('??)}
</Title> </Title>
</div> </div>
<div className='px-2 py-8'> <div className='px-2 py-8'>
<Form className='space-y-3'> <Form className='space-y-3'>
<Form.Input <Form.Input
field='username' field='username'
label={t('用户名')} label={t('?)}
placeholder={t('霂瑁亦鍂')} placeholder={t('霂瑁亦鍂')}
name='username' name='username'
onChange={(value) => handleChange('username', value)} onChange={(value) => handleChange('username', value)}
@@ -585,7 +583,7 @@ const RegisterForm = () => {
<Form.Input <Form.Input
field='password' field='password'
label={t('')} label={t('')}
placeholder={t('输入密码,最短 8 位,最长 20 位')} placeholder={t('颲枏嚗峕?8 雿㵪?20 ?)}
name='password' name='password'
mode='password' mode='password'
onChange={(value) => handleChange('password', value)} onChange={(value) => handleChange('password', value)}
@@ -619,15 +617,15 @@ const RegisterForm = () => {
disabled={disableButton || verificationCodeLoading} disabled={disableButton || verificationCodeLoading}
> >
{disableButton {disableButton
? `${t('重新发送')} (${countdown})` ? `${t('齿鰵?)} (${countdown})`
: t('获取验证码')} : t('撉諹?)}
</Button> </Button>
} }
/> />
<Form.Input <Form.Input
field='verification_code' field='verification_code'
label={t('验证码')} label={t('撉諹?)}
placeholder={t('输入验证码')} placeholder={t('颲枏撉諹?)}
name='verification_code' name='verification_code'
onChange={(value) => onChange={(value) =>
handleChange('verification_code', value) handleChange('verification_code', value)
@@ -644,7 +642,7 @@ const RegisterForm = () => {
onChange={(e) => setAgreedToTerms(e.target.checked)} onChange={(e) => setAgreedToTerms(e.target.checked)}
> >
<Text size='small' className='text-gray-600'> <Text size='small' className='text-gray-600'>
{t('我已阅读并同意')} {t('穃歇粉撟嗅?)}
{hasUserAgreement && ( {hasUserAgreement && (
<> <>
<a <a
@@ -657,7 +655,7 @@ const RegisterForm = () => {
</a> </a>
</> </>
)} )}
{hasUserAgreement && hasPrivacyPolicy && t('和')} {hasUserAgreement && hasPrivacyPolicy && t('?)}
{hasPrivacyPolicy && ( {hasPrivacyPolicy && (
<> <>
<a <a
@@ -695,7 +693,7 @@ const RegisterForm = () => {
{hasOAuthRegisterOptions && ( {hasOAuthRegisterOptions && (
<> <>
<Divider margin='12px' align='center'> <Divider margin='12px' align='center'>
{t('或')} {t('?)}
</Divider> </Divider>
<div className='mt-4 text-center'> <div className='mt-4 text-center'>
@@ -714,7 +712,7 @@ const RegisterForm = () => {
<div className='mt-6 text-center text-sm'> <div className='mt-6 text-center text-sm'>
<Text> <Text>
{t('已有账户?')}{' '} {t('撌脫韐行嚗?)}{' '}
<Link <Link
to='/login' to='/login'
className='text-blue-600 hover:text-blue-800 font-medium' className='text-blue-600 hover:text-blue-800 font-medium'
@@ -745,7 +743,7 @@ const RegisterForm = () => {
}} }}
> >
<div className='flex flex-col items-center'> <div className='flex flex-col items-center'>
<img src={status.wechat_qrcode} alt='微信二维码' className='mb-4' /> <img src={status.wechat_qrcode} alt='敺桐縑鈭𣬚輕? className='mb-4' />
</div> </div>
<div className='text-center mb-4'> <div className='text-center mb-4'>
@@ -757,8 +755,8 @@ const RegisterForm = () => {
<Form> <Form>
<Form.Input <Form.Input
field='wechat_verification_code' field='wechat_verification_code'
placeholder={t('验证码')} placeholder={t('撉諹?)}
label={t('验证码')} label={t('撉諹?)}
value={inputs.wechat_verification_code} value={inputs.wechat_verification_code}
onChange={(value) => onChange={(value) =>
handleChange('wechat_verification_code', value) handleChange('wechat_verification_code', value)
@@ -771,7 +769,7 @@ const RegisterForm = () => {
return ( return (
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'> <div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
{/* 背景模糊晕染球 */} {/* 峕艶璅∠?*/}
<div <div
className='blur-ball blur-ball-indigo' className='blur-ball blur-ball-indigo'
style={{ top: '-80px', right: '-80px', transform: 'none' }} style={{ top: '-80px', right: '-80px', transform: 'none' }}
+21 -28
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import { API, showError, showSuccess } from '../../helpers'; import { API, showError, showSuccess } from '../../helpers';
import { import {
@@ -41,10 +41,10 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
} }
// Validate code format // Validate code format
if (useBackupCode && verificationCode.length !== 8) { if (useBackupCode && verificationCode.length !== 8) {
showError('备用码必须是8位'); showError('憿餅糓8雿?);
return; return;
} else if (!useBackupCode && !/^\d{6}$/.test(verificationCode)) { } else if (!useBackupCode && !/^\d{6}$/.test(verificationCode)) {
showError('验证码必须是6位数字'); showError('撉諹憿餅糓6雿齿㺭摮?);
return; return;
} }
@@ -56,8 +56,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
if (res.data.success) { if (res.data.success) {
showSuccess('𣂼'); showSuccess('𣂼');
// 保存用户信息到本地存储 // 靽嘥靽⊥唳𧋦? localStorage.setItem('user', JSON.stringify(res.data.data));
localStorage.setItem('user', JSON.stringify(res.data.data));
if (onSuccess) { if (onSuccess) {
onSuccess(res.data.data); onSuccess(res.data.data);
} }
@@ -87,8 +86,8 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<Form.Input <Form.Input
field='code' field='code'
label={useBackupCode ? '备用码' : '验证码'} label={useBackupCode ? '? : '撉諹?}
placeholder={useBackupCode ? '请输入8位备用码' : '请输入6位验证码'} placeholder={useBackupCode ? '霂瑁?雿滚' : '霂瑁?雿漤'}
value={verificationCode} value={verificationCode}
onChange={setVerificationCode} onChange={setVerificationCode}
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}
@@ -105,8 +104,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
size='large' size='large'
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
> >
验证并登录 撉諹撟嗥蒈敶? </Button>
</Button>
</Form> </Form>
<Divider /> <Divider />
@@ -121,7 +119,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
}} }}
style={{ marginRight: 16, color: '#1890ff', padding: 0 }} style={{ marginRight: 16, color: '#1890ff', padding: 0 }}
> >
{useBackupCode ? '使用认证器验证码' : '使用备用码'} {useBackupCode ? '雿輻鍂霈方' : '雿輻鍂憭?}
</Button> </Button>
{onBack && ( {onBack && (
@@ -138,13 +136,11 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
<div className='bg-gray-50 dark:bg-gray-800 rounded-lg p-3'> <div className='bg-gray-50 dark:bg-gray-800 rounded-lg p-3'>
<Text size='small' type='secondary'> <Text size='small' type='secondary'>
<strong>提示</strong> <strong>鞟內嚗?/strong>
<br /> <br />
验证码每30秒更新一次 ?撉諹30蝘埝凒甈? <br />
<br /> ?憒撉諹霂瑚蝙
如果无法获取验证码请使用备用码 <br />?瘥譍葵憭賭蝙甈? </Text>
<br /> 每个备用码只能使用一次
</Text>
</div> </div>
</div> </div>
); );
@@ -170,8 +166,8 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<Form.Input <Form.Input
field='code' field='code'
label={useBackupCode ? '备用码' : '验证码'} label={useBackupCode ? '? : '撉諹?}
placeholder={useBackupCode ? '请输入8位备用码' : '请输入6位验证码'} placeholder={useBackupCode ? '霂瑁?雿滚' : '霂瑁?雿漤'}
value={verificationCode} value={verificationCode}
onChange={setVerificationCode} onChange={setVerificationCode}
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}
@@ -188,8 +184,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
size='large' size='large'
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
> >
验证并登录 撉諹撟嗥蒈敶? </Button>
</Button>
</Form> </Form>
<Divider /> <Divider />
@@ -204,7 +199,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
}} }}
style={{ marginRight: 16, color: '#1890ff', padding: 0 }} style={{ marginRight: 16, color: '#1890ff', padding: 0 }}
> >
{useBackupCode ? '使用认证器验证码' : '使用备用码'} {useBackupCode ? '雿輻鍂霈方' : '雿輻鍂憭?}
</Button> </Button>
{onBack && ( {onBack && (
@@ -228,13 +223,11 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
}} }}
> >
<Text size='small' type='secondary'> <Text size='small' type='secondary'>
<strong>提示</strong> <strong>鞟內嚗?/strong>
<br /> <br />
验证码每30秒更新一次 ?撉諹30蝘埝凒? <br />
<br /> ?撉諹霂瑚蝙
如果无法获取验证码请使用备用码 <br />?瘥譍葵憭賭蝙? </Text>
<br /> 每个备用码只能使用一次
</Text>
</div> </div>
</Card> </Card>
</div> </div>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
@@ -65,9 +65,7 @@ const sanitizeHtml = (html) => {
* 通用文档渲染组件 * 通用文档渲染组件
* @param {string} apiEndpoint - API 接å£åœ°å * @param {string} apiEndpoint - API 接å£åœ°å
* @param {string} title - 文档标题 * @param {string} title - 文档标题
* @param {string} cacheKey - 本地存储缓存键 * @param {string} cacheKey - 本地存储缓存é”? * @param {string} emptyMessage - 空内容时的æç¤ºæ¶ˆæ? */
* @param {string} emptyMessage - 空内容时的提示消息
*/
const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => { const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [content, setContent] = useState(''); const [content, setContent] = useState('');
@@ -138,8 +136,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
}; };
}, [cacheKey, htmlPayload]); }, [cacheKey, htmlPayload]);
// 显示加载状态 // 显示加载状æ€? if (loading) {
if (loading) {
return ( return (
<div className='classic-page-fill flex justify-center items-center'> <div className='classic-page-fill flex justify-center items-center'>
<Spin size='large' /> <Spin size='large' />
@@ -147,8 +144,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
); );
} }
// 如果没有内容,显示空状态 // 如果没有内容,显示空状æ€? if (!content || content.trim() === '') {
if (!content || content.trim() === '') {
return ( return (
<div className='classic-page-fill flex justify-center items-center bg-gray-50'> <div className='classic-page-fill flex justify-center items-center bg-gray-50'>
<Empty <Empty
@@ -165,8 +161,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
); );
} }
// 如果是 URL,显示链接卡片 // 如果æ˜?URL,显示链接å¡ç‰? if (isUrl(content)) {
if (isUrl(content)) {
return ( return (
<div className='classic-page-fill flex justify-center items-center bg-gray-50 p-4'> <div className='classic-page-fill flex justify-center items-center bg-gray-50 p-4'>
<Card className='max-w-md w-full'> <Card className='max-w-md w-full'>
@@ -175,7 +170,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
{title} {title}
</Title> </Title>
<p className='text-gray-600 mb-4'> <p className='text-gray-600 mb-4'>
{t('管理员设置了外部链接,点击下方按钮访问')} {t('管ç†å‘˜è®¾ç½®äº†å¤–部链接,点击下方按钮访é—?)}
</p> </p>
<a <a
href={content.trim()} href={content.trim()}
@@ -193,8 +188,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
); );
} }
// 如果是 HTML 内容,直接渲染 // 如果æ˜?HTML 内容,直接渲æŸ? if (isHtmlContent(content)) {
if (isHtmlContent(content)) {
return ( return (
<div className='classic-page-fill bg-gray-50'> <div className='classic-page-fill bg-gray-50'>
<div className='max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8'> <div className='max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8'>
@@ -212,8 +206,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
); );
} }
// 其他内容统一使用 Markdown 渲染器 // 其他内容统一使用 Markdown 渲染å™? return (
return (
<div className='classic-page-fill bg-gray-50'> <div className='classic-page-fill bg-gray-50'>
<div className='max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8'> <div className='max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8'>
<div className='bg-white rounded-lg shadow-sm p-8'> <div className='bg-white rounded-lg shadow-sm p-8'>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState } from 'react'; import React, { useState } from 'react';
@@ -46,8 +46,7 @@ const ChannelKeyViewExample = ({ channelId }) => {
switchVerificationMethod, switchVerificationMethod,
} = useSecureVerification({ } = useSecureVerification({
onSuccess: (result) => { onSuccess: (result) => {
// 验证成功后处理结果 // 撉諹𣂼𤾸? if (result.success && result.data?.key) {
if (result.success && result.data?.key) {
setKeyData(result.data.key); setKeyData(result.data.key);
setShowKeyModal(true); setShowKeyModal(true);
} }
@@ -55,15 +54,13 @@ const ChannelKeyViewExample = ({ channelId }) => {
successMessage: t('撖𤨎𣂼'), successMessage: t('撖𤨎𣂼'),
}); });
// 开始查看密钥流程 // 憪𧢲䰻蝔? const handleViewKey = async () => {
const handleViewKey = async () => {
const apiCall = createApiCalls.viewChannelKey(channelId); const apiCall = createApiCalls.viewChannelKey(channelId);
await startVerification(apiCall, { await startVerification(apiCall, {
title: t('皜𣳇𤨎'), title: t('皜𣳇𤨎'),
description: t('为了保护账户安全,请验证您的身份。'), description: t('銝箔靽脲擪韐行摰匧嚗諹窈撉諹頨思遢?),
preferredMethod: 'passkey', // 可以指定首选验证方式 preferredMethod: 'passkey', // 臭誑擐㚚䲮撘? });
});
}; };
return ( return (
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
@@ -228,7 +228,7 @@ export function PreCode(props) {
const code = codeElement?.textContent ?? ''; const code = codeElement?.textContent ?? '';
copy(code).then((success) => { copy(code).then((success) => {
if (success) { if (success) {
Toast.success(t('代码已复制到剪贴板')); Toast.success(t('代ç å·²å¤åˆ¶åˆ°å‰ªè´´æ?));
} else { } else {
Toast.error(t('å¤åˆå¤±è´¥ï¼Œè¯·æåЍå¤åˆ')); Toast.error(t('å¤åˆå¤±è´¥ï¼Œè¯·æåЍå¤åˆ'));
} }
@@ -390,8 +390,7 @@ function _MarkdownContent(props) {
return tryWrapHtmlCode(escapeBrackets(content)); return tryWrapHtmlCode(escapeBrackets(content));
}, [content]); }, [content]);
// 判断是否为用户消息 // 判断是å¦ä¸ºç”¨æˆ·æ¶ˆæ? const isUserMessage = className && className.includes('user-message');
const isUserMessage = className && className.includes('user-message');
const rehypePluginsBase = useMemo(() => { const rehypePluginsBase = useMemo(() => {
const base = [ const base = [
+8 -8
View File
@@ -60,7 +60,7 @@
.user-message a { .user-message a {
color: #87ceeb !important; color: #87ceeb !important;
/* 浅蓝色链接 */ /* æµ…è“色链æŽ?*/
} }
.user-message a:hover { .user-message a:hover {
@@ -68,7 +68,7 @@
/* hoveræ—¶æ›´æµ…çš„è“色 */ /* hoveræ—¶æ›´æµ…çš„è“色 */
} }
/* 表格在用户消息中的样式 */ /* 表格在用户消æ¯ä¸­çš„æ ·å¼?*/
.user-message table { .user-message table {
border-color: rgba(255, 255, 255, 0.3) !important; border-color: rgba(255, 255, 255, 0.3) !important;
} }
@@ -197,7 +197,7 @@
transform: translateY(-1px); transform: translateY(-1px);
} }
/* 代码块样式增强 */ /* 代ç å—æ ·å¼å¢žå¼?*/
pre { pre {
position: relative; position: relative;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
@@ -229,7 +229,7 @@ pre:hover .copy-code-button {
cursor: pointer !important; cursor: pointer !important;
} }
/* 确保按钮可点击 */ /* ç¡®ä¿æŒ‰é’®å¯ç‚¹å‡?*/
.copy-code-button .semi-button { .copy-code-button .semi-button {
pointer-events: auto !important; pointer-events: auto !important;
cursor: pointer !important; cursor: pointer !important;
@@ -242,7 +242,7 @@ pre:hover .copy-code-button {
transform: scale(1.05); transform: scale(1.05);
} }
/* 表格响应式 */ /* 表格å“应å¼?*/
@media (max-width: 768px) { @media (max-width: 768px) {
.markdown-body table { .markdown-body table {
font-size: 12px; font-size: 12px;
@@ -269,7 +269,7 @@ pre:hover .copy-code-button {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
/* 引用块样式增强 */ /* å¼•ç”¨å—æ ·å¼å¢žå¼?*/
.markdown-body blockquote { .markdown-body blockquote {
position: relative; position: relative;
} }
@@ -294,7 +294,7 @@ pre:hover .copy-code-button {
font-weight: bold; font-weight: bold;
} }
/* 分隔线样式 */ /* 分隔线样å¼?*/
.markdown-body hr { .markdown-body hr {
border: none; border: none;
height: 1px; height: 1px;
@@ -393,7 +393,7 @@ pre:hover .copy-code-button {
text-decoration: underline; text-decoration: underline;
} }
/* 警告块样式 */ /* è­¦å‘Šå—æ ·å¼?*/
.markdown-body .warning { .markdown-body .warning {
background-color: var(--semi-color-warning-light-default); background-color: var(--semi-color-warning-light-default);
border-left: 4px solid var(--semi-color-warning); border-left: 4px solid var(--semi-color-warning);
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -35,12 +35,9 @@ import {
* é…åˆ useSecureVerification Hook 使用 * é…åˆ useSecureVerification Hook 使用
* @param {Object} props * @param {Object} props
* @param {boolean} props.visible - æ˜¯å¦æ˜¾ç¤ºæ¨¡æ€æ¡† * @param {boolean} props.visible - æ˜¯å¦æ˜¾ç¤ºæ¨¡æ€æ¡†
* @param {Object} props.verificationMethods - 可用的验证方式 * @param {Object} props.verificationMethods - å¯ç”¨çš„éªŒè¯æ–¹å¼? * @param {Object} props.verificationState - 当å‰éªŒè¯çжæ€? * @param {Function} props.onVerify - 验è¯å›žè°ƒ
* @param {Object} props.verificationState - 当前验证状态
* @param {Function} props.onVerify - 验证回调
* @param {Function} props.onCancel - å–æ¶ˆå›žè°ƒ * @param {Function} props.onCancel - å–æ¶ˆå›žè°ƒ
* @param {Function} props.onCodeChange - 验证码变化回调 * @param {Function} props.onCodeChange - 验è¯ç å˜åŒ–回è°? * @param {Function} props.onMethodSwitch - éªŒè¯æ–¹å¼åˆ‡æ¢å›žè°ƒ
* @param {Function} props.onMethodSwitch - 验证方式切换回调
* @param {string} props.title - æ¨¡æ€æ¡†æ ‡é¢˜ * @param {string} props.title - æ¨¡æ€æ¡†æ ‡é¢˜
* @param {string} props.description - éªŒè¯æè¿°æ–‡æœ¬ * @param {string} props.description - éªŒè¯æè¿°æ–‡æœ¬
*/ */
@@ -106,14 +103,14 @@ const SecureVerificationModal = ({
</svg> </svg>
</div> </div>
<Typography.Title heading={4} className='mb-2'> <Typography.Title heading={4} className='mb-2'>
{t('需要安全验证')} {t('需è¦å®‰å…¨éªŒè¯?)}
</Typography.Title> </Typography.Title>
<Typography.Text type='tertiary'> <Typography.Text type='tertiary'>
{t('您需要先启用两步验证或 Passkey 才能查看敏感信息。')} {t('æ¨éœè¦åˆå¯ç¨ä¸¤æ­¥éªŒè¯æˆ?Passkey æèƒ½æŸ¥çœææŸä¿¡æ¯ã?)}
</Typography.Text> </Typography.Text>
<br /> <br />
<Typography.Text type='tertiary'> <Typography.Text type='tertiary'>
{t('请前往个人设置 → 安全设置进行配置。')} {t('请å‰å¾€ä¸ªäººè®¾ç½® â†?安全设置进行é…ç½®ã€?)}
</Typography.Text> </Typography.Text>
</div> </div>
</Modal> </Modal>
@@ -164,7 +161,7 @@ const SecureVerificationModal = ({
<div style={{ paddingTop: '20px' }}> <div style={{ paddingTop: '20px' }}>
<div style={{ marginBottom: '12px' }}> <div style={{ marginBottom: '12px' }}>
<Input <Input
placeholder={t('请输入6位验证码或8位备用码')} placeholder={t('请è¾å?ä½éªŒè¯ç æˆ?ä½å¤ç¨ç ')}
value={code} value={code}
onChange={onCodeChange} onChange={onCodeChange}
size='large' size='large'
@@ -204,7 +201,7 @@ const SecureVerificationModal = ({
lineHeight: '1.5', lineHeight: '1.5',
}} }}
> >
{t('从认证器应用中获取验证码,或使用备用码')} {t('从认è¯å¨åºç¨ä¸­èŽ·å验è¯ç ï¼Œæˆä½¿ç¨å¤ç¨ç ?)}
</Typography.Text> </Typography.Text>
<div <div
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -25,15 +25,12 @@ import { Modal, Button, Input, Typography } from '@douyinfe/semi-ui';
* å¯å¤ç”¨çš„ä¸¤æ­¥éªŒè¯æ¨¡æ€æ¡†ç»„ä»¶ * å¯å¤ç”¨çš„ä¸¤æ­¥éªŒè¯æ¨¡æ€æ¡†ç»„ä»¶
* @param {Object} props * @param {Object} props
* @param {boolean} props.visible - æ˜¯å¦æ˜¾ç¤ºæ¨¡æ€æ¡† * @param {boolean} props.visible - æ˜¯å¦æ˜¾ç¤ºæ¨¡æ€æ¡†
* @param {string} props.code - 验证码值 * @param {string} props.code - 验è¯ç å€? * @param {boolean} props.loading - æ˜¯å¦æ­£åœ¨éªŒè¯
* @param {boolean} props.loading - 是否正在验证 * @param {Function} props.onCodeChange - 验è¯ç å˜åŒ–回è°? * @param {Function} props.onVerify - 验è¯å›žè°ƒ
* @param {Function} props.onCodeChange - 验证码变化回调
* @param {Function} props.onVerify - 验证回调
* @param {Function} props.onCancel - å–æ¶ˆå›žè°ƒ * @param {Function} props.onCancel - å–æ¶ˆå›žè°ƒ
* @param {string} props.title - æ¨¡æ€æ¡†æ ‡é¢˜ * @param {string} props.title - æ¨¡æ€æ¡†æ ‡é¢˜
* @param {string} props.description - éªŒè¯æè¿°æ–‡æœ¬ * @param {string} props.description - éªŒè¯æè¿°æ–‡æœ¬
* @param {string} props.placeholder - 输入框占位文本 * @param {string} props.placeholder - 输入框å ä½æ–‡æœ? */
*/
const TwoFactorAuthModal = ({ const TwoFactorAuthModal = ({
visible, visible,
code, code,
@@ -114,19 +111,19 @@ const TwoFactorAuthModal = ({
{t('安全验è¯')} {t('安全验è¯')}
</Typography.Text> </Typography.Text>
<Typography.Text className='block text-blue-700 dark:text-blue-300 text-sm mt-1'> <Typography.Text className='block text-blue-700 dark:text-blue-300 text-sm mt-1'>
{description || t('为了保护账户安全,请验证您的两步验证码。')} {description || t('ä¸ºäº†ä¿æŠ¤è´¦æˆ·å®‰å…¨ï¼Œè¯·éªŒè¯æ‚¨çš„两步验è¯ç ã€?)}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
</div> </div>
{/* 验证码输入 */} {/* 验è¯ç è¾“å…?*/}
<div> <div>
<Typography.Text strong className='block mb-2'> <Typography.Text strong className='block mb-2'>
{t('验è¯èº«ä»½')} {t('验è¯èº«ä»½')}
</Typography.Text> </Typography.Text>
<Input <Input
placeholder={placeholder || t('请输入认证器验证码或备用码')} placeholder={placeholder || t('请è¾å¥è®¤è¯å¨éªŒè¯ç æˆå¤ç¨ç ?)}
value={code} value={code}
onChange={onCodeChange} onChange={onCodeChange}
size='large' size='large'
@@ -136,7 +133,7 @@ const TwoFactorAuthModal = ({
/> />
<Typography.Text type='tertiary' size='small' className='mt-2 block'> <Typography.Text type='tertiary' size='small' className='mt-2 block'>
{t( {t(
'支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。', 'æ¯æŒ6ä½TOTP验è¯ç æˆ8ä½å¤ç¨ç ï¼Œå¯åˆ°`个人设置-安全设置-两步验è¯è®¾ç½®`éç½®æˆæŸ¥çœã?,
)} )}
</Typography.Text> </Typography.Text>
</div> </div>
+15 -22
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState } from 'react'; import React, { useState } from 'react';
@@ -34,31 +34,25 @@ const { Text } = Typography;
* 3. 蝐餃揢/ (tabsArea) * 3. 蝐餃揢/ (tabsArea)
* 4. 厰僼 (actionsArea) * 4. 厰僼 (actionsArea)
* 5. 𦦵揣銵典 (searchArea) * 5. 𦦵揣銵典 (searchArea)
* 6. 分页区域 (paginationArea) - 固定在卡片底部 * 6. (paginationArea) - 典㨃? *
* * 銝厩蝐餃嚗? * - type1: ?(憒okensTable) - 讛膩靽⊥ + 厰僼 + 𦦵揣銵典
* 支持三种布局类型: * - type2: 亥砭?(憒ogsTable) - 蝏蠘恣靽⊥ + 𦦵揣銵典
* - type1: 操作型 (如TokensTable) - 描述信息 + 操作按钮 + 搜索表单 * - type3: 憭齿?(憒hannelsTable) - 讛膩靽⊥ + 蝐餃揢 + 厰僼 + 𦦵揣銵典
* - type2: 查询型 (如LogsTable) - 统计信息 + 搜索表单
* - type3: 复杂型 (如ChannelsTable) - 描述信息 + 类型切换 + 操作按钮 + 搜索表单
*/ */
const CardPro = ({ const CardPro = ({
type = 'type1', type = 'type1',
className = '', className = '',
children, children,
// 各个区域的内容 // 摰? statsArea,
statsArea,
descriptionArea, descriptionArea,
tabsArea, tabsArea,
actionsArea, actionsArea,
searchArea, searchArea,
paginationArea, // paginationArea, //
// 卡片属性 // 撅墧? shadows = '',
shadows = '',
bordered = true, bordered = true,
// 自定义样式 // 銋㗇甅撘? style,
style, // 硋遆? t = (key) => key,
// 国际化函数
t = (key) => key,
...props ...props
}) => { }) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
@@ -94,7 +88,7 @@ const CardPro = ({
{/* 蝐餃揢/ - 銝餉type3 */} {/* 蝐餃揢/ - 銝餉type3 */}
{type === 'type3' && tabsArea && <>{tabsArea}</>} {type === 'type3' && tabsArea && <>{tabsArea}</>}
{/* 移动端操作切换按钮 */} {/* 蝘餃𢆡蝡舀雿𨅯?*/}
{isMobile && hasMobileHideableContent && ( {isMobile && hasMobileHideableContent && (
<> <>
<div className='w-full mb-2'> <div className='w-full mb-2'>
@@ -106,7 +100,7 @@ const CardPro = ({
theme='outline' theme='outline'
block block
> >
{showMobileActions ? t('隐藏操作项') : t('显示操作项')} {showMobileActions ? t('憿?) : t('曄內?)}
</Button> </Button>
</div> </div>
</> </>
@@ -130,10 +124,10 @@ const CardPro = ({
<div className='w-full'>{actionsArea}</div> <div className='w-full'>{actionsArea}</div>
))} ))}
{/* 当同时存在操作区和搜索区时,插入分隔线 */} {/* 敶枏雿𨅯躹蝝W躹蝥?*/}
{actionsArea && searchArea && <Divider />} {actionsArea && searchArea && <Divider />}
{/* 搜索表单区域 - 所有类型都可能有 */} {/* 𦦵揣銵典 - 厩掩?*/}
{searchArea && <div className='w-full'>{searchArea}</div>} {searchArea && <div className='w-full'>{searchArea}</div>}
</div> </div>
</div> </div>
@@ -193,8 +187,7 @@ CardPro.propTypes = {
paginationArea: PropTypes.node, paginationArea: PropTypes.node,
// 銵冽聢 // 銵冽聢
children: PropTypes.node, children: PropTypes.node,
// 国际化函数 // 硋遆? t: PropTypes.func,
t: PropTypes.func,
}; };
export default CardPro; export default CardPro;
+4 -7
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
@@ -34,11 +34,8 @@ import { useIsMobile } from '../../../hooks/common/useIsMobile';
import { useMinimumLoadingTime } from '../../../hooks/common/useMinimumLoadingTime'; import { useMinimumLoadingTime } from '../../../hooks/common/useMinimumLoadingTime';
/** /**
* CardTable 响应式表格组件 * CardTable 撘讛”隞? *
* * Y垢皜脫 Semi-UI ?Table 蝏辣嚗銁蝘餃𢆡蝡臬銵峕㺭格葡𤘪 Card 敶W? * 霂亦隞嗡 Table 蝏 API 靽脲 Table CardTable 喳虾? */
* 在桌面端渲染 Semi-UI 的 Table 组件,在移动端则将每一行数据渲染成 Card 形式。
* 该组件与 Table 组件的大部分 API 保持一致,只需将原 Table 换成 CardTable 即可。
*/
const CardTable = ({ const CardTable = ({
columns = [], columns = [],
dataSource = [], dataSource = [],
+10 -13
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -23,8 +23,7 @@ import { Card, Button, Typography, Tag } from '@douyinfe/semi-ui';
import { copy, showSuccess } from '../../../helpers'; import { copy, showSuccess } from '../../../helpers';
/** /**
* 解析密钥数据,支持多种格式 * è§£æžå¯†é’¥æ•°æ®ï¼Œæ”¯æŒå¤šç§æ ¼å¼? * @param {string} keyData - 密钥数æ®
* @param {string} keyData - 密钥数据
* @param {Function} t - 翻译函数 * @param {Function} t - 翻译函数
* @returns {Array} è§£æžåŽçš„密钥数组 * @returns {Array} è§£æžåŽçš„密钥数组
*/ */
@@ -33,8 +32,7 @@ const parseChannelKeys = (keyData, t) => {
const trimmed = keyData.trim(); const trimmed = keyData.trim();
// 检查是否是JSON数组格式(如Vertex AI // æ£€æŸ¥æ˜¯å¦æ˜¯JSON数组格å¼ï¼ˆå¦‚Vertex AIï¼? if (trimmed.startsWith('[')) {
if (trimmed.startsWith('[')) {
try { try {
const parsed = JSON.parse(trimmed); const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
@@ -47,8 +45,7 @@ const parseChannelKeys = (keyData, t) => {
})); }));
} }
} catch (e) { } catch (e) {
// 如果解析失败,按普通文本处理 // 如果解æžå¤±è´¥ï¼ŒæŒ‰æ™®é€šæ–‡æœ¬å¤„ç? console.warn('Failed to parse JSON keys:', e);
console.warn('Failed to parse JSON keys:', e);
} }
} }
@@ -102,12 +99,12 @@ const ChannelKeyDisplay = ({
const handleCopyKey = (content) => { const handleCopyKey = (content) => {
copy(content); copy(content);
showSuccess(t('密钥已复制到剪贴板')); showSuccess(t('密钥已å¤åˆ¶åˆ°å‰ªè´´æ?));
}; };
return ( return (
<div className='space-y-4'> <div className='space-y-4'>
{/* 成功状态 */} {/* æˆåŠŸçŠ¶æ€?*/}
{showSuccessIcon && ( {showSuccessIcon && (
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<svg <svg
@@ -136,7 +133,7 @@ const ChannelKeyDisplay = ({
{isMultipleKeys && ( {isMultipleKeys && (
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Typography.Text type='tertiary' size='small'> <Typography.Text type='tertiary' size='small'>
{t('共 {{count}} 个密钥', { count: parsedKeys.length })} {t('å?{{count}} 个å¯é?, { count: parsedKeys.length })}
</Typography.Text> </Typography.Text>
<Button <Button
size='small' size='small'
@@ -234,7 +231,7 @@ const ChannelKeyDisplay = ({
/> />
</svg> </svg>
{t( {t(
'检测到多个密钥,您可以单独复制每个密钥,或点击复制全部获取完整内容。', '检测到多个密钥,您å¯ä»¥å•独å¤åˆ¶æ¯ä¸ªå¯†é’¥ï¼Œæˆ–点击å¤åˆ¶å…¨éƒ¨èŽ·å–完整内容ã€?,
)} )}
</Typography.Text> </Typography.Text>
</div> </div>
@@ -266,7 +263,7 @@ const ChannelKeyDisplay = ({
<Typography.Text className='block text-yellow-700 dark:text-yellow-300 text-sm mt-1'> <Typography.Text className='block text-yellow-700 dark:text-yellow-300 text-sm mt-1'>
{warningText || {warningText ||
t( t(
'请妥善保管密钥信息,不要泄露给他人。如有安全疑虑,请及时更换密钥。', '请妥åä¿ç®¡å¯é¥ä¿¡æ¯ï¼Œä¸è¦æ³éœ²ç»ä»äººã妿œå®å¨çèï¼Œè¯·åŠææ´æ¢å¯é¥ã?,
)} )}
</Typography.Text> </Typography.Text>
</div> </div>
+3 -5
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -24,9 +24,7 @@ import { useIsMobile } from '../../../hooks/common/useIsMobile';
/** /**
* 蝝批璅∪厰僼蝏 * 蝝批璅∪厰僼蝏
* 用于在自适应列表和紧凑列表之间切换 * 刻䌊𡑒”𣬚揮銵其? * 函宏函垢嗉䌊𧶏牐蛹蝘餃𢆡蝡臭蝙?曄內憿?厰僼交綉摰寞遬蝷? */
* 在移动端时自动隐藏,因为移动端使用"显示操作项"按钮来控制内容显示
*/
const CompactModeToggle = ({ const CompactModeToggle = ({
compactMode, compactMode,
setCompactMode, setCompactMode,
+26 -36
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useEffect, useCallback, useMemo } from 'react'; import React, { useState, useEffect, useCallback, useMemo } from 'react';
@@ -65,13 +65,12 @@ const JSONEditor = ({
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
// 将对象转换为键值对数组(包含唯一ID // 将对象转æ¢ä¸ºé”®å€¼å¯¹æ•°ç»„(包å«å”¯ä¸€IDï¼? const objectToKeyValueArray = useCallback((obj, prevPairs = []) => {
const objectToKeyValueArray = useCallback((obj, prevPairs = []) => {
if (!obj || typeof obj !== 'object') return []; if (!obj || typeof obj !== 'object') return [];
const entries = Object.entries(obj); const entries = Object.entries(obj);
return entries.map(([key, value], index) => { return entries.map(([key, value], index) => {
// 如果上一次转换后同位置的键一致,则沿用其 id,保持 React key 稳定 // 如果上一次转æ¢åŽåŒä½ç½®çš„键一致,则沿用其 idï¼Œä¿æŒ?React key 稳定
const prev = prevPairs[index]; const prev = prevPairs[index];
const shouldReuseId = prev && prev.key === key; const shouldReuseId = prev && prev.key === key;
return { return {
@@ -117,8 +116,7 @@ const JSONEditor = ({
return ''; return '';
}); });
// 根据键数量决定默认编辑模式 // æ ¹æ®é”®æ•°é‡å†³å®šé»˜è®¤ç¼–辑模å¼? const [editMode, setEditMode] = useState(() => {
const [editMode, setEditMode] = useState(() => {
if (typeof value === 'string' && value.trim()) { if (typeof value === 'string' && value.trim()) {
try { try {
const parsed = JSON.parse(value); const parsed = JSON.parse(value);
@@ -160,8 +158,7 @@ const JSONEditor = ({
parsed = value; parsed = value;
} }
// 只在外部值真正改变时更新,避免循环更新 // åªåœ¨å¤–éƒ¨å€¼çœŸæ­£æ”¹å˜æ—¶æ›´æ–°ï¼Œé¿å…循环更æ–? const currentObj = keyValueArrayToObject(keyValuePairs);
const currentObj = keyValueArrayToObject(keyValuePairs);
if (JSON.stringify(parsed) !== JSON.stringify(currentObj)) { if (JSON.stringify(parsed) !== JSON.stringify(currentObj)) {
setKeyValuePairs(objectToKeyValueArray(parsed, keyValuePairs)); setKeyValuePairs(objectToKeyValueArray(parsed, keyValuePairs));
} }
@@ -172,8 +169,7 @@ const JSONEditor = ({
} }
}, [value]); }, [value]);
// 外部 value 变化时,若不在手动模式,则同步手动文本 // 外部 value å˜åŒ–时,若ä¸åœ¨æ‰‹åŠ¨æ¨¡å¼ï¼Œåˆ™åŒæ­¥æ‰‹åŠ¨æ–‡æœ? useEffect(() => {
useEffect(() => {
if (editMode !== 'manual') { if (editMode !== 'manual') {
if (typeof value === 'string') setManualText(value); if (typeof value === 'string') setManualText(value);
else if (value && typeof value === 'object') else if (value && typeof value === 'object')
@@ -194,8 +190,7 @@ const JSONEditor = ({
setJsonError(''); setJsonError('');
// 通过formApi设置值 // 通过formApi设置å€? if (formApi && field) {
if (formApi && field) {
formApi.setValue(field, jsonString); formApi.setValue(field, jsonString);
} }
@@ -204,8 +199,7 @@ const JSONEditor = ({
[onChange, formApi, field, keyValueArrayToObject], [onChange, formApi, field, keyValueArrayToObject],
); );
// 处理手动编辑的数据变化 // å¤„ç†æ‰‹åŠ¨ç¼–è¾‘çš„æ•°æ®å˜åŒ? const handleManualChange = useCallback(
const handleManualChange = useCallback(
(newValue) => { (newValue) => {
setManualText(newValue); setManualText(newValue);
if (newValue && newValue.trim()) { if (newValue && newValue.trim()) {
@@ -301,8 +295,7 @@ const JSONEditor = ({
[keyValuePairs, handleVisualChange], [keyValuePairs, handleVisualChange],
); );
// 更新值 // æ›´æ–°å€? const updateValue = useCallback(
const updateValue = useCallback(
(id, newValue) => { (id, newValue) => {
const newPairs = keyValuePairs.map((pair) => const newPairs = keyValuePairs.map((pair) =>
pair.id === id ? { ...pair, value: newValue } : pair, pair.id === id ? { ...pair, value: newValue } : pair,
@@ -335,8 +328,7 @@ const JSONEditor = ({
keyValuePairs, keyValuePairs,
]); ]);
// 渲染值输入控件(支持嵌套) // 渲染值输入控件(支æŒåµŒå¥—ï¼? const renderValueInput = (pairId, pairKey, value) => {
const renderValueInput = (pairId, pairKey, value) => {
const valueType = typeof value; const valueType = typeof value;
if (valueType === 'boolean') { if (valueType === 'boolean') {
@@ -386,7 +378,7 @@ const JSONEditor = ({
// 字符串或其他原始类型 // 字符串或其他原始类型
return ( return (
<Input <Input
placeholder={t('参数值')} placeholder={t('傿•°å€?)}
value={String(value)} value={String(value)}
suffix={renderStringValueSuffix?.({ pairId, pairKey, value })} suffix={renderStringValueSuffix?.({ pairId, pairKey, value })}
onChange={(newValue) => { onChange={(newValue) => {
@@ -406,11 +398,10 @@ const JSONEditor = ({
); );
}; };
// 渲染键值对编辑器 // 渲染键值对编辑å™? const renderKeyValueEditor = () => {
const renderKeyValueEditor = () => {
return ( return (
<div className='space-y-1'> <div className='space-y-1'>
{/* 重复键警告 */} {/* é‡å¤é”®è­¦å‘?*/}
{duplicateKeys.size > 0 && ( {duplicateKeys.size > 0 && (
<Banner <Banner
type='warning' type='warning'
@@ -421,7 +412,7 @@ const JSONEditor = ({
<Text>{Array.from(duplicateKeys).join(', ')}</Text> <Text>{Array.from(duplicateKeys).join(', ')}</Text>
<br /> <br />
<Text type='tertiary' size='small'> <Text type='tertiary' size='small'>
{t('注意:JSON中重复的键只会保留最后一个同名键的值')} {t('注æï¼šJSON中éå¤çšé®åªä¼šä¿çæœåŽä¸ä¸ªåŒåé®çšå?)}
</Text> </Text>
</div> </div>
} }
@@ -457,8 +448,8 @@ const JSONEditor = ({
<Tooltip <Tooltip
content={ content={
isLastDuplicate isLastDuplicate
? t('这是重复键中的最后一个,其值将被使用') ? t('这是é‡å¤é”®ä¸­çš„æœ€åŽä¸€ä¸ªï¼Œå…¶å€¼å°†è¢«ä½¿ç”?)
: t('重复的键名,此值将被后面的同名键覆盖') : t('éå¤çšé®å,此å¼å°è¢«åŽé¢çšåŒåé®è¦ç?)
} }
> >
<IconAlertTriangle <IconAlertTriangle
@@ -502,14 +493,13 @@ const JSONEditor = ({
); );
}; };
// 渲染区域编辑器(特殊格式)- 也需要改造以支持重复键 // 渲染区域编辑器(特殊格å¼ï¼? ä¹Ÿéœ€è¦æ”¹é€ ä»¥æ”¯æŒé‡å¤é”? const renderRegionEditor = () => {
const renderRegionEditor = () => {
const defaultPair = keyValuePairs.find((pair) => pair.key === 'default'); const defaultPair = keyValuePairs.find((pair) => pair.key === 'default');
const modelPairs = keyValuePairs.filter((pair) => pair.key !== 'default'); const modelPairs = keyValuePairs.filter((pair) => pair.key !== 'default');
return ( return (
<div className='space-y-2'> <div className='space-y-2'>
{/* 重复键警告 */} {/* é‡å¤é”®è­¦å‘?*/}
{duplicateKeys.size > 0 && ( {duplicateKeys.size > 0 && (
<Banner <Banner
type='warning' type='warning'
@@ -520,7 +510,7 @@ const JSONEditor = ({
<Text>{Array.from(duplicateKeys).join(', ')}</Text> <Text>{Array.from(duplicateKeys).join(', ')}</Text>
<br /> <br />
<Text type='tertiary' size='small'> <Text type='tertiary' size='small'>
{t('注意:JSON中重复的键只会保留最后一个同名键的值')} {t('注æ„:JSON中é‡å¤çš„é”®åªä¼šä¿ç•™æœ€åŽä¸€ä¸ªåŒå键的å€?)}
</Text> </Text>
</div> </div>
} }
@@ -567,7 +557,7 @@ const JSONEditor = ({
status={isDuplicate ? 'warning' : undefined} status={isDuplicate ? 'warning' : undefined}
/> />
{isDuplicate && ( {isDuplicate && (
<Tooltip content={t('重复的键名')}> <Tooltip content={t('éå¤çšé®å?)}>
<IconAlertTriangle <IconAlertTriangle
className='absolute right-2 top-1/2 transform -translate-y-1/2' className='absolute right-2 top-1/2 transform -translate-y-1/2'
style={{ color: '#faad14', fontSize: '14px' }} style={{ color: '#faad14', fontSize: '14px' }}
@@ -642,7 +632,7 @@ const JSONEditor = ({
} }
}} }}
> >
<TabPane tab={t('可视化')} itemKey='visual' /> <TabPane tab={t('å¯è§†åŒ?)} itemKey='visual' />
<TabPane tab={t('æåЍç¼è¾')} itemKey='manual' /> <TabPane tab={t('æåЍç¼è¾')} itemKey='manual' />
</Tabs> </Tabs>
@@ -666,11 +656,11 @@ const JSONEditor = ({
/> />
)} )}
{/* 编辑器内容 */} {/* 编辑器内å®?*/}
{editMode === 'visual' ? ( {editMode === 'visual' ? (
<div> <div>
{renderVisualEditor()} {renderVisualEditor()}
{/* 隐藏的Form字段用于验证和数据绑定 */} {/* éšè—çš„Form字段用于验è¯å’Œæ•°æ®ç»‘å®?*/}
<Form.Input <Form.Input
field={field} field={field}
value={value} value={value}
@@ -689,7 +679,7 @@ const JSONEditor = ({
showClear={showClear} showClear={showClear}
rows={Math.max(8, manualText ? manualText.split('\n').length : 8)} rows={Math.max(8, manualText ? manualText.split('\n').length : 8)}
/> />
{/* 隐藏的Form字段用于验证和数据绑定 */} {/* éšè—çš„Form字段用于验è¯å’Œæ•°æ®ç»‘å®?*/}
<Form.Input <Form.Input
field={field} field={field}
value={value} value={value}
@@ -701,7 +691,7 @@ const JSONEditor = ({
</div> </div>
)} )}
{/* 额外文本显示在卡片底部 */} {/* é¢å¤–文本显示在å¡ç‰‡åº•éƒ?*/}
{extraText && ( {extraText && (
<Divider margin='12px' align='center'> <Divider margin='12px' align='center'>
<Text type='tertiary' size='small'> <Text type='tertiary' size='small'>
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { import React, {
@@ -28,8 +28,7 @@ import React, {
} from 'react'; } from 'react';
/** /**
* ScrollableContainer 可滚动容器组件 * ScrollableContainer 坿»šåŠ¨å®¹å™¨ç»„ä»? *
*
* æä¾›è‡ªåŠ¨æ£€æµ‹æ»šåŠ¨çŠ¶æ€å’Œæ˜¾ç¤ºæ¸å˜æŒ‡ç¤ºå™¨çš„功能 * æä¾›è‡ªåŠ¨æ£€æµ‹æ»šåŠ¨çŠ¶æ€å’Œæ˜¾ç¤ºæ¸å˜æŒ‡ç¤ºå™¨çš„功能
* 当内容超出容器高度且未滚动到底部时,会显示底部æ¸å˜æŒ‡ç¤ºå™¨ * 当内容超出容器高度且未滚动到底部时,会显示底部æ¸å˜æŒ‡ç¤ºå™¨
* *
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
@@ -33,19 +33,15 @@ import {
import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons'; import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons';
/** /**
* 通用可选择按钮组组件 * 通用å¯é€‰æ‹©æŒ‰é’®ç»„组ä»? *
*
* @param {string} title 标题 * @param {string} title 标题
* @param {Array<{value:any,label:string,icon?:React.ReactNode,tagCount?:number}>} items 按钮项 * @param {Array<{value:any,label:string,icon?:React.ReactNode,tagCount?:number}>} items 按钮é¡? * @param {*|Array} activeValue 当剿¿€æ´»çš„值,å¯ä»¥æ˜¯å•个值或数组(多选)
* @param {*|Array} activeValue 当前激活的值,可以是单个值或数组(多选)
* @param {(value:any)=>void} onChange 选择改å˜å›žè°ƒ * @param {(value:any)=>void} onChange 选择改å˜å›žè°ƒ
* @param {function} t i18n * @param {function} t i18n
* @param {object} style é¢å¤–æ ·å¼ * @param {object} style é¢å¤–æ ·å¼
* @param {boolean} collapsible æ˜¯å¦æ”¯æŒæŠ˜å ï¼Œé»˜è®¤true * @param {boolean} collapsible æ˜¯å¦æ”¯æŒæŠ˜å ï¼Œé»˜è®¤true
* @param {number} collapseHeight 折叠时的高度,默认200 * @param {number} collapseHeight æŠ˜å æ—¶çš„高度,默è®?00
* @param {boolean} withCheckbox 是否启用前缀 Checkbox 来控制激活状态 * @param {boolean} withCheckbox 是å¦å¯ç”¨å‰ç¼€ Checkbox æ¥æŽ§åˆ¶æ¿€æ´»çŠ¶æ€? * @param {boolean} loading 是å¦å¤„于加载状æ€? * @param {string} variant 颜色å˜ä½“: 'violet' | 'teal' | 'amber' | 'rose' | 'green',ä¸ä¼ åˆ™ä½¿ç”¨é»˜è®¤è“色
* @param {boolean} loading 是否处于加载状态
* @param {string} variant 颜色变体: 'violet' | 'teal' | 'amber' | 'rose' | 'green',不传则使用默认蓝色
*/ */
const SelectableButtonGroup = ({ const SelectableButtonGroup = ({
title, title,
@@ -89,10 +85,9 @@ const SelectableButtonGroup = ({
// 基于容器宽度计算å“应å¼åˆ—数和标签显示策略 // 基于容器宽度计算å“应å¼åˆ—数和标签显示策略
const getResponsiveConfig = () => { const getResponsiveConfig = () => {
if (containerWidth <= 280) return { columns: 1, showTags: true }; // 极窄:1列+标签 if (containerWidth <= 280) return { columns: 1, showTags: true }; // æžçª„ï¼?åˆ?标签
if (containerWidth <= 380) return { columns: 2, showTags: true }; // 窄屏:2列+标签 if (containerWidth <= 380) return { columns: 2, showTags: true }; // 窄å±ï¼?åˆ?标签
if (containerWidth <= 460) return { columns: 3, showTags: false }; // 中等:3列不加标签 if (containerWidth <= 460) return { columns: 3, showTags: false }; // 中等ï¼?列ä¸åŠ æ ‡ç­? return { columns: 3, showTags: true }; // 最宽:3åˆ?标签
return { columns: 3, showTags: true }; // 最宽:3列+标签
}; };
const { columns: perRow, showTags: shouldShowTags } = getResponsiveConfig(); const { columns: perRow, showTags: shouldShowTags } = getResponsiveConfig();
@@ -100,11 +95,9 @@ const SelectableButtonGroup = ({
const needCollapse = collapsible && items.length > perRow * maxVisibleRows; const needCollapse = collapsible && items.length > perRow * maxVisibleRows;
const showSkeleton = useMinimumLoadingTime(loading); const showSkeleton = useMinimumLoadingTime(loading);
// 统一使用紧凑的网格间距 // 统一使用紧凑的网格间è·? const gutterSize = [4, 4];
const gutterSize = [4, 4];
// 计算 Semi UI Col 的 span 值 // 计算 Semi UI Col çš?span å€? const getColSpan = () => {
const getColSpan = () => {
return Math.floor(24 / perRow); return Math.floor(24 / perRow);
}; };
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -44,7 +44,7 @@ const AnnouncementsPanel = ({
<Bell size={16} /> <Bell size={16} />
{t('系统公告')} {t('系统公告')}
<Tag color='white' shape='circle'> <Tag color='white' shape='circle'>
{t('显示最新20条')} {t('显示最æ–?0æ?)}
</Tag> </Tag>
</div> </div>
{/* 图例 */} {/* 图例 */}
+3 -3
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -71,7 +71,7 @@ const ApiInfoPanel = ({
onClick={() => handleSpeedTest(api.url)} onClick={() => handleSpeedTest(api.url)}
className='cursor-pointer hover:opacity-80 text-xs' className='cursor-pointer hover:opacity-80 text-xs'
> >
{t('测速')} {t('测é€?)}
</Tag> </Tag>
<Tag <Tag
prefixIcon={<ExternalLink size={12} />} prefixIcon={<ExternalLink size={12} />}
+5 -5
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -53,15 +53,15 @@ const ChartsPanel = ({
activeKey={activeChartTab} activeKey={activeChartTab}
onChange={setActiveChartTab} onChange={setActiveChartTab}
> >
<TabPane tab={<span>{t('消耗分布')}</span>} itemKey='1' /> <TabPane tab={<span>{t('消耗分å¸?)}</span>} itemKey='1' />
<TabPane tab={<span>{t('è°ƒç¨è势')}</span>} itemKey='2' /> <TabPane tab={<span>{t('è°ƒç¨è势')}</span>} itemKey='2' />
<TabPane tab={<span>{t('è°ƒç¨æ¬¡æ°åˆå¸ƒ')}</span>} itemKey='3' /> <TabPane tab={<span>{t('è°ƒç¨æ¬¡æ°åˆå¸ƒ')}</span>} itemKey='3' />
<TabPane tab={<span>{t('è°ƒç¨æ¬¡æ°æŽè¡Œ')}</span>} itemKey='4' /> <TabPane tab={<span>{t('è°ƒç¨æ¬¡æ°æŽè¡Œ')}</span>} itemKey='4' />
{isAdminUser && ( {isAdminUser && (
<TabPane tab={<span>{t('用户消耗排行')}</span>} itemKey='5' /> <TabPane tab={<span>{t('ç¨æˆ·æˆèæŽè¡?)}</span>} itemKey='5' />
)} )}
{isAdminUser && ( {isAdminUser && (
<TabPane tab={<span>{t('用户消耗趋势')}</span>} itemKey='6' /> <TabPane tab={<span>{t('用户消耗趋åŠ?)}</span>} itemKey='6' />
)} )}
</Tabs> </Tabs>
</div> </div>
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
+3 -3
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -90,7 +90,7 @@ const StatsCards = ({
navigate('/console/topup'); navigate('/console/topup');
}} }}
> >
{t('充值')} {t('å……å€?)}
</Tag> </Tag>
) : ( ) : (
(loading || (loading ||
+3 -3
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -54,7 +54,7 @@ const UptimePanel = ({
<div className='flex items-center justify-between w-full gap-2'> <div className='flex items-center justify-between w-full gap-2'>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Gauge size={16} /> <Gauge size={16} />
{t('服务可用性')} {t('æœåŠ¡å¯ç”¨æ€?)}
</div> </div>
<Button <Button
icon={<RefreshCw size={14} />} icon={<RefreshCw size={14} />}
+5 -5
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect } from 'react';
@@ -182,7 +182,7 @@ const Dashboard = () => {
CHART_CONFIG={CHART_CONFIG} CHART_CONFIG={CHART_CONFIG}
/> />
{/* API信息和图表面*/} {/* API信息和图表面?*/}
<div className='mb-4'> <div className='mb-4'>
<div <div
className={`grid grid-cols-1 gap-4 ${dashboardData.hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`} className={`grid grid-cols-1 gap-4 ${dashboardData.hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`}
@@ -218,7 +218,7 @@ const Dashboard = () => {
</div> </div>
</div> </div>
{/* 系统公告和常见问答卡*/} {/* 系统公告和常见问答卡?*/}
{dashboardData.hasInfoPanels && ( {dashboardData.hasInfoPanels && (
<div className='mb-4'> <div className='mb-4'>
<div className='grid grid-cols-1 lg:grid-cols-4 gap-4'> <div className='grid grid-cols-1 lg:grid-cols-4 gap-4'>
@@ -249,7 +249,7 @@ const Dashboard = () => {
/> />
)} )}
{/* 服务可用性卡*/} {/* 服务可用性卡?*/}
{dashboardData.uptimeEnabled && ( {dashboardData.uptimeEnabled && (
<UptimePanel <UptimePanel
uptimeData={dashboardData.uptimeData} uptimeData={dashboardData.uptimeData}
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useRef } from 'react'; import React, { useRef } from 'react';
@@ -91,7 +91,7 @@ const SearchModal = ({
field: 'username', field: 'username',
label: t('用户åç§°'), label: t('用户åç§°'),
value: username, value: username,
placeholder: t('可选值'), placeholder: t('å¯é€‰å€?),
name: 'username', name: 'username',
onChange: (value) => handleInputChange(value, 'username'), onChange: (value) => handleInputChange(value, 'username'),
})} })}
+5 -5
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState, useMemo, useContext } from 'react'; import React, { useEffect, useState, useMemo, useContext } from 'react';
@@ -84,7 +84,7 @@ const FooterBar = () => {
rel='noopener noreferrer' rel='noopener noreferrer'
className='!text-semi-color-text-1' className='!text-semi-color-text-1'
> >
{t('功能特性')} {t('鍔熻兘鐗规€?)}
</a> </a>
</div> </div>
</div> </div>
@@ -100,7 +100,7 @@ const FooterBar = () => {
rel='noopener noreferrer' rel='noopener noreferrer'
className='!text-semi-color-text-1' className='!text-semi-color-text-1'
> >
{t('快速开始')} {t('€熷紑濮?)}
</a> </a>
<a <a
href='https://docs.newapi.pro/installation/' href='https://docs.newapi.pro/installation/'
@@ -191,7 +191,7 @@ const FooterBar = () => {
<div className='flex flex-col md:flex-row items-center justify-between w-full max-w-[1110px] gap-6'> <div className='flex flex-col md:flex-row items-center justify-between w-full max-w-[1110px] gap-6'>
<div className='flex flex-wrap items-center gap-2'> <div className='flex flex-wrap items-center gap-2'>
<Typography.Text className='text-sm !text-semi-color-text-1'> <Typography.Text className='text-sm !text-semi-color-text-1'>
© {currentYear} {systemName}. {t('版权所有')} {currentYear} {systemName}. {t('鐗堟潈鎵€鏈?)}
</Typography.Text> </Typography.Text>
</div> </div>
+3 -3
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState, useContext, useMemo } from 'react'; import React, { useEffect, useState, useContext, useMemo } from 'react';
@@ -120,7 +120,7 @@ const NoticeModal = ({
if (loading) { if (loading) {
return ( return (
<div className='py-12'> <div className='py-12'>
<Empty description={t('加载中...')} /> <Empty description={t('加载ä¸?..')} />
</div> </div>
); );
} }
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import HeaderBar from './headerbar'; import HeaderBar from './headerbar';
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect } from 'react';
+14 -19
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
@@ -172,7 +172,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
className: isAdmin() ? '' : 'tableHiddle', className: isAdmin() ? '' : 'tableHiddle',
}, },
{ {
text: t('兑换码管理'), text: t('å…‘æ¢ç ç®¡ç?),
itemKey: 'redemption', itemKey: 'redemption',
to: '/redemption', to: '/redemption',
className: isAdmin() ? '' : 'tableHiddle', className: isAdmin() ? '' : 'tableHiddle',
@@ -203,7 +203,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
const chatMenuItems = useMemo(() => { const chatMenuItems = useMemo(() => {
const items = [ const items = [
{ {
text: t('操练场'), text: t('æç»ƒåœ?),
itemKey: 'playground', itemKey: 'playground',
to: '/playground', to: '/playground',
}, },
@@ -223,8 +223,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
return filteredItems; return filteredItems;
}, [chatItems, t, isModuleVisible]); }, [chatItems, t, isModuleVisible]);
// 更新路由映射,添加聊天路由 // 更新路由映射,添加èŠå¤©è·¯ç”? const updateRouterMapWithChats = (chats) => {
const updateRouterMapWithChats = (chats) => {
const newRouterMap = { ...routerMap }; const newRouterMap = { ...routerMap };
if (Array.isArray(chats) && chats.length > 0) { if (Array.isArray(chats) && chats.length > 0) {
@@ -237,8 +236,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
return newRouterMap; return newRouterMap;
}; };
// 加载聊天项 // 加载èŠå¤©é¡? useEffect(() => {
useEffect(() => {
let chats = localStorage.getItem('chats'); let chats = localStorage.getItem('chats');
if (chats) { if (chats) {
try { try {
@@ -307,13 +305,11 @@ const SiderBar = ({ onNavigate = () => {} }) => {
} }
}, [collapsed]); }, [collapsed]);
// 选中高亮颜色(统一) // 选中高亮颜色(统一ï¼? const SELECTED_COLOR = 'var(--semi-color-primary)';
const SELECTED_COLOR = 'var(--semi-color-primary)';
// 渲染自定义èœå•项 // 渲染自定义èœå•项
const renderNavItem = (item) => { const renderNavItem = (item) => {
// 跳过隐藏的项目 // 跳过éšè—的项ç›? if (item.className === 'tableHiddle') return null;
if (item.className === 'tableHiddle') return null;
const isSelected = selectedKeys.includes(item.itemKey); const isSelected = selectedKeys.includes(item.itemKey);
const textColor = isSelected ? SELECTED_COLOR : 'inherit'; const textColor = isSelected ? SELECTED_COLOR : 'inherit';
@@ -417,8 +413,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
const to = const to =
routerMapState[props.itemKey] || routerMap[props.itemKey]; routerMapState[props.itemKey] || routerMap[props.itemKey];
// 如果没有路由,直接返回元素 // 如果没有路由,直接返回元ç´? if (!to) return itemElement;
if (!to) return itemElement;
return ( return (
<Link <Link
@@ -453,13 +448,13 @@ const SiderBar = ({ onNavigate = () => {} }) => {
</div> </div>
)} )}
{/* 控制台区域 */} {/* 控制å°åŒºåŸ?*/}
{hasSectionVisibleModules('console') && ( {hasSectionVisibleModules('console') && (
<> <>
<Divider className='sidebar-divider' /> <Divider className='sidebar-divider' />
<div> <div>
{!collapsed && ( {!collapsed && (
<div className='sidebar-group-label'>{t('控制台')}</div> <div className='sidebar-group-label'>{t('控制å?)}</div>
)} )}
{workspaceItems.map((item) => renderNavItem(item))} {workspaceItems.map((item) => renderNavItem(item))}
</div> </div>
@@ -479,13 +474,13 @@ const SiderBar = ({ onNavigate = () => {} }) => {
</> </>
)} )}
{/* 管理员区域 - 只在管理员时显示且配置允许时显示 */} {/* 管ç†å‘˜åŒºåŸ?- åªåœ¨ç®¡ç†å‘˜æ—¶æ˜¾ç¤ºä¸”é…ç½®å…许时显示 */}
{isAdmin() && hasSectionVisibleModules('admin') && ( {isAdmin() && hasSectionVisibleModules('admin') && (
<> <>
<Divider className='sidebar-divider' /> <Divider className='sidebar-divider' />
<div> <div>
{!collapsed && ( {!collapsed && (
<div className='sidebar-group-label'>{t('管理员')}</div> <div className='sidebar-group-label'>{t('管çå?)}</div>
)} )}
{adminItems.map((item) => renderNavItem(item))} {adminItems.map((item) => renderNavItem(item))}
</div> </div>
@@ -525,7 +520,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
: { padding: '4px 12px', width: '100%' } : { padding: '4px 12px', width: '100%' }
} }
> >
{!collapsed ? t('收起侧边栏') : null} {!collapsed ? t('æèµ·ä¾§è¾¹æ ?) : null}
</Button> </Button>
</SkeletonWrapper> </SkeletonWrapper>
</div> </div>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -37,8 +37,7 @@ const SkeletonWrapper = ({
return children; return children;
} }
// 导航链接骨架屏 // 导航链接骨架å±? const renderNavigationSkeleton = () => {
const renderNavigationSkeleton = () => {
const skeletonLinkClasses = isMobile const skeletonLinkClasses = isMobile
? 'flex items-center gap-1 p-1 w-full rounded-md' ? 'flex items-center gap-1 p-1 w-full rounded-md'
: 'flex items-center gap-1 p-2 rounded-md'; : 'flex items-center gap-1 p-2 rounded-md';
@@ -60,7 +59,7 @@ const SkeletonWrapper = ({
)); ));
}; };
// 用户区域骨架屏 (头像 + 文本) // 用户区域骨架å±?(å¤´åƒ + 文本)
const renderUserAreaSkeleton = () => { const renderUserAreaSkeleton = () => {
return ( return (
<div <div
@@ -88,8 +87,7 @@ const SkeletonWrapper = ({
); );
}; };
// Logo图片骨架屏 // Logo图片骨架å±? const renderImageSkeleton = () => {
const renderImageSkeleton = () => {
return ( return (
<Skeleton <Skeleton
loading={true} loading={true}
@@ -104,8 +102,7 @@ const SkeletonWrapper = ({
); );
}; };
// 系统名称骨架屏 // 系统å称骨架å±? const renderTitleSkeleton = () => {
const renderTitleSkeleton = () => {
return ( return (
<Skeleton <Skeleton
loading={true} loading={true}
@@ -115,8 +112,7 @@ const SkeletonWrapper = ({
); );
}; };
// 通用文本骨架屏 // 通用文本骨架å±? const renderTextSkeleton = () => {
const renderTextSkeleton = () => {
return ( return (
<div className={className}> <div className={className}>
<Skeleton <Skeleton
@@ -128,8 +124,7 @@ const SkeletonWrapper = ({
); );
}; };
// 按钮骨架屏(支持圆角) // 按钮骨架å±ï¼ˆæ”¯æŒåœ†è§’ï¼? const renderButtonSkeleton = () => {
const renderButtonSkeleton = () => {
return ( return (
<div className={className}> <div className={className}>
<Skeleton <Skeleton
@@ -143,7 +138,7 @@ const SkeletonWrapper = ({
); );
}; };
// 侧边栏导航项骨架屏 (图标 + 文本) // ä¾§è¾¹æ å¯¼èˆªé¡¹éª¨æž¶å±?(图标 + 文本)
const renderSidebarNavItemSkeleton = () => { const renderSidebarNavItemSkeleton = () => {
return Array(count) return Array(count)
.fill(null) .fill(null)
@@ -152,7 +147,7 @@ const SkeletonWrapper = ({
key={index} key={index}
className={`flex items-center p-2 mb-1 rounded-md ${className}`} className={`flex items-center p-2 mb-1 rounded-md ${className}`}
> >
{/* 图标骨架屏 */} {/* 图标骨架å±?*/}
<div className='sidebar-icon-container flex-shrink-0'> <div className='sidebar-icon-container flex-shrink-0'>
<Skeleton <Skeleton
loading={true} loading={true}
@@ -162,7 +157,7 @@ const SkeletonWrapper = ({
} }
/> />
</div> </div>
{/* 文本骨架屏 */} {/* 文本骨架å±?*/}
<Skeleton <Skeleton
loading={true} loading={true}
active active
@@ -176,8 +171,7 @@ const SkeletonWrapper = ({
)); ));
}; };
// 侧边栏组标题骨架屏 // ä¾§è¾¹æ ç»„标题骨架å±? const renderSidebarGroupTitleSkeleton = () => {
const renderSidebarGroupTitleSkeleton = () => {
return ( return (
<div className={`mb-2 ${className}`}> <div className={`mb-2 ${className}`}>
<Skeleton <Skeleton
@@ -193,8 +187,7 @@ const SkeletonWrapper = ({
); );
}; };
// 完整侧边栏骨架屏 - 1:1 还原,去重实现 // 完整侧边æ éª¨æž¶å± - 1:1 还原,去é‡å®žçŽ? const renderSidebarSkeleton = () => {
const renderSidebarSkeleton = () => {
const NAV_WIDTH = 164; const NAV_WIDTH = 164;
const NAV_HEIGHT = 30; const NAV_HEIGHT = 30;
const COLLAPSED_WIDTH = 44; const COLLAPSED_WIDTH = 44;
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -32,8 +32,7 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => {
onClick={() => onLanguageChange('zh-CN')} onClick={() => onLanguageChange('zh-CN')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-CN' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-CN' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
> >
简体中文 ç®ä½ä¸­æ? </Dropdown.Item>
</Dropdown.Item>
<Dropdown.Item <Dropdown.Item
onClick={() => onLanguageChange('zh-TW')} onClick={() => onLanguageChange('zh-TW')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-TW' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh-TW' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
@@ -55,8 +54,7 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => {
onClick={() => onLanguageChange('ja')} onClick={() => onLanguageChange('ja')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ja' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ja' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
> >
日本語 æ¥æœ¬èª? </Dropdown.Item>
</Dropdown.Item>
<Dropdown.Item <Dropdown.Item
onClick={() => onLanguageChange('ru')} onClick={() => onLanguageChange('ru')}
className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ru' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`} className={`!px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'ru' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -43,7 +43,7 @@ const MobileMenuButton = ({
) )
} }
aria-label={ aria-label={
(isMobile ? drawerOpen : collapsed) ? t('关闭侧边栏') : t('打开侧边栏') (isMobile ? drawerOpen : collapsed) ? t('关闭侧边æ ?) : t('æå¼ä¾§è¾¹æ ?)
} }
onClick={onToggle} onClick={onToggle}
theme='borderless' theme='borderless'
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
@@ -87,8 +87,7 @@ const ThemeToggle = ({ theme, onThemeToggle, t }) => {
<> <>
<Dropdown.Divider /> <Dropdown.Divider />
<div className='px-3 py-2 text-xs text-semi-color-text-2'> <div className='px-3 py-2 text-xs text-semi-color-text-2'>
{t('当前跟随系统')} {t('当å‰è·Ÿéšç³»ç»Ÿ')}ï¼? {actualTheme === 'dark' ? t('深色') : t('浅色')}
{actualTheme === 'dark' ? t('深色') : t('浅色')}
</div> </div>
</> </>
)} )}
+3 -3
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useRef } from 'react'; import React, { useRef } from 'react';
@@ -110,7 +110,7 @@ const UserArea = ({
size='small' size='small'
className='text-gray-500 dark:text-gray-400' className='text-gray-500 dark:text-gray-400'
/> />
<span>{t('退出')}</span> <span>{t('退å‡?)}</span>
</div> </div>
</Dropdown.Item> </Dropdown.Item>
</Dropdown.Menu> </Dropdown.Menu>
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -46,7 +46,7 @@ const DeploymentAccessGuard = ({
<div className='mt-[60px] px-2'> <div className='mt-[60px] px-2'>
<Card loading={true} style={{ minHeight: '400px' }}> <Card loading={true} style={{ minHeight: '400px' }}>
<div style={{ textAlign: 'center', padding: '50px 0' }}> <div style={{ textAlign: 'center', padding: '50px 0' }}>
<Text type='secondary'>{t('加载设置中...')}</Text> <Text type='secondary'>{t('加载设置ä¸?..')}</Text>
</div> </div>
</Card> </Card>
</div> </div>
@@ -113,7 +113,7 @@ const DeploymentAccessGuard = ({
fontWeight: '700', fontWeight: '700',
}} }}
> >
{t('模型部署服务未启用')} {t('模型部署æœåŠ¡æœªå¯ç”?)}
</Title> </Title>
<Text <Text
style={{ style={{
@@ -200,7 +200,7 @@ const DeploymentAccessGuard = ({
color: 'var(--semi-color-text-1)', color: 'var(--semi-color-text-1)',
}} }}
> >
{t('启用 io.net 部署开关')} {t('å¯ç¨ io.net 部署å¼å?)}
</Text> </Text>
</div> </div>
<div <div
@@ -221,7 +221,7 @@ const DeploymentAccessGuard = ({
color: 'var(--semi-color-text-1)', color: 'var(--semi-color-text-1)',
}} }}
> >
{t('配置有效的 io.net API Key')} {t('é…置有效çš?io.net API Key')}
</Text> </Text>
</div> </div>
</div> </div>
@@ -272,7 +272,7 @@ const DeploymentAccessGuard = ({
lineHeight: '1.5', lineHeight: '1.5',
}} }}
> >
{t('配置完成后刷新页面即可使用模型部署功能')} {t('é…置完æˆåŽåˆ·æ–°é¡µé¢å³å¯ä½¿ç”¨æ¨¡åž‹éƒ¨ç½²åŠŸèƒ?)}
</Text> </Text>
</Card> </Card>
</div> </div>
@@ -285,7 +285,7 @@ const DeploymentAccessGuard = ({
<div className='mt-[60px] px-2'> <div className='mt-[60px] px-2'>
<Card loading={true} style={{ minHeight: '400px' }}> <Card loading={true} style={{ minHeight: '400px' }}>
<div style={{ textAlign: 'center', padding: '50px 0' }}> <div style={{ textAlign: 'center', padding: '50px 0' }}>
<Text type='secondary'>{t('正在检查 io.net 连接...')}</Text> <Text type='secondary'>{t('æ­£åœ¨æ£æŸ?io.net 连接...')}</Text>
</div> </div>
</Card> </Card>
</div> </div>
@@ -294,10 +294,10 @@ const DeploymentAccessGuard = ({
if (connectionOk === false) { if (connectionOk === false) {
const isExpired = connectionError?.type === 'expired'; const isExpired = connectionError?.type === 'expired';
const title = isExpired ? t('接口密钥已过期') : t('无法连接 io.net'); const title = isExpired ? t('接å£å¯é¥å·²è¿æœ?) : t('无法连接 io.net');
const description = isExpired const description = isExpired
? t('当前 API 密钥已过期,请在设置中更新。') ? t('å½“å‰ API 密钥已过期,请在设置中更新ã€?)
: t('当前配置无法连接到 io.net。'); : t('å½åéç½®æ æ³è¿žæŽ¥åˆ?io.netã?);
const detail = connectionError?.message || ''; const detail = connectionError?.message || '';
return ( return (
+4 -4
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -73,7 +73,7 @@ const ChatArea = ({
{t('AI 对è¯')} {t('AI 对è¯')}
</Typography.Title> </Typography.Title>
<Typography.Text className='!text-white/80 text-sm hidden sm:inline'> <Typography.Text className='!text-white/80 text-sm hidden sm:inline'>
{inputs.model || t('选择模型开始对话')} {inputs.model || t('选择模型开始对è¯?)}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
@@ -119,7 +119,7 @@ const ChatArea = ({
onStopGenerator={onStopGenerator} onStopGenerator={onStopGenerator}
onClear={onClearMessages} onClear={onClearMessages}
className='h-full' className='h-full'
placeholder={t('请输入您的问题...')} placeholder={t('请è¾å¥æ¨çšé®é¢?..')}
/> />
</div> </div>
</Card> </Card>
+7 -7
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useMemo, useCallback } from 'react'; import React, { useState, useMemo, useCallback } from 'react';
@@ -230,7 +230,7 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
const success = await copy(textToCopy); const success = await copy(textToCopy);
setCopied(true); setCopied(true);
Toast.success(t('已复制到剪贴板')); Toast.success(t('撌脣芾斐?));
setTimeout(() => setCopied(false), 2000); setTimeout(() => setCopied(false), 2000);
if (!success) { if (!success) {
@@ -277,11 +277,11 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
{/* 霅血 */} {/* 霅血 */}
{contentMetrics.isLarge && ( {contentMetrics.isLarge && (
<div style={codeThemeStyles.performanceWarning}> <div style={codeThemeStyles.performanceWarning}>
<span></span> <span>?/span>
<span> <span>
{contentMetrics.isVeryLarge {contentMetrics.isVeryLarge
? t('捆颲之嚗舐鍂隡睃') ? t('捆颲之嚗舐鍂隡睃')
: t('内容较大,部分功能可能受限')} : t('捆颲之嚗屸賢虾?)}
</span> </span>
</div> </div>
)} )}
@@ -297,7 +297,7 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
onMouseEnter={() => setIsHoveringCopy(true)} onMouseEnter={() => setIsHoveringCopy(true)}
onMouseLeave={() => setIsHoveringCopy(false)} onMouseLeave={() => setIsHoveringCopy(false)}
> >
<Tooltip content={copied ? t('已复制') : t('复制代码')}> <Tooltip content={copied ? t('撌脣?) : t('憭滚')}>
<Button <Button
icon={<Copy size={14} />} icon={<Copy size={14} />}
onClick={handleCopy} onClick={handleCopy}
@@ -344,7 +344,7 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
marginRight: '8px', marginRight: '8px',
}} }}
/> />
{t('正在处理大内容...')} {t('銁憭憭批?..')}
</div> </div>
) : ( ) : (
<div dangerouslySetInnerHTML={{ __html: renderedContent }} /> <div dangerouslySetInnerHTML={{ __html: renderedContent }} />
+11 -13
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useRef } from 'react'; import React, { useRef } from 'react';
@@ -41,8 +41,7 @@ const ConfigManager = ({
const handleExport = () => { const handleExport = () => {
try { try {
// 在导出前先保存当前配置,确保导出的是最新内容 // 在导出å‰å…ˆä¿å­˜å½“å‰é…置,确ä¿å¯¼å‡ºçš„æ˜¯æœ€æ–°å†…å®? const configWithTimestamp = {
const configWithTimestamp = {
...currentConfig, ...currentConfig,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}; };
@@ -53,7 +52,7 @@ const ConfigManager = ({
exportConfig(currentConfig, messages); exportConfig(currentConfig, messages);
Toast.success({ Toast.success({
content: t('配置已导出到下载文件夹'), content: t('é…置已导出到下载文件å¤?),
duration: 3, duration: 3,
}); });
} catch (error) { } catch (error) {
@@ -115,10 +114,10 @@ const ConfigManager = ({
Modal.confirm({ Modal.confirm({
title: t('éç½®é项'), title: t('éç½®é项'),
content: t( content: t(
'是否同时重置对话消息?选择"是"将清空所有对话记录并恢复默认示例;选择"否"将保留当前对话记录。', '是å¦åŒæéç½®å¯¹è¯æˆæ¯ï¼Ÿéæ©"æ˜?将清空所有对è¯è®°å½•å¹¶æ¢å¤é»˜è®¤ç¤ºä¾‹ï¼›é€‰æ‹©"å?å°ä¿çå½å对è¯è®°å½ã?,
), ),
okText: t(Œæ—¶é‡ç½®æ¶ˆæ¯'), okText: t(Œæ—¶é‡ç½®æ¶ˆæ¯'),
cancelText: t('仅重置配置'), cancelText: t('ä»…é‡ç½®é…ç½?),
okButtonProps: { okButtonProps: {
type: 'danger', type: 'danger',
}, },
@@ -134,7 +133,7 @@ const ConfigManager = ({
clearConfig(); clearConfig();
onConfigReset({ resetMessages: false }); onConfigReset({ resetMessages: false });
Toast.success({ Toast.success({
content: t('配置已重置,对话消息已保留'), content: t('é置已éç½®ï¼Œå¯¹è¯æˆæ¯å·²ä¿ç?),
duration: 3, duration: 3,
}); });
}, },
@@ -150,9 +149,9 @@ const ConfigManager = ({
const date = new Date(timestamp); const date = new Date(timestamp);
return t('上次ä¿å­˜: ') + date.toLocaleString(); return t('上次ä¿å­˜: ') + date.toLocaleString();
} }
return t('已有保存的配置'); return t('已有ä¿å­˜çš„é…ç½?);
} }
return t('暂无保存的配置'); return t('æšæ ä¿å­˜çšéç½?);
}; };
const dropdownItems = [ const dropdownItems = [
@@ -224,8 +223,7 @@ const ConfigManager = ({
); );
} }
// 桌面端显示紧凑的按钮组 // 桌é¢ç«¯æ˜¾ç¤ºç´§å‡‘的按钮ç»? return (
return (
<div className='space-y-3'> <div className='space-y-3'>
{/* é…置状æ€ä¿¡æ¯å’Œé‡ç½®æŒ‰é’® */} {/* é…置状æ€ä¿¡æ¯å’Œé‡ç½®æŒ‰é’® */}
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
@@ -242,7 +240,7 @@ const ConfigManager = ({
/> />
</div> </div>
{/* 导出和导入按钮 */} {/* 导出和导入按é’?*/}
<div className='flex gap-2'> <div className='flex gap-2'>
<Button <Button
icon={<Download size={12} />} icon={<Download size={12} />}
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useRef, useEffect, useCallback } from 'react'; import React, { useRef, useEffect, useCallback } from 'react';
@@ -59,7 +59,7 @@ const CustomInputRender = (props) => {
if (onPasteImage) { if (onPasteImage) {
onPasteImage(base64); onPasteImage(base64);
Toast.success({ Toast.success({
content: t('图片已添加'), content: t('图片已添åŠ?),
duration: 2, duration: 2,
}); });
} else { } else {
@@ -119,8 +119,7 @@ const CustomInputRender = (props) => {
}) })
: null; : null;
// 发送按钮 // å‘逿Œ‰é’? const styledSendNode = React.cloneElement(sendNode, {
const styledSendNode = React.cloneElement(sendNode, {
className: `!rounded-full !bg-purple-500 hover:!bg-purple-600 flex-shrink-0 transition-all ${sendNode.props.className || ''}`, className: `!rounded-full !bg-purple-500 hover:!bg-purple-600 flex-shrink-0 transition-all ${sendNode.props.className || ''}`,
style: { style: {
...sendNode.props.style, ...sendNode.props.style,
@@ -145,7 +144,7 @@ const CustomInputRender = (props) => {
{/* æ¸…ç©ºå¯¹è¯æŒ‰é’® - 左边 */} {/* æ¸…ç©ºå¯¹è¯æŒ‰é’® - 左边 */}
{styledClearNode} {styledClearNode}
<div className='flex-1'>{inputNode}</div> <div className='flex-1'>{inputNode}</div>
{/* 发送按钮 - 右边 */} {/* å‘逿Œ‰é’?- å³è¾¹ */}
{styledSendNode} {styledSendNode}
</div> </div>
</div> </div>
+11 -13
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
@@ -40,8 +40,7 @@ const CustomRequestEditor = ({
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const [localValue, setLocalValue] = useState(customRequestBody || ''); const [localValue, setLocalValue] = useState(customRequestBody || '');
// 当切换到自定义模式时,用默认payload初始化 // 蠖灘謐「蛻ー閾ェ螳壻ケ画ィ。蠑乗慮檎畑鮟倩ョ、payload蛻晏ァ句? useEffect(() => {
useEffect(() => {
if ( if (
customRequestMode && customRequestMode &&
(!customRequestBody || customRequestBody.trim() === '') (!customRequestBody || customRequestBody.trim() === '')
@@ -59,8 +58,7 @@ const CustomRequestEditor = ({
onCustomRequestBodyChange, onCustomRequestBodyChange,
]); ]);
// 同步外部传入的customRequestBody到本地状态 // 蜷梧ュ・螟夜Κ莨蜈・逧ustomRequestBody蛻ー譛ャ蝨ー迥カ諤? useEffect(() => {
useEffect(() => {
if (customRequestBody !== localValue) { if (customRequestBody !== localValue) {
setLocalValue(customRequestBody || ''); setLocalValue(customRequestBody || '');
validateJson(customRequestBody || ''); validateJson(customRequestBody || '');
@@ -118,7 +116,7 @@ const CustomRequestEditor = ({
return ( return (
<div className='space-y-4'> <div className='space-y-4'>
{/* 自定义模式开关 */} {/* 閾ェ螳壻ケ画ィ。蠑丞シ€蜈?*/}
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Code size={16} className='text-gray-500' /> <Code size={16} className='text-gray-500' />
@@ -130,7 +128,7 @@ const CustomRequestEditor = ({
checked={customRequestMode} checked={customRequestMode}
onChange={handleModeToggle} onChange={handleModeToggle}
checkedText={t('蠑€')} checkedText={t('蠑€')}
uncheckedText={t('关')} uncheckedText={t('蜈?)}
size='small' size='small'
/> />
</div> </div>
@@ -141,18 +139,18 @@ const CustomRequestEditor = ({
<Banner <Banner
type='warning' type='warning'
description={t( description={t(
'启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。', '蜷ッ逕ィ豁蠑丞錘悟ースソ逕ィ謔ィ閾ェ螳壻ケ臥噪隸キ豎ゆス灘書騾PI隸キ豎ゑシ梧ィ蝙矩鄂ョ髱譚ソ逧盾謨ー隶セ鄂ョ蟆ォ蠢ス逡?,
)} )}
icon={<AlertTriangle size={16} />} icon={<AlertTriangle size={16} />}
className='!rounded-lg' className='!rounded-lg'
closeIcon={null} closeIcon={null}
/> />
{/* JSON编辑器 */} {/* JSON郛冶セ大?*/}
<div> <div>
<div className='flex items-center justify-between mb-2'> <div className='flex items-center justify-between mb-2'>
<Typography.Text strong className='text-sm'> <Typography.Text strong className='text-sm'>
{t('请求体 JSON')} {t('隸キ豎ゆス?JSON')}
</Typography.Text> </Typography.Text>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
{isValid ? ( {isValid ? (
@@ -179,7 +177,7 @@ const CustomRequestEditor = ({
disabled={!isValid} disabled={!isValid}
className='!rounded-lg' className='!rounded-lg'
> >
{t('格式化')} {t('譬シ蠑丞?)}
</Button> </Button>
</div> </div>
</div> </div>
@@ -204,7 +202,7 @@ const CustomRequestEditor = ({
<Typography.Text className='text-xs text-gray-500 mt-2 block'> <Typography.Text className='text-xs text-gray-500 mt-2 block'>
{t( {t(
'请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。', '隸キ霎灘譛画譜逧ЙSON譬シ蠑冗噪隸キ豎ゆス薙€よお蜿ッ莉蜿り€ァ磯擇譚ソ荳ュ逧サ倩ョ隸キ豎ゆス捺シ蠑上€?,
)} )}
</Typography.Text> </Typography.Text>
</div> </div>
+8 -11
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
@@ -85,12 +85,10 @@ const DebugPanel = ({
> >
{pos === 'start' ? ( {pos === 'start' ? (
<div style={style} onClick={handleArrowClick}> <div style={style} onClick={handleArrowClick}>
â? </div>
</div>
) : ( ) : (
<div style={style} onClick={handleArrowClick}> <div style={style} onClick={handleArrowClick}>
â? </div>
</div>
)} )}
</Dropdown> </Dropdown>
); );
@@ -143,11 +141,10 @@ const DebugPanel = ({
tab={ tab={
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Eye size={16} /> <Eye size={16} />
{t('预览请求体')} {t('预览请求ä½?)}
{customRequestMode && ( {customRequestMode && (
<span className='px-1.5 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full'> <span className='px-1.5 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full'>
自定义 自定ä¹? </span>
</span>
)} )}
</div> </div>
} }
@@ -164,7 +161,7 @@ const DebugPanel = ({
tab={ tab={
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Send size={16} /> <Send size={16} />
{t('实际请求体')} {t('实é请æ±ä½?)}
</div> </div>
} }
itemKey='request' itemKey='request'
@@ -211,7 +208,7 @@ const DebugPanel = ({
{activeKey === 'preview' && debugData.previewTimestamp {activeKey === 'preview' && debugData.previewTimestamp
? `${t('预览更新')}: ${new Date(debugData.previewTimestamp).toLocaleString()}` ? `${t('预览更新')}: ${new Date(debugData.previewTimestamp).toLocaleString()}`
: debugData.timestamp : debugData.timestamp
? `${t('最后请求')}: ${new Date(debugData.timestamp).toLocaleString()}` ? `${t('最åŽè¯·æ±?)}: ${new Date(debugData.timestamp).toLocaleString()}`
: ''} : ''}
</Typography.Text> </Typography.Text>
</div> </div>
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
+8 -8
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -91,19 +91,19 @@ const ImageUrlInput = ({
{!imageEnabled ? ( {!imageEnabled ? (
<Typography.Text className='text-xs text-gray-500 mb-2 block'> <Typography.Text className='text-xs text-gray-500 mb-2 block'>
{disabled {disabled
? t('图片功能在自定义请求体模式下不可用') ? t('图片功能在自定义请求体模å¼ä¸‹ä¸å¯ç”?)
: t('启用后可添加图片URL进行多模态对话')} : t('å¯ç¨åŽå¯æ·»åŠ å¾çURLè¿è¡Œå¤šæ¨¡æå¯¹è¯?)}
</Typography.Text> </Typography.Text>
) : imageUrls.length === 0 ? ( ) : imageUrls.length === 0 ? (
<Typography.Text className='text-xs text-gray-500 mb-2 block'> <Typography.Text className='text-xs text-gray-500 mb-2 block'>
{disabled {disabled
? t('图片功能在自定义请求体模式下不可用') ? t('图片功能在自定义请求体模å¼ä¸‹ä¸å¯ç”?)
: t('点击 + 按钮添加图片URL进行多模态对话')} : t('ç¹å» + æŒé®æ·»åŠ å¾çURLè¿è¡Œå¤šæ¨¡æå¯¹è¯?)}
</Typography.Text> </Typography.Text>
) : ( ) : (
<Typography.Text className='text-xs text-gray-500 mb-2 block'> <Typography.Text className='text-xs text-gray-500 mb-2 block'>
{t('已添加')} {imageUrls.length} {t('张图片')} {t('已添åŠ?)} {imageUrls.length} {t('å¼ å¾ç?)}
{disabled ? ` (${t('自定义模式下不可用')})` : ''} {disabled ? ` (${t('自定义模å¼ä¸‹ä¸å¯ç”?)})` : ''}
</Typography.Text> </Typography.Text>
)} )}
+6 -6
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -50,7 +50,7 @@ const MessageActions = ({
<div className='flex items-center gap-0.5'> <div className='flex items-center gap-0.5'>
{!isLoading && ( {!isLoading && (
<Tooltip <Tooltip
content={shouldDisableActions ? t('操作暂时被禁用') : t('重试')} content={shouldDisableActions ? t('æ“作暂时被ç¦ç”?) : t('éè¯')}
position='top' position='top'
> >
<Button <Button
@@ -82,7 +82,7 @@ const MessageActions = ({
{canEdit && ( {canEdit && (
<Tooltip <Tooltip
content={shouldDisableActions ? t('操作暂时被禁用') : t('编辑')} content={shouldDisableActions ? t('æä½œæšæè¢«ç¦ç?) : t('编辑')}
position='top' position='top'
> >
<Button <Button
@@ -102,7 +102,7 @@ const MessageActions = ({
<Tooltip <Tooltip
content={ content={
shouldDisableActions shouldDisableActions
? t('操作暂时被禁用') ? t('æ“作暂时被ç¦ç”?)
: message.role === 'assistant' : message.role === 'assistant'
? t('åˆæ¢ä¸ºSystemè§è²') ? t('åˆæ¢ä¸ºSystemè§è²')
: t('åˆæ¢ä¸ºAssistantè§è²') : t('åˆæ¢ä¸ºAssistantè§è²')
@@ -130,7 +130,7 @@ const MessageActions = ({
{!isLoading && ( {!isLoading && (
<Tooltip <Tooltip
content={shouldDisableActions ? t('操作暂时被禁用') : t('删除')} content={shouldDisableActions ? t('æä½œæšæè¢«ç¦ç?) : t('删除')}
position='top' position='top'
> >
<Button <Button
+5 -5
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
@@ -78,7 +78,7 @@ const MessageContent = ({
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<AlertTriangle size={16} className='text-orange-500 shrink-0' /> <AlertTriangle size={16} className='text-orange-500 shrink-0' />
<Typography.Text strong className='!text-[var(--semi-color-text-0)]'> <Typography.Text strong className='!text-[var(--semi-color-text-0)]'>
{t('模型价格未配置')} {t('模型价格未é…ç½?)}
</Typography.Text> </Typography.Text>
</div> </div>
<Typography.Paragraph <Typography.Paragraph
@@ -260,7 +260,7 @@ const MessageContent = ({
<TextArea <TextArea
value={editValue} value={editValue}
onChange={(value) => onEditValueChange(value)} onChange={(value) => onEditValueChange(value)}
placeholder={t('请输入消息内容...')} placeholder={t('请è¾å¥æˆæ¯åå®?..')}
autosize={{ minRows: 3, maxRows: 12 }} autosize={{ minRows: 3, maxRows: 12 }}
style={{ style={{
resize: 'vertical', resize: 'vertical',
@@ -311,7 +311,7 @@ const MessageContent = ({
<div key={index} className='max-w-sm'> <div key={index} className='max-w-sm'>
<img <img
src={imgItem.image_url.url} src={imgItem.image_url.url}
alt={`用户上传的图片 ${index + 1}`} alt={`用户上传的图ç‰?${index + 1}`}
className='rounded-lg max-w-full h-auto shadow-sm border' className='rounded-lg max-w-full h-auto shadow-sm border'
style={{ maxHeight: '300px' }} style={{ maxHeight: '300px' }}
onError={(e) => { onError={(e) => {
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -23,12 +23,10 @@ import MessageActions from './MessageActions';
import SettingsPanel from './SettingsPanel'; import SettingsPanel from './SettingsPanel';
import DebugPanel from './DebugPanel'; import DebugPanel from './DebugPanel';
// 优化的消息内容组件 // 优化的消æ¯å†…容组ä»?export const OptimizedMessageContent = React.memo(
export const OptimizedMessageContent = React.memo(
MessageContent, MessageContent,
(prevProps, nextProps) => { (prevProps, nextProps) => {
// 只有这些属性变化时才重新渲染 // åªæœ‰è¿™äº›å±žæ€§å˜åŒ–æ—¶æ‰é‡æ–°æ¸²æŸ? return (
return (
prevProps.message.id === nextProps.message.id && prevProps.message.id === nextProps.message.id &&
prevProps.message.content === nextProps.message.content && prevProps.message.content === nextProps.message.content &&
prevProps.message.status === nextProps.message.status && prevProps.message.status === nextProps.message.status &&
@@ -44,8 +42,7 @@ export const OptimizedMessageContent = React.memo(
}, },
); );
// 优化的消息操作组件 // ä¼˜åŒ–çš„æ¶ˆæ¯æ“作组ä»?export const OptimizedMessageActions = React.memo(
export const OptimizedMessageActions = React.memo(
MessageActions, MessageActions,
(prevProps, nextProps) => { (prevProps, nextProps) => {
return ( return (
@@ -58,8 +55,7 @@ export const OptimizedMessageActions = React.memo(
}, },
); );
// 优化的设置面板组件 // ä¼˜åŒ–çš„è®¾ç½®é¢æ¿ç»„ä»?export const OptimizedSettingsPanel = React.memo(
export const OptimizedSettingsPanel = React.memo(
SettingsPanel, SettingsPanel,
(prevProps, nextProps) => { (prevProps, nextProps) => {
return ( return (
@@ -79,8 +75,7 @@ export const OptimizedSettingsPanel = React.memo(
}, },
); );
// 优化的调试面板组件 // ä¼˜åŒ–çš„è°ƒè¯•é¢æ¿ç»„ä»?export const OptimizedDebugPanel = React.memo(
export const OptimizedDebugPanel = React.memo(
DebugPanel, DebugPanel,
(prevProps, nextProps) => { (prevProps, nextProps) => {
return ( return (
+5 -5
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -80,7 +80,7 @@ const ParameterControl = ({
/> />
</div> </div>
<Typography.Text className='text-xs text-gray-500 mb-2'> <Typography.Text className='text-xs text-gray-500 mb-2'>
{t('控制输出的随机性和创造性')} {t('æŽ§åˆ¶è¾“å‡ºçš„éšæœºæ€§å’Œåˆ›é€ æ€?)}
</Typography.Text> </Typography.Text>
<Slider <Slider
step={0.1} step={0.1}
@@ -120,7 +120,7 @@ const ParameterControl = ({
/> />
</div> </div>
<Typography.Text className='text-xs text-gray-500 mb-2'> <Typography.Text className='text-xs text-gray-500 mb-2'>
{t('核采样,控制词汇选择的多样性')} {t('æ ¸éæ ·ï¼ŒæŽ§åˆè¯æ±éæ©çšå¤šæ ·æ?)}
</Typography.Text> </Typography.Text>
<Slider <Slider
step={0.1} step={0.1}
@@ -285,7 +285,7 @@ const ParameterControl = ({
/> />
</div> </div>
<Input <Input
placeholder={t('随机种子 (留空为随机)')} placeholder={t('éšæœºç§å­ (ç•™ç©ºä¸ºéšæœ?')}
name='seed' name='seed'
autoComplete='new-password' autoComplete='new-password'
value={inputs.seed || ''} value={inputs.seed || ''}
+9 -9
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useMemo, useCallback } from 'react'; import React, { useState, useMemo, useCallback } from 'react';
@@ -108,7 +108,7 @@ const SSEViewer = ({ sseData }) => {
await copy(allData); await copy(allData);
setCopied(true); setCopied(true);
Toast.success(t('已复制全部数据')); Toast.success(t('å·²å¤åˆ¶å…¨éƒ¨æ•°æ?));
setTimeout(() => setCopied(false), 2000); setTimeout(() => setCopied(false), 2000);
} catch (err) { } catch (err) {
Toast.error(t('å¤åˆå¤±è´¥')); Toast.error(t('å¤åˆå¤±è´¥'));
@@ -123,7 +123,7 @@ const SSEViewer = ({ sseData }) => {
? JSON.stringify(item.parsed, null, 2) ? JSON.stringify(item.parsed, null, 2)
: item.raw; : item.raw;
await copy(textToCopy); await copy(textToCopy);
Toast.success(t('已复制')); Toast.success(t('å·²å¤åˆ?));
} catch (err) { } catch (err) {
Toast.error(t('å¤åˆ¶å¤±è´¥')); Toast.error(t('å¤åˆ¶å¤±è´¥'));
} }
@@ -161,7 +161,7 @@ const SSEViewer = ({ sseData }) => {
return ( return (
<div className='space-y-2'> <div className='space-y-2'>
{/* JSON 格式化显示 */} {/* JSON æ ¼å¼åŒ–显ç¤?*/}
<div className='relative'> <div className='relative'>
<pre className='p-4 bg-gray-900 text-gray-100 rounded-lg overflow-auto text-xs font-mono leading-relaxed'> <pre className='p-4 bg-gray-900 text-gray-100 rounded-lg overflow-auto text-xs font-mono leading-relaxed'>
{JSON.stringify(item.parsed, null, 2)} {JSON.stringify(item.parsed, null, 2)}
@@ -185,7 +185,7 @@ const SSEViewer = ({ sseData }) => {
/> />
)} )}
{item.parsed.choices[0].delta?.reasoning_content && ( {item.parsed.choices[0].delta?.reasoning_content && (
<Badge count={t('Reasoning')} type='warning' /> <Badge count={t('�Reasoning')} type='warning' />
)} )}
{item.parsed.choices[0].finish_reason && ( {item.parsed.choices[0].finish_reason && (
<Badge <Badge
@@ -215,11 +215,11 @@ const SSEViewer = ({ sseData }) => {
return ( return (
<div className='h-full flex flex-col bg-gray-50 dark:bg-gray-900/50 rounded-lg'> <div className='h-full flex flex-col bg-gray-50 dark:bg-gray-900/50 rounded-lg'>
{/* 头部工具栏 */} {/* 头部工具æ ?*/}
<div className='flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0'> <div className='flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0'>
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<Zap size={16} className='text-blue-500' /> <Zap size={16} className='text-blue-500' />
<Typography.Text strong>{t('SSE数据流')}</Typography.Text> <Typography.Text strong>{t('SSEæ•°æ®æµ?)}</Typography.Text>
<Badge count={stats.total} type='primary' /> <Badge count={stats.total} type='primary' />
{stats.errors > 0 && ( {stats.errors > 0 && (
<Badge count={`${stats.errors} ${t('é误')}`} type='danger' /> <Badge count={`${stats.errors} ${t('é误')}`} type='danger' />
@@ -234,7 +234,7 @@ const SSEViewer = ({ sseData }) => {
onClick={handleCopyAll} onClick={handleCopyAll}
theme='borderless' theme='borderless'
> >
{copied ? t('已复制') : t('复制全部')} {copied ? t('å·²å¤åˆ?) : t('å¤åˆ¶å…¨éƒ¨')}
</Button> </Button>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
+7 -7
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -67,7 +67,7 @@ const SettingsPanel = ({
flexDirection: 'column', flexDirection: 'column',
}} }}
> >
{/* 标题区域 - 与调试面板保持一*/} {/* 标题区域 - 与调试面板保持一?*/}
<div className='flex items-center justify-between mb-6 flex-shrink-0'> <div className='flex items-center justify-between mb-6 flex-shrink-0'>
<div className='flex items-center'> <div className='flex items-center'>
<div className='w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center mr-3'> <div className='w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center mr-3'>
@@ -90,7 +90,7 @@ const SettingsPanel = ({
)} )}
</div> </div>
{/* 移动端配置管*/} {/* 移动端配置管?*/}
{styleState.isMobile && ( {styleState.isMobile && (
<div className='mb-4 flex-shrink-0'> <div className='mb-4 flex-shrink-0'>
<ConfigManager <ConfigManager
@@ -104,7 +104,7 @@ const SettingsPanel = ({
)} )}
<div className='space-y-6 overflow-y-auto flex-1 pr-2 model-settings-scroll'> <div className='space-y-6 overflow-y-auto flex-1 pr-2 model-settings-scroll'>
{/* 自定义请求体编辑*/} {/* 自定义请求体编辑?*/}
<CustomRequestEditor <CustomRequestEditor
customRequestMode={customRequestMode} customRequestMode={customRequestMode}
customRequestBody={customRequestBody} customRequestBody={customRequestBody}
@@ -200,7 +200,7 @@ const SettingsPanel = ({
/> />
</div> </div>
{/* 流式输出开*/} {/* 流式输出开?*/}
<div className={customRequestMode ? 'opacity-50' : ''}> <div className={customRequestMode ? 'opacity-50' : ''}>
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
@@ -218,7 +218,7 @@ const SettingsPanel = ({
checked={inputs.stream} checked={inputs.stream}
onChange={(checked) => onInputChange('stream', checked)} onChange={(checked) => onInputChange('stream', checked)}
checkedText={t('开')} checkedText={t('开')}
uncheckedText={t('关')} uncheckedText={t('?)}
size='small' size='small'
disabled={customRequestMode} disabled={customRequestMode}
/> />
+3 -3
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
@@ -39,7 +39,7 @@ const ThinkingContent = ({
const headerText = const headerText =
isThinkingStatus && !message.isThinkingComplete isThinkingStatus && !message.isThinkingComplete
? t('思考中...') ? t('思考中...')
: t('思考过程'); : t('æèƒè¿ç¨?);
useEffect(() => { useEffect(() => {
if ( if (
+18 -28
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import { import {
@@ -25,7 +25,7 @@ import {
const MESSAGES_STORAGE_KEY = 'playground_messages'; const MESSAGES_STORAGE_KEY = 'playground_messages';
/** /**
* 保存配置到 localStorage * ä¿å­˜éç½®åˆ?localStorage
* @param {Object} config - 要保存的配置对象 * @param {Object} config - 要保存的配置对象
*/ */
export const saveConfig = (config) => { export const saveConfig = (config) => {
@@ -41,7 +41,7 @@ export const saveConfig = (config) => {
}; };
/** /**
* 保存消息到 localStorage * ä¿å­˜æˆæ¯åˆ?localStorage
* @param {Array} messages - 要保存的消息数组 * @param {Array} messages - 要保存的消息数组
*/ */
export const saveMessages = (messages) => { export const saveMessages = (messages) => {
@@ -57,9 +57,8 @@ export const saveMessages = (messages) => {
}; };
/** /**
* localStorage 加载配置 * ä»?localStorage 加载éç½®
* @returns {Object} 配置对象如果不存在则返回默认配置 * @returns {Object} éç½®å¯¹è±¡ï¼Œå¦æžœä¸å­˜åœ¨åˆè¿åžé»˜è®¤éç½? */
*/
export const loadConfig = () => { export const loadConfig = () => {
try { try {
const savedConfig = localStorage.getItem(STORAGE_KEYS.CONFIG); const savedConfig = localStorage.getItem(STORAGE_KEYS.CONFIG);
@@ -97,8 +96,8 @@ export const loadConfig = () => {
}; };
/** /**
* localStorage 加载消息 * ä»?localStorage åŠ è½½æˆæ¯
* @returns {Array} 消息数组如果不存在则返回 null * @returns {Array} æˆæ¯æ°ç»ï¼Œå¦æžœä¸å­˜åœ¨åˆè¿å?null
*/ */
export const loadMessages = () => { export const loadMessages = () => {
try { try {
@@ -115,8 +114,7 @@ export const loadMessages = () => {
}; };
/** /**
* 清除保存的配置 * æ¸é¤ä¿å­˜çšéç½? */
*/
export const clearConfig = () => { export const clearConfig = () => {
try { try {
localStorage.removeItem(STORAGE_KEYS.CONFIG); localStorage.removeItem(STORAGE_KEYS.CONFIG);
@@ -127,8 +125,7 @@ export const clearConfig = () => {
}; };
/** /**
* 清除保存的消息 * æ¸é¤ä¿å­˜çšæˆæ? */
*/
export const clearMessages = () => { export const clearMessages = () => {
try { try {
localStorage.removeItem(STORAGE_KEYS.MESSAGES); localStorage.removeItem(STORAGE_KEYS.MESSAGES);
@@ -138,22 +135,18 @@ export const clearMessages = () => {
}; };
/** /**
* 检查是否有保存的配置 * æ£æŸ¥æ˜¯å¦æœä¿å­˜çšéç½? * @returns {boolean} 是å¦å­˜åœ¨ä¿å­˜çšéç½? */
* @returns {boolean} 是否存在保存的配置
*/
export const hasStoredConfig = () => { export const hasStoredConfig = () => {
try { try {
return localStorage.getItem(STORAGE_KEYS.CONFIG) !== null; return localStorage.getItem(STORAGE_KEYS.CONFIG) !== null;
} catch (error) { } catch (error) {
console.error('检查配置失败:', error); console.error('检查é…置失è´?', error);
return false; return false;
} }
}; };
/** /**
* 获取配置的最后保存时间 * 获åéç½®çšæœåŽä¿å­˜æé? * @returns {string|null} æœåŽä¿å­˜æé´çš ISO å­ç¬¦ä¸? */
* @returns {string|null} 最后保存时间的 ISO 字符串
*/
export const getConfigTimestamp = () => { export const getConfigTimestamp = () => {
try { try {
const savedConfig = localStorage.getItem(STORAGE_KEYS.CONFIG); const savedConfig = localStorage.getItem(STORAGE_KEYS.CONFIG);
@@ -162,13 +155,13 @@ export const getConfigTimestamp = () => {
return parsedConfig.timestamp || null; return parsedConfig.timestamp || null;
} }
} catch (error) { } catch (error) {
console.error('获取配置时间戳失败:', error); console.error('获å–é…置时间戳失è´?', error);
} }
return null; return null;
}; };
/** /**
* 导出配置为 JSON 文件包含消息 * 导åºéç½®ä¸?JSON æä»ï¼ˆåŒå«æˆæ¯ï¼
* @param {Object} config - 要导出的配置 * @param {Object} config - 要导出的配置
* @param {Array} messages - 要导出的消息 * @param {Array} messages - 要导出的消息
*/ */
@@ -196,10 +189,8 @@ export const exportConfig = (config, messages = null) => {
}; };
/** /**
* 从文件导入配置包含消息 * 从æä»å¯¼å¥é置(åŒå«æˆæ¯ï¼? * @param {File} file - åŒå«éç½®çš?JSON æä»
* @param {File} file - 包含配置的 JSON 文件 * @returns {Promise<Object>} 导å¥çšé置对è±? */
* @returns {Promise<Object>} 导入的配置对象
*/
export const importConfig = (file) => { export const importConfig = (file) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
@@ -209,8 +200,7 @@ export const importConfig = (file) => {
const importedConfig = JSON.parse(e.target.result); const importedConfig = JSON.parse(e.target.result);
if (importedConfig.inputs && importedConfig.parameterEnabled) { if (importedConfig.inputs && importedConfig.parameterEnabled) {
// 如果导入的配置包含消息,也一起导入 // 如果导入的é…ç½®åŒ…å«æ¶ˆæ¯ï¼Œä¹Ÿä¸€èµ·å¯¼å…? if (
if (
importedConfig.messages && importedConfig.messages &&
Array.isArray(importedConfig.messages) Array.isArray(importedConfig.messages)
) { ) {
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
export { default as SettingsPanel } from './SettingsPanel'; export { default as SettingsPanel } from './SettingsPanel';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { import React, {
@@ -182,14 +182,14 @@ const ChannelSelectorModal = forwardRef(
case 1: case 1:
statusTag = ( statusTag = (
<Tag color='green' shape='circle'> <Tag color='green' shape='circle'>
{t('已启用')} {t('å·²å¯ç?)}
</Tag> </Tag>
); );
break; break;
case 2: case 2:
statusTag = ( statusTag = (
<Tag color='red' shape='circle'> <Tag color='red' shape='circle'>
{t('已禁用')} {t('å·²ç¦ç?)}
</Tag> </Tag>
); );
break; break;
@@ -203,7 +203,7 @@ const ChannelSelectorModal = forwardRef(
default: default:
statusTag = ( statusTag = (
<Tag color='grey' shape='circle'> <Tag color='grey' shape='circle'>
{t('未知状态')} {t('æœªçŸ¥çŠæ?)}
</Tag> </Tag>
); );
} }
@@ -240,7 +240,7 @@ const ChannelSelectorModal = forwardRef(
renderBaseUrlCell(record._originalData?.base_url || ''), renderBaseUrlCell(record._originalData?.base_url || ''),
}, },
{ {
title: t('状态'), title: t('çŠæ?),
dataIndex: '_originalData.status', dataIndex: '_originalData.status',
render: (_, record) => renderStatusCell(record), render: (_, record) => renderStatusCell(record),
}, },
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
+50 -50
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -185,8 +185,8 @@ const ACCESS_POLICY_TEMPLATES = {
}; };
const ACCESS_DENIED_TEMPLATES = { const ACCESS_DENIED_TEMPLATES = {
level_hint: '需要等级 {{required}},你当前等级 {{current}}(字段:{{field}}', level_hint: '?{{required}}嚗䔶敶枏蝑厩漣 {{current}}畾蛛{{field}}?,
org_hint: '仅限指定组织或角色访问。组织={{current.org}},角色={{current.roles}}', org_hint: '𤥁脰挪蝏?{{current.org}}嚗諹?{{current.roles}}',
}; };
const CustomOAuthSetting = ({ serverAddress }) => { const CustomOAuthSetting = ({ serverAddress }) => {
@@ -249,7 +249,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
showError(res.data.message); showError(res.data.message);
} }
} catch (error) { } catch (error) {
showError(t('获取自定义 OAuth 提供商列表失败')); showError(t('?OAuth 𣂷銵典仃韐?));
} }
setLoading(false); setLoading(false);
}; };
@@ -322,7 +322,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
for (const field of requiredFields) { for (const field of requiredFields) {
if (!currentValues[field]) { if (!currentValues[field]) {
showError(t(`请填写 ${field}`)); showError(t(`霂瑕‵?${field}`));
return; return;
} }
} }
@@ -334,9 +334,9 @@ const CustomOAuthSetting = ({ serverAddress }) => {
if (value && !value.startsWith('http://') && !value.startsWith('https://')) { if (value && !value.startsWith('http://') && !value.startsWith('https://')) {
// Check if user selected a preset but forgot to fill issuer URL // Check if user selected a preset but forgot to fill issuer URL
if (selectedPreset && !baseUrl) { if (selectedPreset && !baseUrl) {
showError(t('请先填写 Issuer URL,以自动生成完整的端点 URL')); showError(t('霂瑕憛怠 Issuer URL嚗䔶誑芸𢆡摰峕㟲?URL'));
} else { } else {
showError(t('端点 URL 必须是完整地址(以 http:// https:// 开头)')); showError(t('蝡舐 URL 游𧑐 http:// ?https:// 憭湛'));
} }
return; return;
} }
@@ -380,7 +380,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
(cleanBaseUrl ? `${cleanBaseUrl}/.well-known/openid-configuration` : ''); (cleanBaseUrl ? `${cleanBaseUrl}/.well-known/openid-configuration` : '');
if (!wellKnownUrl) { if (!wellKnownUrl) {
showError(t('请先填写 Discovery URL Issuer URL')); showError(t('霂瑕憛怠 Discovery URL ?Issuer URL'));
return; return;
} }
@@ -462,7 +462,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
showSuccess(t('已从 Discovery 自动填充配置')); showSuccess(t('已从 Discovery 自动填充配置'));
} catch (error) { } catch (error) {
showError( showError(
t('获取 Discovery 配置失败:') + (error?.message || t('未知错误')), t(' Discovery 滨蔭憭梯揖嚗?) + (error?.message || t('芰䰻躰秤')),
); );
} finally { } finally {
setDiscoveryLoading(false); setDiscoveryLoading(false);
@@ -518,14 +518,14 @@ const CustomOAuthSetting = ({ serverAddress }) => {
const template = ACCESS_POLICY_TEMPLATES[templateKey]; const template = ACCESS_POLICY_TEMPLATES[templateKey];
if (!template) return; if (!template) return;
mergeFormValues({ access_policy: template }); mergeFormValues({ access_policy: template });
showSuccess(t('已填充策略模板')); showSuccess(t('撌脣交芋?));
}; };
const applyDeniedTemplate = (templateKey) => { const applyDeniedTemplate = (templateKey) => {
const template = ACCESS_DENIED_TEMPLATES[templateKey]; const template = ACCESS_DENIED_TEMPLATES[templateKey];
if (!template) return; if (!template) return;
mergeFormValues({ access_denied_message: template }); mergeFormValues({ access_denied_message: template });
showSuccess(t('已填充提示模板')); showSuccess(t('撌脣蝷箸芋?));
}; };
const columns = [ const columns = [
@@ -548,12 +548,12 @@ const CustomOAuthSetting = ({ serverAddress }) => {
render: (slug) => <Tag>{slug}</Tag>, render: (slug) => <Tag>{slug}</Tag>,
}, },
{ {
title: t('状态'), title: t('?),
dataIndex: 'enabled', dataIndex: 'enabled',
key: 'enabled', key: 'enabled',
render: (enabled) => ( render: (enabled) => (
<Tag color={enabled ? 'green' : 'grey'}> <Tag color={enabled ? 'green' : 'grey'}>
{enabled ? t('已启用') : t('已禁用')} {enabled ? t('撌脣鍳?) : t('撌脩?)}
</Tag> </Tag>
), ),
}, },
@@ -579,7 +579,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
{t('编辑')} {t('编辑')}
</Button> </Button>
<Popconfirm <Popconfirm
title={t('确定要删除此 OAuth 提供商吗?')} title={t('蝖桀斗迨 OAuth 𣂷?)}
onConfirm={() => handleDelete(record.id)} onConfirm={() => handleDelete(record.id)}
> >
<Button icon={<IconDelete />} size="small" type="danger"> <Button icon={<IconDelete />} size="small" type="danger">
@@ -597,13 +597,13 @@ const CustomOAuthSetting = ({ serverAddress }) => {
return ( return (
<Card> <Card>
<Form.Section text={t('自定义 OAuth 提供商')}> <Form.Section text={t('?OAuth 𣂷?)}>
<Banner <Banner
type="info" type="info"
description={ description={
<> <>
{t( {t(
'配置自定义 OAuth 提供商,支持 GitHub Enterprise、GitLab、Gitea、Nextcloud、Keycloak、ORY 等兼容 OAuth 2.0 协议的身份提供商' '滨蔭銋?OAuth 𣂷 GitHub EnterpriseitLabiteaextcloudeycloakRY 蝑匧摰?OAuth 2.0 讛悅澈隞賣靘𥕦'
)} )}
<br /> <br />
{t('回调 URL 格式')}: {serverAddress || t('网站地址')}/oauth/ {t('回调 URL 格式')}: {serverAddress || t('网站地址')}/oauth/
@@ -619,7 +619,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
onClick={handleAdd} onClick={handleAdd}
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
> >
{t('添加 OAuth 提供商')} {t('瘛餃 OAuth 𣂷?)}
</Button> </Button>
<Table <Table
@@ -628,11 +628,11 @@ const CustomOAuthSetting = ({ serverAddress }) => {
loading={loading} loading={loading}
rowKey="id" rowKey="id"
pagination={false} pagination={false}
empty={t('暂无自定义 OAuth 提供商')} empty={t('?OAuth 𣂷?)}
/> />
<Modal <Modal
title={editingProvider ? t('编辑 OAuth 提供商') : t('添加 OAuth 提供商')} title={editingProvider ? t('蝻𤥁 OAuth 𣂷?) : t('瘛餃 OAuth 𣂷?)}
visible={modalVisible} visible={modalVisible}
onCancel={closeModal} onCancel={closeModal}
width={860} width={860}
@@ -649,14 +649,14 @@ const CustomOAuthSetting = ({ serverAddress }) => {
}} }}
> >
<Space spacing={8} align='center'> <Space spacing={8} align='center'>
<Text type='secondary'>{t('启用供应商')}</Text> <Text type='secondary'>{t('舐鍂靘𥕦?)}</Text>
<Switch <Switch
checked={!!formValues.enabled} checked={!!formValues.enabled}
size='large' size='large'
onChange={(checked) => mergeFormValues({ enabled: !!checked })} onChange={(checked) => mergeFormValues({ enabled: !!checked })}
/> />
<Tag color={formValues.enabled ? 'green' : 'grey'}> <Tag color={formValues.enabled ? 'green' : 'grey'}>
{formValues.enabled ? t('已启用') : t('已禁用')} {formValues.enabled ? t('撌脣鍳?) : t('撌脩?)}
</Tag> </Tag>
</Space> </Space>
<Button onClick={closeModal}>{t('取消')}</Button> <Button onClick={closeModal}>{t('取消')}</Button>
@@ -677,7 +677,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
{t('Configuration')} {t('Configuration')}
</Text> </Text>
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}> <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
{t('先填写配置,再自动填充 OAuth 端点,能显著减少手工输入')} {t('蝵殷滩䌊典‵?OAuth 蝡舐嚗諹见極颲枏')}
</Text> </Text>
{discoveryInfo && ( {discoveryInfo && (
<Banner <Banner
@@ -687,7 +687,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
description={ description={
<div> <div>
<div> <div>
{t('已从 Discovery 获取配置,可继续手动修改所有字段。')} {t('撌脖 Discovery 滨蔭嚗虾蝏抒賒见𢆡靽格㺿畾萸?)}
</div> </div>
{discoveryAutoFilledLabels ? ( {discoveryAutoFilledLabels ? (
<div> <div>
@@ -724,7 +724,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
value={selectedPreset} value={selectedPreset}
onChange={handlePresetChange} onChange={handlePresetChange}
optionList={[ optionList={[
{ value: '', label: t('自定义') }, { value: '', label: t('?) },
...Object.entries(OAUTH_PRESETS).map(([key, config]) => ({ ...Object.entries(OAUTH_PRESETS).map(([key, config]) => ({
value: key, value: key,
label: config.name, label: config.name,
@@ -735,14 +735,14 @@ const CustomOAuthSetting = ({ serverAddress }) => {
<Col span={10}> <Col span={10}>
<Form.Input <Form.Input
field="base_url" field="base_url"
label={t('发行者 URLIssuer URL')} label={t('𤏸?URL嚗ssuer URL嚗?)}
placeholder={t('例如:https://gitea.example.com')} placeholder={t('例如:https://gitea.example.com')}
value={baseUrl} value={baseUrl}
onChange={handleBaseUrlChange} onChange={handleBaseUrlChange}
extraText={ extraText={
selectedPreset selectedPreset
? t('填写后会自动拼接预设端点') ? t('填写后会自动拼接预设端点')
: t('可选:用于自动生成端点或 Discovery URL') : t('芸𢆡蝡舐?Discovery URL')
} }
/> />
</Col> </Col>
@@ -776,7 +776,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
field="name" field="name"
label={t('显示名称')} label={t('显示名称')}
placeholder={t('例如:GitHub Enterprise')} placeholder={t('例如:GitHub Enterprise')}
rules={[{ required: true, message: t('请输入显示名称') }]} rules={[{ required: true, message: t('霂瑁交遬蝷箏?) }]}
/> />
</Col> </Col>
<Col span={12}> <Col span={12}>
@@ -784,8 +784,8 @@ const CustomOAuthSetting = ({ serverAddress }) => {
field="slug" field="slug"
label="Slug" label="Slug"
placeholder={t('例如:github-enterprise')} placeholder={t('例如:github-enterprise')}
extraText={t('URL 标识,只能包含小写字母、数字和连字符')} extraText={t('URL 瘥溻㺭摮堒餈𧼮?)}
rules={[{ required: true, message: t('请输入 Slug') }]} rules={[{ required: true, message: t('霂瑁?Slug') }]}
/> />
</Col> </Col>
</Row> </Row>
@@ -831,7 +831,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
field="client_id" field="client_id"
label="Client ID" label="Client ID"
placeholder={t('OAuth Client ID')} placeholder={t('OAuth Client ID')}
rules={[{ required: true, message: t('请输入 Client ID') }]} rules={[{ required: true, message: t('霂瑁?Client ID') }]}
/> />
</Col> </Col>
<Col span={12}> <Col span={12}>
@@ -841,13 +841,13 @@ const CustomOAuthSetting = ({ serverAddress }) => {
type="password" type="password"
placeholder={ placeholder={
editingProvider editingProvider
? t('留空则保持原有密钥') ? t('嗵征?)
: t('OAuth Client Secret') : t('OAuth Client Secret')
} }
rules={ rules={
editingProvider editingProvider
? [] ? []
: [{ required: true, message: t('请输入 Client Secret') }] : [{ required: true, message: t('霂瑁?Client Secret') }]
} }
/> />
</Col> </Col>
@@ -869,7 +869,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
: 'https://example.com/oauth/authorize' : 'https://example.com/oauth/authorize'
} }
rules={[ rules={[
{ required: true, message: t('请输入 Authorization Endpoint') }, { required: true, message: t('霂瑁?Authorization Endpoint') },
]} ]}
/> />
</Col> </Col>
@@ -882,10 +882,10 @@ const CustomOAuthSetting = ({ serverAddress }) => {
label={t('Token Endpoint')} label={t('Token Endpoint')}
placeholder={ placeholder={
selectedPreset && OAUTH_PRESETS[selectedPreset] selectedPreset && OAUTH_PRESETS[selectedPreset]
? t('自动生成:') + OAUTH_PRESETS[selectedPreset].token_endpoint ? t('芸𢆡?) + OAUTH_PRESETS[selectedPreset].token_endpoint
: 'https://example.com/oauth/token' : 'https://example.com/oauth/token'
} }
rules={[{ required: true, message: t('请输入 Token Endpoint') }]} rules={[{ required: true, message: t('霂瑁?Token Endpoint') }]}
/> />
</Col> </Col>
<Col span={12}> <Col span={12}>
@@ -894,11 +894,11 @@ const CustomOAuthSetting = ({ serverAddress }) => {
label={t('User Info Endpoint')} label={t('User Info Endpoint')}
placeholder={ placeholder={
selectedPreset && OAUTH_PRESETS[selectedPreset] selectedPreset && OAUTH_PRESETS[selectedPreset]
? t('自动生成:') + OAUTH_PRESETS[selectedPreset].user_info_endpoint ? t('芸𢆡?) + OAUTH_PRESETS[selectedPreset].user_info_endpoint
: 'https://example.com/api/user' : 'https://example.com/api/user'
} }
rules={[ rules={[
{ required: true, message: t('请输入 User Info Endpoint') }, { required: true, message: t('霂瑁?User Info Endpoint') },
]} ]}
/> />
</Col> </Col>
@@ -912,9 +912,9 @@ const CustomOAuthSetting = ({ serverAddress }) => {
placeholder="openid profile email" placeholder="openid profile email"
extraText={ extraText={
discoveryInfo?.scopesSupported?.length discoveryInfo?.scopesSupported?.length
? t('Discovery 建议 scopes') + ? t('Discovery 撱箄悅 scopes?) +
discoveryInfo.scopesSupported.join(', ') discoveryInfo.scopesSupported.join(', ')
: t('可手动填写,多个 scope 用空格分隔') : t('憭帋葵 scope 函征?)
} }
/> />
</Col> </Col>
@@ -924,7 +924,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
{t('字段映射')} {t('字段映射')}
</Text> </Text>
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}> <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
{t('配置如何从用户信息 API 响应中提取用户数据,支持 JSONPath 语法')} {t('滨蔭憒隞𡒊鍂瑚縑?API 銝剜𣇉鍂瑟㺭 JSONPath 霂剜')}
</Text> </Text>
<Row gutter={16}> <Row gutter={16}>
@@ -933,7 +933,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
field="user_id_field" field="user_id_field"
label={t('用户 ID 字段(可选)')} label={t('用户 ID 字段(可选)')}
placeholder={t('例如:sub、id、data.user.id')} placeholder={t('例如:sub、id、data.user.id')}
extraText={t('用于唯一标识用户的字段路径')} extraText={t('畾菔楝敺?)}
/> />
</Col> </Col>
<Col span={12}> <Col span={12}>
@@ -978,9 +978,9 @@ const CustomOAuthSetting = ({ serverAddress }) => {
field="auth_style" field="auth_style"
label={t('认证方式')} label={t('认证方式')}
optionList={[ optionList={[
{ value: 0, label: t('自动检测') }, { value: 0, label: t('芸𢆡璉?) },
{ value: 1, label: t('POST 参数') }, { value: 1, label: t('POST 参数') },
{ value: 2, label: t('Basic Auth 头') }, { value: 2, label: t('Basic Auth ?) },
]} ]}
/> />
</Col> </Col>
@@ -990,7 +990,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
{t('准入策略')} {t('准入策略')}
</Text> </Text>
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}> <Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
{t('可选:基于用户信息 JSON 做组合条件准入,条件不满足时返回自定义提示')} {t(' JSON 𡁶辺隞嗅辣銝齿說頞單𧒄餈𥪜銋㗇?)}
</Text> </Text>
<Row gutter={16}> <Row gutter={16}>
<Col span={24}> <Col span={24}>
@@ -1007,12 +1007,12 @@ const CustomOAuthSetting = ({ serverAddress }) => {
{"field": "active", "op": "eq", "value": true} {"field": "active", "op": "eq", "value": true}
] ]
}`} }`}
extraText={t('支持逻辑 and/or 与嵌套 groups;操作符支持 eq/ne/gt/gte/lt/lte/in/not_in/contains/exists')} extraText={t(' and/or 銝𤾸憟?groups嚗𥟇雿𦦵泵 eq/ne/gt/gte/lt/lte/in/not_in/contains/exists')}
showClear showClear
/> />
<Space spacing={8} style={{ marginTop: 8 }}> <Space spacing={8} style={{ marginTop: 8 }}>
<Button size='small' theme='light' onClick={() => applyAccessPolicyTemplate('level_active')}> <Button size='small' theme='light' onClick={() => applyAccessPolicyTemplate('level_active')}>
{t('填充模板:等级+激活')} {t('憛怠踎嚗𡁶??)}
</Button> </Button>
<Button size='small' theme='light' onClick={() => applyAccessPolicyTemplate('org_or_role')}> <Button size='small' theme='light' onClick={() => applyAccessPolicyTemplate('org_or_role')}>
{t('填充模板:组织或角色')} {t('填充模板:组织或角色')}
@@ -1027,16 +1027,16 @@ const CustomOAuthSetting = ({ serverAddress }) => {
value={formValues.access_denied_message || ''} value={formValues.access_denied_message || ''}
onChange={(value) => mergeFormValues({ access_denied_message: value })} onChange={(value) => mergeFormValues({ access_denied_message: value })}
label={t('拒绝提示模板(可选)')} label={t('拒绝提示模板(可选)')}
placeholder={t('例如:需要等级 {{required}},你当前等级 {{current}}')} placeholder={t('靘见嚗𡁻蝥?{{required}}嚗䔶敶枏蝑厩漣 {{current}}')}
extraText={t('可用变量:{{provider}} {{field}} {{op}} {{required}} {{current}} 以及 {{current.path}}')} extraText={t('可用变量:{{provider}} {{field}} {{op}} {{required}} {{current}} 以及 {{current.path}}')}
showClear showClear
/> />
<Space spacing={8} style={{ marginTop: 8 }}> <Space spacing={8} style={{ marginTop: 8 }}>
<Button size='small' theme='light' onClick={() => applyDeniedTemplate('level_hint')}> <Button size='small' theme='light' onClick={() => applyDeniedTemplate('level_hint')}>
{t('填充模板:等级提示')} {t('憛怠踎嚗𡁶蝥扳?)}
</Button> </Button>
<Button size='small' theme='light' onClick={() => applyDeniedTemplate('org_hint')}> <Button size='small' theme='light' onClick={() => applyDeniedTemplate('org_hint')}>
{t('填充模板:组织提示')} {t('憛怠踎嚗𡁶?)}
</Button> </Button>
</Space> </Space>
</Col> </Col>
+6 -8
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState, useMemo } from 'react'; import React, { useEffect, useState, useMemo } from 'react';
@@ -51,8 +51,7 @@ const DashboardSetting = () => {
}); });
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);
const [showMigrateModal, setShowMigrateModal] = useState(false); // const [showMigrateModal, setShowMigrateModal] = useState(false); // ä¸ä¸ªçˆæœ¬ä¼šåˆ é?
const getOptions = async () => { const getOptions = async () => {
const res = await API.get('/api/option/'); const res = await API.get('/api/option/');
const { success, message, data } = res.data; const { success, message, data } = res.data;
@@ -110,7 +109,7 @@ const DashboardSetting = () => {
try { try {
setLoading(true); setLoading(true);
await API.post('/api/option/migrate_console_setting'); await API.post('/api/option/migrate_console_setting');
showSuccess('旧配置迁移完成'); showSuccess('æ§éç½®è¿ç§»å®Œæˆ?);
await onRefresh(); await onRefresh();
setShowMigrateModal(false); setShowMigrateModal(false);
} catch (err) { } catch (err) {
@@ -136,9 +135,8 @@ const DashboardSetting = () => {
> >
<p>检测到旧版本的配置数据是否要迁移到新的配置格式</p> <p>检测到旧版本的配置数据是否要迁移到新的配置格式</p>
<p style={{ color: '#f57c00', marginTop: '10px' }}> <p style={{ color: '#f57c00', marginTop: '10px' }}>
<strong>注意</strong> <strong>注æï¼?/strong>
迁移过程中会自动处理数据格式转换迁移完成后旧配置将被清除请在迁移前在数据库中备份好旧配置 è¿ç§»è¿ç¨ä¸­ä¼šèªåЍå¤çæ°æ®æ ¼å¼è½¬æ¢ï¼Œè¿ç§»å®ŒæˆåŽæ§éç½®å°è¢«æ¸é¤ï¼Œè¯·åœ¨è¿ç§»ååœ¨æ°æ®åºä¸­å¤ä»½å¥½æ§éç½®ã? </p>
</p>
</Modal> </Modal>
{/* 数据看板设置 */} {/* 数据看板设置 */}
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
+6 -6
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -53,10 +53,10 @@ const OperationSetting = () => {
/* 顶栏模块管理 */ /* 顶栏模块管理 */
HeaderNavModules: '', HeaderNavModules: '',
/* 左侧边栏模块管理(管理员) */ /* 撌虫儒颲寞璅∪蝞∠嚗?*/
SidebarModulesAdmin: '', SidebarModulesAdmin: '',
/* 敏感词设置 */ /* 𤩺霂滩挽蝵?*/
CheckSensitiveEnabled: false, CheckSensitiveEnabled: false,
CheckSensitiveOnPromptEnabled: false, CheckSensitiveOnPromptEnabled: false,
SensitiveWords: '', SensitiveWords: '',
@@ -130,11 +130,11 @@ const OperationSetting = () => {
<div style={{ marginTop: '10px' }}> <div style={{ marginTop: '10px' }}>
<SettingsHeaderNavModules options={inputs} refresh={onRefresh} /> <SettingsHeaderNavModules options={inputs} refresh={onRefresh} />
</div> </div>
{/* 左侧边栏模块管理(管理员) */} {/* 撌虫儒颲寞璅∪蝞∠嚗?*/}
<div style={{ marginTop: '10px' }}> <div style={{ marginTop: '10px' }}>
<SettingsSidebarModulesAdmin options={inputs} refresh={onRefresh} /> <SettingsSidebarModulesAdmin options={inputs} refresh={onRefresh} />
</div> </div>
{/* 屏蔽词过滤设置 */} {/* 撅讛𤪖霂滩皛方挽蝵?*/}
<Card style={{ marginTop: '10px' }}> <Card style={{ marginTop: '10px' }}>
<SettingsSensitiveWords options={inputs} refresh={onRefresh} /> <SettingsSensitiveWords options={inputs} refresh={onRefresh} />
</Card> </Card>
+24 -25
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useContext, useEffect, useRef, useState } from 'react'; import React, { useContext, useEffect, useRef, useState } from 'react';
@@ -96,7 +96,7 @@ const OtherSetting = () => {
try { try {
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true })); setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true }));
await updateOption('Notice', inputs.Notice); await updateOption('Notice', inputs.Notice);
showSuccess(t('公告已更新')); showSuccess(t('撌脫凒?));
} catch (error) { } catch (error) {
console.error(t('公告更新失败'), error); console.error(t('公告更新失败'), error);
showError(t('公告更新失败')); showError(t('公告更新失败'));
@@ -115,7 +115,7 @@ const OtherSetting = () => {
LEGAL_USER_AGREEMENT_KEY, LEGAL_USER_AGREEMENT_KEY,
inputs[LEGAL_USER_AGREEMENT_KEY], inputs[LEGAL_USER_AGREEMENT_KEY],
); );
showSuccess(t('用户协议已更新')); showSuccess(t('讛悅撌脫凒?));
} catch (error) { } catch (error) {
console.error(t('用户协议更新失败'), error); console.error(t('用户协议更新失败'), error);
showError(t('用户协议更新失败')); showError(t('用户协议更新失败'));
@@ -137,7 +137,7 @@ const OtherSetting = () => {
LEGAL_PRIVACY_POLICY_KEY, LEGAL_PRIVACY_POLICY_KEY,
inputs[LEGAL_PRIVACY_POLICY_KEY], inputs[LEGAL_PRIVACY_POLICY_KEY],
); );
showSuccess(t('隐私政策已更新')); showSuccess(t('撌脫凒?));
} catch (error) { } catch (error) {
console.error(t('隐私政策更新失败'), error); console.error(t('隐私政策更新失败'), error);
showError(t('隐私政策更新失败')); showError(t('隐私政策更新失败'));
@@ -158,7 +158,7 @@ const OtherSetting = () => {
SystemName: true, SystemName: true,
})); }));
await updateOption('SystemName', inputs.SystemName); await updateOption('SystemName', inputs.SystemName);
showSuccess(t('系统名称已更新')); showSuccess(t('蝟餌滨妍撌脫凒?));
} catch (error) { } catch (error) {
console.error(t('系统名称更新失败'), error); console.error(t('系统名称更新失败'), error);
showError(t('系统名称更新失败')); showError(t('系统名称更新失败'));
@@ -175,7 +175,7 @@ const OtherSetting = () => {
try { try {
setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: true })); setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: true }));
await updateOption('Logo', inputs.Logo); await updateOption('Logo', inputs.Logo);
showSuccess('Logo 已更新'); showSuccess('Logo 撌脫凒?);
} catch (error) { } catch (error) {
console.error('Logo 更新失败', error); console.error('Logo 更新失败', error);
showError('Logo 更新失败'); showError('Logo 更新失败');
@@ -191,7 +191,7 @@ const OtherSetting = () => {
HomePageContent: true, HomePageContent: true,
})); }));
await updateOption(key, inputs[key]); await updateOption(key, inputs[key]);
showSuccess('首页内容已更新'); showSuccess('擐㚚捆撌脫凒?);
} catch (error) { } catch (error) {
console.error('首页内容更新失败', error); console.error('首页内容更新失败', error);
showError('首页内容更新失败'); showError('首页内容更新失败');
@@ -207,7 +207,7 @@ const OtherSetting = () => {
try { try {
setLoadingInput((loadingInput) => ({ ...loadingInput, About: true })); setLoadingInput((loadingInput) => ({ ...loadingInput, About: true }));
await updateOption('About', inputs.About); await updateOption('About', inputs.About);
showSuccess('关于内容已更新'); showSuccess('捆撌脫凒?);
} catch (error) { } catch (error) {
console.error('关于内容更新失败', error); console.error('关于内容更新失败', error);
showError('关于内容更新失败'); showError('关于内容更新失败');
@@ -220,7 +220,7 @@ const OtherSetting = () => {
try { try {
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true })); setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true }));
await updateOption('Footer', inputs.Footer); await updateOption('Footer', inputs.Footer);
showSuccess('页脚内容已更新'); showSuccess('憿菔捆撌脫凒?);
} catch (error) { } catch (error) {
console.error('页脚内容更新失败', error); console.error('页脚内容更新失败', error);
showError('页脚内容更新失败'); showError('页脚内容更新失败');
@@ -271,7 +271,7 @@ const OtherSetting = () => {
} }
} catch (error) { } catch (error) {
console.error('Failed to check for updates:', error); console.error('Failed to check for updates:', error);
showError('检查更新失败,请稍后再试'); showError('交凒啣仃韐伐霂瑞𤾸?);
} finally { } finally {
setLoadingInput((loadingInput) => ({ setLoadingInput((loadingInput) => ({
...loadingInput, ...loadingInput,
@@ -282,7 +282,7 @@ const OtherSetting = () => {
const switchToDefaultFrontend = () => { const switchToDefaultFrontend = () => {
Modal.confirm({ Modal.confirm({
title: t('切换到新版前端'), title: t('唳鰵?),
content: t('切换后页面会自动刷新,并进入新版前端。是否继续?'), content: t('切换后页面会自动刷新,并进入新版前端。是否继续?'),
okText: t('确认切换'), okText: t('确认切换'),
cancelText: t('取消'), cancelText: t('取消'),
@@ -301,7 +301,7 @@ const OtherSetting = () => {
showError(message); showError(message);
return; return;
} }
showSuccess(t('已切换到新版前端,正在刷新页面')); showSuccess(t('撌脣滨垢嚗峕迤?));
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 600); }, 600);
@@ -372,21 +372,20 @@ const OtherSetting = () => {
<Col span={16}> <Col span={16}>
<Space> <Space>
<Text> <Text>
{t('当前版本')} {t('敶枏𧋦')}? {statusState?.status?.version || t('芰䰻')}
{statusState?.status?.version || t('未知')}
</Text> </Text>
<Button <Button
type='primary' type='primary'
onClick={checkUpdate} onClick={checkUpdate}
loading={loadingInput['CheckUpdate']} loading={loadingInput['CheckUpdate']}
> >
{t('检查更新')} {t('交凒?)}
</Button> </Button>
<Button <Button
onClick={switchToDefaultFrontend} onClick={switchToDefaultFrontend}
loading={loadingInput['FrontendTheme']} loading={loadingInput['FrontendTheme']}
> >
{t('切换到新版前端')} {t('唳鰵?)}
</Button> </Button>
</Space> </Space>
</Col> </Col>
@@ -411,7 +410,7 @@ const OtherSetting = () => {
<Form.TextArea <Form.TextArea
label={t('公告')} label={t('公告')}
placeholder={t( placeholder={t(
'在此输入新的公告内容,支持 Markdown & HTML 代码', '冽迨颲枏捆嚗峕𣈲?Markdown & HTML ',
)} )}
field={'Notice'} field={'Notice'}
onChange={handleInputChange} onChange={handleInputChange}
@@ -424,7 +423,7 @@ const OtherSetting = () => {
<Form.TextArea <Form.TextArea
label={t('用户协议')} label={t('用户协议')}
placeholder={t( placeholder={t(
'在此输入用户协议内容,支持 Markdown & HTML 代码', '冽迨颲枏讛悅捆嚗峕𣈲?Markdown & HTML ',
)} )}
field={LEGAL_USER_AGREEMENT_KEY} field={LEGAL_USER_AGREEMENT_KEY}
onChange={handleInputChange} onChange={handleInputChange}
@@ -443,7 +442,7 @@ const OtherSetting = () => {
<Form.TextArea <Form.TextArea
label={t('隐私政策')} label={t('隐私政策')}
placeholder={t( placeholder={t(
'在此输入隐私政策内容,支持 Markdown & HTML 代码', '冽迨颲枏捆嚗峕𣈲?Markdown & HTML ',
)} )}
field={LEGAL_PRIVACY_POLICY_KEY} field={LEGAL_PRIVACY_POLICY_KEY}
onChange={handleInputChange} onChange={handleInputChange}
@@ -493,7 +492,7 @@ const OtherSetting = () => {
<Form.TextArea <Form.TextArea
label={t('首页内容')} label={t('首页内容')}
placeholder={t( placeholder={t(
'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe src 属性,这允许你设置任意网页作为首页', '冽迨颲枏擐㚚△捆嚗峕𣈲?Markdown & HTML 隞嚗諹挽蝵桀擐㚚△𠶖銝滚曄內𡏭銝芷曎雿輻鍂霂仿曎銝?iframe ?src 撅墧餈坔霈訾霈曄蔭隞餅蝵煾△雿靝蛹擐㚚△',
)} )}
field={'HomePageContent'} field={'HomePageContent'}
onChange={handleInputChange} onChange={handleInputChange}
@@ -509,7 +508,7 @@ const OtherSetting = () => {
<Form.TextArea <Form.TextArea
label={t('关于')} label={t('关于')}
placeholder={t( placeholder={t(
'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe src 属性,这允许你设置任意网页作为关于页面', '冽迨颲枏捆嚗峕𣈲?Markdown & HTML 隞𡏭銝芷曎雿輻鍂霂仿曎銝?iframe ?src 撅墧餈坔霈訾霈曄蔭隞餅蝵煾△雿靝蛹憿菟𢒰',
)} )}
field={'About'} field={'About'}
onChange={handleInputChange} onChange={handleInputChange}
@@ -524,7 +523,7 @@ const OtherSetting = () => {
fullMode={false} fullMode={false}
type='info' type='info'
description={t( description={t(
'移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目', '蝘駁膄 One API 憿駁繮敺埈憿寧𤌍蝏湔擪韐孵之讐移𨥈桀笆雿䭾嚗諹窈銝餃𢆡?,
)} )}
closeIcon={null} closeIcon={null}
style={{ marginTop: 15 }} style={{ marginTop: 15 }}
@@ -532,7 +531,7 @@ const OtherSetting = () => {
<Form.Input <Form.Input
label={t('页脚')} label={t('页脚')}
placeholder={t( placeholder={t(
'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码', '冽迨颲枏憿菔嚗𣬚蝛箏雿輻鍂暺䁅恕憿菔嚗峕𣈲?HTML ',
)} )}
field={'Footer'} field={'Footer'}
onChange={handleInputChange} onChange={handleInputChange}
@@ -545,7 +544,7 @@ const OtherSetting = () => {
</Form> </Form>
</Col> </Col>
<Modal <Modal
title={t('新版本') + '' + updateData.tag_name} title={t('?) + '? + updateData.tag_name}
visible={showUpdateModal} visible={showUpdateModal}
onCancel={() => setShowUpdateModal(false)} onCancel={() => setShowUpdateModal(false)}
footer={[ footer={[
+21 -21
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -64,38 +64,38 @@ const PaymentSetting = () => {
const complianceStatements = [ const complianceStatements = [
t('你已合法取得所接入模型 API、账号、密钥和额度的授权;'), t('你已合法取得所接入模型 API、账号、密钥和额度的授权;'),
t( t(
'你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。', 'ä½ æ¿è¯ºä»åœ¨å·²åå¾ä¸Šæ¸¸æœåŠ¡åãæ¨¡åžæœåŠ¡æä¾æ¹æˆç¸å³æƒåˆ©æ¹åˆæ³æŽˆæƒçšèŒƒå´å使ç¨å?APIãè´¦å·ãå¯é¥ãé¢åº¦åŠæœåŠ¡èƒ½åŠï¼Œä¸è¿è¡Œæœªç»æŽˆæƒçšè½¬å®ãååãåˆéæˆåä»è¿è§å业åŒä½¿ç¨ã?,
), ),
t( t(
'如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;', 'å¦å中åŽäººæ°å±åŒå½å¢ƒåå¬ä¼æä¾çŸæˆå¼äººå·¥æºèƒ½æœåŠ¡ï¼Œä½ å°ä¾æ³å±¥è¡Œå¤æ¡ˆç»è®°ãå®å¨è¯ä¼°ãå容å®å¨ãæŠè¯ä¸¾æŠ¥ãçŸæˆåˆæˆå容æ è¯ãæ¥å¿çå­˜ã个人信æ¯ä¿æŠ¤ç­åˆè§ä¹åŠ¡ï¼?,
), ),
t( t(
'你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。', 'ä½ æ¿è¯ºä¸ä¼šåˆ©ç¨æœ¬ç³»ç»Ÿå®žæ½ãååŠ©å®žæ½æˆå˜ç¸å®žæ½è¿åéç¨æ³å¾æ³è§ãçç®¡è¦æ±ãå¹³å°è§åˆã社会å¬å±åˆ©çŠæˆç¬¬ä¸æ¹åˆæ³æƒçŠçšè¡Œä¸ºã?,
), ),
t('你理解并自行承担部署、运营和收费行为产生的法律责任。'), t('ä½ çè§£å¹èªè¡Œæ¿æéƒ¨ç½²ãè¿è¥åŒæè´¹è¡Œä¸ºäº§çŸçšæ³å¾è´£ä»»ã?),
t( t(
'你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。', 'ä½ ç解本åˆè§æéä»ç¨äºŽé£Žé©æç¤ºï¼Œä¸æžæˆæ³å¾æè§ãåˆè§å®¡æŸ¥ç»è®ºæˆå¯¹ä½ ä½¿ç¨æœ¬ç³»ç»Ÿè¡Œä¸ºåˆæ³æ§çšä¿è¯ï¼ä½ åºæ ¹æ®å®žé业务场æ¯èªè¡Œå¨è¯¢ä¸ä¸šæ³å¾æˆåˆè§é¡¾é®ã?,
), ),
]; ];
const requiredComplianceText = t( const requiredComplianceText = t(
'我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任', 'æˆå·²é˜è¯»å¹ç解上述åˆè§æé,知æç¸å³æ³å¾é£Žé©ï¼Œå¹ç¡®è®¤èªè¡Œæ¿æéƒ¨ç½²ãè¿è¥åŒæè´¹è¡Œä¸ºäº§çŸçšæ³å¾è´£ä»?,
); );
const requiredComplianceTextParts = [ const requiredComplianceTextParts = [
{ {
type: 'input', type: 'input',
text: t('我已阅读并理解上述合规提醒'), text: t('æˆå·²é˜è¯»å¹ç解上述åˆè§æé?),
}, },
{ type: 'static', text: t('') }, { type: 'static', text: t('ï¼?) },
{ {
type: 'input', type: 'input',
text: t('知悉相关法律风险'), text: t('知悉相关法律风险'),
}, },
{ type: 'static', text: t('') }, { type: 'static', text: t('ï¼?) },
{ {
type: 'input', type: 'input',
text: t('并确认自行承担部署'), text: t('å¹ç¡®è®¤èªè¡Œæ¿æéƒ¨ç½?),
}, },
{ type: 'static', text: t('、') }, { type: 'static', text: t('ã?) },
{ {
type: 'input', type: 'input',
text: t('运营和收费行为产生的法律责任'), text: t('运营和收费行为产生的法律责任'),
@@ -217,12 +217,12 @@ const PaymentSetting = () => {
{!complianceConfirmed ? ( {!complianceConfirmed ? (
<Banner <Banner
type='warning' type='warning'
title={t('需要确认合规声明')} title={t('éœè¦ç¡®è®¤åˆè§å£°æ˜?)}
description={ description={
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
<span> <span>
{t( {t(
'确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。', '确认å,æ¯ä»˜ã忢ç ã订é˜è®¡åˆåŒé请è¿åˆ©åŠŸèƒ½å°ä¿æŒé定ã?,
)} )}
</span> </span>
<Button <Button
@@ -241,7 +241,7 @@ const PaymentSetting = () => {
) : ( ) : (
<Banner <Banner
type='success' type='success'
title={t('合规声明已确认')} title={t('åˆè§å£°æ˜Žå·²ç¡®è®?)}
description={t('确认时间:{{time}},确认用户:#{{userId}}', { description={t('确认时间:{{time}},确认用户:#{{userId}}', {
time: inputs['payment_setting.compliance_confirmed_at'] time: inputs['payment_setting.compliance_confirmed_at']
? new Date( ? new Date(
@@ -275,7 +275,7 @@ const PaymentSetting = () => {
hideSectionTitle hideSectionTitle
/> />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab={t('易支付设置')} itemKey='epay'> <Tabs.TabPane tab={t('易支付设ç½?)} itemKey='epay'>
<SettingsPaymentGateway <SettingsPaymentGateway
options={inputs} options={inputs}
refresh={onRefresh} refresh={onRefresh}
@@ -310,16 +310,16 @@ const PaymentSetting = () => {
visible={complianceVisible} visible={complianceVisible}
title={t('确认合规声明')} title={t('确认合规声明')}
markdownContent={t( markdownContent={t(
'该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。', '该æä½œå°å¯ç¨æ¯ä»˜ã忢ç ã订é˜è®¡åˆåŒé请è¿åˆ©ç¸å³åŠŸèƒ½ã请ä»ç»é˜è¯»å¹ç¡®è®¤ä»¥ä¸å£°æ˜Žã?,
)} )}
checklist={complianceStatements} checklist={complianceStatements}
inputPrompt={t('请输入以下文字以确认:')} inputPrompt={t('请输入以下文字以确认:')}
requiredText={requiredComplianceText} requiredText={requiredComplianceText}
requiredTextParts={requiredComplianceTextParts} requiredTextParts={requiredComplianceTextParts}
inputPlaceholder={t('请输入确认文案')} inputPlaceholder={t('请è¾å¥ç¡®è®¤ææ¡?)}
mismatchText={t('输入内容与要求文案不一致')} mismatchText={t('è¾å¥åå®¹ä¸Žè¦æ±ææ¡ˆä¸ä¸è?)}
cancelText={t('取消')} cancelText={t('取消')}
confirmText={t('确认并启用')} confirmText={t('确认å¹å¯ç?)}
onCancel={() => setComplianceVisible(false)} onCancel={() => setComplianceVisible(false)}
onConfirm={confirmCompliance} onConfirm={confirmCompliance}
/> />
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
+21 -23
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
@@ -35,8 +35,7 @@ import { UserContext } from '../../context/User';
import { Modal } from '@douyinfe/semi-ui'; import { Modal } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
// // ?import UserInfoHeader from './personal/components/UserInfoHeader';
import UserInfoHeader from './personal/components/UserInfoHeader';
import AccountManagement from './personal/cards/AccountManagement'; import AccountManagement from './personal/cards/AccountManagement';
import NotificationSettings from './personal/cards/NotificationSettings'; import NotificationSettings from './personal/cards/NotificationSettings';
import PreferencesSettings from './personal/cards/PreferencesSettings'; import PreferencesSettings from './personal/cards/PreferencesSettings';
@@ -212,7 +211,7 @@ const PersonalSetting = () => {
if (success) { if (success) {
setSystemToken(data); setSystemToken(data);
await copy(data); await copy(data);
showSuccess(t('令牌已重置并已复制到剪贴板')); showSuccess(t('隞斤撌脤蝵桀僎撌脣芾斐?));
} else { } else {
showError(message); showError(message);
} }
@@ -233,8 +232,7 @@ const PersonalSetting = () => {
showError(message); showError(message);
} }
} catch (error) { } catch (error) {
// // 𠶖? }
}
}; };
const startPasskeyManagementVerification = async (apiCall, options = {}) => { const startPasskeyManagementVerification = async (apiCall, options = {}) => {
@@ -246,12 +244,12 @@ const PersonalSetting = () => {
: null; : null;
if (!requiredMethod) { if (!requiredMethod) {
showError(t('您需要先启用两步验证或 Passkey 才能执行此操作')); showError(t('舐鍂銝斗郊撉諹?Passkey 甇斗?));
return; return;
} }
if (requiredMethod === 'passkey' && !methods.passkeySupported) { if (requiredMethod === 'passkey' && !methods.passkeySupported) {
showInfo(t('当前设备不支持 Passkey')); showInfo(t('敶枏霈曉銝齿𣈲?Passkey'));
return; return;
} }
@@ -314,7 +312,7 @@ const PersonalSetting = () => {
return finishRes.data; return finishRes.data;
} catch (error) { } catch (error) {
if (error?.name === 'AbortError') { if (error?.name === 'AbortError') {
showInfo(t('已取消 Passkey 注册')); showInfo(t('撌脣瘨?Passkey 瘜典'));
return { cancelled: true }; return { cancelled: true };
} }
throw new Error(error?.message || t('Passkey 注册失败,请重试')); throw new Error(error?.message || t('Passkey 注册失败,请重试'));
@@ -325,7 +323,7 @@ const PersonalSetting = () => {
const handleRegisterPasskey = async () => { const handleRegisterPasskey = async () => {
if (!passkeySupported || !window.PublicKeyCredential) { if (!passkeySupported || !window.PublicKeyCredential) {
showInfo(t('当前设备不支持 Passkey')); showInfo(t('敶枏霈曉銝齿𣈲?Passkey'));
return; return;
} }
await startPasskeyRegistration(); await startPasskeyRegistration();
@@ -340,7 +338,7 @@ const PersonalSetting = () => {
throw new Error(message || t('操作失败,请重试')); throw new Error(message || t('操作失败,请重试'));
} }
showSuccess(t('Passkey 已解绑')); showSuccess(t('Passkey 撌脰圾蝏?));
await loadPasskeyStatus(); await loadPasskeyStatus();
return res.data; return res.data;
} catch (error) { } catch (error) {
@@ -374,7 +372,7 @@ const PersonalSetting = () => {
const handleSystemTokenClick = async (e) => { const handleSystemTokenClick = async (e) => {
e.target.select(); e.target.select();
await copy(e.target.value); await copy(e.target.value);
showSuccess(t('系统令牌已复制到剪切板')); showSuccess(t('蝟餌隞斤撌脣?));
}; };
const deleteAccount = async () => { const deleteAccount = async () => {
@@ -404,7 +402,7 @@ const PersonalSetting = () => {
}); });
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
showSuccess(t('微信账户绑定成功!')); showSuccess(t('敺桐縑韐行蝏穃𣂼?));
setShowWeChatBindModal(false); setShowWeChatBindModal(false);
} else { } else {
showError(message); showError(message);
@@ -413,11 +411,11 @@ const PersonalSetting = () => {
const changePassword = async () => { const changePassword = async () => {
// if (inputs.original_password === '') { // if (inputs.original_password === '') {
// showError(t('')); // showError(t('?));
// return; // return;
// } // }
if (inputs.set_new_password === '') { if (inputs.set_new_password === '') {
showError(t('请输入新密码!')); showError(t('霂瑁交鰵撖?));
return; return;
} }
if (inputs.original_password === inputs.set_new_password) { if (inputs.original_password === inputs.set_new_password) {
@@ -434,7 +432,7 @@ const PersonalSetting = () => {
}); });
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
showSuccess(t('密码修改成功!')); showSuccess(t('靽格㺿𣂼?));
setShowWeChatBindModal(false); setShowWeChatBindModal(false);
} else { } else {
showError(message); showError(message);
@@ -467,7 +465,7 @@ const PersonalSetting = () => {
const bindEmail = async () => { const bindEmail = async () => {
if (inputs.email_verification_code === '') { if (inputs.email_verification_code === '') {
showError(t('请输入邮箱验证码!')); showError(t('霂瑁仿蝞梢?));
return; return;
} }
setLoading(true); setLoading(true);
@@ -477,7 +475,7 @@ const PersonalSetting = () => {
}); });
const { success, message } = res.data; const { success, message } = res.data;
if (success) { if (success) {
showSuccess(t('邮箱账户绑定成功!')); showSuccess(t('桃拳韐行蝏穃𣂼?));
setShowEmailBindModal(false); setShowEmailBindModal(false);
userState.user.email = inputs.email; userState.user.email = inputs.email;
} else { } else {
@@ -548,7 +546,7 @@ const PersonalSetting = () => {
{/* 顶部用户信息区域 */} {/* 顶部用户信息区域 */}
<UserInfoHeader t={t} userState={userState} /> <UserInfoHeader t={t} userState={userState} />
{/* 签到日历 - 仅在启用时显示 */} {/* 蝑曉 - 隞舐鍂嗆遬蝷?*/}
{status?.checkin_enabled && ( {status?.checkin_enabled && (
<div className='mt-4 md:mt-6'> <div className='mt-4 md:mt-6'>
<CheckinCalendar <CheckinCalendar
@@ -560,9 +558,9 @@ const PersonalSetting = () => {
</div> </div>
)} )}
{/* 账户管理和其他设置 */} {/* 韐行蝞∠隞𤥁挽蝵?*/}
<div className='grid grid-cols-1 xl:grid-cols-2 items-start gap-4 md:gap-6 mt-4 md:mt-6'> <div className='grid grid-cols-1 xl:grid-cols-2 items-start gap-4 md:gap-6 mt-4 md:mt-6'>
{/* 左侧:账户管理设置 */} {/* 撌虫儒嚗朞揭瑞恣挽蝵?*/}
<div className='flex flex-col gap-4 md:gap-6'> <div className='flex flex-col gap-4 md:gap-6'>
<AccountManagement <AccountManagement
t={t} t={t}
@@ -587,7 +585,7 @@ const PersonalSetting = () => {
<PreferencesSettings t={t} /> <PreferencesSettings t={t} />
</div> </div>
{/* 右侧:其他设置 */} {/* 喃儒嚗𡁜隞𤥁挽蝵?*/}
<NotificationSettings <NotificationSettings
t={t} t={t}
notificationSettings={notificationSettings} notificationSettings={notificationSettings}
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
+4 -5
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@@ -62,8 +62,7 @@ const RatioSetting = () => {
try { try {
item.value = JSON.stringify(JSON.parse(item.value), null, 2); item.value = JSON.stringify(JSON.parse(item.value), null, 2);
} catch (e) { } catch (e) {
// JSON // 妿žœåŽç«¯è¿åžçšä¸æ˜¯åˆæ³?JSONï¼Œç´æŽ¥å±ç¤? }
}
} }
if (['DefaultUseAutoGroup', 'ExposeRatioEnabled'].includes(item.key)) { if (['DefaultUseAutoGroup', 'ExposeRatioEnabled'].includes(item.key)) {
newInputs[item.key] = toBoolean(item.value); newInputs[item.key] = toBoolean(item.value);
@@ -103,7 +102,7 @@ const RatioSetting = () => {
<Tabs.TabPane tab={t('分组相关设置')} itemKey='group'> <Tabs.TabPane tab={t('分组相关设置')} itemKey='group'>
<GroupRatioSettings options={inputs} refresh={onRefresh} /> <GroupRatioSettings options={inputs} refresh={onRefresh} />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab={t('未设置价格模型')} itemKey='unset_models'> <Tabs.TabPane tab={t('未设置价格模åž?)} itemKey='unset_models'>
<ModelRatioNotSetEditor options={inputs} refresh={onRefresh} /> <ModelRatioNotSetEditor options={inputs} refresh={onRefresh} />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab={t('上游价格同步')} itemKey='upstream_sync'> <Tabs.TabPane tab={t('上游价格同步')} itemKey='upstream_sync'>
+65 -76
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
@@ -104,9 +104,7 @@ const SystemSetting = () => {
// SSRF // SSRF
'fetch_setting.enable_ssrf_protection': true, 'fetch_setting.enable_ssrf_protection': true,
'fetch_setting.allow_private_ip': '', 'fetch_setting.allow_private_ip': '',
'fetch_setting.domain_filter_mode': false, // true false 'fetch_setting.domain_filter_mode': false, // true 𤏪false ? 'fetch_setting.ip_filter_mode': false, // true 𤏪false ? 'fetch_setting.domain_list': [],
'fetch_setting.ip_filter_mode': false, // true false
'fetch_setting.domain_list': [],
'fetch_setting.ip_list': [], 'fetch_setting.ip_list': [],
'fetch_setting.allowed_ports': [], 'fetch_setting.allowed_ports': [],
'fetch_setting.apply_ip_filter_for_domain': true, 'fetch_setting.apply_ip_filter_for_domain': true,
@@ -193,8 +191,7 @@ const SystemSetting = () => {
item.value = toBoolean(item.value); item.value = toBoolean(item.value);
break; break;
case 'passkey.origins': case 'passkey.origins':
// origins使 // origins𣬚? item.value = item.value || '';
item.value = item.value || '';
break; break;
case 'passkey.rp_display_name': case 'passkey.rp_display_name':
case 'passkey.rp_id': case 'passkey.rp_id':
@@ -203,8 +200,7 @@ const SystemSetting = () => {
item.value = item.value || ''; item.value = item.value || '';
break; break;
case 'passkey.user_verification': case 'passkey.user_verification':
// // ? item.value = item.value || 'preferred';
item.value = item.value || 'preferred';
break; break;
case 'Price': case 'Price':
case 'MinTopUp': case 'MinTopUp':
@@ -217,8 +213,7 @@ const SystemSetting = () => {
}); });
setInputs(newInputs); setInputs(newInputs);
setOriginInputs(newInputs); setOriginInputs(newInputs);
// // 𧋦𠶖? if (
if (
typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined' typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined'
) { ) {
setDomainFilterMode(!!newInputs['fetch_setting.domain_filter_mode']); setDomainFilterMode(!!newInputs['fetch_setting.domain_filter_mode']);
@@ -275,16 +270,14 @@ const SystemSetting = () => {
const results = await Promise.all(requestQueue); const results = await Promise.all(requestQueue);
// // ? const errorResults = results.filter((res) => !res.data.success);
const errorResults = results.filter((res) => !res.data.success);
errorResults.forEach((res) => { errorResults.forEach((res) => {
showError(res.data.message); showError(res.data.message);
}); });
} }
showSuccess(t('更新成功')); showSuccess(t('更新成功'));
// // 𧑐? const newInputs = { ...inputs };
const newInputs = { ...inputs };
options.forEach((opt) => { options.forEach((opt) => {
newInputs[opt.key] = opt.value; newInputs[opt.key] = opt.value;
}); });
@@ -365,8 +358,7 @@ const SystemSetting = () => {
const submitSSRF = async () => { const submitSSRF = async () => {
const options = []; const options = [];
// // 𤾸? options.push({
options.push({
key: 'fetch_setting.domain_filter_mode', key: 'fetch_setting.domain_filter_mode',
value: domainFilterMode, value: domainFilterMode,
}); });
@@ -377,8 +369,7 @@ const SystemSetting = () => {
}); });
} }
// IP // IP𤾸? options.push({
options.push({
key: 'fetch_setting.ip_filter_mode', key: 'fetch_setting.ip_filter_mode',
value: ipFilterMode, value: ipFilterMode,
}); });
@@ -416,13 +407,13 @@ const SystemSetting = () => {
// //
if (emailDomainWhitelist.includes(domain)) { if (emailDomainWhitelist.includes(domain)) {
showError(t('该域名已存在于白名单中')); showError(t('霂亙滚歇摮睃銁鈭𡒊蒾?));
return; return;
} }
setEmailDomainWhitelist([...emailDomainWhitelist, domain]); setEmailDomainWhitelist([...emailDomainWhitelist, domain]);
setEmailToAdd(''); setEmailToAdd('');
showSuccess(t('已添加到白名单')); showSuccess(t('撌脫溶?));
} }
}; };
@@ -511,7 +502,7 @@ const SystemSetting = () => {
!inputs['oidc.well_known'].startsWith('http://') && !inputs['oidc.well_known'].startsWith('http://') &&
!inputs['oidc.well_known'].startsWith('https://') !inputs['oidc.well_known'].startsWith('https://')
) { ) {
showError(t('Well-Known URL 必须以 http:// 或 https:// 开头')); showError(t('Well-Known URL ?http:// ?https:// ?));
return; return;
} }
try { try {
@@ -520,7 +511,7 @@ const SystemSetting = () => {
res.data['authorization_endpoint']; res.data['authorization_endpoint'];
inputs['oidc.token_endpoint'] = res.data['token_endpoint']; inputs['oidc.token_endpoint'] = res.data['token_endpoint'];
inputs['oidc.user_info_endpoint'] = res.data['userinfo_endpoint']; inputs['oidc.user_info_endpoint'] = res.data['userinfo_endpoint'];
showSuccess(t('获取 OIDC 配置成功!')); showSuccess(t(' OIDC 滨蔭𣂼?));
} catch (err) { } catch (err) {
console.error(err); console.error(err);
showError( showError(
@@ -640,8 +631,7 @@ const SystemSetting = () => {
}; };
const submitPasskeySettings = async () => { const submitPasskeySettings = async () => {
// 使formApi // formApi𦻖? const formValues = formApiRef.current?.getValues() || {};
const formValues = formApiRef.current?.getValues() || {};
const options = []; const options = [];
@@ -745,7 +735,7 @@ const SystemSetting = () => {
style={{ marginBottom: 20, marginTop: 16 }} style={{ marginBottom: 20, marginTop: 16 }}
/> />
<Text> <Text>
{t('仅支持')}{' '} {t('𣈲?)}{' '}
<a <a
href='https://github.com/Calcium-Ion/new-api-worker' href='https://github.com/Calcium-Ion/new-api-worker'
target='_blank' target='_blank'
@@ -753,7 +743,7 @@ const SystemSetting = () => {
> >
new-api-worker new-api-worker
</a>{' '} </a>{' '}
{t('或其兼容new-api-worker格式的其他版本')} {t('澆捆new-api-worker隞𣇉?)}
</Text> </Text>
<Row <Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
@@ -778,7 +768,7 @@ const SystemSetting = () => {
field='WorkerAllowHttpImageRequestEnabled' field='WorkerAllowHttpImageRequestEnabled'
noLabel noLabel
> >
{t('允许 HTTP 协议图片请求(适用于自部署代理)')} {t(' HTTP 讛悅霂瑟鍂鈭舘䌊函蔡隞?)}
</Form.Checkbox> </Form.Checkbox>
<Button onClick={submitWorker}>{t('更新Worker设置')}</Button> <Button onClick={submitWorker}>{t('更新Worker设置')}</Button>
</Form.Section> </Form.Section>
@@ -787,7 +777,7 @@ const SystemSetting = () => {
<Card> <Card>
<Form.Section text={t('SSRF防护设置')}> <Form.Section text={t('SSRF防护设置')}>
<Text extraText={t('SSRF防护详细说明')}> <Text extraText={t('SSRF防护详细说明')}>
{t('配置服务器端请求伪造(SSRF)防护,用于保护内网资源安全')} {t('滨蔭滚𦛚函垢霂瑟隡芷?SSRF)脫擪嚗𣬚鍂鈭𦒘蝵𤏸皞𣂼?)}
</Text> </Text>
<Row <Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
@@ -796,7 +786,7 @@ const SystemSetting = () => {
<Form.Checkbox <Form.Checkbox
field='fetch_setting.enable_ssrf_protection' field='fetch_setting.enable_ssrf_protection'
noLabel noLabel
extraText={t('SSRF防护开关详细说明')} extraText={t('SSRF脫擪撘唾祕蝏?)}
onChange={(e) => onChange={(e) =>
handleCheckboxChange( handleCheckboxChange(
'fetch_setting.enable_ssrf_protection', 'fetch_setting.enable_ssrf_protection',
@@ -826,7 +816,7 @@ const SystemSetting = () => {
} }
> >
{t( {t(
'允许访问私有IP地址(127.0.0.1、192.168.x.x等内网地址)', '捂霈輸䔮蝘IP?27.0.0.1?92.168.x.x蝑匧蝵穃𧑐?,
)} )}
</Form.Checkbox> </Form.Checkbox>
</Col> </Col>
@@ -849,10 +839,10 @@ const SystemSetting = () => {
} }
style={{ marginBottom: 8 }} style={{ marginBottom: 8 }}
> >
{t('对域名启用 IP 过滤(推荐开启)')} {t('撖孵滚鍳?IP 餈誘嚗𣂼')}
</Form.Checkbox> </Form.Checkbox>
<Text strong> <Text strong>
{t(domainFilterMode ? '域名白名单' : '域名黑名单')} {t(domainFilterMode ? '? : '暺穃?)}
</Text> </Text>
<Text <Text
type='secondary' type='secondary'
@@ -877,8 +867,8 @@ const SystemSetting = () => {
}} }}
style={{ marginBottom: 8 }} style={{ marginBottom: 8 }}
> >
<Radio value='whitelist'>{t('白名单')}</Radio> <Radio value='whitelist'>{t('?)}</Radio>
<Radio value='blacklist'>{t('黑名单')}</Radio> <Radio value='blacklist'>{t('暺穃?)}</Radio>
</Radio.Group> </Radio.Group>
<TagInput <TagInput
value={domainList} value={domainList}
@@ -902,13 +892,13 @@ const SystemSetting = () => {
> >
<Col xs={24} sm={24} md={24} lg={24} xl={24}> <Col xs={24} sm={24} md={24} lg={24} xl={24}>
<Text strong> <Text strong>
{t(ipFilterMode ? 'IP白名单' : 'IP黑名单')} {t(ipFilterMode ? 'IP? : 'IP暺穃?)}
</Text> </Text>
<Text <Text
type='secondary' type='secondary'
style={{ display: 'block', marginBottom: 8 }} style={{ display: 'block', marginBottom: 8 }}
> >
{t('支持CIDR格式,如:8.8.8.8, 192.168.1.0/24')} {t('CIDR嚗?.8.8.8, 192.168.1.0/24')}
</Text> </Text>
<Radio.Group <Radio.Group
type='button' type='button'
@@ -925,8 +915,8 @@ const SystemSetting = () => {
}} }}
style={{ marginBottom: 8 }} style={{ marginBottom: 8 }}
> >
<Radio value='whitelist'>{t('白名单')}</Radio> <Radio value='whitelist'>{t('?)}</Radio>
<Radio value='blacklist'>{t('黑名单')}</Radio> <Radio value='blacklist'>{t('暺穃?)}</Radio>
</Radio.Group> </Radio.Group>
<TagInput <TagInput
value={ipList} value={ipList}
@@ -949,7 +939,7 @@ const SystemSetting = () => {
style={{ marginTop: 16 }} style={{ marginTop: 16 }}
> >
<Col xs={24} sm={24} md={24} lg={24} xl={24}> <Col xs={24} sm={24} md={24} lg={24} xl={24}>
<Text strong>{t('允许的端口')}</Text> <Text strong>{t('?)}</Text>
<Text <Text
type='secondary' type='secondary'
style={{ display: 'block', marginBottom: 8 }} style={{ display: 'block', marginBottom: 8 }}
@@ -966,7 +956,7 @@ const SystemSetting = () => {
'fetch_setting.allowed_ports': value, 'fetch_setting.allowed_ports': value,
})); }));
}} }}
placeholder={t('输入端口后回车,如:80 8000-8999')} placeholder={t('颲枏蝡臬藁𤾸頧佗80 ?8000-8999')}
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
<Text <Text
@@ -1015,7 +1005,7 @@ const SystemSetting = () => {
handleCheckboxChange('EmailVerificationEnabled', e) handleCheckboxChange('EmailVerificationEnabled', e)
} }
> >
{t('通过密码注册时需要进行邮箱验证')} {t('瘜典銵屸蝞梢?)}
</Form.Checkbox> </Form.Checkbox>
<Form.Checkbox <Form.Checkbox
field='RegisterEnabled' field='RegisterEnabled'
@@ -1024,7 +1014,7 @@ const SystemSetting = () => {
handleCheckboxChange('RegisterEnabled', e) handleCheckboxChange('RegisterEnabled', e)
} }
> >
{t('允许新用户注册')} {t('啁鍂瑟釣?)}
</Form.Checkbox> </Form.Checkbox>
<Form.Checkbox <Form.Checkbox
field='TurnstileCheckEnabled' field='TurnstileCheckEnabled'
@@ -1102,7 +1092,7 @@ const SystemSetting = () => {
<Banner <Banner
type='info' type='info'
description={t( description={t(
'Passkey 是基于 WebAuthn 标准的无密码身份验证方法,支持指纹、面容、硬件密钥等认证方式', 'Passkey 臬抅鈭?WebAuthn 頨思遢撉諹嚗峕𣈲蝥嫘𢒰摰嫘′隞嗅霈方',
)} )}
style={{ marginBottom: 20, marginTop: 16 }} style={{ marginBottom: 20, marginTop: 16 }}
/> />
@@ -1130,7 +1120,7 @@ const SystemSetting = () => {
label={t('服务显示名称')} label={t('服务显示名称')}
placeholder={t('默认使用系统名称')} placeholder={t('默认使用系统名称')}
extraText={t( extraText={t(
"用户注册时看到的网站名称,比如'我的网站'", "瘜典蝵𤑳滨妍嚗峕憒?𤑳蝵𤑳'",
)} )}
/> />
</Col> </Col>
@@ -1153,26 +1143,26 @@ const SystemSetting = () => {
<Form.Select <Form.Select
field="['passkey.user_verification']" field="['passkey.user_verification']"
label={t('安全验证级别')} label={t('安全验证级别')}
placeholder={t('是否要求指纹/面容等生物识别')} placeholder={t('臬炏閬/W捆蝑厩?)}
optionList={[ optionList={[
{ {
label: t('推荐使用(用户可选)'), label: t('推荐使用(用户可选)'),
value: 'preferred', value: 'preferred',
}, },
{ label: t('强制要求'), value: 'required' }, { label: t('强制要求'), value: 'required' },
{ label: t('不建议使用'), value: 'discouraged' }, { label: t('銝滚遣霈桐蝙?), value: 'discouraged' },
]} ]}
extraText={t('推荐:用户可以选择是否使用指纹等验证')} extraText={t('嚗𡁶鍂瑕虾隞仿㗇𥋘臬炏雿輻鍂犒蝑厰?)}
/> />
</Col> </Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12}> <Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Select <Form.Select
field="['passkey.attachment_preference']" field="['passkey.attachment_preference']"
label={t('设备类型偏好')} label={t('设备类型偏好')}
placeholder={t('选择支持的认证设备类型')} placeholder={t('㗇𥋘恕霂挽憭?)}
optionList={[ optionList={[
{ label: t('不限制'), value: '' }, { label: t('銝漤?), value: '' },
{ label: t('本设备内置'), value: 'platform' }, { label: t('祈挽憭蝵?), value: 'platform' },
{ label: t('外接设备'), value: 'cross-platform' }, { label: t('外接设备'), value: 'cross-platform' },
]} ]}
extraText={t( extraText={t(
@@ -1189,7 +1179,7 @@ const SystemSetting = () => {
<Form.Checkbox <Form.Checkbox
field="['passkey.allow_insecure_origin']" field="['passkey.allow_insecure_origin']"
noLabel noLabel
extraText={t('仅用于开发环境,生产环境应使用 HTTPS')} extraText={t('鍂鈭𤾸𤑳㴓憓煺漣摨𥪯蝙?HTTPS')}
onChange={(e) => onChange={(e) =>
handleCheckboxChange( handleCheckboxChange(
'passkey.allow_insecure_origin', 'passkey.allow_insecure_origin',
@@ -1197,7 +1187,7 @@ const SystemSetting = () => {
) )
} }
> >
{t('允许不安全的 OriginHTTP')} {t('捂銝滚 Origin嚗𠃍TTP?)}
</Form.Checkbox> </Form.Checkbox>
</Col> </Col>
</Row> </Row>
@@ -1208,10 +1198,10 @@ const SystemSetting = () => {
<Col xs={24} sm={24} md={24} lg={24} xl={24}> <Col xs={24} sm={24} md={24} lg={24} xl={24}>
<Form.Input <Form.Input
field="['passkey.origins']" field="['passkey.origins']"
label={t('允许的 Origins')} label={t('?Origins')}
placeholder={t('填写带https的域名,逗号分隔')} placeholder={t('填写带https的域名,逗号分隔')}
extraText={t( extraText={t(
'为空则默认使用服务器地址,多个 Origin 用逗号分隔,例如 https://newapi.pro,https://newapi.com ,注意不能携带[],需使用https', '銝箇征霈支蝙∪膥銝?Origin 堒噡嚗䔶憒?https://newapi.pro,https://newapi.com ,瘜冽銝滩箏蒂[]嚗屸雿輻鍂https',
)} )}
/> />
</Col> </Col>
@@ -1226,7 +1216,7 @@ const SystemSetting = () => {
</Card> </Card>
<Card> <Card>
<Form.Section text={t('配置邮箱域名白名单')}> <Form.Section text={t('滨蔭桃拳?)}>
<Text>{t('用以防止恶意用户利用临时邮箱批量注册')}</Text> <Text>{t('用以防止恶意用户利用临时邮箱批量注册')}</Text>
<Row <Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
@@ -1242,8 +1232,7 @@ const SystemSetting = () => {
) )
} }
> >
启用邮箱域名白名单 舐鍂桃拳? </Form.Checkbox>
</Form.Checkbox>
</Col> </Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12}> <Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Checkbox <Form.Checkbox
@@ -1263,7 +1252,7 @@ const SystemSetting = () => {
<TagInput <TagInput
value={emailDomainWhitelist} value={emailDomainWhitelist}
onChange={setEmailDomainWhitelist} onChange={setEmailDomainWhitelist}
placeholder={t('输入域名后回车')} placeholder={t('颲枏𤾸?)}
style={{ width: '100%', marginTop: 16 }} style={{ width: '100%', marginTop: 16 }}
/> />
<Form.Input <Form.Input
@@ -1286,13 +1275,13 @@ const SystemSetting = () => {
onClick={submitEmailDomainWhitelist} onClick={submitEmailDomainWhitelist}
style={{ marginTop: 10 }} style={{ marginTop: 10 }}
> >
{t('保存邮箱域名白名单设置')} {t('靽嘥桃拳閗挽蝵?)}
</Button> </Button>
</Form.Section> </Form.Section>
</Card> </Card>
<Card> <Card>
<Form.Section text={t('配置 SMTP')}> <Form.Section text={t('配置 SMTP')}>
<Text>{t('用以支持系统的邮件发送')}</Text> <Text>{t('其誑蝟餌隞嗅?)}</Text>
<Row <Row
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }} gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
> >
@@ -1316,7 +1305,7 @@ const SystemSetting = () => {
<Col xs={24} sm={24} md={8} lg={8} xl={8}> <Col xs={24} sm={24} md={8} lg={8} xl={8}>
<Form.Input <Form.Input
field='SMTPFrom' field='SMTPFrom'
label={t('SMTP 发送者邮箱')} label={t('SMTP ?)}
/> />
</Col> </Col>
<Col xs={24} sm={24} md={8} lg={8} xl={8}> <Col xs={24} sm={24} md={8} lg={8} xl={8}>
@@ -1355,17 +1344,17 @@ const SystemSetting = () => {
<Form.Section text={t('配置 OIDC')}> <Form.Section text={t('配置 OIDC')}>
<Text> <Text>
{t( {t(
'用以支持通过 OIDC 登录,例如 Okta、Auth0 等兼容 OIDC 协议的 IdP', '其誑 OIDC 嚗䔶憒?Oktauth0 蝑匧摰?OIDC 讛悅?IdP',
)} )}
</Text> </Text>
<Banner <Banner
type='info' type='info'
description={`${t('主页链接填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}${t('重定向 URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}/oauth/oidc`} description={`${t('銝駁△暹𦻖憛?)} ${inputs.ServerAddress ? inputs.ServerAddress : t('蝵𤑳')}嚗?{t('?URL ?)} ${inputs.ServerAddress ? inputs.ServerAddress : t('蝵𤑳')}/oauth/oidc`}
style={{ marginBottom: 20, marginTop: 16 }} style={{ marginBottom: 20, marginTop: 16 }}
/> />
<Text> <Text>
{t( {t(
'若你的 OIDC Provider 支持 Discovery Endpoint,你可以仅填写 OIDC Well-Known URL,系统会自动获取 OIDC 配置', '?OIDC Provider Discovery Endpoint嚗䔶臭誑隞?OIDC Well-Known URL嚗𣬚頂蝏煺芸𢆡 OIDC 滨蔭',
)} )}
</Text> </Text>
<Row <Row
@@ -1375,14 +1364,14 @@ const SystemSetting = () => {
<Form.Input <Form.Input
field="['oidc.well_known']" field="['oidc.well_known']"
label={t('Well-Known URL')} label={t('Well-Known URL')}
placeholder={t('请输入 OIDC Well-Known URL')} placeholder={t('霂瑁?OIDC ?Well-Known URL')}
/> />
</Col> </Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12}> <Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input <Form.Input
field="['oidc.client_id']" field="['oidc.client_id']"
label={t('Client ID')} label={t('Client ID')}
placeholder={t('输入 OIDC Client ID')} placeholder={t('颲枏 OIDC ?Client ID')}
/> />
</Col> </Col>
</Row> </Row>
@@ -1401,7 +1390,7 @@ const SystemSetting = () => {
<Form.Input <Form.Input
field="['oidc.authorization_endpoint']" field="['oidc.authorization_endpoint']"
label={t('Authorization Endpoint')} label={t('Authorization Endpoint')}
placeholder={t('输入 OIDC Authorization Endpoint')} placeholder={t('颲枏 OIDC ?Authorization Endpoint')}
/> />
</Col> </Col>
</Row> </Row>
@@ -1412,14 +1401,14 @@ const SystemSetting = () => {
<Form.Input <Form.Input
field="['oidc.token_endpoint']" field="['oidc.token_endpoint']"
label={t('Token Endpoint')} label={t('Token Endpoint')}
placeholder={t('输入 OIDC Token Endpoint')} placeholder={t('颲枏 OIDC ?Token Endpoint')}
/> />
</Col> </Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12}> <Col xs={24} sm={24} md={12} lg={12} xl={12}>
<Form.Input <Form.Input
field="['oidc.user_info_endpoint']" field="['oidc.user_info_endpoint']"
label={t('User Info Endpoint')} label={t('User Info Endpoint')}
placeholder={t('输入 OIDC Userinfo Endpoint')} placeholder={t('颲枏 OIDC ?Userinfo Endpoint')}
/> />
</Col> </Col>
</Row> </Row>
@@ -1434,7 +1423,7 @@ const SystemSetting = () => {
<Text>{t('用以支持通过 GitHub 进行登录注册')}</Text> <Text>{t('用以支持通过 GitHub 进行登录注册')}</Text>
<Banner <Banner
type='info' type='info'
description={`${t('Homepage URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}${t('Authorization callback URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}/oauth/github`} description={`${t('Homepage URL 憛?)} ${inputs.ServerAddress ? inputs.ServerAddress : t('蝵𤑳')}嚗?{t('Authorization callback URL ?)} ${inputs.ServerAddress ? inputs.ServerAddress : t('蝵𤑳')}/oauth/github`}
style={{ marginBottom: 20, marginTop: 16 }} style={{ marginBottom: 20, marginTop: 16 }}
/> />
<Row <Row
@@ -1465,7 +1454,7 @@ const SystemSetting = () => {
<Text>{t('用以支持通过 Discord 进行登录注册')}</Text> <Text>{t('用以支持通过 Discord 进行登录注册')}</Text>
<Banner <Banner
type='info' type='info'
description={`${t('Homepage URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}${t('Authorization callback URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}/oauth/discord`} description={`${t('Homepage URL 憛?)} ${inputs.ServerAddress ? inputs.ServerAddress : t('蝵𤑳')}嚗?{t('Authorization callback URL ?)} ${inputs.ServerAddress ? inputs.ServerAddress : t('蝵𤑳')}/oauth/discord`}
style={{ marginBottom: 20, marginTop: 16 }} style={{ marginBottom: 20, marginTop: 16 }}
/> />
<Row <Row
@@ -1511,7 +1500,7 @@ const SystemSetting = () => {
</Text> </Text>
<Banner <Banner
type='info' type='info'
description={`${t('回调 URL 填')} ${inputs.ServerAddress ? inputs.ServerAddress : t('网站地址')}/oauth/linuxdo`} description={`${t(' URL 憛?)} ${inputs.ServerAddress ? inputs.ServerAddress : t('蝵𤑳')}/oauth/linuxdo`}
style={{ marginBottom: 20, marginTop: 16 }} style={{ marginBottom: 20, marginTop: 16 }}
/> />
<Row <Row
@@ -1521,7 +1510,7 @@ const SystemSetting = () => {
<Form.Input <Form.Input
field='LinuxDOClientId' field='LinuxDOClientId'
label={t('Linux DO Client ID')} label={t('Linux DO Client ID')}
placeholder={t('输入你注册的 LinuxDO OAuth APP ID')} placeholder={t('颲枏雿䭾釣𣬚 LinuxDO OAuth APP ?ID')}
/> />
</Col> </Col>
<Col xs={24} sm={24} md={10} lg={10} xl={10}> <Col xs={24} sm={24} md={10} lg={10} xl={10}>
@@ -1536,7 +1525,7 @@ const SystemSetting = () => {
<Form.Input <Form.Input
field='LinuxDOMinimumTrustLevel' field='LinuxDOMinimumTrustLevel'
label='LinuxDO Minimum Trust Level' label='LinuxDO Minimum Trust Level'
placeholder='允许注册的最低信任等级' placeholder='捂瘜典雿𦒘縑隞餌?
/> />
</Col> </Col>
</Row> </Row>
@@ -1648,7 +1637,7 @@ const SystemSetting = () => {
> >
<p> <p>
{t( {t(
'您确定要取消密码登录功能吗?这可能会影响用户的登录方式。', '摰朞𡝗餈坔虾敶勗蒈敶閙䲮撘譌?,
)} )}
</p> </p>
</Modal> </Modal>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -74,7 +74,7 @@ const AccountManagement = ({
}) => { }) => {
const renderAccountInfo = (accountId, label) => { const renderAccountInfo = (accountId, label) => {
if (!accountId || accountId === '') { if (!accountId || accountId === '') {
return <span className='text-gray-500'>{t('未绑定')}</span>; return <span className='text-gray-500'>{t('?)}</span>;
} }
const popContent = ( const popContent = (
@@ -120,7 +120,7 @@ const AccountManagement = ({
const handleUnbindCustomOAuth = async (providerId, providerName) => { const handleUnbindCustomOAuth = async (providerId, providerName) => {
Modal.confirm({ Modal.confirm({
title: t('确认解绑'), title: t('确认解绑'),
content: t('确定要解绑 {{name}} 吗?', { name: providerName }), content: t('蝖桀圾蝏?{{name}} ', { name: providerName }),
okText: t('确认'), okText: t('确认'),
cancelText: t('取消'), cancelText: t('取消'),
onOk: async () => { onOk: async () => {
@@ -251,10 +251,10 @@ const AccountManagement = ({
</div> </div>
<div className='text-sm text-gray-500 truncate'> <div className='text-sm text-gray-500 truncate'>
{!status.wechat_login {!status.wechat_login
? t('未启用') ? t('芸鍳?)
: isBound(userState.user?.wechat_id) : isBound(userState.user?.wechat_id)
? t('已绑定') ? t('撌脩?)
: t('未绑定')} : t('?)}
</div> </div>
</div> </div>
</div> </div>
@@ -270,7 +270,7 @@ const AccountManagement = ({
? t('修改绑定') ? t('修改绑定')
: status.wechat_login : status.wechat_login
? t('绑定') ? t('绑定')
: t('未启用')} : t('芸鍳?)}
</Button> </Button>
</div> </div>
</div> </div>
@@ -311,7 +311,7 @@ const AccountManagement = ({
!status.github_oauth !status.github_oauth
} }
> >
{status.github_oauth ? t('绑定') : t('未启用')} {status.github_oauth ? t('蝏穃') : t('芸鍳?)}
</Button> </Button>
</div> </div>
</div> </div>
@@ -352,7 +352,7 @@ const AccountManagement = ({
!status.discord_oauth !status.discord_oauth
} }
> >
{status.discord_oauth ? t('绑定') : t('未启用')} {status.discord_oauth ? t('蝏穃') : t('芸鍳?)}
</Button> </Button>
</div> </div>
</div> </div>
@@ -395,7 +395,7 @@ const AccountManagement = ({
isBound(userState.user?.oidc_id) || !status.oidc_enabled isBound(userState.user?.oidc_id) || !status.oidc_enabled
} }
> >
{status.oidc_enabled ? t('绑定') : t('未启用')} {status.oidc_enabled ? t('蝏穃') : t('芸鍳?)}
</Button> </Button>
</div> </div>
</div> </div>
@@ -432,7 +432,7 @@ const AccountManagement = ({
type='primary' type='primary'
theme='outline' theme='outline'
> >
{t('已绑定')} {t('撌脩?)}
</Button> </Button>
) : ( ) : (
<Button <Button
@@ -451,7 +451,7 @@ const AccountManagement = ({
type='primary' type='primary'
theme='outline' theme='outline'
> >
{t('未启用')} {t('芸鍳?)}
</Button> </Button>
)} )}
</div> </div>
@@ -511,13 +511,13 @@ const AccountManagement = ({
!status.linuxdo_oauth !status.linuxdo_oauth
} }
> >
{status.linuxdo_oauth ? t('绑定') : t('未启用')} {status.linuxdo_oauth ? t('蝏穃') : t('芸鍳?)}
</Button> </Button>
</div> </div>
</div> </div>
</Card> </Card>
{/* 自定义 OAuth 提供商绑定 */} {/* 銋?OAuth 𣂷摰?*/}
{status.custom_oauth_providers && {status.custom_oauth_providers &&
status.custom_oauth_providers.map((provider) => { status.custom_oauth_providers.map((provider) => {
const bound = isCustomOAuthBound(provider.id); const bound = isCustomOAuthBound(provider.id);
@@ -542,7 +542,7 @@ const AccountManagement = ({
binding?.provider_user_id, binding?.provider_user_id,
t('{{name}} ID', { name: provider.name }), t('{{name}} ID', { name: provider.name }),
) )
: t('未绑定')} : t('?)}
</div> </div>
</div> </div>
</div> </div>
@@ -603,7 +603,7 @@ const AccountManagement = ({
{t('系统访问令牌')} {t('系统访问令牌')}
</Typography.Title> </Typography.Title>
<Typography.Text type='tertiary' className='text-sm'> <Typography.Text type='tertiary' className='text-sm'>
{t('用于API调用的身份验证令牌,请妥善保管')} {t('API靚澈隞賡霂瑕戎?)}
</Typography.Text> </Typography.Text>
{systemToken && ( {systemToken && (
<div className='mt-3'> <div className='mt-3'>
@@ -642,7 +642,7 @@ const AccountManagement = ({
{t('密码管理')} {t('密码管理')}
</Typography.Title> </Typography.Title>
<Typography.Text type='tertiary' className='text-sm'> <Typography.Text type='tertiary' className='text-sm'>
{t('定期更改密码可以提高账户安全性')} {t('摰𡁏湔㺿撖臭誑韐行摰匧?)}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
@@ -671,26 +671,26 @@ const AccountManagement = ({
</Typography.Title> </Typography.Title>
<Typography.Text type='tertiary' className='text-sm'> <Typography.Text type='tertiary' className='text-sm'>
{passkeyEnabled {passkeyEnabled
? t('已启用 Passkey,无需密码即可登录') ? t('撌脣鍳?Passkey嚗峕喳虾')
: t('使用 Passkey 实现免密且更安全的登录体验')} : t('雿輻鍂 Passkey 摰䂿緵銝娍凒摰匧蒈敶蓥?)}
</Typography.Text> </Typography.Text>
<div className='mt-2 text-xs text-gray-500 space-y-1'> <div className='mt-2 text-xs text-gray-500 space-y-1'>
<div> <div>
{t('最后使用时间')}{lastUsedLabel} {t('𦒘蝙冽𧒄?)}嚗㝯lastUsedLabel}
</div> </div>
{/*{passkeyEnabled && (*/} {/*{passkeyEnabled && (*/}
{/* <div>*/} {/* <div>*/}
{/* {t('备份支持')}*/} {/* {t('')}嚗?/}
{/* {passkeyStatus?.backup_eligible*/} {/* {passkeyStatus?.backup_eligible*/}
{/* ? t('支持备份')*/} {/* ? t('支持备份')*/}
{/* : t('不支持')}*/} {/* : t('銝齿𣈲?)}*/}
{/* {t('备份状态')}*/} {/* 嚗庙t('?)}嚗?/}
{/* {passkeyStatus?.backup_state ? t('已备份') : t('未备份')}*/} {/* {passkeyStatus?.backup_state ? t('撌脣隞?) : t('隞?)}*/}
{/* </div>*/} {/* </div>*/}
{/*)}*/} {/*)}*/}
{!passkeySupported && ( {!passkeySupported && (
<div className='text-amber-600'> <div className='text-amber-600'>
{t('当前设备不支持 Passkey')} {t('敶枏霈曉銝齿𣈲?Passkey')}
</div> </div>
)} )}
</div> </div>
@@ -747,7 +747,7 @@ const AccountManagement = ({
{t('删除账户')} {t('删除账户')}
</Typography.Title> </Typography.Title>
<Typography.Text type='tertiary' className='text-sm'> <Typography.Text type='tertiary' className='text-sm'>
{t('此操作不可逆,所有数据将被永久删除')} {t('甇斗雿靝㗇㺭鋡急偶銋?)}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
@@ -57,13 +57,10 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
const [currentMonth, setCurrentMonth] = useState( const [currentMonth, setCurrentMonth] = useState(
new Date().toISOString().slice(0, 7), new Date().toISOString().slice(0, 7),
); );
// // åˆå§åŠ è½½çŠæï¼Œç¨äºŽé¿å折å çŠæéªçƒ? const [initialLoaded, setInitialLoaded] = useState(false);
const [initialLoaded, setInitialLoaded] = useState(false); // 折å çŠæï¼šnull 表示未确定(ç­å¾é¦æ¬¡åŠ è½½ï¼? const [isCollapsed, setIsCollapsed] = useState(null);
// null
const [isCollapsed, setIsCollapsed] = useState(null);
// 便 // åˆå»ºæ¥æœŸåˆ°é¢åº¦çšæ˜ å°ï¼Œæ¹ä¾¿å¿«éŸæŸ¥æ? const checkinRecordsMap = useMemo(() => {
const checkinRecordsMap = useMemo(() => {
const map = {}; const map = {};
const records = checkinData.stats?.records || []; const records = checkinData.stats?.records || [];
records.forEach((record) => { records.forEach((record) => {
@@ -72,8 +69,7 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
return map; return map;
}, [checkinData.stats?.records]); }, [checkinData.stats?.records]);
// // è®¡ç®æœ¬æœˆèŽ·å¾çšé¢åº? const monthlyQuota = useMemo(() => {
const monthlyQuota = useMemo(() => {
const records = checkinData.stats?.records || []; const records = checkinData.stats?.records || [];
return records.reduce( return records.reduce(
(sum, record) => sum + (record.quota_awarded || 0), (sum, record) => sum + (record.quota_awarded || 0),
@@ -81,8 +77,7 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
); );
}, [checkinData.stats?.records]); }, [checkinData.stats?.records]);
// // 获åç­¾åˆ°çŠæ? const fetchCheckinStatus = async (month) => {
const fetchCheckinStatus = async (month) => {
const isFirstLoad = !initialLoaded; const isFirstLoad = !initialLoaded;
setLoading(true); setLoading(true);
try { try {
@@ -90,20 +85,19 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
const { success, data, message } = res.data; const { success, data, message } = res.data;
if (success) { if (success) {
setCheckinData(data); setCheckinData(data);
// // 馿¬¡åŠ è½½æï¼Œæ ¹æ®ç­¾åˆ°çŠæè®¾ç½®æŠ˜å çŠæ? if (isFirstLoad) {
if (isFirstLoad) {
setIsCollapsed(data.stats?.checked_in_today ?? false); setIsCollapsed(data.stats?.checked_in_today ?? false);
setInitialLoaded(true); setInitialLoaded(true);
} }
} else { } else {
showError(message || t('获取签到状态失败')); showError(message || t('获åç­¾åˆ°çŠæå¤±è´?));
if (isFirstLoad) { if (isFirstLoad) {
setIsCollapsed(false); setIsCollapsed(false);
setInitialLoaded(true); setInitialLoaded(true);
} }
} }
} catch (error) { } catch (error) {
showError(t('获取签到状态失败')); showError(t('获åç­¾åˆ°çŠæå¤±è´?));
if (isFirstLoad) { if (isFirstLoad) {
setIsCollapsed(false); setIsCollapsed(false);
setInitialLoaded(true); setInitialLoaded(true);
@@ -133,10 +127,9 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
const { success, data, message } = res.data; const { success, data, message } = res.data;
if (success) { if (success) {
showSuccess( showSuccess(
t('签到成功!获得') + ' ' + renderQuota(data.quota_awarded), t('签到æˆåŠŸï¼èŽ·å¾?) + ' ' + renderQuota(data.quota_awarded),
); );
// // 刷æ°ç­¾åˆ°çŠæ? fetchCheckinStatus(currentMonth);
fetchCheckinStatus(currentMonth);
setTurnstileModalVisible(false); setTurnstileModalVisible(false);
} else { } else {
if (!token && shouldTriggerTurnstile(message)) { if (!token && shouldTriggerTurnstile(message)) {
@@ -165,16 +158,13 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
} }
}, [status?.checkin_enabled, currentMonth]); }, [status?.checkin_enabled, currentMonth]);
// // 妿žœç­¾åˆ°åŠŸèƒ½æœªå¯ç¨ï¼Œä¸æ˜¾ç¤ºç»ä»? if (!status?.checkin_enabled) {
if (!status?.checkin_enabled) {
return null; return null;
} }
// - // æ¥æœŸæ¸²æŸå½æ° - æ˜¾ç¤ºç­¾åˆ°çŠæåŒèŽ·å¾çšé¢åº? const dateRender = (dateString) => {
const dateRender = (dateString) => { // Semi Calendar ä¼ å¥çš?dateString æ˜?Date.toString() æ ¼å¼
// Semi Calendar dateString Date.toString() // éœè¦è½¬æ¢ä¸º YYYY-MM-DD æ ¼å¼æ¥åŒ¹éåŽç«¯æ°æ? const date = new Date(dateString);
// YYYY-MM-DD
const date = new Date(dateString);
if (isNaN(date.getTime())) { if (isNaN(date.getTime())) {
return null; return null;
} }
@@ -260,12 +250,12 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
</div> </div>
<div className='text-xs text-gray-500 dark:text-gray-400'> <div className='text-xs text-gray-500 dark:text-gray-400'>
{!initialLoaded {!initialLoaded
? t('正在加载签到状态...') ? t('正在加载签到状æ€?..')
: checkinData.stats?.checked_in_today : checkinData.stats?.checked_in_today
? t('今日已签到,累计签到') + ? t('今日已签到,累计签到') +
` ${checkinData.stats?.total_checkins || 0} ` + ` ${checkinData.stats?.total_checkins || 0} ` +
t('天') t('å¤?)
: t('每日签到可获得随机额度奖励')} : t('æ¯æ¥ç­¾åˆ°å¯èŽ·å¾éšæœºé¢åº¦å¥åŠ?)}
</div> </div>
</div> </div>
</div> </div>
@@ -279,14 +269,14 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
className='!bg-green-600 hover:!bg-green-700' className='!bg-green-600 hover:!bg-green-700'
> >
{!initialLoaded {!initialLoaded
? t('加载中...') ? t('加载ä¸?..')
: checkinData.stats?.checked_in_today : checkinData.stats?.checked_in_today
? t('今日已签到') ? t('今æ¥å·²ç­¾åˆ?)
: t('立即签到')} : t('立即签到')}
</Button> </Button>
</div> </div>
{/* 可折叠内容 */} {/* å¯æŠ˜å å†…å®?*/}
<Collapsible isOpen={isCollapsed === false} keepDOM> <Collapsible isOpen={isCollapsed === false} keepDOM>
{/* 签到统计 */} {/* 签到统计 */}
<div className='grid grid-cols-3 gap-3 mb-4 mt-4'> <div className='grid grid-cols-3 gap-3 mb-4 mt-4'>
@@ -370,7 +360,7 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
<div className='mt-3 p-2.5 bg-slate-50 dark:bg-slate-800 rounded-lg'> <div className='mt-3 p-2.5 bg-slate-50 dark:bg-slate-800 rounded-lg'>
<Typography.Text type='tertiary' className='text-xs'> <Typography.Text type='tertiary' className='text-xs'>
<ul className='list-disc list-inside space-y-0.5'> <ul className='list-disc list-inside space-y-0.5'>
<li>{t('每日签到可获得随机额度奖励')}</li> <li>{t('æ¯æ¥ç­¾åˆ°å¯èŽ·å¾éšæœºé¢åº¦å¥åŠ?)}</li>
<li>{t('签到奖励将直接添加到您的账户余额')}</li> <li>{t('签到奖励将直接添加到您的账户余额')}</li>
<li>{t('每日仅可签到一次,请勿重复签到')}</li> <li>{t('每日仅可签到一次,请勿重复签到')}</li>
</ul> </ul>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
@@ -44,8 +44,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
return savedState ? JSON.parse(savedState) : false; return savedState ? JSON.parse(savedState) : false;
}); });
const [activeModelCategory, setActiveModelCategory] = useState('all'); const [activeModelCategory, setActiveModelCategory] = useState('all');
const MODELS_DISPLAY_COUNT = 25; // const MODELS_DISPLAY_COUNT = 25; // 𧢲?
// Save models expanded state to localStorage whenever it changes // Save models expanded state to localStorage whenever it changes
useEffect(() => { useEffect(() => {
localStorage.setItem('modelsExpanded', JSON.stringify(isModelsExpanded)); localStorage.setItem('modelsExpanded', JSON.stringify(isModelsExpanded));
@@ -63,7 +62,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
{t('可用模型')} {t('可用模型')}
</Typography.Text> </Typography.Text>
<div className='text-xs text-gray-600'> <div className='text-xs text-gray-600'>
{t('查看当前可用的所有模型')} {t('敶枏舐鍂㗇芋?)}
</div> </div>
</div> </div>
</div> </div>
@@ -71,7 +70,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
{/* 可用模型部分 */} {/* 可用模型部分 */}
<div className='bg-gray-50 dark:bg-gray-800 rounded-xl'> <div className='bg-gray-50 dark:bg-gray-800 rounded-xl'>
{modelsLoading ? ( {modelsLoading ? (
// - // 𠶖?- 𡒊
<div className='space-y-4'> <div className='space-y-4'>
{/* 模拟分类标签 */} {/* 模拟分类标签 */}
<div <div
@@ -124,7 +123,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
</div> </div>
) : ( ) : (
<> <>
{/* 模型分类标签页 */} {/* 璅∪倌憿?*/}
<div className='mb-4'> <div className='mb-4'>
<Tabs <Tabs
type='card' type='card'
@@ -135,8 +134,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
> >
{Object.entries(getModelCategories(t)).map( {Object.entries(getModelCategories(t)).map(
([key, category]) => { ([key, category]) => {
// // 𧢲? const modelCount =
const modelCount =
key === 'all' key === 'all'
? models.length ? models.length
: models.filter((model) => : models.filter((model) =>
@@ -175,8 +173,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
<div className='bg-white dark:bg-gray-700 rounded-lg p-3'> <div className='bg-white dark:bg-gray-700 rounded-lg p-3'>
{(() => { {(() => {
// // ? const categories = getModelCategories(t);
const categories = getModelCategories(t);
const filteredModels = const filteredModels =
activeModelCategory === 'all' activeModelCategory === 'all'
? models ? models
@@ -186,8 +183,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
}), }),
); );
// // 𠶖? if (filteredModels.length === 0) {
if (filteredModels.length === 0) {
return ( return (
<Empty <Empty
image={ image={
@@ -261,7 +257,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
> >
{t('更多')}{' '} {t('更多')}{' '}
{filteredModels.length - MODELS_DISPLAY_COUNT}{' '} {filteredModels.length - MODELS_DISPLAY_COUNT}{' '}
{t('个模型')} {t('銝芣芋?)}
</Tag> </Tag>
</Space> </Space>
)} )}
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useRef, useEffect, useState, useContext } from 'react'; import React, { useRef, useEffect, useState, useContext } from 'react';
@@ -60,8 +60,7 @@ const NotificationSettings = ({
const [userState] = useContext(UserContext); const [userState] = useContext(UserContext);
const isAdminOrRoot = (userState?.user?.role || 0) >= 10; const isAdminOrRoot = (userState?.user?.role || 0) >= 10;
// // ? const [sidebarLoading, setSidebarLoading] = useState(false);
const [sidebarLoading, setSidebarLoading] = useState(false);
const [activeTabKey, setActiveTabKey] = useState('notification'); const [activeTabKey, setActiveTabKey] = useState('notification');
const [sidebarModulesUser, setSidebarModulesUser] = useState({ const [sidebarModulesUser, setSidebarModulesUser] = useState({
chat: { chat: {
@@ -141,10 +140,9 @@ const NotificationSettings = ({
sidebar_modules: JSON.stringify(sidebarModulesUser), sidebar_modules: JSON.stringify(sidebarModulesUser),
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(t('侧边栏设置保存成功')); showSuccess(t('靘扯器讛挽蝵桐摮䀹?));
// useSidebar // useSidebar? await refreshUserConfig();
await refreshUserConfig();
} else { } else {
showError(res.data.message); showError(res.data.message);
} }
@@ -217,8 +215,7 @@ const NotificationSettings = ({
loadSidebarConfigs(); loadSidebarConfigs();
}, [statusState]); }, [statusState]);
// // 𤥁? useEffect(() => {
useEffect(() => {
if (formApiRef.current && notificationSettings) { if (formApiRef.current && notificationSettings) {
formApiRef.current.setValues(notificationSettings); formApiRef.current.setValues(notificationSettings);
} }
@@ -229,8 +226,7 @@ const NotificationSettings = ({
handleNotificationSettingChange(field, value); handleNotificationSettingChange(field, value);
}; };
// // 西? const isAllowedByAdmin = (sectionKey, moduleKey = null) => {
const isAllowedByAdmin = (sectionKey, moduleKey = null) => {
if (!adminConfig) return true; if (!adminConfig) return true;
if (moduleKey) { if (moduleKey) {
@@ -251,7 +247,7 @@ const NotificationSettings = ({
modules: [ modules: [
{ {
key: 'playground', key: 'playground',
title: t('操练场'), title: t('?),
description: t('AI模型测试环境'), description: t('AI模型测试环境'),
}, },
{ key: 'chat', title: t('聊天'), description: t('聊天会话管理') }, { key: 'chat', title: t('聊天'), description: t('聊天会话管理') },
@@ -259,8 +255,8 @@ const NotificationSettings = ({
}, },
{ {
key: 'console', key: 'console',
title: t('控制台区域'), title: t('啣躹?),
description: t('数据管理和日志查看'), description: t('唳旿蝞峕𠯫敹埈䰻?),
modules: [ modules: [
{ key: 'detail', title: t('数据看板'), description: t('系统数据统计') }, { key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
{ key: 'token', title: t('令牌管理'), description: t('API令牌管理') }, { key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
@@ -278,7 +274,7 @@ const NotificationSettings = ({
title: t('个人中心区域'), title: t('个人中心区域'),
description: t('用户个人功能'), description: t('用户个人功能'),
modules: [ modules: [
{ key: 'topup', title: t('钱包管理'), description: t('余额充值管理') }, { key: 'topup', title: t('蝞∠'), description: t('雿䠷潛恣?) },
{ {
key: 'personal', key: 'personal',
title: t('个人设置'), title: t('个人设置'),
@@ -289,7 +285,7 @@ const NotificationSettings = ({
// //
{ {
key: 'admin', key: 'admin',
title: t('管理员区域'), title: t('睃躹?),
description: t('系统管理功能'), description: t('系统管理功能'),
modules: [ modules: [
{ key: 'channel', title: t('渠道管理'), description: t('API渠道配置') }, { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
@@ -306,8 +302,8 @@ const NotificationSettings = ({
}, },
{ {
key: 'redemption', key: 'redemption',
title: t('兑换码管理'), title: t('烐揢?),
description: t('兑换码生成管理'), description: t('烐揢鞟恣?),
}, },
{ key: 'user', title: t('用户管理'), description: t('用户账户管理') }, { key: 'user', title: t('用户管理'), description: t('用户账户管理') },
{ {
@@ -344,7 +340,7 @@ const NotificationSettings = ({
}) })
.catch((errors) => { .catch((errors) => {
console.log('表单验证失败:', errors); console.log('表单验证失败:', errors);
Toast.error(t('请检查表单填写是否正确')); Toast.error(t('霂瑟蹱糓行迤蝖?));
}); });
} else { } else {
saveNotificationSettings(); saveNotificationSettings();
@@ -364,7 +360,7 @@ const NotificationSettings = ({
onClick={resetSidebarModules} onClick={resetSidebarModules}
className='!rounded-lg' className='!rounded-lg'
> >
{t('重置为默认')} {t('滨蔭銝粹?)}
</Button> </Button>
<Button <Button
type='primary' type='primary'
@@ -438,13 +434,13 @@ const NotificationSettings = ({
field='warningThreshold' field='warningThreshold'
label={ label={
<span> <span>
{t('额度预警阈值')}{' '} {t('憸嘥漲憸?)}{' '}
{renderQuotaWithPrompt( {renderQuotaWithPrompt(
notificationSettings.warningThreshold, notificationSettings.warningThreshold,
)} )}
</span> </span>
} }
placeholder={t('请输入预警额度')} placeholder={t('霂瑁仿霅阡?)}
data={[ data={[
{ value: 100000, label: '0.2$' }, { value: 100000, label: '0.2$' },
{ value: 500000, label: '1$' }, { value: 500000, label: '1$' },
@@ -458,7 +454,7 @@ const NotificationSettings = ({
)} )}
style={{ width: '100%', maxWidth: '300px' }} style={{ width: '100%', maxWidth: '300px' }}
rules={[ rules={[
{ required: true, message: t('请输入预警阈值') }, { required: true, message: t('霂瑁仿霅阡?) },
{ {
validator: (rule, value) => { validator: (rule, value) => {
const numValue = Number(value); const numValue = Number(value);
@@ -476,12 +472,12 @@ const NotificationSettings = ({
field='upstreamModelUpdateNotifyEnabled' field='upstreamModelUpdateNotifyEnabled'
label={t('接收上游模型更新通知')} label={t('接收上游模型更新通知')}
checkedText={t('开')} checkedText={t('开')}
uncheckedText={t('关')} uncheckedText={t('?)}
onChange={(value) => onChange={(value) =>
handleFormChange('upstreamModelUpdateNotifyEnabled', value) handleFormChange('upstreamModelUpdateNotifyEnabled', value)
} }
extraText={t( extraText={t(
'仅管理员可用。开启后,当系统定时检测全部渠道发现上游模型变更或检测异常时,将按你选择的通知方式发送汇总通知;渠道或模型过多时会自动省略部分明细。', '舐鍂蝟餌摰𡁏𧒄璉瘚见皜豢芋瘚见撣豢𧒄嚗㗇𥋘𡁶䰻𡁶䰻嚗𥟇𤘪芸𢆡𡒊?,
)} )}
/> />
)} )}
@@ -497,7 +493,7 @@ const NotificationSettings = ({
} }
prefix={<IconMail />} prefix={<IconMail />}
extraText={t( extraText={t(
'设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱', '霈曄蔭交𤣰憸嘥漲憸蝞勗𧑐嚗䔶憛怠雿輻鍂韐血噡蝏穃?,
)} )}
showClear showClear
/> />
@@ -510,7 +506,7 @@ const NotificationSettings = ({
field='webhookUrl' field='webhookUrl'
label={t('Webhook地址')} label={t('Webhook地址')}
placeholder={t( placeholder={t(
'请输入Webhook地址,例如: https://example.com/webhook', '霂瑁ebhook嚗䔶憒? https://example.com/webhook',
)} )}
onChange={(val) => handleFormChange('webhookUrl', val)} onChange={(val) => handleFormChange('webhookUrl', val)}
prefix={<IconLink />} prefix={<IconLink />}
@@ -526,7 +522,7 @@ const NotificationSettings = ({
}, },
{ {
pattern: /^https:\/\/.+/, pattern: /^https:\/\/.+/,
message: t('Webhook地址必须以https://开头'), message: t('Webhook隞去ttps://?),
}, },
]} ]}
/> />
@@ -534,11 +530,11 @@ const NotificationSettings = ({
<Form.Input <Form.Input
field='webhookSecret' field='webhookSecret'
label={t('接口凭证')} label={t('接口凭证')}
placeholder={t('请输入密钥')} placeholder={t('霂瑁?)}
onChange={(val) => handleFormChange('webhookSecret', val)} onChange={(val) => handleFormChange('webhookSecret', val)}
prefix={<IconKey />} prefix={<IconKey />}
extraText={t( extraText={t(
'密钥将以Bearer方式添加到请求头中,用于验证webhook请求的合法性', '𤨎撠誑Bearer瘛餃啗窈瘙仍銝哨撉諹webhook霂瑟瘜閙?,
)} )}
showClear showClear
/> />
@@ -569,14 +565,14 @@ const NotificationSettings = ({
</div> </div>
<div> <div>
<strong>content:</strong>{' '} <strong>content:</strong>{' '}
{t('通知内容,支持 {{value}} 变量占位符')} {t('𡁶䰻捆嚗峕𣈲?{{value}} ?)}
</div> </div>
<div> <div>
<strong>values:</strong>{' '} <strong>values:</strong>{' '}
{t('按顺序替换content中的变量占位符')} {t('摨𤩺𤜯ontent銝剔?)}
</div> </div>
<div> <div>
<strong>timestamp:</strong> {t('Unix时间戳')} <strong>timestamp:</strong> {t('Unix園𡢿?)}
</div> </div>
</div> </div>
</div> </div>
@@ -584,19 +580,19 @@ const NotificationSettings = ({
</> </>
)} )}
{/* Bark推送设置 */} {/* Bark挽蝵?*/}
{notificationSettings.warningType === 'bark' && ( {notificationSettings.warningType === 'bark' && (
<> <>
<Form.Input <Form.Input
field='barkUrl' field='barkUrl'
label={t('Bark推送URL')} label={t('Bark推送URL')}
placeholder={t( placeholder={t(
'请输入Bark推送URL,例如: https://api.day.app/yourkey/{{title}}/{{content}}', '霂瑁且arkRL嚗䔶憒? https://api.day.app/yourkey/{{title}}/{{content}}',
)} )}
onChange={(val) => handleFormChange('barkUrl', val)} onChange={(val) => handleFormChange('barkUrl', val)}
prefix={<IconLink />} prefix={<IconLink />}
extraText={t( extraText={t(
'支持HTTP和HTTPS,模板变量: {{title}} (通知标题), {{content}} (通知内容)', 'HTTPTTPS嚗峕芋? {{title}} (𡁶䰻), {{content}} (𡁶䰻)',
)} )}
showClear showClear
rules={[ rules={[
@@ -606,7 +602,7 @@ const NotificationSettings = ({
}, },
{ {
pattern: /^https?:\/\/.+/, pattern: /^https?:\/\/.+/,
message: t('Bark推送URL必须以http://或https://开头'), message: t('BarkRL敹隞去ttp://𧬆ttps://?),
}, },
]} ]}
/> />
@@ -621,14 +617,14 @@ const NotificationSettings = ({
</div> </div>
<div className='text-xs text-gray-500 space-y-2'> <div className='text-xs text-gray-500 space-y-2'>
<div> <div>
<strong>{'title'}:</strong> {t('通知标题')} ?<strong>{'title'}:</strong> {t('𡁶䰻')}
</div> </div>
<div> <div>
<strong>{'content'}:</strong> {t('通知内容')} ?<strong>{'content'}:</strong> {t('𡁶䰻')}
</div> </div>
<div className='mt-3 pt-3 border-t border-gray-200'> <div className='mt-3 pt-3 border-t border-gray-200'>
<span className='text-gray-400'> <span className='text-gray-400'>
{t('更多参数请参考')} {t('㺭霂瑕?)}
</span>{' '} </span>{' '}
<a <a
href='https://github.com/Finb/Bark' href='https://github.com/Finb/Bark'
@@ -644,14 +640,14 @@ const NotificationSettings = ({
</> </>
)} )}
{/* Gotify推送设置 */} {/* Gotify挽蝵?*/}
{notificationSettings.warningType === 'gotify' && ( {notificationSettings.warningType === 'gotify' && (
<> <>
<Form.Input <Form.Input
field='gotifyUrl' field='gotifyUrl'
label={t('Gotify服务器地址')} label={t('Gotify服务器地址')}
placeholder={t( placeholder={t(
'请输入Gotify服务器地址,例如: https://gotify.example.com', '霂瑁乎otify滚𦛚典𧑐嚗䔶憒? https://gotify.example.com',
)} )}
onChange={(val) => handleFormChange('gotifyUrl', val)} onChange={(val) => handleFormChange('gotifyUrl', val)}
prefix={<IconLink />} prefix={<IconLink />}
@@ -668,7 +664,7 @@ const NotificationSettings = ({
{ {
pattern: /^https?:\/\/.+/, pattern: /^https?:\/\/.+/,
message: t( message: t(
'Gotify服务器地址必须以http://或https://开头', 'Gotify滚𦛚典𧑐隞去ttp://𧬆ttps://?,
), ),
}, },
]} ]}
@@ -695,14 +691,14 @@ const NotificationSettings = ({
<Form.AutoComplete <Form.AutoComplete
field='gotifyPriority' field='gotifyPriority'
label={t('消息优先级')} label={t('隡睃?)}
placeholder={t('请选择消息优先级')} placeholder={t('霂琿㗇𥋘瘨隡睃?)}
data={[ data={[
{ value: 0, label: t('0 - 最低') }, { value: 0, label: t('0 - ?) },
{ value: 2, label: t('2 - 低') }, { value: 2, label: t('2 - ?) },
{ value: 5, label: t('5 - 正常(默认)') }, { value: 5, label: t('5 - 正常(默认)') },
{ value: 8, label: t('8 - 高') }, { value: 8, label: t('8 - ?) },
{ value: 10, label: t('10 - 最高') }, { value: 10, label: t('10 - ?) },
]} ]}
onChange={(val) => onChange={(val) =>
handleFormChange('gotifyPriority', val) handleFormChange('gotifyPriority', val)
@@ -729,7 +725,7 @@ const NotificationSettings = ({
<div>3. {t('填写Gotify服务器的完整URL地址')}</div> <div>3. {t('填写Gotify服务器的完整URL地址')}</div>
<div className='mt-3 pt-3 border-t border-gray-200'> <div className='mt-3 pt-3 border-t border-gray-200'>
<span className='text-gray-400'> <span className='text-gray-400'>
{t('更多信息请参考')} {t('霂瑕?)}
</span>{' '} </span>{' '}
<a <a
href='https://gotify.net/' href='https://gotify.net/'
@@ -760,14 +756,14 @@ const NotificationSettings = ({
<div className='py-4'> <div className='py-4'>
<Form.Switch <Form.Switch
field='acceptUnsetModelRatioModel' field='acceptUnsetModelRatioModel'
label={t('接受未设置价格模型')} label={t('芾挽蝵桐遠潭芋?)}
checkedText={t('开')} checkedText={t('开')}
uncheckedText={t('关')} uncheckedText={t('?)}
onChange={(value) => onChange={(value) =>
handleFormChange('acceptUnsetModelRatioModel', value) handleFormChange('acceptUnsetModelRatioModel', value)
} }
extraText={t( extraText={t(
'当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用', '敶𤘪芋𧢲瓷㕑挽蝵桐遠潭𧒄隞齿𦻖𡑒其縑隞餉砲蝵𤑳嗡蝙隡帋漣憸肽晶?,
)} )}
/> />
</div> </div>
@@ -788,10 +784,10 @@ const NotificationSettings = ({
field='recordIpLog' field='recordIpLog'
label={t('记录请求与错误日志IP')} label={t('记录请求与错误日志IP')}
checkedText={t('开')} checkedText={t('开')}
uncheckedText={t('关')} uncheckedText={t('?)}
onChange={(value) => handleFormChange('recordIpLog', value)} onChange={(value) => handleFormChange('recordIpLog', value)}
extraText={t( extraText={t(
'开启后,仅"消费"和"错误"日志将记录您的客户端IP地址', '嚗䔶"瘨晶"?躰秤"扇敶閙瑞垢IP',
)} )}
/> />
</div> </div>
@@ -819,7 +815,7 @@ const NotificationSettings = ({
color: 'var(--semi-color-text-2)', color: 'var(--semi-color-text-2)',
}} }}
> >
{t('您可以个性化设置侧边栏的要显示功能')} {t('典虾隞乩葵霈曄蔭靘扯器遬蝷箏?)}
</Typography.Text> </Typography.Text>
</div> </div>
{/* 边栏设置功能区域容器 */} {/* 边栏设置功能区域容器 */}
@@ -832,7 +828,7 @@ const NotificationSettings = ({
> >
{sectionConfigs.map((section) => ( {sectionConfigs.map((section) => (
<div key={section.key} className='mb-6'> <div key={section.key} className='mb-6'>
{/* 区域标题和总开关 */} {/* ?*/}
<div <div
className='flex justify-between items-center mb-4 p-4 rounded-lg' className='flex justify-between items-center mb-4 p-4 rounded-lg'
style={{ style={{
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
@@ -27,12 +27,12 @@ import { normalizeLanguage } from "../../../../i18n/language";
// Language options with native names // Language options with native names
const languageOptions = [ const languageOptions = [
{ value: "zh-CN", label: "简体中文" }, { value: "zh-CN", label: "ç®ä½ä¸­æ? },
{ value: "zh-TW", label: "繁體中文" }, { value: "zh-TW", label: "繁體中文" },
{ value: "en", label: "English" }, { value: "en", label: "English" },
{ value: 'fr', label: 'Français'}, { value: 'fr', label: 'Français'},
{ value: 'ru', label: 'Русский'}, { value: 'ru', label: 'Русский'},
{ value: 'ja', label: '日本語'}, { value: 'ja', label: 'æ¥æœ¬èª?},
{ value: "vi", label: "Tiếng Việt" }, { value: "vi", label: "Tiếng Việt" },
]; ];
@@ -81,7 +81,7 @@ const PreferencesSettings = ({ t }) => {
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(t("语言偏好已保存")); showSuccess(t("语è¨å好已ä¿å­?));
// Keep backend preference, context state, and local cache aligned. // Keep backend preference, context state, and local cache aligned.
let settings = {}; let settings = {};
if (userState?.user?.setting) { if (userState?.user?.setting) {
@@ -131,7 +131,7 @@ const PreferencesSettings = ({ t }) => {
{t("偏好设置")} {t("偏好设置")}
</Typography.Text> </Typography.Text>
<div className="text-xs text-gray-600 dark:text-gray-400"> <div className="text-xs text-gray-600 dark:text-gray-400">
{t("界面语言和其他个人偏好")} {t("çŒé¢è¯­è¨åŒåä»ä¸ªäººåå¥?)}
</div> </div>
</div> </div>
</div> </div>
@@ -150,7 +150,7 @@ const PreferencesSettings = ({ t }) => {
{t("语言偏好")} {t("语言偏好")}
</Typography.Title> </Typography.Title>
<Typography.Text type="tertiary" className="text-sm"> <Typography.Text type="tertiary" className="text-sm">
{t("选择您的首选界面语言,设置将自动保存并同步到所有设备")} {t("éæ©æ¨çšé¦éçŒé¢è¯­è¨ï¼Œè®¾ç½®å°èªåЍä¿å­˜å¹åŒæ­¥åˆ°ææœè®¾å¤?)}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
@@ -171,7 +171,7 @@ const PreferencesSettings = ({ t }) => {
<div className="mt-4 text-xs text-gray-500 dark:text-gray-400"> <div className="mt-4 text-xs text-gray-500 dark:text-gray-400">
<Typography.Text type="tertiary"> <Typography.Text type="tertiary">
{t( {t(
"提示:语言偏好会同步到您登录的所有设备,并影响API返回的错误消息语言。", "æç¤ºï¼šè¯­è¨åå¥½ä¼šåŒæ­¥åˆ°æ¨ç»å½çšææœè®¾å¤ï¼Œå¹å½±åAPIè¿åžçšéè¯¯æˆæ¯è¯­è¨ã?,
)} )}
</Typography.Text> </Typography.Text>
</div> </div>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import { API, showError, showSuccess, showWarning } from '../../../../helpers'; import { API, showError, showSuccess, showWarning } from '../../../../helpers';
import { import {
@@ -51,8 +51,7 @@ const TwoFASetting = ({ t }) => {
backup_codes_remaining: 0, backup_codes_remaining: 0,
}); });
// // ? const [setupModalVisible, setSetupModalVisible] = useState(false);
const [setupModalVisible, setSetupModalVisible] = useState(false);
const [enableModalVisible, setEnableModalVisible] = useState(false); const [enableModalVisible, setEnableModalVisible] = useState(false);
const [disableModalVisible, setDisableModalVisible] = useState(false); const [disableModalVisible, setDisableModalVisible] = useState(false);
const [backupModalVisible, setBackupModalVisible] = useState(false); const [backupModalVisible, setBackupModalVisible] = useState(false);
@@ -64,15 +63,14 @@ const TwoFASetting = ({ t }) => {
const [confirmDisable, setConfirmDisable] = useState(false); const [confirmDisable, setConfirmDisable] = useState(false);
const [currentStep, setCurrentStep] = useState(0); const [currentStep, setCurrentStep] = useState(0);
// 2FA // 2FA? const fetchStatus = async () => {
const fetchStatus = async () => {
try { try {
const res = await API.get('/api/user/2fa/status'); const res = await API.get('/api/user/2fa/status');
if (res.data.success) { if (res.data.success) {
setStatus(res.data.data); setStatus(res.data.data);
} }
} catch (error) { } catch (error) {
showError(t('获取2FA状态失败')); showError(t('2FA仃韐?));
} }
}; };
@@ -80,7 +78,7 @@ const TwoFASetting = ({ t }) => {
fetchStatus(); fetchStatus();
}, []); }, []);
// 2FA // ?FA
const handleSetup2FA = async () => { const handleSetup2FA = async () => {
setLoading(true); setLoading(true);
try { try {
@@ -112,7 +110,7 @@ const TwoFASetting = ({ t }) => {
code: verificationCode, code: verificationCode,
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(t('两步验证启用成功!')); showSuccess(t('銝斗郊撉諹舐鍂𣂼?));
setEnableModalVisible(false); setEnableModalVisible(false);
setSetupModalVisible(false); setSetupModalVisible(false);
setVerificationCode(''); setVerificationCode('');
@@ -146,7 +144,7 @@ const TwoFASetting = ({ t }) => {
code: verificationCode, code: verificationCode,
}); });
if (res.data.success) { if (res.data.success) {
showSuccess(t('两步验证已禁用')); showSuccess(t('銝斗郊撉諹撌脩?));
setDisableModalVisible(false); setDisableModalVisible(false);
setVerificationCode(''); setVerificationCode('');
setConfirmDisable(false); setConfirmDisable(false);
@@ -161,8 +159,7 @@ const TwoFASetting = ({ t }) => {
} }
}; };
// // 齿? const handleRegenerateBackupCodes = async () => {
const handleRegenerateBackupCodes = async () => {
if (!verificationCode) { if (!verificationCode) {
showWarning(t('请输入验证码')); showWarning(t('请输入验证码'));
return; return;
@@ -175,21 +172,21 @@ const TwoFASetting = ({ t }) => {
}); });
if (res.data.success) { if (res.data.success) {
setBackupCodes(res.data.data.backup_codes); setBackupCodes(res.data.data.backup_codes);
showSuccess(t('备用码重新生成成功')); showSuccess(t('?));
setVerificationCode(''); setVerificationCode('');
fetchStatus(); fetchStatus();
} else { } else {
showError(res.data.message); showError(res.data.message);
} }
} catch (error) { } catch (error) {
showError(t('重新生成备用码失败')); showError(t('齿鰵仃韐?));
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
// //
const copyTextToClipboard = (text, successMessage = t('已复制到剪贴板')) => { const copyTextToClipboard = (text, successMessage = t('撌脣芾斐?)) => {
navigator.clipboard navigator.clipboard
.writeText(text) .writeText(text)
.then(() => { .then(() => {
@@ -205,8 +202,7 @@ const TwoFASetting = ({ t }) => {
copyTextToClipboard(codesText, t('备用码已复制到剪贴板')); copyTextToClipboard(codesText, t('备用码已复制到剪贴板'));
}; };
// // ? const BackupCodesDisplay = ({ codes, title, onCopy }) => {
const BackupCodesDisplay = ({ codes, title, onCopy }) => {
return ( return (
<Card className='!rounded-xl' style={{ width: '100%' }}> <Card className='!rounded-xl' style={{ width: '100%' }}>
<div className='space-y-3'> <div className='space-y-3'>
@@ -242,7 +238,7 @@ const TwoFASetting = ({ t }) => {
onClick={onCopy} onClick={onCopy}
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700 w-full' className='!rounded-lg !bg-slate-600 hover:!bg-slate-700 w-full'
> >
{t('复制所有代码')} {t('憭滚劐誨?)}
</Button> </Button>
</div> </div>
</Card> </Card>
@@ -258,7 +254,7 @@ const TwoFASetting = ({ t }) => {
onClick={() => setCurrentStep(currentStep - 1)} onClick={() => setCurrentStep(currentStep - 1)}
className='!rounded-lg' className='!rounded-lg'
> >
{t('上一步')} {t('銝𠹺?)}
</Button> </Button>
)} )}
{currentStep < 2 ? ( {currentStep < 2 ? (
@@ -268,7 +264,7 @@ const TwoFASetting = ({ t }) => {
onClick={() => setCurrentStep(currentStep + 1)} onClick={() => setCurrentStep(currentStep + 1)}
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700' className='!rounded-lg !bg-slate-600 hover:!bg-slate-700'
> >
{t('下一步')} {t('銝衤?)}
</Button> </Button>
) : ( ) : (
<Button <Button
@@ -284,7 +280,7 @@ const TwoFASetting = ({ t }) => {
}} }}
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700' className='!rounded-lg !bg-slate-600 hover:!bg-slate-700'
> >
{t('完成设置并启用两步验证')} {t('摰峕霈曄蔭撟嗅鍳其舅甇仿?)}
</Button> </Button>
)} )}
</> </>
@@ -358,7 +354,7 @@ const TwoFASetting = ({ t }) => {
onClick={handleRegenerateBackupCodes} onClick={handleRegenerateBackupCodes}
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700' className='!rounded-lg !bg-slate-600 hover:!bg-slate-700'
> >
{t('生成新的备用码')} {t('?)}
</Button> </Button>
</> </>
); );
@@ -382,22 +378,22 @@ const TwoFASetting = ({ t }) => {
</Typography.Title> </Typography.Title>
{status.enabled ? ( {status.enabled ? (
<Tag color='green' shape='circle' size='small'> <Tag color='green' shape='circle' size='small'>
{t('已启用')} {t('撌脣鍳?)}
</Tag> </Tag>
) : ( ) : (
<Tag color='red' shape='circle' size='small'> <Tag color='red' shape='circle' size='small'>
{t('未启用')} {t('芸鍳?)}
</Tag> </Tag>
)} )}
{status.locked && ( {status.locked && (
<Tag color='orange' shape='circle' size='small'> <Tag color='orange' shape='circle' size='small'>
{t('账户已锁定')} {t('韐行撌脤?)}
</Tag> </Tag>
)} )}
</div> </div>
<Typography.Text type='tertiary' className='text-sm'> <Typography.Text type='tertiary' className='text-sm'>
{t( {t(
'两步验证(2FA)为您的账户提供额外的安全保护。启用后,登录时需要输入密码和验证器应用生成的验证码。', '銝斗郊撉諹?FA嚗劐蛹韐行𣂷憸嘥嚗𣬚蒈敶閙𧒄撉諹撉諹?,
)} )}
</Typography.Text> </Typography.Text>
{status.enabled && ( {status.enabled && (
@@ -405,7 +401,7 @@ const TwoFASetting = ({ t }) => {
<Text size='small' type='secondary'> <Text size='small' type='secondary'>
{t('剩余备用码:')} {t('剩余备用码:')}
{status.backup_codes_remaining || 0} {status.backup_codes_remaining || 0}
{t('个')} {t('?)}
</Text> </Text>
</div> </div>
)} )}
@@ -444,7 +440,7 @@ const TwoFASetting = ({ t }) => {
className='!rounded-lg' className='!rounded-lg'
icon={<IconRefresh />} icon={<IconRefresh />}
> >
{t('重新生成备用码')} {t('齿鰵?)}
</Button> </Button>
</div> </div>
)} )}
@@ -476,16 +472,16 @@ const TwoFASetting = ({ t }) => {
{/* 步骤进度 */} {/* 步骤进度 */}
<Steps type='basic' size='small' current={currentStep}> <Steps type='basic' size='small' current={currentStep}>
<Steps.Step <Steps.Step
title={t('扫描二维码')} title={t('鈭𣬚輕?)}
description={t('使用认证器应用扫描二维码')} description={t('使用认证器应用扫描二维码')}
/> />
<Steps.Step <Steps.Step
title={t('保存备用码')} title={t('靽嘥?)}
description={t('保存备用码以备不时之需')} description={t('保存备用码以备不时之需')}
/> />
<Steps.Step <Steps.Step
title={t('验证设置')} title={t('验证设置')}
description={t('输入验证码完成设置')} description={t('颲枏撉諹鞱挽蝵?)}
/> />
</Steps> </Steps>
@@ -495,7 +491,7 @@ const TwoFASetting = ({ t }) => {
<div> <div>
<Paragraph className='text-gray-600 dark:text-gray-300 mb-4'> <Paragraph className='text-gray-600 dark:text-gray-300 mb-4'>
{t( {t(
'使用认证器应用(如 Google Authenticator、Microsoft Authenticator)扫描下方二维码:', '雿輻鍂霈方?Google Authenticatoricrosoft Authenticator嚗㗇醌蝏渡?,
)} )}
</Paragraph> </Paragraph>
<div className='flex justify-center mb-4'> <div className='flex justify-center mb-4'>
@@ -516,7 +512,7 @@ const TwoFASetting = ({ t }) => {
{currentStep === 1 && ( {currentStep === 1 && (
<div className='space-y-4'> <div className='space-y-4'>
{/* 备用码展示 */} {/* 蝷?*/}
<BackupCodesDisplay <BackupCodesDisplay
codes={setupData.backup_codes} codes={setupData.backup_codes}
title={t('备用恢复代码')} title={t('备用恢复代码')}
@@ -567,7 +563,7 @@ const TwoFASetting = ({ t }) => {
<Banner <Banner
type='warning' type='warning'
description={t( description={t(
'警告:禁用两步验证将永久删除您的验证设置和所有备用码,此操作不可撤销!', '霅血嚗𡁶其舅甇仿瘞訾𣳇膄撉諹霈曄蔭嚗峕迨銝滚虾?,
)} )}
className='!rounded-lg' className='!rounded-lg'
/> />
@@ -580,16 +576,16 @@ const TwoFASetting = ({ t }) => {
strong strong
className='block mb-2 text-slate-700 dark:text-slate-200' className='block mb-2 text-slate-700 dark:text-slate-200'
> >
{t('禁用后的影响:')} {t('𡒊敶勗?)}
</Text> </Text>
<ul className='space-y-2 text-sm text-slate-600 dark:text-slate-300'> <ul className='space-y-2 text-sm text-slate-600 dark:text-slate-300'>
<li className='flex items-start gap-2'> <li className='flex items-start gap-2'>
<Badge dot type='warning' /> <Badge dot type='warning' />
{t('降低您账户的安全性')} {t('刻揭摰匧?)}
</li> </li>
<li className='flex items-start gap-2'> <li className='flex items-start gap-2'>
<Badge dot type='warning' /> <Badge dot type='warning' />
{t('需要重新完整设置才能再次启用')} {t('渲挽蝵格?)}
</li> </li>
<li className='flex items-start gap-2'> <li className='flex items-start gap-2'>
<Badge dot type='danger' /> <Badge dot type='danger' />
@@ -613,7 +609,7 @@ const TwoFASetting = ({ t }) => {
{t('验证身份')} {t('验证身份')}
</Text> </Text>
<Input <Input
placeholder={t('请输入认证器验证码或备用码')} placeholder={t('霂瑁亥恕霂膥撉諹?)}
value={verificationCode} value={verificationCode}
onChange={setVerificationCode} onChange={setVerificationCode}
size='large' size='large'
@@ -642,7 +638,7 @@ const TwoFASetting = ({ t }) => {
title={ title={
<div className='flex items-center'> <div className='flex items-center'>
<IconRefresh className='mr-2 text-slate-600' /> <IconRefresh className='mr-2 text-slate-600' />
{t('重新生成备用码')} {t('齿鰵?)}
</div> </div>
} }
visible={backupModalVisible} visible={backupModalVisible}
@@ -663,7 +659,7 @@ const TwoFASetting = ({ t }) => {
<Banner <Banner
type='warning' type='warning'
description={t( description={t(
'重新生成备用码将使现有的备用码失效,请确保您已保存了当前的备用码。', '齿鰵雿輻緵霂瑞靽脲撌脖摮䀝敶枏?,
)} )}
className='!rounded-lg' className='!rounded-lg'
/> />
@@ -679,7 +675,7 @@ const TwoFASetting = ({ t }) => {
{t('验证身份')} {t('验证身份')}
</Text> </Text>
<Input <Input
placeholder={t('请输入认证器验证码')} placeholder={t('霂瑁亥恕霂膥撉諹?)}
value={verificationCode} value={verificationCode}
onChange={setVerificationCode} onChange={setVerificationCode}
size='large' size='large'
@@ -702,10 +698,10 @@ const TwoFASetting = ({ t }) => {
</Text> </Text>
</div> </div>
<Text className='text-slate-500 dark:text-slate-400 text-sm'> <Text className='text-slate-500 dark:text-slate-400 text-sm'>
{t('旧的备用码已失效,请保存新的备用码')} {t('歇憭望嚗諹窈靽嘥?)}
</Text> </Text>
{/* 备用码展示 */} {/* 蝷?*/}
<BackupCodesDisplay <BackupCodesDisplay
codes={backupCodes} codes={backupCodes}
title={t('新的备用恢复代码')} title={t('新的备用恢复代码')}
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -86,7 +86,7 @@ const UserInfoHeader = ({ t, userState }) => {
shape='circle' shape='circle'
style={{ color: 'white' }} style={{ color: 'white' }}
> >
{t('超级管理员')} {t('è级管çå?)}
</Tag> </Tag>
) : isAdmin() ? ( ) : isAdmin() ? (
<Tag <Tag
@@ -94,7 +94,7 @@ const UserInfoHeader = ({ t, userState }) => {
shape='circle' shape='circle'
style={{ color: 'white' }} style={{ color: 'white' }}
> >
{t('管理员')} {t('管çå?)}
</Tag> </Tag>
) : ( ) : (
<Tag <Tag
@@ -102,7 +102,7 @@ const UserInfoHeader = ({ t, userState }) => {
shape='circle' shape='circle'
style={{ color: 'white' }} style={{ color: 'white' }}
> >
{t('普通用户')} {t('æ®éšç¨æˆ?)}
</Tag> </Tag>
)} )}
<Tag size='large' shape='circle' style={{ color: 'white' }}> <Tag size='large' shape='circle' style={{ color: 'white' }}>
@@ -125,7 +125,7 @@ const UserInfoHeader = ({ t, userState }) => {
</div> </div>
</Badge> </Badge>
{/* 桌面版统计信息(Semi UI 卡片) */} {/* 桌é¢ç‰ˆç»Ÿè®¡ä¿¡æ¯ï¼ˆSemi UI å¡ç‰‡ï¼?*/}
<div className='hidden lg:block flex-shrink-0'> <div className='hidden lg:block flex-shrink-0'>
<Card <Card
size='small' size='small'
@@ -136,7 +136,7 @@ const UserInfoHeader = ({ t, userState }) => {
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Coins size={16} /> <Coins size={16} />
<Typography.Text size='small' type='tertiary'> <Typography.Text size='small' type='tertiary'>
{t('历史消耗')} {t('åŽå²æˆè?)}
</Typography.Text> </Typography.Text>
<Typography.Text size='small' type='tertiary' strong> <Typography.Text size='small' type='tertiary' strong>
{renderQuota(userState?.user?.used_quota)} {renderQuota(userState?.user?.used_quota)}
@@ -179,7 +179,7 @@ const UserInfoHeader = ({ t, userState }) => {
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Coins size={16} /> <Coins size={16} />
<Typography.Text size='small' type='tertiary'> <Typography.Text size='small' type='tertiary'>
{t('历史消耗')} {t('åŽå²æˆè?)}
</Typography.Text> </Typography.Text>
</div> </div>
<Typography.Text size='small' type='tertiary' strong> <Typography.Text size='small' type='tertiary' strong>
@@ -1,5 +1,5 @@
/* /*
Copyright (C) 2025 QuantumNous Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com For commercial licensing, please contact admin@modelstoken.com
*/ */
import React from 'react'; import React from 'react';
@@ -59,10 +59,10 @@ const AccountDeleteModal = ({
<div> <div>
<Typography.Text strong className='block mb-2 text-red-600'> <Typography.Text strong className='block mb-2 text-red-600'>
{t('请输入您的用户名以确认删除')} {t('霂瑁隞亦霈文?)}
</Typography.Text> </Typography.Text>
<Input <Input
placeholder={t('输入你的账户名{{username}}以确认删除', { placeholder={t('颲枏雿删韐行{username}}隞亦霈文?, {
username: ` ${userState?.user?.username} `, username: ` ${userState?.user?.username} `,
})} })}
name='self_account_deletion_confirmation' name='self_account_deletion_confirmation'

Some files were not shown because too many files have changed in this diff Show More