refactor: 使用类型导入简化代码结构,重构 AIResponseBox 组件

This commit is contained in:
sd0ric4
2025-03-27 14:37:27 +08:00
parent fcd87e1501
commit 8fd4f2d47f

View File

@@ -10,7 +10,7 @@ import {
HStack HStack
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { import type {
AIChatItemValueItemType, AIChatItemValueItemType,
ToolModuleResponseItemType, ToolModuleResponseItemType,
UserChatItemValueItemType UserChatItemValueItemType
@@ -18,7 +18,7 @@ import {
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar'; import Avatar from '@fastgpt/web/components/common/Avatar';
import { import type {
InteractiveBasicType, InteractiveBasicType,
UserInputInteractive, UserInputInteractive,
UserSelectInteractive UserSelectInteractive
@@ -28,14 +28,61 @@ import { useTranslation } from 'next-i18next';
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus'; import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
import { import {
SelectOptionsComponent, SelectOptionsComponent,
SelectOption, type SelectOptionType,
FormInputComponent, FormInputComponent,
FormItem type FormItemType
} from './Form/FormComponents'; } from './Form/FormComponents';
const onSendPrompt = (e: { text: string; isInteractivePrompt: boolean }) => const onSendPrompt = (e: { text: string; isInteractivePrompt: boolean }) =>
eventBus.emit(EventNameEnum.sendQuestion, e); eventBus.emit(EventNameEnum.sendQuestion, e);
const formatJsonString = (jsonString: string) => {
try {
return JSON.stringify(JSON.parse(jsonString), null, 2);
} catch (error) {
return jsonString;
}
};
const StyledAccordionItem = React.memo(function StyledAccordionItem({
children
}: {
children: React.ReactNode;
}) {
return (
<AccordionItem borderTop={'none'} borderBottom={'none'}>
{children}
</AccordionItem>
);
});
const StyledAccordionButton = React.memo(function StyledAccordionButton({
children,
py = 0
}: {
children: React.ReactNode;
py?: number | string;
}) {
return (
<AccordionButton
w={'auto'}
bg={'white'}
borderRadius={'md'}
borderWidth={'1px'}
borderColor={'myGray.200'}
boxShadow={'1'}
pl={3}
pr={2.5}
py={py}
_hover={{
bg: 'auto'
}}
>
{children}
</AccordionButton>
);
});
const RenderText = React.memo(function RenderText({ const RenderText = React.memo(function RenderText({
showAnimation, showAnimation,
text text
@@ -58,44 +105,20 @@ const RenderTool = React.memo(
return ( return (
<Box> <Box>
{tools.map((tool) => { {tools.map((tool) => {
const toolParams = (() => { const toolParams = formatJsonString(tool.params);
try { const toolResponse = formatJsonString(tool.response);
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 ( return (
<Accordion key={tool.id} allowToggle _notLast={{ mb: 2 }}> <Accordion key={tool.id} allowToggle _notLast={{ mb: 2 }}>
<AccordionItem borderTop={'none'} borderBottom={'none'}> <StyledAccordionItem>
<AccordionButton <StyledAccordionButton>
w={'auto'}
bg={'white'}
borderRadius={'md'}
borderWidth={'1px'}
borderColor={'myGray.200'}
boxShadow={'1'}
pl={3}
pr={2.5}
_hover={{
bg: 'auto'
}}
>
<Avatar src={tool.toolAvatar} w={'1.25rem'} h={'1.25rem'} borderRadius={'sm'} /> <Avatar src={tool.toolAvatar} w={'1.25rem'} h={'1.25rem'} borderRadius={'sm'} />
<Box mx={2} fontSize={'sm'} color={'myGray.900'}> <Box mx={2} fontSize={'sm'} color={'myGray.900'}>
{tool.toolName} {tool.toolName}
</Box> </Box>
{showAnimation && !tool.response && <MyIcon name={'common/loading'} w={'14px'} />} {showAnimation && !tool.response && <MyIcon name={'common/loading'} w={'14px'} />}
<AccordionIcon color={'myGray.600'} ml={5} /> <AccordionIcon color={'myGray.600'} ml={5} />
</AccordionButton> </StyledAccordionButton>
<AccordionPanel <AccordionPanel
py={0} py={0}
px={0} px={0}
@@ -120,7 +143,7 @@ ${toolResponse}`}
/> />
)} )}
</AccordionPanel> </AccordionPanel>
</AccordionItem> </StyledAccordionItem>
</Accordion> </Accordion>
); );
})} })}
@@ -144,21 +167,8 @@ const RenderResoningContent = React.memo(function RenderResoningContent({
return ( return (
<Accordion allowToggle defaultIndex={isLastResponseValue ? 0 : undefined}> <Accordion allowToggle defaultIndex={isLastResponseValue ? 0 : undefined}>
<AccordionItem borderTop={'none'} borderBottom={'none'}> <StyledAccordionItem>
<AccordionButton <StyledAccordionButton py={1}>
w={'auto'}
bg={'white'}
borderRadius={'md'}
borderWidth={'1px'}
borderColor={'myGray.200'}
boxShadow={'1'}
pl={3}
pr={2.5}
py={1}
_hover={{
bg: 'auto'
}}
>
<HStack mr={2} spacing={1}> <HStack mr={2} spacing={1}>
<MyIcon name={'core/chat/think'} w={'0.85rem'} /> <MyIcon name={'core/chat/think'} w={'0.85rem'} />
<Box fontSize={'sm'}>{t('chat:ai_reasoning')}</Box> <Box fontSize={'sm'}>{t('chat:ai_reasoning')}</Box>
@@ -166,7 +176,7 @@ const RenderResoningContent = React.memo(function RenderResoningContent({
{showAnimation && <MyIcon name={'common/loading'} w={'0.85rem'} />} {showAnimation && <MyIcon name={'common/loading'} w={'0.85rem'} />}
<AccordionIcon color={'myGray.600'} ml={5} /> <AccordionIcon color={'myGray.600'} ml={5} />
</AccordionButton> </StyledAccordionButton>
<AccordionPanel <AccordionPanel
py={0} py={0}
pr={0} pr={0}
@@ -178,7 +188,7 @@ const RenderResoningContent = React.memo(function RenderResoningContent({
> >
<Markdown source={content} showAnimation={showAnimation} /> <Markdown source={content} showAnimation={showAnimation} />
</AccordionPanel> </AccordionPanel>
</AccordionItem> </StyledAccordionItem>
</Accordion> </Accordion>
); );
}); });
@@ -190,7 +200,7 @@ const RenderUserSelectInteractive = React.memo(function RenderInteractive({
}) { }) {
return ( return (
<SelectOptionsComponent <SelectOptionsComponent
options={(interactive.params.userSelectOptions || []) as SelectOption[]} options={(interactive.params.userSelectOptions || []) as SelectOptionType[]}
description={interactive.params.description} description={interactive.params.description}
selectedValue={interactive.params.userSelectedVal} selectedValue={interactive.params.userSelectedVal}
onSelectOption={(value: string) => { onSelectOption={(value: string) => {
@@ -232,7 +242,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
return ( return (
<Flex flexDirection={'column'} gap={2} w={'250px'}> <Flex flexDirection={'column'} gap={2} w={'250px'}>
<FormInputComponent <FormInputComponent
inputForm={(interactive.params.inputForm || []) as FormItem[]} inputForm={(interactive.params.inputForm || []) as FormItemType[]}
description={interactive.params.description} description={interactive.params.description}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
isDisabled={interactive.params.submitted} isDisabled={interactive.params.submitted}
@@ -244,6 +254,44 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
); );
}); });
const getResponseRenderer = (
value: UserChatItemValueItemType | AIChatItemValueItemType,
isChatting: boolean,
isLastResponseValue: boolean
) => {
if (value.type === ChatItemValueTypeEnum.text && value.text) {
return (
<RenderText showAnimation={isChatting && isLastResponseValue} text={value.text.content} />
);
}
if (value.type === ChatItemValueTypeEnum.reasoning && value.reasoning) {
return (
<RenderResoningContent
isChatting={isChatting}
isLastResponseValue={isLastResponseValue}
content={value.reasoning.content}
/>
);
}
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
return <RenderTool showAnimation={isChatting} tools={value.tools} />;
}
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
if (value.interactive.type === 'userSelect') {
return <RenderUserSelectInteractive interactive={value.interactive} />;
}
if (value.interactive?.type === 'userInput') {
return <RenderUserFormInteractive interactive={value.interactive} />;
}
}
return null;
};
// AI响应框主组件
const AIResponseBox = React.memo(function AIResponseBox({ const AIResponseBox = React.memo(function AIResponseBox({
value, value,
isLastResponseValue, isLastResponseValue,
@@ -253,28 +301,7 @@ const AIResponseBox = React.memo(function AIResponseBox({
isLastResponseValue: boolean; isLastResponseValue: boolean;
isChatting: boolean; isChatting: boolean;
}) { }) {
if (value.type === ChatItemValueTypeEnum.text && value.text) return getResponseRenderer(value, isChatting, isLastResponseValue);
return (
<RenderText showAnimation={isChatting && isLastResponseValue} text={value.text.content} />
);
if (value.type === ChatItemValueTypeEnum.reasoning && value.reasoning)
return (
<RenderResoningContent
isChatting={isChatting}
isLastResponseValue={isLastResponseValue}
content={value.reasoning.content}
/>
);
if (value.type === ChatItemValueTypeEnum.tool && value.tools)
return <RenderTool showAnimation={isChatting} tools={value.tools} />;
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
if (value.interactive.type === 'userSelect')
return <RenderUserSelectInteractive interactive={value.interactive} />;
if (value.interactive?.type === 'userInput')
return <RenderUserFormInteractive interactive={value.interactive} />;
}
return null;
}); });
export default AIResponseBox; export default AIResponseBox;