diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index 2e1f6c9a7..fbc27b097 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -5,7 +5,7 @@ import { ReactFlow, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; -// import ChatDrawer from '../chat/drawer'; +import { ChatSheet } from '../chat/chat-sheet'; import FormSheet from '../form-sheet/next'; import { useHandleDrop, @@ -15,7 +15,7 @@ import { } from '../hooks'; import { useBeforeDelete } from '../hooks/use-before-delete'; import { useShowDrawer } from '../hooks/use-show-drawer'; -// import RunDrawer from '../run-drawer'; +import RunSheet from '../run-sheet'; import { ButtonEdge } from './edge'; import styles from './index.less'; import { RagNode } from './node'; @@ -66,7 +66,7 @@ interface IProps { hideDrawer(): void; } -function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { +function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { const { nodes, edges, @@ -165,21 +165,21 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { showSingleDebugDrawer={showSingleDebugDrawer} > )} - {/* {chatVisible && ( - + > )} {runVisible && ( - - )} */} + > + )} ); } -export default FlowCanvas; +export default AgentCanvas; diff --git a/web/src/pages/agent/chat/chat-sheet.tsx b/web/src/pages/agent/chat/chat-sheet.tsx new file mode 100644 index 000000000..8356f3f57 --- /dev/null +++ b/web/src/pages/agent/chat/chat-sheet.tsx @@ -0,0 +1,26 @@ +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from '@/components/ui/sheet'; +import { IModalProps } from '@/interfaces/common'; + +export function ChatSheet({ visible }: IModalProps) { + return ( + + Open + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete your + account and remove your data from our servers. + + + + + ); +} diff --git a/web/src/pages/agent/debug-content/index.tsx b/web/src/pages/agent/debug-content/index.tsx index f5493c764..96e25432c 100644 --- a/web/src/pages/agent/debug-content/index.tsx +++ b/web/src/pages/agent/debug-content/index.tsx @@ -1,30 +1,27 @@ -import { Authorization } from '@/constants/authorization'; +import { FileUploader } from '@/components/file-uploader'; +import { ButtonLoading } from '@/components/ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { RAGFlowSelect } from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; +import { Textarea } from '@/components/ui/textarea'; import { useSetModalState } from '@/hooks/common-hooks'; import { useSetSelectedRecord } from '@/hooks/logic-hooks'; -import { useHandleSubmittable } from '@/hooks/login-hooks'; -import api from '@/utils/api'; -import { getAuthorization } from '@/utils/authorization-util'; -import { UploadOutlined } from '@ant-design/icons'; -import { - Button, - Form, - FormItemProps, - Input, - InputNumber, - Select, - Switch, - Upload, -} from 'antd'; +import { zodResolver } from '@hookform/resolvers/zod'; import { UploadChangeParam, UploadFile } from 'antd/es/upload'; -import { pick } from 'lodash'; -import { Link } from 'lucide-react'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { z } from 'zod'; import { BeginQueryType } from '../constant'; import { BeginQuery } from '../interface'; -import { PopoverForm } from './popover-form'; - -import styles from './index.less'; interface IProps { parameters: BeginQuery[]; @@ -34,6 +31,8 @@ interface IProps { submitButtonDisabled?: boolean; } +const values = {}; + const DebugContent = ({ parameters, ok, @@ -42,7 +41,20 @@ const DebugContent = ({ submitButtonDisabled = false, }: IProps) => { const { t } = useTranslation(); - const [form] = Form.useForm(); + + const FormSchema = useMemo(() => { + const obj = parameters.reduce((pre, cur, idx) => { + pre[idx] = z.string().optional(); + return pre; + }, {}); + return z.object(obj); + }, [parameters]); + + const form = useForm({ + defaultValues: values, + resolver: zodResolver(FormSchema), + }); + const { visible, hideModal: hidePopover, @@ -50,7 +62,8 @@ const DebugContent = ({ showModal: showPopover, } = useSetModalState(); const { setRecord, currentRecord } = useSetSelectedRecord(); - const { submittable } = useHandleSubmittable(form); + // const { submittable } = useHandleSubmittable(form); + const submittable = true; const [isUploading, setIsUploading] = useState(false); const handleShowPopover = useCallback( @@ -79,8 +92,8 @@ const DebugContent = ({ ); const renderWidget = useCallback( - (q: BeginQuery, idx: number) => { - const props: FormItemProps & { key: number } = { + (q: BeginQuery, idx: string) => { + const props = { key: idx, label: q.name ?? q.key, name: idx, @@ -89,80 +102,119 @@ const DebugContent = ({ props.rules = [{ required: true }]; } - const urlList: { url: string; result: string }[] = - form.getFieldValue(idx) || []; + // const urlList: { url: string; result: string }[] = + // form.getFieldValue(idx) || []; + + const urlList: { url: string; result: string }[] = []; const BeginQueryTypeMap = { [BeginQueryType.Line]: ( - - - + ( + + {props.label} + + + + + + )} + /> ), [BeginQueryType.Paragraph]: ( - - - + ( + + {props.label} + + + + + + )} + /> ), [BeginQueryType.Options]: ( - - ({ label: x, value: x })) ?? []} - > - + ( + + {props.label} + + ({ label: x, value: x })) ?? [] + } + {...field} + > + + + + )} + /> ), [BeginQueryType.File]: ( - - - - - }> - {t('common.upload')} - - - - 0 ? 'mb-1' : ''} - noStyle - > - - } - > - {t('flow.pasteFileLink')} - - - - - - + ( + + + {t('assistantAvatar')} + + + + + + + )} + /> ), [BeginQueryType.Integer]: ( - - - + ( + + {props.label} + + + + + + )} + /> ), [BeginQueryType.Boolean]: ( - - - + ( + + {props.label} + + + + + + )} + /> ), }; @@ -171,11 +223,11 @@ const DebugContent = ({ BeginQueryTypeMap[BeginQueryType.Paragraph] ); }, - [form, handleShowPopover, onChange, switchVisible, t, visible], + [form, t], ); const onOk = useCallback(async () => { - const values = await form.validateFields(); + // const values = await form.validateFields(); const nextValues = Object.entries(values).map(([key, value]) => { const item = parameters[Number(key)]; let nextValue = value; @@ -193,44 +245,49 @@ const DebugContent = ({ }); ok(nextValues); - }, [form, ok, parameters]); + }, [ok, parameters]); + + const onSubmit = useCallback( + (values: z.infer) => { + 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], + ); return ( <> - - { - if (name === 'urlForm') { - const { basicForm } = forms; - const urlInfo = basicForm.getFieldValue(currentRecord) || []; - basicForm.setFieldsValue({ - [currentRecord]: [...urlInfo, { ...values, name: values.url }], - }); - hidePopover(); - } - }} - > - + + + {parameters.map((x, idx) => { - return renderWidget(x, idx); + return {renderWidget(x, idx.toString())}; })} - - + + - {t(isNext ? 'common.next' : 'flow.run')} - + > ); }; diff --git a/web/src/pages/agent/debug-content/popover-form.tsx b/web/src/pages/agent/debug-content/popover-form.tsx index 557e3185b..9465d903b 100644 --- a/web/src/pages/agent/debug-content/popover-form.tsx +++ b/web/src/pages/agent/debug-content/popover-form.tsx @@ -1,74 +1,103 @@ +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Popover, PopoverContent } from '@/components/ui/popover'; import { useParseDocument } from '@/hooks/document-hooks'; -import { useResetFormOnCloseModal } from '@/hooks/logic-hooks'; import { IModalProps } from '@/interfaces/common'; -import { Button, Form, Input, Popover } from 'antd'; +import { zodResolver } from '@hookform/resolvers/zod'; import { PropsWithChildren } from 'react'; +import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { z } from 'zod'; const reg = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/; +const FormSchema = z.object({ + url: z.string(), + result: z.any(), +}); + +const values = { + url: '', + result: null, +}; + export const PopoverForm = ({ children, visible, switchVisible, }: PropsWithChildren>) => { - const [form] = Form.useForm(); + const form = useForm({ + defaultValues: values, + resolver: zodResolver(FormSchema), + }); const { parseDocument, loading } = useParseDocument(); const { t } = useTranslation(); - useResetFormOnCloseModal({ - form, - visible, - }); + // useResetFormOnCloseModal({ + // form, + // visible, + // }); - const onOk = async () => { - const values = await form.validateFields(); + async function onSubmit(values: z.infer) { const val = values.url; if (reg.test(val)) { const ret = await parseDocument(val); if (ret?.data?.code === 0) { - form.setFieldValue('result', ret?.data?.data); - form.submit(); + form.setValue('result', ret?.data?.data); } } - }; + } const content = ( - - - e.preventDefault()} - placeholder={t('flow.pasteFileLink')} - suffix={ - - {t('common.submit')} - - } + + + ( + + + e.preventDefault()} + placeholder={t('flow.pasteFileLink')} + // suffix={ + // + // {t('common.submit')} + // + // } + /> + + + + )} /> - - + <>>} + /> + ); return ( - + {children} + {content} ); }; diff --git a/web/src/pages/agent/index.tsx b/web/src/pages/agent/index.tsx index 4da19640c..7a195289a 100644 --- a/web/src/pages/agent/index.tsx +++ b/web/src/pages/agent/index.tsx @@ -12,14 +12,19 @@ import { useSetModalState } from '@/hooks/common-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { ReactFlowProvider } from '@xyflow/react'; import { CodeXml, EllipsisVertical, Forward, Import, Key } from 'lucide-react'; -import { ComponentPropsWithoutRef } from 'react'; +import { ComponentPropsWithoutRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { AgentSidebar } from './agent-sidebar'; -import FlowCanvas from './canvas'; +import AgentCanvas from './canvas'; import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; import { useFetchDataOnMount } from './hooks/use-fetch-data'; +import { useGetBeginNodeDataQuery } from './hooks/use-get-begin-query'; import { useOpenDocument } from './hooks/use-open-document'; -import { useSaveGraph } from './hooks/use-save-graph'; +import { + useSaveGraph, + useSaveGraphBeforeOpeningDebugDrawer, +} from './hooks/use-save-graph'; +import { BeginQuery } from './interface'; import { UploadAgentDialog } from './upload-agent-dialog'; function AgentDropdownMenuItem({ @@ -52,6 +57,18 @@ export default function Agent() { const { saveGraph, loading } = useSaveGraph(); const { flowDetail } = useFetchDataOnMount(); + const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); + + const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer); + + const handleRunAgent = useCallback(() => { + const query: BeginQuery[] = getBeginNodeDataQuery(); + if (query.length > 0) { + showChatDrawer(); + } else { + handleRun(); + } + }, [getBeginNodeDataQuery, handleRun, showChatDrawer]); return ( @@ -64,7 +81,9 @@ export default function Agent() { > Save - Run app + + Run app + Publish @@ -104,10 +123,10 @@ export default function Agent() { - + > diff --git a/web/src/pages/agent/run-sheet/index.tsx b/web/src/pages/agent/run-sheet/index.tsx new file mode 100644 index 000000000..26adedda1 --- /dev/null +++ b/web/src/pages/agent/run-sheet/index.tsx @@ -0,0 +1,62 @@ +import { IModalProps } from '@/interfaces/common'; +import { Drawer } from 'antd'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { BeginId } from '../constant'; +import DebugContent from '../debug-content'; +import { useGetBeginNodeDataQuery } from '../hooks/use-get-begin-query'; +import { useSaveGraphBeforeOpeningDebugDrawer } from '../hooks/use-save-graph'; +import { BeginQuery } from '../interface'; +import useGraphStore from '../store'; +import { getDrawerWidth } from '../utils'; + +const RunSheet = ({ + hideModal, + showModal: showChatModal, +}: IModalProps) => { + const { t } = useTranslation(); + const updateNodeForm = useGraphStore((state) => state.updateNodeForm); + + const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); + const query: BeginQuery[] = getBeginNodeDataQuery(); + + const { handleRun, loading } = useSaveGraphBeforeOpeningDebugDrawer( + showChatModal!, + ); + + const handleRunAgent = useCallback( + (nextValues: Record) => { + const currentNodes = updateNodeForm(BeginId, nextValues, ['query']); + handleRun(currentNodes); + hideModal?.(); + }, + [handleRun, hideModal, updateNodeForm], + ); + + const onOk = useCallback( + async (nextValues: any[]) => { + handleRunAgent(nextValues); + }, + [handleRunAgent], + ); + + return ( + + + + ); +}; + +export default RunSheet;