Compare commits

...

2 Commits

Author SHA1 Message Date
zxhlyh
bf20f3aa8b chore: workflow performance 2025-10-11 14:02:25 +08:00
zxhlyh
c90e564d99 chore: workflow performance 2025-10-10 18:23:46 +08:00
20 changed files with 156 additions and 132 deletions

View File

@@ -3,7 +3,7 @@ import {
useCallback, useCallback,
useMemo, useMemo,
} from 'react' } from 'react'
import { useEdges, useNodes, useStore as useReactflowStore } from 'reactflow' import { useStore as useReactflowStore } from 'reactflow'
import { RiApps2AddLine } from '@remixicon/react' import { RiApps2AddLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
@@ -11,7 +11,6 @@ import {
useWorkflowStore, useWorkflowStore,
} from '@/app/components/workflow/store' } from '@/app/components/workflow/store'
import { import {
useChecklist,
useChecklistBeforePublish, useChecklistBeforePublish,
useNodesReadOnly, useNodesReadOnly,
useNodesSyncDraft, useNodesSyncDraft,
@@ -19,10 +18,6 @@ import {
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import AppPublisher from '@/app/components/app/app-publisher' import AppPublisher from '@/app/components/app/app-publisher'
import { useFeatures } from '@/app/components/base/features/hooks' import { useFeatures } from '@/app/components/base/features/hooks'
import type {
CommonEdgeType,
CommonNodeType,
} from '@/app/components/workflow/types'
import { import {
BlockEnum, BlockEnum,
InputVarType, InputVarType,
@@ -97,18 +92,9 @@ const FeaturesTrigger = () => {
} }
}, [appID, setAppDetail]) }, [appID, setAppDetail])
const { mutateAsync: publishWorkflow } = usePublishWorkflow() const { mutateAsync: publishWorkflow } = usePublishWorkflow()
const nodes = useNodes<CommonNodeType>()
const edges = useEdges<CommonEdgeType>()
const needWarningNodes = useChecklist(nodes, edges)
const updatePublishedWorkflow = useInvalidateAppWorkflow() const updatePublishedWorkflow = useInvalidateAppWorkflow()
const onPublish = useCallback(async (params?: PublishWorkflowParams) => { const onPublish = useCallback(async (params?: PublishWorkflowParams) => {
// First check if there are any items in the checklist
if (needWarningNodes.length > 0) {
notify({ type: 'error', message: t('workflow.panel.checklistTip') })
throw new Error('Checklist has unresolved items')
}
// Then perform the detailed validation // Then perform the detailed validation
if (await handleCheckBeforePublish()) { if (await handleCheckBeforePublish()) {
const res = await publishWorkflow({ const res = await publishWorkflow({
@@ -128,7 +114,7 @@ const FeaturesTrigger = () => {
else { else {
throw new Error('Checklist failed') throw new Error('Checklist failed')
} }
}, [needWarningNodes, handleCheckBeforePublish, publishWorkflow, notify, appID, t, updatePublishedWorkflow, updateAppDetail, workflowStore, resetWorkflowVersionHistory]) }, [handleCheckBeforePublish, publishWorkflow, notify, appID, t, updatePublishedWorkflow, updateAppDetail, workflowStore, resetWorkflowVersionHistory])
const onPublisherToggle = useCallback((state: boolean) => { const onPublisherToggle = useCallback((state: boolean) => {
if (state) if (state)

View File

@@ -2,27 +2,25 @@ import {
memo, memo,
useCallback, useCallback,
} from 'react' } from 'react'
import { useNodes } from 'reactflow'
import { useStore } from './store' import { useStore } from './store'
import { import {
useIsChatMode, useIsChatMode,
useNodesReadOnly, useNodesReadOnly,
useNodesSyncDraft, useNodesSyncDraft,
} from './hooks' } from './hooks'
import { type CommonNodeType, type InputVar, InputVarType, type Node } from './types' import { type InputVar, InputVarType, type Node } from './types'
import useConfig from './nodes/start/use-config' import useConfig from './nodes/start/use-config'
import type { StartNodeType } from './nodes/start/types' import type { StartNodeType } from './nodes/start/types'
import type { PromptVariable } from '@/models/debug' import type { PromptVariable } from '@/models/debug'
import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
const Features = () => { const Features = () => {
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel) const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const { nodesReadOnly } = useNodesReadOnly() const { nodesReadOnly } = useNodesReadOnly()
const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const nodes = useNodes<CommonNodeType>() const startNode = useFindNode(['sys'])
const startNode = nodes.find(node => node.data.type === 'start')
const { id, data } = startNode as Node<StartNodeType> const { id, data } = startNode as Node<StartNodeType>
const { handleAddVariable } = useConfig(id, data) const { handleAddVariable } = useConfig(id, data)

View File

@@ -1,12 +1,11 @@
import { import {
useCallback, useCallback,
} from 'react' } from 'react'
import { useNodes } from 'reactflow' import { useStore as useReactFlowStore } from 'reactflow'
import { import {
useStore, useStore,
useWorkflowStore, useWorkflowStore,
} from '../store' } from '../store'
import type { StartNodeType } from '../nodes/start/types'
import { import {
useNodesInteractions, useNodesInteractions,
useNodesReadOnly, useNodesReadOnly,
@@ -39,8 +38,7 @@ const HeaderInNormal = ({
const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel) const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel)
const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel) const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel)
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel) const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
const nodes = useNodes<StartNodeType>() const selectedNode = useReactFlowStore(s => s.getNodes().find(node => node.data.selected))
const selectedNode = nodes.find(node => node.data.selected)
const { handleBackupDraft } = useWorkflowRun() const { handleBackupDraft } = useWorkflowRun()
const { closeAllInputFieldPanels } = useInputFieldPanel() const { closeAllInputFieldPanels } = useInputFieldPanel()

View File

@@ -0,0 +1,36 @@
import { useMemo } from 'react'
import { useStore } from 'reactflow'
import { useShallow } from 'zustand/react/shallow'
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { BlockEnum } from '@/app/components/workflow/types'
import type { Node, ValueSelector } from '@/app/components/workflow/types'
export const useFindNode = (valueSelector: ValueSelector = [], nodes: Node[] = []) => {
const nodeFromOuterNodes = nodes.find(node => node.id === valueSelector[0])
const node = useStore(useShallow((s) => {
if (isSystemVar(valueSelector)) {
for (const n of s.nodeInternals.values()) {
if (n?.data?.type === BlockEnum.Start) {
return {
id: n.id,
data: n.data,
}
}
}
}
else {
if (!!valueSelector.length) {
const id = s.nodeInternals.get(valueSelector[0])?.id
const data = s.nodeInternals.get(valueSelector[0])?.data
if (id && data) {
return {
id,
data,
}
}
}
}
}))
return useMemo(() => nodeFromOuterNodes || node, [nodeFromOuterNodes, node])
}

View File

@@ -4,18 +4,16 @@ import {
useRef, useRef,
} from 'react' } from 'react'
import { useClickAway } from 'ahooks' import { useClickAway } from 'ahooks'
import { useNodes } from 'reactflow'
import PanelOperatorPopup from './nodes/_base/components/panel-operator/panel-operator-popup' import PanelOperatorPopup from './nodes/_base/components/panel-operator/panel-operator-popup'
import type { Node } from './types'
import { useStore } from './store' import { useStore } from './store'
import { usePanelInteractions } from './hooks' import { usePanelInteractions } from './hooks'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
const NodeContextmenu = () => { const NodeContextmenu = () => {
const ref = useRef(null) const ref = useRef(null)
const nodes = useNodes()
const { handleNodeContextmenuCancel, handlePaneContextmenuCancel } = usePanelInteractions() const { handleNodeContextmenuCancel, handlePaneContextmenuCancel } = usePanelInteractions()
const nodeMenu = useStore(s => s.nodeMenu) const nodeMenu = useStore(s => s.nodeMenu)
const currentNode = nodes.find(node => node.id === nodeMenu?.nodeId) as Node const currentNode = useFindNode(nodeMenu?.nodeId ? [nodeMenu.nodeId] : [])
useEffect(() => { useEffect(() => {
if (nodeMenu) if (nodeMenu)

View File

@@ -1,18 +1,18 @@
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { useNodes, useReactFlow, useStoreApi } from 'reactflow' import { useReactFlow, useStoreApi } from 'reactflow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { import type {
CommonNodeType,
Node, Node,
ValueSelector, ValueSelector,
VarType, VarType,
} from '@/app/components/workflow/types' } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types'
import { getNodeInfoById, isConversationVar, isENV, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { isConversationVar, isENV, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { isExceptionVariable } from '@/app/components/workflow/utils' import { isExceptionVariable } from '@/app/components/workflow/utils'
import { import {
VariableLabelInSelect, VariableLabelInSelect,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
type VariableTagProps = { type VariableTagProps = {
valueSelector: ValueSelector valueSelector: ValueSelector
@@ -26,16 +26,16 @@ const VariableTag = ({
isShort, isShort,
availableNodes, availableNodes,
}: VariableTagProps) => { }: VariableTagProps) => {
const nodes = useNodes<CommonNodeType>()
const isRagVar = isRagVariableVar(valueSelector) const isRagVar = isRagVariableVar(valueSelector)
const nodeFromAvailableNodes = useFindNode(isRagVar ? [valueSelector[1]] : [valueSelector[0]], availableNodes)
const node = useMemo(() => { const node = useMemo(() => {
if (isSystemVar(valueSelector)) { if (isSystemVar(valueSelector)) {
const startNode = availableNodes?.find(n => n.data.type === BlockEnum.Start) const startNode = availableNodes?.find(n => n.data.type === BlockEnum.Start)
if (startNode) if (startNode)
return startNode return startNode
} }
return getNodeInfoById(availableNodes || nodes, isRagVar ? valueSelector[1] : valueSelector[0]) return nodeFromAvailableNodes
}, [nodes, valueSelector, availableNodes, isRagVar]) }, [valueSelector, availableNodes, isRagVar, nodeFromAvailableNodes]) as Node
const isEnv = isENV(valueSelector) const isEnv = isENV(valueSelector)
const isChatVar = isConversationVar(valueSelector) const isChatVar = isConversationVar(valueSelector)

View File

@@ -0,0 +1,35 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import type { AssignerNodeOperation } from '../types'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
import {
VariableLabelInNode,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import Badge from '@/app/components/base/badge'
type OperationItemProps = {
value: AssignerNodeOperation
}
const i18nPrefix = 'workflow.nodes.assigner'
const OperationItem = ({ value }: OperationItemProps) => {
const { t } = useTranslation()
const variable = value.variable_selector
const node = useFindNode(variable)
if (!variable || variable.length === 0)
return null
return (
<VariableLabelInNode
variables={variable}
nodeType={node?.data.type}
nodeTitle={node?.data.title}
rightSlot={
value.operation && <Badge className='!ml-auto shrink-0' text={t(`${i18nPrefix}.operations.${value.operation}`)} />
}
/>
)
}
export default memo(OperationItem)

View File

@@ -1,6 +1,6 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { import {
useNodes, useStoreApi,
} from 'reactflow' } from 'reactflow'
import { uniqBy } from 'lodash-es' import { uniqBy } from 'lodash-es'
import { import {
@@ -15,12 +15,14 @@ import type {
import { AssignerNodeInputType, WriteMode } from './types' import { AssignerNodeInputType, WriteMode } from './types'
export const useGetAvailableVars = () => { export const useGetAvailableVars = () => {
const nodes: Node[] = useNodes() const store = useStoreApi()
const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow() const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
const { getNodeAvailableVars } = useWorkflowVariables() const { getNodeAvailableVars } = useWorkflowVariables()
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean, hideEnv = false) => { const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean, hideEnv = false) => {
const availableNodes: Node[] = [] const availableNodes: Node[] = []
const { getNodes } = store.getState()
const nodes = getNodes()
const currentNode = nodes.find(node => node.id === nodeId)! const currentNode = nodes.find(node => node.id === nodeId)!
if (!currentNode) if (!currentNode)
@@ -52,7 +54,7 @@ export const useGetAvailableVars = () => {
isChatMode, isChatMode,
filterVar, filterVar,
}) })
}, [nodes, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode]) }, [store, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode])
return getAvailableVars return getAvailableVars
} }

View File

@@ -1,14 +1,14 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useNodes } from 'reactflow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { AssignerNodeType } from './types' import type { AssignerNodeType } from './types'
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import type { NodeProps } from '@/app/components/workflow/types'
import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types'
import { import {
VariableLabelInNode, VariableLabelInNode,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import Badge from '@/app/components/base/badge' import Badge from '@/app/components/base/badge'
import OperationItem from './components/operation-item'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
const i18nPrefix = 'workflow.nodes.assigner' const i18nPrefix = 'workflow.nodes.assigner'
@@ -16,7 +16,7 @@ const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({
data, data,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const nodes: Node[] = useNodes() const node = useFindNode((data as any).assigned_variable_selector)
if (data.version === '2') { if (data.version === '2') {
const { items: operationItems } = data const { items: operationItems } = data
const validOperationItems = operationItems?.filter(item => const validOperationItems = operationItems?.filter(item =>
@@ -37,22 +37,7 @@ const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({
return ( return (
<div className='relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1'> <div className='relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1'>
{operationItems.map((value, index) => { {operationItems.map((value, index) => {
const variable = value.variable_selector return <OperationItem key={index} value={value} />
if (!variable || variable.length === 0)
return null
const isSystem = isSystemVar(variable)
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
return (
<VariableLabelInNode
key={index}
variables={variable}
nodeType={node?.data.type}
nodeTitle={node?.data.title}
rightSlot={
value.operation && <Badge className='!ml-auto shrink-0' text={t(`${i18nPrefix}.operations.${value.operation}`)} />
}
/>
)
})} })}
</div> </div>
) )
@@ -62,8 +47,6 @@ const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({
if (!variable || variable.length === 0) if (!variable || variable.length === 0)
return null return null
const isSystem = isSystemVar(variable)
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
return ( return (
<div className='relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1'> <div className='relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1'>

View File

@@ -1,13 +1,12 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useNodes } from 'reactflow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { DocExtractorNodeType } from './types' import type { DocExtractorNodeType } from './types'
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import type { NodeProps } from '@/app/components/workflow/types'
import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types'
import { import {
VariableLabelInNode, VariableLabelInNode,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
const i18nPrefix = 'workflow.nodes.docExtractor' const i18nPrefix = 'workflow.nodes.docExtractor'
@@ -15,15 +14,12 @@ const NodeComponent: FC<NodeProps<DocExtractorNodeType>> = ({
data, data,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const nodes: Node[] = useNodes()
const { variable_selector: variable } = data const { variable_selector: variable } = data
const node = useFindNode(variable)
if (!variable || variable.length === 0) if (!variable || variable.length === 0)
return null return null
const isSystem = isSystemVar(variable)
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
return ( return (
<div className='relative px-3'> <div className='relative px-3'>
<div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${i18nPrefix}.inputVar`)}</div> <div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${i18nPrefix}.inputVar`)}</div>

View File

@@ -3,7 +3,6 @@ import {
useMemo, useMemo,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import { ComparisonOperator } from '../types' import { ComparisonOperator } from '../types'
import { import {
comparisonOperatorNotRequireValue, comparisonOperatorNotRequireValue,
@@ -12,13 +11,10 @@ import {
import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants' import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants'
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { isExceptionVariable } from '@/app/components/workflow/utils' import { isExceptionVariable } from '@/app/components/workflow/utils'
import type {
CommonNodeType,
Node,
} from '@/app/components/workflow/types'
import { import {
VariableLabelInText, VariableLabelInText,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
type ConditionValueProps = { type ConditionValueProps = {
variableSelector: string[] variableSelector: string[]
@@ -33,11 +29,10 @@ const ConditionValue = ({
value, value,
}: ConditionValueProps) => { }: ConditionValueProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const nodes = useNodes() const node = useFindNode(variableSelector)
const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.')) const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.'))
const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator
const notHasValue = comparisonOperatorNotRequireValue(operator) const notHasValue = comparisonOperatorNotRequireValue(operator)
const node: Node<CommonNodeType> | undefined = nodes.find(n => n.id === variableSelector[0]) as Node<CommonNodeType>
const isException = isExceptionVariable(variableName, node?.data.type) const isException = isExceptionVariable(variableName, node?.data.type)
const formatValue = useMemo(() => { const formatValue = useMemo(() => {
if (notHasValue) if (notHasValue)

View File

@@ -1,13 +1,12 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useNodes } from 'reactflow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { ListFilterNodeType } from './types' import type { ListFilterNodeType } from './types'
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import type { NodeProps } from '@/app/components/workflow/types'
import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types'
import { import {
VariableLabelInNode, VariableLabelInNode,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
const i18nPrefix = 'workflow.nodes.listFilter' const i18nPrefix = 'workflow.nodes.listFilter'
@@ -15,15 +14,12 @@ const NodeComponent: FC<NodeProps<ListFilterNodeType>> = ({
data, data,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const nodes: Node[] = useNodes()
const { variable } = data const { variable } = data
const node = useFindNode(variable)
if (!variable || variable.length === 0) if (!variable || variable.length === 0)
return null return null
const isSystem = isSystemVar(variable)
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
return ( return (
<div className='relative px-3'> <div className='relative px-3'>
<div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${i18nPrefix}.inputVar`)}</div> <div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${i18nPrefix}.inputVar`)}</div>

View File

@@ -3,11 +3,8 @@ import {
useMemo, useMemo,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import { useStore } from '../../../store' import { useStore } from '../../../store'
import { BlockEnum } from '../../../types'
import type { import type {
Node,
ValueSelector, ValueSelector,
VarType, VarType,
} from '../../../types' } from '../../../types'
@@ -18,12 +15,8 @@ import {
} from '../hooks' } from '../hooks'
import { filterVar } from '../utils' import { filterVar } from '../utils'
import AddVariable from './add-variable' import AddVariable from './add-variable'
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { isExceptionVariable } from '@/app/components/workflow/utils' import VariableLabelItem from './variable-label-item'
import {
VariableLabelInNode,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
const i18nPrefix = 'workflow.nodes.variableAssigner' const i18nPrefix = 'workflow.nodes.variableAssigner'
type GroupItem = { type GroupItem = {
@@ -44,7 +37,6 @@ const NodeGroupItem = ({
const { t } = useTranslation() const { t } = useTranslation()
const enteringNodePayload = useStore(s => s.enteringNodePayload) const enteringNodePayload = useStore(s => s.enteringNodePayload)
const hoveringAssignVariableGroupId = useStore(s => s.hoveringAssignVariableGroupId) const hoveringAssignVariableGroupId = useStore(s => s.hoveringAssignVariableGroupId)
const nodes: Node[] = useNodes()
const { const {
handleGroupItemMouseEnter, handleGroupItemMouseEnter,
handleGroupItemMouseLeave, handleGroupItemMouseLeave,
@@ -128,21 +120,7 @@ const NodeGroupItem = ({
<div className='space-y-0.5'> <div className='space-y-0.5'>
{ {
item.variables.map((variable = [], index) => { item.variables.map((variable = [], index) => {
const isSystem = isSystemVar(variable) return <VariableLabelItem key={index} variable={variable} />
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
const isException = isExceptionVariable(varName, node?.data.type)
return (
<VariableLabelInNode
key={index}
variables={variable}
nodeType={node?.data.type}
nodeTitle={node?.data.title}
isExceptionVariable={isException}
/>
)
}) })
} }
</div> </div>

View File

@@ -0,0 +1,29 @@
import { memo } from 'react'
import type { ValueSelector } from '@/app/components/workflow/types'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { isExceptionVariable } from '@/app/components/workflow/utils'
import {
VariableLabelInNode,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
type VariableLabelItemProps = {
variable: ValueSelector
}
const VariableLabelItem = ({ variable }: VariableLabelItemProps) => {
const isSystem = isSystemVar(variable)
const node = useFindNode(variable)
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
const isException = isExceptionVariable(varName, node?.data.type)
return (
<VariableLabelInNode
variables={variable}
nodeType={node?.data.type}
nodeTitle={node?.data.title}
isExceptionVariable={isException}
/>
)
}
export default memo(VariableLabelItem)

View File

@@ -1,6 +1,5 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { import {
useNodes,
useStoreApi, useStoreApi,
} from 'reactflow' } from 'reactflow'
import { uniqBy } from 'lodash-es' import { uniqBy } from 'lodash-es'
@@ -122,12 +121,14 @@ export const useVariableAssigner = () => {
} }
export const useGetAvailableVars = () => { export const useGetAvailableVars = () => {
const nodes: Node[] = useNodes() const store = useStoreApi()
const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow() const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
const { getNodeAvailableVars } = useWorkflowVariables() const { getNodeAvailableVars } = useWorkflowVariables()
const isChatMode = useIsChatMode() const isChatMode = useIsChatMode()
const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean, hideEnv = false) => { const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean, hideEnv = false) => {
const availableNodes: Node[] = [] const availableNodes: Node[] = []
const { getNodes } = store.getState()
const nodes = getNodes()
const currentNode = nodes.find(node => node.id === nodeId)! const currentNode = nodes.find(node => node.id === nodeId)!
if (!currentNode) if (!currentNode)
@@ -158,7 +159,7 @@ export const useGetAvailableVars = () => {
isChatMode, isChatMode,
filterVar, filterVar,
}) })
}, [nodes, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode]) }, [store, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode])
return getAvailableVars return getAvailableVars
} }

View File

@@ -1,6 +1,5 @@
import { memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react' import { memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react'
import { useNodes } from 'reactflow' import type { Node } from '../../types'
import { BlockEnum } from '../../types'
import { import {
useStore, useStore,
useWorkflowStore, useWorkflowStore,
@@ -23,6 +22,7 @@ import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/cha
import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { FileEntity } from '@/app/components/base/file-uploader/types'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
type ChatWrapperProps = { type ChatWrapperProps = {
showConversationVariableModal: boolean showConversationVariableModal: boolean
@@ -42,8 +42,7 @@ const ChatWrapper = (
ref: React.RefObject<ChatWrapperRefType>; ref: React.RefObject<ChatWrapperRefType>;
}, },
) => { ) => {
const nodes = useNodes<StartNodeType>() const startNode = useFindNode(['sys']) as Node<StartNodeType>
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const startVariables = startNode?.data.variables const startVariables = startNode?.data.variables
const appDetail = useAppStore(s => s.appDetail) const appDetail = useAppStore(s => s.appDetail)
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()

View File

@@ -8,13 +8,13 @@ import {
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react' import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow' import { useStore as useReactFlowStore } from 'reactflow'
import { import {
useWorkflowInteractions, useWorkflowInteractions,
} from '../../hooks' } from '../../hooks'
import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync'
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
import { BlockEnum } from '../../types' import type { Node } from '../../types'
import type { StartNodeType } from '../../nodes/start/types' import type { StartNodeType } from '../../nodes/start/types'
import { useResizePanel } from '../../nodes/_base/hooks/use-resize-panel' import { useResizePanel } from '../../nodes/_base/hooks/use-resize-panel'
import ChatWrapper from './chat-wrapper' import ChatWrapper from './chat-wrapper'
@@ -24,6 +24,7 @@ import Tooltip from '@/app/components/base/tooltip'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import { useStore } from '@/app/components/workflow/store' import { useStore } from '@/app/components/workflow/store'
import { debounce, noop } from 'lodash-es' import { debounce, noop } from 'lodash-es'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
export type ChatWrapperRefType = { export type ChatWrapperRefType = {
handleRestart: () => void handleRestart: () => void
@@ -35,9 +36,8 @@ const DebugAndPreview = () => {
const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync() const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync()
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
const [expanded, setExpanded] = useState(true) const [expanded, setExpanded] = useState(true)
const nodes = useNodes<StartNodeType>() const startNode = useFindNode(['sys']) as Node<StartNodeType>
const selectedNode = nodes.find(node => node.data.selected) const selectedNode = useReactFlowStore(s => s.getNodes().find(node => node.data.selected))
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const variables = startNode?.data.variables || [] const variables = startNode?.data.variables || []
const visibleVariables = variables.filter(v => v.hide !== true) const visibleVariables = variables.filter(v => v.hide !== true)

View File

@@ -1,21 +1,20 @@
import { import {
memo, memo,
} from 'react' } from 'react'
import { useNodes } from 'reactflow'
import FormItem from '../../nodes/_base/components/before-run-form/form-item' import FormItem from '../../nodes/_base/components/before-run-form/form-item'
import { BlockEnum } from '../../types' import type { Node } from '../../types'
import { import {
useStore, useStore,
useWorkflowStore, useWorkflowStore,
} from '../../store' } from '../../store'
import type { StartNodeType } from '../../nodes/start/types' import type { StartNodeType } from '../../nodes/start/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
const UserInput = () => { const UserInput = () => {
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const inputs = useStore(s => s.inputs) const inputs = useStore(s => s.inputs)
const nodes = useNodes<StartNodeType>() const startNode = useFindNode(['sys']) as Node<StartNodeType>
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const variables = startNode?.data.variables || [] const variables = startNode?.data.variables || []
const visibleVariables = variables.filter(v => v.hide !== true) const visibleVariables = variables.filter(v => v.hide !== true)

View File

@@ -4,10 +4,8 @@ import {
useMemo, useMemo,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import FormItem from '../nodes/_base/components/before-run-form/form-item' import FormItem from '../nodes/_base/components/before-run-form/form-item'
import { import {
BlockEnum,
InputVarType, InputVarType,
WorkflowRunningStatus, WorkflowRunningStatus,
} from '../types' } from '../types'
@@ -16,7 +14,6 @@ import {
useWorkflowStore, useWorkflowStore,
} from '../store' } from '../store'
import { useWorkflowRun } from '../hooks' import { useWorkflowRun } from '../hooks'
import type { StartNodeType } from '../nodes/start/types'
import { TransferMethod } from '../../base/text-generation/types' import { TransferMethod } from '../../base/text-generation/types'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { import {
@@ -24,6 +21,7 @@ import {
} from '@/app/components/base/chat/chat/utils' } from '@/app/components/base/chat/chat/utils'
import { useCheckInputsForms } from '@/app/components/base/chat/chat/check-input-forms-hooks' import { useCheckInputsForms } from '@/app/components/base/chat/chat/check-input-forms-hooks'
import { useHooksStore } from '../hooks-store' import { useHooksStore } from '../hooks-store'
import { useFindNode } from '@/app/components/workflow/hooks/use-find-node'
type Props = { type Props = {
onRun: () => void onRun: () => void
@@ -37,19 +35,18 @@ const InputsPanel = ({ onRun }: Props) => {
setInputs: s.setInputs, setInputs: s.setInputs,
})) }))
const fileSettings = useHooksStore(s => s.configsMap?.fileSettings) const fileSettings = useHooksStore(s => s.configsMap?.fileSettings)
const nodes = useNodes<StartNodeType>() const startNode = useFindNode(['sys'])
const files = useStore(s => s.files) const files = useStore(s => s.files)
const workflowRunningData = useStore(s => s.workflowRunningData) const workflowRunningData = useStore(s => s.workflowRunningData)
const { const {
handleRun, handleRun,
} = useWorkflowRun() } = useWorkflowRun()
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const startVariables = startNode?.data.variables const startVariables = startNode?.data.variables
const { checkInputsForm } = useCheckInputsForms() const { checkInputsForm } = useCheckInputsForms()
const initialInputs = { ...inputs } const initialInputs = { ...inputs }
if (startVariables) { if (startVariables) {
startVariables.forEach((variable) => { startVariables.forEach((variable: any) => {
if (variable.default) if (variable.default)
initialInputs[variable.variable] = variable.default initialInputs[variable.variable] = variable.default
if (inputs[variable.variable] !== undefined) if (inputs[variable.variable] !== undefined)
@@ -110,7 +107,7 @@ const InputsPanel = ({ onRun }: Props) => {
<> <>
<div className='px-4 pb-2 pt-3'> <div className='px-4 pb-2 pt-3'>
{ {
variables.map((variable, index) => ( variables.map((variable: any, index: number) => (
<div <div
key={variable.variable} key={variable.variable}
className='mb-2 last-of-type:mb-0' className='mb-2 last-of-type:mb-0'

View File

@@ -1,6 +1,6 @@
import type { FC } from 'react' import type { FC } from 'react'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useNodes } from 'reactflow' import { useStore as useReactFlowStore } from 'reactflow'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiLoader2Line, RiStopCircleFill } from '@remixicon/react' import { RiLoader2Line, RiStopCircleFill } from '@remixicon/react'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
@@ -8,7 +8,6 @@ import { useStore } from '../store'
import useCurrentVars from '../hooks/use-inspect-vars-crud' import useCurrentVars from '../hooks/use-inspect-vars-crud'
import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { NodeRunningStatus } from '@/app/components/workflow/types' import { NodeRunningStatus } from '@/app/components/workflow/types'
import type { CommonNodeType } from '@/app/components/workflow/types'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
@@ -38,8 +37,7 @@ const VariableInspectTrigger: FC = () => {
getNodesReadOnly, getNodesReadOnly,
} = useNodesReadOnly() } = useNodesReadOnly()
const workflowRunningData = useStore(s => s.workflowRunningData) const workflowRunningData = useStore(s => s.workflowRunningData)
const nodes = useNodes<CommonNodeType>() const isStepRunning = useReactFlowStore(s => s.getNodes().some(node => node.data._singleRunningStatus === NodeRunningStatus.Running))
const isStepRunning = useMemo(() => nodes.some(node => node.data._singleRunningStatus === NodeRunningStatus.Running), [nodes])
const isPreviewRunning = useMemo(() => { const isPreviewRunning = useMemo(() => {
if (!workflowRunningData) if (!workflowRunningData)
return false return false