diff --git a/web/src/App.js b/web/src/App.js index 10ad9e34..05fd597f 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -27,44 +27,15 @@ import Task from "./pages/Task/index.js"; import Playground from './pages/Playground/Playground.js'; import OAuth2Callback from "./components/OAuth2Callback.js"; import { useTranslation } from 'react-i18next'; +import { StatusContext } from './context/Status'; +import { setStatusData } from './helpers/data.js'; +import { API, showError } from './helpers'; const Home = lazy(() => import('./pages/Home')); const Detail = lazy(() => import('./pages/Detail')); const About = lazy(() => import('./pages/About')); function App() { - const [userState, userDispatch] = useContext(UserContext); - // const [statusState, statusDispatch] = useContext(StatusContext); - const { i18n } = useTranslation(); - - const loadUser = () => { - let user = localStorage.getItem('user'); - if (user) { - let data = JSON.parse(user); - userDispatch({ type: 'login', payload: data }); - } - }; - - useEffect(() => { - loadUser(); - let systemName = getSystemName(); - if (systemName) { - document.title = systemName; - } - let logo = getLogo(); - if (logo) { - let linkElement = document.querySelector("link[rel~='icon']"); - if (linkElement) { - linkElement.href = logo; - } - } - // 从localStorage获取上次使用的语言 - const savedLang = localStorage.getItem('i18nextLng'); - if (savedLang) { - i18n.changeLanguage(savedLang); - } - }, [i18n]); - return ( <> diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index 3167650f..ceb3fe44 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -249,7 +249,7 @@ const ChannelsTable = () => { } }, { - title: '优先级', + title: t('优先级'), dataIndex: 'priority', render: (text, record, index) => { if (record.children === undefined) { @@ -276,8 +276,8 @@ const ChannelsTable = () => { keepFocus={true} onBlur={(e) => { Modal.warning({ - title: '修改子渠道优先级', - content: '确定要修改所有子渠道优先级为 ' + e.target.value + ' 吗?', + title: t('修改子渠道优先级'), + content: t('确定要修改所有子渠道优先级为 ') + e.target.value + t(' 吗?'), onOk: () => { if (e.target.value === '') { return; @@ -298,7 +298,7 @@ const ChannelsTable = () => { } }, { - title: '权重', + title: t('权重'), dataIndex: 'weight', render: (text, record, index) => { if (record.children === undefined) { @@ -325,8 +325,8 @@ const ChannelsTable = () => { keepFocus={true} onBlur={(e) => { Modal.warning({ - title: '修改子渠道权重', - content: '确定要修改所有子渠道权重为 ' + e.target.value + ' 吗?', + title: t('修改子渠道权重'), + content: t('确定要修改所有子渠道权重为 ') + e.target.value + t(' 吗?'), onOk: () => { if (e.target.value === '') { return; @@ -646,25 +646,25 @@ const ChannelsTable = () => { const copySelectedChannel = async (record) => { const channelToCopy = record - channelToCopy.name += '_复制'; + channelToCopy.name += t('_复制'); channelToCopy.created_time = null; channelToCopy.balance = 0; channelToCopy.used_quota = 0; if (!channelToCopy) { - showError('渠道未找到,请刷新页面后重试。'); + showError(t('渠道未找到,请刷新页面后重试。')); return; } try { const newChannel = { ...channelToCopy, id: undefined }; const response = await API.post('/api/channel/', newChannel); if (response.data.success) { - showSuccess('渠道复制成功'); + showSuccess(t('渠道复制成功')); await refresh(); } else { showError(response.data.message); } } catch (error) { - showError('渠道复制失败: ' + error.message); + showError(t('渠道复制失败: ') + error.message); } }; @@ -723,7 +723,7 @@ const ChannelsTable = () => { } const { success, message } = res.data; if (success) { - showSuccess('操作成功完成!'); + showSuccess(t('操作成功完成!')); let channel = res.data.data; let newChannels = [...channels]; if (action === 'delete') { diff --git a/web/src/components/HeaderBar.js b/web/src/components/HeaderBar.js index 759a7278..d885d59b 100644 --- a/web/src/components/HeaderBar.js +++ b/web/src/components/HeaderBar.js @@ -25,24 +25,6 @@ import { stringToColor } from '../helpers/render'; import Text from '@douyinfe/semi-ui/lib/es/typography/text'; import { StyleContext } from '../context/Style/index.js'; -// HeaderBar Buttons -let headerButtons = [ - { - text: '关于', - itemKey: 'about', - to: '/about', - icon: , - }, -]; - -if (localStorage.getItem('chat_link')) { - headerButtons.splice(1, 0, { - name: '聊天', - to: '/chat', - icon: 'comments', - }); -} - const HeaderBar = () => { const { t, i18n } = useTranslation(); const [userState, userDispatch] = useContext(UserContext); diff --git a/web/src/components/PageLayout.js b/web/src/components/PageLayout.js index 6a951c06..da1c09f9 100644 --- a/web/src/components/PageLayout.js +++ b/web/src/components/PageLayout.js @@ -4,15 +4,65 @@ import SiderBar from './SiderBar.js'; import App from '../App.js'; import FooterBar from './Footer.js'; import { ToastContainer } from 'react-toastify'; -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { StyleContext } from '../context/Style/index.js'; import { useTranslation } from 'react-i18next'; +import { API, getLogo, getSystemName, showError } from '../helpers/index.js'; +import { setStatusData } from '../helpers/data.js'; +import { UserContext } from '../context/User/index.js'; +import { StatusContext } from '../context/Status/index.js'; const { Sider, Content, Header, Footer } = Layout; const PageLayout = () => { + const [userState, userDispatch] = useContext(UserContext); + const [statusState, statusDispatch] = useContext(StatusContext); const [styleState, styleDispatch] = useContext(StyleContext); - const { t } = useTranslation(); + const { i18n } = useTranslation(); + + const loadUser = () => { + let user = localStorage.getItem('user'); + if (user) { + let data = JSON.parse(user); + userDispatch({ type: 'login', payload: data }); + } + }; + + const loadStatus = async () => { + try { + const res = await API.get('/api/status'); + const { success, data } = res.data; + if (success) { + statusDispatch({ type: 'set', payload: data }); + setStatusData(data); + } else { + showError('Unable to connect to server'); + } + } catch (error) { + showError('Failed to load status'); + } + }; + + useEffect(() => { + loadUser(); + loadStatus().catch(console.error); + let systemName = getSystemName(); + if (systemName) { + document.title = systemName; + } + let logo = getLogo(); + if (logo) { + let linkElement = document.querySelector("link[rel~='icon']"); + if (linkElement) { + linkElement.href = logo; + } + } + // 从localStorage获取上次使用的语言 + const savedLang = localStorage.getItem('i18nextLng'); + if (savedLang) { + i18n.changeLanguage(savedLang); + } + }, [i18n]); return ( diff --git a/web/src/components/SiderBar.js b/web/src/components/SiderBar.js index c2566535..503dc81a 100644 --- a/web/src/components/SiderBar.js +++ b/web/src/components/SiderBar.js @@ -168,31 +168,13 @@ const SiderBar = () => { ], ); - const loadStatus = async () => { - const res = await API.get('/api/status'); - if (res === undefined) { - return; - } - const { success, data } = res.data; - if (success) { - statusDispatch({ type: 'set', payload: data }); - setStatusData(data); - } else { - showError('无法正常连接至服务器!'); - } - }; - useEffect(() => { - loadStatus().then(() => { - setIsCollapsed( - localStorage.getItem('default_collapse_sidebar') === 'true', - ); - }); let localKey = window.location.pathname.split('/')[1]; if (localKey === '') { localKey = 'home'; } setSelectedKeys([localKey]); + let chatLink = localStorage.getItem('chat_link'); if (!chatLink) { let chats = localStorage.getItem('chats'); @@ -220,6 +202,8 @@ const SiderBar = () => { } } } + + setIsCollapsed(localStorage.getItem('default_collapse_sidebar') === 'true'); }, []); return ( diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 5b4596c3..32da3670 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -709,7 +709,7 @@ "密码修改成功!": "Password changed successfully!", "划转金额最低为": "The minimum transfer amount is", "请输入邮箱!": "Please enter your email!", - "验证码发送成功,请检查���箱!": "The verification code was sent successfully, please check your email!", + "验证码发送成功,请检查邮箱!": "The verification code was sent successfully, please check your email!", "请输入邮箱验证码!": "Please enter the email verification code!", "请输入要划转的数量": "Please enter the amount to be transferred", "当前余额": "Current balance", @@ -827,8 +827,8 @@ "模型消耗分布": "Model consumption distribution", "模型调用次数占比": "Proportion of model calls", "用户消耗分布": "User consumption distribution", - "时间粒度": "time granularity", - "天": "sky", + "时间粒度": "Time granularity", + "天": "day", "模型概览": "Model overview", "用户概览": "User overview", "正在策划中": "Under planning", @@ -1209,5 +1209,13 @@ "首页内容已更新": "Home page content updated", "关于已更新": "About updated", "模型测试": "model test", - "当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。": "Current Midjourney callback is not enabled, some projects may not be able to obtain drawing results, which can be enabled in the operation settings." + "当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。": "Current Midjourney callback is not enabled, some projects may not be able to obtain drawing results, which can be enabled in the operation settings.", + "Telegram 身份验证": "Telegram authentication", + "Linux DO 身份验证": "Linux DO authentication", + "协议": "License", + "修改子渠道权重": "Modify sub-channel weight", + "确定要修改所有子渠道权重为 ": "Confirm to modify all sub-channel weights to ", + " 吗?": "?", + "修改子渠道优先级": "Modify sub-channel priority", + "确定要修改所有子渠道优先级为 ": "Confirm to modify all sub-channel priorities to " } \ No newline at end of file diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index 1f58d719..af42541d 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -4,8 +4,10 @@ import { API, showError, showNotice, timestamp2string } from '../../helpers'; import { StatusContext } from '../../context/Status'; import { marked } from 'marked'; import { StyleContext } from '../../context/Style/index.js'; +import { useTranslation } from 'react-i18next'; const Home = () => { + const { t } = useTranslation(); const [statusState] = useContext(StatusContext); const [homePageContentLoaded, setHomePageContentLoaded] = useState(false); const [homePageContent, setHomePageContent] = useState(''); @@ -52,7 +54,8 @@ const Home = () => { useEffect(() => { displayNotice().then(); displayHomePageContent().then(); - }, []); + }); + return ( <> {homePageContentLoaded && homePageContent === '' ? ( @@ -60,13 +63,13 @@ const Home = () => { { color: 'var(--semi-color-text-1)', }} > - 系统信息总览 + {t('系统信息总览')} } > -

名称:{statusState?.status?.system_name}

+

{t('名称')}:{statusState?.status?.system_name}

- 版本: + {t('版本')}: {statusState?.status?.version ? statusState?.status?.version : 'unknown'}

- 源码: + {t('源码')}: {

- 协议: + {t('协议')}: { Apache-2.0 License

-

启动时间:{getStartTimeString()}

+

{t('启动时间')}:{getStartTimeString()}

{ color: 'var(--semi-color-text-1)', }} > - 系统配置总览 + {t('系统配置总览')} } >

- 邮箱验证: + {t('邮箱验证')}: {statusState?.status?.email_verification === true - ? '已启用' - : '未启用'} + ? t('已启用') + : t('未启用')}

- GitHub 身份验证: + {t('GitHub 身份验证')}: {statusState?.status?.github_oauth === true - ? '已启用' - : '未启用'} + ? t('已启用') + : t('未启用')}

- 微信身份验证: + {t('微信身份验证')}: {statusState?.status?.wechat_login === true - ? '已启用' - : '未启用'} + ? t('已启用') + : t('未启用')}

- Turnstile 用户校验: + {t('Turnstile 用户校验')}: {statusState?.status?.turnstile_check === true - ? '已启用' - : '未启用'} + ? t('已启用') + : t('未启用')}

- Telegram 身份验证: + {t('Telegram 身份验证')}: {statusState?.status?.telegram_oauth === true - ? '已启用' - : '未启用'} + ? t('已启用') + : t('未启用')}

- Linux DO 身份验证: + {t('Linux DO 身份验证')}: {statusState?.status?.linuxdo_oauth === true - ? '已启用' - : '未启用'} + ? t('已启用') + : t('未启用')}

diff --git a/web/src/pages/Playground/Playground.js b/web/src/pages/Playground/Playground.js index ecd96f50..935e7b6e 100644 --- a/web/src/pages/Playground/Playground.js +++ b/web/src/pages/Playground/Playground.js @@ -97,32 +97,29 @@ const Playground = () => { let res = await API.get(`/api/user/self/groups`); const { success, message, data } = res.data; if (success) { - // return data is a map, key is group name, value is group description - // label is group description, value is group name let localGroupOptions = Object.keys(data).map((group) => ({ label: data[group], value: group, })); - // handleInputChange('group', localGroupOptions[0].value); - if (localGroupOptions.length > 0) { - // set user group at first - if (userState.user && userState.user.group) { - let userGroup = userState.user.group; - // Find and move user's group to the front + if (localGroupOptions.length === 0) { + localGroupOptions = [{ + label: t('用户分组'), + value: '', + }]; + } else { + const localUser = JSON.parse(localStorage.getItem('user')); + const userGroup = (userState.user && userState.user.group) || (localUser && localUser.group); + + if (userGroup) { const userGroupIndex = localGroupOptions.findIndex(g => g.value === userGroup); if (userGroupIndex > -1) { const userGroupOption = localGroupOptions.splice(userGroupIndex, 1)[0]; localGroupOptions.unshift(userGroupOption); } } - } else { - localGroupOptions = [{ - label: t('用户分组'), - value: '', - }]; - setGroups(localGroupOptions); } + setGroups(localGroupOptions); handleInputChange('group', localGroupOptions[0].value); } else {