User select node (#2397)

* feat: add user select node (#2300)

* feat: add user select node

* fix

* type

* fix

* fix

* fix

* perf: user select code

* perf: user select histories

* perf: i18n

---------

Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2024-08-15 12:27:04 +08:00
committed by GitHub
parent f8b8fcc172
commit fdeb1590d7
51 changed files with 1060 additions and 184 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -18,7 +18,12 @@ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { uploadFile2DB } from '@/web/common/file/controller';
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from '../type';
import {
ChatBoxInputFormType,
ChatBoxInputType,
SendPromptFnType,
UserInputFileItemType
} from '../type';
import { textareaMinH } from '../constants';
import { UseFormReturn, useFieldArray } from 'react-hook-form';
import { ChatBoxContext } from '../Provider';
@@ -51,7 +56,7 @@ const ChatInput = ({
chatForm,
appId
}: {
onSendMessage: (val: ChatBoxInputType & { autoTTSResponse?: boolean }) => void;
onSendMessage: SendPromptFnType;
onStop: () => void;
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
resetInputVal: (val: ChatBoxInputType) => void;

View File

@@ -15,6 +15,8 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useTranslation } from 'next-i18next';
import { SendPromptFnType } from '../type';
const colorMap = {
[ChatStatusEnum.loading]: {
bg: 'myGray.100',
@@ -30,16 +32,7 @@ const colorMap = {
}
};
const ChatItem = ({
type,
avatar,
statusBoxData,
children,
isLastChild,
questionGuides = [],
...chatControllerProps
}: {
type: ChatRoleEnum.Human | ChatRoleEnum.AI;
type BasicProps = {
avatar?: string;
statusBoxData?: {
status: `${ChatStatusEnum}`;
@@ -47,7 +40,28 @@ const ChatItem = ({
};
questionGuides?: string[];
children?: React.ReactNode;
} & ChatControllerProps) => {
} & ChatControllerProps;
type UserItemType = BasicProps & {
type: ChatRoleEnum.Human;
onSendMessage: undefined;
};
type AiItemType = BasicProps & {
type: ChatRoleEnum.AI;
onSendMessage: SendPromptFnType;
};
type Props = UserItemType | AiItemType;
const ChatItem = ({
type,
avatar,
statusBoxData,
children,
isLastChild,
questionGuides = [],
onSendMessage,
...chatControllerProps
}: Props) => {
const styleMap: BoxProps =
type === ChatRoleEnum.Human
? {
@@ -96,12 +110,13 @@ const ChatItem = ({
isLastChild={isLastChild}
isChatting={isChatting}
questionGuides={questionGuides}
onSendMessage={onSendMessage}
/>
);
})}
</Flex>
);
}, [chat, isChatting, isLastChild, questionGuides, type]);
}, [chat, isChatting, isLastChild, onSendMessage, questionGuides, type]);
const chatStatusMap = useMemo(() => {
if (!statusBoxData?.status) return;

View File

@@ -11,7 +11,6 @@ import React, {
import Script from 'next/script';
import type {
AIChatItemValueItemType,
ChatHistoryItemResType,
ChatSiteItemType,
UserChatItemValueItemType
} from '@fastgpt/global/core/chat/type.d';
@@ -34,7 +33,12 @@ import type { AdminMarkType } from './components/SelectMarkCollection';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { postQuestionGuide } from '@/web/core/ai/api';
import type { ComponentRef, ChatBoxInputType, ChatBoxInputFormType } from './type.d';
import type {
ComponentRef,
ChatBoxInputType,
ChatBoxInputFormType,
SendPromptFnType
} from './type.d';
import type { StartChatFnProps, generatingMessageProps } from '../type';
import ChatInput from './Input/ChatInput';
import ChatBoxDivider from '../../Divider';
@@ -151,6 +155,16 @@ const ChatBox = (
isChatting
} = useContextSelector(ChatBoxContext, (v) => v);
const isInteractive = useMemo(() => {
const lastAIHistory = chatHistories[chatHistories.length - 1];
if (!lastAIHistory) return false;
const lastAIMessage = lastAIHistory.value as AIChatItemValueItemType[];
const interactiveContent = lastAIMessage?.find(
(item) => item.type === ChatItemValueTypeEnum.interactive
)?.interactive?.params;
return !!interactiveContent;
}, [chatHistories]);
// compute variable input is finish.
const chatForm = useForm<ChatBoxInputFormType>({
defaultValues: {
@@ -201,6 +215,7 @@ const ChatBox = (
status,
name,
tool,
interactive,
autoTTSResponse,
variables
}: generatingMessageProps & { autoTTSResponse?: boolean }) => {
@@ -287,6 +302,16 @@ const ChatBox = (
};
} else if (event === SseResponseEventEnum.updateVariables && variables) {
variablesForm.reset(variables);
} else if (event === SseResponseEventEnum.interactive) {
const val: AIChatItemValueItemType = {
type: ChatItemValueTypeEnum.interactive,
interactive
};
return {
...item,
value: item.value.concat(val)
};
}
return item;
@@ -355,16 +380,8 @@ const ChatBox = (
/**
* user confirm send prompt
*/
const sendPrompt = useCallback(
({
text = '',
files = [],
history = chatHistories,
autoTTSResponse = false
}: ChatBoxInputType & {
autoTTSResponse?: boolean;
history?: ChatSiteItemType[];
}) => {
const sendPrompt: SendPromptFnType = useCallback(
({ text = '', files = [], history = chatHistories, autoTTSResponse = false }) => {
variablesForm.handleSubmit(
async (variables) => {
if (!onStartChat) return;
@@ -898,6 +915,7 @@ const ChatBox = (
onRetry={retryInput(item.dataId)}
onDelete={delOneMessage(item.dataId)}
isLastChild={index === chatHistories.length - 1}
onSendMessage={undefined}
/>
)}
{item.obj === ChatRoleEnum.AI && (
@@ -907,7 +925,8 @@ const ChatBox = (
avatar={appAvatar}
chat={item}
isLastChild={index === chatHistories.length - 1}
{...(item.obj === ChatRoleEnum.AI && {
onSendMessage={sendPrompt}
{...{
showVoiceIcon,
shareId,
outLinkUid,
@@ -923,7 +942,7 @@ const ChatBox = (
onCloseUserLike: onCloseUserLike(item),
onAddUserDislike: onAddUserDislike(item),
onReadUserDislike: onReadUserDislike(item)
})}
}}
>
<ResponseTags
showTags={index !== chatHistories.length - 1 || !isChatting}
@@ -973,7 +992,7 @@ const ChatBox = (
</Box>
</Box>
{/* message input */}
{onStartChat && chatStarted && active && appId && (
{onStartChat && chatStarted && active && appId && !isInteractive && (
<ChatInput
onSendMessage={sendPrompt}
onStop={() => chatController.current?.abort('stop')}

View File

@@ -29,6 +29,16 @@ export type ChatBoxInputType = {
files?: UserInputFileItemType[];
};
export type SendPromptFnType = ({
text,
files,
history,
autoTTSResponse
}: ChatBoxInputType & {
autoTTSResponse?: boolean;
history?: ChatSiteItemType[];
}) => void;
export type ComponentRef = {
restartChat: () => void;
scrollToBottom: (behavior?: 'smooth' | 'auto') => void;

View File

@@ -1,7 +1,7 @@
import { ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { ChatItemValueItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type';
import { ChatBoxInputType, UserInputFileItemType } from './type';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { getFileIcon } from '@fastgpt/global/common/file/icon';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
if (!value) {
@@ -37,3 +37,37 @@ export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): Chat
files
};
};
export const setUserSelectResultToHistories = (
histories: ChatSiteItemType[],
selectVal: string
): ChatSiteItemType[] => {
if (histories.length === 0) return histories;
// @ts-ignore
return histories.map((item, i) => {
if (i !== histories.length - 1) return item;
item.value;
const value = item.value.map((val) => {
if (val.type !== ChatItemValueTypeEnum.interactive || !val.interactive) return val;
return {
...val,
interactive: {
...val.interactive,
params: {
...val.interactive.params,
userSelectedVal: val.interactive.params.userSelectOptions.find(
(item) => item.value === selectVal
)?.value
}
}
};
});
return {
...item,
value
};
});
};

View File

@@ -1,6 +1,7 @@
import { StreamResponseType } from '@/web/common/api/fetch';
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
import { ChatSiteItemType, ToolModuleResponseItemType } from '@fastgpt/global/core/chat/type';
import { InteractiveNodeResponseItemType } from '@fastgpt/global/core/workflow/template/system/userSelect/type';
export type generatingMessageProps = {
event: SseResponseEventEnum;
@@ -8,6 +9,7 @@ export type generatingMessageProps = {
name?: string;
status?: 'running' | 'finish';
tool?: ToolModuleResponseItemType;
interactive?: InteractiveNodeResponseItemType;
variables?: Record<string, any>;
};

View File

@@ -6,7 +6,9 @@ import {
AccordionIcon,
AccordionItem,
AccordionPanel,
Box
Box,
Button,
Flex
} from '@chakra-ui/react';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import {
@@ -17,6 +19,10 @@ import {
import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import { SendPromptFnType } from '../ChatContainer/ChatBox/type';
import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../ChatContainer/ChatBox/Provider';
import { setUserSelectResultToHistories } from '../ChatContainer/ChatBox/utils';
type props = {
value: UserChatItemValueItemType | AIChatItemValueItemType;
@@ -25,10 +31,21 @@ type props = {
isLastChild: boolean;
isChatting: boolean;
questionGuides: string[];
onSendMessage?: SendPromptFnType;
};
const AIResponseBox = ({ value, index, chat, isLastChild, isChatting, questionGuides }: props) => {
if (value.text) {
const AIResponseBox = ({
value,
index,
chat,
isLastChild,
isChatting,
questionGuides,
onSendMessage
}: props) => {
const chatHistories = useContextSelector(ChatBoxContext, (v) => v.chatHistories);
if (value.type === ChatItemValueTypeEnum.text && value.text) {
let source = (value.text?.content || '').trim();
// First empty line
@@ -126,6 +143,45 @@ ${toolResponse}`}
</Box>
);
}
if (
value.type === ChatItemValueTypeEnum.interactive &&
value.interactive &&
value.interactive.type === 'userSelect'
) {
return (
<Flex flexDirection={'column'} gap={2} minW={'200px'} maxW={'250px'}>
{value.interactive.params.userSelectOptions?.map((option) => {
const selected = option.value === value.interactive?.params?.userSelectedVal;
return (
<Button
key={option.key}
variant={'whitePrimary'}
isDisabled={!isLastChild && value.interactive?.params?.userSelectedVal !== undefined}
{...(selected
? {
_disabled: {
cursor: 'default',
borderColor: 'primary.300',
bg: 'primary.50 !important',
color: 'primary.600'
}
}
: {})}
onClick={() => {
onSendMessage?.({
text: option.value,
history: setUserSelectResultToHistories(chatHistories, option.value)
});
}}
>
{option.value}
</Button>
);
})}
</Flex>
);
}
return null;
};

View File

@@ -17,6 +17,7 @@ import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../ChatContainer/ChatBox/Provider';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getFileIcon } from '@fastgpt/global/common/file/icon';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
type sideTabItemType = {
moduleLogo?: string;
@@ -124,7 +125,11 @@ const WholeResponseModal = ({
</Flex>
}
>
{response?.length && <ResponseBox response={response} showDetail={showDetail} />}
{!!response?.length ? (
<ResponseBox response={response} showDetail={showDetail} />
) : (
<EmptyTip text={t('chat:no_workflow_response')} />
)}
</MyModal>
);
};
@@ -480,6 +485,12 @@ export const WholeResponseContent = ({
value={activeModule?.readFilesResult}
/>
</>
{/* user select */}
<Row
label={t('common:core.chat.response.user_select_result')}
value={activeModule?.userSelectResult}
/>
</Box>
)}
</>

View File

@@ -9,8 +9,7 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
@@ -22,11 +21,18 @@ import { NextAPI } from '@/service/middleware/entry';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import {
getWorkflowEntryNodeIds,
initWorkflowEdgeStatus,
rewriteNodeOutputByHistories,
storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
export type Props = {
messages: ChatCompletionMessageParam[];
nodes: RuntimeNodeItemType[];
edges: RuntimeEdgeItemType[];
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
variables: Record<string, any>;
appId: string;
appName: string;
@@ -52,8 +58,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatConfig
} = req.body as Props;
try {
// [histories, user]
const chatMessages = GPTMessages2Chats(messages);
const userInput = chatMessages.pop()?.value as UserChatItemValueItemType[] | undefined;
/* user auth */
@@ -64,6 +70,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
authToken: true
})
]);
// auth balance
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
const isPlugin = app.type === AppTypeEnum.plugin;
if (!Array.isArray(nodes)) {
@@ -73,18 +82,19 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
throw new Error('Edges is not array');
}
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, chatMessages));
// Plugin need to replace inputs
if (isPlugin) {
nodes = updatePluginInputByVariables(nodes, variables);
variables = removePluginInputVariables(variables, nodes);
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
variables = removePluginInputVariables(variables, runtimeNodes);
} else {
if (!userInput) {
throw new Error('Params Error');
}
}
// auth balance
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
runtimeNodes = rewriteNodeOutputByHistories(chatMessages, runtimeNodes);
/* start process */
const { flowResponses, flowUsages } = await dispatchWorkFlow({
@@ -95,8 +105,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
tmbId,
user,
app,
runtimeNodes: nodes,
runtimeEdges: edges,
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(edges, chatMessages),
variables,
query: removeEmptyUserInput(userInput),
chatConfig,

View File

@@ -13,7 +13,7 @@ import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
import {
getDefaultEntryNodeIds,
getWorkflowEntryNodeIds,
getMaxHistoryLimitFromNodes,
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes,
@@ -64,6 +64,7 @@ import {
getPluginRunContent
} from '@fastgpt/global/core/app/plugin/utils';
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { rewriteNodeOutputByHistories } from '@fastgpt/global/core/workflow/runtime/utils';
type FastGptWebChatProps = {
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
@@ -225,24 +226,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
appId: app._id,
chatId,
limit,
field: `dataId obj value`
field: `dataId obj value nodeOutputs`
}),
getAppLatestVersion(app._id, app)
]);
const newHistories = concatHistories(histories, chatMessages);
// Get runtimeNodes
const runtimeNodes = isPlugin
? updatePluginInputByVariables(
storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
variables
)
: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes));
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
const runtimeVariables = removePluginInputVariables(
variables,
storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes))
);
if (isPlugin) {
// Rewrite plugin run params variables
variables = removePluginInputVariables(variables, runtimeNodes);
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
}
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
/* start flow controller */
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
@@ -258,8 +257,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId,
responseChatItemId,
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(edges),
variables: runtimeVariables,
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
variables,
query: removeEmptyUserInput(userQuestion.value),
chatConfig,
histories: newHistories,

View File

@@ -145,7 +145,7 @@ const Header = () => {
}
}}
>
{t('common:core.workflow.Debug')}
{t('common:core.workflow.run_test')}
</Button>
{!historiesDefaultData && (

View File

@@ -146,7 +146,7 @@ const Header = () => {
}
}}
>
{t('common:core.workflow.Debug')}
{t('common:core.workflow.run_test')}
</Button>
{!historiesDefaultData && (

View File

@@ -56,7 +56,8 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
[FlowNodeTypeEnum.lafModule]: dynamic(() => import('./nodes/NodeLaf')),
[FlowNodeTypeEnum.ifElseNode]: dynamic(() => import('./nodes/NodeIfElse')),
[FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate')),
[FlowNodeTypeEnum.code]: dynamic(() => import('./nodes/NodeCode'))
[FlowNodeTypeEnum.code]: dynamic(() => import('./nodes/NodeCode')),
[FlowNodeTypeEnum.userSelect]: dynamic(() => import('./nodes/NodeUserSelect'))
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge

View File

@@ -0,0 +1,144 @@
import React, { useMemo } from 'react';
import { NodeProps, Position } from 'reactflow';
import { Box, Button, HStack, Input } from '@chakra-ui/react';
import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
import Container from '../components/Container';
import RenderInput from './render/RenderInput';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { SourceHandle } from './render/Handle';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { UserSelectOptionItemType } from '@fastgpt/global/core/workflow/template/system/userSelect/type';
import IOTitle from '../components/IOTitle';
import RenderOutput from './render/RenderOutput';
const NodeUserSelect = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs } = data;
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const CustomComponent = useMemo(
() => ({
[NodeInputKeyEnum.userSelectOptions]: ({
key: optionKey,
value = [],
...props
}: FlowNodeInputItemType) => {
const options = value as UserSelectOptionItemType[];
return (
<Box>
{options.map((item, i) => (
<Box key={item.key} mb={4}>
<HStack spacing={1}>
<MyTooltip label={t('common:common.Delete')}>
<MyIcon
mt={0.5}
name={'minus'}
w={'0.8rem'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{ color: 'red.600' }}
onClick={() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: optionKey,
value: {
...props,
key: optionKey,
value: options.filter((input) => input.key !== item.key)
}
});
onChangeNode({
nodeId,
type: 'delOutput',
key: item.key
});
}}
/>
</MyTooltip>
<Box color={'myGray.600'} fontWeight={'medium'} fontSize={'sm'}>
{t('common:option') + (i + 1)}
</Box>
</HStack>
<Box position={'relative'}>
<Input
mt={1}
defaultValue={item.value}
bg={'white'}
fontSize={'sm'}
onChange={(e) => {
const newVal = options.map((val) =>
val.key === item.key
? {
...val,
value: e.target.value
}
: val
);
onChangeNode({
nodeId,
type: 'updateInput',
key: optionKey,
value: {
...props,
key: optionKey,
value: newVal
}
});
}}
/>
<SourceHandle
nodeId={nodeId}
handleId={getHandleId(nodeId, 'source', item.key)}
position={Position.Right}
translate={[26, 0]}
/>
</Box>
</Box>
))}
<Button
fontSize={'sm'}
leftIcon={<MyIcon name={'common/addLight'} w={4} />}
onClick={() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: optionKey,
value: {
...props,
key: optionKey,
value: options.concat({ value: '', key: getNanoid() })
}
});
}}
>
{t('common:core.module.Add_option')}
</Button>
</Box>
);
}
}),
[nodeId, onChangeNode, t]
);
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
<Container>
<RenderInput nodeId={nodeId} flowInputList={inputs} CustomComponent={CustomComponent} />
</Container>
<Container>
<IOTitle text={t('common:common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
</NodeCard>
);
};
export default React.memo(NodeUserSelect);

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Box, Button, Card, Flex } from '@chakra-ui/react';
import { Box, Button, Card, Flex, Image } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
@@ -42,7 +42,6 @@ type Props = FlowNodeItemType & {
const NodeCard = (props: Props) => {
const { t } = useTranslation();
const { appT } = useI18n();
const { toast } = useToast();
@@ -70,7 +69,7 @@ const NodeCard = (props: Props) => {
// custom title edit
const { onOpenModal: onOpenCustomTitleModal, EditModal: EditTitleModal } = useEditTitle({
title: t('common:common.Custom Title'),
placeholder: appT('module.Custom Title Tip') || ''
placeholder: t('app:module.Custom Title Tip') || ''
});
const showToolHandle = useMemo(
@@ -166,7 +165,7 @@ const NodeCard = (props: Props) => {
onSuccess: (e) => {
if (!e) {
return toast({
title: appT('modules.Title is required'),
title: t('app:modules.Title is required'),
status: 'warning'
});
}
@@ -183,7 +182,7 @@ const NodeCard = (props: Props) => {
)}
<Box flex={1} />
{hasNewVersion && (
<MyTooltip label={appT('app.modules.click to update')}>
<MyTooltip label={t('app:app.modules.click to update')}>
<Button
bg={'yellow.50'}
color={'yellow.600'}
@@ -197,11 +196,29 @@ const NodeCard = (props: Props) => {
_hover={{ bg: 'yellow.100' }}
onClick={onOpenConfirmSync(onClickSyncVersion)}
>
<Box>{appT('app.modules.has new version')}</Box>
<Box>{t('app:app.modules.has new version')}</Box>
<QuestionOutlineIcon ml={1} />
</Button>
</MyTooltip>
)}
{!!nodeTemplate?.diagram && (
<MyTooltip
label={
<Image src={nodeTemplate?.diagram} w={'100%'} minH={['auto', '200px']} alt={''} />
}
>
<Box
fontSize={'sm'}
color={'primary.700'}
p={1}
rounded={'sm'}
cursor={'default'}
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
>
{t('common:core.module.Diagram')}
</Box>
</MyTooltip>
)}
</Flex>
<MenuRender nodeId={nodeId} menuForbid={menuForbid} />
<NodeIntro nodeId={nodeId} intro={intro} />
@@ -217,9 +234,9 @@ const NodeCard = (props: Props) => {
name,
menuForbid,
hasNewVersion,
appT,
onOpenConfirmSync,
onClickSyncVersion,
nodeTemplate?.diagram,
intro,
ConfirmSyncModal,
onOpenCustomTitleModal,

View File

@@ -621,7 +621,6 @@ const WorkflowContextProvider = ({
},
appId
});
// console.log({ finishedEdges, finishedNodes, nextStepRunNodes, flowResponses });
// 5. Store debug result
const newStoreDebugData = {
runtimeNodes: finishedNodes,

View File

@@ -2,13 +2,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import React from 'react';
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
import { streamFetch } from '@/web/common/api/fetch';
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
import {
getDefaultEntryNodeIds,
getMaxHistoryLimitFromNodes,
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils';
import { getMaxHistoryLimitFromNodes } from '@fastgpt/global/core/workflow/runtime/utils';
import { useMemoizedFn } from 'ahooks';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from './context';
@@ -47,8 +41,8 @@ export const useChatTest = ({
data: {
// Send histories and user messages
messages: messages.slice(-historyMaxLen - 2),
nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
edges: initWorkflowEdgeStatus(edges),
nodes,
edges,
variables,
appId: appDetail._id,
appName: `调试-${appDetail.name}`,

View File

@@ -6,7 +6,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import {
getDefaultEntryNodeIds,
getWorkflowEntryNodeIds,
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils';
@@ -38,7 +38,7 @@ export const getScheduleTriggerApp = async () => {
teamId: String(app.teamId),
tmbId: String(app.tmbId),
app,
runtimeNodes: storeNodes2RuntimeNodes(app.modules, getDefaultEntryNodeIds(app.modules)),
runtimeNodes: storeNodes2RuntimeNodes(app.modules, getWorkflowEntryNodeIds(app.modules)),
runtimeEdges: initWorkflowEdgeStatus(app.edges),
variables: {},
query: [

View File

@@ -201,6 +201,11 @@ export const streamFetch = ({
event,
variables: parseJson
});
} else if (event === SseResponseEventEnum.interactive) {
responseQueue.push({
event,
...parseJson
});
} else if (event === SseResponseEventEnum.error) {
if (parseJson.statusText === TeamErrEnum.aiPointsNotEnough) {
useSystemStore.getState().setIsNotSufficientModal(true);