mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-10 21:39:33 +00:00
feat(web): 🎨 Unified color theme, AntV light/dark theme switching, Antd first-screen style loading. (#1020)
Co-authored-by: 黄振洪 <hzh01509324@alibaba-inc.com> Co-authored-by: Aralhi <xiaoping0501@gmail.com>
This commit is contained in:
@@ -1,333 +0,0 @@
|
||||
'use client';
|
||||
import React, { useState, useEffect, useMemo, useContext } from 'react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { Modal } from 'antd';
|
||||
import {
|
||||
Box,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemDecorator,
|
||||
ListItemContent,
|
||||
Typography,
|
||||
Button,
|
||||
useColorScheme,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
} from '@mui/joy';
|
||||
import Article from '@mui/icons-material/Article';
|
||||
import DarkModeIcon from '@mui/icons-material/DarkMode';
|
||||
import WbSunnyIcon from '@mui/icons-material/WbSunny';
|
||||
import SmsOutlinedIcon from '@mui/icons-material/SmsOutlined';
|
||||
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
||||
import Image from 'next/image';
|
||||
import classNames from 'classnames';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import DatasetIcon from '@mui/icons-material/Dataset';
|
||||
import ExpandIcon from '@mui/icons-material/Expand';
|
||||
import LanguageIcon from '@mui/icons-material/Language';
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
import ModelTrainingIcon from '@mui/icons-material/ModelTraining';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChatContext } from '@/app/chat-context';
|
||||
import { DialogueListResponse } from '@/types/chat';
|
||||
import { apiInterceptors, delDialogue } from '@/client/api';
|
||||
|
||||
const LeftSide = () => {
|
||||
const pathname = usePathname();
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [logoPath, setLogoPath] = useState('/LOGO_1.png');
|
||||
const { dialogueList, chatId, queryDialogueList, refreshDialogList, isMenuExpand, setIsMenuExpand } = useContext(ChatContext);
|
||||
const { mode, setMode } = useColorScheme();
|
||||
const menus = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: t('Prompt'),
|
||||
route: '/prompt',
|
||||
icon: <ChatIcon fontSize="small" />,
|
||||
tooltip: t('Prompt'),
|
||||
active: pathname === '/prompt',
|
||||
},
|
||||
{
|
||||
label: t('Data_Source'),
|
||||
route: '/database',
|
||||
icon: <DatasetIcon fontSize="small" />,
|
||||
tooltip: t('Data_Source'),
|
||||
active: pathname === '/database',
|
||||
},
|
||||
{
|
||||
label: t('Knowledge_Space'),
|
||||
route: '/knowledge',
|
||||
icon: <Article fontSize="small" />,
|
||||
tooltip: t('Knowledge_Space'),
|
||||
active: pathname === '/knowledge',
|
||||
},
|
||||
{
|
||||
label: t('model_manage'),
|
||||
route: '/models',
|
||||
icon: <ModelTrainingIcon fontSize="small" />,
|
||||
tooltip: t('model_manage'),
|
||||
active: pathname === '/models',
|
||||
},
|
||||
];
|
||||
}, [pathname, i18n.language]);
|
||||
|
||||
function handleChangeTheme() {
|
||||
if (mode === 'light') {
|
||||
setMode('dark');
|
||||
} else {
|
||||
setMode('light');
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangeLanguage = () => {
|
||||
const language = i18n.language === 'en' ? 'zh' : 'en';
|
||||
i18n.changeLanguage(language);
|
||||
window.localStorage.setItem('db_gpt_lng', language);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === 'light') {
|
||||
setLogoPath('/LOGO_1.png');
|
||||
} else {
|
||||
setLogoPath('/WHITE_LOGO.png');
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await queryDialogueList();
|
||||
})();
|
||||
}, []);
|
||||
|
||||
function expandMenu() {
|
||||
return (
|
||||
<>
|
||||
<Box className="p-2 gap-2 flex flex-row justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={'/'}>
|
||||
<Image src={logoPath} alt="DB-GPT" width={633} height={157} className="w-full max-w-full" />
|
||||
</Link>
|
||||
</div>
|
||||
</Box>
|
||||
<Box className="p-2">
|
||||
<Link href={`/`}>
|
||||
<Button
|
||||
color="primary"
|
||||
className="w-full bg-gradient-to-r from-[#31afff] to-[#1677ff] dark:bg-gradient-to-r dark:from-[#6a6a6a] dark:to-[#80868f]"
|
||||
style={{
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
+ New Chat
|
||||
</Button>
|
||||
</Link>
|
||||
</Box>
|
||||
<Box className="p-2 hidden xs:block sm:inline-block max-h-full overflow-auto">
|
||||
<List size="sm" sx={{ '--ListItem-radius': '8px' }}>
|
||||
<ListItem nested>
|
||||
<List
|
||||
size="sm"
|
||||
aria-labelledby="nav-list-browse"
|
||||
sx={{
|
||||
'& .JoyListItemButton-root': { p: '8px' },
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
{(dialogueList || []).map((dialogue: DialogueListResponse[0]) => {
|
||||
const isSelect = (pathname === `/chat` || pathname === '/chat/') && chatId === dialogue.conv_uid;
|
||||
return (
|
||||
<ListItem key={dialogue.conv_uid}>
|
||||
<ListItemButton
|
||||
selected={isSelect}
|
||||
variant={isSelect ? 'soft' : 'plain'}
|
||||
sx={{
|
||||
'&:hover .del-btn': {
|
||||
visibility: 'visible',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemContent>
|
||||
<Link href={`/chat?id=${dialogue.conv_uid}&scene=${dialogue?.chat_mode}`} className="flex items-center justify-between">
|
||||
<Typography fontSize={14} noWrap={true}>
|
||||
<SmsOutlinedIcon style={{ marginRight: '0.5rem' }} />
|
||||
{dialogue?.user_name || dialogue?.user_input || 'undefined'}
|
||||
</Typography>
|
||||
<IconButton
|
||||
color="neutral"
|
||||
variant="plain"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Modal.confirm({
|
||||
title: 'Delete Chat',
|
||||
content: 'Are you sure delete this chat?',
|
||||
width: '276px',
|
||||
centered: true,
|
||||
async onOk() {
|
||||
await apiInterceptors(delDialogue(dialogue.conv_uid));
|
||||
await refreshDialogList();
|
||||
if (pathname === `/chat` && chatId === dialogue.conv_uid) {
|
||||
router.push('/');
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="del-btn invisible"
|
||||
>
|
||||
<DeleteOutlineOutlinedIcon />
|
||||
</IconButton>
|
||||
</Link>
|
||||
</ListItemContent>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
<div className="flex flex-col justify-end flex-1">
|
||||
<Box className="p-2 pt-3 pb-6 border-t border-divider xs:block sticky bottom-0 z-100">
|
||||
<List size="sm" sx={{ '--ListItem-radius': '8px' }}>
|
||||
<ListItem nested>
|
||||
<List
|
||||
size="sm"
|
||||
aria-labelledby="nav-list-browse"
|
||||
sx={{
|
||||
'& .JoyListItemButton-root': { p: '8px' },
|
||||
}}
|
||||
>
|
||||
{menus.map((menu) => (
|
||||
<Link key={menu.route} href={menu.route}>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
color="neutral"
|
||||
sx={{ marginBottom: 1, height: '2.5rem' }}
|
||||
selected={menu.active}
|
||||
variant={menu.active ? 'soft' : 'plain'}
|
||||
>
|
||||
<ListItemDecorator
|
||||
sx={{
|
||||
color: menu.active ? 'inherit' : 'neutral.500',
|
||||
}}
|
||||
>
|
||||
{menu.icon}
|
||||
</ListItemDecorator>
|
||||
<ListItemContent>{menu.label}</ListItemContent>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton className="h-10" onClick={handleChangeTheme}>
|
||||
<Tooltip title={t('Theme')}>
|
||||
<ListItemDecorator>{mode === 'dark' ? <DarkModeIcon fontSize="small" /> : <WbSunnyIcon fontSize="small" />}</ListItemDecorator>
|
||||
</Tooltip>
|
||||
<ListItemContent>{t('Theme')}</ListItemContent>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton className="h-10" onClick={handleChangeLanguage}>
|
||||
<Tooltip title={t('language')}>
|
||||
<ListItemDecorator className="text-2xl">
|
||||
<LanguageIcon fontSize="small" />
|
||||
</ListItemDecorator>
|
||||
</Tooltip>
|
||||
<ListItemContent>{t('language')}</ListItemContent>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
className="h-10"
|
||||
onClick={() => {
|
||||
setIsMenuExpand(false);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={t('Close_Sidebar')}>
|
||||
<ListItemDecorator className="text-2xl">
|
||||
<ExpandIcon className="transform rotate-90" fontSize="small" />
|
||||
</ListItemDecorator>
|
||||
</Tooltip>
|
||||
<ListItemContent>{t('Close_Sidebar')}</ListItemContent>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function notExpandMenu() {
|
||||
return (
|
||||
<Box className="h-full py-6 flex flex-col justify-between">
|
||||
<Box className="flex justify-center items-center">
|
||||
<Tooltip title="Menu">
|
||||
<MenuIcon
|
||||
className="cursor-pointer text-2xl"
|
||||
onClick={() => {
|
||||
setIsMenuExpand(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box className="flex flex-col gap-4 justify-center items-center">
|
||||
{menus.map((menu, index) => (
|
||||
<div className="flex justify-center text-2xl cursor-pointer" key={`menu_${index}`}>
|
||||
<Tooltip title={menu.tooltip}>{menu.icon}</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
<ListItem>
|
||||
<ListItemButton onClick={handleChangeTheme}>
|
||||
<Tooltip title={t('Theme')}>
|
||||
<ListItemDecorator className="text-2xl">
|
||||
{mode === 'dark' ? <DarkModeIcon fontSize="small" /> : <WbSunnyIcon fontSize="small" />}
|
||||
</ListItemDecorator>
|
||||
</Tooltip>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton onClick={handleChangeLanguage}>
|
||||
<Tooltip title={t('language')}>
|
||||
<ListItemDecorator className="text-2xl">
|
||||
<LanguageIcon fontSize="small" />
|
||||
</ListItemDecorator>
|
||||
</Tooltip>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
setIsMenuExpand(true);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={t('Open_Sidebar')}>
|
||||
<ListItemDecorator className="text-2xl">
|
||||
<ExpandIcon className="transform rotate-90" fontSize="small" />
|
||||
</ListItemDecorator>
|
||||
</Tooltip>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className={classNames('grid max-h-screen h-full max-md:hidden')}>
|
||||
<Box className="flex flex-col border-r border-divider max-h-screen sticky left-0 top-0 overflow-hidden">
|
||||
{isMenuExpand ? expandMenu() : notExpandMenu()}
|
||||
</Box>
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeftSide;
|
@@ -2,7 +2,6 @@ import { ChatContext } from '@/app/chat-context';
|
||||
import { apiInterceptors, delDialogue } from '@/client/api';
|
||||
import { STORAGE_LANG_KEY, STORAGE_THEME_KEY } from '@/utils';
|
||||
import { DarkSvg, SunnySvg, ModelSvg } from '@/components/icons';
|
||||
import { useColorScheme } from '@mui/joy';
|
||||
import { IChatDialogueSchema } from '@/types/chat';
|
||||
import Icon, {
|
||||
ConsoleSqlOutlined,
|
||||
@@ -43,22 +42,21 @@ type RouteItem = {
|
||||
};
|
||||
|
||||
function menuItemStyle(active?: boolean) {
|
||||
return `flex items-center px-2 h-8 hover:bg-slate-100 dark:hover:bg-[#353539] text-base w-full my-2 rounded transition-colors whitespace-nowrap ${
|
||||
active ? 'bg-slate-100 dark:bg-[#353539]' : ''
|
||||
return `flex items-center h-12 hover:bg-[#F1F5F9] dark:hover:bg-theme-dark text-base w-full transition-colors whitespace-nowrap px-4 ${
|
||||
active ? 'bg-[#F1F5F9] dark:bg-theme-dark' : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
function smallMenuItemStyle(active?: boolean) {
|
||||
return `flex items-center justify-center mx-auto w-12 h-12 text-xl rounded hover:bg-slate-100 dark:hover:bg-[#353539] cursor-pointer ${
|
||||
active ? 'bg-slate-100 dark:bg-[#353539]' : ''
|
||||
return `flex items-center justify-center mx-auto rounded w-14 h-14 text-xl hover:bg-[#F1F5F9] dark:hover:bg-theme-dark transition-colors cursor-pointer ${
|
||||
active ? 'bg-[#F1F5F9] dark:bg-theme-dark' : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
function SideBar() {
|
||||
const { chatId, scene, isMenuExpand, dialogueList, queryDialogueList, refreshDialogList, setIsMenuExpand } = useContext(ChatContext);
|
||||
const { chatId, scene, isMenuExpand, dialogueList, queryDialogueList, refreshDialogList, setIsMenuExpand, mode, setMode } = useContext(ChatContext);
|
||||
const { pathname, replace } = useRouter();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { mode, setMode } = useColorScheme();
|
||||
|
||||
const [logo, setLogo] = useState<string>('/LOGO_1.png');
|
||||
|
||||
@@ -195,7 +193,7 @@ function SideBar() {
|
||||
);
|
||||
|
||||
const copyLink = useCallback((item: IChatDialogueSchema) => {
|
||||
const success = copy(`${location.origin}/chat/${item.chat_mode}/${item.conv_uid}`);
|
||||
const success = copy(`${location.origin}/chat?scene=${item.chat_mode}&id=${item.conv_uid}`);
|
||||
message[success ? 'success' : 'error'](success ? 'Copy success' : 'Copy failed');
|
||||
}, []);
|
||||
|
||||
@@ -209,20 +207,17 @@ function SideBar() {
|
||||
|
||||
if (!isMenuExpand) {
|
||||
return (
|
||||
<div className="flex flex-col justify-between h-screen border-r dark:bg-[#1A1E26] animate-fade animate-duration-300">
|
||||
<div className="flex flex-col justify-between h-screen bg-white dark:bg-[#232734] animate-fade animate-duration-300">
|
||||
<Link href="/" className="px-2 py-3">
|
||||
<Image src="/LOGO_SMALL.png" alt="DB-GPT" width={63} height={46} className="w-[63px] h-[46px]" />
|
||||
</Link>
|
||||
<div className="border-t border-dashed">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center justify-center my-4 mx-auto w-12 h-12 bg-gradient-to-r from-[#31afff] to-[#1677ff] dark:bg-gradient-to-r dark:from-[#6a6a6a] dark:to-[#80868f] border-none rounded-full text-white"
|
||||
>
|
||||
<div>
|
||||
<Link href="/" className="flex items-center justify-center my-4 mx-auto w-12 h-12 bg-theme-primary rounded-full text-white">
|
||||
<PlusOutlined className="text-lg" />
|
||||
</Link>
|
||||
</div>
|
||||
{/* Chat List */}
|
||||
<div className="flex-1 overflow-y-scroll py-4 border-t border-dashed space-y-2">
|
||||
<div className="flex-1 overflow-y-scroll py-4 space-y-2">
|
||||
{dialogueList?.map((item) => {
|
||||
const active = item.conv_uid === chatId && item.chat_mode === scene;
|
||||
|
||||
@@ -235,7 +230,7 @@ function SideBar() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="py-4 space-y-2 border-t">
|
||||
<div className="py-4">
|
||||
<Dropdown menu={{ items: dropDownRoutes }} placement="topRight">
|
||||
<div className={smallMenuItemStyle()}>
|
||||
<MenuOutlined />
|
||||
@@ -261,20 +256,17 @@ function SideBar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen border-r dark:border-gray-700">
|
||||
<div className="flex flex-col h-screen bg-white dark:bg-[#232734]">
|
||||
{/* LOGO */}
|
||||
<Link href="/" className="p-2">
|
||||
<Image src={logo} alt="DB-GPT" width={239} height={60} className="w-full h-full" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center justify-center mb-4 mx-4 h-11 bg-gradient-to-r from-[#31afff] to-[#1677ff] dark:bg-gradient-to-r dark:from-[#6a6a6a] dark:to-[#80868f] border-none rounded text-white"
|
||||
>
|
||||
<Link href="/" className="flex items-center justify-center mb-4 mx-4 h-11 bg-theme-primary rounded text-white">
|
||||
<PlusOutlined className="mr-2" />
|
||||
<span>New Chat</span>
|
||||
<span>{t('new_chat')}</span>
|
||||
</Link>
|
||||
{/* Chat List */}
|
||||
<div className="flex-1 overflow-y-scroll py-4 px-2 border-t dark:border-gray-700">
|
||||
<div className="flex-1 overflow-y-scroll">
|
||||
{dialogueList?.map((item) => {
|
||||
const active = item.conv_uid === chatId && item.chat_mode === scene;
|
||||
|
||||
@@ -305,18 +297,18 @@ function SideBar() {
|
||||
})}
|
||||
</div>
|
||||
{/* Settings */}
|
||||
<div className="py-2 border-t dark:border-gray-700">
|
||||
<div className="px-2">
|
||||
<div className="pt-4">
|
||||
<div className="max-h-52 overflow-y-auto">
|
||||
{routes.map((item) => (
|
||||
<Link key={item.key} href={item.path} className={`${menuItemStyle(pathname === item.path)}`}>
|
||||
<Link key={item.key} href={item.path} className={`${menuItemStyle(pathname === item.path)} overflow-hidden`}>
|
||||
<>
|
||||
{item.icon}
|
||||
<span className="ml-2 text-sm">{item.name}</span>
|
||||
<span className="ml-3 text-sm">{item.name}</span>
|
||||
</>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-around py-4 border-t border-dashed dark:border-gray-700">
|
||||
<div className="flex items-center justify-around py-4 mt-2">
|
||||
{settings.map((item) => (
|
||||
<Tooltip key={item.key} title={item.name}>
|
||||
<div className="flex-1 flex items-center justify-center cursor-pointer text-xl" onClick={item.onClick}>
|
||||
|
Reference in New Issue
Block a user