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