diff --git a/web/src/components/ui/select.tsx b/web/src/components/ui/select.tsx index 0e670624f..cf83959e1 100644 --- a/web/src/components/ui/select.tsx +++ b/web/src/components/ui/select.tsx @@ -291,7 +291,7 @@ export const RAGFlowSelect = forwardRef< onReset={handleReset} allowClear={allowClear} ref={ref} - className={cn(triggerClassName, 'bg-bg-base')} + className={cn('bg-bg-base', triggerClassName)} > {label} diff --git a/web/src/interfaces/database/flow.ts b/web/src/interfaces/database/flow.ts index 8b35579e2..86fd6d564 100644 --- a/web/src/interfaces/database/flow.ts +++ b/web/src/interfaces/database/flow.ts @@ -161,7 +161,7 @@ export type IIterationNode = BaseNode; export type IIterationStartNode = BaseNode; export type IKeywordNode = BaseNode; export type ICodeNode = BaseNode; -export type IAgentNode = BaseNode; +export type IAgentNode = BaseNode; export type RAGFlowNodeType = | IBeginNode diff --git a/web/src/pages/agent/canvas/node/agent-node.tsx b/web/src/pages/agent/canvas/node/agent-node.tsx index 530eea0f1..6b0e4b0b3 100644 --- a/web/src/pages/agent/canvas/node/agent-node.tsx +++ b/web/src/pages/agent/canvas/node/agent-node.tsx @@ -1,12 +1,14 @@ import LLMLabel from '@/components/llm-select/llm-label'; import { IAgentNode } from '@/interfaces/database/flow'; +import { cn } from '@/lib/utils'; import { Handle, NodeProps, Position } from '@xyflow/react'; import { get } from 'lodash'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { AgentExceptionMethod, NodeHandleId } from '../../constant'; +import { AgentFormSchemaType } from '../../form/agent-form'; import useGraphStore from '../../store'; -import { isBottomSubAgent } from '../../utils'; +import { hasSubAgent, isBottomSubAgent } from '../../utils'; import { CommonHandle, LeftEndHandle } from './handle'; import { RightHandleStyle } from './handle-icon'; import NodeHeader from './node-header'; @@ -18,7 +20,7 @@ function InnerAgentNode({ data, isConnectable = true, selected, -}: NodeProps) { +}: NodeProps>) { const edges = useGraphStore((state) => state.edges); const { t } = useTranslation(); @@ -30,6 +32,12 @@ function InnerAgentNode({ return get(data, 'form.exception_method'); }, [data]); + const hasTools = useMemo(() => { + const tools = get(data, 'form.tools', []); + const mcp = get(data, 'form.mcp', []); + return tools.length > 0 || mcp.length > 0; + }, [data]); + const isGotoMethod = useMemo(() => { return exceptionMethod === AgentExceptionMethod.Goto; }, [exceptionMethod]); @@ -51,7 +59,6 @@ function InnerAgentNode({ > )} - {isHeadAgent || (
diff --git a/web/src/pages/agent/form/agent-form/index.tsx b/web/src/pages/agent/form/agent-form/index.tsx index f38fbf17f..734ccdc39 100644 --- a/web/src/pages/agent/form/agent-form/index.tsx +++ b/web/src/pages/agent/form/agent-form/index.tsx @@ -69,6 +69,8 @@ const FormSchema = z.object({ cite: z.boolean().optional(), }); +export type AgentFormSchemaType = z.infer; + const outputList = buildOutputList(initialAgentValues.outputs); function AgentForm({ node }: INextOperatorForm) { @@ -92,7 +94,7 @@ function AgentForm({ node }: INextOperatorForm) { return isBottomSubAgent(edges, node?.id); }, [edges, node?.id]); - const form = useForm>({ + const form = useForm({ defaultValues: defaultValues, resolver: zodResolver(FormSchema), }); diff --git a/web/src/pages/agent/form/agent-form/use-build-prompt-options.ts b/web/src/pages/agent/form/agent-form/use-build-prompt-options.ts index 5a0f04abb..15e41049e 100644 --- a/web/src/pages/agent/form/agent-form/use-build-prompt-options.ts +++ b/web/src/pages/agent/form/agent-form/use-build-prompt-options.ts @@ -26,6 +26,7 @@ export function useBuildPromptExtraPromptOptions( .map(([key, value]) => ({ label: key, value: wrapPromptWithTag(value, key), + icon: null, })) .filter((x) => { if (!has) { diff --git a/web/src/pages/agent/utils.ts b/web/src/pages/agent/utils.ts index a1b392b0e..b8ba36e57 100644 --- a/web/src/pages/agent/utils.ts +++ b/web/src/pages/agent/utils.ts @@ -162,6 +162,13 @@ export function hasSubAgentOrTool(edges: Edge[], nodeId?: string) { return !!edge; } +export function hasSubAgent(edges: Edge[], nodeId?: string) { + const edge = edges.find( + (x) => x.source === nodeId && x.sourceHandle === NodeHandleId.AgentBottom, + ); + return !!edge; +} + // construct a dsl based on the node information of the graph export const buildDslComponentsByGraph = ( nodes: RAGFlowNodeType[], diff --git a/web/src/stories/node-collapsible.stories.tsx b/web/src/stories/node-collapsible.stories.tsx new file mode 100644 index 000000000..88ca218b3 --- /dev/null +++ b/web/src/stories/node-collapsible.stories.tsx @@ -0,0 +1,165 @@ +import { Form } from '@/components/ui/form'; +import type { Meta, StoryObj } from '@storybook/react-webpack5'; +import { useForm } from 'react-hook-form'; + +import { NodeCollapsible } from '@/components/collapse'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Example/NodeCollapsible', + component: NodeCollapsible, + parameters: { + layout: 'centered', + docs: { + description: { + component: ` +## Component Description + +NodeCollapsible is a specialized component for displaying collapsible content within nodes. +It automatically shows only the first 3 items and provides a toggle button to expand/collapse the rest. +The component is designed to work within the application's node-based UI, such as in agent or data flow canvases. + +The toggle button is displayed as a small circle at the bottom center of the component when there are more than 3 items. + `, + }, + }, + }, + tags: ['autodocs'], + argTypes: { + items: { + control: 'object', + description: 'Array of items to display in the collapsible component', + }, + children: { + control: false, + description: 'Function to render each item', + }, + className: { + control: 'text', + description: 'Additional CSS classes to apply to the component', + }, + }, +} satisfies Meta; + +// Form wrapper decorator +const WithFormProvider = ({ children }: { children: React.ReactNode }) => { + const form = useForm({ + defaultValues: {}, + resolver: zodResolver(z.object({})), + }); + return
{children}
; +}; + +const withFormProvider = (Story: any) => ( + + + +); + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Default: Story = { + decorators: [withFormProvider], + args: { + items: [ + 'Document Analysis Parser', + 'Web Search Parser', + 'Database Query Parser', + 'Image Recognition Parser', + 'Audio Transcription Parser', + 'Video Processing Parser', + 'Code Analysis Parser', + 'Spreadsheet Parser', + ], + children: (item: string) => ( +
{item}
+ ), + }, + parameters: { + docs: { + description: { + story: ` +### Basic Usage + +By default, the NodeCollapsible component shows the first 3 items and collapses the rest. +A toggle button appears at the bottom when there are more than 3 items. + +\`\`\`tsx +import { NodeCollapsible } from '@/components/collapse'; + + + {(item) => ( +
+ {item} +
+ )} +
+\`\`\` + `, + }, + }, + }, +}; + +export const WithFewItems: Story = { + decorators: [withFormProvider], + args: { + items: ['Single Item'], + children: (item: string) => ( +
{item}
+ ), + }, + parameters: { + docs: { + description: { + story: ` +When there are 3 or fewer items, no toggle button is shown. + `, + }, + }, + }, +}; + +export const WithManyItems: Story = { + decorators: [withFormProvider], + args: { + items: [ + 'Item 1', + 'Item 2', + 'Item 3', + 'Item 4', + 'Item 5', + 'Item 6', + 'Item 7', + 'Item 8', + ], + children: (item: string) => ( +
{item}
+ ), + }, + parameters: { + docs: { + description: { + story: ` +When there are more than 3 items, a toggle button is shown at the bottom center. +Clicking it will expand to show all items. + `, + }, + }, + }, +};