mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Add a child operator node by clicking the operator node anchor point #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -149,38 +149,40 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<ReactFlow
|
<AgentInstanceContext.Provider value={{ addCanvasNode }}>
|
||||||
connectionMode={ConnectionMode.Loose}
|
<ReactFlow
|
||||||
nodes={nodes}
|
connectionMode={ConnectionMode.Loose}
|
||||||
onNodesChange={onNodesChange}
|
nodes={nodes}
|
||||||
edges={edges}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChange}
|
edges={edges}
|
||||||
fitView
|
onEdgesChange={onEdgesChange}
|
||||||
onConnect={onConnect}
|
fitView
|
||||||
nodeTypes={nodeTypes}
|
onConnect={onConnect}
|
||||||
edgeTypes={edgeTypes}
|
nodeTypes={nodeTypes}
|
||||||
onDrop={onDrop}
|
edgeTypes={edgeTypes}
|
||||||
onDragOver={onDragOver}
|
onDrop={onDrop}
|
||||||
onNodeClick={onNodeClick}
|
onDragOver={onDragOver}
|
||||||
onPaneClick={onPaneClick}
|
onNodeClick={onNodeClick}
|
||||||
onInit={setReactFlowInstance}
|
onPaneClick={onPaneClick}
|
||||||
onSelectionChange={onSelectionChange}
|
onInit={setReactFlowInstance}
|
||||||
nodeOrigin={[0.5, 0]}
|
onSelectionChange={onSelectionChange}
|
||||||
isValidConnection={isValidConnection}
|
nodeOrigin={[0.5, 0]}
|
||||||
defaultEdgeOptions={{
|
isValidConnection={isValidConnection}
|
||||||
type: 'buttonEdge',
|
defaultEdgeOptions={{
|
||||||
markerEnd: 'logo',
|
type: 'buttonEdge',
|
||||||
style: {
|
markerEnd: 'logo',
|
||||||
strokeWidth: 2,
|
style: {
|
||||||
stroke: 'rgb(202 197 245)',
|
strokeWidth: 2,
|
||||||
},
|
stroke: 'rgb(202 197 245)',
|
||||||
zIndex: 1001, // https://github.com/xyflow/xyflow/discussions/3498
|
},
|
||||||
}}
|
zIndex: 1001, // https://github.com/xyflow/xyflow/discussions/3498
|
||||||
deleteKeyCode={['Delete', 'Backspace']}
|
}}
|
||||||
onBeforeDelete={handleBeforeDelete}
|
deleteKeyCode={['Delete', 'Backspace']}
|
||||||
>
|
onBeforeDelete={handleBeforeDelete}
|
||||||
<Background />
|
>
|
||||||
</ReactFlow>
|
<Background />
|
||||||
|
</ReactFlow>
|
||||||
|
</AgentInstanceContext.Provider>
|
||||||
{formDrawerVisible && (
|
{formDrawerVisible && (
|
||||||
<AgentInstanceContext.Provider value={{ addCanvasNode }}>
|
<AgentInstanceContext.Provider value={{ addCanvasNode }}>
|
||||||
<FormSheet
|
<FormSheet
|
||||||
|
|||||||
@ -36,6 +36,7 @@ function InnerAgentNode({
|
|||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
style={LeftHandleStyle}
|
style={LeftHandleStyle}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
@ -44,6 +45,7 @@ function InnerAgentNode({
|
|||||||
className={styles.handle}
|
className={styles.handle}
|
||||||
id="b"
|
id="b"
|
||||||
style={RightHandleStyle}
|
style={RightHandleStyle}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import styles from './index.less';
|
|||||||
import { NodeWrapper } from './node-wrapper';
|
import { NodeWrapper } from './node-wrapper';
|
||||||
|
|
||||||
// TODO: do not allow other nodes to connect to this node
|
// TODO: do not allow other nodes to connect to this node
|
||||||
function InnerBeginNode({ data }: NodeProps<IBeginNode>) {
|
function InnerBeginNode({ data, id }: NodeProps<IBeginNode>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const query: BeginQuery[] = get(data, 'form.query', []);
|
const query: BeginQuery[] = get(data, 'form.query', []);
|
||||||
|
|
||||||
@ -29,14 +29,15 @@ function InnerBeginNode({ data }: NodeProps<IBeginNode>) {
|
|||||||
isConnectable
|
isConnectable
|
||||||
className={styles.handle}
|
className={styles.handle}
|
||||||
style={RightHandleStyle}
|
style={RightHandleStyle}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
|
|
||||||
<Flex align="center" justify={'center'} gap={10}>
|
<section className="flex items-center justify-center gap-2">
|
||||||
<OperatorIcon name={data.label as Operator}></OperatorIcon>
|
<OperatorIcon name={data.label as Operator}></OperatorIcon>
|
||||||
<div className="truncate text-center font-semibold text-sm">
|
<div className="truncate text-center font-semibold text-sm">
|
||||||
{t(`flow.begin`)}
|
{t(`flow.begin`)}
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</section>
|
||||||
<Flex gap={8} vertical className={styles.generateParameters}>
|
<Flex gap={8} vertical className={styles.generateParameters}>
|
||||||
{query.map((x, idx) => {
|
{query.map((x, idx) => {
|
||||||
const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType];
|
const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType];
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export function InnerCategorizeNode({
|
|||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
isConnectable
|
isConnectable
|
||||||
id={'a'}
|
id={'a'}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
|
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
@ -45,6 +46,7 @@ export function InnerCategorizeNode({
|
|||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
isConnectable
|
isConnectable
|
||||||
style={{ ...RightHandleStyle, top: position.top }}
|
style={{ ...RightHandleStyle, top: position.top }}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
111
web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
Normal file
111
web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from '@/components/ui/accordion';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { Operator } from '@/pages/agent/constant';
|
||||||
|
import { AgentInstanceContext, HandleContext } from '@/pages/agent/context';
|
||||||
|
import OperatorIcon from '@/pages/agent/operator-icon';
|
||||||
|
import { PropsWithChildren, useContext } from 'react';
|
||||||
|
|
||||||
|
type OperatorItemProps = { operators: Operator[] };
|
||||||
|
|
||||||
|
function OperatorItemList({ operators }: OperatorItemProps) {
|
||||||
|
const { addCanvasNode } = useContext(AgentInstanceContext);
|
||||||
|
const { nodeId, id, type, position } = useContext(HandleContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{operators.map((x) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={x}
|
||||||
|
className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"
|
||||||
|
onClick={addCanvasNode(x, {
|
||||||
|
id: nodeId,
|
||||||
|
sourceHandle: id,
|
||||||
|
position,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<OperatorIcon name={x}></OperatorIcon>
|
||||||
|
{x}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccordionOperators() {
|
||||||
|
return (
|
||||||
|
<Accordion
|
||||||
|
type="multiple"
|
||||||
|
className="px-2 text-text-title"
|
||||||
|
defaultValue={['item-1', 'item-2', 'item-3', 'item-4', 'item-5']}
|
||||||
|
>
|
||||||
|
<AccordionItem value="item-1">
|
||||||
|
<AccordionTrigger className="text-xl">AI</AccordionTrigger>
|
||||||
|
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||||
|
<OperatorItemList
|
||||||
|
operators={[Operator.Agent, Operator.Retrieval]}
|
||||||
|
></OperatorItemList>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="item-2">
|
||||||
|
<AccordionTrigger className="text-xl">Dialogue </AccordionTrigger>
|
||||||
|
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||||
|
<OperatorItemList operators={[Operator.Message]}></OperatorItemList>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="item-3">
|
||||||
|
<AccordionTrigger className="text-xl">Flow</AccordionTrigger>
|
||||||
|
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||||
|
<OperatorItemList
|
||||||
|
operators={[
|
||||||
|
Operator.Switch,
|
||||||
|
Operator.Iteration,
|
||||||
|
Operator.Categorize,
|
||||||
|
]}
|
||||||
|
></OperatorItemList>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="item-4">
|
||||||
|
<AccordionTrigger className="text-xl">
|
||||||
|
Data Manipulation
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||||
|
<OperatorItemList operators={[Operator.Code]}></OperatorItemList>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem value="item-5">
|
||||||
|
<AccordionTrigger className="text-xl">Tools</AccordionTrigger>
|
||||||
|
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||||
|
<OperatorItemList operators={[]}></OperatorItemList>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NextStepDropdown({ children }: PropsWithChildren) {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="w-[300px] font-semibold"
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel>Next Step</DropdownMenuLabel>
|
||||||
|
<AccordionOperators></AccordionOperators>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,17 +1,41 @@
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Handle, HandleProps } from '@xyflow/react';
|
import { Handle, HandleProps } from '@xyflow/react';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { HandleContext } from '../../context';
|
||||||
|
import { NextStepDropdown } from './dropdown/next-step-dropdown';
|
||||||
|
|
||||||
|
export function CommonHandle({
|
||||||
|
className,
|
||||||
|
nodeId,
|
||||||
|
...props
|
||||||
|
}: HandleProps & { nodeId: string }) {
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
nodeId,
|
||||||
|
id: props.id,
|
||||||
|
type: props.type,
|
||||||
|
position: props.position,
|
||||||
|
}),
|
||||||
|
[nodeId, props.id, props.position, props.type],
|
||||||
|
);
|
||||||
|
|
||||||
export function CommonHandle({ className, ...props }: HandleProps) {
|
|
||||||
return (
|
return (
|
||||||
<Handle
|
<HandleContext.Provider value={value}>
|
||||||
{...props}
|
<NextStepDropdown>
|
||||||
className={cn(
|
<Handle
|
||||||
'inline-flex justify-center items-center !bg-background-checked !size-4 !rounded-sm !border-none ',
|
{...props}
|
||||||
className,
|
className={cn(
|
||||||
)}
|
'inline-flex justify-center items-center !bg-background-checked !size-4 !rounded-sm !border-none ',
|
||||||
>
|
className,
|
||||||
<Plus className="size-3 pointer-events-none" />
|
)}
|
||||||
</Handle>
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="size-3 pointer-events-none" />
|
||||||
|
</Handle>
|
||||||
|
</NextStepDropdown>
|
||||||
|
</HandleContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IRagNode } from '@/interfaces/database/flow';
|
import { IRagNode } from '@/interfaces/database/flow';
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
import { NodeProps, Position } from '@xyflow/react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { CommonHandle } from './handle';
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
import NodeHeader from './node-header';
|
||||||
|
import { NodeWrapper } from './node-wrapper';
|
||||||
import { ToolBar } from './toolbar';
|
import { ToolBar } from './toolbar';
|
||||||
|
|
||||||
function InnerRagNode({
|
function InnerRagNode({
|
||||||
@ -14,36 +13,27 @@ function InnerRagNode({
|
|||||||
isConnectable = true,
|
isConnectable = true,
|
||||||
selected,
|
selected,
|
||||||
}: NodeProps<IRagNode>) {
|
}: NodeProps<IRagNode>) {
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<section
|
<NodeWrapper>
|
||||||
className={classNames(
|
<CommonHandle
|
||||||
styles.ragNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
id="c"
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
style={LeftHandleStyle}
|
||||||
></Handle>
|
nodeId={id}
|
||||||
<Handle
|
></CommonHandle>
|
||||||
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
className={styles.handle}
|
|
||||||
id="b"
|
id="b"
|
||||||
style={RightHandleStyle}
|
style={RightHandleStyle}
|
||||||
></Handle>
|
nodeId={id}
|
||||||
|
></CommonHandle>
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
</section>
|
</NodeWrapper>
|
||||||
</ToolBar>
|
</ToolBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { ILogicNode } from '@/interfaces/database/flow';
|
import { ILogicNode } from '@/interfaces/database/flow';
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
import { NodeProps, Position } from '@xyflow/react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { CommonHandle } from './handle';
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
import NodeHeader from './node-header';
|
||||||
|
import { NodeWrapper } from './node-wrapper';
|
||||||
|
import { ToolBar } from './toolbar';
|
||||||
|
|
||||||
export function InnerLogicNode({
|
export function InnerLogicNode({
|
||||||
id,
|
id,
|
||||||
@ -13,35 +13,28 @@ export function InnerLogicNode({
|
|||||||
isConnectable = true,
|
isConnectable = true,
|
||||||
selected,
|
selected,
|
||||||
}: NodeProps<ILogicNode>) {
|
}: NodeProps<ILogicNode>) {
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<section
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
className={classNames(
|
<NodeWrapper>
|
||||||
styles.logicNode,
|
<CommonHandle
|
||||||
theme === 'dark' ? styles.dark : '',
|
id="c"
|
||||||
{
|
type="source"
|
||||||
[styles.selectedNode]: selected,
|
position={Position.Left}
|
||||||
},
|
isConnectable={isConnectable}
|
||||||
)}
|
style={LeftHandleStyle}
|
||||||
>
|
nodeId={id}
|
||||||
<Handle
|
></CommonHandle>
|
||||||
id="c"
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
position={Position.Left}
|
position={Position.Right}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
className={styles.handle}
|
style={RightHandleStyle}
|
||||||
style={LeftHandleStyle}
|
id="b"
|
||||||
></Handle>
|
nodeId={id}
|
||||||
<Handle
|
></CommonHandle>
|
||||||
type="source"
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
position={Position.Right}
|
</NodeWrapper>
|
||||||
isConnectable={isConnectable}
|
</ToolBar>
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ function InnerMessageNode({
|
|||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
style={LeftHandleStyle}
|
style={LeftHandleStyle}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
@ -34,6 +35,7 @@ function InnerMessageNode({
|
|||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
style={RightHandleStyle}
|
style={RightHandleStyle}
|
||||||
id="b"
|
id="b"
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
<NodeHeader
|
<NodeHeader
|
||||||
id={id}
|
id={id}
|
||||||
|
|||||||
@ -42,6 +42,7 @@ function InnerRetrievalNode({
|
|||||||
isConnectable={isConnectable}
|
isConnectable={isConnectable}
|
||||||
className={styles.handle}
|
className={styles.handle}
|
||||||
style={LeftHandleStyle}
|
style={LeftHandleStyle}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
type="source"
|
type="source"
|
||||||
@ -50,6 +51,7 @@ function InnerRetrievalNode({
|
|||||||
className={styles.handle}
|
className={styles.handle}
|
||||||
style={RightHandleStyle}
|
style={RightHandleStyle}
|
||||||
id="b"
|
id="b"
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
<NodeHeader
|
<NodeHeader
|
||||||
id={id}
|
id={id}
|
||||||
|
|||||||
@ -66,6 +66,7 @@ function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
|||||||
position={Position.Left}
|
position={Position.Left}
|
||||||
isConnectable
|
isConnectable
|
||||||
id={'a'}
|
id={'a'}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||||
<section className="gap-2.5 flex flex-col">
|
<section className="gap-2.5 flex flex-col">
|
||||||
@ -94,6 +95,7 @@ function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
|||||||
position={Position.Right}
|
position={Position.Right}
|
||||||
isConnectable
|
isConnectable
|
||||||
style={{ ...RightHandleStyle, top: position.top }}
|
style={{ ...RightHandleStyle, top: position.top }}
|
||||||
|
nodeId={id}
|
||||||
></CommonHandle>
|
></CommonHandle>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
|
import { HandleType, Position } from '@xyflow/react';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { useAddNode } from './hooks/use-add-node';
|
import { useAddNode } from './hooks/use-add-node';
|
||||||
import { useCacheChatLog } from './hooks/use-cache-chat-log';
|
import { useCacheChatLog } from './hooks/use-cache-chat-log';
|
||||||
@ -34,3 +35,14 @@ type AgentChatLogContextType = Pick<
|
|||||||
export const AgentChatLogContext = createContext<AgentChatLogContextType>(
|
export const AgentChatLogContext = createContext<AgentChatLogContextType>(
|
||||||
{} as AgentChatLogContextType,
|
{} as AgentChatLogContextType,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type HandleContextType = {
|
||||||
|
nodeId?: string;
|
||||||
|
id?: string;
|
||||||
|
type: HandleType;
|
||||||
|
position: Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HandleContext = createContext<HandleContextType>(
|
||||||
|
{} as HandleContextType,
|
||||||
|
);
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { Position } from '@xyflow/react';
|
||||||
import { useContext, useMemo } from 'react';
|
import { useContext, useMemo } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -109,7 +110,12 @@ const AgentForm = ({ node }: INextOperatorForm) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
<BlockButton onClick={addCanvasNode(Operator.Agent, node?.id)}>
|
<BlockButton
|
||||||
|
onClick={addCanvasNode(Operator.Agent, {
|
||||||
|
id: node?.id,
|
||||||
|
position: Position.Bottom,
|
||||||
|
})}
|
||||||
|
>
|
||||||
Add Agent
|
Add Agent
|
||||||
</BlockButton>
|
</BlockButton>
|
||||||
<Output list={outputList}></Output>
|
<Output list={outputList}></Output>
|
||||||
|
|||||||
@ -124,6 +124,39 @@ export const useGetNodeName = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function useCalculateNewlyChildPosition() {
|
||||||
|
const getNode = useGraphStore((state) => state.getNode);
|
||||||
|
const nodes = useGraphStore((state) => state.nodes);
|
||||||
|
const edges = useGraphStore((state) => state.edges);
|
||||||
|
|
||||||
|
const calculateNewlyBackChildPosition = useCallback(
|
||||||
|
(id?: string, sourceHandle?: string) => {
|
||||||
|
const parentNode = getNode(id);
|
||||||
|
|
||||||
|
// Calculate the coordinates of child nodes to prevent newly added child nodes from covering other child nodes
|
||||||
|
const allChildNodeIds = edges
|
||||||
|
.filter((x) => x.source === id && x.sourceHandle === sourceHandle)
|
||||||
|
.map((x) => x.target);
|
||||||
|
|
||||||
|
const yAxises = nodes
|
||||||
|
.filter((x) => allChildNodeIds.some((y) => y === x.id))
|
||||||
|
.map((x) => x.position.y);
|
||||||
|
|
||||||
|
const maxY = Math.max(...yAxises);
|
||||||
|
|
||||||
|
const position = {
|
||||||
|
y: yAxises.length > 0 ? maxY + 262 : (parentNode?.position.y || 0) + 82,
|
||||||
|
x: (parentNode?.position.x || 0) + 140,
|
||||||
|
};
|
||||||
|
|
||||||
|
return position;
|
||||||
|
},
|
||||||
|
[edges, getNode, nodes],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { calculateNewlyBackChildPosition };
|
||||||
|
}
|
||||||
|
|
||||||
export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||||
const addNode = useGraphStore((state) => state.addNode);
|
const addNode = useGraphStore((state) => state.addNode);
|
||||||
const getNode = useGraphStore((state) => state.getNode);
|
const getNode = useGraphStore((state) => state.getNode);
|
||||||
@ -132,95 +165,111 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
|||||||
const edges = useGraphStore((state) => state.edges);
|
const edges = useGraphStore((state) => state.edges);
|
||||||
const getNodeName = useGetNodeName();
|
const getNodeName = useGetNodeName();
|
||||||
const initializeOperatorParams = useInitializeOperatorParams();
|
const initializeOperatorParams = useInitializeOperatorParams();
|
||||||
|
const { calculateNewlyBackChildPosition } = useCalculateNewlyChildPosition();
|
||||||
// const [reactFlowInstance, setReactFlowInstance] =
|
// const [reactFlowInstance, setReactFlowInstance] =
|
||||||
// useState<ReactFlowInstance<any, any>>();
|
// useState<ReactFlowInstance<any, any>>();
|
||||||
|
|
||||||
const addCanvasNode = useCallback(
|
const addCanvasNode = useCallback(
|
||||||
(type: string, id?: string) => (event: React.MouseEvent<HTMLElement>) => {
|
(
|
||||||
// reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
|
type: string,
|
||||||
// and you don't need to subtract the reactFlowBounds.left/top anymore
|
params: { id?: string; position?: Position; sourceHandle?: string } = {
|
||||||
// details: https://@xyflow/react.dev/whats-new/2023-11-10
|
position: Position.Right,
|
||||||
const position = reactFlowInstance?.screenToFlowPosition({
|
},
|
||||||
x: event.clientX,
|
) =>
|
||||||
y: event.clientY,
|
(event: React.MouseEvent<HTMLElement>) => {
|
||||||
});
|
const id = params.id;
|
||||||
|
|
||||||
const newNode: Node<any> = {
|
// reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
|
||||||
id: `${type}:${humanId()}`,
|
// and you don't need to subtract the reactFlowBounds.left/top anymore
|
||||||
type: NodeMap[type as Operator] || 'ragNode',
|
// details: https://@xyflow/react.dev/whats-new/2023-11-10
|
||||||
position: position || {
|
let position = reactFlowInstance?.screenToFlowPosition({
|
||||||
x: 0,
|
x: event.clientX,
|
||||||
y: 0,
|
y: event.clientY,
|
||||||
},
|
});
|
||||||
data: {
|
|
||||||
label: `${type}`,
|
|
||||||
name: generateNodeNamesWithIncreasingIndex(getNodeName(type), nodes),
|
|
||||||
form: initializeOperatorParams(type as Operator),
|
|
||||||
},
|
|
||||||
sourcePosition: Position.Right,
|
|
||||||
targetPosition: Position.Left,
|
|
||||||
dragHandle: getNodeDragHandle(type),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type === Operator.Iteration) {
|
if (params.position === Position.Right) {
|
||||||
newNode.width = 500;
|
position = calculateNewlyBackChildPosition(id, params.sourceHandle);
|
||||||
newNode.height = 250;
|
}
|
||||||
const iterationStartNode: Node<any> = {
|
|
||||||
id: `${Operator.IterationStart}:${humanId()}`,
|
const newNode: Node<any> = {
|
||||||
type: 'iterationStartNode',
|
id: `${type}:${humanId()}`,
|
||||||
position: { x: 50, y: 100 },
|
type: NodeMap[type as Operator] || 'ragNode',
|
||||||
// draggable: false,
|
position: position || {
|
||||||
data: {
|
x: 0,
|
||||||
label: Operator.IterationStart,
|
y: 0,
|
||||||
name: Operator.IterationStart,
|
|
||||||
form: {},
|
|
||||||
},
|
},
|
||||||
parentId: newNode.id,
|
data: {
|
||||||
extent: 'parent',
|
label: `${type}`,
|
||||||
|
name: generateNodeNamesWithIncreasingIndex(
|
||||||
|
getNodeName(type),
|
||||||
|
nodes,
|
||||||
|
),
|
||||||
|
form: initializeOperatorParams(type as Operator),
|
||||||
|
},
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
dragHandle: getNodeDragHandle(type),
|
||||||
};
|
};
|
||||||
addNode(newNode);
|
|
||||||
addNode(iterationStartNode);
|
|
||||||
} else if (type === Operator.Agent) {
|
|
||||||
const agentNode = getNode(id);
|
|
||||||
if (agentNode) {
|
|
||||||
// Calculate the coordinates of child nodes to prevent newly added child nodes from covering other child nodes
|
|
||||||
const allChildAgentNodeIds = edges
|
|
||||||
.filter((x) => x.source === id && x.sourceHandle === 'e')
|
|
||||||
.map((x) => x.target);
|
|
||||||
|
|
||||||
const xAxises = nodes
|
if (type === Operator.Iteration) {
|
||||||
.filter((x) => allChildAgentNodeIds.some((y) => y === x.id))
|
newNode.width = 500;
|
||||||
.map((x) => x.position.x);
|
newNode.height = 250;
|
||||||
|
const iterationStartNode: Node<any> = {
|
||||||
const maxX = Math.max(...xAxises);
|
id: `${Operator.IterationStart}:${humanId()}`,
|
||||||
|
type: 'iterationStartNode',
|
||||||
newNode.position = {
|
position: { x: 50, y: 100 },
|
||||||
x: xAxises.length > 0 ? maxX + 262 : agentNode.position.x + 82,
|
// draggable: false,
|
||||||
y: agentNode.position.y + 140,
|
data: {
|
||||||
|
label: Operator.IterationStart,
|
||||||
|
name: Operator.IterationStart,
|
||||||
|
form: {},
|
||||||
|
},
|
||||||
|
parentId: newNode.id,
|
||||||
|
extent: 'parent',
|
||||||
};
|
};
|
||||||
|
addNode(newNode);
|
||||||
|
addNode(iterationStartNode);
|
||||||
|
} else if (type === Operator.Agent) {
|
||||||
|
const agentNode = getNode(id);
|
||||||
|
if (agentNode) {
|
||||||
|
// Calculate the coordinates of child nodes to prevent newly added child nodes from covering other child nodes
|
||||||
|
const allChildAgentNodeIds = edges
|
||||||
|
.filter((x) => x.source === id && x.sourceHandle === 'e')
|
||||||
|
.map((x) => x.target);
|
||||||
|
|
||||||
|
const xAxises = nodes
|
||||||
|
.filter((x) => allChildAgentNodeIds.some((y) => y === x.id))
|
||||||
|
.map((x) => x.position.x);
|
||||||
|
|
||||||
|
const maxX = Math.max(...xAxises);
|
||||||
|
|
||||||
|
newNode.position = {
|
||||||
|
x: xAxises.length > 0 ? maxX + 262 : agentNode.position.x + 82,
|
||||||
|
y: agentNode.position.y + 140,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
addNode(newNode);
|
||||||
|
if (id) {
|
||||||
|
addEdge({
|
||||||
|
source: id,
|
||||||
|
target: newNode.id,
|
||||||
|
sourceHandle: 'e',
|
||||||
|
targetHandle: 'f',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const subNodeOfIteration = getRelativePositionToIterationNode(
|
||||||
|
nodes,
|
||||||
|
position,
|
||||||
|
);
|
||||||
|
if (subNodeOfIteration) {
|
||||||
|
newNode.parentId = subNodeOfIteration.parentId;
|
||||||
|
newNode.position = subNodeOfIteration.position;
|
||||||
|
newNode.extent = 'parent';
|
||||||
|
}
|
||||||
|
addNode(newNode);
|
||||||
}
|
}
|
||||||
addNode(newNode);
|
},
|
||||||
if (id) {
|
|
||||||
addEdge({
|
|
||||||
source: id,
|
|
||||||
target: newNode.id,
|
|
||||||
sourceHandle: 'e',
|
|
||||||
targetHandle: 'f',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const subNodeOfIteration = getRelativePositionToIterationNode(
|
|
||||||
nodes,
|
|
||||||
position,
|
|
||||||
);
|
|
||||||
if (subNodeOfIteration) {
|
|
||||||
newNode.parentId = subNodeOfIteration.parentId;
|
|
||||||
newNode.position = subNodeOfIteration.position;
|
|
||||||
newNode.extent = 'parent';
|
|
||||||
}
|
|
||||||
addNode(newNode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
[
|
||||||
addEdge,
|
addEdge,
|
||||||
addNode,
|
addNode,
|
||||||
|
|||||||
Reference in New Issue
Block a user