diff --git a/web/app/components/sub-graph/components/sub-graph-children.tsx b/web/app/components/sub-graph/components/sub-graph-children.tsx new file mode 100644 index 0000000000..ec1f9ee4d6 --- /dev/null +++ b/web/app/components/sub-graph/components/sub-graph-children.tsx @@ -0,0 +1,57 @@ +import type { FC } from 'react' +import type { SubGraphConfig } from '../types' +import { memo, useMemo } from 'react' +import { useStore as useReactFlowStore } from 'reactflow' +import { useShallow } from 'zustand/react/shallow' +import { Panel as NodePanel } from '@/app/components/workflow/nodes' + +type SubGraphChildrenProps = { + toolNodeId: string + paramKey: string + onConfigChange: (config: Partial) => void +} + +const SubGraphChildren: FC = ({ + toolNodeId: _toolNodeId, + paramKey: _paramKey, + onConfigChange: _onConfigChange, +}) => { + const selectedNode = useReactFlowStore(useShallow((s) => { + const nodes = s.getNodes() + const currentNode = nodes.find(node => node.data.selected) + + if (currentNode) { + return { + id: currentNode.id, + type: currentNode.type, + data: currentNode.data, + } + } + return null + })) + + const nodePanel = useMemo(() => { + if (!selectedNode) + return null + + return ( + + ) + }, [selectedNode]) + + return ( +
+ {nodePanel && ( +
+ {nodePanel} +
+ )} +
+ ) +} + +export default memo(SubGraphChildren) diff --git a/web/app/components/sub-graph/components/sub-graph-main.tsx b/web/app/components/sub-graph/components/sub-graph-main.tsx new file mode 100644 index 0000000000..efd1009692 --- /dev/null +++ b/web/app/components/sub-graph/components/sub-graph-main.tsx @@ -0,0 +1,107 @@ +import type { FC } from 'react' +import type { Viewport } from 'reactflow' +import type { SubGraphConfig } from '../types' +import type { Edge, Node } from '@/app/components/workflow/types' +import { useCallback, useMemo } from 'react' +import { WorkflowWithInnerContext } from '@/app/components/workflow' +import { useAvailableNodesMetaData, useSubGraphPersistence } from '../hooks' +import SubGraphChildren from './sub-graph-children' + +type SubGraphMainProps = { + nodes: Node[] + edges: Edge[] + viewport: Viewport + toolNodeId: string + paramKey: string +} + +const SubGraphMain: FC = ({ + nodes, + edges, + viewport, + toolNodeId, + paramKey, +}) => { + const availableNodesMetaData = useAvailableNodesMetaData() + const { + saveSubGraphData, + loadSubGraphData, + updateSubGraphConfig, + } = useSubGraphPersistence({ toolNodeId, paramKey }) + + const handleNodesChange = useCallback((updatedNodes: Node[]) => { + const existingData = loadSubGraphData() + const defaultConfig: SubGraphConfig = { + enabled: true, + startNodeId: updatedNodes[0]?.id || '', + selectedOutputVar: [], + whenOutputNone: 'skip', + } + + saveSubGraphData({ + nodes: updatedNodes, + edges, + config: existingData?.config || defaultConfig, + }) + }, [edges, loadSubGraphData, saveSubGraphData]) + + const hooksStore = useMemo(() => { + return { + availableNodesMetaData, + doSyncWorkflowDraft: async () => { + handleNodesChange(nodes) + }, + syncWorkflowDraftWhenPageClose: () => { + handleNodesChange(nodes) + }, + handleRefreshWorkflowDraft: () => {}, + handleBackupDraft: () => {}, + handleLoadBackupDraft: () => {}, + handleRestoreFromPublishedWorkflow: () => {}, + handleRun: () => {}, + handleStopRun: () => {}, + handleStartWorkflowRun: () => {}, + handleWorkflowStartRunInWorkflow: () => {}, + handleWorkflowStartRunInChatflow: () => {}, + handleWorkflowTriggerScheduleRunInWorkflow: () => {}, + handleWorkflowTriggerWebhookRunInWorkflow: () => {}, + handleWorkflowTriggerPluginRunInWorkflow: () => {}, + handleWorkflowRunAllTriggersInWorkflow: () => {}, + getWorkflowRunAndTraceUrl: () => ({ runUrl: '', traceUrl: '' }), + exportCheck: async () => {}, + handleExportDSL: async () => {}, + fetchInspectVars: async () => {}, + hasNodeInspectVars: () => false, + hasSetInspectVar: () => false, + fetchInspectVarValue: async () => {}, + editInspectVarValue: async () => {}, + renameInspectVarName: async () => {}, + appendNodeInspectVars: () => {}, + deleteInspectVar: async () => {}, + deleteNodeInspectorVars: async () => {}, + deleteAllInspectorVars: async () => {}, + isInspectVarEdited: () => false, + resetToLastRunVar: async () => {}, + invalidateSysVarValues: () => {}, + resetConversationVar: async () => {}, + invalidateConversationVarValues: () => {}, + } + }, [availableNodesMetaData, handleNodesChange, nodes]) + + return ( + + + + ) +} + +export default SubGraphMain diff --git a/web/app/components/sub-graph/hooks/index.ts b/web/app/components/sub-graph/hooks/index.ts new file mode 100644 index 0000000000..d67a22f2cc --- /dev/null +++ b/web/app/components/sub-graph/hooks/index.ts @@ -0,0 +1,5 @@ +export { useAvailableNodesMetaData } from './use-available-nodes-meta-data' +export { useSubGraphInit } from './use-sub-graph-init' +export { useSubGraphNodes } from './use-sub-graph-nodes' +export { useSubGraphPersistence } from './use-sub-graph-persistence' +export type { SubGraphData } from './use-sub-graph-persistence' diff --git a/web/app/components/sub-graph/hooks/use-available-nodes-meta-data.ts b/web/app/components/sub-graph/hooks/use-available-nodes-meta-data.ts new file mode 100644 index 0000000000..f9a843e7a4 --- /dev/null +++ b/web/app/components/sub-graph/hooks/use-available-nodes-meta-data.ts @@ -0,0 +1,43 @@ +import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node' +import { BlockEnum } from '@/app/components/workflow/types' + +export const useAvailableNodesMetaData = () => { + const { t } = useTranslation() + + const availableNodesMetaData = useMemo(() => WORKFLOW_COMMON_NODES.map((node) => { + const { metaData } = node + const title = t(`blocks.${metaData.type}`, { ns: 'workflow' }) + const description = t(`blocksAbout.${metaData.type}`, { ns: 'workflow' }) + return { + ...node, + metaData: { + ...metaData, + title, + description, + }, + defaultValue: { + ...node.defaultValue, + type: metaData.type, + title, + }, + } + }), [t]) + + const availableNodesMetaDataMap = useMemo(() => availableNodesMetaData.reduce((acc, node) => { + acc![node.metaData.type] = node + return acc + }, {} as AvailableNodesMetaData['nodesMap']), [availableNodesMetaData]) + + return useMemo(() => { + return { + nodes: availableNodesMetaData, + nodesMap: { + ...availableNodesMetaDataMap, + [BlockEnum.VariableAssigner]: availableNodesMetaDataMap?.[BlockEnum.VariableAggregator], + }, + } + }, [availableNodesMetaData, availableNodesMetaDataMap]) +} diff --git a/web/app/components/sub-graph/hooks/use-sub-graph-init.ts b/web/app/components/sub-graph/hooks/use-sub-graph-init.ts new file mode 100644 index 0000000000..ba6f391a83 --- /dev/null +++ b/web/app/components/sub-graph/hooks/use-sub-graph-init.ts @@ -0,0 +1,90 @@ +import type { SubGraphProps } from '../types' +import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types' +import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' +import type { Edge, Node } from '@/app/components/workflow/types' +import { useMemo } from 'react' +import { BlockEnum, PromptRole } from '@/app/components/workflow/types' +import { AppModeEnum } from '@/types/app' + +const SUBGRAPH_SOURCE_NODE_ID = 'subgraph-source' +const SUBGRAPH_LLM_NODE_ID = 'subgraph-llm' + +export const useSubGraphInit = (props: SubGraphProps) => { + const { sourceVariable, agentName } = props + + const initialNodes = useMemo((): Node[] => { + const sourceVarName = sourceVariable.length > 1 + ? sourceVariable.slice(1).join('.') + : 'output' + + const startNode: Node = { + id: SUBGRAPH_SOURCE_NODE_ID, + type: 'custom', + position: { x: 100, y: 150 }, + data: { + type: BlockEnum.Start, + title: `${agentName}: ${sourceVarName}`, + desc: 'Source variable from agent', + _connectedSourceHandleIds: ['source'], + _connectedTargetHandleIds: [], + variables: [], + }, + } + + const llmNode: Node = { + id: SUBGRAPH_LLM_NODE_ID, + type: 'custom', + position: { x: 450, y: 150 }, + data: { + type: BlockEnum.LLM, + title: 'LLM', + desc: 'Transform the output', + _connectedSourceHandleIds: [], + _connectedTargetHandleIds: ['target'], + model: { + provider: '', + name: '', + mode: AppModeEnum.CHAT, + completion_params: { + temperature: 0.7, + }, + }, + prompt_template: [{ + role: PromptRole.system, + text: '', + }], + context: { + enabled: false, + variable_selector: [], + }, + vision: { + enabled: false, + }, + }, + } + + return [startNode, llmNode] + }, [sourceVariable, agentName]) + + const initialEdges = useMemo((): Edge[] => { + return [ + { + id: `${SUBGRAPH_SOURCE_NODE_ID}-${SUBGRAPH_LLM_NODE_ID}`, + source: SUBGRAPH_SOURCE_NODE_ID, + sourceHandle: 'source', + target: SUBGRAPH_LLM_NODE_ID, + targetHandle: 'target', + type: 'custom', + data: { + sourceType: BlockEnum.Start, + targetType: BlockEnum.LLM, + }, + }, + ] + }, []) + + return { + initialNodes, + initialEdges, + } +} diff --git a/web/app/components/sub-graph/hooks/use-sub-graph-nodes.ts b/web/app/components/sub-graph/hooks/use-sub-graph-nodes.ts new file mode 100644 index 0000000000..c2a868f05e --- /dev/null +++ b/web/app/components/sub-graph/hooks/use-sub-graph-nodes.ts @@ -0,0 +1,20 @@ +import type { Edge, Node } from '@/app/components/workflow/types' +import { useMemo } from 'react' +import { initialEdges, initialNodes } from '@/app/components/workflow/utils' + +export const useSubGraphNodes = (nodes: Node[], edges: Edge[]) => { + const processedNodes = useMemo( + () => initialNodes(nodes, edges), + [nodes, edges], + ) + + const processedEdges = useMemo( + () => initialEdges(edges, nodes), + [edges, nodes], + ) + + return { + nodes: processedNodes, + edges: processedEdges, + } +} diff --git a/web/app/components/sub-graph/hooks/use-sub-graph-persistence.ts b/web/app/components/sub-graph/hooks/use-sub-graph-persistence.ts new file mode 100644 index 0000000000..f3fd97565d --- /dev/null +++ b/web/app/components/sub-graph/hooks/use-sub-graph-persistence.ts @@ -0,0 +1,128 @@ +import type { SubGraphConfig } from '../types' +import type { ToolNodeType } from '@/app/components/workflow/nodes/tool/types' +import type { Edge, Node } from '@/app/components/workflow/types' +import { useCallback } from 'react' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { VarKindType } from '@/app/components/workflow/nodes/_base/types' + +type SubGraphPersistenceProps = { + toolNodeId: string + paramKey: string +} + +export type SubGraphData = { + nodes: Node[] + edges: Edge[] + config: SubGraphConfig +} + +const SUB_GRAPH_DATA_PREFIX = '__subgraph__' + +export const useSubGraphPersistence = ({ + toolNodeId, + paramKey, +}: SubGraphPersistenceProps) => { + const { inputs, setInputs } = useNodeCrud(toolNodeId, {} as ToolNodeType) + + const getSubGraphDataKey = useCallback(() => { + return `${SUB_GRAPH_DATA_PREFIX}${paramKey}` + }, [paramKey]) + + const loadSubGraphData = useCallback((): SubGraphData | null => { + const dataKey = getSubGraphDataKey() + const toolParameters = inputs.tool_parameters || {} + const storedData = toolParameters[dataKey] + + if (!storedData || storedData.type !== VarKindType.constant) { + return null + } + + try { + const parsed = typeof storedData.value === 'string' + ? JSON.parse(storedData.value) + : storedData.value + + return parsed as SubGraphData + } + catch { + return null + } + }, [getSubGraphDataKey, inputs.tool_parameters]) + + const saveSubGraphData = useCallback((data: SubGraphData) => { + const dataKey = getSubGraphDataKey() + const newToolParameters = { + ...inputs.tool_parameters, + [dataKey]: { + type: VarKindType.constant, + value: JSON.stringify(data), + }, + } + + setInputs({ + ...inputs, + tool_parameters: newToolParameters, + }) + }, [getSubGraphDataKey, inputs, setInputs]) + + const clearSubGraphData = useCallback(() => { + const dataKey = getSubGraphDataKey() + const newToolParameters = { ...inputs.tool_parameters } + delete newToolParameters[dataKey] + + setInputs({ + ...inputs, + tool_parameters: newToolParameters, + }) + }, [getSubGraphDataKey, inputs, setInputs]) + + const hasSubGraphData = useCallback(() => { + const dataKey = getSubGraphDataKey() + const toolParameters = inputs.tool_parameters || {} + return !!toolParameters[dataKey] + }, [getSubGraphDataKey, inputs.tool_parameters]) + + const updateSubGraphConfig = useCallback(( + config: Partial, + ) => { + const existingData = loadSubGraphData() + if (!existingData) + return + + saveSubGraphData({ + ...existingData, + config: { + ...existingData.config, + ...config, + }, + }) + }, [loadSubGraphData, saveSubGraphData]) + + const updateSubGraphNodes = useCallback(( + nodes: Node[], + edges: Edge[], + ) => { + const existingData = loadSubGraphData() + const defaultConfig: SubGraphConfig = { + enabled: true, + startNodeId: nodes[0]?.id || '', + selectedOutputVar: [], + whenOutputNone: 'skip', + } + + saveSubGraphData({ + nodes, + edges, + config: existingData?.config || defaultConfig, + }) + }, [loadSubGraphData, saveSubGraphData]) + + return { + loadSubGraphData, + saveSubGraphData, + clearSubGraphData, + hasSubGraphData, + updateSubGraphConfig, + updateSubGraphNodes, + } +} diff --git a/web/app/components/sub-graph/index.tsx b/web/app/components/sub-graph/index.tsx new file mode 100644 index 0000000000..7a97ec152a --- /dev/null +++ b/web/app/components/sub-graph/index.tsx @@ -0,0 +1,57 @@ +import type { FC } from 'react' +import type { Viewport } from 'reactflow' +import type { SubGraphProps } from './types' +import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store' +import { memo, useMemo } from 'react' +import WorkflowWithDefaultContext from '@/app/components/workflow' +import { WorkflowContextProvider } from '@/app/components/workflow/context' +import SubGraphMain from './components/sub-graph-main' +import { useSubGraphInit, useSubGraphNodes, useSubGraphPersistence } from './hooks' +import { createSubGraphSlice } from './store' + +const defaultViewport: Viewport = { + x: 50, + y: 50, + zoom: 1, +} + +const SubGraph: FC = (props) => { + const { toolNodeId, paramKey } = props + + const { loadSubGraphData } = useSubGraphPersistence({ toolNodeId, paramKey }) + const savedData = useMemo(() => loadSubGraphData(), [loadSubGraphData]) + + const { initialNodes, initialEdges } = useSubGraphInit(props) + + const nodesSource = savedData?.nodes || initialNodes + const edgesSource = savedData?.edges || initialEdges + + const { nodes, edges } = useSubGraphNodes(nodesSource, edgesSource) + + return ( + + + + ) +} + +const SubGraphWrapper: FC = (props) => { + return ( + + + + ) +} + +export default memo(SubGraphWrapper) diff --git a/web/app/components/sub-graph/store/index.ts b/web/app/components/sub-graph/store/index.ts new file mode 100644 index 0000000000..52accd4a21 --- /dev/null +++ b/web/app/components/sub-graph/store/index.ts @@ -0,0 +1,49 @@ +import type { CreateSubGraphSlice, SubGraphSliceShape } from '../types' + +const initialState: Omit = { + parentToolNodeId: '', + parameterKey: '', + sourceAgentNodeId: '', + sourceVariable: [], + + subGraphNodes: [], + subGraphEdges: [], + + selectedOutputVar: [], + whenOutputNone: 'skip', + defaultValue: '', + + showDebugPanel: false, + isRunning: false, + + parentAvailableVars: [], +} + +export const createSubGraphSlice: CreateSubGraphSlice = set => ({ + ...initialState, + + setSubGraphContext: context => set(() => ({ + parentToolNodeId: context.parentToolNodeId, + parameterKey: context.parameterKey, + sourceAgentNodeId: context.sourceAgentNodeId, + sourceVariable: context.sourceVariable, + })), + + setSubGraphNodes: nodes => set(() => ({ subGraphNodes: nodes })), + + setSubGraphEdges: edges => set(() => ({ subGraphEdges: edges })), + + setSelectedOutputVar: selector => set(() => ({ selectedOutputVar: selector })), + + setWhenOutputNone: option => set(() => ({ whenOutputNone: option })), + + setDefaultValue: value => set(() => ({ defaultValue: value })), + + setShowDebugPanel: show => set(() => ({ showDebugPanel: show })), + + setIsRunning: running => set(() => ({ isRunning: running })), + + setParentAvailableVars: vars => set(() => ({ parentAvailableVars: vars })), + + resetSubGraph: () => set(() => ({ ...initialState })), +}) diff --git a/web/app/components/sub-graph/types.ts b/web/app/components/sub-graph/types.ts new file mode 100644 index 0000000000..f9b376d5c9 --- /dev/null +++ b/web/app/components/sub-graph/types.ts @@ -0,0 +1,65 @@ +import type { StateCreator } from 'zustand' +import type { Edge, Node, NodeOutPutVar, ValueSelector, VarType } from '@/app/components/workflow/types' + +export type WhenOutputNoneOption = 'skip' | 'error' | 'default' + +export type SubGraphConfig = { + enabled: boolean + startNodeId: string + selectedOutputVar: ValueSelector + whenOutputNone: WhenOutputNoneOption + defaultValue?: string +} + +export type SubGraphOutputVariable = { + nodeId: string + nodeName: string + variable: string + type: VarType + description?: string +} + +export type SubGraphProps = { + toolNodeId: string + paramKey: string + sourceVariable: ValueSelector + agentNodeId: string + agentName: string +} + +export type SubGraphSliceShape = { + parentToolNodeId: string + parameterKey: string + sourceAgentNodeId: string + sourceVariable: ValueSelector + + subGraphNodes: Node[] + subGraphEdges: Edge[] + + selectedOutputVar: ValueSelector + whenOutputNone: WhenOutputNoneOption + defaultValue: string + + showDebugPanel: boolean + isRunning: boolean + + parentAvailableVars: NodeOutPutVar[] + + setSubGraphContext: (context: { + parentToolNodeId: string + parameterKey: string + sourceAgentNodeId: string + sourceVariable: ValueSelector + }) => void + setSubGraphNodes: (nodes: Node[]) => void + setSubGraphEdges: (edges: Edge[]) => void + setSelectedOutputVar: (selector: ValueSelector) => void + setWhenOutputNone: (option: WhenOutputNoneOption) => void + setDefaultValue: (value: string) => void + setShowDebugPanel: (show: boolean) => void + setIsRunning: (running: boolean) => void + setParentAvailableVars: (vars: NodeOutPutVar[]) => void + resetSubGraph: () => void +} + +export type CreateSubGraphSlice = StateCreator diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 419f905fa5..caafd34e97 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -337,6 +337,8 @@ const FormInputItem: FC = ({ showManageInputField={showManageInputField} onManageInputField={onManageInputField} disableVariableInsertion={disableVariableInsertion} + toolNodeId={nodeId} + paramKey={variable} /> )} {isNumber && isConstant && ( diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx index 167cb41807..8dfb63c2e2 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx @@ -2,11 +2,11 @@ import type { AgentBlockType } from '@/app/components/base/prompt-editor/types' import type { Node, NodeOutPutVar, + ValueSelector, } from '@/app/components/workflow/types' import { memo, useCallback, - useEffect, useMemo, useState, } from 'react' @@ -15,6 +15,7 @@ import PromptEditor from '@/app/components/base/prompt-editor' import { useStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import SubGraphModal from '../sub-graph-modal' import AgentHeaderBar from './agent-header-bar' import Placeholder from './placeholder' @@ -33,7 +34,8 @@ type MixedVariableTextInputProps = { showManageInputField?: boolean onManageInputField?: () => void disableVariableInsertion?: boolean - onViewInternals?: () => void + toolNodeId?: string + paramKey?: string } const MixedVariableTextInput = ({ @@ -45,11 +47,13 @@ const MixedVariableTextInput = ({ showManageInputField, onManageInputField, disableVariableInsertion = false, - onViewInternals, + toolNodeId, + paramKey = '', }: MixedVariableTextInputProps) => { const { t } = useTranslation() const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey) const setControlPromptEditorRerenderKey = useStore(s => s.setControlPromptEditorRerenderKey) + const [isSubGraphModalOpen, setIsSubGraphModalOpen] = useState(false) const nodesByIdMap = useMemo(() => { return availableNodes.reduce((acc, node) => { @@ -79,11 +83,6 @@ const MixedVariableTextInput = ({ const [selectedAgent, setSelectedAgent] = useState<{ id: string, title: string } | null>(null) - useEffect(() => { - if (!detectedAgentFromValue && selectedAgent) - setSelectedAgent(null) - }, [detectedAgentFromValue, selectedAgent]) - const agentNodes = useMemo(() => { return availableNodes .filter(node => node.data.type === BlockEnum.Agent) @@ -115,6 +114,18 @@ const MixedVariableTextInput = ({ const displayedAgent = detectedAgentFromValue || (selectedAgent ? { nodeId: selectedAgent.id, name: selectedAgent.title } : null) + const handleOpenSubGraphModal = useCallback(() => { + setIsSubGraphModalOpen(true) + }, []) + + const handleCloseSubGraphModal = useCallback(() => { + setIsSubGraphModalOpen(false) + }, []) + + const sourceVariable: ValueSelector | undefined = displayedAgent + ? [displayedAgent.nodeId, 'text'] + : undefined + return (
)} } onChange={onChange} /> + {toolNodeId && displayedAgent && sourceVariable && ( + + )}
) } diff --git a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/config-panel.tsx b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/config-panel.tsx new file mode 100644 index 0000000000..acc9929ee1 --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/config-panel.tsx @@ -0,0 +1,83 @@ +'use client' +import type { FC } from 'react' +import type { ConfigPanelProps, WhenOutputNoneOption } from './types' +import { memo, useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import { cn } from '@/utils/classnames' + +const outputVariables = [ + { name: 'text', type: 'string' }, + { name: 'structured_output', type: 'object' }, +] + +const ConfigPanel: FC = ({ + toolNodeId: _toolNodeId, + paramKey: _paramKey, + activeTab, +}) => { + const { t } = useTranslation() + const [whenOutputNone, setWhenOutputNone] = useState('skip') + + const handleWhenOutputNoneChange = useCallback((e: React.ChangeEvent) => { + setWhenOutputNone(e.target.value as WhenOutputNoneOption) + }, []) + + if (activeTab === 'lastRun') { + return ( +
+
+

+ {t('subGraphModal.noRunHistory', { ns: 'workflow' })} +

+
+
+ ) + } + + return ( +
+ +
+ {outputVariables.map(variable => ( +
+ {variable.name} + {variable.type} +
+ ))} +
+
+ + + + +
+ ) +} + +export default memo(ConfigPanel) diff --git a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/index.tsx b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/index.tsx new file mode 100644 index 0000000000..ee550b9c86 --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/index.tsx @@ -0,0 +1,72 @@ +'use client' +import type { FC } from 'react' +import type { SubGraphModalProps } from './types' +import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react' +import { RiCloseLine } from '@remixicon/react' +import { noop } from 'es-toolkit/function' +import { Fragment, memo } from 'react' +import { useTranslation } from 'react-i18next' +import { Agent } from '@/app/components/base/icons/src/vender/workflow' +import SubGraphCanvas from './sub-graph-canvas' + +const SubGraphModal: FC = ({ + isOpen, + onClose, + toolNodeId, + paramKey, + sourceVariable, + agentName, + agentNodeId, +}) => { + const { t } = useTranslation() + + return ( + + + +
+ +
+
+ + +
+
+
+ +
+ + @ + {agentName} + {' '} + {t('subGraphModal.title', { ns: 'workflow' })} + +
+ +
+ +
+ +
+
+
+
+
+
+
+ ) +} + +export default memo(SubGraphModal) diff --git a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/store.ts b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/store.ts new file mode 100644 index 0000000000..63723459f0 --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/store.ts @@ -0,0 +1,49 @@ +import type { CreateSubGraphSlice, SubGraphSliceShape } from './types' + +const initialState: Omit = { + parentToolNodeId: '', + parameterKey: '', + sourceAgentNodeId: '', + sourceVariable: [], + + subGraphNodes: [], + subGraphEdges: [], + + selectedOutputVar: [], + whenOutputNone: 'skip', + defaultValue: '', + + showDebugPanel: false, + isRunning: false, + + parentAvailableVars: [], +} + +export const createSubGraphSlice: CreateSubGraphSlice = set => ({ + ...initialState, + + setSubGraphContext: context => set(() => ({ + parentToolNodeId: context.parentToolNodeId, + parameterKey: context.parameterKey, + sourceAgentNodeId: context.sourceAgentNodeId, + sourceVariable: context.sourceVariable, + })), + + setSubGraphNodes: nodes => set(() => ({ subGraphNodes: nodes })), + + setSubGraphEdges: edges => set(() => ({ subGraphEdges: edges })), + + setSelectedOutputVar: selector => set(() => ({ selectedOutputVar: selector })), + + setWhenOutputNone: option => set(() => ({ whenOutputNone: option })), + + setDefaultValue: value => set(() => ({ defaultValue: value })), + + setShowDebugPanel: show => set(() => ({ showDebugPanel: show })), + + setIsRunning: running => set(() => ({ isRunning: running })), + + setParentAvailableVars: vars => set(() => ({ parentAvailableVars: vars })), + + resetSubGraph: () => set(() => ({ ...initialState })), +}) diff --git a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/sub-graph-canvas.tsx b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/sub-graph-canvas.tsx new file mode 100644 index 0000000000..f13a48d87c --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/sub-graph-canvas.tsx @@ -0,0 +1,27 @@ +'use client' +import type { FC } from 'react' +import type { SubGraphCanvasProps } from './types' +import { memo } from 'react' +import SubGraph from '@/app/components/sub-graph' + +const SubGraphCanvas: FC = ({ + toolNodeId, + paramKey, + sourceVariable, + agentNodeId, + agentName, +}) => { + return ( +
+ +
+ ) +} + +export default memo(SubGraphCanvas) diff --git a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/types.ts b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/types.ts new file mode 100644 index 0000000000..a8ce0496be --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/types.ts @@ -0,0 +1,103 @@ +import type { StateCreator } from 'zustand' +import type { Edge, Node, NodeOutPutVar, ValueSelector, VarType } from '@/app/components/workflow/types' + +export type SubGraphNodeData = { + isInSubGraph: boolean + subGraph_id: string + subGraphParamKey: string +} + +export type SubGraphNode = Node & { + data: Node['data'] & SubGraphNodeData +} + +export type SubGraphSourceNodeData = { + title: string + sourceAgentNodeId: string + sourceVariable: ValueSelector + sourceVarType: VarType + isReadOnly: true + isInSubGraph: true + subGraph_id: string + subGraphParamKey: string +} + +export type WhenOutputNoneOption = 'skip' | 'error' | 'default' + +export type SubGraphConfig = { + enabled: boolean + startNodeId: string + selectedOutputVar: ValueSelector + whenOutputNone: WhenOutputNoneOption + defaultValue?: string +} + +export type SubGraphOutputVariable = { + nodeId: string + nodeName: string + variable: string + type: VarType + description?: string +} + +export type SubGraphModalProps = { + isOpen: boolean + onClose: () => void + toolNodeId: string + paramKey: string + sourceVariable: ValueSelector + agentName: string + agentNodeId: string +} + +export type ConfigPanelProps = { + toolNodeId: string + paramKey: string + activeTab: 'settings' | 'lastRun' + onTabChange: (tab: 'settings' | 'lastRun') => void +} + +export type SubGraphCanvasProps = { + toolNodeId: string + paramKey: string + sourceVariable: ValueSelector + agentNodeId: string + agentName: string +} + +export type SubGraphSliceShape = { + parentToolNodeId: string + parameterKey: string + sourceAgentNodeId: string + sourceVariable: ValueSelector + + subGraphNodes: SubGraphNode[] + subGraphEdges: Edge[] + + selectedOutputVar: ValueSelector + whenOutputNone: WhenOutputNoneOption + defaultValue: string + + showDebugPanel: boolean + isRunning: boolean + + parentAvailableVars: NodeOutPutVar[] + + setSubGraphContext: (context: { + parentToolNodeId: string + parameterKey: string + sourceAgentNodeId: string + sourceVariable: ValueSelector + }) => void + setSubGraphNodes: (nodes: SubGraphNode[]) => void + setSubGraphEdges: (edges: Edge[]) => void + setSelectedOutputVar: (selector: ValueSelector) => void + setWhenOutputNone: (option: WhenOutputNoneOption) => void + setDefaultValue: (value: string) => void + setShowDebugPanel: (show: boolean) => void + setIsRunning: (running: boolean) => void + setParentAvailableVars: (vars: NodeOutPutVar[]) => void + resetSubGraph: () => void +} + +export type CreateSubGraphSlice = StateCreator diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 39bcee3050..686fe3f7cc 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -205,7 +205,7 @@ "common.runApp": "Run App", "common.runHistory": "Run History", "common.running": "Running", - "common.searchAgent": "Search agent", + "common.searchAgent": "Search agent...", "common.searchVar": "Search variable", "common.setVarValuePlaceholder": "Set variable", "common.showRunHistory": "Show Run History", @@ -217,7 +217,7 @@ "common.variableNamePlaceholder": "Variable name", "common.versionHistory": "Version History", "common.viewDetailInTracingPanel": "View details", - "common.viewInternals": "View internals", + "common.viewInternals": "View Internals", "common.viewOnly": "View Only", "common.viewRunHistory": "View run history", "common.workflowAsTool": "Workflow as Tool", @@ -988,6 +988,18 @@ "singleRun.testRun": "Test Run", "singleRun.testRunIteration": "Test Run Iteration", "singleRun.testRunLoop": "Test Run Loop", + "subGraphModal.canvasPlaceholder": "Click to configure the internal structure", + "subGraphModal.internalStructure": "Internal structure", + "subGraphModal.lastRun": "LAST RUN", + "subGraphModal.noRunHistory": "No run history yet", + "subGraphModal.outputVariables": "OUTPUT VARIABLES", + "subGraphModal.settings": "SETTINGS", + "subGraphModal.sourceNode": "SOURCE", + "subGraphModal.title": "INTERNAL STRUCTURE", + "subGraphModal.whenOutputIsNone": "WHEN OUTPUT IS NONE", + "subGraphModal.whenOutputNone.default": "Use default value", + "subGraphModal.whenOutputNone.error": "Raise an error", + "subGraphModal.whenOutputNone.skip": "Skip this step", "tabs.-": "Default", "tabs.addAll": "Add all", "tabs.agent": "Agent Strategy", diff --git a/web/i18n/ja-JP/workflow.json b/web/i18n/ja-JP/workflow.json index ed7abb48a3..1435f672e8 100644 --- a/web/i18n/ja-JP/workflow.json +++ b/web/i18n/ja-JP/workflow.json @@ -203,7 +203,7 @@ "common.runApp": "アプリを実行", "common.runHistory": "実行履歴", "common.running": "実行中", - "common.searchAgent": "エージェントを検索", + "common.searchAgent": "エージェントを検索...", "common.searchVar": "変数を検索", "common.setVarValuePlaceholder": "変数値を設定", "common.showRunHistory": "実行履歴を表示", @@ -215,7 +215,7 @@ "common.variableNamePlaceholder": "変数名を入力", "common.versionHistory": "バージョン履歴", "common.viewDetailInTracingPanel": "詳細を表示", - "common.viewInternals": "内部を表示", + "common.viewInternals": "内部構造を表示", "common.viewOnly": "閲覧のみ", "common.viewRunHistory": "実行履歴を表示", "common.workflowAsTool": "ワークフローをツールとして公開する", @@ -985,6 +985,18 @@ "singleRun.testRun": "テスト実行", "singleRun.testRunIteration": "テスト実行(イテレーション)", "singleRun.testRunLoop": "テスト実行ループ", + "subGraphModal.canvasPlaceholder": "クリックして内部構造を設定", + "subGraphModal.internalStructure": "内部構造", + "subGraphModal.lastRun": "前回の実行", + "subGraphModal.noRunHistory": "実行履歴がありません", + "subGraphModal.outputVariables": "出力変数", + "subGraphModal.settings": "設定", + "subGraphModal.sourceNode": "ソース", + "subGraphModal.title": "内部構造", + "subGraphModal.whenOutputIsNone": "出力が空の場合", + "subGraphModal.whenOutputNone.default": "デフォルト値を使用", + "subGraphModal.whenOutputNone.error": "エラーを発生させる", + "subGraphModal.whenOutputNone.skip": "このステップをスキップ", "tabs.-": "デフォルト", "tabs.addAll": "すべてを追加する", "tabs.agent": "エージェント戦略", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 24be2d6c67..2b863487b0 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -171,7 +171,7 @@ "common.needConnectTip": "此节点尚未连接到其他节点", "common.needOutputNode": "必须添加输出节点", "common.needStartNode": "必须添加至少一个开始节点", - "common.noAgentNodes": "没有可用的代理节点", + "common.noAgentNodes": "没有可用的 Agent 节点", "common.noHistory": "没有历史版本", "common.noVar": "没有变量", "common.notRunning": "尚未运行", @@ -203,7 +203,7 @@ "common.runApp": "运行", "common.runHistory": "运行历史", "common.running": "运行中", - "common.searchAgent": "搜索代理", + "common.searchAgent": "搜索 Agent...", "common.searchVar": "搜索变量", "common.setVarValuePlaceholder": "设置变量值", "common.showRunHistory": "显示运行历史", @@ -215,7 +215,7 @@ "common.variableNamePlaceholder": "变量名", "common.versionHistory": "版本历史", "common.viewDetailInTracingPanel": "查看详细信息", - "common.viewInternals": "查看内部", + "common.viewInternals": "查看内部结构", "common.viewOnly": "只读", "common.viewRunHistory": "查看运行历史", "common.workflowAsTool": "发布为工具", @@ -986,6 +986,18 @@ "singleRun.testRun": "测试运行", "singleRun.testRunIteration": "测试运行迭代", "singleRun.testRunLoop": "测试运行循环", + "subGraphModal.canvasPlaceholder": "点击配置内部结构", + "subGraphModal.internalStructure": "内部结构", + "subGraphModal.lastRun": "上次运行", + "subGraphModal.noRunHistory": "暂无运行记录", + "subGraphModal.outputVariables": "输出变量", + "subGraphModal.settings": "设置", + "subGraphModal.sourceNode": "来源", + "subGraphModal.title": "内部结构", + "subGraphModal.whenOutputIsNone": "当输出为空时", + "subGraphModal.whenOutputNone.default": "使用默认值", + "subGraphModal.whenOutputNone.error": "抛出错误", + "subGraphModal.whenOutputNone.skip": "跳过此步骤", "tabs.-": "默认", "tabs.addAll": "添加全部", "tabs.agent": "Agent 策略", diff --git a/web/i18n/zh-Hant/workflow.json b/web/i18n/zh-Hant/workflow.json index e6f8e88fc5..11e91a7a9d 100644 --- a/web/i18n/zh-Hant/workflow.json +++ b/web/i18n/zh-Hant/workflow.json @@ -171,7 +171,7 @@ "common.needConnectTip": "此節點尚未連接到其他節點", "common.needOutputNode": "必須新增輸出節點", "common.needStartNode": "至少必須新增一個起始節點", - "common.noAgentNodes": "沒有可用的代理節點", + "common.noAgentNodes": "沒有可用的 Agent 節點", "common.noHistory": "無歷史記錄", "common.noVar": "沒有變數", "common.notRunning": "尚未運行", @@ -203,7 +203,7 @@ "common.runApp": "運行", "common.runHistory": "運行歷史", "common.running": "運行中", - "common.searchAgent": "搜索代理", + "common.searchAgent": "搜尋 Agent...", "common.searchVar": "搜索變數", "common.setVarValuePlaceholder": "設置變數值", "common.showRunHistory": "顯示運行歷史", @@ -215,7 +215,7 @@ "common.variableNamePlaceholder": "變數名", "common.versionHistory": "版本歷史", "common.viewDetailInTracingPanel": "查看詳細信息", - "common.viewInternals": "查看內部", + "common.viewInternals": "檢視內部結構", "common.viewOnly": "只讀", "common.viewRunHistory": "查看運行歷史", "common.workflowAsTool": "發佈為工具", @@ -985,6 +985,18 @@ "singleRun.testRun": "測試運行", "singleRun.testRunIteration": "測試運行迭代", "singleRun.testRunLoop": "測試運行循環", + "subGraphModal.canvasPlaceholder": "點擊配置內部結構", + "subGraphModal.internalStructure": "內部結構", + "subGraphModal.lastRun": "上次執行", + "subGraphModal.noRunHistory": "暫無執行記錄", + "subGraphModal.outputVariables": "輸出變數", + "subGraphModal.settings": "設定", + "subGraphModal.sourceNode": "來源", + "subGraphModal.title": "內部結構", + "subGraphModal.whenOutputIsNone": "當輸出為空時", + "subGraphModal.whenOutputNone.default": "使用預設值", + "subGraphModal.whenOutputNone.error": "拋出錯誤", + "subGraphModal.whenOutputNone.skip": "跳過此步驟", "tabs.-": "預設", "tabs.addAll": "全部新增", "tabs.agent": "代理策略",