perf: 运行详情弹窗优化 (#2192)

* perf: 运行详情弹窗优化

* style: 调整样式

* style: 弹窗样式优化&&应用切换圆角添加

* fix: 修复编译错误
This commit is contained in:
papapatrick
2024-07-29 18:36:13 +08:00
committed by GitHub
parent 5cecef5836
commit e27abe1f6b
6 changed files with 376 additions and 94 deletions

View File

@@ -13,7 +13,7 @@ const RenderResponseDetail = () => {
<>{'进行中'}</>
) : (
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
<ResponseBox response={responseData} showDetail={true} />
<ResponseBox useMobile={true} response={responseData} showDetail={true} />
</Box>
);
};

View File

@@ -1,10 +1,8 @@
import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image, BoxProps } from '@chakra-ui/react';
import { Box, Flex, BoxProps, useDisclosure } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import MyModal from '@fastgpt/web/components/common/MyModal';
import Markdown from '@/components/Markdown';
import { QuoteList } from '../ChatContainer/ChatBox/components/QuoteModal';
@@ -13,6 +11,17 @@ import { formatNumber } from '@fastgpt/global/common/math/tools';
import { useI18n } from '@/web/context/I18n';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import Avatar from '@fastgpt/web/components/common/Avatar';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyIcon from '@fastgpt/web/components/common/Icon';
type sideTabItemType = {
moduleLogo?: string;
moduleName: string;
runningTime?: number;
moduleType: string;
nodeId: string;
children: sideTabItemType[];
};
function RowRender({
children,
@@ -40,7 +49,6 @@ function Row({
value?: string | number | boolean | object;
rawDom?: React.ReactNode;
}) {
const theme = useTheme();
const val = value || rawDom;
const isObject = typeof value === 'object';
@@ -66,9 +74,9 @@ function Row({
label={label}
mb={isObject ? 0 : 1}
{...(isObject
? { transform: 'translateY(-3px)' }
? { py: 2, transform: 'translateY(-3px)' }
: value
? { px: 3, py: 2, border: theme.borders.base }
? { px: 3, py: 2, border: 'base' }
: {})}
>
<Markdown source={formatValue} />
@@ -93,7 +101,8 @@ const WholeResponseModal = ({
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
minW={['90vw', '600px']}
maxH={['90vh', '700px']}
minW={['90vw', '880px']}
iconSrc="/imgs/modal/wholeRecord.svg"
title={
<Flex alignItems={'center'}>
@@ -112,59 +121,138 @@ export default WholeResponseModal;
export const ResponseBox = React.memo(function ResponseBox({
response,
showDetail,
hideTabs = false
hideTabs = false,
useMobile = false
}: {
response: ChatHistoryItemResType[];
showDetail: boolean;
hideTabs?: boolean;
useMobile?: boolean;
}) {
const theme = useTheme();
const { t } = useTranslation();
const { isPc } = useSystem();
const flattedResponse = useMemo(() => flattenArray(response), [response]);
const [currentNodeId, setCurrentNodeId] = useState(
flattedResponse[0]?.nodeId ? flattedResponse[0].nodeId : ''
);
const activeModule = useMemo(
() => flattedResponse.find((item) => item.nodeId === currentNodeId) as ChatHistoryItemResType,
[currentNodeId, flattedResponse]
);
const sideResponse: sideTabItemType[] = useMemo(() => {
return pretreatmentResponse(response);
}, [response]);
const {
isOpen: isOpenMobileModal,
onOpen: onOpenMobileModal,
onClose: onCloseMobileModal
} = useDisclosure();
return (
<>
{isPc && !useMobile ? (
<Flex overflow={'hidden'}>
<Box flex={'2 0 0'} borderRight={'sm'} p={3}>
<Box overflow={'auto'} height={'100%'}>
<WholeResponseSideTab
response={sideResponse}
value={currentNodeId}
onChange={setCurrentNodeId}
/>
</Box>
</Box>
<Box flex={'5 0 0'} overflowY={'auto'} overflowX={'hidden'} height={'100%'}>
<WholeResponseContent
activeModule={activeModule}
hideTabs={hideTabs}
showDetail={showDetail}
/>
</Box>
</Flex>
) : (
<>
<Box position={'relative'}>
{!isOpenMobileModal && (
<Box height={'100%'}>
<WholeResponseSideTab
response={sideResponse}
value={currentNodeId}
onChange={(item: string) => {
setCurrentNodeId(item);
onOpenMobileModal();
}}
isMobile={true}
/>
</Box>
)}
{isOpenMobileModal && (
<Box h={'100%'} w={'100%'} zIndex={10} background={'white'}>
<Flex
align={'center'}
justifyContent={'center'}
px={2}
py={2}
borderBottom={'sm'}
position={'relative'}
>
<MyIcon
width={4}
height={4}
name="common/backLight"
onClick={(e) => {
e.stopPropagation();
onCloseMobileModal();
}}
position={'absolute'}
left={2}
top={'50%'}
transform={'translateY(-50%)'}
/>
<Avatar
src={
activeModule.moduleLogo ||
moduleTemplatesFlat.find(
(template) => activeModule.moduleType === template.flowNodeType
)?.avatar
}
w={'1.25rem'}
h={'1.25rem'}
borderRadius={'sm'}
/>
<Box ml={1.5} lineHeight={'1.25rem'} alignItems={'center'}>
{t(activeModule.moduleName as any)}
</Box>
</Flex>
<WholeResponseContent
activeModule={activeModule}
hideTabs={hideTabs}
showDetail={showDetail}
/>
</Box>
)}
</Box>
</>
)}
</>
);
});
const WholeResponseContent = ({
activeModule,
hideTabs,
showDetail
}: {
activeModule: ChatHistoryItemResType;
hideTabs?: boolean;
showDetail: boolean;
}) => {
const { t } = useTranslation();
const { workflowT } = useI18n();
const list = useMemo(
() =>
response.map((item, i) => ({
label: (
<Flex alignItems={'center'} justifyContent={'center'} px={2} py={1}>
<Avatar
src={
item.moduleLogo ||
moduleTemplatesFlat.find((template) => item.moduleType === template.flowNodeType)
?.avatar
}
alt={''}
w={'1.25rem'}
borderRadius={'sm'}
/>
<Box ml={1.5}> {t(item.moduleName as any)}</Box>
</Flex>
),
value: `${i}`
})),
[response, t]
);
const [currentTab, setCurrentTab] = useState(`0`);
const activeModule = useMemo(() => response[Number(currentTab)], [currentTab, response]);
return (
<Box
{...(hideTabs ? { overflow: 'auto' } : { display: 'flex', flexDirection: 'column' })}
h={'100%'}
>
{!hideTabs && (
<Box>
<LightRowTabs
w={'100%'}
list={list}
value={currentTab}
inlineStyles={{ pt: 0 }}
onChange={setCurrentTab}
/>
</Box>
)}
<>
{activeModule && (
<Box
py={2}
@@ -176,6 +264,7 @@ export const ResponseBox = React.memo(function ResponseBox({
overflow: 'auto'
})}
>
{/* common info */}
<>
<Row
label={t('common:core.chat.response.module name')}
@@ -208,7 +297,6 @@ export const ResponseBox = React.memo(function ResponseBox({
/>
<Row label={workflowT('response.Error')} value={activeModule?.error} />
</>
{/* ai chat */}
<>
<Row
@@ -223,7 +311,7 @@ export const ResponseBox = React.memo(function ResponseBox({
label={t('common:core.chat.response.module historyPreview')}
rawDom={
activeModule.historyPreview ? (
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
<Box px={3} py={2} border={'base'} borderRadius={'md'}>
{activeModule.historyPreview?.map((item, i) => (
<Box
key={i}
@@ -245,7 +333,6 @@ export const ResponseBox = React.memo(function ResponseBox({
}
/>
</>
{/* dataset search */}
<>
{activeModule?.searchMode && (
@@ -279,7 +366,6 @@ export const ResponseBox = React.memo(function ResponseBox({
/>
)}
</>
{/* classify question */}
<>
<Row
@@ -294,7 +380,6 @@ export const ResponseBox = React.memo(function ResponseBox({
})()}
/>
</>
{/* if-else */}
<>
<Row
@@ -302,7 +387,6 @@ export const ResponseBox = React.memo(function ResponseBox({
value={activeModule?.ifElseResult}
/>
</>
{/* extract */}
<>
<Row
@@ -314,7 +398,6 @@ export const ResponseBox = React.memo(function ResponseBox({
value={activeModule?.extractResult}
/>
</>
{/* http */}
<>
<Row label={'Headers'} value={activeModule?.headers} />
@@ -325,49 +408,236 @@ export const ResponseBox = React.memo(function ResponseBox({
value={activeModule?.httpResult}
/>
</>
{/* plugin */}
<>
<Row
label={t('common:core.chat.response.plugin output')}
value={activeModule?.pluginOutput}
/>
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('common:core.chat.response.Plugin response detail')}
rawDom={
<Box h={'60vh'}>
<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />
</Box>
}
/>
)}
</>
{/* text output */}
<Row
label={t('common:core.chat.response.text output')}
value={activeModule?.textOutput}
/>
{/* tool call */}
{activeModule?.toolDetail && activeModule?.toolDetail.length > 0 && (
<Row
label={t('common:core.chat.response.Tool call response detail')}
rawDom={
<Box h={'60vh'}>
<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />
</Box>
}
/>
)}
{/* code */}
<Row label={workflowT('response.Custom outputs')} value={activeModule?.customOutputs} />
<Row label={workflowT('response.Custom inputs')} value={activeModule?.customInputs} />
<Row label={workflowT('response.Code log')} value={activeModule?.codeLog} />
</Box>
)}
</Box>
</>
);
});
};
const WholeResponseSideTab = ({
response,
value,
onChange,
isMobile = false
}: {
response: sideTabItemType[];
value: string;
onChange: (index: string) => void;
isMobile?: boolean;
}) => {
return (
<>
{response.map((item) => (
<Box
key={item.nodeId}
bg={isMobile ? 'myGray.100' : ''}
m={isMobile ? 3 : 0}
borderRadius={'md'}
minW={'12rem'}
>
<SideTabItem value={value} onChange={onChange} sideBarItem={item} index={0} />
</Box>
))}
</>
);
};
const AccordionSideTabItem = ({
sideBarItem,
onChange,
value,
index
}: {
sideBarItem: sideTabItemType;
onChange: (nodeId: string) => void;
value: string;
index: number;
}) => {
const { isOpen: isShowAccordion, onToggle: onToggleShowAccordion } = useDisclosure({
defaultIsOpen: false
});
return (
<>
<Flex align={'center'} position={'relative'}>
<NormalSideTabItem
index={index}
value={value}
onChange={onChange}
sideBarItem={sideBarItem}
>
<MyIcon
h={'20px'}
w={'20px'}
name={isShowAccordion ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
onClick={(e) => {
e.stopPropagation();
onToggleShowAccordion();
}}
_hover={{ color: 'primary.600', cursor: 'pointer' }}
/>
</NormalSideTabItem>
</Flex>
{isShowAccordion && (
<Box position={'relative'}>
{sideBarItem.children.map((item) => (
<SideTabItem
value={value}
key={item.nodeId}
sideBarItem={item}
onChange={onChange}
index={index + 1}
/>
))}
</Box>
)}
</>
);
};
const NormalSideTabItem = ({
sideBarItem,
onChange,
value,
index,
children
}: {
sideBarItem: sideTabItemType;
onChange: (nodeId: string) => void;
value: string;
index: number;
children?: React.ReactNode;
}) => {
const { t } = useTranslation();
const leftIndex = index > 3 ? 3 : index;
return (
<Flex
alignItems={'center'}
onClick={() => {
onChange(sideBarItem.nodeId);
}}
background={value === sideBarItem.nodeId ? 'myGray.100' : ''}
_hover={{ background: 'myGray.100' }}
p={2}
width={'100%'}
cursor={'pointer'}
pl={leftIndex === 0 ? '0.5rem' : `${1.5 * leftIndex + 0.5}rem`}
borderRadius={'md'}
position={'relative'}
>
<Avatar
src={
sideBarItem.moduleLogo ||
moduleTemplatesFlat.find((template) => sideBarItem.moduleType === template.flowNodeType)
?.avatar
}
alt={''}
w={'1.5rem'}
h={'1.5rem'}
borderRadius={'sm'}
/>
<Box ml={2}>
<Box fontSize={'xs'} fontWeight={'bold'}>
{t(sideBarItem.moduleName as any)}
</Box>
<Box fontSize={'2xs'} color={'myGray.500'}>
{t(sideBarItem.runningTime as any) + 's'}
</Box>
</Box>
<Box
h={'20px'}
w={'20px'}
position={'absolute'}
right={2}
top={'50%'}
transform={'translateY(-50%)'}
>
{children}
</Box>
</Flex>
);
};
const SideTabItem = ({
sideBarItem,
onChange,
value,
index
}: {
sideBarItem: sideTabItemType;
onChange: (nodeId: string) => void;
value: string;
index: number;
}) => {
if (!sideBarItem) return null;
return sideBarItem.children.length !== 0 ? (
<>
<Box>
<AccordionSideTabItem
sideBarItem={sideBarItem}
onChange={onChange}
value={value}
index={index}
/>
</Box>
</>
) : (
<NormalSideTabItem index={index} value={value} onChange={onChange} sideBarItem={sideBarItem} />
);
};
function pretreatmentResponse(res: ChatHistoryItemResType[]): sideTabItemType[] {
return res.map((item) => {
let children: sideTabItemType[] = [];
if (!!(item?.toolDetail || item?.pluginDetail)) {
if (item?.toolDetail) children.push(...pretreatmentResponse(item?.toolDetail));
if (item?.pluginDetail) children.push(...pretreatmentResponse(item?.pluginDetail));
}
return {
moduleLogo: item.moduleLogo,
moduleName: item.moduleName,
runningTime: item.runningTime,
moduleType: item.moduleType,
nodeId: item.nodeId,
children
};
});
}
function flattenArray(arr: ChatHistoryItemResType[]) {
const result: ChatHistoryItemResType[] = [];
function helper(currentArray: ChatHistoryItemResType[]) {
currentArray.forEach((item) => {
if (item && typeof item === 'object') {
result.push(item);
if (Array.isArray(item.toolDetail)) {
helper(item.toolDetail);
}
if (Array.isArray(item.pluginDetail)) {
helper(item.pluginDetail);
}
}
});
}
helper(arr);
return result;
}

View File

@@ -573,8 +573,8 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
top={0}
zIndex={10}
w={'420px'}
maxH={'100%'}
minH={'300px'}
maxH={'100%'}
border={'base'}
>
{/* Status header */}
@@ -614,7 +614,7 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
)}
</Flex>
{/* Show result */}
<Box maxH={'calc(100%-54px)'} overflow={'auto'}>
<Box overflowY={'auto'}>
{!debugResult.message && !response && (
<EmptyTip text={t('common:core.workflow.debug.Not result')} pt={2} pb={5} />
)}
@@ -623,7 +623,9 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
{debugResult.message}
</Box>
)}
{response && <ResponseBox response={[response]} showDetail hideTabs />}
{response && (
<ResponseBox useMobile={true} response={[response]} showDetail hideTabs />
)}
</Box>
</Card>
)}

View File

@@ -174,7 +174,7 @@ const MobileDrawer = ({
onClick: () => onclickApp(item._id)
})}
>
<Avatar src={item.avatar} w={'24px'} />
<Avatar src={item.avatar} w={'24px'} borderRadius={'sm'} />
<Box ml={2} className={'textEllipsis'}>
{item.name}
</Box>
@@ -223,7 +223,7 @@ const MobileHeader = ({
)}
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
<Flex alignItems={'center'} onClick={toggleDrawer}>
<Avatar src={avatar} w={'1rem'} />
<Avatar borderRadius={'sm'} src={avatar} w={'1rem'} />
<Box ml={1} className="textEllipsis">
{name}
</Box>

View File

@@ -27,7 +27,7 @@ const CustomPluginRunBox = (props: PluginRunBoxProps) => {
<PluginRunBox {...props} tab={PluginRunBoxTabEnum.input} />
</Box>
<Stack px={3} py={4} h={'100%'} alignItems={'flex-start'} w={'100%'} overflow={'auto'}>
<Box display={'inline-block'} mb={5}>
<Box display={'inline-block'}>
<LightRowTabs<PluginRunBoxTabEnum>
list={[
{ label: t('common:common.Output'), value: PluginRunBoxTabEnum.output },
@@ -61,7 +61,7 @@ const CustomPluginRunBox = (props: PluginRunBoxProps) => {
py={0}
fontSize={'sm'}
/>
<Box mt={3} flex={'1 0 0'} w={'100%'}>
<Box flex={'1 0 0'} w={'100%'}>
<PluginRunBox {...props} />
</Box>
</Stack>