From e0659a4f0ec6a5ffba96060390bc47fbf92e159f Mon Sep 17 00:00:00 2001 From: balibabu Date: Fri, 15 Nov 2024 15:17:23 +0800 Subject: [PATCH] feat: Add RunDrawer #3355 (#3434) ### What problem does this PR solve? feat: Translation test run form #3355 feat: Wrap QueryTable with Collapse #3355 feat: If the required fields are not filled in, the submit button will be grayed out. #3355 feat: Add RunDrawer #3355 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/hooks/document-hooks.ts | 27 +- web/src/hooks/login-hooks.ts | 20 +- web/src/locales/en.ts | 6 +- web/src/locales/zh-traditional.ts | 4 + web/src/locales/zh.ts | 4 + web/src/pages/flow/canvas/index.tsx | 106 +++++-- web/src/pages/flow/canvas/node/begin-node.tsx | 34 ++- web/src/pages/flow/constant.tsx | 25 +- web/src/pages/flow/flow-drawer/index.tsx | 4 +- web/src/pages/flow/form/begin-form/index.less | 24 ++ web/src/pages/flow/form/begin-form/index.tsx | 18 +- .../flow/form/begin-form/paramater-modal.tsx | 13 +- .../flow/form/begin-form/query-table.tsx | 33 +- .../components/dynamic-input-variable.tsx | 22 +- web/src/pages/flow/header/index.tsx | 19 +- web/src/pages/flow/{hooks.ts => hooks.tsx} | 140 ++++++--- web/src/pages/flow/index.tsx | 4 +- web/src/pages/flow/run-drawer/index.less | 5 + web/src/pages/flow/run-drawer/index.tsx | 284 ++++++++++++++++++ .../pages/flow/run-drawer/popover-form.tsx | 74 +++++ web/src/pages/flow/store.ts | 41 +-- web/src/utils/api.ts | 1 + web/src/utils/request.ts | 4 +- web/tailwind.config.js | 1 + web/tailwind.css | 2 + 25 files changed, 780 insertions(+), 135 deletions(-) create mode 100644 web/src/pages/flow/form/begin-form/index.less rename web/src/pages/flow/{hooks.ts => hooks.tsx} (87%) create mode 100644 web/src/pages/flow/run-drawer/index.less create mode 100644 web/src/pages/flow/run-drawer/index.tsx create mode 100644 web/src/pages/flow/run-drawer/popover-form.tsx diff --git a/web/src/hooks/document-hooks.ts b/web/src/hooks/document-hooks.ts index a794962df..17995b93c 100644 --- a/web/src/hooks/document-hooks.ts +++ b/web/src/hooks/document-hooks.ts @@ -4,8 +4,9 @@ import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; import i18n from '@/locales/config'; import chatService from '@/services/chat-service'; import kbService from '@/services/knowledge-service'; -import { api_host } from '@/utils/api'; +import api, { api_host } from '@/utils/api'; import { buildChunkHighlights } from '@/utils/document-util'; +import { post } from '@/utils/request'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { UploadFile, message } from 'antd'; import { get } from 'lodash'; @@ -442,3 +443,27 @@ export const useUploadAndParseDocument = (uploadMethod: string) => { return { data, loading, uploadAndParseDocument: mutateAsync }; }; + +export const useParseDocument = () => { + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: ['parseDocument'], + mutationFn: async (url: string) => { + try { + const data = await post(api.parse, { url }); + if (data?.code === 0) { + message.success(i18n.t('message.uploaded')); + } + return data; + } catch (error) { + console.log('🚀 ~ mutationFn: ~ error:', error); + message.error('error'); + } + }, + }); + + return { parseDocument: mutateAsync, data, loading }; +}; diff --git a/web/src/hooks/login-hooks.ts b/web/src/hooks/login-hooks.ts index 35ee67f9f..378bb8563 100644 --- a/web/src/hooks/login-hooks.ts +++ b/web/src/hooks/login-hooks.ts @@ -2,7 +2,9 @@ import { Authorization } from '@/constants/authorization'; import userService from '@/services/user-service'; import authorizationUtil from '@/utils/authorization-util'; import { useMutation } from '@tanstack/react-query'; -import { message } from 'antd'; +import { Form, message } from 'antd'; +import { FormInstance } from 'antd/lib'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { history } from 'umi'; @@ -95,3 +97,19 @@ export const useLogout = () => { return { data, loading, logout: mutateAsync }; }; + +export const useHandleSubmittable = (form: FormInstance) => { + const [submittable, setSubmittable] = useState(false); + + // Watch all values + const values = Form.useWatch([], form); + + useEffect(() => { + form + .validateFields({ validateOnly: true }) + .then(() => setSubmittable(true)) + .catch(() => setSubmittable(false)); + }, [form, values]); + + return { submittable }; +}; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index a47e37104..f41986191 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -32,6 +32,7 @@ export default { s: 'S', pleaseSelect: 'Please select', pleaseInput: 'Please input', + submit: 'Submit', }, login: { login: 'Sign in', @@ -176,7 +177,7 @@ export default { chunkTokenNumber: 'Chunk token number', chunkTokenNumberMessage: 'Chunk token number is required', embeddingModelTip: - "The model that converts chunks into embeddings. It cannot be changed once the knowledge base has chunks. To switch to a different embedding model, You must delete all chunks in the knowledge base.", + 'The model that converts chunks into embeddings. It cannot be changed once the knowledge base has chunks. To switch to a different embedding model, You must delete all chunks in the knowledge base.', permissionsTip: "If set to 'Team', all team members will be able to manage the knowledge base.", chunkTokenNumberTip: @@ -1025,6 +1026,9 @@ The above is the content you need to summarize.`, content: 'Content', operationResults: 'Operation Results', autosaved: 'Autosaved', + optional: 'Optional', + pasteFileLink: 'Paste file link', + testRun: 'Test Run', }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index cfbc7bf08..7dc518226 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -32,6 +32,7 @@ export default { s: '秒', pleaseSelect: '請選擇', pleaseInput: '請輸入', + submit: '提交', }, login: { login: '登入', @@ -985,6 +986,9 @@ export default { content: '內容', operationResults: '運行結果', autosaved: '已自動儲存', + optional: '可選項', + pasteFileLink: '貼上文件連結', + testRun: '試運行', }, footer: { profile: '“保留所有權利 @ react”', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 8b29281fa..086c151f2 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -32,6 +32,7 @@ export default { s: '秒', pleaseSelect: '请选择', pleaseInput: '请输入', + submit: '提交', }, login: { login: '登录', @@ -1005,6 +1006,9 @@ export default { content: '内容', operationResults: '运行结果', autosaved: '已自动保存', + optional: '可选项', + pasteFileLink: '粘贴文件链接', + testRun: '试运行', }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/pages/flow/canvas/index.tsx b/web/src/pages/flow/canvas/index.tsx index c38f6e8ef..0f20b894b 100644 --- a/web/src/pages/flow/canvas/index.tsx +++ b/web/src/pages/flow/canvas/index.tsx @@ -1,4 +1,5 @@ -import { useCallback } from 'react'; +import { useSetModalState } from '@/hooks/common-hooks'; +import { useCallback, useEffect } from 'react'; import ReactFlow, { Background, ConnectionMode, @@ -8,14 +9,17 @@ import ReactFlow, { import 'reactflow/dist/style.css'; import ChatDrawer from '../chat/drawer'; import { Operator } from '../constant'; -import FlowDrawer from '../flow-drawer'; +import FormDrawer from '../flow-drawer'; import { + useGetBeginNodeDataQuery, useHandleDrop, useSelectCanvasData, - useShowDrawer, + useShowFormDrawer, useValidateConnection, useWatchNodeFormDataChange, } from '../hooks'; +import { BeginQuery } from '../interface'; +import RunDrawer from '../run-drawer'; import { ButtonEdge } from './edge'; import styles from './index.less'; import { RagNode } from './node'; @@ -53,11 +57,11 @@ const edgeTypes = { }; interface IProps { - chatDrawerVisible: boolean; - hideChatDrawer(): void; + drawerVisible: boolean; + hideDrawer(): void; } -function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) { +function FlowCanvas({ drawerVisible, hideDrawer }: IProps) { const { nodes, edges, @@ -67,27 +71,66 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) { onSelectionChange, } = useSelectCanvasData(); const isValidConnection = useValidateConnection(); + const { + visible: runVisible, + showModal: showRunModal, + hideModal: hideRunModal, + } = useSetModalState(); + const { + visible: chatVisible, + showModal: showChatModal, + hideModal: hideChatModal, + } = useSetModalState(); - const { drawerVisible, hideDrawer, showDrawer, clickedNode } = - useShowDrawer(); - - const onNodeClick: NodeMouseHandler = useCallback( - (e, node) => { - if (node.data.label !== Operator.Note) { - showDrawer(node); - } - }, - [showDrawer], - ); + const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } = + useShowFormDrawer(); const onPaneClick = useCallback(() => { - hideDrawer(); - }, [hideDrawer]); + hideFormDrawer(); + }, [hideFormDrawer]); const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(); useWatchNodeFormDataChange(); + const hideRunOrChatDrawer = useCallback(() => { + hideChatModal(); + hideRunModal(); + hideDrawer(); + }, [hideChatModal, hideDrawer, hideRunModal]); + + const onNodeClick: NodeMouseHandler = useCallback( + (e, node) => { + if (node.data.label !== Operator.Note) { + hideRunOrChatDrawer(); + showFormDrawer(node); + } + }, + [hideRunOrChatDrawer, showFormDrawer], + ); + + const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); + + useEffect(() => { + if (drawerVisible) { + const query: BeginQuery[] = getBeginNodeDataQuery(); + if (query.length > 0) { + showRunModal(); + hideChatModal(); + } else { + showChatModal(); + hideRunModal(); + } + } + }, [ + hideChatModal, + hideRunModal, + showChatModal, + showRunModal, + drawerVisible, + getBeginNodeDataQuery, + ]); + return (
- - {chatDrawerVisible && ( + {formDrawerVisible && ( + + )} + {chatVisible && ( )} + + {runVisible && ( + + )}
); } diff --git a/web/src/pages/flow/canvas/node/begin-node.tsx b/web/src/pages/flow/canvas/node/begin-node.tsx index 250687047..88125b23f 100644 --- a/web/src/pages/flow/canvas/node/begin-node.tsx +++ b/web/src/pages/flow/canvas/node/begin-node.tsx @@ -1,9 +1,15 @@ import { Flex } from 'antd'; import classNames from 'classnames'; +import get from 'lodash/get'; import { useTranslation } from 'react-i18next'; import { Handle, NodeProps, Position } from 'reactflow'; -import { Operator, operatorMap } from '../../constant'; -import { NodeData } from '../../interface'; +import { + BeginQueryType, + BeginQueryTypeIconMap, + Operator, + operatorMap, +} from '../../constant'; +import { BeginQuery, NodeData } from '../../interface'; import OperatorIcon from '../../operator-icon'; import { RightHandleStyle } from './handle-icon'; import styles from './index.less'; @@ -11,15 +17,13 @@ import styles from './index.less'; // TODO: do not allow other nodes to connect to this node export function BeginNode({ selected, data }: NodeProps) { const { t } = useTranslation(); + const query: BeginQuery[] = get(data, 'form.query', []); return (
) { style={RightHandleStyle} > - + ) { >
{t(`flow.begin`)}
+ + {query.map((x, idx) => { + const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType]; + return ( + + + + {x.name} + {x.optional ? 'Yes' : 'No'} + + ); + })} +
); } diff --git a/web/src/pages/flow/constant.tsx b/web/src/pages/flow/constant.tsx index 40d134cac..173de2c0e 100644 --- a/web/src/pages/flow/constant.tsx +++ b/web/src/pages/flow/constant.tsx @@ -43,6 +43,15 @@ import { SendOutlined, } from '@ant-design/icons'; import upperFirst from 'lodash/upperFirst'; +import { + CloudUpload, + Link2, + ListOrdered, + OptionIcon, + TextCursorInput, + ToggleLeft, + WrapText, +} from 'lucide-react'; export enum Operator { Begin = 'Begin', @@ -2870,12 +2879,12 @@ export enum BeginQueryType { Url = 'url', } -export const BeginQueryTypeMap = { - [BeginQueryType.Line]: 'input', - [BeginQueryType.Paragraph]: 'textarea', - [BeginQueryType.Options]: 'select', - [BeginQueryType.File]: 'file', - [BeginQueryType.Integer]: 'inputnumber', - [BeginQueryType.Boolean]: 'switch', - [BeginQueryType.Url]: 'input', +export const BeginQueryTypeIconMap = { + [BeginQueryType.Line]: TextCursorInput, + [BeginQueryType.Paragraph]: WrapText, + [BeginQueryType.Options]: OptionIcon, + [BeginQueryType.File]: CloudUpload, + [BeginQueryType.Integer]: ListOrdered, + [BeginQueryType.Boolean]: ToggleLeft, + [BeginQueryType.Url]: Link2, }; diff --git a/web/src/pages/flow/flow-drawer/index.tsx b/web/src/pages/flow/flow-drawer/index.tsx index fe5c7c642..945ca18fd 100644 --- a/web/src/pages/flow/flow-drawer/index.tsx +++ b/web/src/pages/flow/flow-drawer/index.tsx @@ -83,7 +83,7 @@ const FormMap = { const EmptyContent = () =>
; -const FlowDrawer = ({ +const FormDrawer = ({ visible, hideModal, node, @@ -152,4 +152,4 @@ const FlowDrawer = ({ ); }; -export default FlowDrawer; +export default FormDrawer; diff --git a/web/src/pages/flow/form/begin-form/index.less b/web/src/pages/flow/form/begin-form/index.less new file mode 100644 index 000000000..0c6654eeb --- /dev/null +++ b/web/src/pages/flow/form/begin-form/index.less @@ -0,0 +1,24 @@ +.dynamicInputVariable { + background-color: #ebe9e9; + :global(.ant-collapse-content) { + background-color: #f6f6f6; + } + :global(.ant-collapse-content-box) { + padding: 0 !important; + } + margin-bottom: 20px; + .title { + font-weight: 600; + font-size: 16px; + } + + .addButton { + color: rgb(22, 119, 255); + font-weight: 600; + } +} + +.addButton { + color: rgb(22, 119, 255); + font-weight: 600; +} diff --git a/web/src/pages/flow/form/begin-form/index.tsx b/web/src/pages/flow/form/begin-form/index.tsx index 00265f4cb..8df1181f1 100644 --- a/web/src/pages/flow/form/begin-form/index.tsx +++ b/web/src/pages/flow/form/begin-form/index.tsx @@ -1,17 +1,20 @@ -import { useTranslate } from '@/hooks/common-hooks'; +import { PlusOutlined } from '@ant-design/icons'; import { Button, Form, Input } from 'antd'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { BeginQuery, IOperatorForm } from '../../interface'; import { useEditQueryRecord } from './hooks'; import { ModalForm } from './paramater-modal'; import QueryTable from './query-table'; +import styles from './index.less'; + type FieldType = { prologue?: string; }; const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { - const { t } = useTranslate('chat'); + const { t } = useTranslation(); const { ok, currentRecord, @@ -55,9 +58,9 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { > name={'prologue'} - label={t('setAnOpener')} - tooltip={t('setAnOpenerTip')} - initialValue={t('setAnOpenerInitial')} + label={t('chat.setAnOpener')} + tooltip={t('chat.setAnOpenerTip')} + initialValue={t('chat.setAnOpenerInitial')} > @@ -65,7 +68,6 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { prevValues.query !== curValues.query } @@ -86,9 +88,11 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { htmlType="button" style={{ margin: '0 8px' }} onClick={() => showModal()} + icon={} block + className={styles.addButton} > - Add + + {t('flow.addItem')} {visible && ( { return Object.values(BeginQueryType).reduce( (pre, cur) => { + const Icon = BeginQueryTypeIconMap[cur]; + return [ ...pre, { - label: cur, + label: ( +
+ + {cur} +
+ ), value: cur, }, ]; diff --git a/web/src/pages/flow/form/begin-form/query-table.tsx b/web/src/pages/flow/form/begin-form/query-table.tsx index a4df68f35..c7614e682 100644 --- a/web/src/pages/flow/form/begin-form/query-table.tsx +++ b/web/src/pages/flow/form/begin-form/query-table.tsx @@ -1,8 +1,11 @@ import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import type { TableProps } from 'antd'; -import { Space, Table, Tooltip } from 'antd'; +import { Collapse, Space, Table, Tooltip } from 'antd'; import { BeginQuery } from '../../interface'; +import { useTranslation } from 'react-i18next'; +import styles from './index.less'; + interface IProps { data: BeginQuery[]; deleteRecord(index: number): void; @@ -10,6 +13,8 @@ interface IProps { } const QueryTable = ({ data, deleteRecord, showModal }: IProps) => { + const { t } = useTranslation(); + const columns: TableProps['columns'] = [ { title: 'Key', @@ -25,7 +30,7 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => { ), }, { - title: 'Name', + title: t('flow.name'), dataIndex: 'name', key: 'name', ellipsis: { @@ -38,18 +43,18 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => { ), }, { - title: 'Type', + title: t('flow.type'), dataIndex: 'type', key: 'type', }, { - title: 'Optional', + title: t('flow.optional'), dataIndex: 'optional', key: 'optional', render: (optional) => (optional ? 'Yes' : 'No'), }, { - title: 'Action', + title: t('common.action'), key: 'action', render: (_, record, idx) => ( @@ -64,7 +69,23 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => { ]; return ( - columns={columns} dataSource={data} pagination={false} /> + {t('flow.input')}, + children: ( + + columns={columns} + dataSource={data} + pagination={false} + /> + ), + }, + ]} + /> ); }; diff --git a/web/src/pages/flow/form/components/dynamic-input-variable.tsx b/web/src/pages/flow/form/components/dynamic-input-variable.tsx index cbc5bd4d1..ec574173f 100644 --- a/web/src/pages/flow/form/components/dynamic-input-variable.tsx +++ b/web/src/pages/flow/form/components/dynamic-input-variable.tsx @@ -1,7 +1,7 @@ import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Collapse, Flex, Form, Input, Select } from 'antd'; -import { useCallback } from 'react'; +import { PropsWithChildren, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useBuildComponentIdSelectOptions } from '../../hooks'; import styles from './index.less'; @@ -95,9 +95,10 @@ const DynamicVariableForm = ({ nodeId }: IProps) => { ); }; -const DynamicInputVariable = ({ nodeId }: IProps) => { - const { t } = useTranslation(); - +export function FormCollapse({ + children, + title, +}: PropsWithChildren<{ title: string }>) { return ( { items={[ { key: '1', - label: {t('flow.input')}, - children: , + label: {title}, + children, }, ]} /> ); +} + +const DynamicInputVariable = ({ nodeId }: IProps) => { + const { t } = useTranslation(); + return ( + + + + ); }; export default DynamicInputVariable; diff --git a/web/src/pages/flow/header/index.tsx b/web/src/pages/flow/header/index.tsx index b1f13ec9b..6bf5aa9e3 100644 --- a/web/src/pages/flow/header/index.tsx +++ b/web/src/pages/flow/header/index.tsx @@ -3,13 +3,16 @@ import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; import { useFetchFlow } from '@/hooks/flow-hooks'; import { ArrowLeftOutlined } from '@ant-design/icons'; import { Button, Flex, Space } from 'antd'; +import { useCallback } from 'react'; import { Link, useParams } from 'umi'; import FlowIdModal from '../flow-id-modal'; import { + useGetBeginNodeDataQuery, useSaveGraph, useSaveGraphBeforeOpeningDebugDrawer, useWatchAgentChange, } from '../hooks'; +import { BeginQuery } from '../interface'; import styles from './index.less'; interface IProps { @@ -19,7 +22,7 @@ interface IProps { const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => { const { saveGraph } = useSaveGraph(); - const handleRun = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer); + const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer); const { data } = useFetchFlow(); const { t } = useTranslate('flow'); const { @@ -30,6 +33,16 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => { const { visible, hideModal, showModal } = useSetModalState(); const { id } = useParams(); const time = useWatchAgentChange(chatDrawerVisible); + const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); + + const handleRunAgent = useCallback(() => { + const query: BeginQuery[] = getBeginNodeDataQuery(); + if (query.length > 0) { + showChatDrawer(); + } else { + handleRun(); + } + }, [getBeginNodeDataQuery, handleRun, showChatDrawer]); return ( <> @@ -51,10 +64,10 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => { - - {/* + +
+ + + prevValues[idx] !== curValues[idx] + } + > + {({ getFieldValue }) => { + const urlInfo: { url: string; result: string }[] = + getFieldValue(idx) || []; + return urlInfo.length ? ( + + {urlInfo.map((u, index) => ( +
+ + {u.url} + +
+ ))} +
+ ) : null; + }} +
+ + ), + }; + + return BeginQueryTypeMap[q.type as BeginQueryType]; + }, + [form, handleRemoveUrl, handleShowPopover, switchVisible, t, visible], + ); + + const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatModal!); + + const handleRunAgent = useCallback( + (nextValues: Record) => { + const currentNodes = updateNodeForm('begin', nextValues, ['query']); + handleRun(currentNodes); + hideModal?.(); + }, + [handleRun, hideModal, updateNodeForm], + ); + + const onOk = useCallback(async () => { + const values = await form.validateFields(); + const nextValues = Object.entries(values).map(([key, value]) => { + const item = query[Number(key)]; + let nextValue = value; + if (Array.isArray(value)) { + nextValue = ``; + + value.forEach((x, idx) => { + if (x?.originFileObj instanceof File) { + if (idx === 0) { + nextValue += `${x.name}\n\n${x.response.data}\n\n`; + } else { + nextValue += `${x.response.data}\n\n`; + } + } else { + if (idx === 0) { + nextValue += `${x.url}\n\n${x.result}\n\n`; + } else { + nextValue += `${x.result}\n\n`; + } + } + }); + } + return { ...item, value: nextValue }; + }); + handleRunAgent(nextValues); + }, [form, handleRunAgent, query]); + + return ( + +
+ { + if (name === 'urlForm') { + const { basicForm } = forms; + const urlInfo = basicForm.getFieldValue(currentRecord) || []; + basicForm.setFieldsValue({ + [currentRecord]: [...urlInfo, values], + }); + hidePopover(); + } + }} + > +
+ {query.map((x, idx) => { + return renderWidget(x, idx); + })} +
+
+
+ +
+ ); +}; + +export default RunDrawer; diff --git a/web/src/pages/flow/run-drawer/popover-form.tsx b/web/src/pages/flow/run-drawer/popover-form.tsx new file mode 100644 index 000000000..557e3185b --- /dev/null +++ b/web/src/pages/flow/run-drawer/popover-form.tsx @@ -0,0 +1,74 @@ +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 { PropsWithChildren } from 'react'; +import { useTranslation } from 'react-i18next'; + +const reg = + /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/; + +export const PopoverForm = ({ + children, + visible, + switchVisible, +}: PropsWithChildren>) => { + const [form] = Form.useForm(); + const { parseDocument, loading } = useParseDocument(); + const { t } = useTranslation(); + + useResetFormOnCloseModal({ + form, + visible, + }); + + const onOk = async () => { + const values = await form.validateFields(); + 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(); + } + } + }; + + const content = ( +
+ + e.preventDefault()} + placeholder={t('flow.pasteFileLink')} + suffix={ + + } + /> + + + + ); + + return ( + + {children} + + ); +}; diff --git a/web/src/pages/flow/store.ts b/web/src/pages/flow/store.ts index e78246570..54fccb9a0 100644 --- a/web/src/pages/flow/store.ts +++ b/web/src/pages/flow/store.ts @@ -47,7 +47,7 @@ export type RFState = { nodeId: string, values: any, path?: (string | number)[], - ) => void; + ) => Node[]; onSelectionChange: OnSelectionChangeFunc; addNode: (nodes: Node) => void; getNode: (id?: string | null) => Node | undefined; @@ -331,27 +331,30 @@ const useGraphStore = create()( values: any, path: (string | number)[] = [], ) => { - set({ - nodes: get().nodes.map((node) => { - if (node.id === nodeId) { - let nextForm: Record = { ...node.data.form }; - if (path.length === 0) { - nextForm = Object.assign(nextForm, values); - } else { - lodashSet(nextForm, path, values); - } - return { - ...node, - data: { - ...node.data, - form: nextForm, - }, - } as any; + const nextNodes = get().nodes.map((node) => { + if (node.id === nodeId) { + let nextForm: Record = { ...node.data.form }; + if (path.length === 0) { + nextForm = Object.assign(nextForm, values); + } else { + lodashSet(nextForm, path, values); } + return { + ...node, + data: { + ...node.data, + form: nextForm, + }, + } as any; + } - return node; - }), + return node; }); + set({ + nodes: nextNodes, + }); + + return nextNodes; }, updateSwitchFormData: (source, sourceHandle, target) => { const { updateNodeForm } = get(); diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 7fe6c6a29..f2d5d7821 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -62,6 +62,7 @@ export default { web_crawl: `${api_host}/document/web_crawl`, document_infos: `${api_host}/document/infos`, upload_and_parse: `${api_host}/document/upload_and_parse`, + parse: `${api_host}/document/parse`, // chat setDialog: `${api_host}/dialog/set`, diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index 2d03c5e8f..9668a93c0 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -99,8 +99,8 @@ request.interceptors.request.use((url: string, options: any) => { }); request.interceptors.response.use(async (response: any, options) => { - if (response?.status === 413) { - message.error(RetcodeMessage[413]); + if (response?.status === 413 || response?.status === 504) { + message.error(RetcodeMessage[response?.status as ResultCode]); } if (options.responseType === 'blob') { diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 76cdc2347..7dce4733e 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -24,6 +24,7 @@ module.exports = { ring: 'hsl(var(--ring))', background: 'var(--background)', foreground: 'hsl(var(--foreground))', + buttonBlueText: 'var(--button-blue-text)', primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', diff --git a/web/tailwind.css b/web/tailwind.css index 9a45baf58..d2831e914 100644 --- a/web/tailwind.css +++ b/web/tailwind.css @@ -37,6 +37,8 @@ --background-inverse-standard: rgba(58, 56, 65, 0.15); --background-inverse-standard-foreground: rgb(92, 81, 81); + + --button-blue-text: rgb(22, 119, 255); } .dark {