feat: add optional query parameter to PostWorkflowDebugProps and remove realmode from ModuleDispatchProps
This commit is contained in:
@@ -65,7 +65,6 @@ export type ModuleDispatchProps<T> = ChatDispatchProps & {
|
|||||||
runtimeNodes: RuntimeNodeItemType[];
|
runtimeNodes: RuntimeNodeItemType[];
|
||||||
runtimeEdges: RuntimeEdgeItemType[];
|
runtimeEdges: RuntimeEdgeItemType[];
|
||||||
params: T;
|
params: T;
|
||||||
realmode?: 'test' | 'chat' | 'debug';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SystemVariablesType = {
|
export type SystemVariablesType = {
|
||||||
|
|||||||
@@ -581,8 +581,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
params,
|
params,
|
||||||
mode: props.mode === 'debug' ? 'test' : props.mode,
|
mode: props.mode === 'debug' ? 'test' : props.mode
|
||||||
realmode: props.mode
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// run module
|
// run module
|
||||||
|
|||||||
@@ -32,11 +32,10 @@ export const dispatchFormInput = async (props: Props): Promise<FormInputResponse
|
|||||||
query
|
query
|
||||||
} = props;
|
} = props;
|
||||||
const { isEntry } = node;
|
const { isEntry } = node;
|
||||||
const mode = props.realmode;
|
|
||||||
const interactive = getLastInteractiveValue(histories);
|
const interactive = getLastInteractiveValue(histories);
|
||||||
|
|
||||||
// Interactive node is not the entry node, return interactive result
|
// Interactive node is not the entry node, return interactive result
|
||||||
if ((!isEntry || interactive?.type !== 'userInput') && mode !== 'debug') {
|
if (!isEntry || interactive?.type !== 'userInput') {
|
||||||
return {
|
return {
|
||||||
[DispatchNodeResponseKeyEnum.interactive]: {
|
[DispatchNodeResponseKeyEnum.interactive]: {
|
||||||
type: 'userInput',
|
type: 'userInput',
|
||||||
|
|||||||
@@ -30,11 +30,10 @@ export const dispatchUserSelect = async (props: Props): Promise<UserSelectRespon
|
|||||||
query
|
query
|
||||||
} = props;
|
} = props;
|
||||||
const { nodeId, isEntry } = node;
|
const { nodeId, isEntry } = node;
|
||||||
const mode = props.realmode;
|
|
||||||
const interactive = getLastInteractiveValue(histories);
|
const interactive = getLastInteractiveValue(histories);
|
||||||
|
|
||||||
// Interactive node is not the entry node, return interactive result
|
// Interactive node is not the entry node, return interactive result
|
||||||
if ((!isEntry || interactive?.type !== 'userSelect') && mode !== 'debug') {
|
if (!isEntry || interactive?.type !== 'userSelect') {
|
||||||
return {
|
return {
|
||||||
[DispatchNodeResponseKeyEnum.interactive]: {
|
[DispatchNodeResponseKeyEnum.interactive]: {
|
||||||
type: 'userSelect',
|
type: 'userSelect',
|
||||||
|
|||||||
@@ -0,0 +1,414 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Box, Button, Flex, Textarea, FormLabel as ChakraFormLabel } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import Markdown from '@/components/Markdown';
|
||||||
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
|
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||||
|
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||||
|
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||||
|
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
import {
|
||||||
|
InteractiveBasicType,
|
||||||
|
UserInputInteractive,
|
||||||
|
UserSelectInteractive
|
||||||
|
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context';
|
||||||
|
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
import { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
|
||||||
|
export const RenderUserSelectInteractive = React.memo(function RenderInteractive({
|
||||||
|
interactive,
|
||||||
|
nodeId
|
||||||
|
}: {
|
||||||
|
interactive: UserSelectInteractive;
|
||||||
|
nodeId?: string;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [selectedValue, setSelectedValue] = useState<string | undefined>(undefined);
|
||||||
|
const { onChangeNode, onNextNodeDebug, workflowDebugData, setWorkflowDebugData } =
|
||||||
|
useContextSelector(WorkflowContext, (v) => ({
|
||||||
|
onChangeNode: v.onChangeNode,
|
||||||
|
onNextNodeDebug: v.onNextNodeDebug,
|
||||||
|
workflowDebugData: v.workflowDebugData,
|
||||||
|
setWorkflowDebugData: v.setWorkflowDebugData
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (!nodeId || !workflowDebugData) return;
|
||||||
|
|
||||||
|
// 保存选中的值到本地状态
|
||||||
|
setSelectedValue(value);
|
||||||
|
|
||||||
|
// 更新查询以包含用户的选择
|
||||||
|
const updatedQuery: UserChatItemValueItemType[] = [
|
||||||
|
...(workflowDebugData.query || []),
|
||||||
|
{
|
||||||
|
type: ChatItemValueTypeEnum.text,
|
||||||
|
text: {
|
||||||
|
content: value
|
||||||
|
}
|
||||||
|
} as UserChatItemValueItemType
|
||||||
|
];
|
||||||
|
|
||||||
|
// 更新工作流调试数据
|
||||||
|
setWorkflowDebugData({
|
||||||
|
...workflowDebugData,
|
||||||
|
query: updatedQuery
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[nodeId, onChangeNode, workflowDebugData, setWorkflowDebugData]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 处理下一步调试的逻辑
|
||||||
|
const handleStartDebug = useCallback(() => {
|
||||||
|
if (!nodeId || !workflowDebugData) return;
|
||||||
|
|
||||||
|
// 先将当前节点设置为入口节点
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'attr',
|
||||||
|
key: 'isEntry',
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 然后调用onNextNodeDebug函数
|
||||||
|
onNextNodeDebug();
|
||||||
|
}, [nodeId, workflowDebugData, onNextNodeDebug, onChangeNode]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={4} py={3}>
|
||||||
|
{interactive?.params?.description && (
|
||||||
|
<Box
|
||||||
|
mb={4}
|
||||||
|
p={3}
|
||||||
|
borderLeft="4px solid"
|
||||||
|
borderColor="primary.100"
|
||||||
|
bg="primary.50"
|
||||||
|
borderRadius="md"
|
||||||
|
>
|
||||||
|
<Markdown source={interactive.params.description} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Flex flexDirection={'column'} gap={3} maxW={'400px'} mx="auto">
|
||||||
|
{interactive.params.userSelectOptions?.map((option) => {
|
||||||
|
const selected =
|
||||||
|
option.value === selectedValue || option.value === interactive?.params?.userSelectedVal;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={option.key}
|
||||||
|
variant={'outline'}
|
||||||
|
height="auto"
|
||||||
|
py={3}
|
||||||
|
px={4}
|
||||||
|
fontWeight="medium"
|
||||||
|
borderWidth="1.5px"
|
||||||
|
whiteSpace={'pre-wrap'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'primary.50',
|
||||||
|
borderColor: 'primary.300'
|
||||||
|
}}
|
||||||
|
isDisabled={
|
||||||
|
selectedValue !== undefined || interactive?.params?.userSelectedVal !== undefined
|
||||||
|
}
|
||||||
|
{...(selected
|
||||||
|
? {
|
||||||
|
borderColor: 'primary.500',
|
||||||
|
bg: 'primary.50',
|
||||||
|
color: 'primary.700',
|
||||||
|
_disabled: {
|
||||||
|
cursor: 'default',
|
||||||
|
borderColor: 'primary.500',
|
||||||
|
bg: 'primary.50 !important',
|
||||||
|
color: 'primary.700',
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
onClick={() => handleSelect(option.value)}
|
||||||
|
>
|
||||||
|
{option.value}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 添加下一步按钮,在选择完成后显示 */}
|
||||||
|
{(selectedValue !== undefined || interactive?.params?.userSelectedVal !== undefined) && (
|
||||||
|
<Flex justify="flex-end" mt={4}>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<MyIcon name={'core/workflow/debugNext'} w={'16px'} />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={handleStartDebug}
|
||||||
|
>
|
||||||
|
{t('common:common.Next Step')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||||
|
interactive,
|
||||||
|
nodeId
|
||||||
|
}: {
|
||||||
|
interactive: UserInputInteractive;
|
||||||
|
nodeId?: string;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { register, setValue, handleSubmit: handleSubmitChat, control, reset } = useForm();
|
||||||
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
const { onChangeNode, onNextNodeDebug, workflowDebugData, setWorkflowDebugData } =
|
||||||
|
useContextSelector(WorkflowContext, (v) => ({
|
||||||
|
onChangeNode: v.onChangeNode,
|
||||||
|
onNextNodeDebug: v.onNextNodeDebug,
|
||||||
|
workflowDebugData: v.workflowDebugData,
|
||||||
|
setWorkflowDebugData: v.setWorkflowDebugData
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(data: any) => {
|
||||||
|
if (!nodeId || !workflowDebugData) return;
|
||||||
|
|
||||||
|
// 标记表单已提交
|
||||||
|
setIsSubmitted(true);
|
||||||
|
|
||||||
|
const jsonData = JSON.stringify(data);
|
||||||
|
|
||||||
|
// 更新查询以包含用户的表单数据
|
||||||
|
const updatedQuery: UserChatItemValueItemType[] = [
|
||||||
|
...(workflowDebugData.query || []),
|
||||||
|
{
|
||||||
|
type: ChatItemValueTypeEnum.text,
|
||||||
|
text: {
|
||||||
|
content: jsonData
|
||||||
|
}
|
||||||
|
} as UserChatItemValueItemType
|
||||||
|
];
|
||||||
|
|
||||||
|
// 更新工作流调试数据
|
||||||
|
setWorkflowDebugData({
|
||||||
|
...workflowDebugData,
|
||||||
|
query: updatedQuery
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[nodeId, onChangeNode, workflowDebugData, setWorkflowDebugData]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 处理下一步调试的逻辑
|
||||||
|
const handleStartDebug = useCallback(() => {
|
||||||
|
if (!nodeId || !workflowDebugData) return;
|
||||||
|
|
||||||
|
// 先将当前节点设置为入口节点
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'attr',
|
||||||
|
key: 'isEntry',
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 然后调用onNextNodeDebug函数
|
||||||
|
onNextNodeDebug();
|
||||||
|
}, [nodeId, workflowDebugData, onNextNodeDebug, onChangeNode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (interactive.type === 'userInput') {
|
||||||
|
const defaultValues = interactive.params.inputForm?.reduce(
|
||||||
|
(acc: Record<string, any>, item) => {
|
||||||
|
acc[item.label] = !!item.value ? item.value : item.defaultValue;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
reset(defaultValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经有表单结果,标记为已提交
|
||||||
|
if (interactive.params.submitted) {
|
||||||
|
setIsSubmitted(true);
|
||||||
|
}
|
||||||
|
}, [interactive, reset, nodeId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={4} py={4} bg="white" borderRadius="md">
|
||||||
|
{interactive.params.description && (
|
||||||
|
<Box
|
||||||
|
mb={4}
|
||||||
|
p={3}
|
||||||
|
borderLeft="4px solid"
|
||||||
|
borderColor="blue.100"
|
||||||
|
bg="blue.50"
|
||||||
|
borderRadius="md"
|
||||||
|
>
|
||||||
|
<Markdown source={interactive.params.description} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
as="form"
|
||||||
|
onSubmit={handleSubmitChat(onSubmit)}
|
||||||
|
maxW="560px"
|
||||||
|
mx="auto"
|
||||||
|
bg="white"
|
||||||
|
p={4}
|
||||||
|
borderRadius="md"
|
||||||
|
>
|
||||||
|
<Flex flexDirection={'column'} gap={5} w={'100%'}>
|
||||||
|
{interactive.params.inputForm?.map((input) => (
|
||||||
|
<Box key={input.label} mb={2}>
|
||||||
|
<Flex mb={2} alignItems={'center'}>
|
||||||
|
<FormLabel required={input.required} mb={0} fontWeight="medium" color="gray.700">
|
||||||
|
{input.label}
|
||||||
|
</FormLabel>
|
||||||
|
{input.description && <QuestionTip ml={1} label={input.description} />}
|
||||||
|
</Flex>
|
||||||
|
{input.type === FlowNodeInputTypeEnum.input && (
|
||||||
|
<MyTextarea
|
||||||
|
isDisabled={isSubmitted || interactive.params.submitted}
|
||||||
|
{...register(input.label, {
|
||||||
|
required: input.required
|
||||||
|
})}
|
||||||
|
bg={'white'}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor="gray.300"
|
||||||
|
_hover={{ borderColor: 'gray.400' }}
|
||||||
|
_focus={{
|
||||||
|
borderColor: 'primary.500',
|
||||||
|
boxShadow: '0 0 0 1px var(--chakra-colors-primary-500)'
|
||||||
|
}}
|
||||||
|
autoHeight
|
||||||
|
minH={40}
|
||||||
|
maxH={100}
|
||||||
|
borderRadius="md"
|
||||||
|
p={3}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{input.type === FlowNodeInputTypeEnum.textarea && (
|
||||||
|
<Textarea
|
||||||
|
isDisabled={isSubmitted || interactive.params.submitted}
|
||||||
|
bg={'white'}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor="gray.300"
|
||||||
|
_hover={{ borderColor: 'gray.400' }}
|
||||||
|
_focus={{
|
||||||
|
borderColor: 'primary.500',
|
||||||
|
boxShadow: '0 0 0 1px var(--chakra-colors-primary-500)'
|
||||||
|
}}
|
||||||
|
{...register(input.label, {
|
||||||
|
required: input.required
|
||||||
|
})}
|
||||||
|
rows={5}
|
||||||
|
maxLength={input.maxLength || 4000}
|
||||||
|
borderRadius="md"
|
||||||
|
p={3}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{input.type === FlowNodeInputTypeEnum.numberInput && (
|
||||||
|
<Box position="relative">
|
||||||
|
<MyNumberInput
|
||||||
|
min={input.min}
|
||||||
|
max={input.max}
|
||||||
|
defaultValue={input.defaultValue}
|
||||||
|
isDisabled={isSubmitted || interactive.params.submitted}
|
||||||
|
bg={'white'}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor="gray.300"
|
||||||
|
_hover={{ borderColor: 'gray.400' }}
|
||||||
|
_focus={{ borderColor: 'primary.500' }}
|
||||||
|
register={register}
|
||||||
|
name={input.label}
|
||||||
|
isRequired={input.required}
|
||||||
|
sx={{
|
||||||
|
'& input': {
|
||||||
|
width: '100%',
|
||||||
|
height: '40px',
|
||||||
|
px: 3,
|
||||||
|
borderRadius: 'md',
|
||||||
|
border: 'none',
|
||||||
|
_focus: { outline: 'none' }
|
||||||
|
},
|
||||||
|
'& button': {
|
||||||
|
border: 'none',
|
||||||
|
bg: 'transparent',
|
||||||
|
color: 'gray.500'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{input.type === FlowNodeInputTypeEnum.select && (
|
||||||
|
<Controller
|
||||||
|
key={input.label}
|
||||||
|
control={control}
|
||||||
|
name={input.label}
|
||||||
|
rules={{ required: input.required }}
|
||||||
|
render={({ field: { ref, value } }) => {
|
||||||
|
if (!input.list) return <></>;
|
||||||
|
return (
|
||||||
|
<MySelect
|
||||||
|
ref={ref}
|
||||||
|
width={'100%'}
|
||||||
|
variant="outline"
|
||||||
|
borderColor="gray.300"
|
||||||
|
borderRadius="md"
|
||||||
|
height="40px"
|
||||||
|
bg="white"
|
||||||
|
_hover={{ borderColor: 'gray.400' }}
|
||||||
|
list={input.list}
|
||||||
|
value={value}
|
||||||
|
isDisabled={isSubmitted || interactive.params.submitted}
|
||||||
|
onChange={(e) => setValue(input.label, e)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Flex w={'full'} justifyContent={'flex-end'} mt={3} gap={2}>
|
||||||
|
{!isSubmitted && !interactive.params.submitted && (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
colorScheme="blue"
|
||||||
|
size="md"
|
||||||
|
height="44px"
|
||||||
|
px={8}
|
||||||
|
fontWeight="medium"
|
||||||
|
borderRadius="md"
|
||||||
|
boxShadow="sm"
|
||||||
|
_hover={{ transform: 'translateY(-1px)', boxShadow: 'md' }}
|
||||||
|
_active={{ transform: 'translateY(0)' }}
|
||||||
|
transition="all 0.2s"
|
||||||
|
>
|
||||||
|
{t('common:Submit')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 提交完成后显示下一步按钮 */}
|
||||||
|
{(isSubmitted || interactive.params.submitted) && (
|
||||||
|
<Button
|
||||||
|
size="md"
|
||||||
|
height="44px"
|
||||||
|
leftIcon={<MyIcon name={'core/workflow/debugNext'} w={'16px'} />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={handleStartDebug}
|
||||||
|
>
|
||||||
|
{t('common:common.Next Step')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -17,6 +17,7 @@ import { ChatBoxContext } from '../ChatContainer/ChatBox/Provider';
|
|||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
|
|
||||||
type sideTabItemType = {
|
type sideTabItemType = {
|
||||||
moduleLogo?: string;
|
moduleLogo?: string;
|
||||||
@@ -33,12 +34,14 @@ export const WholeResponseContent = ({
|
|||||||
activeModule,
|
activeModule,
|
||||||
hideTabs,
|
hideTabs,
|
||||||
dataId,
|
dataId,
|
||||||
chatTime
|
chatTime,
|
||||||
|
interactive
|
||||||
}: {
|
}: {
|
||||||
activeModule: ChatHistoryItemResType;
|
activeModule: ChatHistoryItemResType;
|
||||||
hideTabs?: boolean;
|
hideTabs?: boolean;
|
||||||
dataId?: string;
|
dataId?: string;
|
||||||
chatTime?: Date;
|
chatTime?: Date;
|
||||||
|
interactive?: UserInputInteractive;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type PostWorkflowDebugProps = {
|
|||||||
edges: RuntimeEdgeItemType[];
|
edges: RuntimeEdgeItemType[];
|
||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
appId: string;
|
appId: string;
|
||||||
|
query?: UserChatItemValueItemType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PostWorkflowDebugResponse = {
|
export type PostWorkflowDebugResponse = {
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ 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 {
|
||||||
|
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;
|
||||||
@@ -62,6 +72,7 @@ const NodeCard = (props: Props) => {
|
|||||||
w = 'full',
|
w = 'full',
|
||||||
h = 'full',
|
h = 'full',
|
||||||
nodeId,
|
nodeId,
|
||||||
|
flowNodeType,
|
||||||
selected,
|
selected,
|
||||||
menuForbid,
|
menuForbid,
|
||||||
isTool = false,
|
isTool = false,
|
||||||
@@ -670,12 +681,60 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
|||||||
debugResult: FlowNodeItemType['debugResult'];
|
debugResult: FlowNodeItemType['debugResult'];
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
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(
|
const { onChangeNode, onStopNodeDebug, onNextNodeDebug, workflowDebugData } = useContextSelector(
|
||||||
WorkflowContext,
|
WorkflowContext,
|
||||||
(v) => v
|
(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({
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
content: t('common:core.workflow.Confirm stop debug')
|
content: t('common:core.workflow.Confirm stop debug')
|
||||||
});
|
});
|
||||||
@@ -784,16 +843,18 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
|||||||
{t('common:common.Next Step')}
|
{t('common:common.Next Step')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{workflowDebugData?.nextRunNodes && workflowDebugData?.nextRunNodes.length === 0 && (
|
{!firstInteractive &&
|
||||||
<Button ml={2} size={'sm'} variant={'primary'} onClick={onStopNodeDebug}>
|
workflowDebugData?.nextRunNodes &&
|
||||||
{t('common:core.workflow.debug.Done')}
|
workflowDebugData?.nextRunNodes.length === 0 && (
|
||||||
</Button>
|
<Button ml={2} size={'sm'} variant={'primary'} onClick={onStopNodeDebug}>
|
||||||
)}
|
{t('common:core.workflow.debug.Done')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* Response list */}
|
{/* Response list */}
|
||||||
{debugResult.status !== 'skipped' && (
|
{debugResult.status !== 'skipped' && (
|
||||||
<Box borderTop={'base'} mt={1} overflowY={'auto'} minH={'250px'}>
|
<Box borderTop={'base'} mt={1} overflowY={'auto'} minH={'250px'}>
|
||||||
{!debugResult.message && !response && (
|
{!debugResult.message && !response && !firstInteractive && (
|
||||||
<EmptyTip text={t('common:core.workflow.debug.Not result')} pt={2} pb={5} />
|
<EmptyTip text={t('common:core.workflow.debug.Not result')} pt={2} pb={5} />
|
||||||
)}
|
)}
|
||||||
{debugResult.message && (
|
{debugResult.message && (
|
||||||
@@ -801,6 +862,16 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
|||||||
{debugResult.message}
|
{debugResult.message}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
{firstInteractive && interactive && (
|
||||||
|
<>
|
||||||
|
{interactive.type === 'userSelect' && (
|
||||||
|
<RenderUserSelectInteractive interactive={interactive} nodeId={nodeId} />
|
||||||
|
)}
|
||||||
|
{interactive.type === 'userInput' && (
|
||||||
|
<RenderUserFormInteractive interactive={interactive} nodeId={nodeId} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{response && <WholeResponseContent activeModule={response} />}
|
{response && <WholeResponseContent activeModule={response} />}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -809,6 +880,8 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
|||||||
</>
|
</>
|
||||||
) : null;
|
) : null;
|
||||||
}, [
|
}, [
|
||||||
|
interactive,
|
||||||
|
firstInteractive,
|
||||||
debugResult,
|
debugResult,
|
||||||
nodeId,
|
nodeId,
|
||||||
onChangeNode,
|
onChangeNode,
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import WorkflowInitContextProvider, { WorkflowNodeEdgeContext } from './workflow
|
|||||||
import WorkflowEventContextProvider from './workflowEventContext';
|
import WorkflowEventContextProvider from './workflowEventContext';
|
||||||
import { getAppConfigByDiff } from '@/web/core/app/diff';
|
import { getAppConfigByDiff } from '@/web/core/app/diff';
|
||||||
import WorkflowStatusContextProvider from './workflowStatusContext';
|
import WorkflowStatusContextProvider from './workflowStatusContext';
|
||||||
|
import { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import { ChatRoleEnum, ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Context
|
Context
|
||||||
@@ -161,18 +163,34 @@ type WorkflowContextType = {
|
|||||||
runtimeNodes: RuntimeNodeItemType[];
|
runtimeNodes: RuntimeNodeItemType[];
|
||||||
runtimeEdges: RuntimeEdgeItemType[];
|
runtimeEdges: RuntimeEdgeItemType[];
|
||||||
nextRunNodes: RuntimeNodeItemType[];
|
nextRunNodes: RuntimeNodeItemType[];
|
||||||
|
query?: UserChatItemValueItemType[];
|
||||||
|
variables: Record<string, any>;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
setWorkflowDebugData: React.Dispatch<
|
||||||
|
React.SetStateAction<
|
||||||
|
| {
|
||||||
|
runtimeNodes: RuntimeNodeItemType[];
|
||||||
|
runtimeEdges: RuntimeEdgeItemType[];
|
||||||
|
nextRunNodes: RuntimeNodeItemType[];
|
||||||
|
query?: UserChatItemValueItemType[];
|
||||||
|
variables: Record<string, any>;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>
|
||||||
|
>;
|
||||||
onNextNodeDebug: () => Promise<void>;
|
onNextNodeDebug: () => Promise<void>;
|
||||||
onStartNodeDebug: ({
|
onStartNodeDebug: ({
|
||||||
entryNodeId,
|
entryNodeId,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
|
query,
|
||||||
variables
|
variables
|
||||||
}: {
|
}: {
|
||||||
entryNodeId: string;
|
entryNodeId: string;
|
||||||
runtimeNodes: RuntimeNodeItemType[];
|
runtimeNodes: RuntimeNodeItemType[];
|
||||||
runtimeEdges: RuntimeEdgeItemType[];
|
runtimeEdges: RuntimeEdgeItemType[];
|
||||||
|
query?: UserChatItemValueItemType[];
|
||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
onStopNodeDebug: () => void;
|
onStopNodeDebug: () => void;
|
||||||
@@ -194,6 +212,7 @@ type DebugDataType = {
|
|||||||
runtimeEdges: RuntimeEdgeItemType[];
|
runtimeEdges: RuntimeEdgeItemType[];
|
||||||
nextRunNodes: RuntimeNodeItemType[];
|
nextRunNodes: RuntimeNodeItemType[];
|
||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
|
query?: UserChatItemValueItemType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowContext = createContext<WorkflowContextType>({
|
export const WorkflowContext = createContext<WorkflowContextType>({
|
||||||
@@ -236,6 +255,20 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
|||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
workflowDebugData: undefined,
|
workflowDebugData: undefined,
|
||||||
|
setWorkflowDebugData: function (
|
||||||
|
value: React.SetStateAction<
|
||||||
|
| {
|
||||||
|
runtimeNodes: RuntimeNodeItemType[];
|
||||||
|
runtimeEdges: RuntimeEdgeItemType[];
|
||||||
|
nextRunNodes: RuntimeNodeItemType[];
|
||||||
|
query?: UserChatItemValueItemType[];
|
||||||
|
variables: Record<string, any>;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>
|
||||||
|
): void {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
onNextNodeDebug: function (): Promise<void> {
|
onNextNodeDebug: function (): Promise<void> {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
@@ -549,7 +582,37 @@ const WorkflowContextProvider = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* debug */
|
/* debug */
|
||||||
const [workflowDebugData, setWorkflowDebugData] = useState<DebugDataType>();
|
const [workflowDebugData, setWorkflowDebugData] = useState<
|
||||||
|
DebugDataType & {
|
||||||
|
query?: UserChatItemValueItemType[];
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
// 添加这个函数用于捕获入口节点的输入值
|
||||||
|
const captureEntryInputValues = (entryNodeId: string, nodes: RuntimeNodeItemType[]) => {
|
||||||
|
const entryNode = nodes.find((node) => node.nodeId === entryNodeId);
|
||||||
|
if (!entryNode || !entryNode.inputs) return null;
|
||||||
|
|
||||||
|
// 提取用户输入值
|
||||||
|
const userInput = entryNode.inputs.find((input) => input.key === 'userChatInput')?.value || '';
|
||||||
|
return userInput;
|
||||||
|
};
|
||||||
|
// 添加函数用于准备调试数据
|
||||||
|
const prepareDebugData = (entryNodeId: string, nodes: RuntimeNodeItemType[]) => {
|
||||||
|
const userInput = captureEntryInputValues(entryNodeId, nodes);
|
||||||
|
if (!userInput) return null;
|
||||||
|
|
||||||
|
// 构建查询项
|
||||||
|
const queryItem: UserChatItemValueItemType = {
|
||||||
|
type: ChatItemValueTypeEnum.text,
|
||||||
|
text: {
|
||||||
|
content: userInput
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: [queryItem]
|
||||||
|
};
|
||||||
|
};
|
||||||
const onNextNodeDebug = useCallback(
|
const onNextNodeDebug = useCallback(
|
||||||
async (debugData = workflowDebugData) => {
|
async (debugData = workflowDebugData) => {
|
||||||
if (!debugData) return;
|
if (!debugData) return;
|
||||||
@@ -611,7 +674,7 @@ const WorkflowContextProvider = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 4. Run one step
|
// 4. Run one step - 添加历史记录和查询到请求中
|
||||||
const { finishedEdges, finishedNodes, nextStepRunNodes, flowResponses, newVariables } =
|
const { finishedEdges, finishedNodes, nextStepRunNodes, flowResponses, newVariables } =
|
||||||
await postWorkflowDebug({
|
await postWorkflowDebug({
|
||||||
nodes: runtimeNodes,
|
nodes: runtimeNodes,
|
||||||
@@ -621,6 +684,7 @@ const WorkflowContextProvider = ({
|
|||||||
cTime: formatTime2YMDHMW(),
|
cTime: formatTime2YMDHMW(),
|
||||||
...debugData.variables
|
...debugData.variables
|
||||||
},
|
},
|
||||||
|
query: debugData.query || [],
|
||||||
appId
|
appId
|
||||||
});
|
});
|
||||||
// 5. Store debug result
|
// 5. Store debug result
|
||||||
@@ -629,7 +693,8 @@ const WorkflowContextProvider = ({
|
|||||||
// edges need to save status
|
// edges need to save status
|
||||||
runtimeEdges: finishedEdges,
|
runtimeEdges: finishedEdges,
|
||||||
nextRunNodes: nextStepRunNodes,
|
nextRunNodes: nextStepRunNodes,
|
||||||
variables: newVariables
|
variables: newVariables,
|
||||||
|
query: debugData.query // 保留查询
|
||||||
};
|
};
|
||||||
setWorkflowDebugData(newStoreDebugData);
|
setWorkflowDebugData(newStoreDebugData);
|
||||||
|
|
||||||
@@ -719,10 +784,12 @@ const WorkflowContextProvider = ({
|
|||||||
runtimeEdges: RuntimeEdgeItemType[];
|
runtimeEdges: RuntimeEdgeItemType[];
|
||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
}) => {
|
}) => {
|
||||||
|
const debugHistoryData = prepareDebugData(entryNodeId, runtimeNodes);
|
||||||
const data = {
|
const data = {
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges,
|
runtimeEdges,
|
||||||
nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId),
|
nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId),
|
||||||
|
query: debugHistoryData?.query || [],
|
||||||
variables
|
variables
|
||||||
};
|
};
|
||||||
onStopNodeDebug();
|
onStopNodeDebug();
|
||||||
@@ -992,6 +1059,7 @@ const WorkflowContextProvider = ({
|
|||||||
flowData2StoreData,
|
flowData2StoreData,
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
|
setWorkflowDebugData,
|
||||||
workflowDebugData,
|
workflowDebugData,
|
||||||
onNextNodeDebug,
|
onNextNodeDebug,
|
||||||
onStartNodeDebug,
|
onStartNodeDebug,
|
||||||
|
|||||||
Reference in New Issue
Block a user