mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
feat: add AssembleVariablesAlt icon and integrate into sub-graph
components.
This commit is contained in:
@ -0,0 +1,26 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "12",
|
||||
"height": "12",
|
||||
"viewBox": "0 0 12 12",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M5.14286 5.14286V3.42857L8 5.71429L5.14286 8V6.28571H0V5.14286H5.14286ZM0.83303 7.42857H2.04658C2.72474 9.10389 4.36721 10.28571 6.28571 10.28571C8.81049 10.28571 10.85717 8.23903 10.85717 5.71429C10.85717 3.18956 8.81049 1.14285 6.28571 1.14285C4.36721 1.14285 2.72474 2.32467 2.04658 4H0.83303C1.56118 1.68165 3.72706 0 6.28571 0C9.4416 0 12 2.55837 12 5.71429C12 8.87014 9.4416 11.42854 6.28571 11.42854C3.72706 11.42854 1.56118 9.74691 0.83303 7.42857Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "AssembleVariablesAlt"
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './AssembleVariablesAlt.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'AssembleVariablesAlt'
|
||||
|
||||
export default Icon
|
||||
@ -1,4 +1,5 @@
|
||||
export { default as AssembleVariables } from './AssembleVariables'
|
||||
export { default as AssembleVariablesAlt } from './AssembleVariablesAlt'
|
||||
export { default as AtSign } from './AtSign'
|
||||
export { default as Bookmark } from './Bookmark'
|
||||
export { default as Check } from './Check'
|
||||
|
||||
@ -7,22 +7,28 @@ import { useShallow } from 'zustand/react/shallow'
|
||||
import { useIsChatMode, useWorkflowVariables } from '@/app/components/workflow/hooks'
|
||||
import Panel from '@/app/components/workflow/panel'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import ConfigPanel from './config-panel'
|
||||
|
||||
type SubGraphChildrenProps = {
|
||||
agentName: string
|
||||
extractorNodeId: string
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
}
|
||||
type SubGraphChildrenProps
|
||||
= | {
|
||||
variant: 'agent'
|
||||
title: string
|
||||
extractorNodeId: string
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
}
|
||||
| {
|
||||
variant: 'assemble'
|
||||
title: string
|
||||
extractorNodeId: string
|
||||
}
|
||||
|
||||
const SubGraphChildren: FC<SubGraphChildrenProps> = ({
|
||||
agentName,
|
||||
extractorNodeId,
|
||||
mentionConfig,
|
||||
onMentionConfigChange,
|
||||
}) => {
|
||||
const SubGraphChildren: FC<SubGraphChildrenProps> = (props) => {
|
||||
const {
|
||||
variant,
|
||||
title,
|
||||
extractorNodeId,
|
||||
} = props
|
||||
const { getNodeAvailableVars } = useWorkflowVariables()
|
||||
const isChatMode = useIsChatMode()
|
||||
const nodePanelWidth = useStore(s => s.nodePanelWidth)
|
||||
@ -32,7 +38,7 @@ const SubGraphChildren: FC<SubGraphChildrenProps> = ({
|
||||
}))
|
||||
|
||||
const extractorNode = useReactFlowStore(useShallow((s) => {
|
||||
return s.getNodes().find(node => node.data.type === BlockEnum.LLM)
|
||||
return s.getNodes().find(node => node.id === extractorNodeId)
|
||||
}))
|
||||
|
||||
const availableNodes = useMemo(() => {
|
||||
@ -51,8 +57,10 @@ const SubGraphChildren: FC<SubGraphChildrenProps> = ({
|
||||
return vars.filter(item => item.nodeId === extractorNode.id)
|
||||
}, [extractorNode, getNodeAvailableVars, isChatMode])
|
||||
|
||||
const agentProps = variant === 'agent' ? props : null
|
||||
|
||||
const panelRight = useMemo(() => {
|
||||
if (selectedNode)
|
||||
if (!agentProps || selectedNode)
|
||||
return null
|
||||
|
||||
return (
|
||||
@ -62,17 +70,25 @@ const SubGraphChildren: FC<SubGraphChildrenProps> = ({
|
||||
style={{ width: `${nodePanelWidth}px` }}
|
||||
>
|
||||
<ConfigPanel
|
||||
agentName={agentName}
|
||||
agentName={title}
|
||||
extractorNodeId={extractorNodeId}
|
||||
mentionConfig={mentionConfig}
|
||||
mentionConfig={agentProps.mentionConfig}
|
||||
availableNodes={availableNodes}
|
||||
availableVars={availableVars}
|
||||
onMentionConfigChange={onMentionConfigChange}
|
||||
onMentionConfigChange={agentProps.onMentionConfigChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [agentName, availableNodes, availableVars, extractorNodeId, mentionConfig, nodePanelWidth, onMentionConfigChange, selectedNode])
|
||||
}, [agentProps, availableNodes, availableVars, extractorNodeId, nodePanelWidth, selectedNode, title])
|
||||
|
||||
if (variant === 'assemble') {
|
||||
return (
|
||||
<Panel
|
||||
withHeader={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
|
||||
@ -9,35 +9,46 @@ import { useStoreApi } from 'reactflow'
|
||||
import { WorkflowWithInnerContext } from '@/app/components/workflow'
|
||||
import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars'
|
||||
import { useInspectVarsCrudCommon } from '@/app/components/workflow/hooks/use-inspect-vars-crud-common'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { FlowType } from '@/types/common'
|
||||
import { useAvailableNodesMetaData } from '../hooks'
|
||||
import SubGraphChildren from './sub-graph-children'
|
||||
|
||||
type SubGraphMainProps = {
|
||||
type SubGraphMainBaseProps = {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
viewport: Viewport
|
||||
agentName: string
|
||||
title: string
|
||||
extractorNodeId: string
|
||||
configsMap?: HooksStoreShape['configsMap']
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
selectableNodeTypes?: BlockEnum[]
|
||||
onSave?: (nodes: Node[], edges: Edge[]) => void
|
||||
onSyncWorkflowDraft?: SyncWorkflowDraft
|
||||
}
|
||||
|
||||
const SubGraphMain: FC<SubGraphMainProps> = ({
|
||||
nodes,
|
||||
edges,
|
||||
viewport,
|
||||
agentName,
|
||||
extractorNodeId,
|
||||
configsMap,
|
||||
mentionConfig,
|
||||
onMentionConfigChange,
|
||||
onSave,
|
||||
onSyncWorkflowDraft,
|
||||
}) => {
|
||||
type SubGraphMainProps
|
||||
= | (SubGraphMainBaseProps & {
|
||||
variant: 'agent'
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
})
|
||||
| (SubGraphMainBaseProps & {
|
||||
variant: 'assemble'
|
||||
})
|
||||
|
||||
const SubGraphMain: FC<SubGraphMainProps> = (props) => {
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
viewport,
|
||||
variant,
|
||||
title,
|
||||
extractorNodeId,
|
||||
configsMap,
|
||||
selectableNodeTypes,
|
||||
onSave,
|
||||
onSyncWorkflowDraft,
|
||||
} = props
|
||||
const reactFlowStore = useStoreApi()
|
||||
const availableNodesMetaData = useAvailableNodesMetaData()
|
||||
const flowType = configsMap?.flowType ?? FlowType.appFlow
|
||||
@ -76,32 +87,53 @@ const SubGraphMain: FC<SubGraphMainProps> = ({
|
||||
}
|
||||
}, [handleSyncSubGraphDraft, onSyncWorkflowDraft])
|
||||
|
||||
const resolvedSelectableTypes = useMemo(() => {
|
||||
if (selectableNodeTypes && selectableNodeTypes.length > 0)
|
||||
return selectableNodeTypes
|
||||
return variant === 'agent' ? [BlockEnum.LLM] : [BlockEnum.Code]
|
||||
}, [selectableNodeTypes, variant])
|
||||
|
||||
const hooksStore = useMemo(() => ({
|
||||
interactionMode: 'subgraph',
|
||||
subGraphSelectableNodeTypes: resolvedSelectableTypes,
|
||||
availableNodesMetaData,
|
||||
configsMap,
|
||||
fetchInspectVars,
|
||||
...inspectVarsCrud,
|
||||
doSyncWorkflowDraft: handleSyncWorkflowDraft,
|
||||
syncWorkflowDraftWhenPageClose: handleSyncSubGraphDraft,
|
||||
}), [availableNodesMetaData, configsMap, fetchInspectVars, handleSyncSubGraphDraft, handleSyncWorkflowDraft, inspectVarsCrud])
|
||||
}), [availableNodesMetaData, configsMap, fetchInspectVars, handleSyncSubGraphDraft, handleSyncWorkflowDraft, inspectVarsCrud, resolvedSelectableTypes])
|
||||
|
||||
const subGraphChildren = variant === 'agent'
|
||||
? (
|
||||
<SubGraphChildren
|
||||
variant="agent"
|
||||
title={title}
|
||||
extractorNodeId={extractorNodeId}
|
||||
mentionConfig={props.mentionConfig}
|
||||
onMentionConfigChange={props.onMentionConfigChange}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<SubGraphChildren
|
||||
variant="assemble"
|
||||
title={title}
|
||||
extractorNodeId={extractorNodeId}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<WorkflowWithInnerContext
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
viewport={viewport}
|
||||
// eslint-disable-next-line ts/no-explicit-any -- TODO: remove after typing boundary
|
||||
hooksStore={hooksStore as any}
|
||||
allowSelectionWhenReadOnly
|
||||
canvasReadOnly
|
||||
interactionMode="subgraph"
|
||||
>
|
||||
<SubGraphChildren
|
||||
agentName={agentName}
|
||||
extractorNodeId={extractorNodeId}
|
||||
mentionConfig={mentionConfig}
|
||||
onMentionConfigChange={onMentionConfigChange}
|
||||
/>
|
||||
{subGraphChildren}
|
||||
</WorkflowWithInnerContext>
|
||||
)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { memo, useEffect, useMemo } from 'react'
|
||||
import WorkflowWithDefaultContext from '@/app/components/workflow'
|
||||
import { NODE_WIDTH_X_OFFSET, START_INITIAL_POSITION } from '@/app/components/workflow/constants'
|
||||
import { WorkflowContextProvider } from '@/app/components/workflow/context'
|
||||
import { CUSTOM_SUB_GRAPH_START_NODE } from '@/app/components/workflow/nodes/sub-graph-start/constants'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { BlockEnum, EditionType, isPromptMessageContext, PromptRole } from '@/app/components/workflow/types'
|
||||
import SubGraphMain from './components/sub-graph-main'
|
||||
@ -18,7 +19,7 @@ const SUB_GRAPH_ENTRY_POSITION = {
|
||||
x: START_INITIAL_POSITION.x,
|
||||
y: 150,
|
||||
}
|
||||
const SUB_GRAPH_LLM_POSITION = {
|
||||
const SUB_GRAPH_EXTRACTOR_POSITION = {
|
||||
x: SUB_GRAPH_ENTRY_POSITION.x + NODE_WIDTH_X_OFFSET - SUB_GRAPH_EDGE_GAP,
|
||||
y: SUB_GRAPH_ENTRY_POSITION.y,
|
||||
}
|
||||
@ -33,19 +34,19 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
|
||||
const {
|
||||
toolNodeId,
|
||||
paramKey,
|
||||
agentName,
|
||||
agentNodeId,
|
||||
mentionConfig,
|
||||
onMentionConfigChange,
|
||||
extractorNode,
|
||||
toolParamValue,
|
||||
parentAvailableNodes,
|
||||
parentAvailableVars,
|
||||
configsMap,
|
||||
selectableNodeTypes,
|
||||
onSave,
|
||||
onSyncWorkflowDraft,
|
||||
} = props
|
||||
|
||||
const isAgentVariant = props.variant === 'agent'
|
||||
const sourceTitle = isAgentVariant ? (props.agentName || '') : (props.title || '')
|
||||
const resolvedAgentNodeId = isAgentVariant ? props.agentNodeId : ''
|
||||
|
||||
const setParentAvailableVars = useStore(state => state.setParentAvailableVars)
|
||||
const setParentAvailableNodes = useStore(state => state.setParentAvailableNodes)
|
||||
|
||||
@ -55,28 +56,47 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
|
||||
}, [parentAvailableNodes, parentAvailableVars, setParentAvailableNodes, setParentAvailableVars])
|
||||
|
||||
const promptText = useMemo(() => {
|
||||
if (!toolParamValue)
|
||||
if (!isAgentVariant || !toolParamValue)
|
||||
return ''
|
||||
// Reason: escape agent id before building a regex pattern.
|
||||
const escapedAgentId = agentNodeId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const escapedAgentId = resolvedAgentNodeId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const leadingPattern = new RegExp(`^\\{\\{[@#]${escapedAgentId}\\.context[@#]\\}\\}`)
|
||||
return toolParamValue.replace(leadingPattern, '')
|
||||
}, [agentNodeId, toolParamValue])
|
||||
}, [isAgentVariant, resolvedAgentNodeId, toolParamValue])
|
||||
|
||||
const startNode = useMemo(() => {
|
||||
if (!isAgentVariant) {
|
||||
return {
|
||||
id: 'subgraph-source',
|
||||
type: CUSTOM_SUB_GRAPH_START_NODE,
|
||||
position: SUB_GRAPH_ENTRY_POSITION,
|
||||
data: {
|
||||
type: BlockEnum.Start,
|
||||
title: sourceTitle,
|
||||
desc: '',
|
||||
selected: false,
|
||||
iconType: 'assemble',
|
||||
variables: [],
|
||||
},
|
||||
selected: false,
|
||||
selectable: false,
|
||||
draggable: false,
|
||||
connectable: false,
|
||||
focusable: false,
|
||||
deletable: false,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'subgraph-source',
|
||||
type: 'custom',
|
||||
type: CUSTOM_SUB_GRAPH_START_NODE,
|
||||
position: SUB_GRAPH_ENTRY_POSITION,
|
||||
data: {
|
||||
type: BlockEnum.Start,
|
||||
title: agentName,
|
||||
title: sourceTitle,
|
||||
desc: '',
|
||||
_connectedSourceHandleIds: ['source'],
|
||||
_connectedTargetHandleIds: [],
|
||||
_subGraphEntry: true,
|
||||
_iconTypeOverride: BlockEnum.Agent,
|
||||
selected: false,
|
||||
iconType: 'agent',
|
||||
variables: [],
|
||||
},
|
||||
selected: false,
|
||||
@ -86,65 +106,83 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
|
||||
focusable: false,
|
||||
deletable: false,
|
||||
}
|
||||
}, [agentName])
|
||||
}, [isAgentVariant, sourceTitle])
|
||||
|
||||
const extractorDisplayNode = useMemo(() => {
|
||||
if (!extractorNode)
|
||||
return null
|
||||
if (isAgentVariant) {
|
||||
const extractorNode = props.extractorNode
|
||||
if (!extractorNode)
|
||||
return null
|
||||
|
||||
const applyPromptText = (item: PromptItem) => {
|
||||
if (item.edition_type === EditionType.jinja2) {
|
||||
return {
|
||||
...item,
|
||||
text: promptText,
|
||||
jinja2_text: promptText,
|
||||
}
|
||||
}
|
||||
return { ...item, text: promptText }
|
||||
}
|
||||
|
||||
const nextPromptTemplate = (() => {
|
||||
const template = extractorNode.data.prompt_template
|
||||
if (!Array.isArray(template))
|
||||
return applyPromptText(template as PromptItem)
|
||||
|
||||
const userIndex = template.findIndex(
|
||||
item => !isPromptMessageContext(item) && (item as PromptItem).role === PromptRole.user,
|
||||
)
|
||||
if (userIndex >= 0) {
|
||||
return template.map((item, index) => {
|
||||
if (index !== userIndex)
|
||||
return item
|
||||
return applyPromptText(item as PromptItem)
|
||||
}) as PromptTemplateItem[]
|
||||
}
|
||||
|
||||
const useJinja = template.some(
|
||||
item => !isPromptMessageContext(item) && (item as PromptItem).edition_type === EditionType.jinja2,
|
||||
)
|
||||
const defaultUserPrompt: PromptItem = useJinja
|
||||
? {
|
||||
role: PromptRole.user,
|
||||
const applyPromptText = (item: PromptItem) => {
|
||||
if (item.edition_type === EditionType.jinja2) {
|
||||
return {
|
||||
...item,
|
||||
text: promptText,
|
||||
jinja2_text: promptText,
|
||||
edition_type: EditionType.jinja2,
|
||||
}
|
||||
: { role: PromptRole.user, text: promptText }
|
||||
return [...template, defaultUserPrompt] as PromptTemplateItem[]
|
||||
})()
|
||||
}
|
||||
return { ...item, text: promptText }
|
||||
}
|
||||
|
||||
const nextPromptTemplate = (() => {
|
||||
const template = extractorNode.data.prompt_template
|
||||
if (!Array.isArray(template))
|
||||
return applyPromptText(template as PromptItem)
|
||||
|
||||
const userIndex = template.findIndex(
|
||||
item => !isPromptMessageContext(item) && (item as PromptItem).role === PromptRole.user,
|
||||
)
|
||||
if (userIndex >= 0) {
|
||||
return template.map((item, index) => {
|
||||
if (index !== userIndex)
|
||||
return item
|
||||
return applyPromptText(item as PromptItem)
|
||||
}) as PromptTemplateItem[]
|
||||
}
|
||||
|
||||
const useJinja = template.some(
|
||||
item => !isPromptMessageContext(item) && (item as PromptItem).edition_type === EditionType.jinja2,
|
||||
)
|
||||
const defaultUserPrompt: PromptItem = useJinja
|
||||
? {
|
||||
role: PromptRole.user,
|
||||
text: promptText,
|
||||
jinja2_text: promptText,
|
||||
edition_type: EditionType.jinja2,
|
||||
}
|
||||
: { role: PromptRole.user, text: promptText }
|
||||
return [...template, defaultUserPrompt] as PromptTemplateItem[]
|
||||
})()
|
||||
|
||||
return {
|
||||
...extractorNode,
|
||||
hidden: false,
|
||||
selected: false,
|
||||
position: SUB_GRAPH_EXTRACTOR_POSITION,
|
||||
data: {
|
||||
...extractorNode.data,
|
||||
selected: false,
|
||||
prompt_template: nextPromptTemplate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const extractorNode = props.extractorNode
|
||||
if (!extractorNode)
|
||||
return null
|
||||
|
||||
return {
|
||||
...extractorNode,
|
||||
hidden: false,
|
||||
selected: false,
|
||||
position: SUB_GRAPH_LLM_POSITION,
|
||||
position: SUB_GRAPH_EXTRACTOR_POSITION,
|
||||
data: {
|
||||
...extractorNode.data,
|
||||
selected: false,
|
||||
prompt_template: nextPromptTemplate,
|
||||
},
|
||||
}
|
||||
}, [extractorNode, promptText])
|
||||
}, [isAgentVariant, promptText, props.extractorNode])
|
||||
|
||||
const nodesSource = useMemo(() => {
|
||||
if (!extractorDisplayNode)
|
||||
@ -168,30 +206,54 @@ const SubGraphContent: FC<SubGraphProps> = (props) => {
|
||||
selectable: false,
|
||||
data: {
|
||||
sourceType: BlockEnum.Start,
|
||||
targetType: BlockEnum.LLM,
|
||||
targetType: isAgentVariant ? BlockEnum.LLM : BlockEnum.Code,
|
||||
_isTemp: true,
|
||||
_isSubGraphTemp: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
}, [extractorDisplayNode, startNode])
|
||||
}, [extractorDisplayNode, isAgentVariant, startNode])
|
||||
|
||||
const { nodes, edges } = useSubGraphNodes(nodesSource, edgesSource)
|
||||
|
||||
if (isAgentVariant) {
|
||||
return (
|
||||
<WorkflowWithDefaultContext
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
>
|
||||
<SubGraphMain
|
||||
variant="agent"
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
viewport={defaultViewport}
|
||||
title={sourceTitle}
|
||||
extractorNodeId={`${toolNodeId}_ext_${paramKey}`}
|
||||
configsMap={configsMap}
|
||||
mentionConfig={props.mentionConfig}
|
||||
onMentionConfigChange={props.onMentionConfigChange}
|
||||
selectableNodeTypes={selectableNodeTypes}
|
||||
onSave={onSave}
|
||||
onSyncWorkflowDraft={onSyncWorkflowDraft}
|
||||
/>
|
||||
</WorkflowWithDefaultContext>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkflowWithDefaultContext
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
>
|
||||
<SubGraphMain
|
||||
variant="assemble"
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
viewport={defaultViewport}
|
||||
agentName={agentName}
|
||||
title={sourceTitle}
|
||||
extractorNodeId={`${toolNodeId}_ext_${paramKey}`}
|
||||
configsMap={configsMap}
|
||||
mentionConfig={mentionConfig}
|
||||
onMentionConfigChange={onMentionConfigChange}
|
||||
selectableNodeTypes={selectableNodeTypes}
|
||||
onSave={onSave}
|
||||
onSyncWorkflowDraft={onSyncWorkflowDraft}
|
||||
/>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import type { StateCreator } from 'zustand'
|
||||
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { Edge, Node, NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types'
|
||||
import type { BlockEnum, Edge, Node, NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
export type SyncWorkflowDraftCallback = {
|
||||
onSuccess?: () => void
|
||||
@ -15,23 +16,38 @@ export type SyncWorkflowDraft = (
|
||||
callback?: SyncWorkflowDraftCallback,
|
||||
) => Promise<void>
|
||||
|
||||
export type SubGraphProps = {
|
||||
export type SubGraphVariant = 'agent' | 'assemble'
|
||||
|
||||
type BaseSubGraphProps = {
|
||||
toolNodeId: string
|
||||
paramKey: string
|
||||
sourceVariable: ValueSelector
|
||||
agentNodeId: string
|
||||
agentName: string
|
||||
configsMap?: HooksStoreShape['configsMap']
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
extractorNode?: Node<LLMNodeType>
|
||||
toolParamValue?: string
|
||||
parentAvailableNodes?: Node[]
|
||||
parentAvailableVars?: NodeOutPutVar[]
|
||||
selectableNodeTypes?: BlockEnum[]
|
||||
onSave?: (nodes: Node[], edges: Edge[]) => void
|
||||
onSyncWorkflowDraft?: SyncWorkflowDraft
|
||||
}
|
||||
|
||||
export type AgentSubGraphProps = BaseSubGraphProps & {
|
||||
variant: 'agent'
|
||||
sourceVariable: ValueSelector
|
||||
agentNodeId: string
|
||||
agentName: string
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
extractorNode?: Node<LLMNodeType>
|
||||
}
|
||||
|
||||
export type AssembleSubGraphProps = BaseSubGraphProps & {
|
||||
variant: 'assemble'
|
||||
title: string
|
||||
extractorNode?: Node<CodeNodeType>
|
||||
}
|
||||
|
||||
export type SubGraphProps = AgentSubGraphProps | AssembleSubGraphProps
|
||||
|
||||
export type SubGraphSliceShape = {
|
||||
parentAvailableVars: NodeOutPutVar[]
|
||||
parentAvailableNodes: Node[]
|
||||
|
||||
@ -46,6 +46,7 @@ export type CommonHooksFnMap = {
|
||||
handleWorkflowTriggerWebhookRunInWorkflow: (params: { nodeId: string }) => void
|
||||
handleWorkflowTriggerPluginRunInWorkflow: (nodeId?: string) => void
|
||||
handleWorkflowRunAllTriggersInWorkflow: (nodeIds: string[]) => void
|
||||
subGraphSelectableNodeTypes?: BlockEnum[]
|
||||
availableNodesMetaData?: AvailableNodesMetaData
|
||||
getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string, traceUrl: string }
|
||||
exportCheck?: () => Promise<void>
|
||||
@ -93,6 +94,7 @@ export const createHooksStore = ({
|
||||
handleWorkflowTriggerWebhookRunInWorkflow = noop,
|
||||
handleWorkflowTriggerPluginRunInWorkflow = noop,
|
||||
handleWorkflowRunAllTriggersInWorkflow = noop,
|
||||
subGraphSelectableNodeTypes,
|
||||
availableNodesMetaData = {
|
||||
nodes: [],
|
||||
},
|
||||
@ -136,6 +138,7 @@ export const createHooksStore = ({
|
||||
handleWorkflowTriggerWebhookRunInWorkflow,
|
||||
handleWorkflowTriggerPluginRunInWorkflow,
|
||||
handleWorkflowRunAllTriggersInWorkflow,
|
||||
subGraphSelectableNodeTypes,
|
||||
availableNodesMetaData,
|
||||
getWorkflowRunAndTraceUrl,
|
||||
exportCheck,
|
||||
|
||||
@ -89,6 +89,8 @@ import CustomIterationStartNode from './nodes/iteration-start'
|
||||
import { CUSTOM_ITERATION_START_NODE } from './nodes/iteration-start/constants'
|
||||
import CustomLoopStartNode from './nodes/loop-start'
|
||||
import { CUSTOM_LOOP_START_NODE } from './nodes/loop-start/constants'
|
||||
import CustomSubGraphStartNode from './nodes/sub-graph-start'
|
||||
import { CUSTOM_SUB_GRAPH_START_NODE } from './nodes/sub-graph-start/constants'
|
||||
import CustomNoteNode from './note-node'
|
||||
import { CUSTOM_NOTE_NODE } from './note-node/constants'
|
||||
import Operator from './operator'
|
||||
@ -119,6 +121,7 @@ const nodeTypes = {
|
||||
[CUSTOM_NODE]: CustomNode,
|
||||
[CUSTOM_NOTE_NODE]: CustomNoteNode,
|
||||
[CUSTOM_SIMPLE_NODE]: CustomSimpleNode,
|
||||
[CUSTOM_SUB_GRAPH_START_NODE]: CustomSubGraphStartNode,
|
||||
[CUSTOM_ITERATION_START_NODE]: CustomIterationStartNode,
|
||||
[CUSTOM_LOOP_START_NODE]: CustomLoopStartNode,
|
||||
[CUSTOM_DATA_SOURCE_EMPTY_NODE]: CustomDataSourceEmptyNode,
|
||||
@ -355,6 +358,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
const dataSourceList = useStore(s => s.dataSourceList)
|
||||
// buildInTools, customTools, workflowTools, mcpTools, dataSourceList
|
||||
const configsMap = useHooksStore(s => s.configsMap)
|
||||
const subGraphSelectableNodeTypes = useHooksStore(s => s.subGraphSelectableNodeTypes)
|
||||
const [isLoadedVars, setIsLoadedVars] = useState(false)
|
||||
const [vars, setVars] = useState<VarInInspect[]>([])
|
||||
useEffect(() => {
|
||||
@ -393,12 +397,17 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
|
||||
const handleNodeClickInMode = useCallback<NodeMouseHandler>(
|
||||
(event, node) => {
|
||||
if (isSubGraph && node.data.type !== BlockEnum.LLM)
|
||||
return
|
||||
if (isSubGraph) {
|
||||
const allowTypes = subGraphSelectableNodeTypes?.length
|
||||
? subGraphSelectableNodeTypes
|
||||
: [BlockEnum.LLM]
|
||||
if (!allowTypes.includes(node.data.type))
|
||||
return
|
||||
}
|
||||
|
||||
handleNodeClick(event, node)
|
||||
},
|
||||
[handleNodeClick, isSubGraph],
|
||||
[handleNodeClick, isSubGraph, subGraphSelectableNodeTypes],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@ -351,6 +351,25 @@ const VarReferenceVars: FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
showAssembleVariables && (
|
||||
<div className="flex items-center border-t border-divider-subtle pt-1">
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-full items-center rounded-md pl-3 pr-[18px] text-text-secondary hover:bg-state-base-hover"
|
||||
onClick={handleAssembleVariables}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
>
|
||||
<span className="mr-1 flex h-4 w-4 items-center justify-center rounded bg-util-colors-blue-blue-500">
|
||||
<AssembleVariables className="h-3 w-3 text-text-primary-on-surface" />
|
||||
</span>
|
||||
<span className="system-xs-medium truncate" title={t('nodes.tool.assembleVariables', { ns: 'workflow' })}>
|
||||
{t('nodes.tool.assembleVariables', { ns: 'workflow' })}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{filteredVars.length > 0
|
||||
? (
|
||||
<div className={cn('max-h-[85vh] overflow-y-auto', maxHeightClass)}>
|
||||
@ -404,25 +423,6 @@ const VarReferenceVars: FC<Props> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
showAssembleVariables && (
|
||||
<div className="flex items-center border-t border-divider-subtle pt-1">
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-full items-center rounded-md pl-3 pr-[18px] text-text-secondary hover:bg-state-base-hover"
|
||||
onClick={handleAssembleVariables}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
>
|
||||
<span className="mr-1 flex h-4 w-4 items-center justify-center rounded bg-util-colors-blue-blue-500">
|
||||
<AssembleVariables className="h-3 w-3 text-text-primary-on-surface" />
|
||||
</span>
|
||||
<span className="system-xs-medium truncate" title={t('nodes.tool.assembleVariables', { ns: 'workflow' })}>
|
||||
{t('nodes.tool.assembleVariables', { ns: 'workflow' })}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const CUSTOM_SUB_GRAPH_START_NODE = 'custom-sub-graph-start'
|
||||
60
web/app/components/workflow/nodes/sub-graph-start/index.tsx
Normal file
60
web/app/components/workflow/nodes/sub-graph-start/index.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AssembleVariablesAlt } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { NodeSourceHandle } from '@/app/components/workflow/nodes/_base/components/node-handle'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type SubGraphStartNodeData = CommonNodeType<{
|
||||
tooltip?: string
|
||||
iconType?: string
|
||||
}>
|
||||
|
||||
type IconComponent = typeof Agent
|
||||
|
||||
const iconMap: Record<string, IconComponent> = {
|
||||
agent: Agent,
|
||||
assemble: AssembleVariablesAlt,
|
||||
}
|
||||
|
||||
const SubGraphStartNode = ({ id, data }: NodeProps<SubGraphStartNodeData>) => {
|
||||
const { t } = useTranslation()
|
||||
const iconType = data?.iconType || 'agent'
|
||||
const Icon = iconMap[iconType] || Agent
|
||||
const rawTitle = data?.title?.trim() || ''
|
||||
const showTitle = iconType === 'agent' && !!rawTitle
|
||||
const displayTitle = showTitle && (rawTitle.startsWith('@') ? rawTitle : `@${rawTitle}`)
|
||||
const tooltip = data?.tooltip
|
||||
|| (iconType === 'assemble' ? t('blocks.start', { ns: 'workflow' }) : (data?.title || t('blocks.start', { ns: 'workflow' })))
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'nodrag group mt-1 flex h-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg shadow-xs',
|
||||
showTitle ? 'gap-1.5 px-2' : 'w-11',
|
||||
)}
|
||||
>
|
||||
<Tooltip popupContent={tooltip} asChild={false}>
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500">
|
||||
<Icon className="h-3 w-3 text-text-primary-on-surface" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{showTitle && (
|
||||
<span className="system-xs-medium max-w-[160px] truncate text-text-secondary">
|
||||
{displayTitle}
|
||||
</span>
|
||||
)}
|
||||
<NodeSourceHandle
|
||||
id={id}
|
||||
data={data}
|
||||
handleClassName="!top-1/2 !-right-[9px] !-translate-y-1/2"
|
||||
handleId="source"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(SubGraphStartNode)
|
||||
@ -2,6 +2,7 @@ import type { FC } from 'react'
|
||||
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AssembleVariables } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle'
|
||||
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -34,8 +35,11 @@ const AgentHeaderBar: FC<AgentHeaderBarProps> = ({
|
||||
: 'border-components-panel-border-subtle bg-components-badge-white-to-dark',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-4 w-4 items-center justify-center rounded bg-util-colors-indigo-indigo-500">
|
||||
<Agent className="h-3 w-3 text-text-primary-on-surface" />
|
||||
<div className={cn('flex h-4 w-4 items-center justify-center rounded', showAtPrefix
|
||||
? 'bg-util-colors-indigo-indigo-500'
|
||||
: 'bg-util-colors-blue-blue-500')}
|
||||
>
|
||||
{showAtPrefix ? <Agent className="h-3 w-3 text-text-primary-on-surface" /> : <AssembleVariables className="h-3 w-3 text-text-primary-on-surface" />}
|
||||
</div>
|
||||
<span className="system-xs-medium text-text-secondary">
|
||||
{showAtPrefix && '@'}
|
||||
|
||||
@ -36,9 +36,16 @@ import Placeholder from './placeholder'
|
||||
|
||||
/**
|
||||
* Matches agent context variable syntax: {{@nodeId.context@}}
|
||||
* Example: {{@agent-123.context@}} -> captures "agent-123"
|
||||
* Example: {{@agent-123.context@}}
|
||||
*/
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{@([^.@#]+)\.context@\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{@[^.@#]+\.context@\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PREFIX = '{{@'
|
||||
const AGENT_CONTEXT_VAR_SUFFIX = '.context@}}'
|
||||
const getAgentNodeIdFromContextVar = (placeholder: string) => {
|
||||
if (!placeholder.startsWith(AGENT_CONTEXT_VAR_PREFIX) || !placeholder.endsWith(AGENT_CONTEXT_VAR_SUFFIX))
|
||||
return ''
|
||||
return placeholder.slice(AGENT_CONTEXT_VAR_PREFIX.length, -AGENT_CONTEXT_VAR_SUFFIX.length)
|
||||
}
|
||||
|
||||
const buildAssemblePlaceholder = (toolNodeId?: string, paramKey?: string) => {
|
||||
if (!toolNodeId || !paramKey)
|
||||
@ -309,8 +316,9 @@ const MixedVariableTextInput = ({
|
||||
|
||||
const matches = text.matchAll(AGENT_CONTEXT_VAR_PATTERN)
|
||||
for (const match of matches) {
|
||||
const variablePath = match[1]
|
||||
const nodeId = variablePath.split('.')[0]
|
||||
const nodeId = getAgentNodeIdFromContextVar(match[0])
|
||||
if (!nodeId)
|
||||
continue
|
||||
const node = nodesByIdMap[nodeId]
|
||||
if (node && contextNodeIds.has(nodeId)) {
|
||||
return {
|
||||
@ -461,8 +469,8 @@ const MixedVariableTextInput = ({
|
||||
if (!agentNodeId || !onChange)
|
||||
return
|
||||
|
||||
const valueWithoutAgentVars = value.replace(AGENT_CONTEXT_VAR_PATTERN, (match, variablePath) => {
|
||||
const nodeId = variablePath.split('.')[0]
|
||||
const valueWithoutAgentVars = value.replace(AGENT_CONTEXT_VAR_PATTERN, (match) => {
|
||||
const nodeId = getAgentNodeIdFromContextVar(match)
|
||||
return nodeId === agentNodeId ? '' : match
|
||||
})
|
||||
|
||||
@ -552,6 +560,7 @@ const MixedVariableTextInput = ({
|
||||
<AgentHeaderBar
|
||||
agentName={t('nodes.tool.assembleVariables', { ns: 'workflow' })}
|
||||
onRemove={handleAssembleRemove}
|
||||
onViewInternals={handleOpenSubGraphModal}
|
||||
hasWarning={hasAssembleWarning}
|
||||
showAtPrefix={false}
|
||||
/>
|
||||
@ -599,10 +608,21 @@ const MixedVariableTextInput = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{toolNodeId && detectedAgentFromValue && sourceVariable && (
|
||||
{toolNodeId && paramKey && isAssembleValue && (
|
||||
<SubGraphModal
|
||||
isOpen={isSubGraphModalOpen}
|
||||
onClose={handleCloseSubGraphModal}
|
||||
variant="assemble"
|
||||
toolNodeId={toolNodeId}
|
||||
paramKey={paramKey}
|
||||
title={t('nodes.tool.assembleVariables', { ns: 'workflow' })}
|
||||
/>
|
||||
)}
|
||||
{toolNodeId && paramKey && !isAssembleValue && detectedAgentFromValue && sourceVariable && (
|
||||
<SubGraphModal
|
||||
isOpen={isSubGraphModalOpen}
|
||||
onClose={handleCloseSubGraphModal}
|
||||
variant="agent"
|
||||
toolNodeId={toolNodeId}
|
||||
paramKey={paramKey}
|
||||
sourceVariable={sourceVariable}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type { SubGraphModalProps } from './types'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { ToolNodeType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import type { Node, PromptItem, PromptTemplateItem } from '@/app/components/workflow/types'
|
||||
@ -11,24 +12,29 @@ import { noop } from 'es-toolkit/function'
|
||||
import { Fragment, memo, useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useReactFlowStore, useStoreApi } from 'reactflow'
|
||||
import { AssembleVariablesAlt } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { useIsChatMode, useNodesSyncDraft, useWorkflow, useWorkflowVariables } from '@/app/components/workflow/hooks'
|
||||
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||
import { VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
import { useStore as useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { BlockEnum, EditionType, isPromptMessageContext, PromptRole } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, EditionType, isPromptMessageContext, PromptRole, VarType } from '@/app/components/workflow/types'
|
||||
import SubGraphCanvas from './sub-graph-canvas'
|
||||
|
||||
const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
toolNodeId,
|
||||
paramKey,
|
||||
sourceVariable,
|
||||
agentName,
|
||||
agentNodeId,
|
||||
}) => {
|
||||
const SubGraphModal: FC<SubGraphModalProps> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const { isOpen, onClose, variant, toolNodeId, paramKey } = props
|
||||
const isAgentVariant = variant === 'agent'
|
||||
const resolvedAgentNodeId = isAgentVariant ? props.agentNodeId : ''
|
||||
const agentName = isAgentVariant ? props.agentName : ''
|
||||
const assembleTitle = !isAgentVariant ? props.title : ''
|
||||
const modalTitle = useMemo(() => {
|
||||
const baseTitle = isAgentVariant
|
||||
? agentName
|
||||
: (assembleTitle || t('nodes.tool.assembleVariables', { ns: 'workflow' }))
|
||||
const prefix = isAgentVariant && baseTitle ? '@' : ''
|
||||
return `${prefix}${baseTitle} ${t('subGraphModal.title', { ns: 'workflow' })}`.trim()
|
||||
}, [agentName, assembleTitle, isAgentVariant, t])
|
||||
const reactflowStore = useStoreApi()
|
||||
const workflowNodes = useWorkflowStore(state => state.nodes)
|
||||
const workflowEdges = useReactFlowStore(state => state.edges)
|
||||
@ -41,13 +47,16 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
|
||||
const extractorNodeId = `${toolNodeId}_ext_${paramKey}`
|
||||
const extractorNode = useMemo(() => {
|
||||
return workflowNodes.find(node => node.id === extractorNodeId) as Node<LLMNodeType> | undefined
|
||||
return workflowNodes.find(node => node.id === extractorNodeId) as Node<LLMNodeType | CodeNodeType> | undefined
|
||||
}, [extractorNodeId, workflowNodes])
|
||||
const toolNode = useMemo(() => {
|
||||
return workflowNodes.find(node => node.id === toolNodeId)
|
||||
}, [toolNodeId, workflowNodes])
|
||||
const toolParam = (toolNode?.data as ToolNodeType | undefined)?.tool_parameters?.[paramKey]
|
||||
const toolParamValue = toolParam?.value as string | undefined
|
||||
const assemblePlaceholder = useMemo(() => {
|
||||
return `{{#${toolNodeId}_ext_${paramKey}.result#}}`
|
||||
}, [paramKey, toolNodeId])
|
||||
|
||||
const parentBeforeNodes = useMemo(() => {
|
||||
if (!isOpen)
|
||||
@ -56,25 +65,28 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
}, [getBeforeNodesInSameBranch, isOpen, toolNodeId, workflowEdges, workflowNodes])
|
||||
|
||||
const parentContextNodes = useMemo(() => {
|
||||
if (!parentBeforeNodes.length)
|
||||
if (!parentBeforeNodes.length || !isAgentVariant)
|
||||
return []
|
||||
return parentBeforeNodes.filter(node => node.data.type === BlockEnum.Agent || node.data.type === BlockEnum.LLM)
|
||||
}, [parentBeforeNodes])
|
||||
}, [isAgentVariant, parentBeforeNodes])
|
||||
|
||||
const parentContextNodeIds = useMemo(() => {
|
||||
return parentContextNodes.map(node => node.id)
|
||||
}, [parentContextNodes])
|
||||
const parentAvailableNodes = useMemo(() => {
|
||||
if (!isOpen)
|
||||
return []
|
||||
return isAgentVariant ? parentContextNodes : parentBeforeNodes
|
||||
}, [isAgentVariant, isOpen, parentBeforeNodes, parentContextNodes])
|
||||
|
||||
const parentAvailableVars = useMemo(() => {
|
||||
if (!parentContextNodeIds.length)
|
||||
if (!parentAvailableNodes.length)
|
||||
return []
|
||||
const vars = getNodeAvailableVars({
|
||||
beforeNodes: parentContextNodes,
|
||||
beforeNodes: parentAvailableNodes,
|
||||
isChatMode,
|
||||
filterVar: () => true,
|
||||
})
|
||||
return vars.filter(nodeVar => parentContextNodeIds.includes(nodeVar.nodeId))
|
||||
}, [getNodeAvailableVars, isChatMode, parentContextNodeIds, parentContextNodes])
|
||||
const availableNodeIds = new Set(parentAvailableNodes.map(node => node.id))
|
||||
return vars.filter(nodeVar => availableNodeIds.has(nodeVar.nodeId))
|
||||
}, [getNodeAvailableVars, isChatMode, parentAvailableNodes])
|
||||
|
||||
const mentionConfig = useMemo<MentionConfig>(() => {
|
||||
const current = toolParam?.mention_config
|
||||
@ -91,6 +103,9 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
}, [extractorNodeId, paramKey, toolParam?.mention_config])
|
||||
|
||||
const handleMentionConfigChange = useCallback((config: MentionConfig) => {
|
||||
if (!isAgentVariant)
|
||||
return
|
||||
|
||||
const { getNodes, setNodes } = reactflowStore.getState()
|
||||
const nextNodes = getNodes().map((node) => {
|
||||
if (node.id !== toolNodeId)
|
||||
@ -118,10 +133,10 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
})
|
||||
setNodes(nextNodes)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [handleSyncWorkflowDraft, paramKey, reactflowStore, toolNodeId])
|
||||
}, [handleSyncWorkflowDraft, isAgentVariant, paramKey, reactflowStore, toolNodeId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!toolParam || (toolParam.type && toolParam.type !== VarKindType.mention))
|
||||
if (!isAgentVariant || !toolParam || (toolParam.type && toolParam.type !== VarKindType.mention))
|
||||
return
|
||||
|
||||
const current = toolParam.mention_config
|
||||
@ -132,7 +147,7 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
|
||||
if (needsExtractor || needsNullStrategy || needsOutputSelector || needsDefaultValue)
|
||||
handleMentionConfigChange(mentionConfig)
|
||||
}, [handleMentionConfigChange, mentionConfig, toolParam])
|
||||
}, [handleMentionConfigChange, isAgentVariant, mentionConfig, toolParam])
|
||||
|
||||
const getUserPromptText = useCallback((promptTemplate?: PromptTemplateItem[] | PromptItem) => {
|
||||
if (!promptTemplate)
|
||||
@ -156,23 +171,46 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
|
||||
// TODO: handle external workflow updates while sub-graph modal is open.
|
||||
const handleSave = useCallback((subGraphNodes: Node[]) => {
|
||||
const extractorNodeData = subGraphNodes.find(node => node.id === extractorNodeId) as Node<LLMNodeType> | undefined
|
||||
const extractorNodeData = subGraphNodes.find(node => node.id === extractorNodeId) as Node<LLMNodeType | CodeNodeType> | undefined
|
||||
if (!extractorNodeData)
|
||||
return
|
||||
|
||||
const userPromptText = getUserPromptText(extractorNodeData.data?.prompt_template)
|
||||
const placeholder = `{{@${agentNodeId}.context@}}`
|
||||
const nextValue = `${placeholder}${userPromptText}`
|
||||
const ensureAssembleOutputs = (payload: CodeNodeType) => {
|
||||
const outputs = payload.outputs || {}
|
||||
if (outputs.result)
|
||||
return payload
|
||||
return {
|
||||
...payload,
|
||||
outputs: {
|
||||
...outputs,
|
||||
result: {
|
||||
type: VarType.string,
|
||||
children: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const userPromptText = isAgentVariant
|
||||
? getUserPromptText((extractorNodeData.data as LLMNodeType).prompt_template)
|
||||
: ''
|
||||
const placeholder = isAgentVariant && resolvedAgentNodeId ? `{{@${resolvedAgentNodeId}.context@}}` : ''
|
||||
const nextValue = isAgentVariant
|
||||
? `${placeholder}${userPromptText}`
|
||||
: assemblePlaceholder
|
||||
|
||||
const { getNodes, setNodes } = reactflowStore.getState()
|
||||
const nextNodes = getNodes().map((node) => {
|
||||
if (node.id === extractorNodeId) {
|
||||
const nextData = isAgentVariant
|
||||
? extractorNodeData.data
|
||||
: ensureAssembleOutputs(extractorNodeData.data as CodeNodeType)
|
||||
return {
|
||||
...node,
|
||||
hidden: true,
|
||||
data: {
|
||||
...node.data,
|
||||
...extractorNodeData.data,
|
||||
...nextData,
|
||||
parent_node_id: toolNodeId,
|
||||
},
|
||||
}
|
||||
@ -200,7 +238,7 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
})
|
||||
setNodes(nextNodes)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [agentNodeId, extractorNodeId, getUserPromptText, paramKey, reactflowStore, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
}, [assemblePlaceholder, extractorNodeId, getUserPromptText, isAgentVariant, paramKey, reactflowStore, resolvedAgentNodeId, setControlPromptEditorRerenderKey, toolNodeId])
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
@ -215,13 +253,12 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
<div className="flex h-14 shrink-0 items-center justify-between border-b border-divider-subtle px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded bg-util-colors-indigo-indigo-500">
|
||||
<Agent className="h-4 w-4 text-text-primary-on-surface" />
|
||||
{isAgentVariant
|
||||
? <Agent className="h-4 w-4 text-text-primary-on-surface" />
|
||||
: <AssembleVariablesAlt className="h-4 w-4 text-text-primary-on-surface" />}
|
||||
</div>
|
||||
<span className="system-md-semibold text-text-primary">
|
||||
@
|
||||
{agentName}
|
||||
{' '}
|
||||
{t('subGraphModal.title', { ns: 'workflow' })}
|
||||
{modalTitle}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
@ -234,22 +271,41 @@ const SubGraphModal: FC<SubGraphModalProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="bg-workflow-canvas-wrapper relative flex-1 overflow-hidden">
|
||||
<SubGraphCanvas
|
||||
toolNodeId={toolNodeId}
|
||||
paramKey={paramKey}
|
||||
sourceVariable={sourceVariable}
|
||||
agentNodeId={agentNodeId}
|
||||
agentName={agentName}
|
||||
configsMap={configsMap}
|
||||
mentionConfig={mentionConfig}
|
||||
onMentionConfigChange={handleMentionConfigChange}
|
||||
extractorNode={extractorNode}
|
||||
toolParamValue={toolParamValue}
|
||||
parentAvailableNodes={parentContextNodes}
|
||||
parentAvailableVars={parentAvailableVars}
|
||||
onSave={handleSave}
|
||||
onSyncWorkflowDraft={doSyncWorkflowDraft}
|
||||
/>
|
||||
{variant === 'agent'
|
||||
? (
|
||||
<SubGraphCanvas
|
||||
variant="agent"
|
||||
toolNodeId={toolNodeId}
|
||||
paramKey={paramKey}
|
||||
sourceVariable={props.sourceVariable}
|
||||
agentNodeId={props.agentNodeId}
|
||||
agentName={props.agentName}
|
||||
configsMap={configsMap}
|
||||
mentionConfig={mentionConfig}
|
||||
onMentionConfigChange={handleMentionConfigChange}
|
||||
extractorNode={extractorNode as Node<LLMNodeType> | undefined}
|
||||
toolParamValue={toolParamValue}
|
||||
parentAvailableNodes={parentAvailableNodes}
|
||||
parentAvailableVars={parentAvailableVars}
|
||||
onSave={handleSave}
|
||||
onSyncWorkflowDraft={doSyncWorkflowDraft}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<SubGraphCanvas
|
||||
variant="assemble"
|
||||
toolNodeId={toolNodeId}
|
||||
paramKey={paramKey}
|
||||
title={props.title}
|
||||
configsMap={configsMap}
|
||||
extractorNode={extractorNode as Node<CodeNodeType> | undefined}
|
||||
toolParamValue={toolParamValue}
|
||||
parentAvailableNodes={parentAvailableNodes}
|
||||
parentAvailableVars={parentAvailableVars}
|
||||
onSave={handleSave}
|
||||
onSyncWorkflowDraft={doSyncWorkflowDraft}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
|
||||
@ -4,40 +4,10 @@ import type { SubGraphCanvasProps } from './types'
|
||||
import { memo } from 'react'
|
||||
import SubGraph from '@/app/components/sub-graph'
|
||||
|
||||
const SubGraphCanvas: FC<SubGraphCanvasProps> = ({
|
||||
toolNodeId,
|
||||
paramKey,
|
||||
sourceVariable,
|
||||
agentNodeId,
|
||||
agentName,
|
||||
configsMap,
|
||||
mentionConfig,
|
||||
onMentionConfigChange,
|
||||
extractorNode,
|
||||
toolParamValue,
|
||||
parentAvailableNodes,
|
||||
parentAvailableVars,
|
||||
onSave,
|
||||
onSyncWorkflowDraft,
|
||||
}) => {
|
||||
const SubGraphCanvas: FC<SubGraphCanvasProps> = (props) => {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<SubGraph
|
||||
toolNodeId={toolNodeId}
|
||||
paramKey={paramKey}
|
||||
sourceVariable={sourceVariable}
|
||||
agentNodeId={agentNodeId}
|
||||
agentName={agentName}
|
||||
configsMap={configsMap}
|
||||
mentionConfig={mentionConfig}
|
||||
onMentionConfigChange={onMentionConfigChange}
|
||||
extractorNode={extractorNode}
|
||||
toolParamValue={toolParamValue}
|
||||
parentAvailableNodes={parentAvailableNodes}
|
||||
parentAvailableVars={parentAvailableVars}
|
||||
onSave={onSave}
|
||||
onSyncWorkflowDraft={onSyncWorkflowDraft}
|
||||
/>
|
||||
<SubGraph {...props} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,34 +1,25 @@
|
||||
import type { SyncWorkflowDraft } from '@/app/components/sub-graph/types'
|
||||
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store'
|
||||
import type { MentionConfig } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { NodeOutPutVar, Edge as WorkflowEdge, Node as WorkflowNode } from '@/app/components/workflow/types'
|
||||
import type { SubGraphProps } from '@/app/components/sub-graph/types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
type WorkflowValueSelector = string[]
|
||||
|
||||
export type SubGraphModalProps = {
|
||||
type BaseSubGraphModalProps = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
toolNodeId: string
|
||||
paramKey: string
|
||||
sourceVariable: WorkflowValueSelector
|
||||
}
|
||||
|
||||
type AgentSubGraphModalProps = BaseSubGraphModalProps & {
|
||||
variant: 'agent'
|
||||
sourceVariable: ValueSelector
|
||||
agentName: string
|
||||
agentNodeId: string
|
||||
}
|
||||
|
||||
export type SubGraphCanvasProps = {
|
||||
toolNodeId: string
|
||||
paramKey: string
|
||||
sourceVariable: WorkflowValueSelector
|
||||
agentNodeId: string
|
||||
agentName: string
|
||||
configsMap?: HooksStoreShape['configsMap']
|
||||
mentionConfig: MentionConfig
|
||||
onMentionConfigChange: (config: MentionConfig) => void
|
||||
extractorNode?: WorkflowNode<LLMNodeType>
|
||||
toolParamValue?: string
|
||||
parentAvailableNodes?: WorkflowNode[]
|
||||
parentAvailableVars?: NodeOutPutVar[]
|
||||
onSave?: (nodes: WorkflowNode[], edges: WorkflowEdge[]) => void
|
||||
onSyncWorkflowDraft?: SyncWorkflowDraft
|
||||
type AssembleSubGraphModalProps = BaseSubGraphModalProps & {
|
||||
variant: 'assemble'
|
||||
title: string
|
||||
}
|
||||
|
||||
export type SubGraphModalProps = AgentSubGraphModalProps | AssembleSubGraphModalProps
|
||||
|
||||
export type SubGraphCanvasProps = SubGraphProps
|
||||
|
||||
@ -20,7 +20,14 @@ import { useStrategyProviders } from '@/service/use-strategy'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { VarType } from './types'
|
||||
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{@([^.@#]+)\.context@\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{@[^.@#]+\.context@\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PREFIX = '{{@'
|
||||
const AGENT_CONTEXT_VAR_SUFFIX = '.context@}}'
|
||||
const getAgentNodeIdFromContextVar = (placeholder: string) => {
|
||||
if (!placeholder.startsWith(AGENT_CONTEXT_VAR_PREFIX) || !placeholder.endsWith(AGENT_CONTEXT_VAR_SUFFIX))
|
||||
return ''
|
||||
return placeholder.slice(AGENT_CONTEXT_VAR_PREFIX.length, -AGENT_CONTEXT_VAR_SUFFIX.length)
|
||||
}
|
||||
type AgentCheckValidContext = {
|
||||
provider?: StrategyPluginDetail
|
||||
strategy?: StrategyDetail
|
||||
@ -80,7 +87,7 @@ const Node: FC<NodeProps<ToolNodeType>> = ({
|
||||
return
|
||||
const matches = value.matchAll(AGENT_CONTEXT_VAR_PATTERN)
|
||||
for (const match of matches) {
|
||||
const agentNodeId = match[1]
|
||||
const agentNodeId = getAgentNodeIdFromContextVar(match[0])
|
||||
if (!agentNodeId)
|
||||
continue
|
||||
const entryKey = `${paramKey}:${agentNodeId}`
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
} from '@/app/components/workflow/constants'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
|
||||
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
|
||||
import { CUSTOM_SUB_GRAPH_START_NODE } from '@/app/components/workflow/nodes/sub-graph-start/constants'
|
||||
import {
|
||||
BlockEnum,
|
||||
} from '@/app/components/workflow/types'
|
||||
@ -442,6 +443,7 @@ const normaliseChildLayout = (
|
||||
const startNode = nodes.find(node =>
|
||||
node.type === CUSTOM_ITERATION_START_NODE
|
||||
|| node.type === CUSTOM_LOOP_START_NODE
|
||||
|| node.type === CUSTOM_SUB_GRAPH_START_NODE
|
||||
|| node.data?.type === BlockEnum.LoopStart
|
||||
|| node.data?.type === BlockEnum.IterationStart,
|
||||
)
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AssembleVariablesAlt } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { NodeSourceHandle } from '../../node-handle'
|
||||
|
||||
type SubGraphStartNodeData = CommonNodeType<{
|
||||
tooltip?: string
|
||||
iconType?: string
|
||||
}>
|
||||
|
||||
type IconComponent = typeof Agent
|
||||
|
||||
const iconMap: Record<string, IconComponent> = {
|
||||
agent: Agent,
|
||||
assemble: AssembleVariablesAlt,
|
||||
}
|
||||
|
||||
const SubGraphStartNode = ({ id, data }: NodeProps<SubGraphStartNodeData>) => {
|
||||
const { t } = useTranslation()
|
||||
const iconType = data?.iconType || 'agent'
|
||||
const Icon = iconMap[iconType] || Agent
|
||||
const rawTitle = data?.title?.trim() || ''
|
||||
const showTitle = iconType === 'agent' && !!rawTitle
|
||||
const displayTitle = showTitle && (rawTitle.startsWith('@') ? rawTitle : `@${rawTitle}`)
|
||||
const tooltip = data?.tooltip
|
||||
|| (iconType === 'assemble' ? t('blocks.start', { ns: 'workflow' }) : (data?.title || t('blocks.start', { ns: 'workflow' })))
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'nodrag group mt-1 flex h-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg shadow-xs',
|
||||
showTitle ? 'gap-1.5 px-2' : 'w-11',
|
||||
)}
|
||||
>
|
||||
<Tooltip popupContent={tooltip} asChild={false}>
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500">
|
||||
<Icon className="h-3 w-3 text-text-primary-on-surface" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{showTitle && (
|
||||
<span className="system-xs-medium max-w-[160px] truncate text-text-secondary">
|
||||
{displayTitle}
|
||||
</span>
|
||||
)}
|
||||
<NodeSourceHandle
|
||||
id={id}
|
||||
data={data}
|
||||
handleClassName="!top-1/2 !-right-[9px] !-translate-y-1/2"
|
||||
handleId="source"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(SubGraphStartNode)
|
||||
@ -29,6 +29,7 @@ import {
|
||||
import CustomConnectionLine from '@/app/components/workflow/custom-connection-line'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
|
||||
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
|
||||
import { CUSTOM_SUB_GRAPH_START_NODE } from '@/app/components/workflow/nodes/sub-graph-start/constants'
|
||||
import { CUSTOM_NOTE_NODE } from '@/app/components/workflow/note-node/constants'
|
||||
import { CUSTOM_SIMPLE_NODE } from '@/app/components/workflow/simple-node/constants'
|
||||
import {
|
||||
@ -40,6 +41,7 @@ import CustomEdge from './components/custom-edge'
|
||||
import CustomNode from './components/nodes'
|
||||
import IterationStartNode from './components/nodes/iteration-start'
|
||||
import LoopStartNode from './components/nodes/loop-start'
|
||||
import SubGraphStartNode from './components/nodes/sub-graph-start'
|
||||
import CustomNoteNode from './components/note-node'
|
||||
import ZoomInOut from './components/zoom-in-out'
|
||||
import 'reactflow/dist/style.css'
|
||||
@ -49,6 +51,7 @@ const nodeTypes = {
|
||||
[CUSTOM_NODE]: CustomNode,
|
||||
[CUSTOM_NOTE_NODE]: CustomNoteNode,
|
||||
[CUSTOM_SIMPLE_NODE]: CustomNode,
|
||||
[CUSTOM_SUB_GRAPH_START_NODE]: SubGraphStartNode,
|
||||
[CUSTOM_ITERATION_START_NODE]: IterationStartNode,
|
||||
[CUSTOM_LOOP_START_NODE]: LoopStartNode,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user