diff --git a/projects/app/src/components/Markdown/index.tsx b/projects/app/src/components/Markdown/index.tsx index 26eaec3e2171..9f1fe26250a4 100644 --- a/projects/app/src/components/Markdown/index.tsx +++ b/projects/app/src/components/Markdown/index.tsx @@ -49,7 +49,7 @@ const Markdown = ({ const formatSource = useMemo(() => { const formatSource = source .replace( - /([\u4e00-\u9fa5\u3000-\u303f])([\w\u0020-\u007e])|([a-zA-Z0-9\u0020-\u007e])([\u4e00-\u9fa5\u3000-\u303f])/g, + /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g, '$1$3 $2$4' ) // Chinese and english chars separated by space .replace(/\n*(\[QUOTE SIGN\]\(.*\))/g, '$1'); diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatController.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatController.tsx index 9bd1dc01b05f..368b2c10a0ef 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatController.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatController.tsx @@ -9,11 +9,13 @@ import { formatChatValue2InputType } from '../utils'; import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatBoxContext } from '../Provider'; import { useContextSelector } from 'use-context-selector'; +import { SendPromptFnType } from '../type'; export type ChatControllerProps = { isLastChild: boolean; chat: ChatSiteItemType; showVoiceIcon?: boolean; + onSendMessage: SendPromptFnType; onRetry?: () => void; onDelete?: () => void; onMark?: () => void; @@ -25,7 +27,6 @@ export type ChatControllerProps = { const ChatController = ({ chat, - isLastChild, showVoiceIcon, onReadUserDislike, onCloseUserLike, diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx index 8f628ec90c46..3bc11ee14bb5 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx @@ -6,7 +6,11 @@ import { MessageCardStyle } from '../constants'; import { formatChatValue2InputType } from '../utils'; import Markdown from '@/components/Markdown'; import styles from '../index.module.scss'; -import { ChatRoleEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; +import { + ChatItemValueTypeEnum, + ChatRoleEnum, + ChatStatusEnum +} from '@fastgpt/global/core/chat/constants'; import FilesBlock from './FilesBox'; import { ChatBoxContext } from '../Provider'; import { useContextSelector } from 'use-context-selector'; @@ -16,6 +20,9 @@ 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'; +import { AIChatItemValueItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type'; +import { CodeClassNameEnum } from '@/components/Markdown/utils'; +import { isEqual } from 'lodash'; const colorMap = { [ChatStatusEnum.loading]: { @@ -42,26 +49,81 @@ type BasicProps = { children?: React.ReactNode; } & ChatControllerProps; -type UserItemType = BasicProps & { - type: ChatRoleEnum.Human; - onSendMessage: undefined; -}; -type AiItemType = BasicProps & { - type: ChatRoleEnum.AI; +type Props = BasicProps & { + type: ChatRoleEnum.Human | ChatRoleEnum.AI; onSendMessage: SendPromptFnType; }; -type Props = UserItemType | AiItemType; -const ChatItem = ({ - type, - avatar, - statusBoxData, - children, +const RenderQuestionGuide = ({ questionGuides }: { questionGuides: string[] }) => { + return ( + + ); +}; + +const HumanContentCard = React.memo( + function HumanContentCard({ chatValue }: { chatValue: ChatItemValueItemType[] }) { + const { text, files = [] } = formatChatValue2InputType(chatValue); + return ( + + {files.length > 0 && } + {text && } + + ); + }, + (prevProps, nextProps) => isEqual(prevProps.chatValue, nextProps.chatValue) +); +const AIContentCard = React.memo(function AIContentCard({ + chatValue, + dataId, isLastChild, - questionGuides = [], + isChatting, onSendMessage, - ...chatControllerProps -}: Props) => { + questionGuides +}: { + dataId: string; + chatValue: ChatItemValueItemType[]; + isLastChild: boolean; + isChatting: boolean; + onSendMessage: SendPromptFnType; + questionGuides: string[]; +}) { + return ( + + {chatValue.map((value, i) => { + const key = `${dataId}-ai-${i}`; + + return ( + + ); + })} + {isLastChild && questionGuides.length > 0 && ( + + )} + + ); +}); + +const ChatItem = (props: Props) => { + const { + type, + avatar, + statusBoxData, + children, + isLastChild, + questionGuides = [], + onSendMessage, + chat + } = props; + const styleMap: BoxProps = type === ChatRoleEnum.Human ? { @@ -81,59 +143,84 @@ const ChatItem = ({ const { t } = useTranslation(); const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting); - const { chat } = chatControllerProps; - const { copyData } = useCopyData(); - const chatText = useMemo(() => formatChatValue2InputType(chat.value).text || '', [chat.value]); - const ContentCard = useMemo(() => { - if (type === 'Human') { - const { text, files = [] } = formatChatValue2InputType(chat.value); - return ( - - {files.length > 0 && } - {text && } - - ); - } - /* AI */ - return ( - - {chat.value.map((value, i) => { - const key = `${chat.dataId}-ai-${i}`; - - return ( - - ); - })} - - ); - }, [chat, isChatting, isLastChild, onSendMessage, questionGuides, type]); + const { copyData } = useCopyData(); const chatStatusMap = useMemo(() => { if (!statusBoxData?.status) return; return colorMap[statusBoxData.status]; }, [statusBoxData?.status]); + /* + 1. The interactive node is divided into n dialog boxes. + 2. Auto-complete the last textnode + */ + const splitAiResponseResults = useMemo(() => { + if (chat.obj !== ChatRoleEnum.AI) return [chat.value]; + + // Remove empty text node + const filterList = chat.value.filter((item, i) => { + if (item.type === ChatItemValueTypeEnum.text && !item.text?.content?.trim()) { + return false; + } + return item; + }); + + const groupedValues: AIChatItemValueItemType[][] = []; + let currentGroup: AIChatItemValueItemType[] = []; + + filterList.forEach((value) => { + if (value.type === 'interactive') { + if (currentGroup.length > 0) { + groupedValues.push(currentGroup); + currentGroup = []; + } + + groupedValues.push([value]); + } else { + currentGroup.push(value); + } + }); + + if (currentGroup.length > 0) { + groupedValues.push(currentGroup); + } + + // Check last group is interactive, Auto add a empty text node(animation) + const lastGroup = groupedValues[groupedValues.length - 1]; + if (isChatting) { + if ( + (lastGroup && + lastGroup[lastGroup.length - 1] && + lastGroup[lastGroup.length - 1].type === ChatItemValueTypeEnum.interactive) || + groupedValues.length === 0 + ) { + groupedValues.push([ + { + type: ChatItemValueTypeEnum.text, + text: { + content: '' + } + } + ]); + } + } + + return groupedValues; + }, [chat.obj, chat.value, isChatting]); + return ( <> {/* control icon */} {isChatting && type === ChatRoleEnum.AI && isLastChild ? null : ( - + )} + {/* Workflow status */} {!!chatStatusMap && statusBoxData && isLastChild && ( {/* content */} - - ( + - {ContentCard} - {children} - {/* 对话框底部的复制按钮 */} - {type == ChatRoleEnum.AI && (!isChatting || (isChatting && !isLastChild)) && ( - - - copyData(chatText)} - /> - - - )} - - + + {type === ChatRoleEnum.Human && } + {type === ChatRoleEnum.AI && ( + + )} + {/* Example: Response tags. A set of dialogs only needs to be displayed once*/} + {i === splitAiResponseResults.length - 1 && <>{children}} + {/* 对话框底部的复制按钮 */} + {type == ChatRoleEnum.AI && + value[0]?.type !== 'interactive' && + (!isChatting || (isChatting && !isLastChild)) && ( + + + copyData(formatChatValue2InputType(value).text ?? '')} + /> + + + )} + + + ))} ); }; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx index 8fb5dcbeab85..2f0081a6a64a 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx @@ -56,7 +56,7 @@ import dynamic from 'next/dynamic'; import type { StreamResponseType } from '@/web/common/api/fetch'; import { useContextSelector } from 'use-context-selector'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; -import { useThrottleFn } from 'ahooks'; +import { useCreation, useMemoizedFn, useThrottleFn, useTrackedEffect } from 'ahooks'; import MyIcon from '@fastgpt/web/components/common/Icon'; const ResponseTags = dynamic(() => import('./components/ResponseTags')); @@ -574,233 +574,205 @@ const ChatBox = ( ); // retry input - const retryInput = useCallback( - (dataId?: string) => { - if (!dataId || !onDelMessage) return; - - return async () => { - setLoading(true); - const index = chatHistories.findIndex((item) => item.dataId === dataId); - const delHistory = chatHistories.slice(index); - try { - await Promise.all( - delHistory.map((item) => { - if (item.dataId) { - return onDelMessage({ contentId: item.dataId }); - } - }) - ); - setChatHistories((state) => (index === 0 ? [] : state.slice(0, index))); + const retryInput = useMemoizedFn((dataId?: string) => { + if (!dataId || !onDelMessage) return; - sendPrompt({ - ...formatChatValue2InputType(delHistory[0].value), - history: chatHistories.slice(0, index) - }); - } catch (error) { - toast({ - status: 'warning', - title: getErrText(error, 'Retry failed') - }); - } - setLoading(false); - }; - }, - [chatHistories, onDelMessage, sendPrompt, setChatHistories, setLoading, toast] - ); - // delete one message(One human and the ai response) - const delOneMessage = useCallback( - (dataId?: string) => { - if (!dataId || !onDelMessage) return; - return () => { - setChatHistories((state) => { - let aiIndex = -1; - - return state.filter((chat, i) => { - if (chat.dataId === dataId) { - aiIndex = i + 1; - onDelMessage({ - contentId: dataId - }); - return false; - } else if (aiIndex === i && chat.obj === ChatRoleEnum.AI && chat.dataId) { - onDelMessage({ - contentId: chat.dataId - }); - return false; + return async () => { + setLoading(true); + const index = chatHistories.findIndex((item) => item.dataId === dataId); + const delHistory = chatHistories.slice(index); + try { + await Promise.all( + delHistory.map((item) => { + if (item.dataId) { + return onDelMessage({ contentId: item.dataId }); } - return true; - }); + }) + ); + setChatHistories((state) => (index === 0 ? [] : state.slice(0, index))); + + sendPrompt({ + ...formatChatValue2InputType(delHistory[0].value), + history: chatHistories.slice(0, index) }); - }; - }, - [onDelMessage, setChatHistories] - ); + } catch (error) { + toast({ + status: 'warning', + title: getErrText(error, 'Retry failed') + }); + } + setLoading(false); + }; + }); + // delete one message(One human and the ai response) + const delOneMessage = useMemoizedFn((dataId?: string) => { + if (!dataId || !onDelMessage) return; + return () => { + setChatHistories((state) => { + let aiIndex = -1; + + return state.filter((chat, i) => { + if (chat.dataId === dataId) { + aiIndex = i + 1; + onDelMessage({ + contentId: dataId + }); + return false; + } else if (aiIndex === i && chat.obj === ChatRoleEnum.AI && chat.dataId) { + onDelMessage({ + contentId: chat.dataId + }); + return false; + } + return true; + }); + }); + }; + }); // admin mark - const onMark = useCallback( - (chat: ChatSiteItemType, q = '') => { - if (!showMarkIcon || chat.obj !== ChatRoleEnum.AI) return; + const onMark = useMemoizedFn((chat: ChatSiteItemType, q = '') => { + if (!showMarkIcon || chat.obj !== ChatRoleEnum.AI) return; - return () => { - if (!chat.dataId) return; + return () => { + if (!chat.dataId) return; - if (chat.adminFeedback) { - setAdminMarkData({ - chatItemId: chat.dataId, - datasetId: chat.adminFeedback.datasetId, - collectionId: chat.adminFeedback.collectionId, - dataId: chat.adminFeedback.dataId, - q: chat.adminFeedback.q || q || '', - a: chat.adminFeedback.a - }); - } else { - setAdminMarkData({ - chatItemId: chat.dataId, - q, - a: formatChatValue2InputType(chat.value).text - }); - } - }; - }, - [showMarkIcon] - ); - const onAddUserLike = useCallback( - (chat: ChatSiteItemType) => { - if ( - feedbackType !== FeedbackTypeEnum.user || - chat.obj !== ChatRoleEnum.AI || - chat.userBadFeedback - ) - return; + if (chat.adminFeedback) { + setAdminMarkData({ + chatItemId: chat.dataId, + datasetId: chat.adminFeedback.datasetId, + collectionId: chat.adminFeedback.collectionId, + dataId: chat.adminFeedback.dataId, + q: chat.adminFeedback.q || q || '', + a: chat.adminFeedback.a + }); + } else { + setAdminMarkData({ + chatItemId: chat.dataId, + q, + a: formatChatValue2InputType(chat.value).text + }); + } + }; + }); + const onAddUserLike = useMemoizedFn((chat: ChatSiteItemType) => { + if ( + feedbackType !== FeedbackTypeEnum.user || + chat.obj !== ChatRoleEnum.AI || + chat.userBadFeedback + ) + return; + return () => { + if (!chat.dataId || !chatId || !appId) return; + + const isGoodFeedback = !!chat.userGoodFeedback; + setChatHistories((state) => + state.map((chatItem) => + chatItem.dataId === chat.dataId + ? { + ...chatItem, + userGoodFeedback: isGoodFeedback ? undefined : 'yes' + } + : chatItem + ) + ); + try { + updateChatUserFeedback({ + appId, + chatId, + teamId, + teamToken, + chatItemId: chat.dataId, + shareId, + outLinkUid, + userGoodFeedback: isGoodFeedback ? undefined : 'yes' + }); + } catch (error) {} + }; + }); + const onCloseUserLike = useMemoizedFn((chat: ChatSiteItemType) => { + if (feedbackType !== FeedbackTypeEnum.admin) return; + return () => { + if (!chat.dataId || !chatId || !appId) return; + setChatHistories((state) => + state.map((chatItem) => + chatItem.dataId === chat.dataId ? { ...chatItem, userGoodFeedback: undefined } : chatItem + ) + ); + updateChatUserFeedback({ + appId, + teamId, + teamToken, + chatId, + chatItemId: chat.dataId, + userGoodFeedback: undefined + }); + }; + }); + const onAddUserDislike = useMemoizedFn((chat: ChatSiteItemType) => { + if ( + feedbackType !== FeedbackTypeEnum.user || + chat.obj !== ChatRoleEnum.AI || + chat.userGoodFeedback + ) { + return; + } + if (chat.userBadFeedback) { return () => { if (!chat.dataId || !chatId || !appId) return; - - const isGoodFeedback = !!chat.userGoodFeedback; setChatHistories((state) => state.map((chatItem) => - chatItem.dataId === chat.dataId - ? { - ...chatItem, - userGoodFeedback: isGoodFeedback ? undefined : 'yes' - } - : chatItem + chatItem.dataId === chat.dataId ? { ...chatItem, userBadFeedback: undefined } : chatItem ) ); try { updateChatUserFeedback({ appId, chatId, - teamId, - teamToken, chatItemId: chat.dataId, shareId, - outLinkUid, - userGoodFeedback: isGoodFeedback ? undefined : 'yes' + teamId, + teamToken, + outLinkUid }); } catch (error) {} }; - }, - [appId, chatId, feedbackType, outLinkUid, setChatHistories, shareId, teamId, teamToken] - ); - const onCloseUserLike = useCallback( - (chat: ChatSiteItemType) => { - if (feedbackType !== FeedbackTypeEnum.admin) return; - return () => { - if (!chat.dataId || !chatId || !appId) return; + } else { + return () => setFeedbackId(chat.dataId); + } + }); + const onReadUserDislike = useMemoizedFn((chat: ChatSiteItemType) => { + if (feedbackType !== FeedbackTypeEnum.admin || chat.obj !== ChatRoleEnum.AI) return; + return () => { + if (!chat.dataId) return; + setReadFeedbackData({ + chatItemId: chat.dataId || '', + content: chat.userBadFeedback || '' + }); + }; + }); + const onCloseCustomFeedback = useMemoizedFn((chat: ChatSiteItemType, i: number) => { + return (e: React.ChangeEvent) => { + if (e.target.checked && appId && chatId && chat.dataId) { + closeCustomFeedback({ + appId, + chatId, + chatItemId: chat.dataId, + index: i + }); + // update dom setChatHistories((state) => state.map((chatItem) => - chatItem.dataId === chat.dataId - ? { ...chatItem, userGoodFeedback: undefined } + chatItem.obj === ChatRoleEnum.AI && chatItem.dataId === chat.dataId + ? { + ...chatItem, + customFeedbacks: chatItem.customFeedbacks?.filter((_, index) => index !== i) + } : chatItem ) ); - updateChatUserFeedback({ - appId, - teamId, - teamToken, - chatId, - chatItemId: chat.dataId, - userGoodFeedback: undefined - }); - }; - }, - [appId, chatId, feedbackType, setChatHistories, teamId, teamToken] - ); - const onAddUserDislike = useCallback( - (chat: ChatSiteItemType) => { - if ( - feedbackType !== FeedbackTypeEnum.user || - chat.obj !== ChatRoleEnum.AI || - chat.userGoodFeedback - ) { - return; } - if (chat.userBadFeedback) { - return () => { - if (!chat.dataId || !chatId || !appId) return; - setChatHistories((state) => - state.map((chatItem) => - chatItem.dataId === chat.dataId - ? { ...chatItem, userBadFeedback: undefined } - : chatItem - ) - ); - try { - updateChatUserFeedback({ - appId, - chatId, - chatItemId: chat.dataId, - shareId, - teamId, - teamToken, - outLinkUid - }); - } catch (error) {} - }; - } else { - return () => setFeedbackId(chat.dataId); - } - }, - [appId, chatId, feedbackType, outLinkUid, setChatHistories, shareId, teamId, teamToken] - ); - const onReadUserDislike = useCallback( - (chat: ChatSiteItemType) => { - if (feedbackType !== FeedbackTypeEnum.admin || chat.obj !== ChatRoleEnum.AI) return; - return () => { - if (!chat.dataId) return; - setReadFeedbackData({ - chatItemId: chat.dataId || '', - content: chat.userBadFeedback || '' - }); - }; - }, - [feedbackType] - ); - const onCloseCustomFeedback = useCallback( - (chat: ChatSiteItemType, i: number) => { - return (e: React.ChangeEvent) => { - if (e.target.checked && appId && chatId && chat.dataId) { - closeCustomFeedback({ - appId, - chatId, - chatItemId: chat.dataId, - index: i - }); - // update dom - setChatHistories((state) => - state.map((chatItem) => - chatItem.obj === ChatRoleEnum.AI && chatItem.dataId === chat.dataId - ? { - ...chatItem, - customFeedbacks: chatItem.customFeedbacks?.filter((_, index) => index !== i) - } - : chatItem - ) - ); - } - }; - }, - [appId, chatId, setChatHistories] - ); + }; + }); const showEmpty = useMemo( () => @@ -817,7 +789,7 @@ const ChatBox = ( welcomeText ] ); - const statusBoxData = useMemo(() => { + const statusBoxData = useCreation(() => { if (!isChatting) return; const chatContent = chatHistories[chatHistories.length - 1]; if (!chatContent) return; @@ -883,10 +855,9 @@ const ChatBox = ( }); } })); - return ( - - - {/* chat box container */} + + const RenderRecords = useMemo(() => { + return ( {showEmpty && } @@ -907,7 +878,7 @@ const ChatBox = ( onRetry={retryInput(item.dataId)} onDelete={delOneMessage(item.dataId)} isLastChild={index === chatHistories.length - 1} - onSendMessage={undefined} + onSendMessage={sendPrompt} /> )} {item.obj === ChatRoleEnum.AI && ( @@ -986,6 +957,42 @@ const ChatBox = ( + ); + }, [ + appAvatar, + chatForm, + chatHistories, + chatStarted, + delOneMessage, + isChatting, + onAddUserDislike, + onAddUserLike, + onCloseCustomFeedback, + onCloseUserLike, + onMark, + onReadUserDislike, + outLinkUid, + questionGuides, + retryInput, + sendPrompt, + shareId, + showEmpty, + showMarkIcon, + showVoiceIcon, + statusBoxData, + t, + teamId, + teamToken, + userAvatar, + variableList?.length, + welcomeText + ]); + + return ( + + + {/* chat box container */} + {RenderRecords} {/* message input */} {onStartChat && chatStarted && active && appId && !isInteractive && ( { - const chatHistories = useContextSelector(ChatBoxContext, (v) => v.chatHistories); - - // Question guide - const RenderQuestionGuide = useMemo(() => { - if ( - isLastChild && - !isChatting && - questionGuides.length > 0 && - index === chat.value.length - 1 - ) { - return ( - - ); - } - return null; - }, [chat.value.length, index, isChatting, isLastChild, questionGuides]); +const RenderText = React.memo(function RenderText({ + showAnimation, + text +}: { + showAnimation: boolean; + text?: string; +}) { + let source = (text || '').trim(); - const Render = useMemo(() => { - if (value.type === ChatItemValueTypeEnum.text && value.text) { - let source = (value.text?.content || '').trim(); + // First empty line + // if (!source && !isLastChild) return null; - // First empty line - if (!source && chat.value.length > 1) return null; + return ; +}); +const RenderTool = React.memo( + function RenderTool({ + showAnimation, + tools + }: { + showAnimation: boolean; + tools: ToolModuleResponseItemType[]; + }) { + return ( + + {tools.map((tool) => { + const toolParams = (() => { + try { + return JSON.stringify(JSON.parse(tool.params), null, 2); + } catch (error) { + return tool.params; + } + })(); + const toolResponse = (() => { + try { + return JSON.stringify(JSON.parse(tool.response), null, 2); + } catch (error) { + return tool.response; + } + })(); - return ( - - ); - } - if (value.type === ChatItemValueTypeEnum.tool && value.tools) { - return ( - - {value.tools.map((tool) => { - const toolParams = (() => { - try { - return JSON.stringify(JSON.parse(tool.params), null, 2); - } catch (error) { - return tool.params; - } - })(); - const toolResponse = (() => { - try { - return JSON.stringify(JSON.parse(tool.response), null, 2); - } catch (error) { - return tool.response; - } - })(); - - return ( - - - - - - {tool.toolName} - - {isChatting && !tool.response && } - - - - {toolParams && toolParams !== '{}' && ( - - - - )} - {toolResponse && ( + return ( + + + + + + {tool.toolName} + + {showAnimation && !tool.response && } + + + + {toolParams && toolParams !== '{}' && ( + - )} - - - + + )} + {toolResponse && ( + + )} + + + + ); + })} + + ); + }, + (prevProps, nextProps) => isEqual(prevProps, nextProps) +); +const RenderInteractive = React.memo( + function RenderInteractive({ + isChatting, + interactive, + onSendMessage, + chatHistories + }: { + isChatting: boolean; + interactive: InteractiveNodeResponseItemType; + onSendMessage: SendPromptFnType; + chatHistories: ChatSiteItemType[]; + }) { + return ( + <> + {interactive?.params?.description && } + + {interactive.params.userSelectOptions?.map((option) => { + const selected = option.value === interactive?.params?.userSelectedVal; + + return ( + ); })} - - ); - } - if ( - value.type === ChatItemValueTypeEnum.interactive && - value.interactive && - value.interactive.type === 'userSelect' - ) { - return ( - <> - {value.interactive?.params?.description && ( - - )} - - {value.interactive.params.userSelectOptions?.map((option) => { - const selected = option.value === value.interactive?.params?.userSelectedVal; + + + ); + }, + ( + prevProps, + nextProps // isChatting 更新时候,onSendMessage 和 chatHistories 肯定都更新了,这里不需要额外的刷新 + ) => + prevProps.isChatting === nextProps.isChatting && + isEqual(prevProps.interactive, nextProps.interactive) +); - return ( - - ); - })} - - {/* Animation */} - {isLastChild && isChatting && index === chat.value.length - 1 && ( - - )} - - ); - } - }, [chat.value.length, chatHistories, index, isChatting, isLastChild, onSendMessage, value]); +const AIResponseBox = ({ value, isLastChild, isChatting, onSendMessage }: props) => { + const chatHistories = useContextSelector(ChatBoxContext, (v) => v.chatHistories); - return ( - <> - {Render} - {RenderQuestionGuide} - - ); + if (value.type === ChatItemValueTypeEnum.text && value.text) + return ; + if (value.type === ChatItemValueTypeEnum.tool && value.tools) + return ; + if ( + value.type === ChatItemValueTypeEnum.interactive && + value.interactive && + value.interactive.type === 'userSelect' + ) + return ( + + ); }; export default React.memo(AIResponseBox); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx index d1c19cb5d4ec..0dc427592d75 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx @@ -217,8 +217,8 @@ function ExportPopover({ return ( { if (!newKey) { setUpdateTrigger((prev) => !prev); - toast({ - status: 'warning', - title: t('common:core.module.http.Key cannot be empty') - }); - return prevList; - } - const checkExist = prevList.find((item, i) => i !== index && item.key == newKey); - if (checkExist) { + // toast({ + // status: 'warning', + // title: t('common:core.module.http.Key cannot be empty') + // }); + } else if (prevList.find((item, i) => i !== index && item.key == newKey)) { setUpdateTrigger((prev) => !prev); toast({ status: 'warning', title: t('common:core.module.http.Key already exists') }); - return prevList; } return prevList.map((item, i) => (i === index ? { ...item, key: newKey } : item)); }); @@ -470,14 +466,15 @@ const RenderForm = ({ [t, toast] ); + // Add new params/headers key const handleAddNewProps = useCallback( - (key: string, value: string = '') => { + (value: string) => { setList((prevList) => { - if (!key) { + if (!value) { return prevList; } - const checkExist = prevList.find((item) => item.key === key); + const checkExist = prevList.find((item) => item.key === value); if (checkExist) { setUpdateTrigger((prev) => !prev); toast({ @@ -486,7 +483,7 @@ const RenderForm = ({ }); return prevList; } - return [...prevList, { key, type: 'string', value }]; + return [...prevList, { key: value, type: 'string', value: '' }]; }); setShouldUpdateNode(true); @@ -520,17 +517,15 @@ const RenderForm = ({ { handleKeyChange(index, val); - if (index === list.length) { + + // Last item blur, add the next item. + if (index === list.length && val) { handleAddNewProps(val); setUpdateTrigger((prev) => !prev); } @@ -541,11 +536,7 @@ const RenderForm = ({ { startSts(() => { onChangeNode({ diff --git a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx index 718c2906d942..6821cffb421a 100644 --- a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx +++ b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx @@ -204,7 +204,6 @@ const InputDataModal = ({ a: '', indexes: [] }); - console.log('执行onSuccess'); onSuccess(e); }, errorToast: t('common:common.error.unKnow') diff --git a/projects/app/src/web/common/hooks/useCopyData.tsx b/projects/app/src/web/common/hooks/useCopyData.tsx index de112a519050..a9724eb44bfa 100644 --- a/projects/app/src/web/common/hooks/useCopyData.tsx +++ b/projects/app/src/web/common/hooks/useCopyData.tsx @@ -17,6 +17,8 @@ export const useCopyData = () => { title: string | null = t('common:common.Copy Successful'), duration = 1000 ) => { + data = data.trim(); + try { if ((hasHttps() || !isProduction) && navigator.clipboard) { await navigator.clipboard.writeText(data);