diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index f5db3a89c..0de271276 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -65,6 +65,7 @@ export type ModuleDispatchProps = ChatDispatchProps & { runtimeNodes: RuntimeNodeItemType[]; runtimeEdges: RuntimeEdgeItemType[]; params: T; + realmode: 'chat' | 'debug' | 'test'; }; export type SystemVariablesType = { diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index 24b279f5c..ff3f9d4f4 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -581,7 +581,8 @@ export async function dispatchWorkFlow(data: Props): Promise(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 ( {interactive?.params?.description && ( @@ -155,7 +254,6 @@ export const RenderUserSelectInteractive = React.memo(function RenderInteractive ); }); - 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({ ))} - {!isSubmitted && !interactive.params.submitted && ( - - )} - - {/* 提交完成后显示下一步按钮 */} - {(isSubmitted || interactive.params.submitted) && ( - - )} + diff --git a/projects/app/src/global/core/workflow/api.d.ts b/projects/app/src/global/core/workflow/api.d.ts index 240009ace..c4a37c272 100644 --- a/projects/app/src/global/core/workflow/api.d.ts +++ b/projects/app/src/global/core/workflow/api.d.ts @@ -10,6 +10,7 @@ export type PostWorkflowDebugProps = { variables: Record; appId: string; query?: UserChatItemValueItemType[]; + history?: ChatItemType[]; }; export type PostWorkflowDebugResponse = { diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index 1fdaabb70..8c160b48a 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -830,6 +830,7 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({ )} {(debugResult.status === 'success' || debugResult.status === 'skipped') && + !firstInteractive && !debugResult.isExpired && workflowDebugData?.nextRunNodes && workflowDebugData.nextRunNodes.length > 0 && ( diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx index 21691e222..66a831441 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/context/index.tsx @@ -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; + query?: UserChatItemValueItemType[]; + history?: ChatItemType[]; } | undefined; - setWorkflowDebugData: React.Dispatch< - React.SetStateAction< - | { - runtimeNodes: RuntimeNodeItemType[]; - runtimeEdges: RuntimeEdgeItemType[]; - nextRunNodes: RuntimeNodeItemType[]; - query?: UserChatItemValueItemType[]; - variables: Record; - } - | undefined - > - >; - onNextNodeDebug: () => Promise; + onNextNodeDebug: ( + history?: ChatItemType[], + query?: UserChatItemValueItemType[], + debugData?: { + runtimeNodes: RuntimeNodeItemType[]; + runtimeEdges: RuntimeEdgeItemType[]; + nextRunNodes: RuntimeNodeItemType[]; + variables: Record; + } + ) => Promise; onStartNodeDebug: ({ entryNodeId, runtimeNodes, runtimeEdges, + variables, query, - variables + history }: { entryNodeId: string; runtimeNodes: RuntimeNodeItemType[]; runtimeEdges: RuntimeEdgeItemType[]; - query?: UserChatItemValueItemType[]; variables: Record; + query?: UserChatItemValueItemType[]; + history?: ChatItemType[]; }) => Promise; onStopNodeDebug: () => void; @@ -212,7 +211,6 @@ type DebugDataType = { runtimeEdges: RuntimeEdgeItemType[]; nextRunNodes: RuntimeNodeItemType[]; variables: Record; - query?: UserChatItemValueItemType[]; }; export const WorkflowContext = createContext({ @@ -255,31 +253,25 @@ export const WorkflowContext = createContext({ throw new Error('Function not implemented.'); }, workflowDebugData: undefined, - setWorkflowDebugData: function ( - value: React.SetStateAction< - | { - runtimeNodes: RuntimeNodeItemType[]; - runtimeEdges: RuntimeEdgeItemType[]; - nextRunNodes: RuntimeNodeItemType[]; - query?: UserChatItemValueItemType[]; - variables: Record; - } - | undefined - > - ): void { - throw new Error('Function not implemented.'); - }, - onNextNodeDebug: function (): Promise { + onNextNodeDebug: function ( + history?: ChatItemType[], + query?: UserChatItemValueItemType[], + debugData?: any + ): Promise { 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 { 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(); 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; + 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, diff --git a/projects/app/src/pages/api/core/workflow/debug.ts b/projects/app/src/pages/api/core/workflow/debug.ts index 5fe8d92b1..2a4efee9e 100644 --- a/projects/app/src/pages/api/core/workflow/debug.ts +++ b/projects/app/src/pages/api/core/workflow/debug.ts @@ -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 });