### What problem does this PR solve? Feat: Add AgentTemplates component. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.0
| @@ -46,6 +46,10 @@ export const useNavigatePage = () => { | |||
| navigate(Routes.Agent); | |||
| }, [navigate]); | |||
| const navigateToAgentTemplates = useCallback(() => { | |||
| navigate(Routes.AgentTemplates); | |||
| }, [navigate]); | |||
| const navigateToSearchList = useCallback(() => { | |||
| navigate(Routes.Searches); | |||
| }, [navigate]); | |||
| @@ -99,6 +103,7 @@ export const useNavigatePage = () => { | |||
| navigateToChunk, | |||
| navigateToAgentList, | |||
| navigateToAgent, | |||
| navigateToAgentTemplates, | |||
| navigateToSearchList, | |||
| navigateToSearch, | |||
| }; | |||
| @@ -0,0 +1,51 @@ | |||
| import { PageHeader } from '@/components/page-header'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useFetchFlowTemplates } from '@/hooks/flow-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { CreateAgentDialog } from './create-agent-dialog'; | |||
| import { TemplateCard } from './template-card'; | |||
| export default function AgentTemplates() { | |||
| const { navigateToAgentList } = useNavigatePage(); | |||
| const { t } = useTranslation(); | |||
| const { data: list } = useFetchFlowTemplates(); | |||
| const { | |||
| visible: creatingVisible, | |||
| hideModal: hideCreatingModal, | |||
| showModal: showCreatingModal, | |||
| } = useSetModalState(); | |||
| const handleOk = useCallback(async () => { | |||
| // return onOk(name, checkedId); | |||
| }, []); | |||
| return ( | |||
| <section> | |||
| <PageHeader | |||
| back={navigateToAgentList} | |||
| title={t('flow.createGraph')} | |||
| ></PageHeader> | |||
| <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"> | |||
| {list?.map((x) => { | |||
| return ( | |||
| <TemplateCard | |||
| key={x.id} | |||
| data={x} | |||
| showModal={showCreatingModal} | |||
| ></TemplateCard> | |||
| ); | |||
| })} | |||
| </div> | |||
| {creatingVisible && ( | |||
| <CreateAgentDialog | |||
| loading={false} | |||
| visible={creatingVisible} | |||
| hideModal={hideCreatingModal} | |||
| onOk={handleOk} | |||
| ></CreateAgentDialog> | |||
| )} | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| import { | |||
| Dialog, | |||
| DialogContent, | |||
| DialogFooter, | |||
| DialogHeader, | |||
| DialogTitle, | |||
| } from '@/components/ui/dialog'; | |||
| import { LoadingButton } from '@/components/ui/loading-button'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { TagRenameId } from '@/pages/add-knowledge/constant'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { CreateAgentForm } from './create-agent-form'; | |||
| export function CreateAgentDialog({ | |||
| hideModal, | |||
| onOk, | |||
| loading, | |||
| }: IModalProps<any>) { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Dialog open onOpenChange={hideModal}> | |||
| <DialogContent className="sm:max-w-[425px]"> | |||
| <DialogHeader> | |||
| <DialogTitle>{t('flow.createGraph')}</DialogTitle> | |||
| </DialogHeader> | |||
| <CreateAgentForm hideModal={hideModal} onOk={onOk}></CreateAgentForm> | |||
| <DialogFooter> | |||
| <LoadingButton type="submit" form={TagRenameId} loading={loading}> | |||
| {t('common.save')} | |||
| </LoadingButton> | |||
| </DialogFooter> | |||
| </DialogContent> | |||
| </Dialog> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| 'use client'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { TagRenameId } from '@/pages/add-knowledge/constant'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| export function CreateAgentForm({ hideModal, onOk }: IModalProps<any>) { | |||
| const { t } = useTranslation(); | |||
| const FormSchema = z.object({ | |||
| name: z | |||
| .string() | |||
| .min(1, { | |||
| message: t('common.namePlaceholder'), | |||
| }) | |||
| .trim(), | |||
| tag: z.string().trim().optional(), | |||
| description: z.string().trim().optional(), | |||
| }); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| resolver: zodResolver(FormSchema), | |||
| defaultValues: { name: '' }, | |||
| }); | |||
| async function onSubmit(data: z.infer<typeof FormSchema>) { | |||
| const ret = await onOk?.(data); | |||
| if (ret) { | |||
| hideModal?.(); | |||
| } | |||
| } | |||
| return ( | |||
| <Form {...form}> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| className="space-y-6" | |||
| id={TagRenameId} | |||
| > | |||
| <FormField | |||
| control={form.control} | |||
| name="name" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('common.name')}</FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| placeholder={t('common.namePlaceholder')} | |||
| {...field} | |||
| autoComplete="off" | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="tag" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.tag')}</FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| placeholder={t('flow.tagPlaceholder')} | |||
| {...field} | |||
| autoComplete="off" | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="description" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('flow.description')}</FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| placeholder={t('flow.descriptionPlaceholder')} | |||
| {...field} | |||
| autoComplete="off" | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </form> | |||
| </Form> | |||
| ); | |||
| } | |||
| @@ -1,15 +1,17 @@ | |||
| import ListFilterBar from '@/components/list-filter-bar'; | |||
| import { useFetchFlowList } from '@/hooks/flow-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { AgentCard } from './agent-card'; | |||
| export default function Agent() { | |||
| const { data } = useFetchFlowList(); | |||
| const { navigateToAgentTemplates } = useNavigatePage(); | |||
| return ( | |||
| <section> | |||
| <div className="px-8 pt-8"> | |||
| <ListFilterBar title="Agents"> | |||
| <ListFilterBar title="Agents" showDialog={navigateToAgentTemplates}> | |||
| <Plus className="mr-2 h-4 w-4" /> | |||
| Create app | |||
| </ListFilterBar> | |||
| @@ -0,0 +1,45 @@ | |||
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { IFlowTemplate } from '@/interfaces/database/flow'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| interface IProps { | |||
| data: IFlowTemplate; | |||
| } | |||
| export function TemplateCard({ | |||
| data, | |||
| showModal, | |||
| }: IProps & Pick<ReturnType<typeof useSetModalState>, 'showModal'>) { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard group relative"> | |||
| <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 className="break-words">{data.description}</p> | |||
| <Button | |||
| variant="tertiary" | |||
| className="absolute bottom-4 right-4 left-4 hidden justify-end group-hover:block text-center" | |||
| onClick={showModal} | |||
| > | |||
| {t('flow.useTemplate')} | |||
| </Button> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| } | |||
| @@ -5,6 +5,7 @@ export enum Routes { | |||
| DatasetBase = '/dataset', | |||
| Dataset = `${Routes.DatasetBase}${Routes.DatasetBase}`, | |||
| Agent = '/agent', | |||
| AgentTemplates = '/agent-templates', | |||
| Agents = '/agents', | |||
| Searches = '/next-searches', | |||
| Search = '/next-search', | |||
| @@ -218,6 +219,11 @@ const routes = [ | |||
| layout: false, | |||
| component: `@/pages${Routes.Agent}`, | |||
| }, | |||
| { | |||
| path: Routes.AgentTemplates, | |||
| layout: false, | |||
| component: `@/pages${Routes.Agents}${Routes.AgentTemplates}`, | |||
| }, | |||
| { | |||
| path: Routes.Files, | |||
| layout: false, | |||