feat: View will move when workflow check error;fix: ui refresh error when continuous file upload (#3077)
* fix: plugin output check * fix: ui refresh error when continuous file upload * feat: View will move when workflow check error
This commit is contained in:
parent
0f1932aadc
commit
a9ee6e6a5e
@ -12,9 +12,10 @@ weight: 811
|
|||||||
1.
|
1.
|
||||||
2. 新增 - 文件上传方案调整,节点直接支持接收文件链接,插件自定义变量支持文件上传。
|
2. 新增 - 文件上传方案调整,节点直接支持接收文件链接,插件自定义变量支持文件上传。
|
||||||
3. 新增 - 对话记录增加时间显示。
|
3. 新增 - 对话记录增加时间显示。
|
||||||
4. 优化 - 知识库上传文件,优化报错提示。
|
4. 新增 - 工作流校验错误时,跳转至错误节点。
|
||||||
5. 优化 - 全文检索语句,减少一轮查询。
|
5. 优化 - 知识库上传文件,优化报错提示。
|
||||||
6. 优化 - 修改 findLast 为 [...array].reverse().find,适配旧版浏览器。
|
6. 优化 - 全文检索语句,减少一轮查询。
|
||||||
7. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。
|
7. 优化 - 修改 findLast 为 [...array].reverse().find,适配旧版浏览器。
|
||||||
8. 修复 - Dockerfile pnpm install 支持代理。
|
8. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。
|
||||||
9. 修复 - BI 图表生成无法写入文件。
|
9. 修复 - Dockerfile pnpm install 支持代理。
|
||||||
|
10. 修复 - BI 图表生成无法写入文件。
|
||||||
|
|||||||
@ -500,7 +500,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
nodes: runtimeNodes,
|
nodes: runtimeNodes,
|
||||||
variables
|
variables
|
||||||
});
|
});
|
||||||
|
console.log(value, '=-=-');
|
||||||
// Dynamic input is stored in the dynamic key
|
// Dynamic input is stored in the dynamic key
|
||||||
if (input.canEdit && dynamicInput && params[dynamicInput.key]) {
|
if (input.canEdit && dynamicInput && params[dynamicInput.key]) {
|
||||||
params[dynamicInput.key][input.key] = valueTypeFormat(value, input.valueType);
|
params[dynamicInput.key][input.key] = valueTypeFormat(value, input.valueType);
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
|
|||||||
let totalPoints = 0;
|
let totalPoints = 0;
|
||||||
let newVariables: Record<string, any> = props.variables;
|
let newVariables: Record<string, any> = props.variables;
|
||||||
|
|
||||||
for await (const item of loopInputArray) {
|
for await (const item of loopInputArray.filter(Boolean)) {
|
||||||
runtimeNodes.forEach((node) => {
|
runtimeNodes.forEach((node) => {
|
||||||
if (
|
if (
|
||||||
childrenNodeIdList.includes(node.nodeId) &&
|
childrenNodeIdList.includes(node.nodeId) &&
|
||||||
|
|||||||
@ -196,16 +196,13 @@ const VariableEdit = ({
|
|||||||
<Thead h={8}>
|
<Thead h={8}>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th
|
<Th
|
||||||
fontSize={'mini'}
|
|
||||||
borderRadius={'none !important'}
|
borderRadius={'none !important'}
|
||||||
|
fontSize={'mini'}
|
||||||
bg={'myGray.50'}
|
bg={'myGray.50'}
|
||||||
p={0}
|
p={0}
|
||||||
px={4}
|
px={4}
|
||||||
fontWeight={'medium'}
|
fontWeight={'medium'}
|
||||||
>
|
>
|
||||||
{t('common:core.module.variable.key')}
|
|
||||||
</Th>
|
|
||||||
<Th fontSize={'mini'} bg={'myGray.50'} p={0} px={4} fontWeight={'medium'}>
|
|
||||||
{t('workflow:Variable_name')}
|
{t('workflow:Variable_name')}
|
||||||
</Th>
|
</Th>
|
||||||
<Th fontSize={'mini'} bg={'myGray.50'} p={0} px={4} fontWeight={'medium'}>
|
<Th fontSize={'mini'} bg={'myGray.50'} p={0} px={4} fontWeight={'medium'}>
|
||||||
@ -236,19 +233,9 @@ const VariableEdit = ({
|
|||||||
>
|
>
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
|
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
|
||||||
{item.label}
|
{item.key}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Td>
|
</Td>
|
||||||
<Td
|
|
||||||
p={0}
|
|
||||||
px={4}
|
|
||||||
h={8}
|
|
||||||
color={'myGray.900'}
|
|
||||||
fontSize={'mini'}
|
|
||||||
fontWeight={'medium'}
|
|
||||||
>
|
|
||||||
<Flex alignItems={'center'}>{item.key}</Flex>
|
|
||||||
</Td>
|
|
||||||
<Td p={0} px={4} h={8} color={'myGray.900'} fontSize={'mini'}>
|
<Td p={0} px={4} h={8} color={'myGray.900'} fontSize={'mini'}>
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
{item.required ? (
|
{item.required ? (
|
||||||
|
|||||||
@ -73,7 +73,8 @@ const ChatInput = ({
|
|||||||
showSelectFile,
|
showSelectFile,
|
||||||
showSelectImg,
|
showSelectImg,
|
||||||
removeFiles,
|
removeFiles,
|
||||||
replaceFiles
|
replaceFiles,
|
||||||
|
hasFileUploading
|
||||||
} = useFileUpload({
|
} = useFileUpload({
|
||||||
outLinkAuthData,
|
outLinkAuthData,
|
||||||
chatId: chatId || '',
|
chatId: chatId || '',
|
||||||
@ -81,7 +82,6 @@ const ChatInput = ({
|
|||||||
fileCtrl
|
fileCtrl
|
||||||
});
|
});
|
||||||
const havInput = !!inputValue || fileList.length > 0;
|
const havInput = !!inputValue || fileList.length > 0;
|
||||||
const hasFileUploading = fileList.some((item) => !item.url);
|
|
||||||
const canSendMessage = havInput && !hasFileUploading;
|
const canSendMessage = havInput && !hasFileUploading;
|
||||||
|
|
||||||
// Upload files
|
// Upload files
|
||||||
@ -206,7 +206,7 @@ const ChatInput = ({
|
|||||||
<MyTooltip label={selectFileLabel}>
|
<MyTooltip label={selectFileLabel}>
|
||||||
<MyIcon name={selectFileIcon as any} w={'18px'} color={'myGray.600'} />
|
<MyIcon name={selectFileIcon as any} w={'18px'} color={'myGray.600'} />
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
<File onSelect={(files) => onSelectFile({ files, fileList })} />
|
<File onSelect={(files) => onSelectFile({ files })} />
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ const ChatInput = ({
|
|||||||
.filter((file) => {
|
.filter((file) => {
|
||||||
return file && fileTypeFilter(file);
|
return file && fileTypeFilter(file);
|
||||||
}) as File[];
|
}) as File[];
|
||||||
onSelectFile({ files, fileList });
|
onSelectFile({ files });
|
||||||
|
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@ -33,8 +33,10 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
|||||||
update: updateFiles,
|
update: updateFiles,
|
||||||
remove: removeFiles,
|
remove: removeFiles,
|
||||||
fields: fileList,
|
fields: fileList,
|
||||||
replace: replaceFiles
|
replace: replaceFiles,
|
||||||
|
append: appendFiles
|
||||||
} = fileCtrl;
|
} = fileCtrl;
|
||||||
|
const hasFileUploading = fileList.some((item) => !item.url);
|
||||||
|
|
||||||
const showSelectFile = fileSelectConfig?.canSelectFile;
|
const showSelectFile = fileSelectConfig?.canSelectFile;
|
||||||
const showSelectImg = fileSelectConfig?.canSelectImg;
|
const showSelectImg = fileSelectConfig?.canSelectImg;
|
||||||
@ -69,7 +71,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSelectFile = useCallback(
|
const onSelectFile = useCallback(
|
||||||
async ({ files, fileList }: { files: File[]; fileList: UserInputFileItemType[] }) => {
|
async ({ files }: { files: File[] }) => {
|
||||||
if (!files || files.length === 0) {
|
if (!files || files.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -128,22 +130,11 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Document, image
|
appendFiles(loadFiles);
|
||||||
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);
|
|
||||||
|
|
||||||
return loadFiles;
|
return loadFiles;
|
||||||
},
|
},
|
||||||
[maxSelectFiles, replaceFiles, toast, t, maxSize]
|
[maxSelectFiles, appendFiles, toast, t, maxSize]
|
||||||
);
|
);
|
||||||
|
|
||||||
const uploadFiles = async () => {
|
const uploadFiles = async () => {
|
||||||
@ -197,10 +188,23 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
|||||||
removeFiles(errorFileIndex);
|
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 {
|
return {
|
||||||
File,
|
File,
|
||||||
onOpenSelectFile,
|
onOpenSelectFile,
|
||||||
fileList,
|
fileList: sortFileList,
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
uploadFiles,
|
uploadFiles,
|
||||||
selectFileIcon,
|
selectFileIcon,
|
||||||
@ -208,6 +212,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
|||||||
showSelectFile,
|
showSelectFile,
|
||||||
showSelectImg,
|
showSelectImg,
|
||||||
removeFiles,
|
removeFiles,
|
||||||
replaceFiles
|
replaceFiles,
|
||||||
|
hasFileUploading
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const RenderInput = () => {
|
|||||||
showSelectFile,
|
showSelectFile,
|
||||||
showSelectImg,
|
showSelectImg,
|
||||||
removeFiles,
|
removeFiles,
|
||||||
replaceFiles
|
hasFileUploading
|
||||||
} = useFileUpload({
|
} = useFileUpload({
|
||||||
outLinkAuthData,
|
outLinkAuthData,
|
||||||
chatId: chatId || '',
|
chatId: chatId || '',
|
||||||
@ -64,9 +64,7 @@ const RenderInput = () => {
|
|||||||
fileCtrl
|
fileCtrl
|
||||||
});
|
});
|
||||||
const isDisabledInput = histories.length > 0;
|
const isDisabledInput = histories.length > 0;
|
||||||
const hasFileUploading = useMemo(() => {
|
|
||||||
return fileList.some((item) => !item.url);
|
|
||||||
}, [fileList]);
|
|
||||||
useRequest2(uploadFiles, {
|
useRequest2(uploadFiles, {
|
||||||
manual: false,
|
manual: false,
|
||||||
errorToast: t('common:upload_file_error'),
|
errorToast: t('common:upload_file_error'),
|
||||||
@ -83,6 +81,7 @@ const RenderInput = () => {
|
|||||||
[onNewChat, setRestartData]
|
[onNewChat, setRestartData]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get plugin input components
|
||||||
const formatPluginInputs = useMemo(() => {
|
const formatPluginInputs = useMemo(() => {
|
||||||
if (histories.length === 0) return pluginInputs;
|
if (histories.length === 0) return pluginInputs;
|
||||||
try {
|
try {
|
||||||
@ -203,7 +202,7 @@ const RenderInput = () => {
|
|||||||
{t('chat:select')}
|
{t('chat:select')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<File onSelect={(files) => onSelectFile({ files, fileList })} />
|
<File onSelect={(files) => onSelectFile({ files })} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<FilePreview
|
<FilePreview
|
||||||
fileList={fileList}
|
fileList={fileList}
|
||||||
|
|||||||
@ -50,7 +50,8 @@ const FileSelector = ({
|
|||||||
onOpenSelectFile,
|
onOpenSelectFile,
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
removeFiles,
|
removeFiles,
|
||||||
replaceFiles
|
replaceFiles,
|
||||||
|
hasFileUploading
|
||||||
} = useFileUpload({
|
} = useFileUpload({
|
||||||
outLinkAuthData,
|
outLinkAuthData,
|
||||||
chatId: chatId || '',
|
chatId: chatId || '',
|
||||||
@ -84,9 +85,6 @@ const FileSelector = ({
|
|||||||
errorToast: t('common:upload_file_error'),
|
errorToast: t('common:upload_file_error'),
|
||||||
refreshDeps: [fileList, outLinkAuthData, chatId]
|
refreshDeps: [fileList, outLinkAuthData, chatId]
|
||||||
});
|
});
|
||||||
const hasFileUploading = useMemo(() => {
|
|
||||||
return fileList.some((item) => !item.url);
|
|
||||||
}, [fileList]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUploading(hasFileUploading);
|
setUploading(hasFileUploading);
|
||||||
@ -128,7 +126,7 @@ const FileSelector = ({
|
|||||||
<FilePreview fileList={fileList} removeFiles={isDisabledInput ? undefined : removeFiles} />
|
<FilePreview fileList={fileList} removeFiles={isDisabledInput ? undefined : removeFiles} />
|
||||||
{fileList.length === 0 && <EmptyTip py={0} mt={3} text={t('chat:not_select_file')} />}
|
{fileList.length === 0 && <EmptyTip py={0} mt={3} text={t('chat:not_select_file')} />}
|
||||||
|
|
||||||
<File onSelect={(files) => onSelectFile({ files, fileList })} />
|
<File onSelect={(files) => onSelectFile({ files })} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,13 +13,16 @@ import dynamic from 'next/dynamic';
|
|||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
import Flow from '../WorkflowComponents/Flow';
|
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 Logs = dynamic(() => import('../Logs/index'));
|
||||||
const PublishChannel = dynamic(() => import('../Publish'));
|
const PublishChannel = dynamic(() => import('../Publish'));
|
||||||
|
|
||||||
const WorkflowEdit = () => {
|
const WorkflowEdit = () => {
|
||||||
const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e);
|
const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e);
|
||||||
const isV2Workflow = appDetail?.version === 'v2';
|
const isV2Workflow = appDetail?.version === 'v2';
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { openConfirm, ConfirmModal } = useConfirm({
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
@ -64,9 +67,11 @@ const WorkflowEdit = () => {
|
|||||||
|
|
||||||
const Render = () => {
|
const Render = () => {
|
||||||
return (
|
return (
|
||||||
|
<ReactFlowProvider>
|
||||||
<WorkflowContextProvider basicNodeTemplates={pluginSystemModuleTemplates}>
|
<WorkflowContextProvider basicNodeTemplates={pluginSystemModuleTemplates}>
|
||||||
<WorkflowEdit />
|
<WorkflowEdit />
|
||||||
</WorkflowContextProvider>
|
</WorkflowContextProvider>
|
||||||
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -11,15 +11,18 @@ import { Flex } from '@chakra-ui/react';
|
|||||||
import { workflowBoxStyles } from '../constants';
|
import { workflowBoxStyles } from '../constants';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import Flow from '../WorkflowComponents/Flow';
|
import Flow from '../WorkflowComponents/Flow';
|
||||||
import { t } from 'i18next';
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
|
|
||||||
const Logs = dynamic(() => import('../Logs/index'));
|
const Logs = dynamic(() => import('../Logs/index'));
|
||||||
const PublishChannel = dynamic(() => import('../Publish'));
|
const PublishChannel = dynamic(() => import('../Publish'));
|
||||||
|
|
||||||
const WorkflowEdit = () => {
|
const WorkflowEdit = () => {
|
||||||
const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e);
|
const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e);
|
||||||
const isV2Workflow = appDetail?.version === 'v2';
|
const isV2Workflow = appDetail?.version === 'v2';
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { openConfirm, ConfirmModal } = useConfirm({
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
@ -64,9 +67,11 @@ const WorkflowEdit = () => {
|
|||||||
|
|
||||||
const Render = () => {
|
const Render = () => {
|
||||||
return (
|
return (
|
||||||
|
<ReactFlowProvider>
|
||||||
<WorkflowContextProvider basicNodeTemplates={appSystemModuleTemplates}>
|
<WorkflowContextProvider basicNodeTemplates={appSystemModuleTemplates}>
|
||||||
<WorkflowEdit />
|
<WorkflowEdit />
|
||||||
</WorkflowContextProvider>
|
</WorkflowContextProvider>
|
||||||
|
</ReactFlowProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -475,8 +475,7 @@ const RenderList = React.memo(function RenderList({
|
|||||||
NodeOutputKeyEnum.userChatInput
|
NodeOutputKeyEnum.userChatInput
|
||||||
];
|
];
|
||||||
defaultValueMap[NodeInputKeyEnum.fileUrlList] = [
|
defaultValueMap[NodeInputKeyEnum.fileUrlList] = [
|
||||||
node.nodeId,
|
[node.nodeId, NodeOutputKeyEnum.userFiles]
|
||||||
NodeOutputKeyEnum.userFiles
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -169,12 +169,4 @@ const Workflow = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Render = () => {
|
export default React.memo(Workflow);
|
||||||
return (
|
|
||||||
<ReactFlowProvider>
|
|
||||||
<Workflow />
|
|
||||||
</ReactFlowProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default React.memo(Render);
|
|
||||||
|
|||||||
@ -172,6 +172,7 @@ const InputTypeConfig = ({
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
bg={'myGray.50'}
|
bg={'myGray.50'}
|
||||||
|
maxLength={30}
|
||||||
placeholder="appointment/sql"
|
placeholder="appointment/sql"
|
||||||
{...register('label', {
|
{...register('label', {
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@ -247,11 +247,18 @@ const MultipleReferenceSelector = ({
|
|||||||
|
|
||||||
const ArraySelector = useMemo(() => {
|
const ArraySelector = useMemo(() => {
|
||||||
const selectorVal = value as ReferenceItemValueType[];
|
const selectorVal = value as ReferenceItemValueType[];
|
||||||
|
const notValidItem =
|
||||||
|
!selectorVal ||
|
||||||
|
selectorVal.length === 0 ||
|
||||||
|
selectorVal.every((item) => {
|
||||||
|
const [nodeName, outputName] = getSelectValue(item);
|
||||||
|
return !nodeName || !outputName;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MultipleRowArraySelect
|
<MultipleRowArraySelect
|
||||||
label={
|
label={
|
||||||
selectorVal && selectorVal.length > 0 ? (
|
!notValidItem ? (
|
||||||
<Grid py={3} gridTemplateColumns={'1fr 1fr'} gap={2} fontSize={'sm'}>
|
<Grid py={3} gridTemplateColumns={'1fr 1fr'} gap={2} fontSize={'sm'}>
|
||||||
{selectorVal.map((item, index) => {
|
{selectorVal.map((item, index) => {
|
||||||
const [nodeName, outputName] = getSelectValue(item);
|
const [nodeName, outputName] = getSelectValue(item);
|
||||||
@ -261,17 +268,18 @@ const MultipleReferenceSelector = ({
|
|||||||
<Flex
|
<Flex
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
key={index}
|
key={index}
|
||||||
bg={isInvalidItem ? 'red.50' : 'primary.50'}
|
bg={'primary.50'}
|
||||||
color={isInvalidItem ? 'red.600' : 'myGray.900'}
|
color={'myGray.900'}
|
||||||
py={1}
|
py={1}
|
||||||
px={1.5}
|
px={1.5}
|
||||||
rounded={'sm'}
|
rounded={'sm'}
|
||||||
>
|
>
|
||||||
<Flex alignItems={'center'} flex={1}>
|
<Flex
|
||||||
{isInvalidItem ? (
|
alignItems={'center'}
|
||||||
t('common:invalid_variable')
|
flex={'1 0 0'}
|
||||||
) : (
|
maxW={'200px'}
|
||||||
<>
|
className="textEllipsis"
|
||||||
|
>
|
||||||
{nodeName}
|
{nodeName}
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name={'common/rightArrowLight'}
|
name={'common/rightArrowLight'}
|
||||||
@ -280,17 +288,15 @@ const MultipleReferenceSelector = ({
|
|||||||
color={'myGray.500'}
|
color={'myGray.500'}
|
||||||
/>
|
/>
|
||||||
{outputName}
|
{outputName}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name={'common/closeLight'}
|
name={'common/closeLight'}
|
||||||
w={'16px'}
|
w={'1rem'}
|
||||||
ml={1}
|
ml={1}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
color={'myGray.500'}
|
color={'myGray.500'}
|
||||||
_hover={{
|
_hover={{
|
||||||
color: 'primary.600'
|
color: 'red.600'
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@ -32,7 +32,8 @@ import {
|
|||||||
NodeChange,
|
NodeChange,
|
||||||
OnConnectStartParams,
|
OnConnectStartParams,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
useNodesState
|
useNodesState,
|
||||||
|
useReactFlow
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import { createContext, useContextSelector } from 'use-context-selector';
|
import { createContext, useContextSelector } from 'use-context-selector';
|
||||||
import { defaultRunningStatus } from './constants';
|
import { defaultRunningStatus } from './constants';
|
||||||
@ -568,6 +569,7 @@ const WorkflowContextProvider = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
/* ui flow to store data */
|
/* ui flow to store data */
|
||||||
|
const { fitView } = useReactFlow();
|
||||||
const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => {
|
const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => {
|
||||||
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
|
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
|
||||||
|
|
||||||
@ -577,6 +579,12 @@ const WorkflowContextProvider = ({
|
|||||||
return storeWorkflow;
|
return storeWorkflow;
|
||||||
} else if (!hideTip) {
|
} else if (!hideTip) {
|
||||||
checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true));
|
checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true));
|
||||||
|
|
||||||
|
// View move to the node that failed
|
||||||
|
fitView({
|
||||||
|
nodes: nodes.filter((node) => checkResults.includes(node.data.nodeId))
|
||||||
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
title: t('common:core.workflow.Check Failed')
|
title: t('common:core.workflow.Check Failed')
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
import React, { useRef, useCallback } from 'react';
|
import React, { useRef, useCallback } from 'react';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { useTranslation } from 'next-i18next';
|
|
||||||
import { useI18n } from '@/web/context/I18n';
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
|
||||||
export const useSelectFile = (props?: {
|
export const useSelectFile = (props?: {
|
||||||
fileType?: string;
|
fileType?: string;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
maxCount?: number;
|
maxCount?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { fileT } = useI18n();
|
const { fileT } = useI18n();
|
||||||
const { fileType = '*', multiple = false, maxCount = 10 } = props || {};
|
const { fileType = '*', multiple = false, maxCount = 10 } = props || {};
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const SelectFileDom = useRef<HTMLInputElement>(null);
|
const SelectFileDom = useRef<HTMLInputElement>(null);
|
||||||
const openSign = useRef<any>();
|
const openSign = useRef<any>();
|
||||||
|
|
||||||
const File = useCallback(
|
const File = useMemoizedFn(({ onSelect }: { onSelect: (e: File[], sign?: any) => void }) => (
|
||||||
({ onSelect }: { onSelect: (e: File[], sign?: any) => void }) => (
|
|
||||||
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
|
<Box position={'absolute'} w={0} h={0} overflow={'hidden'}>
|
||||||
<input
|
<input
|
||||||
ref={SelectFileDom}
|
ref={SelectFileDom}
|
||||||
@ -43,9 +41,7 @@ export const useSelectFile = (props?: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
),
|
));
|
||||||
[fileT, fileType, maxCount, multiple, toast]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onOpen = useCallback((sign?: any) => {
|
const onOpen = useCallback((sign?: any) => {
|
||||||
openSign.current = sign;
|
openSign.current = sign;
|
||||||
|
|||||||
@ -23,7 +23,8 @@ import {
|
|||||||
formatEditorVariablePickerIcon,
|
formatEditorVariablePickerIcon,
|
||||||
getAppChatConfig,
|
getAppChatConfig,
|
||||||
getGuideModule,
|
getGuideModule,
|
||||||
isReferenceValue
|
isReferenceValue,
|
||||||
|
isReferenceValueArray
|
||||||
} from '@fastgpt/global/core/workflow/utils';
|
} from '@fastgpt/global/core/workflow/utils';
|
||||||
import { TFunction } from 'next-i18next';
|
import { TFunction } from 'next-i18next';
|
||||||
import {
|
import {
|
||||||
@ -328,7 +329,8 @@ export const checkWorkflowNodeAndConnection = ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
node.data.flowNodeType === FlowNodeTypeEnum.pluginOutput &&
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user