### What problem does this PR solve? Feat: Add dataset sidebar #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.14.1
| Configuration = 'configuration', | Configuration = 'configuration', | ||||
| } | } | ||||
| export const DatasetBaseKey = 'dataset'; | |||||
| export enum RunningStatus { | export enum RunningStatus { | ||||
| UNSTART = '0', // need to run | UNSTART = '0', // need to run | ||||
| RUNNING = '1', // need to cancel | RUNNING = '1', // need to cancel |
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||||
| import { Button } from '@/components/ui/button'; | |||||
| import { Container } from '@/components/ui/container'; | |||||
| import { Segmented, SegmentedValue } from '@/components/ui/segmented '; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { useNavigateWithFromState } from '@/hooks/route-hook'; | |||||
| import { | |||||
| ChevronDown, | |||||
| Cpu, | |||||
| Github, | |||||
| Library, | |||||
| MessageSquareText, | |||||
| Search, | |||||
| Star, | |||||
| Zap, | |||||
| } from 'lucide-react'; | |||||
| import { useCallback, useMemo, useState } from 'react'; | |||||
| import { useLocation } from 'umi'; | |||||
| export function Header() { | |||||
| const { t } = useTranslate('header'); | |||||
| const { pathname } = useLocation(); | |||||
| const navigate = useNavigateWithFromState(); | |||||
| const [currentPath, setCurrentPath] = useState('/home'); | |||||
| const tagsData = useMemo( | |||||
| () => [ | |||||
| { path: '/home', name: t('knowledgeBase'), icon: Library }, | |||||
| { path: '/chat', name: t('chat'), icon: MessageSquareText }, | |||||
| { path: '/search', name: t('search'), icon: Search }, | |||||
| { path: '/flow', name: t('flow'), icon: Cpu }, | |||||
| // { path: '/file', name: t('fileManager'), icon: FileIcon }, | |||||
| ], | |||||
| [t], | |||||
| ); | |||||
| const options = useMemo(() => { | |||||
| return tagsData.map((tag) => { | |||||
| const HeaderIcon = tag.icon; | |||||
| return { | |||||
| label: ( | |||||
| <div className="flex items-center gap-1"> | |||||
| <HeaderIcon className="size-5"></HeaderIcon> | |||||
| <span>{tag.name}</span> | |||||
| </div> | |||||
| ), | |||||
| value: tag.path, | |||||
| }; | |||||
| }); | |||||
| }, [tagsData]); | |||||
| // const currentPath = useMemo(() => { | |||||
| // return tagsData.find((x) => pathname.startsWith(x.path))?.name || 'home'; | |||||
| // }, [pathname, tagsData]); | |||||
| const handleChange = (path: SegmentedValue) => { | |||||
| // navigate(path as string); | |||||
| setCurrentPath(path as string); | |||||
| }; | |||||
| const handleLogoClick = useCallback(() => { | |||||
| navigate('/'); | |||||
| }, [navigate]); | |||||
| return ( | |||||
| <section className="py-6 px-10 flex justify-between items-center border-b"> | |||||
| <div className="flex items-center gap-4"> | |||||
| <img | |||||
| src={'/logo.svg'} | |||||
| alt="logo" | |||||
| className="w-[100] h-[100] mr-[12]" | |||||
| onClick={handleLogoClick} | |||||
| /> | |||||
| <Button variant="secondary"> | |||||
| <Github /> | |||||
| 21.5k stars | |||||
| <Star /> | |||||
| </Button> | |||||
| </div> | |||||
| <div> | |||||
| <Segmented | |||||
| options={options} | |||||
| value={currentPath} | |||||
| onChange={handleChange} | |||||
| className="bg-colors-background-inverse-standard text-backgroundInverseStandard-foreground" | |||||
| ></Segmented> | |||||
| </div> | |||||
| <div className="flex items-center gap-4"> | |||||
| <Container> | |||||
| V 0.13.0 | |||||
| <Button variant="secondary" className="size-8"> | |||||
| <ChevronDown /> | |||||
| </Button> | |||||
| </Container> | |||||
| <Container className="px-3 py-2"> | |||||
| <Avatar className="w-[30px] h-[30px]"> | |||||
| <AvatarImage src="https://github.com/shadcn.png" /> | |||||
| <AvatarFallback>CN</AvatarFallback> | |||||
| </Avatar> | |||||
| yifanwu92@gmail.com | |||||
| <Button | |||||
| variant="destructive" | |||||
| className="py-[2px] px-[8px] h-[23px] rounded-[4px]" | |||||
| > | |||||
| <Zap /> | |||||
| Pro | |||||
| </Button> | |||||
| </Container> | |||||
| </div> | |||||
| </section> | |||||
| ); | |||||
| } |
| import { Outlet } from 'umi'; | |||||
| import { Header } from './next-header'; | |||||
| export default function NextLayout() { | |||||
| return ( | |||||
| <section> | |||||
| <Header></Header> | |||||
| <Outlet /> | |||||
| </section> | |||||
| ); | |||||
| } |
| export default function Dataset() { | |||||
| return <div>Outset</div>; | |||||
| } |
| import { Outlet } from 'umi'; | |||||
| import { SideBar } from './sidebar'; | |||||
| export default function DatasetWrapper() { | |||||
| return ( | |||||
| <div className="text-foreground flex"> | |||||
| <SideBar></SideBar> | |||||
| <div className="p-6"> | |||||
| <Outlet /> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| } |
| export default function DatasetSettings() { | |||||
| return <div>DatasetSettings</div>; | |||||
| } |
| import { DatasetBaseKey, KnowledgeRouteKey } from '@/constants/knowledge'; | |||||
| import { useCallback } from 'react'; | |||||
| import { useNavigate } from 'umi'; | |||||
| export const useHandleMenuClick = () => { | |||||
| const navigate = useNavigate(); | |||||
| const handleMenuClick = useCallback( | |||||
| (key: KnowledgeRouteKey) => () => { | |||||
| navigate(`/${DatasetBaseKey}/${key}`); | |||||
| }, | |||||
| [navigate], | |||||
| ); | |||||
| return { handleMenuClick }; | |||||
| }; |
| import { Button } from '@/components/ui/button'; | |||||
| import { KnowledgeRouteKey } from '@/constants/knowledge'; | |||||
| import { useSecondPathName } from '@/hooks/route-hook'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| import { Banknote, LayoutGrid, User } from 'lucide-react'; | |||||
| import { useHandleMenuClick } from './hooks'; | |||||
| const items = [ | |||||
| { icon: User, label: 'Dataset', key: KnowledgeRouteKey.Dataset }, | |||||
| { | |||||
| icon: LayoutGrid, | |||||
| label: 'Retrieval testing', | |||||
| key: KnowledgeRouteKey.Testing, | |||||
| }, | |||||
| { icon: Banknote, label: 'Settings', key: KnowledgeRouteKey.Configuration }, | |||||
| ]; | |||||
| const dataset = { | |||||
| id: 1, | |||||
| title: 'Legal knowledge base', | |||||
| files: '1,242 files', | |||||
| size: '152 MB', | |||||
| created: '12.02.2024', | |||||
| image: 'https://github.com/shadcn.png', | |||||
| }; | |||||
| export function SideBar() { | |||||
| const pathName = useSecondPathName(); | |||||
| const { handleMenuClick } = useHandleMenuClick(); | |||||
| return ( | |||||
| <aside className="w-[303px]"> | |||||
| <div className="p-6 space-y-2 border-b"> | |||||
| <div | |||||
| className="w-[70px] h-[70px] rounded-xl bg-cover" | |||||
| style={{ backgroundImage: `url(${dataset.image})` }} | |||||
| /> | |||||
| <h3 className="text-lg font-semibold mb-2">{dataset.title}</h3> | |||||
| <div className="text-sm opacity-80"> | |||||
| {dataset.files} | {dataset.size} | |||||
| </div> | |||||
| <div className="text-sm opacity-80">Created {dataset.created}</div> | |||||
| </div> | |||||
| <div className="mt-4"> | |||||
| {items.map((item, itemIdx) => { | |||||
| const active = pathName === item.key; | |||||
| return ( | |||||
| <Button | |||||
| key={itemIdx} | |||||
| variant={active ? 'secondary' : 'ghost'} | |||||
| className={cn('w-full justify-start gap-2.5 p-6 relative')} | |||||
| onClick={handleMenuClick(item.key)} | |||||
| > | |||||
| <item.icon className="w-6 h-6" /> | |||||
| <span>{item.label}</span> | |||||
| {active && ( | |||||
| <div className="absolute right-0 w-[5px] h-[66px] bg-primary rounded-l-xl shadow-[0_0_5.94px_#7561ff,0_0_11.88px_#7561ff,0_0_41.58px_#7561ff,0_0_83.16px_#7561ff,0_0_142.56px_#7561ff,0_0_249.48px_#7561ff]" /> | |||||
| )} | |||||
| </Button> | |||||
| ); | |||||
| })} | |||||
| </div> | |||||
| </aside> | |||||
| ); | |||||
| } |
| export default function RetrievalTesting() { | |||||
| return <div>Retrieval testing</div>; | |||||
| } |
| { | { | ||||
| path: '/datasets', | path: '/datasets', | ||||
| layout: false, | layout: false, | ||||
| component: '@/pages/datasets', | |||||
| component: '@/layouts/next', | |||||
| routes: [ | |||||
| { | |||||
| path: '/datasets', | |||||
| component: '@/pages/datasets', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| path: '/dataset', | |||||
| layout: false, | |||||
| component: '@/layouts/next', | |||||
| routes: [ | |||||
| { path: '/dataset', redirect: '/dataset/dataset' }, | |||||
| { | |||||
| path: '/dataset', | |||||
| component: '@/pages/dataset', | |||||
| routes: [ | |||||
| { | |||||
| path: '/dataset/dataset', | |||||
| component: '@/pages/dataset/dataset', | |||||
| }, | |||||
| { | |||||
| path: '/dataset/configuration', | |||||
| component: '@/pages/dataset/settings', | |||||
| }, | |||||
| { | |||||
| path: '/dataset/testing', | |||||
| component: '@/pages/dataset/testing', | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| path: '/profile-setting', | path: '/profile-setting', |