mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 02:58:59 +08:00
feat: Variable click jumps to source node (#13623)
This commit is contained in:
parent
6a9e0b1005
commit
276c02f341
@ -74,9 +74,11 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
)
|
||||
|
||||
const handleSelect = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
if (!e.metaKey && !e.ctrlKey) {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
}
|
||||
}, [setSelected, clearSelection])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
@ -13,6 +14,7 @@ import {
|
||||
RiErrorWarningFill,
|
||||
RiMoreLine,
|
||||
} from '@remixicon/react'
|
||||
import { useReactFlow, useStoreApi } from 'reactflow'
|
||||
import { useSelectOrDelete } from '../../hooks'
|
||||
import type { WorkflowNodesMap } from './node'
|
||||
import { WorkflowVariableBlockNode } from './node'
|
||||
@ -66,6 +68,9 @@ const WorkflowVariableBlockComponent = ({
|
||||
const isChatVar = isConversationVar(variables)
|
||||
const isException = isExceptionVariable(varName, node?.type)
|
||||
|
||||
const reactflow = useReactFlow()
|
||||
const store = useStoreApi()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([WorkflowVariableBlockNode]))
|
||||
throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor')
|
||||
@ -83,6 +88,26 @@ const WorkflowVariableBlockComponent = ({
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
const handleVariableJump = useCallback(() => {
|
||||
const workflowContainer = document.getElementById('workflow-container')
|
||||
const {
|
||||
clientWidth,
|
||||
clientHeight,
|
||||
} = workflowContainer!
|
||||
|
||||
const {
|
||||
setViewport,
|
||||
} = reactflow
|
||||
const { transform } = store.getState()
|
||||
const zoom = transform[2]
|
||||
const position = node.position
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - node.width! * zoom) / 2 - position!.x * zoom,
|
||||
y: (clientHeight - node.height! * zoom) / 2 - position!.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
}, [node, reactflow, store])
|
||||
|
||||
const Item = (
|
||||
<div
|
||||
className={cn(
|
||||
@ -90,6 +115,10 @@ const WorkflowVariableBlockComponent = ({
|
||||
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
|
||||
!node && !isEnv && !isChatVar && '!border-state-destructive-solid !bg-state-destructive-hover',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleVariableJump()
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
{!isEnv && !isChatVar && (
|
||||
|
@ -64,7 +64,7 @@ export type GetVarType = (payload: {
|
||||
export type WorkflowVariableBlockType = {
|
||||
show?: boolean
|
||||
variables?: NodeOutPutVar[]
|
||||
workflowNodesMap?: Record<string, Pick<Node['data'], 'title' | 'type'>>
|
||||
workflowNodesMap?: Record<string, Pick<Node['data'], 'title' | 'type' | 'height' | 'width' | 'position'>>
|
||||
onInsert?: () => void
|
||||
onDelete?: () => void
|
||||
getVarType?: GetVarType
|
||||
|
@ -91,6 +91,9 @@ const Editor: FC<Props> = ({
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
|
@ -259,6 +259,9 @@ const Editor: FC<Props> = ({
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useNodes, useReactFlow, useStoreApi } from 'reactflow'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiErrorWarningFill } from '@remixicon/react'
|
||||
@ -48,12 +48,42 @@ const VariableTag = ({
|
||||
const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.')
|
||||
const isException = isExceptionVariable(variableName, node?.data.type)
|
||||
|
||||
const reactflow = useReactFlow()
|
||||
const store = useStoreApi()
|
||||
|
||||
const handleVariableJump = useCallback(() => {
|
||||
const workflowContainer = document.getElementById('workflow-container')
|
||||
const {
|
||||
clientWidth,
|
||||
clientHeight,
|
||||
} = workflowContainer!
|
||||
|
||||
const {
|
||||
setViewport,
|
||||
} = reactflow
|
||||
const { transform } = store.getState()
|
||||
const zoom = transform[2]
|
||||
const position = node.position
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - node.width! * zoom) / 2 - position!.x * zoom,
|
||||
y: (clientHeight - node.height! * zoom) / 2 - position!.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
}, [node, reactflow, store])
|
||||
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Tooltip popupContent={!isValid && t('workflow.errorMsg.invalidVariable')}>
|
||||
<div className={cn('border-[rgba(16, 2440,0.08)] inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-divider-subtle bg-components-badge-white-to-dark px-1.5 text-xs shadow-xs',
|
||||
!isValid && 'border-red-400 !bg-[#FEF3F2]',
|
||||
)}>
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
e.stopPropagation()
|
||||
handleVariableJump()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(!isEnv && !isChatVar && <>
|
||||
{node && (
|
||||
<>
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
RiMoreLine,
|
||||
} from '@remixicon/react'
|
||||
import produce from 'immer'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { useReactFlow, useStoreApi } from 'reactflow'
|
||||
import RemoveButton from '../remove-button'
|
||||
import useAvailableVarList from '../../hooks/use-available-var-list'
|
||||
import VarReferencePopup from './var-reference-popup'
|
||||
@ -111,6 +111,9 @@ const VarReferencePicker: FC<Props> = ({
|
||||
passedInAvailableNodes,
|
||||
filterVar,
|
||||
})
|
||||
|
||||
const reactflow = useReactFlow()
|
||||
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
@ -172,7 +175,11 @@ const VarReferencePicker: FC<Props> = ({
|
||||
if (isSystemVar(value as ValueSelector))
|
||||
return startNode?.data
|
||||
|
||||
return getNodeInfoById(availableNodes, outputVarNodeId)?.data
|
||||
const node = getNodeInfoById(availableNodes, outputVarNodeId)?.data
|
||||
return {
|
||||
...node,
|
||||
id: outputVarNodeId,
|
||||
}
|
||||
}, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode, isLoopVar, loopNode])
|
||||
|
||||
const isShowAPart = (value as ValueSelector).length > 2
|
||||
@ -237,6 +244,28 @@ const VarReferencePicker: FC<Props> = ({
|
||||
onChange([], varKindType)
|
||||
}, [onChange, varKindType])
|
||||
|
||||
const handleVariableJump = useCallback((nodeId: string) => {
|
||||
const currentNodeIndex = availableNodes.findIndex(node => node.id === nodeId)
|
||||
const currentNode = availableNodes[currentNodeIndex]
|
||||
|
||||
const workflowContainer = document.getElementById('workflow-container')
|
||||
const {
|
||||
clientWidth,
|
||||
clientHeight,
|
||||
} = workflowContainer!
|
||||
const {
|
||||
setViewport,
|
||||
} = reactflow
|
||||
const { transform } = store.getState()
|
||||
const zoom = transform[2]
|
||||
const position = currentNode.position
|
||||
setViewport({
|
||||
x: (clientWidth - 400 - currentNode.width! * zoom) / 2 - position.x * zoom,
|
||||
y: (clientHeight - currentNode.height! * zoom) / 2 - position.y * zoom,
|
||||
zoom: transform[2],
|
||||
})
|
||||
}, [availableNodes, reactflow, store])
|
||||
|
||||
const type = getCurrentVariableType({
|
||||
parentNode: isInIteration ? iterationNode : loopNode,
|
||||
valueSelector: value as ValueSelector,
|
||||
@ -357,7 +386,12 @@ const VarReferencePicker: FC<Props> = ({
|
||||
? (
|
||||
<>
|
||||
{isShowNodeName && !isEnv && !isChatVar && (
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center' onClick={(e) => {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
e.stopPropagation()
|
||||
handleVariableJump(outputVarNode?.id)
|
||||
}
|
||||
}}>
|
||||
<div className='h-3 px-[1px]'>
|
||||
{outputVarNode?.type && <VarBlockIcon
|
||||
className='!text-text-primary'
|
||||
|
@ -37,6 +37,9 @@ const ConditionInput = ({
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
|
@ -2,6 +2,7 @@ import type {
|
||||
Edge as ReactFlowEdge,
|
||||
Node as ReactFlowNode,
|
||||
Viewport,
|
||||
XYPosition,
|
||||
} from 'reactflow'
|
||||
import type { Resolution, TransferMethod } from '@/types/app'
|
||||
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
|
||||
@ -83,6 +84,7 @@ export type CommonNodeType<T = {}> = {
|
||||
type: BlockEnum
|
||||
width?: number
|
||||
height?: number
|
||||
position?: XYPosition
|
||||
_loopLength?: number
|
||||
_loopIndex?: number
|
||||
isInLoop?: boolean
|
||||
|
Loading…
x
Reference in New Issue
Block a user