feat: Variable click jumps to source node (#13623)

This commit is contained in:
wellCh4n 2025-05-19 23:17:18 +08:00 committed by GitHub
parent 6a9e0b1005
commit 276c02f341
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 116 additions and 10 deletions

View File

@ -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(() => {

View File

@ -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 && (

View File

@ -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

View File

@ -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 = {

View File

@ -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 = {

View File

@ -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 && (
<>

View File

@ -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'

View File

@ -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 = {

View File

@ -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