From b86aeb9150c197ffa96ce6f48d7c4b852fbfba2f Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Sat, 14 Dec 2024 12:57:56 +0800 Subject: [PATCH 1/3] feat: Implement status loading in App component and refactor SiderBar - Added a new function to load status data from the API in the App component, enhancing the application's ability to display real-time status updates. - Integrated error handling for API calls to improve user feedback in case of connection issues. - Removed the redundant status loading logic from the SiderBar component, streamlining the code and ensuring a single source of truth for status management. - Updated the useEffect hook in SiderBar to maintain sidebar collapse state based on local storage, improving user experience. --- web/src/App.js | 25 ++++++++++++++++++++++++- web/src/components/SiderBar.js | 22 +++------------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/web/src/App.js b/web/src/App.js index 10ad9e34..8aa16fab 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -27,6 +27,9 @@ 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')); @@ -34,7 +37,7 @@ const About = lazy(() => import('./pages/About')); function App() { const [userState, userDispatch] = useContext(UserContext); - // const [statusState, statusDispatch] = useContext(StatusContext); + const [statusState, statusDispatch] = useContext(StatusContext); const { i18n } = useTranslation(); const loadUser = () => { @@ -45,6 +48,23 @@ function App() { } }; + const loadStatus = async () => { + try { + const res = await API.get('/api/status'); + if (!res?.data) return; + + 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(); let systemName = getSystemName(); @@ -63,6 +83,9 @@ function App() { if (savedLang) { i18n.changeLanguage(savedLang); } + loadStatus(); + + }, [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 ( From 68b87736b696c388514b4d18b4a4a1791b1d2e8b Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Sat, 14 Dec 2024 12:58:10 +0800 Subject: [PATCH 2/3] feat: Enhance i18n support in Home component and update translations - Integrated translation functions in the Home component to support dynamic localization for various UI elements, improving accessibility for users in different languages. - Added new translation keys for "Telegram authentication", "Linux DO authentication", and "License" in the English locale file, expanding the localization coverage. - Updated existing text elements to utilize translation functions, ensuring consistency in language display across the application. --- web/src/i18n/locales/en.json | 5 +++- web/src/pages/Home/index.js | 58 +++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 5b4596c3..d7947a0b 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1209,5 +1209,8 @@ "首页内容已更新": "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" } \ No newline at end of file diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index 1f58d719..094e4692 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(''); @@ -60,13 +62,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('未启用')}

From 41a7cee98e454d5bebb96dd0778ff107e83b9430 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Sat, 14 Dec 2024 14:09:30 +0800 Subject: [PATCH 3/3] feat: Refactor App and ChannelsTable components for improved i18n support - Removed redundant user and status loading logic from the App component, centralizing it in the PageLayout component for better maintainability. - Enhanced the ChannelsTable component by integrating translation functions for various UI elements, ensuring consistent localization of titles and modal messages. - Updated the English locale file with new translation keys for sub-channel modifications, improving the overall localization coverage. - Streamlined the code structure in multiple components to enhance readability and performance. --- web/src/App.js | 52 ------------------------- web/src/components/ChannelsTable.js | 22 +++++------ web/src/components/HeaderBar.js | 18 --------- web/src/components/PageLayout.js | 54 +++++++++++++++++++++++++- web/src/i18n/locales/en.json | 13 +++++-- web/src/pages/Home/index.js | 3 +- web/src/pages/Playground/Playground.js | 25 ++++++------ 7 files changed, 85 insertions(+), 102 deletions(-) diff --git a/web/src/App.js b/web/src/App.js index 8aa16fab..05fd597f 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -36,58 +36,6 @@ 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 }); - } - }; - - const loadStatus = async () => { - try { - const res = await API.get('/api/status'); - if (!res?.data) return; - - 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(); - 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); - } - loadStatus(); - - - }, [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/i18n/locales/en.json b/web/src/i18n/locales/en.json index d7947a0b..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", @@ -1212,5 +1212,10 @@ "当前未开启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" + "协议": "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 094e4692..af42541d 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -54,7 +54,8 @@ const Home = () => { useEffect(() => { displayNotice().then(); displayHomePageContent().then(); - }, []); + }); + return ( <> {homePageContentLoaded && homePageContent === '' ? ( 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 {