Selaa lähdekoodia

feat(workflow): add relations panel to visualize dependencies (#21998)

Co-authored-by: crazywoola <427733928@qq.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
tags/1.7.2
Minamiyama 2 kuukautta sitten
vanhempi
commit
4934dbd0e6
No account linked to committer's email address

+ 2
- 1
web/app/components/workflow/custom-edge.tsx Näytä tiedosto

@@ -134,7 +134,8 @@ const CustomEdge = ({
style={{
stroke,
strokeWidth: 2,
opacity: data._waitingRun ? 0.7 : 1,
opacity: data._dimmed ? 0.3 : (data._waitingRun ? 0.7 : 1),
strokeDasharray: data._isTemp ? '8 8' : undefined,
}}
/>
<EdgeLabelRenderer>

+ 133
- 1
web/app/components/workflow/hooks/use-nodes-interactions.ts Näytä tiedosto

@@ -1,5 +1,5 @@
import type { MouseEvent } from 'react'
import { useCallback, useRef } from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
import type {
@@ -61,6 +61,7 @@ import {
} from './use-workflow'
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
import useInspectVarsCrud from './use-inspect-vars-crud'
import { getNodeUsedVars } from '../nodes/_base/components/variable/utils'

export const useNodesInteractions = () => {
const { t } = useTranslation()
@@ -1530,6 +1531,135 @@ export const useNodesInteractions = () => {
setNodes(nodes)
}, [redo, store, workflowHistoryStore, getNodesReadOnly, getWorkflowReadOnly])

const [isDimming, setIsDimming] = useState(false)
/** Add opacity-30 to all nodes except the nodeId */
const dimOtherNodes = useCallback(() => {
if (isDimming)
return
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()

const selectedNode = nodes.find(n => n.data.selected)
if (!selectedNode)
return

setIsDimming(true)

// const workflowNodes = useStore(s => s.getNodes())
const workflowNodes = nodes

const usedVars = getNodeUsedVars(selectedNode)
const dependencyNodes: Node[] = []
usedVars.forEach((valueSelector) => {
const node = workflowNodes.find(node => node.id === valueSelector?.[0])
if (node) {
if (!dependencyNodes.includes(node))
dependencyNodes.push(node)
}
})

const outgoers = getOutgoers(selectedNode as Node, nodes as Node[], edges)
for (let currIdx = 0; currIdx < outgoers.length; currIdx++) {
const node = outgoers[currIdx]
const outgoersForNode = getOutgoers(node, nodes as Node[], edges)
outgoersForNode.forEach((item) => {
const existed = outgoers.some(v => v.id === item.id)
if (!existed)
outgoers.push(item)
})
}

const dependentNodes: Node[] = []
outgoers.forEach((node) => {
const usedVars = getNodeUsedVars(node)
const used = usedVars.some(v => v?.[0] === selectedNode.id)
if (used) {
const existed = dependentNodes.some(v => v.id === node.id)
if (!existed)
dependentNodes.push(node)
}
})

const dimNodes = [...dependencyNodes, ...dependentNodes, selectedNode]

const newNodes = produce(nodes, (draft) => {
draft.forEach((n) => {
const dimNode = dimNodes.find(v => v.id === n.id)
if (!dimNode)
n.data._dimmed = true
})
})

setNodes(newNodes)

const tempEdges: Edge[] = []

dependencyNodes.forEach((n) => {
tempEdges.push({
id: `tmp_${n.id}-source-${selectedNode.id}-target`,
type: CUSTOM_EDGE,
source: n.id,
sourceHandle: 'source_tmp',
target: selectedNode.id,
targetHandle: 'target_tmp',
animated: true,
data: {
sourceType: n.data.type,
targetType: selectedNode.data.type,
_isTemp: true,
_connectedNodeIsHovering: true,
},
})
})
dependentNodes.forEach((n) => {
tempEdges.push({
id: `tmp_${selectedNode.id}-source-${n.id}-target`,
type: CUSTOM_EDGE,
source: selectedNode.id,
sourceHandle: 'source_tmp',
target: n.id,
targetHandle: 'target_tmp',
animated: true,
data: {
sourceType: selectedNode.data.type,
targetType: n.data.type,
_isTemp: true,
_connectedNodeIsHovering: true,
},
})
})

const newEdges = produce(edges, (draft) => {
draft.forEach((e) => {
e.data._dimmed = true
})
draft.push(...tempEdges)
})
setEdges(newEdges)
}, [isDimming, store])

/** Restore all nodes to full opacity */
const undimAllNodes = useCallback(() => {
const { getNodes, setNodes, edges, setEdges } = store.getState()
const nodes = getNodes()
setIsDimming(false)

const newNodes = produce(nodes, (draft) => {
draft.forEach((n) => {
n.data._dimmed = false
})
})

setNodes(newNodes)

const newEdges = produce(edges.filter(e => !e.data._isTemp), (draft) => {
draft.forEach((e) => {
e.data._dimmed = false
})
})
setEdges(newEdges)
}, [store])

return {
handleNodeDragStart,
handleNodeDrag,
@@ -1554,5 +1684,7 @@ export const useNodesInteractions = () => {
handleNodeDisconnect,
handleHistoryBack,
handleHistoryForward,
dimOtherNodes,
undimAllNodes,
}
}

+ 33
- 0
web/app/components/workflow/hooks/use-shortcuts.ts Näytä tiedosto

@@ -25,6 +25,8 @@ export const useShortcuts = (): void => {
handleNodesDelete,
handleHistoryBack,
handleHistoryForward,
dimOtherNodes,
undimAllNodes,
} = useNodesInteractions()
const { handleStartWorkflowRun } = useWorkflowStartRun()
const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore()
@@ -211,4 +213,35 @@ export const useShortcuts = (): void => {
exactMatch: true,
useCapture: true,
})

// Shift ↓
useKeyPress(
'shift',
(e) => {
console.log('Shift down', e)
if (shouldHandleShortcut(e))
dimOtherNodes()
},
{
exactMatch: true,
useCapture: true,
events: ['keydown'],
},
)

// Shift ↑
useKeyPress(
(e) => {
return e.key === 'Shift'
},
(e) => {
if (shouldHandleShortcut(e))
undimAllNodes()
},
{
exactMatch: true,
useCapture: true,
events: ['keyup'],
},
)
}

+ 1
- 0
web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx Näytä tiedosto

@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'
export enum TabType {
settings = 'settings',
lastRun = 'lastRun',
relations = 'relations',
}

type Props = {

+ 1
- 0
web/app/components/workflow/nodes/_base/node.tsx Näytä tiedosto

@@ -143,6 +143,7 @@ const BaseNode: FC<BaseNodeProps> = ({
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
data._waitingRun && 'opacity-70',
data._dimmed && 'opacity-30',
)}
ref={nodeRef}
style={{

+ 3
- 1
web/app/components/workflow/types.ts Näytä tiedosto

@@ -94,6 +94,7 @@ export type CommonNodeType<T = {}> = {
retry_config?: WorkflowRetryConfig
default_value?: DefaultValueForm[]
credential_id?: string
_dimmed?: boolean
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>

export type CommonEdgeType = {
@@ -109,7 +110,8 @@ export type CommonEdgeType = {
isInLoop?: boolean
loop_id?: string
sourceType: BlockEnum
targetType: BlockEnum
targetType: BlockEnum,
_isTemp?: boolean,
}

export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>>

+ 9
- 0
web/i18n/en-US/workflow.ts Näytä tiedosto

@@ -943,6 +943,7 @@ const translation = {
debug: {
settingsTab: 'Settings',
lastRunTab: 'Last Run',
relationsTab: 'Relations',
noData: {
description: 'The results of the last run will be displayed here',
runThisNode: 'Run this node',
@@ -968,6 +969,14 @@ const translation = {
chatNode: 'Conversation',
systemNode: 'System',
},
relations: {
dependencies: 'Dependencies',
dependents: 'Dependents',
dependenciesDescription: 'Nodes that this node relies on',
dependentsDescription: 'Nodes that rely on this node',
noDependencies: 'No dependencies',
noDependents: 'No dependents',
},
},
}


+ 9
- 0
web/i18n/ja-JP/workflow.ts Näytä tiedosto

@@ -968,6 +968,15 @@ const translation = {
},
settingsTab: '設定',
lastRunTab: '最後の実行',
relationsTab: '関係',
relations: {
dependencies: '依存元',
dependents: '依存先',
dependenciesDescription: 'このノードが依存している他のノード',
dependentsDescription: 'このノードに依存している他のノード',
noDependencies: '依存元なし',
noDependents: '依存先なし',
},
},
}


+ 9
- 0
web/i18n/zh-Hans/workflow.ts Näytä tiedosto

@@ -943,6 +943,7 @@ const translation = {
debug: {
settingsTab: '设置',
lastRunTab: '上次运行',
relationsTab: '关系',
noData: {
description: '上次运行的结果将显示在这里',
runThisNode: '运行此节点',
@@ -968,6 +969,14 @@ const translation = {
chatNode: '会话变量',
systemNode: '系统变量',
},
relations: {
dependencies: '依赖',
dependents: '被依赖',
dependenciesDescription: '本节点依赖的其他节点',
dependentsDescription: '依赖于本节点的其他节点',
noDependencies: '无依赖',
noDependents: '无被依赖',
},
},
}


+ 11
- 2
web/i18n/zh-Hant/workflow.ts Näytä tiedosto

@@ -941,6 +941,9 @@ const translation = {
copyId: '複製ID',
},
debug: {
settingsTab: '設定',
lastRunTab: '最後一次運行',
relationsTab: '關係',
noData: {
runThisNode: '運行此節點',
description: '上次運行的結果將顯示在這裡',
@@ -966,8 +969,14 @@ const translation = {
emptyTip: '在畫布上逐步執行節點或逐步運行節點後,您可以在變數檢視中查看節點變數的當前值。',
resetConversationVar: '將對話變數重置為默認值',
},
settingsTab: '設定',
lastRunTab: '最後一次運行',
relations: {
dependencies: '依賴',
dependents: '被依賴',
dependenciesDescription: '此節點所依賴的其他節點',
dependentsDescription: '依賴此節點的其他節點',
noDependencies: '無依賴',
noDependents: '無被依賴',
},
},
}


Loading…
Peruuta
Tallenna