mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Feat: The bottom anchor of the agent node is only displayed when there is a downstream node #9869 (#10611)
### What problem does this PR solve? Feat: The bottom anchor of the agent node is only displayed when there is a downstream node #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -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)}
|
||||
>
|
||||
<SelectValue placeholder={placeholder}>{label}</SelectValue>
|
||||
</SelectTrigger>
|
||||
|
||||
@ -161,7 +161,7 @@ export type IIterationNode = BaseNode;
|
||||
export type IIterationStartNode = BaseNode;
|
||||
export type IKeywordNode = BaseNode;
|
||||
export type ICodeNode = BaseNode<ICodeForm>;
|
||||
export type IAgentNode = BaseNode;
|
||||
export type IAgentNode<T = any> = BaseNode<T>;
|
||||
|
||||
export type RAGFlowNodeType =
|
||||
| IBeginNode
|
||||
|
||||
@ -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<IAgentNode>) {
|
||||
}: NodeProps<IAgentNode<AgentFormSchemaType>>) {
|
||||
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({
|
||||
></CommonHandle>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isHeadAgent || (
|
||||
<Handle
|
||||
type="target"
|
||||
@ -67,7 +74,9 @@ function InnerAgentNode({
|
||||
isConnectable={false}
|
||||
id={NodeHandleId.AgentBottom}
|
||||
style={{ left: 180 }}
|
||||
className="!bg-accent-primary !size-2"
|
||||
className={cn('!bg-accent-primary !size-2 invisible', {
|
||||
visible: hasSubAgent(edges, id),
|
||||
})}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
@ -75,7 +84,9 @@ function InnerAgentNode({
|
||||
isConnectable={false}
|
||||
id={NodeHandleId.Tool}
|
||||
style={{ left: 20 }}
|
||||
className="!bg-accent-primary !size-2"
|
||||
className={cn('!bg-accent-primary !size-2 invisible', {
|
||||
visible: hasTools,
|
||||
})}
|
||||
></Handle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
<section className="flex flex-col gap-2">
|
||||
|
||||
@ -69,6 +69,8 @@ const FormSchema = z.object({
|
||||
cite: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type AgentFormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
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<z.infer<typeof FormSchema>>({
|
||||
const form = useForm<AgentFormSchemaType>({
|
||||
defaultValues: defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
|
||||
@ -26,6 +26,7 @@ export function useBuildPromptExtraPromptOptions(
|
||||
.map(([key, value]) => ({
|
||||
label: key,
|
||||
value: wrapPromptWithTag(value, key),
|
||||
icon: null,
|
||||
}))
|
||||
.filter((x) => {
|
||||
if (!has) {
|
||||
|
||||
@ -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[],
|
||||
|
||||
165
web/src/stories/node-collapsible.stories.tsx
Normal file
165
web/src/stories/node-collapsible.stories.tsx
Normal file
@ -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<typeof NodeCollapsible>;
|
||||
|
||||
// Form wrapper decorator
|
||||
const WithFormProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const form = useForm({
|
||||
defaultValues: {},
|
||||
resolver: zodResolver(z.object({})),
|
||||
});
|
||||
return <Form {...form}>{children}</Form>;
|
||||
};
|
||||
|
||||
const withFormProvider = (Story: any) => (
|
||||
<WithFormProvider>
|
||||
<Story />
|
||||
</WithFormProvider>
|
||||
);
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// 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) => (
|
||||
<div className="px-4 py-2 border rounded-md bg-bg-component">{item}</div>
|
||||
),
|
||||
},
|
||||
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';
|
||||
|
||||
<NodeCollapsible
|
||||
items={[
|
||||
'Document Analysis Parser',
|
||||
'Web Search Parser',
|
||||
'Database Query Parser',
|
||||
'Image Recognition Parser',
|
||||
'Audio Transcription Parser',
|
||||
'Video Processing Parser',
|
||||
'Code Analysis Parser',
|
||||
'Spreadsheet Parser'
|
||||
]}
|
||||
>
|
||||
{(item) => (
|
||||
<div className="px-4 py-2 border rounded-md bg-bg-component">
|
||||
{item}
|
||||
</div>
|
||||
)}
|
||||
</NodeCollapsible>
|
||||
\`\`\`
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WithFewItems: Story = {
|
||||
decorators: [withFormProvider],
|
||||
args: {
|
||||
items: ['Single Item'],
|
||||
children: (item: string) => (
|
||||
<div className="px-4 py-2 border rounded-md bg-bg-component">{item}</div>
|
||||
),
|
||||
},
|
||||
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) => (
|
||||
<div className="px-4 py-2 border rounded-md bg-bg-component">{item}</div>
|
||||
),
|
||||
},
|
||||
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.
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user