feat: add history parameter to PostWorkflowDebugProps and update related components

This commit is contained in:
sd0ric4
2025-03-26 15:53:08 +08:00
parent 5ec7bbf3d3
commit 51fa72717e
7 changed files with 337 additions and 192 deletions

View File

@@ -65,6 +65,7 @@ export type ModuleDispatchProps<T> = ChatDispatchProps & {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
params: T;
realmode: 'chat' | 'debug' | 'test';
};
export type SystemVariablesType = {

View File

@@ -581,7 +581,8 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
runtimeNodes,
runtimeEdges,
params,
mode: props.mode === 'debug' ? 'test' : props.mode
mode: props.mode === 'debug' ? 'test' : props.mode,
realmode: props.mode
};
// run module

View File

@@ -16,9 +16,15 @@ import {
} 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 { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import {
AIChatItemValueItemType,
ChatItemType,
UserChatItemValueItemType
} from '@fastgpt/global/core/chat/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { StoreEdgeItemType, RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { initWorkflowEdgeStatus } from '@fastgpt/global/core/workflow/runtime/utils';
export const RenderUserSelectInteractive = React.memo(function RenderInteractive({
interactive,
@@ -29,42 +35,78 @@ export const RenderUserSelectInteractive = React.memo(function RenderInteractive
}) {
const { t } = useTranslation();
const [selectedValue, setSelectedValue] = useState<string | undefined>(undefined);
const { onChangeNode, onNextNodeDebug, workflowDebugData, setWorkflowDebugData } =
useContextSelector(WorkflowContext, (v) => ({
// 在两个组件中更新上下文选择器,添加 onStartNodeDebug
const { onChangeNode, onStartNodeDebug, workflowDebugData } = useContextSelector(
WorkflowContext,
(v) => ({
onChangeNode: v.onChangeNode,
onNextNodeDebug: v.onNextNodeDebug,
workflowDebugData: v.workflowDebugData,
setWorkflowDebugData: v.setWorkflowDebugData
onStartNodeDebug: v.onStartNodeDebug,
// onNextNodeDebug: v.onNextNodeDebug, // 不再使用
workflowDebugData: v.workflowDebugData
})
);
/**
* 创建交互数据结构
* @param nodeId 交互节点ID
* @param interactive 交互组件数据
* @param edges 当前的边数组
* @returns 交互数据结构
*/
const createInteractiveData = (
nodeId: string,
interactive: UserSelectInteractive | UserInputInteractive,
edges: StoreEdgeItemType[]
) => {
// 创建 memoryEdges - 指向交互节点的边设为 active
const memoryEdges: RuntimeEdgeItemType[] = edges.map((edge) => ({
...edge,
status: edge.target === nodeId ? ('active' as const) : ('waiting' as const)
}));
return {
...interactive,
entryNodeIds: [nodeId],
memoryEdges,
nodeOutputs: [] // 如果有需要可以填充节点输出数据
};
};
/**
* 创建模拟的历史记录
* @param nodeId 交互节点ID
* @param interactive 交互组件数据
* @param edges 当前的边数组
* @returns 模拟的历史记录
*/
const createMockHistory = (
nodeId: string,
interactive: UserSelectInteractive | UserInputInteractive,
edges: StoreEdgeItemType[]
): ChatItemType[] => {
const interactiveData = createInteractiveData(nodeId, interactive, edges);
return [
{
obj: ChatRoleEnum.AI,
value: [
{
type: ChatItemValueTypeEnum.interactive,
interactive: interactiveData
}
]
}
];
};
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]
[nodeId, workflowDebugData]
);
// 处理下一步调试的逻辑
const handleStartDebug = useCallback(() => {
if (!nodeId || !workflowDebugData) return;
@@ -76,10 +118,67 @@ export const RenderUserSelectInteractive = React.memo(function RenderInteractive
value: true
});
// 然后调用onNextNodeDebug函数
onNextNodeDebug();
}, [nodeId, workflowDebugData, onNextNodeDebug, onChangeNode]);
// 更新此节点的值(将用户选择保存到节点)
if (selectedValue) {
onChangeNode({
nodeId,
type: 'attr',
key: 'userSelectedVal',
value: selectedValue
});
}
// 创建包含用户选择的查询数据
const updatedQuery: UserChatItemValueItemType[] = [
...(workflowDebugData.query || []),
{
type: ChatItemValueTypeEnum.text,
text: {
content: selectedValue || ''
}
} as UserChatItemValueItemType
];
// 创建模拟的历史记录
const mockHistory = createMockHistory(nodeId, interactive, workflowDebugData.runtimeEdges);
// 使用模拟的历史记录初始化边状态
const updatedRuntimeEdges = initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, mockHistory);
// 更新 runtimeNodes 以反映用户的选择
const updatedRuntimeNodes = workflowDebugData.runtimeNodes.map((node) => {
if (node.nodeId === nodeId) {
// 找到我们需要的输入字段并更新它
return {
...node,
inputs: node.inputs.map((input) => {
// 根据您的实际字段结构,这里可能需要调整
if (input.key === 'userSelect' || input.key === 'selectedOption') {
return {
...input,
value: selectedValue
};
}
return input;
}),
// 添加或更新任何需要的节点属性
userSelectedVal: selectedValue
};
}
return node;
});
// 更新 runtimeEdges 状态
// 使用 onStartNodeDebug 替代 onNextNodeDebug带上更新后的 nodes 和 edges
onStartNodeDebug({
entryNodeId: nodeId,
runtimeNodes: updatedRuntimeNodes,
runtimeEdges: updatedRuntimeEdges,
variables: workflowDebugData.variables,
query: updatedQuery,
history: mockHistory
});
}, [nodeId, workflowDebugData, onStartNodeDebug, onChangeNode, selectedValue, interactive]);
return (
<Box px={4} py={3}>
{interactive?.params?.description && (
@@ -155,7 +254,6 @@ export const RenderUserSelectInteractive = React.memo(function RenderInteractive
</Box>
);
});
export const RenderUserFormInteractive = React.memo(function RenderFormInput({
interactive,
nodeId
@@ -164,61 +262,167 @@ export const RenderUserFormInteractive = React.memo(function RenderFormInput({
nodeId?: string;
}) {
const { t } = useTranslation();
const { register, setValue, handleSubmit: handleSubmitChat, control, reset } = useForm();
const {
register,
setValue,
handleSubmit: handleSubmitChat,
control,
reset,
getValues
} = useForm();
const [isSubmitted, setIsSubmitted] = useState(false);
const { onChangeNode, onNextNodeDebug, workflowDebugData, setWorkflowDebugData } =
useContextSelector(WorkflowContext, (v) => ({
// 在两个组件中更新上下文选择器,添加 onStartNodeDebug
const { onChangeNode, onStartNodeDebug, workflowDebugData } = useContextSelector(
WorkflowContext,
(v) => ({
onChangeNode: v.onChangeNode,
onNextNodeDebug: v.onNextNodeDebug,
workflowDebugData: v.workflowDebugData,
setWorkflowDebugData: v.setWorkflowDebugData
onStartNodeDebug: v.onStartNodeDebug,
workflowDebugData: v.workflowDebugData
})
);
/**
* 创建交互数据结构
* @param nodeId 交互节点ID
* @param interactive 交互组件数据
* @param edges 当前的边数组
* @returns 交互数据结构
*/
const createInteractiveData = (
nodeId: string,
interactive: UserInputInteractive,
edges: StoreEdgeItemType[]
) => {
// 创建 memoryEdges - 指向交互节点的边设为 active
const memoryEdges: RuntimeEdgeItemType[] = edges.map((edge) => ({
...edge,
status: edge.target === nodeId ? ('active' as const) : ('waiting' as const)
}));
return {
...interactive,
entryNodeIds: [nodeId],
memoryEdges,
nodeOutputs: [] // 如果有需要可以填充节点输出数据
};
};
/**
* 创建模拟的历史记录
* @param nodeId 交互节点ID
* @param interactive 交互组件数据
* @param edges 当前的边数组
* @returns 模拟的历史记录
*/
const createMockHistory = (
nodeId: string,
interactive: UserInputInteractive,
edges: StoreEdgeItemType[]
): ChatItemType[] => {
// 创建一个新的 interactive 对象,确保 submitted 为 false
const adjustedInteractive = {
...interactive,
params: {
...interactive.params,
submitted: false // 关键修改点:确保 submitted 为 false
}
};
const interactiveData = createInteractiveData(nodeId, adjustedInteractive, edges);
return [
{
obj: ChatRoleEnum.AI,
value: [
{
type: ChatItemValueTypeEnum.interactive,
interactive: interactiveData
}
]
}
];
};
const onSubmit = useCallback(
(data: any) => {
if (!nodeId || !workflowDebugData) return;
// 标记表单已提交
setIsSubmitted(true);
const jsonData = JSON.stringify(data);
// 直接调用 handleStartDebug合并提交和下一步操作
const formData = getValues();
onChangeNode({
nodeId,
type: 'attr',
key: 'isEntry',
value: true
});
// 更新查询以包含用户的表单数据
const updatedQuery: UserChatItemValueItemType[] = [
...(workflowDebugData.query || []),
{
type: ChatItemValueTypeEnum.text,
text: {
content: jsonData
content: JSON.stringify(formData)
}
} as UserChatItemValueItemType
];
// 更新工作流调试数据
setWorkflowDebugData({
...workflowDebugData,
query: updatedQuery
const updatedInteractive = {
...interactive,
params: {
...interactive.params,
submitted: true
}
};
const mockHistory = createMockHistory(
nodeId,
updatedInteractive,
workflowDebugData.runtimeEdges
);
const updatedRuntimeEdges = initWorkflowEdgeStatus(
workflowDebugData.runtimeEdges,
mockHistory
);
const updatedRuntimeNodes = workflowDebugData.runtimeNodes.map((node) => {
if (node.nodeId === nodeId) {
return {
...node,
inputs: node.inputs.map((input) => {
const formField = interactive.params.inputForm?.find(
(field) => field.label === input.key || field.key === input.key
);
if (formField) {
return {
...input,
value: formData[formField.label]
};
}
return input;
}),
formSubmitted: true
};
}
return node;
});
onStartNodeDebug({
entryNodeId: nodeId,
runtimeNodes: updatedRuntimeNodes,
runtimeEdges: updatedRuntimeEdges,
variables: workflowDebugData.variables,
query: updatedQuery,
history: mockHistory
});
},
[nodeId, onChangeNode, workflowDebugData, setWorkflowDebugData]
[nodeId, workflowDebugData, onStartNodeDebug, onChangeNode, getValues, interactive]
);
// 处理下一步调试的逻辑
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(
@@ -375,37 +579,15 @@ export const RenderUserFormInteractive = React.memo(function RenderFormInput({
))}
<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>
)}
<Button
type="submit"
size="sm"
leftIcon={<MyIcon name={'core/workflow/debugNext'} w={'16px'} />}
colorScheme="blue"
variant="solid"
>
{t('common:Submit')}
</Button>
</Flex>
</Flex>
</Box>

View File

@@ -10,6 +10,7 @@ export type PostWorkflowDebugProps = {
variables: Record<string, any>;
appId: string;
query?: UserChatItemValueItemType[];
history?: ChatItemType[];
};
export type PostWorkflowDebugResponse = {

View File

@@ -830,6 +830,7 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
</Button>
)}
{(debugResult.status === 'success' || debugResult.status === 'skipped') &&
!firstInteractive &&
!debugResult.isExpired &&
workflowDebugData?.nextRunNodes &&
workflowDebugData.nextRunNodes.length > 0 && (

View File

@@ -36,7 +36,6 @@ 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 { ChatRoleEnum, ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
/*
Context
@@ -163,35 +162,35 @@ type WorkflowContextType = {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
nextRunNodes: RuntimeNodeItemType[];
query?: UserChatItemValueItemType[];
variables: Record<string, any>;
query?: UserChatItemValueItemType[];
history?: ChatItemType[];
}
| undefined;
setWorkflowDebugData: React.Dispatch<
React.SetStateAction<
| {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
nextRunNodes: RuntimeNodeItemType[];
query?: UserChatItemValueItemType[];
variables: Record<string, any>;
}
| undefined
>
>;
onNextNodeDebug: () => Promise<void>;
onNextNodeDebug: (
history?: ChatItemType[],
query?: UserChatItemValueItemType[],
debugData?: {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
nextRunNodes: RuntimeNodeItemType[];
variables: Record<string, any>;
}
) => Promise<void>;
onStartNodeDebug: ({
entryNodeId,
runtimeNodes,
runtimeEdges,
variables,
query,
variables
history
}: {
entryNodeId: string;
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
query?: UserChatItemValueItemType[];
variables: Record<string, any>;
query?: UserChatItemValueItemType[];
history?: ChatItemType[];
}) => Promise<void>;
onStopNodeDebug: () => void;
@@ -212,7 +211,6 @@ type DebugDataType = {
runtimeEdges: RuntimeEdgeItemType[];
nextRunNodes: RuntimeNodeItemType[];
variables: Record<string, any>;
query?: UserChatItemValueItemType[];
};
export const WorkflowContext = createContext<WorkflowContextType>({
@@ -255,31 +253,25 @@ export const WorkflowContext = createContext<WorkflowContextType>({
throw new Error('Function not implemented.');
},
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 (
history?: ChatItemType[],
query?: UserChatItemValueItemType[],
debugData?: any
): 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.');
},
@@ -582,39 +574,13 @@ const WorkflowContextProvider = ({
});
/* debug */
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 [workflowDebugData, setWorkflowDebugData] = useState<DebugDataType>();
const onNextNodeDebug = useCallback(
async (debugData = workflowDebugData) => {
async (
history?: ChatItemType[],
query?: UserChatItemValueItemType[],
debugData = workflowDebugData
) => {
if (!debugData) return;
// 1. Cancel node selected status and debugResult.showStatus
setNodes((state) =>
@@ -674,7 +640,7 @@ const WorkflowContextProvider = ({
});
try {
// 4. Run one step - 添加历史记录和查询到请求中
// 4. Run one step
const { finishedEdges, finishedNodes, nextStepRunNodes, flowResponses, newVariables } =
await postWorkflowDebug({
nodes: runtimeNodes,
@@ -684,7 +650,8 @@ const WorkflowContextProvider = ({
cTime: formatTime2YMDHMW(),
...debugData.variables
},
query: debugData.query || [],
query, // 添加 query 参数
history,
appId
});
// 5. Store debug result
@@ -694,7 +661,8 @@ const WorkflowContextProvider = ({
runtimeEdges: finishedEdges,
nextRunNodes: nextStepRunNodes,
variables: newVariables,
query: debugData.query // 保留查询
query,
history
};
setWorkflowDebugData(newStoreDebugData);
@@ -739,7 +707,7 @@ const WorkflowContextProvider = ({
// Check for an empty response
if (flowResponses.length === 0 && nextStepRunNodes.length > 0) {
onNextNodeDebug(newStoreDebugData);
onNextNodeDebug(history, query, newStoreDebugData);
}
} catch (error) {
entryNodes.forEach((node) => {
@@ -777,25 +745,29 @@ const WorkflowContextProvider = ({
entryNodeId,
runtimeNodes,
runtimeEdges,
variables
variables,
query,
history
}: {
entryNodeId: string;
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
variables: Record<string, any>;
query?: UserChatItemValueItemType[];
history?: ChatItemType[];
}) => {
const debugHistoryData = prepareDebugData(entryNodeId, runtimeNodes);
const data = {
runtimeNodes,
runtimeEdges,
nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId),
query: debugHistoryData?.query || [],
variables
variables,
query,
history
};
onStopNodeDebug();
setWorkflowDebugData(data);
onNextNodeDebug(data);
onNextNodeDebug(history, query, data);
}
);
@@ -1059,7 +1031,6 @@ const WorkflowContextProvider = ({
flowData2StoreData,
// debug
setWorkflowDebugData,
workflowDebugData,
onNextNodeDebug,
onStartNodeDebug,

View File

@@ -10,9 +10,7 @@ import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { defaultApp } from '@/web/core/app/constants';
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
async function handler(
req: NextApiRequest,
@@ -24,10 +22,10 @@ async function handler(
variables = {},
appId,
query: requestQuery,
histories: requestHistories
history: requestHistories
} = req.body as PostWorkflowDebugProps & {
query?: UserChatItemValueItemType[];
histories?: ChatItemType[];
history?: ChatItemType[];
};
if (!nodes) {
@@ -39,26 +37,16 @@ async function handler(
if (!Array.isArray(edges)) {
throw new Error('Edges is not array');
}
const entryNode = nodes.find((node) => node.isEntry === true);
if (!entryNode) {
throw new Error('No entry node found');
}
const query_form_input: UserChatItemValueItemType[] = requestQuery || [
{
type: ChatItemValueTypeEnum.text,
text: {
content: '{"未知":"未知","数字":2}'
}
}
];
const isEntryNodeInteractive =
entryNode.flowNodeType === 'formInput' || entryNode.flowNodeType === 'userSelect';
const query: UserChatItemValueItemType[] =
requestQuery ||
[
// {
// type: ChatItemValueTypeEnum.text,
// text: {
// content: 'Cancel'
// }
// }
];
const histories: ChatItemType[] = isEntryNodeInteractive ? requestHistories || [] : [];
const query: UserChatItemValueItemType[] = requestQuery || [];
/* user auth */
const [{ teamId, tmbId }, { app }] = await Promise.all([
@@ -96,7 +84,7 @@ async function handler(
variables,
query: query,
chatConfig: defaultApp.chatConfig,
histories: [],
histories: histories,
stream: false,
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
});