mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-23 23:16:58 +08:00
### What problem does this PR solve? Feat: Render agent details #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
57
web/src/pages/agent/hooks/use-before-delete.tsx
Normal file
57
web/src/pages/agent/hooks/use-before-delete.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { OnBeforeDelete } from '@xyflow/react';
|
||||
import { Operator } from '../constant';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
const UndeletableNodes = [Operator.Begin, Operator.IterationStart];
|
||||
|
||||
export function useBeforeDelete() {
|
||||
const getOperatorTypeFromId = useGraphStore(
|
||||
(state) => state.getOperatorTypeFromId,
|
||||
);
|
||||
const handleBeforeDelete: OnBeforeDelete<RAGFlowNodeType> = async ({
|
||||
nodes, // Nodes to be deleted
|
||||
edges, // Edges to be deleted
|
||||
}) => {
|
||||
const toBeDeletedNodes = nodes.filter((node) => {
|
||||
const operatorType = node.data?.label as Operator;
|
||||
if (operatorType === Operator.Begin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
operatorType === Operator.IterationStart &&
|
||||
!nodes.some((x) => x.id === node.parentId)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const toBeDeletedEdges = edges.filter((edge) => {
|
||||
const sourceType = getOperatorTypeFromId(edge.source) as Operator;
|
||||
const downStreamNodes = nodes.filter((x) => x.id === edge.target);
|
||||
|
||||
// This edge does not need to be deleted, the range of edges that do not need to be deleted is smaller, so consider the case where it does not need to be deleted
|
||||
if (
|
||||
UndeletableNodes.includes(sourceType) && // Upstream node is Begin or IterationStart
|
||||
downStreamNodes.length === 0 // Downstream node does not exist in the nodes to be deleted
|
||||
) {
|
||||
if (!nodes.some((x) => x.id === edge.source)) {
|
||||
return true; // Can be deleted
|
||||
}
|
||||
return false; // Cannot be deleted
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
nodes: toBeDeletedNodes,
|
||||
edges: toBeDeletedEdges,
|
||||
};
|
||||
};
|
||||
|
||||
return { handleBeforeDelete };
|
||||
}
|
||||
29
web/src/pages/agent/hooks/use-build-dsl.ts
Normal file
29
web/src/pages/agent/hooks/use-build-dsl.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { useCallback } from 'react';
|
||||
import useGraphStore from '../store';
|
||||
import { buildDslComponentsByGraph } from '../utils';
|
||||
|
||||
export const useBuildDslData = () => {
|
||||
const { data } = useFetchFlow();
|
||||
const { nodes, edges } = useGraphStore((state) => state);
|
||||
|
||||
const buildDslData = useCallback(
|
||||
(currentNodes?: RAGFlowNodeType[]) => {
|
||||
const dslComponents = buildDslComponentsByGraph(
|
||||
currentNodes ?? nodes,
|
||||
edges,
|
||||
data.dsl.components,
|
||||
);
|
||||
|
||||
return {
|
||||
...data.dsl,
|
||||
graph: { nodes: currentNodes ?? nodes, edges },
|
||||
components: dslComponents,
|
||||
};
|
||||
},
|
||||
[data.dsl, edges, nodes],
|
||||
);
|
||||
|
||||
return { buildDslData };
|
||||
};
|
||||
62
web/src/pages/agent/hooks/use-export-json.ts
Normal file
62
web/src/pages/agent/hooks/use-export-json.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { FileMimeType } from '@/constants/common';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
||||
import { IGraph } from '@/interfaces/database/flow';
|
||||
import { downloadJsonFile } from '@/utils/file-util';
|
||||
import { message, UploadFile } from 'antd';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBuildDslData } from './use-build-dsl';
|
||||
import { useSetGraphInfo } from './use-set-graph';
|
||||
|
||||
export const useHandleExportOrImportJsonFile = () => {
|
||||
const { buildDslData } = useBuildDslData();
|
||||
const {
|
||||
visible: fileUploadVisible,
|
||||
hideModal: hideFileUploadModal,
|
||||
showModal: showFileUploadModal,
|
||||
} = useSetModalState();
|
||||
const setGraphInfo = useSetGraphInfo();
|
||||
const { data } = useFetchFlow();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onFileUploadOk = useCallback(
|
||||
async (fileList: UploadFile[]) => {
|
||||
if (fileList.length > 0) {
|
||||
const file: File = fileList[0] as unknown as File;
|
||||
if (file.type !== FileMimeType.Json) {
|
||||
message.error(t('flow.jsonUploadTypeErrorMessage'));
|
||||
return;
|
||||
}
|
||||
|
||||
const graphStr = await file.text();
|
||||
const errorMessage = t('flow.jsonUploadContentErrorMessage');
|
||||
try {
|
||||
const graph = JSON.parse(graphStr);
|
||||
if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) {
|
||||
setGraphInfo(graph ?? ({} as IGraph));
|
||||
hideFileUploadModal();
|
||||
} else {
|
||||
message.error(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(errorMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
[hideFileUploadModal, setGraphInfo, t],
|
||||
);
|
||||
|
||||
const handleExportJson = useCallback(() => {
|
||||
downloadJsonFile(buildDslData().graph, `${data.title}.json`);
|
||||
}, [buildDslData, data.title]);
|
||||
|
||||
return {
|
||||
fileUploadVisible,
|
||||
handleExportJson,
|
||||
handleImportJson: showFileUploadModal,
|
||||
hideFileUploadModal,
|
||||
onFileUploadOk,
|
||||
};
|
||||
};
|
||||
19
web/src/pages/agent/hooks/use-fetch-data.ts
Normal file
19
web/src/pages/agent/hooks/use-fetch-data.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
||||
import { IGraph } from '@/interfaces/database/flow';
|
||||
import { useEffect } from 'react';
|
||||
import { useSetGraphInfo } from './use-set-graph';
|
||||
|
||||
export const useFetchDataOnMount = () => {
|
||||
const { loading, data, refetch } = useFetchFlow();
|
||||
const setGraphInfo = useSetGraphInfo();
|
||||
|
||||
useEffect(() => {
|
||||
setGraphInfo(data?.dsl?.graph ?? ({} as IGraph));
|
||||
}, [setGraphInfo, data]);
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
}, [refetch]);
|
||||
|
||||
return { loading, flowDetail: data };
|
||||
};
|
||||
113
web/src/pages/agent/hooks/use-get-begin-query.tsx
Normal file
113
web/src/pages/agent/hooks/use-get-begin-query.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import get from 'lodash/get';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { BeginId, Operator } from '../constant';
|
||||
import { BeginQuery } from '../interface';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
export const useGetBeginNodeDataQuery = () => {
|
||||
const getNode = useGraphStore((state) => state.getNode);
|
||||
|
||||
const getBeginNodeDataQuery = useCallback(() => {
|
||||
return get(getNode(BeginId), 'data.form.query', []);
|
||||
}, [getNode]);
|
||||
|
||||
return getBeginNodeDataQuery;
|
||||
};
|
||||
|
||||
export const useGetBeginNodeDataQueryIsSafe = () => {
|
||||
const [isBeginNodeDataQuerySafe, setIsBeginNodeDataQuerySafe] =
|
||||
useState(false);
|
||||
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
|
||||
useEffect(() => {
|
||||
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||
const isSafe = !query.some((q) => !q.optional && q.type === 'file');
|
||||
setIsBeginNodeDataQuerySafe(isSafe);
|
||||
}, [getBeginNodeDataQuery, nodes]);
|
||||
|
||||
return isBeginNodeDataQuerySafe;
|
||||
};
|
||||
|
||||
// exclude nodes with branches
|
||||
const ExcludedNodes = [
|
||||
Operator.Categorize,
|
||||
Operator.Relevant,
|
||||
Operator.Begin,
|
||||
Operator.Note,
|
||||
];
|
||||
|
||||
export const useBuildComponentIdSelectOptions = (
|
||||
nodeId?: string,
|
||||
parentId?: string,
|
||||
) => {
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||
|
||||
// Limit the nodes inside iteration to only reference peer nodes with the same parentId and other external nodes other than their parent nodes
|
||||
const filterChildNodesToSameParentOrExternal = useCallback(
|
||||
(node: RAGFlowNodeType) => {
|
||||
// Node inside iteration
|
||||
if (parentId) {
|
||||
return (
|
||||
(node.parentId === parentId || node.parentId === undefined) &&
|
||||
node.id !== parentId
|
||||
);
|
||||
}
|
||||
|
||||
return node.parentId === undefined; // The outermost node
|
||||
},
|
||||
[parentId],
|
||||
);
|
||||
|
||||
const componentIdOptions = useMemo(() => {
|
||||
return nodes
|
||||
.filter(
|
||||
(x) =>
|
||||
x.id !== nodeId &&
|
||||
!ExcludedNodes.some((y) => y === x.data.label) &&
|
||||
filterChildNodesToSameParentOrExternal(x),
|
||||
)
|
||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
||||
}, [nodes, nodeId, filterChildNodesToSameParentOrExternal]);
|
||||
|
||||
const groupedOptions = [
|
||||
{
|
||||
label: <span>Component Output</span>,
|
||||
title: 'Component Output',
|
||||
options: componentIdOptions,
|
||||
},
|
||||
{
|
||||
label: <span>Begin Input</span>,
|
||||
title: 'Begin Input',
|
||||
options: query.map((x) => ({
|
||||
label: x.name,
|
||||
value: `begin@${x.key}`,
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
return groupedOptions;
|
||||
};
|
||||
|
||||
export const useGetComponentLabelByValue = (nodeId: string) => {
|
||||
const options = useBuildComponentIdSelectOptions(nodeId);
|
||||
const flattenOptions = useMemo(
|
||||
() =>
|
||||
options.reduce<DefaultOptionType[]>((pre, cur) => {
|
||||
return [...pre, ...cur.options];
|
||||
}, []),
|
||||
[options],
|
||||
);
|
||||
|
||||
const getLabel = useCallback(
|
||||
(val?: string) => {
|
||||
return flattenOptions.find((x) => x.value === val)?.label;
|
||||
},
|
||||
[flattenOptions],
|
||||
);
|
||||
return getLabel;
|
||||
};
|
||||
0
web/src/pages/agent/hooks/use-iteration.ts
Normal file
0
web/src/pages/agent/hooks/use-iteration.ts
Normal file
12
web/src/pages/agent/hooks/use-open-document.ts
Normal file
12
web/src/pages/agent/hooks/use-open-document.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useOpenDocument() {
|
||||
const openDocument = useCallback(() => {
|
||||
window.open(
|
||||
'https://ragflow.io/docs/dev/category/agent-components',
|
||||
'_blank',
|
||||
);
|
||||
}, []);
|
||||
|
||||
return openDocument;
|
||||
}
|
||||
85
web/src/pages/agent/hooks/use-save-graph.ts
Normal file
85
web/src/pages/agent/hooks/use-save-graph.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { useFetchFlow, useResetFlow, useSetFlow } from '@/hooks/flow-hooks';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { useDebounceEffect } from 'ahooks';
|
||||
import dayjs from 'dayjs';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import useGraphStore from '../store';
|
||||
import { useBuildDslData } from './use-build-dsl';
|
||||
|
||||
export const useSaveGraph = () => {
|
||||
const { data } = useFetchFlow();
|
||||
const { setFlow, loading } = useSetFlow();
|
||||
const { id } = useParams();
|
||||
const { buildDslData } = useBuildDslData();
|
||||
|
||||
const saveGraph = useCallback(
|
||||
async (currentNodes?: RAGFlowNodeType[]) => {
|
||||
return setFlow({
|
||||
id,
|
||||
title: data.title,
|
||||
dsl: buildDslData(currentNodes),
|
||||
});
|
||||
},
|
||||
[setFlow, id, data.title, buildDslData],
|
||||
);
|
||||
|
||||
return { saveGraph, loading };
|
||||
};
|
||||
|
||||
export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
|
||||
const { saveGraph, loading } = useSaveGraph();
|
||||
const { resetFlow } = useResetFlow();
|
||||
|
||||
const handleRun = useCallback(
|
||||
async (nextNodes?: RAGFlowNodeType[]) => {
|
||||
const saveRet = await saveGraph(nextNodes);
|
||||
if (saveRet?.code === 0) {
|
||||
// Call the reset api before opening the run drawer each time
|
||||
const resetRet = await resetFlow();
|
||||
// After resetting, all previous messages will be cleared.
|
||||
if (resetRet?.code === 0) {
|
||||
show();
|
||||
}
|
||||
}
|
||||
},
|
||||
[saveGraph, resetFlow, show],
|
||||
);
|
||||
|
||||
return { handleRun, loading };
|
||||
};
|
||||
|
||||
export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
||||
const [time, setTime] = useState<string>();
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
const { saveGraph } = useSaveGraph();
|
||||
const { data: flowDetail } = useFetchFlow();
|
||||
|
||||
const setSaveTime = useCallback((updateTime: number) => {
|
||||
setTime(dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss'));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSaveTime(flowDetail?.update_time);
|
||||
}, [flowDetail, setSaveTime]);
|
||||
|
||||
const saveAgent = useCallback(async () => {
|
||||
if (!chatDrawerVisible) {
|
||||
const ret = await saveGraph();
|
||||
setSaveTime(ret.data.update_time);
|
||||
}
|
||||
}, [chatDrawerVisible, saveGraph, setSaveTime]);
|
||||
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
// saveAgent();
|
||||
},
|
||||
[nodes, edges],
|
||||
{
|
||||
wait: 1000 * 20,
|
||||
},
|
||||
);
|
||||
|
||||
return time;
|
||||
};
|
||||
17
web/src/pages/agent/hooks/use-set-graph.ts
Normal file
17
web/src/pages/agent/hooks/use-set-graph.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { IGraph } from '@/interfaces/database/flow';
|
||||
import { useCallback } from 'react';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
export const useSetGraphInfo = () => {
|
||||
const { setEdges, setNodes } = useGraphStore((state) => state);
|
||||
const setGraphInfo = useCallback(
|
||||
({ nodes = [], edges = [] }: IGraph) => {
|
||||
if (nodes.length || edges.length) {
|
||||
setNodes(nodes);
|
||||
setEdges(edges);
|
||||
}
|
||||
},
|
||||
[setEdges, setNodes],
|
||||
);
|
||||
return setGraphInfo;
|
||||
};
|
||||
153
web/src/pages/agent/hooks/use-show-drawer.tsx
Normal file
153
web/src/pages/agent/hooks/use-show-drawer.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { Node, NodeMouseHandler } from '@xyflow/react';
|
||||
import get from 'lodash/get';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { Operator } from '../constant';
|
||||
import { BeginQuery } from '../interface';
|
||||
import useGraphStore from '../store';
|
||||
import { useGetBeginNodeDataQuery } from './use-get-begin-query';
|
||||
import { useSaveGraph } from './use-save-graph';
|
||||
|
||||
export const useShowFormDrawer = () => {
|
||||
const {
|
||||
clickedNodeId: clickNodeId,
|
||||
setClickedNodeId,
|
||||
getNode,
|
||||
} = useGraphStore((state) => state);
|
||||
const {
|
||||
visible: formDrawerVisible,
|
||||
hideModal: hideFormDrawer,
|
||||
showModal: showFormDrawer,
|
||||
} = useSetModalState();
|
||||
|
||||
const handleShow = useCallback(
|
||||
(node: Node) => {
|
||||
setClickedNodeId(node.id);
|
||||
showFormDrawer();
|
||||
},
|
||||
[showFormDrawer, setClickedNodeId],
|
||||
);
|
||||
|
||||
return {
|
||||
formDrawerVisible,
|
||||
hideFormDrawer,
|
||||
showFormDrawer: handleShow,
|
||||
clickedNode: getNode(clickNodeId),
|
||||
};
|
||||
};
|
||||
|
||||
export const useShowSingleDebugDrawer = () => {
|
||||
const { visible, showModal, hideModal } = useSetModalState();
|
||||
const { saveGraph } = useSaveGraph();
|
||||
|
||||
const showSingleDebugDrawer = useCallback(async () => {
|
||||
const saveRet = await saveGraph();
|
||||
if (saveRet?.code === 0) {
|
||||
showModal();
|
||||
}
|
||||
}, [saveGraph, showModal]);
|
||||
|
||||
return {
|
||||
singleDebugDrawerVisible: visible,
|
||||
hideSingleDebugDrawer: hideModal,
|
||||
showSingleDebugDrawer,
|
||||
};
|
||||
};
|
||||
|
||||
const ExcludedNodes = [Operator.IterationStart, Operator.Note];
|
||||
|
||||
export function useShowDrawer({
|
||||
drawerVisible,
|
||||
hideDrawer,
|
||||
}: {
|
||||
drawerVisible: boolean;
|
||||
hideDrawer(): void;
|
||||
}) {
|
||||
const {
|
||||
visible: runVisible,
|
||||
showModal: showRunModal,
|
||||
hideModal: hideRunModal,
|
||||
} = useSetModalState();
|
||||
const {
|
||||
visible: chatVisible,
|
||||
showModal: showChatModal,
|
||||
hideModal: hideChatModal,
|
||||
} = useSetModalState();
|
||||
const {
|
||||
singleDebugDrawerVisible,
|
||||
showSingleDebugDrawer,
|
||||
hideSingleDebugDrawer,
|
||||
} = useShowSingleDebugDrawer();
|
||||
const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
|
||||
useShowFormDrawer();
|
||||
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (drawerVisible) {
|
||||
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||
if (query.length > 0) {
|
||||
showRunModal();
|
||||
hideChatModal();
|
||||
} else {
|
||||
showChatModal();
|
||||
hideRunModal();
|
||||
}
|
||||
}
|
||||
}, [
|
||||
hideChatModal,
|
||||
hideRunModal,
|
||||
showChatModal,
|
||||
showRunModal,
|
||||
drawerVisible,
|
||||
getBeginNodeDataQuery,
|
||||
]);
|
||||
|
||||
const hideRunOrChatDrawer = useCallback(() => {
|
||||
hideChatModal();
|
||||
hideRunModal();
|
||||
hideDrawer();
|
||||
}, [hideChatModal, hideDrawer, hideRunModal]);
|
||||
|
||||
const onPaneClick = useCallback(() => {
|
||||
hideFormDrawer();
|
||||
}, [hideFormDrawer]);
|
||||
|
||||
const onNodeClick: NodeMouseHandler = useCallback(
|
||||
(e, node) => {
|
||||
if (!ExcludedNodes.some((x) => x === node.data.label)) {
|
||||
hideSingleDebugDrawer();
|
||||
hideRunOrChatDrawer();
|
||||
showFormDrawer(node);
|
||||
}
|
||||
// handle single debug icon click
|
||||
if (
|
||||
get(e.target, 'dataset.play') === 'true' ||
|
||||
get(e.target, 'parentNode.dataset.play') === 'true'
|
||||
) {
|
||||
showSingleDebugDrawer();
|
||||
}
|
||||
},
|
||||
[
|
||||
hideRunOrChatDrawer,
|
||||
hideSingleDebugDrawer,
|
||||
showFormDrawer,
|
||||
showSingleDebugDrawer,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
chatVisible,
|
||||
runVisible,
|
||||
onPaneClick,
|
||||
singleDebugDrawerVisible,
|
||||
showSingleDebugDrawer,
|
||||
hideSingleDebugDrawer,
|
||||
formDrawerVisible,
|
||||
showFormDrawer,
|
||||
clickedNode,
|
||||
onNodeClick,
|
||||
hideFormDrawer,
|
||||
hideRunOrChatDrawer,
|
||||
showChatModal,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user