mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Display agent history versions #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -33,6 +33,8 @@ export const enum AgentApiAction {
|
|||||||
TestDbConnect = 'testDbConnect',
|
TestDbConnect = 'testDbConnect',
|
||||||
DebugSingle = 'debugSingle',
|
DebugSingle = 'debugSingle',
|
||||||
FetchInputForm = 'fetchInputForm',
|
FetchInputForm = 'fetchInputForm',
|
||||||
|
FetchVersionList = 'fetchVersionList',
|
||||||
|
FetchVersion = 'fetchVersion',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyDsl = {
|
export const EmptyDsl = {
|
||||||
@ -396,3 +398,44 @@ export const useFetchInputForm = (componentId?: string) => {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useFetchVersionList = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const { data, isFetching: loading } = useQuery<
|
||||||
|
Array<{ created_at: string; title: string; id: string }>
|
||||||
|
>({
|
||||||
|
queryKey: [AgentApiAction.FetchVersionList],
|
||||||
|
initialData: [],
|
||||||
|
gcTime: 0,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await flowService.getListVersion({}, id);
|
||||||
|
|
||||||
|
return data?.data ?? [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, loading };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchVersion = (
|
||||||
|
version_id?: string,
|
||||||
|
): {
|
||||||
|
data?: IFlow;
|
||||||
|
loading: boolean;
|
||||||
|
} => {
|
||||||
|
const { data, isFetching: loading } = useQuery({
|
||||||
|
queryKey: [AgentApiAction.FetchVersion, version_id],
|
||||||
|
initialData: undefined,
|
||||||
|
gcTime: 0,
|
||||||
|
enabled: !!version_id, // Only call API when both values are provided
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!version_id) return undefined;
|
||||||
|
|
||||||
|
const { data } = await flowService.getVersion({}, version_id);
|
||||||
|
|
||||||
|
return data?.data ?? undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, loading };
|
||||||
|
};
|
||||||
|
|||||||
@ -56,7 +56,7 @@ import { SwitchNode } from './node/switch-node';
|
|||||||
import { TemplateNode } from './node/template-node';
|
import { TemplateNode } from './node/template-node';
|
||||||
import { ToolNode } from './node/tool-node';
|
import { ToolNode } from './node/tool-node';
|
||||||
|
|
||||||
const nodeTypes: NodeTypes = {
|
export const nodeTypes: NodeTypes = {
|
||||||
ragNode: RagNode,
|
ragNode: RagNode,
|
||||||
categorizeNode: CategorizeNode,
|
categorizeNode: CategorizeNode,
|
||||||
beginNode: BeginNode,
|
beginNode: BeginNode,
|
||||||
|
|||||||
@ -64,7 +64,7 @@ const ConditionBlock = ({
|
|||||||
function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
||||||
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label} showRun={false}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="target"
|
type="target"
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import {
|
|||||||
import { useShowEmbedModal } from './hooks/use-show-dialog';
|
import { useShowEmbedModal } from './hooks/use-show-dialog';
|
||||||
import { BeginQuery } from './interface';
|
import { BeginQuery } from './interface';
|
||||||
import { UploadAgentDialog } from './upload-agent-dialog';
|
import { UploadAgentDialog } from './upload-agent-dialog';
|
||||||
|
import { VersionDialog } from './version-dialog';
|
||||||
|
|
||||||
function AgentDropdownMenuItem({
|
function AgentDropdownMenuItem({
|
||||||
children,
|
children,
|
||||||
@ -79,6 +80,11 @@ export default function Agent() {
|
|||||||
handleRun();
|
handleRun();
|
||||||
}
|
}
|
||||||
}, [getBeginNodeDataQuery, handleRun, showChatDrawer]);
|
}, [getBeginNodeDataQuery, handleRun, showChatDrawer]);
|
||||||
|
const {
|
||||||
|
visible: versionDialogVisible,
|
||||||
|
hideModal: hideVersionDialog,
|
||||||
|
showModal: showVersionDialog,
|
||||||
|
} = useSetModalState();
|
||||||
|
|
||||||
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
|
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
|
||||||
useShowEmbedModal();
|
useShowEmbedModal();
|
||||||
@ -98,7 +104,7 @@ export default function Agent() {
|
|||||||
<CirclePlay />
|
<CirclePlay />
|
||||||
Run app
|
Run app
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={'secondary'}>
|
<Button variant={'secondary'} onClick={showVersionDialog}>
|
||||||
<History />
|
<History />
|
||||||
History version
|
History version
|
||||||
</Button>
|
</Button>
|
||||||
@ -159,6 +165,9 @@ export default function Agent() {
|
|||||||
isAgent
|
isAgent
|
||||||
></EmbedDialog>
|
></EmbedDialog>
|
||||||
)}
|
)}
|
||||||
|
{versionDialogVisible && (
|
||||||
|
<VersionDialog hideModal={hideVersionDialog}></VersionDialog>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
131
web/src/pages/agent/version-dialog/index.tsx
Normal file
131
web/src/pages/agent/version-dialog/index.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Spin } from '@/components/ui/spin';
|
||||||
|
import {
|
||||||
|
useFetchVersion,
|
||||||
|
useFetchVersionList,
|
||||||
|
} from '@/hooks/use-agent-request';
|
||||||
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { formatDate } from '@/utils/date';
|
||||||
|
import { downloadJsonFile } from '@/utils/file-util';
|
||||||
|
import {
|
||||||
|
Background,
|
||||||
|
ConnectionMode,
|
||||||
|
ReactFlow,
|
||||||
|
ReactFlowProvider,
|
||||||
|
} from '@xyflow/react';
|
||||||
|
import { ArrowDownToLine } from 'lucide-react';
|
||||||
|
import { ReactNode, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { nodeTypes } from '../canvas';
|
||||||
|
|
||||||
|
export function VersionDialog({
|
||||||
|
hideModal,
|
||||||
|
}: IModalProps<any> & { initialName?: string; title?: ReactNode }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data, loading } = useFetchVersionList();
|
||||||
|
const [selectedId, setSelectedId] = useState<string>('');
|
||||||
|
const { data: agent, loading: versionLoading } = useFetchVersion(selectedId);
|
||||||
|
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(id: string) => () => {
|
||||||
|
setSelectedId(id);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const downloadFile = useCallback(() => {
|
||||||
|
const graph = agent?.dsl.graph;
|
||||||
|
if (graph) {
|
||||||
|
downloadJsonFile(graph, agent?.title);
|
||||||
|
}
|
||||||
|
}, [agent?.dsl.graph, agent?.title]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data.length > 0) {
|
||||||
|
setSelectedId(data[0].id);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open onOpenChange={hideModal}>
|
||||||
|
<DialogContent className="max-w-[60vw]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t('flow.historyversion')}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<section className="flex gap-2 relative">
|
||||||
|
<div className="w-1/3 max-h-[60vh] overflow-auto min-h-[40vh]">
|
||||||
|
{loading ? (
|
||||||
|
<Spin className="top-1/2"></Spin>
|
||||||
|
) : (
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{data.map((x) => (
|
||||||
|
<li
|
||||||
|
key={x.id}
|
||||||
|
className={cn('cursor-pointer', {
|
||||||
|
'bg-card rounded p-1': x.id === selectedId,
|
||||||
|
})}
|
||||||
|
onClick={handleClick(x.id)}
|
||||||
|
>
|
||||||
|
{x.title}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative flex-1 ">
|
||||||
|
{versionLoading ? (
|
||||||
|
<Spin className="top-1/2" />
|
||||||
|
) : (
|
||||||
|
<Card className="h-full">
|
||||||
|
<CardContent className="h-full p-5">
|
||||||
|
<section className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="pb-1">{agent?.title}</div>
|
||||||
|
<p className="text-text-sub-title text-xs">
|
||||||
|
{formatDate(agent?.create_date)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button variant={'ghost'} onClick={downloadFile}>
|
||||||
|
<ArrowDownToLine />
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<ReactFlowProvider key={`flow-${selectedId}`}>
|
||||||
|
<ReactFlow
|
||||||
|
connectionMode={ConnectionMode.Loose}
|
||||||
|
nodes={agent?.dsl.graph?.nodes || []}
|
||||||
|
edges={
|
||||||
|
agent?.dsl.graph?.edges.flatMap((x) => ({
|
||||||
|
...x,
|
||||||
|
type: 'default',
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
fitView
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
edgeTypes={{}}
|
||||||
|
zoomOnScroll={true}
|
||||||
|
panOnDrag={true}
|
||||||
|
zoomOnDoubleClick={false}
|
||||||
|
preventScrolling={true}
|
||||||
|
minZoom={0.1}
|
||||||
|
>
|
||||||
|
<Background />
|
||||||
|
</ReactFlow>
|
||||||
|
</ReactFlowProvider>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -199,6 +199,7 @@
|
|||||||
--background-checked: rgba(76, 164, 231, 1);
|
--background-checked: rgba(76, 164, 231, 1);
|
||||||
|
|
||||||
--background-highlight: rgba(76, 164, 231, 0.1);
|
--background-highlight: rgba(76, 164, 231, 0.1);
|
||||||
|
--background-agent: rgba(22, 22, 24, 1);
|
||||||
|
|
||||||
--input-border: rgba(255, 255, 255, 0.2);
|
--input-border: rgba(255, 255, 255, 0.2);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user