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