mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-30 00:32:30 +08:00
### What problem does this PR solve? feat: Add hint for operators, round to square, input variable, readable operator ID. #3056 ### Type of change - [ ] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) - [ ] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe):
This commit is contained in:
@ -22,9 +22,15 @@ import styles from './index.less';
|
||||
import { RagNode } from './node';
|
||||
import { BeginNode } from './node/begin-node';
|
||||
import { CategorizeNode } from './node/categorize-node';
|
||||
import { GenerateNode } from './node/generate-node';
|
||||
import { KeywordNode } from './node/keyword-node';
|
||||
import { LogicNode } from './node/logic-node';
|
||||
import { MessageNode } from './node/message-node';
|
||||
import NoteNode from './node/note-node';
|
||||
import { RelevantNode } from './node/relevant-node';
|
||||
import { RetrievalNode } from './node/retrieval-node';
|
||||
import { RewriteNode } from './node/rewrite-node';
|
||||
import { SwitchNode } from './node/switch-node';
|
||||
|
||||
const nodeTypes = {
|
||||
ragNode: RagNode,
|
||||
@ -33,6 +39,12 @@ const nodeTypes = {
|
||||
relevantNode: RelevantNode,
|
||||
logicNode: LogicNode,
|
||||
noteNode: NoteNode,
|
||||
switchNode: SwitchNode,
|
||||
generateNode: GenerateNode,
|
||||
retrievalNode: RetrievalNode,
|
||||
messageNode: MessageNode,
|
||||
rewriteNode: RewriteNode,
|
||||
keywordNode: KeywordNode,
|
||||
};
|
||||
|
||||
const edgeTypes = {
|
||||
|
||||
@ -1,25 +1,24 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import lowerFirst from 'lodash/lowerFirst';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import { Operator, operatorMap } from '../../constant';
|
||||
import { NodeData } from '../../interface';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
|
||||
// TODO: do not allow other nodes to connect to this node
|
||||
export function BeginNode({ id, data, selected }: NodeProps<NodeData>) {
|
||||
const { t } = useTranslate('flow');
|
||||
export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames(styles.ragNode, {
|
||||
[styles.selectedNode]: selected,
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: operatorMap[data.label as Operator].backgroundColor,
|
||||
color: 'white',
|
||||
width: 50,
|
||||
height: 50,
|
||||
width: 100,
|
||||
}}
|
||||
>
|
||||
<Handle
|
||||
@ -27,13 +26,17 @@ export function BeginNode({ id, data, selected }: NodeProps<NodeData>) {
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
></Handle>
|
||||
<Flex vertical align="center" justify="center" gap={6}>
|
||||
<span className={styles.type}>{t(lowerFirst(data.label))}</span>
|
||||
|
||||
<Flex align="center" justify={'space-around'}>
|
||||
<OperatorIcon
|
||||
name={data.label as Operator}
|
||||
fontSize={24}
|
||||
color={operatorMap[data.label as Operator].color}
|
||||
></OperatorIcon>
|
||||
<div className={styles.nodeTitle}>{t(`flow.begin`)}</div>
|
||||
</Flex>
|
||||
<section className={styles.bottomBox}>
|
||||
<div className={styles.nodeName}>{data.name}</div>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,22 +1,17 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import lowerFirst from 'lodash/lowerFirst';
|
||||
import { get } from 'lodash';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import { Operator, SwitchElseTo, operatorMap } from '../../constant';
|
||||
import { NodeData } from '../../interface';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import CategorizeHandle from './categorize-handle';
|
||||
import NodeDropdown from './dropdown';
|
||||
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 style = operatorMap[data.label as Operator];
|
||||
const { t } = useTranslate('flow');
|
||||
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
||||
const operatorName = data.label;
|
||||
|
||||
return (
|
||||
<NodePopover nodeId={id}>
|
||||
@ -24,10 +19,6 @@ export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
|
||||
className={classNames(styles.logicNode, {
|
||||
[styles.selectedNode]: selected,
|
||||
})}
|
||||
style={{
|
||||
backgroundColor: style.backgroundColor,
|
||||
color: style.color,
|
||||
}}
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
@ -36,47 +27,35 @@ export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
|
||||
className={styles.handle}
|
||||
id={'a'}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
id={'b'}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Bottom}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
id={'c'}
|
||||
></Handle>
|
||||
{operatorName === Operator.Switch && (
|
||||
<CategorizeHandle top={50} right={-4} id={SwitchElseTo}>
|
||||
To
|
||||
</CategorizeHandle>
|
||||
)}
|
||||
{positions.map((position, idx) => {
|
||||
return (
|
||||
<CategorizeHandle
|
||||
top={position.top}
|
||||
right={position.right}
|
||||
key={idx}
|
||||
id={position.text}
|
||||
idx={idx}
|
||||
></CategorizeHandle>
|
||||
);
|
||||
})}
|
||||
<Flex vertical align="center" justify="center" gap={6}>
|
||||
<OperatorIcon
|
||||
name={data.label as Operator}
|
||||
fontSize={24}
|
||||
></OperatorIcon>
|
||||
<span className={styles.type}>{t(lowerFirst(data.label))}</span>
|
||||
<NodeDropdown id={id}></NodeDropdown>
|
||||
|
||||
<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 className={styles.bottomBox}>
|
||||
<div className={styles.nodeName}>{data.name}</div>
|
||||
</section>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
|
||||
@ -38,7 +38,7 @@ const NodeDropdown = ({ id, iconFontColor }: IProps) => {
|
||||
|
||||
return (
|
||||
<OperateDropdown
|
||||
iconFontSize={14}
|
||||
iconFontSize={22}
|
||||
height={14}
|
||||
deleteItem={deleteNode}
|
||||
items={items}
|
||||
|
||||
74
web/src/pages/flow/canvas/node/generate-node.tsx
Normal file
74
web/src/pages/flow/canvas/node/generate-node.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import { useGetComponentLabelByValue } from '../../hooks';
|
||||
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,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<NodeData>) {
|
||||
const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
|
||||
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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
20
web/src/pages/flow/canvas/node/handle-icon.tsx
Normal file
20
web/src/pages/flow/canvas/node/handle-icon.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export const HandleIcon = () => {
|
||||
return (
|
||||
<PlusOutlined
|
||||
style={{ fontSize: 6, color: 'white', position: 'absolute', zIndex: 10 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const RightHandleStyle: CSSProperties = {
|
||||
right: -5,
|
||||
};
|
||||
|
||||
export const LeftHandleStyle: CSSProperties = {
|
||||
left: -7,
|
||||
};
|
||||
|
||||
export default HandleIcon;
|
||||
@ -1,14 +1,13 @@
|
||||
import get from 'lodash/get';
|
||||
import pick from 'lodash/pick';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useUpdateNodeInternals } from 'reactflow';
|
||||
import { Operator } from '../../constant';
|
||||
import { IPosition, NodeData } from '../../interface';
|
||||
import { SwitchElseTo } from '../../constant';
|
||||
import {
|
||||
buildNewPositionMap,
|
||||
generateSwitchHandleText,
|
||||
isKeysEqual,
|
||||
} from '../../utils';
|
||||
ICategorizeItemResult,
|
||||
ISwitchCondition,
|
||||
NodeData,
|
||||
} from '../../interface';
|
||||
import { generateSwitchHandleText } from '../../utils';
|
||||
|
||||
export const useBuildCategorizeHandlePositions = ({
|
||||
data,
|
||||
@ -17,85 +16,86 @@ export const useBuildCategorizeHandlePositions = ({
|
||||
id: string;
|
||||
data: NodeData;
|
||||
}) => {
|
||||
const operatorName = data.label as Operator;
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const [positionMap, setPositionMap] = useState<Record<string, IPosition>>({});
|
||||
|
||||
const categoryData = useMemo(() => {
|
||||
if (operatorName === Operator.Categorize) {
|
||||
return get(data, `form.category_description`, {});
|
||||
} else if (operatorName === Operator.Switch) {
|
||||
return get(data, 'form.conditions', []);
|
||||
}
|
||||
return {};
|
||||
}, [data, operatorName]);
|
||||
const categoryData: ICategorizeItemResult = useMemo(() => {
|
||||
return get(data, `form.category_description`, {});
|
||||
}, [data]);
|
||||
|
||||
const positions = useMemo(() => {
|
||||
return Object.keys(categoryData)
|
||||
.map((x, idx) => {
|
||||
const position = positionMap[x];
|
||||
let text = x;
|
||||
if (operatorName === Operator.Switch) {
|
||||
text = generateSwitchHandleText(idx);
|
||||
}
|
||||
return { text, ...position };
|
||||
})
|
||||
.filter((x) => typeof x?.right === 'number');
|
||||
}, [categoryData, positionMap, operatorName]);
|
||||
const list: Array<{
|
||||
text: string;
|
||||
top: number;
|
||||
idx: number;
|
||||
}> = [];
|
||||
|
||||
useEffect(() => {
|
||||
// Cache used coordinates
|
||||
setPositionMap((state) => {
|
||||
const categoryDataKeys = Object.keys(categoryData);
|
||||
const stateKeys = Object.keys(state);
|
||||
if (!isKeysEqual(categoryDataKeys, stateKeys)) {
|
||||
const { newPositionMap, intersectionKeys } = buildNewPositionMap(
|
||||
categoryDataKeys,
|
||||
state,
|
||||
);
|
||||
|
||||
const nextPositionMap = {
|
||||
...pick(state, intersectionKeys),
|
||||
...newPositionMap,
|
||||
};
|
||||
|
||||
return nextPositionMap;
|
||||
}
|
||||
return state;
|
||||
Object.keys(categoryData).forEach((x, idx) => {
|
||||
list.push({
|
||||
text: x,
|
||||
idx,
|
||||
top: idx === 0 ? 98 : list[idx - 1].top + 8 + 26,
|
||||
});
|
||||
});
|
||||
|
||||
return list;
|
||||
}, [categoryData]);
|
||||
|
||||
useEffect(() => {
|
||||
updateNodeInternals(id);
|
||||
}, [id, updateNodeInternals, positionMap]);
|
||||
}, [id, updateNodeInternals, categoryData]);
|
||||
|
||||
return { positions };
|
||||
};
|
||||
|
||||
// export const useBuildSwitchHandlePositions = ({
|
||||
// data,
|
||||
// id,
|
||||
// }: {
|
||||
// id: string;
|
||||
// data: NodeData;
|
||||
// }) => {
|
||||
// const [positionMap, setPositionMap] = useState<Record<string, IPosition>>({});
|
||||
// const conditions = useMemo(() => get(data, 'form.conditions', []), [data]);
|
||||
// const updateNodeInternals = useUpdateNodeInternals();
|
||||
export const useBuildSwitchHandlePositions = ({
|
||||
data,
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
data: NodeData;
|
||||
}) => {
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
// const positions = useMemo(() => {
|
||||
// return conditions
|
||||
// .map((x, idx) => {
|
||||
// const text = `Item ${idx}`;
|
||||
// const position = positionMap[text];
|
||||
// return { text: text, ...position };
|
||||
// })
|
||||
// .filter((x) => typeof x?.right === 'number');
|
||||
// }, [conditions, positionMap]);
|
||||
const conditions: ISwitchCondition[] = useMemo(() => {
|
||||
return get(data, 'form.conditions', []);
|
||||
}, [data]);
|
||||
|
||||
// useEffect(() => {
|
||||
// updateNodeInternals(id);
|
||||
// }, [id, updateNodeInternals, positionMap]);
|
||||
const positions = useMemo(() => {
|
||||
const list: Array<{
|
||||
text: string;
|
||||
top: number;
|
||||
idx: number;
|
||||
condition?: ISwitchCondition;
|
||||
}> = [];
|
||||
|
||||
// return { positions };
|
||||
// };
|
||||
[...conditions, ''].forEach((x, idx) => {
|
||||
let top = idx === 0 ? 58 : list[idx - 1].top + 32; // case number (Case 1) height + flex gap
|
||||
if (idx - 1 >= 0) {
|
||||
const previousItems = conditions[idx - 1]?.items ?? [];
|
||||
if (previousItems.length > 0) {
|
||||
top += 12; // ConditionBlock padding
|
||||
top += previousItems.length * 22; // condition variable height
|
||||
top += (previousItems.length - 1) * 25; // operator height
|
||||
}
|
||||
}
|
||||
|
||||
list.push({
|
||||
text:
|
||||
idx < conditions.length
|
||||
? generateSwitchHandleText(idx)
|
||||
: SwitchElseTo,
|
||||
idx,
|
||||
top,
|
||||
condition: typeof x === 'string' ? undefined : x,
|
||||
});
|
||||
});
|
||||
|
||||
return list;
|
||||
}, [conditions]);
|
||||
|
||||
useEffect(() => {
|
||||
updateNodeInternals(id);
|
||||
}, [id, updateNodeInternals, conditions]);
|
||||
|
||||
return { positions };
|
||||
};
|
||||
|
||||
@ -3,22 +3,16 @@
|
||||
-6px 0 12px 0 rgba(179, 177, 177, 0.08),
|
||||
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
|
||||
-6px 0 16px 6px rgba(0, 0, 0, 0.05);
|
||||
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.ragNode {
|
||||
position: relative;
|
||||
.commonNode();
|
||||
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background: white;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
// justify-self: center;
|
||||
justify-content: center;
|
||||
.nodeName {
|
||||
font-size: 10px;
|
||||
color: black;
|
||||
@ -28,23 +22,10 @@
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
}
|
||||
.type {
|
||||
// font-size: 12px;
|
||||
}
|
||||
.description {
|
||||
font-size: 10px;
|
||||
}
|
||||
.bottomBox {
|
||||
position: absolute;
|
||||
bottom: -34px;
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
box-shadow:
|
||||
-6px 0 12px 0 rgba(179, 177, 177, 0.08),
|
||||
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
|
||||
-6px 0 16px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.categorizeAnchorPointText {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
@ -53,14 +34,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
@lightBackgroundColor: rgba(150, 150, 150, 0.1);
|
||||
@darkBackgroundColor: rgba(150, 150, 150, 0.2);
|
||||
|
||||
.selectedNode {
|
||||
border: 1px solid rgb(59, 118, 244);
|
||||
border: 1.5px solid rgb(59, 118, 244);
|
||||
}
|
||||
|
||||
.handle {
|
||||
display: inline-flex;
|
||||
text-align: center;
|
||||
// align-items: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: rgb(59, 88, 253);
|
||||
border: 1px solid white;
|
||||
z-index: 1;
|
||||
background-image: url('@/assets/svg/plus.svg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.jsonView {
|
||||
@ -71,19 +63,8 @@
|
||||
}
|
||||
|
||||
.logicNode {
|
||||
position: relative;
|
||||
.commonNode();
|
||||
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background: white;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
// justify-self: center;
|
||||
justify-content: center;
|
||||
.nodeName {
|
||||
font-size: 10px;
|
||||
color: black;
|
||||
@ -93,41 +74,122 @@
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
}
|
||||
.type {
|
||||
// font-size: 12px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 10px;
|
||||
}
|
||||
.bottomBox {
|
||||
position: absolute;
|
||||
bottom: -34px;
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 5px;
|
||||
box-shadow:
|
||||
-6px 0 12px 0 rgba(179, 177, 177, 0.08),
|
||||
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
|
||||
-6px 0 16px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.categorizeAnchorPointText {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.relevantSourceLabel {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.noteNode {
|
||||
.commonNode();
|
||||
width: 140px;
|
||||
padding: 4px 6px 6px;
|
||||
min-width: 140px;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border-radius: 10px;
|
||||
background-color: #dbf8f4;
|
||||
min-height: 128px;
|
||||
.noteTitle {
|
||||
background-color: #edfcff;
|
||||
font-size: 12px;
|
||||
padding: 6px 6px 4px;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
.noteForm {
|
||||
margin-top: 4px;
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
.noteName {
|
||||
padding: 0px 4px;
|
||||
}
|
||||
.noteTextarea {
|
||||
resize: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
height: 100%;
|
||||
&:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nodeText {
|
||||
padding-inline: 0.4em;
|
||||
padding-block: 0.2em 0.1em;
|
||||
background: @lightBackgroundColor;
|
||||
border-radius: 3px;
|
||||
min-height: 22px;
|
||||
.textEllipsis();
|
||||
}
|
||||
|
||||
.nodeTitle {
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
.textEllipsis();
|
||||
}
|
||||
|
||||
.nodeHeader {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.zeroDivider {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.conditionBlock {
|
||||
border-radius: 4px;
|
||||
padding: 6px;
|
||||
background: @lightBackgroundColor;
|
||||
}
|
||||
|
||||
.conditionLine {
|
||||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
background: @darkBackgroundColor;
|
||||
.textEllipsis();
|
||||
}
|
||||
|
||||
.conditionKey {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.conditionOperator {
|
||||
padding: 0 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.relevantLabel {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.knowledgeNodeName {
|
||||
.textEllipsis();
|
||||
}
|
||||
|
||||
.messageNodeContainer {
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.generateParameters {
|
||||
padding-top: 8px;
|
||||
label {
|
||||
flex: 2;
|
||||
.textEllipsis();
|
||||
}
|
||||
.parameterValue {
|
||||
flex: 3;
|
||||
.conditionLine;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import pick from 'lodash/pick';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import { Operator, operatorMap } from '../../constant';
|
||||
import { NodeData } from '../../interface';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import NodeDropdown from './dropdown';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import NodePopover from './popover';
|
||||
|
||||
export function RagNode({
|
||||
@ -15,17 +12,12 @@ export function RagNode({
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<NodeData>) {
|
||||
const style = operatorMap[data.label as Operator];
|
||||
|
||||
return (
|
||||
<NodePopover nodeId={id}>
|
||||
<section
|
||||
className={classNames(styles.ragNode, {
|
||||
[styles.selectedNode]: selected,
|
||||
})}
|
||||
style={{
|
||||
...pick(style, ['backgroundColor', 'color']),
|
||||
}}
|
||||
>
|
||||
<Handle
|
||||
id="c"
|
||||
@ -33,39 +25,17 @@ export function RagNode({
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></Handle>
|
||||
<Handle type="source" position={Position.Top} id="d" isConnectable />
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
id="b"
|
||||
style={RightHandleStyle}
|
||||
></Handle>
|
||||
<Handle type="source" position={Position.Bottom} id="a" isConnectable />
|
||||
<Flex vertical align="center" justify={'space-around'}>
|
||||
<Flex flex={1} justify="center" align="center">
|
||||
<label htmlFor=""> </label>
|
||||
</Flex>
|
||||
|
||||
<Flex flex={1}>
|
||||
<OperatorIcon
|
||||
name={data.label as Operator}
|
||||
fontSize={style?.iconFontSize ?? 16}
|
||||
width={style?.iconWidth}
|
||||
></OperatorIcon>
|
||||
</Flex>
|
||||
<Flex flex={1}>
|
||||
<NodeDropdown
|
||||
id={id}
|
||||
iconFontColor={style?.moreIconColor}
|
||||
></NodeDropdown>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<section className={styles.bottomBox}>
|
||||
<div className={styles.nodeName}>{data.name}</div>
|
||||
</section>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
|
||||
54
web/src/pages/flow/canvas/node/keyword-node.tsx
Normal file
54
web/src/pages/flow/canvas/node/keyword-node.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
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,
|
||||
data,
|
||||
isConnectable = true,
|
||||
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}
|
||||
className={styles.nodeHeader}
|
||||
></NodeHeader>
|
||||
|
||||
<div className={styles.nodeText}>
|
||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||
</div>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
}
|
||||
@ -1,38 +1,23 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import lowerFirst from 'lodash/lowerFirst';
|
||||
import pick from 'lodash/pick';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import { Operator, operatorMap } from '../../constant';
|
||||
import { NodeData } from '../../interface';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import NodeDropdown from './dropdown';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import NodePopover from './popover';
|
||||
|
||||
const ZeroGapOperators = [
|
||||
Operator.RewriteQuestion,
|
||||
Operator.KeywordExtract,
|
||||
Operator.ArXiv,
|
||||
];
|
||||
|
||||
export function LogicNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<NodeData>) {
|
||||
const style = operatorMap[data.label as Operator];
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
return (
|
||||
<NodePopover nodeId={id}>
|
||||
<section
|
||||
className={classNames(styles.logicNode, {
|
||||
[styles.selectedNode]: selected,
|
||||
})}
|
||||
style={pick(style, ['backgroundColor', 'width', 'height', 'color'])}
|
||||
>
|
||||
<Handle
|
||||
id="c"
|
||||
@ -40,49 +25,17 @@ export function LogicNode({
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
></Handle>
|
||||
<Handle type="source" position={Position.Top} id="d" isConnectable />
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
id="b"
|
||||
></Handle>
|
||||
<Handle type="source" position={Position.Bottom} id="a" isConnectable />
|
||||
<Flex
|
||||
vertical
|
||||
align="center"
|
||||
justify={'space-around'}
|
||||
gap={ZeroGapOperators.some((x) => x === data.label) ? 0 : 6}
|
||||
>
|
||||
<Flex flex={1} justify="center" align="center">
|
||||
<OperatorIcon
|
||||
name={data.label as Operator}
|
||||
fontSize={style?.iconFontSize ?? 24}
|
||||
width={style?.iconWidth}
|
||||
></OperatorIcon>
|
||||
</Flex>
|
||||
|
||||
<Flex flex={1}>
|
||||
<span
|
||||
className={styles.type}
|
||||
style={{ fontSize: style?.fontSize ?? 14 }}
|
||||
>
|
||||
{t(lowerFirst(data.label))}
|
||||
</span>
|
||||
</Flex>
|
||||
<Flex flex={1}>
|
||||
<NodeDropdown
|
||||
id={id}
|
||||
iconFontColor={style?.moreIconColor}
|
||||
></NodeDropdown>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<section className={styles.bottomBox}>
|
||||
<div className={styles.nodeName}>{data.name}</div>
|
||||
</section>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
|
||||
63
web/src/pages/flow/canvas/node/message-node.tsx
Normal file
63
web/src/pages/flow/canvas/node/message-node.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
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,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<NodeData>) {
|
||||
const messages: string[] = get(data, 'form.messages', []);
|
||||
|
||||
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}
|
||||
className={classNames({
|
||||
[styles.nodeHeader]: messages.length > 0,
|
||||
})}
|
||||
></NodeHeader>
|
||||
|
||||
<Flex vertical gap={8} className={styles.messageNodeContainer}>
|
||||
{messages.map((message, idx) => {
|
||||
return (
|
||||
<div className={styles.nodeText} key={idx}>
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
}
|
||||
35
web/src/pages/flow/canvas/node/node-header.tsx
Normal file
35
web/src/pages/flow/canvas/node/node-header.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Flex } from 'antd';
|
||||
|
||||
import { Operator, operatorMap } from '../../constant';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import NodeDropdown from './dropdown';
|
||||
import styles from './index.less';
|
||||
|
||||
interface IProps {
|
||||
id: string;
|
||||
label: string;
|
||||
name: string;
|
||||
gap?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeHeader;
|
||||
@ -1,20 +1,33 @@
|
||||
import { Flex, Form, Input, Space } from 'antd';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Flex, Form, Input } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { NodeProps, NodeResizeControl } from 'reactflow';
|
||||
import { NodeData } from '../../interface';
|
||||
import NodeDropdown from './dropdown';
|
||||
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useEffect } from 'react';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHandleFormValuesChange } from '../../hooks';
|
||||
import {
|
||||
useHandleFormValuesChange,
|
||||
useHandleNodeNameChange,
|
||||
} from '../../hooks';
|
||||
import styles from './index.less';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
const controlStyle = {
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
};
|
||||
|
||||
function NoteNode({ data, id }: NodeProps<NodeData>) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
|
||||
id,
|
||||
data,
|
||||
});
|
||||
const { handleValuesChange } = useHandleFormValuesChange(id);
|
||||
|
||||
useEffect(() => {
|
||||
@ -22,25 +35,51 @@ function NoteNode({ data, id }: NodeProps<NodeData>) {
|
||||
}, [form, data?.form]);
|
||||
|
||||
return (
|
||||
<section className={styles.noteNode}>
|
||||
<Flex justify={'space-between'}>
|
||||
<Space size={'small'}>
|
||||
<>
|
||||
<NodeResizeControl style={controlStyle} minWidth={190} minHeight={128}>
|
||||
<SvgIcon
|
||||
name="resize"
|
||||
width={12}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 5,
|
||||
bottom: 5,
|
||||
cursor: 'nwse-resize',
|
||||
}}
|
||||
></SvgIcon>
|
||||
</NodeResizeControl>
|
||||
<section className={styles.noteNode}>
|
||||
<Flex
|
||||
justify={'space-between'}
|
||||
className={classNames(styles.noteTitle, 'note-drag-handle')}
|
||||
align="center"
|
||||
gap={6}
|
||||
>
|
||||
<SvgIcon name="note" width={14}></SvgIcon>
|
||||
<span className={styles.noteTitle}>{t('flow.note')}</span>
|
||||
</Space>
|
||||
<NodeDropdown id={id}></NodeDropdown>
|
||||
</Flex>
|
||||
<Form
|
||||
onValuesChange={handleValuesChange}
|
||||
form={form}
|
||||
className={styles.noteForm}
|
||||
>
|
||||
<Form.Item name="text" noStyle>
|
||||
<TextArea rows={3} placeholder={t('flow.notePlaceholder')} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</section>
|
||||
<Input
|
||||
value={name ?? t('flow.note')}
|
||||
onBlur={handleNameBlur}
|
||||
onChange={handleNameChange}
|
||||
className={styles.noteName}
|
||||
></Input>
|
||||
<NodeDropdown id={id}></NodeDropdown>
|
||||
</Flex>
|
||||
<Form
|
||||
onValuesChange={handleValuesChange}
|
||||
form={form}
|
||||
className={styles.noteForm}
|
||||
>
|
||||
<Form.Item name="text" noStyle>
|
||||
<TextArea
|
||||
rows={3}
|
||||
placeholder={t('flow.notePlaceholder')}
|
||||
className={styles.noteTextarea}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoteNode;
|
||||
export default memo(NoteNode);
|
||||
|
||||
@ -1,28 +1,23 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import lowerFirst from 'lodash/lowerFirst';
|
||||
import pick from 'lodash/pick';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import { Operator, operatorMap } from '../../constant';
|
||||
import { NodeData } from '../../interface';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import NodeDropdown from './dropdown';
|
||||
|
||||
import CategorizeHandle from './categorize-handle';
|
||||
import styles from './index.less';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import NodePopover from './popover';
|
||||
|
||||
import { get } from 'lodash';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
|
||||
const style = operatorMap[data.label as Operator];
|
||||
const { t } = useTranslate('flow');
|
||||
const yes = get(data, 'form.yes');
|
||||
const no = get(data, 'form.no');
|
||||
return (
|
||||
<NodePopover nodeId={id}>
|
||||
<section
|
||||
className={classNames(styles.logicNode, {
|
||||
[styles.selectedNode]: selected,
|
||||
})}
|
||||
style={pick(style, ['backgroundColor', 'width', 'height', 'color'])}
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
@ -32,43 +27,38 @@ export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
|
||||
id={'a'}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
id={'b'}
|
||||
id={'yes'}
|
||||
style={{ ...RightHandleStyle, top: 59 }}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Bottom}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
id={'c'}
|
||||
id={'no'}
|
||||
style={{ ...RightHandleStyle, top: 112 }}
|
||||
></Handle>
|
||||
<CategorizeHandle top={20} right={6} id={'yes'}></CategorizeHandle>
|
||||
<CategorizeHandle top={80} right={6} id={'no'}></CategorizeHandle>
|
||||
<Flex vertical align="center" justify="center" gap={0}>
|
||||
<Flex flex={1}>
|
||||
<OperatorIcon
|
||||
name={data.label as Operator}
|
||||
fontSize={style.iconFontSize}
|
||||
></OperatorIcon>
|
||||
<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 flex={1}>
|
||||
<span
|
||||
className={styles.type}
|
||||
style={{ fontSize: style.fontSize ?? 14 }}
|
||||
>
|
||||
{t(lowerFirst(data.label))}
|
||||
</span>
|
||||
</Flex>
|
||||
<Flex flex={1}>
|
||||
<NodeDropdown id={id}></NodeDropdown>
|
||||
<Flex vertical>
|
||||
<div className={styles.relevantLabel}>No</div>
|
||||
<div className={styles.nodeText}>{no}</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<section className={styles.bottomBox}>
|
||||
<div className={styles.nodeName}>{data.name}</div>
|
||||
</section>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
|
||||
85
web/src/pages/flow/canvas/node/retrieval-node.tsx
Normal file
85
web/src/pages/flow/canvas/node/retrieval-node.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar, Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
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,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<NodeData>) {
|
||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
||||
const { list: knowledgeList } = useNextFetchKnowledgeList(true);
|
||||
const knowledgeBases = useMemo(() => {
|
||||
return knowledgeBaseIds.map((x) => {
|
||||
const item = knowledgeList.find((y) => x === y.id);
|
||||
return {
|
||||
name: item?.name,
|
||||
avatar: item?.avatar,
|
||||
id: x,
|
||||
};
|
||||
});
|
||||
}, [knowledgeList, knowledgeBaseIds]);
|
||||
|
||||
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}
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
}
|
||||
54
web/src/pages/flow/canvas/node/rewrite-node.tsx
Normal file
54
web/src/pages/flow/canvas/node/rewrite-node.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
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,
|
||||
data,
|
||||
isConnectable = true,
|
||||
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}
|
||||
className={styles.nodeHeader}
|
||||
></NodeHeader>
|
||||
|
||||
<div className={styles.nodeText}>
|
||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||
</div>
|
||||
</section>
|
||||
</NodePopover>
|
||||
);
|
||||
}
|
||||
112
web/src/pages/flow/canvas/node/switch-node.tsx
Normal file
112
web/src/pages/flow/canvas/node/switch-node.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { Divider, Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
import { useGetComponentLabelByValue } from '../../hooks';
|
||||
import { ISwitchCondition, NodeData } from '../../interface';
|
||||
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) {
|
||||
return 'If';
|
||||
} else if (idx === length - 1) {
|
||||
return 'Else';
|
||||
}
|
||||
|
||||
return 'ElseIf';
|
||||
};
|
||||
|
||||
const ConditionBlock = ({
|
||||
condition,
|
||||
nodeId,
|
||||
}: {
|
||||
condition: ISwitchCondition;
|
||||
nodeId: string;
|
||||
}) => {
|
||||
const items = condition?.items ?? [];
|
||||
const getLabel = useGetComponentLabelByValue(nodeId);
|
||||
return (
|
||||
<Flex vertical className={styles.conditionBlock}>
|
||||
{items.map((x, idx) => (
|
||||
<div key={idx}>
|
||||
<Flex>
|
||||
<div
|
||||
className={classNames(styles.conditionLine, styles.conditionKey)}
|
||||
>
|
||||
{getLabel(x?.cpn_id)}
|
||||
</div>
|
||||
<span className={styles.conditionOperator}>{x?.operator}</span>
|
||||
<Flex flex={1} className={styles.conditionLine}>
|
||||
{x?.value}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{idx + 1 < items.length && (
|
||||
<Divider orientationMargin="0" className={styles.zeroDivider}>
|
||||
{condition?.logical_operator}
|
||||
</Divider>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user