Feat: Display sub-agents in agent form #3221 (#8536)

### 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:
balibabu
2025-06-27 15:45:53 +08:00
committed by GitHub
parent 5a2099a1c7
commit 0f7c955634
3 changed files with 122 additions and 31 deletions

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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);
}