diff --git a/docSite/content/zh-cn/docs/development/upgrading/4813.md b/docSite/content/zh-cn/docs/development/upgrading/4813.md index 50cb8f281..ba0071ee1 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4813.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4813.md @@ -12,9 +12,10 @@ weight: 811 1. 2. 新增 - 文件上传方案调整,节点直接支持接收文件链接,插件自定义变量支持文件上传。 3. 新增 - 对话记录增加时间显示。 -4. 优化 - 知识库上传文件,优化报错提示。 -5. 优化 - 全文检索语句,减少一轮查询。 -6. 优化 - 修改 findLast 为 [...array].reverse().find,适配旧版浏览器。 -7. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。 -8. 修复 - Dockerfile pnpm install 支持代理。 -9. 修复 - BI 图表生成无法写入文件。 +4. 新增 - 工作流校验错误时,跳转至错误节点。 +5. 优化 - 知识库上传文件,优化报错提示。 +6. 优化 - 全文检索语句,减少一轮查询。 +7. 优化 - 修改 findLast 为 [...array].reverse().find,适配旧版浏览器。 +8. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。 +9. 修复 - Dockerfile pnpm install 支持代理。 +10. 修复 - BI 图表生成无法写入文件。 diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index cc686752e..f24290458 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -500,7 +500,7 @@ export async function dispatchWorkFlow(data: Props): Promise => { let totalPoints = 0; let newVariables: Record = props.variables; - for await (const item of loopInputArray) { + for await (const item of loopInputArray.filter(Boolean)) { runtimeNodes.forEach((node) => { if ( childrenNodeIdList.includes(node.nodeId) && diff --git a/projects/app/src/components/core/app/VariableEdit.tsx b/projects/app/src/components/core/app/VariableEdit.tsx index e7e405737..95c00be11 100644 --- a/projects/app/src/components/core/app/VariableEdit.tsx +++ b/projects/app/src/components/core/app/VariableEdit.tsx @@ -196,16 +196,13 @@ const VariableEdit = ({ - {t('common:core.module.variable.key')} - - {t('workflow:Variable_name')} @@ -236,19 +233,9 @@ const VariableEdit = ({ > - {item.label} + {item.key} - - {item.key} - {item.required ? ( diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx index 04f49d12b..bbc295dc5 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx @@ -73,7 +73,8 @@ const ChatInput = ({ showSelectFile, showSelectImg, removeFiles, - replaceFiles + replaceFiles, + hasFileUploading } = useFileUpload({ outLinkAuthData, chatId: chatId || '', @@ -81,7 +82,6 @@ const ChatInput = ({ fileCtrl }); const havInput = !!inputValue || fileList.length > 0; - const hasFileUploading = fileList.some((item) => !item.url); const canSendMessage = havInput && !hasFileUploading; // Upload files @@ -206,7 +206,7 @@ const ChatInput = ({ - onSelectFile({ files, fileList })} /> + onSelectFile({ files })} /> )} @@ -282,7 +282,7 @@ const ChatInput = ({ .filter((file) => { return file && fileTypeFilter(file); }) as File[]; - onSelectFile({ files, fileList }); + onSelectFile({ files }); if (files.length > 0) { e.stopPropagation(); diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx index 628bf5afc..5f150fde2 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx @@ -33,8 +33,10 @@ export const useFileUpload = (props: UseFileUploadOptions) => { update: updateFiles, remove: removeFiles, fields: fileList, - replace: replaceFiles + replace: replaceFiles, + append: appendFiles } = fileCtrl; + const hasFileUploading = fileList.some((item) => !item.url); const showSelectFile = fileSelectConfig?.canSelectFile; const showSelectImg = fileSelectConfig?.canSelectImg; @@ -69,7 +71,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => { }); const onSelectFile = useCallback( - async ({ files, fileList }: { files: File[]; fileList: UserInputFileItemType[] }) => { + async ({ files }: { files: File[] }) => { if (!files || files.length === 0) { return []; } @@ -128,22 +130,11 @@ export const useFileUpload = (props: UseFileUploadOptions) => { ) ); - // Document, image - const concatFileList = clone( - fileList.concat(loadFiles).sort((a, b) => { - if (a.type === ChatFileTypeEnum.image && b.type === ChatFileTypeEnum.file) { - return 1; - } else if (a.type === ChatFileTypeEnum.file && b.type === ChatFileTypeEnum.image) { - return -1; - } - return 0; - }) - ); - replaceFiles(concatFileList); + appendFiles(loadFiles); return loadFiles; }, - [maxSelectFiles, replaceFiles, toast, t, maxSize] + [maxSelectFiles, appendFiles, toast, t, maxSize] ); const uploadFiles = async () => { @@ -197,10 +188,23 @@ export const useFileUpload = (props: UseFileUploadOptions) => { removeFiles(errorFileIndex); }; + const sortFileList = useMemo(() => { + // Sort: Document, image + const sortResult = clone(fileList).sort((a, b) => { + if (a.type === ChatFileTypeEnum.image && b.type === ChatFileTypeEnum.file) { + return 1; + } else if (a.type === ChatFileTypeEnum.file && b.type === ChatFileTypeEnum.image) { + return -1; + } + return 0; + }); + return sortResult; + }, [fileList]); + return { File, onOpenSelectFile, - fileList, + fileList: sortFileList, onSelectFile, uploadFiles, selectFileIcon, @@ -208,6 +212,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => { showSelectFile, showSelectImg, removeFiles, - replaceFiles + replaceFiles, + hasFileUploading }; }; diff --git a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx index b0288cd16..093207a11 100644 --- a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/components/RenderInput.tsx @@ -56,7 +56,7 @@ const RenderInput = () => { showSelectFile, showSelectImg, removeFiles, - replaceFiles + hasFileUploading } = useFileUpload({ outLinkAuthData, chatId: chatId || '', @@ -64,9 +64,7 @@ const RenderInput = () => { fileCtrl }); const isDisabledInput = histories.length > 0; - const hasFileUploading = useMemo(() => { - return fileList.some((item) => !item.url); - }, [fileList]); + useRequest2(uploadFiles, { manual: false, errorToast: t('common:upload_file_error'), @@ -83,6 +81,7 @@ const RenderInput = () => { [onNewChat, setRestartData] ); + // Get plugin input components const formatPluginInputs = useMemo(() => { if (histories.length === 0) return pluginInputs; try { @@ -203,7 +202,7 @@ const RenderInput = () => { {t('chat:select')} )} - onSelectFile({ files, fileList })} /> + onSelectFile({ files })} /> { - return fileList.some((item) => !item.url); - }, [fileList]); useEffect(() => { setUploading(hasFileUploading); @@ -128,7 +126,7 @@ const FileSelector = ({ {fileList.length === 0 && } - onSelectFile({ files, fileList })} /> + onSelectFile({ files })} /> ); }; diff --git a/projects/app/src/pages/app/detail/components/Plugin/index.tsx b/projects/app/src/pages/app/detail/components/Plugin/index.tsx index e6062c4da..bae51d83f 100644 --- a/projects/app/src/pages/app/detail/components/Plugin/index.tsx +++ b/projects/app/src/pages/app/detail/components/Plugin/index.tsx @@ -13,13 +13,16 @@ import dynamic from 'next/dynamic'; import { cloneDeep } from 'lodash'; import Flow from '../WorkflowComponents/Flow'; -import { t } from 'i18next'; +import { ReactFlowProvider } from 'reactflow'; +import { useTranslation } from 'next-i18next'; + const Logs = dynamic(() => import('../Logs/index')); const PublishChannel = dynamic(() => import('../Publish')); const WorkflowEdit = () => { const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e); const isV2Workflow = appDetail?.version === 'v2'; + const { t } = useTranslation(); const { openConfirm, ConfirmModal } = useConfirm({ showCancel: false, @@ -64,9 +67,11 @@ const WorkflowEdit = () => { const Render = () => { return ( - - - + + + + + ); }; diff --git a/projects/app/src/pages/app/detail/components/Workflow/index.tsx b/projects/app/src/pages/app/detail/components/Workflow/index.tsx index f046da564..5ebbe3507 100644 --- a/projects/app/src/pages/app/detail/components/Workflow/index.tsx +++ b/projects/app/src/pages/app/detail/components/Workflow/index.tsx @@ -11,15 +11,18 @@ import { Flex } from '@chakra-ui/react'; import { workflowBoxStyles } from '../constants'; import dynamic from 'next/dynamic'; import { cloneDeep } from 'lodash'; +import { useTranslation } from 'next-i18next'; import Flow from '../WorkflowComponents/Flow'; -import { t } from 'i18next'; +import { ReactFlowProvider } from 'reactflow'; + const Logs = dynamic(() => import('../Logs/index')); const PublishChannel = dynamic(() => import('../Publish')); const WorkflowEdit = () => { const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e); const isV2Workflow = appDetail?.version === 'v2'; + const { t } = useTranslation(); const { openConfirm, ConfirmModal } = useConfirm({ showCancel: false, @@ -64,9 +67,11 @@ const WorkflowEdit = () => { const Render = () => { return ( - - - + + + + + ); }; diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx index 426d1a964..8eec02669 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx @@ -475,8 +475,7 @@ const RenderList = React.memo(function RenderList({ NodeOutputKeyEnum.userChatInput ]; defaultValueMap[NodeInputKeyEnum.fileUrlList] = [ - node.nodeId, - NodeOutputKeyEnum.userFiles + [node.nodeId, NodeOutputKeyEnum.userFiles] ]; } }); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx index 7d01b4e19..c29c25473 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx @@ -169,12 +169,4 @@ const Workflow = () => { ); }; -const Render = () => { - return ( - - - - ); -}; - -export default React.memo(Render); +export default React.memo(Workflow); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx index a39f07afc..ff148b5f4 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx @@ -172,6 +172,7 @@ const InputTypeConfig = ({ { const selectorVal = value as ReferenceItemValueType[]; + const notValidItem = + !selectorVal || + selectorVal.length === 0 || + selectorVal.every((item) => { + const [nodeName, outputName] = getSelectValue(item); + return !nodeName || !outputName; + }); return ( 0 ? ( + !notValidItem ? ( {selectorVal.map((item, index) => { const [nodeName, outputName] = getSelectValue(item); @@ -261,36 +268,35 @@ const MultipleReferenceSelector = ({ - - {isInvalidItem ? ( - t('common:invalid_variable') - ) : ( - <> - {nodeName} - - {outputName} - - )} + + {nodeName} + + {outputName} { e.stopPropagation(); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx index 5f2c96735..5f2998081 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx @@ -32,7 +32,8 @@ import { NodeChange, OnConnectStartParams, useEdgesState, - useNodesState + useNodesState, + useReactFlow } from 'reactflow'; import { createContext, useContextSelector } from 'use-context-selector'; import { defaultRunningStatus } from './constants'; @@ -568,6 +569,7 @@ const WorkflowContextProvider = ({ ); /* ui flow to store data */ + const { fitView } = useReactFlow(); const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => { const checkResults = checkWorkflowNodeAndConnection({ nodes, edges }); @@ -577,6 +579,12 @@ const WorkflowContextProvider = ({ return storeWorkflow; } else if (!hideTip) { checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true)); + + // View move to the node that failed + fitView({ + nodes: nodes.filter((node) => checkResults.includes(node.data.nodeId)) + }); + toast({ status: 'warning', title: t('common:core.workflow.Check Failed') diff --git a/projects/app/src/web/common/file/hooks/useSelectFile.tsx b/projects/app/src/web/common/file/hooks/useSelectFile.tsx index 276c8456d..bc05ca41e 100644 --- a/projects/app/src/web/common/file/hooks/useSelectFile.tsx +++ b/projects/app/src/web/common/file/hooks/useSelectFile.tsx @@ -1,51 +1,47 @@ import React, { useRef, useCallback } from 'react'; import { Box } from '@chakra-ui/react'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { useTranslation } from 'next-i18next'; import { useI18n } from '@/web/context/I18n'; +import { useMemoizedFn } from 'ahooks'; export const useSelectFile = (props?: { fileType?: string; multiple?: boolean; maxCount?: number; }) => { - const { t } = useTranslation(); const { fileT } = useI18n(); const { fileType = '*', multiple = false, maxCount = 10 } = props || {}; const { toast } = useToast(); const SelectFileDom = useRef(null); const openSign = useRef(); - const File = useCallback( - ({ onSelect }: { onSelect: (e: File[], sign?: any) => void }) => ( - - { - const files = e.target.files; + const File = useMemoizedFn(({ onSelect }: { onSelect: (e: File[], sign?: any) => void }) => ( + + { + const files = e.target.files; - if (!files || files?.length === 0) return; + if (!files || files?.length === 0) return; - let fileList = Array.from(files); - if (fileList.length > maxCount) { - toast({ - status: 'warning', - title: fileT('select_file_amount_limit', { max: maxCount }) - }); - fileList = fileList.slice(0, maxCount); - } - onSelect(fileList, openSign.current); + let fileList = Array.from(files); + if (fileList.length > maxCount) { + toast({ + status: 'warning', + title: fileT('select_file_amount_limit', { max: maxCount }) + }); + fileList = fileList.slice(0, maxCount); + } + onSelect(fileList, openSign.current); - e.target.value = ''; - }} - /> - - ), - [fileT, fileType, maxCount, multiple, toast] - ); + e.target.value = ''; + }} + /> + + )); const onOpen = useCallback((sign?: any) => { openSign.current = sign; diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index c8c9df740..b8878de8f 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -23,7 +23,8 @@ import { formatEditorVariablePickerIcon, getAppChatConfig, getGuideModule, - isReferenceValue + isReferenceValue, + isReferenceValueArray } from '@fastgpt/global/core/workflow/utils'; import { TFunction } from 'next-i18next'; import { @@ -328,7 +329,8 @@ export const checkWorkflowNodeAndConnection = ({ if ( node.data.flowNodeType === FlowNodeTypeEnum.pluginOutput && - !(isReferenceValue(input.value, nodeIds) && input.value[1]) + (input.value?.length === 0 || + (isReferenceValue(input.value, nodeIds) && !input.value?.[1])) ) { return true; }