V4.8.14 dev (#3234)

* feat: rewrite chat context (#3176)

* feat: add app auto execute (#3115)

* feat: add app auto execute

* auto exec configtion

* chatting animation

* change icon

* fix

* fix

* fix link

* feat: add chat context to all chatbox

* perf: loading ui

---------

Co-authored-by: heheer <heheer@sealos.io>

* app auto exec (#3179)

* add chat records loaded state (#3184)

* perf: chat store reset storage (#3186)

* perf: chat store reset storage

* perf: auto exec code

* chore: workflow ui (#3175)

* chore: workflow ui

* fix

* change icon color config

* change popover to mymenu

* 4.8.14 test (#3189)

* update doc

* fix: token check

* perf: icon button

* update doc

* feat: share page support configuration Whether to allow the original view (#3194)

* update doc

* perf: fix index (#3206)

* perf: i18n

* perf: Add service entry (#3226)

* 4.8.14 test (#3228)

* fix: ai log

* fix: text splitter

* fix: reference unselect & user form description & simple to advance (#3229)

* fix: reference unselect & user form description & simple to advance

* change abort position

* perf

* perf: code (#3232)

* perf: code

* update doc

* fix: create btn permission (#3233)

* update doc

* fix: refresh chatbox listener

* perf: check invalid reference

* perf: check invalid reference

* update doc

* fix: ui props

---------

Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2024-11-26 12:02:58 +08:00
committed by GitHub
parent 7e1d31b5a9
commit 8aa6b53760
221 changed files with 3831 additions and 2737 deletions

View File

@@ -532,12 +532,14 @@ export const workflowSystemVariables: EditorVariablePickerType[] = [
{
key: 'chatId',
label: i18nT('common:core.module.http.ChatId'),
valueType: WorkflowIOValueTypeEnum.string
valueType: WorkflowIOValueTypeEnum.string,
required: true
},
{
key: 'responseChatItemId',
label: i18nT('common:core.module.http.ResponseChatItemId'),
valueType: WorkflowIOValueTypeEnum.string
valueType: WorkflowIOValueTypeEnum.string,
required: true
},
{
key: 'histories',

View File

@@ -1,6 +1,6 @@
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useRouter } from 'next/router';
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
import React, { ReactNode, useCallback, useMemo, useRef } from 'react';
import { createContext } from 'use-context-selector';
import {
delClearChatHistories,
@@ -9,20 +9,25 @@ import {
getChatHistories
} from '../api';
import { ChatHistoryItemType } from '@fastgpt/global/core/chat/type';
import { ClearHistoriesProps, DelHistoryProps, UpdateHistoryProps } from '@/global/core/chat/api';
import { UpdateHistoryProps } from '@/global/core/chat/api';
import { BoxProps, useDisclosure } from '@chakra-ui/react';
import { useChatStore } from './storeChat';
import { useChatStore } from './useChatStore';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
type UpdateHistoryParams = {
chatId: UpdateHistoryProps['chatId'];
customTitle?: UpdateHistoryProps['customTitle'];
top?: UpdateHistoryProps['top'];
};
type ChatContextValueType = {
params: Record<string, string | number | boolean>;
};
type ChatContextType = {
chatId: string;
onUpdateHistory: (data: UpdateHistoryProps) => void;
onDelHistory: (data: DelHistoryProps) => Promise<undefined>;
onClearHistories: (data: ClearHistoriesProps) => Promise<undefined>;
onUpdateHistory: (data: UpdateHistoryParams) => void;
onDelHistory: (chatId: string) => Promise<undefined>;
onClearHistories: () => Promise<undefined>;
isOpenSlider: boolean;
onCloseSlider: () => void;
onOpenSlider: () => void;
@@ -46,8 +51,11 @@ type ChatContextType = {
onUpdateHistoryTitle: ({ chatId, newTitle }: { chatId: string; newTitle: string }) => void;
};
/*
主要存放历史记录数据。
同时还存放外部链接鉴权信息,不会在 chatTest 下使用
*/
export const ChatContext = createContext<ChatContextType>({
chatId: '',
// forbidLoadChat: undefined,
histories: [],
onUpdateHistoryTitle: function (): void {
@@ -62,13 +70,13 @@ export const ChatContext = createContext<ChatContextType>({
setHistories: function (): void {
throw new Error('Function not implemented.');
},
onUpdateHistory: function (data: UpdateHistoryProps): void {
onUpdateHistory: function (data: UpdateHistoryParams): void {
throw new Error('Function not implemented.');
},
onDelHistory: function (data: DelHistoryProps): Promise<undefined> {
onDelHistory: function (data: string): Promise<undefined> {
throw new Error('Function not implemented.');
},
onClearHistories: function (data: ClearHistoriesProps): Promise<undefined> {
onClearHistories: function (): Promise<undefined> {
throw new Error('Function not implemented.');
},
isOpenSlider: false,
@@ -93,9 +101,9 @@ const ChatContextProvider = ({
params
}: ChatContextValueType & { children: ReactNode }) => {
const router = useRouter();
const { chatId = '' } = router.query as { chatId: string };
const forbidLoadChat = useRef(false);
const { chatId, appId, setChatId, outLinkAuthData } = useChatStore();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
@@ -108,38 +116,26 @@ const ChatContextProvider = ({
} = useScrollPagination(getChatHistories, {
pageSize: 20,
params,
refreshDeps: [params]
refreshDeps: [params],
showErrorToast: false
});
const { setLastChatId } = useChatStore();
const onChangeChatId = useCallback(
(changeChatId = getNanoid(), forbid = false) => {
(changeChatId = getNanoid(24), forbid = false) => {
if (chatId !== changeChatId) {
forbidLoadChat.current = forbid;
setLastChatId(changeChatId);
router.replace({
query: {
...router.query,
chatId: changeChatId || ''
}
});
setChatId(changeChatId);
}
onCloseSlider();
},
[chatId, onCloseSlider, router, setLastChatId]
[chatId, onCloseSlider, setChatId]
);
// Refresh lastChatId
useEffect(() => {
setLastChatId(chatId);
}, [chatId, setLastChatId]);
const onChangeAppId = useCallback(
(appId: string) => {
router.replace({
query: {
...router.query,
chatId: '',
appId
}
});
@@ -148,40 +144,63 @@ const ChatContextProvider = ({
[onCloseSlider, router]
);
const { runAsync: onUpdateHistory, loading: isUpdatingHistory } = useRequest2(putChatHistory, {
onSuccess(data, params) {
const { chatId, top, customTitle } = params[0];
const { runAsync: onUpdateHistory } = useRequest2(
(data: UpdateHistoryParams) =>
putChatHistory({
appId,
...data,
...outLinkAuthData
}),
{
onBefore(params) {
const { chatId, top, customTitle } = params[0];
setHistories((histories) => {
const updatedHistories = histories.map((history) => {
if (history.chatId === chatId) {
return {
...history,
customTitle: customTitle || history.customTitle,
top: top !== undefined ? top : history.top
};
}
return history;
setHistories((histories) => {
const updatedHistories = histories.map((history) => {
if (history.chatId === chatId) {
return {
...history,
customTitle: customTitle || history.customTitle,
top: top !== undefined ? top : history.top
};
}
return history;
});
return top !== undefined
? updatedHistories.sort((a, b) => (b.top ? 1 : 0) - (a.top ? 1 : 0))
: updatedHistories;
});
return top !== undefined
? updatedHistories.sort((a, b) => (b.top ? 1 : 0) - (a.top ? 1 : 0))
: updatedHistories;
});
},
errorToast: undefined
});
const { runAsync: onDelHistory, loading: isDeletingHistory } = useRequest2(delChatHistoryById, {
onSuccess(data, params) {
const { chatId } = params[0];
setHistories((old) => old.filter((i) => i.chatId !== chatId));
},
refreshDeps: [outLinkAuthData, appId],
errorToast: undefined
}
});
);
const { runAsync: onDelHistory, loading: isDeletingHistory } = useRequest2(
(chatId: string) =>
delChatHistoryById({
appId: appId,
chatId,
...outLinkAuthData
}),
{
onSuccess(data, params) {
const chatId = params[0];
setHistories((old) => old.filter((i) => i.chatId !== chatId));
},
refreshDeps: [outLinkAuthData, appId]
}
);
const { runAsync: onClearHistories, loading: isClearingHistory } = useRequest2(
delClearChatHistories,
() =>
delClearChatHistories({
appId: appId,
...outLinkAuthData
}),
{
refreshDeps: [outLinkAuthData, appId],
onSuccess() {
setHistories([]);
},
@@ -206,27 +225,43 @@ const ChatContextProvider = ({
[histories, loadHistories, setHistories]
);
const isLoading =
isUpdatingHistory || isDeletingHistory || isClearingHistory || isPaginationLoading;
const isLoading = isDeletingHistory || isClearingHistory || isPaginationLoading;
const contextValue = {
chatId,
onUpdateHistory,
onDelHistory,
onClearHistories,
isOpenSlider,
onCloseSlider,
onOpenSlider,
forbidLoadChat,
onChangeChatId,
onChangeAppId,
isLoading,
setHistories,
ScrollData,
loadHistories,
histories,
onUpdateHistoryTitle
};
const contextValue = useMemo(
() => ({
onUpdateHistory,
onDelHistory,
onClearHistories,
isOpenSlider,
onCloseSlider,
onOpenSlider,
forbidLoadChat,
onChangeChatId,
onChangeAppId,
isLoading,
setHistories,
ScrollData,
loadHistories,
histories,
onUpdateHistoryTitle
}),
[
ScrollData,
histories,
isLoading,
isOpenSlider,
loadHistories,
onChangeAppId,
onChangeChatId,
onClearHistories,
onCloseSlider,
onDelHistory,
onOpenSlider,
onUpdateHistory,
onUpdateHistoryTitle,
setHistories
]
);
return <ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>;
};

View File

@@ -0,0 +1,125 @@
import { ChatBoxInputFormType } from '@/components/core/chat/ChatContainer/ChatBox/type';
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
import React, { ReactNode, useCallback, useMemo, useRef, useState } from 'react';
import { createContext } from 'use-context-selector';
import { ComponentRef as ChatComponentRef } from '@/components/core/chat/ChatContainer/ChatBox/type';
import { useForm, UseFormReturn } from 'react-hook-form';
import { defaultChatData } from '@/global/core/chat/constants';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
type ChatBoxDataType = {
userAvatar?: string;
app: {
chatConfig?: AppChatConfigType;
name: string;
avatar: string;
type: `${AppTypeEnum}`;
pluginInputs: FlowNodeInputItemType[];
};
};
type ChatItemContextType = {
ChatBoxRef: React.RefObject<ChatComponentRef> | null;
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
pluginRunTab: PluginRunBoxTabEnum;
setPluginRunTab: React.Dispatch<React.SetStateAction<PluginRunBoxTabEnum>>;
resetVariables: (props?: { variables?: Record<string, any> }) => void;
clearChatRecords: () => void;
chatBoxData: ChatBoxDataType;
setChatBoxData: React.Dispatch<React.SetStateAction<ChatBoxDataType>>;
isPlugin: boolean;
};
export const ChatItemContext = createContext<ChatItemContextType>({
ChatBoxRef: null,
// @ts-ignore
variablesForm: undefined,
pluginRunTab: PluginRunBoxTabEnum.input,
setPluginRunTab: function (value: React.SetStateAction<PluginRunBoxTabEnum>): void {
throw new Error('Function not implemented.');
},
resetVariables: function (props?: { variables?: Record<string, any> }): void {
throw new Error('Function not implemented.');
},
clearChatRecords: function (): void {
throw new Error('Function not implemented.');
}
});
/*
Chat 对象的上下文
*/
const ChatItemContextProvider = ({ children }: { children: ReactNode }) => {
const ChatBoxRef = useRef<ChatComponentRef>(null);
const variablesForm = useForm<ChatBoxInputFormType>();
const [chatBoxData, setChatBoxData] = useState<ChatBoxDataType>(defaultChatData);
const isPlugin = chatBoxData.app.type === AppTypeEnum.plugin;
// plugin
const [pluginRunTab, setPluginRunTab] = useState<PluginRunBoxTabEnum>(PluginRunBoxTabEnum.input);
const resetVariables = useCallback(
(props?: { variables?: Record<string, any> }) => {
const { variables = {} } = props || {};
// Reset to empty input
const data = variablesForm.getValues();
// Reset the old variables to empty
const resetVariables: Record<string, any> = {};
for (const key in data.variables) {
resetVariables[key] = (() => {
if (Array.isArray(data.variables[key])) {
return [];
}
return '';
})();
}
variablesForm.setValue('variables', {
...resetVariables,
...variables
});
},
[variablesForm]
);
const clearChatRecords = useCallback(() => {
const data = variablesForm.getValues();
for (const key in data.variables) {
variablesForm.setValue(`variables.${key}`, '');
}
ChatBoxRef.current?.restartChat?.();
}, [variablesForm]);
const contextValue = useMemo(() => {
return {
chatBoxData,
setChatBoxData,
isPlugin,
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
clearChatRecords
};
}, [
chatBoxData,
setChatBoxData,
clearChatRecords,
isPlugin,
pluginRunTab,
resetVariables,
variablesForm
]);
return <ChatItemContext.Provider value={contextValue}>{children}</ChatItemContext.Provider>;
};
export default ChatItemContextProvider;

View File

@@ -0,0 +1,121 @@
import { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRecords';
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import React, { ReactNode, useMemo, useState } from 'react';
import { createContext, useContextSelector } from 'use-context-selector';
import { ChatItemContext } from './chatItemContext';
import { getChatRecords } from '../api';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { BoxProps } from '@chakra-ui/react';
type ChatRecordContextType = {
chatRecords: ChatSiteItemType[];
setChatRecords: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
isChatRecordsLoaded: boolean;
setIsChatRecordsLoaded: React.Dispatch<React.SetStateAction<boolean>>;
totalRecordsCount: number;
ScrollData: ({
children,
...props
}: {
children: React.ReactNode;
ScrollContainerRef?: React.RefObject<HTMLDivElement>;
} & BoxProps) => React.JSX.Element;
};
export const ChatRecordContext = createContext<ChatRecordContextType>({
chatRecords: [],
setChatRecords: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
throw new Error('Function not implemented.');
},
isChatRecordsLoaded: false,
setIsChatRecordsLoaded: function (value: React.SetStateAction<boolean>): void {
throw new Error('Function not implemented.');
},
totalRecordsCount: 0,
ScrollData: function ({
children,
...props
}: {
children: React.ReactNode;
ScrollContainerRef?: React.RefObject<HTMLDivElement>;
} & BoxProps): React.JSX.Element {
throw new Error('Function not implemented.');
}
});
/*
具体对话记录的上下文
*/
const ChatRecordContextProvider = ({
children,
params
}: {
children: ReactNode;
params: Record<string, any>;
}) => {
const ChatBoxRef = useContextSelector(ChatItemContext, (v) => v.ChatBoxRef);
const [isChatRecordsLoaded, setIsChatRecordsLoaded] = useState(false);
const {
data: chatRecords,
ScrollData,
setData: setChatRecords,
total: totalRecordsCount
} = useScrollPagination(
async (data: getPaginationRecordsBody): Promise<PaginationResponse<ChatSiteItemType>> => {
const res = await getChatRecords(data);
// First load scroll to bottom
if (data.offset === 0) {
function scrollToBottom() {
requestAnimationFrame(
ChatBoxRef?.current ? () => ChatBoxRef?.current?.scrollToBottom?.() : scrollToBottom
);
}
scrollToBottom();
}
setIsChatRecordsLoaded(true);
return {
...res,
list: res.list.map((item) => ({
...item,
dataId: item.dataId || getNanoid(),
status: ChatStatusEnum.finish
}))
};
},
{
pageSize: 10,
refreshDeps: [params],
params,
scrollLoadType: 'top',
showErrorToast: false
}
);
const contextValue = useMemo(() => {
return {
chatRecords,
setChatRecords,
totalRecordsCount,
ScrollData,
isChatRecordsLoaded,
setIsChatRecordsLoaded
};
}, [
ScrollData,
chatRecords,
setChatRecords,
totalRecordsCount,
isChatRecordsLoaded,
setIsChatRecordsLoaded
]);
return <ChatRecordContext.Provider value={contextValue}>{children}</ChatRecordContext.Provider>;
};
export default ChatRecordContextProvider;

View File

@@ -1,39 +0,0 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import type { DeleteChatItemProps } from '@/global/core/chat/api';
type State = {
lastChatAppId: string;
setLastChatAppId: (id: string) => void;
lastChatId: string;
setLastChatId: (id: string) => void;
};
export const useChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
lastChatAppId: '',
setLastChatAppId(id: string) {
set((state) => {
state.lastChatAppId = id;
});
},
lastChatId: '',
setLastChatId(id: string) {
set((state) => {
state.lastChatId = id;
});
}
})),
{
name: 'chatStore',
partialize: (state) => ({
lastChatAppId: state.lastChatAppId,
lastChatId: state.lastChatId
})
}
)
)
);

View File

@@ -0,0 +1,129 @@
import { create } from 'zustand';
import { createJSONStorage, devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
type State = {
source?: `${ChatSourceEnum}`;
setSource: (e: `${ChatSourceEnum}`) => any;
appId: string;
setAppId: (e: string) => any;
lastChatAppId: string;
setLastChatAppId: (e: string) => any;
lastChatId: string;
chatId: string;
setChatId: (e?: string) => any;
outLinkAuthData: OutLinkChatAuthProps;
setOutLinkAuthData: (e: OutLinkChatAuthProps) => any;
};
const createCustomStorage = () => {
const sessionKeys = ['source', 'chatId', 'appId'];
return {
getItem: (name: string) => {
const sessionData = JSON.parse(sessionStorage.getItem(name) || '{}');
const localData = JSON.parse(localStorage.getItem(name) || '{}');
return JSON.stringify({ ...localData, ...sessionData });
},
setItem: (name: string, value: string) => {
const data = JSON.parse(value);
// 分离 session 和 local 数据
const sessionData = Object.fromEntries(
Object.entries(data.state).filter(([key]) => sessionKeys.includes(key))
);
const localData = Object.fromEntries(
Object.entries(data.state).filter(([key]) => !sessionKeys.includes(key))
);
// 分别存储
if (Object.keys(sessionData).length > 0) {
sessionStorage.setItem(name, JSON.stringify({ state: sessionData, version: 0 }));
}
if (Object.keys(localData).length > 0) {
localStorage.setItem(name, JSON.stringify({ state: localData, version: 0 }));
}
},
removeItem: (name: string) => {
sessionStorage.removeItem(name);
localStorage.removeItem(name);
}
};
};
/*
appId chatId source 存在当前 tab 中,刷新浏览器不会丢失。
lastChatId 和 lastChatAppId 全局存储,切换 tab 或浏览器也不会丢失。用于首次 tab 进入对话时,恢复上一次的 chat。(只恢复相同来源的)
*/
export const useChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
source: undefined,
setSource(e) {
set((state) => {
// 首次进入 chat 页面,如果相同的 source则恢复上一次的 chatId
if (!state.chatId && state.lastChatId && state.lastChatId.startsWith(e)) {
state.chatId = state.lastChatId.split('-')[1];
} else if (e !== get().source) {
// 来源改变,强制重置 chatId
state.chatId = getNanoid(24);
}
if (!state.appId && state.lastChatAppId) {
state.appId = state.lastChatAppId;
}
state.source = e;
});
},
appId: '',
setAppId(e) {
if (!e) return;
set((state) => {
state.appId = e;
state.lastChatAppId = e;
});
},
lastChatId: '',
chatId: '',
setChatId(e) {
const id = e || getNanoid(24);
set((state) => {
state.chatId = id;
state.lastChatId = `${state.source}-${id}`;
});
},
lastChatAppId: '',
setLastChatAppId(e) {
set((state) => {
state.lastChatAppId = e;
});
},
outLinkAuthData: {},
setOutLinkAuthData(e) {
set((state) => {
state.outLinkAuthData = e;
});
}
})),
{
name: 'chatStore',
storage: createJSONStorage(createCustomStorage),
partialize: (state) => ({
source: state.source,
chatId: state.chatId,
appId: state.appId,
lastChatId: state.lastChatId,
lastChatAppId: state.lastChatAppId
})
}
)
)
);

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { Dispatch, ReactNode, SetStateAction, useEffect, useMemo, useState } from 'react';
import { Dispatch, ReactNode, SetStateAction, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { createContext } from 'use-context-selector';
import {

View File

@@ -3,15 +3,11 @@ import { getCollectionSource } from '@/web/core/dataset/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next';
import { ShareChatAuthProps } from '@fastgpt/global/support/permission/chat';
import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read';
export function getCollectionSourceAndOpen({
collectionId,
shareId,
outLinkUid
}: {
collectionId: string;
} & ShareChatAuthProps) {
export function getCollectionSourceAndOpen(
props: { collectionId: string } & readCollectionSourceBody
) {
const { toast } = useToast();
const { t } = useTranslation();
const { setLoading } = useSystemStore();
@@ -20,7 +16,7 @@ export function getCollectionSourceAndOpen({
try {
setLoading(true);
const { value: url } = await getCollectionSource({ collectionId, shareId, outLinkUid });
const { value: url } = await getCollectionSource(props);
if (!url) {
throw new Error('No file found');
@@ -33,7 +29,7 @@ export function getCollectionSourceAndOpen({
}
} catch (error) {
toast({
title: getErrText(error, t('common:error.fileNotFound')),
title: t(getErrText(error, t('common:error.fileNotFound'))),
status: 'error'
});
}

View File

@@ -340,7 +340,6 @@ export const checkWorkflowNodeAndConnection = ({
nodes: Node<FlowNodeItemType, string | undefined>[];
edges: Edge<any>[];
}): string[] | undefined => {
const nodeIds: string[] = nodes.map((node) => node.data.nodeId);
// 1. reference check. Required value
for (const node of nodes) {
const data = node.data;
@@ -382,6 +381,26 @@ export const checkWorkflowNodeAndConnection = ({
continue;
}
}
if (data.flowNodeType === FlowNodeTypeEnum.userSelect) {
const configValue = data.inputs.find(
(input) => input.key === NodeInputKeyEnum.userSelectOptions
)?.value;
if (
!configValue ||
configValue.length === 0 ||
configValue.some((item: any) => !item.value)
) {
return [data.nodeId];
}
}
if (data.flowNodeType === FlowNodeTypeEnum.formInput) {
const value = data.inputs.find(
(input) => input.key === NodeInputKeyEnum.userInputForms
)?.value;
if (!value || value.length === 0) {
return [data.nodeId];
}
}
// check node input
if (
@@ -392,32 +411,37 @@ export const checkWorkflowNodeAndConnection = ({
}
if (input.required) {
if (Array.isArray(input.value) && input.value.length === 0) return true;
if (input.value === undefined) return true;
if (Array.isArray(input.value) && input.value.length === 0) return true;
}
// Check plugin output
// if (
// node.data.flowNodeType === FlowNodeTypeEnum.pluginOutput &&
// (input.value?.length === 0 ||
// (isValidReferenceValue(input.value, nodeIds) && !input.value?.[1]))
// ) {
// return true;
// }
// check reference invalid
const renderType = input.renderTypeList[input.selectedTypeIndex || 0];
if (renderType === FlowNodeInputTypeEnum.reference) {
// 无效引用时,返回 true
const checkValueValid = (value: ReferenceItemValueType) => {
const nodeId = value?.[0];
const outputId = value?.[1];
if (!nodeId || !outputId) return false;
return !!nodes
.find((node) => node.data.nodeId === nodeId)
?.data.outputs.find((output) => output.id === outputId);
};
if (input.valueType?.startsWith('array')) {
if (input.required && (!input.value || input.value.length === 0)) {
input.value = input.value ?? [];
// 如果内容为空,则报错
if (input.required && input.value.length === 0) {
return true;
}
return !isValidArrayReferenceValue(input.value, nodeIds);
} else {
// Single reference
if (input.required) {
return !checkValueValid(input.value);
}
}
// Single reference
return input.required && !isValidReferenceValue(input.value, nodeIds);
}
return false;
})
@@ -566,7 +590,8 @@ export const compareSnapshot = (
scheduledTriggerConfig: clone1.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: clone1.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: clone1.chatConfig?.fileSelectConfig || undefined,
instruction: clone1.chatConfig?.instruction || ''
instruction: clone1.chatConfig?.instruction || '',
autoExecute: clone1.chatConfig?.autoExecute || undefined
},
{
welcomeText: clone2.chatConfig?.welcomeText || '',
@@ -577,7 +602,8 @@ export const compareSnapshot = (
scheduledTriggerConfig: clone2.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: clone2.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: clone2.chatConfig?.fileSelectConfig || undefined,
instruction: clone2.chatConfig?.instruction || ''
instruction: clone2.chatConfig?.instruction || '',
autoExecute: clone2.chatConfig?.autoExecute || undefined
}
)
) {