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)
This commit is contained in:
balibabu 2025-05-29 11:10:45 +08:00 committed by GitHub
parent 64f930b1c5
commit 3f695a542c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 78 additions and 28 deletions

View File

@ -1,4 +1,5 @@
import { getLLMIconName, getLlmNameAndFIdByLlmId } from '@/utils/llm-util'; import { getLLMIconName, getLlmNameAndFIdByLlmId } from '@/utils/llm-util';
import { memo } from 'react';
import { LlmIcon } from '../svg-icon'; import { LlmIcon } from '../svg-icon';
interface IProps { interface IProps {
@ -24,4 +25,4 @@ const LLMLabel = ({ value }: IProps) => {
); );
}; };
export default LLMLabel; export default memo(LLMLabel);

View File

@ -1,7 +1,7 @@
import { LlmModelType } from '@/constants/knowledge'; import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks'; import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
import * as SelectPrimitive from '@radix-ui/react-select'; 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 { LlmSettingFieldItems } from '../llm-setting-items/next';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import { Select, SelectTrigger, SelectValue } from '../ui/select'; import { Select, SelectTrigger, SelectValue } from '../ui/select';
@ -14,7 +14,7 @@ interface IProps {
disabled?: boolean; disabled?: boolean;
} }
export const NextLLMSelect = forwardRef< const NextInnerLLMSelect = forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
IProps IProps
>(({ value, disabled }, ref) => { >(({ value, disabled }, ref) => {
@ -52,4 +52,6 @@ export const NextLLMSelect = forwardRef<
); );
}); });
NextLLMSelect.displayName = 'LLMSelect'; NextInnerLLMSelect.displayName = 'LLMSelect';
export const NextLLMSelect = memo(NextInnerLLMSelect);

View File

@ -14,7 +14,7 @@ import {
SidebarHeader, SidebarHeader,
SidebarMenu, SidebarMenu,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
import { useMemo } from 'react'; import { memo, useMemo } from 'react';
import { import {
AgentOperatorList, AgentOperatorList,
Operator, Operator,
@ -77,7 +77,7 @@ function OperatorCollapsible({
); );
} }
export function AgentSidebar() { function InnerAgentSidebar() {
const agentOperatorList = useMemo(() => { const agentOperatorList = useMemo(() => {
return componentMenuList.filter((x) => return componentMenuList.filter((x) =>
AgentOperatorList.some((y) => y === x.name), AgentOperatorList.some((y) => y === x.name),
@ -108,3 +108,5 @@ export function AgentSidebar() {
</Sidebar> </Sidebar>
); );
} }
export const AgentSidebar = memo(InnerAgentSidebar);

View File

@ -18,7 +18,7 @@ import { RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
// TODO: do not allow other nodes to connect to this node // TODO: do not allow other nodes to connect to this node
function Node({ selected, data }: NodeProps<IBeginNode>) { function InnerBeginNode({ selected, data }: NodeProps<IBeginNode>) {
const { t } = useTranslation(); const { t } = useTranslation();
const query: BeginQuery[] = get(data, 'form.query', []); const query: BeginQuery[] = get(data, 'form.query', []);
const { theme } = useTheme(); const { theme } = useTheme();
@ -72,4 +72,4 @@ function Node({ selected, data }: NodeProps<IBeginNode>) {
); );
} }
export const BeginNode = memo(Node); export const BeginNode = memo(InnerBeginNode);

View File

@ -1,6 +1,6 @@
import { Handle, Position } from '@xyflow/react'; import { Handle, Position } from '@xyflow/react';
import React from 'react'; import React, { memo } from 'react';
import styles from './index.less'; import styles from './index.less';
const DEFAULT_HANDLE_STYLE = { const DEFAULT_HANDLE_STYLE = {
@ -37,4 +37,4 @@ const CategorizeHandle = ({ top, right, id, children }: IProps) => {
); );
}; };
export default CategorizeHandle; export default memo(CategorizeHandle);

View File

@ -5,12 +5,13 @@ import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd'; import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo } from 'react';
import { RightHandleStyle } from './handle-icon'; import { RightHandleStyle } from './handle-icon';
import { useBuildCategorizeHandlePositions } from './hooks'; import { useBuildCategorizeHandlePositions } from './hooks';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function CategorizeNode({ export function InnerCategorizeNode({
id, id,
data, data,
selected, selected,
@ -66,3 +67,5 @@ export function CategorizeNode({
</section> </section>
); );
} }
export const CategorizeNode = memo(InnerCategorizeNode);

View File

@ -2,12 +2,12 @@ import { IEmailNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react'; import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd'; import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useState } from 'react'; import { memo, useState } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function EmailNode({ export function InnerEmailNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -76,3 +76,5 @@ export function EmailNode({
</section> </section>
); );
} }
export const EmailNode = memo(InnerEmailNode);

View File

@ -4,11 +4,12 @@ import { IGenerateNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react'; import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function GenerateNode({ export function InnerGenerateNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -55,3 +56,5 @@ export function GenerateNode({
</section> </section>
); );
} }
export const GenerateNode = memo(InnerGenerateNode);

View File

@ -2,11 +2,12 @@ import { useTheme } from '@/components/theme-provider';
import { IRagNode } from '@/interfaces/database/flow'; import { IRagNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react'; import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function RagNode({ function InnerRagNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -43,3 +44,5 @@ export function RagNode({
</section> </section>
); );
} }
export const RagNode = memo(InnerRagNode);

View File

@ -4,12 +4,13 @@ import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd'; import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function InvokeNode({ function InnerInvokeNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -57,3 +58,5 @@ export function InvokeNode({
</section> </section>
); );
} }
export const InvokeNode = memo(InnerInvokeNode);

View File

@ -6,6 +6,7 @@ import {
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Handle, NodeProps, NodeResizeControl, Position } from '@xyflow/react'; import { Handle, NodeProps, NodeResizeControl, Position } from '@xyflow/react';
import { ListRestart } from 'lucide-react'; import { ListRestart } from 'lucide-react';
import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
@ -43,7 +44,7 @@ const controlStyle = {
cursor: 'nwse-resize', cursor: 'nwse-resize',
}; };
export function IterationNode({ export function InnerIterationNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -98,7 +99,7 @@ export function IterationNode({
); );
} }
export function IterationStartNode({ function InnerIterationStartNode({
isConnectable = true, isConnectable = true,
selected, selected,
}: NodeProps<IIterationStartNode>) { }: NodeProps<IIterationStartNode>) {
@ -125,3 +126,7 @@ export function IterationStartNode({
</section> </section>
); );
} }
export const IterationStartNode = memo(InnerIterationStartNode);
export const IterationNode = memo(InnerIterationNode);

View File

@ -4,11 +4,12 @@ import { IKeywordNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react'; import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function KeywordNode({ export function InnerKeywordNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -55,3 +56,5 @@ export function KeywordNode({
</section> </section>
); );
} }
export const KeywordNode = memo(InnerKeywordNode);

View File

@ -2,11 +2,12 @@ import { useTheme } from '@/components/theme-provider';
import { ILogicNode } from '@/interfaces/database/flow'; import { ILogicNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react'; import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function LogicNode({ export function InnerLogicNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -43,3 +44,5 @@ export function LogicNode({
</section> </section>
); );
} }
export const LogicNode = memo(InnerLogicNode);

View File

@ -4,11 +4,12 @@ import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd'; import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function MessageNode({ function InnerMessageNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -63,3 +64,5 @@ export function MessageNode({
</section> </section>
); );
} }
export const MessageNode = memo(InnerMessageNode);

View File

@ -7,6 +7,7 @@ import { needsSingleStepDebugging } from '../../utils';
import NodeDropdown from './dropdown'; import NodeDropdown from './dropdown';
import { NextNodePopover } from './popover'; import { NextNodePopover } from './popover';
import { memo } from 'react';
import { RunTooltip } from '../../flow-tooltip'; import { RunTooltip } from '../../flow-tooltip';
interface IProps { interface IProps {
id: string; id: string;
@ -37,7 +38,7 @@ export function RunStatus({ id, name, label }: IProps) {
); );
} }
const NodeHeader = ({ const InnerNodeHeader = ({
label, label,
id, id,
name, name,
@ -70,4 +71,6 @@ const NodeHeader = ({
); );
}; };
const NodeHeader = memo(InnerNodeHeader);
export default NodeHeader; export default NodeHeader;

View File

@ -6,11 +6,12 @@ import { RightHandleStyle } from './handle-icon';
import { useTheme } from '@/components/theme-provider'; import { useTheme } from '@/components/theme-provider';
import { IRelevantNode } from '@/interfaces/database/flow'; import { IRelevantNode } from '@/interfaces/database/flow';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo } from 'react';
import { useReplaceIdWithName } from '../../hooks'; import { useReplaceIdWithName } from '../../hooks';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function RelevantNode({ id, data, selected }: NodeProps<IRelevantNode>) { function InnerRelevantNode({ id, data, selected }: NodeProps<IRelevantNode>) {
const yes = get(data, 'form.yes'); const yes = get(data, 'form.yes');
const no = get(data, 'form.no'); const no = get(data, 'form.no');
const replaceIdWithName = useReplaceIdWithName(); const replaceIdWithName = useReplaceIdWithName();
@ -68,3 +69,5 @@ export function RelevantNode({ id, data, selected }: NodeProps<IRelevantNode>) {
</section> </section>
); );
} }
export const RelevantNode = memo(InnerRelevantNode);

View File

@ -6,12 +6,12 @@ import { Handle, NodeProps, Position } from '@xyflow/react';
import { Avatar, Flex } from 'antd'; import { Avatar, Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { useMemo } from 'react'; import { memo, useMemo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function RetrievalNode({ function InnerRetrievalNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -86,3 +86,5 @@ export function RetrievalNode({
</section> </section>
); );
} }
export const RetrievalNode = memo(InnerRetrievalNode);

View File

@ -4,11 +4,12 @@ import { IRewriteNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react'; import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
export function RewriteNode({ function InnerRewriteNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -55,3 +56,5 @@ export function RewriteNode({
</section> </section>
); );
} }
export const RewriteNode = memo(InnerRewriteNode);

View File

@ -3,6 +3,7 @@ import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react'; import { Handle, NodeProps, Position } from '@xyflow/react';
import { Divider, Flex } from 'antd'; import { Divider, Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { memo } from 'react';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
import { RightHandleStyle } from './handle-icon'; import { RightHandleStyle } from './handle-icon';
import { useBuildSwitchHandlePositions } from './hooks'; import { useBuildSwitchHandlePositions } from './hooks';
@ -54,7 +55,7 @@ const ConditionBlock = ({
); );
}; };
export function SwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) { function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
const { positions } = useBuildSwitchHandlePositions({ data, id }); const { positions } = useBuildSwitchHandlePositions({ data, id });
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
@ -112,3 +113,5 @@ export function SwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
</section> </section>
); );
} }
export const SwitchNode = memo(InnerSwitchNode);

View File

@ -9,9 +9,10 @@ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import { ITemplateNode } from '@/interfaces/database/flow'; import { ITemplateNode } from '@/interfaces/database/flow';
import { memo } from 'react';
import styles from './index.less'; import styles from './index.less';
export function TemplateNode({ function InnerTemplateNode({
id, id,
data, data,
isConnectable = true, isConnectable = true,
@ -73,3 +74,5 @@ export function TemplateNode({
</section> </section>
); );
} }
export const TemplateNode = memo(InnerTemplateNode);