From 3f695a542c7e0f54ae9293a772419dbc657da29c Mon Sep 17 00:00:00 2001 From: balibabu Date: Thu, 29 May 2025 11:10:45 +0800 Subject: [PATCH] Feat: Use memo to wrap canvas nodes to improve fluency #3221 (#7929) ### What problem does this PR solve? Feat: Use memo to wrap canvas nodes to improve fluency #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/llm-select/llm-label.tsx | 3 ++- web/src/components/llm-select/next.tsx | 8 +++++--- web/src/pages/agent/agent-sidebar.tsx | 6 ++++-- web/src/pages/agent/canvas/node/begin-node.tsx | 4 ++-- web/src/pages/agent/canvas/node/categorize-handle.tsx | 4 ++-- web/src/pages/agent/canvas/node/categorize-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/email-node.tsx | 6 ++++-- web/src/pages/agent/canvas/node/generate-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/index.tsx | 5 ++++- web/src/pages/agent/canvas/node/invoke-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/iteration-node.tsx | 9 +++++++-- web/src/pages/agent/canvas/node/keyword-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/logic-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/message-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/node-header.tsx | 5 ++++- web/src/pages/agent/canvas/node/relevant-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/retrieval-node.tsx | 6 ++++-- web/src/pages/agent/canvas/node/rewrite-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/switch-node.tsx | 5 ++++- web/src/pages/agent/canvas/node/template-node.tsx | 5 ++++- 20 files changed, 78 insertions(+), 28 deletions(-) diff --git a/web/src/components/llm-select/llm-label.tsx b/web/src/components/llm-select/llm-label.tsx index 29c75c91f..1840ae957 100644 --- a/web/src/components/llm-select/llm-label.tsx +++ b/web/src/components/llm-select/llm-label.tsx @@ -1,4 +1,5 @@ import { getLLMIconName, getLlmNameAndFIdByLlmId } from '@/utils/llm-util'; +import { memo } from 'react'; import { LlmIcon } from '../svg-icon'; interface IProps { @@ -24,4 +25,4 @@ const LLMLabel = ({ value }: IProps) => { ); }; -export default LLMLabel; +export default memo(LLMLabel); diff --git a/web/src/components/llm-select/next.tsx b/web/src/components/llm-select/next.tsx index 9e1141a59..4b0c0b07a 100644 --- a/web/src/components/llm-select/next.tsx +++ b/web/src/components/llm-select/next.tsx @@ -1,7 +1,7 @@ import { LlmModelType } from '@/constants/knowledge'; import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks'; import * as SelectPrimitive from '@radix-ui/react-select'; -import { forwardRef, useState } from 'react'; +import { forwardRef, memo, useState } from 'react'; import { LlmSettingFieldItems } from '../llm-setting-items/next'; import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { Select, SelectTrigger, SelectValue } from '../ui/select'; @@ -14,7 +14,7 @@ interface IProps { disabled?: boolean; } -export const NextLLMSelect = forwardRef< +const NextInnerLLMSelect = forwardRef< React.ElementRef, IProps >(({ value, disabled }, ref) => { @@ -52,4 +52,6 @@ export const NextLLMSelect = forwardRef< ); }); -NextLLMSelect.displayName = 'LLMSelect'; +NextInnerLLMSelect.displayName = 'LLMSelect'; + +export const NextLLMSelect = memo(NextInnerLLMSelect); diff --git a/web/src/pages/agent/agent-sidebar.tsx b/web/src/pages/agent/agent-sidebar.tsx index f5aef42bf..dd642981e 100644 --- a/web/src/pages/agent/agent-sidebar.tsx +++ b/web/src/pages/agent/agent-sidebar.tsx @@ -14,7 +14,7 @@ import { SidebarHeader, SidebarMenu, } from '@/components/ui/sidebar'; -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { AgentOperatorList, Operator, @@ -77,7 +77,7 @@ function OperatorCollapsible({ ); } -export function AgentSidebar() { +function InnerAgentSidebar() { const agentOperatorList = useMemo(() => { return componentMenuList.filter((x) => AgentOperatorList.some((y) => y === x.name), @@ -108,3 +108,5 @@ export function AgentSidebar() { ); } + +export const AgentSidebar = memo(InnerAgentSidebar); diff --git a/web/src/pages/agent/canvas/node/begin-node.tsx b/web/src/pages/agent/canvas/node/begin-node.tsx index 3f461a69f..87fed0ee8 100644 --- a/web/src/pages/agent/canvas/node/begin-node.tsx +++ b/web/src/pages/agent/canvas/node/begin-node.tsx @@ -18,7 +18,7 @@ import { RightHandleStyle } from './handle-icon'; import styles from './index.less'; // TODO: do not allow other nodes to connect to this node -function Node({ selected, data }: NodeProps) { +function InnerBeginNode({ selected, data }: NodeProps) { const { t } = useTranslation(); const query: BeginQuery[] = get(data, 'form.query', []); const { theme } = useTheme(); @@ -72,4 +72,4 @@ function Node({ selected, data }: NodeProps) { ); } -export const BeginNode = memo(Node); +export const BeginNode = memo(InnerBeginNode); diff --git a/web/src/pages/agent/canvas/node/categorize-handle.tsx b/web/src/pages/agent/canvas/node/categorize-handle.tsx index ce1fc3624..f1eeff765 100644 --- a/web/src/pages/agent/canvas/node/categorize-handle.tsx +++ b/web/src/pages/agent/canvas/node/categorize-handle.tsx @@ -1,6 +1,6 @@ import { Handle, Position } from '@xyflow/react'; -import React from 'react'; +import React, { memo } from 'react'; import styles from './index.less'; const DEFAULT_HANDLE_STYLE = { @@ -37,4 +37,4 @@ const CategorizeHandle = ({ top, right, id, children }: IProps) => { ); }; -export default CategorizeHandle; +export default memo(CategorizeHandle); diff --git a/web/src/pages/agent/canvas/node/categorize-node.tsx b/web/src/pages/agent/canvas/node/categorize-node.tsx index 18c3cdff0..14b7a66db 100644 --- a/web/src/pages/agent/canvas/node/categorize-node.tsx +++ b/web/src/pages/agent/canvas/node/categorize-node.tsx @@ -5,12 +5,13 @@ import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; +import { memo } from 'react'; import { RightHandleStyle } from './handle-icon'; import { useBuildCategorizeHandlePositions } from './hooks'; import styles from './index.less'; import NodeHeader from './node-header'; -export function CategorizeNode({ +export function InnerCategorizeNode({ id, data, selected, @@ -66,3 +67,5 @@ export function CategorizeNode({ ); } + +export const CategorizeNode = memo(InnerCategorizeNode); diff --git a/web/src/pages/agent/canvas/node/email-node.tsx b/web/src/pages/agent/canvas/node/email-node.tsx index ae4af848c..9482194f3 100644 --- a/web/src/pages/agent/canvas/node/email-node.tsx +++ b/web/src/pages/agent/canvas/node/email-node.tsx @@ -2,12 +2,12 @@ import { IEmailNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; -import { useState } from 'react'; +import { memo, useState } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function EmailNode({ +export function InnerEmailNode({ id, data, isConnectable = true, @@ -76,3 +76,5 @@ export function EmailNode({ ); } + +export const EmailNode = memo(InnerEmailNode); diff --git a/web/src/pages/agent/canvas/node/generate-node.tsx b/web/src/pages/agent/canvas/node/generate-node.tsx index 255eccd99..8ffbbd79c 100644 --- a/web/src/pages/agent/canvas/node/generate-node.tsx +++ b/web/src/pages/agent/canvas/node/generate-node.tsx @@ -4,11 +4,12 @@ import { IGenerateNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; import { get } from 'lodash'; +import { memo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function GenerateNode({ +export function InnerGenerateNode({ id, data, isConnectable = true, @@ -55,3 +56,5 @@ export function GenerateNode({ ); } + +export const GenerateNode = memo(InnerGenerateNode); diff --git a/web/src/pages/agent/canvas/node/index.tsx b/web/src/pages/agent/canvas/node/index.tsx index 32191f5cc..286c3e080 100644 --- a/web/src/pages/agent/canvas/node/index.tsx +++ b/web/src/pages/agent/canvas/node/index.tsx @@ -2,11 +2,12 @@ import { useTheme } from '@/components/theme-provider'; import { IRagNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; +import { memo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function RagNode({ +function InnerRagNode({ id, data, isConnectable = true, @@ -43,3 +44,5 @@ export function RagNode({ ); } + +export const RagNode = memo(InnerRagNode); diff --git a/web/src/pages/agent/canvas/node/invoke-node.tsx b/web/src/pages/agent/canvas/node/invoke-node.tsx index 42d109f3d..cf1e28d02 100644 --- a/web/src/pages/agent/canvas/node/invoke-node.tsx +++ b/web/src/pages/agent/canvas/node/invoke-node.tsx @@ -4,12 +4,13 @@ import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function InvokeNode({ +function InnerInvokeNode({ id, data, isConnectable = true, @@ -57,3 +58,5 @@ export function InvokeNode({ ); } + +export const InvokeNode = memo(InnerInvokeNode); diff --git a/web/src/pages/agent/canvas/node/iteration-node.tsx b/web/src/pages/agent/canvas/node/iteration-node.tsx index c15b4fc6c..53a84835d 100644 --- a/web/src/pages/agent/canvas/node/iteration-node.tsx +++ b/web/src/pages/agent/canvas/node/iteration-node.tsx @@ -6,6 +6,7 @@ import { import { cn } from '@/lib/utils'; import { Handle, NodeProps, NodeResizeControl, Position } from '@xyflow/react'; import { ListRestart } from 'lucide-react'; +import { memo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; @@ -43,7 +44,7 @@ const controlStyle = { cursor: 'nwse-resize', }; -export function IterationNode({ +export function InnerIterationNode({ id, data, isConnectable = true, @@ -98,7 +99,7 @@ export function IterationNode({ ); } -export function IterationStartNode({ +function InnerIterationStartNode({ isConnectable = true, selected, }: NodeProps) { @@ -125,3 +126,7 @@ export function IterationStartNode({ ); } + +export const IterationStartNode = memo(InnerIterationStartNode); + +export const IterationNode = memo(InnerIterationNode); diff --git a/web/src/pages/agent/canvas/node/keyword-node.tsx b/web/src/pages/agent/canvas/node/keyword-node.tsx index f607d4317..012dcf26c 100644 --- a/web/src/pages/agent/canvas/node/keyword-node.tsx +++ b/web/src/pages/agent/canvas/node/keyword-node.tsx @@ -4,11 +4,12 @@ import { IKeywordNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; import { get } from 'lodash'; +import { memo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function KeywordNode({ +export function InnerKeywordNode({ id, data, isConnectable = true, @@ -55,3 +56,5 @@ export function KeywordNode({ ); } + +export const KeywordNode = memo(InnerKeywordNode); diff --git a/web/src/pages/agent/canvas/node/logic-node.tsx b/web/src/pages/agent/canvas/node/logic-node.tsx index 28215617b..3e43954ae 100644 --- a/web/src/pages/agent/canvas/node/logic-node.tsx +++ b/web/src/pages/agent/canvas/node/logic-node.tsx @@ -2,11 +2,12 @@ import { useTheme } from '@/components/theme-provider'; import { ILogicNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; +import { memo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function LogicNode({ +export function InnerLogicNode({ id, data, isConnectable = true, @@ -43,3 +44,5 @@ export function LogicNode({ ); } + +export const LogicNode = memo(InnerLogicNode); diff --git a/web/src/pages/agent/canvas/node/message-node.tsx b/web/src/pages/agent/canvas/node/message-node.tsx index 5b3a1736e..5ed80dcbc 100644 --- a/web/src/pages/agent/canvas/node/message-node.tsx +++ b/web/src/pages/agent/canvas/node/message-node.tsx @@ -4,11 +4,12 @@ import { Handle, NodeProps, Position } from '@xyflow/react'; import { Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; +import { memo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function MessageNode({ +function InnerMessageNode({ id, data, isConnectable = true, @@ -63,3 +64,5 @@ export function MessageNode({ ); } + +export const MessageNode = memo(InnerMessageNode); diff --git a/web/src/pages/agent/canvas/node/node-header.tsx b/web/src/pages/agent/canvas/node/node-header.tsx index 99a37dc1e..29847e7a5 100644 --- a/web/src/pages/agent/canvas/node/node-header.tsx +++ b/web/src/pages/agent/canvas/node/node-header.tsx @@ -7,6 +7,7 @@ import { needsSingleStepDebugging } from '../../utils'; import NodeDropdown from './dropdown'; import { NextNodePopover } from './popover'; +import { memo } from 'react'; import { RunTooltip } from '../../flow-tooltip'; interface IProps { id: string; @@ -37,7 +38,7 @@ export function RunStatus({ id, name, label }: IProps) { ); } -const NodeHeader = ({ +const InnerNodeHeader = ({ label, id, name, @@ -70,4 +71,6 @@ const NodeHeader = ({ ); }; +const NodeHeader = memo(InnerNodeHeader); + export default NodeHeader; diff --git a/web/src/pages/agent/canvas/node/relevant-node.tsx b/web/src/pages/agent/canvas/node/relevant-node.tsx index acc098d69..410a7accd 100644 --- a/web/src/pages/agent/canvas/node/relevant-node.tsx +++ b/web/src/pages/agent/canvas/node/relevant-node.tsx @@ -6,11 +6,12 @@ import { RightHandleStyle } from './handle-icon'; import { useTheme } from '@/components/theme-provider'; import { IRelevantNode } from '@/interfaces/database/flow'; import { get } from 'lodash'; +import { memo } from 'react'; import { useReplaceIdWithName } from '../../hooks'; import styles from './index.less'; import NodeHeader from './node-header'; -export function RelevantNode({ id, data, selected }: NodeProps) { +function InnerRelevantNode({ id, data, selected }: NodeProps) { const yes = get(data, 'form.yes'); const no = get(data, 'form.no'); const replaceIdWithName = useReplaceIdWithName(); @@ -68,3 +69,5 @@ export function RelevantNode({ id, data, selected }: NodeProps) { ); } + +export const RelevantNode = memo(InnerRelevantNode); diff --git a/web/src/pages/agent/canvas/node/retrieval-node.tsx b/web/src/pages/agent/canvas/node/retrieval-node.tsx index 0fd2760ed..708aebd05 100644 --- a/web/src/pages/agent/canvas/node/retrieval-node.tsx +++ b/web/src/pages/agent/canvas/node/retrieval-node.tsx @@ -6,12 +6,12 @@ import { Handle, NodeProps, Position } from '@xyflow/react'; import { Avatar, Flex } from 'antd'; import classNames from 'classnames'; import { get } from 'lodash'; -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function RetrievalNode({ +function InnerRetrievalNode({ id, data, isConnectable = true, @@ -86,3 +86,5 @@ export function RetrievalNode({ ); } + +export const RetrievalNode = memo(InnerRetrievalNode); diff --git a/web/src/pages/agent/canvas/node/rewrite-node.tsx b/web/src/pages/agent/canvas/node/rewrite-node.tsx index 093b2c80e..134899c8b 100644 --- a/web/src/pages/agent/canvas/node/rewrite-node.tsx +++ b/web/src/pages/agent/canvas/node/rewrite-node.tsx @@ -4,11 +4,12 @@ import { IRewriteNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; import classNames from 'classnames'; import { get } from 'lodash'; +import { memo } from 'react'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import styles from './index.less'; import NodeHeader from './node-header'; -export function RewriteNode({ +function InnerRewriteNode({ id, data, isConnectable = true, @@ -55,3 +56,5 @@ export function RewriteNode({ ); } + +export const RewriteNode = memo(InnerRewriteNode); diff --git a/web/src/pages/agent/canvas/node/switch-node.tsx b/web/src/pages/agent/canvas/node/switch-node.tsx index 860a0ba96..7b82574f6 100644 --- a/web/src/pages/agent/canvas/node/switch-node.tsx +++ b/web/src/pages/agent/canvas/node/switch-node.tsx @@ -3,6 +3,7 @@ import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow'; import { Handle, NodeProps, Position } from '@xyflow/react'; import { Divider, Flex } from 'antd'; import classNames from 'classnames'; +import { memo } from 'react'; import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; import { RightHandleStyle } from './handle-icon'; import { useBuildSwitchHandlePositions } from './hooks'; @@ -54,7 +55,7 @@ const ConditionBlock = ({ ); }; -export function SwitchNode({ id, data, selected }: NodeProps) { +function InnerSwitchNode({ id, data, selected }: NodeProps) { const { positions } = useBuildSwitchHandlePositions({ data, id }); const { theme } = useTheme(); return ( @@ -112,3 +113,5 @@ export function SwitchNode({ id, data, selected }: NodeProps) { ); } + +export const SwitchNode = memo(InnerSwitchNode); diff --git a/web/src/pages/agent/canvas/node/template-node.tsx b/web/src/pages/agent/canvas/node/template-node.tsx index 971fbab38..b204717ab 100644 --- a/web/src/pages/agent/canvas/node/template-node.tsx +++ b/web/src/pages/agent/canvas/node/template-node.tsx @@ -9,9 +9,10 @@ import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import NodeHeader from './node-header'; import { ITemplateNode } from '@/interfaces/database/flow'; +import { memo } from 'react'; import styles from './index.less'; -export function TemplateNode({ +function InnerTemplateNode({ id, data, isConnectable = true, @@ -73,3 +74,5 @@ export function TemplateNode({ ); } + +export const TemplateNode = memo(InnerTemplateNode);