feat: 添加 NodeDebugResponse 组件以增强调试功能
This commit is contained in:
@@ -23,22 +23,12 @@ import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/cons
|
|||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useWorkflowUtils } from '../../hooks/useUtils';
|
import { useWorkflowUtils } from '../../hooks/useUtils';
|
||||||
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
|
|
||||||
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
|
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
|
||||||
import { WorkflowEventContext } from '../../../context/workflowEventContext';
|
import { WorkflowEventContext } from '../../../context/workflowEventContext';
|
||||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||||
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
||||||
import {
|
import NodeDebugResponse from './RenderDebug/NodeDebugResponse';
|
||||||
RenderUserSelectInteractive,
|
|
||||||
RenderUserFormInteractive
|
|
||||||
} from '@/components/core/chat/components/InteractiveComponents';
|
|
||||||
import {
|
|
||||||
InteractiveBasicType,
|
|
||||||
UserInputInteractive,
|
|
||||||
UserSelectInteractive,
|
|
||||||
WorkflowInteractiveResponseType
|
|
||||||
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
|
||||||
|
|
||||||
type Props = FlowNodeItemType & {
|
type Props = FlowNodeItemType & {
|
||||||
children?: React.ReactNode | React.ReactNode[] | string;
|
children?: React.ReactNode | React.ReactNode[] | string;
|
||||||
@@ -672,231 +662,3 @@ const NodeIntro = React.memo(function NodeIntro({
|
|||||||
|
|
||||||
return Render;
|
return Render;
|
||||||
});
|
});
|
||||||
|
|
||||||
const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
|
||||||
nodeId,
|
|
||||||
debugResult
|
|
||||||
}: {
|
|
||||||
nodeId: string;
|
|
||||||
debugResult: FlowNodeItemType['debugResult'];
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
|
||||||
|
|
||||||
// 获取当前节点
|
|
||||||
const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]);
|
|
||||||
const firstInteractive = useMemo(() => {
|
|
||||||
if (
|
|
||||||
node &&
|
|
||||||
node.flowNodeType === FlowNodeTypeEnum.userSelect &&
|
|
||||||
!node.debugResult?.response?.userSelectResult
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
node &&
|
|
||||||
node.flowNodeType === FlowNodeTypeEnum.formInput &&
|
|
||||||
!node.debugResult?.response?.formInputResult
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false; // 明确返回值
|
|
||||||
}, [node]);
|
|
||||||
|
|
||||||
const { onChangeNode, onStopNodeDebug, onNextNodeDebug, workflowDebugData } = useContextSelector(
|
|
||||||
WorkflowContext,
|
|
||||||
(v) => v
|
|
||||||
);
|
|
||||||
|
|
||||||
const interactive: UserSelectInteractive | UserInputInteractive | undefined = useMemo(() => {
|
|
||||||
const description = node?.inputs?.find((input) => input.key === 'description')?.value;
|
|
||||||
const userSelectOptions = node?.inputs?.find(
|
|
||||||
(input) => input.key === 'userSelectOptions'
|
|
||||||
)?.value;
|
|
||||||
const formInputForms = node?.inputs?.find((input) => input.key === 'userInputForms')?.value;
|
|
||||||
if (node?.flowNodeType === FlowNodeTypeEnum.userSelect) {
|
|
||||||
return {
|
|
||||||
type: 'userSelect',
|
|
||||||
params: {
|
|
||||||
description,
|
|
||||||
userSelectOptions
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (node?.flowNodeType === FlowNodeTypeEnum.formInput) {
|
|
||||||
return {
|
|
||||||
type: 'userInput',
|
|
||||||
params: {
|
|
||||||
description,
|
|
||||||
inputForm: formInputForms
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}, [node]);
|
|
||||||
|
|
||||||
const { openConfirm, ConfirmModal } = useConfirm({
|
|
||||||
content: t('common:core.workflow.Confirm stop debug')
|
|
||||||
});
|
|
||||||
|
|
||||||
const RenderStatus = useMemo(() => {
|
|
||||||
const map = {
|
|
||||||
running: {
|
|
||||||
bg: 'primary.50',
|
|
||||||
text: t('common:core.workflow.Running'),
|
|
||||||
icon: 'core/workflow/running'
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
bg: 'green.50',
|
|
||||||
text: t('common:core.workflow.Success'),
|
|
||||||
icon: 'core/workflow/runSuccess'
|
|
||||||
},
|
|
||||||
failed: {
|
|
||||||
bg: 'red.50',
|
|
||||||
text: t('common:core.workflow.Failed'),
|
|
||||||
icon: 'core/workflow/runError'
|
|
||||||
},
|
|
||||||
skipped: {
|
|
||||||
bg: 'myGray.50',
|
|
||||||
text: t('common:core.workflow.Skipped'),
|
|
||||||
icon: 'core/workflow/runSkip'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusData = map[debugResult?.status || 'running'];
|
|
||||||
|
|
||||||
const response = debugResult?.response;
|
|
||||||
|
|
||||||
const onStop = () => {
|
|
||||||
openConfirm(onStopNodeDebug)();
|
|
||||||
};
|
|
||||||
|
|
||||||
return !!debugResult && !!statusData ? (
|
|
||||||
<>
|
|
||||||
<Flex px={3} bg={statusData.bg} borderTopRadius={'md'} py={3}>
|
|
||||||
<MyIcon name={statusData.icon as any} w={'16px'} mr={2} />
|
|
||||||
<Box color={'myGray.900'} fontWeight={'bold'} flex={'1 0 0'}>
|
|
||||||
{statusData.text}
|
|
||||||
</Box>
|
|
||||||
{debugResult.status !== 'running' && (
|
|
||||||
<Box
|
|
||||||
color={'primary.700'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
fontSize={'sm'}
|
|
||||||
onClick={() =>
|
|
||||||
onChangeNode({
|
|
||||||
nodeId,
|
|
||||||
type: 'attr',
|
|
||||||
key: 'debugResult',
|
|
||||||
value: {
|
|
||||||
...debugResult,
|
|
||||||
showResult: !debugResult.showResult
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{debugResult.showResult
|
|
||||||
? t('common:core.workflow.debug.Hide result')
|
|
||||||
: t('common:core.workflow.debug.Show result')}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
{/* Result card */}
|
|
||||||
{debugResult.showResult && (
|
|
||||||
<Card
|
|
||||||
className="nowheel"
|
|
||||||
position={'absolute'}
|
|
||||||
right={'-430px'}
|
|
||||||
top={0}
|
|
||||||
zIndex={10}
|
|
||||||
w={'420px'}
|
|
||||||
maxH={'max(100%,500px)'}
|
|
||||||
border={'base'}
|
|
||||||
>
|
|
||||||
{/* Status header */}
|
|
||||||
<Flex h={'54x'} px={3} py={3} alignItems={'center'}>
|
|
||||||
<MyIcon mr={1} name={'core/workflow/debugResult'} w={'20px'} color={'primary.600'} />
|
|
||||||
<Box fontWeight={'bold'} flex={'1'}>
|
|
||||||
{t('common:core.workflow.debug.Run result')}
|
|
||||||
</Box>
|
|
||||||
{workflowDebugData?.nextRunNodes.length !== 0 && (
|
|
||||||
<Button
|
|
||||||
size={'sm'}
|
|
||||||
leftIcon={<MyIcon name={'core/chat/stopSpeech'} w={'16px'} />}
|
|
||||||
variant={'whiteDanger'}
|
|
||||||
onClick={onStop}
|
|
||||||
>
|
|
||||||
{t('common:core.workflow.Stop debug')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{(debugResult.status === 'success' || debugResult.status === 'skipped') &&
|
|
||||||
!firstInteractive &&
|
|
||||||
!debugResult.isExpired &&
|
|
||||||
workflowDebugData?.nextRunNodes &&
|
|
||||||
workflowDebugData.nextRunNodes.length > 0 && (
|
|
||||||
<Button
|
|
||||||
ml={2}
|
|
||||||
size={'sm'}
|
|
||||||
leftIcon={<MyIcon name={'core/workflow/debugNext'} w={'16px'} />}
|
|
||||||
variant={'primary'}
|
|
||||||
onClick={() => onNextNodeDebug()}
|
|
||||||
>
|
|
||||||
{t('common:common.Next Step')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!firstInteractive &&
|
|
||||||
workflowDebugData?.nextRunNodes &&
|
|
||||||
workflowDebugData?.nextRunNodes.length === 0 && (
|
|
||||||
<Button ml={2} size={'sm'} variant={'primary'} onClick={onStopNodeDebug}>
|
|
||||||
{t('common:core.workflow.debug.Done')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
{/* Response list */}
|
|
||||||
{debugResult.status !== 'skipped' && (
|
|
||||||
<Box borderTop={'base'} mt={1} overflowY={'auto'} minH={'250px'}>
|
|
||||||
{!debugResult.message && !response && !firstInteractive && (
|
|
||||||
<EmptyTip text={t('common:core.workflow.debug.Not result')} pt={2} pb={5} />
|
|
||||||
)}
|
|
||||||
{debugResult.message && (
|
|
||||||
<Box color={'red.600'} px={3} py={4}>
|
|
||||||
{debugResult.message}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{firstInteractive && interactive && (
|
|
||||||
<>
|
|
||||||
{interactive.type === 'userSelect' && (
|
|
||||||
<RenderUserSelectInteractive interactive={interactive} nodeId={nodeId} />
|
|
||||||
)}
|
|
||||||
{interactive.type === 'userInput' && (
|
|
||||||
<RenderUserFormInteractive interactive={interactive} nodeId={nodeId} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{response && <WholeResponseContent activeModule={response} />}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : null;
|
|
||||||
}, [
|
|
||||||
interactive,
|
|
||||||
firstInteractive,
|
|
||||||
debugResult,
|
|
||||||
nodeId,
|
|
||||||
onChangeNode,
|
|
||||||
onNextNodeDebug,
|
|
||||||
onStopNodeDebug,
|
|
||||||
openConfirm,
|
|
||||||
t,
|
|
||||||
workflowDebugData
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{RenderStatus}
|
|
||||||
<ConfirmModal />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Box, Button, Card, Flex } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { WorkflowContext } from '../../../../context';
|
||||||
|
import { WorkflowEventContext } from '../../../../context/workflowEventContext';
|
||||||
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
|
||||||
|
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||||
|
import {
|
||||||
|
RenderUserSelectInteractive,
|
||||||
|
RenderUserFormInteractive
|
||||||
|
} from '@/components/core/chat/components/InteractiveComponents';
|
||||||
|
import {
|
||||||
|
UserInputInteractive,
|
||||||
|
UserSelectInteractive
|
||||||
|
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
|
|
||||||
|
interface NodeDebugResponseProps {
|
||||||
|
nodeId: string;
|
||||||
|
debugResult: FlowNodeItemType['debugResult'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
|
|
||||||
|
const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]);
|
||||||
|
const firstInteractive = useMemo(() => {
|
||||||
|
if (
|
||||||
|
node &&
|
||||||
|
node.flowNodeType === FlowNodeTypeEnum.userSelect &&
|
||||||
|
!node.debugResult?.response?.userSelectResult
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
node &&
|
||||||
|
node.flowNodeType === FlowNodeTypeEnum.formInput &&
|
||||||
|
!node.debugResult?.response?.formInputResult
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [node]);
|
||||||
|
|
||||||
|
const { onChangeNode, onStopNodeDebug, onNextNodeDebug, workflowDebugData } = useContextSelector(
|
||||||
|
WorkflowContext,
|
||||||
|
(v) => v
|
||||||
|
);
|
||||||
|
|
||||||
|
const interactive: UserSelectInteractive | UserInputInteractive | undefined = useMemo(() => {
|
||||||
|
const description = node?.inputs?.find((input) => input.key === 'description')?.value;
|
||||||
|
const userSelectOptions = node?.inputs?.find(
|
||||||
|
(input) => input.key === 'userSelectOptions'
|
||||||
|
)?.value;
|
||||||
|
const formInputForms = node?.inputs?.find((input) => input.key === 'userInputForms')?.value;
|
||||||
|
if (node?.flowNodeType === FlowNodeTypeEnum.userSelect) {
|
||||||
|
return {
|
||||||
|
type: 'userSelect',
|
||||||
|
params: {
|
||||||
|
description,
|
||||||
|
userSelectOptions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (node?.flowNodeType === FlowNodeTypeEnum.formInput) {
|
||||||
|
return {
|
||||||
|
type: 'userInput',
|
||||||
|
params: {
|
||||||
|
description,
|
||||||
|
inputForm: formInputForms
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [node]);
|
||||||
|
|
||||||
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
|
content: t('common:core.workflow.Confirm stop debug')
|
||||||
|
});
|
||||||
|
|
||||||
|
const RenderStatus = useMemo(() => {
|
||||||
|
const map = {
|
||||||
|
running: {
|
||||||
|
bg: 'primary.50',
|
||||||
|
text: t('common:core.workflow.Running'),
|
||||||
|
icon: 'core/workflow/running'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
bg: 'green.50',
|
||||||
|
text: t('common:core.workflow.Success'),
|
||||||
|
icon: 'core/workflow/runSuccess'
|
||||||
|
},
|
||||||
|
failed: {
|
||||||
|
bg: 'red.50',
|
||||||
|
text: t('common:core.workflow.Failed'),
|
||||||
|
icon: 'core/workflow/runError'
|
||||||
|
},
|
||||||
|
skipped: {
|
||||||
|
bg: 'myGray.50',
|
||||||
|
text: t('common:core.workflow.Skipped'),
|
||||||
|
icon: 'core/workflow/runSkip'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusData = map[debugResult?.status || 'running'];
|
||||||
|
|
||||||
|
const response = debugResult?.response;
|
||||||
|
|
||||||
|
const onStop = () => {
|
||||||
|
openConfirm(onStopNodeDebug)();
|
||||||
|
};
|
||||||
|
|
||||||
|
return !!debugResult && !!statusData ? (
|
||||||
|
<>
|
||||||
|
<Flex px={3} bg={statusData.bg} borderTopRadius={'md'} py={3}>
|
||||||
|
<MyIcon name={statusData.icon as any} w={'16px'} mr={2} />
|
||||||
|
<Box color={'myGray.900'} fontWeight={'bold'} flex={'1 0 0'}>
|
||||||
|
{statusData.text}
|
||||||
|
</Box>
|
||||||
|
{debugResult.status !== 'running' && (
|
||||||
|
<Box
|
||||||
|
color={'primary.700'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
fontSize={'sm'}
|
||||||
|
onClick={() =>
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'attr',
|
||||||
|
key: 'debugResult',
|
||||||
|
value: {
|
||||||
|
...debugResult,
|
||||||
|
showResult: !debugResult.showResult
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{debugResult.showResult
|
||||||
|
? t('common:core.workflow.debug.Hide result')
|
||||||
|
: t('common:core.workflow.debug.Show result')}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
{/* Result card */}
|
||||||
|
{debugResult.showResult && (
|
||||||
|
<Card
|
||||||
|
className="nowheel"
|
||||||
|
position={'absolute'}
|
||||||
|
right={'-430px'}
|
||||||
|
top={0}
|
||||||
|
zIndex={10}
|
||||||
|
w={'420px'}
|
||||||
|
maxH={'max(100%,500px)'}
|
||||||
|
border={'base'}
|
||||||
|
>
|
||||||
|
{/* Status header */}
|
||||||
|
<Flex h={'54x'} px={3} py={3} alignItems={'center'}>
|
||||||
|
<MyIcon mr={1} name={'core/workflow/debugResult'} w={'20px'} color={'primary.600'} />
|
||||||
|
<Box fontWeight={'bold'} flex={'1'}>
|
||||||
|
{t('common:core.workflow.debug.Run result')}
|
||||||
|
</Box>
|
||||||
|
{workflowDebugData?.nextRunNodes.length !== 0 && (
|
||||||
|
<Button
|
||||||
|
size={'sm'}
|
||||||
|
leftIcon={<MyIcon name={'core/chat/stopSpeech'} w={'16px'} />}
|
||||||
|
variant={'whiteDanger'}
|
||||||
|
onClick={onStop}
|
||||||
|
>
|
||||||
|
{t('common:core.workflow.Stop debug')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{(debugResult.status === 'success' || debugResult.status === 'skipped') &&
|
||||||
|
!firstInteractive &&
|
||||||
|
!debugResult.isExpired &&
|
||||||
|
workflowDebugData?.nextRunNodes &&
|
||||||
|
workflowDebugData.nextRunNodes.length > 0 && (
|
||||||
|
<Button
|
||||||
|
ml={2}
|
||||||
|
size={'sm'}
|
||||||
|
leftIcon={<MyIcon name={'core/workflow/debugNext'} w={'16px'} />}
|
||||||
|
variant={'primary'}
|
||||||
|
onClick={() => onNextNodeDebug()}
|
||||||
|
>
|
||||||
|
{t('common:common.Next Step')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!firstInteractive &&
|
||||||
|
workflowDebugData?.nextRunNodes &&
|
||||||
|
workflowDebugData?.nextRunNodes.length === 0 && (
|
||||||
|
<Button ml={2} size={'sm'} variant={'primary'} onClick={onStopNodeDebug}>
|
||||||
|
{t('common:core.workflow.debug.Done')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
{/* Response list */}
|
||||||
|
{debugResult.status !== 'skipped' && (
|
||||||
|
<Box borderTop={'base'} mt={1} overflowY={'auto'} minH={'250px'}>
|
||||||
|
{!debugResult.message && !response && !firstInteractive && (
|
||||||
|
<EmptyTip text={t('common:core.workflow.debug.Not result')} pt={2} pb={5} />
|
||||||
|
)}
|
||||||
|
{debugResult.message && (
|
||||||
|
<Box color={'red.600'} px={3} py={4}>
|
||||||
|
{debugResult.message}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{firstInteractive && interactive && (
|
||||||
|
<>
|
||||||
|
{interactive.type === 'userSelect' && (
|
||||||
|
<RenderUserSelectInteractive interactive={interactive} nodeId={nodeId} />
|
||||||
|
)}
|
||||||
|
{interactive.type === 'userInput' && (
|
||||||
|
<RenderUserFormInteractive interactive={interactive} nodeId={nodeId} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{response && <WholeResponseContent activeModule={response} />}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : null;
|
||||||
|
}, [
|
||||||
|
interactive,
|
||||||
|
firstInteractive,
|
||||||
|
debugResult,
|
||||||
|
nodeId,
|
||||||
|
onChangeNode,
|
||||||
|
onNextNodeDebug,
|
||||||
|
onStopNodeDebug,
|
||||||
|
openConfirm,
|
||||||
|
t,
|
||||||
|
workflowDebugData
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{RenderStatus}
|
||||||
|
<ConfirmModal />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(NodeDebugResponse);
|
||||||
Reference in New Issue
Block a user