diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json index b710e4961..9f7bb3653 100644 --- a/client/public/locales/en/common.json +++ b/client/public/locales/en/common.json @@ -1,7 +1,18 @@ { + "app": { + "My Apps": "My Apps" + }, "home": { "Quickly build AI question and answer library": "Quickly build AI question and answer library", "Start Now": "Start Now", "Visual AI orchestration": "Visual AI orchestration" + }, + "navbar": { + "Account": "Account", + "Apps": "Apps", + "Chat": "Chat", + "Datasets": "DataSets", + "Store": "Store", + "Tools": "Tools" } } diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json index 37494028b..ace76730a 100644 --- a/client/public/locales/zh/common.json +++ b/client/public/locales/zh/common.json @@ -1,7 +1,18 @@ { + "app": { + "My Apps": "我的应用" + }, "home": { "Quickly build AI question and answer library": "快速搭建 AI 问答系统", "Start Now": "立即开始", "Visual AI orchestration": "可视化 AI 编排" + }, + "navbar": { + "Account": "账号", + "Apps": "应用", + "Chat": "聊天", + "Datasets": "知识库", + "Store": "应用市场", + "Tools": "工具" } } diff --git a/client/src/components/Charts/Line.tsx b/client/src/components/Charts/Line.tsx deleted file mode 100644 index 5e97120ff..000000000 --- a/client/src/components/Charts/Line.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import React, { useEffect, useMemo, useRef } from 'react'; -import * as echarts from 'echarts'; -import { useGlobalStore } from '@/store/global'; - -const LineChart = ({ - type, - limit = 1000000, - data -}: { - type: 'blue' | 'deepBlue' | 'green' | 'purple'; - limit: number; - data: number[]; -}) => { - const { screenWidth } = useGlobalStore(); - - const Dom = useRef(null); - const myChart = useRef(); - - const map = { - blue: { - backgroundColor: { - type: 'linear', - x: 0, - y: 0, - x2: 0, - y2: 1, - colorStops: [ - { - offset: 0, - color: 'rgba(3, 190, 232, 0.42)' // 0% 处的颜色 - }, - { - offset: 1, - color: 'rgba(0, 182, 240, 0)' - } - ], - global: false // 缺省为 false - }, - lineColor: '#36ADEF' - }, - deepBlue: { - backgroundColor: { - type: 'linear', - x: 0, - y: 0, - x2: 0, - y2: 1, - colorStops: [ - { - offset: 0, - color: 'rgba(47, 112, 237, 0.42)' // 0% 处的颜色 - }, - { - offset: 1, - color: 'rgba(94, 159, 235, 0)' - } - ], - global: false - }, - lineColor: '#3293EC' - }, - purple: { - backgroundColor: { - type: 'linear', - x: 0, - y: 0, - x2: 0, - y2: 1, - colorStops: [ - { - offset: 0, - color: 'rgba(211, 190, 255, 0.42)' // 0% 处的颜色 - }, - { - offset: 1, - color: 'rgba(52, 60, 255, 0)' - } - ], - global: false // 缺省为 false - }, - lineColor: '#8172D8' - }, - green: { - backgroundColor: { - type: 'linear', - x: 0, - y: 0, - x2: 0, - y2: 1, - colorStops: [ - { - offset: 0, - color: 'rgba(4, 209, 148, 0.42)' // 0% 处的颜色 - }, - { - offset: 1, - color: 'rgba(19, 217, 181, 0)' - } - ], - global: false // 缺省为 false - }, - lineColor: '#00A9A6', - max: 100 - } - }; - - const option = useMemo( - () => ({ - xAxis: { - type: 'category', - show: false, - boundaryGap: false, - data: data.map((_, i) => i) - }, - yAxis: { - type: 'value', - boundaryGap: false, - splitNumber: 2, - max: 100, - min: 0 - }, - grid: { - show: false, - left: 0, - right: 0, - top: 0, - bottom: 2 - }, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'line' - }, - formatter: (e: any[]) => `${e[0]?.value || 0}%` - }, - series: [ - { - data: new Array(data.length).fill(0), - type: 'line', - showSymbol: false, - smooth: true, - animationDuration: 300, - animationEasingUpdate: 'linear', - areaStyle: { - color: map[type].backgroundColor - }, - lineStyle: { - width: '1', - color: map[type].lineColor - }, - itemStyle: { - width: 1.5, - color: map[type].lineColor - }, - emphasis: { - // highlight - disabled: true - } - } - ] - }), - [limit, type] - ); - - // init chart - useEffect(() => { - if (!Dom.current || myChart?.current?.getOption()) return; - myChart.current = echarts.init(Dom.current); - myChart.current && myChart.current.setOption(option); - }, [Dom]); - - // data changed, update - useEffect(() => { - if (!myChart.current || !myChart?.current?.getOption()) return; - - const uniData = data.map((item) => ((item / limit) * 100).toFixed(2)); - - const x = option.xAxis.data; - option.xAxis.data = [...x.slice(1), x[x.length - 1] + 1]; - option.series[0].data = uniData; - myChart.current.setOption(option); - }, [data, limit]); - - // limit changed, update - useEffect(() => { - if (!myChart.current || !myChart?.current?.getOption()) return; - myChart.current.setOption(option); - }, [limit, option, type]); - - // resize chart - useEffect(() => { - if (!myChart.current || !myChart.current.getOption()) return; - myChart.current.resize(); - }, [screenWidth]); - - return
; -}; - -export default React.memo(LineChart); diff --git a/client/src/components/Icon/icons/language/en.svg b/client/src/components/Icon/icons/language/en.svg new file mode 100644 index 000000000..3c07767ce --- /dev/null +++ b/client/src/components/Icon/icons/language/en.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/components/Icon/icons/language/zh.svg b/client/src/components/Icon/icons/language/zh.svg new file mode 100644 index 000000000..070eff583 --- /dev/null +++ b/client/src/components/Icon/icons/language/zh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/components/Icon/index.tsx b/client/src/components/Icon/index.tsx index 03402c69c..3c0046499 100644 --- a/client/src/components/Icon/index.tsx +++ b/client/src/components/Icon/index.tsx @@ -68,7 +68,9 @@ const map = { informLight: require('./icons/light/inform.svg').default, payRecordLight: require('./icons/light/payRecord.svg').default, loginoutLight: require('./icons/light/loginout.svg').default, - chatModelTag: require('./icons/light/chatModelTag.svg').default + chatModelTag: require('./icons/light/chatModelTag.svg').default, + language_en: require('./icons/language/en.svg').default, + language_zh: require('./icons/language/zh.svg').default }; export type IconName = keyof typeof map; diff --git a/client/src/components/Language/index.tsx b/client/src/components/Language/index.tsx new file mode 100644 index 000000000..1d5023450 --- /dev/null +++ b/client/src/components/Language/index.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import { Menu, MenuButton, MenuItem, MenuList, MenuButtonProps } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; +import { getLangStore, LangEnum, setLangStore } from '@/utils/i18n'; +import MyIcon from '@/components/Icon'; + +const langMap = { + [LangEnum.en]: { + label: 'English', + icon: 'language_en' + }, + [LangEnum.zh]: { + label: '简体中文', + icon: 'language_zh' + } +}; + +const Language = (props: MenuButtonProps) => { + const router = useRouter(); + const [language, setLanguage] = useState<`${LangEnum}`>(getLangStore()); + + return ( + + + + + + {Object.entries(langMap).map(([key, lang]) => ( + { + const lang = key as `${LangEnum}`; + setLangStore(lang); + setLanguage(lang); + router.replace({ + query: { + ...router.query, + lang + } + }); + }} + > + {lang.label} + + ))} + + + ); +}; + +export default React.memo(Language); diff --git a/client/src/components/Layout/navbar.tsx b/client/src/components/Layout/navbar.tsx index 388ce7b68..bcd9ef403 100644 --- a/client/src/components/Layout/navbar.tsx +++ b/client/src/components/Layout/navbar.tsx @@ -9,6 +9,8 @@ import NextLink from 'next/link'; import Badge from '../Badge'; import Avatar from '../Avatar'; import MyIcon from '../Icon'; +import Language from '../Language'; +import { useTranslation } from 'next-i18next'; export enum NavbarTypeEnum { normal = 'normal', @@ -16,50 +18,54 @@ export enum NavbarTypeEnum { } const Navbar = ({ unread }: { unread: number }) => { + const { t } = useTranslation(); const router = useRouter(); const { userInfo } = useUserStore(); const { lastChatAppId, lastChatId } = useChatStore(); - const navbarList = [ - { - label: '聊天', - icon: 'chatLight', - activeIcon: 'chatFill', - link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`, - activeLink: ['/chat'] - }, - { - label: '应用', - icon: 'appLight', - activeIcon: 'appFill', - link: `/app/list`, - activeLink: ['/app/list', '/app/detail'] - }, - { - label: '知识库', - icon: 'dbLight', - activeIcon: 'dbFill', - link: `/kb/list`, - activeLink: ['/kb/list', '/kb/detail'] - }, - ...(feConfigs?.show_appStore - ? [ - { - label: '市场', - icon: 'appStoreLight', - activeIcon: 'appStoreFill', - link: '/appStore', - activeLink: ['/appStore'] - } - ] - : []), - { - label: '账号', - icon: 'meLight', - activeIcon: 'meFill', - link: '/number', - activeLink: ['/number'] - } - ]; + const navbarList = useMemo( + () => [ + { + label: t('navbar.Chat'), + icon: 'chatLight', + activeIcon: 'chatFill', + link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`, + activeLink: ['/chat'] + }, + { + label: t('navbar.Apps'), + icon: 'appLight', + activeIcon: 'appFill', + link: `/app/list`, + activeLink: ['/app/list', '/app/detail'] + }, + { + label: t('navbar.Datasets'), + icon: 'dbLight', + activeIcon: 'dbFill', + link: `/kb/list`, + activeLink: ['/kb/list', '/kb/detail'] + }, + ...(feConfigs?.show_appStore + ? [ + { + label: t('navbar.Store'), + icon: 'appStoreLight', + activeIcon: 'appStoreFill', + link: '/appStore', + activeLink: ['/appStore'] + } + ] + : []), + { + label: t('navbar.Account'), + icon: 'meLight', + activeIcon: 'meFill', + link: '/account', + activeLink: ['/account'] + } + ], + [lastChatAppId, lastChatId, t] + ); const itemStyles: any = { my: 3, @@ -94,7 +100,7 @@ const Navbar = ({ unread }: { unread: number }) => { borderRadius={'50%'} overflow={'hidden'} cursor={'pointer'} - onClick={() => router.push('/number')} + onClick={() => router.push('/account')} > @@ -133,25 +139,31 @@ const Navbar = ({ unread }: { unread: number }) => { {unread > 0 && ( - + )} + {feConfigs?.show_git && ( - - - - - + + + )} ); diff --git a/client/src/components/Layout/navbarPhone.tsx b/client/src/components/Layout/navbarPhone.tsx index 240e90983..aff1fc04f 100644 --- a/client/src/components/Layout/navbarPhone.tsx +++ b/client/src/components/Layout/navbarPhone.tsx @@ -2,44 +2,46 @@ import React, { useMemo } from 'react'; import { useRouter } from 'next/router'; import { Flex, Box } from '@chakra-ui/react'; import { useChatStore } from '@/store/chat'; +import { useTranslation } from 'react-i18next'; import Badge from '../Badge'; import MyIcon from '../Icon'; const NavbarPhone = ({ unread }: { unread: number }) => { const router = useRouter(); + const { t } = useTranslation(); const { lastChatAppId, lastChatId } = useChatStore(); const navbarList = useMemo( () => [ { - label: '聊天', + label: t('navbar.Chat'), icon: 'chatLight', link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`, activeLink: ['/chat'], unread: 0 }, { - label: '应用', + label: t('navbar.Apps'), icon: 'tabbarModel', link: `/app/list`, activeLink: ['/app/list', '/app/detail'], unread: 0 }, { - label: '工具', + label: t('navbar.Tools'), icon: 'tabbarMore', link: '/tools', activeLink: ['/tools'], unread: 0 }, { - label: '我的', + label: t('navbar.Account'), icon: 'tabbarMe', - link: '/number', - activeLink: ['/number'], + link: '/account', + activeLink: ['/account'], unread } ], - [lastChatId, lastChatAppId, unread] + [t, lastChatAppId, lastChatId, unread] ); return ( diff --git a/client/src/pages/number/components/BillDetail.tsx b/client/src/pages/account/components/BillDetail.tsx similarity index 100% rename from client/src/pages/number/components/BillDetail.tsx rename to client/src/pages/account/components/BillDetail.tsx diff --git a/client/src/pages/number/components/BillTable.tsx b/client/src/pages/account/components/BillTable.tsx similarity index 100% rename from client/src/pages/number/components/BillTable.tsx rename to client/src/pages/account/components/BillTable.tsx diff --git a/client/src/pages/number/components/Info.tsx b/client/src/pages/account/components/Info.tsx similarity index 100% rename from client/src/pages/number/components/Info.tsx rename to client/src/pages/account/components/Info.tsx diff --git a/client/src/pages/number/components/InformTable.tsx b/client/src/pages/account/components/InformTable.tsx similarity index 100% rename from client/src/pages/number/components/InformTable.tsx rename to client/src/pages/account/components/InformTable.tsx diff --git a/client/src/pages/number/components/PayModal.tsx b/client/src/pages/account/components/PayModal.tsx similarity index 100% rename from client/src/pages/number/components/PayModal.tsx rename to client/src/pages/account/components/PayModal.tsx diff --git a/client/src/pages/number/components/PayRecordTable.tsx b/client/src/pages/account/components/PayRecordTable.tsx similarity index 100% rename from client/src/pages/number/components/PayRecordTable.tsx rename to client/src/pages/account/components/PayRecordTable.tsx diff --git a/client/src/pages/number/index.tsx b/client/src/pages/account/index.tsx similarity index 91% rename from client/src/pages/number/index.tsx rename to client/src/pages/account/index.tsx index dbde3208b..a4c79cc4a 100644 --- a/client/src/pages/number/index.tsx +++ b/client/src/pages/account/index.tsx @@ -10,6 +10,7 @@ import PageContainer from '@/components/PageContainer'; import SideTabs from '@/components/SideTabs'; import Tabs from '@/components/Tabs'; import UserInfo from './components/Info'; +import { serviceSideProps } from '@/utils/i18n'; const BillTable = dynamic(() => import('./components/BillTable'), { ssr: false @@ -29,7 +30,7 @@ enum TabEnum { 'loginout' = 'loginout' } -const NumberSetting = ({ currentTab }: { currentTab: `${TabEnum}` }) => { +const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const tabList = useRef([ { icon: 'meLight', label: '个人信息', id: TabEnum.info, Component: }, { icon: 'billRecordLight', label: '消费记录', id: TabEnum.bill, Component: }, @@ -115,12 +116,13 @@ const NumberSetting = ({ currentTab }: { currentTab: `${TabEnum}` }) => { ); }; -export async function getServerSideProps({ query }: any) { +export async function getServerSideProps(content: any) { return { props: { - currentTab: query?.currentTab || TabEnum.info + currentTab: content?.query?.currentTab || TabEnum.info, + ...(await serviceSideProps(content)) } }; } -export default NumberSetting; +export default Account; diff --git a/client/src/pages/app/detail/index.tsx b/client/src/pages/app/detail/index.tsx index b97b1672e..6eaa03905 100644 --- a/client/src/pages/app/detail/index.tsx +++ b/client/src/pages/app/detail/index.tsx @@ -14,6 +14,7 @@ import MyIcon from '@/components/Icon'; import PageContainer from '@/components/PageContainer'; import Loading from '@/components/Loading'; import BasicEdit from './components/BasicEdit'; +import { serviceSideProps } from '@/utils/i18n'; const AdEdit = dynamic(() => import('./components/AdEdit'), { ssr: false, @@ -188,7 +189,7 @@ export async function getServerSideProps(context: any) { const currentTab = context?.query?.currentTab || TabEnum.basicEdit; return { - props: { currentTab } + props: { currentTab, ...(await serviceSideProps(context)) } }; } diff --git a/client/src/pages/app/list/index.tsx b/client/src/pages/app/list/index.tsx index 69c166095..b76b88c69 100644 --- a/client/src/pages/app/list/index.tsx +++ b/client/src/pages/app/list/index.tsx @@ -16,17 +16,21 @@ import { AddIcon } from '@chakra-ui/icons'; import { delModelById } from '@/api/app'; import { useToast } from '@/hooks/useToast'; import { useConfirm } from '@/hooks/useConfirm'; -import dynamic from 'next/dynamic'; +import { serviceSideProps } from '@/utils/i18n'; +import { useTranslation } from 'next-i18next'; +import dynamic from 'next/dynamic'; import MyIcon from '@/components/Icon'; import PageContainer from '@/components/PageContainer'; import Avatar from '@/components/Avatar'; -const CreateModal = dynamic(() => import('./component/CreateModal')); -import styles from './index.module.scss'; import MyTooltip from '@/components/MyTooltip'; +const CreateModal = dynamic(() => import('./component/CreateModal')); + +import styles from './index.module.scss'; const MyApps = () => { const { toast } = useToast(); + const { t } = useTranslation(); const theme = useTheme(); const router = useRouter(); const { myApps, loadMyModels } = useUserStore(); @@ -69,7 +73,7 @@ const MyApps = () => { - 我的应用 + {t('app.My Apps')}