### 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)tags/v0.20.0
| 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'; | ||||
| ); | ); | ||||
| } | } | ||||
| 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(); | ||||
| {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"> | |||||
| <PencilLine className="size-4 cursor-pointer" data-tool={x} /> | |||||
| <X | |||||
| className="size-4 cursor-pointer" | |||||
| onClick={deleteNodeTool(x)} | |||||
| /> | |||||
| </div> | |||||
| <ActionButton | |||||
| record={x} | |||||
| edit={() => {}} | |||||
| deleteRecord={deleteNodeTool(x)} | |||||
| ></ActionButton> | |||||
| </ToolCard> | </ToolCard> | ||||
| ))} | ))} | ||||
| </ul> | </ul> | ||||
| </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> | |||||
| ); | |||||
| } |
| 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, | ||||
| 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 { 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 { AgentInstanceContext } from '../../context'; | |||||
| import { initialAgentValues } from '../../constant'; | |||||
| 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'; | ||||
| useWatchFormChange(node?.id, form); | useWatchFormChange(node?.id, form); | ||||
| const { addCanvasNode } = useContext(AgentInstanceContext); | |||||
| return ( | return ( | ||||
| <Form {...form}> | <Form {...form}> | ||||
| <form | <form | ||||
| )} | )} | ||||
| <FormContainer> | <FormContainer> | ||||
| <AgentTools></AgentTools> | <AgentTools></AgentTools> | ||||
| <BlockButton | |||||
| onClick={addCanvasNode(Operator.Agent, { | |||||
| nodeId: node?.id, | |||||
| position: Position.Bottom, | |||||
| })} | |||||
| > | |||||
| Add Agent | |||||
| </BlockButton> | |||||
| <Agents node={node}></Agents> | |||||
| </FormContainer> | </FormContainer> | ||||
| <Output list={outputList}></Output> | <Output list={outputList}></Output> | ||||
| </form> | </form> |
| 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 | |||||
| export function filterAllDownstreamAgentAndToolNodeIds( | |||||
| // Get all downstream node ids | |||||
| 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.source === nodeId && | |||||
| (x.sourceHandle === NodeHandleId.AgentBottom || | |||||
| x.sourceHandle === NodeHandleId.Tool), | |||||
| (x) => x.source === nodeId && predicate(x), | |||||
| ); | ); | ||||
| 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) => { | ||||
| 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); | |||||
| } |