diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 6a3f53eca..17d5424ba 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -24,9 +24,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; import { get, set } from 'lodash'; import { useCallback, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import { useParams, useSearchParams } from 'umi'; -import { v4 as uuid } from 'uuid'; import { useGetPaginationWithRouter, useHandleSearchChange, @@ -80,7 +78,7 @@ export const EmptyDsl = { component_name: 'Begin', params: {}, }, - downstream: ['Answer:China'], // other edge target is downstream, edge source is current node id + downstream: [], // other edge target is downstream, edge source is current node id upstream: [], // edge source is upstream, edge target is current node id }, }, @@ -96,21 +94,11 @@ export const EmptyDsl = { }; export const useFetchAgentTemplates = () => { - const { t } = useTranslation(); - const { data } = useQuery({ queryKey: [AgentApiAction.FetchAgentTemplates], initialData: [], queryFn: async () => { const { data } = await agentService.listTemplates(); - if (Array.isArray(data?.data)) { - data.data.unshift({ - id: uuid(), - title: t('flow.blank'), - description: t('flow.createFromNothing'), - dsl: EmptyDsl, - }); - } return data.data; }, diff --git a/web/src/interfaces/database/agent.ts b/web/src/interfaces/database/agent.ts index 4fd36de2f..7994426ea 100644 --- a/web/src/interfaces/database/agent.ts +++ b/web/src/interfaces/database/agent.ts @@ -41,8 +41,8 @@ export interface DSL { path?: string[]; answer?: any[]; graph?: IGraph; - messages: Message[]; - reference: IReference[]; + messages?: Message[]; + reference?: IReference[]; globals: Record; retrieval: IReference[]; } diff --git a/web/src/pages/agent/hooks/use-export-json.ts b/web/src/pages/agent/hooks/use-export-json.ts index 1efe6bb50..2f2e9242e 100644 --- a/web/src/pages/agent/hooks/use-export-json.ts +++ b/web/src/pages/agent/hooks/use-export-json.ts @@ -1,71 +1,17 @@ -import { useToast } from '@/components/hooks/use-toast'; -import { FileMimeType, Platform } from '@/constants/common'; -import { useSetModalState } from '@/hooks/common-hooks'; import { useFetchAgent } from '@/hooks/use-agent-request'; -import { IGraph } from '@/interfaces/database/flow'; import { downloadJsonFile } from '@/utils/file-util'; -import { message } from 'antd'; -import isEmpty from 'lodash/isEmpty'; import { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; import { useBuildDslData } from './use-build-dsl'; -import { useSetGraphInfo } from './use-set-graph'; -export const useHandleExportOrImportJsonFile = () => { +export const useHandleExportJsonFile = () => { const { buildDslData } = useBuildDslData(); - const { - visible: fileUploadVisible, - hideModal: hideFileUploadModal, - showModal: showFileUploadModal, - } = useSetModalState(); - const setGraphInfo = useSetGraphInfo(); const { data } = useFetchAgent(); - const { t } = useTranslation(); - const { toast } = useToast(); - - const onFileUploadOk = useCallback( - async ({ - fileList, - platform, - }: { - fileList: File[]; - platform: Platform; - }) => { - console.log('🚀 ~ useHandleExportOrImportJsonFile ~ platform:', platform); - if (fileList.length > 0) { - const file = fileList[0]; - if (file.type !== FileMimeType.Json) { - toast({ title: t('flow.jsonUploadTypeErrorMessage') }); - return; - } - - const graphStr = await file.text(); - const errorMessage = t('flow.jsonUploadContentErrorMessage'); - try { - const graph = JSON.parse(graphStr); - if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) { - setGraphInfo(graph ?? ({} as IGraph)); - hideFileUploadModal(); - } else { - message.error(errorMessage); - } - } catch (error) { - message.error(errorMessage); - } - } - }, - [hideFileUploadModal, setGraphInfo, t, toast], - ); const handleExportJson = useCallback(() => { downloadJsonFile(buildDslData().graph, `${data.title}.json`); }, [buildDslData, data.title]); return { - fileUploadVisible, handleExportJson, - handleImportJson: showFileUploadModal, - hideFileUploadModal, - onFileUploadOk, }; }; diff --git a/web/src/pages/agent/index.tsx b/web/src/pages/agent/index.tsx index b356a9319..4c49c6daa 100644 --- a/web/src/pages/agent/index.tsx +++ b/web/src/pages/agent/index.tsx @@ -24,7 +24,6 @@ import { ReactFlowProvider } from '@xyflow/react'; import { ChevronDown, CirclePlay, - Download, History, LaptopMinimalCheck, Logs, @@ -37,7 +36,7 @@ import { useTranslation } from 'react-i18next'; import { useParams } from 'umi'; import AgentCanvas from './canvas'; import { DropdownProvider } from './canvas/context'; -import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; +import { useHandleExportJsonFile } from './hooks/use-export-json'; import { useFetchDataOnMount } from './hooks/use-fetch-data'; import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query'; import { @@ -46,7 +45,6 @@ import { useWatchAgentChange, } from './hooks/use-save-graph'; import { SettingDialog } from './setting-dialog'; -import { UploadAgentDialog } from './upload-agent-dialog'; import { useAgentHistoryManager } from './use-agent-history-manager'; import { VersionDialog } from './version-dialog'; @@ -71,13 +69,8 @@ export default function Agent() { } = useSetModalState(); const { t } = useTranslation(); useAgentHistoryManager(); - const { - handleExportJson, - handleImportJson, - fileUploadVisible, - onFileUploadOk, - hideFileUploadModal, - } = useHandleExportOrImportJsonFile(); + + const { handleExportJson } = useHandleExportJsonFile(); const { saveGraph, loading } = useSaveGraph(); const { flowDetail: agentDetail } = useFetchDataOnMount(); const inputs = useGetBeginNodeDataInputs(); @@ -158,11 +151,6 @@ export default function Agent() { - - - {t('flow.import')} - - {t('flow.export')} @@ -193,12 +181,6 @@ export default function Agent() { > - {fileUploadVisible && ( - - )} {embedVisible && (
- {tempListFilter?.map((x, index) => { + {tempListFilter?.map((x) => { return ( & { shouldChooseAgent?: boolean; }; -enum FlowType { - Agent = 'agent', - Flow = 'flow', -} - type FlowTypeCardProps = { value?: FlowType; onChange?: (value: FlowType) => void; @@ -51,7 +40,7 @@ function FlowTypeCards({ value, onChange }: FlowTypeCardProps) { @@ -81,30 +70,28 @@ function FlowTypeCards({ value, onChange }: FlowTypeCardProps) { ); } +export const FormSchema = z.object({ + ...NameFormSchema, + tag: z.string().trim().optional(), + description: z.string().trim().optional(), + type: z.nativeEnum(FlowType).optional(), +}); + +export type FormSchemaType = z.infer; + export function CreateAgentForm({ hideModal, onOk, shouldChooseAgent = false, }: CreateAgentFormProps) { 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(), - type: z.nativeEnum(FlowType).optional(), - }); - const form = useForm>({ + const form = useForm({ resolver: zodResolver(FormSchema), defaultValues: { name: '', type: FlowType.Agent }, }); - async function onSubmit(data: z.infer) { + async function onSubmit(data: FormSchemaType) { const ret = await onOk?.(data); if (ret) { hideModal?.(); @@ -123,23 +110,7 @@ export function CreateAgentForm({ )} - ( - - {t('common.name')} - - - - - - )} - /> + ); diff --git a/web/src/pages/agents/hooks/use-create-agent.ts b/web/src/pages/agents/hooks/use-create-agent.ts new file mode 100644 index 000000000..3693ce4ec --- /dev/null +++ b/web/src/pages/agents/hooks/use-create-agent.ts @@ -0,0 +1,42 @@ +import { useSetModalState } from '@/hooks/common-hooks'; +import { EmptyDsl, useSetAgent } from '@/hooks/use-agent-request'; +import { DSL } from '@/interfaces/database/agent'; +import { useCallback } from 'react'; +import { FlowType } from '../constant'; +import { FormSchemaType } from '../create-agent-form'; + +export function useCreateAgentOrPipeline() { + const { loading, setAgent } = useSetAgent(); + const { + visible: creatingVisible, + hideModal: hideCreatingModal, + showModal: showCreatingModal, + } = useSetModalState(); + + const createAgent = useCallback( + async (name: string) => { + return setAgent({ title: name, dsl: EmptyDsl as DSL }); + }, + [setAgent], + ); + + const handleCreateAgentOrPipeline = useCallback( + async (data: FormSchemaType) => { + if (data.type === FlowType.Agent) { + const ret = await createAgent(data.name); + if (ret.code === 0) { + hideCreatingModal(); + } + } + }, + [createAgent, hideCreatingModal], + ); + + return { + loading, + creatingVisible, + hideCreatingModal, + showCreatingModal, + handleCreateAgentOrPipeline, + }; +} diff --git a/web/src/pages/agents/index.tsx b/web/src/pages/agents/index.tsx index 95c6f1808..5889753dd 100644 --- a/web/src/pages/agents/index.tsx +++ b/web/src/pages/agents/index.tsx @@ -8,7 +8,6 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; -import { useSetModalState } from '@/hooks/common-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useFetchAgentListByPage } from '@/hooks/use-agent-request'; import { t } from 'i18next'; @@ -17,6 +16,9 @@ import { Clipboard, ClipboardPlus, FileInput, Plus } from 'lucide-react'; import { useCallback } from 'react'; import { AgentCard } from './agent-card'; import { CreateAgentDialog } from './create-agent-dialog'; +import { useCreateAgentOrPipeline } from './hooks/use-create-agent'; +import { UploadAgentDialog } from './upload-agent-dialog'; +import { useHandleImportJsonFile } from './use-import-json'; import { useRenameAgent } from './use-rename-agent'; export default function Agents() { @@ -34,10 +36,19 @@ export default function Agents() { } = useRenameAgent(); const { - visible: creatingVisible, - hideModal: hideCreatingModal, - showModal: showCreatingModal, - } = useSetModalState(); + creatingVisible, + hideCreatingModal, + showCreatingModal, + loading, + handleCreateAgentOrPipeline, + } = useCreateAgentOrPipeline(); + + const { + handleImportJson, + fileUploadVisible, + onFileUploadOk, + hideFileUploadModal, + } = useHandleImportJsonFile(); const handlePageChange = useCallback( (page: number, pageSize?: number) => { @@ -77,7 +88,10 @@ export default function Agents() { Create from Template - + Import json file @@ -115,13 +129,19 @@ export default function Agents() { )} {creatingVisible && ( {}} + onOk={handleCreateAgentOrPipeline} > )} + {fileUploadVisible && ( + + )} ); } diff --git a/web/src/pages/agents/name-form-field.tsx b/web/src/pages/agents/name-form-field.tsx new file mode 100644 index 000000000..f18e46924 --- /dev/null +++ b/web/src/pages/agents/name-form-field.tsx @@ -0,0 +1,28 @@ +import { RAGFlowFormItem } from '@/components/ragflow-form'; +import { Input } from '@/components/ui/input'; +import i18n from '@/locales/config'; +import { useTranslation } from 'react-i18next'; +import { z } from 'zod'; + +export const NameFormSchema = { + name: z + .string() + .min(1, { + message: i18n.t('common.namePlaceholder'), + }) + .trim(), +}; + +export function NameFormField() { + const { t } = useTranslation(); + return ( + + + + ); +} diff --git a/web/src/pages/agents/template-card.tsx b/web/src/pages/agents/template-card.tsx index 3a59e40c8..76b02ee3c 100644 --- a/web/src/pages/agents/template-card.tsx +++ b/web/src/pages/agents/template-card.tsx @@ -3,7 +3,6 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { IFlowTemplate } from '@/interfaces/database/flow'; import i18n from '@/locales/config'; -import { Plus } from 'lucide-react'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; interface IProps { @@ -12,7 +11,7 @@ interface IProps { showModal(record: IFlowTemplate): void; } -export function TemplateCard({ data, showModal, isCreate = false }: IProps) { +export function TemplateCard({ data, showModal }: IProps) { const { t } = useTranslation(); const handleClick = useCallback(() => { @@ -26,41 +25,24 @@ export function TemplateCard({ data, showModal, isCreate = false }: IProps) { return ( - {isCreate && ( -
+ +
{data?.title[language]}
+
+

{data?.description[language]}

+
+
- )} - {!isCreate && ( - <> -
- -
- {data?.title[language]} -
-
-

{data?.description[language]}

-
- -
- - )} + {t('flow.useTemplate')} + +
); diff --git a/web/src/pages/agent/upload-agent-dialog/index.tsx b/web/src/pages/agents/upload-agent-dialog/index.tsx similarity index 85% rename from web/src/pages/agent/upload-agent-dialog/index.tsx rename to web/src/pages/agents/upload-agent-dialog/index.tsx index 45f9ec882..b2084bc0f 100644 --- a/web/src/pages/agent/upload-agent-dialog/index.tsx +++ b/web/src/pages/agents/upload-agent-dialog/index.tsx @@ -1,3 +1,4 @@ +import { ButtonLoading } from '@/components/ui/button'; import { Dialog, DialogContent, @@ -5,7 +6,6 @@ import { 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'; @@ -26,9 +26,9 @@ export function UploadAgentDialog({ - + {t('common.save')} - + diff --git a/web/src/pages/agent/upload-agent-dialog/upload-agent-form.tsx b/web/src/pages/agents/upload-agent-dialog/upload-agent-form.tsx similarity index 56% rename from web/src/pages/agent/upload-agent-dialog/upload-agent-form.tsx rename to web/src/pages/agents/upload-agent-dialog/upload-agent-form.tsx index 39a4ac012..df2d21c15 100644 --- a/web/src/pages/agent/upload-agent-dialog/upload-agent-form.tsx +++ b/web/src/pages/agents/upload-agent-dialog/upload-agent-form.tsx @@ -13,32 +13,24 @@ import { FormLabel, FormMessage, } from '@/components/ui/form'; -import { FileMimeType, Platform } from '@/constants/common'; +import { FileMimeType } from '@/constants/common'; import { IModalProps } from '@/interfaces/common'; import { TagRenameId } from '@/pages/add-knowledge/constant'; -import { useTranslation } from 'react-i18next'; +import { NameFormField, NameFormSchema } from '../name-form-field'; -// const options = Object.values(Platform).map((x) => ({ label: x, value: x })); +export const FormSchema = z.object({ + fileList: z.array(z.instanceof(File)), + ...NameFormSchema, +}); +export type FormSchemaType = z.infer; export function UploadAgentForm({ hideModal, onOk }: IModalProps) { - const { t } = useTranslation(); - const FormSchema = z.object({ - platform: z - .string() - .min(1, { - message: t('common.namePlaceholder'), - }) - .trim(), - fileList: z.array(z.instanceof(File)), - }); - const form = useForm>({ resolver: zodResolver(FormSchema), - defaultValues: { platform: Platform.RAGFlow }, + defaultValues: { name: '' }, }); - async function onSubmit(data: z.infer) { - console.log('🚀 ~ onSubmit ~ data:', data); + async function onSubmit(data: FormSchemaType) { const ret = await onOk?.(data); if (ret) { hideModal?.(); @@ -52,12 +44,13 @@ export function UploadAgentForm({ hideModal, onOk }: IModalProps) { className="space-y-6" id={TagRenameId} > + ( - {t('common.name')} + DSL ) { )} /> - {/* ( - - {t('common.name')} - - - - - - )} - /> */} ); diff --git a/web/src/pages/agents/use-import-json.ts b/web/src/pages/agents/use-import-json.ts new file mode 100644 index 000000000..54c0f89a3 --- /dev/null +++ b/web/src/pages/agents/use-import-json.ts @@ -0,0 +1,56 @@ +import { useToast } from '@/components/hooks/use-toast'; +import { FileMimeType } from '@/constants/common'; +import { useSetModalState } from '@/hooks/common-hooks'; +import { EmptyDsl, useSetAgent } from '@/hooks/use-agent-request'; +import { message } from 'antd'; +import isEmpty from 'lodash/isEmpty'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FormSchemaType } from './upload-agent-dialog/upload-agent-form'; + +export const useHandleImportJsonFile = () => { + const { + visible: fileUploadVisible, + hideModal: hideFileUploadModal, + showModal: showFileUploadModal, + } = useSetModalState(); + const { t } = useTranslation(); + const { toast } = useToast(); + const { loading, setAgent } = useSetAgent(); + + const onFileUploadOk = useCallback( + async ({ fileList, name }: FormSchemaType) => { + if (fileList.length > 0) { + const file = fileList[0]; + if (file.type !== FileMimeType.Json) { + toast({ title: t('flow.jsonUploadTypeErrorMessage') }); + return; + } + + const graphStr = await file.text(); + const errorMessage = t('flow.jsonUploadContentErrorMessage'); + try { + const graph = JSON.parse(graphStr); + if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) { + const dsl = { ...EmptyDsl, graph }; + setAgent({ title: name, dsl }); + hideFileUploadModal(); + } else { + message.error(errorMessage); + } + } catch (error) { + message.error(errorMessage); + } + } + }, + [hideFileUploadModal, setAgent, t, toast], + ); + + return { + fileUploadVisible, + handleImportJson: showFileUploadModal, + hideFileUploadModal, + onFileUploadOk, + loading, + }; +};