mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-12 23:19:03 +08:00
### What problem does this PR solve? feat: add delete menu to graph node #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
e05395d2a7
commit
3b7b6240c3
@ -1,6 +1,5 @@
|
|||||||
import { ReactComponent as MoreIcon } from '@/assets/svg/more.svg';
|
|
||||||
import { useShowDeleteConfirm } from '@/hooks/commonHooks';
|
import { useShowDeleteConfirm } from '@/hooks/commonHooks';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
|
||||||
import { Dropdown, MenuProps, Space } from 'antd';
|
import { Dropdown, MenuProps, Space } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -8,12 +7,14 @@ import React from 'react';
|
|||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
deleteItem: () => Promise<any>;
|
deleteItem: () => Promise<any> | void;
|
||||||
|
iconFontSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OperateDropdown = ({
|
const OperateDropdown = ({
|
||||||
deleteItem,
|
deleteItem,
|
||||||
children,
|
children,
|
||||||
|
iconFontSize = 30,
|
||||||
}: React.PropsWithChildren<IProps>) => {
|
}: React.PropsWithChildren<IProps>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const showDeleteConfirm = useShowDeleteConfirm();
|
const showDeleteConfirm = useShowDeleteConfirm();
|
||||||
@ -51,7 +52,10 @@ const OperateDropdown = ({
|
|||||||
>
|
>
|
||||||
{children || (
|
{children || (
|
||||||
<span className={styles.delete}>
|
<span className={styles.delete}>
|
||||||
<MoreIcon />
|
<MoreOutlined
|
||||||
|
rotate={90}
|
||||||
|
style={{ fontSize: iconFontSize, color: 'gray', cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
@ -18,12 +18,12 @@ import {
|
|||||||
useSelectCanvasData,
|
useSelectCanvasData,
|
||||||
useShowDrawer,
|
useShowDrawer,
|
||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
import { TextUpdaterNode } from './node';
|
import { RagNode } from './node';
|
||||||
|
|
||||||
import ChatDrawer from '../chat/drawer';
|
import ChatDrawer from '../chat/drawer';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const nodeTypes = { textUpdater: TextUpdaterNode };
|
const nodeTypes = { ragNode: RagNode };
|
||||||
|
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
buttonEdge: ButtonEdge,
|
buttonEdge: ButtonEdge,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
.textUpdaterNode {
|
.ragNode {
|
||||||
// height: 50px;
|
// height: 50px;
|
||||||
|
position: relative;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
-6px 0 12px 0 rgba(179, 177, 177, 0.08),
|
-6px 0 12px 0 rgba(179, 177, 177, 0.08),
|
||||||
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
|
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
|
||||||
@ -13,6 +14,9 @@
|
|||||||
color: #777;
|
color: #777;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.selectedNode {
|
.selectedNode {
|
||||||
border: 1px solid rgb(59, 118, 244);
|
border: 1px solid rgb(59, 118, 244);
|
||||||
|
@ -1,19 +1,28 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Handle, NodeProps, Position } from 'reactflow';
|
import { Handle, NodeProps, Position } from 'reactflow';
|
||||||
|
|
||||||
import { Space } from 'antd';
|
import OperateDropdown from '@/components/operate-dropdown';
|
||||||
import { Operator } from '../../constant';
|
import { Flex, Space } from 'antd';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { Operator, operatorMap } from '../../constant';
|
||||||
import OperatorIcon from '../../operator-icon';
|
import OperatorIcon from '../../operator-icon';
|
||||||
|
import useGraphStore from '../../store';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
export function TextUpdaterNode({
|
export function RagNode({
|
||||||
|
id,
|
||||||
data,
|
data,
|
||||||
isConnectable = true,
|
isConnectable = true,
|
||||||
selected,
|
selected,
|
||||||
}: NodeProps<{ label: string }>) {
|
}: NodeProps<{ label: string }>) {
|
||||||
|
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||||
|
const deleteNode = useCallback(() => {
|
||||||
|
deleteNodeById(id);
|
||||||
|
}, [id, deleteNodeById]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={classNames(styles.textUpdaterNode, {
|
className={classNames(styles.ragNode, {
|
||||||
[styles.selectedNode]: selected,
|
[styles.selectedNode]: selected,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@ -37,14 +46,21 @@ export function TextUpdaterNode({
|
|||||||
{/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */}
|
{/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */}
|
||||||
</Handle>
|
</Handle>
|
||||||
<Handle type="source" position={Position.Bottom} id="a" isConnectable />
|
<Handle type="source" position={Position.Bottom} id="a" isConnectable />
|
||||||
<div>
|
<Flex gap={10} justify={'space-between'}>
|
||||||
<Space size={4}>
|
<Space size={6}>
|
||||||
<OperatorIcon
|
<OperatorIcon
|
||||||
name={data.label as Operator}
|
name={data.label as Operator}
|
||||||
fontSize={12}
|
fontSize={12}
|
||||||
></OperatorIcon>
|
></OperatorIcon>
|
||||||
{data.label}
|
<span>{data.label}</span>
|
||||||
</Space>
|
</Space>
|
||||||
|
<OperateDropdown
|
||||||
|
iconFontSize={14}
|
||||||
|
deleteItem={deleteNode}
|
||||||
|
></OperateDropdown>
|
||||||
|
</Flex>
|
||||||
|
<div className={styles.description}>
|
||||||
|
{operatorMap[data.label as Operator].description}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -19,18 +19,27 @@ export const operatorIconMap = {
|
|||||||
[Operator.Begin]: SlidersOutlined,
|
[Operator.Begin]: SlidersOutlined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentList = [
|
export const operatorMap = {
|
||||||
|
[Operator.Retrieval]: {
|
||||||
|
description: 'Retrieval description drjlftglrthjftl',
|
||||||
|
},
|
||||||
|
[Operator.Generate]: { description: 'Generate description' },
|
||||||
|
[Operator.Answer]: { description: 'Answer description' },
|
||||||
|
[Operator.Begin]: { description: 'Begin description' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const componentMenuList = [
|
||||||
{
|
{
|
||||||
name: Operator.Retrieval,
|
name: Operator.Retrieval,
|
||||||
description: '',
|
description: operatorMap[Operator.Retrieval].description,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: Operator.Generate,
|
name: Operator.Generate,
|
||||||
description: '',
|
description: operatorMap[Operator.Generate].description,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: Operator.Answer,
|
name: Operator.Answer,
|
||||||
description: '',
|
description: operatorMap[Operator.Answer].description,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { Card, Flex, Layout, Space } from 'antd';
|
import { Card, Flex, Layout, Space, Typography } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { componentList } from '../constant';
|
import { componentMenuList } from '../constant';
|
||||||
import { useHandleDrag } from '../hooks';
|
import { useHandleDrag } from '../hooks';
|
||||||
import OperatorIcon from '../operator-icon';
|
import OperatorIcon from '../operator-icon';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
setCollapsed: (width: boolean) => void;
|
setCollapsed: (width: boolean) => void;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
@ -25,7 +27,7 @@ const FlowSide = ({ setCollapsed, collapsed }: IProps) => {
|
|||||||
onCollapse={(value) => setCollapsed(value)}
|
onCollapse={(value) => setCollapsed(value)}
|
||||||
>
|
>
|
||||||
<Flex vertical gap={10} className={styles.siderContent}>
|
<Flex vertical gap={10} className={styles.siderContent}>
|
||||||
{componentList.map((x) => {
|
{componentMenuList.map((x) => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={x.name}
|
key={x.name}
|
||||||
@ -37,13 +39,14 @@ const FlowSide = ({ setCollapsed, collapsed }: IProps) => {
|
|||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Space size={15}>
|
<Space size={15}>
|
||||||
<OperatorIcon name={x.name}></OperatorIcon>
|
<OperatorIcon name={x.name}></OperatorIcon>
|
||||||
{/* <Avatar
|
|
||||||
icon={<OperatorIcon name={x.name}></OperatorIcon>}
|
|
||||||
shape={'square'}
|
|
||||||
/> */}
|
|
||||||
<section>
|
<section>
|
||||||
<b>{x.name}</b>
|
<b>{x.name}</b>
|
||||||
<div>{x.description}</div>
|
<Text
|
||||||
|
ellipsis={{ tooltip: x.description }}
|
||||||
|
style={{ width: 130 }}
|
||||||
|
>
|
||||||
|
{x.description}
|
||||||
|
</Text>
|
||||||
</section>
|
</section>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -18,6 +18,7 @@ import { Node, Position, ReactFlowInstance } from 'reactflow';
|
|||||||
import { useDebounceEffect } from 'ahooks';
|
import { useDebounceEffect } from 'ahooks';
|
||||||
import { humanId } from 'human-id';
|
import { humanId } from 'human-id';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
|
import { Operator } from './constant';
|
||||||
import useGraphStore, { RFState } from './store';
|
import useGraphStore, { RFState } from './store';
|
||||||
import { buildDslComponentsByGraph } from './utils';
|
import { buildDslComponentsByGraph } from './utils';
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ export const useHandleDrop = () => {
|
|||||||
});
|
});
|
||||||
const newNode = {
|
const newNode = {
|
||||||
id: `${type}:${humanId()}`,
|
id: `${type}:${humanId()}`,
|
||||||
type: 'textUpdater',
|
type: 'ragNode',
|
||||||
position: position || {
|
position: position || {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@ -110,7 +111,9 @@ export const useShowDrawer = () => {
|
|||||||
const handleShow = useCallback(
|
const handleShow = useCallback(
|
||||||
(node: Node) => {
|
(node: Node) => {
|
||||||
setClickedNode(node);
|
setClickedNode(node);
|
||||||
showDrawer();
|
if (node.data.label !== Operator.Answer) {
|
||||||
|
showDrawer();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[showDrawer],
|
[showDrawer],
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ export const initialNodes = [
|
|||||||
sourcePosition: Position.Left,
|
sourcePosition: Position.Left,
|
||||||
targetPosition: Position.Right,
|
targetPosition: Position.Right,
|
||||||
id: 'node-1',
|
id: 'node-1',
|
||||||
type: 'textUpdater',
|
type: 'ragNode',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
// position: { x: 400, y: 100 },
|
// position: { x: 400, y: 100 },
|
||||||
data: { label: 123 },
|
data: { label: 123 },
|
||||||
@ -38,7 +38,7 @@ export const dsl = {
|
|||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
id: 'begin',
|
id: 'begin',
|
||||||
type: 'textUpdater',
|
type: 'ragNode',
|
||||||
position: {
|
position: {
|
||||||
x: 50,
|
x: 50,
|
||||||
y: 200,
|
y: 200,
|
||||||
@ -51,7 +51,7 @@ export const dsl = {
|
|||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// id: 'Answer:China',
|
// id: 'Answer:China',
|
||||||
// type: 'textUpdater',
|
// type: 'ragNode',
|
||||||
// position: {
|
// position: {
|
||||||
// x: 150,
|
// x: 150,
|
||||||
// y: 200,
|
// y: 200,
|
||||||
@ -64,7 +64,7 @@ export const dsl = {
|
|||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// id: 'Retrieval:China',
|
// id: 'Retrieval:China',
|
||||||
// type: 'textUpdater',
|
// type: 'ragNode',
|
||||||
// position: {
|
// position: {
|
||||||
// x: 250,
|
// x: 250,
|
||||||
// y: 200,
|
// y: 200,
|
||||||
@ -77,7 +77,7 @@ export const dsl = {
|
|||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// id: 'Generate:China',
|
// id: 'Generate:China',
|
||||||
// type: 'textUpdater',
|
// type: 'ragNode',
|
||||||
// position: {
|
// position: {
|
||||||
// x: 100,
|
// x: 100,
|
||||||
// y: 100,
|
// y: 100,
|
||||||
|
@ -34,75 +34,88 @@ export type RFState = {
|
|||||||
addNode: (nodes: Node) => void;
|
addNode: (nodes: Node) => void;
|
||||||
deleteEdge: () => void;
|
deleteEdge: () => void;
|
||||||
deleteEdgeById: (id: string) => void;
|
deleteEdgeById: (id: string) => void;
|
||||||
|
deleteNodeById: (id: string) => void;
|
||||||
findNodeByName: (operatorName: Operator) => Node | undefined;
|
findNodeByName: (operatorName: Operator) => Node | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
||||||
const useGraphStore = create<RFState>()(
|
const useGraphStore = create<RFState>()(
|
||||||
devtools((set, get) => ({
|
devtools(
|
||||||
nodes: [] as Node[],
|
(set, get) => ({
|
||||||
edges: [] as Edge[],
|
nodes: [] as Node[],
|
||||||
selectedNodeIds: [],
|
edges: [] as Edge[],
|
||||||
selectedEdgeIds: [],
|
selectedNodeIds: [] as string[],
|
||||||
onNodesChange: (changes: NodeChange[]) => {
|
selectedEdgeIds: [] as string[],
|
||||||
set({
|
onNodesChange: (changes: NodeChange[]) => {
|
||||||
nodes: applyNodeChanges(changes, get().nodes),
|
set({
|
||||||
});
|
nodes: applyNodeChanges(changes, get().nodes),
|
||||||
},
|
});
|
||||||
onEdgesChange: (changes: EdgeChange[]) => {
|
},
|
||||||
set({
|
onEdgesChange: (changes: EdgeChange[]) => {
|
||||||
edges: applyEdgeChanges(changes, get().edges),
|
set({
|
||||||
});
|
edges: applyEdgeChanges(changes, get().edges),
|
||||||
},
|
});
|
||||||
onConnect: (connection: Connection) => {
|
},
|
||||||
set({
|
onConnect: (connection: Connection) => {
|
||||||
edges: addEdge(connection, get().edges),
|
set({
|
||||||
});
|
edges: addEdge(connection, get().edges),
|
||||||
},
|
});
|
||||||
onSelectionChange: ({ nodes, edges }: OnSelectionChangeParams) => {
|
},
|
||||||
set({
|
onSelectionChange: ({ nodes, edges }: OnSelectionChangeParams) => {
|
||||||
selectedEdgeIds: edges.map((x) => x.id),
|
set({
|
||||||
selectedNodeIds: nodes.map((x) => x.id),
|
selectedEdgeIds: edges.map((x) => x.id),
|
||||||
});
|
selectedNodeIds: nodes.map((x) => x.id),
|
||||||
},
|
});
|
||||||
setNodes: (nodes: Node[]) => {
|
},
|
||||||
set({ nodes });
|
setNodes: (nodes: Node[]) => {
|
||||||
},
|
set({ nodes });
|
||||||
setEdges: (edges: Edge[]) => {
|
},
|
||||||
set({ edges });
|
setEdges: (edges: Edge[]) => {
|
||||||
},
|
set({ edges });
|
||||||
addNode: (node: Node) => {
|
},
|
||||||
set({ nodes: get().nodes.concat(node) });
|
addNode: (node: Node) => {
|
||||||
},
|
set({ nodes: get().nodes.concat(node) });
|
||||||
deleteEdge: () => {
|
},
|
||||||
const { edges, selectedEdgeIds } = get();
|
deleteEdge: () => {
|
||||||
set({
|
const { edges, selectedEdgeIds } = get();
|
||||||
edges: edges.filter((edge) =>
|
set({
|
||||||
selectedEdgeIds.every((x) => x !== edge.id),
|
edges: edges.filter((edge) =>
|
||||||
),
|
selectedEdgeIds.every((x) => x !== edge.id),
|
||||||
});
|
),
|
||||||
},
|
});
|
||||||
deleteEdgeById: (id: string) => {
|
},
|
||||||
const { edges } = get();
|
deleteEdgeById: (id: string) => {
|
||||||
set({
|
const { edges } = get();
|
||||||
edges: edges.filter((edge) => edge.id !== id),
|
set({
|
||||||
});
|
edges: edges.filter((edge) => edge.id !== id),
|
||||||
},
|
});
|
||||||
findNodeByName: (name: Operator) => {
|
},
|
||||||
return get().nodes.find((x) => x.data.label === name);
|
deleteNodeById: (id: string) => {
|
||||||
},
|
const { nodes, edges } = get();
|
||||||
updateNodeForm: (nodeId: string, values: any) => {
|
set({
|
||||||
set({
|
nodes: nodes.filter((node) => node.id !== id),
|
||||||
nodes: get().nodes.map((node) => {
|
edges: edges
|
||||||
if (node.id === nodeId) {
|
.filter((edge) => edge.source !== id)
|
||||||
node.data = { ...node.data, form: values };
|
.filter((edge) => edge.target !== id),
|
||||||
}
|
});
|
||||||
|
},
|
||||||
|
findNodeByName: (name: Operator) => {
|
||||||
|
return get().nodes.find((x) => x.data.label === name);
|
||||||
|
},
|
||||||
|
updateNodeForm: (nodeId: string, values: any) => {
|
||||||
|
set({
|
||||||
|
nodes: get().nodes.map((node) => {
|
||||||
|
if (node.id === nodeId) {
|
||||||
|
node.data = { ...node.data, form: values };
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
})),
|
}),
|
||||||
|
{ name: 'graph' },
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default useGraphStore;
|
export default useGraphStore;
|
||||||
|
@ -41,7 +41,7 @@ export const buildNodesAndEdgesFromDSLComponents = (data: DSLComponents) => {
|
|||||||
const upstream = [...value.upstream];
|
const upstream = [...value.upstream];
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: key,
|
id: key,
|
||||||
type: 'textUpdater',
|
type: 'ragNode',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
label: value.obj.component_name,
|
label: value.obj.component_name,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user