mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-19 03:56:42 +08:00
Feat: Use data pipeline to visualize the parsing configuration of the knowledge base (#10423)
### What problem does this PR solve? #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: jinhai <haijin.chn@gmail.com> Signed-off-by: Jin Hai <haijin.chn@gmail.com> Co-authored-by: chanx <1243304602@qq.com> Co-authored-by: balibabu <cike8899@users.noreply.github.com> Co-authored-by: Lynn <lynn_inf@hotmail.com> Co-authored-by: 纷繁下的无奈 <zhileihuang@126.com> Co-authored-by: huangzl <huangzl@shinemo.com> Co-authored-by: writinwaters <93570324+writinwaters@users.noreply.github.com> Co-authored-by: Wilmer <33392318@qq.com> Co-authored-by: Adrian Weidig <adrianweidig@gmx.net> Co-authored-by: Zhichang Yu <yuzhichang@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Yongteng Lei <yongtengrey@outlook.com> Co-authored-by: Liu An <asiro@qq.com> Co-authored-by: buua436 <66937541+buua436@users.noreply.github.com> Co-authored-by: BadwomanCraZY <511528396@qq.com> Co-authored-by: cucusenok <31804608+cucusenok@users.noreply.github.com> Co-authored-by: Russell Valentine <russ@coldstonelabs.org> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Billy Bao <newyorkupperbay@gmail.com> Co-authored-by: Zhedong Cen <cenzhedong2@126.com> Co-authored-by: TensorNull <129579691+TensorNull@users.noreply.github.com> Co-authored-by: TensorNull <tensor.null@gmail.com> Co-authored-by: TeslaZY <TeslaZY@outlook.com> Co-authored-by: Ajay <160579663+aybanda@users.noreply.github.com> Co-authored-by: AB <aj@Ajays-MacBook-Air.local> Co-authored-by: 天海蒼灆 <huangaoqin@tecpie.com> Co-authored-by: He Wang <wanghechn@qq.com> Co-authored-by: Atsushi Hatakeyama <atu729@icloud.com> Co-authored-by: Jin Hai <haijin.chn@gmail.com> Co-authored-by: Mohamed Mathari <155896313+melmathari@users.noreply.github.com> Co-authored-by: Mohamed Mathari <nocodeventure@Mac-mini-van-Mohamed.fritz.box> Co-authored-by: Stephen Hu <stephenhu@seismic.com> Co-authored-by: Shaun Zhang <zhangwfjh@users.noreply.github.com> Co-authored-by: zhimeng123 <60221886+zhimeng123@users.noreply.github.com> Co-authored-by: mxc <mxc@example.com> Co-authored-by: Dominik Novotný <50611433+SgtMarmite@users.noreply.github.com> Co-authored-by: EVGENY M <168018528+rjohny55@users.noreply.github.com> Co-authored-by: mcoder6425 <mcoder64@gmail.com> Co-authored-by: lemsn <lemsn@msn.com> Co-authored-by: lemsn <lemsn@126.com> Co-authored-by: Adrian Gora <47756404+adagora@users.noreply.github.com> Co-authored-by: Womsxd <45663319+Womsxd@users.noreply.github.com> Co-authored-by: FatMii <39074672+FatMii@users.noreply.github.com>
This commit is contained in:
@ -1,116 +0,0 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { IAgentNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { get } from 'lodash';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AgentExceptionMethod, NodeHandleId } from '../../constant';
|
||||
import useGraphStore from '../../store';
|
||||
import { isBottomSubAgent } from '../../utils';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function InnerAgentNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IAgentNode>) {
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isHeadAgent = useMemo(() => {
|
||||
return !isBottomSubAgent(edges, id);
|
||||
}, [edges, id]);
|
||||
|
||||
const exceptionMethod = useMemo(() => {
|
||||
return get(data, 'form.exception_method');
|
||||
}, [data]);
|
||||
|
||||
const isGotoMethod = useMemo(() => {
|
||||
return exceptionMethod === AgentExceptionMethod.Goto;
|
||||
}, [exceptionMethod]);
|
||||
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper selected={selected}>
|
||||
{isHeadAgent && (
|
||||
<>
|
||||
<CommonHandle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
id={NodeHandleId.End}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
id={NodeHandleId.Start}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable={false}
|
||||
id={NodeHandleId.AgentTop}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
isConnectable={false}
|
||||
id={NodeHandleId.AgentBottom}
|
||||
style={{ left: 180 }}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
isConnectable={false}
|
||||
id={NodeHandleId.Tool}
|
||||
style={{ left: 20 }}
|
||||
></Handle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
<section className="flex flex-col gap-2">
|
||||
<div className={'bg-bg-card rounded-sm p-1'}>
|
||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||
</div>
|
||||
{(isGotoMethod ||
|
||||
exceptionMethod === AgentExceptionMethod.Comment) && (
|
||||
<div className="bg-bg-card rounded-sm p-1 flex justify-between gap-2">
|
||||
<span className="text-text-secondary">{t('flow.onFailure')}</span>
|
||||
<span className="truncate flex-1 text-right">
|
||||
{t(`flow.${exceptionMethod}`)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
{isGotoMethod && (
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className="!bg-state-error"
|
||||
style={{ ...RightHandleStyle, top: 94 }}
|
||||
nodeId={id}
|
||||
id={NodeHandleId.AgentException}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
)}
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export const AgentNode = memo(InnerAgentNode);
|
||||
@ -36,7 +36,7 @@ function InnerBeginNode({ data, id, selected }: NodeProps<IBeginNode>) {
|
||||
<section className="flex items-center gap-2">
|
||||
<OperatorIcon name={data.label as Operator}></OperatorIcon>
|
||||
<div className="truncate text-center font-semibold text-sm">
|
||||
{t(`flow.begin`)}
|
||||
{t(`dataflow.begin`)}
|
||||
</div>
|
||||
</section>
|
||||
<section className={cn(styles.generateParameters, 'flex gap-2 flex-col')}>
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { ICategorizeNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { CommonHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
import { useBuildCategorizeHandlePositions } from './use-build-categorize-handle-positions';
|
||||
|
||||
export function InnerCategorizeNode({
|
||||
id,
|
||||
data,
|
||||
selected,
|
||||
}: NodeProps<ICategorizeNode>) {
|
||||
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable
|
||||
id={NodeHandleId.End}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<div className={'bg-bg-card rounded-sm px-1'}>
|
||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
||||
</div>
|
||||
{positions.map((position) => {
|
||||
return (
|
||||
<div key={position.uuid}>
|
||||
<div className={'bg-bg-card rounded-sm p-1 truncate'}>
|
||||
{position.name}
|
||||
</div>
|
||||
<CommonHandle
|
||||
// key={position.text}
|
||||
id={position.uuid}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
style={{ ...RightHandleStyle, top: position.top }}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export const CategorizeNode = memo(InnerCategorizeNode);
|
||||
@ -1,49 +0,0 @@
|
||||
import { IRagNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { needsSingleStepDebugging } from '../../utils';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function ChunkerNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IRagNode>) {
|
||||
return (
|
||||
<ToolBar
|
||||
selected={selected}
|
||||
id={id}
|
||||
label={data.label}
|
||||
showRun={needsSingleStepDebugging(data.label)}
|
||||
>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
id={NodeHandleId.Start}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ChunkerNode);
|
||||
@ -1,9 +1,3 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -18,6 +12,7 @@ import {
|
||||
} from '@/components/ui/tooltip';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { useGetNodeDescription, useGetNodeName } from '@/pages/data-flow/hooks';
|
||||
import useGraphStore from '@/pages/data-flow/store';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { t } from 'i18next';
|
||||
import {
|
||||
@ -26,9 +21,10 @@ import {
|
||||
memo,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Operator } from '../../../constant';
|
||||
import { Operator, SingleOperators } from '../../../constant';
|
||||
import { AgentInstanceContext, HandleContext } from '../../../context';
|
||||
import OperatorIcon from '../../../operator-icon';
|
||||
|
||||
@ -116,6 +112,20 @@ function OperatorItemList({
|
||||
return <ul className="space-y-2">{operators.map(renderOperatorItem)}</ul>;
|
||||
}
|
||||
|
||||
// Limit the number of operators of a certain type on the canvas to only one
|
||||
function useRestrictSingleOperatorOnCanvas() {
|
||||
const list: Operator[] = [];
|
||||
const { findNodeByName } = useGraphStore((state) => state);
|
||||
|
||||
SingleOperators.forEach((operator) => {
|
||||
if (!findNodeByName(operator)) {
|
||||
list.push(operator);
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
function AccordionOperators({
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
@ -123,83 +133,19 @@ function AccordionOperators({
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
}) {
|
||||
const singleOperators = useRestrictSingleOperatorOnCanvas();
|
||||
const operators = useMemo(() => {
|
||||
const list = [...singleOperators];
|
||||
list.push(Operator.Extractor);
|
||||
return list;
|
||||
}, [singleOperators]);
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="px-2 text-text-title max-h-[45vh] overflow-auto"
|
||||
defaultValue={['item-1', 'item-2', 'item-3', 'item-4', 'item-5']}
|
||||
>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.foundation')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[
|
||||
Operator.Agent,
|
||||
Operator.Retrieval,
|
||||
Operator.Parser,
|
||||
Operator.Chunker,
|
||||
Operator.Tokenizer,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.dialog')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Message, Operator.UserFillUp]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.flow')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[
|
||||
Operator.Switch,
|
||||
Operator.Iteration,
|
||||
Operator.Categorize,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-4">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.dataManipulation')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Code, Operator.StringTransform]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-5">
|
||||
<AccordionTrigger className="text-xl">
|
||||
{t('flow.tools')}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.ExeSQL, Operator.Email, Operator.Invoke]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<OperatorItemList
|
||||
operators={operators}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
import { IEmailNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useState } from 'react';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
export function InnerEmailNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IEmailNode>) {
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
return (
|
||||
<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}
|
||||
style={RightHandleStyle}
|
||||
id="b"
|
||||
></Handle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
|
||||
<Flex vertical gap={8} className={styles.emailNodeContainer}>
|
||||
<div
|
||||
className={styles.emailConfig}
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
>
|
||||
<div className={styles.configItem}>
|
||||
<span className={styles.configLabel}>SMTP:</span>
|
||||
<span className={styles.configValue}>{data.form?.smtp_server}</span>
|
||||
</div>
|
||||
<div className={styles.configItem}>
|
||||
<span className={styles.configLabel}>Port:</span>
|
||||
<span className={styles.configValue}>{data.form?.smtp_port}</span>
|
||||
</div>
|
||||
<div className={styles.configItem}>
|
||||
<span className={styles.configLabel}>From:</span>
|
||||
<span className={styles.configValue}>{data.form?.email}</span>
|
||||
</div>
|
||||
<div className={styles.expandIcon}>{showDetails ? '▼' : '▶'}</div>
|
||||
</div>
|
||||
|
||||
{showDetails && (
|
||||
<div className={styles.jsonExample}>
|
||||
<div className={styles.jsonTitle}>Expected Input JSON:</div>
|
||||
<pre className={styles.jsonContent}>
|
||||
{`{
|
||||
"to_email": "...",
|
||||
"cc_email": "...",
|
||||
"subject": "...",
|
||||
"content": "..."
|
||||
}`}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export const EmailNode = memo(InnerEmailNode);
|
||||
1
web/src/pages/data-flow/canvas/node/extractor-node.tsx
Normal file
1
web/src/pages/data-flow/canvas/node/extractor-node.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { RagNode as ExtractorNode } from './index';
|
||||
@ -1,60 +0,0 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IGenerateNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
export function InnerGenerateNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IGenerateNode>) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[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>
|
||||
);
|
||||
}
|
||||
|
||||
export const GenerateNode = memo(InnerGenerateNode);
|
||||
@ -4,8 +4,9 @@ import { Handle, HandleProps } from '@xyflow/react';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { HandleContext } from '../../context';
|
||||
import useGraphStore from '../../store';
|
||||
import { useDropdownManager } from '../context';
|
||||
import { InnerNextStepDropdown } from './dropdown/next-step-dropdown';
|
||||
import { NextStepDropdown } from './dropdown/next-step-dropdown';
|
||||
|
||||
export function CommonHandle({
|
||||
className,
|
||||
@ -17,6 +18,8 @@ export function CommonHandle({
|
||||
const { canShowDropdown, setActiveDropdown, clearActiveDropdown } =
|
||||
useDropdownManager();
|
||||
|
||||
const { hasChildNode } = useGraphStore((state) => state);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
nodeId,
|
||||
@ -39,6 +42,10 @@ export function CommonHandle({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (hasChildNode(nodeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canShowDropdown()) {
|
||||
return;
|
||||
}
|
||||
@ -49,14 +56,14 @@ export function CommonHandle({
|
||||
>
|
||||
<Plus className="size-3 pointer-events-none text-text-title-invert" />
|
||||
{visible && (
|
||||
<InnerNextStepDropdown
|
||||
<NextStepDropdown
|
||||
hideModal={() => {
|
||||
hideModal();
|
||||
clearActiveDropdown();
|
||||
}}
|
||||
>
|
||||
<span></span>
|
||||
</InnerNextStepDropdown>
|
||||
</NextStepDropdown>
|
||||
)}
|
||||
</Handle>
|
||||
</HandleContext.Provider>
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export { RagNode as HierarchicalMergerNode } from './index';
|
||||
@ -1,8 +1,8 @@
|
||||
import { IRagNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { needsSingleStepDebugging } from '../../utils';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { NodeHandleId, SingleOperators } from '../../constant';
|
||||
import useGraphStore from '../../store';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
@ -15,13 +15,17 @@ function InnerRagNode({
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IRagNode>) {
|
||||
const getOperatorTypeFromId = useGraphStore(
|
||||
(state) => state.getOperatorTypeFromId,
|
||||
);
|
||||
|
||||
const showCopy = useMemo(() => {
|
||||
const operatorName = getOperatorTypeFromId(id);
|
||||
return SingleOperators.every((x) => x !== operatorName);
|
||||
}, [getOperatorTypeFromId, id]);
|
||||
|
||||
return (
|
||||
<ToolBar
|
||||
selected={selected}
|
||||
id={id}
|
||||
label={data.label}
|
||||
showRun={needsSingleStepDebugging(data.label)}
|
||||
>
|
||||
<ToolBar selected={selected} id={id} label={data.label} showCopy={showCopy}>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IInvokeNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
function InnerInvokeNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IInvokeNode>) {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useTheme();
|
||||
const url = get(data, 'form.url');
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.ragNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[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>
|
||||
);
|
||||
}
|
||||
|
||||
export const InvokeNode = memo(InnerInvokeNode);
|
||||
@ -1,93 +0,0 @@
|
||||
import {
|
||||
IIterationNode,
|
||||
IIterationStartNode,
|
||||
} from '@/interfaces/database/flow';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { NodeProps, NodeResizeControl, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId, Operator } from '../../constant';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import { CommonHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ResizeIcon, controlStyle } from './resize-icon';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
export function InnerIterationNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IIterationNode>) {
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label} showRun={false}>
|
||||
<section
|
||||
className={cn('h-full bg-transparent rounded-b-md ', {
|
||||
[styles.selectedHeader]: selected,
|
||||
})}
|
||||
>
|
||||
<NodeResizeControl style={controlStyle} minWidth={100} minHeight={50}>
|
||||
<ResizeIcon />
|
||||
</NodeResizeControl>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.Start}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={data.name}
|
||||
label={data.label}
|
||||
wrapperClassName={cn(
|
||||
'bg-background-header-bar p-2 rounded-t-[10px] absolute w-full top-[-44px] left-[-0.3px]',
|
||||
{
|
||||
[styles.selectedHeader]: selected,
|
||||
},
|
||||
)}
|
||||
></NodeHeader>
|
||||
</section>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
function InnerIterationStartNode({
|
||||
isConnectable = true,
|
||||
id,
|
||||
selected,
|
||||
}: NodeProps<IIterationStartNode>) {
|
||||
return (
|
||||
<NodeWrapper className="w-20" selected={selected}>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
isConnectableEnd={false}
|
||||
id={NodeHandleId.Start}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<div>
|
||||
<OperatorIcon name={Operator.Begin}></OperatorIcon>
|
||||
</div>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export const IterationStartNode = memo(InnerIterationStartNode);
|
||||
|
||||
export const IterationNode = memo(InnerIterationNode);
|
||||
@ -1,60 +0,0 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IKeywordNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
export function InnerKeywordNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IKeywordNode>) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[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>
|
||||
);
|
||||
}
|
||||
|
||||
export const KeywordNode = memo(InnerKeywordNode);
|
||||
@ -1,41 +0,0 @@
|
||||
import { ILogicNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
export function InnerLogicNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<ILogicNode>) {
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id="c"
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
style={RightHandleStyle}
|
||||
id="b"
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export const LogicNode = memo(InnerLogicNode);
|
||||
@ -1,65 +0,0 @@
|
||||
import { IMessageNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function InnerMessageNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IMessageNode>) {
|
||||
const messages: string[] = get(data, 'form.messages', []);
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
id={NodeHandleId.End}
|
||||
></CommonHandle>
|
||||
{/* <CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
style={RightHandleStyle}
|
||||
id={NodeHandleId.Start}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle> */}
|
||||
<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>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export const MessageNode = memo(InnerMessageNode);
|
||||
@ -2,12 +2,10 @@ import { IRagNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { needsSingleStepDebugging } from '../../utils';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function ParserNode({
|
||||
id,
|
||||
@ -16,33 +14,26 @@ function ParserNode({
|
||||
selected,
|
||||
}: NodeProps<IRagNode>) {
|
||||
return (
|
||||
<ToolBar
|
||||
selected={selected}
|
||||
id={id}
|
||||
label={data.label}
|
||||
showRun={needsSingleStepDebugging(data.label)}
|
||||
>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
id={NodeHandleId.Start}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
id={NodeHandleId.Start}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IRelevantNode } from '@/interfaces/database/flow';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { useReplaceIdWithName } from '../../hooks';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
function InnerRelevantNode({ id, data, selected }: NodeProps<IRelevantNode>) {
|
||||
const yes = get(data, 'form.yes');
|
||||
const no = get(data, 'form.no');
|
||||
const replaceIdWithName = useReplaceIdWithName();
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[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: 57 + 20 }}
|
||||
></Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
className={styles.handle}
|
||||
id={'no'}
|
||||
style={{ ...RightHandleStyle, top: 115 + 20 }}
|
||||
></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}>{replaceIdWithName(yes)}</div>
|
||||
</Flex>
|
||||
<Flex vertical>
|
||||
<div className={styles.relevantLabel}>No</div>
|
||||
<div className={styles.nodeText}>{replaceIdWithName(no)}</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export const RelevantNode = memo(InnerRelevantNode);
|
||||
@ -1,84 +0,0 @@
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { IRetrievalNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
function InnerRetrievalNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IRetrievalNode>) {
|
||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||
|
||||
const getLabel = useGetVariableLabelByValue(id);
|
||||
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
id={NodeHandleId.Start}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
className={styles.handle}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
<NodeHeader
|
||||
id={id}
|
||||
name={data.name}
|
||||
label={data.label}
|
||||
className={classNames({
|
||||
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
|
||||
})}
|
||||
></NodeHeader>
|
||||
<section className="flex flex-col gap-2">
|
||||
{knowledgeBaseIds.map((id) => {
|
||||
const item = knowledgeList.find((y) => id === y.id);
|
||||
const label = getLabel(id);
|
||||
|
||||
return (
|
||||
<div className={styles.nodeText} key={id}>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<RAGFlowAvatar
|
||||
className="size-6 rounded-lg"
|
||||
avatar={id}
|
||||
name={item?.name || (label as string) || 'CN'}
|
||||
isPerson={true}
|
||||
/>
|
||||
|
||||
<div className={'truncate flex-1'}>{label || item?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export const RetrievalNode = memo(InnerRetrievalNode);
|
||||
@ -1,60 +0,0 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { IRewriteNode } from '@/interfaces/database/flow';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
function InnerRewriteNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IRewriteNode>) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
{
|
||||
[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>
|
||||
);
|
||||
}
|
||||
|
||||
export const RewriteNode = memo(InnerRewriteNode);
|
||||
1
web/src/pages/data-flow/canvas/node/splitter-node.tsx
Normal file
1
web/src/pages/data-flow/canvas/node/splitter-node.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { RagNode as SplitterNode } from './index';
|
||||
@ -1,118 +0,0 @@
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { NodeHandleId, SwitchOperatorOptions } from '../../constant';
|
||||
import { LogicalOperatorIcon } from '../../form/switch-form';
|
||||
import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
|
||||
import { CommonHandle } from './handle';
|
||||
import { RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
import { useBuildSwitchHandlePositions } from './use-build-switch-handle-positions';
|
||||
|
||||
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 = useGetVariableLabelByValue(nodeId);
|
||||
|
||||
const renderOperatorIcon = useCallback((operator?: string) => {
|
||||
const item = SwitchOperatorOptions.find((x) => x.value === operator);
|
||||
if (item) {
|
||||
return (
|
||||
<LogicalOperatorIcon
|
||||
icon={item?.icon}
|
||||
value={item?.value}
|
||||
></LogicalOperatorIcon>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-0 divide-y divide-background-card">
|
||||
{items.map((x, idx) => (
|
||||
<div key={idx}>
|
||||
<section className="flex justify-between gap-2 items-center text-xs p-1">
|
||||
<div className="flex-1 truncate text-accent-primary">
|
||||
{getLabel(x?.cpn_id)}
|
||||
</div>
|
||||
<span>{renderOperatorIcon(x?.operator)}</span>
|
||||
<div className="flex-1 truncate">{x?.value}</div>
|
||||
</section>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
||||
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label} showRun={false}>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable
|
||||
nodeId={id}
|
||||
id={NodeHandleId.End}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
<section className="gap-2.5 flex flex-col">
|
||||
{positions.map((position, idx) => {
|
||||
return (
|
||||
<div key={idx}>
|
||||
<section className="flex flex-col text-xs">
|
||||
<div className="text-right">
|
||||
<span>{getConditionKey(idx, positions.length)}</span>
|
||||
<div className="text-text-secondary">
|
||||
{idx < positions.length - 1 && position.text}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-accent-primary">
|
||||
{idx < positions.length - 1 &&
|
||||
position.condition?.logical_operator?.toUpperCase()}
|
||||
</span>
|
||||
{position.condition && (
|
||||
<ConditionBlock
|
||||
condition={position.condition}
|
||||
nodeId={id}
|
||||
></ConditionBlock>
|
||||
)}
|
||||
</section>
|
||||
<CommonHandle
|
||||
key={position.text}
|
||||
id={position.text}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable
|
||||
style={{ ...RightHandleStyle, top: position.top }}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
|
||||
export const SwitchNode = memo(InnerSwitchNode);
|
||||
@ -1,78 +0,0 @@
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
||||
import { IGenerateParameter } from '../../interface';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
|
||||
import { ITemplateNode } from '@/interfaces/database/flow';
|
||||
import { memo } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
function InnerTemplateNode({
|
||||
id,
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<ITemplateNode>) {
|
||||
const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
|
||||
const getLabel = useGetComponentLabelByValue(id);
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
styles.logicNode,
|
||||
theme === 'dark' ? styles.dark : '',
|
||||
|
||||
{
|
||||
[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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export const TemplateNode = memo(InnerTemplateNode);
|
||||
@ -2,9 +2,8 @@ import { IRagNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { needsSingleStepDebugging } from '../../utils';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import { LeftHandleStyle } from './handle-icon';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
@ -20,7 +19,8 @@ function TokenizerNode({
|
||||
selected={selected}
|
||||
id={id}
|
||||
label={data.label}
|
||||
showRun={needsSingleStepDebugging(data.label)}
|
||||
showRun={false}
|
||||
showCopy={false}
|
||||
>
|
||||
<NodeWrapper selected={selected}>
|
||||
<CommonHandle
|
||||
@ -31,15 +31,6 @@ function TokenizerNode({
|
||||
style={LeftHandleStyle}
|
||||
nodeId={id}
|
||||
></CommonHandle>
|
||||
<CommonHandle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
id={NodeHandleId.Start}
|
||||
style={RightHandleStyle}
|
||||
nodeId={id}
|
||||
isConnectableEnd={false}
|
||||
></CommonHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
import { IAgentForm, IToolNode } from '@/interfaces/database/agent';
|
||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
||||
import { get } from 'lodash';
|
||||
import { MouseEventHandler, memo, useCallback } from 'react';
|
||||
import { NodeHandleId, Operator } from '../../constant';
|
||||
import { ToolCard } from '../../form/agent-form/agent-tools';
|
||||
import { useFindMcpById } from '../../hooks/use-find-mcp-by-id';
|
||||
import OperatorIcon from '../../operator-icon';
|
||||
import useGraphStore from '../../store';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
|
||||
function InnerToolNode({
|
||||
id,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<IToolNode>) {
|
||||
const { edges, getNode } = useGraphStore((state) => state);
|
||||
const upstreamAgentNodeId = edges.find((x) => x.target === id)?.source;
|
||||
const upstreamAgentNode = getNode(upstreamAgentNodeId);
|
||||
const { findMcpById } = useFindMcpById();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(operator: string): MouseEventHandler<HTMLLIElement> =>
|
||||
(e) => {
|
||||
if (operator === Operator.Code) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const tools: IAgentForm['tools'] = get(
|
||||
upstreamAgentNode,
|
||||
'data.form.tools',
|
||||
[],
|
||||
);
|
||||
|
||||
const mcpList: IAgentForm['mcp'] = get(
|
||||
upstreamAgentNode,
|
||||
'data.form.mcp',
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<NodeWrapper selected={selected}>
|
||||
<Handle
|
||||
id={NodeHandleId.End}
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable={isConnectable}
|
||||
></Handle>
|
||||
<ul className="space-y-2">
|
||||
{tools.map((x) => (
|
||||
<ToolCard
|
||||
key={x.component_name}
|
||||
onClick={handleClick(x.component_name)}
|
||||
className="cursor-pointer"
|
||||
data-tool={x.component_name}
|
||||
>
|
||||
<div className="flex gap-1 items-center pointer-events-none">
|
||||
<OperatorIcon name={x.component_name as Operator}></OperatorIcon>
|
||||
{x.component_name}
|
||||
</div>
|
||||
</ToolCard>
|
||||
))}
|
||||
|
||||
{mcpList.map((x) => (
|
||||
<ToolCard
|
||||
key={x.mcp_id}
|
||||
onClick={handleClick(x.mcp_id)}
|
||||
className="cursor-pointer"
|
||||
data-tool={x.mcp_id}
|
||||
>
|
||||
{findMcpById(x.mcp_id)?.name}
|
||||
</ToolCard>
|
||||
))}
|
||||
</ul>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export const ToolNode = memo(InnerToolNode);
|
||||
@ -11,7 +11,6 @@ import {
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { Operator } from '../../constant';
|
||||
import { useDuplicateNode } from '../../hooks';
|
||||
import useGraphStore from '../../store';
|
||||
|
||||
@ -28,6 +27,7 @@ type ToolBarProps = {
|
||||
label: string;
|
||||
id: string;
|
||||
showRun?: boolean;
|
||||
showCopy?: boolean;
|
||||
} & PropsWithChildren;
|
||||
|
||||
export function ToolBar({
|
||||
@ -35,23 +35,17 @@ export function ToolBar({
|
||||
children,
|
||||
label,
|
||||
id,
|
||||
showRun = true,
|
||||
showRun = false,
|
||||
showCopy = true,
|
||||
}: ToolBarProps) {
|
||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||
const deleteIterationNodeById = useGraphStore(
|
||||
(store) => store.deleteIterationNodeById,
|
||||
);
|
||||
|
||||
const deleteNode: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
if (label === Operator.Iteration) {
|
||||
deleteIterationNodeById(id);
|
||||
} else {
|
||||
deleteNodeById(id);
|
||||
}
|
||||
deleteNodeById(id);
|
||||
},
|
||||
[deleteIterationNodeById, deleteNodeById, id, label],
|
||||
[deleteNodeById, id],
|
||||
);
|
||||
|
||||
const duplicateNode = useDuplicateNode();
|
||||
@ -74,10 +68,13 @@ export function ToolBar({
|
||||
<IconWrapper>
|
||||
<Play className="size-3.5" data-play />
|
||||
</IconWrapper>
|
||||
)}{' '}
|
||||
<IconWrapper onClick={handleDuplicate}>
|
||||
<Copy className="size-3.5" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
{showCopy && (
|
||||
<IconWrapper onClick={handleDuplicate}>
|
||||
<Copy className="size-3.5" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
|
||||
<IconWrapper onClick={deleteNode}>
|
||||
<Trash2 className="size-3.5" />
|
||||
</IconWrapper>
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
||||
import { get } from 'lodash';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { z } from 'zod';
|
||||
import { useCreateCategorizeFormSchema } from '../../form/categorize-form/use-form-schema';
|
||||
|
||||
export const useBuildCategorizeHandlePositions = ({
|
||||
data,
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
data: RAGFlowNodeType['data'];
|
||||
}) => {
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
const FormSchema = useCreateCategorizeFormSchema();
|
||||
|
||||
type FormSchemaType = z.infer<typeof FormSchema>;
|
||||
|
||||
const items: Required<FormSchemaType['items']> = useMemo(() => {
|
||||
return get(data, `form.items`, []);
|
||||
}, [data]);
|
||||
|
||||
const positions = useMemo(() => {
|
||||
const list: Array<{
|
||||
top: number;
|
||||
name: string;
|
||||
uuid: string;
|
||||
}> &
|
||||
Required<FormSchemaType['items']> = [];
|
||||
|
||||
items.forEach((x, idx) => {
|
||||
list.push({
|
||||
...x,
|
||||
top: idx === 0 ? 86 : list[idx - 1].top + 8 + 24,
|
||||
});
|
||||
});
|
||||
|
||||
return list;
|
||||
}, [items]);
|
||||
|
||||
useEffect(() => {
|
||||
updateNodeInternals(id);
|
||||
}, [id, updateNodeInternals, items]);
|
||||
|
||||
return { positions };
|
||||
};
|
||||
@ -1,59 +0,0 @@
|
||||
import { ISwitchCondition, RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
||||
import get from 'lodash/get';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { SwitchElseTo } from '../../constant';
|
||||
import { generateSwitchHandleText } from '../../utils';
|
||||
|
||||
export const useBuildSwitchHandlePositions = ({
|
||||
data,
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
data: RAGFlowNodeType['data'];
|
||||
}) => {
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
const conditions: ISwitchCondition[] = useMemo(() => {
|
||||
return get(data, 'form.conditions', []);
|
||||
}, [data]);
|
||||
|
||||
const positions = useMemo(() => {
|
||||
const list: Array<{
|
||||
text: string;
|
||||
top: number;
|
||||
idx: number;
|
||||
condition?: ISwitchCondition;
|
||||
}> = [];
|
||||
|
||||
[...conditions, ''].forEach((x, idx) => {
|
||||
let top = idx === 0 ? 53 : list[idx - 1].top + 10 + 14 + 16 + 16; // case number (Case 1) height + flex gap
|
||||
if (idx >= 1) {
|
||||
const previousItems = conditions[idx - 1]?.items ?? [];
|
||||
if (previousItems.length > 0) {
|
||||
// top += 12; // ConditionBlock padding
|
||||
top += previousItems.length * 26; // 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 };
|
||||
};
|
||||
Reference in New Issue
Block a user