mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-23 23:16:58 +08:00
feat: supports multiple retrieval tool under an agent (#12046)
### What problem does this PR solve? Add support for multiple Retrieval tools under an agent ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -68,7 +68,7 @@ export function LargeModelFormField({
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'ghost'}>
|
||||
<Funnel className="text-text-disabled" />
|
||||
</Button>
|
||||
|
||||
@ -170,6 +170,7 @@ export interface IAgentForm {
|
||||
tools: Array<{
|
||||
name: string;
|
||||
component_name: string;
|
||||
id: string;
|
||||
params: Record<string, any>;
|
||||
}>;
|
||||
mcp: Array<{
|
||||
|
||||
@ -2,7 +2,7 @@ import { NodeCollapsible } from '@/components/collapse';
|
||||
import { IAgentForm, IToolNode } from '@/interfaces/database/agent';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { get } from 'lodash';
|
||||
import { MouseEventHandler, memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId, Operator } from '../../constant';
|
||||
import { ToolCard } from '../../form/agent-form/agent-tools';
|
||||
import { useFindMcpById } from '../../hooks/use-find-mcp-by-id';
|
||||
@ -15,22 +15,11 @@ function InnerToolNode({
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IToolNode>) {
|
||||
const { edges, getNode } = useGraphStore((state) => state);
|
||||
const { edges, getNode, setClickedToolId } = useGraphStore();
|
||||
const upstreamAgentNodeId = edges.find((x) => x.target === id)?.source;
|
||||
const upstreamAgentNode = getNode(upstreamAgentNodeId);
|
||||
const { findMcpById } = useFindMcpById();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(operator: string): MouseEventHandler<HTMLLIElement> =>
|
||||
(e) => {
|
||||
if (operator === Operator.Code) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const tools: IAgentForm['tools'] = get(
|
||||
upstreamAgentNode,
|
||||
'data.form.tools',
|
||||
@ -51,17 +40,24 @@ function InnerToolNode({
|
||||
position={Position.Top}
|
||||
isConnectable={isConnectable}
|
||||
className="!bg-accent-primary !size-2"
|
||||
></Handle>
|
||||
/>
|
||||
|
||||
<NodeCollapsible items={[tools, mcpList]}>
|
||||
{(x) => {
|
||||
if ('mcp_id' in x) {
|
||||
if (Reflect.has(x, 'mcp_id')) {
|
||||
const mcp = x as unknown as IAgentForm['mcp'][number];
|
||||
|
||||
return (
|
||||
<ToolCard
|
||||
key={mcp.mcp_id}
|
||||
onClick={handleClick(mcp.mcp_id)}
|
||||
onClick={(e) => {
|
||||
if (mcp.mcp_id === Operator.Code) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
data-tool={x.mcp_id}
|
||||
data-tool={mcp.mcp_id}
|
||||
>
|
||||
{findMcpById(mcp.mcp_id)?.name}
|
||||
</ToolCard>
|
||||
@ -69,18 +65,28 @@ function InnerToolNode({
|
||||
}
|
||||
|
||||
const tool = x as unknown as IAgentForm['tools'][number];
|
||||
|
||||
return (
|
||||
<ToolCard
|
||||
key={tool.component_name}
|
||||
onClick={handleClick(tool.component_name)}
|
||||
key={tool.id}
|
||||
onClick={(e) => {
|
||||
if (tool.component_name === Operator.Code) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
setClickedToolId(tool.id || tool.component_name);
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
data-tool={tool.component_name}
|
||||
data-tool-id={tool.id}
|
||||
>
|
||||
<div className="flex gap-1 items-center pointer-events-none">
|
||||
<OperatorIcon
|
||||
name={tool.component_name as Operator}
|
||||
></OperatorIcon>
|
||||
{tool.component_name}
|
||||
<OperatorIcon name={tool.component_name as Operator} />
|
||||
|
||||
{tool.component_name === Operator.Retrieval
|
||||
? tool.name
|
||||
: tool.component_name}
|
||||
</div>
|
||||
</ToolCard>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Button, ButtonProps } from '@/components/ui/button';
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipNode,
|
||||
@ -6,31 +7,24 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { Copy, Play, Trash2 } from 'lucide-react';
|
||||
import {
|
||||
HTMLAttributes,
|
||||
MouseEventHandler,
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { Operator } from '../../constant';
|
||||
import { useDuplicateNode } from '../../hooks';
|
||||
import useGraphStore from '../../store';
|
||||
|
||||
function IconWrapper({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement>) {
|
||||
function IconWrapper({ children, className, ...props }: ButtonProps) {
|
||||
return (
|
||||
<div
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className={cn(
|
||||
'p-1.5 bg-bg-component border border-border-button rounded-sm cursor-pointer hover:text-text-primary',
|
||||
'size-7 p-0 bg-bg-component text-current hover:text-text-primary focus-visible:text-text-primary',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,7 +49,7 @@ export function ToolBar({
|
||||
(store) => store.deleteIterationNodeById,
|
||||
);
|
||||
|
||||
const deleteNode: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
const deleteNode: MouseEventHandler<HTMLButtonElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
if ([Operator.Iteration, Operator.Loop].includes(label as Operator)) {
|
||||
@ -69,7 +63,7 @@ export function ToolBar({
|
||||
|
||||
const duplicateNode = useDuplicateNode();
|
||||
|
||||
const handleDuplicate: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
const handleDuplicate: MouseEventHandler<HTMLButtonElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateNode(id, label);
|
||||
@ -82,7 +76,7 @@ export function ToolBar({
|
||||
<TooltipTrigger className="h-full">{children}</TooltipTrigger>
|
||||
|
||||
<TooltipContent position={Position.Top}>
|
||||
<section className="flex gap-2 items-center text-text-secondary">
|
||||
<section className="flex gap-2 items-center text-text-secondary pb-2">
|
||||
{showRun && (
|
||||
<IconWrapper>
|
||||
<Play className="size-3.5" data-play />
|
||||
@ -94,8 +88,8 @@ export function ToolBar({
|
||||
</IconWrapper>
|
||||
)}
|
||||
<IconWrapper
|
||||
onClick={deleteNode}
|
||||
className="hover:text-state-error hover:border-state-error"
|
||||
onClick={deleteNode}
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</IconWrapper>
|
||||
|
||||
@ -778,6 +778,7 @@ export const NoDebugOperatorsList = [
|
||||
Operator.Splitter,
|
||||
Operator.HierarchicalMerger,
|
||||
Operator.Extractor,
|
||||
Operator.Tool,
|
||||
];
|
||||
|
||||
export const NoCopyOperatorsList = [
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
@ -41,49 +42,69 @@ const FormSheet = ({
|
||||
showSingleDebugDrawer,
|
||||
}: IModalProps<any> & IProps) => {
|
||||
const operatorName: Operator = node?.data.label as Operator;
|
||||
const clickedToolId = useGraphStore((state) => state.clickedToolId);
|
||||
const { clickedToolId, getAgentToolById } = useGraphStore();
|
||||
|
||||
const currentFormMap = FormConfigMap[operatorName];
|
||||
|
||||
const OperatorForm = currentFormMap?.component ?? EmptyContent;
|
||||
|
||||
const isMcp = useIsMcp(operatorName);
|
||||
|
||||
const { t } = useTranslate('flow');
|
||||
const { component_name: toolComponentName } = (getAgentToolById(
|
||||
clickedToolId,
|
||||
) ?? {}) as {
|
||||
component_name: Operator;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={visible} modal={false}>
|
||||
<SheetContent
|
||||
className={cn('top-20 p-0 flex flex-col pb-20', {
|
||||
className={cn('top-20 p-0 flex flex-col pb-20 gap-0', {
|
||||
'right-[clamp(0px,34%,620px)]': chatVisible,
|
||||
})}
|
||||
closeIcon={false}
|
||||
>
|
||||
<SheetHeader>
|
||||
<SheetTitle className="hidden"></SheetTitle>
|
||||
<section className="flex-col border-b py-2 px-5">
|
||||
<section className="flex-col border-b pt-2 pb-4 px-5">
|
||||
<div className="flex items-center gap-2 pb-3">
|
||||
<OperatorIcon name={operatorName}></OperatorIcon>
|
||||
<OperatorIcon
|
||||
name={toolComponentName || operatorName}
|
||||
></OperatorIcon>
|
||||
<TitleInput node={node}></TitleInput>
|
||||
{needsSingleStepDebugging(operatorName) && (
|
||||
<RunTooltip>
|
||||
<CirclePlay
|
||||
className="size-3.5 cursor-pointer"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-6 !p-0 bg-transparent"
|
||||
onClick={showSingleDebugDrawer}
|
||||
/>
|
||||
>
|
||||
<CirclePlay className="size-3.5 cursor-pointer" />
|
||||
</Button>
|
||||
</RunTooltip>
|
||||
)}
|
||||
<X onClick={hideModal} className="size-3.5 cursor-pointer" />
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-6 !p-0 bg-transparent"
|
||||
onClick={hideModal}
|
||||
>
|
||||
<X className="size-3.5 cursor-pointer" />
|
||||
</Button>
|
||||
</div>
|
||||
{isMcp || (
|
||||
<span className="text-text-secondary">
|
||||
|
||||
{!isMcp && (
|
||||
<p className="text-text-secondary">
|
||||
{t(
|
||||
`${lowerFirst(operatorName === Operator.Tool ? clickedToolId : operatorName)}Description`,
|
||||
`${lowerFirst(operatorName === Operator.Tool ? toolComponentName : operatorName)}Description`,
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
</SheetHeader>
|
||||
|
||||
<section className="pt-4 overflow-auto flex-1">
|
||||
{visible && (
|
||||
<AgentFormContext.Provider value={node}>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/agent';
|
||||
import { PenLine } from 'lucide-react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BeginId, Operator } from '../constant';
|
||||
import { useHandleNodeNameChange } from '../hooks/use-change-node-name';
|
||||
@ -13,47 +14,75 @@ type TitleInputProps = {
|
||||
|
||||
export function TitleInput({ node }: TitleInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
|
||||
id: node?.id,
|
||||
data: node?.data,
|
||||
});
|
||||
|
||||
const operatorName: Operator = node?.data.label as Operator;
|
||||
|
||||
const isMcp = useIsMcp(operatorName);
|
||||
|
||||
const [isEditingMode, setIsEditingMode] = useState(false);
|
||||
|
||||
const switchIsEditingMode = useCallback(() => {
|
||||
setIsEditingMode((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
handleNameBlur();
|
||||
setIsEditingMode(false);
|
||||
}, [handleNameBlur]);
|
||||
const handleBlur = useCallback(
|
||||
(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (handleNameBlur()) {
|
||||
setIsEditingMode(false);
|
||||
} else {
|
||||
// Re-focus the input if name doesn't change successfully
|
||||
e.target.focus();
|
||||
e.target.select();
|
||||
}
|
||||
},
|
||||
[handleNameBlur],
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isEditingMode && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [isEditingMode]);
|
||||
|
||||
if (isMcp) {
|
||||
return <div className="flex-1 text-base">MCP Config</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
// Give a fixed height to prevent layout shift when switching between edit and view modes
|
||||
<div className="flex items-center gap-1 flex-1 h-8 mr-2">
|
||||
{node?.id === BeginId ? (
|
||||
<span>{t(BeginId)}</span>
|
||||
// Begin node is not editable
|
||||
<span>{t(`flow.${BeginId}`)}</span>
|
||||
) : isEditingMode ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={name}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={(e) => {
|
||||
// Support committing the value changes by pressing Enter
|
||||
if (e.key === 'Enter') {
|
||||
handleBlur(e as unknown as React.FocusEvent<HTMLInputElement>);
|
||||
}
|
||||
}}
|
||||
onChange={handleNameChange}
|
||||
></Input>
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center gap-2.5 text-base">
|
||||
{name}
|
||||
<PenLine
|
||||
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
className="size-6 !p-0 border-0 bg-transparent"
|
||||
onClick={switchIsEditingMode}
|
||||
className="size-3.5 text-text-secondary cursor-pointer"
|
||||
/>
|
||||
>
|
||||
<PenLine className="size-3.5 text-text-secondary cursor-pointer" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BlockButton } from '@/components/ui/button';
|
||||
import { BlockButton, Button } from '@/components/ui/button';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@ -26,7 +26,7 @@ import { filterDownstreamAgentNodeIds } from '../../utils/filter-downstream-node
|
||||
import { ToolPopover } from './tool-popover';
|
||||
import { useDeleteAgentNodeMCP } from './tool-popover/use-update-mcp';
|
||||
import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools';
|
||||
import { useGetAgentMCPIds, useGetAgentToolNames } from './use-get-tools';
|
||||
import { useGetAgentMCPIds, useGetNodeTools } from './use-get-tools';
|
||||
|
||||
type ToolCardProps = React.HTMLAttributes<HTMLLIElement> &
|
||||
PropsWithChildren & {
|
||||
@ -79,20 +79,33 @@ function ActionButton<T>({ deleteRecord, record, edit }: ActionButtonProps<T>) {
|
||||
deleteRecord(record);
|
||||
}, [deleteRecord, record]);
|
||||
|
||||
// Wrapping into buttons to solve the issue that clicking icon occasionally not jumping to corresponding form
|
||||
return (
|
||||
<div className="flex items-center gap-4 text-text-secondary">
|
||||
<PencilLine
|
||||
className="size-3.5 cursor-pointer"
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
className="size-3.5 !bg-transparent !border-none"
|
||||
data-tool={record}
|
||||
onClick={edit}
|
||||
/>
|
||||
<X className="size-3.5 cursor-pointer" onClick={handleDelete} />
|
||||
>
|
||||
<PencilLine className="size-full" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
className="size-3.5 !bg-transparent !border-none"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<X className="size-full" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentTools() {
|
||||
const { toolNames } = useGetAgentToolNames();
|
||||
const tools = useGetNodeTools();
|
||||
const { deleteNodeTool } = useDeleteAgentNodeTools();
|
||||
const { mcpIds } = useGetAgentMCPIds();
|
||||
const { findMcpById } = useFindMcpById();
|
||||
@ -105,6 +118,7 @@ export function AgentTools() {
|
||||
const handleEdit: MouseEventHandler<SVGSVGElement> = useCallback(
|
||||
(e) => {
|
||||
const toolNodeId = findAgentToolNodeById(clickedNodeId);
|
||||
|
||||
if (toolNodeId) {
|
||||
selectNodeIds([toolNodeId]);
|
||||
showFormDrawer(e, toolNodeId);
|
||||
@ -117,19 +131,20 @@ export function AgentTools() {
|
||||
<section className="space-y-2.5">
|
||||
<span className="text-text-secondary text-sm">{t('flow.tools')}</span>
|
||||
<ul className="space-y-2.5">
|
||||
{toolNames.map((x) => (
|
||||
<ToolCard key={x} isNodeTool={false}>
|
||||
{tools.map(({ id, component_name, name }) => (
|
||||
<ToolCard key={id} isNodeTool={false}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<OperatorIcon name={x as Operator}></OperatorIcon>
|
||||
{x}
|
||||
<OperatorIcon name={component_name as Operator}></OperatorIcon>
|
||||
{component_name === Operator.Retrieval ? name : component_name}
|
||||
</div>
|
||||
<ActionButton
|
||||
record={x}
|
||||
deleteRecord={deleteNodeTool(x)}
|
||||
record={id}
|
||||
deleteRecord={deleteNodeTool(id)}
|
||||
edit={handleEdit}
|
||||
></ActionButton>
|
||||
/>
|
||||
</ToolCard>
|
||||
))}
|
||||
|
||||
{mcpIds.map((id) => (
|
||||
<ToolCard key={id} isNodeTool={false}>
|
||||
{findMcpById(id)?.name}
|
||||
|
||||
@ -9,21 +9,19 @@ import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context';
|
||||
import useGraphStore from '@/pages/agent/store';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { t } from 'i18next';
|
||||
import { PropsWithChildren, useCallback, useContext, useEffect } from 'react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useGetAgentMCPIds, useGetAgentToolNames } from '../use-get-tools';
|
||||
import { MCPCommand, ToolCommand } from './tool-command';
|
||||
import { useUpdateAgentNodeMCP } from './use-update-mcp';
|
||||
import { useUpdateAgentNodeTools } from './use-update-tools';
|
||||
|
||||
enum ToolType {
|
||||
Common = 'common',
|
||||
MCP = 'mcp',
|
||||
}
|
||||
|
||||
export function ToolPopover({ children }: PropsWithChildren) {
|
||||
export function ToolPopover({ children }: React.PropsWithChildren) {
|
||||
const { addCanvasNode } = useContext(AgentInstanceContext);
|
||||
const node = useContext(AgentFormContext);
|
||||
const { updateNodeTools } = useUpdateAgentNodeTools();
|
||||
const { toolNames } = useGetAgentToolNames();
|
||||
const deleteAgentToolNodeById = useGraphStore(
|
||||
(state) => state.deleteAgentToolNodeById,
|
||||
@ -31,15 +29,6 @@ export function ToolPopover({ children }: PropsWithChildren) {
|
||||
const { mcpIds } = useGetAgentMCPIds();
|
||||
const { updateNodeMCP } = useUpdateAgentNodeMCP();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string[]) => {
|
||||
if (Array.isArray(value) && node?.id) {
|
||||
updateNodeTools(value);
|
||||
}
|
||||
},
|
||||
[node?.id, updateNodeTools],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const total = toolNames.length + mcpIds.length;
|
||||
if (node?.id) {
|
||||
@ -72,10 +61,7 @@ export function ToolPopover({ children }: PropsWithChildren) {
|
||||
<TabsTrigger value={ToolType.MCP}>MCP</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={ToolType.Common}>
|
||||
<ToolCommand
|
||||
onChange={handleChange}
|
||||
value={toolNames}
|
||||
></ToolCommand>
|
||||
<ToolCommand />
|
||||
</TabsContent>
|
||||
<TabsContent value={ToolType.MCP}>
|
||||
<MCPCommand value={mcpIds} onChange={updateNodeMCP}></MCPCommand>
|
||||
|
||||
@ -12,8 +12,10 @@ import { Operator } from '@/pages/agent/constant';
|
||||
import OperatorIcon from '@/pages/agent/operator-icon';
|
||||
import { t } from 'i18next';
|
||||
import { lowerFirst } from 'lodash';
|
||||
import { LucidePlus } from 'lucide-react';
|
||||
import { PropsWithChildren, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetNodeTools, useUpdateAgentNodeTools } from './use-update-tools';
|
||||
|
||||
const Menus = [
|
||||
{
|
||||
@ -66,7 +68,13 @@ function ToolCommandItem({
|
||||
}: ToolCommandItemProps & PropsWithChildren) {
|
||||
return (
|
||||
<CommandItem className="cursor-pointer" onSelect={() => toggleOption(id)}>
|
||||
<Checkbox checked={isSelected} />
|
||||
{id === Operator.Retrieval ? (
|
||||
<span>
|
||||
<LucidePlus className="size-4" />
|
||||
</span>
|
||||
) : (
|
||||
<Checkbox checked={isSelected} />
|
||||
)}
|
||||
{children}
|
||||
</CommandItem>
|
||||
);
|
||||
@ -98,12 +106,12 @@ function useHandleSelectChange({ onChange, value }: ToolCommandProps) {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export function ToolCommand({ value, onChange }: ToolCommandProps) {
|
||||
const { t } = useTranslation();
|
||||
const { toggleOption, currentValue } = useHandleSelectChange({
|
||||
onChange,
|
||||
value,
|
||||
});
|
||||
|
||||
const currentValue = useGetNodeTools();
|
||||
const { updateNodeTools } = useUpdateAgentNodeTools();
|
||||
|
||||
return (
|
||||
<Command>
|
||||
@ -112,22 +120,17 @@ export function ToolCommand({ value, onChange }: ToolCommandProps) {
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
{Menus.map((x) => (
|
||||
<CommandGroup heading={x.label} key={x.label}>
|
||||
{x.list.map((y) => {
|
||||
const isSelected = currentValue.includes(y);
|
||||
return (
|
||||
<ToolCommandItem
|
||||
key={y}
|
||||
id={y}
|
||||
toggleOption={toggleOption}
|
||||
isSelected={isSelected}
|
||||
>
|
||||
<>
|
||||
<OperatorIcon name={y as Operator}></OperatorIcon>
|
||||
<span>{t(`flow.${lowerFirst(y)}`)}</span>
|
||||
</>
|
||||
</ToolCommandItem>
|
||||
);
|
||||
})}
|
||||
{x.list.map((y) => (
|
||||
<ToolCommandItem
|
||||
key={y}
|
||||
id={y}
|
||||
toggleOption={updateNodeTools}
|
||||
isSelected={currentValue.some((x) => x.component_name === y)}
|
||||
>
|
||||
<OperatorIcon name={y as Operator}></OperatorIcon>
|
||||
<span>{t(`flow.${lowerFirst(y)}`)}</span>
|
||||
</ToolCommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
|
||||
@ -16,32 +16,59 @@ export function useGetNodeTools() {
|
||||
}
|
||||
|
||||
export function useUpdateAgentNodeTools() {
|
||||
const { updateNodeForm } = useGraphStore((state) => state);
|
||||
const node = useContext(AgentFormContext);
|
||||
const { generateAgentToolName, generateAgentToolId, updateNodeForm } =
|
||||
useGraphStore((state) => state);
|
||||
const node = useContext(AgentFormContext)!;
|
||||
const tools = useGetNodeTools();
|
||||
const { initializeAgentToolValues } = useAgentToolInitialValues();
|
||||
|
||||
const updateNodeTools = useCallback(
|
||||
(value: string[]) => {
|
||||
if (node?.id) {
|
||||
const nextValue = value.reduce<IAgentForm['tools']>((pre, cur) => {
|
||||
const tool = tools.find((x) => x.component_name === cur);
|
||||
pre.push(
|
||||
tool
|
||||
? tool
|
||||
: {
|
||||
component_name: cur,
|
||||
name: cur,
|
||||
params: initializeAgentToolValues(cur as Operator),
|
||||
},
|
||||
);
|
||||
return pre;
|
||||
}, []);
|
||||
(value: string) => {
|
||||
if (!node?.id) return;
|
||||
|
||||
updateNodeForm(node?.id, nextValue, ['tools']);
|
||||
// Append
|
||||
if (value === Operator.Retrieval) {
|
||||
updateNodeForm(
|
||||
node.id,
|
||||
[
|
||||
...tools,
|
||||
{
|
||||
component_name: value,
|
||||
name: generateAgentToolName(node.id, value),
|
||||
params: initializeAgentToolValues(value as Operator),
|
||||
id: generateAgentToolId(value),
|
||||
},
|
||||
],
|
||||
['tools'],
|
||||
);
|
||||
}
|
||||
// Toggle
|
||||
else {
|
||||
updateNodeForm(
|
||||
node.id,
|
||||
tools.some((x) => x.component_name === value)
|
||||
? tools.filter((x) => x.component_name !== value)
|
||||
: [
|
||||
...tools,
|
||||
{
|
||||
component_name: value,
|
||||
name: value,
|
||||
params: initializeAgentToolValues(value as Operator),
|
||||
id: generateAgentToolId(value),
|
||||
},
|
||||
],
|
||||
['tools'],
|
||||
);
|
||||
}
|
||||
},
|
||||
[initializeAgentToolValues, node?.id, tools, updateNodeForm],
|
||||
[
|
||||
generateAgentToolName,
|
||||
generateAgentToolId,
|
||||
initializeAgentToolValues,
|
||||
node?.id,
|
||||
tools,
|
||||
updateNodeForm,
|
||||
],
|
||||
);
|
||||
|
||||
return { updateNodeTools };
|
||||
@ -53,8 +80,9 @@ export function useDeleteAgentNodeTools() {
|
||||
const node = useContext(AgentFormContext);
|
||||
|
||||
const deleteNodeTool = useCallback(
|
||||
(value: string) => () => {
|
||||
const nextTools = tools.filter((x) => x.component_name !== value);
|
||||
(toolId: string) => () => {
|
||||
const nextTools = tools.filter((x) => x.id !== toolId);
|
||||
|
||||
if (node?.id) {
|
||||
updateNodeForm(node?.id, nextTools, ['tools']);
|
||||
}
|
||||
|
||||
@ -3,6 +3,11 @@ import { get } from 'lodash';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { AgentFormContext } from '../../context';
|
||||
|
||||
export function useGetNodeTools() {
|
||||
const node = useContext(AgentFormContext);
|
||||
return get(node, 'data.form.tools', []) as IAgentForm['tools'];
|
||||
}
|
||||
|
||||
export function useGetAgentToolNames() {
|
||||
const node = useContext(AgentFormContext);
|
||||
|
||||
|
||||
@ -7,9 +7,11 @@ const EmptyContent = () => <div></div>;
|
||||
|
||||
function ToolForm() {
|
||||
const clickedToolId = useGraphStore((state) => state.clickedToolId);
|
||||
const { getAgentToolById } = useGraphStore();
|
||||
const tool = getAgentToolById(clickedToolId);
|
||||
|
||||
const ToolForm =
|
||||
ToolFormConfigMap[clickedToolId as keyof typeof ToolFormConfigMap] ??
|
||||
ToolFormConfigMap[tool?.component_name as keyof typeof ToolFormConfigMap] ??
|
||||
MCPForm ??
|
||||
EmptyContent;
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ import { useMemo } from 'react';
|
||||
import { Operator } from '../../constant';
|
||||
import { useAgentToolInitialValues } from '../../hooks/use-agent-tool-initial-values';
|
||||
import useGraphStore from '../../store';
|
||||
import { getAgentNodeTools } from '../../utils';
|
||||
|
||||
export enum SearchDepth {
|
||||
Basic = 'basic',
|
||||
@ -16,22 +15,23 @@ export enum Topic {
|
||||
}
|
||||
|
||||
export function useValues() {
|
||||
const { clickedToolId, clickedNodeId, findUpstreamNodeById } = useGraphStore(
|
||||
(state) => state,
|
||||
);
|
||||
const {
|
||||
clickedToolId,
|
||||
clickedNodeId,
|
||||
findUpstreamNodeById,
|
||||
getAgentToolById,
|
||||
} = useGraphStore();
|
||||
|
||||
const { initializeAgentToolValues } = useAgentToolInitialValues();
|
||||
|
||||
const values = useMemo(() => {
|
||||
const agentNode = findUpstreamNodeById(clickedNodeId);
|
||||
const tools = getAgentNodeTools(agentNode);
|
||||
|
||||
const formData = tools.find(
|
||||
(x) => x.component_name === clickedToolId,
|
||||
)?.params;
|
||||
const tool = getAgentToolById(clickedToolId, agentNode!);
|
||||
const formData = tool?.params;
|
||||
|
||||
if (isEmpty(formData)) {
|
||||
const defaultValues = initializeAgentToolValues(
|
||||
clickedNodeId as Operator,
|
||||
(tool?.component_name || clickedNodeId) as Operator,
|
||||
);
|
||||
|
||||
return defaultValues;
|
||||
@ -44,6 +44,7 @@ export function useValues() {
|
||||
clickedNodeId,
|
||||
clickedToolId,
|
||||
findUpstreamNodeById,
|
||||
getAgentToolById,
|
||||
initializeAgentToolValues,
|
||||
]);
|
||||
|
||||
|
||||
@ -1,39 +1,38 @@
|
||||
import { useEffect } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
import useGraphStore from '../../store';
|
||||
import { getAgentNodeTools } from '../../utils';
|
||||
|
||||
export function useWatchFormChange(form?: UseFormReturn<any>) {
|
||||
let values = useWatch({ control: form?.control });
|
||||
const { clickedToolId, clickedNodeId, findUpstreamNodeById, updateNodeForm } =
|
||||
useGraphStore((state) => state);
|
||||
|
||||
const {
|
||||
clickedToolId,
|
||||
clickedNodeId,
|
||||
findUpstreamNodeById,
|
||||
getAgentToolById,
|
||||
updateAgentToolById,
|
||||
updateNodeForm,
|
||||
} = useGraphStore();
|
||||
|
||||
useEffect(() => {
|
||||
const agentNode = findUpstreamNodeById(clickedNodeId);
|
||||
// Manually triggered form updates are synchronized to the canvas
|
||||
if (agentNode && form?.formState.isDirty) {
|
||||
const agentNodeId = agentNode?.id;
|
||||
const tools = getAgentNodeTools(agentNode);
|
||||
|
||||
values = form?.getValues();
|
||||
const nextTools = tools.map((x) => {
|
||||
if (x.component_name === clickedToolId) {
|
||||
return {
|
||||
...x,
|
||||
params: {
|
||||
...values,
|
||||
},
|
||||
};
|
||||
}
|
||||
return x;
|
||||
updateAgentToolById(agentNode, clickedToolId, {
|
||||
params: {
|
||||
...(values ?? {}),
|
||||
},
|
||||
});
|
||||
|
||||
const nextValues = {
|
||||
...(agentNode?.data?.form ?? {}),
|
||||
tools: nextTools,
|
||||
};
|
||||
|
||||
updateNodeForm(agentNodeId, nextValues);
|
||||
}
|
||||
}, [form?.formState.isDirty, updateNodeForm, values]);
|
||||
}, [
|
||||
clickedNodeId,
|
||||
clickedToolId,
|
||||
findUpstreamNodeById,
|
||||
form,
|
||||
form?.formState.isDirty,
|
||||
getAgentToolById,
|
||||
updateAgentToolById,
|
||||
updateNodeForm,
|
||||
values,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -6,14 +6,13 @@ import {
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Operator } from '../constant';
|
||||
import useGraphStore from '../store';
|
||||
import { getAgentNodeTools } from '../utils';
|
||||
|
||||
export function useHandleTooNodeNameChange({
|
||||
export function useHandleToolNodeNameChange({
|
||||
id,
|
||||
name,
|
||||
setName,
|
||||
@ -22,48 +21,44 @@ export function useHandleTooNodeNameChange({
|
||||
name?: string;
|
||||
setName: Dispatch<SetStateAction<string>>;
|
||||
}) {
|
||||
const { clickedToolId, findUpstreamNodeById, updateNodeForm } = useGraphStore(
|
||||
(state) => state,
|
||||
);
|
||||
const agentNode = findUpstreamNodeById(id);
|
||||
const {
|
||||
clickedToolId,
|
||||
findUpstreamNodeById,
|
||||
getAgentToolById,
|
||||
updateAgentToolById,
|
||||
} = useGraphStore((state) => state);
|
||||
const agentNode = findUpstreamNodeById(id)!;
|
||||
const tools = getAgentNodeTools(agentNode);
|
||||
|
||||
const previousName = useMemo(() => {
|
||||
const tool = tools.find((x) => x.component_name === clickedToolId);
|
||||
return tool?.name || tool?.component_name;
|
||||
}, [clickedToolId, tools]);
|
||||
const previousName = getAgentToolById(clickedToolId, agentNode)?.name;
|
||||
|
||||
const handleToolNameBlur = useCallback(() => {
|
||||
const trimmedName = trim(name);
|
||||
const existsSameName = tools.some((x) => x.name === trimmedName);
|
||||
if (trimmedName === '' || existsSameName) {
|
||||
if (existsSameName && previousName !== name) {
|
||||
message.error('The name cannot be repeated');
|
||||
}
|
||||
|
||||
// Not changed
|
||||
if (trimmedName === '') {
|
||||
setName(previousName || '');
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (existsSameName && previousName !== name) {
|
||||
message.error('The name cannot be repeated');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (agentNode?.id) {
|
||||
const nextTools = tools.map((x) => {
|
||||
if (x.component_name === clickedToolId) {
|
||||
return {
|
||||
...x,
|
||||
name,
|
||||
};
|
||||
}
|
||||
return x;
|
||||
});
|
||||
updateNodeForm(agentNode?.id, nextTools, ['tools']);
|
||||
updateAgentToolById(agentNode, clickedToolId, { name });
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [
|
||||
agentNode?.id,
|
||||
agentNode,
|
||||
clickedToolId,
|
||||
name,
|
||||
previousName,
|
||||
setName,
|
||||
tools,
|
||||
updateNodeForm,
|
||||
updateAgentToolById,
|
||||
]);
|
||||
|
||||
return { handleToolNameBlur, previousToolName: previousName };
|
||||
@ -83,28 +78,35 @@ export const useHandleNodeNameChange = ({
|
||||
const previousName = data?.name;
|
||||
const isToolNode = getOperatorTypeFromId(id) === Operator.Tool;
|
||||
|
||||
const { handleToolNameBlur, previousToolName } = useHandleTooNodeNameChange({
|
||||
const { handleToolNameBlur, previousToolName } = useHandleToolNodeNameChange({
|
||||
id,
|
||||
name,
|
||||
setName,
|
||||
});
|
||||
|
||||
const handleNameBlur = useCallback(() => {
|
||||
const trimmedName = trim(name);
|
||||
const existsSameName = nodes.some((x) => x.data.name === name);
|
||||
if (trim(name) === '' || existsSameName) {
|
||||
if (existsSameName && previousName !== name) {
|
||||
message.error('The name cannot be repeated');
|
||||
}
|
||||
setName(previousName);
|
||||
return;
|
||||
|
||||
// Not changed
|
||||
if (!trimmedName) {
|
||||
setName(previousName || '');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (existsSameName && previousName !== name) {
|
||||
message.error('The name cannot be repeated');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
updateNodeName(id, name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [name, id, updateNodeName, previousName, nodes]);
|
||||
|
||||
const handleNameChange = useCallback((e: ChangeEvent<any>) => {
|
||||
const handleNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setName(e.target.value);
|
||||
}, []);
|
||||
|
||||
|
||||
@ -2,10 +2,12 @@ import { Operator } from '../constant';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
export function useIsMcp(operatorName: Operator) {
|
||||
const clickedToolId = useGraphStore((state) => state.clickedToolId);
|
||||
const { clickedToolId, getAgentToolById } = useGraphStore();
|
||||
|
||||
const { component_name: toolName } = getAgentToolById(clickedToolId) ?? {};
|
||||
|
||||
return (
|
||||
operatorName === Operator.Tool &&
|
||||
Object.values(Operator).every((x) => x !== clickedToolId)
|
||||
Object.values(Operator).every((x) => x !== toolName)
|
||||
);
|
||||
}
|
||||
|
||||
@ -24,7 +24,9 @@ export const useShowFormDrawer = () => {
|
||||
|
||||
const handleShow = useCallback(
|
||||
(e: React.MouseEvent<Element>, nodeId: string) => {
|
||||
const tool = get(e.target, 'dataset.tool');
|
||||
const toolId = (e.target as HTMLElement).dataset.toolId;
|
||||
const tool = (e.target as HTMLElement).dataset.tool;
|
||||
|
||||
// TODO: Operator type judgment should be used
|
||||
const operatorType = getOperatorTypeFromId(nodeId);
|
||||
if (
|
||||
@ -36,7 +38,8 @@ export const useShowFormDrawer = () => {
|
||||
return;
|
||||
}
|
||||
setClickedNodeId(nodeId);
|
||||
setClickedToolId(tool);
|
||||
// Guess this could gracefully handle the case where the tool id is not provided?
|
||||
setClickedToolId(toolId || tool);
|
||||
showFormDrawer();
|
||||
},
|
||||
[getOperatorTypeFromId, setClickedNodeId, setClickedToolId, showFormDrawer],
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import type { IAgentForm } from '@/interfaces/database/agent';
|
||||
import { IAgentNode, RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import type {} from '@redux-devtools/extension';
|
||||
import {
|
||||
Connection,
|
||||
@ -14,10 +15,15 @@ import {
|
||||
applyEdgeChanges,
|
||||
applyNodeChanges,
|
||||
} from '@xyflow/react';
|
||||
import { cloneDeep, omit } from 'lodash';
|
||||
import differenceWith from 'lodash/differenceWith';
|
||||
import intersectionWith from 'lodash/intersectionWith';
|
||||
import lodashSet from 'lodash/set';
|
||||
import humanId from 'human-id';
|
||||
import {
|
||||
cloneDeep,
|
||||
differenceWith,
|
||||
intersectionWith,
|
||||
get as lodashGet,
|
||||
set as lodashSet,
|
||||
omit,
|
||||
} from 'lodash';
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
@ -26,12 +32,26 @@ import {
|
||||
duplicateNodeForm,
|
||||
generateDuplicateNode,
|
||||
generateNodeNamesWithIncreasingIndex,
|
||||
getAgentNodeTools,
|
||||
getOperatorIndex,
|
||||
isEdgeEqual,
|
||||
mapEdgeMouseEvent,
|
||||
} from './utils';
|
||||
import { deleteAllDownstreamAgentsAndTool } from './utils/delete-node';
|
||||
|
||||
type IAgentTool = IAgentForm['tools'][number];
|
||||
|
||||
interface GetAgentToolByIdFunc {
|
||||
(id: string): IAgentTool | undefined;
|
||||
(id: string, agentNode: RAGFlowNodeType): IAgentTool | undefined;
|
||||
(id: string, agentNodeId: string): IAgentTool | undefined;
|
||||
}
|
||||
|
||||
interface UpdateAgentToolByIdFunc {
|
||||
(agentNode: RAGFlowNodeType, id: string, value?: Partial<IAgentTool>): void;
|
||||
(agentNodeId: string, id: string, value?: Partial<IAgentTool>): void;
|
||||
}
|
||||
|
||||
export type RFState = {
|
||||
nodes: RAGFlowNodeType[];
|
||||
edges: Edge[];
|
||||
@ -81,6 +101,11 @@ export type RFState = {
|
||||
getParentIdById: (id?: string | null) => string | undefined;
|
||||
updateNodeName: (id: string, name: string) => void;
|
||||
generateNodeName: (name: string) => string;
|
||||
generateAgentToolName: (id: string, name: string) => string;
|
||||
generateAgentToolId: (prefix: string) => string;
|
||||
getAllAgentTools: () => IAgentTool[];
|
||||
getAgentToolById: GetAgentToolByIdFunc;
|
||||
updateAgentToolById: UpdateAgentToolByIdFunc;
|
||||
setClickedNodeId: (id?: string) => void;
|
||||
setClickedToolId: (id?: string) => void;
|
||||
findUpstreamNodeById: (id?: string | null) => RAGFlowNodeType | undefined;
|
||||
@ -501,6 +526,95 @@ const useGraphStore = create<RFState>()(
|
||||
|
||||
return generateNodeNamesWithIncreasingIndex(name, nodes);
|
||||
},
|
||||
generateAgentToolName: (id: string, name: string) => {
|
||||
const node = get().nodes.find(
|
||||
(x) => x.id === id,
|
||||
) as IAgentNode<IAgentForm>;
|
||||
|
||||
if (!node) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tools = (node.data.form!.tools as any[]).filter(
|
||||
(x) => x.component_name === name,
|
||||
);
|
||||
const lastIndex = tools.length
|
||||
? tools
|
||||
.map((x) => {
|
||||
const idx = x.name.match(/(\d+)$/)?.[1];
|
||||
return idx && isNaN(idx) ? -1 : Number(idx);
|
||||
})
|
||||
.sort((a, b) => a - b)
|
||||
.at(-1) ?? -1
|
||||
: -1;
|
||||
|
||||
return `${name}_${lastIndex + 1}`;
|
||||
},
|
||||
generateAgentToolId: (prefix: string) => {
|
||||
const allAgentToolIds = get()
|
||||
.getAllAgentTools()
|
||||
.map((t) => t.id || t.component_name);
|
||||
|
||||
let id: string;
|
||||
|
||||
// Loop for avoiding id collisions
|
||||
do {
|
||||
id = `${prefix}:${humanId()}`;
|
||||
} while (allAgentToolIds.includes(id));
|
||||
|
||||
return id;
|
||||
},
|
||||
getAllAgentTools: () => {
|
||||
return get()
|
||||
.nodes.filter((n) => n?.data?.label === Operator.Agent)
|
||||
.flatMap((n) => n?.data?.form?.tools);
|
||||
},
|
||||
getAgentToolById: (
|
||||
id: string,
|
||||
nodeOrNodeId?: RAGFlowNodeType | string,
|
||||
) => {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
const tools =
|
||||
nodeOrNodeId != null
|
||||
? getAgentNodeTools(
|
||||
typeof nodeOrNodeId === 'string'
|
||||
? get().getNode(nodeOrNodeId)
|
||||
: nodeOrNodeId,
|
||||
)
|
||||
: get().getAllAgentTools();
|
||||
|
||||
// For backward compatibility
|
||||
return tools.find((t) => (t.id || t.component_name) === id);
|
||||
},
|
||||
updateAgentToolById: (
|
||||
nodeOrNodeId: RAGFlowNodeType | string,
|
||||
id: string,
|
||||
value?: Partial<IAgentTool>,
|
||||
) => {
|
||||
const { getNode, updateNodeForm } = get();
|
||||
|
||||
const agentNode =
|
||||
typeof nodeOrNodeId === 'string'
|
||||
? getNode(nodeOrNodeId)
|
||||
: nodeOrNodeId;
|
||||
|
||||
if (!agentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolIndex = getAgentNodeTools(agentNode).findIndex(
|
||||
(t) => (t.id || t.component_name) === id,
|
||||
);
|
||||
|
||||
updateNodeForm(
|
||||
agentNode.id,
|
||||
{
|
||||
...lodashGet(agentNode.data.form, ['tools', toolIndex], {}),
|
||||
...(value ?? {}),
|
||||
},
|
||||
['tools', toolIndex],
|
||||
);
|
||||
},
|
||||
setClickedToolId: (id?: string) => {
|
||||
set({ clickedToolId: id });
|
||||
},
|
||||
|
||||
@ -120,13 +120,17 @@ function buildAgentTools(edges: Edge[], nodes: Node[], nodeId: string) {
|
||||
return {
|
||||
component_name: Operator.Agent,
|
||||
id,
|
||||
name: name as string, // Cast name to string and provide fallback
|
||||
name,
|
||||
params: { ...formData },
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
return { params, name: node?.data.name, id: node?.id };
|
||||
return { params, name: node?.data.name, id: node?.id } as {
|
||||
params: IAgentForm;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
function filterTargetsBySourceHandleId(edges: Edge[], handleId: string) {
|
||||
|
||||
Reference in New Issue
Block a user