diff --git a/web/app/components/base/prompt-editor/hooks.ts b/web/app/components/base/prompt-editor/hooks.ts
index c9e4cc129c..87119f8b49 100644
--- a/web/app/components/base/prompt-editor/hooks.ts
+++ b/web/app/components/base/prompt-editor/hooks.ts
@@ -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(() => {
diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx
index 50ff29633a..731841f423 100644
--- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx
+++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx
@@ -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 = (
{
+ e.stopPropagation()
+ handleVariableJump()
+ }}
ref={ref}
>
{!isEnv && !isChatVar && (
diff --git a/web/app/components/base/prompt-editor/types.ts b/web/app/components/base/prompt-editor/types.ts
index 0f09fb2473..e82ec1da16 100644
--- a/web/app/components/base/prompt-editor/types.ts
+++ b/web/app/components/base/prompt-editor/types.ts
@@ -64,7 +64,7 @@ export type GetVarType = (payload: {
export type WorkflowVariableBlockType = {
show?: boolean
variables?: NodeOutPutVar[]
- workflowNodesMap?: Record
>
+ workflowNodesMap?: Record>
onInsert?: () => void
onDelete?: () => void
getVarType?: GetVarType
diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx
index aab14bb6f9..a741629da8 100644
--- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx
+++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx
@@ -91,6 +91,9 @@ const Editor: FC = ({
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 = {
diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx
index c6233ff377..0a7ebc2a09 100644
--- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx
+++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx
@@ -259,6 +259,9 @@ const Editor: FC = ({
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 = {
diff --git a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx
index 83b07715fe..d73a3d4924 100644
--- a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx
+++ b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx
@@ -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 (
+ )}
+ onClick={(e) => {
+ if (e.metaKey || e.ctrlKey) {
+ e.stopPropagation()
+ handleVariableJump()
+ }
+ }}
+ >
{(!isEnv && !isChatVar && <>
{node && (
<>
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
index 789da34f9d..e9825cd44a 100644
--- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
+++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
@@ -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
= ({
passedInAvailableNodes,
filterVar,
})
+
+ const reactflow = useReactFlow()
+
const startNode = availableNodes.find((node: any) => {
return node.data.type === BlockEnum.Start
})
@@ -172,7 +175,11 @@ const VarReferencePicker: FC = ({
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 = ({
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 = ({
? (
<>
{isShowNodeName && !isEnv && !isChatVar && (
-
+
{
+ if (e.metaKey || e.ctrlKey) {
+ e.stopPropagation()
+ handleVariableJump(outputVarNode?.id)
+ }
+ }}>
{outputVarNode?.type && = {
type: BlockEnum
width?: number
height?: number
+ position?: XYPosition
_loopLength?: number
_loopIndex?: number
isInLoop?: boolean