chore: rebrand copyright from QuantumNous to modelstoken
Docker Build / Build and Push Docker Image (push) Failing after 1m2s
Docker Build / Build and Push Docker Image (push) Failing after 1m2s
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
new-api Notices
|
||||
|
||||
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.
|
||||
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
|
||||
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
|
||||
mark their changes in accordance with AGPLv3 Section 7(c).
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
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 SystemName = "New API"
|
||||
var SystemName = "ModelsToken"
|
||||
var Footer = ""
|
||||
var Logo = ""
|
||||
var TopUpLink = ""
|
||||
|
||||
Vendored
+2
-2
@@ -18,10 +18,10 @@
|
||||
"openai",
|
||||
"claude"
|
||||
],
|
||||
"author": "QuantumNous",
|
||||
"author": "modelstoken",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/QuantumNous/new-api"
|
||||
"url": "https://git.viaeon.com/admin/new-api"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
|
||||
@@ -164,7 +164,7 @@ func main() {
|
||||
common.SysLog(fmt.Sprintf("panic detected: %v", err))
|
||||
c.JSON(http.StatusInternalServerError, 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",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ func RelayPanicRecover() gin.HandlerFunc {
|
||||
common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack())))
|
||||
c.JSON(http.StatusInternalServerError, 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",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -221,7 +221,7 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, header *http.Header, info *
|
||||
header.Set("HTTP-Referer", "https://www.newapi.ai")
|
||||
}
|
||||
if header.Get("X-OpenRouter-Title") == "" {
|
||||
header.Set("X-OpenRouter-Title", "New API")
|
||||
header.Set("X-OpenRouter-Title", "ModelsToken")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
Vendored
+1
-1
@@ -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."
|
||||
/>
|
||||
<meta name="generator" content="new-api" />
|
||||
<title>New API</title>
|
||||
<title>ModelsToken</title>
|
||||
<!--umami-->
|
||||
<!--Google Analytics-->
|
||||
</head>
|
||||
|
||||
Vendored
+6
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -72,20 +72,16 @@ function App() {
|
||||
try {
|
||||
const modules = JSON.parse(headerNavModulesConfig);
|
||||
|
||||
// 处理向后兼容性:如果pricing是boolean,默认不需要登录
|
||||
if (typeof modules.pricing === 'boolean') {
|
||||
return false; // 默认不需要登录鉴权
|
||||
}
|
||||
// 处���兼容性:如果pricing是boolean,默认�需�登� if (typeof modules.pricing === 'boolean') {
|
||||
return false; // 默认�需�登录鉴� }
|
||||
|
||||
// å¦‚æžœæ˜¯å¯¹è±¡æ ¼å¼�,使用requireAuthé…�ç½®
|
||||
return modules.pricing?.requireAuth === true;
|
||||
} catch (error) {
|
||||
console.error('è§£æž�é¡¶æ �模å�—é…�置失败:', error);
|
||||
return false; // 默认不需要登录
|
||||
}
|
||||
return false; // 默认�需�登� }
|
||||
}
|
||||
return false; // 默认不需要登录
|
||||
}, [statusState?.status?.HeaderNavModules]);
|
||||
return false; // 默认�需�登� }, [statusState?.status?.HeaderNavModules]);
|
||||
|
||||
return (
|
||||
<SetupCheck>
|
||||
|
||||
+34
-41
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -73,7 +73,7 @@ const LoginForm = () => {
|
||||
const githubButtonTextKeyByState = {
|
||||
idle: '雿輻鍂 GitHub 蝏抒賒',
|
||||
redirecting: '甇�銁頝唾蓮 GitHub...',
|
||||
timeout: '请求超时,请刷新页面后重新发起 GitHub 登录',
|
||||
timeout: '霂瑟�頞�𧒄嚗諹窈�瑟鰵憿菟𢒰�𡡞��啣�韏?GitHub �餃�',
|
||||
};
|
||||
const [inputs, setInputs] = useState({
|
||||
username: '',
|
||||
@@ -149,8 +149,7 @@ const LoginForm = () => {
|
||||
setTurnstileSiteKey(status.turnstile_site_key);
|
||||
}
|
||||
|
||||
// 从 status 获取用户协议和隐私政策的启用状态
|
||||
setHasUserAgreement(status?.user_agreement_enabled || false);
|
||||
// 隞?status �瑕��冽��讛悅�屸�蝘�錇蝑𣇉��舐鍂�嗆�? setHasUserAgreement(status?.user_agreement_enabled || false);
|
||||
setHasPrivacyPolicy(status?.privacy_policy_enabled || false);
|
||||
}, [status]);
|
||||
|
||||
@@ -168,7 +167,7 @@ const LoginForm = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get('expired')) {
|
||||
showError(t('未登录或登录已过期,请重新登录'));
|
||||
showError(t('�芰蒈敶閙��餃�撌脰����霂琿��啁蒈敶?));
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -199,7 +198,7 @@ const LoginForm = () => {
|
||||
setUserData(data);
|
||||
updateAPI();
|
||||
navigate('/');
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('�餃��𣂼�嚗?);
|
||||
setShowWeChatLoginModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -237,7 +236,7 @@ const LoginForm = () => {
|
||||
);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
// 检查是否需要2FA验证
|
||||
// 璉��交糓�阡�閬?FA撉諹�
|
||||
if (data && data.require_2fa) {
|
||||
setShowTwoFA(true);
|
||||
setLoginLoading(false);
|
||||
@@ -247,7 +246,7 @@ const LoginForm = () => {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
setUserData(data);
|
||||
updateAPI();
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('�餃��𣂼�嚗?);
|
||||
if (username === 'root' && password === '123456') {
|
||||
Modal.error({
|
||||
title: '�冽迤�其蝙�券�霈文����',
|
||||
@@ -297,7 +296,7 @@ const LoginForm = () => {
|
||||
if (success) {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('�餃��𣂼�嚗?);
|
||||
setUserData(data);
|
||||
updateAPI();
|
||||
navigate('/');
|
||||
@@ -332,8 +331,7 @@ const LoginForm = () => {
|
||||
try {
|
||||
onGitHubOAuthClicked(status.github_client_id, { shouldLogout: true });
|
||||
} finally {
|
||||
// 由于重定向,这里不会执行到,但为了完整性添加
|
||||
setTimeout(() => setGithubLoading(false), 3000);
|
||||
// �曹��滚��𡢅�餈䠷�銝滢��扯��堆�雿�蛹鈭���湔�扳溶�? setTimeout(() => setGithubLoading(false), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -347,8 +345,7 @@ const LoginForm = () => {
|
||||
try {
|
||||
onDiscordOAuthClicked(status.discord_client_id, { shouldLogout: true });
|
||||
} finally {
|
||||
// 由于重定向,这里不会执行到,但为了完整性添加
|
||||
setTimeout(() => setDiscordLoading(false), 3000);
|
||||
// �曹��滚��𡢅�餈䠷�銝滢��扯��堆�雿�蛹鈭���湔�扳溶�? setTimeout(() => setDiscordLoading(false), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -367,8 +364,7 @@ const LoginForm = () => {
|
||||
{ shouldLogout: true },
|
||||
);
|
||||
} finally {
|
||||
// 由于重定向,这里不会执行到,但为了完整性添加
|
||||
setTimeout(() => setOidcLoading(false), 3000);
|
||||
// �曹��滚��𡢅�餈䠷�銝滢��扯��堆�雿�蛹鈭���湔�扳溶�? setTimeout(() => setOidcLoading(false), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -382,8 +378,7 @@ const LoginForm = () => {
|
||||
try {
|
||||
onLinuxDOOAuthClicked(status.linuxdo_client_id, { shouldLogout: true });
|
||||
} finally {
|
||||
// 由于重定向,这里不会执行到,但为了完整性添加
|
||||
setTimeout(() => setLinuxdoLoading(false), 3000);
|
||||
// �曹��滚��𡢅�餈䠷�銝滢��扯��堆�雿�蛹鈭���湔�扳溶�? setTimeout(() => setLinuxdoLoading(false), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -397,8 +392,7 @@ const LoginForm = () => {
|
||||
try {
|
||||
onCustomOAuthClicked(provider, { shouldLogout: true });
|
||||
} finally {
|
||||
// 由于重定向,这里不会执行到,但为了完整性添加
|
||||
setTimeout(() => {
|
||||
// �曹��滚��𡢅�餈䠷�銝滢��扯��堆�雿�蛹鈭���湔�扳溶�? setTimeout(() => {
|
||||
setCustomOAuthLoading((prev) => ({ ...prev, [provider.slug]: false }));
|
||||
}, 3000);
|
||||
}
|
||||
@@ -455,14 +449,14 @@ const LoginForm = () => {
|
||||
userDispatch({ type: 'login', payload: finish.data });
|
||||
setUserData(finish.data);
|
||||
updateAPI();
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('�餃��𣂼�嚗?);
|
||||
navigate('/console');
|
||||
} else {
|
||||
showError(finish.message || 'Passkey �餃�憭梯揖嚗諹窈�滩�');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') {
|
||||
showInfo('已取消 Passkey 登录');
|
||||
showInfo('撌脣�瘨?Passkey �餃�');
|
||||
} else {
|
||||
showError('Passkey �餃�憭梯揖嚗諹窈�滩�');
|
||||
}
|
||||
@@ -471,8 +465,7 @@ const LoginForm = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 包装的重置密码点击处理
|
||||
const handleResetPasswordClick = () => {
|
||||
// ������蝵桀�����餃��? const handleResetPasswordClick = () => {
|
||||
setResetPasswordLoading(true);
|
||||
navigate('/reset');
|
||||
setResetPasswordLoading(false);
|
||||
@@ -490,7 +483,7 @@ const LoginForm = () => {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
setUserData(data);
|
||||
updateAPI();
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('�餃��𣂼�嚗?);
|
||||
navigate('/console');
|
||||
};
|
||||
|
||||
@@ -514,7 +507,7 @@ const LoginForm = () => {
|
||||
<Card className='border-0 !rounded-2xl overflow-hidden'>
|
||||
<div className='flex justify-center pt-6 pb-2'>
|
||||
<Title heading={3} className='text-gray-800 dark:text-gray-200'>
|
||||
{t('登 录')}
|
||||
{t('�?敶?)}
|
||||
</Title>
|
||||
</div>
|
||||
<div className='px-2 py-8'>
|
||||
@@ -643,7 +636,7 @@ const LoginForm = () => {
|
||||
)}
|
||||
|
||||
<Divider margin='12px' align='center'>
|
||||
{t('或')}
|
||||
{t('�?)}
|
||||
</Divider>
|
||||
|
||||
<Button
|
||||
@@ -665,7 +658,7 @@ const LoginForm = () => {
|
||||
onChange={(e) => setAgreedToTerms(e.target.checked)}
|
||||
>
|
||||
<Text size='small' className='text-gray-600'>
|
||||
{t('我已阅读并同意')}
|
||||
{t('�穃歇��粉撟嗅��?)}
|
||||
{hasUserAgreement && (
|
||||
<>
|
||||
<a
|
||||
@@ -678,7 +671,7 @@ const LoginForm = () => {
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
{hasUserAgreement && hasPrivacyPolicy && t('和')}
|
||||
{hasUserAgreement && hasPrivacyPolicy && t('�?)}
|
||||
{hasPrivacyPolicy && (
|
||||
<>
|
||||
<a
|
||||
@@ -699,7 +692,7 @@ const LoginForm = () => {
|
||||
{!status.self_use_mode_enabled && (
|
||||
<div className='mt-6 text-center text-sm'>
|
||||
<Text>
|
||||
{t('没有账户?')}{' '}
|
||||
{t('瘝⊥�韐行�嚗?)}{' '}
|
||||
<Link
|
||||
to='/register'
|
||||
className='text-blue-600 hover:text-blue-800 font-medium'
|
||||
@@ -728,7 +721,7 @@ const LoginForm = () => {
|
||||
<Card className='border-0 !rounded-2xl overflow-hidden'>
|
||||
<div className='flex justify-center pt-6 pb-2'>
|
||||
<Title heading={3} className='text-gray-800 dark:text-gray-200'>
|
||||
{t('登 录')}
|
||||
{t('�?敶?)}
|
||||
</Title>
|
||||
</div>
|
||||
<div className='px-2 py-8'>
|
||||
@@ -757,7 +750,7 @@ const LoginForm = () => {
|
||||
<Form.Input
|
||||
field='password'
|
||||
label={t('撖��')}
|
||||
placeholder={t('请输入您的密码')}
|
||||
placeholder={t('霂瑁��交�����?)}
|
||||
name='password'
|
||||
mode='password'
|
||||
onChange={(value) => handleChange('password', value)}
|
||||
@@ -771,7 +764,7 @@ const LoginForm = () => {
|
||||
onChange={(e) => setAgreedToTerms(e.target.checked)}
|
||||
>
|
||||
<Text size='small' className='text-gray-600'>
|
||||
{t('我已阅读并同意')}
|
||||
{t('�穃歇��粉撟嗅��?)}
|
||||
{hasUserAgreement && (
|
||||
<>
|
||||
<a
|
||||
@@ -784,7 +777,7 @@ const LoginForm = () => {
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
{hasUserAgreement && hasPrivacyPolicy && t('和')}
|
||||
{hasUserAgreement && hasPrivacyPolicy && t('�?)}
|
||||
{hasPrivacyPolicy && (
|
||||
<>
|
||||
<a
|
||||
@@ -824,7 +817,7 @@ const LoginForm = () => {
|
||||
onClick={handleResetPasswordClick}
|
||||
loading={resetPasswordLoading}
|
||||
>
|
||||
{t('忘记密码?')}
|
||||
{t('敹䁅扇撖��嚗?)}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
@@ -832,7 +825,7 @@ const LoginForm = () => {
|
||||
{hasOAuthLoginOptions && (
|
||||
<>
|
||||
<Divider margin='12px' align='center'>
|
||||
{t('或')}
|
||||
{t('�?)}
|
||||
</Divider>
|
||||
|
||||
<div className='mt-4 text-center'>
|
||||
@@ -852,7 +845,7 @@ const LoginForm = () => {
|
||||
{!status.self_use_mode_enabled && (
|
||||
<div className='mt-6 text-center text-sm'>
|
||||
<Text>
|
||||
{t('没有账户?')}{' '}
|
||||
{t('瘝⊥�韐行�嚗?)}{' '}
|
||||
<Link
|
||||
to='/register'
|
||||
className='text-blue-600 hover:text-blue-800 font-medium'
|
||||
@@ -885,7 +878,7 @@ const LoginForm = () => {
|
||||
}}
|
||||
>
|
||||
<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 className='text-center mb-4'>
|
||||
@@ -897,8 +890,8 @@ const LoginForm = () => {
|
||||
<Form>
|
||||
<Form.Input
|
||||
field='wechat_verification_code'
|
||||
placeholder={t('验证码')}
|
||||
label={t('验证码')}
|
||||
placeholder={t('撉諹��?)}
|
||||
label={t('撉諹��?)}
|
||||
value={inputs.wechat_verification_code}
|
||||
onChange={(value) =>
|
||||
handleChange('wechat_verification_code', value)
|
||||
@@ -948,7 +941,7 @@ const LoginForm = () => {
|
||||
|
||||
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='blur-ball blur-ball-indigo'
|
||||
style={{ top: '-80px', right: '-80px', transform: 'none' }}
|
||||
|
||||
+9
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -36,11 +36,9 @@ const OAuth2Callback = (props) => {
|
||||
const [, userDispatch] = useContext(UserContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 防止 React 18 Strict Mode 下重复执行
|
||||
const hasExecuted = useRef(false);
|
||||
// é˜²æ¢ React 18 Strict Mode 下é‡�å¤�执è¡? const hasExecuted = useRef(false);
|
||||
|
||||
// 最大重试次数
|
||||
const MAX_RETRIES = 3;
|
||||
// 最大�试次� const MAX_RETRIES = 3;
|
||||
|
||||
const sendCode = async (code, state, retry = 0) => {
|
||||
try {
|
||||
@@ -57,21 +55,20 @@ const OAuth2Callback = (props) => {
|
||||
}
|
||||
|
||||
if (data?.action === 'bind') {
|
||||
showSuccess(t('绑定成功!'));
|
||||
showSuccess(t('绑定�功�));
|
||||
navigate('/console/personal');
|
||||
} else {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
setUserData(data);
|
||||
updateAPI();
|
||||
showSuccess(t('登录成功!'));
|
||||
showSuccess(t('登录�功�));
|
||||
navigate('/console/token');
|
||||
}
|
||||
} catch (error) {
|
||||
// 网络错误ç‰å�¯é‡�试
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -82,8 +79,7 @@ const OAuth2Callback = (props) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 防止 React 18 Strict Mode 下重复执行
|
||||
if (hasExecuted.current) {
|
||||
// é˜²æ¢ React 18 Strict Mode 下é‡�å¤�执è¡? if (hasExecuted.current) {
|
||||
return;
|
||||
}
|
||||
hasExecuted.current = true;
|
||||
@@ -93,7 +89,7 @@ const OAuth2Callback = (props) => {
|
||||
|
||||
// �数缺失直接返回
|
||||
if (!code) {
|
||||
showError(t('未获取到授权码'));
|
||||
showError(t('未获å�–到授æ�ƒç ?));
|
||||
navigate('/console/personal');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -82,7 +82,7 @@ const PasswordResetConfirm = () => {
|
||||
|
||||
async function handleSubmit(e) {
|
||||
if (!email || !token) {
|
||||
showError(t('无效的重置链接,请重新发起密码重置请求'));
|
||||
showError(t('æ— æ•ˆçš„é‡�置链接,请é‡�æ–°å�‘起密ç �é‡�置请æ±?));
|
||||
return;
|
||||
}
|
||||
setDisableButton(true);
|
||||
@@ -105,7 +105,7 @@ const PasswordResetConfirm = () => {
|
||||
|
||||
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='blur-ball blur-ball-indigo'
|
||||
style={{ top: '-80px', right: '-80px', transform: 'none' }}
|
||||
@@ -134,7 +134,7 @@ const PasswordResetConfirm = () => {
|
||||
{!isValidResetLink && (
|
||||
<Banner
|
||||
type='danger'
|
||||
description={t('无效的重置链接,请重新发起密码重置请求')}
|
||||
description={t('æ— æ•ˆçš„é‡�置链接,请é‡�æ–°å�‘起密ç �é‡�置请æ±?)}
|
||||
className='mb-4 !rounded-lg'
|
||||
closeIcon={null}
|
||||
/>
|
||||
@@ -159,7 +159,7 @@ const PasswordResetConfirm = () => {
|
||||
{newPassword && (
|
||||
<Form.Input
|
||||
field='newPassword'
|
||||
label={t('新密码')}
|
||||
label={t('新密ç ?)}
|
||||
name='newPassword'
|
||||
disabled={true}
|
||||
prefix={<IconLock />}
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -105,7 +105,7 @@ const PasswordResetForm = () => {
|
||||
|
||||
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='blur-ball blur-ball-indigo'
|
||||
style={{ top: '-80px', right: '-80px', transform: 'none' }}
|
||||
@@ -161,7 +161,7 @@ const PasswordResetForm = () => {
|
||||
|
||||
<div className='mt-6 text-center text-sm'>
|
||||
<Text>
|
||||
{t('想起来了?')}{' '}
|
||||
{t('想起�了�)}{' '}
|
||||
<Link
|
||||
to='/login'
|
||||
className='text-blue-600 hover:text-blue-800 font-medium'
|
||||
|
||||
+28
-30
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -71,7 +71,7 @@ const RegisterForm = () => {
|
||||
const githubButtonTextKeyByState = {
|
||||
idle: '雿輻鍂 GitHub 蝏抒賒',
|
||||
redirecting: '甇�銁頝唾蓮 GitHub...',
|
||||
timeout: '请求超时,请刷新页面后重新发起 GitHub 登录',
|
||||
timeout: '霂瑟�頞�𧒄嚗諹窈�瑟鰵憿菟𢒰�𡡞��啣�韏?GitHub �餃�',
|
||||
};
|
||||
const [inputs, setInputs] = useState({
|
||||
username: '',
|
||||
@@ -150,8 +150,7 @@ const RegisterForm = () => {
|
||||
setTurnstileSiteKey(status.turnstile_site_key);
|
||||
}
|
||||
|
||||
// 从 status 获取用户协议和隐私政策的启用状态
|
||||
setHasUserAgreement(status?.user_agreement_enabled || false);
|
||||
// 隞?status �瑕��冽��讛悅�屸�蝘�錇蝑𣇉��舐鍂�嗆�? setHasUserAgreement(status?.user_agreement_enabled || false);
|
||||
setHasPrivacyPolicy(status?.privacy_policy_enabled || false);
|
||||
}, [status]);
|
||||
|
||||
@@ -199,7 +198,7 @@ const RegisterForm = () => {
|
||||
setUserData(data);
|
||||
updateAPI();
|
||||
navigate('/');
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('�餃��𣂼�嚗?);
|
||||
setShowWeChatLoginModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -221,7 +220,7 @@ const RegisterForm = () => {
|
||||
return;
|
||||
}
|
||||
if (password !== password2) {
|
||||
showInfo('两次输入的密码不一致');
|
||||
showInfo('銝斗活颲枏�������銝��?);
|
||||
return;
|
||||
}
|
||||
if (username && password) {
|
||||
@@ -242,7 +241,7 @@ const RegisterForm = () => {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
navigate('/login');
|
||||
showSuccess('注册成功!');
|
||||
showSuccess('瘜典��𣂼�嚗?);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@@ -268,8 +267,7 @@ const RegisterForm = () => {
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('撉諹����������霂瑟��乩����蝞梧�');
|
||||
setDisableButton(true); // 发送成功后禁用按钮,开始倒计时
|
||||
} else {
|
||||
setDisableButton(true); // �煾����笔�蝳�鍂�厰僼嚗��憪见�坿恣�? } else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -379,7 +377,7 @@ const RegisterForm = () => {
|
||||
if (success) {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
showSuccess('登录成功!');
|
||||
showSuccess('�餃��𣂼�嚗?);
|
||||
setUserData(data);
|
||||
updateAPI();
|
||||
navigate('/');
|
||||
@@ -405,7 +403,7 @@ const RegisterForm = () => {
|
||||
<Card className='border-0 !rounded-2xl overflow-hidden'>
|
||||
<div className='flex justify-center pt-6 pb-2'>
|
||||
<Title heading={3} className='text-gray-800 dark:text-gray-200'>
|
||||
{t('注 册')}
|
||||
{t('瘜?�?)}
|
||||
</Title>
|
||||
</div>
|
||||
<div className='px-2 py-8'>
|
||||
@@ -521,7 +519,7 @@ const RegisterForm = () => {
|
||||
)}
|
||||
|
||||
<Divider margin='12px' align='center'>
|
||||
{t('或')}
|
||||
{t('�?)}
|
||||
</Divider>
|
||||
|
||||
<Button
|
||||
@@ -532,13 +530,13 @@ const RegisterForm = () => {
|
||||
onClick={handleEmailRegisterClick}
|
||||
loading={emailRegisterLoading}
|
||||
>
|
||||
<span className='ml-3'>{t('使用 用户名 注册')}</span>
|
||||
<span className='ml-3'>{t('雿輻鍂 �冽��?瘜典�')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='mt-6 text-center text-sm'>
|
||||
<Text>
|
||||
{t('已有账户?')}{' '}
|
||||
{t('撌脫�韐行�嚗?)}{' '}
|
||||
<Link
|
||||
to='/login'
|
||||
className='text-blue-600 hover:text-blue-800 font-medium'
|
||||
@@ -568,14 +566,14 @@ const RegisterForm = () => {
|
||||
<Card className='border-0 !rounded-2xl overflow-hidden'>
|
||||
<div className='flex justify-center pt-6 pb-2'>
|
||||
<Title heading={3} className='text-gray-800 dark:text-gray-200'>
|
||||
{t('注 册')}
|
||||
{t('瘜?�?)}
|
||||
</Title>
|
||||
</div>
|
||||
<div className='px-2 py-8'>
|
||||
<Form className='space-y-3'>
|
||||
<Form.Input
|
||||
field='username'
|
||||
label={t('用户名')}
|
||||
label={t('�冽��?)}
|
||||
placeholder={t('霂瑁��亦鍂�瑕�')}
|
||||
name='username'
|
||||
onChange={(value) => handleChange('username', value)}
|
||||
@@ -585,7 +583,7 @@ const RegisterForm = () => {
|
||||
<Form.Input
|
||||
field='password'
|
||||
label={t('撖��')}
|
||||
placeholder={t('输入密码,最短 8 位,最长 20 位')}
|
||||
placeholder={t('颲枏�撖��嚗峕��?8 雿㵪����?20 雿?)}
|
||||
name='password'
|
||||
mode='password'
|
||||
onChange={(value) => handleChange('password', value)}
|
||||
@@ -619,15 +617,15 @@ const RegisterForm = () => {
|
||||
disabled={disableButton || verificationCodeLoading}
|
||||
>
|
||||
{disableButton
|
||||
? `${t('重新发送')} (${countdown})`
|
||||
: t('获取验证码')}
|
||||
? `${t('�齿鰵�煾�?)} (${countdown})`
|
||||
: t('�瑕�撉諹��?)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Form.Input
|
||||
field='verification_code'
|
||||
label={t('验证码')}
|
||||
placeholder={t('输入验证码')}
|
||||
label={t('撉諹��?)}
|
||||
placeholder={t('颲枏�撉諹��?)}
|
||||
name='verification_code'
|
||||
onChange={(value) =>
|
||||
handleChange('verification_code', value)
|
||||
@@ -644,7 +642,7 @@ const RegisterForm = () => {
|
||||
onChange={(e) => setAgreedToTerms(e.target.checked)}
|
||||
>
|
||||
<Text size='small' className='text-gray-600'>
|
||||
{t('我已阅读并同意')}
|
||||
{t('�穃歇��粉撟嗅��?)}
|
||||
{hasUserAgreement && (
|
||||
<>
|
||||
<a
|
||||
@@ -657,7 +655,7 @@ const RegisterForm = () => {
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
{hasUserAgreement && hasPrivacyPolicy && t('和')}
|
||||
{hasUserAgreement && hasPrivacyPolicy && t('�?)}
|
||||
{hasPrivacyPolicy && (
|
||||
<>
|
||||
<a
|
||||
@@ -695,7 +693,7 @@ const RegisterForm = () => {
|
||||
{hasOAuthRegisterOptions && (
|
||||
<>
|
||||
<Divider margin='12px' align='center'>
|
||||
{t('或')}
|
||||
{t('�?)}
|
||||
</Divider>
|
||||
|
||||
<div className='mt-4 text-center'>
|
||||
@@ -714,7 +712,7 @@ const RegisterForm = () => {
|
||||
|
||||
<div className='mt-6 text-center text-sm'>
|
||||
<Text>
|
||||
{t('已有账户?')}{' '}
|
||||
{t('撌脫�韐行�嚗?)}{' '}
|
||||
<Link
|
||||
to='/login'
|
||||
className='text-blue-600 hover:text-blue-800 font-medium'
|
||||
@@ -745,7 +743,7 @@ const RegisterForm = () => {
|
||||
}}
|
||||
>
|
||||
<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 className='text-center mb-4'>
|
||||
@@ -757,8 +755,8 @@ const RegisterForm = () => {
|
||||
<Form>
|
||||
<Form.Input
|
||||
field='wechat_verification_code'
|
||||
placeholder={t('验证码')}
|
||||
label={t('验证码')}
|
||||
placeholder={t('撉諹��?)}
|
||||
label={t('撉諹��?)}
|
||||
value={inputs.wechat_verification_code}
|
||||
onChange={(value) =>
|
||||
handleChange('wechat_verification_code', value)
|
||||
@@ -771,7 +769,7 @@ const RegisterForm = () => {
|
||||
|
||||
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='blur-ball blur-ball-indigo'
|
||||
style={{ top: '-80px', right: '-80px', transform: 'none' }}
|
||||
|
||||
+21
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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 {
|
||||
@@ -41,10 +41,10 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
}
|
||||
// Validate code format
|
||||
if (useBackupCode && verificationCode.length !== 8) {
|
||||
showError('备用码必须是8位');
|
||||
showError('憭�鍂���憿餅糓8雿?);
|
||||
return;
|
||||
} else if (!useBackupCode && !/^\d{6}$/.test(verificationCode)) {
|
||||
showError('验证码必须是6位数字');
|
||||
showError('撉諹����憿餅糓6雿齿㺭摮?);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,8 +56,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
|
||||
if (res.data.success) {
|
||||
showSuccess('�餃��𣂼�');
|
||||
// 保存用户信息到本地存储
|
||||
localStorage.setItem('user', JSON.stringify(res.data.data));
|
||||
// 靽嘥��冽�靽⊥��唳𧋦�啣��? localStorage.setItem('user', JSON.stringify(res.data.data));
|
||||
if (onSuccess) {
|
||||
onSuccess(res.data.data);
|
||||
}
|
||||
@@ -87,8 +86,8 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Input
|
||||
field='code'
|
||||
label={useBackupCode ? '备用码' : '验证码'}
|
||||
placeholder={useBackupCode ? '请输入8位备用码' : '请输入6位验证码'}
|
||||
label={useBackupCode ? '憭�鍂�? : '撉諹��?}
|
||||
placeholder={useBackupCode ? '霂瑁��?雿滚��函�' : '霂瑁��?雿漤�霂��'}
|
||||
value={verificationCode}
|
||||
onChange={setVerificationCode}
|
||||
onKeyPress={handleKeyPress}
|
||||
@@ -105,8 +104,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
size='large'
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
验证并登录
|
||||
</Button>
|
||||
撉諹�撟嗥蒈敶? </Button>
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
@@ -121,7 +119,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
}}
|
||||
style={{ marginRight: 16, color: '#1890ff', padding: 0 }}
|
||||
>
|
||||
{useBackupCode ? '使用认证器验证码' : '使用备用码'}
|
||||
{useBackupCode ? '雿輻鍂霈方��券�霂��' : '雿輻鍂憭�鍂�?}
|
||||
</Button>
|
||||
|
||||
{onBack && (
|
||||
@@ -138,13 +136,11 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
|
||||
<div className='bg-gray-50 dark:bg-gray-800 rounded-lg p-3'>
|
||||
<Text size='small' type='secondary'>
|
||||
<strong>提示:</strong>
|
||||
<strong>�鞟內嚗?/strong>
|
||||
<br />
|
||||
• 验证码每30秒更新一次
|
||||
<br />
|
||||
• 如果无法获取验证码,请使用备用码
|
||||
<br />• 每个备用码只能使用一次
|
||||
</Text>
|
||||
�?撉諹����30蝘埝凒�唬�甈? <br />
|
||||
�?憒���䭾��瑕�撉諹����霂瑚蝙�典��函�
|
||||
<br />�?瘥譍葵憭�鍂��蘨�賭蝙�其�甈? </Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -170,8 +166,8 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Input
|
||||
field='code'
|
||||
label={useBackupCode ? '备用码' : '验证码'}
|
||||
placeholder={useBackupCode ? '请输入8位备用码' : '请输入6位验证码'}
|
||||
label={useBackupCode ? '憭�鍂�? : '撉諹��?}
|
||||
placeholder={useBackupCode ? '霂瑁��?雿滚��函�' : '霂瑁��?雿漤�霂��'}
|
||||
value={verificationCode}
|
||||
onChange={setVerificationCode}
|
||||
onKeyPress={handleKeyPress}
|
||||
@@ -188,8 +184,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
size='large'
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
验证并登录
|
||||
</Button>
|
||||
撉諹�撟嗥蒈敶? </Button>
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
@@ -204,7 +199,7 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
}}
|
||||
style={{ marginRight: 16, color: '#1890ff', padding: 0 }}
|
||||
>
|
||||
{useBackupCode ? '使用认证器验证码' : '使用备用码'}
|
||||
{useBackupCode ? '雿輻鍂霈方��券�霂��' : '雿輻鍂憭�鍂�?}
|
||||
</Button>
|
||||
|
||||
{onBack && (
|
||||
@@ -228,13 +223,11 @@ const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => {
|
||||
}}
|
||||
>
|
||||
<Text size='small' type='secondary'>
|
||||
<strong>提示:</strong>
|
||||
<strong>�鞟內嚗?/strong>
|
||||
<br />
|
||||
• 验证码每30秒更新一次
|
||||
<br />
|
||||
• 如果无法获取验证码,请使用备用码
|
||||
<br />• 每个备用码只能使用一次
|
||||
</Text>
|
||||
�?撉諹����30蝘埝凒�唬�甈? <br />
|
||||
�?憒���䭾��瑕�撉諹����霂瑚蝙�典��函�
|
||||
<br />�?瘥譍葵憭�鍂��蘨�賭蝙�其�甈? </Text>
|
||||
</div>
|
||||
</Card>
|
||||
</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
|
||||
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
|
||||
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';
|
||||
@@ -65,9 +65,7 @@ const sanitizeHtml = (html) => {
|
||||
* 通用文档渲染组件
|
||||
* @param {string} apiEndpoint - API 接�地�
|
||||
* @param {string} title - æ–‡æ¡£æ ‡é¢˜
|
||||
* @param {string} cacheKey - 本地存储缓存键
|
||||
* @param {string} emptyMessage - 空内容时的提示消息
|
||||
*/
|
||||
* @param {string} cacheKey - 本地å˜å‚¨ç¼“å˜é”? * @param {string} emptyMessage - 空内容时的æ��示消æ�? */
|
||||
const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
const { t } = useTranslation();
|
||||
const [content, setContent] = useState('');
|
||||
@@ -138,8 +136,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
};
|
||||
}, [cacheKey, htmlPayload]);
|
||||
|
||||
// 显示加载状态
|
||||
if (loading) {
|
||||
// æ˜¾ç¤ºåŠ è½½çŠ¶æ€? if (loading) {
|
||||
return (
|
||||
<div className='classic-page-fill flex justify-center items-center'>
|
||||
<Spin size='large' />
|
||||
@@ -147,8 +144,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// 如果没有内容,显示空状态
|
||||
if (!content || content.trim() === '') {
|
||||
// 如果没有内容,显示空状� if (!content || content.trim() === '') {
|
||||
return (
|
||||
<div className='classic-page-fill flex justify-center items-center bg-gray-50'>
|
||||
<Empty
|
||||
@@ -165,8 +161,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// 如果是 URL,显示链接卡片
|
||||
if (isUrl(content)) {
|
||||
// 如果�URL,显示链接�� if (isUrl(content)) {
|
||||
return (
|
||||
<div className='classic-page-fill flex justify-center items-center bg-gray-50 p-4'>
|
||||
<Card className='max-w-md w-full'>
|
||||
@@ -175,7 +170,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
{title}
|
||||
</Title>
|
||||
<p className='text-gray-600 mb-4'>
|
||||
{t('管理员设置了外部链接,点击下方按钮访问')}
|
||||
{t('管�员设置了外部链接,点击下方按钮访�)}
|
||||
</p>
|
||||
<a
|
||||
href={content.trim()}
|
||||
@@ -193,8 +188,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// 如果是 HTML 内容,直接渲染
|
||||
if (isHtmlContent(content)) {
|
||||
// 如果�HTML 内容,直接渲� if (isHtmlContent(content)) {
|
||||
return (
|
||||
<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'>
|
||||
@@ -212,8 +206,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// 其他内容统一使用 Markdown 渲染器
|
||||
return (
|
||||
// 其他内容统一使用 Markdown 渲染� return (
|
||||
<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='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
|
||||
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
|
||||
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';
|
||||
@@ -46,8 +46,7 @@ const ChannelKeyViewExample = ({ channelId }) => {
|
||||
switchVerificationMethod,
|
||||
} = useSecureVerification({
|
||||
onSuccess: (result) => {
|
||||
// 验证成功后处理结果
|
||||
if (result.success && result.data?.key) {
|
||||
// 撉諹��𣂼��𤾸�����? if (result.success && result.data?.key) {
|
||||
setKeyData(result.data.key);
|
||||
setShowKeyModal(true);
|
||||
}
|
||||
@@ -55,15 +54,13 @@ const ChannelKeyViewExample = ({ channelId }) => {
|
||||
successMessage: t('撖�𤨎�瑕��𣂼�'),
|
||||
});
|
||||
|
||||
// 开始查看密钥流程
|
||||
const handleViewKey = async () => {
|
||||
// 撘�憪𧢲䰻�见��交�蝔? const handleViewKey = async () => {
|
||||
const apiCall = createApiCalls.viewChannelKey(channelId);
|
||||
|
||||
await startVerification(apiCall, {
|
||||
title: t('�亦�皜𣳇�撖�𤨎'),
|
||||
description: t('为了保护账户安全,请验证您的身份。'),
|
||||
preferredMethod: 'passkey', // 可以指定首选验证方式
|
||||
});
|
||||
description: t('銝箔�靽脲擪韐行�摰匧�嚗諹窈撉諹��函�頨思遢�?),
|
||||
preferredMethod: 'passkey', // �臭誑���擐㚚�厰�霂�䲮撘? });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -228,7 +228,7 @@ export function PreCode(props) {
|
||||
const code = codeElement?.textContent ?? '';
|
||||
copy(code).then((success) => {
|
||||
if (success) {
|
||||
Toast.success(t('代码已复制到剪贴板'));
|
||||
Toast.success(t('代ç �å·²å¤�制到剪贴æ�?));
|
||||
} else {
|
||||
Toast.error(t('�制失败,请手动�制'));
|
||||
}
|
||||
@@ -390,8 +390,7 @@ function _MarkdownContent(props) {
|
||||
return tryWrapHtmlCode(escapeBrackets(content));
|
||||
}, [content]);
|
||||
|
||||
// 判断是否为用户消息
|
||||
const isUserMessage = className && className.includes('user-message');
|
||||
// åˆ¤æ–æ˜¯å�¦ä¸ºç”¨æˆ·æ¶ˆæ�? const isUserMessage = className && className.includes('user-message');
|
||||
|
||||
const rehypePluginsBase = useMemo(() => {
|
||||
const base = [
|
||||
|
||||
+8
-8
@@ -60,7 +60,7 @@
|
||||
|
||||
.user-message a {
|
||||
color: #87ceeb !important;
|
||||
/* 浅蓝色链接 */
|
||||
/* 浅�色链�*/
|
||||
}
|
||||
|
||||
.user-message a:hover {
|
||||
@@ -68,7 +68,7 @@
|
||||
/* hover时更浅的�色 */
|
||||
}
|
||||
|
||||
/* 表格在用户消息中的样式 */
|
||||
/* è¡¨æ ¼åœ¨ç”¨æˆ·æ¶ˆæ�¯ä¸çš„æ ·å¼?*/
|
||||
.user-message table {
|
||||
border-color: rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
@@ -197,7 +197,7 @@
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 代码块样式增强 */
|
||||
/* 代ç �å�—æ ·å¼�增å¼?*/
|
||||
pre {
|
||||
position: relative;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
@@ -229,7 +229,7 @@ pre:hover .copy-code-button {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
/* 确保按钮可点击 */
|
||||
/* 确�按钮�点�*/
|
||||
.copy-code-button .semi-button {
|
||||
pointer-events: auto !important;
|
||||
cursor: pointer !important;
|
||||
@@ -242,7 +242,7 @@ pre:hover .copy-code-button {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 表格响应式 */
|
||||
/* è¡¨æ ¼å“�应å¼?*/
|
||||
@media (max-width: 768px) {
|
||||
.markdown-body table {
|
||||
font-size: 12px;
|
||||
@@ -269,7 +269,7 @@ pre:hover .copy-code-button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 引用块样式增强 */
|
||||
/* 引用å�—æ ·å¼�增å¼?*/
|
||||
.markdown-body blockquote {
|
||||
position: relative;
|
||||
}
|
||||
@@ -294,7 +294,7 @@ pre:hover .copy-code-button {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 分隔线样式 */
|
||||
/* åˆ†éš”çº¿æ ·å¼?*/
|
||||
.markdown-body hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
@@ -393,7 +393,7 @@ pre:hover .copy-code-button {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 警告块样式 */
|
||||
/* è¦å‘Šå�—æ ·å¼?*/
|
||||
.markdown-body .warning {
|
||||
background-color: var(--semi-color-warning-light-default);
|
||||
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
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -35,12 +35,9 @@ import {
|
||||
* �� useSecureVerification Hook 使用
|
||||
* @param {Object} props
|
||||
* @param {boolean} props.visible - 是�显示模�框
|
||||
* @param {Object} props.verificationMethods - 可用的验证方式
|
||||
* @param {Object} props.verificationState - 当前验证状态
|
||||
* @param {Function} props.onVerify - 验证回调
|
||||
* @param {Object} props.verificationMethods - �用的验�方� * @param {Object} props.verificationState - 当�验�状� * @param {Function} props.onVerify - 验�回调
|
||||
* @param {Function} props.onCancel - �消回调
|
||||
* @param {Function} props.onCodeChange - 验证码变化回调
|
||||
* @param {Function} props.onMethodSwitch - 验证方式切换回调
|
||||
* @param {Function} props.onCodeChange - 验è¯�ç �å�˜åŒ–回è°? * @param {Function} props.onMethodSwitch - 验è¯�æ–¹å¼�切æ�¢å›žè°ƒ
|
||||
* @param {string} props.title - 模æ€�æ¡†æ ‡é¢˜
|
||||
* @param {string} props.description - 验��述文本
|
||||
*/
|
||||
@@ -106,14 +103,14 @@ const SecureVerificationModal = ({
|
||||
</svg>
|
||||
</div>
|
||||
<Typography.Title heading={4} className='mb-2'>
|
||||
{t('需要安全验证')}
|
||||
{t('需�安全验�)}
|
||||
</Typography.Title>
|
||||
<Typography.Text type='tertiary'>
|
||||
{t('您需要先启用两步验证或 Passkey 才能查看敏感信息。')}
|
||||
{t('您需è¦�å…ˆå�¯ç”¨ä¸¤æ¥éªŒè¯�æˆ?Passkey æ‰�能查看æ•�感信æ�¯ã€?)}
|
||||
</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text type='tertiary'>
|
||||
{t('请前往个人设置 → 安全设置进行配置。')}
|
||||
{t('请�往个人设置 �安全设置进行�置�)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -164,7 +161,7 @@ const SecureVerificationModal = ({
|
||||
<div style={{ paddingTop: '20px' }}>
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
<Input
|
||||
placeholder={t('请输入6位验证码或8位备用码')}
|
||||
placeholder={t('请输å…?ä½�验è¯�ç �æˆ?ä½�备用ç �')}
|
||||
value={code}
|
||||
onChange={onCodeChange}
|
||||
size='large'
|
||||
@@ -204,7 +201,7 @@ const SecureVerificationModal = ({
|
||||
lineHeight: '1.5',
|
||||
}}
|
||||
>
|
||||
{t('从认证器应用中获取验证码,或使用备用码')}
|
||||
{t('从认è¯�器应用ä¸èŽ·å�–验è¯�ç �,或使用备用ç ?)}
|
||||
</Typography.Text>
|
||||
|
||||
<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
|
||||
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
|
||||
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';
|
||||
@@ -25,15 +25,12 @@ import { Modal, Button, Input, Typography } from '@douyinfe/semi-ui';
|
||||
* å�¯å¤�用的两æ¥éªŒè¯�模æ€�框组件
|
||||
* @param {Object} props
|
||||
* @param {boolean} props.visible - 是�显示模�框
|
||||
* @param {string} props.code - 验证码值
|
||||
* @param {boolean} props.loading - 是否正在验证
|
||||
* @param {Function} props.onCodeChange - 验证码变化回调
|
||||
* @param {Function} props.onVerify - 验证回调
|
||||
* @param {string} props.code - 验è¯�ç �å€? * @param {boolean} props.loading - 是å�¦æ£åœ¨éªŒè¯�
|
||||
* @param {Function} props.onCodeChange - 验è¯�ç �å�˜åŒ–回è°? * @param {Function} props.onVerify - 验è¯�回调
|
||||
* @param {Function} props.onCancel - �消回调
|
||||
* @param {string} props.title - 模æ€�æ¡†æ ‡é¢˜
|
||||
* @param {string} props.description - 验��述文本
|
||||
* @param {string} props.placeholder - 输入框占位文本
|
||||
*/
|
||||
* @param {string} props.placeholder - 输入框å� ä½�æ–‡æœ? */
|
||||
const TwoFactorAuthModal = ({
|
||||
visible,
|
||||
code,
|
||||
@@ -114,19 +111,19 @@ const TwoFactorAuthModal = ({
|
||||
{t('安全验�')}
|
||||
</Typography.Text>
|
||||
<Typography.Text className='block text-blue-700 dark:text-blue-300 text-sm mt-1'>
|
||||
{description || t('为了保护账户安全,请验证您的两步验证码。')}
|
||||
{description || t('为了ä¿�护账户安全,请验è¯�您的两æ¥éªŒè¯�ç �ã€?)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 验证码输入 */}
|
||||
{/* 验è¯�ç �输å…?*/}
|
||||
<div>
|
||||
<Typography.Text strong className='block mb-2'>
|
||||
{t('验�身份')}
|
||||
</Typography.Text>
|
||||
<Input
|
||||
placeholder={placeholder || t('请输入认证器验证码或备用码')}
|
||||
placeholder={placeholder || t('请输入认è¯�器验è¯�ç �或备用ç ?)}
|
||||
value={code}
|
||||
onChange={onCodeChange}
|
||||
size='large'
|
||||
@@ -136,7 +133,7 @@ const TwoFactorAuthModal = ({
|
||||
/>
|
||||
<Typography.Text type='tertiary' size='small' className='mt-2 block'>
|
||||
{t(
|
||||
'支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。',
|
||||
'支æŒ�6ä½�TOTP验è¯�ç �或8ä½�备用ç �,å�¯åˆ°`个人设置-安全设置-两æ¥éªŒè¯�设置`é…�置或查看ã€?,
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
+15
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -34,31 +34,25 @@ const { Text } = Typography;
|
||||
* 3. 蝐餃���揢/��倌�箏� (tabsArea)
|
||||
* 4. �滢��厰僼�箏� (actionsArea)
|
||||
* 5. �𦦵揣銵典��箏� (searchArea)
|
||||
* 6. 分页区域 (paginationArea) - 固定在卡片底部
|
||||
*
|
||||
* 支持三种布局类型:
|
||||
* - type1: 操作型 (如TokensTable) - 描述信息 + 操作按钮 + 搜索表单
|
||||
* - type2: 查询型 (如LogsTable) - 统计信息 + 搜索表单
|
||||
* - type3: 复杂型 (如ChannelsTable) - 描述信息 + 类型切换 + 操作按钮 + 搜索表单
|
||||
* 6. ��△�箏� (paginationArea) - �箏��典㨃����? *
|
||||
* �舀�銝厩�撣��蝐餃�嚗? * - type1: �滢��?(憒�okensTable) - �讛膩靽⊥� + �滢��厰僼 + �𦦵揣銵典�
|
||||
* - type2: �亥砭�?(憒�ogsTable) - 蝏蠘恣靽⊥� + �𦦵揣銵典�
|
||||
* - type3: 憭齿��?(憒�hannelsTable) - �讛膩靽⊥� + 蝐餃���揢 + �滢��厰僼 + �𦦵揣銵典�
|
||||
*/
|
||||
const CardPro = ({
|
||||
type = 'type1',
|
||||
className = '',
|
||||
children,
|
||||
// 各个区域的内容
|
||||
statsArea,
|
||||
// ��葵�箏����摰? statsArea,
|
||||
descriptionArea,
|
||||
tabsArea,
|
||||
actionsArea,
|
||||
searchArea,
|
||||
paginationArea, // �啣���△�箏�
|
||||
// 卡片属性
|
||||
shadows = '',
|
||||
// �∠�撅墧�? shadows = '',
|
||||
bordered = true,
|
||||
// 自定义样式
|
||||
style,
|
||||
// 国际化函数
|
||||
t = (key) => key,
|
||||
// �芸�銋㗇甅撘? style,
|
||||
// �賡��硋遆�? t = (key) => key,
|
||||
...props
|
||||
}) => {
|
||||
const isMobile = useIsMobile();
|
||||
@@ -94,7 +88,7 @@ const CardPro = ({
|
||||
{/* 蝐餃���揢/��倌�箏� - 銝餉��其�type3 */}
|
||||
{type === 'type3' && tabsArea && <>{tabsArea}</>}
|
||||
|
||||
{/* 移动端操作切换按钮 */}
|
||||
{/* 蝘餃𢆡蝡舀�雿𨅯��X��?*/}
|
||||
{isMobile && hasMobileHideableContent && (
|
||||
<>
|
||||
<div className='w-full mb-2'>
|
||||
@@ -106,7 +100,7 @@ const CardPro = ({
|
||||
theme='outline'
|
||||
block
|
||||
>
|
||||
{showMobileActions ? t('隐藏操作项') : t('显示操作项')}
|
||||
{showMobileActions ? t('�鞱��滢�憿?) : t('�曄內�滢�憿?)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@@ -130,10 +124,10 @@ const CardPro = ({
|
||||
<div className='w-full'>{actionsArea}</div>
|
||||
))}
|
||||
|
||||
{/* 当同时存在操作区和搜索区时,插入分隔线 */}
|
||||
{/* 敶枏��嗅��冽�雿𨅯躹�峕�蝝W躹�塚��鍦����蝥?*/}
|
||||
{actionsArea && searchArea && <Divider />}
|
||||
|
||||
{/* 搜索表单区域 - 所有类型都可能有 */}
|
||||
{/* �𦦵揣銵典��箏� - ���厩掩�钅��航��?*/}
|
||||
{searchArea && <div className='w-full'>{searchArea}</div>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -193,8 +187,7 @@ CardPro.propTypes = {
|
||||
paginationArea: PropTypes.node,
|
||||
// 銵冽聢��捆
|
||||
children: PropTypes.node,
|
||||
// 国际化函数
|
||||
t: PropTypes.func,
|
||||
// �賡��硋遆�? t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default CardPro;
|
||||
|
||||
+4
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -34,11 +34,8 @@ import { useIsMobile } from '../../../hooks/common/useIsMobile';
|
||||
import { useMinimumLoadingTime } from '../../../hooks/common/useMinimumLoadingTime';
|
||||
|
||||
/**
|
||||
* CardTable 响应式表格组件
|
||||
*
|
||||
* 在桌面端渲染 Semi-UI 的 Table 组件,在移动端则将每一行数据渲染成 Card 形式。
|
||||
* 该组件与 Table 组件的大部分 API 保持一致,只需将原 Table 换成 CardTable 即可。
|
||||
*/
|
||||
* CardTable �滚�撘讛”�潛�隞? *
|
||||
* �冽��Y垢皜脫� Semi-UI �?Table 蝏�辣嚗�銁蝘餃𢆡蝡臬�撠��銝�銵峕㺭�格葡�𤘪� Card 敶W��? * 霂亦�隞嗡� Table 蝏�辣��之�典� API 靽脲�銝��湛��芷�撠�� Table �X� CardTable �喳虾�? */
|
||||
const CardTable = ({
|
||||
columns = [],
|
||||
dataSource = [],
|
||||
|
||||
+10
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -23,8 +23,7 @@ import { Card, Button, Typography, Tag } from '@douyinfe/semi-ui';
|
||||
import { copy, showSuccess } from '../../../helpers';
|
||||
|
||||
/**
|
||||
* 解析密钥数据,支持多种格式
|
||||
* @param {string} keyData - 密钥数据
|
||||
* è§£æž�密钥数æ�®ï¼Œæ”¯æŒ�多ç§�æ ¼å¼? * @param {string} keyData - 密钥数æ�®
|
||||
* @param {Function} t - 翻译函数
|
||||
* @returns {Array} 解��的密钥数组
|
||||
*/
|
||||
@@ -33,8 +32,7 @@ const parseChannelKeys = (keyData, t) => {
|
||||
|
||||
const trimmed = keyData.trim();
|
||||
|
||||
// 检查是否是JSON数组格式(如Vertex AI)
|
||||
if (trimmed.startsWith('[')) {
|
||||
// 检查是å�¦æ˜¯JSONæ•°ç»„æ ¼å¼�(如Vertex AIï¼? if (trimmed.startsWith('[')) {
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
if (Array.isArray(parsed)) {
|
||||
@@ -47,8 +45,7 @@ const parseChannelKeys = (keyData, t) => {
|
||||
}));
|
||||
}
|
||||
} 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) => {
|
||||
copy(content);
|
||||
showSuccess(t('密钥已复制到剪贴板'));
|
||||
showSuccess(t('密钥已�制到剪贴�));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
{/* 成功状态 */}
|
||||
{/* �功状�*/}
|
||||
{showSuccessIcon && (
|
||||
<div className='flex items-center gap-2'>
|
||||
<svg
|
||||
@@ -136,7 +133,7 @@ const ChannelKeyDisplay = ({
|
||||
{isMultipleKeys && (
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography.Text type='tertiary' size='small'>
|
||||
{t('共 {{count}} 个密钥', { count: parsedKeys.length })}
|
||||
{t('�{{count}} 个密�, { count: parsedKeys.length })}
|
||||
</Typography.Text>
|
||||
<Button
|
||||
size='small'
|
||||
@@ -234,7 +231,7 @@ const ChannelKeyDisplay = ({
|
||||
/>
|
||||
</svg>
|
||||
{t(
|
||||
'检测到多个密钥,您可以单独复制每个密钥,或点击复制全部获取完整内容。',
|
||||
'检测到多个密钥,您�以�独�制�个密钥,或点击�制全部获�完整内容�,
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
@@ -266,7 +263,7 @@ const ChannelKeyDisplay = ({
|
||||
<Typography.Text className='block text-yellow-700 dark:text-yellow-300 text-sm mt-1'>
|
||||
{warningText ||
|
||||
t(
|
||||
'请妥善保管密钥信息,不要泄露给他人。如有安全疑虑,请及时更换密钥。',
|
||||
'请妥善�管密钥信�,��泄露给他人。如有安全疑虑,请�时更�密钥�,
|
||||
)}
|
||||
</Typography.Text>
|
||||
</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
|
||||
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
|
||||
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';
|
||||
@@ -24,9 +24,7 @@ import { useIsMobile } from '../../../hooks/common/useIsMobile';
|
||||
|
||||
/**
|
||||
* 蝝批�璅∪���揢�厰僼蝏�辣
|
||||
* 用于在自适应列表和紧凑列表之间切换
|
||||
* 在移动端时自动隐藏,因为移动端使用"显示操作项"按钮来控制内容显示
|
||||
*/
|
||||
* �其��刻䌊����𡑒”�𣬚揮�穃�銵其��游��? * �函宏�函垢�嗉䌊�券��𧶏��牐蛹蝘餃𢆡蝡臭蝙�?�曄內�滢�憿?�厰僼�交綉�嗅�摰寞遬蝷? */
|
||||
const CompactModeToggle = ({
|
||||
compactMode,
|
||||
setCompactMode,
|
||||
|
||||
+26
-36
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -65,13 +65,12 @@ const JSONEditor = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 将对象转换为键值对数组(包含唯一ID)
|
||||
const objectToKeyValueArray = useCallback((obj, prevPairs = []) => {
|
||||
// 将对象转�为键值对数组(包�唯一ID� const objectToKeyValueArray = useCallback((obj, prevPairs = []) => {
|
||||
if (!obj || typeof obj !== 'object') return [];
|
||||
|
||||
const entries = Object.entries(obj);
|
||||
return entries.map(([key, value], index) => {
|
||||
// 如果上一次转换后同位置的键一致,则沿用其 id,保持 React key 稳定
|
||||
// 如果上一次转����置的键一致,则沿用其 id,��React key 稳定
|
||||
const prev = prevPairs[index];
|
||||
const shouldReuseId = prev && prev.key === key;
|
||||
return {
|
||||
@@ -117,8 +116,7 @@ const JSONEditor = ({
|
||||
return '';
|
||||
});
|
||||
|
||||
// 根据键数量决定默认编辑模式
|
||||
const [editMode, setEditMode] = useState(() => {
|
||||
// æ ¹æ�®é”®æ•°é‡�决定默认编辑模å¼? const [editMode, setEditMode] = useState(() => {
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
@@ -160,8 +158,7 @@ const JSONEditor = ({
|
||||
parsed = value;
|
||||
}
|
||||
|
||||
// 只在外部值真正改变时更新,避免循环更新
|
||||
const currentObj = keyValueArrayToObject(keyValuePairs);
|
||||
// å�ªåœ¨å¤–éƒ¨å€¼çœŸæ£æ”¹å�˜æ—¶æ›´æ–°ï¼Œé�¿å…�循环更æ–? const currentObj = keyValueArrayToObject(keyValuePairs);
|
||||
if (JSON.stringify(parsed) !== JSON.stringify(currentObj)) {
|
||||
setKeyValuePairs(objectToKeyValueArray(parsed, keyValuePairs));
|
||||
}
|
||||
@@ -172,8 +169,7 @@ const JSONEditor = ({
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
// 外部 value 变化时,若不在手动模式,则同步手动文本
|
||||
useEffect(() => {
|
||||
// 外部 value å�˜åŒ–时,若ä¸�在手动模å¼�,则å�Œæ¥æ‰‹åŠ¨æ–‡æœ? useEffect(() => {
|
||||
if (editMode !== 'manual') {
|
||||
if (typeof value === 'string') setManualText(value);
|
||||
else if (value && typeof value === 'object')
|
||||
@@ -194,8 +190,7 @@ const JSONEditor = ({
|
||||
|
||||
setJsonError('');
|
||||
|
||||
// 通过formApi设置值
|
||||
if (formApi && field) {
|
||||
// 通过formApi设置� if (formApi && field) {
|
||||
formApi.setValue(field, jsonString);
|
||||
}
|
||||
|
||||
@@ -204,8 +199,7 @@ const JSONEditor = ({
|
||||
[onChange, formApi, field, keyValueArrayToObject],
|
||||
);
|
||||
|
||||
// 处理手动编辑的数据变化
|
||||
const handleManualChange = useCallback(
|
||||
// 处�手动编辑的数��� const handleManualChange = useCallback(
|
||||
(newValue) => {
|
||||
setManualText(newValue);
|
||||
if (newValue && newValue.trim()) {
|
||||
@@ -301,8 +295,7 @@ const JSONEditor = ({
|
||||
[keyValuePairs, handleVisualChange],
|
||||
);
|
||||
|
||||
// 更新值
|
||||
const updateValue = useCallback(
|
||||
// 更新� const updateValue = useCallback(
|
||||
(id, newValue) => {
|
||||
const newPairs = keyValuePairs.map((pair) =>
|
||||
pair.id === id ? { ...pair, value: newValue } : pair,
|
||||
@@ -335,8 +328,7 @@ const JSONEditor = ({
|
||||
keyValuePairs,
|
||||
]);
|
||||
|
||||
// 渲染值输入控件(支持嵌套)
|
||||
const renderValueInput = (pairId, pairKey, value) => {
|
||||
// 渲染值输入控件(支�嵌套� const renderValueInput = (pairId, pairKey, value) => {
|
||||
const valueType = typeof value;
|
||||
|
||||
if (valueType === 'boolean') {
|
||||
@@ -386,7 +378,7 @@ const JSONEditor = ({
|
||||
// å—符串或其他原始类型
|
||||
return (
|
||||
<Input
|
||||
placeholder={t('参数值')}
|
||||
placeholder={t('�数�)}
|
||||
value={String(value)}
|
||||
suffix={renderStringValueSuffix?.({ pairId, pairKey, value })}
|
||||
onChange={(newValue) => {
|
||||
@@ -406,11 +398,10 @@ const JSONEditor = ({
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染键值对编辑器
|
||||
const renderKeyValueEditor = () => {
|
||||
// 渲染键值对编辑� const renderKeyValueEditor = () => {
|
||||
return (
|
||||
<div className='space-y-1'>
|
||||
{/* 重复键警告 */}
|
||||
{/* é‡�å¤�é”®è¦å‘?*/}
|
||||
{duplicateKeys.size > 0 && (
|
||||
<Banner
|
||||
type='warning'
|
||||
@@ -421,7 +412,7 @@ const JSONEditor = ({
|
||||
<Text>{Array.from(duplicateKeys).join(', ')}</Text>
|
||||
<br />
|
||||
<Text type='tertiary' size='small'>
|
||||
{t('注意:JSON中重复的键只会保留最后一个同名键的值')}
|
||||
{t('注æ„�:JSONä¸é‡�å¤�的键å�ªä¼šä¿�留最å�Žä¸€ä¸ªå�Œå��键的å€?)}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
@@ -457,8 +448,8 @@ const JSONEditor = ({
|
||||
<Tooltip
|
||||
content={
|
||||
isLastDuplicate
|
||||
? t('这是重复键中的最后一个,其值将被使用')
|
||||
: t('重复的键名,此值将被后面的同名键覆盖')
|
||||
? t('这是é‡�å¤�é”®ä¸çš„æœ€å�Žä¸€ä¸ªï¼Œå…¶å€¼å°†è¢«ä½¿ç”?)
|
||||
: t('é‡�å¤�的键å��,æ¤å€¼å°†è¢«å�Žé�¢çš„å�Œå��键覆ç›?)
|
||||
}
|
||||
>
|
||||
<IconAlertTriangle
|
||||
@@ -502,14 +493,13 @@ const JSONEditor = ({
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染区域编辑器(特殊格式)- 也需要改造以支持重复键
|
||||
const renderRegionEditor = () => {
|
||||
// æ¸²æŸ“åŒºåŸŸç¼–è¾‘å™¨ï¼ˆç‰¹æ®Šæ ¼å¼�ï¼? 也需è¦�æ”¹é€ ä»¥æ”¯æŒ�é‡�å¤�é”? const renderRegionEditor = () => {
|
||||
const defaultPair = keyValuePairs.find((pair) => pair.key === 'default');
|
||||
const modelPairs = keyValuePairs.filter((pair) => pair.key !== 'default');
|
||||
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
{/* 重复键警告 */}
|
||||
{/* é‡�å¤�é”®è¦å‘?*/}
|
||||
{duplicateKeys.size > 0 && (
|
||||
<Banner
|
||||
type='warning'
|
||||
@@ -520,7 +510,7 @@ const JSONEditor = ({
|
||||
<Text>{Array.from(duplicateKeys).join(', ')}</Text>
|
||||
<br />
|
||||
<Text type='tertiary' size='small'>
|
||||
{t('注意:JSON中重复的键只会保留最后一个同名键的值')}
|
||||
{t('注æ„�:JSONä¸é‡�å¤�的键å�ªä¼šä¿�留最å�Žä¸€ä¸ªå�Œå��键的å€?)}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
@@ -567,7 +557,7 @@ const JSONEditor = ({
|
||||
status={isDuplicate ? 'warning' : undefined}
|
||||
/>
|
||||
{isDuplicate && (
|
||||
<Tooltip content={t('重复的键名')}>
|
||||
<Tooltip content={t('��的键�)}>
|
||||
<IconAlertTriangle
|
||||
className='absolute right-2 top-1/2 transform -translate-y-1/2'
|
||||
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' />
|
||||
</Tabs>
|
||||
|
||||
@@ -666,11 +656,11 @@ const JSONEditor = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 编辑器内容 */}
|
||||
{/* 编辑器内�*/}
|
||||
{editMode === 'visual' ? (
|
||||
<div>
|
||||
{renderVisualEditor()}
|
||||
{/* 隐藏的Form字段用于验证和数据绑定 */}
|
||||
{/* éš�è—�çš„Formå—æ®µç”¨äºŽéªŒè¯�和数æ�®ç»‘å®?*/}
|
||||
<Form.Input
|
||||
field={field}
|
||||
value={value}
|
||||
@@ -689,7 +679,7 @@ const JSONEditor = ({
|
||||
showClear={showClear}
|
||||
rows={Math.max(8, manualText ? manualText.split('\n').length : 8)}
|
||||
/>
|
||||
{/* 隐藏的Form字段用于验证和数据绑定 */}
|
||||
{/* éš�è—�çš„Formå—æ®µç”¨äºŽéªŒè¯�和数æ�®ç»‘å®?*/}
|
||||
<Form.Input
|
||||
field={field}
|
||||
value={value}
|
||||
@@ -701,7 +691,7 @@ const JSONEditor = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 额外文本显示在卡片底部 */}
|
||||
{/* �外文本显示在�片底�*/}
|
||||
{extraText && (
|
||||
<Divider margin='12px' align='center'>
|
||||
<Text type='tertiary' size='small'>
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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, {
|
||||
@@ -28,8 +28,7 @@ import React, {
|
||||
} from 'react';
|
||||
|
||||
/**
|
||||
* ScrollableContainer 可滚动容器组件
|
||||
*
|
||||
* ScrollableContainer �滚动容器组� *
|
||||
* �供自动检测滚动状�和显示��指示器的功能
|
||||
* 当内容超出容器高度且未滚动到底部时,会显示底部��指示器
|
||||
*
|
||||
|
||||
+11
-18
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -33,19 +33,15 @@ import {
|
||||
import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons';
|
||||
|
||||
/**
|
||||
* 通用可选择按钮组组件
|
||||
*
|
||||
* 通用�选择按钮组组� *
|
||||
* @param {string} title æ ‡é¢˜
|
||||
* @param {Array<{value:any,label:string,icon?:React.ReactNode,tagCount?:number}>} items 按钮项
|
||||
* @param {*|Array} activeValue 当前激活的值,可以是单个值或数组(多选)
|
||||
* @param {Array<{value:any,label:string,icon?:React.ReactNode,tagCount?:number}>} items 按钮� * @param {*|Array} activeValue 当�激活的值,�以是�个值或数组(多选)
|
||||
* @param {(value:any)=>void} onChange 选择改�回调
|
||||
* @param {function} t i18n
|
||||
* @param {object} style é¢�å¤–æ ·å¼�
|
||||
* @param {boolean} collapsible 是å�¦æ”¯æŒ�折å� ,默认true
|
||||
* @param {number} collapseHeight 折叠时的高度,默认200
|
||||
* @param {boolean} withCheckbox 是否启用前缀 Checkbox 来控制激活状态
|
||||
* @param {boolean} loading 是否处于加载状态
|
||||
* @param {string} variant 颜色变体: 'violet' | 'teal' | 'amber' | 'rose' | 'green',不传则使用默认蓝色
|
||||
* @param {number} collapseHeight 折å� 时的高度,默è®?00
|
||||
* @param {boolean} withCheckbox 是å�¦å�¯ç”¨å‰�ç¼€ Checkbox æ�¥æŽ§åˆ¶æ¿€æ´»çжæ€? * @param {boolean} loading 是å�¦å¤„äºŽåŠ è½½çŠ¶æ€? * @param {string} variant 颜色å�˜ä½“: 'violet' | 'teal' | 'amber' | 'rose' | 'green',ä¸�ä¼ åˆ™ä½¿ç”¨é»˜è®¤è“�色
|
||||
*/
|
||||
const SelectableButtonGroup = ({
|
||||
title,
|
||||
@@ -89,10 +85,9 @@ const SelectableButtonGroup = ({
|
||||
|
||||
// 基于容器宽度计算å“�应å¼�åˆ—æ•°å’Œæ ‡ç¾æ˜¾ç¤ºç–ç•¥
|
||||
const getResponsiveConfig = () => {
|
||||
if (containerWidth <= 280) return { columns: 1, showTags: true }; // 极窄:1列+标签
|
||||
if (containerWidth <= 380) return { columns: 2, showTags: true }; // 窄屏:2列+标签
|
||||
if (containerWidth <= 460) return { columns: 3, showTags: false }; // 中等:3列不加标签
|
||||
return { columns: 3, showTags: true }; // 最宽:3列+标签
|
||||
if (containerWidth <= 280) return { columns: 1, showTags: true }; // æž�窄ï¼?åˆ?æ ‡ç¾
|
||||
if (containerWidth <= 380) return { columns: 2, showTags: true }; // 窄å±�ï¼?åˆ?æ ‡ç¾
|
||||
if (containerWidth <= 460) return { columns: 3, showTags: false }; // ä¸ç‰ï¼?列ä¸�åŠ æ ‡ç? return { columns: 3, showTags: true }; // 最宽:3åˆ?æ ‡ç¾
|
||||
};
|
||||
|
||||
const { columns: perRow, showTags: shouldShowTags } = getResponsiveConfig();
|
||||
@@ -100,11 +95,9 @@ const SelectableButtonGroup = ({
|
||||
const needCollapse = collapsible && items.length > perRow * maxVisibleRows;
|
||||
const showSkeleton = useMinimumLoadingTime(loading);
|
||||
|
||||
// 统一使用紧凑的网格间距
|
||||
const gutterSize = [4, 4];
|
||||
// ç»Ÿä¸€ä½¿ç”¨ç´§å‡‘çš„ç½‘æ ¼é—´è·? const gutterSize = [4, 4];
|
||||
|
||||
// 计算 Semi UI Col 的 span 值
|
||||
const getColSpan = () => {
|
||||
// 计算 Semi UI Col �span � const getColSpan = () => {
|
||||
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
|
||||
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
|
||||
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';
|
||||
@@ -44,7 +44,7 @@ const AnnouncementsPanel = ({
|
||||
<Bell size={16} />
|
||||
{t('系统公告')}
|
||||
<Tag color='white' shape='circle'>
|
||||
{t('显示最新20条')}
|
||||
{t('显示最�0�)}
|
||||
</Tag>
|
||||
</div>
|
||||
{/* 图例 */}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -71,7 +71,7 @@ const ApiInfoPanel = ({
|
||||
onClick={() => handleSpeedTest(api.url)}
|
||||
className='cursor-pointer hover:opacity-80 text-xs'
|
||||
>
|
||||
{t('测速')}
|
||||
{t('测�)}
|
||||
</Tag>
|
||||
<Tag
|
||||
prefixIcon={<ExternalLink size={12} />}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -53,15 +53,15 @@ const ChartsPanel = ({
|
||||
activeKey={activeChartTab}
|
||||
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='3' />
|
||||
<TabPane tab={<span>{t('调用次数排行')}</span>} itemKey='4' />
|
||||
{isAdminUser && (
|
||||
<TabPane tab={<span>{t('用户消耗排行')}</span>} itemKey='5' />
|
||||
<TabPane tab={<span>{t('用户消耗排�)}</span>} itemKey='5' />
|
||||
)}
|
||||
{isAdminUser && (
|
||||
<TabPane tab={<span>{t('用户消耗趋势')}</span>} itemKey='6' />
|
||||
<TabPane tab={<span>{t('用户消耗趋�)}</span>} itemKey='6' />
|
||||
)}
|
||||
</Tabs>
|
||||
</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
|
||||
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
|
||||
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';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -90,7 +90,7 @@ const StatsCards = ({
|
||||
navigate('/console/topup');
|
||||
}}
|
||||
>
|
||||
{t('充值')}
|
||||
{t('充�)}
|
||||
</Tag>
|
||||
) : (
|
||||
(loading ||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -54,7 +54,7 @@ const UptimePanel = ({
|
||||
<div className='flex items-center justify-between w-full gap-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Gauge size={16} />
|
||||
{t('服务可用性')}
|
||||
{t('�务�用�)}
|
||||
</div>
|
||||
<Button
|
||||
icon={<RefreshCw size={14} />}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -182,7 +182,7 @@ const Dashboard = () => {
|
||||
CHART_CONFIG={CHART_CONFIG}
|
||||
/>
|
||||
|
||||
{/* API信息和图表面板 */}
|
||||
{/* API信息和图表面��?*/}
|
||||
<div className='mb-4'>
|
||||
<div
|
||||
className={`grid grid-cols-1 gap-4 ${dashboardData.hasApiInfoPanel ? 'lg:grid-cols-4' : ''}`}
|
||||
@@ -218,7 +218,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 系统公告和常见问答卡片 */}
|
||||
{/* 系统公告和常见问答卡��?*/}
|
||||
{dashboardData.hasInfoPanels && (
|
||||
<div className='mb-4'>
|
||||
<div className='grid grid-cols-1 lg:grid-cols-4 gap-4'>
|
||||
@@ -249,7 +249,7 @@ const Dashboard = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 服务可用性卡片 */}
|
||||
{/* 服务可用性卡��?*/}
|
||||
{dashboardData.uptimeEnabled && (
|
||||
<UptimePanel
|
||||
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
|
||||
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
|
||||
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';
|
||||
@@ -91,7 +91,7 @@ const SearchModal = ({
|
||||
field: 'username',
|
||||
label: t('用户�称'),
|
||||
value: username,
|
||||
placeholder: t('可选值'),
|
||||
placeholder: t('�选�),
|
||||
name: 'username',
|
||||
onChange: (value) => handleInputChange(value, 'username'),
|
||||
})}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -84,7 +84,7 @@ const FooterBar = () => {
|
||||
rel='noopener noreferrer'
|
||||
className='!text-semi-color-text-1'
|
||||
>
|
||||
{t('功能特性')}
|
||||
{t('鍔熻兘鐗规€?)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,7 +100,7 @@ const FooterBar = () => {
|
||||
rel='noopener noreferrer'
|
||||
className='!text-semi-color-text-1'
|
||||
>
|
||||
{t('快速开始')}
|
||||
{t('蹇�€熷紑濮?)}
|
||||
</a>
|
||||
<a
|
||||
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-wrap items-center gap-2'>
|
||||
<Typography.Text className='text-sm !text-semi-color-text-1'>
|
||||
© {currentYear} {systemName}. {t('版权所有')}
|
||||
漏 {currentYear} {systemName}. {t('鐗堟潈鎵€鏈?)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -120,7 +120,7 @@ const NoticeModal = ({
|
||||
if (loading) {
|
||||
return (
|
||||
<div className='py-12'>
|
||||
<Empty description={t('加载中...')} />
|
||||
<Empty description={t('åŠ è½½ä¸?..')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+14
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -172,7 +172,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
className: isAdmin() ? '' : 'tableHiddle',
|
||||
},
|
||||
{
|
||||
text: t('兑换码管理'),
|
||||
text: t('å…‘æ�¢ç �管ç�?),
|
||||
itemKey: 'redemption',
|
||||
to: '/redemption',
|
||||
className: isAdmin() ? '' : 'tableHiddle',
|
||||
@@ -203,7 +203,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
const chatMenuItems = useMemo(() => {
|
||||
const items = [
|
||||
{
|
||||
text: t('操练场'),
|
||||
text: t('�练�),
|
||||
itemKey: 'playground',
|
||||
to: '/playground',
|
||||
},
|
||||
@@ -223,8 +223,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
return filteredItems;
|
||||
}, [chatItems, t, isModuleVisible]);
|
||||
|
||||
// 更新路由映射,添加聊天路由
|
||||
const updateRouterMapWithChats = (chats) => {
|
||||
// æ›´æ–°è·¯ç”±æ˜ å°„ï¼Œæ·»åŠ è�Šå¤©è·¯ç”? const updateRouterMapWithChats = (chats) => {
|
||||
const newRouterMap = { ...routerMap };
|
||||
|
||||
if (Array.isArray(chats) && chats.length > 0) {
|
||||
@@ -237,8 +236,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
return newRouterMap;
|
||||
};
|
||||
|
||||
// 加载聊天项
|
||||
useEffect(() => {
|
||||
// åŠ è½½è�Šå¤©é¡? useEffect(() => {
|
||||
let chats = localStorage.getItem('chats');
|
||||
if (chats) {
|
||||
try {
|
||||
@@ -307,13 +305,11 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
}
|
||||
}, [collapsed]);
|
||||
|
||||
// 选中高亮颜色(统一)
|
||||
const SELECTED_COLOR = 'var(--semi-color-primary)';
|
||||
// 选ä¸é«˜äº®é¢œè‰²ï¼ˆç»Ÿä¸€ï¼? const SELECTED_COLOR = 'var(--semi-color-primary)';
|
||||
|
||||
// 渲染自定义��项
|
||||
const renderNavItem = (item) => {
|
||||
// 跳过隐藏的项目
|
||||
if (item.className === 'tableHiddle') return null;
|
||||
// 跳过��的项� if (item.className === 'tableHiddle') return null;
|
||||
|
||||
const isSelected = selectedKeys.includes(item.itemKey);
|
||||
const textColor = isSelected ? SELECTED_COLOR : 'inherit';
|
||||
@@ -417,8 +413,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
const to =
|
||||
routerMapState[props.itemKey] || routerMap[props.itemKey];
|
||||
|
||||
// 如果没有路由,直接返回元素
|
||||
if (!to) return itemElement;
|
||||
// 如果没有路由,直接返回元� if (!to) return itemElement;
|
||||
|
||||
return (
|
||||
<Link
|
||||
@@ -453,13 +448,13 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 控制台区域 */}
|
||||
{/* 控制�区�*/}
|
||||
{hasSectionVisibleModules('console') && (
|
||||
<>
|
||||
<Divider className='sidebar-divider' />
|
||||
<div>
|
||||
{!collapsed && (
|
||||
<div className='sidebar-group-label'>{t('控制台')}</div>
|
||||
<div className='sidebar-group-label'>{t('控制�)}</div>
|
||||
)}
|
||||
{workspaceItems.map((item) => renderNavItem(item))}
|
||||
</div>
|
||||
@@ -479,13 +474,13 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 管理员区域 - 只在管理员时显示且配置允许时显示 */}
|
||||
{/* 管�员区�- �在管�员时显示且�置�许时显示 */}
|
||||
{isAdmin() && hasSectionVisibleModules('admin') && (
|
||||
<>
|
||||
<Divider className='sidebar-divider' />
|
||||
<div>
|
||||
{!collapsed && (
|
||||
<div className='sidebar-group-label'>{t('管理员')}</div>
|
||||
<div className='sidebar-group-label'>{t('管��)}</div>
|
||||
)}
|
||||
{adminItems.map((item) => renderNavItem(item))}
|
||||
</div>
|
||||
@@ -525,7 +520,7 @@ const SiderBar = ({ onNavigate = () => {} }) => {
|
||||
: { padding: '4px 12px', width: '100%' }
|
||||
}
|
||||
>
|
||||
{!collapsed ? t('收起侧边栏') : null}
|
||||
{!collapsed ? t('æ”¶èµ·ä¾§è¾¹æ ?) : null}
|
||||
</Button>
|
||||
</SkeletonWrapper>
|
||||
</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
|
||||
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
|
||||
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';
|
||||
@@ -37,8 +37,7 @@ const SkeletonWrapper = ({
|
||||
return children;
|
||||
}
|
||||
|
||||
// 导航链接骨架屏
|
||||
const renderNavigationSkeleton = () => {
|
||||
// 导航链接骨架� const renderNavigationSkeleton = () => {
|
||||
const skeletonLinkClasses = isMobile
|
||||
? 'flex items-center gap-1 p-1 w-full rounded-md'
|
||||
: 'flex items-center gap-1 p-2 rounded-md';
|
||||
@@ -60,7 +59,7 @@ const SkeletonWrapper = ({
|
||||
));
|
||||
};
|
||||
|
||||
// 用户区域骨架屏 (头像 + 文本)
|
||||
// 用户区域骨架�(头� + 文本)
|
||||
const renderUserAreaSkeleton = () => {
|
||||
return (
|
||||
<div
|
||||
@@ -88,8 +87,7 @@ const SkeletonWrapper = ({
|
||||
);
|
||||
};
|
||||
|
||||
// Logo图片骨架屏
|
||||
const renderImageSkeleton = () => {
|
||||
// Logo图片骨架� const renderImageSkeleton = () => {
|
||||
return (
|
||||
<Skeleton
|
||||
loading={true}
|
||||
@@ -104,8 +102,7 @@ const SkeletonWrapper = ({
|
||||
);
|
||||
};
|
||||
|
||||
// 系统名称骨架屏
|
||||
const renderTitleSkeleton = () => {
|
||||
// 系统�称骨架� const renderTitleSkeleton = () => {
|
||||
return (
|
||||
<Skeleton
|
||||
loading={true}
|
||||
@@ -115,8 +112,7 @@ const SkeletonWrapper = ({
|
||||
);
|
||||
};
|
||||
|
||||
// 通用文本骨架屏
|
||||
const renderTextSkeleton = () => {
|
||||
// 通用文本骨架� const renderTextSkeleton = () => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Skeleton
|
||||
@@ -128,8 +124,7 @@ const SkeletonWrapper = ({
|
||||
);
|
||||
};
|
||||
|
||||
// 按钮骨架屏(支持圆角)
|
||||
const renderButtonSkeleton = () => {
|
||||
// 按钮骨架�(支�圆角� const renderButtonSkeleton = () => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Skeleton
|
||||
@@ -143,7 +138,7 @@ const SkeletonWrapper = ({
|
||||
);
|
||||
};
|
||||
|
||||
// 侧边栏导航项骨架屏 (图标 + 文本)
|
||||
// ä¾§è¾¹æ �导航项骨架å±?(å›¾æ ‡ + 文本)
|
||||
const renderSidebarNavItemSkeleton = () => {
|
||||
return Array(count)
|
||||
.fill(null)
|
||||
@@ -152,7 +147,7 @@ const SkeletonWrapper = ({
|
||||
key={index}
|
||||
className={`flex items-center p-2 mb-1 rounded-md ${className}`}
|
||||
>
|
||||
{/* 图标骨架屏 */}
|
||||
{/* å›¾æ ‡éª¨æž¶å±?*/}
|
||||
<div className='sidebar-icon-container flex-shrink-0'>
|
||||
<Skeleton
|
||||
loading={true}
|
||||
@@ -162,7 +157,7 @@ const SkeletonWrapper = ({
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{/* 文本骨架屏 */}
|
||||
{/* 文本骨架�*/}
|
||||
<Skeleton
|
||||
loading={true}
|
||||
active
|
||||
@@ -176,8 +171,7 @@ const SkeletonWrapper = ({
|
||||
));
|
||||
};
|
||||
|
||||
// 侧边栏组标题骨架屏
|
||||
const renderSidebarGroupTitleSkeleton = () => {
|
||||
// ä¾§è¾¹æ �ç»„æ ‡é¢˜éª¨æž¶å±? const renderSidebarGroupTitleSkeleton = () => {
|
||||
return (
|
||||
<div className={`mb-2 ${className}`}>
|
||||
<Skeleton
|
||||
@@ -193,8 +187,7 @@ const SkeletonWrapper = ({
|
||||
);
|
||||
};
|
||||
|
||||
// 完整侧边栏骨架屏 - 1:1 还原,去重实现
|
||||
const renderSidebarSkeleton = () => {
|
||||
// 完整侧边æ �骨架å±� - 1:1 还原,去é‡�实çŽ? const renderSidebarSkeleton = () => {
|
||||
const NAV_WIDTH = 164;
|
||||
const NAV_HEIGHT = 30;
|
||||
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
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -32,8 +32,7 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => {
|
||||
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'}`}
|
||||
>
|
||||
简体中文
|
||||
</Dropdown.Item>
|
||||
ç®€ä½“ä¸æ–? </Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
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'}`}
|
||||
@@ -55,8 +54,7 @@ const LanguageSelector = ({ currentLang, onLanguageChange, t }) => {
|
||||
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'}`}
|
||||
>
|
||||
日本語
|
||||
</Dropdown.Item>
|
||||
日本� </Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
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'}`}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -43,7 +43,7 @@ const MobileMenuButton = ({
|
||||
)
|
||||
}
|
||||
aria-label={
|
||||
(isMobile ? drawerOpen : collapsed) ? t('关闭侧边栏') : t('打开侧边栏')
|
||||
(isMobile ? drawerOpen : collapsed) ? t('å…³é—ä¾§è¾¹æ ?) : t('打开侧边æ ?)
|
||||
}
|
||||
onClick={onToggle}
|
||||
theme='borderless'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -87,8 +87,7 @@ const ThemeToggle = ({ theme, onThemeToggle, t }) => {
|
||||
<>
|
||||
<Dropdown.Divider />
|
||||
<div className='px-3 py-2 text-xs text-semi-color-text-2'>
|
||||
{t('当前跟随系统')}:
|
||||
{actualTheme === 'dark' ? t('深色') : t('浅色')}
|
||||
{t('当�跟�系统')}� {actualTheme === 'dark' ? t('深色') : t('浅色')}
|
||||
</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
|
||||
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
|
||||
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';
|
||||
@@ -110,7 +110,7 @@ const UserArea = ({
|
||||
size='small'
|
||||
className='text-gray-500 dark:text-gray-400'
|
||||
/>
|
||||
<span>{t('退出')}</span>
|
||||
<span>{t('退�)}</span>
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -46,7 +46,7 @@ const DeploymentAccessGuard = ({
|
||||
<div className='mt-[60px] px-2'>
|
||||
<Card loading={true} style={{ minHeight: '400px' }}>
|
||||
<div style={{ textAlign: 'center', padding: '50px 0' }}>
|
||||
<Text type='secondary'>{t('加载设置中...')}</Text>
|
||||
<Text type='secondary'>{t('åŠ è½½è®¾ç½®ä¸?..')}</Text>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -113,7 +113,7 @@ const DeploymentAccessGuard = ({
|
||||
fontWeight: '700',
|
||||
}}
|
||||
>
|
||||
{t('模型部署服务未启用')}
|
||||
{t('模型部署�务未��)}
|
||||
</Title>
|
||||
<Text
|
||||
style={{
|
||||
@@ -200,7 +200,7 @@ const DeploymentAccessGuard = ({
|
||||
color: 'var(--semi-color-text-1)',
|
||||
}}
|
||||
>
|
||||
{t('启用 io.net 部署开关')}
|
||||
{t('�用 io.net 部署开�)}
|
||||
</Text>
|
||||
</div>
|
||||
<div
|
||||
@@ -221,7 +221,7 @@ const DeploymentAccessGuard = ({
|
||||
color: 'var(--semi-color-text-1)',
|
||||
}}
|
||||
>
|
||||
{t('配置有效的 io.net API Key')}
|
||||
{t('�置有效�io.net API Key')}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
@@ -272,7 +272,7 @@ const DeploymentAccessGuard = ({
|
||||
lineHeight: '1.5',
|
||||
}}
|
||||
>
|
||||
{t('配置完成后刷新页面即可使用模型部署功能')}
|
||||
{t('�置完��刷新页���使用模型部署功�)}
|
||||
</Text>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -285,7 +285,7 @@ const DeploymentAccessGuard = ({
|
||||
<div className='mt-[60px] px-2'>
|
||||
<Card loading={true} style={{ minHeight: '400px' }}>
|
||||
<div style={{ textAlign: 'center', padding: '50px 0' }}>
|
||||
<Text type='secondary'>{t('正在检查 io.net 连接...')}</Text>
|
||||
<Text type='secondary'>{t('æ£åœ¨æ£€æŸ?io.net 连接...')}</Text>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -294,10 +294,10 @@ const DeploymentAccessGuard = ({
|
||||
|
||||
if (connectionOk === false) {
|
||||
const isExpired = connectionError?.type === 'expired';
|
||||
const title = isExpired ? t('接口密钥已过期') : t('无法连接 io.net');
|
||||
const title = isExpired ? t('接å�£å¯†é’¥å·²è¿‡æœ?) : t('æ— æ³•è¿žæŽ¥ io.net');
|
||||
const description = isExpired
|
||||
? t('当前 API 密钥已过期,请在设置中更新。')
|
||||
: t('当前配置无法连接到 io.net。');
|
||||
? t('当å‰� API å¯†é’¥å·²è¿‡æœŸï¼Œè¯·åœ¨è®¾ç½®ä¸æ›´æ–°ã€?)
|
||||
: t('当å‰�é…�ç½®æ— æ³•è¿žæŽ¥åˆ?io.netã€?);
|
||||
const detail = connectionError?.message || '';
|
||||
|
||||
return (
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -73,7 +73,7 @@ const ChatArea = ({
|
||||
{t('AI 对�')}
|
||||
</Typography.Title>
|
||||
<Typography.Text className='!text-white/80 text-sm hidden sm:inline'>
|
||||
{inputs.model || t('选择模型开始对话')}
|
||||
{inputs.model || t('选择模型开始对�)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,7 +119,7 @@ const ChatArea = ({
|
||||
onStopGenerator={onStopGenerator}
|
||||
onClear={onClearMessages}
|
||||
className='h-full'
|
||||
placeholder={t('请输入您的问题...')}
|
||||
placeholder={t('请输入您的问�..')}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -230,7 +230,7 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
|
||||
|
||||
const success = await copy(textToCopy);
|
||||
setCopied(true);
|
||||
Toast.success(t('已复制到剪贴板'));
|
||||
Toast.success(t('撌脣��嗅��芾斐�?));
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
|
||||
if (!success) {
|
||||
@@ -277,11 +277,11 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
|
||||
{/* �扯�霅血� */}
|
||||
{contentMetrics.isLarge && (
|
||||
<div style={codeThemeStyles.performanceWarning}>
|
||||
<span>⚡</span>
|
||||
<span>�?/span>
|
||||
<span>
|
||||
{contentMetrics.isVeryLarge
|
||||
? t('��捆颲�之嚗�歇�舐鍂�扯�隡睃�璅∪�')
|
||||
: t('内容较大,部分功能可能受限')}
|
||||
: t('��捆颲�之嚗屸�����賢虾�賢��?)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -297,7 +297,7 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
|
||||
onMouseEnter={() => setIsHoveringCopy(true)}
|
||||
onMouseLeave={() => setIsHoveringCopy(false)}
|
||||
>
|
||||
<Tooltip content={copied ? t('已复制') : t('复制代码')}>
|
||||
<Tooltip content={copied ? t('撌脣��?) : t('憭滚�隞��')}>
|
||||
<Button
|
||||
icon={<Copy size={14} />}
|
||||
onClick={handleCopy}
|
||||
@@ -344,7 +344,7 @@ const CodeViewer = ({ content, title, language = 'json' }) => {
|
||||
marginRight: '8px',
|
||||
}}
|
||||
/>
|
||||
{t('正在处理大内容...')}
|
||||
{t('甇�銁憭��憭批�摰?..')}
|
||||
</div>
|
||||
) : (
|
||||
<div dangerouslySetInnerHTML={{ __html: renderedContent }} />
|
||||
|
||||
+11
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -41,8 +41,7 @@ const ConfigManager = ({
|
||||
|
||||
const handleExport = () => {
|
||||
try {
|
||||
// 在导出前先保存当前配置,确保导出的是最新内容
|
||||
const configWithTimestamp = {
|
||||
// 在导出å‰�å…ˆä¿�å˜å½“å‰�é…�置,确ä¿�导出的是最新内å®? const configWithTimestamp = {
|
||||
...currentConfig,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
@@ -53,7 +52,7 @@ const ConfigManager = ({
|
||||
|
||||
exportConfig(currentConfig, messages);
|
||||
Toast.success({
|
||||
content: t('配置已导出到下载文件夹'),
|
||||
content: t('�置已导出到下载文件�),
|
||||
duration: 3,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -115,10 +114,10 @@ const ConfigManager = ({
|
||||
Modal.confirm({
|
||||
title: t('�置选项'),
|
||||
content: t(
|
||||
'是否同时重置对话消息?选择"是"将清空所有对话记录并恢复默认示例;选择"否"将保留当前对话记录。',
|
||||
'是��时�置对�消�?选择"�将清空所有对�记录并��默认示例;选择"�将�留当�对�记录�,
|
||||
),
|
||||
okText: t('�时�置消�'),
|
||||
cancelText: t('仅重置配置'),
|
||||
cancelText: t('仅�置��),
|
||||
okButtonProps: {
|
||||
type: 'danger',
|
||||
},
|
||||
@@ -134,7 +133,7 @@ const ConfigManager = ({
|
||||
clearConfig();
|
||||
onConfigReset({ resetMessages: false });
|
||||
Toast.success({
|
||||
content: t('配置已重置,对话消息已保留'),
|
||||
content: t('�置已�置,对�消�已��),
|
||||
duration: 3,
|
||||
});
|
||||
},
|
||||
@@ -150,9 +149,9 @@ const ConfigManager = ({
|
||||
const date = new Date(timestamp);
|
||||
return t('上次ä¿�å˜: ') + date.toLocaleString();
|
||||
}
|
||||
return t('已有保存的配置');
|
||||
return t('已有ä¿�å˜çš„é…�ç½?);
|
||||
}
|
||||
return t('暂无保存的配置');
|
||||
return t('æš‚æ— ä¿�å˜çš„é…�ç½?);
|
||||
};
|
||||
|
||||
const dropdownItems = [
|
||||
@@ -224,8 +223,7 @@ const ConfigManager = ({
|
||||
);
|
||||
}
|
||||
|
||||
// 桌面端显示紧凑的按钮组
|
||||
return (
|
||||
// 桌�端显示紧凑的按钮� return (
|
||||
<div className='space-y-3'>
|
||||
{/* �置状�信�和�置按钮 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
@@ -242,7 +240,7 @@ const ConfigManager = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 导出和导入按钮 */}
|
||||
{/* 导出和导入按�*/}
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
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
|
||||
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
|
||||
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';
|
||||
@@ -59,7 +59,7 @@ const CustomInputRender = (props) => {
|
||||
if (onPasteImage) {
|
||||
onPasteImage(base64);
|
||||
Toast.success({
|
||||
content: t('图片已添加'),
|
||||
content: t('图片已添�),
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
@@ -119,8 +119,7 @@ const CustomInputRender = (props) => {
|
||||
})
|
||||
: 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 || ''}`,
|
||||
style: {
|
||||
...sendNode.props.style,
|
||||
@@ -145,7 +144,7 @@ const CustomInputRender = (props) => {
|
||||
{/* 清空对�按钮 - 左边 */}
|
||||
{styledClearNode}
|
||||
<div className='flex-1'>{inputNode}</div>
|
||||
{/* 发送按钮 - 右边 */}
|
||||
{/* ��按�- �边 */}
|
||||
{styledSendNode}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+11
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -40,8 +40,7 @@ const CustomRequestEditor = ({
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [localValue, setLocalValue] = useState(customRequestBody || '');
|
||||
|
||||
// 当切换到自定义模式时,用默认payload初始化
|
||||
useEffect(() => {
|
||||
// 蠖灘�謐「蛻ー閾ェ螳壻ケ画ィ。蠑乗慮�檎畑鮟倩ョ、payload蛻晏ァ句�? useEffect(() => {
|
||||
if (
|
||||
customRequestMode &&
|
||||
(!customRequestBody || customRequestBody.trim() === '')
|
||||
@@ -59,8 +58,7 @@ const CustomRequestEditor = ({
|
||||
onCustomRequestBodyChange,
|
||||
]);
|
||||
|
||||
// 同步外部传入的customRequestBody到本地状态
|
||||
useEffect(() => {
|
||||
// 蜷梧ュ・螟夜Κ莨�蜈・逧�ustomRequestBody蛻ー譛ャ蝨ー迥カ諤? useEffect(() => {
|
||||
if (customRequestBody !== localValue) {
|
||||
setLocalValue(customRequestBody || '');
|
||||
validateJson(customRequestBody || '');
|
||||
@@ -118,7 +116,7 @@ const CustomRequestEditor = ({
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
{/* 自定义模式开关 */}
|
||||
{/* 閾ェ螳壻ケ画ィ。蠑丞シ蜈?*/}
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Code size={16} className='text-gray-500' />
|
||||
@@ -130,7 +128,7 @@ const CustomRequestEditor = ({
|
||||
checked={customRequestMode}
|
||||
onChange={handleModeToggle}
|
||||
checkedText={t('蠑')}
|
||||
uncheckedText={t('关')}
|
||||
uncheckedText={t('蜈?)}
|
||||
size='small'
|
||||
/>
|
||||
</div>
|
||||
@@ -141,18 +139,18 @@ const CustomRequestEditor = ({
|
||||
<Banner
|
||||
type='warning'
|
||||
description={t(
|
||||
'启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。',
|
||||
'蜷ッ逕ィ豁、讓。蠑丞錘�悟ー�スソ逕ィ謔ィ閾ェ螳壻ケ臥噪隸キ豎ゆス灘書騾、PI隸キ豎ゑシ梧ィ。蝙矩�鄂ョ髱「譚ソ逧�盾謨ー隶セ鄂ョ蟆�「ォ蠢ス逡・縲?,
|
||||
)}
|
||||
icon={<AlertTriangle size={16} />}
|
||||
className='!rounded-lg'
|
||||
closeIcon={null}
|
||||
/>
|
||||
|
||||
{/* JSON编辑器 */}
|
||||
{/* JSON郛冶セ大�?*/}
|
||||
<div>
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<Typography.Text strong className='text-sm'>
|
||||
{t('请求体 JSON')}
|
||||
{t('隸キ豎ゆス?JSON')}
|
||||
</Typography.Text>
|
||||
<div className='flex items-center gap-2'>
|
||||
{isValid ? (
|
||||
@@ -179,7 +177,7 @@ const CustomRequestEditor = ({
|
||||
disabled={!isValid}
|
||||
className='!rounded-lg'
|
||||
>
|
||||
{t('格式化')}
|
||||
{t('譬シ蠑丞�?)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,7 +202,7 @@ const CustomRequestEditor = ({
|
||||
|
||||
<Typography.Text className='text-xs text-gray-500 mt-2 block'>
|
||||
{t(
|
||||
'请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。',
|
||||
'隸キ霎灘�譛画譜逧ЙSON譬シ蠑冗噪隸キ豎ゆス薙よお蜿ッ莉・蜿り�「�ァ磯擇譚ソ荳ュ逧�サ倩ョ、隸キ豎ゆス捺�シ蠑上?,
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
+8
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -85,12 +85,10 @@ const DebugPanel = ({
|
||||
>
|
||||
{pos === 'start' ? (
|
||||
<div style={style} onClick={handleArrowClick}>
|
||||
←
|
||||
</div>
|
||||
� </div>
|
||||
) : (
|
||||
<div style={style} onClick={handleArrowClick}>
|
||||
→
|
||||
</div>
|
||||
� </div>
|
||||
)}
|
||||
</Dropdown>
|
||||
);
|
||||
@@ -143,11 +141,10 @@ const DebugPanel = ({
|
||||
tab={
|
||||
<div className='flex items-center gap-2'>
|
||||
<Eye size={16} />
|
||||
{t('预览请求体')}
|
||||
{t('预览请求�)}
|
||||
{customRequestMode && (
|
||||
<span className='px-1.5 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full'>
|
||||
自定义
|
||||
</span>
|
||||
自定� </span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
@@ -164,7 +161,7 @@ const DebugPanel = ({
|
||||
tab={
|
||||
<div className='flex items-center gap-2'>
|
||||
<Send size={16} />
|
||||
{t('实际请求体')}
|
||||
{t('实际请求�)}
|
||||
</div>
|
||||
}
|
||||
itemKey='request'
|
||||
@@ -211,7 +208,7 @@ const DebugPanel = ({
|
||||
{activeKey === 'preview' && debugData.previewTimestamp
|
||||
? `${t('预览更新')}: ${new Date(debugData.previewTimestamp).toLocaleString()}`
|
||||
: debugData.timestamp
|
||||
? `${t('最后请求')}: ${new Date(debugData.timestamp).toLocaleString()}`
|
||||
? `${t('最�请�)}: ${new Date(debugData.timestamp).toLocaleString()}`
|
||||
: ''}
|
||||
</Typography.Text>
|
||||
</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
|
||||
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
|
||||
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';
|
||||
|
||||
+8
-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
|
||||
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
|
||||
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';
|
||||
@@ -91,19 +91,19 @@ const ImageUrlInput = ({
|
||||
{!imageEnabled ? (
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
||||
{disabled
|
||||
? t('图片功能在自定义请求体模式下不可用')
|
||||
: t('启用后可添加图片URL进行多模态对话')}
|
||||
? t('图片功能在自定义请求体模�下���)
|
||||
: t('å�¯ç”¨å�Žå�¯æ·»åŠ å›¾ç‰‡URL进行多模æ€�对è¯?)}
|
||||
</Typography.Text>
|
||||
) : imageUrls.length === 0 ? (
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
||||
{disabled
|
||||
? t('图片功能在自定义请求体模式下不可用')
|
||||
: t('点击 + 按钮添加图片URL进行多模态对话')}
|
||||
? t('图片功能在自定义请求体模�下���)
|
||||
: t('点击 + æŒ‰é’®æ·»åŠ å›¾ç‰‡URL进行多模æ€�对è¯?)}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
||||
{t('已添加')} {imageUrls.length} {t('张图片')}
|
||||
{disabled ? ` (${t('自定义模式下不可用')})` : ''}
|
||||
{t('已添åŠ?)} {imageUrls.length} {t('å¼ å›¾ç‰?)}
|
||||
{disabled ? ` (${t('自定义模�下���)})` : ''}
|
||||
</Typography.Text>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -50,7 +50,7 @@ const MessageActions = ({
|
||||
<div className='flex items-center gap-0.5'>
|
||||
{!isLoading && (
|
||||
<Tooltip
|
||||
content={shouldDisableActions ? t('操作暂时被禁用') : t('重试')}
|
||||
content={shouldDisableActions ? t('�作暂时被��) : t('�试')}
|
||||
position='top'
|
||||
>
|
||||
<Button
|
||||
@@ -82,7 +82,7 @@ const MessageActions = ({
|
||||
|
||||
{canEdit && (
|
||||
<Tooltip
|
||||
content={shouldDisableActions ? t('操作暂时被禁用') : t('编辑')}
|
||||
content={shouldDisableActions ? t('�作暂时被��) : t('编辑')}
|
||||
position='top'
|
||||
>
|
||||
<Button
|
||||
@@ -102,7 +102,7 @@ const MessageActions = ({
|
||||
<Tooltip
|
||||
content={
|
||||
shouldDisableActions
|
||||
? t('操作暂时被禁用')
|
||||
? t('�作暂时被��)
|
||||
: message.role === 'assistant'
|
||||
? t('切�为System角色')
|
||||
: t('切�为Assistant角色')
|
||||
@@ -130,7 +130,7 @@ const MessageActions = ({
|
||||
|
||||
{!isLoading && (
|
||||
<Tooltip
|
||||
content={shouldDisableActions ? t('操作暂时被禁用') : t('删除')}
|
||||
content={shouldDisableActions ? t('æ“�作暂时被ç¦�ç”?) : t('åˆ é™¤')}
|
||||
position='top'
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -78,7 +78,7 @@ const MessageContent = ({
|
||||
<div className='flex items-center gap-2'>
|
||||
<AlertTriangle size={16} className='text-orange-500 shrink-0' />
|
||||
<Typography.Text strong className='!text-[var(--semi-color-text-0)]'>
|
||||
{t('模型价格未配置')}
|
||||
{t('æ¨¡åž‹ä»·æ ¼æœªé…�ç½?)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Typography.Paragraph
|
||||
@@ -260,7 +260,7 @@ const MessageContent = ({
|
||||
<TextArea
|
||||
value={editValue}
|
||||
onChange={(value) => onEditValueChange(value)}
|
||||
placeholder={t('请输入消息内容...')}
|
||||
placeholder={t('请输入消�内�..')}
|
||||
autosize={{ minRows: 3, maxRows: 12 }}
|
||||
style={{
|
||||
resize: 'vertical',
|
||||
@@ -311,7 +311,7 @@ const MessageContent = ({
|
||||
<div key={index} className='max-w-sm'>
|
||||
<img
|
||||
src={imgItem.image_url.url}
|
||||
alt={`用户上传的图片 ${index + 1}`}
|
||||
alt={`ç”¨æˆ·ä¸Šä¼ çš„å›¾ç‰?${index + 1}`}
|
||||
className='rounded-lg max-w-full h-auto shadow-sm border'
|
||||
style={{ maxHeight: '300px' }}
|
||||
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
|
||||
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
|
||||
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';
|
||||
@@ -23,12 +23,10 @@ import MessageActions from './MessageActions';
|
||||
import SettingsPanel from './SettingsPanel';
|
||||
import DebugPanel from './DebugPanel';
|
||||
|
||||
// 优化的消息内容组件
|
||||
export const OptimizedMessageContent = React.memo(
|
||||
// 优化的消�内容组�export const OptimizedMessageContent = React.memo(
|
||||
MessageContent,
|
||||
(prevProps, nextProps) => {
|
||||
// 只有这些属性变化时才重新渲染
|
||||
return (
|
||||
// �有这些属性�化时��新渲� return (
|
||||
prevProps.message.id === nextProps.message.id &&
|
||||
prevProps.message.content === nextProps.message.content &&
|
||||
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,
|
||||
(prevProps, nextProps) => {
|
||||
return (
|
||||
@@ -58,8 +55,7 @@ export const OptimizedMessageActions = React.memo(
|
||||
},
|
||||
);
|
||||
|
||||
// 优化的设置面板组件
|
||||
export const OptimizedSettingsPanel = React.memo(
|
||||
// 优化的设置��组�export const OptimizedSettingsPanel = React.memo(
|
||||
SettingsPanel,
|
||||
(prevProps, nextProps) => {
|
||||
return (
|
||||
@@ -79,8 +75,7 @@ export const OptimizedSettingsPanel = React.memo(
|
||||
},
|
||||
);
|
||||
|
||||
// 优化的调试面板组件
|
||||
export const OptimizedDebugPanel = React.memo(
|
||||
// 优化的调试��组�export const OptimizedDebugPanel = React.memo(
|
||||
DebugPanel,
|
||||
(prevProps, nextProps) => {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -80,7 +80,7 @@ const ParameterControl = ({
|
||||
/>
|
||||
</div>
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2'>
|
||||
{t('控制输出的随机性和创造性')}
|
||||
{t('控制输出的éš�æœºæ€§å’Œåˆ›é€ æ€?)}
|
||||
</Typography.Text>
|
||||
<Slider
|
||||
step={0.1}
|
||||
@@ -120,7 +120,7 @@ const ParameterControl = ({
|
||||
/>
|
||||
</div>
|
||||
<Typography.Text className='text-xs text-gray-500 mb-2'>
|
||||
{t('核采样,控制词汇选择的多样性')}
|
||||
{t('æ ¸é‡‡æ ·ï¼ŒæŽ§åˆ¶è¯�æ±‡é€‰æ‹©çš„å¤šæ ·æ€?)}
|
||||
</Typography.Text>
|
||||
<Slider
|
||||
step={0.1}
|
||||
@@ -285,7 +285,7 @@ const ParameterControl = ({
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
placeholder={t('随机种子 (留空为随机)')}
|
||||
placeholder={t('éš�机ç§�å� (留空为éš�æœ?')}
|
||||
name='seed'
|
||||
autoComplete='new-password'
|
||||
value={inputs.seed || ''}
|
||||
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -108,7 +108,7 @@ const SSEViewer = ({ sseData }) => {
|
||||
|
||||
await copy(allData);
|
||||
setCopied(true);
|
||||
Toast.success(t('已复制全部数据'));
|
||||
Toast.success(t('已�制全部数�));
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
Toast.error(t('�制失败'));
|
||||
@@ -123,7 +123,7 @@ const SSEViewer = ({ sseData }) => {
|
||||
? JSON.stringify(item.parsed, null, 2)
|
||||
: item.raw;
|
||||
await copy(textToCopy);
|
||||
Toast.success(t('已复制'));
|
||||
Toast.success(t('已��));
|
||||
} catch (err) {
|
||||
Toast.error(t('�制失败'));
|
||||
}
|
||||
@@ -161,7 +161,7 @@ const SSEViewer = ({ sseData }) => {
|
||||
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
{/* JSON 格式化显示 */}
|
||||
{/* JSON æ ¼å¼�化显ç¤?*/}
|
||||
<div className='relative'>
|
||||
<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)}
|
||||
@@ -185,7 +185,7 @@ const SSEViewer = ({ sseData }) => {
|
||||
/>
|
||||
)}
|
||||
{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 && (
|
||||
<Badge
|
||||
@@ -215,11 +215,11 @@ const SSEViewer = ({ sseData }) => {
|
||||
|
||||
return (
|
||||
<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 gap-3'>
|
||||
<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' />
|
||||
{stats.errors > 0 && (
|
||||
<Badge count={`${stats.errors} ${t('错误')}`} type='danger' />
|
||||
@@ -234,7 +234,7 @@ const SSEViewer = ({ sseData }) => {
|
||||
onClick={handleCopyAll}
|
||||
theme='borderless'
|
||||
>
|
||||
{copied ? t('已复制') : t('复制全部')}
|
||||
{copied ? t('已��) : t('�制全部')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -67,7 +67,7 @@ const SettingsPanel = ({
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{/* 标题区域 - 与调试面板保持一致 */}
|
||||
{/* æ ‡é¢˜åŒºåŸŸ - 与调试é�¢æ�¿ä¿�æŒ�一è‡?*/}
|
||||
<div className='flex items-center justify-between mb-6 flex-shrink-0'>
|
||||
<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'>
|
||||
@@ -90,7 +90,7 @@ const SettingsPanel = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 移动端配置管理 */}
|
||||
{/* 移动端�置管�*/}
|
||||
{styleState.isMobile && (
|
||||
<div className='mb-4 flex-shrink-0'>
|
||||
<ConfigManager
|
||||
@@ -104,7 +104,7 @@ const SettingsPanel = ({
|
||||
)}
|
||||
|
||||
<div className='space-y-6 overflow-y-auto flex-1 pr-2 model-settings-scroll'>
|
||||
{/* 自定义请求体编辑器 */}
|
||||
{/* 自定义请求体编辑�*/}
|
||||
<CustomRequestEditor
|
||||
customRequestMode={customRequestMode}
|
||||
customRequestBody={customRequestBody}
|
||||
@@ -200,7 +200,7 @@ const SettingsPanel = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 流式输出开关 */}
|
||||
{/* ��输出开�*/}
|
||||
<div className={customRequestMode ? 'opacity-50' : ''}>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center gap-2'>
|
||||
@@ -218,7 +218,7 @@ const SettingsPanel = ({
|
||||
checked={inputs.stream}
|
||||
onChange={(checked) => onInputChange('stream', checked)}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
uncheckedText={t('å…?)}
|
||||
size='small'
|
||||
disabled={customRequestMode}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -39,7 +39,7 @@ const ThinkingContent = ({
|
||||
const headerText =
|
||||
isThinkingStatus && !message.isThinkingComplete
|
||||
? t('思考中...')
|
||||
: t('思考过程');
|
||||
: t('�考过�);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
||||
+18
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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 {
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
const MESSAGES_STORAGE_KEY = 'playground_messages';
|
||||
|
||||
/**
|
||||
* 保存配置到 localStorage
|
||||
* ä¿�å˜é…�ç½®åˆ?localStorage
|
||||
* @param {Object} config - 要保存的配置对象
|
||||
*/
|
||||
export const saveConfig = (config) => {
|
||||
@@ -41,7 +41,7 @@ export const saveConfig = (config) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存消息到 localStorage
|
||||
* ä¿�å˜æ¶ˆæ�¯åˆ?localStorage
|
||||
* @param {Array} messages - 要保存的消息数组
|
||||
*/
|
||||
export const saveMessages = (messages) => {
|
||||
@@ -57,9 +57,8 @@ export const saveMessages = (messages) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 从 localStorage 加载配置
|
||||
* @returns {Object} 配置对象,如果不存在则返回默认配置
|
||||
*/
|
||||
* ä»?localStorage åŠ è½½é…�ç½®
|
||||
* @returns {Object} é…�置对象,如果ä¸�å˜åœ¨åˆ™è¿”回默认é…�ç½? */
|
||||
export const loadConfig = () => {
|
||||
try {
|
||||
const savedConfig = localStorage.getItem(STORAGE_KEYS.CONFIG);
|
||||
@@ -97,8 +96,8 @@ export const loadConfig = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 从 localStorage 加载消息
|
||||
* @returns {Array} 消息数组,如果不存在则返回 null
|
||||
* ä»?localStorage åŠ è½½æ¶ˆæ�¯
|
||||
* @returns {Array} 消æ�¯æ•°ç»„,如果ä¸�å˜åœ¨åˆ™è¿”å›?null
|
||||
*/
|
||||
export const loadMessages = () => {
|
||||
try {
|
||||
@@ -115,8 +114,7 @@ export const loadMessages = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除保存的配置
|
||||
*/
|
||||
* 清除ä¿�å˜çš„é…�ç½? */
|
||||
export const clearConfig = () => {
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEYS.CONFIG);
|
||||
@@ -127,8 +125,7 @@ export const clearConfig = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除保存的消息
|
||||
*/
|
||||
* 清除ä¿�å˜çš„æ¶ˆæ�? */
|
||||
export const clearMessages = () => {
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEYS.MESSAGES);
|
||||
@@ -138,22 +135,18 @@ export const clearMessages = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查是否有保存的配置
|
||||
* @returns {boolean} 是否存在保存的配置
|
||||
*/
|
||||
* 检查是å�¦æœ‰ä¿�å˜çš„é…�ç½? * @returns {boolean} 是å�¦å˜åœ¨ä¿�å˜çš„é…�ç½? */
|
||||
export const hasStoredConfig = () => {
|
||||
try {
|
||||
return localStorage.getItem(STORAGE_KEYS.CONFIG) !== null;
|
||||
} catch (error) {
|
||||
console.error('检查配置失败:', error);
|
||||
console.error('检查�置失�', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取配置的最后保存时间
|
||||
* @returns {string|null} 最后保存时间的 ISO 字符串
|
||||
*/
|
||||
* 获å�–é…�置的最å�Žä¿�å˜æ—¶é—? * @returns {string|null} 最å�Žä¿�å˜æ—¶é—´çš„ ISO å—符ä¸? */
|
||||
export const getConfigTimestamp = () => {
|
||||
try {
|
||||
const savedConfig = localStorage.getItem(STORAGE_KEYS.CONFIG);
|
||||
@@ -162,13 +155,13 @@ export const getConfigTimestamp = () => {
|
||||
return parsedConfig.timestamp || null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取配置时间戳失败:', error);
|
||||
console.error('获��置时间戳失�', error);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出配置为 JSON 文件(包含消息)
|
||||
* 导出�置�JSON 文件(包�消�)
|
||||
* @param {Object} config - 要导出的配置
|
||||
* @param {Array} messages - 要导出的消息
|
||||
*/
|
||||
@@ -196,10 +189,8 @@ export const exportConfig = (config, messages = null) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 从文件导入配置(包含消息)
|
||||
* @param {File} file - 包含配置的 JSON 文件
|
||||
* @returns {Promise<Object>} 导入的配置对象
|
||||
*/
|
||||
* 从文件导入�置(包�消�� * @param {File} file - 包��置�JSON 文件
|
||||
* @returns {Promise<Object>} 导入的�置对� */
|
||||
export const importConfig = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
@@ -209,8 +200,7 @@ export const importConfig = (file) => {
|
||||
const importedConfig = JSON.parse(e.target.result);
|
||||
|
||||
if (importedConfig.inputs && importedConfig.parameterEnabled) {
|
||||
// 如果导入的配置包含消息,也一起导入
|
||||
if (
|
||||
// 如果导入的�置包�消�,也一起导� if (
|
||||
importedConfig.messages &&
|
||||
Array.isArray(importedConfig.messages)
|
||||
) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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, {
|
||||
@@ -182,14 +182,14 @@ const ChannelSelectorModal = forwardRef(
|
||||
case 1:
|
||||
statusTag = (
|
||||
<Tag color='green' shape='circle'>
|
||||
{t('已启用')}
|
||||
{t('已��)}
|
||||
</Tag>
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
statusTag = (
|
||||
<Tag color='red' shape='circle'>
|
||||
{t('已禁用')}
|
||||
{t('已��)}
|
||||
</Tag>
|
||||
);
|
||||
break;
|
||||
@@ -203,7 +203,7 @@ const ChannelSelectorModal = forwardRef(
|
||||
default:
|
||||
statusTag = (
|
||||
<Tag color='grey' shape='circle'>
|
||||
{t('未知状态')}
|
||||
{t('未知状�)}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
@@ -240,7 +240,7 @@ const ChannelSelectorModal = forwardRef(
|
||||
renderBaseUrlCell(record._originalData?.base_url || ''),
|
||||
},
|
||||
{
|
||||
title: t('状态'),
|
||||
title: t('状�),
|
||||
dataIndex: '_originalData.status',
|
||||
render: (_, record) => renderStatusCell(record),
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+50
-50
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -185,8 +185,8 @@ const ACCESS_POLICY_TEMPLATES = {
|
||||
};
|
||||
|
||||
const ACCESS_DENIED_TEMPLATES = {
|
||||
level_hint: '需要等级 {{required}},你当前等级 {{current}}(字段:{{field}})',
|
||||
org_hint: '仅限指定组织或角色访问。组织={{current.org}},角色={{current.roles}}',
|
||||
level_hint: '��閬��蝥?{{required}}嚗䔶�敶枏�蝑厩漣 {{current}}嚗��畾蛛�{{field}}嚗?,
|
||||
org_hint: '隞�����蝏���𤥁��脰挪�柴���蝏?{{current.org}}嚗諹��?{{current.roles}}',
|
||||
};
|
||||
|
||||
const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
@@ -249,7 +249,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
showError(res.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('获取自定义 OAuth 提供商列表失败'));
|
||||
showError(t('�瑕��芸�銋?OAuth �𣂷����銵典仃韐?));
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
@@ -322,7 +322,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!currentValues[field]) {
|
||||
showError(t(`请填写 ${field}`));
|
||||
showError(t(`霂瑕‵�?${field}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -334,9 +334,9 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
if (value && !value.startsWith('http://') && !value.startsWith('https://')) {
|
||||
// Check if user selected a preset but forgot to fill issuer URL
|
||||
if (selectedPreset && !baseUrl) {
|
||||
showError(t('请先填写 Issuer URL,以自动生成完整的端点 URL'));
|
||||
showError(t('霂瑕�憛怠� Issuer URL嚗䔶誑�芸𢆡���摰峕㟲��垢�?URL'));
|
||||
} else {
|
||||
showError(t('端点 URL 必须是完整地址(以 http:// 或 https:// 开头)'));
|
||||
showError(t('蝡舐� URL 敹�◆�臬��游𧑐��嚗�誑 http:// �?https:// 撘�憭湛�'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -380,7 +380,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
(cleanBaseUrl ? `${cleanBaseUrl}/.well-known/openid-configuration` : '');
|
||||
|
||||
if (!wellKnownUrl) {
|
||||
showError(t('请先填写 Discovery URL 或 Issuer URL'));
|
||||
showError(t('霂瑕�憛怠� Discovery URL �?Issuer URL'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
showSuccess(t('已从 Discovery 自动填充配置'));
|
||||
} catch (error) {
|
||||
showError(
|
||||
t('获取 Discovery 配置失败:') + (error?.message || t('未知错误')),
|
||||
t('�瑕� Discovery �滨蔭憭梯揖嚗?) + (error?.message || t('�芰䰻�躰秤')),
|
||||
);
|
||||
} finally {
|
||||
setDiscoveryLoading(false);
|
||||
@@ -518,14 +518,14 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
const template = ACCESS_POLICY_TEMPLATES[templateKey];
|
||||
if (!template) return;
|
||||
mergeFormValues({ access_policy: template });
|
||||
showSuccess(t('已填充策略模板'));
|
||||
showSuccess(t('撌脣‵����交芋�?));
|
||||
};
|
||||
|
||||
const applyDeniedTemplate = (templateKey) => {
|
||||
const template = ACCESS_DENIED_TEMPLATES[templateKey];
|
||||
if (!template) return;
|
||||
mergeFormValues({ access_denied_message: template });
|
||||
showSuccess(t('已填充提示模板'));
|
||||
showSuccess(t('撌脣‵���蝷箸芋�?));
|
||||
};
|
||||
|
||||
const columns = [
|
||||
@@ -548,12 +548,12 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
render: (slug) => <Tag>{slug}</Tag>,
|
||||
},
|
||||
{
|
||||
title: t('状态'),
|
||||
title: t('�嗆�?),
|
||||
dataIndex: 'enabled',
|
||||
key: 'enabled',
|
||||
render: (enabled) => (
|
||||
<Tag color={enabled ? 'green' : 'grey'}>
|
||||
{enabled ? t('已启用') : t('已禁用')}
|
||||
{enabled ? t('撌脣鍳�?) : t('撌脩��?)}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
@@ -579,7 +579,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
{t('编辑')}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={t('确定要删除此 OAuth 提供商吗?')}
|
||||
title={t('蝖桀�閬���斗迨 OAuth �𣂷����嚗?)}
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
>
|
||||
<Button icon={<IconDelete />} size="small" type="danger">
|
||||
@@ -597,13 +597,13 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form.Section text={t('自定义 OAuth 提供商')}>
|
||||
<Form.Section text={t('�芸�銋?OAuth �𣂷��?)}>
|
||||
<Banner
|
||||
type="info"
|
||||
description={
|
||||
<>
|
||||
{t(
|
||||
'配置自定义 OAuth 提供商,支持 GitHub Enterprise、GitLab、Gitea、Nextcloud、Keycloak、ORY 等兼容 OAuth 2.0 协议的身份提供商'
|
||||
'�滨蔭�芸�銋?OAuth �𣂷�����舀� GitHub Enterprise��itLab��itea��extcloud��eycloak��RY 蝑匧�摰?OAuth 2.0 �讛悅��澈隞賣�靘𥕦�'
|
||||
)}
|
||||
<br />
|
||||
{t('回调 URL 格式')}: {serverAddress || t('网站地址')}/oauth/
|
||||
@@ -619,7 +619,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
onClick={handleAdd}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
{t('添加 OAuth 提供商')}
|
||||
{t('瘛餃� OAuth �𣂷��?)}
|
||||
</Button>
|
||||
|
||||
<Table
|
||||
@@ -628,11 +628,11 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
empty={t('暂无自定义 OAuth 提供商')}
|
||||
empty={t('����芸�銋?OAuth �𣂷��?)}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={editingProvider ? t('编辑 OAuth 提供商') : t('添加 OAuth 提供商')}
|
||||
title={editingProvider ? t('蝻𤥁� OAuth �𣂷��?) : t('瘛餃� OAuth �𣂷��?)}
|
||||
visible={modalVisible}
|
||||
onCancel={closeModal}
|
||||
width={860}
|
||||
@@ -649,14 +649,14 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
}}
|
||||
>
|
||||
<Space spacing={8} align='center'>
|
||||
<Text type='secondary'>{t('启用供应商')}</Text>
|
||||
<Text type='secondary'>{t('�舐鍂靘𥕦��?)}</Text>
|
||||
<Switch
|
||||
checked={!!formValues.enabled}
|
||||
size='large'
|
||||
onChange={(checked) => mergeFormValues({ enabled: !!checked })}
|
||||
/>
|
||||
<Tag color={formValues.enabled ? 'green' : 'grey'}>
|
||||
{formValues.enabled ? t('已启用') : t('已禁用')}
|
||||
{formValues.enabled ? t('撌脣鍳�?) : t('撌脩��?)}
|
||||
</Tag>
|
||||
</Space>
|
||||
<Button onClick={closeModal}>{t('取消')}</Button>
|
||||
@@ -677,7 +677,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
{t('Configuration')}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||
{t('先填写配置,再自动填充 OAuth 端点,能显著减少手工输入')}
|
||||
{t('��‵�䠷�蝵殷��滩䌊�典‵�?OAuth 蝡舐�嚗諹��曇��誩��见極颲枏�')}
|
||||
</Text>
|
||||
{discoveryInfo && (
|
||||
<Banner
|
||||
@@ -687,7 +687,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
description={
|
||||
<div>
|
||||
<div>
|
||||
{t('已从 Discovery 获取配置,可继续手动修改所有字段。')}
|
||||
{t('撌脖� Discovery �瑕��滨蔭嚗�虾蝏抒賒�见𢆡靽格㺿���匧�畾萸�?)}
|
||||
</div>
|
||||
{discoveryAutoFilledLabels ? (
|
||||
<div>
|
||||
@@ -724,7 +724,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
value={selectedPreset}
|
||||
onChange={handlePresetChange}
|
||||
optionList={[
|
||||
{ value: '', label: t('自定义') },
|
||||
{ value: '', label: t('�芸�銋?) },
|
||||
...Object.entries(OAUTH_PRESETS).map(([key, config]) => ({
|
||||
value: key,
|
||||
label: config.name,
|
||||
@@ -735,14 +735,14 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
<Col span={10}>
|
||||
<Form.Input
|
||||
field="base_url"
|
||||
label={t('发行者 URL(Issuer URL)')}
|
||||
label={t('�𤏸��?URL嚗㇆ssuer URL嚗?)}
|
||||
placeholder={t('例如:https://gitea.example.com')}
|
||||
value={baseUrl}
|
||||
onChange={handleBaseUrlChange}
|
||||
extraText={
|
||||
selectedPreset
|
||||
? t('填写后会自动拼接预设端点')
|
||||
: t('可选:用于自动生成端点或 Discovery URL')
|
||||
: t('�舫�㚁��其��芸𢆡���蝡舐��?Discovery URL')
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
@@ -776,7 +776,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
field="name"
|
||||
label={t('显示名称')}
|
||||
placeholder={t('例如:GitHub Enterprise')}
|
||||
rules={[{ required: true, message: t('请输入显示名称') }]}
|
||||
rules={[{ required: true, message: t('霂瑁��交遬蝷箏�蝘?) }]}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
@@ -784,8 +784,8 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
field="slug"
|
||||
label="Slug"
|
||||
placeholder={t('例如:github-enterprise')}
|
||||
extraText={t('URL 标识,只能包含小写字母、数字和连字符')}
|
||||
rules={[{ required: true, message: t('请输入 Slug') }]}
|
||||
extraText={t('URL ���嚗�蘨�賢��怠��坔�瘥溻��㺭摮堒�餈𧼮�蝚?)}
|
||||
rules={[{ required: true, message: t('霂瑁��?Slug') }]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -831,7 +831,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
field="client_id"
|
||||
label="Client ID"
|
||||
placeholder={t('OAuth Client ID')}
|
||||
rules={[{ required: true, message: t('请输入 Client ID') }]}
|
||||
rules={[{ required: true, message: t('霂瑁��?Client ID') }]}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
@@ -841,13 +841,13 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
type="password"
|
||||
placeholder={
|
||||
editingProvider
|
||||
? t('留空则保持原有密钥')
|
||||
? t('�嗵征�嗘�����匧��?)
|
||||
: t('OAuth Client Secret')
|
||||
}
|
||||
rules={
|
||||
editingProvider
|
||||
? []
|
||||
: [{ required: true, message: t('请输入 Client Secret') }]
|
||||
: [{ required: true, message: t('霂瑁��?Client Secret') }]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
@@ -869,7 +869,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
: 'https://example.com/oauth/authorize'
|
||||
}
|
||||
rules={[
|
||||
{ required: true, message: t('请输入 Authorization Endpoint') },
|
||||
{ required: true, message: t('霂瑁��?Authorization Endpoint') },
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
@@ -882,10 +882,10 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
label={t('Token Endpoint')}
|
||||
placeholder={
|
||||
selectedPreset && OAUTH_PRESETS[selectedPreset]
|
||||
? t('自动生成:') + OAUTH_PRESETS[selectedPreset].token_endpoint
|
||||
? t('�芸𢆡���嚗?) + OAUTH_PRESETS[selectedPreset].token_endpoint
|
||||
: 'https://example.com/oauth/token'
|
||||
}
|
||||
rules={[{ required: true, message: t('请输入 Token Endpoint') }]}
|
||||
rules={[{ required: true, message: t('霂瑁��?Token Endpoint') }]}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
@@ -894,11 +894,11 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
label={t('User Info Endpoint')}
|
||||
placeholder={
|
||||
selectedPreset && OAUTH_PRESETS[selectedPreset]
|
||||
? t('自动生成:') + OAUTH_PRESETS[selectedPreset].user_info_endpoint
|
||||
? t('�芸𢆡���嚗?) + OAUTH_PRESETS[selectedPreset].user_info_endpoint
|
||||
: 'https://example.com/api/user'
|
||||
}
|
||||
rules={[
|
||||
{ required: true, message: t('请输入 User Info Endpoint') },
|
||||
{ required: true, message: t('霂瑁��?User Info Endpoint') },
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
@@ -912,9 +912,9 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
placeholder="openid profile email"
|
||||
extraText={
|
||||
discoveryInfo?.scopesSupported?.length
|
||||
? t('Discovery 建议 scopes:') +
|
||||
? t('Discovery 撱箄悅 scopes嚗?) +
|
||||
discoveryInfo.scopesSupported.join(', ')
|
||||
: t('可手动填写,多个 scope 用空格分隔')
|
||||
: t('�舀��典‵�辷�憭帋葵 scope �函征�澆��?)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
@@ -924,7 +924,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
{t('字段映射')}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||
{t('配置如何从用户信息 API 响应中提取用户数据,支持 JSONPath 语法')}
|
||||
{t('�滨蔭憒��隞𡒊鍂�瑚縑�?API �滚�銝剜��𣇉鍂�瑟㺭�殷��舀� JSONPath 霂剜�')}
|
||||
</Text>
|
||||
|
||||
<Row gutter={16}>
|
||||
@@ -933,7 +933,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
field="user_id_field"
|
||||
label={t('用户 ID 字段(可选)')}
|
||||
placeholder={t('例如:sub、id、data.user.id')}
|
||||
extraText={t('用于唯一标识用户的字段路径')}
|
||||
extraText={t('�其��臭�����冽����畾菔楝敺?)}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
@@ -978,9 +978,9 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
field="auth_style"
|
||||
label={t('认证方式')}
|
||||
optionList={[
|
||||
{ value: 0, label: t('自动检测') },
|
||||
{ value: 0, label: t('�芸𢆡璉�瘚?) },
|
||||
{ value: 1, label: t('POST 参数') },
|
||||
{ value: 2, label: t('Basic Auth 头') },
|
||||
{ value: 2, label: t('Basic Auth 憭?) },
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
@@ -990,7 +990,7 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
{t('准入策略')}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||
{t('可选:基于用户信息 JSON 做组合条件准入,条件不满足时返回自定义提示')}
|
||||
{t('�舫�㚁��箔��冽�靽⊥� JSON �𡁶���辺隞嗅��伐��∩辣銝齿說頞單𧒄餈𥪜��芸�銋㗇�蝷?)}
|
||||
</Text>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
@@ -1007,12 +1007,12 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
{"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
|
||||
/>
|
||||
<Space spacing={8} style={{ marginTop: 8 }}>
|
||||
<Button size='small' theme='light' onClick={() => applyAccessPolicyTemplate('level_active')}>
|
||||
{t('填充模板:等级+激活')}
|
||||
{t('憛怠�璅⊥踎嚗𡁶�蝥?瞈�瘣?)}
|
||||
</Button>
|
||||
<Button size='small' theme='light' onClick={() => applyAccessPolicyTemplate('org_or_role')}>
|
||||
{t('填充模板:组织或角色')}
|
||||
@@ -1027,16 +1027,16 @@ const CustomOAuthSetting = ({ serverAddress }) => {
|
||||
value={formValues.access_denied_message || ''}
|
||||
onChange={(value) => mergeFormValues({ access_denied_message: value })}
|
||||
label={t('拒绝提示模板(可选)')}
|
||||
placeholder={t('例如:需要等级 {{required}},你当前等级 {{current}}')}
|
||||
placeholder={t('靘见�嚗𡁻�閬��蝥?{{required}}嚗䔶�敶枏�蝑厩漣 {{current}}')}
|
||||
extraText={t('可用变量:{{provider}} {{field}} {{op}} {{required}} {{current}} 以及 {{current.path}}')}
|
||||
showClear
|
||||
/>
|
||||
<Space spacing={8} style={{ marginTop: 8 }}>
|
||||
<Button size='small' theme='light' onClick={() => applyDeniedTemplate('level_hint')}>
|
||||
{t('填充模板:等级提示')}
|
||||
{t('憛怠�璅⊥踎嚗𡁶�蝥扳�蝷?)}
|
||||
</Button>
|
||||
<Button size='small' theme='light' onClick={() => applyDeniedTemplate('org_hint')}>
|
||||
{t('填充模板:组织提示')}
|
||||
{t('憛怠�璅⊥踎嚗𡁶�蝏��蝷?)}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -51,8 +51,7 @@ const DashboardSetting = () => {
|
||||
});
|
||||
|
||||
let [loading, setLoading] = useState(false);
|
||||
const [showMigrateModal, setShowMigrateModal] = useState(false); // 下个版本会删除
|
||||
|
||||
const [showMigrateModal, setShowMigrateModal] = useState(false); // ä¸‹ä¸ªç‰ˆæœ¬ä¼šåˆ é™?
|
||||
const getOptions = async () => {
|
||||
const res = await API.get('/api/option/');
|
||||
const { success, message, data } = res.data;
|
||||
@@ -110,7 +109,7 @@ const DashboardSetting = () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await API.post('/api/option/migrate_console_setting');
|
||||
showSuccess('旧配置迁移完成');
|
||||
showSuccess('旧�置�移完�);
|
||||
await onRefresh();
|
||||
setShowMigrateModal(false);
|
||||
} catch (err) {
|
||||
@@ -136,9 +135,8 @@ const DashboardSetting = () => {
|
||||
>
|
||||
<p>检测到旧版本的配置数据,是否要迁移到新的配置格式?</p>
|
||||
<p style={{ color: '#f57c00', marginTop: '10px' }}>
|
||||
<strong>注意:</strong>
|
||||
迁移过程中会自动处理数据格式转换,迁移完成后旧配置将被清除,请在迁移前在数据库中备份好旧配置。
|
||||
</p>
|
||||
<strong>注��/strong>
|
||||
è¿�移过程ä¸ä¼šè‡ªåЍ处ç�†æ•°æ�®æ ¼å¼�转æ�¢ï¼Œè¿�移完æˆ�å�Žæ—§é…�置将被清除,请在è¿�ç§»å‰�在数æ�®åº“ä¸å¤‡ä»½å¥½æ—§é…�ç½®ã€? </p>
|
||||
</Modal>
|
||||
|
||||
{/* 数据看板设置 */}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -53,10 +53,10 @@ const OperationSetting = () => {
|
||||
/* 顶栏模块管理 */
|
||||
HeaderNavModules: '',
|
||||
|
||||
/* 左侧边栏模块管理(管理员) */
|
||||
/* 撌虫儒颲寞�璅∪�蝞∠�嚗�恣���嚗?*/
|
||||
SidebarModulesAdmin: '',
|
||||
|
||||
/* 敏感词设置 */
|
||||
/* �𤩺�霂滩挽蝵?*/
|
||||
CheckSensitiveEnabled: false,
|
||||
CheckSensitiveOnPromptEnabled: false,
|
||||
SensitiveWords: '',
|
||||
@@ -130,11 +130,11 @@ const OperationSetting = () => {
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<SettingsHeaderNavModules options={inputs} refresh={onRefresh} />
|
||||
</div>
|
||||
{/* 左侧边栏模块管理(管理员) */}
|
||||
{/* 撌虫儒颲寞�璅∪�蝞∠�嚗�恣���嚗?*/}
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<SettingsSidebarModulesAdmin options={inputs} refresh={onRefresh} />
|
||||
</div>
|
||||
{/* 屏蔽词过滤设置 */}
|
||||
{/* 撅讛𤪖霂滩�皛方挽蝵?*/}
|
||||
<Card style={{ marginTop: '10px' }}>
|
||||
<SettingsSensitiveWords options={inputs} refresh={onRefresh} />
|
||||
</Card>
|
||||
|
||||
+24
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -96,7 +96,7 @@ const OtherSetting = () => {
|
||||
try {
|
||||
setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true }));
|
||||
await updateOption('Notice', inputs.Notice);
|
||||
showSuccess(t('公告已更新'));
|
||||
showSuccess(t('�砍�撌脫凒�?));
|
||||
} catch (error) {
|
||||
console.error(t('公告更新失败'), error);
|
||||
showError(t('公告更新失败'));
|
||||
@@ -115,7 +115,7 @@ const OtherSetting = () => {
|
||||
LEGAL_USER_AGREEMENT_KEY,
|
||||
inputs[LEGAL_USER_AGREEMENT_KEY],
|
||||
);
|
||||
showSuccess(t('用户协议已更新'));
|
||||
showSuccess(t('�冽��讛悅撌脫凒�?));
|
||||
} catch (error) {
|
||||
console.error(t('用户协议更新失败'), error);
|
||||
showError(t('用户协议更新失败'));
|
||||
@@ -137,7 +137,7 @@ const OtherSetting = () => {
|
||||
LEGAL_PRIVACY_POLICY_KEY,
|
||||
inputs[LEGAL_PRIVACY_POLICY_KEY],
|
||||
);
|
||||
showSuccess(t('隐私政策已更新'));
|
||||
showSuccess(t('�鞟��輻�撌脫凒�?));
|
||||
} catch (error) {
|
||||
console.error(t('隐私政策更新失败'), error);
|
||||
showError(t('隐私政策更新失败'));
|
||||
@@ -158,7 +158,7 @@ const OtherSetting = () => {
|
||||
SystemName: true,
|
||||
}));
|
||||
await updateOption('SystemName', inputs.SystemName);
|
||||
showSuccess(t('系统名称已更新'));
|
||||
showSuccess(t('蝟餌��滨妍撌脫凒�?));
|
||||
} catch (error) {
|
||||
console.error(t('系统名称更新失败'), error);
|
||||
showError(t('系统名称更新失败'));
|
||||
@@ -175,7 +175,7 @@ const OtherSetting = () => {
|
||||
try {
|
||||
setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: true }));
|
||||
await updateOption('Logo', inputs.Logo);
|
||||
showSuccess('Logo 已更新');
|
||||
showSuccess('Logo 撌脫凒�?);
|
||||
} catch (error) {
|
||||
console.error('Logo 更新失败', error);
|
||||
showError('Logo 更新失败');
|
||||
@@ -191,7 +191,7 @@ const OtherSetting = () => {
|
||||
HomePageContent: true,
|
||||
}));
|
||||
await updateOption(key, inputs[key]);
|
||||
showSuccess('首页内容已更新');
|
||||
showSuccess('擐㚚△��捆撌脫凒�?);
|
||||
} catch (error) {
|
||||
console.error('首页内容更新失败', error);
|
||||
showError('首页内容更新失败');
|
||||
@@ -207,7 +207,7 @@ const OtherSetting = () => {
|
||||
try {
|
||||
setLoadingInput((loadingInput) => ({ ...loadingInput, About: true }));
|
||||
await updateOption('About', inputs.About);
|
||||
showSuccess('关于内容已更新');
|
||||
showSuccess('�喃���捆撌脫凒�?);
|
||||
} catch (error) {
|
||||
console.error('关于内容更新失败', error);
|
||||
showError('关于内容更新失败');
|
||||
@@ -220,7 +220,7 @@ const OtherSetting = () => {
|
||||
try {
|
||||
setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true }));
|
||||
await updateOption('Footer', inputs.Footer);
|
||||
showSuccess('页脚内容已更新');
|
||||
showSuccess('憿菔���捆撌脫凒�?);
|
||||
} catch (error) {
|
||||
console.error('页脚内容更新失败', error);
|
||||
showError('页脚内容更新失败');
|
||||
@@ -271,7 +271,7 @@ const OtherSetting = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check for updates:', error);
|
||||
showError('检查更新失败,请稍后再试');
|
||||
showError('璉��交凒�啣仃韐伐�霂瑞��𤾸�霂?);
|
||||
} finally {
|
||||
setLoadingInput((loadingInput) => ({
|
||||
...loadingInput,
|
||||
@@ -282,7 +282,7 @@ const OtherSetting = () => {
|
||||
|
||||
const switchToDefaultFrontend = () => {
|
||||
Modal.confirm({
|
||||
title: t('切换到新版前端'),
|
||||
title: t('��揢�唳鰵���蝡?),
|
||||
content: t('切换后页面会自动刷新,并进入新版前端。是否继续?'),
|
||||
okText: t('确认切换'),
|
||||
cancelText: t('取消'),
|
||||
@@ -301,7 +301,7 @@ const OtherSetting = () => {
|
||||
showError(message);
|
||||
return;
|
||||
}
|
||||
showSuccess(t('已切换到新版前端,正在刷新页面'));
|
||||
showSuccess(t('撌脣��W��啁��滨垢嚗峕迤�典��圈△�?));
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 600);
|
||||
@@ -372,21 +372,20 @@ const OtherSetting = () => {
|
||||
<Col span={16}>
|
||||
<Space>
|
||||
<Text>
|
||||
{t('当前版本')}:
|
||||
{statusState?.status?.version || t('未知')}
|
||||
{t('敶枏���𧋦')}嚗? {statusState?.status?.version || t('�芰䰻')}
|
||||
</Text>
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={checkUpdate}
|
||||
loading={loadingInput['CheckUpdate']}
|
||||
>
|
||||
{t('检查更新')}
|
||||
{t('璉��交凒�?)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={switchToDefaultFrontend}
|
||||
loading={loadingInput['FrontendTheme']}
|
||||
>
|
||||
{t('切换到新版前端')}
|
||||
{t('��揢�唳鰵���蝡?)}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
@@ -411,7 +410,7 @@ const OtherSetting = () => {
|
||||
<Form.TextArea
|
||||
label={t('公告')}
|
||||
placeholder={t(
|
||||
'在此输入新的公告内容,支持 Markdown & HTML 代码',
|
||||
'�冽迨颲枏��啁��砍���捆嚗峕𣈲�?Markdown & HTML 隞��',
|
||||
)}
|
||||
field={'Notice'}
|
||||
onChange={handleInputChange}
|
||||
@@ -424,7 +423,7 @@ const OtherSetting = () => {
|
||||
<Form.TextArea
|
||||
label={t('用户协议')}
|
||||
placeholder={t(
|
||||
'在此输入用户协议内容,支持 Markdown & HTML 代码',
|
||||
'�冽迨颲枏��冽��讛悅��捆嚗峕𣈲�?Markdown & HTML 隞��',
|
||||
)}
|
||||
field={LEGAL_USER_AGREEMENT_KEY}
|
||||
onChange={handleInputChange}
|
||||
@@ -443,7 +442,7 @@ const OtherSetting = () => {
|
||||
<Form.TextArea
|
||||
label={t('隐私政策')}
|
||||
placeholder={t(
|
||||
'在此输入隐私政策内容,支持 Markdown & HTML 代码',
|
||||
'�冽迨颲枏��鞟��輻���捆嚗峕𣈲�?Markdown & HTML 隞��',
|
||||
)}
|
||||
field={LEGAL_PRIVACY_POLICY_KEY}
|
||||
onChange={handleInputChange}
|
||||
@@ -493,7 +492,7 @@ const OtherSetting = () => {
|
||||
<Form.TextArea
|
||||
label={t('首页内容')}
|
||||
placeholder={t(
|
||||
'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页',
|
||||
'�冽迨颲枏�擐㚚△��捆嚗峕𣈲�?Markdown & HTML 隞��嚗諹挽蝵桀�擐㚚△��𠶖��縑�臬�銝滚��曄內����𡏭��亦��臭�銝芷曎�伐��嗘�雿輻鍂霂仿曎�乩�銝?iframe �?src 撅墧�改�餈坔�霈訾�霈曄蔭隞餅�蝵煾△雿靝蛹擐㚚△',
|
||||
)}
|
||||
field={'HomePageContent'}
|
||||
onChange={handleInputChange}
|
||||
@@ -509,7 +508,7 @@ const OtherSetting = () => {
|
||||
<Form.TextArea
|
||||
label={t('关于')}
|
||||
placeholder={t(
|
||||
'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面',
|
||||
'�冽迨颲枏��啁��喃���捆嚗峕𣈲�?Markdown & HTML 隞������𡏭��亦��臭�銝芷曎�伐��嗘�雿輻鍂霂仿曎�乩�銝?iframe �?src 撅墧�改�餈坔�霈訾�霈曄蔭隞餅�蝵煾△雿靝蛹�喃�憿菟𢒰',
|
||||
)}
|
||||
field={'About'}
|
||||
onChange={handleInputChange}
|
||||
@@ -524,7 +523,7 @@ const OtherSetting = () => {
|
||||
fullMode={false}
|
||||
type='info'
|
||||
description={t(
|
||||
'移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目',
|
||||
'蝘駁膄 One API ������霂��憿駁���繮敺埈����憿寧𤌍蝏湔擪��閬��韐孵之�讐移�𨥈�憒���祇★�桀笆雿䭾��譍�嚗諹窈銝餃𢆡�舀��祇★�?,
|
||||
)}
|
||||
closeIcon={null}
|
||||
style={{ marginTop: 15 }}
|
||||
@@ -532,7 +531,7 @@ const OtherSetting = () => {
|
||||
<Form.Input
|
||||
label={t('页脚')}
|
||||
placeholder={t(
|
||||
'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码',
|
||||
'�冽迨颲枏��啁�憿菔�嚗𣬚�蝛箏�雿輻鍂暺䁅恕憿菔�嚗峕𣈲�?HTML 隞��',
|
||||
)}
|
||||
field={'Footer'}
|
||||
onChange={handleInputChange}
|
||||
@@ -545,7 +544,7 @@ const OtherSetting = () => {
|
||||
</Form>
|
||||
</Col>
|
||||
<Modal
|
||||
title={t('新版本') + ':' + updateData.tag_name}
|
||||
title={t('�啁��?) + '嚗? + updateData.tag_name}
|
||||
visible={showUpdateModal}
|
||||
onCancel={() => setShowUpdateModal(false)}
|
||||
footer={[
|
||||
|
||||
+21
-21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -64,38 +64,38 @@ const PaymentSetting = () => {
|
||||
const complianceStatements = [
|
||||
t('你已合法取得所接入模型 API、账号、密钥和额度的授权;'),
|
||||
t(
|
||||
'你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。',
|
||||
'ä½ æ‰¿è¯ºä»…åœ¨å·²å�–得上游æœ�务商ã€�模型æœ�务æ��供方或相关æ�ƒåˆ©æ–¹å�ˆæ³•授æ�ƒçš„范围内使用å…?APIã€�è´¦å�·ã€�密钥ã€�é¢�度å�Šæœ�务能力,ä¸�进行未ç»�授æ�ƒçš„转售ã€�倒å�–ã€�分销或其他è¿�规商业化使用ã€?,
|
||||
),
|
||||
t(
|
||||
'如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务;',
|
||||
'如å�‘ä¸å�Žäººæ°‘共和国境内公众æ��供生æˆ�å¼�人工智能æœ�åŠ¡ï¼Œä½ å°†ä¾�法履行备案登记ã€�安全评估ã€�内容安全ã€�投诉举报ã€�生æˆ�å�ˆæˆ�å†…å®¹æ ‡è¯†ã€�日志留å˜ã€�个人信æ�¯ä¿�护ç‰å�ˆè§„义务ï¼?,
|
||||
),
|
||||
t(
|
||||
'你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。',
|
||||
'ä½ æ‰¿è¯ºä¸�会利用本系统实施ã€�å��助实施或å�˜ç›¸å®žæ–½è¿�å��适用法律法规ã€�监管è¦�求ã€�å¹³å�°è§„则ã€�社会公共利益或第三方å�ˆæ³•æ�ƒç›Šçš„行为ã€?,
|
||||
),
|
||||
t('你理解并自行承担部署、运营和收费行为产生的法律责任。'),
|
||||
t('ä½ ç�†è§£å¹¶è‡ªè¡Œæ‰¿æ‹…部署ã€�è¿�è�¥å’Œæ”¶è´¹è¡Œä¸ºäº§ç”Ÿçš„æ³•律责任ã€?),
|
||||
t(
|
||||
'你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。',
|
||||
'ä½ ç�†è§£æœ¬å�ˆè§„æ��醒仅用于风险æ��示,ä¸�æž„æˆ�法律æ„�è§�ã€�å�ˆè§„å®¡æŸ¥ç»“è®ºæˆ–å¯¹ä½ ä½¿ç”¨æœ¬ç³»ç»Ÿè¡Œä¸ºå�ˆæ³•性的ä¿�è¯�ï¼›ä½ åº”æ ¹æ�®å®žé™…业务场景自行咨询专业法律或å�ˆè§„顾问ã€?,
|
||||
),
|
||||
];
|
||||
const requiredComplianceText = t(
|
||||
'我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任',
|
||||
'我已阅读并�解上述�规�醒,知悉相关法律风险,并确认自行承担部署���和收费行为产生的法律责�,
|
||||
);
|
||||
const requiredComplianceTextParts = [
|
||||
{
|
||||
type: 'input',
|
||||
text: t('我已阅读并理解上述合规提醒'),
|
||||
text: t('我已阅读并�解上述�规��),
|
||||
},
|
||||
{ type: 'static', text: t(',') },
|
||||
{ type: 'static', text: t('ï¼?) },
|
||||
{
|
||||
type: 'input',
|
||||
text: t('知悉相关法律风险'),
|
||||
},
|
||||
{ type: 'static', text: t(',') },
|
||||
{ type: 'static', text: t('ï¼?) },
|
||||
{
|
||||
type: 'input',
|
||||
text: t('并确认自行承担部署'),
|
||||
text: t('并确认自行承担部�),
|
||||
},
|
||||
{ type: 'static', text: t('、') },
|
||||
{ type: 'static', text: t('�) },
|
||||
{
|
||||
type: 'input',
|
||||
text: t('运营和收费行为产生的法律责任'),
|
||||
@@ -217,12 +217,12 @@ const PaymentSetting = () => {
|
||||
{!complianceConfirmed ? (
|
||||
<Banner
|
||||
type='warning'
|
||||
title={t('需要确认合规声明')}
|
||||
title={t('需�确认�规声�)}
|
||||
description={
|
||||
<div className='flex flex-col gap-2'>
|
||||
<span>
|
||||
{t(
|
||||
'确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。',
|
||||
'确认å‰�,支付ã€�å…‘æ�¢ç �ã€�订阅计划和邀请返利功能将ä¿�æŒ�é”�定ã€?,
|
||||
)}
|
||||
</span>
|
||||
<Button
|
||||
@@ -241,7 +241,7 @@ const PaymentSetting = () => {
|
||||
) : (
|
||||
<Banner
|
||||
type='success'
|
||||
title={t('合规声明已确认')}
|
||||
title={t('�规声明已确�)}
|
||||
description={t('确认时间:{{time}},确认用户:#{{userId}}', {
|
||||
time: inputs['payment_setting.compliance_confirmed_at']
|
||||
? new Date(
|
||||
@@ -275,7 +275,7 @@ const PaymentSetting = () => {
|
||||
hideSectionTitle
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('易支付设置')} itemKey='epay'>
|
||||
<Tabs.TabPane tab={t('易支付设�)} itemKey='epay'>
|
||||
<SettingsPaymentGateway
|
||||
options={inputs}
|
||||
refresh={onRefresh}
|
||||
@@ -310,16 +310,16 @@ const PaymentSetting = () => {
|
||||
visible={complianceVisible}
|
||||
title={t('确认合规声明')}
|
||||
markdownContent={t(
|
||||
'该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。',
|
||||
'该æ“�作将å�¯ç”¨æ”¯ä»˜ã€�å…‘æ�¢ç �ã€�订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明ã€?,
|
||||
)}
|
||||
checklist={complianceStatements}
|
||||
inputPrompt={t('请输入以下文字以确认:')}
|
||||
requiredText={requiredComplianceText}
|
||||
requiredTextParts={requiredComplianceTextParts}
|
||||
inputPlaceholder={t('请输入确认文案')}
|
||||
mismatchText={t('输入内容与要求文案不一致')}
|
||||
inputPlaceholder={t('请输入确认文�)}
|
||||
mismatchText={t('输入内容与�求文案�一�)}
|
||||
cancelText={t('取消')}
|
||||
confirmText={t('确认并启用')}
|
||||
confirmText={t('确认并��)}
|
||||
onCancel={() => setComplianceVisible(false)}
|
||||
onConfirm={confirmCompliance}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+21
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -35,8 +35,7 @@ import { UserContext } from '../../context/User';
|
||||
import { Modal } from '@douyinfe/semi-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// 导入子组件
|
||||
import UserInfoHeader from './personal/components/UserInfoHeader';
|
||||
// 撖澆�摮鞟�隞?import UserInfoHeader from './personal/components/UserInfoHeader';
|
||||
import AccountManagement from './personal/cards/AccountManagement';
|
||||
import NotificationSettings from './personal/cards/NotificationSettings';
|
||||
import PreferencesSettings from './personal/cards/PreferencesSettings';
|
||||
@@ -212,7 +211,7 @@ const PersonalSetting = () => {
|
||||
if (success) {
|
||||
setSystemToken(data);
|
||||
await copy(data);
|
||||
showSuccess(t('令牌已重置并已复制到剪贴板'));
|
||||
showSuccess(t('隞斤�撌脤�蝵桀僎撌脣��嗅��芾斐�?));
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@@ -233,8 +232,7 @@ const PersonalSetting = () => {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误,保留默认状态
|
||||
}
|
||||
// 敹賜裦�躰秤嚗䔶��䠷�霈斤𠶖�? }
|
||||
};
|
||||
|
||||
const startPasskeyManagementVerification = async (apiCall, options = {}) => {
|
||||
@@ -246,12 +244,12 @@ const PersonalSetting = () => {
|
||||
: null;
|
||||
|
||||
if (!requiredMethod) {
|
||||
showError(t('您需要先启用两步验证或 Passkey 才能执行此操作'));
|
||||
showError(t('�券�閬���舐鍂銝斗郊撉諹��?Passkey �滩��扯�甇斗�雿?));
|
||||
return;
|
||||
}
|
||||
|
||||
if (requiredMethod === 'passkey' && !methods.passkeySupported) {
|
||||
showInfo(t('当前设备不支持 Passkey'));
|
||||
showInfo(t('敶枏�霈曉�銝齿𣈲�?Passkey'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -314,7 +312,7 @@ const PersonalSetting = () => {
|
||||
return finishRes.data;
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') {
|
||||
showInfo(t('已取消 Passkey 注册'));
|
||||
showInfo(t('撌脣�瘨?Passkey 瘜典�'));
|
||||
return { cancelled: true };
|
||||
}
|
||||
throw new Error(error?.message || t('Passkey 注册失败,请重试'));
|
||||
@@ -325,7 +323,7 @@ const PersonalSetting = () => {
|
||||
|
||||
const handleRegisterPasskey = async () => {
|
||||
if (!passkeySupported || !window.PublicKeyCredential) {
|
||||
showInfo(t('当前设备不支持 Passkey'));
|
||||
showInfo(t('敶枏�霈曉�銝齿𣈲�?Passkey'));
|
||||
return;
|
||||
}
|
||||
await startPasskeyRegistration();
|
||||
@@ -340,7 +338,7 @@ const PersonalSetting = () => {
|
||||
throw new Error(message || t('操作失败,请重试'));
|
||||
}
|
||||
|
||||
showSuccess(t('Passkey 已解绑'));
|
||||
showSuccess(t('Passkey 撌脰圾蝏?));
|
||||
await loadPasskeyStatus();
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
@@ -374,7 +372,7 @@ const PersonalSetting = () => {
|
||||
const handleSystemTokenClick = async (e) => {
|
||||
e.target.select();
|
||||
await copy(e.target.value);
|
||||
showSuccess(t('系统令牌已复制到剪切板'));
|
||||
showSuccess(t('蝟餌�隞斤�撌脣��嗅��芸��?));
|
||||
};
|
||||
|
||||
const deleteAccount = async () => {
|
||||
@@ -404,7 +402,7 @@ const PersonalSetting = () => {
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('微信账户绑定成功!'));
|
||||
showSuccess(t('敺桐縑韐行�蝏穃��𣂼�嚗?));
|
||||
setShowWeChatBindModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -413,11 +411,11 @@ const PersonalSetting = () => {
|
||||
|
||||
const changePassword = async () => {
|
||||
// if (inputs.original_password === '') {
|
||||
// showError(t('请输入原密码!'));
|
||||
// showError(t('霂瑁��亙�撖��嚗?));
|
||||
// return;
|
||||
// }
|
||||
if (inputs.set_new_password === '') {
|
||||
showError(t('请输入新密码!'));
|
||||
showError(t('霂瑁��交鰵撖��嚗?));
|
||||
return;
|
||||
}
|
||||
if (inputs.original_password === inputs.set_new_password) {
|
||||
@@ -434,7 +432,7 @@ const PersonalSetting = () => {
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('密码修改成功!'));
|
||||
showSuccess(t('撖��靽格㺿�𣂼�嚗?));
|
||||
setShowWeChatBindModal(false);
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -467,7 +465,7 @@ const PersonalSetting = () => {
|
||||
|
||||
const bindEmail = async () => {
|
||||
if (inputs.email_verification_code === '') {
|
||||
showError(t('请输入邮箱验证码!'));
|
||||
showError(t('霂瑁��仿�蝞梢�霂��嚗?));
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@@ -477,7 +475,7 @@ const PersonalSetting = () => {
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(t('邮箱账户绑定成功!'));
|
||||
showSuccess(t('�桃拳韐行�蝏穃��𣂼�嚗?));
|
||||
setShowEmailBindModal(false);
|
||||
userState.user.email = inputs.email;
|
||||
} else {
|
||||
@@ -548,7 +546,7 @@ const PersonalSetting = () => {
|
||||
{/* 顶部用户信息区域 */}
|
||||
<UserInfoHeader t={t} userState={userState} />
|
||||
|
||||
{/* 签到日历 - 仅在启用时显示 */}
|
||||
{/* 蝑曉��亙� - 隞�銁�舐鍂�嗆遬蝷?*/}
|
||||
{status?.checkin_enabled && (
|
||||
<div className='mt-4 md:mt-6'>
|
||||
<CheckinCalendar
|
||||
@@ -560,9 +558,9 @@ const PersonalSetting = () => {
|
||||
</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='flex flex-col gap-4 md:gap-6'>
|
||||
<AccountManagement
|
||||
t={t}
|
||||
@@ -587,7 +585,7 @@ const PersonalSetting = () => {
|
||||
<PreferencesSettings t={t} />
|
||||
</div>
|
||||
|
||||
{/* 右侧:其他设置 */}
|
||||
{/* �喃儒嚗𡁜�隞𤥁挽蝵?*/}
|
||||
<NotificationSettings
|
||||
t={t}
|
||||
notificationSettings={notificationSettings}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
|
||||
+4
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -62,8 +62,7 @@ const RatioSetting = () => {
|
||||
try {
|
||||
item.value = JSON.stringify(JSON.parse(item.value), null, 2);
|
||||
} catch (e) {
|
||||
// 如果后端返回的不是合法 JSON,直接展示
|
||||
}
|
||||
// 如果�端返回的�是��JSON,直接展� }
|
||||
}
|
||||
if (['DefaultUseAutoGroup', 'ExposeRatioEnabled'].includes(item.key)) {
|
||||
newInputs[item.key] = toBoolean(item.value);
|
||||
@@ -103,7 +102,7 @@ const RatioSetting = () => {
|
||||
<Tabs.TabPane tab={t('分组相关设置')} itemKey='group'>
|
||||
<GroupRatioSettings options={inputs} refresh={onRefresh} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('未设置价格模型')} itemKey='unset_models'>
|
||||
<Tabs.TabPane tab={t('æœªè®¾ç½®ä»·æ ¼æ¨¡åž?)} itemKey='unset_models'>
|
||||
<ModelRatioNotSetEditor options={inputs} refresh={onRefresh} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab={t('上游价格同步')} itemKey='upstream_sync'>
|
||||
|
||||
+65
-76
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -104,9 +104,7 @@ const SystemSetting = () => {
|
||||
// SSRF防护配置
|
||||
'fetch_setting.enable_ssrf_protection': true,
|
||||
'fetch_setting.allow_private_ip': '',
|
||||
'fetch_setting.domain_filter_mode': false, // true 白名单,false 黑名单
|
||||
'fetch_setting.ip_filter_mode': false, // true 白名单,false 黑名单
|
||||
'fetch_setting.domain_list': [],
|
||||
'fetch_setting.domain_filter_mode': false, // true �賢��𤏪�false 暺穃��? 'fetch_setting.ip_filter_mode': false, // true �賢��𤏪�false 暺穃��? 'fetch_setting.domain_list': [],
|
||||
'fetch_setting.ip_list': [],
|
||||
'fetch_setting.allowed_ports': [],
|
||||
'fetch_setting.apply_ip_filter_for_domain': true,
|
||||
@@ -193,8 +191,7 @@ const SystemSetting = () => {
|
||||
item.value = toBoolean(item.value);
|
||||
break;
|
||||
case 'passkey.origins':
|
||||
// origins是逗号分隔的字符串,直接使用
|
||||
item.value = item.value || '';
|
||||
// origins�舫�堒噡������蝚虫葡嚗𣬚凒�乩蝙�? item.value = item.value || '';
|
||||
break;
|
||||
case 'passkey.rp_display_name':
|
||||
case 'passkey.rp_id':
|
||||
@@ -203,8 +200,7 @@ const SystemSetting = () => {
|
||||
item.value = item.value || '';
|
||||
break;
|
||||
case 'passkey.user_verification':
|
||||
// 确保有默认值
|
||||
item.value = item.value || 'preferred';
|
||||
// 蝖桐��厰�霈文�? item.value = item.value || 'preferred';
|
||||
break;
|
||||
case 'Price':
|
||||
case 'MinTopUp':
|
||||
@@ -217,8 +213,7 @@ const SystemSetting = () => {
|
||||
});
|
||||
setInputs(newInputs);
|
||||
setOriginInputs(newInputs);
|
||||
// 同步模式布尔到本地状态
|
||||
if (
|
||||
// �峕郊璅∪�撣���唳𧋦�啁𠶖�? if (
|
||||
typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined'
|
||||
) {
|
||||
setDomainFilterMode(!!newInputs['fetch_setting.domain_filter_mode']);
|
||||
@@ -275,16 +270,14 @@ const SystemSetting = () => {
|
||||
|
||||
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) => {
|
||||
showError(res.data.message);
|
||||
});
|
||||
}
|
||||
|
||||
showSuccess(t('更新成功'));
|
||||
// 更新本地状态
|
||||
const newInputs = { ...inputs };
|
||||
// �湔鰵�砍𧑐�嗆�? const newInputs = { ...inputs };
|
||||
options.forEach((opt) => {
|
||||
newInputs[opt.key] = opt.value;
|
||||
});
|
||||
@@ -365,8 +358,7 @@ const SystemSetting = () => {
|
||||
const submitSSRF = async () => {
|
||||
const options = [];
|
||||
|
||||
// 处理域名过滤模式与列表
|
||||
options.push({
|
||||
// 憭���笔�餈�誘璅∪�銝𤾸�銵? options.push({
|
||||
key: 'fetch_setting.domain_filter_mode',
|
||||
value: domainFilterMode,
|
||||
});
|
||||
@@ -377,8 +369,7 @@ const SystemSetting = () => {
|
||||
});
|
||||
}
|
||||
|
||||
// 处理IP过滤模式与列表
|
||||
options.push({
|
||||
// 憭��IP餈�誘璅∪�銝𤾸�銵? options.push({
|
||||
key: 'fetch_setting.ip_filter_mode',
|
||||
value: ipFilterMode,
|
||||
});
|
||||
@@ -416,13 +407,13 @@ const SystemSetting = () => {
|
||||
|
||||
// 检查是否已存在
|
||||
if (emailDomainWhitelist.includes(domain)) {
|
||||
showError(t('该域名已存在于白名单中'));
|
||||
showError(t('霂亙��滚歇摮睃銁鈭𡒊蒾�滚�銝?));
|
||||
return;
|
||||
}
|
||||
|
||||
setEmailDomainWhitelist([...emailDomainWhitelist, domain]);
|
||||
setEmailToAdd('');
|
||||
showSuccess(t('已添加到白名单'));
|
||||
showSuccess(t('撌脫溶�惩��賢��?));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -511,7 +502,7 @@ const SystemSetting = () => {
|
||||
!inputs['oidc.well_known'].startsWith('http://') &&
|
||||
!inputs['oidc.well_known'].startsWith('https://')
|
||||
) {
|
||||
showError(t('Well-Known URL 必须以 http:// 或 https:// 开头'));
|
||||
showError(t('Well-Known URL 敹�◆隞?http:// �?https:// 撘�憭?));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -520,7 +511,7 @@ const SystemSetting = () => {
|
||||
res.data['authorization_endpoint'];
|
||||
inputs['oidc.token_endpoint'] = res.data['token_endpoint'];
|
||||
inputs['oidc.user_info_endpoint'] = res.data['userinfo_endpoint'];
|
||||
showSuccess(t('获取 OIDC 配置成功!'));
|
||||
showSuccess(t('�瑕� OIDC �滨蔭�𣂼�嚗?));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showError(
|
||||
@@ -640,8 +631,7 @@ const SystemSetting = () => {
|
||||
};
|
||||
|
||||
const submitPasskeySettings = async () => {
|
||||
// 使用formApi直接获取当前表单值
|
||||
const formValues = formApiRef.current?.getValues() || {};
|
||||
// 雿輻鍂formApi�湔𦻖�瑕�敶枏�銵典��? const formValues = formApiRef.current?.getValues() || {};
|
||||
|
||||
const options = [];
|
||||
|
||||
@@ -745,7 +735,7 @@ const SystemSetting = () => {
|
||||
style={{ marginBottom: 20, marginTop: 16 }}
|
||||
/>
|
||||
<Text>
|
||||
{t('仅支持')}{' '}
|
||||
{t('隞�𣈲�?)}{' '}
|
||||
<a
|
||||
href='https://github.com/Calcium-Ion/new-api-worker'
|
||||
target='_blank'
|
||||
@@ -753,7 +743,7 @@ const SystemSetting = () => {
|
||||
>
|
||||
new-api-worker
|
||||
</a>{' '}
|
||||
{t('或其兼容new-api-worker格式的其他版本')}
|
||||
{t('�硋��澆捆new-api-worker�澆����隞𣇉��?)}
|
||||
</Text>
|
||||
<Row
|
||||
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
|
||||
@@ -778,7 +768,7 @@ const SystemSetting = () => {
|
||||
field='WorkerAllowHttpImageRequestEnabled'
|
||||
noLabel
|
||||
>
|
||||
{t('允许 HTTP 协议图片请求(适用于自部署代理)')}
|
||||
{t('��捂 HTTP �讛悅�曄�霂瑟�嚗���鍂鈭舘䌊�函蔡隞��嚗?)}
|
||||
</Form.Checkbox>
|
||||
<Button onClick={submitWorker}>{t('更新Worker设置')}</Button>
|
||||
</Form.Section>
|
||||
@@ -787,7 +777,7 @@ const SystemSetting = () => {
|
||||
<Card>
|
||||
<Form.Section text={t('SSRF防护设置')}>
|
||||
<Text extraText={t('SSRF防护详细说明')}>
|
||||
{t('配置服务器端请求伪造(SSRF)防护,用于保护内网资源安全')}
|
||||
{t('�滨蔭�滚𦛚�函垢霂瑟�隡芷�?SSRF)�脫擪嚗𣬚鍂鈭𦒘��文�蝵𤏸�皞𣂼��?)}
|
||||
</Text>
|
||||
<Row
|
||||
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
|
||||
@@ -796,7 +786,7 @@ const SystemSetting = () => {
|
||||
<Form.Checkbox
|
||||
field='fetch_setting.enable_ssrf_protection'
|
||||
noLabel
|
||||
extraText={t('SSRF防护开关详细说明')}
|
||||
extraText={t('SSRF�脫擪撘��唾祕蝏�秩�?)}
|
||||
onChange={(e) =>
|
||||
handleCheckboxChange(
|
||||
'fetch_setting.enable_ssrf_protection',
|
||||
@@ -826,7 +816,7 @@ const SystemSetting = () => {
|
||||
}
|
||||
>
|
||||
{t(
|
||||
'允许访问私有IP地址(127.0.0.1、192.168.x.x等内网地址)',
|
||||
'��捂霈輸䔮蝘��IP�啣�嚗?27.0.0.1�?92.168.x.x蝑匧�蝵穃𧑐��嚗?,
|
||||
)}
|
||||
</Form.Checkbox>
|
||||
</Col>
|
||||
@@ -849,10 +839,10 @@ const SystemSetting = () => {
|
||||
}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
{t('对域名启用 IP 过滤(推荐开启)')}
|
||||
{t('撖孵��滚鍳�?IP 餈�誘嚗�綫�𣂼��荔�')}
|
||||
</Form.Checkbox>
|
||||
<Text strong>
|
||||
{t(domainFilterMode ? '域名白名单' : '域名黑名单')}
|
||||
{t(domainFilterMode ? '�笔��賢��? : '�笔�暺穃��?)}
|
||||
</Text>
|
||||
<Text
|
||||
type='secondary'
|
||||
@@ -877,8 +867,8 @@ const SystemSetting = () => {
|
||||
}}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<Radio value='whitelist'>{t('白名单')}</Radio>
|
||||
<Radio value='blacklist'>{t('黑名单')}</Radio>
|
||||
<Radio value='whitelist'>{t('�賢��?)}</Radio>
|
||||
<Radio value='blacklist'>{t('暺穃��?)}</Radio>
|
||||
</Radio.Group>
|
||||
<TagInput
|
||||
value={domainList}
|
||||
@@ -902,13 +892,13 @@ const SystemSetting = () => {
|
||||
>
|
||||
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||
<Text strong>
|
||||
{t(ipFilterMode ? 'IP白名单' : 'IP黑名单')}
|
||||
{t(ipFilterMode ? 'IP�賢��? : 'IP暺穃��?)}
|
||||
</Text>
|
||||
<Text
|
||||
type='secondary'
|
||||
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>
|
||||
<Radio.Group
|
||||
type='button'
|
||||
@@ -925,8 +915,8 @@ const SystemSetting = () => {
|
||||
}}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<Radio value='whitelist'>{t('白名单')}</Radio>
|
||||
<Radio value='blacklist'>{t('黑名单')}</Radio>
|
||||
<Radio value='whitelist'>{t('�賢��?)}</Radio>
|
||||
<Radio value='blacklist'>{t('暺穃��?)}</Radio>
|
||||
</Radio.Group>
|
||||
<TagInput
|
||||
value={ipList}
|
||||
@@ -949,7 +939,7 @@ const SystemSetting = () => {
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||
<Text strong>{t('允许的端口')}</Text>
|
||||
<Text strong>{t('��捂��垢�?)}</Text>
|
||||
<Text
|
||||
type='secondary'
|
||||
style={{ display: 'block', marginBottom: 8 }}
|
||||
@@ -966,7 +956,7 @@ const SystemSetting = () => {
|
||||
'fetch_setting.allowed_ports': value,
|
||||
}));
|
||||
}}
|
||||
placeholder={t('输入端口后回车,如:80 或 8000-8999')}
|
||||
placeholder={t('颲枏�蝡臬藁�𤾸�頧佗�憒��80 �?8000-8999')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Text
|
||||
@@ -1015,7 +1005,7 @@ const SystemSetting = () => {
|
||||
handleCheckboxChange('EmailVerificationEnabled', e)
|
||||
}
|
||||
>
|
||||
{t('通过密码注册时需要进行邮箱验证')}
|
||||
{t('�朞�撖��瘜典��園�閬��銵屸�蝞梢�霂?)}
|
||||
</Form.Checkbox>
|
||||
<Form.Checkbox
|
||||
field='RegisterEnabled'
|
||||
@@ -1024,7 +1014,7 @@ const SystemSetting = () => {
|
||||
handleCheckboxChange('RegisterEnabled', e)
|
||||
}
|
||||
>
|
||||
{t('允许新用户注册')}
|
||||
{t('��捂�啁鍂�瑟釣�?)}
|
||||
</Form.Checkbox>
|
||||
<Form.Checkbox
|
||||
field='TurnstileCheckEnabled'
|
||||
@@ -1102,7 +1092,7 @@ const SystemSetting = () => {
|
||||
<Banner
|
||||
type='info'
|
||||
description={t(
|
||||
'Passkey 是基于 WebAuthn 标准的无密码身份验证方法,支持指纹、面容、硬件密钥等认证方式',
|
||||
'Passkey �臬抅鈭?WebAuthn ������撖��頨思遢撉諹��寞�嚗峕𣈲���蝥嫘��𢒰摰嫘��′隞嗅��亦�霈方��孵�',
|
||||
)}
|
||||
style={{ marginBottom: 20, marginTop: 16 }}
|
||||
/>
|
||||
@@ -1130,7 +1120,7 @@ const SystemSetting = () => {
|
||||
label={t('服务显示名称')}
|
||||
placeholder={t('默认使用系统名称')}
|
||||
extraText={t(
|
||||
"用户注册时看到的网站名称,比如'我的网站'",
|
||||
"�冽�瘜典��嗥��啁�蝵𤑳��滨妍嚗峕�憒?�𤑳�蝵𤑳�'",
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
@@ -1153,26 +1143,26 @@ const SystemSetting = () => {
|
||||
<Form.Select
|
||||
field="['passkey.user_verification']"
|
||||
label={t('安全验证级别')}
|
||||
placeholder={t('是否要求指纹/面容等生物识别')}
|
||||
placeholder={t('�臬炏閬����犒/�W捆蝑厩��抵��?)}
|
||||
optionList={[
|
||||
{
|
||||
label: t('推荐使用(用户可选)'),
|
||||
value: 'preferred',
|
||||
},
|
||||
{ label: t('强制要求'), value: 'required' },
|
||||
{ label: t('不建议使用'), value: 'discouraged' },
|
||||
{ label: t('銝滚遣霈桐蝙�?), value: 'discouraged' },
|
||||
]}
|
||||
extraText={t('推荐:用户可以选择是否使用指纹等验证')}
|
||||
extraText={t('�刻�嚗𡁶鍂�瑕虾隞仿�㗇𥋘�臬炏雿輻鍂��犒蝑厰�霂?)}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||
<Form.Select
|
||||
field="['passkey.attachment_preference']"
|
||||
label={t('设备类型偏好')}
|
||||
placeholder={t('选择支持的认证设备类型')}
|
||||
placeholder={t('�㗇𥋘�舀���恕霂�挽憭�掩�?)}
|
||||
optionList={[
|
||||
{ label: t('不限制'), value: '' },
|
||||
{ label: t('本设备内置'), value: 'platform' },
|
||||
{ label: t('銝漤��?), value: '' },
|
||||
{ label: t('�祈挽憭��蝵?), value: 'platform' },
|
||||
{ label: t('外接设备'), value: 'cross-platform' },
|
||||
]}
|
||||
extraText={t(
|
||||
@@ -1189,7 +1179,7 @@ const SystemSetting = () => {
|
||||
<Form.Checkbox
|
||||
field="['passkey.allow_insecure_origin']"
|
||||
noLabel
|
||||
extraText={t('仅用于开发环境,生产环境应使用 HTTPS')}
|
||||
extraText={t('隞�鍂鈭𤾸��𤑳㴓憓���煺漣�臬�摨𥪯蝙�?HTTPS')}
|
||||
onChange={(e) =>
|
||||
handleCheckboxChange(
|
||||
'passkey.allow_insecure_origin',
|
||||
@@ -1197,7 +1187,7 @@ const SystemSetting = () => {
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('允许不安全的 Origin(HTTP)')}
|
||||
{t('��捂銝滚��函� Origin嚗𠃍TTP嚗?)}
|
||||
</Form.Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -1208,10 +1198,10 @@ const SystemSetting = () => {
|
||||
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||
<Form.Input
|
||||
field="['passkey.origins']"
|
||||
label={t('允许的 Origins')}
|
||||
label={t('��捂�?Origins')}
|
||||
placeholder={t('填写带https的域名,逗号分隔')}
|
||||
extraText={t(
|
||||
'为空则默认使用服务器地址,多个 Origin 用逗号分隔,例如 https://newapi.pro,https://newapi.com ,注意不能携带[],需使用https',
|
||||
'銝箇征�䠷�霈支蝙�冽��∪膥�啣�嚗��銝?Origin �券�堒噡���嚗䔶�憒?https://newapi.pro,https://newapi.com ,瘜冽�銝滩��箏蒂[]嚗屸�雿輻鍂https',
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
@@ -1226,7 +1216,7 @@ const SystemSetting = () => {
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Form.Section text={t('配置邮箱域名白名单')}>
|
||||
<Form.Section text={t('�滨蔭�桃拳�笔��賢��?)}>
|
||||
<Text>{t('用以防止恶意用户利用临时邮箱批量注册')}</Text>
|
||||
<Row
|
||||
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 xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||
<Form.Checkbox
|
||||
@@ -1263,7 +1252,7 @@ const SystemSetting = () => {
|
||||
<TagInput
|
||||
value={emailDomainWhitelist}
|
||||
onChange={setEmailDomainWhitelist}
|
||||
placeholder={t('输入域名后回车')}
|
||||
placeholder={t('颲枏��笔��𤾸�頧?)}
|
||||
style={{ width: '100%', marginTop: 16 }}
|
||||
/>
|
||||
<Form.Input
|
||||
@@ -1286,13 +1275,13 @@ const SystemSetting = () => {
|
||||
onClick={submitEmailDomainWhitelist}
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
{t('保存邮箱域名白名单设置')}
|
||||
{t('靽嘥��桃拳�笔��賢��閗挽蝵?)}
|
||||
</Button>
|
||||
</Form.Section>
|
||||
</Card>
|
||||
<Card>
|
||||
<Form.Section text={t('配置 SMTP')}>
|
||||
<Text>{t('用以支持系统的邮件发送')}</Text>
|
||||
<Text>{t('�其誑�舀�蝟餌����隞嗅��?)}</Text>
|
||||
<Row
|
||||
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}>
|
||||
<Form.Input
|
||||
field='SMTPFrom'
|
||||
label={t('SMTP 发送者邮箱')}
|
||||
label={t('SMTP �煾�����蝞?)}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
||||
@@ -1355,17 +1344,17 @@ const SystemSetting = () => {
|
||||
<Form.Section text={t('配置 OIDC')}>
|
||||
<Text>
|
||||
{t(
|
||||
'用以支持通过 OIDC 登录,例如 Okta、Auth0 等兼容 OIDC 协议的 IdP',
|
||||
'�其誑�舀��朞� OIDC �餃�嚗䔶�憒?Okta��uth0 蝑匧�摰?OIDC �讛悅�?IdP',
|
||||
)}
|
||||
</Text>
|
||||
<Banner
|
||||
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 }}
|
||||
/>
|
||||
<Text>
|
||||
{t(
|
||||
'若你的 OIDC Provider 支持 Discovery Endpoint,你可以仅填写 OIDC Well-Known URL,系统会自动获取 OIDC 配置',
|
||||
'�乩��?OIDC Provider �舀� Discovery Endpoint嚗䔶��臭誑隞�‵�?OIDC Well-Known URL嚗𣬚頂蝏煺��芸𢆡�瑕� OIDC �滨蔭',
|
||||
)}
|
||||
</Text>
|
||||
<Row
|
||||
@@ -1375,14 +1364,14 @@ const SystemSetting = () => {
|
||||
<Form.Input
|
||||
field="['oidc.well_known']"
|
||||
label={t('Well-Known URL')}
|
||||
placeholder={t('请输入 OIDC 的 Well-Known URL')}
|
||||
placeholder={t('霂瑁��?OIDC �?Well-Known URL')}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||
<Form.Input
|
||||
field="['oidc.client_id']"
|
||||
label={t('Client ID')}
|
||||
placeholder={t('输入 OIDC 的 Client ID')}
|
||||
placeholder={t('颲枏� OIDC �?Client ID')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -1401,7 +1390,7 @@ const SystemSetting = () => {
|
||||
<Form.Input
|
||||
field="['oidc.authorization_endpoint']"
|
||||
label={t('Authorization Endpoint')}
|
||||
placeholder={t('输入 OIDC 的 Authorization Endpoint')}
|
||||
placeholder={t('颲枏� OIDC �?Authorization Endpoint')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -1412,14 +1401,14 @@ const SystemSetting = () => {
|
||||
<Form.Input
|
||||
field="['oidc.token_endpoint']"
|
||||
label={t('Token Endpoint')}
|
||||
placeholder={t('输入 OIDC 的 Token Endpoint')}
|
||||
placeholder={t('颲枏� OIDC �?Token Endpoint')}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||
<Form.Input
|
||||
field="['oidc.user_info_endpoint']"
|
||||
label={t('User Info Endpoint')}
|
||||
placeholder={t('输入 OIDC 的 Userinfo Endpoint')}
|
||||
placeholder={t('颲枏� OIDC �?Userinfo Endpoint')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -1434,7 +1423,7 @@ const SystemSetting = () => {
|
||||
<Text>{t('用以支持通过 GitHub 进行登录注册')}</Text>
|
||||
<Banner
|
||||
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 }}
|
||||
/>
|
||||
<Row
|
||||
@@ -1465,7 +1454,7 @@ const SystemSetting = () => {
|
||||
<Text>{t('用以支持通过 Discord 进行登录注册')}</Text>
|
||||
<Banner
|
||||
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 }}
|
||||
/>
|
||||
<Row
|
||||
@@ -1511,7 +1500,7 @@ const SystemSetting = () => {
|
||||
</Text>
|
||||
<Banner
|
||||
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 }}
|
||||
/>
|
||||
<Row
|
||||
@@ -1521,7 +1510,7 @@ const SystemSetting = () => {
|
||||
<Form.Input
|
||||
field='LinuxDOClientId'
|
||||
label={t('Linux DO Client ID')}
|
||||
placeholder={t('输入你注册的 LinuxDO OAuth APP 的 ID')}
|
||||
placeholder={t('颲枏�雿䭾釣�𣬚� LinuxDO OAuth APP �?ID')}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={10} lg={10} xl={10}>
|
||||
@@ -1536,7 +1525,7 @@ const SystemSetting = () => {
|
||||
<Form.Input
|
||||
field='LinuxDOMinimumTrustLevel'
|
||||
label='LinuxDO Minimum Trust Level'
|
||||
placeholder='允许注册的最低信任等级'
|
||||
placeholder='��捂瘜典����雿𦒘縑隞餌�蝥?
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -1648,7 +1637,7 @@ const SystemSetting = () => {
|
||||
>
|
||||
<p>
|
||||
{t(
|
||||
'您确定要取消密码登录功能吗?这可能会影响用户的登录方式。',
|
||||
'�函&摰朞��𡝗�撖���餃��蠘��梹�餈坔虾�賭�敶勗��冽���蒈敶閙䲮撘譌�?,
|
||||
)}
|
||||
</p>
|
||||
</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
|
||||
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
|
||||
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';
|
||||
@@ -74,7 +74,7 @@ const AccountManagement = ({
|
||||
}) => {
|
||||
const renderAccountInfo = (accountId, label) => {
|
||||
if (!accountId || accountId === '') {
|
||||
return <span className='text-gray-500'>{t('未绑定')}</span>;
|
||||
return <span className='text-gray-500'>{t('�芰�摰?)}</span>;
|
||||
}
|
||||
|
||||
const popContent = (
|
||||
@@ -120,7 +120,7 @@ const AccountManagement = ({
|
||||
const handleUnbindCustomOAuth = async (providerId, providerName) => {
|
||||
Modal.confirm({
|
||||
title: t('确认解绑'),
|
||||
content: t('确定要解绑 {{name}} 吗?', { name: providerName }),
|
||||
content: t('蝖桀�閬�圾蝏?{{name}} �梹�', { name: providerName }),
|
||||
okText: t('确认'),
|
||||
cancelText: t('取消'),
|
||||
onOk: async () => {
|
||||
@@ -251,10 +251,10 @@ const AccountManagement = ({
|
||||
</div>
|
||||
<div className='text-sm text-gray-500 truncate'>
|
||||
{!status.wechat_login
|
||||
? t('未启用')
|
||||
? t('�芸鍳�?)
|
||||
: isBound(userState.user?.wechat_id)
|
||||
? t('已绑定')
|
||||
: t('未绑定')}
|
||||
? t('撌脩�摰?)
|
||||
: t('�芰�摰?)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -270,7 +270,7 @@ const AccountManagement = ({
|
||||
? t('修改绑定')
|
||||
: status.wechat_login
|
||||
? t('绑定')
|
||||
: t('未启用')}
|
||||
: t('�芸鍳�?)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -311,7 +311,7 @@ const AccountManagement = ({
|
||||
!status.github_oauth
|
||||
}
|
||||
>
|
||||
{status.github_oauth ? t('绑定') : t('未启用')}
|
||||
{status.github_oauth ? t('蝏穃�') : t('�芸鍳�?)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -352,7 +352,7 @@ const AccountManagement = ({
|
||||
!status.discord_oauth
|
||||
}
|
||||
>
|
||||
{status.discord_oauth ? t('绑定') : t('未启用')}
|
||||
{status.discord_oauth ? t('蝏穃�') : t('�芸鍳�?)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -395,7 +395,7 @@ const AccountManagement = ({
|
||||
isBound(userState.user?.oidc_id) || !status.oidc_enabled
|
||||
}
|
||||
>
|
||||
{status.oidc_enabled ? t('绑定') : t('未启用')}
|
||||
{status.oidc_enabled ? t('蝏穃�') : t('�芸鍳�?)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -432,7 +432,7 @@ const AccountManagement = ({
|
||||
type='primary'
|
||||
theme='outline'
|
||||
>
|
||||
{t('已绑定')}
|
||||
{t('撌脩�摰?)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -451,7 +451,7 @@ const AccountManagement = ({
|
||||
type='primary'
|
||||
theme='outline'
|
||||
>
|
||||
{t('未启用')}
|
||||
{t('�芸鍳�?)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -511,13 +511,13 @@ const AccountManagement = ({
|
||||
!status.linuxdo_oauth
|
||||
}
|
||||
>
|
||||
{status.linuxdo_oauth ? t('绑定') : t('未启用')}
|
||||
{status.linuxdo_oauth ? t('蝏穃�') : t('�芸鍳�?)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 自定义 OAuth 提供商绑定 */}
|
||||
{/* �芸�銋?OAuth �𣂷����摰?*/}
|
||||
{status.custom_oauth_providers &&
|
||||
status.custom_oauth_providers.map((provider) => {
|
||||
const bound = isCustomOAuthBound(provider.id);
|
||||
@@ -542,7 +542,7 @@ const AccountManagement = ({
|
||||
binding?.provider_user_id,
|
||||
t('{{name}} ID', { name: provider.name }),
|
||||
)
|
||||
: t('未绑定')}
|
||||
: t('�芰�摰?)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -603,7 +603,7 @@ const AccountManagement = ({
|
||||
{t('系统访问令牌')}
|
||||
</Typography.Title>
|
||||
<Typography.Text type='tertiary' className='text-sm'>
|
||||
{t('用于API调用的身份验证令牌,请妥善保管')}
|
||||
{t('�其�API靚�鍂��澈隞賡�霂�誘�䕘�霂瑕戎���蝞?)}
|
||||
</Typography.Text>
|
||||
{systemToken && (
|
||||
<div className='mt-3'>
|
||||
@@ -642,7 +642,7 @@ const AccountManagement = ({
|
||||
{t('密码管理')}
|
||||
</Typography.Title>
|
||||
<Typography.Text type='tertiary' className='text-sm'>
|
||||
{t('定期更改密码可以提高账户安全性')}
|
||||
{t('摰𡁏��湔㺿撖���臭誑�鞾�韐行�摰匧��?)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
@@ -671,26 +671,26 @@ const AccountManagement = ({
|
||||
</Typography.Title>
|
||||
<Typography.Text type='tertiary' className='text-sm'>
|
||||
{passkeyEnabled
|
||||
? t('已启用 Passkey,无需密码即可登录')
|
||||
: t('使用 Passkey 实现免密且更安全的登录体验')}
|
||||
? t('撌脣鍳�?Passkey嚗峕���撖���喳虾�餃�')
|
||||
: t('雿輻鍂 Passkey 摰䂿緵�滚�銝娍凒摰匧���蒈敶蓥�撉?)}
|
||||
</Typography.Text>
|
||||
<div className='mt-2 text-xs text-gray-500 space-y-1'>
|
||||
<div>
|
||||
{t('最后使用时间')}:{lastUsedLabel}
|
||||
{t('���𦒘蝙�冽𧒄�?)}嚗㝯lastUsedLabel}
|
||||
</div>
|
||||
{/*{passkeyEnabled && (*/}
|
||||
{/* <div>*/}
|
||||
{/* {t('备份支持')}:*/}
|
||||
{/* {t('憭�遢�舀�')}嚗?/}
|
||||
{/* {passkeyStatus?.backup_eligible*/}
|
||||
{/* ? t('支持备份')*/}
|
||||
{/* : t('不支持')}*/}
|
||||
{/* ,{t('备份状态')}:*/}
|
||||
{/* {passkeyStatus?.backup_state ? t('已备份') : t('未备份')}*/}
|
||||
{/* : t('銝齿𣈲�?)}*/}
|
||||
{/* 嚗庙t('憭�遢�嗆�?)}嚗?/}
|
||||
{/* {passkeyStatus?.backup_state ? t('撌脣�隞?) : t('�芸�隞?)}*/}
|
||||
{/* </div>*/}
|
||||
{/*)}*/}
|
||||
{!passkeySupported && (
|
||||
<div className='text-amber-600'>
|
||||
{t('当前设备不支持 Passkey')}
|
||||
{t('敶枏�霈曉�銝齿𣈲�?Passkey')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -747,7 +747,7 @@ const AccountManagement = ({
|
||||
{t('删除账户')}
|
||||
</Typography.Title>
|
||||
<Typography.Text type='tertiary' className='text-sm'>
|
||||
{t('此操作不可逆,所有数据将被永久删除')}
|
||||
{t('甇斗�雿靝��舫������㗇㺭�桀�鋡急偶銋���?)}
|
||||
</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
|
||||
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
|
||||
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';
|
||||
@@ -57,13 +57,10 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
const [currentMonth, setCurrentMonth] = useState(
|
||||
new Date().toISOString().slice(0, 7),
|
||||
);
|
||||
// 初始加载状态,用于避免折叠状态闪烁
|
||||
const [initialLoaded, setInitialLoaded] = useState(false);
|
||||
// 折叠状态:null 表示未确定(等待首次加载)
|
||||
const [isCollapsed, setIsCollapsed] = useState(null);
|
||||
// åˆ�å§‹åŠ è½½çŠ¶æ€�,用于é�¿å…�折å� 状æ€�é—ªçƒ? const [initialLoaded, setInitialLoaded] = useState(false);
|
||||
// 折å� 状æ€�:null 表示未确定(ç‰å¾…é¦–æ¬¡åŠ è½½ï¼? const [isCollapsed, setIsCollapsed] = useState(null);
|
||||
|
||||
// 创建日期到额度的映射,方便快速查找
|
||||
const checkinRecordsMap = useMemo(() => {
|
||||
// 创建日期到é¢�åº¦çš„æ˜ å°„ï¼Œæ–¹ä¾¿å¿«é€ŸæŸ¥æ‰? const checkinRecordsMap = useMemo(() => {
|
||||
const map = {};
|
||||
const records = checkinData.stats?.records || [];
|
||||
records.forEach((record) => {
|
||||
@@ -72,8 +69,7 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
return map;
|
||||
}, [checkinData.stats?.records]);
|
||||
|
||||
// 计算本月获得的额度
|
||||
const monthlyQuota = useMemo(() => {
|
||||
// 计算本月获得的�� const monthlyQuota = useMemo(() => {
|
||||
const records = checkinData.stats?.records || [];
|
||||
return records.reduce(
|
||||
(sum, record) => sum + (record.quota_awarded || 0),
|
||||
@@ -81,8 +77,7 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
);
|
||||
}, [checkinData.stats?.records]);
|
||||
|
||||
// 获取签到状态
|
||||
const fetchCheckinStatus = async (month) => {
|
||||
// 获å�–ç¾åˆ°çжæ€? const fetchCheckinStatus = async (month) => {
|
||||
const isFirstLoad = !initialLoaded;
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -90,20 +85,19 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
const { success, data, message } = res.data;
|
||||
if (success) {
|
||||
setCheckinData(data);
|
||||
// 首次加载时,根据签到状态设置折叠状态
|
||||
if (isFirstLoad) {
|
||||
// é¦–æ¬¡åŠ è½½æ—¶ï¼Œæ ¹æ�®ç¾åˆ°çжæ€�设置折å� 状æ€? if (isFirstLoad) {
|
||||
setIsCollapsed(data.stats?.checked_in_today ?? false);
|
||||
setInitialLoaded(true);
|
||||
}
|
||||
} else {
|
||||
showError(message || t('获取签到状态失败'));
|
||||
showError(message || t('获å�–ç¾åˆ°çжæ€�失è´?));
|
||||
if (isFirstLoad) {
|
||||
setIsCollapsed(false);
|
||||
setInitialLoaded(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('获取签到状态失败'));
|
||||
showError(t('获å�–ç¾åˆ°çжæ€�失è´?));
|
||||
if (isFirstLoad) {
|
||||
setIsCollapsed(false);
|
||||
setInitialLoaded(true);
|
||||
@@ -133,10 +127,9 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
const { success, data, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(
|
||||
t('签到成功!获得') + ' ' + renderQuota(data.quota_awarded),
|
||||
t('ç¾åˆ°æˆ�功ï¼�获å¾?) + ' ' + renderQuota(data.quota_awarded),
|
||||
);
|
||||
// 刷新签到状态
|
||||
fetchCheckinStatus(currentMonth);
|
||||
// 刷新ç¾åˆ°çжæ€? fetchCheckinStatus(currentMonth);
|
||||
setTurnstileModalVisible(false);
|
||||
} else {
|
||||
if (!token && shouldTriggerTurnstile(message)) {
|
||||
@@ -165,16 +158,13 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
}
|
||||
}, [status?.checkin_enabled, currentMonth]);
|
||||
|
||||
// 如果签到功能未启用,不显示组件
|
||||
if (!status?.checkin_enabled) {
|
||||
// 如果ç¾åˆ°åŠŸèƒ½æœªå�¯ç”¨ï¼Œä¸�显示组ä»? if (!status?.checkin_enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 日期渲染函数 - 显示签到状态和获得的额度
|
||||
const dateRender = (dateString) => {
|
||||
// Semi Calendar 传入的 dateString 是 Date.toString() 格式
|
||||
// 需要转换为 YYYY-MM-DD 格式来匹配后端数据
|
||||
const date = new Date(dateString);
|
||||
// 日期渲染函数 - 显示ç¾åˆ°çжæ€�和获得的é¢�åº? const dateRender = (dateString) => {
|
||||
// Semi Calendar ä¼ å…¥çš?dateString æ˜?Date.toString() æ ¼å¼�
|
||||
// 需è¦�转æ�¢ä¸º YYYY-MM-DD æ ¼å¼�æ�¥åŒ¹é…�å�Žç«¯æ•°æ�? const date = new Date(dateString);
|
||||
if (isNaN(date.getTime())) {
|
||||
return null;
|
||||
}
|
||||
@@ -260,12 +250,12 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 dark:text-gray-400'>
|
||||
{!initialLoaded
|
||||
? t('正在加载签到状态...')
|
||||
? t('æ£åœ¨åŠ è½½ç¾åˆ°çжæ€?..')
|
||||
: checkinData.stats?.checked_in_today
|
||||
? t('今日已签到,累计签到') +
|
||||
` ${checkinData.stats?.total_checkins || 0} ` +
|
||||
t('天')
|
||||
: t('每日签到可获得随机额度奖励')}
|
||||
t('�)
|
||||
: t('æ¯�æ—¥ç¾åˆ°å�¯èŽ·å¾—éš�机é¢�度奖åŠ?)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -279,14 +269,14 @@ const CheckinCalendar = ({ t, status, turnstileEnabled, turnstileSiteKey }) => {
|
||||
className='!bg-green-600 hover:!bg-green-700'
|
||||
>
|
||||
{!initialLoaded
|
||||
? t('加载中...')
|
||||
? t('åŠ è½½ä¸?..')
|
||||
: checkinData.stats?.checked_in_today
|
||||
? t('今日已签到')
|
||||
? t('今日已ç¾åˆ?)
|
||||
: t('立即签到')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 可折叠内容 */}
|
||||
{/* å�¯æŠ˜å� 内å®?*/}
|
||||
<Collapsible isOpen={isCollapsed === false} keepDOM>
|
||||
{/* 签到统计 */}
|
||||
<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'>
|
||||
<Typography.Text type='tertiary' className='text-xs'>
|
||||
<ul className='list-disc list-inside space-y-0.5'>
|
||||
<li>{t('每日签到可获得随机额度奖励')}</li>
|
||||
<li>{t('æ¯�æ—¥ç¾åˆ°å�¯èŽ·å¾—éš�机é¢�度奖åŠ?)}</li>
|
||||
<li>{t('签到奖励将直接添加到您的账户余额')}</li>
|
||||
<li>{t('每日仅可签到一次,请勿重复签到')}</li>
|
||||
</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
|
||||
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
|
||||
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';
|
||||
@@ -44,8 +44,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
|
||||
return savedState ? JSON.parse(savedState) : false;
|
||||
});
|
||||
const [activeModelCategory, setActiveModelCategory] = useState('all');
|
||||
const MODELS_DISPLAY_COUNT = 25; // 默认显示的模型数量
|
||||
|
||||
const MODELS_DISPLAY_COUNT = 25; // 暺䁅恕�曄內��芋�𧢲㺭�?
|
||||
// Save models expanded state to localStorage whenever it changes
|
||||
useEffect(() => {
|
||||
localStorage.setItem('modelsExpanded', JSON.stringify(isModelsExpanded));
|
||||
@@ -63,7 +62,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
|
||||
{t('可用模型')}
|
||||
</Typography.Text>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('查看当前可用的所有模型')}
|
||||
{t('�亦�敶枏��舐鍂����㗇芋�?)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +70,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
|
||||
{/* 可用模型部分 */}
|
||||
<div className='bg-gray-50 dark:bg-gray-800 rounded-xl'>
|
||||
{modelsLoading ? (
|
||||
// 骨架屏加载状态 - 模拟实际加载后的布局
|
||||
// 撉冽沲撅誩�頧賜𠶖�?- 璅⊥�摰鮋��㰘蝸�𡒊�撣��
|
||||
<div className='space-y-4'>
|
||||
{/* 模拟分类标签 */}
|
||||
<div
|
||||
@@ -124,7 +123,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 模型分类标签页 */}
|
||||
{/* 璅∪���掩��倌憿?*/}
|
||||
<div className='mb-4'>
|
||||
<Tabs
|
||||
type='card'
|
||||
@@ -135,8 +134,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
|
||||
>
|
||||
{Object.entries(getModelCategories(t)).map(
|
||||
([key, category]) => {
|
||||
// 计算该分类下的模型数量
|
||||
const modelCount =
|
||||
// 霈∠�霂亙�蝐颱���芋�𧢲㺭�? const modelCount =
|
||||
key === 'all'
|
||||
? models.length
|
||||
: 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'>
|
||||
{(() => {
|
||||
// 根据当前选中的分类过滤模型
|
||||
const categories = getModelCategories(t);
|
||||
// �寞旿敶枏��劐葉���蝐餉�皛斗芋�? const categories = getModelCategories(t);
|
||||
const filteredModels =
|
||||
activeModelCategory === 'all'
|
||||
? models
|
||||
@@ -186,8 +183,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
|
||||
}),
|
||||
);
|
||||
|
||||
// 如果过滤后没有模型,显示空状态
|
||||
if (filteredModels.length === 0) {
|
||||
// 憒��餈�誘�擧瓷�㗇芋�页��曄內蝛箇𠶖�? if (filteredModels.length === 0) {
|
||||
return (
|
||||
<Empty
|
||||
image={
|
||||
@@ -261,7 +257,7 @@ const ModelsList = ({ t, models, modelsLoading, copyText }) => {
|
||||
>
|
||||
{t('更多')}{' '}
|
||||
{filteredModels.length - MODELS_DISPLAY_COUNT}{' '}
|
||||
{t('个模型')}
|
||||
{t('銝芣芋�?)}
|
||||
</Tag>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
+53
-57
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
Copyright (C) 2025 modelstoken
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
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
|
||||
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';
|
||||
@@ -60,8 +60,7 @@ const NotificationSettings = ({
|
||||
const [userState] = useContext(UserContext);
|
||||
const isAdminOrRoot = (userState?.user?.role || 0) >= 10;
|
||||
|
||||
// 左侧边栏设置相关状态
|
||||
const [sidebarLoading, setSidebarLoading] = useState(false);
|
||||
// 撌虫儒颲寞�霈曄蔭�詨��嗆�? const [sidebarLoading, setSidebarLoading] = useState(false);
|
||||
const [activeTabKey, setActiveTabKey] = useState('notification');
|
||||
const [sidebarModulesUser, setSidebarModulesUser] = useState({
|
||||
chat: {
|
||||
@@ -141,10 +140,9 @@ const NotificationSettings = ({
|
||||
sidebar_modules: JSON.stringify(sidebarModulesUser),
|
||||
});
|
||||
if (res.data.success) {
|
||||
showSuccess(t('侧边栏设置保存成功'));
|
||||
showSuccess(t('靘扯器�讛挽蝵桐�摮䀹��?));
|
||||
|
||||
// 刷新useSidebar钩子中的用户配置,实现实时更新
|
||||
await refreshUserConfig();
|
||||
// �瑟鰵useSidebar�拙�銝剔��冽��滨蔭嚗���啣��嗆凒�? await refreshUserConfig();
|
||||
} else {
|
||||
showError(res.data.message);
|
||||
}
|
||||
@@ -217,8 +215,7 @@ const NotificationSettings = ({
|
||||
loadSidebarConfigs();
|
||||
}, [statusState]);
|
||||
|
||||
// 初始化表单值
|
||||
useEffect(() => {
|
||||
// �嘥��𤥁”�訫�? useEffect(() => {
|
||||
if (formApiRef.current && notificationSettings) {
|
||||
formApiRef.current.setValues(notificationSettings);
|
||||
}
|
||||
@@ -229,8 +226,7 @@ const NotificationSettings = ({
|
||||
handleNotificationSettingChange(field, value);
|
||||
};
|
||||
|
||||
// 检查功能是否被管理员允许
|
||||
const isAllowedByAdmin = (sectionKey, moduleKey = null) => {
|
||||
// 璉��亙��賣糓�西◤蝞∠��睃�霈? const isAllowedByAdmin = (sectionKey, moduleKey = null) => {
|
||||
if (!adminConfig) return true;
|
||||
|
||||
if (moduleKey) {
|
||||
@@ -251,7 +247,7 @@ const NotificationSettings = ({
|
||||
modules: [
|
||||
{
|
||||
key: 'playground',
|
||||
title: t('操练场'),
|
||||
title: t('�滨��?),
|
||||
description: t('AI模型测试环境'),
|
||||
},
|
||||
{ key: 'chat', title: t('聊天'), description: t('聊天会话管理') },
|
||||
@@ -259,8 +255,8 @@ const NotificationSettings = ({
|
||||
},
|
||||
{
|
||||
key: 'console',
|
||||
title: t('控制台区域'),
|
||||
description: t('数据管理和日志查看'),
|
||||
title: t('�批��啣躹�?),
|
||||
description: t('�唳旿蝞∠��峕𠯫敹埈䰻�?),
|
||||
modules: [
|
||||
{ key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
|
||||
{ key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
|
||||
@@ -278,7 +274,7 @@ const NotificationSettings = ({
|
||||
title: t('个人中心区域'),
|
||||
description: t('用户个人功能'),
|
||||
modules: [
|
||||
{ key: 'topup', title: t('钱包管理'), description: t('余额充值管理') },
|
||||
{ key: 'topup', title: t('�勗�蝞∠�'), description: t('雿䠷����潛恣�?) },
|
||||
{
|
||||
key: 'personal',
|
||||
title: t('个人设置'),
|
||||
@@ -289,7 +285,7 @@ const NotificationSettings = ({
|
||||
// 管理员区域:根据后端权限控制显示
|
||||
{
|
||||
key: 'admin',
|
||||
title: t('管理员区域'),
|
||||
title: t('蝞∠��睃躹�?),
|
||||
description: t('系统管理功能'),
|
||||
modules: [
|
||||
{ key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
|
||||
@@ -306,8 +302,8 @@ const NotificationSettings = ({
|
||||
},
|
||||
{
|
||||
key: 'redemption',
|
||||
title: t('兑换码管理'),
|
||||
description: t('兑换码生成管理'),
|
||||
title: t('�烐揢��恣�?),
|
||||
description: t('�烐揢����鞟恣�?),
|
||||
},
|
||||
{ key: 'user', title: t('用户管理'), description: t('用户账户管理') },
|
||||
{
|
||||
@@ -344,7 +340,7 @@ const NotificationSettings = ({
|
||||
})
|
||||
.catch((errors) => {
|
||||
console.log('表单验证失败:', errors);
|
||||
Toast.error(t('请检查表单填写是否正确'));
|
||||
Toast.error(t('霂瑟��亥”�訫‵�蹱糓�行迤蝖?));
|
||||
});
|
||||
} else {
|
||||
saveNotificationSettings();
|
||||
@@ -364,7 +360,7 @@ const NotificationSettings = ({
|
||||
onClick={resetSidebarModules}
|
||||
className='!rounded-lg'
|
||||
>
|
||||
{t('重置为默认')}
|
||||
{t('�滨蔭銝粹�霈?)}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
@@ -438,13 +434,13 @@ const NotificationSettings = ({
|
||||
field='warningThreshold'
|
||||
label={
|
||||
<span>
|
||||
{t('额度预警阈值')}{' '}
|
||||
{t('憸嘥漲憸�郎���?)}{' '}
|
||||
{renderQuotaWithPrompt(
|
||||
notificationSettings.warningThreshold,
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
placeholder={t('请输入预警额度')}
|
||||
placeholder={t('霂瑁��仿�霅阡�摨?)}
|
||||
data={[
|
||||
{ value: 100000, label: '0.2$' },
|
||||
{ value: 500000, label: '1$' },
|
||||
@@ -458,7 +454,7 @@ const NotificationSettings = ({
|
||||
)}
|
||||
style={{ width: '100%', maxWidth: '300px' }}
|
||||
rules={[
|
||||
{ required: true, message: t('请输入预警阈值') },
|
||||
{ required: true, message: t('霂瑁��仿�霅阡��?) },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
const numValue = Number(value);
|
||||
@@ -476,12 +472,12 @@ const NotificationSettings = ({
|
||||
field='upstreamModelUpdateNotifyEnabled'
|
||||
label={t('接收上游模型更新通知')}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
uncheckedText={t('�?)}
|
||||
onChange={(value) =>
|
||||
handleFormChange('upstreamModelUpdateNotifyEnabled', value)
|
||||
}
|
||||
extraText={t(
|
||||
'仅管理员可用。开启后,当系统定时检测全部渠道发现上游模型变更或检测异常时,将按你选择的通知方式发送汇总通知;渠道或模型过多时会自动省略部分明细。',
|
||||
'隞�恣����舐鍂����臬�嚗��蝟餌�摰𡁏𧒄璉�瘚见��冽��枏��唬�皜豢芋�见��湔�璉�瘚见�撣豢𧒄嚗���劐��㗇𥋘���𡁶䰻�孵��煾����駁�𡁶䰻嚗𥟇��𤘪�璅∪�餈���嗡��芸𢆡��裦�典��𡒊��?,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -497,7 +493,7 @@ const NotificationSettings = ({
|
||||
}
|
||||
prefix={<IconMail />}
|
||||
extraText={t(
|
||||
'设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱',
|
||||
'霈曄蔭�其��交𤣰憸嘥漲憸�郎���蝞勗𧑐��嚗䔶�憛怠�雿輻鍂韐血噡蝏穃����蝞?,
|
||||
)}
|
||||
showClear
|
||||
/>
|
||||
@@ -510,7 +506,7 @@ const NotificationSettings = ({
|
||||
field='webhookUrl'
|
||||
label={t('Webhook地址')}
|
||||
placeholder={t(
|
||||
'请输入Webhook地址,例如: https://example.com/webhook',
|
||||
'霂瑁��凹ebhook�啣�嚗䔶�憒? https://example.com/webhook',
|
||||
)}
|
||||
onChange={(val) => handleFormChange('webhookUrl', val)}
|
||||
prefix={<IconLink />}
|
||||
@@ -526,7 +522,7 @@ const NotificationSettings = ({
|
||||
},
|
||||
{
|
||||
pattern: /^https:\/\/.+/,
|
||||
message: t('Webhook地址必须以https://开头'),
|
||||
message: t('Webhook�啣�敹�◆隞去ttps://撘�憭?),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -534,11 +530,11 @@ const NotificationSettings = ({
|
||||
<Form.Input
|
||||
field='webhookSecret'
|
||||
label={t('接口凭证')}
|
||||
placeholder={t('请输入密钥')}
|
||||
placeholder={t('霂瑁��亙��?)}
|
||||
onChange={(val) => handleFormChange('webhookSecret', val)}
|
||||
prefix={<IconKey />}
|
||||
extraText={t(
|
||||
'密钥将以Bearer方式添加到请求头中,用于验证webhook请求的合法性',
|
||||
'撖�𤨎撠�誑Bearer�孵�瘛餃��啗窈瘙�仍銝哨��其�撉諹�webhook霂瑟����瘜閙�?,
|
||||
)}
|
||||
showClear
|
||||
/>
|
||||
@@ -569,14 +565,14 @@ const NotificationSettings = ({
|
||||
</div>
|
||||
<div>
|
||||
<strong>content:</strong>{' '}
|
||||
{t('通知内容,支持 {{value}} 变量占位符')}
|
||||
{t('�𡁶䰻��捆嚗峕𣈲�?{{value}} �㗛��牐�蝚?)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>values:</strong>{' '}
|
||||
{t('按顺序替换content中的变量占位符')}
|
||||
{t('�厰◇摨𤩺𤜯�▂ontent銝剔��㗛��牐�蝚?)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>timestamp:</strong> {t('Unix时间戳')}
|
||||
<strong>timestamp:</strong> {t('Unix�園𡢿�?)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -584,19 +580,19 @@ const NotificationSettings = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Bark推送设置 */}
|
||||
{/* Bark�券��挽蝵?*/}
|
||||
{notificationSettings.warningType === 'bark' && (
|
||||
<>
|
||||
<Form.Input
|
||||
field='barkUrl'
|
||||
label={t('Bark推送URL')}
|
||||
placeholder={t(
|
||||
'请输入Bark推送URL,例如: https://api.day.app/yourkey/{{title}}/{{content}}',
|
||||
'霂瑁��且ark�券��RL嚗䔶�憒? https://api.day.app/yourkey/{{title}}/{{content}}',
|
||||
)}
|
||||
onChange={(val) => handleFormChange('barkUrl', val)}
|
||||
prefix={<IconLink />}
|
||||
extraText={t(
|
||||
'支持HTTP和HTTPS,模板变量: {{title}} (通知标题), {{content}} (通知内容)',
|
||||
'�舀�HTTP�峵TTPS嚗峕芋�踹��? {{title}} (�𡁶䰻���), {{content}} (�𡁶䰻��捆)',
|
||||
)}
|
||||
showClear
|
||||
rules={[
|
||||
@@ -606,7 +602,7 @@ const NotificationSettings = ({
|
||||
},
|
||||
{
|
||||
pattern: /^https?:\/\/.+/,
|
||||
message: t('Bark推送URL必须以http://或https://开头'),
|
||||
message: t('Bark�券��RL敹�◆隞去ttp://�𧬆ttps://撘�憭?),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -621,14 +617,14 @@ const NotificationSettings = ({
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 space-y-2'>
|
||||
<div>
|
||||
• <strong>{'title'}:</strong> {t('通知标题')}
|
||||
�?<strong>{'title'}:</strong> {t('�𡁶䰻���')}
|
||||
</div>
|
||||
<div>
|
||||
• <strong>{'content'}:</strong> {t('通知内容')}
|
||||
�?<strong>{'content'}:</strong> {t('�𡁶䰻��捆')}
|
||||
</div>
|
||||
<div className='mt-3 pt-3 border-t border-gray-200'>
|
||||
<span className='text-gray-400'>
|
||||
{t('更多参数请参考')}
|
||||
{t('�游���㺭霂瑕��?)}
|
||||
</span>{' '}
|
||||
<a
|
||||
href='https://github.com/Finb/Bark'
|
||||
@@ -644,14 +640,14 @@ const NotificationSettings = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Gotify推送设置 */}
|
||||
{/* Gotify�券��挽蝵?*/}
|
||||
{notificationSettings.warningType === 'gotify' && (
|
||||
<>
|
||||
<Form.Input
|
||||
field='gotifyUrl'
|
||||
label={t('Gotify服务器地址')}
|
||||
placeholder={t(
|
||||
'请输入Gotify服务器地址,例如: https://gotify.example.com',
|
||||
'霂瑁��乎otify�滚𦛚�典𧑐��嚗䔶�憒? https://gotify.example.com',
|
||||
)}
|
||||
onChange={(val) => handleFormChange('gotifyUrl', val)}
|
||||
prefix={<IconLink />}
|
||||
@@ -668,7 +664,7 @@ const NotificationSettings = ({
|
||||
{
|
||||
pattern: /^https?:\/\/.+/,
|
||||
message: t(
|
||||
'Gotify服务器地址必须以http://或https://开头',
|
||||
'Gotify�滚𦛚�典𧑐��敹�◆隞去ttp://�𧬆ttps://撘�憭?,
|
||||
),
|
||||
},
|
||||
]}
|
||||
@@ -695,14 +691,14 @@ const NotificationSettings = ({
|
||||
|
||||
<Form.AutoComplete
|
||||
field='gotifyPriority'
|
||||
label={t('消息优先级')}
|
||||
placeholder={t('请选择消息优先级')}
|
||||
label={t('瘨��隡睃�蝥?)}
|
||||
placeholder={t('霂琿�㗇𥋘瘨��隡睃�蝥?)}
|
||||
data={[
|
||||
{ value: 0, label: t('0 - 最低') },
|
||||
{ value: 2, label: t('2 - 低') },
|
||||
{ value: 0, label: t('0 - ��雿?) },
|
||||
{ value: 2, label: t('2 - 雿?) },
|
||||
{ value: 5, label: t('5 - 正常(默认)') },
|
||||
{ value: 8, label: t('8 - 高') },
|
||||
{ value: 10, label: t('10 - 最高') },
|
||||
{ value: 8, label: t('8 - 擃?) },
|
||||
{ value: 10, label: t('10 - ��擃?) },
|
||||
]}
|
||||
onChange={(val) =>
|
||||
handleFormChange('gotifyPriority', val)
|
||||
@@ -729,7 +725,7 @@ const NotificationSettings = ({
|
||||
<div>3. {t('填写Gotify服务器的完整URL地址')}</div>
|
||||
<div className='mt-3 pt-3 border-t border-gray-200'>
|
||||
<span className='text-gray-400'>
|
||||
{t('更多信息请参考')}
|
||||
{t('�游�靽⊥�霂瑕��?)}
|
||||
</span>{' '}
|
||||
<a
|
||||
href='https://gotify.net/'
|
||||
@@ -760,14 +756,14 @@ const NotificationSettings = ({
|
||||
<div className='py-4'>
|
||||
<Form.Switch
|
||||
field='acceptUnsetModelRatioModel'
|
||||
label={t('接受未设置价格模型')}
|
||||
label={t('�亙��芾挽蝵桐遠�潭芋�?)}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
uncheckedText={t('�?)}
|
||||
onChange={(value) =>
|
||||
handleFormChange('acceptUnsetModelRatioModel', value)
|
||||
}
|
||||
extraText={t(
|
||||
'当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用',
|
||||
'敶𤘪芋�𧢲瓷�㕑挽蝵桐遠�潭𧒄隞齿𦻖�𡑒��剁�隞���其縑隞餉砲蝵𤑳��嗡蝙�剁��航�隡帋漣�罸�憸肽晶�?,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -788,10 +784,10 @@ const NotificationSettings = ({
|
||||
field='recordIpLog'
|
||||
label={t('记录请求与错误日志IP')}
|
||||
checkedText={t('开')}
|
||||
uncheckedText={t('关')}
|
||||
uncheckedText={t('�?)}
|
||||
onChange={(value) => handleFormChange('recordIpLog', value)}
|
||||
extraText={t(
|
||||
'开启后,仅"消费"和"错误"日志将记录您的客户端IP地址',
|
||||
'撘��臬�嚗䔶�"瘨�晶"�?�躰秤"�亙�撠�扇敶閙���恥�瑞垢IP�啣�',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -819,7 +815,7 @@ const NotificationSettings = ({
|
||||
color: 'var(--semi-color-text-2)',
|
||||
}}
|
||||
>
|
||||
{t('您可以个性化设置侧边栏的要显示功能')}
|
||||
{t('�典虾隞乩葵�批�霈曄蔭靘扯器�讐�閬�遬蝷箏��?)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{/* 边栏设置功能区域容器 */}
|
||||
@@ -832,7 +828,7 @@ const NotificationSettings = ({
|
||||
>
|
||||
{sectionConfigs.map((section) => (
|
||||
<div key={section.key} className='mb-6'>
|
||||
{/* 区域标题和总开关 */}
|
||||
{/* �箏�����峕�餃��?*/}
|
||||
<div
|
||||
className='flex justify-between items-center mb-4 p-4 rounded-lg'
|
||||
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
|
||||
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
|
||||
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";
|
||||
@@ -27,12 +27,12 @@ import { normalizeLanguage } from "../../../../i18n/language";
|
||||
|
||||
// Language options with native names
|
||||
const languageOptions = [
|
||||
{ value: "zh-CN", label: "简体中文" },
|
||||
{ value: "zh-CN", label: "ç®€ä½“ä¸æ–? },
|
||||
{ value: "zh-TW", label: "繁體中文" },
|
||||
{ value: "en", label: "English" },
|
||||
{ value: 'fr', label: 'Français'},
|
||||
{ value: 'ru', label: 'Русский'},
|
||||
{ value: 'ja', label: '日本語'},
|
||||
{ value: 'ja', label: '日本�},
|
||||
{ value: "vi", label: "Tiếng Việt" },
|
||||
];
|
||||
|
||||
@@ -81,7 +81,7 @@ const PreferencesSettings = ({ t }) => {
|
||||
});
|
||||
|
||||
if (res.data.success) {
|
||||
showSuccess(t("语言偏好已保存"));
|
||||
showSuccess(t("è¯è¨€å��好已ä¿�å?));
|
||||
// Keep backend preference, context state, and local cache aligned.
|
||||
let settings = {};
|
||||
if (userState?.user?.setting) {
|
||||
@@ -131,7 +131,7 @@ const PreferencesSettings = ({ t }) => {
|
||||
{t("偏好设置")}
|
||||
</Typography.Text>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
{t("界面语言和其他个人偏好")}
|
||||
{t("界é�¢è¯è¨€å’Œå…¶ä»–个人å��å¥?)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ const PreferencesSettings = ({ t }) => {
|
||||
{t("语言偏好")}
|
||||
</Typography.Title>
|
||||
<Typography.Text type="tertiary" className="text-sm">
|
||||
{t("选择您的首选界面语言,设置将自动保存并同步到所有设备")}
|
||||
{t("选择您的首选界é�¢è¯è¨€ï¼Œè®¾ç½®å°†è‡ªåЍä¿�å˜å¹¶å�Œæ¥åˆ°æ‰€æœ‰è®¾å¤?)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,7 +171,7 @@ const PreferencesSettings = ({ t }) => {
|
||||
<div className="mt-4 text-xs text-gray-500 dark:text-gray-400">
|
||||
<Typography.Text type="tertiary">
|
||||
{t(
|
||||
"提示:语言偏好会同步到您登录的所有设备,并影响API返回的错误消息语言。",
|
||||
"æ��示:è¯è¨€å��好会å�Œæ¥åˆ°æ‚¨ç™»å½•的所有设备,并影å“�API返回的错误消æ�¯è¯è¨€ã€?,
|
||||
)}
|
||||
</Typography.Text>
|
||||
</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
|
||||
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
|
||||
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 {
|
||||
@@ -51,8 +51,7 @@ const TwoFASetting = ({ t }) => {
|
||||
backup_codes_remaining: 0,
|
||||
});
|
||||
|
||||
// 模态框状态
|
||||
const [setupModalVisible, setSetupModalVisible] = useState(false);
|
||||
// 璅⊥����嗆�? const [setupModalVisible, setSetupModalVisible] = useState(false);
|
||||
const [enableModalVisible, setEnableModalVisible] = useState(false);
|
||||
const [disableModalVisible, setDisableModalVisible] = useState(false);
|
||||
const [backupModalVisible, setBackupModalVisible] = useState(false);
|
||||
@@ -64,15 +63,14 @@ const TwoFASetting = ({ t }) => {
|
||||
const [confirmDisable, setConfirmDisable] = useState(false);
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
|
||||
// 获取2FA状态
|
||||
const fetchStatus = async () => {
|
||||
// �瑕�2FA�嗆�? const fetchStatus = async () => {
|
||||
try {
|
||||
const res = await API.get('/api/user/2fa/status');
|
||||
if (res.data.success) {
|
||||
setStatus(res.data.data);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('获取2FA状态失败'));
|
||||
showError(t('�瑕�2FA�嗆��仃韐?));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,7 +78,7 @@ const TwoFASetting = ({ t }) => {
|
||||
fetchStatus();
|
||||
}, []);
|
||||
|
||||
// 初始化2FA设置
|
||||
// �嘥��?FA霈曄蔭
|
||||
const handleSetup2FA = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -112,7 +110,7 @@ const TwoFASetting = ({ t }) => {
|
||||
code: verificationCode,
|
||||
});
|
||||
if (res.data.success) {
|
||||
showSuccess(t('两步验证启用成功!'));
|
||||
showSuccess(t('銝斗郊撉諹��舐鍂�𣂼�嚗?));
|
||||
setEnableModalVisible(false);
|
||||
setSetupModalVisible(false);
|
||||
setVerificationCode('');
|
||||
@@ -146,7 +144,7 @@ const TwoFASetting = ({ t }) => {
|
||||
code: verificationCode,
|
||||
});
|
||||
if (res.data.success) {
|
||||
showSuccess(t('两步验证已禁用'));
|
||||
showSuccess(t('銝斗郊撉諹�撌脩��?));
|
||||
setDisableModalVisible(false);
|
||||
setVerificationCode('');
|
||||
setConfirmDisable(false);
|
||||
@@ -161,8 +159,7 @@ const TwoFASetting = ({ t }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 重新生成备用码
|
||||
const handleRegenerateBackupCodes = async () => {
|
||||
// �齿鰵���憭�鍂�? const handleRegenerateBackupCodes = async () => {
|
||||
if (!verificationCode) {
|
||||
showWarning(t('请输入验证码'));
|
||||
return;
|
||||
@@ -175,21 +172,21 @@ const TwoFASetting = ({ t }) => {
|
||||
});
|
||||
if (res.data.success) {
|
||||
setBackupCodes(res.data.data.backup_codes);
|
||||
showSuccess(t('备用码重新生成成功'));
|
||||
showSuccess(t('憭�鍂����啁��鞉��?));
|
||||
setVerificationCode('');
|
||||
fetchStatus();
|
||||
} else {
|
||||
showError(res.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('重新生成备用码失败'));
|
||||
showError(t('�齿鰵���憭�鍂��仃韐?));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 通用复制函数
|
||||
const copyTextToClipboard = (text, successMessage = t('已复制到剪贴板')) => {
|
||||
const copyTextToClipboard = (text, successMessage = t('撌脣��嗅��芾斐�?)) => {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
@@ -205,8 +202,7 @@ const TwoFASetting = ({ t }) => {
|
||||
copyTextToClipboard(codesText, t('备用码已复制到剪贴板'));
|
||||
};
|
||||
|
||||
// 备用码展示组件
|
||||
const BackupCodesDisplay = ({ codes, title, onCopy }) => {
|
||||
// 憭�鍂���蝷箇�隞? const BackupCodesDisplay = ({ codes, title, onCopy }) => {
|
||||
return (
|
||||
<Card className='!rounded-xl' style={{ width: '100%' }}>
|
||||
<div className='space-y-3'>
|
||||
@@ -242,7 +238,7 @@ const TwoFASetting = ({ t }) => {
|
||||
onClick={onCopy}
|
||||
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700 w-full'
|
||||
>
|
||||
{t('复制所有代码')}
|
||||
{t('憭滚����劐誨�?)}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -258,7 +254,7 @@ const TwoFASetting = ({ t }) => {
|
||||
onClick={() => setCurrentStep(currentStep - 1)}
|
||||
className='!rounded-lg'
|
||||
>
|
||||
{t('上一步')}
|
||||
{t('銝𠹺�甇?)}
|
||||
</Button>
|
||||
)}
|
||||
{currentStep < 2 ? (
|
||||
@@ -268,7 +264,7 @@ const TwoFASetting = ({ t }) => {
|
||||
onClick={() => setCurrentStep(currentStep + 1)}
|
||||
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700'
|
||||
>
|
||||
{t('下一步')}
|
||||
{t('銝衤�甇?)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -284,7 +280,7 @@ const TwoFASetting = ({ t }) => {
|
||||
}}
|
||||
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700'
|
||||
>
|
||||
{t('完成设置并启用两步验证')}
|
||||
{t('摰峕�霈曄蔭撟嗅鍳�其舅甇仿�霂?)}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -358,7 +354,7 @@ const TwoFASetting = ({ t }) => {
|
||||
onClick={handleRegenerateBackupCodes}
|
||||
className='!rounded-lg !bg-slate-600 hover:!bg-slate-700'
|
||||
>
|
||||
{t('生成新的备用码')}
|
||||
{t('����啁�憭�鍂�?)}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
@@ -382,22 +378,22 @@ const TwoFASetting = ({ t }) => {
|
||||
</Typography.Title>
|
||||
{status.enabled ? (
|
||||
<Tag color='green' shape='circle' size='small'>
|
||||
{t('已启用')}
|
||||
{t('撌脣鍳�?)}
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag color='red' shape='circle' size='small'>
|
||||
{t('未启用')}
|
||||
{t('�芸鍳�?)}
|
||||
</Tag>
|
||||
)}
|
||||
{status.locked && (
|
||||
<Tag color='orange' shape='circle' size='small'>
|
||||
{t('账户已锁定')}
|
||||
{t('韐行�撌脤�摰?)}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
<Typography.Text type='tertiary' className='text-sm'>
|
||||
{t(
|
||||
'两步验证(2FA)为您的账户提供额外的安全保护。启用后,登录时需要输入密码和验证器应用生成的验证码。',
|
||||
'銝斗郊撉諹�嚗?FA嚗劐蛹�函�韐行��𣂷�憸嘥�����其��扎��鍳�典�嚗𣬚蒈敶閙𧒄��閬���亙����撉諹��典��函��鞟�撉諹����?,
|
||||
)}
|
||||
</Typography.Text>
|
||||
{status.enabled && (
|
||||
@@ -405,7 +401,7 @@ const TwoFASetting = ({ t }) => {
|
||||
<Text size='small' type='secondary'>
|
||||
{t('剩余备用码:')}
|
||||
{status.backup_codes_remaining || 0}
|
||||
{t('个')}
|
||||
{t('銝?)}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
@@ -444,7 +440,7 @@ const TwoFASetting = ({ t }) => {
|
||||
className='!rounded-lg'
|
||||
icon={<IconRefresh />}
|
||||
>
|
||||
{t('重新生成备用码')}
|
||||
{t('�齿鰵���憭�鍂�?)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -476,16 +472,16 @@ const TwoFASetting = ({ t }) => {
|
||||
{/* 步骤进度 */}
|
||||
<Steps type='basic' size='small' current={currentStep}>
|
||||
<Steps.Step
|
||||
title={t('扫描二维码')}
|
||||
title={t('�急�鈭𣬚輕�?)}
|
||||
description={t('使用认证器应用扫描二维码')}
|
||||
/>
|
||||
<Steps.Step
|
||||
title={t('保存备用码')}
|
||||
title={t('靽嘥�憭�鍂�?)}
|
||||
description={t('保存备用码以备不时之需')}
|
||||
/>
|
||||
<Steps.Step
|
||||
title={t('验证设置')}
|
||||
description={t('输入验证码完成设置')}
|
||||
description={t('颲枏�撉諹�����鞱挽蝵?)}
|
||||
/>
|
||||
</Steps>
|
||||
|
||||
@@ -495,7 +491,7 @@ const TwoFASetting = ({ t }) => {
|
||||
<div>
|
||||
<Paragraph className='text-gray-600 dark:text-gray-300 mb-4'>
|
||||
{t(
|
||||
'使用认证器应用(如 Google Authenticator、Microsoft Authenticator)扫描下方二维码:',
|
||||
'雿輻鍂霈方��典��剁�憒?Google Authenticator��icrosoft Authenticator嚗㗇醌�譍��嫣�蝏渡�嚗?,
|
||||
)}
|
||||
</Paragraph>
|
||||
<div className='flex justify-center mb-4'>
|
||||
@@ -516,7 +512,7 @@ const TwoFASetting = ({ t }) => {
|
||||
|
||||
{currentStep === 1 && (
|
||||
<div className='space-y-4'>
|
||||
{/* 备用码展示 */}
|
||||
{/* 憭�鍂���蝷?*/}
|
||||
<BackupCodesDisplay
|
||||
codes={setupData.backup_codes}
|
||||
title={t('备用恢复代码')}
|
||||
@@ -567,7 +563,7 @@ const TwoFASetting = ({ t }) => {
|
||||
<Banner
|
||||
type='warning'
|
||||
description={t(
|
||||
'警告:禁用两步验证将永久删除您的验证设置和所有备用码,此操作不可撤销!',
|
||||
'霅血�嚗𡁶��其舅甇仿�霂��瘞訾��𣳇膄�函�撉諹�霈曄蔭�峕��匧��函�嚗峕迨�滢�銝滚虾�日�嚗?,
|
||||
)}
|
||||
className='!rounded-lg'
|
||||
/>
|
||||
@@ -580,16 +576,16 @@ const TwoFASetting = ({ t }) => {
|
||||
strong
|
||||
className='block mb-2 text-slate-700 dark:text-slate-200'
|
||||
>
|
||||
{t('禁用后的影响:')}
|
||||
{t('蝳�鍂�𡒊�敶勗�嚗?)}
|
||||
</Text>
|
||||
<ul className='space-y-2 text-sm text-slate-600 dark:text-slate-300'>
|
||||
<li className='flex items-start gap-2'>
|
||||
<Badge dot type='warning' />
|
||||
{t('降低您账户的安全性')}
|
||||
{t('�滢��刻揭�瑞�摰匧��?)}
|
||||
</li>
|
||||
<li className='flex items-start gap-2'>
|
||||
<Badge dot type='warning' />
|
||||
{t('需要重新完整设置才能再次启用')}
|
||||
{t('��閬���啣��渲挽蝵格��賢�甈∪鍳�?)}
|
||||
</li>
|
||||
<li className='flex items-start gap-2'>
|
||||
<Badge dot type='danger' />
|
||||
@@ -613,7 +609,7 @@ const TwoFASetting = ({ t }) => {
|
||||
{t('验证身份')}
|
||||
</Text>
|
||||
<Input
|
||||
placeholder={t('请输入认证器验证码或备用码')}
|
||||
placeholder={t('霂瑁��亥恕霂�膥撉諹����憭�鍂�?)}
|
||||
value={verificationCode}
|
||||
onChange={setVerificationCode}
|
||||
size='large'
|
||||
@@ -642,7 +638,7 @@ const TwoFASetting = ({ t }) => {
|
||||
title={
|
||||
<div className='flex items-center'>
|
||||
<IconRefresh className='mr-2 text-slate-600' />
|
||||
{t('重新生成备用码')}
|
||||
{t('�齿鰵���憭�鍂�?)}
|
||||
</div>
|
||||
}
|
||||
visible={backupModalVisible}
|
||||
@@ -663,7 +659,7 @@ const TwoFASetting = ({ t }) => {
|
||||
<Banner
|
||||
type='warning'
|
||||
description={t(
|
||||
'重新生成备用码将使现有的备用码失效,请确保您已保存了当前的备用码。',
|
||||
'�齿鰵���憭�鍂���雿輻緵�厩�憭�鍂��仃���霂瑞&靽脲�撌脖�摮䀝�敶枏�����函��?,
|
||||
)}
|
||||
className='!rounded-lg'
|
||||
/>
|
||||
@@ -679,7 +675,7 @@ const TwoFASetting = ({ t }) => {
|
||||
{t('验证身份')}
|
||||
</Text>
|
||||
<Input
|
||||
placeholder={t('请输入认证器验证码')}
|
||||
placeholder={t('霂瑁��亥恕霂�膥撉諹��?)}
|
||||
value={verificationCode}
|
||||
onChange={setVerificationCode}
|
||||
size='large'
|
||||
@@ -702,10 +698,10 @@ const TwoFASetting = ({ t }) => {
|
||||
</Text>
|
||||
</div>
|
||||
<Text className='text-slate-500 dark:text-slate-400 text-sm'>
|
||||
{t('旧的备用码已失效,请保存新的备用码')}
|
||||
{t('�抒�憭�鍂��歇憭望�嚗諹窈靽嘥��啁�憭�鍂�?)}
|
||||
</Text>
|
||||
|
||||
{/* 备用码展示 */}
|
||||
{/* 憭�鍂���蝷?*/}
|
||||
<BackupCodesDisplay
|
||||
codes={backupCodes}
|
||||
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
|
||||
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
|
||||
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';
|
||||
@@ -86,7 +86,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
||||
shape='circle'
|
||||
style={{ color: 'white' }}
|
||||
>
|
||||
{t('超级管理员')}
|
||||
{t('超级管��)}
|
||||
</Tag>
|
||||
) : isAdmin() ? (
|
||||
<Tag
|
||||
@@ -94,7 +94,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
||||
shape='circle'
|
||||
style={{ color: 'white' }}
|
||||
>
|
||||
{t('管理员')}
|
||||
{t('管��)}
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag
|
||||
@@ -102,7 +102,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
||||
shape='circle'
|
||||
style={{ color: 'white' }}
|
||||
>
|
||||
{t('普通用户')}
|
||||
{t('普通用�)}
|
||||
</Tag>
|
||||
)}
|
||||
<Tag size='large' shape='circle' style={{ color: 'white' }}>
|
||||
@@ -125,7 +125,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
||||
</div>
|
||||
</Badge>
|
||||
|
||||
{/* 桌面版统计信息(Semi UI 卡片) */}
|
||||
{/* 桌�版统计信�(Semi UI �片�*/}
|
||||
<div className='hidden lg:block flex-shrink-0'>
|
||||
<Card
|
||||
size='small'
|
||||
@@ -136,7 +136,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
||||
<div className='flex items-center gap-2'>
|
||||
<Coins size={16} />
|
||||
<Typography.Text size='small' type='tertiary'>
|
||||
{t('历史消耗')}
|
||||
{t('历�消�)}
|
||||
</Typography.Text>
|
||||
<Typography.Text size='small' type='tertiary' strong>
|
||||
{renderQuota(userState?.user?.used_quota)}
|
||||
@@ -179,7 +179,7 @@ const UserInfoHeader = ({ t, userState }) => {
|
||||
<div className='flex items-center gap-2'>
|
||||
<Coins size={16} />
|
||||
<Typography.Text size='small' type='tertiary'>
|
||||
{t('历史消耗')}
|
||||
{t('历�消�)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<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
|
||||
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
|
||||
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';
|
||||
@@ -59,10 +59,10 @@ const AccountDeleteModal = ({
|
||||
|
||||
<div>
|
||||
<Typography.Text strong className='block mb-2 text-red-600'>
|
||||
{t('请输入您的用户名以确认删除')}
|
||||
{t('霂瑁��交���鍂�瑕�隞亦&霈文��?)}
|
||||
</Typography.Text>
|
||||
<Input
|
||||
placeholder={t('输入你的账户名{{username}}以确认删除', {
|
||||
placeholder={t('颲枏�雿删�韐行��拥{username}}隞亦&霈文��?, {
|
||||
username: ` ${userState?.user?.username} `,
|
||||
})}
|
||||
name='self_account_deletion_confirmation'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user