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 sub-agents in agent form #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,7 +1,13 @@
|
|||||||
import { BlockButton } from '@/components/ui/button';
|
import { BlockButton } from '@/components/ui/button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Position } from '@xyflow/react';
|
||||||
import { PencilLine, X } from 'lucide-react';
|
import { PencilLine, X } from 'lucide-react';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren, useCallback, useContext, useMemo } from 'react';
|
||||||
|
import { Operator } from '../../constant';
|
||||||
|
import { AgentInstanceContext } from '../../context';
|
||||||
|
import { INextOperatorForm } from '../../interface';
|
||||||
|
import useGraphStore from '../../store';
|
||||||
|
import { filterDownstreamAgentNodeIds } from '../../utils/filter-downstream-nodes';
|
||||||
import { ToolPopover } from './tool-popover';
|
import { ToolPopover } from './tool-popover';
|
||||||
import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools';
|
import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools';
|
||||||
import { useGetAgentToolNames } from './use-get-tools';
|
import { useGetAgentToolNames } from './use-get-tools';
|
||||||
@ -24,6 +30,32 @@ export function ToolCard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActionButtonProps<T> = {
|
||||||
|
record: T;
|
||||||
|
deleteRecord(record: T): void;
|
||||||
|
edit(record: T): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ActionButton<T>({ edit, deleteRecord, record }: ActionButtonProps<T>) {
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
deleteRecord(record);
|
||||||
|
}, [deleteRecord, record]);
|
||||||
|
const handleEdit = useCallback(() => {
|
||||||
|
edit(record);
|
||||||
|
}, [edit, record]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 text-text-sub-title">
|
||||||
|
<PencilLine
|
||||||
|
className="size-4 cursor-pointer"
|
||||||
|
data-tool={record}
|
||||||
|
onClick={handleEdit}
|
||||||
|
/>
|
||||||
|
<X className="size-4 cursor-pointer" onClick={handleDelete} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function AgentTools() {
|
export function AgentTools() {
|
||||||
const { toolNames } = useGetAgentToolNames();
|
const { toolNames } = useGetAgentToolNames();
|
||||||
const { deleteNodeTool } = useDeleteAgentNodeTools();
|
const { deleteNodeTool } = useDeleteAgentNodeTools();
|
||||||
@ -35,13 +67,11 @@ export function AgentTools() {
|
|||||||
{toolNames.map((x) => (
|
{toolNames.map((x) => (
|
||||||
<ToolCard key={x}>
|
<ToolCard key={x}>
|
||||||
{x}
|
{x}
|
||||||
<div className="flex items-center gap-2 text-text-sub-title">
|
<ActionButton
|
||||||
<PencilLine className="size-4 cursor-pointer" data-tool={x} />
|
record={x}
|
||||||
<X
|
edit={() => {}}
|
||||||
className="size-4 cursor-pointer"
|
deleteRecord={deleteNodeTool(x)}
|
||||||
onClick={deleteNodeTool(x)}
|
></ActionButton>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ToolCard>
|
</ToolCard>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@ -51,3 +81,44 @@ export function AgentTools() {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Agents({ node }: INextOperatorForm) {
|
||||||
|
const { addCanvasNode } = useContext(AgentInstanceContext);
|
||||||
|
const { deleteAgentDownstreamNodesById, edges, getNode } = useGraphStore(
|
||||||
|
(state) => state,
|
||||||
|
);
|
||||||
|
|
||||||
|
const subBottomAgentNodeIds = useMemo(() => {
|
||||||
|
return filterDownstreamAgentNodeIds(edges, node?.id);
|
||||||
|
}, [edges, node?.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="space-y-2.5">
|
||||||
|
<span className="text-text-sub-title">Agents</span>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{subBottomAgentNodeIds.map((id) => {
|
||||||
|
const currentNode = getNode(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolCard key={id}>
|
||||||
|
{currentNode?.data.name}
|
||||||
|
<ActionButton
|
||||||
|
record={id}
|
||||||
|
edit={() => {}}
|
||||||
|
deleteRecord={deleteAgentDownstreamNodesById}
|
||||||
|
></ActionButton>
|
||||||
|
</ToolCard>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<BlockButton
|
||||||
|
onClick={addCanvasNode(Operator.Agent, {
|
||||||
|
nodeId: node?.id,
|
||||||
|
position: Position.Bottom,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Add Agent
|
||||||
|
</BlockButton>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { FormContainer } from '@/components/form-container';
|
|||||||
import { LargeModelFormField } from '@/components/large-model-form-field';
|
import { LargeModelFormField } from '@/components/large-model-form-field';
|
||||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
||||||
import { BlockButton } from '@/components/ui/button';
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -11,20 +10,18 @@ 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 { 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';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { Operator, initialAgentValues } from '../../constant';
|
import { initialAgentValues } from '../../constant';
|
||||||
import { AgentInstanceContext } from '../../context';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
import useGraphStore from '../../store';
|
import useGraphStore from '../../store';
|
||||||
import { isBottomSubAgent } from '../../utils';
|
import { isBottomSubAgent } from '../../utils';
|
||||||
import { DescriptionField } from '../components/description-field';
|
import { DescriptionField } from '../components/description-field';
|
||||||
import { Output } from '../components/output';
|
import { Output } from '../components/output';
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
import { PromptEditor } from '../components/prompt-editor';
|
||||||
import { AgentTools } from './agent-tools';
|
import { AgentTools, Agents } from './agent-tools';
|
||||||
import { useValues } from './use-values';
|
import { useValues } from './use-values';
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
import { useWatchFormChange } from './use-watch-change';
|
||||||
|
|
||||||
@ -74,8 +71,6 @@ const AgentForm = ({ node }: INextOperatorForm) => {
|
|||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
useWatchFormChange(node?.id, form);
|
||||||
|
|
||||||
const { addCanvasNode } = useContext(AgentInstanceContext);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@ -129,14 +124,7 @@ const AgentForm = ({ node }: INextOperatorForm) => {
|
|||||||
)}
|
)}
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<AgentTools></AgentTools>
|
<AgentTools></AgentTools>
|
||||||
<BlockButton
|
<Agents node={node}></Agents>
|
||||||
onClick={addCanvasNode(Operator.Agent, {
|
|
||||||
nodeId: node?.id,
|
|
||||||
position: Position.Bottom,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Add Agent
|
|
||||||
</BlockButton>
|
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
<Output list={outputList}></Output>
|
<Output list={outputList}></Output>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
import { Edge } from '@xyflow/react';
|
import { Edge } from '@xyflow/react';
|
||||||
import { NodeHandleId } from '../constant';
|
import { NodeHandleId } from '../constant';
|
||||||
|
|
||||||
// Get all downstream agent operators of the current agent operator
|
// Get all downstream node ids
|
||||||
export function filterAllDownstreamAgentAndToolNodeIds(
|
export function filterAllDownstreamNodeIds(
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
nodeIds: string[],
|
nodeIds: string[],
|
||||||
|
predicate: (edge: Edge) => boolean,
|
||||||
) {
|
) {
|
||||||
return nodeIds.reduce<string[]>((pre, nodeId) => {
|
return nodeIds.reduce<string[]>((pre, nodeId) => {
|
||||||
const currentEdges = edges.filter(
|
const currentEdges = edges.filter(
|
||||||
(x) =>
|
(x) => x.source === nodeId && predicate(x),
|
||||||
x.source === nodeId &&
|
|
||||||
(x.sourceHandle === NodeHandleId.AgentBottom ||
|
|
||||||
x.sourceHandle === NodeHandleId.Tool),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const downstreamNodeIds: string[] = currentEdges.map((x) => x.target);
|
const downstreamNodeIds: string[] = currentEdges.map((x) => x.target);
|
||||||
|
|
||||||
const ids = downstreamNodeIds.concat(
|
const ids = downstreamNodeIds.concat(
|
||||||
filterAllDownstreamAgentAndToolNodeIds(edges, downstreamNodeIds),
|
filterAllDownstreamNodeIds(edges, downstreamNodeIds, predicate),
|
||||||
);
|
);
|
||||||
|
|
||||||
ids.forEach((x) => {
|
ids.forEach((x) => {
|
||||||
@ -29,3 +27,37 @@ export function filterAllDownstreamAgentAndToolNodeIds(
|
|||||||
return pre;
|
return pre;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all downstream agent and tool operators of the current agent operator
|
||||||
|
export function filterAllDownstreamAgentAndToolNodeIds(
|
||||||
|
edges: Edge[],
|
||||||
|
nodeIds: string[],
|
||||||
|
) {
|
||||||
|
return filterAllDownstreamNodeIds(
|
||||||
|
edges,
|
||||||
|
nodeIds,
|
||||||
|
(edge: Edge) =>
|
||||||
|
edge.sourceHandle === NodeHandleId.AgentBottom ||
|
||||||
|
edge.sourceHandle === NodeHandleId.Tool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all downstream agent operators of the current agent operator
|
||||||
|
export function filterAllDownstreamAgentNodeIds(
|
||||||
|
edges: Edge[],
|
||||||
|
nodeIds: string[],
|
||||||
|
) {
|
||||||
|
return filterAllDownstreamNodeIds(
|
||||||
|
edges,
|
||||||
|
nodeIds,
|
||||||
|
(edge: Edge) => edge.sourceHandle === NodeHandleId.AgentBottom,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// The direct child agent node of the current node
|
||||||
|
export function filterDownstreamAgentNodeIds(edges: Edge[], nodeId?: string) {
|
||||||
|
return edges
|
||||||
|
.filter(
|
||||||
|
(x) => x.source === nodeId && x.sourceHandle === NodeHandleId.AgentBottom,
|
||||||
|
)
|
||||||
|
.map((x) => x.target);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user