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