Check debug (#4384)

* feat : Added support for interactive nodes in the debugging interface (#4339)

* feat: add VSCode launch configuration and enhance debug API handler

* feat: refactor debug API handler to streamline workflow processing and enhance interactive chat features

* feat: enhance debug API handler with structured input forms and improved query handling

* feat: enhance debug API handler to support optional query and histories parameters

* feat: simplify query and histories initialization in debug API handler

* feat: add realmode parameter to workflow dispatch and update interactive handling

* feat: add optional query parameter to PostWorkflowDebugProps and remove realmode from ModuleDispatchProps

* feat: add history parameter to PostWorkflowDebugProps and update related components

* feat: remove realmode

* feat: simplify handler parameter destructuring in debug.ts

* feat: remove unused interactive prop from WholeResponseContent component

* feat: refactor onNextNodeDebug to use parameter object for better readability

* feat: Merge selections and next actions to remove unused state management

* feat: 添加 NodeDebugResponse 组件以增强调试功能

* feat: Simplify the import statements in InteractiveComponents.tsx

* feat: Update the handler function to use default parameters to simplify the code

* feat: Add optional workflowInteractiveResponse field to PostWorkflowDebugResponse type

* feat: Add the workflowInteractiveResponse field in the debugging handler to enhance response capabilities

* feat: Added workflowInteractiveResponse field in FlowNodeItemType to enhance responsiveness

* feat: Refactor NodeDebugResponse to utilize workflowInteractiveResponse for improved interactivity

* feat: Extend UserSelectInteractive and UserInputInteractive types to inherit from InteractiveBasicType

* feat: Refactor NodeDebugResponse to streamline interactive handling and improve code clarity

* feat: 重构交互式调试逻辑,创建共用 Hook 以简化用户选择和输入处理

* fix: type error

* feat: 重构 AIResponseBox 组件,简化用户交互逻辑并引入共用表单组件

* feat: 清理 AIResponseBox 和表单组件代码,移除冗余注释和未使用的导入

* fix: type error

* feat: 重构 AIResponseBox 组件,简化类型定义并优化代码结构

* refactor: 将 FormItem 接口更改为类型定义,优化代码结构

* refactor: 将 NodeDebugResponseProps 接口更改为类型定义,优化代码结构

* refactor: 移除不必要的入口节点检查,简化调试处理逻辑

* feat: 移动调试交互组件位置

* refactor: 将 InteractiveBasicType 中的属性设为可选,简化数据结构

* refactor: 优化类型定义

* refactor: 移除未使用的 ChatItemType 和 UserChatItemValueItemType 导入

* refactor: 将接口定义更改为类型别名,简化代码结构

* refactor: 更新类型定义,使用类型别名简化代码结构

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

* refactor: 提取描述框和表单项标签组件,简化代码结构

* refactor: 移除多余的空行

* refactor: 移除多余的空行和注释

* refactor: 移除多余的空行,简化 AIResponseBox 组件代码

* refactor: 重构组件,移动 FormComponents 到 InteractiveComponents,简化代码结构

* refactor: 移除多余的空行,简化 NodeDebugResponse 组件代码

* refactor: 更新导入语句,使用 type 关键字优化类型导入

* refactor: 在 tsconfig.json 中启用 verbatimModuleSyntax 选项

* Revert "refactor: 在 tsconfig.json 中启用 verbatimModuleSyntax 选项"

This reverts commit 2b335a9938.

* revert: rendertool

* refactor: Remove unused imports and functions to simplify code

* perf: debug interactive

---------

Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com>
This commit is contained in:
Archer
2025-03-28 17:09:08 +08:00
committed by GitHub
parent 2d3ae7f944
commit 0ed99d8c9a
16 changed files with 792 additions and 503 deletions

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useMemo } from 'react';
import { Box, Button, Card, Flex, FlexProps } from '@chakra-ui/react';
import { Box, Button, Flex, type FlexProps } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
@@ -13,7 +13,6 @@ import { ToolSourceHandle, ToolTargetHandle } from './Handle/ToolHandle';
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
import { useDebug } from '../../hooks/useDebug';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { getPreviewPluginNode } from '@/web/core/app/api/plugin';
import { storeNode2FlowNode } from '@/web/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools';
@@ -23,12 +22,12 @@ import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/cons
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useWorkflowUtils } from '../../hooks/useUtils';
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
import { WorkflowEventContext } from '../../../context/workflowEventContext';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
import NodeDebugResponse from './RenderDebug/NodeDebugResponse';
type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
@@ -62,6 +61,7 @@ const NodeCard = (props: Props) => {
w = 'full',
h = 'full',
nodeId,
flowNodeType,
selected,
menuForbid,
isTool = false,
@@ -409,7 +409,7 @@ const NodeCard = (props: Props) => {
})}
{...customStyle}
>
<NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />
{debugResult && <NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />}
{Header}
<Flex flexDirection={'column'} flex={1} my={!isFolded ? 3 : 0} gap={2}>
{!isFolded ? children : <Box h={4} />}
@@ -661,168 +661,3 @@ const NodeIntro = React.memo(function NodeIntro({
return Render;
});
const NodeDebugResponse = React.memo(function NodeDebugResponse({
nodeId,
debugResult
}: {
nodeId: string;
debugResult: FlowNodeItemType['debugResult'];
}) {
const { t } = useTranslation();
const { onChangeNode, onStopNodeDebug, onNextNodeDebug, workflowDebugData } = useContextSelector(
WorkflowContext,
(v) => v
);
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') &&
!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>
)}
{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 && (
<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>
)}
{response && <WholeResponseContent activeModule={response} />}
</Box>
)}
</Card>
)}
</>
) : null;
}, [
debugResult,
nodeId,
onChangeNode,
onNextNodeDebug,
onStopNodeDebug,
openConfirm,
t,
workflowDebugData
]);
return (
<>
{RenderStatus}
<ConfirmModal />
</>
);
});

View File

@@ -0,0 +1,269 @@
import React, { useCallback, useMemo, useRef } 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 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 {
FormInputComponent,
SelectOptionsComponent
} from '@/components/core/chat/components/Interactive/InteractiveComponents';
import { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { initWorkflowEdgeStatus } from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
type NodeDebugResponseProps = {
nodeId: string;
debugResult: FlowNodeItemType['debugResult'];
};
const RenderUserFormInteractive = React.memo(function RenderFormInput({
interactive,
onNext
}: {
interactive: UserInputInteractive;
onNext: (val: string) => void;
}) {
const { t } = useTranslation();
const defaultValues = useMemo(() => {
return interactive.params.inputForm?.reduce((acc: Record<string, any>, item) => {
acc[item.label] = !!item.value ? item.value : item.defaultValue;
return acc;
}, {});
}, [interactive.params.inputForm]);
return (
<Box px={4} py={4} bg="white" borderRadius="md">
<FormInputComponent
defaultValues={defaultValues}
interactiveParams={interactive.params}
SubmitButton={({ onSubmit }) => (
<Button
leftIcon={<MyIcon name="core/workflow/debugNext" />}
onClick={() =>
onSubmit((data) => {
onNext(JSON.stringify(data));
})()
}
>
{t('common:common.Next Step')}
</Button>
)}
/>
</Box>
);
});
const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
const { t } = useTranslation();
const { onChangeNode, onStopNodeDebug, onNextNodeDebug, workflowDebugData } = useContextSelector(
WorkflowContext,
(v) => v
);
const statusMap = useRef({
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 = statusMap.current[debugResult?.status || 'running'];
const response = debugResult?.response;
const { openConfirm, ConfirmModal } = useConfirm({
content: t('common:core.workflow.Confirm stop debug')
});
const onStop = () => {
openConfirm(onStopNodeDebug)();
};
const interactive = debugResult?.workflowInteractiveResponse;
const onNextInteractive = useCallback(
(userContent: string) => {
if (!workflowDebugData || !workflowDebugData || !interactive) return;
const updatedQuery: UserChatItemValueItemType[] = [
{
type: ChatItemValueTypeEnum.text,
text: { content: userContent }
}
];
const mockHistory: ChatItemType[] = [
{
obj: ChatRoleEnum.AI,
value: [
{
type: ChatItemValueTypeEnum.interactive,
interactive: {
...interactive,
memoryEdges: interactive.memoryEdges || [],
entryNodeIds: interactive.entryNodeIds || [],
nodeOutputs: interactive.nodeOutputs || []
}
}
]
}
];
onNextNodeDebug({
...workflowDebugData,
// Rewrite runtimeEdges
runtimeEdges: initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, mockHistory),
query: updatedQuery,
history: mockHistory
});
},
[workflowDebugData, interactive, onNextNodeDebug]
);
return !!debugResult && !!statusData ? (
<>
{/* Status header */}
<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>
)}
{!interactive && (
<>
{(debugResult.status === 'success' || debugResult.status === 'skipped') &&
!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(workflowDebugData)}
>
{t('common:common.Next Step')}
</Button>
)}
{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 && !interactive && (
<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>
)}
{interactive && onNextInteractive && (
<>
{interactive.type === 'userSelect' && (
<Box px={4} py={3}>
<SelectOptionsComponent
interactiveParams={interactive.params}
onSelect={(val) => {
onNextInteractive(val);
}}
/>
</Box>
)}
{interactive.type === 'userInput' && (
<RenderUserFormInteractive
interactive={interactive}
onNext={onNextInteractive}
/>
)}
</>
)}
{response && <WholeResponseContent activeModule={response} />}
</Box>
)}
</Card>
)}
<ConfirmModal />
</>
) : null;
};
export default React.memo(NodeDebugResponse);

View File

@@ -35,6 +35,8 @@ import WorkflowInitContextProvider, { WorkflowNodeEdgeContext } from './workflow
import WorkflowEventContextProvider from './workflowEventContext';
import { getAppConfigByDiff } from '@/web/core/app/diff';
import WorkflowStatusContextProvider from './workflowStatusContext';
import { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
/*
Context
@@ -156,24 +158,22 @@ type WorkflowContextType = {
| undefined;
// debug
workflowDebugData:
| {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
nextRunNodes: RuntimeNodeItemType[];
}
| undefined;
onNextNodeDebug: () => Promise<void>;
workflowDebugData?: DebugDataType;
onNextNodeDebug: (params: DebugDataType) => Promise<void>;
onStartNodeDebug: ({
entryNodeId,
runtimeNodes,
runtimeEdges,
variables
variables,
query,
history
}: {
entryNodeId: string;
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
variables: Record<string, any>;
query?: UserChatItemValueItemType[];
history?: ChatItemType[];
}) => Promise<void>;
onStopNodeDebug: () => void;
@@ -189,11 +189,14 @@ type WorkflowContextType = {
>;
};
type DebugDataType = {
export type DebugDataType = {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
nextRunNodes: RuntimeNodeItemType[];
variables: Record<string, any>;
history?: ChatItemType[];
query?: UserChatItemValueItemType[];
workflowInteractiveResponse?: WorkflowInteractiveResponseType;
};
export const WorkflowContext = createContext<WorkflowContextType>({
@@ -236,17 +239,25 @@ export const WorkflowContext = createContext<WorkflowContextType>({
throw new Error('Function not implemented.');
},
workflowDebugData: undefined,
onNextNodeDebug: function (): Promise<void> {
onNextNodeDebug: function (params?: {
history?: ChatItemType[];
query?: UserChatItemValueItemType[];
debugData?: DebugDataType;
}): Promise<void> {
throw new Error('Function not implemented.');
},
onStartNodeDebug: function ({
entryNodeId,
runtimeNodes,
runtimeEdges
runtimeEdges,
query,
history
}: {
entryNodeId: string;
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
query?: UserChatItemValueItemType[];
history?: ChatItemType[];
}): Promise<void> {
throw new Error('Function not implemented.');
},
@@ -551,8 +562,7 @@ const WorkflowContextProvider = ({
/* debug */
const [workflowDebugData, setWorkflowDebugData] = useState<DebugDataType>();
const onNextNodeDebug = useCallback(
async (debugData = workflowDebugData) => {
if (!debugData) return;
async (debugData: DebugDataType) => {
// 1. Cancel node selected status and debugResult.showStatus
setNodes((state) =>
state.map((node) => ({
@@ -612,26 +622,35 @@ const WorkflowContextProvider = ({
try {
// 4. Run one step
const { finishedEdges, finishedNodes, nextStepRunNodes, flowResponses, newVariables } =
await postWorkflowDebug({
nodes: runtimeNodes,
edges: debugData.runtimeEdges,
variables: {
appId,
cTime: formatTime2YMDHMW(),
...debugData.variables
},
appId
});
const {
finishedEdges,
finishedNodes,
nextStepRunNodes,
flowResponses,
newVariables,
workflowInteractiveResponse
} = await postWorkflowDebug({
nodes: runtimeNodes,
edges: debugData.runtimeEdges,
variables: {
appId,
cTime: formatTime2YMDHMW(),
...debugData.variables
},
query: debugData.query, // 添加 query 参数
history: debugData.history,
appId
});
// 5. Store debug result
const newStoreDebugData = {
setWorkflowDebugData({
runtimeNodes: finishedNodes,
// edges need to save status
runtimeEdges: finishedEdges,
nextRunNodes: nextStepRunNodes,
variables: newVariables
};
setWorkflowDebugData(newStoreDebugData);
variables: newVariables,
workflowInteractiveResponse: workflowInteractiveResponse
});
// 6. selected entry node and Update entry node debug result
setNodes((state) =>
@@ -665,16 +684,21 @@ const WorkflowContextProvider = ({
status: 'success',
response: result,
showResult: true,
isExpired: false
isExpired: false,
workflowInteractiveResponse: workflowInteractiveResponse
}
}
};
})
);
// Check for an empty response
if (flowResponses.length === 0 && nextStepRunNodes.length > 0) {
onNextNodeDebug(newStoreDebugData);
// Check for an empty response(Skip node)
if (
!workflowInteractiveResponse &&
flowResponses.length === 0 &&
nextStepRunNodes.length > 0
) {
onNextNodeDebug(debugData);
}
} catch (error) {
entryNodes.forEach((node) => {
@@ -692,7 +716,7 @@ const WorkflowContextProvider = ({
console.log(error);
}
},
[appId, onChangeNode, setNodes, workflowDebugData]
[appId, onChangeNode, setNodes]
);
const onStopNodeDebug = useMemoizedFn(() => {
setWorkflowDebugData(undefined);
@@ -712,18 +736,24 @@ const WorkflowContextProvider = ({
entryNodeId,
runtimeNodes,
runtimeEdges,
variables
variables,
query,
history
}: {
entryNodeId: string;
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
variables: Record<string, any>;
query?: UserChatItemValueItemType[];
history?: ChatItemType[];
}) => {
const data = {
const data: DebugDataType = {
runtimeNodes,
runtimeEdges,
nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId),
variables
variables,
query,
history
};
onStopNodeDebug();
setWorkflowDebugData(data);