diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 17589dbb3..c372b4c32 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -1,9 +1,17 @@ -import { IFlow } from '@/interfaces/database/flow'; +import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; +import i18n from '@/locales/config'; +import { BeginId } from '@/pages/agent/constant'; +import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks'; import flowService from '@/services/flow-service'; +import { buildMessageListWithUuid } from '@/utils/chat'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; import { message } from 'antd'; +import { get, set } from 'lodash'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'umi'; +import { v4 as uuid } from 'uuid'; import { useGetPaginationWithRouter, useHandleSearchChange, @@ -13,8 +21,77 @@ export const enum AgentApiAction { FetchAgentList = 'fetchAgentList', UpdateAgentSetting = 'updateAgentSetting', DeleteAgent = 'deleteAgent', + FetchAgentDetail = 'fetchAgentDetail', + ResetAgent = 'resetAgent', + SetAgent = 'setAgent', + FetchAgentTemplates = 'fetchAgentTemplates', } +export const EmptyDsl = { + graph: { + nodes: [ + { + id: BeginId, + type: 'beginNode', + position: { + x: 50, + y: 200, + }, + data: { + label: 'Begin', + name: 'begin', + }, + sourcePosition: 'left', + targetPosition: 'right', + }, + ], + edges: [], + }, + components: { + begin: { + obj: { + component_name: 'Begin', + params: {}, + }, + downstream: ['Answer:China'], // other edge target is downstream, edge source is current node id + upstream: [], // edge source is upstream, edge target is current node id + }, + }, + retrieval: [], // reference + history: [], + path: [], + globals: { + 'sys.query': '', + 'sys.user_id': '', + 'sys.conversation_turns': 0, + 'sys.files': [], + }, +}; + +export const useFetchAgentTemplates = () => { + const { t } = useTranslation(); + + const { data } = useQuery({ + queryKey: [AgentApiAction.FetchAgentTemplates], + initialData: [], + queryFn: async () => { + const { data } = await flowService.listTemplates(); + if (Array.isArray(data?.data)) { + data.data.unshift({ + id: uuid(), + title: t('flow.blank'), + description: t('flow.createFromNothing'), + dsl: EmptyDsl, + }); + } + + return data.data; + }, + }); + + return data; +}; + export const useFetchAgentListByPage = () => { const { searchString, handleInputChange } = useHandleSearchChange(); const { pagination, setPagination } = useGetPaginationWithRouter(); @@ -109,3 +186,84 @@ export const useDeleteAgent = () => { return { data, loading, deleteAgent: mutateAsync }; }; + +export const useFetchAgent = (): { + data: IFlow; + loading: boolean; + refetch: () => void; +} => { + const { id } = useParams(); + const { sharedId } = useGetSharedChatSearchParams(); + + const { + data, + isFetching: loading, + refetch, + } = useQuery({ + queryKey: [AgentApiAction.FetchAgentDetail], + initialData: {} as IFlow, + refetchOnReconnect: false, + refetchOnMount: false, + refetchOnWindowFocus: false, + gcTime: 0, + queryFn: async () => { + const { data } = await flowService.getCanvas({}, sharedId || id); + + const messageList = buildMessageListWithUuid( + get(data, 'data.dsl.messages', []), + ); + set(data, 'data.dsl.messages', messageList); + + return data?.data ?? {}; + }, + }); + + return { data, loading, refetch }; +}; + +export const useResetAgent = () => { + const { id } = useParams(); + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [AgentApiAction.ResetAgent], + mutationFn: async () => { + const { data } = await flowService.resetCanvas({ id }); + return data; + }, + }); + + return { data, loading, resetAgent: mutateAsync }; +}; + +export const useSetAgent = () => { + const queryClient = useQueryClient(); + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [AgentApiAction.SetAgent], + mutationFn: async (params: { + id?: string; + title?: string; + dsl?: DSL; + avatar?: string; + }) => { + const { data = {} } = await flowService.setCanvas(params); + if (data.code === 0) { + message.success( + i18n.t(`message.${params?.id ? 'modified' : 'created'}`), + ); + queryClient.invalidateQueries({ + queryKey: [AgentApiAction.FetchAgentList], + }); + } + return data; + }, + }); + + return { data, loading, setAgent: mutateAsync }; +}; diff --git a/web/src/pages/agent/debug-content/index.less b/web/src/pages/agent/debug-content/index.less deleted file mode 100644 index fda707810..000000000 --- a/web/src/pages/agent/debug-content/index.less +++ /dev/null @@ -1,5 +0,0 @@ -.formWrapper { - :global(.ant-form-item-label) { - font-weight: 600 !important; - } -} diff --git a/web/src/pages/agent/debug-content/index.tsx b/web/src/pages/agent/debug-content/index.tsx index 96e25432c..377c53e04 100644 --- a/web/src/pages/agent/debug-content/index.tsx +++ b/web/src/pages/agent/debug-content/index.tsx @@ -23,6 +23,21 @@ import { z } from 'zod'; import { BeginQueryType } from '../constant'; import { BeginQuery } from '../interface'; +export const BeginQueryComponentMap = { + [BeginQueryType.Line]: 'string', + [BeginQueryType.Paragraph]: 'string', + [BeginQueryType.Options]: 'string', + [BeginQueryType.File]: 'file', + [BeginQueryType.Integer]: 'number', + [BeginQueryType.Boolean]: 'boolean', +}; + +const StringFields = [ + BeginQueryType.Line, + BeginQueryType.Paragraph, + BeginQueryType.Options, +]; + interface IProps { parameters: BeginQuery[]; ok(parameters: any[]): void; @@ -44,9 +59,27 @@ const DebugContent = ({ const FormSchema = useMemo(() => { const obj = parameters.reduce((pre, cur, idx) => { - pre[idx] = z.string().optional(); + const type = cur.type; + let fieldSchema; + if (StringFields.some((x) => x === type)) { + fieldSchema = z.string(); + } else if (type === BeginQueryType.Boolean) { + fieldSchema = z.boolean(); + } else if (type === BeginQueryType.Integer) { + fieldSchema = z.coerce.number(); + } else { + fieldSchema = z.instanceof(File); + } + + if (cur.optional) { + fieldSchema.optional(); + } + + pre[idx.toString()] = fieldSchema; + return pre; }, {}); + return z.object(obj); }, [parameters]); @@ -61,6 +94,7 @@ const DebugContent = ({ switchVisible, showModal: showPopover, } = useSetModalState(); + const { setRecord, currentRecord } = useSetSelectedRecord(); // const { submittable } = useHandleSubmittable(form); const submittable = true; @@ -226,29 +260,10 @@ const DebugContent = ({ [form, t], ); - const onOk = useCallback(async () => { - // const values = await form.validateFields(); - const nextValues = Object.entries(values).map(([key, value]) => { - const item = parameters[Number(key)]; - let nextValue = value; - if (Array.isArray(value)) { - nextValue = ``; - - value.forEach((x) => { - nextValue += - x?.originFileObj instanceof File - ? `${x.name}\n${x.response?.data}\n----\n` - : `${x.url}\n${x.result}\n----\n`; - }); - } - return { ...item, value: nextValue }; - }); - - ok(nextValues); - }, [ok, parameters]); - const onSubmit = useCallback( (values: z.infer) => { + console.log('🚀 ~ values:', values); + return values; const nextValues = Object.entries(values).map(([key, value]) => { const item = parameters[Number(key)]; let nextValue = value; @@ -274,20 +289,21 @@ const DebugContent = ({ <>
- + {parameters.map((x, idx) => { return
{renderWidget(x, idx.toString())}
; })} + + {t(isNext ? 'common.next' : 'flow.run')} +
- - {t(isNext ? 'common.next' : 'flow.run')} - ); }; diff --git a/web/src/pages/agent/form/begin-form/index.tsx b/web/src/pages/agent/form/begin-form/index.tsx index c5b4263b2..6edb72cb0 100644 --- a/web/src/pages/agent/form/begin-form/index.tsx +++ b/web/src/pages/agent/form/begin-form/index.tsx @@ -191,7 +191,6 @@ const BeginForm = ({ node }: INextOperatorForm) => { {visible && ( >({ resolver: zodResolver(FormSchema), + mode: 'onChange', defaultValues: { type: BeginQueryType.Line, optional: false, key: '', name: '', + options: [], }, }); @@ -98,10 +101,12 @@ function ParameterForm({ }); useEffect(() => { - form.reset({ - ...initialValue, - options: initialValue.options?.map((x) => ({ value: x })), - }); + if (!isEmpty(initialValue)) { + form.reset({ + ...initialValue, + options: initialValue.options?.map((x) => ({ value: x })), + }); + } }, [form, initialValue]); function onSubmit(data: z.infer) { diff --git a/web/src/pages/agent/hooks/use-save-graph.ts b/web/src/pages/agent/hooks/use-save-graph.ts index 9567b56f2..1023375bb 100644 --- a/web/src/pages/agent/hooks/use-save-graph.ts +++ b/web/src/pages/agent/hooks/use-save-graph.ts @@ -1,4 +1,8 @@ -import { useFetchFlow, useResetFlow, useSetFlow } from '@/hooks/flow-hooks'; +import { + useFetchAgent, + useResetAgent, + useSetAgent, +} from '@/hooks/use-agent-request'; import { RAGFlowNodeType } from '@/interfaces/database/flow'; import { useDebounceEffect } from 'ahooks'; import dayjs from 'dayjs'; @@ -8,20 +12,20 @@ import useGraphStore from '../store'; import { useBuildDslData } from './use-build-dsl'; export const useSaveGraph = () => { - const { data } = useFetchFlow(); - const { setFlow, loading } = useSetFlow(); + const { data } = useFetchAgent(); + const { setAgent, loading } = useSetAgent(); const { id } = useParams(); const { buildDslData } = useBuildDslData(); const saveGraph = useCallback( async (currentNodes?: RAGFlowNodeType[]) => { - return setFlow({ + return setAgent({ id, title: data.title, dsl: buildDslData(currentNodes), }); }, - [setFlow, id, data.title, buildDslData], + [setAgent, id, data.title, buildDslData], ); return { saveGraph, loading }; @@ -29,21 +33,21 @@ export const useSaveGraph = () => { export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => { const { saveGraph, loading } = useSaveGraph(); - const { resetFlow } = useResetFlow(); + const { resetAgent } = useResetAgent(); const handleRun = useCallback( async (nextNodes?: RAGFlowNodeType[]) => { const saveRet = await saveGraph(nextNodes); if (saveRet?.code === 0) { // Call the reset api before opening the run drawer each time - const resetRet = await resetFlow(); + const resetRet = await resetAgent(); // After resetting, all previous messages will be cleared. if (resetRet?.code === 0) { show(); } } }, - [saveGraph, resetFlow, show], + [saveGraph, resetAgent, show], ); return { handleRun, loading }; @@ -54,7 +58,7 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => { const nodes = useGraphStore((state) => state.nodes); const edges = useGraphStore((state) => state.edges); const { saveGraph } = useSaveGraph(); - const { data: flowDetail } = useFetchFlow(); + const { data: flowDetail } = useFetchAgent(); const setSaveTime = useCallback((updateTime: number) => { setTime(dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss')); diff --git a/web/src/pages/agents/agent-templates.tsx b/web/src/pages/agents/agent-templates.tsx index eb2c3df25..3d4b43f83 100644 --- a/web/src/pages/agents/agent-templates.tsx +++ b/web/src/pages/agents/agent-templates.tsx @@ -1,8 +1,9 @@ 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 { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request'; +import { IFlowTemplate } from '@/interfaces/database/flow'; +import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CreateAgentDialog } from './create-agent-dialog'; import { TemplateCard } from './template-card'; @@ -10,16 +11,49 @@ import { TemplateCard } from './template-card'; export default function AgentTemplates() { const { navigateToAgentList } = useNavigatePage(); const { t } = useTranslation(); - const { data: list } = useFetchFlowTemplates(); + const list = useFetchAgentTemplates(); + const { loading, setAgent } = useSetAgent(); + const { visible: creatingVisible, hideModal: hideCreatingModal, showModal: showCreatingModal, } = useSetModalState(); - const handleOk = useCallback(async () => { - // return onOk(name, checkedId); - }, []); + const [template, setTemplate] = useState(); + + const showModal = useCallback( + (record: IFlowTemplate) => { + setTemplate(record); + showCreatingModal(); + }, + [showCreatingModal], + ); + + const { navigateToAgent } = useNavigatePage(); + + const handleOk = useCallback( + async (payload: any) => { + let dsl = template?.dsl; + const ret = await setAgent({ + title: payload.name, + dsl, + avatar: template?.avatar, + }); + + if (ret?.code === 0) { + hideCreatingModal(); + navigateToAgent(ret.data.id)(); + } + }, + [ + hideCreatingModal, + navigateToAgent, + setAgent, + template?.avatar, + template?.dsl, + ], + ); return (
@@ -33,14 +67,14 @@ export default function AgentTemplates() { ); })} {creatingVisible && ( - + {t('common.save')} - + diff --git a/web/src/pages/agents/template-card.tsx b/web/src/pages/agents/template-card.tsx index 38c7fa44e..a0de31d77 100644 --- a/web/src/pages/agents/template-card.tsx +++ b/web/src/pages/agents/template-card.tsx @@ -1,19 +1,22 @@ 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 { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; interface IProps { data: IFlowTemplate; + showModal(record: IFlowTemplate): void; } -export function TemplateCard({ - data, - showModal, -}: IProps & Pick, 'showModal'>) { +export function TemplateCard({ data, showModal }: IProps) { const { t } = useTranslation(); + + const handleClick = useCallback(() => { + showModal(data); + }, [data, showModal]); + return ( @@ -35,7 +38,7 @@ export function TemplateCard({