mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-10 13:29:35 +00:00
refactor: Add frontend code to DB-GPT (#912)
This commit is contained in:
333
web/components/layout/left-side.tsx
Normal file
333
web/components/layout/left-side.tsx
Normal file
@@ -0,0 +1,333 @@
|
||||
'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;
|
333
web/components/layout/side-bar.tsx
Normal file
333
web/components/layout/side-bar.tsx
Normal file
@@ -0,0 +1,333 @@
|
||||
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,
|
||||
PartitionOutlined,
|
||||
DeleteOutlined,
|
||||
MessageOutlined,
|
||||
GlobalOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
PlusOutlined,
|
||||
ShareAltOutlined,
|
||||
MenuOutlined,
|
||||
SettingOutlined,
|
||||
BuildOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Modal, message, Tooltip, Dropdown } from 'antd';
|
||||
import { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type SettingItem = {
|
||||
key: string;
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
noDropdownItem?: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
type RouteItem = {
|
||||
key: string;
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
path: string;
|
||||
};
|
||||
|
||||
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]' : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
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]' : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
function SideBar() {
|
||||
const { chatId, scene, isMenuExpand, dialogueList, queryDialogueList, refreshDialogList, setIsMenuExpand } = useContext(ChatContext);
|
||||
const { pathname, replace } = useRouter();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { mode, setMode } = useColorScheme();
|
||||
|
||||
const [logo, setLogo] = useState<string>('/LOGO_1.png');
|
||||
|
||||
const routes = useMemo(() => {
|
||||
const items: RouteItem[] = [
|
||||
{
|
||||
key: 'prompt',
|
||||
name: t('Prompt'),
|
||||
icon: <MessageOutlined />,
|
||||
path: '/prompt',
|
||||
},
|
||||
{
|
||||
key: 'database',
|
||||
name: t('Database'),
|
||||
icon: <ConsoleSqlOutlined />,
|
||||
path: '/database',
|
||||
},
|
||||
{
|
||||
key: 'knowledge',
|
||||
name: t('Knowledge_Space'),
|
||||
icon: <PartitionOutlined />,
|
||||
path: '/knowledge',
|
||||
},
|
||||
{
|
||||
key: 'models',
|
||||
name: t('model_manage'),
|
||||
path: '/models',
|
||||
icon: <Icon component={ModelSvg} />,
|
||||
},
|
||||
{
|
||||
key: 'agent',
|
||||
name: t('Plugins'),
|
||||
path: '/agent',
|
||||
icon: <BuildOutlined />,
|
||||
},
|
||||
];
|
||||
return items;
|
||||
}, [i18n.language]);
|
||||
|
||||
const handleToggleMenu = () => {
|
||||
setIsMenuExpand(!isMenuExpand);
|
||||
};
|
||||
|
||||
const handleToggleTheme = useCallback(() => {
|
||||
const theme = mode === 'light' ? 'dark' : 'light';
|
||||
setMode(theme);
|
||||
localStorage.setItem(STORAGE_THEME_KEY, theme);
|
||||
}, [mode]);
|
||||
|
||||
const handleChangeLang = useCallback(() => {
|
||||
const language = i18n.language === 'en' ? 'zh' : 'en';
|
||||
i18n.changeLanguage(language);
|
||||
localStorage.setItem(STORAGE_LANG_KEY, language);
|
||||
}, [i18n.language, i18n.changeLanguage]);
|
||||
|
||||
const settings = useMemo(() => {
|
||||
const items: SettingItem[] = [
|
||||
{
|
||||
key: 'theme',
|
||||
name: t('Theme'),
|
||||
icon: mode === 'dark' ? <Icon component={DarkSvg} /> : <Icon component={SunnySvg} />,
|
||||
onClick: handleToggleTheme,
|
||||
},
|
||||
{
|
||||
key: 'language',
|
||||
name: t('language'),
|
||||
icon: <GlobalOutlined />,
|
||||
onClick: handleChangeLang,
|
||||
},
|
||||
{
|
||||
key: 'fold',
|
||||
name: t(isMenuExpand ? 'Close_Sidebar' : 'Show_Sidebar'),
|
||||
icon: isMenuExpand ? <MenuFoldOutlined /> : <MenuUnfoldOutlined />,
|
||||
onClick: handleToggleMenu,
|
||||
noDropdownItem: true,
|
||||
},
|
||||
];
|
||||
return items;
|
||||
}, [mode, handleChangeLang, handleToggleMenu, handleChangeLang]);
|
||||
|
||||
const dropDownRoutes: ItemType[] = useMemo(() => {
|
||||
return routes.map<ItemType>((item) => ({
|
||||
key: item.key,
|
||||
label: (
|
||||
<Link href={item.path} className="text-base">
|
||||
{item.icon}
|
||||
<span className="ml-2 text-sm">{item.name}</span>
|
||||
</Link>
|
||||
),
|
||||
}));
|
||||
}, [routes]);
|
||||
|
||||
const dropDownSettings: ItemType[] = useMemo(() => {
|
||||
return settings
|
||||
.filter((item) => !item.noDropdownItem)
|
||||
.map<ItemType>((item) => ({
|
||||
key: item.key,
|
||||
label: (
|
||||
<div className="text-base" onClick={item.onClick}>
|
||||
{item.icon}
|
||||
<span className="ml-2 text-sm">{item.name}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
}, [settings]);
|
||||
|
||||
const handleDelChat = useCallback(
|
||||
(dialogue: IChatDialogueSchema) => {
|
||||
Modal.confirm({
|
||||
title: 'Delete Chat',
|
||||
content: 'Are you sure delete this chat?',
|
||||
width: '276px',
|
||||
centered: true,
|
||||
onOk() {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
const [err] = await apiInterceptors(delDialogue(dialogue.conv_uid));
|
||||
if (err) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
message.success('success');
|
||||
refreshDialogList();
|
||||
dialogue.chat_mode === scene && dialogue.conv_uid === chatId && replace('/');
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
[refreshDialogList],
|
||||
);
|
||||
|
||||
const copyLink = useCallback((item: IChatDialogueSchema) => {
|
||||
const success = copy(`${location.origin}/chat/${item.chat_mode}/${item.conv_uid}`);
|
||||
message[success ? 'success' : 'error'](success ? 'Copy success' : 'Copy failed');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
queryDialogueList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogo(mode === 'dark' ? '/WHITE_LOGO.png' : '/LOGO_1.png');
|
||||
}, [mode]);
|
||||
|
||||
if (!isMenuExpand) {
|
||||
return (
|
||||
<div className="flex flex-col justify-between h-screen border-r dark:bg-[#1A1E26] 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"
|
||||
>
|
||||
<PlusOutlined className="text-lg" />
|
||||
</Link>
|
||||
</div>
|
||||
{/* Chat List */}
|
||||
<div className="flex-1 overflow-y-scroll py-4 border-t border-dashed space-y-2">
|
||||
{dialogueList?.map((item) => {
|
||||
const active = item.conv_uid === chatId && item.chat_mode === scene;
|
||||
|
||||
return (
|
||||
<Tooltip key={item.conv_uid} title={item.user_name || item.user_input} placement="right">
|
||||
<Link href={`/chat?scene=${item.chat_mode}&id=${item.conv_uid}`} className={smallMenuItemStyle(active)}>
|
||||
<MessageOutlined />
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="py-4 space-y-2 border-t">
|
||||
<Dropdown menu={{ items: dropDownRoutes }} placement="topRight">
|
||||
<div className={smallMenuItemStyle()}>
|
||||
<MenuOutlined />
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Dropdown menu={{ items: dropDownSettings }} placement="topRight">
|
||||
<div className={smallMenuItemStyle()}>
|
||||
<SettingOutlined />
|
||||
</div>
|
||||
</Dropdown>
|
||||
{settings
|
||||
.filter((item) => item.noDropdownItem)
|
||||
.map((item) => (
|
||||
<Tooltip key={item.key} title={item.name} placement="right">
|
||||
<div className={smallMenuItemStyle()} onClick={item.onClick}>
|
||||
{item.icon}
|
||||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen border-r dark:border-gray-700">
|
||||
{/* 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"
|
||||
>
|
||||
<PlusOutlined className="mr-2" />
|
||||
<span>New Chat</span>
|
||||
</Link>
|
||||
{/* Chat List */}
|
||||
<div className="flex-1 overflow-y-scroll py-4 px-2 border-t dark:border-gray-700">
|
||||
{dialogueList?.map((item) => {
|
||||
const active = item.conv_uid === chatId && item.chat_mode === scene;
|
||||
|
||||
return (
|
||||
<Link key={item.conv_uid} href={`/chat?scene=${item.chat_mode}&id=${item.conv_uid}`} className={`group/item ${menuItemStyle(active)}`}>
|
||||
<MessageOutlined className="text-base" />
|
||||
<div className="flex-1 line-clamp-1 mx-2 text-sm">{item.user_name || item.user_input}</div>
|
||||
<div
|
||||
className="group-hover/item:opacity-100 cursor-pointer opacity-0 mr-1"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
copyLink(item);
|
||||
}}
|
||||
>
|
||||
<ShareAltOutlined />
|
||||
</div>
|
||||
<div
|
||||
className="group-hover/item:opacity-100 cursor-pointer opacity-0"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleDelChat(item);
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Settings */}
|
||||
<div className="py-2 border-t dark:border-gray-700">
|
||||
<div className="px-2">
|
||||
{routes.map((item) => (
|
||||
<Link key={item.key} href={item.path} className={`${menuItemStyle(pathname === item.path)}`}>
|
||||
<>
|
||||
{item.icon}
|
||||
<span className="ml-2 text-sm">{item.name}</span>
|
||||
</>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-around py-4 border-t border-dashed dark:border-gray-700">
|
||||
{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}>
|
||||
{item.icon}
|
||||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SideBar;
|
61
web/components/layout/top-progress-bar.tsx
Normal file
61
web/components/layout/top-progress-bar.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import Router from 'next/router';
|
||||
import NProgress from 'nprogress';
|
||||
|
||||
let timer: any;
|
||||
let state: any;
|
||||
let activeRequests = 0;
|
||||
const delay = 250;
|
||||
|
||||
function load() {
|
||||
if (state === 'loading') {
|
||||
return;
|
||||
}
|
||||
|
||||
state = 'loading';
|
||||
|
||||
timer = setTimeout(function () {
|
||||
NProgress.start();
|
||||
}, delay); // only show progress bar if it takes longer than the delay
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (activeRequests > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = 'stop';
|
||||
|
||||
clearTimeout(timer);
|
||||
NProgress.done();
|
||||
}
|
||||
|
||||
Router.events.on('routeChangeStart', load);
|
||||
Router.events.on('routeChangeComplete', stop);
|
||||
Router.events.on('routeChangeError', stop);
|
||||
|
||||
if (typeof window !== 'undefined' && typeof window?.fetch === 'function') {
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = async function (...args) {
|
||||
if (activeRequests === 0) {
|
||||
load();
|
||||
}
|
||||
|
||||
activeRequests++;
|
||||
|
||||
try {
|
||||
const response = await originalFetch(...args);
|
||||
return response;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
} finally {
|
||||
activeRequests -= 1;
|
||||
if (activeRequests === 0) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function TopProgressBar() {
|
||||
return null;
|
||||
}
|
Reference in New Issue
Block a user