4.8.10 test (#2618)

* perf: menu arrow ui

* perf: http node placeholder

* perf: http node form input

* perf: chatBox performance
This commit is contained in:
Archer
2024-09-05 11:49:13 +08:00
committed by GitHub
parent 3bcc3430fb
commit 3671e55001
9 changed files with 607 additions and 502 deletions

View File

@@ -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');

View File

@@ -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,

View File

@@ -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,17 +49,71 @@ 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 = ({
const RenderQuestionGuide = ({ questionGuides }: { questionGuides: string[] }) => {
return (
<Markdown
source={`\`\`\`${CodeClassNameEnum.questionGuide}
${JSON.stringify(questionGuides)}`}
/>
);
};
const HumanContentCard = React.memo(
function HumanContentCard({ chatValue }: { chatValue: ChatItemValueItemType[] }) {
const { text, files = [] } = formatChatValue2InputType(chatValue);
return (
<Flex flexDirection={'column'} gap={4}>
{files.length > 0 && <FilesBlock files={files} />}
{text && <Markdown source={text} />}
</Flex>
);
},
(prevProps, nextProps) => isEqual(prevProps.chatValue, nextProps.chatValue)
);
const AIContentCard = React.memo(function AIContentCard({
chatValue,
dataId,
isLastChild,
isChatting,
onSendMessage,
questionGuides
}: {
dataId: string;
chatValue: ChatItemValueItemType[];
isLastChild: boolean;
isChatting: boolean;
onSendMessage: SendPromptFnType;
questionGuides: string[];
}) {
return (
<Flex flexDirection={'column'} gap={2}>
{chatValue.map((value, i) => {
const key = `${dataId}-ai-${i}`;
return (
<AIResponseBox
key={key}
value={value}
isLastChild={isLastChild && i === chatValue.length - 1}
isChatting={isChatting}
onSendMessage={onSendMessage}
/>
);
})}
{isLastChild && questionGuides.length > 0 && (
<RenderQuestionGuide questionGuides={questionGuides} />
)}
</Flex>
);
});
const ChatItem = (props: Props) => {
const {
type,
avatar,
statusBoxData,
@@ -60,8 +121,9 @@ const ChatItem = ({
isLastChild,
questionGuides = [],
onSendMessage,
...chatControllerProps
}: Props) => {
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 (
<Flex flexDirection={'column'} gap={4}>
{files.length > 0 && <FilesBlock files={files} />}
{text && <Markdown source={text} />}
</Flex>
);
}
/* AI */
return (
<Flex flexDirection={'column'} key={chat.dataId} gap={2}>
{chat.value.map((value, i) => {
const key = `${chat.dataId}-ai-${i}`;
return (
<AIResponseBox
key={key}
value={value}
index={i}
chat={chat}
isLastChild={isLastChild}
isChatting={isChatting}
questionGuides={questionGuides}
onSendMessage={onSendMessage}
/>
);
})}
</Flex>
);
}, [chat, isChatting, isLastChild, onSendMessage, questionGuides, type]);
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 */}
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
<Box order={styleMap.order} ml={styleMap.ml}>
<ChatController {...chatControllerProps} isLastChild={isLastChild} />
<ChatController {...props} isLastChild={isLastChild} />
</Box>
)}
<ChatAvatar src={avatar} type={type} />
{/* Workflow status */}
{!!chatStatusMap && statusBoxData && isLastChild && (
<Flex
alignItems={'center'}
@@ -158,7 +245,9 @@ const ChatItem = ({
)}
</Flex>
{/* content */}
{splitAiResponseResults.map((value, i) => (
<Box
key={i}
mt={['6px', 2]}
className="chat-box-card"
textAlign={styleMap.textAlign}
@@ -174,10 +263,23 @@ const ChatItem = ({
borderRadius={styleMap.borderRadius}
textAlign={'left'}
>
{ContentCard}
{children}
{type === ChatRoleEnum.Human && <HumanContentCard chatValue={value} />}
{type === ChatRoleEnum.AI && (
<AIContentCard
chatValue={value}
dataId={chat.dataId}
isLastChild={isLastChild && i === splitAiResponseResults.length - 1}
isChatting={isChatting}
onSendMessage={onSendMessage}
questionGuides={questionGuides}
/>
)}
{/* Example: Response tags. A set of dialogs only needs to be displayed once*/}
{i === splitAiResponseResults.length - 1 && <>{children}</>}
{/* 对话框底部的复制按钮 */}
{type == ChatRoleEnum.AI && (!isChatting || (isChatting && !isLastChild)) && (
{type == ChatRoleEnum.AI &&
value[0]?.type !== 'interactive' &&
(!isChatting || (isChatting && !isLastChild)) && (
<Box
className="footer-copy"
display={['block', 'none']}
@@ -195,13 +297,14 @@ const ChatItem = ({
name={'copy'}
color={'myGray.500'}
_hover={{ color: 'primary.600' }}
onClick={() => copyData(chatText)}
onClick={() => copyData(formatChatValue2InputType(value).text ?? '')}
/>
</MyTooltip>
</Box>
)}
</Card>
</Box>
))}
</>
);
};

View File

@@ -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,8 +574,7 @@ const ChatBox = (
);
// retry input
const retryInput = useCallback(
(dataId?: string) => {
const retryInput = useMemoizedFn((dataId?: string) => {
if (!dataId || !onDelMessage) return;
return async () => {
@@ -604,12 +603,9 @@ const ChatBox = (
}
setLoading(false);
};
},
[chatHistories, onDelMessage, sendPrompt, setChatHistories, setLoading, toast]
);
});
// delete one message(One human and the ai response)
const delOneMessage = useCallback(
(dataId?: string) => {
const delOneMessage = useMemoizedFn((dataId?: string) => {
if (!dataId || !onDelMessage) return;
return () => {
setChatHistories((state) => {
@@ -632,12 +628,9 @@ const ChatBox = (
});
});
};
},
[onDelMessage, setChatHistories]
);
});
// admin mark
const onMark = useCallback(
(chat: ChatSiteItemType, q = '') => {
const onMark = useMemoizedFn((chat: ChatSiteItemType, q = '') => {
if (!showMarkIcon || chat.obj !== ChatRoleEnum.AI) return;
return () => {
@@ -660,11 +653,8 @@ const ChatBox = (
});
}
};
},
[showMarkIcon]
);
const onAddUserLike = useCallback(
(chat: ChatSiteItemType) => {
});
const onAddUserLike = useMemoizedFn((chat: ChatSiteItemType) => {
if (
feedbackType !== FeedbackTypeEnum.user ||
chat.obj !== ChatRoleEnum.AI ||
@@ -698,19 +688,14 @@ const ChatBox = (
});
} catch (error) {}
};
},
[appId, chatId, feedbackType, outLinkUid, setChatHistories, shareId, teamId, teamToken]
);
const onCloseUserLike = useCallback(
(chat: ChatSiteItemType) => {
});
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
chatItem.dataId === chat.dataId ? { ...chatItem, userGoodFeedback: undefined } : chatItem
)
);
updateChatUserFeedback({
@@ -722,11 +707,8 @@ const ChatBox = (
userGoodFeedback: undefined
});
};
},
[appId, chatId, feedbackType, setChatHistories, teamId, teamToken]
);
const onAddUserDislike = useCallback(
(chat: ChatSiteItemType) => {
});
const onAddUserDislike = useMemoizedFn((chat: ChatSiteItemType) => {
if (
feedbackType !== FeedbackTypeEnum.user ||
chat.obj !== ChatRoleEnum.AI ||
@@ -739,9 +721,7 @@ const ChatBox = (
if (!chat.dataId || !chatId || !appId) return;
setChatHistories((state) =>
state.map((chatItem) =>
chatItem.dataId === chat.dataId
? { ...chatItem, userBadFeedback: undefined }
: chatItem
chatItem.dataId === chat.dataId ? { ...chatItem, userBadFeedback: undefined } : chatItem
)
);
try {
@@ -759,11 +739,8 @@ const ChatBox = (
} else {
return () => setFeedbackId(chat.dataId);
}
},
[appId, chatId, feedbackType, outLinkUid, setChatHistories, shareId, teamId, teamToken]
);
const onReadUserDislike = useCallback(
(chat: ChatSiteItemType) => {
});
const onReadUserDislike = useMemoizedFn((chat: ChatSiteItemType) => {
if (feedbackType !== FeedbackTypeEnum.admin || chat.obj !== ChatRoleEnum.AI) return;
return () => {
if (!chat.dataId) return;
@@ -772,11 +749,8 @@ const ChatBox = (
content: chat.userBadFeedback || ''
});
};
},
[feedbackType]
);
const onCloseCustomFeedback = useCallback(
(chat: ChatSiteItemType, i: number) => {
});
const onCloseCustomFeedback = useMemoizedFn((chat: ChatSiteItemType, i: number) => {
return (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked && appId && chatId && chat.dataId) {
closeCustomFeedback({
@@ -798,9 +772,7 @@ const ChatBox = (
);
}
};
},
[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 = (
});
}
}));
const RenderRecords = useMemo(() => {
return (
<Flex flexDirection={'column'} h={'100%'} position={'relative'}>
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
{/* chat box container */}
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
<Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
{showEmpty && <Empty />}
@@ -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 = (
</Box>
</Box>
</Box>
);
}, [
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 (
<Flex flexDirection={'column'} h={'100%'} position={'relative'}>
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
{/* chat box container */}
{RenderRecords}
{/* message input */}
{onStartChat && chatStarted && active && appId && !isInteractive && (
<ChatInput

View File

@@ -14,6 +14,7 @@ import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import {
AIChatItemValueItemType,
ChatSiteItemType,
ToolModuleResponseItemType,
UserChatItemValueItemType
} from '@fastgpt/global/core/chat/type';
import React, { useMemo } from 'react';
@@ -23,64 +24,41 @@ import { SendPromptFnType } from '../ChatContainer/ChatBox/type';
import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../ChatContainer/ChatBox/Provider';
import { setUserSelectResultToHistories } from '../ChatContainer/ChatBox/utils';
import { InteractiveNodeResponseItemType } from '@fastgpt/global/core/workflow/template/system/userSelect/type';
import { isEqual } from 'lodash';
type props = {
value: UserChatItemValueItemType | AIChatItemValueItemType;
index: number;
chat: ChatSiteItemType;
isLastChild: boolean;
isChatting: boolean;
questionGuides: string[];
onSendMessage?: SendPromptFnType;
onSendMessage: SendPromptFnType;
};
const AIResponseBox = ({
value,
index,
chat,
isLastChild,
isChatting,
questionGuides,
onSendMessage
}: props) => {
const chatHistories = useContextSelector(ChatBoxContext, (v) => v.chatHistories);
// Question guide
const RenderQuestionGuide = useMemo(() => {
if (
isLastChild &&
!isChatting &&
questionGuides.length > 0 &&
index === chat.value.length - 1
) {
return (
<Markdown
source={`\`\`\`${CodeClassNameEnum.questionGuide}
${JSON.stringify(questionGuides)}`}
/>
);
}
return null;
}, [chat.value.length, index, isChatting, isLastChild, questionGuides]);
const Render = useMemo(() => {
if (value.type === ChatItemValueTypeEnum.text && value.text) {
let source = (value.text?.content || '').trim();
const RenderText = React.memo(function RenderText({
showAnimation,
text
}: {
showAnimation: boolean;
text?: string;
}) {
let source = (text || '').trim();
// First empty line
if (!source && chat.value.length > 1) return null;
// if (!source && !isLastChild) return null;
return (
<Markdown
source={source}
showAnimation={isLastChild && isChatting && index === chat.value.length - 1}
/>
);
}
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
return <Markdown source={source} showAnimation={showAnimation} />;
});
const RenderTool = React.memo(
function RenderTool({
showAnimation,
tools
}: {
showAnimation: boolean;
tools: ToolModuleResponseItemType[];
}) {
return (
<Box>
{value.tools.map((tool) => {
{tools.map((tool) => {
const toolParams = (() => {
try {
return JSON.stringify(JSON.parse(tool.params), null, 2);
@@ -116,7 +94,7 @@ ${JSON.stringify(questionGuides)}`}
<Box mx={2} fontSize={'sm'} color={'myGray.900'}>
{tool.toolName}
</Box>
{isChatting && !tool.response && <MyIcon name={'common/loading'} w={'14px'} />}
{showAnimation && !tool.response && <MyIcon name={'common/loading'} w={'14px'} />}
<AccordionIcon color={'myGray.600'} ml={5} />
</AccordionButton>
<AccordionPanel
@@ -132,14 +110,14 @@ ${JSON.stringify(questionGuides)}`}
<Box mb={3}>
<Markdown
source={`~~~json#Input
${toolParams}`}
${toolParams}`}
/>
</Box>
)}
{toolResponse && (
<Markdown
source={`~~~json#Response
${toolResponse}`}
${toolResponse}`}
/>
)}
</AccordionPanel>
@@ -149,27 +127,34 @@ ${JSON.stringify(questionGuides)}`}
})}
</Box>
);
}
if (
value.type === ChatItemValueTypeEnum.interactive &&
value.interactive &&
value.interactive.type === 'userSelect'
) {
},
(prevProps, nextProps) => isEqual(prevProps, nextProps)
);
const RenderInteractive = React.memo(
function RenderInteractive({
isChatting,
interactive,
onSendMessage,
chatHistories
}: {
isChatting: boolean;
interactive: InteractiveNodeResponseItemType;
onSendMessage: SendPromptFnType;
chatHistories: ChatSiteItemType[];
}) {
return (
<>
{value.interactive?.params?.description && (
<Markdown source={value.interactive.params.description} />
)}
{interactive?.params?.description && <Markdown source={interactive.params.description} />}
<Flex flexDirection={'column'} gap={2} w={'250px'}>
{value.interactive.params.userSelectOptions?.map((option) => {
const selected = option.value === value.interactive?.params?.userSelectedVal;
{interactive.params.userSelectOptions?.map((option) => {
const selected = option.value === interactive?.params?.userSelectedVal;
return (
<Button
key={option.key}
variant={'whitePrimary'}
whiteSpace={'pre-wrap'}
isDisabled={value.interactive?.params?.userSelectedVal !== undefined}
isDisabled={interactive?.params?.userSelectedVal !== undefined}
{...(selected
? {
_disabled: {
@@ -192,20 +177,36 @@ ${JSON.stringify(questionGuides)}`}
);
})}
</Flex>
{/* Animation */}
{isLastChild && isChatting && index === chat.value.length - 1 && (
<Markdown source={''} showAnimation />
)}
</>
);
}
}, [chat.value.length, chatHistories, index, isChatting, isLastChild, onSendMessage, value]);
},
(
prevProps,
nextProps // isChatting 更新时候onSendMessage 和 chatHistories 肯定都更新了,这里不需要额外的刷新
) =>
prevProps.isChatting === nextProps.isChatting &&
isEqual(prevProps.interactive, nextProps.interactive)
);
const AIResponseBox = ({ value, isLastChild, isChatting, onSendMessage }: props) => {
const chatHistories = useContextSelector(ChatBoxContext, (v) => v.chatHistories);
if (value.type === ChatItemValueTypeEnum.text && value.text)
return <RenderText showAnimation={isChatting && isLastChild} text={value.text.content} />;
if (value.type === ChatItemValueTypeEnum.tool && value.tools)
return <RenderTool showAnimation={isChatting && isLastChild} tools={value.tools} />;
if (
value.type === ChatItemValueTypeEnum.interactive &&
value.interactive &&
value.interactive.type === 'userSelect'
)
return (
<>
{Render}
{RenderQuestionGuide}
</>
<RenderInteractive
isChatting={isChatting}
interactive={value.interactive}
onSendMessage={onSendMessage}
chatHistories={chatHistories}
/>
);
};

View File

@@ -217,8 +217,8 @@ function ExportPopover({
return (
<MyPopover
placement={'right-start'}
offset={[-5, 5]}
hasArrow={false}
offset={[0, 0]}
hasArrow
trigger={'hover'}
w={'8.6rem'}
Trigger={

View File

@@ -448,20 +448,16 @@ const RenderForm = ({
setList((prevList) => {
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 = ({
<Tr key={`${input.key}${index}`}>
<Td p={0} w={'50%'} borderRight={'1px solid'} borderColor={'myGray.200'}>
<HttpInput
placeholder={
index !== list.length
? t('common:core.module.http.Props name_and_tips')
: t('common:core.module.http.Add props_and_tips')
}
placeholder={t('common:textarea_variable_picker_tip')}
value={item.key}
variableLabels={variables}
variables={variables}
onBlur={(val) => {
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 = ({
<Td p={0} w={'50%'}>
<Box display={'flex'} alignItems={'center'}>
<HttpInput
placeholder={
index !== list.length
? t('common:core.module.http.Props value_and_tips')
: ''
}
placeholder={t('common:textarea_variable_picker_tip')}
value={item.value}
variables={variables}
variableLabels={variables}
@@ -694,6 +685,7 @@ const RenderBody = ({
{(typeInput?.value === ContentTypes.xml || typeInput?.value === ContentTypes.raw) && (
<PromptEditor
value={jsonBody.value}
placeholder={t('common:textarea_variable_picker_tip')}
onChange={(e) => {
startSts(() => {
onChangeNode({

View File

@@ -204,7 +204,6 @@ const InputDataModal = ({
a: '',
indexes: []
});
console.log('执行onSuccess');
onSuccess(e);
},
errorToast: t('common:common.error.unKnow')

View File

@@ -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);