### What problem does this PR solve? Feat: Add SearchPage component. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.0
| @@ -46,6 +46,14 @@ export const useNavigatePage = () => { | |||
| navigate(Routes.Agent); | |||
| }, [navigate]); | |||
| const navigateToSearchList = useCallback(() => { | |||
| navigate(Routes.Searches); | |||
| }, [navigate]); | |||
| const navigateToSearch = useCallback(() => { | |||
| navigate(Routes.Search); | |||
| }, [navigate]); | |||
| const navigateToChunkParsedResult = useCallback( | |||
| (id: string, knowledgeId?: string) => () => { | |||
| navigate( | |||
| @@ -91,5 +99,7 @@ export const useNavigatePage = () => { | |||
| navigateToChunk, | |||
| navigateToAgentList, | |||
| navigateToAgent, | |||
| navigateToSearchList, | |||
| navigateToSearch, | |||
| }; | |||
| }; | |||
| @@ -32,7 +32,7 @@ export function Header() { | |||
| () => [ | |||
| { path: Routes.Datasets, name: t('knowledgeBase'), icon: Library }, | |||
| { path: Routes.Chats, name: t('chat'), icon: MessageSquareText }, | |||
| { path: Routes.Search, name: t('search'), icon: Search }, | |||
| { path: Routes.Searches, name: t('search'), icon: Search }, | |||
| { path: Routes.Agents, name: t('flow'), icon: Cpu }, | |||
| { path: Routes.Files, name: t('fileManager'), icon: File }, | |||
| ], | |||
| @@ -1,3 +1,23 @@ | |||
| export default function Search() { | |||
| return <div>Search</div>; | |||
| import { PageHeader } from '@/components/page-header'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { EllipsisVertical } from 'lucide-react'; | |||
| export default function SearchPage() { | |||
| const { navigateToSearchList } = useNavigatePage(); | |||
| return ( | |||
| <section> | |||
| <PageHeader back={navigateToSearchList} title="Search app 01"> | |||
| <div className="flex items-center gap-2"> | |||
| <Button variant={'icon'} size={'icon'}> | |||
| <EllipsisVertical /> | |||
| </Button> | |||
| <Button variant={'tertiary'} size={'sm'}> | |||
| Publish | |||
| </Button> | |||
| </div> | |||
| </PageHeader> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| import ListFilterBar from '@/components/list-filter-bar'; | |||
| import { useFetchFlowList } from '@/hooks/flow-hooks'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { SearchCard } from './search-card'; | |||
| export default function SearchList() { | |||
| const { data } = useFetchFlowList(); | |||
| return ( | |||
| <section> | |||
| <div className="px-8 pt-8"> | |||
| <ListFilterBar title="Search apps"> | |||
| <Plus className="mr-2 h-4 w-4" /> | |||
| Create app | |||
| </ListFilterBar> | |||
| </div> | |||
| <div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8 max-h-[84vh] overflow-auto px-8"> | |||
| {data.map((x) => { | |||
| return <SearchCard key={x.id} data={x}></SearchCard>; | |||
| })} | |||
| </div> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,53 @@ | |||
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { IFlow } from '@/interfaces/database/flow'; | |||
| import { formatPureDate } from '@/utils/date'; | |||
| import { ChevronRight, Trash2 } from 'lucide-react'; | |||
| interface IProps { | |||
| data: IFlow; | |||
| } | |||
| export function SearchCard({ data }: IProps) { | |||
| const { navigateToSearch } = useNavigatePage(); | |||
| return ( | |||
| <Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard"> | |||
| <CardContent className="p-4"> | |||
| <div className="flex justify-between mb-4"> | |||
| {data.avatar ? ( | |||
| <div | |||
| className="w-[70px] h-[70px] rounded-xl bg-cover" | |||
| style={{ backgroundImage: `url(${data.avatar})` }} | |||
| /> | |||
| ) : ( | |||
| <Avatar className="w-[70px] h-[70px]"> | |||
| <AvatarImage src="https://github.com/shadcn.png" /> | |||
| <AvatarFallback>CN</AvatarFallback> | |||
| </Avatar> | |||
| )} | |||
| </div> | |||
| <h3 className="text-xl font-bold mb-2">{data.title}</h3> | |||
| <p>An app that does things An app that does things</p> | |||
| <section className="flex justify-between pt-3"> | |||
| <div> | |||
| Search app | |||
| <p className="text-sm opacity-80"> | |||
| {formatPureDate(data.update_time)} | |||
| </p> | |||
| </div> | |||
| <div className="space-x-2"> | |||
| <Button variant="icon" size="icon" onClick={navigateToSearch}> | |||
| <ChevronRight className="h-6 w-6" /> | |||
| </Button> | |||
| <Button variant="icon" size="icon"> | |||
| <Trash2 /> | |||
| </Button> | |||
| </div> | |||
| </section> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| } | |||
| @@ -6,6 +6,7 @@ export enum Routes { | |||
| Dataset = `${Routes.DatasetBase}${Routes.DatasetBase}`, | |||
| Agent = '/agent', | |||
| Agents = '/agents', | |||
| Searches = '/next-searches', | |||
| Search = '/next-search', | |||
| Chats = '/next-chats', | |||
| Chat = '/next-chat', | |||
| @@ -186,16 +187,21 @@ const routes = [ | |||
| component: `@/pages${Routes.Chats}/chat`, | |||
| }, | |||
| { | |||
| path: Routes.Search, | |||
| path: Routes.Searches, | |||
| layout: false, | |||
| component: '@/layouts/next', | |||
| routes: [ | |||
| { | |||
| path: Routes.Search, | |||
| component: `@/pages${Routes.Search}`, | |||
| path: Routes.Searches, | |||
| component: `@/pages${Routes.Searches}`, | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| path: Routes.Search, | |||
| layout: false, | |||
| component: `@/pages${Routes.Search}`, | |||
| }, | |||
| { | |||
| path: Routes.Agents, | |||
| layout: false, | |||