mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-14 03:55:58 +08:00
### What problem does this PR solve? Feat: Add AgentTemplates component. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
c326f14fed
commit
744ff55c62
@ -46,6 +46,10 @@ export const useNavigatePage = () => {
|
|||||||
navigate(Routes.Agent);
|
navigate(Routes.Agent);
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
|
const navigateToAgentTemplates = useCallback(() => {
|
||||||
|
navigate(Routes.AgentTemplates);
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
const navigateToSearchList = useCallback(() => {
|
const navigateToSearchList = useCallback(() => {
|
||||||
navigate(Routes.Searches);
|
navigate(Routes.Searches);
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
@ -99,6 +103,7 @@ export const useNavigatePage = () => {
|
|||||||
navigateToChunk,
|
navigateToChunk,
|
||||||
navigateToAgentList,
|
navigateToAgentList,
|
||||||
navigateToAgent,
|
navigateToAgent,
|
||||||
|
navigateToAgentTemplates,
|
||||||
navigateToSearchList,
|
navigateToSearchList,
|
||||||
navigateToSearch,
|
navigateToSearch,
|
||||||
};
|
};
|
||||||
|
51
web/src/pages/agents/agent-templates.tsx
Normal file
51
web/src/pages/agents/agent-templates.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
36
web/src/pages/agents/create-agent-dialog.tsx
Normal file
36
web/src/pages/agents/create-agent-dialog.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
106
web/src/pages/agents/create-agent-form.tsx
Normal file
106
web/src/pages/agents/create-agent-form.tsx
Normal file
@ -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 ListFilterBar from '@/components/list-filter-bar';
|
||||||
import { useFetchFlowList } from '@/hooks/flow-hooks';
|
import { useFetchFlowList } from '@/hooks/flow-hooks';
|
||||||
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import { AgentCard } from './agent-card';
|
import { AgentCard } from './agent-card';
|
||||||
|
|
||||||
export default function Agent() {
|
export default function Agent() {
|
||||||
const { data } = useFetchFlowList();
|
const { data } = useFetchFlowList();
|
||||||
|
const { navigateToAgentTemplates } = useNavigatePage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<div className="px-8 pt-8">
|
<div className="px-8 pt-8">
|
||||||
<ListFilterBar title="Agents">
|
<ListFilterBar title="Agents" showDialog={navigateToAgentTemplates}>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
Create app
|
Create app
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
|
45
web/src/pages/agents/template-card.tsx
Normal file
45
web/src/pages/agents/template-card.tsx
Normal file
@ -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',
|
DatasetBase = '/dataset',
|
||||||
Dataset = `${Routes.DatasetBase}${Routes.DatasetBase}`,
|
Dataset = `${Routes.DatasetBase}${Routes.DatasetBase}`,
|
||||||
Agent = '/agent',
|
Agent = '/agent',
|
||||||
|
AgentTemplates = '/agent-templates',
|
||||||
Agents = '/agents',
|
Agents = '/agents',
|
||||||
Searches = '/next-searches',
|
Searches = '/next-searches',
|
||||||
Search = '/next-search',
|
Search = '/next-search',
|
||||||
@ -218,6 +219,11 @@ const routes = [
|
|||||||
layout: false,
|
layout: false,
|
||||||
component: `@/pages${Routes.Agent}`,
|
component: `@/pages${Routes.Agent}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: Routes.AgentTemplates,
|
||||||
|
layout: false,
|
||||||
|
component: `@/pages${Routes.Agents}${Routes.AgentTemplates}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Files,
|
path: Routes.Files,
|
||||||
layout: false,
|
layout: false,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user