feat: Display input parameters on operator nodes #3240 (#3241)

### What problem does this PR solve?
feat: Display input parameters on operator nodes #3240


### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2024-11-06 18:48:05 +08:00 committed by GitHub
parent 4097912d59
commit d3bb5e9f3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 772 additions and 482 deletions

37
web/package-lock.json generated
View File

@ -17,6 +17,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2", "@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
@ -4370,6 +4371,42 @@
} }
} }
}, },
"node_modules/@radix-ui/react-popover": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-popover/-/react-popover-1.1.2.tgz",
"integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.1",
"@radix-ui/react-focus-guards": "1.1.1",
"@radix-ui/react-focus-scope": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-popper": "1.2.0",
"@radix-ui/react-portal": "1.1.2",
"@radix-ui/react-presence": "1.1.1",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.6.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": { "node_modules/@radix-ui/react-popper": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",

View File

@ -28,6 +28,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2", "@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",

View File

@ -0,0 +1,31 @@
'use client';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import * as React from 'react';
import { cn } from '@/lib/utils';
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverContent, PopoverTrigger };

View File

@ -0,0 +1,117 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
));
Table.displayName = 'Table';
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
));
TableHeader.displayName = 'TableHeader';
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
));
TableBody.displayName = 'TableBody';
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
className,
)}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
className,
)}
{...props}
/>
));
TableRow.displayName = 'TableRow';
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
className,
)}
{...props}
/>
));
TableHead.displayName = 'TableHead';
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)}
{...props}
/>
));
TableCell.displayName = 'TableCell';
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn('mt-4 text-sm text-muted-foreground', className)}
{...props}
/>
));
TableCaption.displayName = 'TableCaption';
export {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
};

View File

@ -11,7 +11,8 @@ export interface BaseState {
export interface IModalProps<T> { export interface IModalProps<T> {
showModal?(): void; showModal?(): void;
hideModal(): void; hideModal?(): void;
switchVisible?(visible: boolean): void;
visible?: boolean; visible?: boolean;
loading?: boolean; loading?: boolean;
onOk?(payload?: T): Promise<any> | void; onOk?(payload?: T): Promise<any> | void;

View File

@ -1031,8 +1031,11 @@ The above is the content you need to summarize.`,
'If the response is HTML formatted and only the primary content wanted, please toggle it on.', 'If the response is HTML formatted and only the primary content wanted, please toggle it on.',
reference: 'Reference', reference: 'Reference',
input: 'Input', input: 'Input',
output: 'Output',
parameter: 'Parameter', parameter: 'Parameter',
howUseId: 'How to use agent ID?', howUseId: 'How to use agent ID?',
content: 'Content',
operationResults: 'Operation Results',
}, },
footer: { footer: {
profile: 'All rights reserved @ React', profile: 'All rights reserved @ React',

View File

@ -979,8 +979,11 @@ export default {
cleanHtmlTip: '如果回應是 HTML 格式並且只需要主要內容,請將其開啟。', cleanHtmlTip: '如果回應是 HTML 格式並且只需要主要內容,請將其開啟。',
reference: '引用', reference: '引用',
input: '輸入', input: '輸入',
output: '輸出',
parameter: '參數', parameter: '參數',
howUseId: '如何使用Agent ID', howUseId: '如何使用Agent ID',
content: '內容',
operationResults: '運行結果',
}, },
footer: { footer: {
profile: '“保留所有權利 @ react”', profile: '“保留所有權利 @ react”',

View File

@ -999,8 +999,11 @@ export default {
cleanHtmlTip: '如果响应是 HTML 格式且只需要主要内容,请将其打开。', cleanHtmlTip: '如果响应是 HTML 格式且只需要主要内容,请将其打开。',
reference: '引用', reference: '引用',
input: '输入', input: '输入',
output: '输出',
parameter: '参数', parameter: '参数',
howUseId: '如何使用Agent ID', howUseId: '如何使用Agent ID',
content: '内容',
operationResults: '运行结果',
}, },
footer: { footer: {
profile: 'All rights reserved @ React', profile: 'All rights reserved @ React',

View File

@ -0,0 +1,57 @@
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
export function CardWithForm() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Create project</CardTitle>
<CardDescription>Deploy your new project in one-click.</CardDescription>
</CardHeader>
<CardContent>
<form>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Name of your project" />
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="framework">Framework</Label>
<Select>
<SelectTrigger id="framework">
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent position="popper">
<SelectItem value="next">Next.js</SelectItem>
<SelectItem value="sveltekit">SvelteKit</SelectItem>
<SelectItem value="astro">Astro</SelectItem>
<SelectItem value="nuxt">Nuxt.js</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">Cancel</Button>
<Button>Deploy</Button>
</CardFooter>
</Card>
);
}

View File

@ -8,55 +8,52 @@ import { RightHandleStyle } from './handle-icon';
import { useBuildCategorizeHandlePositions } from './hooks'; import { useBuildCategorizeHandlePositions } from './hooks';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) { export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
const { positions } = useBuildCategorizeHandlePositions({ data, id }); const { positions } = useBuildCategorizeHandlePositions({ data, id });
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
>
<Handle
type="target"
position={Position.Left}
isConnectable
className={styles.handle}
id={'a'}
></Handle>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={styles.nodeHeader}
></NodeHeader>
<Flex vertical gap={8}>
<div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div>
{positions.map((position, idx) => {
return (
<div key={idx}>
<div className={styles.nodeText}>{position.text}</div>
<Handle
key={position.text}
id={position.text}
type="source"
position={Position.Right}
isConnectable
className={styles.handle}
style={{ ...RightHandleStyle, top: position.top }}
></Handle>
</div>
);
})} })}
> </Flex>
<Handle </section>
type="target"
position={Position.Left}
isConnectable
className={styles.handle}
id={'a'}
></Handle>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={styles.nodeHeader}
></NodeHeader>
<Flex vertical gap={8}>
<div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div>
{positions.map((position, idx) => {
return (
<div key={idx}>
<div className={styles.nodeText}>{position.text}</div>
<Handle
key={position.text}
id={position.text}
type="source"
position={Position.Right}
isConnectable
className={styles.handle}
style={{ ...RightHandleStyle, top: position.top }}
></Handle>
</div>
);
})}
</Flex>
</section>
</NodePopover>
); );
} }

View File

@ -8,7 +8,6 @@ import { IGenerateParameter, NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function GenerateNode({ export function GenerateNode({
id, id,
@ -20,55 +19,53 @@ export function GenerateNode({
const getLabel = useGetComponentLabelByValue(id); const getLabel = useGetComponentLabelByValue(id);
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle id="c"
id="c" type="source"
type="source" position={Position.Left}
position={Position.Left} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={LeftHandleStyle}
style={LeftHandleStyle} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={RightHandleStyle}
style={RightHandleStyle} id="b"
id="b" ></Handle>
></Handle>
<NodeHeader <NodeHeader
id={id} id={id}
name={data.name} name={data.name}
label={data.label} label={data.label}
className={styles.nodeHeader} className={styles.nodeHeader}
></NodeHeader> ></NodeHeader>
<div className={styles.nodeText}> <div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel> <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div> </div>
<Flex gap={8} vertical className={styles.generateParameters}> <Flex gap={8} vertical className={styles.generateParameters}>
{parameters.map((x) => ( {parameters.map((x) => (
<Flex <Flex
key={x.id} key={x.id}
align="center" align="center"
gap={6} gap={6}
className={styles.conditionBlock} className={styles.conditionBlock}
> >
<label htmlFor="">{x.key}</label> <label htmlFor="">{x.key}</label>
<span className={styles.parameterValue}> <span className={styles.parameterValue}>
{getLabel(x.component_id)} {getLabel(x.component_id)}
</span> </span>
</Flex> </Flex>
))} ))}
</Flex> </Flex>
</section> </section>
</NodePopover>
); );
} }

View File

@ -4,7 +4,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function RagNode({ export function RagNode({
id, id,
@ -13,30 +12,28 @@ export function RagNode({
selected, selected,
}: NodeProps<NodeData>) { }: NodeProps<NodeData>) {
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.ragNode, {
className={classNames(styles.ragNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle id="c"
id="c" type="source"
type="source" position={Position.Left}
position={Position.Left} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={LeftHandleStyle}
style={LeftHandleStyle} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} id="b"
id="b" style={RightHandleStyle}
style={RightHandleStyle} ></Handle>
></Handle> <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader> </section>
</section>
</NodePopover>
); );
} }

View File

@ -7,7 +7,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function InvokeNode({ export function InvokeNode({
id, id,
@ -18,39 +17,37 @@ export function InvokeNode({
const { t } = useTranslation(); const { t } = useTranslation();
const url = get(data, 'form.url'); const url = get(data, 'form.url');
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.ragNode, {
className={classNames(styles.ragNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle id="c"
id="c" type="source"
type="source" position={Position.Left}
position={Position.Left} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={LeftHandleStyle}
style={LeftHandleStyle} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} id="b"
id="b" style={RightHandleStyle}
style={RightHandleStyle} ></Handle>
></Handle> <NodeHeader
<NodeHeader id={id}
id={id} name={data.name}
name={data.name} label={data.label}
label={data.label} className={styles.nodeHeader}
className={styles.nodeHeader} ></NodeHeader>
></NodeHeader> <Flex vertical>
<Flex vertical> <div>{t('flow.url')}</div>
<div>{t('flow.url')}</div> <div className={styles.nodeText}>{url}</div>
<div className={styles.nodeText}>{url}</div> </Flex>
</Flex> </section>
</section>
</NodePopover>
); );
} }

View File

@ -6,7 +6,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function KeywordNode({ export function KeywordNode({
id, id,
@ -15,40 +14,38 @@ export function KeywordNode({
selected, selected,
}: NodeProps<NodeData>) { }: NodeProps<NodeData>) {
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle id="c"
id="c" type="source"
type="source" position={Position.Left}
position={Position.Left} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={LeftHandleStyle}
style={LeftHandleStyle} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={RightHandleStyle}
style={RightHandleStyle} id="b"
id="b" ></Handle>
></Handle>
<NodeHeader <NodeHeader
id={id} id={id}
name={data.name} name={data.name}
label={data.label} label={data.label}
className={styles.nodeHeader} className={styles.nodeHeader}
></NodeHeader> ></NodeHeader>
<div className={styles.nodeText}> <div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel> <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div> </div>
</section> </section>
</NodePopover>
); );
} }

View File

@ -4,7 +4,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function LogicNode({ export function LogicNode({
id, id,
@ -13,30 +12,28 @@ export function LogicNode({
selected, selected,
}: NodeProps<NodeData>) { }: NodeProps<NodeData>) {
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle id="c"
id="c" type="source"
type="source" position={Position.Left}
position={Position.Left} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={LeftHandleStyle}
style={LeftHandleStyle} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={RightHandleStyle}
style={RightHandleStyle} id="b"
id="b" ></Handle>
></Handle> <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader> </section>
</section>
</NodePopover>
); );
} }

View File

@ -6,7 +6,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function MessageNode({ export function MessageNode({
id, id,
@ -17,47 +16,45 @@ export function MessageNode({
const messages: string[] = get(data, 'form.messages', []); const messages: string[] = get(data, 'form.messages', []);
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
>
<Handle
id="c"
type="source"
position={Position.Left}
isConnectable={isConnectable}
className={styles.handle}
style={LeftHandleStyle}
></Handle>
<Handle
type="source"
position={Position.Right}
isConnectable={isConnectable}
className={styles.handle}
style={RightHandleStyle}
id="b"
></Handle>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={classNames({
[styles.nodeHeader]: messages.length > 0,
})} })}
> ></NodeHeader>
<Handle
id="c"
type="source"
position={Position.Left}
isConnectable={isConnectable}
className={styles.handle}
style={LeftHandleStyle}
></Handle>
<Handle
type="source"
position={Position.Right}
isConnectable={isConnectable}
className={styles.handle}
style={RightHandleStyle}
id="b"
></Handle>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={classNames({
[styles.nodeHeader]: messages.length > 0,
})}
></NodeHeader>
<Flex vertical gap={8} className={styles.messageNodeContainer}> <Flex vertical gap={8} className={styles.messageNodeContainer}>
{messages.map((message, idx) => { {messages.map((message, idx) => {
return ( return (
<div className={styles.nodeText} key={idx}> <div className={styles.nodeText} key={idx}>
{message} {message}
</div> </div>
); );
})} })}
</Flex> </Flex>
</section> </section>
</NodePopover>
); );
} }

View File

@ -1,34 +1,52 @@
import { Flex } from 'antd'; import { Flex } from 'antd';
import { Operator, operatorMap } from '../../constant'; import { Operator, operatorMap } from '../../constant';
import OperatorIcon from '../../operator-icon'; import OperatorIcon from '../../operator-icon';
import NodeDropdown from './dropdown'; import NodeDropdown from './dropdown';
import { useTranslate } from '@/hooks/common-hooks';
import styles from './index.less'; import styles from './index.less';
import { NextNodePopover } from './popover';
interface IProps { interface IProps {
id: string; id: string;
label: string; label?: string;
name: string; name?: string;
gap?: number; gap?: number;
className?: string; className?: string;
} }
export function RunStatus({ id, name }: IProps) {
const { t } = useTranslate('flow');
return (
<section className="flex justify-end items-center pb-1 ">
<NextNodePopover nodeId={id} name={name}>
<span className="text-blue-600 cursor-pointer text-[10px]">
{t('operationResults')}
</span>
</NextNodePopover>
</section>
);
}
const NodeHeader = ({ label, id, name, gap = 4, className }: IProps) => { const NodeHeader = ({ label, id, name, gap = 4, className }: IProps) => {
return ( return (
<Flex <section className="haha">
flex={1} {label !== Operator.Answer && <RunStatus id={id} name={name}></RunStatus>}
align="center" <Flex
justify={'space-between'} flex={1}
gap={gap} align="center"
className={className} justify={'space-between'}
> gap={gap}
<OperatorIcon className={className}
name={label as Operator} >
color={operatorMap[label as Operator].color} <OperatorIcon
></OperatorIcon> name={label as Operator}
<span className={styles.nodeTitle}>{name}</span> color={operatorMap[label as Operator].color}
<NodeDropdown id={id}></NodeDropdown> ></OperatorIcon>
</Flex> <span className={styles.nodeTitle}>{name}</span>
<NodeDropdown id={id}></NodeDropdown>
</Flex>
</section>
); );
}; };

View File

@ -1,49 +1,98 @@
import { useFetchFlow } from '@/hooks/flow-hooks'; import { useFetchFlow } from '@/hooks/flow-hooks';
import { Popover } from 'antd';
import get from 'lodash/get'; import get from 'lodash/get';
import React, { useMemo } from 'react'; import React, { MouseEventHandler, useCallback, useMemo } from 'react';
import JsonView from 'react18-json-view'; import JsonView from 'react18-json-view';
import 'react18-json-view/src/style.css'; import 'react18-json-view/src/style.css';
import { Operator } from '../../constant';
import { useReplaceIdWithText } from '../../hooks'; import { useReplaceIdWithText } from '../../hooks';
import styles from './index.less'; import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { useTranslate } from '@/hooks/common-hooks';
interface IProps extends React.PropsWithChildren { interface IProps extends React.PropsWithChildren {
nodeId: string; nodeId: string;
name?: string;
} }
const NodePopover = ({ children, nodeId }: IProps) => { export function NextNodePopover({ children, nodeId, name }: IProps) {
const { t } = useTranslate('flow');
const { data } = useFetchFlow(); const { data } = useFetchFlow();
const component = useMemo(() => { const component = useMemo(() => {
return get(data, ['dsl', 'components', nodeId], {}); return get(data, ['dsl', 'components', nodeId], {});
}, [nodeId, data]); }, [nodeId, data]);
const inputs: Array<{ component_id: string; content: string }> = get(
component,
['obj', 'params', 'inputs'],
[],
);
const output = get(component, ['obj', 'params', 'output'], {}); const output = get(component, ['obj', 'params', 'output'], {});
const componentName = get(component, ['obj', 'component_name'], ''); const { replacedOutput, getNameById } = useReplaceIdWithText(output);
const replacedOutput = useReplaceIdWithText(output); const stopPropagation: MouseEventHandler = useCallback((e) => {
e.stopPropagation();
const content = }, []);
componentName !== Operator.Answer ? (
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<JsonView
src={replacedOutput}
displaySize={30}
className={styles.jsonView}
/>
</div>
) : undefined;
return ( return (
<Popover content={content} placement="right" destroyTooltipOnHide> <Popover>
{children} <PopoverTrigger onClick={stopPropagation} asChild>
{children}
</PopoverTrigger>
<PopoverContent
align={'start'}
side={'right'}
sideOffset={20}
onClick={stopPropagation}
className="w-[400px]"
>
<div className="mb-3 font-semibold text-[16px]">
{name} {t('operationResults')}
</div>
<div className="flex w-full gap-4 flex-col">
<div className="flex flex-col space-y-1.5">
<span className="font-semibold text-[14px]">{t('input')}</span>
<div className="bg-gray-100 p-1 rounded">
<Table>
<TableHeader>
<TableRow>
<TableHead>{t('componentId')}</TableHead>
<TableHead className="w-[60px]">{t('content')}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{inputs.map((x, idx) => (
<TableRow key={idx}>
<TableCell>{getNameById(x.component_id)}</TableCell>
<TableCell className="truncate">{x.content}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
<div className="flex flex-col space-y-1.5">
<span className="font-semibold text-[14px]">{t('output')}</span>
<div className="bg-gray-100 p-1 rounded">
<JsonView
src={replacedOutput}
displaySize={30}
className="w-full max-h-[300px] break-words overflow-auto"
/>
</div>
</div>
</div>
</PopoverContent>
</Popover> </Popover>
); );
}; }
export default NodePopover;

View File

@ -3,7 +3,6 @@ import classNames from 'classnames';
import { Handle, NodeProps, Position } from 'reactflow'; import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface'; import { NodeData } from '../../interface';
import { RightHandleStyle } from './handle-icon'; import { RightHandleStyle } from './handle-icon';
import NodePopover from './popover';
import { get } from 'lodash'; import { get } from 'lodash';
import styles from './index.less'; import styles from './index.less';
@ -13,53 +12,51 @@ export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
const yes = get(data, 'form.yes'); const yes = get(data, 'form.yes');
const no = get(data, 'form.no'); const no = get(data, 'form.no');
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle type="target"
type="target" position={Position.Left}
position={Position.Left} isConnectable
isConnectable className={styles.handle}
className={styles.handle} id={'a'}
id={'a'} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable
isConnectable className={styles.handle}
className={styles.handle} id={'yes'}
id={'yes'} style={{ ...RightHandleStyle, top: 59 }}
style={{ ...RightHandleStyle, top: 59 }} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable
isConnectable className={styles.handle}
className={styles.handle} id={'no'}
id={'no'} style={{ ...RightHandleStyle, top: 112 }}
style={{ ...RightHandleStyle, top: 112 }} ></Handle>
></Handle> <NodeHeader
<NodeHeader id={id}
id={id} name={data.name}
name={data.name} label={data.label}
label={data.label} className={styles.nodeHeader}
className={styles.nodeHeader} ></NodeHeader>
></NodeHeader>
<Flex vertical gap={10}> <Flex vertical gap={10}>
<Flex vertical> <Flex vertical>
<div className={styles.relevantLabel}>Yes</div> <div className={styles.relevantLabel}>Yes</div>
<div className={styles.nodeText}>{yes}</div> <div className={styles.nodeText}>{yes}</div>
</Flex>
<Flex vertical>
<div className={styles.relevantLabel}>No</div>
<div className={styles.nodeText}>{no}</div>
</Flex>
</Flex> </Flex>
</section> <Flex vertical>
</NodePopover> <div className={styles.relevantLabel}>No</div>
<div className={styles.nodeText}>{no}</div>
</Flex>
</Flex>
</section>
); );
} }

View File

@ -9,7 +9,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function RetrievalNode({ export function RetrievalNode({
id, id,
@ -31,55 +30,53 @@ export function RetrievalNode({
}, [knowledgeList, knowledgeBaseIds]); }, [knowledgeList, knowledgeBaseIds]);
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
>
<Handle
id="c"
type="source"
position={Position.Left}
isConnectable={isConnectable}
className={styles.handle}
style={LeftHandleStyle}
></Handle>
<Handle
type="source"
position={Position.Right}
isConnectable={isConnectable}
className={styles.handle}
style={RightHandleStyle}
id="b"
></Handle>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={classNames({
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
})} })}
> ></NodeHeader>
<Handle <Flex vertical gap={8}>
id="c" {knowledgeBases.map((knowledge) => {
type="source" return (
position={Position.Left} <div className={styles.nodeText} key={knowledge.id}>
isConnectable={isConnectable} <Flex align={'center'} gap={6}>
className={styles.handle} <Avatar
style={LeftHandleStyle} size={26}
></Handle> icon={<UserOutlined />}
<Handle src={knowledge.avatar}
type="source" />
position={Position.Right} <Flex className={styles.knowledgeNodeName} flex={1}>
isConnectable={isConnectable} {knowledge.name}
className={styles.handle}
style={RightHandleStyle}
id="b"
></Handle>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={classNames({
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
})}
></NodeHeader>
<Flex vertical gap={8}>
{knowledgeBases.map((knowledge) => {
return (
<div className={styles.nodeText} key={knowledge.id}>
<Flex align={'center'} gap={6}>
<Avatar
size={26}
icon={<UserOutlined />}
src={knowledge.avatar}
/>
<Flex className={styles.knowledgeNodeName} flex={1}>
{knowledge.name}
</Flex>
</Flex> </Flex>
</div> </Flex>
); </div>
})} );
</Flex> })}
</section> </Flex>
</NodePopover> </section>
); );
} }

View File

@ -6,7 +6,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
export function RewriteNode({ export function RewriteNode({
id, id,
@ -15,40 +14,38 @@ export function RewriteNode({
selected, selected,
}: NodeProps<NodeData>) { }: NodeProps<NodeData>) {
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle id="c"
id="c" type="source"
type="source" position={Position.Left}
position={Position.Left} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={LeftHandleStyle}
style={LeftHandleStyle} ></Handle>
></Handle> <Handle
<Handle type="source"
type="source" position={Position.Right}
position={Position.Right} isConnectable={isConnectable}
isConnectable={isConnectable} className={styles.handle}
className={styles.handle} style={RightHandleStyle}
style={RightHandleStyle} id="b"
id="b" ></Handle>
></Handle>
<NodeHeader <NodeHeader
id={id} id={id}
name={data.name} name={data.name}
label={data.label} label={data.label}
className={styles.nodeHeader} className={styles.nodeHeader}
></NodeHeader> ></NodeHeader>
<div className={styles.nodeText}> <div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel> <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div> </div>
</section> </section>
</NodePopover>
); );
} }

View File

@ -7,7 +7,6 @@ import { RightHandleStyle } from './handle-icon';
import { useBuildSwitchHandlePositions } from './hooks'; import { useBuildSwitchHandlePositions } from './hooks';
import styles from './index.less'; import styles from './index.less';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
import NodePopover from './popover';
const getConditionKey = (idx: number, length: number) => { const getConditionKey = (idx: number, length: number) => {
if (idx === 0 && length !== 1) { if (idx === 0 && length !== 1) {
@ -58,55 +57,53 @@ export function SwitchNode({ id, data, selected }: NodeProps<NodeData>) {
const { positions } = useBuildSwitchHandlePositions({ data, id }); const { positions } = useBuildSwitchHandlePositions({ data, id });
return ( return (
<NodePopover nodeId={id}> <section
<section className={classNames(styles.logicNode, {
className={classNames(styles.logicNode, { [styles.selectedNode]: selected,
[styles.selectedNode]: selected, })}
})} >
> <Handle
<Handle type="target"
type="target" position={Position.Left}
position={Position.Left} isConnectable
isConnectable className={styles.handle}
className={styles.handle} id={'a'}
id={'a'} ></Handle>
></Handle> <NodeHeader
<NodeHeader id={id}
id={id} name={data.name}
name={data.name} label={data.label}
label={data.label} className={styles.nodeHeader}
className={styles.nodeHeader} ></NodeHeader>
></NodeHeader> <Flex vertical gap={10}>
<Flex vertical gap={10}> {positions.map((position, idx) => {
{positions.map((position, idx) => { return (
return ( <div key={idx}>
<div key={idx}> <Flex vertical>
<Flex vertical> <Flex justify={'space-between'}>
<Flex justify={'space-between'}> <span>{idx < positions.length - 1 && position.text}</span>
<span>{idx < positions.length - 1 && position.text}</span> <span>{getConditionKey(idx, positions.length)}</span>
<span>{getConditionKey(idx, positions.length)}</span>
</Flex>
{position.condition && (
<ConditionBlock
nodeId={id}
condition={position.condition}
></ConditionBlock>
)}
</Flex> </Flex>
<Handle {position.condition && (
key={position.text} <ConditionBlock
id={position.text} nodeId={id}
type="source" condition={position.condition}
position={Position.Right} ></ConditionBlock>
isConnectable )}
className={styles.handle} </Flex>
style={{ ...RightHandleStyle, top: position.top }} <Handle
></Handle> key={position.text}
</div> id={position.text}
); type="source"
})} position={Position.Right}
</Flex> isConnectable
</section> className={styles.handle}
</NodePopover> style={{ ...RightHandleStyle, top: position.top }}
></Handle>
</div>
);
})}
</Flex>
</section>
); );
} }

View File

@ -524,7 +524,10 @@ export const useReplaceIdWithText = (output: unknown) => {
return getNode(id)?.data.name; return getNode(id)?.data.name;
}; };
return replaceIdWithText(output, getNameById); return {
replacedOutput: replaceIdWithText(output, getNameById),
getNameById,
};
}; };
/** /**