Merge a293b78d80 into bd43ca6275
commit
fd8a862b98
@ -0,0 +1,35 @@
|
|||||||
|
import Empty from './empty'
|
||||||
|
import Item from './item'
|
||||||
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
import { RelationType } from './types'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
type ContainerProps = {
|
||||||
|
nextNode?: Node
|
||||||
|
relationType: RelationType
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = ({
|
||||||
|
nextNode,
|
||||||
|
relationType,
|
||||||
|
}: ContainerProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'space-y-0.5 rounded-[10px] bg-background-section-burn p-0.5',
|
||||||
|
)}>
|
||||||
|
{nextNode && (
|
||||||
|
<Item
|
||||||
|
key={nextNode.id}
|
||||||
|
nodeId={nextNode.id}
|
||||||
|
data={nextNode.data}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!nextNode && (
|
||||||
|
<Empty display={relationType === RelationType.dependencies ? t('workflow.debug.relations.noDependencies') : t('workflow.debug.relations.noDependents')} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Container
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
|
||||||
|
type EmptyProps = {
|
||||||
|
display: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Empty = ({
|
||||||
|
display = '',
|
||||||
|
}: EmptyProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
bg-dropzone-bg hover:bg-dropzone-bg-hover relative flex h-9 cursor-default items-center rounded-lg border border-dashed
|
||||||
|
border-divider-regular !bg-components-dropzone-bg-alt px-2 text-xs
|
||||||
|
text-text-placeholder
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className='flex items-center uppercase'>
|
||||||
|
{display}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Empty)
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import { isEqual } from 'lodash-es'
|
||||||
|
import {
|
||||||
|
getOutgoers,
|
||||||
|
useStore,
|
||||||
|
} from 'reactflow'
|
||||||
|
import { useToolIcon } from '@/app/components/workflow/hooks'
|
||||||
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
|
import type {
|
||||||
|
Node,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import Line from './line'
|
||||||
|
import Container from './container'
|
||||||
|
import { getNodeUsedVars } from '../../variable/utils'
|
||||||
|
import { RelationType } from './types'
|
||||||
|
|
||||||
|
type RelationsProps = {
|
||||||
|
selectedNode: Node
|
||||||
|
relationType: RelationType
|
||||||
|
}
|
||||||
|
const Relations = ({
|
||||||
|
selectedNode,
|
||||||
|
relationType,
|
||||||
|
}: RelationsProps) => {
|
||||||
|
const data = selectedNode.data
|
||||||
|
const toolIcon = useToolIcon(data)
|
||||||
|
const edges = useStore(s => s.edges.map(edge => ({
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.source,
|
||||||
|
sourceHandle: edge.sourceHandle,
|
||||||
|
target: edge.target,
|
||||||
|
targetHandle: edge.targetHandle,
|
||||||
|
})), isEqual)
|
||||||
|
const nodes = useStore(s => s.getNodes().map(node => ({
|
||||||
|
id: node.id,
|
||||||
|
data: node.data,
|
||||||
|
})), isEqual)
|
||||||
|
const workflowNodes = useStore(s => s.getNodes())
|
||||||
|
|
||||||
|
const list: Node[] = []
|
||||||
|
|
||||||
|
if (relationType === RelationType.dependencies) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
list.push(...dependencyNodes)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
list.push(...dependentNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getThisNode = () => {
|
||||||
|
return (
|
||||||
|
<div className='relative flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-background-default shadow-xs'>
|
||||||
|
<BlockIcon
|
||||||
|
type={selectedNode!.data.type}
|
||||||
|
toolIcon={toolIcon}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLinkedNodes = () => {
|
||||||
|
return (
|
||||||
|
<div className='grow space-y-2'>
|
||||||
|
{list.length > 0 ? (
|
||||||
|
list.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
key={index}
|
||||||
|
nextNode={item}
|
||||||
|
relationType={relationType}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<Container key={0} relationType={relationType}/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex py-1'>
|
||||||
|
{getThisNode()}
|
||||||
|
<Line
|
||||||
|
rowCount={Math.max(1, list.length)}
|
||||||
|
relationType={relationType}
|
||||||
|
/>
|
||||||
|
{getLinkedNodes()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Relations)
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type {
|
||||||
|
CommonNodeType,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
|
import {
|
||||||
|
useNodesInteractions,
|
||||||
|
useNodesReadOnly,
|
||||||
|
useToolIcon,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
|
||||||
|
type ItemProps = {
|
||||||
|
nodeId: string
|
||||||
|
data: CommonNodeType
|
||||||
|
}
|
||||||
|
const Item = ({
|
||||||
|
nodeId,
|
||||||
|
data,
|
||||||
|
}: ItemProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { nodesReadOnly } = useNodesReadOnly()
|
||||||
|
const { handleNodeSelect } = useNodesInteractions()
|
||||||
|
const toolIcon = useToolIcon(data)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='group relative flex h-9 cursor-pointer items-center rounded-lg border-[0.5px] border-divider-regular bg-background-default px-2 text-xs text-text-secondary shadow-xs last-of-type:mb-0 hover:bg-background-default-hover'
|
||||||
|
>
|
||||||
|
<BlockIcon
|
||||||
|
type={data.type}
|
||||||
|
toolIcon={toolIcon}
|
||||||
|
className='mr-1.5 shrink-0'
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className='system-xs-medium grow truncate text-text-secondary'
|
||||||
|
title={data.title}
|
||||||
|
>
|
||||||
|
{data.title}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!nodesReadOnly && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
className='mr-1 hidden shrink-0 group-hover:flex'
|
||||||
|
size='small'
|
||||||
|
onClick={() => handleNodeSelect(nodeId)}
|
||||||
|
>
|
||||||
|
{t('workflow.common.jumpToNode')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Item)
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import { RelationType } from './types'
|
||||||
|
|
||||||
|
type LineProps = {
|
||||||
|
rowCount: number
|
||||||
|
relationType: RelationType
|
||||||
|
}
|
||||||
|
const Line = ({
|
||||||
|
rowCount = 1,
|
||||||
|
relationType,
|
||||||
|
}: LineProps) => {
|
||||||
|
const list: number[] = []
|
||||||
|
const listH = 40
|
||||||
|
const ySpacing = 8
|
||||||
|
|
||||||
|
const svgHeight = listH * rowCount + ySpacing * (rowCount - 1)
|
||||||
|
|
||||||
|
const lineWidth = 48
|
||||||
|
const arrowStrokeWidth = 1
|
||||||
|
const arrowLineLength = 6
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg className='w-12 shrink-0' style={{ height: svgHeight }}>
|
||||||
|
{
|
||||||
|
Array.from({ length: rowCount }).map((_, index) => {
|
||||||
|
const space = index * listH + index * ySpacing + 16
|
||||||
|
return (
|
||||||
|
<g key={index}>
|
||||||
|
{
|
||||||
|
index === 0 && (
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
d={`M0,18 L${lineWidth},18`}
|
||||||
|
strokeWidth={1}
|
||||||
|
fill='none'
|
||||||
|
className='stroke-divider-solid'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d={relationType === RelationType.dependencies
|
||||||
|
? `M${6 + arrowLineLength},${18 - arrowLineLength} L6,${18} L${6 + arrowLineLength},${18 + arrowLineLength}`
|
||||||
|
: `M${lineWidth - 6 - arrowLineLength},${18 - arrowLineLength} L${lineWidth - 6},${18} L${lineWidth - 6 - arrowLineLength},${18 + arrowLineLength}`
|
||||||
|
}
|
||||||
|
strokeWidth={arrowStrokeWidth}
|
||||||
|
fill='none'
|
||||||
|
className='stroke-divider-solid-alt'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x={lineWidth - 1}
|
||||||
|
y={16}
|
||||||
|
width={1}
|
||||||
|
height={4}
|
||||||
|
className='fill-divider-solid-alt'
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
index > 0 && (
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
d={(`M0,18 L${lineWidth / 2 - 12},18 Q${lineWidth / 2},18 ${lineWidth / 2},28 L${lineWidth / 2},${space - 10 + 2} Q${lineWidth / 2},${space + 2} ${lineWidth / 2 + 12},${space + 2} L${lineWidth},${space + 2}`)}
|
||||||
|
strokeWidth={1}
|
||||||
|
fill='none'
|
||||||
|
className='stroke-divider-solid'
|
||||||
|
/>
|
||||||
|
{relationType === RelationType.dependents && (
|
||||||
|
<path
|
||||||
|
d={`M${lineWidth - 6 - arrowLineLength},${space + 2 - arrowLineLength} L${lineWidth - 6},${space + 2} L${lineWidth - 6 - arrowLineLength},${space + 2 + arrowLineLength}`}
|
||||||
|
strokeWidth={arrowStrokeWidth}
|
||||||
|
fill='none'
|
||||||
|
className='stroke-divider-solid-alt'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<rect
|
||||||
|
x={lineWidth - 1}
|
||||||
|
y={space}
|
||||||
|
width={1}
|
||||||
|
height={4}
|
||||||
|
className='fill-divider-solid-alt'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Line)
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export enum RelationType {
|
||||||
|
dependencies,
|
||||||
|
dependents,
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue