Files
ragflow/web/src/pages/agent/canvas/index.tsx
Kevin Hu 20b577a72c Fix: Merge main branch (#10377)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: jinhai <haijin.chn@gmail.com>
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
Co-authored-by: Lynn <lynn_inf@hotmail.com>
Co-authored-by: chanx <1243304602@qq.com>
Co-authored-by: balibabu <cike8899@users.noreply.github.com>
Co-authored-by: 纷繁下的无奈 <zhileihuang@126.com>
Co-authored-by: huangzl <huangzl@shinemo.com>
Co-authored-by: writinwaters <93570324+writinwaters@users.noreply.github.com>
Co-authored-by: Wilmer <33392318@qq.com>
Co-authored-by: Adrian Weidig <adrianweidig@gmx.net>
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Yongteng Lei <yongtengrey@outlook.com>
Co-authored-by: Liu An <asiro@qq.com>
Co-authored-by: buua436 <66937541+buua436@users.noreply.github.com>
Co-authored-by: BadwomanCraZY <511528396@qq.com>
Co-authored-by: cucusenok <31804608+cucusenok@users.noreply.github.com>
Co-authored-by: Russell Valentine <russ@coldstonelabs.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Billy Bao <newyorkupperbay@gmail.com>
Co-authored-by: Zhedong Cen <cenzhedong2@126.com>
Co-authored-by: TensorNull <129579691+TensorNull@users.noreply.github.com>
Co-authored-by: TensorNull <tensor.null@gmail.com>
Co-authored-by: Ajay <160579663+aybanda@users.noreply.github.com>
Co-authored-by: AB <aj@Ajays-MacBook-Air.local>
Co-authored-by: 天海蒼灆 <huangaoqin@tecpie.com>
Co-authored-by: He Wang <wanghechn@qq.com>
Co-authored-by: Atsushi Hatakeyama <atu729@icloud.com>
Co-authored-by: Jin Hai <haijin.chn@gmail.com>
Co-authored-by: Mohamed Mathari <155896313+melmathari@users.noreply.github.com>
Co-authored-by: Mohamed Mathari <nocodeventure@Mac-mini-van-Mohamed.fritz.box>
Co-authored-by: Stephen Hu <stephenhu@seismic.com>
Co-authored-by: Shaun Zhang <zhangwfjh@users.noreply.github.com>
Co-authored-by: zhimeng123 <60221886+zhimeng123@users.noreply.github.com>
Co-authored-by: mxc <mxc@example.com>
Co-authored-by: Dominik Novotný <50611433+SgtMarmite@users.noreply.github.com>
Co-authored-by: EVGENY M <168018528+rjohny55@users.noreply.github.com>
Co-authored-by: mcoder6425 <mcoder64@gmail.com>
Co-authored-by: TeslaZY <TeslaZY@outlook.com>
Co-authored-by: lemsn <lemsn@msn.com>
Co-authored-by: lemsn <lemsn@126.com>
Co-authored-by: Adrian Gora <47756404+adagora@users.noreply.github.com>
Co-authored-by: Womsxd <45663319+Womsxd@users.noreply.github.com>
Co-authored-by: FatMii <39074672+FatMii@users.noreply.github.com>
2025-09-30 13:13:15 +08:00

385 lines
11 KiB
TypeScript

import { useIsDarkTheme, useTheme } from '@/components/theme-provider';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { useSetModalState } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils';
import {
ConnectionMode,
ControlButton,
Controls,
NodeTypes,
Position,
ReactFlow,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { NotebookPen } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ChatSheet } from '../chat/chat-sheet';
import { AgentBackground } from '../components/background';
import {
AgentChatContext,
AgentChatLogContext,
AgentInstanceContext,
HandleContext,
} from '../context';
import FormSheet from '../form-sheet/next';
import {
useHandleDrop,
useSelectCanvasData,
useValidateConnection,
} from '../hooks';
import { useAddNode } from '../hooks/use-add-node';
import { useBeforeDelete } from '../hooks/use-before-delete';
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
import { useConnectionDrag } from '../hooks/use-connection-drag';
import { useDropdownPosition } from '../hooks/use-dropdown-position';
import { useMoveNote } from '../hooks/use-move-note';
import { usePlaceholderManager } from '../hooks/use-placeholder-manager';
import { useDropdownManager } from './context';
import Spotlight from '@/components/spotlight';
import {
useHideFormSheetOnNodeDeletion,
useShowDrawer,
useShowLogSheet,
} from '../hooks/use-show-drawer';
import { LogSheet } from '../log-sheet';
import RunSheet from '../run-sheet';
import { ButtonEdge } from './edge';
import styles from './index.less';
import { RagNode } from './node';
import { AgentNode } from './node/agent-node';
import { BeginNode } from './node/begin-node';
import { CategorizeNode } from './node/categorize-node';
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
import { GenerateNode } from './node/generate-node';
import { InvokeNode } from './node/invoke-node';
import { IterationNode, IterationStartNode } from './node/iteration-node';
import { KeywordNode } from './node/keyword-node';
import { LogicNode } from './node/logic-node';
import { MessageNode } from './node/message-node';
import NoteNode from './node/note-node';
import { PlaceholderNode } from './node/placeholder-node';
import { RelevantNode } from './node/relevant-node';
import { RetrievalNode } from './node/retrieval-node';
import { RewriteNode } from './node/rewrite-node';
import { SwitchNode } from './node/switch-node';
import { TemplateNode } from './node/template-node';
import { ToolNode } from './node/tool-node';
export const nodeTypes: NodeTypes = {
ragNode: RagNode,
categorizeNode: CategorizeNode,
beginNode: BeginNode,
placeholderNode: PlaceholderNode,
relevantNode: RelevantNode,
logicNode: LogicNode,
noteNode: NoteNode,
switchNode: SwitchNode,
generateNode: GenerateNode,
retrievalNode: RetrievalNode,
messageNode: MessageNode,
rewriteNode: RewriteNode,
keywordNode: KeywordNode,
invokeNode: InvokeNode,
templateNode: TemplateNode,
// emailNode: EmailNode,
group: IterationNode,
iterationStartNode: IterationStartNode,
agentNode: AgentNode,
toolNode: ToolNode,
};
const edgeTypes = {
buttonEdge: ButtonEdge,
};
interface IProps {
drawerVisible: boolean;
hideDrawer(): void;
}
function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
const { t } = useTranslation();
const {
nodes,
edges,
onConnect: originalOnConnect,
onEdgesChange,
onNodesChange,
onSelectionChange,
onEdgeMouseEnter,
onEdgeMouseLeave,
} = useSelectCanvasData();
const isValidConnection = useValidateConnection();
const { onDrop, onDragOver, setReactFlowInstance, reactFlowInstance } =
useHandleDrop();
const {
onNodeClick,
clickedNode,
formDrawerVisible,
hideFormDrawer,
singleDebugDrawerVisible,
hideSingleDebugDrawer,
showSingleDebugDrawer,
chatVisible,
runVisible,
hideRunOrChatDrawer,
showChatModal,
showFormDrawer,
} = useShowDrawer({
drawerVisible,
hideDrawer,
});
const {
addEventList,
setCurrentMessageId,
currentEventListWithoutMessageById,
clearEventList,
currentMessageId,
} = useCacheChatLog();
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({
setCurrentMessageId,
});
const [lastSendLoading, setLastSendLoading] = useState(false);
const { handleBeforeDelete } = useBeforeDelete();
const { addCanvasNode, addNoteNode } = useAddNode(reactFlowInstance);
const { ref, showImage, hideImage, imgVisible, mouse } = useMoveNote();
const { theme } = useTheme();
useEffect(() => {
if (!chatVisible) {
clearEventList();
}
}, [chatVisible, clearEventList]);
const setLastSendLoadingFunc = (loading: boolean, messageId: string) => {
if (messageId === currentMessageId) {
setLastSendLoading(loading);
} else {
setLastSendLoading(false);
}
};
const isDarkTheme = useIsDarkTheme();
useHideFormSheetOnNodeDeletion({ hideFormDrawer });
const { visible, hideModal, showModal } = useSetModalState();
const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0 });
const { clearActiveDropdown } = useDropdownManager();
const { removePlaceholderNode, onNodeCreated, setCreatedPlaceholderRef } =
usePlaceholderManager(reactFlowInstance);
const { calculateDropdownPosition } = useDropdownPosition(reactFlowInstance);
const {
onConnectStart,
onConnectEnd,
handleConnect,
getConnectionStartContext,
shouldPreventClose,
onMove,
} = useConnectionDrag(
reactFlowInstance,
originalOnConnect,
showModal,
hideModal,
setDropdownPosition,
setCreatedPlaceholderRef,
calculateDropdownPosition,
removePlaceholderNode,
clearActiveDropdown,
);
const onPaneClick = useCallback(() => {
hideFormDrawer();
if (visible && !shouldPreventClose()) {
removePlaceholderNode();
hideModal();
clearActiveDropdown();
}
if (imgVisible) {
addNoteNode(mouse);
hideImage();
}
}, [
hideFormDrawer,
visible,
shouldPreventClose,
hideModal,
imgVisible,
addNoteNode,
mouse,
hideImage,
clearActiveDropdown,
removePlaceholderNode,
]);
return (
<div className={styles.canvasWrapper}>
<svg
xmlns="http://www.w3.org/2000/svg"
style={{ position: 'absolute', top: 10, left: 0 }}
>
<defs>
<marker
fill="rgb(157 149 225)"
id="logo"
viewBox="0 0 40 40"
refX="8"
refY="5"
markerUnits="strokeWidth"
markerWidth="20"
markerHeight="20"
orient="auto-start-reverse"
>
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
</svg>
<AgentInstanceContext.Provider value={{ addCanvasNode, showFormDrawer }}>
<ReactFlow
connectionMode={ConnectionMode.Loose}
nodes={nodes}
onNodesChange={onNodesChange}
edges={edges}
onEdgesChange={onEdgesChange}
fitView
onConnect={handleConnect}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
onDrop={onDrop}
onConnectStart={onConnectStart}
onConnectEnd={onConnectEnd}
onMove={onMove}
onDragOver={onDragOver}
onNodeClick={onNodeClick}
onPaneClick={onPaneClick}
onInit={setReactFlowInstance}
onSelectionChange={onSelectionChange}
nodeOrigin={[0.5, 0]}
isValidConnection={isValidConnection}
onEdgeMouseEnter={onEdgeMouseEnter}
onEdgeMouseLeave={onEdgeMouseLeave}
className="h-full"
colorMode={theme}
defaultEdgeOptions={{
type: 'buttonEdge',
markerEnd: 'logo',
style: {
strokeWidth: 1,
stroke: isDarkTheme
? 'rgba(91, 93, 106, 1)'
: 'rgba(151, 154, 171, 1)',
},
zIndex: 1001, // https://github.com/xyflow/xyflow/discussions/3498
}}
deleteKeyCode={['Delete', 'Backspace']}
onBeforeDelete={handleBeforeDelete}
>
<AgentBackground></AgentBackground>
<Spotlight className="z-0" opcity={0.7} coverage={70} />
<Controls position={'bottom-center'} orientation="horizontal">
<ControlButton>
<Tooltip>
<TooltipTrigger asChild>
<NotebookPen className="!fill-none" onClick={showImage} />
</TooltipTrigger>
<TooltipContent>{t('flow.note')}</TooltipContent>
</Tooltip>
</ControlButton>
</Controls>
</ReactFlow>
{visible && (
<HandleContext.Provider
value={
getConnectionStartContext() || {
nodeId: '',
id: '',
type: 'source',
position: Position.Right,
isFromConnectionDrag: true,
}
}
>
<InnerNextStepDropdown
hideModal={() => {
removePlaceholderNode();
hideModal();
clearActiveDropdown();
}}
position={dropdownPosition}
onNodeCreated={onNodeCreated}
>
<span></span>
</InnerNextStepDropdown>
</HandleContext.Provider>
)}
</AgentInstanceContext.Provider>
<NotebookPen
className={cn('hidden absolute size-6', { block: imgVisible })}
ref={ref}
></NotebookPen>
{formDrawerVisible && (
<AgentInstanceContext.Provider
value={{ addCanvasNode, showFormDrawer }}
>
<FormSheet
node={clickedNode}
visible={formDrawerVisible}
hideModal={hideFormDrawer}
chatVisible={chatVisible}
singleDebugDrawerVisible={singleDebugDrawerVisible}
hideSingleDebugDrawer={hideSingleDebugDrawer}
showSingleDebugDrawer={showSingleDebugDrawer}
></FormSheet>
</AgentInstanceContext.Provider>
)}
{chatVisible && (
<AgentChatContext.Provider
value={{ showLogSheet, setLastSendLoadingFunc }}
>
<AgentChatLogContext.Provider
value={{ addEventList, setCurrentMessageId }}
>
<ChatSheet hideModal={hideRunOrChatDrawer}></ChatSheet>
</AgentChatLogContext.Provider>
</AgentChatContext.Provider>
)}
{runVisible && (
<RunSheet
hideModal={hideRunOrChatDrawer}
showModal={showChatModal}
></RunSheet>
)}
{logSheetVisible && (
<LogSheet
hideModal={hideLogSheet}
currentEventListWithoutMessageById={
currentEventListWithoutMessageById
}
currentMessageId={currentMessageId}
sendLoading={lastSendLoading}
></LogSheet>
)}
</div>
);
}
export default AgentCanvas;