mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-06-26 22:19:57 +00:00
### What problem does this PR solve? Feat: Modify the style of the canvas operator node #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
f7074037ef
commit
0bde5397d0
@ -9,7 +9,7 @@ export const BaseNode = forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative rounded-md border bg-card text-card-foreground',
|
||||
'relative rounded-md bg-card text-card-foreground',
|
||||
className,
|
||||
selected ? 'border-muted-foreground shadow-lg' : '',
|
||||
'hover:ring-1',
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IAgentNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { Operator } from '../../constant';
|
||||
import useGraphStore from '../../store';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader, { ToolBar } from './node-header';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function InnerAgentNode({
|
||||
id,
|
||||
@ -15,7 +16,6 @@ function InnerAgentNode({
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IAgentNode>) {
|
||||
const { theme } = useTheme();
|
||||
const getNode = useGraphStore((state) => state.getNode);
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
|
||||
@ -26,34 +26,25 @@ function InnerAgentNode({
|
||||
}, [edges, getNode, id]);
|
||||
|
||||
return (
|
||||
<ToolBar selected={selected}>
|
||||
<section
|
||||
className={classNames(
|
||||
styles.ragNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[styles.selectedNode]: selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper>
|
||||
{isNotParentAgent && (
|
||||
<>
|
||||
<Handle
|
||||
<CommonHandle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></Handle>
|
||||
<Handle
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
id="b"
|
||||
style={RightHandleStyle}
|
||||
></Handle>
|
||||
></CommonHandle>
|
||||
</>
|
||||
)}
|
||||
<Handle
|
||||
@ -70,7 +61,7 @@ function InnerAgentNode({
|
||||
style={{ left: 180 }}
|
||||
></Handle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IBeginNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import get from 'lodash/get';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -10,42 +8,31 @@ import {
|
||||
BeginQueryType,
|
||||
BeginQueryTypeIconMap,
|
||||
Operator,
|
||||
operatorMap,
|
||||
} from '../../constant';
|
||||
import { BeginQuery } from '../../interface';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import { CommonHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
|
||||
// TODO: do not allow other nodes to connect to this node
|
||||
function InnerBeginNode({ selected, data }: NodeProps<IBeginNode>) {
|
||||
function InnerBeginNode({ data }: NodeProps<IBeginNode>) {
|
||||
const { t } = useTranslation();
|
||||
const query: BeginQuery[] = get(data, 'form.query', []);
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.ragNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[styles.selectedNode]: selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Handle
|
||||
<NodeWrapper>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
></Handle>
|
||||
></CommonHandle>
|
||||
|
||||
<Flex align="center" justify={'center'} gap={10}>
|
||||
<OperatorIcon
|
||||
name={data.label as Operator}
|
||||
fontSize={24}
|
||||
color={operatorMap[data.label as Operator].color}
|
||||
></OperatorIcon>
|
||||
<OperatorIcon name={data.label as Operator}></OperatorIcon>
|
||||
<div className="truncate text-center font-semibold text-sm">
|
||||
{t(`flow.begin`)}
|
||||
</div>
|
||||
@ -68,7 +55,7 @@ function InnerBeginNode({ selected, data }: NodeProps<IBeginNode>) {
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
17
web/src/pages/agent/canvas/node/handle.tsx
Normal file
17
web/src/pages/agent/canvas/node/handle.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Handle, HandleProps } from '@xyflow/react';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
export function CommonHandle({ className, ...props }: HandleProps) {
|
||||
return (
|
||||
<Handle
|
||||
{...props}
|
||||
className={cn(
|
||||
'inline-flex justify-center items-center !bg-background-checked !size-4 !rounded-sm !border-none ',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Plus className="size-3 pointer-events-none" />
|
||||
</Handle>
|
||||
);
|
||||
}
|
@ -6,6 +6,7 @@ import { memo } from 'react';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function InnerRagNode({
|
||||
id,
|
||||
@ -15,33 +16,35 @@ function InnerRagNode({
|
||||
}: NodeProps<IRagNode>) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.ragNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[styles.selectedNode]: selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Handle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
id="b"
|
||||
style={RightHandleStyle}
|
||||
></Handle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</section>
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<section
|
||||
className={classNames(
|
||||
styles.ragNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[styles.selectedNode]: selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Handle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
id="b"
|
||||
style={RightHandleStyle}
|
||||
></Handle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</section>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IMessageNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function InnerMessageNode({
|
||||
id,
|
||||
@ -16,52 +18,43 @@ function InnerMessageNode({
|
||||
selected,
|
||||
}: NodeProps<IMessageNode>) {
|
||||
const messages: string[] = get(data, 'form.messages', []);
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[styles.selectedNode]: selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Handle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
id="b"
|
||||
></Handle>
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={data.name}
|
||||
label={data.label}
|
||||
className={classNames({
|
||||
[styles.nodeHeader]: messages.length > 0,
|
||||
})}
|
||||
></NodeHeader>
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper>
|
||||
<CommonHandle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
style={RightHandleStyle}
|
||||
id="b"
|
||||
></CommonHandle>
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={data.name}
|
||||
label={data.label}
|
||||
className={classNames({
|
||||
[styles.nodeHeader]: messages.length > 0,
|
||||
})}
|
||||
></NodeHeader>
|
||||
|
||||
<Flex vertical gap={8} className={styles.messageNodeContainer}>
|
||||
{messages.map((message, idx) => {
|
||||
return (
|
||||
<div className={styles.nodeText} key={idx}>
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</section>
|
||||
<Flex vertical gap={8} className={styles.messageNodeContainer}>
|
||||
{messages.map((message, idx) => {
|
||||
return (
|
||||
<div className={styles.nodeText} key={idx}>
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,7 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Flex } from 'antd';
|
||||
import { Copy, Play, Trash2 } from 'lucide-react';
|
||||
import { Operator, operatorMap } from '../../constant';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { memo } from 'react';
|
||||
import { Operator } from '../../constant';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import { needsSingleStepDebugging } from '../../utils';
|
||||
import NodeDropdown from './dropdown';
|
||||
import { NextNodePopover } from './popover';
|
||||
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipNode,
|
||||
TooltipTrigger,
|
||||
} from '@/components/xyflow/tooltip-node';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import { RunTooltip } from '../../flow-tooltip';
|
||||
interface IProps {
|
||||
id: string;
|
||||
label: string;
|
||||
@ -24,55 +11,20 @@ interface IProps {
|
||||
wrapperClassName?: string;
|
||||
}
|
||||
|
||||
const ExcludedRunStateOperators = [Operator.Answer];
|
||||
|
||||
export function RunStatus({ id, name, label }: IProps) {
|
||||
const { t } = useTranslate('flow');
|
||||
return (
|
||||
<section className="flex justify-end items-center pb-1 gap-2 text-blue-600">
|
||||
{needsSingleStepDebugging(label) && (
|
||||
<RunTooltip>
|
||||
<Play className="size-3 cursor-pointer" data-play />
|
||||
</RunTooltip> // data-play is used to trigger single step debugging
|
||||
)}
|
||||
<NextNodePopover nodeId={id} name={name}>
|
||||
<span className="cursor-pointer text-[10px]">
|
||||
{t('operationResults')}
|
||||
</span>
|
||||
</NextNodePopover>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const InnerNodeHeader = ({
|
||||
label,
|
||||
id,
|
||||
name,
|
||||
gap = 4,
|
||||
className,
|
||||
wrapperClassName,
|
||||
}: IProps) => {
|
||||
return (
|
||||
<section className={wrapperClassName}>
|
||||
{!ExcludedRunStateOperators.includes(label as Operator) && (
|
||||
<RunStatus id={id} name={name} label={label}></RunStatus>
|
||||
)}
|
||||
<Flex
|
||||
flex={1}
|
||||
align="center"
|
||||
justify={'space-between'}
|
||||
gap={gap}
|
||||
className={className}
|
||||
>
|
||||
<OperatorIcon
|
||||
name={label as Operator}
|
||||
color={operatorMap[label as Operator]?.color}
|
||||
></OperatorIcon>
|
||||
<section className={cn(wrapperClassName, 'pb-4')}>
|
||||
<div className={cn(className, 'flex gap-2.5')}>
|
||||
<OperatorIcon name={label as Operator}></OperatorIcon>
|
||||
<span className="truncate text-center font-semibold text-sm">
|
||||
{name}
|
||||
</span>
|
||||
<NodeDropdown id={id} label={label}></NodeDropdown>
|
||||
</Flex>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@ -80,37 +32,3 @@ const InnerNodeHeader = ({
|
||||
const NodeHeader = memo(InnerNodeHeader);
|
||||
|
||||
export default NodeHeader;
|
||||
|
||||
function IconWrapper({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<div className="p-1.5 bg-text-title rounded-sm cursor-pointer">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ToolBarProps = {
|
||||
selected?: boolean | undefined;
|
||||
} & PropsWithChildren;
|
||||
|
||||
export function ToolBar({ selected, children }: ToolBarProps) {
|
||||
return (
|
||||
<TooltipNode selected={selected}>
|
||||
<TooltipTrigger>{children}</TooltipTrigger>
|
||||
|
||||
<TooltipContent position={Position.Top}>
|
||||
<section className="flex gap-2 items-center">
|
||||
<IconWrapper>
|
||||
<Play className="size-3.5" />
|
||||
</IconWrapper>
|
||||
<IconWrapper>
|
||||
<Copy className="size-3.5" />
|
||||
</IconWrapper>
|
||||
<IconWrapper>
|
||||
<Trash2 className="size-3.5" />
|
||||
</IconWrapper>
|
||||
</section>
|
||||
</TooltipContent>
|
||||
</TooltipNode>
|
||||
);
|
||||
}
|
||||
|
18
web/src/pages/agent/canvas/node/node-wrapper.tsx
Normal file
18
web/src/pages/agent/canvas/node/node-wrapper.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { HTMLAttributes, PropsWithChildren } from 'react';
|
||||
|
||||
export function NodeWrapper({
|
||||
children,
|
||||
className,
|
||||
}: PropsWithChildren & HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'bg-background-header-bar p-2.5 rounded-md w-[200px]',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { IRetrievalNode } from '@/interfaces/database/flow';
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { Avatar, Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function InnerRetrievalNode({
|
||||
id,
|
||||
@ -18,7 +20,6 @@ function InnerRetrievalNode({
|
||||
selected,
|
||||
}: NodeProps<IRetrievalNode>) {
|
||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
||||
const { theme } = useTheme();
|
||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||
const knowledgeBases = useMemo(() => {
|
||||
return knowledgeBaseIds.map((x) => {
|
||||
@ -32,58 +33,52 @@ function InnerRetrievalNode({
|
||||
}, [knowledgeList, knowledgeBaseIds]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[styles.selectedNode]: selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Handle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
id="b"
|
||||
></Handle>
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={data.name}
|
||||
label={data.label}
|
||||
className={classNames({
|
||||
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
|
||||
})}
|
||||
></NodeHeader>
|
||||
<Flex vertical gap={8}>
|
||||
{knowledgeBases.map((knowledge) => {
|
||||
return (
|
||||
<div className={styles.nodeText} key={knowledge.id}>
|
||||
<Flex align={'center'} gap={6}>
|
||||
<Avatar
|
||||
size={26}
|
||||
icon={<UserOutlined />}
|
||||
src={knowledge.avatar}
|
||||
/>
|
||||
<Flex className={styles.knowledgeNodeName} flex={1}>
|
||||
{knowledge.name}
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper>
|
||||
<CommonHandle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
id="b"
|
||||
></CommonHandle>
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={data.name}
|
||||
label={data.label}
|
||||
className={classNames({
|
||||
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
|
||||
})}
|
||||
></NodeHeader>
|
||||
<Flex vertical gap={8}>
|
||||
{knowledgeBases.map((knowledge) => {
|
||||
return (
|
||||
<div className={styles.nodeText} key={knowledge.id}>
|
||||
<Flex align={'center'} gap={6}>
|
||||
<Avatar
|
||||
size={26}
|
||||
icon={<UserOutlined />}
|
||||
src={knowledge.avatar}
|
||||
/>
|
||||
<Flex className={styles.knowledgeNodeName} flex={1}>
|
||||
{knowledge.name}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { IconFont } from '@/components/icon-font';
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import classNames from 'classnames';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { SwitchOperatorOptions } from '../../constant';
|
||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
||||
import { CommonHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import { useBuildSwitchHandlePositions } from './hooks';
|
||||
import styles from './index.less';
|
||||
import NodeHeader, { ToolBar } from './node-header';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
const getConditionKey = (idx: number, length: number) => {
|
||||
if (idx === 0 && length !== 1) {
|
||||
@ -58,32 +58,16 @@ const ConditionBlock = ({
|
||||
|
||||
function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
||||
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<ToolBar selected={selected}>
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[styles.selectedNode]: selected,
|
||||
},
|
||||
'group/operator hover:bg-slate-100',
|
||||
)}
|
||||
>
|
||||
<Handle
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper>
|
||||
<CommonHandle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
id={'a'}
|
||||
></Handle>
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={data.name}
|
||||
label={data.label}
|
||||
className={styles.nodeHeader}
|
||||
></NodeHeader>
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
<section className="gap-2.5 flex flex-col">
|
||||
{positions.map((position, idx) => {
|
||||
return (
|
||||
@ -103,20 +87,19 @@ function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
||||
></ConditionBlock>
|
||||
)}
|
||||
</section>
|
||||
<Handle
|
||||
<CommonHandle
|
||||
key={position.text}
|
||||
id={position.text}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
style={{ ...RightHandleStyle, top: position.top }}
|
||||
></Handle>
|
||||
></CommonHandle>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
74
web/src/pages/agent/canvas/node/toolbar.tsx
Normal file
74
web/src/pages/agent/canvas/node/toolbar.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipNode,
|
||||
TooltipTrigger,
|
||||
} from '@/components/xyflow/tooltip-node';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { Copy, Play, Trash2 } from 'lucide-react';
|
||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { Operator } from '../../constant';
|
||||
import { useDuplicateNode } from '../../hooks';
|
||||
import useGraphStore from '../../store';
|
||||
|
||||
function IconWrapper({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<div className="p-1.5 bg-text-title rounded-sm cursor-pointer">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ToolBarProps = {
|
||||
selected?: boolean | undefined;
|
||||
label: string;
|
||||
id: string;
|
||||
} & PropsWithChildren;
|
||||
|
||||
export function ToolBar({ selected, children, label, id }: ToolBarProps) {
|
||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||
const deleteIterationNodeById = useGraphStore(
|
||||
(store) => store.deleteIterationNodeById,
|
||||
);
|
||||
|
||||
const deleteNode: MouseEventHandler<SVGElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
if (label === Operator.Iteration) {
|
||||
deleteIterationNodeById(id);
|
||||
} else {
|
||||
deleteNodeById(id);
|
||||
}
|
||||
},
|
||||
[deleteIterationNodeById, deleteNodeById, id, label],
|
||||
);
|
||||
|
||||
const duplicateNode = useDuplicateNode();
|
||||
|
||||
const handleDuplicate: MouseEventHandler<SVGElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateNode(id, label);
|
||||
},
|
||||
[duplicateNode, id, label],
|
||||
);
|
||||
|
||||
return (
|
||||
<TooltipNode selected={selected}>
|
||||
<TooltipTrigger>{children}</TooltipTrigger>
|
||||
|
||||
<TooltipContent position={Position.Top}>
|
||||
<section className="flex gap-2 items-center">
|
||||
<IconWrapper>
|
||||
<Play className="size-3.5" />
|
||||
</IconWrapper>
|
||||
<IconWrapper>
|
||||
<Copy className="size-3.5" onClick={handleDuplicate} />
|
||||
</IconWrapper>
|
||||
<IconWrapper>
|
||||
<Trash2 className="size-3.5" onClick={deleteNode} />
|
||||
</IconWrapper>
|
||||
</section>
|
||||
</TooltipContent>
|
||||
</TooltipNode>
|
||||
);
|
||||
}
|
@ -9,7 +9,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
export const OperatorIconMap = {
|
||||
[Operator.Retrieval]: 'retrival-0',
|
||||
[Operator.Retrieval]: 'KR',
|
||||
// [Operator.Generate]: MergeCellsOutlined,
|
||||
// [Operator.Answer]: SendOutlined,
|
||||
[Operator.Begin]: CirclePlay,
|
||||
|
Loading…
x
Reference in New Issue
Block a user