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-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^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": {
"version": "1.2.0",
"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-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^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> {
showModal?(): void;
hideModal(): void;
hideModal?(): void;
switchVisible?(visible: boolean): void;
visible?: boolean;
loading?: boolean;
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.',
reference: 'Reference',
input: 'Input',
output: 'Output',
parameter: 'Parameter',
howUseId: 'How to use agent ID?',
content: 'Content',
operationResults: 'Operation Results',
},
footer: {
profile: 'All rights reserved @ React',

View File

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

View File

@ -999,8 +999,11 @@ export default {
cleanHtmlTip: '如果响应是 HTML 格式且只需要主要内容,请将其打开。',
reference: '引用',
input: '输入',
output: '输出',
parameter: '参数',
howUseId: '如何使用Agent ID',
content: '内容',
operationResults: '运行结果',
},
footer: {
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 styles from './index.less';
import NodeHeader from './node-header';
import NodePopover from './popover';
export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
const { positions } = useBuildCategorizeHandlePositions({ data, id });
return (
<NodePopover nodeId={id}>
<section
className={classNames(styles.logicNode, {
[styles.selectedNode]: selected,
<section
className={classNames(styles.logicNode, {
[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>
);
})}
>
<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>
</section>
</NodePopover>
</Flex>
</section>
);
}

View File

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

View File

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

View File

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

View File

@ -6,7 +6,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
import NodePopover from './popover';
export function KeywordNode({
id,
@ -15,40 +14,38 @@ export function KeywordNode({
selected,
}: NodeProps<NodeData>) {
return (
<NodePopover nodeId={id}>
<section
className={classNames(styles.logicNode, {
[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>
<section
className={classNames(styles.logicNode, {
[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={styles.nodeHeader}
></NodeHeader>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={styles.nodeHeader}
></NodeHeader>
<div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div>
</section>
</NodePopover>
<div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div>
</section>
);
}

View File

@ -4,7 +4,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
import NodePopover from './popover';
export function LogicNode({
id,
@ -13,30 +12,28 @@ export function LogicNode({
selected,
}: NodeProps<NodeData>) {
return (
<NodePopover nodeId={id}>
<section
className={classNames(styles.logicNode, {
[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}></NodeHeader>
</section>
</NodePopover>
<section
className={classNames(styles.logicNode, {
[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}></NodeHeader>
</section>
);
}

View File

@ -6,7 +6,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
import NodePopover from './popover';
export function MessageNode({
id,
@ -17,47 +16,45 @@ export function MessageNode({
const messages: string[] = get(data, 'form.messages', []);
return (
<NodePopover nodeId={id}>
<section
className={classNames(styles.logicNode, {
[styles.selectedNode]: selected,
<section
className={classNames(styles.logicNode, {
[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,
})}
>
<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>
></NodeHeader>
<Flex vertical gap={8} className={styles.messageNodeContainer}>
{messages.map((message, idx) => {
return (
<div className={styles.nodeText} key={idx}>
{message}
</div>
);
})}
</Flex>
</section>
</NodePopover>
<Flex vertical gap={8} className={styles.messageNodeContainer}>
{messages.map((message, idx) => {
return (
<div className={styles.nodeText} key={idx}>
{message}
</div>
);
})}
</Flex>
</section>
);
}

View File

@ -1,34 +1,52 @@
import { Flex } from 'antd';
import { Operator, operatorMap } from '../../constant';
import OperatorIcon from '../../operator-icon';
import NodeDropdown from './dropdown';
import { useTranslate } from '@/hooks/common-hooks';
import styles from './index.less';
import { NextNodePopover } from './popover';
interface IProps {
id: string;
label: string;
name: string;
label?: string;
name?: string;
gap?: number;
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) => {
return (
<Flex
flex={1}
align="center"
justify={'space-between'}
gap={gap}
className={className}
>
<OperatorIcon
name={label as Operator}
color={operatorMap[label as Operator].color}
></OperatorIcon>
<span className={styles.nodeTitle}>{name}</span>
<NodeDropdown id={id}></NodeDropdown>
</Flex>
<section className="haha">
{label !== Operator.Answer && <RunStatus id={id} name={name}></RunStatus>}
<Flex
flex={1}
align="center"
justify={'space-between'}
gap={gap}
className={className}
>
<OperatorIcon
name={label as Operator}
color={operatorMap[label as Operator].color}
></OperatorIcon>
<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 { Popover } from 'antd';
import get from 'lodash/get';
import React, { useMemo } from 'react';
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
import JsonView from 'react18-json-view';
import 'react18-json-view/src/style.css';
import { Operator } from '../../constant';
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 {
nodeId: string;
name?: string;
}
const NodePopover = ({ children, nodeId }: IProps) => {
export function NextNodePopover({ children, nodeId, name }: IProps) {
const { t } = useTranslate('flow');
const { data } = useFetchFlow();
const component = useMemo(() => {
return get(data, ['dsl', 'components', nodeId], {});
}, [nodeId, data]);
const inputs: Array<{ component_id: string; content: string }> = get(
component,
['obj', 'params', 'inputs'],
[],
);
const output = get(component, ['obj', 'params', 'output'], {});
const componentName = get(component, ['obj', 'component_name'], '');
const replacedOutput = useReplaceIdWithText(output);
const content =
componentName !== Operator.Answer ? (
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<JsonView
src={replacedOutput}
displaySize={30}
className={styles.jsonView}
/>
</div>
) : undefined;
const { replacedOutput, getNameById } = useReplaceIdWithText(output);
const stopPropagation: MouseEventHandler = useCallback((e) => {
e.stopPropagation();
}, []);
return (
<Popover content={content} placement="right" destroyTooltipOnHide>
{children}
<Popover>
<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>
);
};
export default NodePopover;
}

View File

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

View File

@ -6,7 +6,6 @@ import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
import NodePopover from './popover';
export function RewriteNode({
id,
@ -15,40 +14,38 @@ export function RewriteNode({
selected,
}: NodeProps<NodeData>) {
return (
<NodePopover nodeId={id}>
<section
className={classNames(styles.logicNode, {
[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>
<section
className={classNames(styles.logicNode, {
[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={styles.nodeHeader}
></NodeHeader>
<NodeHeader
id={id}
name={data.name}
label={data.label}
className={styles.nodeHeader}
></NodeHeader>
<div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div>
</section>
</NodePopover>
<div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div>
</section>
);
}

View File

@ -7,7 +7,6 @@ import { RightHandleStyle } from './handle-icon';
import { useBuildSwitchHandlePositions } from './hooks';
import styles from './index.less';
import NodeHeader from './node-header';
import NodePopover from './popover';
const getConditionKey = (idx: number, length: number) => {
if (idx === 0 && length !== 1) {
@ -58,55 +57,53 @@ export function SwitchNode({ id, data, selected }: NodeProps<NodeData>) {
const { positions } = useBuildSwitchHandlePositions({ data, id });
return (
<NodePopover nodeId={id}>
<section
className={classNames(styles.logicNode, {
[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={10}>
{positions.map((position, idx) => {
return (
<div key={idx}>
<Flex vertical>
<Flex justify={'space-between'}>
<span>{idx < positions.length - 1 && position.text}</span>
<span>{getConditionKey(idx, positions.length)}</span>
</Flex>
{position.condition && (
<ConditionBlock
nodeId={id}
condition={position.condition}
></ConditionBlock>
)}
<section
className={classNames(styles.logicNode, {
[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={10}>
{positions.map((position, idx) => {
return (
<div key={idx}>
<Flex vertical>
<Flex justify={'space-between'}>
<span>{idx < positions.length - 1 && position.text}</span>
<span>{getConditionKey(idx, positions.length)}</span>
</Flex>
<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>
{position.condition && (
<ConditionBlock
nodeId={id}
condition={position.condition}
></ConditionBlock>
)}
</Flex>
<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>
);
}

View File

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