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 ;
+};
+
+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.
+ `,
+ },
+ },
+ },
+};