mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-11 22:09:44 +00:00
feat: update style of canvas node (#1829)
This commit is contained in:
@@ -59,6 +59,7 @@ function GPTCard({
|
||||
return icon;
|
||||
}, [icon]);
|
||||
|
||||
// TODO: 算子资源标签
|
||||
const tagNode = useMemo(() => {
|
||||
if (!tags || !tags.length) return null;
|
||||
return (
|
||||
|
@@ -18,7 +18,7 @@ type CanvasNodeProps = {
|
||||
const ICON_PATH_PREFIX = '/icons/node/';
|
||||
|
||||
function TypeLabel({ label }: { label: string }) {
|
||||
return <div className="w-full h-8 bg-stone-100 dark:bg-zinc-700 px-2 flex items-center justify-center">{label}</div>;
|
||||
return <div className="w-full h-8 align-middle font-semibold">{label}</div>;
|
||||
}
|
||||
|
||||
const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
|
||||
@@ -74,20 +74,24 @@ const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
|
||||
function renderOutput(data: IFlowNode) {
|
||||
if (flowType === 'operator' && outputs?.length > 0) {
|
||||
return (
|
||||
<>
|
||||
<div className="bg-zinc-100 rounded p-2">
|
||||
<TypeLabel label="Outputs" />
|
||||
{(outputs || []).map((output, index) => (
|
||||
<NodeHandler key={`${data.id}_input_${index}`} node={data} data={output} type="source" label="outputs" index={index} />
|
||||
))}
|
||||
</>
|
||||
<div className="text-sm flex flex-col space-y-3">
|
||||
{outputs?.map((output, index) => (
|
||||
<NodeHandler key={`${data.id}_input_${index}`} node={data} data={output} type="source" label="outputs" index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (flowType === 'resource') {
|
||||
// resource nodes show output default
|
||||
return (
|
||||
<>
|
||||
<div className="bg-zinc-100 rounded p-2">
|
||||
<TypeLabel label="Outputs" />
|
||||
<NodeHandler key={`${data.id}_input_0`} node={data} data={data} type="source" label="outputs" index={0} />
|
||||
</>
|
||||
<div className="text-sm">
|
||||
<NodeHandler key={`${data.id}_input_0`} node={data} data={data} type="source" label="outputs" index={0} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +109,15 @@ const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
|
||||
<DeleteOutlined className="h-full text-lg cursor-pointer" onClick={deleteNode} />
|
||||
</IconWrapper>
|
||||
<IconWrapper className="mt-2">
|
||||
<Tooltip title={<><p className="font-bold">{node.label}</p><p>{node.description}</p></>} placement="right">
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
<p className="font-bold">{node.label}</p>
|
||||
<p>{node.description}</p>
|
||||
</>
|
||||
}
|
||||
placement="right"
|
||||
>
|
||||
<InfoCircleOutlined className="h-full text-lg cursor-pointer" />
|
||||
</Tooltip>
|
||||
</IconWrapper>
|
||||
@@ -113,36 +125,46 @@ const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={classNames('w-72 h-auto rounded-xl shadow-md p-0 border bg-white dark:bg-zinc-800 cursor-grab', {
|
||||
'border-blue-500': node.selected || isHovered,
|
||||
'border-stone-400 dark:border-white': !node.selected && !isHovered,
|
||||
'border-dashed': flowType !== 'operator',
|
||||
'border-red-600': node.invalid,
|
||||
})}
|
||||
className={classNames(
|
||||
'w-72 h-auto rounded-xl shadow-md px-2 py-4 border bg-white dark:bg-zinc-800 cursor-grab flex flex-col space-y-2 text-sm',
|
||||
{
|
||||
'border-blue-500': node.selected || isHovered,
|
||||
'border-stone-400 dark:border-white': !node.selected && !isHovered,
|
||||
'border-dashed': flowType !== 'operator',
|
||||
'border-red-600': node.invalid,
|
||||
},
|
||||
)}
|
||||
onMouseEnter={onHover}
|
||||
onMouseLeave={onLeave}
|
||||
>
|
||||
{/* icon and label */}
|
||||
<div className="flex flex-row items-center p-2">
|
||||
<div className="flex flex-row items-center">
|
||||
<Image src={'/icons/node/vis.png'} width={24} height={24} alt="" />
|
||||
<p className="ml-2 text-lg font-bold text-ellipsis overflow-hidden whitespace-nowrap">{node.label}</p>
|
||||
</div>
|
||||
{inputs && inputs.length > 0 && (
|
||||
<>
|
||||
|
||||
{inputs?.length > 0 && (
|
||||
<div className="bg-zinc-100 rounded p-2">
|
||||
<TypeLabel label="Inputs" />
|
||||
{(inputs || []).map((input, index) => (
|
||||
<NodeHandler key={`${node.id}_input_${index}`} node={node} data={input} type="target" label="inputs" index={index} />
|
||||
))}
|
||||
</>
|
||||
<div className="text-sm flex flex-col space-y-2">
|
||||
{inputs?.map((input, index) => (
|
||||
<NodeHandler key={`${node.id}_input_${index}`} node={node} data={input} type="target" label="inputs" index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{parameters && parameters.length > 0 && (
|
||||
<>
|
||||
|
||||
{parameters?.length > 0 && (
|
||||
<div className="bg-zinc-100 rounded p-2">
|
||||
<TypeLabel label="Parameters" />
|
||||
{(parameters || []).map((parameter, index) => (
|
||||
<NodeParamHandler key={`${node.id}_param_${index}`} node={node} data={parameter} label="parameters" index={index} />
|
||||
))}
|
||||
</>
|
||||
<div className="text-sm flex flex-col space-y-3 text-neutral-500">
|
||||
{parameters?.map((parameter, index) => (
|
||||
<NodeParamHandler key={`${node.id}_param_${index}`} node={node} data={parameter} label="parameters" index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{renderOutput(node)}
|
||||
</div>
|
||||
</Popover>
|
||||
|
@@ -94,15 +94,15 @@ const NodeHandler: React.FC<NodeHandlerProps> = ({ node, data, type, label, inde
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
className="w-2 h-2"
|
||||
className={classNames('w-2 h-2', type === 'source' ? '-mr-4' : '-ml-4')}
|
||||
type={type}
|
||||
position={type === 'source' ? Position.Right : Position.Left}
|
||||
id={`${node.id}|${label}|${index}`}
|
||||
isValidConnection={(connection) => isValidConnection(connection)}
|
||||
/>
|
||||
<Typography
|
||||
className={classNames('p-2', {
|
||||
'pr-4': label === 'outputs',
|
||||
className={classNames('bg-white w-full px-2 py-1 rounded text-neutral-500',{
|
||||
'text-right': label === 'outputs',
|
||||
})}
|
||||
>
|
||||
<Popconfirm
|
||||
|
@@ -39,7 +39,7 @@ const NodeParamHandler: React.FC<NodeParamHandlerProps> = ({ node, data, label,
|
||||
case 'int':
|
||||
case 'float':
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<div className="text-sm">
|
||||
<p>
|
||||
{data.label}:<RequiredIcon optional={data.optional} />
|
||||
{data.description && (
|
||||
@@ -59,7 +59,7 @@ const NodeParamHandler: React.FC<NodeParamHandlerProps> = ({ node, data, label,
|
||||
);
|
||||
case 'str':
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<div className="text-sm">
|
||||
<p>
|
||||
{data.label}:<RequiredIcon optional={data.optional} />
|
||||
{data.description && (
|
||||
@@ -86,11 +86,11 @@ const NodeParamHandler: React.FC<NodeParamHandlerProps> = ({ node, data, label,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case 'checkbox':
|
||||
case 'bool':
|
||||
defaultValue = defaultValue === 'False' ? false : defaultValue;
|
||||
defaultValue = defaultValue === 'True' ? true : defaultValue;
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<div className="text-sm">
|
||||
<p>
|
||||
{data.label}:<RequiredIcon optional={data.optional} />
|
||||
{data.description && (
|
||||
|
@@ -13,15 +13,13 @@ export const RenderCascader = (params: Props) => {
|
||||
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<Cascader
|
||||
{...attr}
|
||||
options={data.options}
|
||||
defaultValue={defaultValue}
|
||||
placeholder="please select"
|
||||
className="w-full nodrag"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<Cascader
|
||||
{...attr}
|
||||
options={data.options}
|
||||
defaultValue={defaultValue}
|
||||
placeholder="please select"
|
||||
className="w-full nodrag"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@@ -14,7 +14,7 @@ export const RenderCheckbox = (params: Props) => {
|
||||
|
||||
return (
|
||||
data.options?.length > 0 && (
|
||||
<div className="p-2 text-sm">
|
||||
<div className="bg-white p-2 rounded">
|
||||
<Checkbox.Group {...attr} options={data.options} defaultValue={defaultValue} onChange={onChange} />
|
||||
</div>
|
||||
)
|
||||
|
@@ -17,9 +17,5 @@ export const RenderDatePicker = (params: Props) => {
|
||||
onChange(dateString);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<DatePicker {...attr} className="w-full" defaultValue={dayjs(defaultValue)} onChange={onChangeDate} />
|
||||
</div>
|
||||
);
|
||||
return <DatePicker {...attr} className="w-full" defaultValue={dayjs(defaultValue)} onChange={onChangeDate} />;
|
||||
};
|
||||
|
@@ -13,16 +13,14 @@ export const RenderInput = (params: Props) => {
|
||||
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<Input
|
||||
{...attr}
|
||||
className="w-full"
|
||||
placeholder="please input"
|
||||
defaultValue={defaultValue}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
{...attr}
|
||||
className="w-full"
|
||||
placeholder="please input"
|
||||
defaultValue={defaultValue}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@@ -13,7 +13,7 @@ export const RenderRadio = (params: Props) => {
|
||||
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<div className="bg-white p-2 rounded">
|
||||
<Radio.Group
|
||||
{...attr}
|
||||
options={data.options}
|
||||
|
@@ -13,15 +13,6 @@ export const RenderSelect = (params: Props) => {
|
||||
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<Select
|
||||
{...attr}
|
||||
className="w-full nodrag"
|
||||
placeholder="please select"
|
||||
defaultValue={defaultValue}
|
||||
options={data.options}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<Select {...attr} className="w-full nodrag" placeholder="please select" defaultValue={defaultValue} options={data.options} onChange={onChange} />
|
||||
);
|
||||
};
|
||||
|
@@ -1,40 +0,0 @@
|
||||
import { IFlowNodeParameter } from '@/types/flow';
|
||||
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||
import { Col, InputNumber, Row, Slider, Space } from 'antd';
|
||||
import type { InputNumberProps } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
type TextAreaProps = {
|
||||
data: IFlowNodeParameter;
|
||||
defaultValue: any;
|
||||
onChange: (value: any) => void;
|
||||
};
|
||||
|
||||
export const RenderSlider = (params: TextAreaProps) => {
|
||||
const { data, defaultValue, onChange } = params;
|
||||
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||
const [inputValue, setInputValue] = useState(defaultValue);
|
||||
|
||||
const onChangeSlider: InputNumberProps['onChange'] = (newValue) => {
|
||||
setInputValue(newValue as number);
|
||||
onChange(newValue as number);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
{data?.ui?.show_input ? (
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider {...attr} onChange={onChangeSlider} value={typeof inputValue === 'number' ? inputValue : 0} />
|
||||
</Col>
|
||||
|
||||
<Col span={4}>
|
||||
<InputNumber {...attr} style={{ margin: '0 16px' }} value={inputValue} onChange={onChangeSlider} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Slider {...attr} onChange={onChangeSlider} value={typeof inputValue === 'number' ? inputValue : 0} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -21,7 +21,7 @@ export const RenderSlider = (params: TextAreaProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<>
|
||||
{data?.ui?.show_input ? (
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
@@ -34,6 +34,6 @@ export const RenderSlider = (params: TextAreaProps) => {
|
||||
) : (
|
||||
<Slider {...attr} onChange={onChangeSlider} value={typeof inputValue === 'number' ? inputValue : 0} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { IFlowNodeParameter } from '@/types/flow';
|
||||
import { Input } from 'antd';
|
||||
import { convertKeysToCamelCase } from '@/utils/flow';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
@@ -12,11 +13,11 @@ type TextAreaProps = {
|
||||
|
||||
export const RenderTextArea = (params: TextAreaProps) => {
|
||||
const { data, defaultValue, onChange } = params;
|
||||
convertKeysToCamelCase(data?.ui?.attr?.autosize || {});
|
||||
const attr = convertKeysToCamelCase(data.ui?.attr || {});
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<TextArea {...data.ui.attr} defaultValue={defaultValue} onChange={(e) => onChange(e.target.value)} {...data.ui.attr.autosize} rows={4} />
|
||||
<div className={classNames({ 'mb-3': attr.showCount === true })}>
|
||||
<TextArea {...attr} defaultValue={defaultValue} onChange={onChange} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -17,9 +17,5 @@ export const RenderTimePicker = (params: TextAreaProps) => {
|
||||
onChange(timeString);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<TimePicker {...attr} className="w-full" defaultValue={defaultValue} onChange={onChangeTime} />
|
||||
</div>
|
||||
);
|
||||
return <TimePicker {...attr} className="w-full" defaultValue={defaultValue} onChange={onChangeTime} />;
|
||||
};
|
||||
|
@@ -79,18 +79,16 @@ export const RenderTreeSelect = (params: TextAreaProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 text-sm">
|
||||
<TreeSelect
|
||||
fieldNames={{ label: 'label', value: 'value', children: 'children' }}
|
||||
{...data.ui.attr}
|
||||
style={{ width: '100%' }}
|
||||
value={defaultValue}
|
||||
treeDefaultExpandAll
|
||||
onChange={onChange}
|
||||
treeData={data.options}
|
||||
onDropdownVisibleChange={handleDropdownVisibleChange}
|
||||
/>
|
||||
</div>
|
||||
<TreeSelect
|
||||
fieldNames={{ label: 'label', value: 'value', children: 'children' }}
|
||||
{...data.ui.attr}
|
||||
style={{ width: '100%' }}
|
||||
value={defaultValue}
|
||||
treeDefaultExpandAll
|
||||
onChange={onChange}
|
||||
treeData={data.options}
|
||||
onDropdownVisibleChange={handleDropdownVisibleChange}
|
||||
/>
|
||||
|
||||
// TODO: Implement the TreeSelect component
|
||||
// <TreeSelect
|
||||
|
Reference in New Issue
Block a user