mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-20 04:39:00 +08:00
### What problem does this PR solve? Feat: Translate the splitter operator field #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1663,6 +1663,11 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
fileFormats: 'File formats',
|
fileFormats: 'File formats',
|
||||||
fields: 'Fields',
|
fields: 'Fields',
|
||||||
addParser: 'Add Parser',
|
addParser: 'Add Parser',
|
||||||
|
hierarchy: 'Hierarchy',
|
||||||
|
regularExpressions: 'Regular Expressions',
|
||||||
|
overlappedPercent: 'Overlapped percent',
|
||||||
|
searchMethod: 'Search method',
|
||||||
|
filenameEmbdWeight: 'Filename embd weight',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1573,6 +1573,11 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
fileFormats: '文件格式',
|
fileFormats: '文件格式',
|
||||||
fields: '字段',
|
fields: '字段',
|
||||||
addParser: '增加解析器',
|
addParser: '增加解析器',
|
||||||
|
hierarchy: '层次结构',
|
||||||
|
regularExpressions: '正则表达式',
|
||||||
|
overlappedPercent: '重叠百分比',
|
||||||
|
searchMethod: '搜索方法',
|
||||||
|
filenameEmbdWeight: '文件名嵌入权重',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -38,51 +38,19 @@ import RunSheet from '../run-sheet';
|
|||||||
import { ButtonEdge } from './edge';
|
import { ButtonEdge } from './edge';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { RagNode } from './node';
|
import { RagNode } from './node';
|
||||||
import { AgentNode } from './node/agent-node';
|
|
||||||
import { BeginNode } from './node/begin-node';
|
import { BeginNode } from './node/begin-node';
|
||||||
import { CategorizeNode } from './node/categorize-node';
|
|
||||||
import ChunkerNode from './node/chunker-node';
|
|
||||||
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
|
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
|
||||||
import { GenerateNode } from './node/generate-node';
|
|
||||||
import { HierarchicalMergerNode } from './node/hierarchical-merger-node';
|
import { HierarchicalMergerNode } from './node/hierarchical-merger-node';
|
||||||
import { InvokeNode } from './node/invoke-node';
|
|
||||||
import { IterationNode, IterationStartNode } from './node/iteration-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 NoteNode from './node/note-node';
|
||||||
import ParserNode from './node/parser-node';
|
import ParserNode from './node/parser-node';
|
||||||
import { RelevantNode } from './node/relevant-node';
|
|
||||||
import { RetrievalNode } from './node/retrieval-node';
|
|
||||||
import { RewriteNode } from './node/rewrite-node';
|
|
||||||
import { SplitterNode } from './node/splitter-node';
|
import { SplitterNode } from './node/splitter-node';
|
||||||
import { SwitchNode } from './node/switch-node';
|
|
||||||
import { TemplateNode } from './node/template-node';
|
|
||||||
import TokenizerNode from './node/tokenizer-node';
|
import TokenizerNode from './node/tokenizer-node';
|
||||||
import { ToolNode } from './node/tool-node';
|
|
||||||
|
|
||||||
export const nodeTypes: NodeTypes = {
|
export const nodeTypes: NodeTypes = {
|
||||||
ragNode: RagNode,
|
ragNode: RagNode,
|
||||||
categorizeNode: CategorizeNode,
|
|
||||||
beginNode: BeginNode,
|
beginNode: BeginNode,
|
||||||
relevantNode: RelevantNode,
|
|
||||||
logicNode: LogicNode,
|
|
||||||
noteNode: NoteNode,
|
noteNode: NoteNode,
|
||||||
switchNode: SwitchNode,
|
|
||||||
generateNode: GenerateNode,
|
|
||||||
retrievalNode: RetrievalNode,
|
|
||||||
messageNode: MessageNode,
|
|
||||||
rewriteNode: RewriteNode,
|
|
||||||
keywordNode: KeywordNode,
|
|
||||||
invokeNode: InvokeNode,
|
|
||||||
templateNode: TemplateNode,
|
|
||||||
// emailNode: EmailNode,
|
|
||||||
group: IterationNode,
|
|
||||||
iterationStartNode: IterationStartNode,
|
|
||||||
agentNode: AgentNode,
|
|
||||||
toolNode: ToolNode,
|
|
||||||
parserNode: ParserNode,
|
parserNode: ParserNode,
|
||||||
chunkerNode: ChunkerNode,
|
|
||||||
tokenizerNode: TokenizerNode,
|
tokenizerNode: TokenizerNode,
|
||||||
splitterNode: SplitterNode,
|
splitterNode: SplitterNode,
|
||||||
hierarchicalMergerNode: HierarchicalMergerNode,
|
hierarchicalMergerNode: HierarchicalMergerNode,
|
||||||
|
|||||||
@ -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);
|
|
||||||
@ -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,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,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);
|
|
||||||
@ -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,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);
|
|
||||||
@ -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,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);
|
|
||||||
@ -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,
|
PropsWithChildren,
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Operator } from '../../constant';
|
|
||||||
import { useDuplicateNode } from '../../hooks';
|
import { useDuplicateNode } from '../../hooks';
|
||||||
import useGraphStore from '../../store';
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
@ -38,20 +37,13 @@ export function ToolBar({
|
|||||||
showRun = true,
|
showRun = true,
|
||||||
}: ToolBarProps) {
|
}: ToolBarProps) {
|
||||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||||
const deleteIterationNodeById = useGraphStore(
|
|
||||||
(store) => store.deleteIterationNodeById,
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteNode: MouseEventHandler<HTMLDivElement> = useCallback(
|
const deleteNode: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (label === Operator.Iteration) {
|
deleteNodeById(id);
|
||||||
deleteIterationNodeById(id);
|
|
||||||
} else {
|
|
||||||
deleteNodeById(id);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[deleteIterationNodeById, deleteNodeById, id, label],
|
[deleteNodeById, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const duplicateNode = useDuplicateNode();
|
const duplicateNode = useDuplicateNode();
|
||||||
|
|||||||
@ -42,29 +42,8 @@ export const BeginId = 'begin';
|
|||||||
|
|
||||||
export enum Operator {
|
export enum Operator {
|
||||||
Begin = 'Begin',
|
Begin = 'Begin',
|
||||||
Retrieval = 'Retrieval',
|
|
||||||
Categorize = 'Categorize',
|
|
||||||
Message = 'Message',
|
|
||||||
Relevant = 'Relevant',
|
|
||||||
RewriteQuestion = 'RewriteQuestion',
|
|
||||||
KeywordExtract = 'KeywordExtract',
|
|
||||||
ExeSQL = 'ExeSQL',
|
|
||||||
Switch = 'Switch',
|
|
||||||
Concentrator = 'Concentrator',
|
|
||||||
Note = 'Note',
|
Note = 'Note',
|
||||||
Crawler = 'Crawler',
|
|
||||||
Invoke = 'Invoke',
|
|
||||||
Email = 'Email',
|
|
||||||
Iteration = 'Iteration',
|
|
||||||
IterationStart = 'IterationItem',
|
|
||||||
Code = 'CodeExec',
|
|
||||||
WaitingDialogue = 'WaitingDialogue',
|
|
||||||
Agent = 'Agent',
|
|
||||||
Tool = 'Tool',
|
|
||||||
UserFillUp = 'UserFillUp',
|
|
||||||
StringTransform = 'StringTransform',
|
|
||||||
Parser = 'Parser',
|
Parser = 'Parser',
|
||||||
Chunker = 'Chunker',
|
|
||||||
Tokenizer = 'Tokenizer',
|
Tokenizer = 'Tokenizer',
|
||||||
Splitter = 'Splitter',
|
Splitter = 'Splitter',
|
||||||
HierarchicalMerger = 'HierarchicalMerger',
|
HierarchicalMerger = 'HierarchicalMerger',
|
||||||
@ -404,7 +383,7 @@ export const CategorizeAnchorPointPositions = [
|
|||||||
// key is the source of the edge, value is the target of the edge
|
// key is the source of the edge, value is the target of the edge
|
||||||
// no connection lines are allowed between key and value
|
// no connection lines are allowed between key and value
|
||||||
export const RestrictedUpstreamMap = {
|
export const RestrictedUpstreamMap = {
|
||||||
[Operator.Begin]: [Operator.Relevant],
|
[Operator.Begin]: [],
|
||||||
[Operator.Parser]: [Operator.Begin],
|
[Operator.Parser]: [Operator.Begin],
|
||||||
[Operator.Splitter]: [Operator.Begin],
|
[Operator.Splitter]: [Operator.Begin],
|
||||||
[Operator.HierarchicalMerger]: [Operator.Begin],
|
[Operator.HierarchicalMerger]: [Operator.Begin],
|
||||||
@ -413,29 +392,8 @@ export const RestrictedUpstreamMap = {
|
|||||||
|
|
||||||
export const NodeMap = {
|
export const NodeMap = {
|
||||||
[Operator.Begin]: 'beginNode',
|
[Operator.Begin]: 'beginNode',
|
||||||
[Operator.Categorize]: 'categorizeNode',
|
|
||||||
[Operator.Retrieval]: 'retrievalNode',
|
|
||||||
[Operator.Message]: 'messageNode',
|
|
||||||
[Operator.Relevant]: 'relevantNode',
|
|
||||||
[Operator.RewriteQuestion]: 'rewriteNode',
|
|
||||||
[Operator.KeywordExtract]: 'keywordNode',
|
|
||||||
[Operator.ExeSQL]: 'ragNode',
|
|
||||||
[Operator.Switch]: 'switchNode',
|
|
||||||
[Operator.Concentrator]: 'logicNode',
|
|
||||||
[Operator.Note]: 'noteNode',
|
[Operator.Note]: 'noteNode',
|
||||||
[Operator.Crawler]: 'ragNode',
|
|
||||||
[Operator.Invoke]: 'ragNode',
|
|
||||||
[Operator.Email]: 'ragNode',
|
|
||||||
[Operator.Iteration]: 'group',
|
|
||||||
[Operator.IterationStart]: 'iterationStartNode',
|
|
||||||
[Operator.Code]: 'ragNode',
|
|
||||||
[Operator.WaitingDialogue]: 'ragNode',
|
|
||||||
[Operator.Agent]: 'agentNode',
|
|
||||||
[Operator.Tool]: 'toolNode',
|
|
||||||
[Operator.UserFillUp]: 'ragNode',
|
|
||||||
[Operator.StringTransform]: 'ragNode',
|
|
||||||
[Operator.Parser]: 'parserNode',
|
[Operator.Parser]: 'parserNode',
|
||||||
[Operator.Chunker]: 'chunkerNode',
|
|
||||||
[Operator.Tokenizer]: 'tokenizerNode',
|
[Operator.Tokenizer]: 'tokenizerNode',
|
||||||
[Operator.Splitter]: 'splitterNode',
|
[Operator.Splitter]: 'splitterNode',
|
||||||
[Operator.HierarchicalMerger]: 'hierarchicalMergerNode',
|
[Operator.HierarchicalMerger]: 'hierarchicalMergerNode',
|
||||||
@ -459,16 +417,7 @@ export const BeginQueryTypeIconMap = {
|
|||||||
[BeginQueryType.Boolean]: ToggleLeft,
|
[BeginQueryType.Boolean]: ToggleLeft,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoDebugOperatorsList = [
|
export const NoDebugOperatorsList = [Operator.Begin];
|
||||||
Operator.Begin,
|
|
||||||
Operator.Concentrator,
|
|
||||||
Operator.Message,
|
|
||||||
Operator.RewriteQuestion,
|
|
||||||
Operator.Switch,
|
|
||||||
Operator.Iteration,
|
|
||||||
Operator.UserFillUp,
|
|
||||||
Operator.IterationStart,
|
|
||||||
];
|
|
||||||
|
|
||||||
export enum NodeHandleId {
|
export enum NodeHandleId {
|
||||||
Start = 'start',
|
Start = 'start',
|
||||||
|
|||||||
@ -1,98 +1,20 @@
|
|||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
import AgentForm from '../form/agent-form';
|
|
||||||
import BeginForm from '../form/begin-form';
|
import BeginForm from '../form/begin-form';
|
||||||
import CategorizeForm from '../form/categorize-form';
|
|
||||||
import ChunkerForm from '../form/chunker-form';
|
|
||||||
import CodeForm from '../form/code-form';
|
|
||||||
import CrawlerForm from '../form/crawler-form';
|
|
||||||
import EmailForm from '../form/email-form';
|
|
||||||
import ExeSQLForm from '../form/exesql-form';
|
|
||||||
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
||||||
import InvokeForm from '../form/invoke-form';
|
|
||||||
import IterationForm from '../form/iteration-form';
|
|
||||||
import IterationStartForm from '../form/iteration-start-from';
|
|
||||||
import KeywordExtractForm from '../form/keyword-extract-form';
|
|
||||||
import MessageForm from '../form/message-form';
|
|
||||||
import ParserForm from '../form/parser-form';
|
import ParserForm from '../form/parser-form';
|
||||||
import RelevantForm from '../form/relevant-form';
|
|
||||||
import RetrievalForm from '../form/retrieval-form/next';
|
|
||||||
import RewriteQuestionForm from '../form/rewrite-question-form';
|
|
||||||
import SplitterForm from '../form/splitter-form';
|
import SplitterForm from '../form/splitter-form';
|
||||||
import StringTransformForm from '../form/string-transform-form';
|
|
||||||
import SwitchForm from '../form/switch-form';
|
|
||||||
import TokenizerForm from '../form/tokenizer-form';
|
import TokenizerForm from '../form/tokenizer-form';
|
||||||
import UserFillUpForm from '../form/user-fill-up-form';
|
|
||||||
|
|
||||||
export const FormConfigMap = {
|
export const FormConfigMap = {
|
||||||
[Operator.Begin]: {
|
[Operator.Begin]: {
|
||||||
component: BeginForm,
|
component: BeginForm,
|
||||||
},
|
},
|
||||||
[Operator.Retrieval]: {
|
|
||||||
component: RetrievalForm,
|
|
||||||
},
|
|
||||||
[Operator.Categorize]: {
|
|
||||||
component: CategorizeForm,
|
|
||||||
},
|
|
||||||
[Operator.Message]: {
|
|
||||||
component: MessageForm,
|
|
||||||
},
|
|
||||||
[Operator.Relevant]: {
|
|
||||||
component: RelevantForm,
|
|
||||||
},
|
|
||||||
[Operator.RewriteQuestion]: {
|
|
||||||
component: RewriteQuestionForm,
|
|
||||||
},
|
|
||||||
[Operator.Code]: {
|
|
||||||
component: CodeForm,
|
|
||||||
},
|
|
||||||
[Operator.WaitingDialogue]: {
|
|
||||||
component: CodeForm,
|
|
||||||
},
|
|
||||||
[Operator.Agent]: {
|
|
||||||
component: AgentForm,
|
|
||||||
},
|
|
||||||
[Operator.KeywordExtract]: {
|
|
||||||
component: KeywordExtractForm,
|
|
||||||
},
|
|
||||||
[Operator.ExeSQL]: {
|
|
||||||
component: ExeSQLForm,
|
|
||||||
},
|
|
||||||
[Operator.Switch]: {
|
|
||||||
component: SwitchForm,
|
|
||||||
},
|
|
||||||
[Operator.Crawler]: {
|
|
||||||
component: CrawlerForm,
|
|
||||||
},
|
|
||||||
[Operator.Invoke]: {
|
|
||||||
component: InvokeForm,
|
|
||||||
},
|
|
||||||
[Operator.Concentrator]: {
|
|
||||||
component: () => <></>,
|
|
||||||
},
|
|
||||||
[Operator.Note]: {
|
[Operator.Note]: {
|
||||||
component: () => <></>,
|
component: () => <></>,
|
||||||
},
|
},
|
||||||
[Operator.Email]: {
|
|
||||||
component: EmailForm,
|
|
||||||
},
|
|
||||||
[Operator.Iteration]: {
|
|
||||||
component: IterationForm,
|
|
||||||
},
|
|
||||||
[Operator.IterationStart]: {
|
|
||||||
component: IterationStartForm,
|
|
||||||
},
|
|
||||||
[Operator.UserFillUp]: {
|
|
||||||
component: UserFillUpForm,
|
|
||||||
},
|
|
||||||
[Operator.StringTransform]: {
|
|
||||||
component: StringTransformForm,
|
|
||||||
},
|
|
||||||
[Operator.Parser]: {
|
[Operator.Parser]: {
|
||||||
component: ParserForm,
|
component: ParserForm,
|
||||||
},
|
},
|
||||||
[Operator.Chunker]: {
|
|
||||||
component: ChunkerForm,
|
|
||||||
},
|
|
||||||
[Operator.Tokenizer]: {
|
[Operator.Tokenizer]: {
|
||||||
component: TokenizerForm,
|
component: TokenizerForm,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,17 +8,12 @@ import {
|
|||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { lowerFirst } from 'lodash';
|
import { X } from 'lucide-react';
|
||||||
import { Play, X } from 'lucide-react';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BeginId, Operator } from '../constant';
|
import { BeginId, Operator } from '../constant';
|
||||||
import { AgentFormContext } from '../context';
|
import { AgentFormContext } from '../context';
|
||||||
import { RunTooltip } from '../flow-tooltip';
|
|
||||||
import { useHandleNodeNameChange } from '../hooks/use-change-node-name';
|
import { useHandleNodeNameChange } from '../hooks/use-change-node-name';
|
||||||
import OperatorIcon from '../operator-icon';
|
import OperatorIcon from '../operator-icon';
|
||||||
import useGraphStore from '../store';
|
|
||||||
import { needsSingleStepDebugging } from '../utils';
|
|
||||||
import { FormConfigMap } from './form-config-map';
|
import { FormConfigMap } from './form-config-map';
|
||||||
import SingleDebugSheet from './single-debug-sheet';
|
import SingleDebugSheet from './single-debug-sheet';
|
||||||
|
|
||||||
@ -39,10 +34,9 @@ const FormSheet = ({
|
|||||||
singleDebugDrawerVisible,
|
singleDebugDrawerVisible,
|
||||||
chatVisible,
|
chatVisible,
|
||||||
hideSingleDebugDrawer,
|
hideSingleDebugDrawer,
|
||||||
showSingleDebugDrawer,
|
|
||||||
}: IModalProps<any> & IProps) => {
|
}: IModalProps<any> & IProps) => {
|
||||||
const operatorName: Operator = node?.data.label as Operator;
|
const operatorName: Operator = node?.data.label as Operator;
|
||||||
const clickedToolId = useGraphStore((state) => state.clickedToolId);
|
// const clickedToolId = useGraphStore((state) => state.clickedToolId);
|
||||||
|
|
||||||
const currentFormMap = FormConfigMap[operatorName];
|
const currentFormMap = FormConfigMap[operatorName];
|
||||||
|
|
||||||
@ -53,13 +47,6 @@ const FormSheet = ({
|
|||||||
data: node?.data,
|
data: node?.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isMcp = useMemo(() => {
|
|
||||||
return (
|
|
||||||
operatorName === Operator.Tool &&
|
|
||||||
Object.values(Operator).every((x) => x !== clickedToolId)
|
|
||||||
);
|
|
||||||
}, [clickedToolId, operatorName]);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -75,41 +62,28 @@ const FormSheet = ({
|
|||||||
<section className="flex-col border-b py-2 px-5">
|
<section className="flex-col border-b py-2 px-5">
|
||||||
<div className="flex items-center gap-2 pb-3">
|
<div className="flex items-center gap-2 pb-3">
|
||||||
<OperatorIcon name={operatorName}></OperatorIcon>
|
<OperatorIcon name={operatorName}></OperatorIcon>
|
||||||
|
<div className="flex items-center gap-1 flex-1">
|
||||||
{isMcp ? (
|
<label htmlFor="">{t('flow.title')}</label>
|
||||||
<div className="flex-1">MCP Config</div>
|
{node?.id === BeginId ? (
|
||||||
) : (
|
<span>{t(BeginId)}</span>
|
||||||
<div className="flex items-center gap-1 flex-1">
|
) : (
|
||||||
<label htmlFor="">{t('flow.title')}</label>
|
<Input
|
||||||
{node?.id === BeginId ? (
|
value={name}
|
||||||
<span>{t(BeginId)}</span>
|
onBlur={handleNameBlur}
|
||||||
) : (
|
onChange={handleNameChange}
|
||||||
<Input
|
></Input>
|
||||||
value={name}
|
)}
|
||||||
onBlur={handleNameBlur}
|
</div>
|
||||||
onChange={handleNameChange}
|
{/* {needsSingleStepDebugging(operatorName) && (
|
||||||
></Input>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{needsSingleStepDebugging(operatorName) && (
|
|
||||||
<RunTooltip>
|
<RunTooltip>
|
||||||
<Play
|
<Play
|
||||||
className="size-5 cursor-pointer"
|
className="size-5 cursor-pointer"
|
||||||
onClick={showSingleDebugDrawer}
|
onClick={showSingleDebugDrawer}
|
||||||
/>
|
/>
|
||||||
</RunTooltip>
|
</RunTooltip>
|
||||||
)}
|
)} */}
|
||||||
<X onClick={hideModal} />
|
<X onClick={hideModal} />
|
||||||
</div>
|
</div>
|
||||||
{isMcp || (
|
|
||||||
<span>
|
|
||||||
{t(
|
|
||||||
`dataflow.${lowerFirst(operatorName === Operator.Tool ? clickedToolId : operatorName)}Description`,
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<section className="pt-4 overflow-auto flex-1">
|
<section className="pt-4 overflow-auto flex-1">
|
||||||
|
|||||||
@ -1,191 +0,0 @@
|
|||||||
import { BlockButton } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@/components/ui/tooltip';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { Position } from '@xyflow/react';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { PencilLine, X } from 'lucide-react';
|
|
||||||
import {
|
|
||||||
MouseEventHandler,
|
|
||||||
PropsWithChildren,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useMemo,
|
|
||||||
} from 'react';
|
|
||||||
import { Operator } from '../../constant';
|
|
||||||
import { AgentInstanceContext } from '../../context';
|
|
||||||
import { useFindMcpById } from '../../hooks/use-find-mcp-by-id';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import OperatorIcon from '../../operator-icon';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import { filterDownstreamAgentNodeIds } from '../../utils/filter-downstream-nodes';
|
|
||||||
import { ToolPopover } from './tool-popover';
|
|
||||||
import { useDeleteAgentNodeMCP } from './tool-popover/use-update-mcp';
|
|
||||||
import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools';
|
|
||||||
import { useGetAgentMCPIds, useGetAgentToolNames } from './use-get-tools';
|
|
||||||
|
|
||||||
export function ToolCard({
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: PropsWithChildren & React.HTMLAttributes<HTMLLIElement>) {
|
|
||||||
const element = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
{...props}
|
|
||||||
className={cn(
|
|
||||||
'flex bg-bg-card p-1 rounded-sm justify-between',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}, [children, className, props]);
|
|
||||||
|
|
||||||
if (children === Operator.Code) {
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>{element}</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>It doesn't have any config.</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionButtonProps<T> = {
|
|
||||||
record: T;
|
|
||||||
deleteRecord(record: T): void;
|
|
||||||
edit: MouseEventHandler<HTMLOrSVGElement>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ActionButton<T>({ deleteRecord, record, edit }: ActionButtonProps<T>) {
|
|
||||||
const handleDelete = useCallback(() => {
|
|
||||||
deleteRecord(record);
|
|
||||||
}, [deleteRecord, record]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2 text-text-secondary">
|
|
||||||
<PencilLine
|
|
||||||
className="size-4 cursor-pointer"
|
|
||||||
data-tool={record}
|
|
||||||
onClick={edit}
|
|
||||||
/>
|
|
||||||
<X className="size-4 cursor-pointer" onClick={handleDelete} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AgentTools() {
|
|
||||||
const { toolNames } = useGetAgentToolNames();
|
|
||||||
const { deleteNodeTool } = useDeleteAgentNodeTools();
|
|
||||||
const { mcpIds } = useGetAgentMCPIds();
|
|
||||||
const { findMcpById } = useFindMcpById();
|
|
||||||
const { deleteNodeMCP } = useDeleteAgentNodeMCP();
|
|
||||||
const { showFormDrawer } = useContext(AgentInstanceContext);
|
|
||||||
const { clickedNodeId, findAgentToolNodeById, selectNodeIds } = useGraphStore(
|
|
||||||
(state) => state,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleEdit: MouseEventHandler<SVGSVGElement> = useCallback(
|
|
||||||
(e) => {
|
|
||||||
const toolNodeId = findAgentToolNodeById(clickedNodeId);
|
|
||||||
if (toolNodeId) {
|
|
||||||
selectNodeIds([toolNodeId]);
|
|
||||||
showFormDrawer(e, toolNodeId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[clickedNodeId, findAgentToolNodeById, selectNodeIds, showFormDrawer],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="space-y-2.5">
|
|
||||||
<span className="text-text-secondary">{t('flow.tools')}</span>
|
|
||||||
<ul className="space-y-2">
|
|
||||||
{toolNames.map((x) => (
|
|
||||||
<ToolCard key={x}>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<OperatorIcon name={x as Operator}></OperatorIcon>
|
|
||||||
{x}
|
|
||||||
</div>
|
|
||||||
<ActionButton
|
|
||||||
record={x}
|
|
||||||
deleteRecord={deleteNodeTool(x)}
|
|
||||||
edit={handleEdit}
|
|
||||||
></ActionButton>
|
|
||||||
</ToolCard>
|
|
||||||
))}
|
|
||||||
{mcpIds.map((id) => (
|
|
||||||
<ToolCard key={id}>
|
|
||||||
{findMcpById(id)?.name}
|
|
||||||
<ActionButton
|
|
||||||
record={id}
|
|
||||||
deleteRecord={deleteNodeMCP(id)}
|
|
||||||
edit={handleEdit}
|
|
||||||
></ActionButton>
|
|
||||||
</ToolCard>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<ToolPopover>
|
|
||||||
<BlockButton>{t('flow.addTools')}</BlockButton>
|
|
||||||
</ToolPopover>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Agents({ node }: INextOperatorForm) {
|
|
||||||
const { addCanvasNode } = useContext(AgentInstanceContext);
|
|
||||||
const { deleteAgentDownstreamNodesById, edges, getNode, selectNodeIds } =
|
|
||||||
useGraphStore((state) => state);
|
|
||||||
const { showFormDrawer } = useContext(AgentInstanceContext);
|
|
||||||
|
|
||||||
const handleEdit = useCallback(
|
|
||||||
(nodeId: string): MouseEventHandler<SVGSVGElement> =>
|
|
||||||
(e) => {
|
|
||||||
selectNodeIds([nodeId]);
|
|
||||||
showFormDrawer(e, nodeId);
|
|
||||||
},
|
|
||||||
[selectNodeIds, showFormDrawer],
|
|
||||||
);
|
|
||||||
|
|
||||||
const subBottomAgentNodeIds = useMemo(() => {
|
|
||||||
return filterDownstreamAgentNodeIds(edges, node?.id);
|
|
||||||
}, [edges, node?.id]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="space-y-2.5">
|
|
||||||
<span className="text-text-secondary">{t('flow.agent')}</span>
|
|
||||||
<ul className="space-y-2">
|
|
||||||
{subBottomAgentNodeIds.map((id) => {
|
|
||||||
const currentNode = getNode(id);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToolCard key={id}>
|
|
||||||
{currentNode?.data.name}
|
|
||||||
<ActionButton
|
|
||||||
record={id}
|
|
||||||
deleteRecord={deleteAgentDownstreamNodesById}
|
|
||||||
edit={handleEdit(id)}
|
|
||||||
></ActionButton>
|
|
||||||
</ToolCard>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
<BlockButton
|
|
||||||
onClick={addCanvasNode(Operator.Agent, {
|
|
||||||
nodeId: node?.id,
|
|
||||||
position: Position.Bottom,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('flow.addAgent')}
|
|
||||||
</BlockButton>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
import { BlockButton, Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PromptRole } from '../../constant';
|
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
{ label: 'User', value: PromptRole.User },
|
|
||||||
{ label: 'Assistant', value: PromptRole.Assistant },
|
|
||||||
];
|
|
||||||
|
|
||||||
const DynamicPrompt = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
const name = 'prompts';
|
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
|
||||||
name: name,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('flow.msgTip')}>{t('flow.msg')}</FormLabel>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<div key={field.id} className="flex">
|
|
||||||
<div className="space-y-2 flex-1">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.role`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="w-1/3">
|
|
||||||
<FormLabel />
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
{...field}
|
|
||||||
options={options}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.content`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormControl>
|
|
||||||
<section>
|
|
||||||
<PromptEditor
|
|
||||||
{...field}
|
|
||||||
showToolbar={false}
|
|
||||||
></PromptEditor>
|
|
||||||
</section>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
|
||||||
<X />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<FormMessage />
|
|
||||||
<BlockButton
|
|
||||||
onClick={() => append({ content: '', role: PromptRole.User })}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</BlockButton>
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(DynamicPrompt);
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import { BlockButton, Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
|
||||||
|
|
||||||
const DynamicTool = () => {
|
|
||||||
const form = useFormContext();
|
|
||||||
const name = 'tools';
|
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
|
||||||
name: name,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<div key={field.id} className="flex">
|
|
||||||
<div className="space-y-2 flex-1">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.component_name`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormControl>
|
|
||||||
<section>
|
|
||||||
<PromptEditor
|
|
||||||
{...field}
|
|
||||||
showToolbar={false}
|
|
||||||
></PromptEditor>
|
|
||||||
</section>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
|
||||||
<X />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<FormMessage />
|
|
||||||
<BlockButton onClick={() => append({ component_name: '' })}>
|
|
||||||
Add
|
|
||||||
</BlockButton>
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(DynamicTool);
|
|
||||||
@ -1,280 +0,0 @@
|
|||||||
import { Collapse } from '@/components/collapse';
|
|
||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import {
|
|
||||||
LargeModelFilterFormSchema,
|
|
||||||
LargeModelFormField,
|
|
||||||
} from '@/components/large-model-form-field';
|
|
||||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
|
||||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input, NumberInput } from '@/components/ui/input';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import { LlmModelType } from '@/constants/knowledge';
|
|
||||||
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo, useEffect, useMemo } from 'react';
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import {
|
|
||||||
AgentExceptionMethod,
|
|
||||||
NodeHandleId,
|
|
||||||
VariableType,
|
|
||||||
initialAgentValues,
|
|
||||||
} from '../../constant';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import { isBottomSubAgent } from '../../utils';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { DescriptionField } from '../components/description-field';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
import { AgentTools, Agents } from './agent-tools';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
sys_prompt: z.string(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
user_prompt: z.string().optional(),
|
|
||||||
prompts: z.string().optional(),
|
|
||||||
// prompts: z
|
|
||||||
// .array(
|
|
||||||
// z.object({
|
|
||||||
// role: z.string(),
|
|
||||||
// content: z.string(),
|
|
||||||
// }),
|
|
||||||
// )
|
|
||||||
// .optional(),
|
|
||||||
message_history_window_size: z.coerce.number(),
|
|
||||||
tools: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
component_name: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
...LlmSettingSchema,
|
|
||||||
max_retries: z.coerce.number(),
|
|
||||||
delay_after_error: z.coerce.number().optional(),
|
|
||||||
visual_files_var: z.string().optional(),
|
|
||||||
max_rounds: z.coerce.number().optional(),
|
|
||||||
exception_method: z.string().optional(),
|
|
||||||
exception_goto: z.array(z.string()).optional(),
|
|
||||||
exception_default_value: z.string().optional(),
|
|
||||||
...LargeModelFilterFormSchema,
|
|
||||||
cite: z.boolean().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputList = buildOutputList(initialAgentValues.outputs);
|
|
||||||
|
|
||||||
function AgentForm({ node }: INextOperatorForm) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { edges, deleteEdgesBySourceAndSourceHandle } = useGraphStore(
|
|
||||||
(state) => state,
|
|
||||||
);
|
|
||||||
|
|
||||||
const defaultValues = useValues(node);
|
|
||||||
|
|
||||||
const ExceptionMethodOptions = Object.values(AgentExceptionMethod).map(
|
|
||||||
(x) => ({
|
|
||||||
label: t(`flow.${x}`),
|
|
||||||
value: x,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSubAgent = useMemo(() => {
|
|
||||||
return isBottomSubAgent(edges, node?.id);
|
|
||||||
}, [edges, node?.id]);
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
defaultValues: defaultValues,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const llmId = useWatch({ control: form.control, name: 'llm_id' });
|
|
||||||
|
|
||||||
const findLlmByUuid = useFindLlmByUuid();
|
|
||||||
|
|
||||||
const exceptionMethod = useWatch({
|
|
||||||
control: form.control,
|
|
||||||
name: 'exception_method',
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (exceptionMethod !== AgentExceptionMethod.Goto) {
|
|
||||||
if (node?.id) {
|
|
||||||
deleteEdgesBySourceAndSourceHandle(
|
|
||||||
node?.id,
|
|
||||||
NodeHandleId.AgentException,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [deleteEdgesBySourceAndSourceHandle, exceptionMethod, node?.id]);
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
{isSubAgent && <DescriptionField></DescriptionField>}
|
|
||||||
<LargeModelFormField showSpeech2TextModel></LargeModelFormField>
|
|
||||||
{findLlmByUuid(llmId)?.model_type === LlmModelType.Image2text && (
|
|
||||||
<QueryVariable
|
|
||||||
name="visual_files_var"
|
|
||||||
label="Visual Input File"
|
|
||||||
type={VariableType.File}
|
|
||||||
></QueryVariable>
|
|
||||||
)}
|
|
||||||
</FormContainer>
|
|
||||||
|
|
||||||
<FormContainer>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`sys_prompt`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.systemPrompt')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<PromptEditor
|
|
||||||
{...field}
|
|
||||||
placeholder={t('flow.messagePlaceholder')}
|
|
||||||
showToolbar={false}
|
|
||||||
></PromptEditor>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormContainer>
|
|
||||||
{isSubAgent || (
|
|
||||||
<FormContainer>
|
|
||||||
{/* <DynamicPrompt></DynamicPrompt> */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`prompts`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.userPrompt')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<section>
|
|
||||||
<PromptEditor
|
|
||||||
{...field}
|
|
||||||
showToolbar={false}
|
|
||||||
></PromptEditor>
|
|
||||||
</section>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormContainer>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormContainer>
|
|
||||||
<AgentTools></AgentTools>
|
|
||||||
<Agents node={node}></Agents>
|
|
||||||
</FormContainer>
|
|
||||||
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
|
|
||||||
<FormContainer>
|
|
||||||
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`cite`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel tooltip={t('flow.citeTip')}>
|
|
||||||
{t('flow.cite')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
></Switch>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`max_retries`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.maxRetries')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NumberInput {...field} max={8}></NumberInput>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`delay_after_error`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.delayEfterError')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NumberInput {...field} max={5} step={0.1}></NumberInput>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`max_rounds`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.maxRounds')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NumberInput {...field}></NumberInput>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`exception_method`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.exceptionMethod')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch
|
|
||||||
{...field}
|
|
||||||
options={ExceptionMethodOptions}
|
|
||||||
allowClear
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{exceptionMethod === AgentExceptionMethod.Comment && (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`exception_default_value`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('flow.ExceptionDefaultValue')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</FormContainer>
|
|
||||||
</Collapse>
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</FormWrapper>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(AgentForm);
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@/components/ui/popover';
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
||||||
import { Operator } from '@/pages/agent/constant';
|
|
||||||
import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context';
|
|
||||||
import useGraphStore from '@/pages/agent/store';
|
|
||||||
import { Position } from '@xyflow/react';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { PropsWithChildren, useCallback, useContext, useEffect } from 'react';
|
|
||||||
import { useGetAgentMCPIds, useGetAgentToolNames } from '../use-get-tools';
|
|
||||||
import { MCPCommand, ToolCommand } from './tool-command';
|
|
||||||
import { useUpdateAgentNodeMCP } from './use-update-mcp';
|
|
||||||
import { useUpdateAgentNodeTools } from './use-update-tools';
|
|
||||||
|
|
||||||
enum ToolType {
|
|
||||||
Common = 'common',
|
|
||||||
MCP = 'mcp',
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ToolPopover({ children }: PropsWithChildren) {
|
|
||||||
const { addCanvasNode } = useContext(AgentInstanceContext);
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
const { updateNodeTools } = useUpdateAgentNodeTools();
|
|
||||||
const { toolNames } = useGetAgentToolNames();
|
|
||||||
const deleteAgentToolNodeById = useGraphStore(
|
|
||||||
(state) => state.deleteAgentToolNodeById,
|
|
||||||
);
|
|
||||||
const { mcpIds } = useGetAgentMCPIds();
|
|
||||||
const { updateNodeMCP } = useUpdateAgentNodeMCP();
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
|
||||||
(value: string[]) => {
|
|
||||||
if (Array.isArray(value) && node?.id) {
|
|
||||||
updateNodeTools(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[node?.id, updateNodeTools],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const total = toolNames.length + mcpIds.length;
|
|
||||||
if (node?.id) {
|
|
||||||
if (total > 0) {
|
|
||||||
addCanvasNode(Operator.Tool, {
|
|
||||||
position: Position.Bottom,
|
|
||||||
nodeId: node?.id,
|
|
||||||
})();
|
|
||||||
} else {
|
|
||||||
deleteAgentToolNodeById(node.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
addCanvasNode,
|
|
||||||
deleteAgentToolNodeById,
|
|
||||||
mcpIds.length,
|
|
||||||
node?.id,
|
|
||||||
toolNames.length,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80 p-4">
|
|
||||||
<Tabs defaultValue={ToolType.Common}>
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value={ToolType.Common} className="bg-bg-card">
|
|
||||||
{t('flow.builtIn')}
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value={ToolType.MCP} className="bg-bg-card">
|
|
||||||
MCP
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value={ToolType.Common}>
|
|
||||||
<ToolCommand
|
|
||||||
onChange={handleChange}
|
|
||||||
value={toolNames}
|
|
||||||
></ToolCommand>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value={ToolType.MCP}>
|
|
||||||
<MCPCommand value={mcpIds} onChange={updateNodeMCP}></MCPCommand>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,178 +0,0 @@
|
|||||||
import { CheckIcon } from 'lucide-react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
CommandList,
|
|
||||||
} from '@/components/ui/command';
|
|
||||||
import { useListMcpServer } from '@/hooks/use-mcp-request';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { Operator } from '@/pages/agent/constant';
|
|
||||||
import OperatorIcon from '@/pages/agent/operator-icon';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { lowerFirst } from 'lodash';
|
|
||||||
import { PropsWithChildren, useCallback, useEffect, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const Menus = [
|
|
||||||
{
|
|
||||||
label: t('flow.search'),
|
|
||||||
list: [
|
|
||||||
Operator.TavilySearch,
|
|
||||||
Operator.TavilyExtract,
|
|
||||||
Operator.Google,
|
|
||||||
// Operator.Bing,
|
|
||||||
Operator.DuckDuckGo,
|
|
||||||
Operator.Wikipedia,
|
|
||||||
Operator.SearXNG,
|
|
||||||
Operator.YahooFinance,
|
|
||||||
Operator.PubMed,
|
|
||||||
Operator.GoogleScholar,
|
|
||||||
Operator.ArXiv,
|
|
||||||
Operator.WenCai,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('flow.communication'),
|
|
||||||
list: [Operator.Email],
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: 'Productivity',
|
|
||||||
// list: [],
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
label: t('flow.developer'),
|
|
||||||
list: [Operator.GitHub, Operator.ExeSQL, Operator.Code, Operator.Retrieval],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type ToolCommandProps = {
|
|
||||||
value?: string[];
|
|
||||||
onChange?(values: string[]): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ToolCommandItemProps = {
|
|
||||||
toggleOption(id: string): void;
|
|
||||||
id: string;
|
|
||||||
isSelected: boolean;
|
|
||||||
} & ToolCommandProps;
|
|
||||||
|
|
||||||
function ToolCommandItem({
|
|
||||||
toggleOption,
|
|
||||||
id,
|
|
||||||
isSelected,
|
|
||||||
children,
|
|
||||||
}: ToolCommandItemProps & PropsWithChildren) {
|
|
||||||
return (
|
|
||||||
<CommandItem className="cursor-pointer" onSelect={() => toggleOption(id)}>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
|
||||||
isSelected
|
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'opacity-50 [&_svg]:invisible',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CheckIcon className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</CommandItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useHandleSelectChange({ onChange, value }: ToolCommandProps) {
|
|
||||||
const [currentValue, setCurrentValue] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const toggleOption = useCallback(
|
|
||||||
(option: string) => {
|
|
||||||
const newSelectedValues = currentValue.includes(option)
|
|
||||||
? currentValue.filter((value) => value !== option)
|
|
||||||
: [...currentValue, option];
|
|
||||||
setCurrentValue(newSelectedValues);
|
|
||||||
onChange?.(newSelectedValues);
|
|
||||||
},
|
|
||||||
[currentValue, onChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
setCurrentValue(value);
|
|
||||||
}
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
toggleOption,
|
|
||||||
currentValue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ToolCommand({ value, onChange }: ToolCommandProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { toggleOption, currentValue } = useHandleSelectChange({
|
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder={t('flow.typeCommandOrsearch')} />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
|
||||||
{Menus.map((x) => (
|
|
||||||
<CommandGroup heading={x.label} key={x.label}>
|
|
||||||
{x.list.map((y) => {
|
|
||||||
const isSelected = currentValue.includes(y);
|
|
||||||
return (
|
|
||||||
<ToolCommandItem
|
|
||||||
key={y}
|
|
||||||
id={y}
|
|
||||||
toggleOption={toggleOption}
|
|
||||||
isSelected={isSelected}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<OperatorIcon name={y as Operator}></OperatorIcon>
|
|
||||||
<span>{t(`flow.${lowerFirst(y)}`)}</span>
|
|
||||||
</>
|
|
||||||
</ToolCommandItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CommandGroup>
|
|
||||||
))}
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MCPCommand({ onChange, value }: ToolCommandProps) {
|
|
||||||
const { data } = useListMcpServer();
|
|
||||||
const { toggleOption, currentValue } = useHandleSelectChange({
|
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="Type a command or search..." />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
|
||||||
{data.mcp_servers.map((item) => {
|
|
||||||
const isSelected = currentValue.includes(item.id);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToolCommandItem
|
|
||||||
key={item.id}
|
|
||||||
id={item.id}
|
|
||||||
isSelected={isSelected}
|
|
||||||
toggleOption={toggleOption}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</ToolCommandItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import { useListMcpServer } from '@/hooks/use-mcp-request';
|
|
||||||
import { IAgentForm } from '@/interfaces/database/agent';
|
|
||||||
import { AgentFormContext } from '@/pages/agent/context';
|
|
||||||
import useGraphStore from '@/pages/agent/store';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useCallback, useContext, useMemo } from 'react';
|
|
||||||
|
|
||||||
export function useGetNodeMCP() {
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const mcp: IAgentForm['mcp'] = get(node, 'data.form.mcp');
|
|
||||||
return mcp;
|
|
||||||
}, [node]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdateAgentNodeMCP() {
|
|
||||||
const { updateNodeForm } = useGraphStore((state) => state);
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
const mcpList = useGetNodeMCP();
|
|
||||||
const { data } = useListMcpServer();
|
|
||||||
const mcpServers = data.mcp_servers;
|
|
||||||
|
|
||||||
const findMcpTools = useCallback(
|
|
||||||
(mcpId: string) => {
|
|
||||||
const mcp = mcpServers.find((x) => x.id === mcpId);
|
|
||||||
return mcp?.variables.tools;
|
|
||||||
},
|
|
||||||
[mcpServers],
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateNodeMCP = useCallback(
|
|
||||||
(value: string[]) => {
|
|
||||||
if (node?.id) {
|
|
||||||
const nextValue = value.reduce<IAgentForm['mcp']>((pre, cur) => {
|
|
||||||
const mcp = mcpList.find((x) => x.mcp_id === cur);
|
|
||||||
const tools = findMcpTools(cur);
|
|
||||||
if (mcp) {
|
|
||||||
pre.push(mcp);
|
|
||||||
} else if (tools) {
|
|
||||||
pre.push({
|
|
||||||
mcp_id: cur,
|
|
||||||
tools: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return pre;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
updateNodeForm(node?.id, nextValue, ['mcp']);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[node?.id, updateNodeForm, mcpList, findMcpTools],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { updateNodeMCP };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteAgentNodeMCP() {
|
|
||||||
const { updateNodeForm } = useGraphStore((state) => state);
|
|
||||||
const mcpList = useGetNodeMCP();
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
|
|
||||||
const deleteNodeMCP = useCallback(
|
|
||||||
(value: string) => () => {
|
|
||||||
const nextMCP = mcpList.filter((x) => x.mcp_id !== value);
|
|
||||||
if (node?.id) {
|
|
||||||
updateNodeForm(node?.id, nextMCP, ['mcp']);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[node?.id, mcpList, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { deleteNodeMCP };
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import { IAgentForm } from '@/interfaces/database/agent';
|
|
||||||
import { Operator } from '@/pages/agent/constant';
|
|
||||||
import { AgentFormContext } from '@/pages/agent/context';
|
|
||||||
import { useAgentToolInitialValues } from '@/pages/agent/hooks/use-agent-tool-initial-values';
|
|
||||||
import useGraphStore from '@/pages/agent/store';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useCallback, useContext, useMemo } from 'react';
|
|
||||||
|
|
||||||
export function useGetNodeTools() {
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const tools: IAgentForm['tools'] = get(node, 'data.form.tools');
|
|
||||||
return tools;
|
|
||||||
}, [node]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdateAgentNodeTools() {
|
|
||||||
const { updateNodeForm } = useGraphStore((state) => state);
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
const tools = useGetNodeTools();
|
|
||||||
const { initializeAgentToolValues } = useAgentToolInitialValues();
|
|
||||||
|
|
||||||
const updateNodeTools = useCallback(
|
|
||||||
(value: string[]) => {
|
|
||||||
if (node?.id) {
|
|
||||||
const nextValue = value.reduce<IAgentForm['tools']>((pre, cur) => {
|
|
||||||
const tool = tools.find((x) => x.component_name === cur);
|
|
||||||
pre.push(
|
|
||||||
tool
|
|
||||||
? tool
|
|
||||||
: {
|
|
||||||
component_name: cur,
|
|
||||||
name: cur,
|
|
||||||
params: initializeAgentToolValues(cur as Operator),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return pre;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
updateNodeForm(node?.id, nextValue, ['tools']);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[initializeAgentToolValues, node?.id, tools, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { updateNodeTools };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteAgentNodeTools() {
|
|
||||||
const { updateNodeForm } = useGraphStore((state) => state);
|
|
||||||
const tools = useGetNodeTools();
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
|
|
||||||
const deleteNodeTool = useCallback(
|
|
||||||
(value: string) => () => {
|
|
||||||
const nextTools = tools.filter((x) => x.component_name !== value);
|
|
||||||
if (node?.id) {
|
|
||||||
updateNodeForm(node?.id, nextTools, ['tools']);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[node?.id, tools, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { deleteNodeTool };
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import { IAgentForm } from '@/interfaces/database/agent';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useContext, useMemo } from 'react';
|
|
||||||
import { AgentFormContext } from '../../context';
|
|
||||||
|
|
||||||
export function useGetAgentToolNames() {
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
|
|
||||||
const toolNames = useMemo(() => {
|
|
||||||
const tools: IAgentForm['tools'] = get(node, 'data.form.tools', []);
|
|
||||||
return tools.map((x) => x.component_name);
|
|
||||||
}, [node]);
|
|
||||||
|
|
||||||
return { toolNames };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGetAgentMCPIds() {
|
|
||||||
const node = useContext(AgentFormContext);
|
|
||||||
|
|
||||||
const mcpIds = useMemo(() => {
|
|
||||||
const ids: IAgentForm['mcp'] = get(node, 'data.form.mcp', []);
|
|
||||||
return ids.map((x) => x.mcp_id);
|
|
||||||
}, [node]);
|
|
||||||
|
|
||||||
return { mcpIds };
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { get, isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { initialAgentValues } from '../../constant';
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const llmId = useFetchModelId();
|
|
||||||
|
|
||||||
const defaultValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
...initialAgentValues,
|
|
||||||
llm_id: llmId,
|
|
||||||
prompts: '',
|
|
||||||
}),
|
|
||||||
[llmId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return defaultValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...formData,
|
|
||||||
prompts: get(formData, 'prompts.0.content', ''),
|
|
||||||
};
|
|
||||||
}, [defaultValues, node?.data?.form]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import { PromptRole } from '../../constant';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn<any>) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
|
||||||
if (id && form?.formState.isDirty) {
|
|
||||||
values = form?.getValues();
|
|
||||||
let nextValues: any = {
|
|
||||||
...values,
|
|
||||||
prompts: [{ role: PromptRole.User, content: values.prompts }],
|
|
||||||
};
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -1,249 +0,0 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from '@/components/ui/collapsible';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { BlurTextarea } from '@/components/ui/textarea';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
|
||||||
import humanId from 'human-id';
|
|
||||||
import trim from 'lodash/trim';
|
|
||||||
import { ChevronsUpDown, X } from 'lucide-react';
|
|
||||||
import {
|
|
||||||
ChangeEventHandler,
|
|
||||||
FocusEventHandler,
|
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { UseFormReturn, useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import DynamicExample from './dynamic-example';
|
|
||||||
import { useCreateCategorizeFormSchema } from './use-form-schema';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
nodeId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INameInputProps {
|
|
||||||
value?: string;
|
|
||||||
onChange?: (value: string) => void;
|
|
||||||
otherNames?: string[];
|
|
||||||
validate(error?: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOtherFieldValues = (
|
|
||||||
form: UseFormReturn,
|
|
||||||
formListName: string = 'items',
|
|
||||||
index: number,
|
|
||||||
latestField: string,
|
|
||||||
) =>
|
|
||||||
(form.getValues(formListName) ?? [])
|
|
||||||
.map((x: any) => x[latestField])
|
|
||||||
.filter(
|
|
||||||
(x: string) =>
|
|
||||||
x !== form.getValues(`${formListName}.${index}.${latestField}`),
|
|
||||||
);
|
|
||||||
|
|
||||||
const InnerNameInput = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
otherNames,
|
|
||||||
validate,
|
|
||||||
}: INameInputProps) => {
|
|
||||||
const [name, setName] = useState<string | undefined>();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const handleNameChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
|
||||||
(e) => {
|
|
||||||
const val = e.target.value;
|
|
||||||
setName(val);
|
|
||||||
const trimmedVal = trim(val);
|
|
||||||
// trigger validation
|
|
||||||
if (otherNames?.some((x) => x === trimmedVal)) {
|
|
||||||
validate(t('nameRepeatedMsg'));
|
|
||||||
} else if (trimmedVal === '') {
|
|
||||||
validate(t('nameRequiredMsg'));
|
|
||||||
} else {
|
|
||||||
validate('');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[otherNames, validate, t],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNameBlur: FocusEventHandler<HTMLInputElement> = useCallback(
|
|
||||||
(e) => {
|
|
||||||
const val = e.target.value;
|
|
||||||
if (otherNames?.every((x) => x !== val) && trim(val) !== '') {
|
|
||||||
onChange?.(val);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onChange, otherNames],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setName(value);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
value={name}
|
|
||||||
onChange={handleNameChange}
|
|
||||||
onBlur={handleNameBlur}
|
|
||||||
></Input>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const NameInput = memo(InnerNameInput);
|
|
||||||
|
|
||||||
const InnerFormSet = ({ index }: IProps & { index: number }) => {
|
|
||||||
const form = useFormContext();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const buildFieldName = useCallback(
|
|
||||||
(name: string) => {
|
|
||||||
return `items.${index}.${name}`;
|
|
||||||
},
|
|
||||||
[index],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="space-y-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={buildFieldName('name')}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('categoryName')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NameInput
|
|
||||||
{...field}
|
|
||||||
otherNames={getOtherFieldValues(form, 'items', index, 'name')}
|
|
||||||
validate={(error?: string) => {
|
|
||||||
const fieldName = buildFieldName('name');
|
|
||||||
if (error) {
|
|
||||||
form.setError(fieldName, { message: error });
|
|
||||||
} else {
|
|
||||||
form.clearErrors(fieldName);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
></NameInput>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={buildFieldName('description')}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('description')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<BlurTextarea {...field} rows={3} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* Create a hidden field to make Form instance record this */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'uuid'}
|
|
||||||
render={() => <div></div>}
|
|
||||||
/>
|
|
||||||
<DynamicExample name={buildFieldName('examples')}></DynamicExample>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormSet = memo(InnerFormSet);
|
|
||||||
|
|
||||||
const DynamicCategorize = ({ nodeId }: IProps) => {
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
const FormSchema = useCreateCategorizeFormSchema();
|
|
||||||
|
|
||||||
const deleteCategorizeCaseEdges = useGraphStore(
|
|
||||||
(state) => state.deleteEdgesBySourceAndSourceHandle,
|
|
||||||
);
|
|
||||||
const form = useFormContext<z.infer<typeof FormSchema>>();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
|
||||||
name: 'items',
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleAdd = useCallback(() => {
|
|
||||||
append({
|
|
||||||
name: humanId(),
|
|
||||||
description: '',
|
|
||||||
uuid: uuid(),
|
|
||||||
examples: [{ value: '' }],
|
|
||||||
});
|
|
||||||
if (nodeId) updateNodeInternals(nodeId);
|
|
||||||
}, [append, nodeId, updateNodeInternals]);
|
|
||||||
|
|
||||||
const handleRemove = useCallback(
|
|
||||||
(index: number) => () => {
|
|
||||||
remove(index);
|
|
||||||
if (nodeId) {
|
|
||||||
const uuid = fields[index].uuid;
|
|
||||||
deleteCategorizeCaseEdges(nodeId, uuid);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[deleteCategorizeCaseEdges, fields, nodeId, remove],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4 ">
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<Collapsible key={field.id} defaultOpen>
|
|
||||||
<div className="flex items-center justify-between space-x-4">
|
|
||||||
<h4 className="font-bold">
|
|
||||||
{form.getValues(`items.${index}.name`)}
|
|
||||||
</h4>
|
|
||||||
<CollapsibleTrigger asChild>
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="w-9 p-0"
|
|
||||||
onClick={handleRemove(index)}
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="sm" className="w-9 p-0">
|
|
||||||
<ChevronsUpDown className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Toggle</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
</div>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<FormSet nodeId={nodeId} index={index}></FormSet>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Button type={'button'} onClick={handleAdd}>
|
|
||||||
<PlusOutlined />
|
|
||||||
{t('addCategory')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(DynamicCategorize);
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { Plus, X } from 'lucide-react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type DynamicExampleProps = { name: string };
|
|
||||||
|
|
||||||
const DynamicExample = ({ name }: DynamicExampleProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
|
||||||
name: name,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('flow.msgTip')}>{t('flow.examples')}</FormLabel>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<div key={field.id} className="flex items-start gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.value`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormControl>
|
|
||||||
<Textarea {...field}> </Textarea>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{index === 0 ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => append({ value: '' })}
|
|
||||||
>
|
|
||||||
<Plus />
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
|
||||||
<X />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(DynamicExample);
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { LargeModelFormField } from '@/components/large-model-form-field';
|
|
||||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
|
||||||
import { Form } from '@/components/ui/form';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { initialCategorizeValues } from '../../constant';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
import DynamicCategorize from './dynamic-categorize';
|
|
||||||
import { useCreateCategorizeFormSchema } from './use-form-schema';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
|
||||||
|
|
||||||
const outputList = buildOutputList(initialCategorizeValues.outputs);
|
|
||||||
|
|
||||||
function CategorizeForm({ node }: INextOperatorForm) {
|
|
||||||
const values = useValues(node);
|
|
||||||
|
|
||||||
const FormSchema = useCreateCategorizeFormSchema();
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: values,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<QueryVariable></QueryVariable>
|
|
||||||
<LargeModelFormField></LargeModelFormField>
|
|
||||||
</FormContainer>
|
|
||||||
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
|
|
||||||
<DynamicCategorize nodeId={node?.id}></DynamicCategorize>
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</FormWrapper>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(CategorizeForm);
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export function useCreateCategorizeFormSchema() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
query: z.string().optional(),
|
|
||||||
parameter: z.string().optional(),
|
|
||||||
...LlmSettingSchema,
|
|
||||||
message_history_window_size: z.coerce.number(),
|
|
||||||
items: z.array(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
name: z.string().min(1, t('flow.nameMessage')).trim(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
uuid: z.string(),
|
|
||||||
examples: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return FormSchema;
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import { ModelVariableType } from '@/constants/knowledge';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty, isPlainObject } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
const defaultValues = {
|
|
||||||
parameter: ModelVariableType.Precise,
|
|
||||||
message_history_window_size: 1,
|
|
||||||
temperatureEnabled: true,
|
|
||||||
topPEnabled: true,
|
|
||||||
presencePenaltyEnabled: true,
|
|
||||||
frequencyPenaltyEnabled: true,
|
|
||||||
maxTokensEnabled: true,
|
|
||||||
items: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return defaultValues;
|
|
||||||
}
|
|
||||||
if (isPlainObject(formData)) {
|
|
||||||
// const nextValues = {
|
|
||||||
// ...omit(formData, 'category_description'),
|
|
||||||
// items,
|
|
||||||
// };
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
}
|
|
||||||
}, [node]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn<any>) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
|
||||||
if (id) {
|
|
||||||
values = form?.getValues();
|
|
||||||
|
|
||||||
updateNodeForm(id, { ...values, items: values.items?.slice() || [] });
|
|
||||||
}
|
|
||||||
}, [id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import NumberInput from '@/components/originui/number-input';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useForm, useFormContext } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { initialChunkerValues } from '../../constant';
|
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { GoogleCountryOptions, GoogleLanguageOptions } from '../../options';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { ApiKeyField } from '../components/api-key-field';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
|
|
||||||
const outputList = buildOutputList(initialChunkerValues.outputs);
|
|
||||||
|
|
||||||
export const GoogleFormPartialSchema = {
|
|
||||||
api_key: z.string(),
|
|
||||||
country: z.string(),
|
|
||||||
language: z.string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FormSchema = z.object({
|
|
||||||
...GoogleFormPartialSchema,
|
|
||||||
q: z.string(),
|
|
||||||
start: z.number(),
|
|
||||||
num: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function GoogleFormWidgets() {
|
|
||||||
const form = useFormContext();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`country`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('country')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch
|
|
||||||
{...field}
|
|
||||||
options={GoogleCountryOptions}
|
|
||||||
></SelectWithSearch>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`language`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>{t('language')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch
|
|
||||||
{...field}
|
|
||||||
options={GoogleLanguageOptions}
|
|
||||||
></SelectWithSearch>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChunkerForm = ({ node }: INextOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const defaultValues = useFormValues(initialChunkerValues, node);
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
defaultValues,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<QueryVariable name="q"></QueryVariable>
|
|
||||||
</FormContainer>
|
|
||||||
<FormContainer>
|
|
||||||
<ApiKeyField placeholder={t('apiKeyPlaceholder')}></ApiKeyField>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`start`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flowStart')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NumberInput {...field} className="w-full"></NumberInput>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`num`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flowNum')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NumberInput {...field} className="w-full"></NumberInput>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<GoogleFormWidgets></GoogleFormWidgets>
|
|
||||||
</FormContainer>
|
|
||||||
</FormWrapper>
|
|
||||||
<div className="p-5">
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ChunkerForm);
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
import Editor, { loader } from '@monaco-editor/react';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
|
|
||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { ProgrammingLanguage } from '@/constants/agent';
|
|
||||||
import { ICodeForm } from '@/interfaces/database/agent';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import {
|
|
||||||
DynamicInputVariable,
|
|
||||||
TypeOptions,
|
|
||||||
VariableTitle,
|
|
||||||
} from './next-variable';
|
|
||||||
import { FormSchema, FormSchemaType } from './schema';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import {
|
|
||||||
useHandleLanguageChange,
|
|
||||||
useWatchFormChange,
|
|
||||||
} from './use-watch-change';
|
|
||||||
|
|
||||||
loader.config({ paths: { vs: '/vs' } });
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
ProgrammingLanguage.Python,
|
|
||||||
ProgrammingLanguage.Javascript,
|
|
||||||
].map((x) => ({ value: x, label: x }));
|
|
||||||
|
|
||||||
const DynamicFieldName = 'outputs';
|
|
||||||
|
|
||||||
function CodeForm({ node }: INextOperatorForm) {
|
|
||||||
const formData = node?.data.form as ICodeForm;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const values = useValues(node);
|
|
||||||
const isDarkTheme = useIsDarkTheme();
|
|
||||||
|
|
||||||
const form = useForm<FormSchemaType>({
|
|
||||||
defaultValues: values,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
const handleLanguageChange = useHandleLanguageChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<DynamicInputVariable
|
|
||||||
node={node}
|
|
||||||
title={t('flow.input')}
|
|
||||||
isOutputs={false}
|
|
||||||
></DynamicInputVariable>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="script"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel className="flex items-center justify-between">
|
|
||||||
Code
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="lang"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
{...field}
|
|
||||||
onChange={(val) => {
|
|
||||||
field.onChange(val);
|
|
||||||
handleLanguageChange(val);
|
|
||||||
}}
|
|
||||||
options={options}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Editor
|
|
||||||
height={300}
|
|
||||||
theme={isDarkTheme ? 'vs-dark' : 'vs'}
|
|
||||||
language={formData.lang}
|
|
||||||
options={{
|
|
||||||
minimap: { enabled: false },
|
|
||||||
automaticLayout: true,
|
|
||||||
}}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{formData.lang === ProgrammingLanguage.Python ? (
|
|
||||||
<DynamicInputVariable
|
|
||||||
node={node}
|
|
||||||
title={'Return Values'}
|
|
||||||
name={DynamicFieldName}
|
|
||||||
isOutputs
|
|
||||||
></DynamicInputVariable>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<VariableTitle title={'Return Values'}></VariableTitle>
|
|
||||||
<FormContainer className="space-y-5">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${DynamicFieldName}.name`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder={t('common.pleaseInput')}
|
|
||||||
></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${DynamicFieldName}.type`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormLabel>Type</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={TypeOptions}
|
|
||||||
{...field}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormContainer>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</FormWrapper>
|
|
||||||
<div className="p-5">
|
|
||||||
<Output list={buildOutputList(formData.outputs)}></Output>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(CodeForm);
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { BlurInput } from '@/components/ui/input';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { ReactNode } from 'react';
|
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
name?: string;
|
|
||||||
isOutputs: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TypeOptions = [
|
|
||||||
'String',
|
|
||||||
'Number',
|
|
||||||
'Boolean',
|
|
||||||
'Array<String>',
|
|
||||||
'Array<Number>',
|
|
||||||
'Object',
|
|
||||||
].map((x) => ({ label: x, value: x }));
|
|
||||||
|
|
||||||
export function DynamicVariableForm({ name = 'arguments', isOutputs }: IProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
|
||||||
name: name,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
const nextOptions = useBuildQueryVariableOptions();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-5">
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
const typeField = `${name}.${index}.name`;
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex w-full items-center gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={typeField}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1 overflow-hidden">
|
|
||||||
<FormControl>
|
|
||||||
<BlurInput
|
|
||||||
{...field}
|
|
||||||
placeholder={t('common.pleaseInput')}
|
|
||||||
></BlurInput>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Separator className="w-3 text-text-secondary" />
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.type`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1 overflow-hidden">
|
|
||||||
<FormControl>
|
|
||||||
{isOutputs ? (
|
|
||||||
<RAGFlowSelect
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={TypeOptions}
|
|
||||||
{...field}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
) : (
|
|
||||||
<SelectWithSearch
|
|
||||||
options={nextOptions}
|
|
||||||
{...field}
|
|
||||||
></SelectWithSearch>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
|
||||||
<X className="text-text-sub-title-invert " />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<BlockButton onClick={() => append({ name: '', type: undefined })}>
|
|
||||||
{t('flow.addVariable')}
|
|
||||||
</BlockButton>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VariableTitle({ title }: { title: ReactNode }) {
|
|
||||||
return <div className="font-medium text-text-primary pb-2">{title}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DynamicInputVariable({
|
|
||||||
node,
|
|
||||||
name,
|
|
||||||
title,
|
|
||||||
isOutputs = false,
|
|
||||||
}: IProps & { title: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<VariableTitle title={title}></VariableTitle>
|
|
||||||
<FormContainer>
|
|
||||||
<DynamicVariableForm
|
|
||||||
node={node}
|
|
||||||
name={name}
|
|
||||||
isOutputs={isOutputs}
|
|
||||||
></DynamicVariableForm>
|
|
||||||
</FormContainer>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import { ProgrammingLanguage } from '@/constants/agent';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export const FormSchema = z.object({
|
|
||||||
lang: z.enum([ProgrammingLanguage.Python, ProgrammingLanguage.Javascript]),
|
|
||||||
script: z.string(),
|
|
||||||
arguments: z.array(z.object({ name: z.string(), type: z.string() })),
|
|
||||||
outputs: z.union([
|
|
||||||
z.array(z.object({ name: z.string(), type: z.string() })).optional(),
|
|
||||||
z.object({ name: z.string(), type: z.string() }),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type FormSchemaType = z.infer<typeof FormSchema>;
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { ProgrammingLanguage } from '@/constants/agent';
|
|
||||||
import { ICodeForm } from '@/interfaces/database/agent';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { initialCodeValues } from '../../constant';
|
|
||||||
|
|
||||||
function convertToArray(args: Record<string, string>) {
|
|
||||||
return Object.entries(args).map(([key, value]) => ({
|
|
||||||
name: key,
|
|
||||||
type: value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutputsFormType = { name: string; type: string };
|
|
||||||
|
|
||||||
function convertOutputsToArray({ lang, outputs = {} }: ICodeForm) {
|
|
||||||
if (lang === ProgrammingLanguage.Python) {
|
|
||||||
return Object.entries(outputs).map(([key, val]) => ({
|
|
||||||
name: key,
|
|
||||||
type: val.type,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return Object.entries(outputs).reduce<OutputsFormType>((pre, [key, val]) => {
|
|
||||||
pre.name = key;
|
|
||||||
pre.type = val.type;
|
|
||||||
return pre;
|
|
||||||
}, {} as OutputsFormType);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return initialCodeValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...formData,
|
|
||||||
arguments: convertToArray(formData.arguments),
|
|
||||||
outputs: convertOutputsToArray(formData),
|
|
||||||
};
|
|
||||||
}, [node?.data?.form]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent';
|
|
||||||
import { ICodeForm } from '@/interfaces/database/agent';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import { FormSchemaType } from './schema';
|
|
||||||
|
|
||||||
function convertToObject(list: FormSchemaType['arguments'] = []) {
|
|
||||||
return list.reduce<Record<string, string>>((pre, cur) => {
|
|
||||||
pre[cur.name] = cur.type;
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
type ArrayOutputs = Extract<FormSchemaType['outputs'], Array<any>>;
|
|
||||||
|
|
||||||
type ObjectOutputs = Exclude<FormSchemaType['outputs'], Array<any>>;
|
|
||||||
|
|
||||||
function convertOutputsToObject({ lang, outputs }: FormSchemaType) {
|
|
||||||
if (lang === ProgrammingLanguage.Python) {
|
|
||||||
return (outputs as ArrayOutputs).reduce<ICodeForm['outputs']>(
|
|
||||||
(pre, cur) => {
|
|
||||||
pre[cur.name] = {
|
|
||||||
value: '',
|
|
||||||
type: cur.type,
|
|
||||||
};
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const outputsObject = outputs as ObjectOutputs;
|
|
||||||
if (isEmpty(outputsObject)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
[outputsObject.name]: {
|
|
||||||
value: '',
|
|
||||||
type: outputsObject.type,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWatchFormChange(
|
|
||||||
id?: string,
|
|
||||||
form?: UseFormReturn<FormSchemaType>,
|
|
||||||
) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
|
||||||
if (id) {
|
|
||||||
values = form?.getValues() || {};
|
|
||||||
let nextValues: any = {
|
|
||||||
...values,
|
|
||||||
arguments: convertToObject(
|
|
||||||
values?.arguments as FormSchemaType['arguments'],
|
|
||||||
),
|
|
||||||
outputs: convertOutputsToObject(values as FormSchemaType),
|
|
||||||
};
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useHandleLanguageChange(
|
|
||||||
id?: string,
|
|
||||||
form?: UseFormReturn<FormSchemaType>,
|
|
||||||
) {
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
const handleLanguageChange = useCallback(
|
|
||||||
(lang: string) => {
|
|
||||||
if (id) {
|
|
||||||
const script = CodeTemplateStrMap[lang as ProgrammingLanguage];
|
|
||||||
form?.setValue('script', script);
|
|
||||||
form?.setValue(
|
|
||||||
'outputs',
|
|
||||||
(lang === ProgrammingLanguage.Python
|
|
||||||
? []
|
|
||||||
: {}) as FormSchemaType['outputs'],
|
|
||||||
);
|
|
||||||
updateNodeForm(id, script, ['script']);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[form, id, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
return handleLanguageChange;
|
|
||||||
}
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { useForm, useFormContext } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { initialCrawlerValues } from '../../constant';
|
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { CrawlerResultOptions } from '../../options';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
|
|
||||||
export function CrawlerProxyFormField() {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="proxy"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('proxy')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="like: http://127.0.0.1:8888" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CrawlerExtractTypeFormField() {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const form = useFormContext();
|
|
||||||
const crawlerResultOptions = useMemo(() => {
|
|
||||||
return CrawlerResultOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`crawlerResultOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="extract_type"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('extractType')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch {...field} options={crawlerResultOptions} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CrawlerFormSchema = {
|
|
||||||
proxy: z.string().url(),
|
|
||||||
extract_type: z.string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
query: z.string().optional(),
|
|
||||||
...CrawlerFormSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
function CrawlerForm({ node }: INextOperatorForm) {
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
defaultValues: initialCrawlerValues,
|
|
||||||
mode: 'onChange',
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
className="space-y-6 p-4"
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<QueryVariable></QueryVariable>
|
|
||||||
<CrawlerProxyFormField></CrawlerProxyFormField>
|
|
||||||
<CrawlerExtractTypeFormField></CrawlerExtractTypeFormField>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(CrawlerForm);
|
|
||||||
@ -1,161 +0,0 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { ReactNode } from 'react';
|
|
||||||
import { useForm, useFormContext } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { initialEmailValues } from '../../constant';
|
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
|
||||||
|
|
||||||
interface InputFormFieldProps {
|
|
||||||
name: string;
|
|
||||||
label: ReactNode;
|
|
||||||
type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function InputFormField({ name, label, type }: InputFormFieldProps) {
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={name}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} type={type}></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PromptFormField({ name, label }: InputFormFieldProps) {
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={name}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<PromptEditor
|
|
||||||
{...field}
|
|
||||||
showToolbar={false}
|
|
||||||
multiLine={false}
|
|
||||||
></PromptEditor>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export function EmailFormWidgets() {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<InputFormField
|
|
||||||
name="smtp_server"
|
|
||||||
label={t('smtpServer')}
|
|
||||||
></InputFormField>
|
|
||||||
<InputFormField
|
|
||||||
name="smtp_port"
|
|
||||||
label={t('smtpPort')}
|
|
||||||
type="number"
|
|
||||||
></InputFormField>
|
|
||||||
<InputFormField name="email" label={t('senderEmail')}></InputFormField>
|
|
||||||
<InputFormField
|
|
||||||
name="password"
|
|
||||||
label={t('authCode')}
|
|
||||||
type="password"
|
|
||||||
></InputFormField>
|
|
||||||
<InputFormField
|
|
||||||
name="sender_name"
|
|
||||||
label={t('senderName')}
|
|
||||||
></InputFormField>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EmailFormPartialSchema = {
|
|
||||||
smtp_server: z.string(),
|
|
||||||
smtp_port: z.number(),
|
|
||||||
email: z.string(),
|
|
||||||
password: z.string(),
|
|
||||||
sender_name: z.string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
to_email: z.string(),
|
|
||||||
cc_email: z.string(),
|
|
||||||
content: z.string(),
|
|
||||||
subject: z.string(),
|
|
||||||
...EmailFormPartialSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputList = buildOutputList(initialEmailValues.outputs);
|
|
||||||
|
|
||||||
const EmailForm = ({ node }: INextOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const defaultValues = useFormValues(initialEmailValues, node);
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
defaultValues,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<PromptFormField
|
|
||||||
name="to_email"
|
|
||||||
label={t('toEmail')}
|
|
||||||
></PromptFormField>
|
|
||||||
<PromptFormField
|
|
||||||
name="cc_email"
|
|
||||||
label={t('ccEmail')}
|
|
||||||
></PromptFormField>
|
|
||||||
<PromptFormField
|
|
||||||
name="content"
|
|
||||||
label={t('content')}
|
|
||||||
></PromptFormField>
|
|
||||||
<PromptFormField
|
|
||||||
name="subject"
|
|
||||||
label={t('subject')}
|
|
||||||
></PromptFormField>
|
|
||||||
<EmailFormWidgets></EmailFormWidgets>
|
|
||||||
</FormContainer>
|
|
||||||
</FormWrapper>
|
|
||||||
<div className="p-5">
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmailForm;
|
|
||||||
@ -1,167 +0,0 @@
|
|||||||
import NumberInput from '@/components/originui/number-input';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import { ButtonLoading } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useForm, useFormContext } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { initialExeSqlValues } from '../../constant';
|
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { ExeSQLOptions } from '../../options';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
import { FormSchema, useSubmitForm } from './use-submit-form';
|
|
||||||
|
|
||||||
const outputList = buildOutputList(initialExeSqlValues.outputs);
|
|
||||||
|
|
||||||
export function ExeSQLFormWidgets({ loading }: { loading: boolean }) {
|
|
||||||
const form = useFormContext();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="db_type"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('dbType')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch
|
|
||||||
{...field}
|
|
||||||
options={ExeSQLOptions}
|
|
||||||
></SelectWithSearch>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="database"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('database')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field}></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="username"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('username')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field}></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="host"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('host')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field}></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="port"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('port')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NumberInput {...field} className="w-full"></NumberInput>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('password')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} type="password"></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="max_records"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('maxRecords')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NumberInput {...field} className="w-full"></NumberInput>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<ButtonLoading loading={loading} type="submit">
|
|
||||||
{t('test')}
|
|
||||||
</ButtonLoading>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ExeSQLForm({ node }: INextOperatorForm) {
|
|
||||||
const defaultValues = useFormValues(initialExeSqlValues, node);
|
|
||||||
|
|
||||||
const { onSubmit, loading } = useSubmitForm();
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
defaultValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper onSubmit={form.handleSubmit(onSubmit)}>
|
|
||||||
<QueryVariable name="sql"></QueryVariable>
|
|
||||||
<ExeSQLFormWidgets loading={loading}></ExeSQLFormWidgets>
|
|
||||||
</FormWrapper>
|
|
||||||
<div className="p-5">
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(ExeSQLForm);
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { useTestDbConnect } from '@/hooks/use-agent-request';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export const ExeSQLFormSchema = {
|
|
||||||
db_type: z.string().min(1),
|
|
||||||
database: z.string().min(1),
|
|
||||||
username: z.string().min(1),
|
|
||||||
host: z.string().min(1),
|
|
||||||
port: z.number(),
|
|
||||||
password: z.string().min(1),
|
|
||||||
max_records: z.number(),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FormSchema = z.object({
|
|
||||||
sql: z.string().optional(),
|
|
||||||
...ExeSQLFormSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useSubmitForm() {
|
|
||||||
const { testDbConnect, loading } = useTestDbConnect();
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
|
||||||
async (data: z.infer<typeof FormSchema>) => {
|
|
||||||
testDbConnect(data);
|
|
||||||
},
|
|
||||||
[testDbConnect],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { loading, onSubmit };
|
|
||||||
}
|
|
||||||
@ -1,13 +1,14 @@
|
|||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
import { BlockButton, Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
import { Form } from '@/components/ui/form';
|
import { Form, FormLabel } from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { X } from 'lucide-react';
|
import { Plus, Trash2 } from 'lucide-react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { initialHierarchicalMergerValues } from '../../constant';
|
import { initialHierarchicalMergerValues } from '../../constant';
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
@ -39,7 +40,24 @@ export const FormSchema = z.object({
|
|||||||
hierarchy: z.number(),
|
hierarchy: z.number(),
|
||||||
levels: z.array(
|
levels: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
expressions: z.array(z.object({ expression: z.string() })),
|
expressions: z.array(
|
||||||
|
z.object({
|
||||||
|
expression: z.string().refine(
|
||||||
|
(val) => {
|
||||||
|
try {
|
||||||
|
// Try converting the string to a RegExp
|
||||||
|
new RegExp(val);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Must be a valid regular expression string',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -50,13 +68,16 @@ type RegularExpressionsProps = {
|
|||||||
index: number;
|
index: number;
|
||||||
parentName: string;
|
parentName: string;
|
||||||
removeParent: (index: number) => void;
|
removeParent: (index: number) => void;
|
||||||
|
isLatest: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RegularExpressions({
|
export function RegularExpressions({
|
||||||
index,
|
index,
|
||||||
parentName,
|
parentName,
|
||||||
|
isLatest,
|
||||||
removeParent,
|
removeParent,
|
||||||
}: RegularExpressionsProps) {
|
}: RegularExpressionsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
|
|
||||||
const name = `${parentName}.${index}.expressions`;
|
const name = `${parentName}.${index}.expressions`;
|
||||||
@ -69,16 +90,21 @@ export function RegularExpressions({
|
|||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex-row justify-between items-center">
|
<CardHeader className="flex-row justify-between items-center">
|
||||||
<CardTitle>H{index + 1}</CardTitle>
|
<span>H{index + 1}</span>
|
||||||
<Button
|
{isLatest && (
|
||||||
type="button"
|
<Button
|
||||||
variant={'ghost'}
|
type="button"
|
||||||
onClick={() => removeParent(index)}
|
variant={'ghost'}
|
||||||
>
|
onClick={() => removeParent(index)}
|
||||||
<X />
|
>
|
||||||
</Button>
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
<FormLabel required className="mb-2 text-text-secondary">
|
||||||
|
{t('dataflow.regularExpressions')}
|
||||||
|
</FormLabel>
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<div key={field.id} className="flex items-center gap-2">
|
<div key={field.id} className="flex items-center gap-2">
|
||||||
@ -91,33 +117,38 @@ export function RegularExpressions({
|
|||||||
<Input className="!m-0"></Input>
|
<Input className="!m-0"></Input>
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{index === 0 ? (
|
||||||
type="button"
|
<Button
|
||||||
variant={'ghost'}
|
onClick={() => append({ expression: '' })}
|
||||||
onClick={() => remove(index)}
|
variant={'ghost'}
|
||||||
>
|
>
|
||||||
<X />
|
<Plus></Plus>
|
||||||
</Button>
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={'ghost'}
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
<BlockButton
|
|
||||||
onClick={() => append({ expression: '' })}
|
|
||||||
className="mt-6"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</BlockButton>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const HierarchicalMergerForm = ({ node }: INextOperatorForm) => {
|
const HierarchicalMergerForm = ({ node }: INextOperatorForm) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const defaultValues = useFormValues(initialHierarchicalMergerValues, node);
|
const defaultValues = useFormValues(initialHierarchicalMergerValues, node);
|
||||||
|
|
||||||
const form = useForm<HierarchicalMergerFormSchemaType>({
|
const form = useForm<HierarchicalMergerFormSchemaType>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
|
mode: 'onChange',
|
||||||
});
|
});
|
||||||
|
|
||||||
const name = 'levels';
|
const name = 'levels';
|
||||||
@ -132,23 +163,28 @@ const HierarchicalMergerForm = ({ node }: INextOperatorForm) => {
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<RAGFlowFormItem name={'hierarchy'} label={'hierarchy'}>
|
<RAGFlowFormItem name={'hierarchy'} label={t('dataflow.hierarchy')}>
|
||||||
<SelectWithSearch options={HierarchyOptions}></SelectWithSearch>
|
<SelectWithSearch options={HierarchyOptions}></SelectWithSearch>
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<div key={field.id} className="flex items-center gap-2">
|
<div key={field.id} className="flex items-center">
|
||||||
<div className="space-y-2 flex-1">
|
<div className="flex-1">
|
||||||
<RegularExpressions
|
<RegularExpressions
|
||||||
parentName={name}
|
parentName={name}
|
||||||
index={index}
|
index={index}
|
||||||
removeParent={remove}
|
removeParent={remove}
|
||||||
|
isLatest={index === fields.length - 1}
|
||||||
></RegularExpressions>
|
></RegularExpressions>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<BlockButton onClick={() => append({ expressions: [] })}>
|
{fields.length < 5 && (
|
||||||
Add
|
<BlockButton
|
||||||
</BlockButton>
|
onClick={() => append({ expressions: [{ expression: '' }] })}
|
||||||
|
>
|
||||||
|
{t('common.add')}
|
||||||
|
</BlockButton>
|
||||||
|
)}
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<Output list={outputList}></Output>
|
<Output list={outputList}></Output>
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
import get from 'lodash/get';
|
|
||||||
import {
|
|
||||||
ChangeEventHandler,
|
|
||||||
MouseEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
} from 'react';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { IGenerateParameter, IInvokeVariable } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export const useHandleOperateParameters = (nodeId: string) => {
|
|
||||||
const { getNode, updateNodeForm } = useGraphStore((state) => state);
|
|
||||||
const node = getNode(nodeId);
|
|
||||||
const dataSource: IGenerateParameter[] = useMemo(
|
|
||||||
() => get(node, 'data.form.variables', []) as IGenerateParameter[],
|
|
||||||
[node],
|
|
||||||
);
|
|
||||||
|
|
||||||
const changeValue = useCallback(
|
|
||||||
(row: IInvokeVariable, field: string, value: string) => {
|
|
||||||
const newData = [...dataSource];
|
|
||||||
const index = newData.findIndex((item) => row.id === item.id);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
[field]: value,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeForm(nodeId, { variables: newData });
|
|
||||||
},
|
|
||||||
[dataSource, nodeId, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleComponentIdChange = useCallback(
|
|
||||||
(row: IInvokeVariable) => (value: string) => {
|
|
||||||
changeValue(row, 'component_id', value);
|
|
||||||
},
|
|
||||||
[changeValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleValueChange = useCallback(
|
|
||||||
(row: IInvokeVariable): ChangeEventHandler<HTMLInputElement> =>
|
|
||||||
(e) => {
|
|
||||||
changeValue(row, 'value', e.target.value);
|
|
||||||
},
|
|
||||||
[changeValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemove = useCallback(
|
|
||||||
(id?: string) => () => {
|
|
||||||
const newData = dataSource.filter((item) => item.id !== id);
|
|
||||||
updateNodeForm(nodeId, { variables: newData });
|
|
||||||
},
|
|
||||||
[updateNodeForm, nodeId, dataSource],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAdd: MouseEventHandler = useCallback(
|
|
||||||
(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
updateNodeForm(nodeId, {
|
|
||||||
variables: [
|
|
||||||
...dataSource,
|
|
||||||
{
|
|
||||||
id: uuid(),
|
|
||||||
key: '',
|
|
||||||
component_id: undefined,
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[dataSource, nodeId, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSave = (row: IGenerateParameter) => {
|
|
||||||
const newData = [...dataSource];
|
|
||||||
const index = newData.findIndex((item) => row.id === item.id);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
...row,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeForm(nodeId, { variables: newData });
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleAdd,
|
|
||||||
handleRemove,
|
|
||||||
handleComponentIdChange,
|
|
||||||
handleValueChange,
|
|
||||||
handleSave,
|
|
||||||
dataSource,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,226 +0,0 @@
|
|||||||
import { Collapse } from '@/components/collapse';
|
|
||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import NumberInput from '@/components/originui/number-input';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import Editor, { loader } from '@monaco-editor/react';
|
|
||||||
import { Plus } from 'lucide-react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { initialInvokeValues } from '../../constant';
|
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { FormSchema, FormSchemaType } from './schema';
|
|
||||||
import { useEditVariableRecord } from './use-edit-variable';
|
|
||||||
import { VariableDialog } from './variable-dialog';
|
|
||||||
import { VariableTable } from './variable-table';
|
|
||||||
|
|
||||||
loader.config({ paths: { vs: '/vs' } });
|
|
||||||
|
|
||||||
enum Method {
|
|
||||||
GET = 'GET',
|
|
||||||
POST = 'POST',
|
|
||||||
PUT = 'PUT',
|
|
||||||
}
|
|
||||||
|
|
||||||
const MethodOptions = [Method.GET, Method.POST, Method.PUT].map((x) => ({
|
|
||||||
label: x,
|
|
||||||
value: x,
|
|
||||||
}));
|
|
||||||
|
|
||||||
interface TimeoutInputProps {
|
|
||||||
value?: number;
|
|
||||||
onChange?: (value: number | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<NumberInput value={value} onChange={onChange} /> {t('flow.seconds')}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const outputList = buildOutputList(initialInvokeValues.outputs);
|
|
||||||
|
|
||||||
function InvokeForm({ node }: INextOperatorForm) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const defaultValues = useFormValues(initialInvokeValues, node);
|
|
||||||
|
|
||||||
const form = useForm<FormSchemaType>({
|
|
||||||
defaultValues,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
mode: 'onChange',
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
showModal,
|
|
||||||
ok,
|
|
||||||
currentRecord,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
handleDeleteRecord,
|
|
||||||
} = useEditVariableRecord({
|
|
||||||
form,
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
|
|
||||||
const variables = useWatch({ control: form.control, name: 'variables' });
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="url"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.url')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} placeholder="http://" />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="method"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.method')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch {...field} options={MethodOptions} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="timeout"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.timeout')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<TimeoutInput {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="headers"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.headers')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Editor
|
|
||||||
height={200}
|
|
||||||
defaultLanguage="json"
|
|
||||||
theme="vs-dark"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="proxy"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.proxy')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="clean_html"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('flow.cleanHtmlTip')}>
|
|
||||||
{t('flow.cleanHtml')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
checked={field.value}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* Create a hidden field to make Form instance record this */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'variables'}
|
|
||||||
render={() => <div></div>}
|
|
||||||
/>
|
|
||||||
</FormContainer>
|
|
||||||
<Collapse
|
|
||||||
title={<div>{t('flow.parameter')}</div>}
|
|
||||||
rightContent={
|
|
||||||
<Button
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
showModal();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<VariableTable
|
|
||||||
data={variables}
|
|
||||||
showModal={showModal}
|
|
||||||
deleteRecord={handleDeleteRecord}
|
|
||||||
nodeId={node?.id}
|
|
||||||
></VariableTable>
|
|
||||||
</Collapse>
|
|
||||||
{visible && (
|
|
||||||
<VariableDialog
|
|
||||||
hideModal={hideModal}
|
|
||||||
initialValue={currentRecord}
|
|
||||||
otherThanCurrentQuery={otherThanCurrentQuery}
|
|
||||||
submit={ok}
|
|
||||||
></VariableDialog>
|
|
||||||
)}
|
|
||||||
</FormWrapper>
|
|
||||||
<div className="p-5">
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(InvokeForm);
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export const VariableFormSchema = z.object({
|
|
||||||
key: z.string(),
|
|
||||||
ref: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const FormSchema = z.object({
|
|
||||||
url: z.string().url(),
|
|
||||||
method: z.string(),
|
|
||||||
timeout: z.number(),
|
|
||||||
headers: z.string(),
|
|
||||||
proxy: z.string().url(),
|
|
||||||
clean_html: z.boolean(),
|
|
||||||
variables: z.array(VariableFormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type FormSchemaType = z.infer<typeof FormSchema>;
|
|
||||||
|
|
||||||
export type VariableFormSchemaType = z.infer<typeof VariableFormSchema>;
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { FormSchemaType, VariableFormSchemaType } from './schema';
|
|
||||||
|
|
||||||
export const useEditVariableRecord = ({
|
|
||||||
form,
|
|
||||||
}: INextOperatorForm & { form: UseFormReturn<FormSchemaType> }) => {
|
|
||||||
const { setRecord, currentRecord } =
|
|
||||||
useSetSelectedRecord<VariableFormSchemaType>();
|
|
||||||
|
|
||||||
const { visible, hideModal, showModal } = useSetModalState();
|
|
||||||
const [index, setIndex] = useState(-1);
|
|
||||||
const variables = useWatch({
|
|
||||||
control: form.control,
|
|
||||||
name: 'variables',
|
|
||||||
});
|
|
||||||
|
|
||||||
const otherThanCurrentQuery = useMemo(() => {
|
|
||||||
return variables.filter((item, idx) => idx !== index);
|
|
||||||
}, [index, variables]);
|
|
||||||
|
|
||||||
const handleEditRecord = useCallback(
|
|
||||||
(record: VariableFormSchemaType) => {
|
|
||||||
const variables = form?.getValues('variables') || [];
|
|
||||||
|
|
||||||
const nextVaribales =
|
|
||||||
index > -1
|
|
||||||
? variables.toSpliced(index, 1, record)
|
|
||||||
: [...variables, record];
|
|
||||||
|
|
||||||
form.setValue('variables', nextVaribales);
|
|
||||||
|
|
||||||
hideModal();
|
|
||||||
},
|
|
||||||
[form, hideModal, index],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleShowModal = useCallback(
|
|
||||||
(idx?: number, record?: VariableFormSchemaType) => {
|
|
||||||
setIndex(idx ?? -1);
|
|
||||||
setRecord(record ?? ({} as VariableFormSchemaType));
|
|
||||||
showModal();
|
|
||||||
},
|
|
||||||
[setRecord, showModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeleteRecord = useCallback(
|
|
||||||
(idx: number) => {
|
|
||||||
const variables = form?.getValues('variables') || [];
|
|
||||||
const nextVariables = variables.filter((item, index) => index !== idx);
|
|
||||||
|
|
||||||
form.setValue('variables', nextVariables);
|
|
||||||
},
|
|
||||||
[form],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: handleEditRecord,
|
|
||||||
currentRecord,
|
|
||||||
setRecord,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
showModal: handleShowModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
handleDeleteRecord,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
import { VariableFormSchemaType } from './schema';
|
|
||||||
|
|
||||||
type ModalFormProps = {
|
|
||||||
initialValue: VariableFormSchemaType;
|
|
||||||
otherThanCurrentQuery: VariableFormSchemaType[];
|
|
||||||
submit(values: any): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormId = 'BeginParameterForm';
|
|
||||||
|
|
||||||
function VariableForm({
|
|
||||||
initialValue,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
submit,
|
|
||||||
}: ModalFormProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const FormSchema = z.object({
|
|
||||||
key: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1)
|
|
||||||
.refine(
|
|
||||||
(value) =>
|
|
||||||
!value || !otherThanCurrentQuery.some((x) => x.key === value),
|
|
||||||
{ message: 'The key cannot be repeated!' },
|
|
||||||
),
|
|
||||||
ref: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
mode: 'onChange',
|
|
||||||
defaultValues: {
|
|
||||||
key: '',
|
|
||||||
value: '',
|
|
||||||
ref: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isEmpty(initialValue)) {
|
|
||||||
form.reset(initialValue);
|
|
||||||
}
|
|
||||||
}, [form, initialValue]);
|
|
||||||
|
|
||||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
|
||||||
submit(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
id={FormId}
|
|
||||||
className="space-y-5"
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
name="key"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.key')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} autoComplete="off" />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<QueryVariable name="ref" label={t('flow.ref')}></QueryVariable>
|
|
||||||
<FormField
|
|
||||||
name="value"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.value')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VariableDialog({
|
|
||||||
initialValue,
|
|
||||||
hideModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
submit,
|
|
||||||
}: ModalFormProps & IModalProps<VariableFormSchemaType>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open onOpenChange={hideModal}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{t('flow.variableSettings')}</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<VariableForm
|
|
||||||
initialValue={initialValue}
|
|
||||||
otherThanCurrentQuery={otherThanCurrentQuery}
|
|
||||||
submit={submit}
|
|
||||||
></VariableForm>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button type="submit" form={FormId}>
|
|
||||||
{t('modal.okText')}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from '@tanstack/react-table';
|
|
||||||
import { Pencil, Trash2 } from 'lucide-react';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { TableEmpty } from '@/components/table-skeleton';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table';
|
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@/components/ui/tooltip';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
|
|
||||||
import { VariableFormSchemaType } from './schema';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
data: VariableFormSchemaType[];
|
|
||||||
deleteRecord(index: number): void;
|
|
||||||
showModal(index: number, record: VariableFormSchemaType): void;
|
|
||||||
nodeId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VariableTable({
|
|
||||||
data = [],
|
|
||||||
deleteRecord,
|
|
||||||
showModal,
|
|
||||||
nodeId,
|
|
||||||
}: IProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const getLabel = useGetVariableLabelByValue(nodeId!);
|
|
||||||
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
|
|
||||||
const columns: ColumnDef<VariableFormSchemaType>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: 'key',
|
|
||||||
header: t('flow.key'),
|
|
||||||
meta: { cellClassName: 'max-w-30' },
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const key: string = row.getValue('key');
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div className="truncate">{key}</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{key}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'ref',
|
|
||||||
header: t('flow.ref'),
|
|
||||||
meta: { cellClassName: 'max-w-30' },
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const ref: string = row.getValue('ref');
|
|
||||||
const label = getLabel(ref);
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div className="truncate">{label}</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{label}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'value',
|
|
||||||
header: t('flow.value'),
|
|
||||||
cell: ({ row }) => <div>{row.getValue('value')}</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'actions',
|
|
||||||
enableHiding: false,
|
|
||||||
header: t('common.action'),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const record = row.original;
|
|
||||||
const idx = row.index;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
className="bg-transparent text-foreground hover:bg-muted-foreground hover:text-foreground"
|
|
||||||
onClick={() => showModal(idx, record)}
|
|
||||||
>
|
|
||||||
<Pencil />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="bg-transparent text-foreground hover:bg-muted-foreground hover:text-foreground"
|
|
||||||
onClick={() => deleteRecord(idx)}
|
|
||||||
>
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="rounded-md border">
|
|
||||||
<Table rootClassName="rounded-md">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext(),
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && 'selected'}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell
|
|
||||||
key={cell.id}
|
|
||||||
className={cn(cell.column.columnDef.meta?.cellClassName)}
|
|
||||||
>
|
|
||||||
{flexRender(
|
|
||||||
cell.column.columnDef.cell,
|
|
||||||
cell.getContext(),
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableEmpty columnsLength={columns.length}></TableEmpty>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { ReactNode, useCallback, useMemo } from 'react';
|
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildSubNodeOutputOptions } from './use-build-options';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DynamicOutputForm({ node }: IProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
const options = useBuildSubNodeOutputOptions(node?.id);
|
|
||||||
const name = 'outputs';
|
|
||||||
|
|
||||||
const flatOptions = useMemo(() => {
|
|
||||||
return options.reduce<{ label: string; value: string; type: string }[]>(
|
|
||||||
(pre, cur) => {
|
|
||||||
pre.push(...cur.options);
|
|
||||||
return pre;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
}, [options]);
|
|
||||||
|
|
||||||
const findType = useCallback(
|
|
||||||
(val: string) => {
|
|
||||||
const type = flatOptions.find((x) => x.value === val)?.type;
|
|
||||||
if (type) {
|
|
||||||
return `Array<${type}>`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[flatOptions],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
|
||||||
name: name,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-5">
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
const nameField = `${name}.${index}.name`;
|
|
||||||
const typeField = `${name}.${index}.type`;
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex items-center gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={nameField}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder={t('common.pleaseInput')}
|
|
||||||
></Input>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Separator className="w-3 text-text-secondary" />
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.ref`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="w-2/5">
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch
|
|
||||||
options={options}
|
|
||||||
{...field}
|
|
||||||
onChange={(val) => {
|
|
||||||
form.setValue(typeField, findType(val));
|
|
||||||
field.onChange(val);
|
|
||||||
}}
|
|
||||||
></SelectWithSearch>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={typeField}
|
|
||||||
render={() => <div></div>}
|
|
||||||
/>
|
|
||||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
|
||||||
<X className="text-text-sub-title-invert " />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<BlockButton onClick={() => append({ name: '', ref: undefined })}>
|
|
||||||
{t('common.add')}
|
|
||||||
</BlockButton>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VariableTitle({ title }: { title: ReactNode }) {
|
|
||||||
return <div className="font-medium text-text-primary pb-2">{title}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DynamicOutput({ node }: IProps) {
|
|
||||||
return (
|
|
||||||
<FormContainer>
|
|
||||||
<VariableTitle title={t('flow.output')}></VariableTitle>
|
|
||||||
<DynamicOutputForm node={node}></DynamicOutputForm>
|
|
||||||
</FormContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { Form } from '@/components/ui/form';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { VariableType } from '../../constant';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
import { DynamicOutput } from './dynamic-output';
|
|
||||||
import { OutputArray } from './interface';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-form-change';
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
query: z.string().optional(),
|
|
||||||
outputs: z.array(z.object({ name: z.string(), value: z.any() })).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
function IterationForm({ node }: INextOperatorForm) {
|
|
||||||
const defaultValues = useValues(node);
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: defaultValues,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputs: OutputArray = useWatch({
|
|
||||||
control: form?.control,
|
|
||||||
name: 'outputs',
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputList = useMemo(() => {
|
|
||||||
return outputs.map((x) => ({ title: x.name, type: x?.type }));
|
|
||||||
}, [outputs]);
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<QueryVariable
|
|
||||||
name="items_ref"
|
|
||||||
type={VariableType.Array}
|
|
||||||
></QueryVariable>
|
|
||||||
</FormContainer>
|
|
||||||
<DynamicOutput node={node}></DynamicOutput>
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</FormWrapper>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(IterationForm);
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export type OutputArray = Array<{ name: string; ref: string; type?: string }>;
|
|
||||||
export type OutputObject = Record<string, { ref: string; type?: string }>;
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { Operator } from '../../constant';
|
|
||||||
import { buildOutputOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export function useBuildSubNodeOutputOptions(nodeId?: string) {
|
|
||||||
const { nodes } = useGraphStore((state) => state);
|
|
||||||
|
|
||||||
const nodeOutputOptions = useMemo(() => {
|
|
||||||
if (!nodeId) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const subNodeWithOutputList = nodes.filter(
|
|
||||||
(x) =>
|
|
||||||
x.parentId === nodeId &&
|
|
||||||
x.data.label !== Operator.IterationStart &&
|
|
||||||
!isEmpty(x.data?.form?.outputs),
|
|
||||||
);
|
|
||||||
|
|
||||||
return subNodeWithOutputList.map((x) => ({
|
|
||||||
label: x.data.name,
|
|
||||||
value: x.id,
|
|
||||||
title: x.data.name,
|
|
||||||
options: buildOutputOptions(x.data.form.outputs, x.id),
|
|
||||||
}));
|
|
||||||
}, [nodeId, nodes]);
|
|
||||||
|
|
||||||
return nodeOutputOptions;
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { initialIterationValues } from '../../constant';
|
|
||||||
import { OutputObject } from './interface';
|
|
||||||
|
|
||||||
function convertToArray(outputObject: OutputObject) {
|
|
||||||
return Object.entries(outputObject).map(([key, value]) => ({
|
|
||||||
name: key,
|
|
||||||
ref: value.ref,
|
|
||||||
type: value.type,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return { ...initialIterationValues, outputs: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...formData, outputs: convertToArray(formData.outputs) };
|
|
||||||
}, [node?.data?.form]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import { OutputArray, OutputObject } from './interface';
|
|
||||||
|
|
||||||
export function transferToObject(list: OutputArray) {
|
|
||||||
return list.reduce<OutputObject>((pre, cur) => {
|
|
||||||
pre[cur.name] = { ref: cur.ref, type: cur.type };
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
|
||||||
if (id && form?.formState.isDirty) {
|
|
||||||
values = form?.getValues();
|
|
||||||
console.log('🚀 ~ useEffect ~ values:', values);
|
|
||||||
let nextValues: any = {
|
|
||||||
...values,
|
|
||||||
outputs: transferToObject(values.outputs),
|
|
||||||
};
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { Output, OutputType } from '@/pages/agent/form/components/output';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { initialIterationStartValues } from '../../constant';
|
|
||||||
|
|
||||||
const outputs = initialIterationStartValues.outputs;
|
|
||||||
|
|
||||||
const outputList = Object.entries(outputs).reduce<OutputType[]>(
|
|
||||||
(pre, [key, value]) => {
|
|
||||||
pre.push({ title: key, type: value.type });
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
function IterationStartForm() {
|
|
||||||
return (
|
|
||||||
<section className="space-y-6 p-4">
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(IterationStartForm);
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
import { NextLLMSelect } from '@/components/llm-select/next';
|
|
||||||
import { TopNFormField } from '@/components/top-n-item';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
|
||||||
|
|
||||||
const KeywordExtractForm = ({ form, node }: INextOperatorForm) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
className="space-y-6"
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="llm_id"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('chat.modelTip')}>
|
|
||||||
{t('chat.model')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NextLLMSelect {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<TopNFormField></TopNFormField>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KeywordExtractForm;
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
|
||||||
|
|
||||||
function MessageForm({ node }: INextOperatorForm) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const values = useValues(node);
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
content: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
value: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: values,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
|
||||||
name: 'content',
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('flow.msgTip')}>{t('flow.msg')}</FormLabel>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<div key={field.id} className="flex items-start gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`content.${index}.value`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1">
|
|
||||||
<FormControl>
|
|
||||||
{/* <Textarea {...field}> </Textarea> */}
|
|
||||||
<PromptEditor
|
|
||||||
{...field}
|
|
||||||
placeholder={t('flow.messagePlaceholder')}
|
|
||||||
></PromptEditor>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{fields.length > 1 && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
|
||||||
<X />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<BlockButton
|
|
||||||
type="button"
|
|
||||||
onClick={() => append({ value: '' })} // "" will cause the inability to add, refer to: https://github.com/orgs/react-hook-form/discussions/8485#discussioncomment-2961861
|
|
||||||
>
|
|
||||||
{t('flow.addMessage')}
|
|
||||||
</BlockButton>
|
|
||||||
</div>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
</FormContainer>
|
|
||||||
</FormWrapper>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(MessageForm);
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { initialMessageValues } from '../../constant';
|
|
||||||
import { convertToObjectArray } from '../../utils';
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return initialMessageValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...formData,
|
|
||||||
content: convertToObjectArray(formData.content),
|
|
||||||
};
|
|
||||||
}, [node]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import { convertToStringArray } from '../../utils';
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
|
||||||
if (id && form?.formState.isDirty) {
|
|
||||||
values = form?.getValues();
|
|
||||||
let nextValues: any = values;
|
|
||||||
|
|
||||||
nextValues = {
|
|
||||||
...values,
|
|
||||||
content: convertToStringArray(values.content),
|
|
||||||
};
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import pick from 'lodash/pick';
|
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export const useBuildRelevantOptions = () => {
|
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
|
||||||
|
|
||||||
const buildRelevantOptions = useCallback(
|
|
||||||
(toList: string[]) => {
|
|
||||||
return nodes
|
|
||||||
.filter(
|
|
||||||
(x) => !toList.some((y) => y === x.id), // filter out selected values in other to fields from the current drop-down box options
|
|
||||||
)
|
|
||||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
|
||||||
},
|
|
||||||
[nodes],
|
|
||||||
);
|
|
||||||
|
|
||||||
return buildRelevantOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* monitor changes in the connection and synchronize the target to the yes and no fields of the form
|
|
||||||
* similar to the categorize-form's useHandleFormValuesChange method
|
|
||||||
* @param param0
|
|
||||||
*/
|
|
||||||
export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
|
|
||||||
const getNode = useGraphStore((state) => state.getNode);
|
|
||||||
const node = getNode(nodeId);
|
|
||||||
|
|
||||||
const watchFormChanges = useCallback(() => {
|
|
||||||
if (node) {
|
|
||||||
form?.setFieldsValue(pick(node, ['yes', 'no']));
|
|
||||||
}
|
|
||||||
}, [node, form]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
watchFormChanges();
|
|
||||||
}, [watchFormChanges]);
|
|
||||||
};
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import LLMSelect from '@/components/llm-select';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import { Operator } from '../../constant';
|
|
||||||
import { useBuildFormSelectOptions } from '../../form-hooks';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import { useWatchConnectionChanges } from './hooks';
|
|
||||||
|
|
||||||
const RelevantForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const buildRelevantOptions = useBuildFormSelectOptions(
|
|
||||||
Operator.Relevant,
|
|
||||||
node?.id,
|
|
||||||
);
|
|
||||||
useWatchConnectionChanges({ nodeId: node?.id, form });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
labelCol={{ span: 4 }}
|
|
||||||
wrapperCol={{ span: 20 }}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name={'llm_id'}
|
|
||||||
label={t('model', { keyPrefix: 'chat' })}
|
|
||||||
tooltip={t('modelTip', { keyPrefix: 'chat' })}
|
|
||||||
>
|
|
||||||
<LLMSelect></LLMSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('yes')} name={'yes'}>
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
options={buildRelevantOptions([form?.getFieldValue('no')])}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('no')} name={'no'}>
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
options={buildRelevantOptions([form?.getFieldValue('yes')])}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RelevantForm;
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
import { Collapse } from '@/components/collapse';
|
|
||||||
import { CrossLanguageFormField } from '@/components/cross-language-form-field';
|
|
||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
|
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
|
||||||
import { RerankFormFields } from '@/components/rerank';
|
|
||||||
import { SimilaritySliderFormField } from '@/components/similarity-slider';
|
|
||||||
import { TopNFormField } from '@/components/top-n-item';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { UseKnowledgeGraphFormField } from '@/components/use-knowledge-graph-item';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { useForm, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { initialRetrievalValues } from '../../constant';
|
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
|
|
||||||
export const RetrievalPartialSchema = {
|
|
||||||
similarity_threshold: z.coerce.number(),
|
|
||||||
keywords_similarity_weight: z.coerce.number(),
|
|
||||||
top_n: z.coerce.number(),
|
|
||||||
top_k: z.coerce.number(),
|
|
||||||
kb_ids: z.array(z.string()),
|
|
||||||
rerank_id: z.string(),
|
|
||||||
empty_response: z.string(),
|
|
||||||
cross_languages: z.array(z.string()),
|
|
||||||
use_kg: z.boolean(),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FormSchema = z.object({
|
|
||||||
query: z.string().optional(),
|
|
||||||
...RetrievalPartialSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function EmptyResponseField() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="empty_response"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('chat.emptyResponseTip')}>
|
|
||||||
{t('chat.emptyResponse')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea
|
|
||||||
placeholder={t('common.namePlaceholder')}
|
|
||||||
{...field}
|
|
||||||
autoComplete="off"
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RetrievalForm({ node }: INextOperatorForm) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const outputList = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: 'formalized_content',
|
|
||||||
type: initialRetrievalValues.outputs.formalized_content.type,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const defaultValues = useValues(node);
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: defaultValues,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<RAGFlowFormItem name="query" label={t('flow.query')}>
|
|
||||||
<PromptEditor></PromptEditor>
|
|
||||||
</RAGFlowFormItem>
|
|
||||||
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
|
|
||||||
</FormContainer>
|
|
||||||
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
|
|
||||||
<FormContainer>
|
|
||||||
<SimilaritySliderFormField
|
|
||||||
vectorSimilarityWeightName="keywords_similarity_weight"
|
|
||||||
isTooltipShown
|
|
||||||
></SimilaritySliderFormField>
|
|
||||||
<TopNFormField></TopNFormField>
|
|
||||||
<RerankFormFields></RerankFormFields>
|
|
||||||
<EmptyResponseField></EmptyResponseField>
|
|
||||||
<CrossLanguageFormField name="cross_languages"></CrossLanguageFormField>
|
|
||||||
<UseKnowledgeGraphFormField name="use_kg"></UseKnowledgeGraphFormField>
|
|
||||||
</FormContainer>
|
|
||||||
</Collapse>
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</FormWrapper>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(RetrievalForm);
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { initialRetrievalValues } from '../../constant';
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const defaultValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
...initialRetrievalValues,
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return defaultValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
}, [defaultValues, node?.data?.form]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import { NextLLMSelect } from '@/components/llm-select/next';
|
|
||||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { GoogleLanguageOptions } from '../../options';
|
|
||||||
|
|
||||||
const RewriteQuestionForm = ({ form }: INextOperatorForm) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
className="space-y-6"
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="llm_id"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('chat.modelTip')}>
|
|
||||||
{t('chat.model')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<NextLLMSelect {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="language"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('chat.languageTip')}>
|
|
||||||
{t('chat.language')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
options={GoogleLanguageOptions}
|
|
||||||
allowClear={true}
|
|
||||||
{...field}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RewriteQuestionForm;
|
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
import { BlockButton, Button } from '@/components/ui/button';
|
||||||
import { Form } from '@/components/ui/form';
|
import { Form } from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { X } from 'lucide-react';
|
import { Trash2 } from 'lucide-react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
import { useFieldArray, useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { initialChunkerValues, initialSplitterValues } from '../../constant';
|
import { initialChunkerValues, initialSplitterValues } from '../../constant';
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
@ -33,6 +33,7 @@ export type SplitterFormSchemaType = z.infer<typeof FormSchema>;
|
|||||||
|
|
||||||
const SplitterForm = ({ node }: INextOperatorForm) => {
|
const SplitterForm = ({ node }: INextOperatorForm) => {
|
||||||
const defaultValues = useFormValues(initialChunkerValues, node);
|
const defaultValues = useFormValues(initialChunkerValues, node);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = useForm<SplitterFormSchemaType>({
|
const form = useForm<SplitterFormSchemaType>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
@ -50,42 +51,42 @@ const SplitterForm = ({ node }: INextOperatorForm) => {
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<FormContainer>
|
<SliderInputFormField
|
||||||
<SliderInputFormField
|
name="chunk_token_size"
|
||||||
name="chunk_token_size"
|
max={2048}
|
||||||
max={2048}
|
label={t('knowledgeConfiguration.chunkTokenNumber')}
|
||||||
label="chunk_token_size"
|
></SliderInputFormField>
|
||||||
></SliderInputFormField>
|
<SliderInputFormField
|
||||||
<SliderInputFormField
|
name="overlapped_percent"
|
||||||
name="overlapped_percent"
|
max={0.3}
|
||||||
max={0.3}
|
min={0.1}
|
||||||
min={0.1}
|
step={0.01}
|
||||||
step={0.01}
|
label={t('dataflow.overlappedPercent')}
|
||||||
label="overlapped_percent"
|
></SliderInputFormField>
|
||||||
></SliderInputFormField>
|
<span>{t('flow.delimiters')}</span>
|
||||||
<span>delimiters</span>
|
{fields.map((field, index) => (
|
||||||
{fields.map((field, index) => (
|
<div key={field.id} className="flex items-center gap-2">
|
||||||
<div key={field.id} className="flex items-center gap-2">
|
<div className="space-y-2 flex-1">
|
||||||
<div className="space-y-2 flex-1">
|
<RAGFlowFormItem
|
||||||
<RAGFlowFormItem
|
name={`${name}.${index}.value`}
|
||||||
name={`${name}.${index}.value`}
|
label="delimiter"
|
||||||
label="delimiter"
|
labelClassName="!hidden"
|
||||||
labelClassName="!hidden"
|
|
||||||
>
|
|
||||||
<Input className="!m-0"></Input>
|
|
||||||
</RAGFlowFormItem>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
>
|
||||||
<X />
|
<Input className="!m-0"></Input>
|
||||||
</Button>
|
</RAGFlowFormItem>
|
||||||
</div>
|
</div>
|
||||||
))}
|
<Button
|
||||||
<BlockButton onClick={() => append({ value: '' })}>Add</BlockButton>
|
type="button"
|
||||||
</FormContainer>
|
variant={'ghost'}
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
>
|
||||||
|
<Trash2 />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<BlockButton onClick={() => append({ value: '' })}>
|
||||||
|
{t('common.add')}
|
||||||
|
</BlockButton>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<Output list={outputList}></Output>
|
<Output list={outputList}></Output>
|
||||||
|
|||||||
@ -1,166 +0,0 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { MultiSelect } from '@/components/ui/multi-select';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { toLower } from 'lodash';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import {
|
|
||||||
StringTransformDelimiter,
|
|
||||||
StringTransformMethod,
|
|
||||||
initialStringTransformValues,
|
|
||||||
} from '../../constant';
|
|
||||||
import { INextOperatorForm } from '../../interface';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { Output, transferOutputs } from '../components/output';
|
|
||||||
import { PromptEditor } from '../components/prompt-editor';
|
|
||||||
import { QueryVariable } from '../components/query-variable';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-form-change';
|
|
||||||
|
|
||||||
const DelimiterOptions = Object.entries(StringTransformDelimiter).map(
|
|
||||||
([key, val]) => ({ label: t('flow.' + toLower(key)), value: val }),
|
|
||||||
);
|
|
||||||
|
|
||||||
function StringTransformForm({ node }: INextOperatorForm) {
|
|
||||||
const values = useValues(node);
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
method: z.string(),
|
|
||||||
split_ref: z.string().optional(),
|
|
||||||
script: z.string().optional(),
|
|
||||||
delimiters: z.array(z.string()).or(z.string()),
|
|
||||||
outputs: z.object({ result: z.object({ type: z.string() }) }).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
defaultValues: values,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const method = useWatch({ control: form.control, name: 'method' });
|
|
||||||
|
|
||||||
const isSplit = method === StringTransformMethod.Split;
|
|
||||||
|
|
||||||
const outputList = useMemo(() => {
|
|
||||||
return transferOutputs(values.outputs);
|
|
||||||
}, [values.outputs]);
|
|
||||||
|
|
||||||
const handleMethodChange = useCallback(
|
|
||||||
(value: StringTransformMethod) => {
|
|
||||||
const isMerge = value === StringTransformMethod.Merge;
|
|
||||||
const outputs = {
|
|
||||||
...initialStringTransformValues.outputs,
|
|
||||||
result: {
|
|
||||||
type: isMerge ? 'string' : 'Array<string>',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
form.setValue('outputs', outputs);
|
|
||||||
form.setValue(
|
|
||||||
'delimiters',
|
|
||||||
isMerge ? StringTransformDelimiter.Comma : [],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[form],
|
|
||||||
);
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
<FormContainer>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="method"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.method')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
{...field}
|
|
||||||
options={Object.values(StringTransformMethod).map(
|
|
||||||
(val) => ({ label: t('flow.' + val), value: val }),
|
|
||||||
)}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleMethodChange(value);
|
|
||||||
field.onChange(value);
|
|
||||||
}}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{isSplit && (
|
|
||||||
<QueryVariable
|
|
||||||
label={<FormLabel>split_ref</FormLabel>}
|
|
||||||
name="split_ref"
|
|
||||||
></QueryVariable>
|
|
||||||
)}
|
|
||||||
{isSplit || (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="script"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.script')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<PromptEditor {...field} showToolbar={false}></PromptEditor>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="delimiters"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t('flow.delimiters')}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
{isSplit ? (
|
|
||||||
<MultiSelect
|
|
||||||
options={DelimiterOptions}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
defaultValue={field.value as string[]}
|
|
||||||
variant="inverted"
|
|
||||||
// {...field}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<RAGFlowSelect
|
|
||||||
{...field}
|
|
||||||
options={DelimiterOptions}
|
|
||||||
></RAGFlowSelect>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="outputs"
|
|
||||||
render={() => <div></div>}
|
|
||||||
/>
|
|
||||||
</FormContainer>
|
|
||||||
</FormWrapper>
|
|
||||||
<div className="p-5">
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(StringTransformForm);
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
initialStringTransformValues,
|
|
||||||
StringTransformMethod,
|
|
||||||
} from '../../constant';
|
|
||||||
|
|
||||||
function transferDelimiters(formData: typeof initialStringTransformValues) {
|
|
||||||
return formData.method === StringTransformMethod.Merge
|
|
||||||
? formData.delimiters[0]
|
|
||||||
: formData.delimiters;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return {
|
|
||||||
...initialStringTransformValues,
|
|
||||||
delimiters: transferDelimiters(formData),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...formData,
|
|
||||||
delimiters: transferDelimiters(formData),
|
|
||||||
};
|
|
||||||
}, [node?.data?.form]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import { StringTransformMethod } from '../../constant';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn<any>) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
|
||||||
if (id && form?.formState.isDirty) {
|
|
||||||
values = form?.getValues();
|
|
||||||
let nextValues: any = values;
|
|
||||||
|
|
||||||
if (
|
|
||||||
values.delimiters !== undefined &&
|
|
||||||
values.method === StringTransformMethod.Merge
|
|
||||||
) {
|
|
||||||
nextValues.delimiters = [values.delimiters];
|
|
||||||
}
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -1,328 +0,0 @@
|
|||||||
import { FormContainer } from '@/components/form-container';
|
|
||||||
import { IconFont } from '@/components/icon-font';
|
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { toLower } from 'lodash';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import {
|
|
||||||
SwitchLogicOperatorOptions,
|
|
||||||
SwitchOperatorOptions,
|
|
||||||
VariableType,
|
|
||||||
} from '../../constant';
|
|
||||||
import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
|
||||||
|
|
||||||
const ConditionKey = 'conditions';
|
|
||||||
const ItemKey = 'items';
|
|
||||||
|
|
||||||
type ConditionCardsProps = {
|
|
||||||
name: string;
|
|
||||||
removeParent(index: number): void;
|
|
||||||
parentIndex: number;
|
|
||||||
parentLength: number;
|
|
||||||
} & IOperatorForm;
|
|
||||||
|
|
||||||
export const LogicalOperatorIcon = function OperatorIcon({
|
|
||||||
icon,
|
|
||||||
value,
|
|
||||||
}: Omit<(typeof SwitchOperatorOptions)[0], 'label'>) {
|
|
||||||
if (typeof icon === 'string') {
|
|
||||||
return (
|
|
||||||
<IconFont
|
|
||||||
name={icon}
|
|
||||||
className={cn('size-4', {
|
|
||||||
'rotate-180': value === '>',
|
|
||||||
})}
|
|
||||||
></IconFont>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return icon;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useBuildSwitchOperatorOptions() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const switchOperatorOptions = useMemo(() => {
|
|
||||||
return SwitchOperatorOptions.map((x) => ({
|
|
||||||
value: x.value,
|
|
||||||
icon: (
|
|
||||||
<LogicalOperatorIcon
|
|
||||||
icon={x.icon}
|
|
||||||
value={x.value}
|
|
||||||
></LogicalOperatorIcon>
|
|
||||||
),
|
|
||||||
label: t(`flow.switchOperatorOptions.${x.label}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
return switchOperatorOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConditionCards({
|
|
||||||
name: parentName,
|
|
||||||
parentIndex,
|
|
||||||
removeParent,
|
|
||||||
parentLength,
|
|
||||||
}: ConditionCardsProps) {
|
|
||||||
const form = useFormContext();
|
|
||||||
|
|
||||||
const nextOptions = useBuildQueryVariableOptions();
|
|
||||||
|
|
||||||
const finalOptions = useMemo(() => {
|
|
||||||
return nextOptions.map((x) => {
|
|
||||||
return {
|
|
||||||
...x,
|
|
||||||
options: x.options.filter(
|
|
||||||
(y) => !toLower(y.type).includes(VariableType.Array),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [nextOptions]);
|
|
||||||
|
|
||||||
const switchOperatorOptions = useBuildSwitchOperatorOptions();
|
|
||||||
|
|
||||||
const name = `${parentName}.${ItemKey}`;
|
|
||||||
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
|
||||||
name: name,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleRemove = useCallback(
|
|
||||||
(index: number) => () => {
|
|
||||||
remove(index);
|
|
||||||
if (parentIndex !== 0 && index === 0 && parentLength === 1) {
|
|
||||||
removeParent(parentIndex);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[parentIndex, parentLength, remove, removeParent],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="flex-1 space-y-2.5 min-w-0">
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex">
|
|
||||||
<Card
|
|
||||||
className={cn(
|
|
||||||
'relative bg-transparent border-input-border border flex-1 min-w-0',
|
|
||||||
{
|
|
||||||
'before:w-10 before:absolute before:h-[1px] before:bg-input-border before:top-1/2 before:-left-10':
|
|
||||||
fields.length > 1 &&
|
|
||||||
(index === 0 || index === fields.length - 1),
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<section className="p-2 bg-bg-card flex justify-between items-center">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.cpn_id`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex-1 min-w-0">
|
|
||||||
<FormControl>
|
|
||||||
<SelectWithSearch
|
|
||||||
{...field}
|
|
||||||
options={finalOptions}
|
|
||||||
triggerClassName="text-accent-primary bg-transparent border-none truncate"
|
|
||||||
></SelectWithSearch>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Separator orientation="vertical" className="h-2.5" />
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.operator`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
{...field}
|
|
||||||
options={switchOperatorOptions}
|
|
||||||
onlyShowSelectedIcon
|
|
||||||
triggerClassName="w-30 bg-transparent border-none"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<CardContent className="p-4 ">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${name}.${index}.value`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea {...field} className="bg-transparent" />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Button variant={'ghost'} onClick={handleRemove(index)}>
|
|
||||||
<X />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div className="pr-9">
|
|
||||||
<BlockButton
|
|
||||||
className="mt-6"
|
|
||||||
onClick={() => append({ operator: switchOperatorOptions[0].value })}
|
|
||||||
>
|
|
||||||
{t('common.add')}
|
|
||||||
</BlockButton>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SwitchForm({ node }: IOperatorForm) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const values = useValues(node);
|
|
||||||
const switchOperatorOptions = useBuildSwitchOperatorOptions();
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
conditions: z.array(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
logical_operator: z.string(),
|
|
||||||
items: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
cpn_id: z.string(),
|
|
||||||
operator: z.string(),
|
|
||||||
value: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
to: z.array(z.string()).optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: values,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
|
||||||
name: ConditionKey,
|
|
||||||
control: form.control,
|
|
||||||
});
|
|
||||||
|
|
||||||
const switchLogicOperatorOptions = useMemo(() => {
|
|
||||||
return SwitchLogicOperatorOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`flow.switchLogicOperatorOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form {...form}>
|
|
||||||
<FormWrapper>
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
const name = `${ConditionKey}.${index}`;
|
|
||||||
const conditions: Array<any> = form.getValues(`${name}.${ItemKey}`);
|
|
||||||
const conditionLength = conditions.length;
|
|
||||||
return (
|
|
||||||
<FormContainer key={field.id} className="">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<section>
|
|
||||||
<span>{index === 0 ? 'IF' : 'ELSEIF'}</span>
|
|
||||||
<div className="text-text-secondary">Case {index + 1}</div>
|
|
||||||
</section>
|
|
||||||
{index !== 0 && (
|
|
||||||
<Button
|
|
||||||
variant={'secondary'}
|
|
||||||
className="-translate-y-1"
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
|
||||||
{t('common.remove')} <X />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<section className="flex gap-2 !mt-2 relative">
|
|
||||||
{conditionLength > 1 && (
|
|
||||||
<section className="flex flex-col w-[72px]">
|
|
||||||
<div className="relative w-1 flex-1 before:absolute before:w-[1px] before:bg-input-border before:top-20 before:bottom-0 before:left-10"></div>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={`${ConditionKey}.${index}.logical_operator`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<RAGFlowSelect
|
|
||||||
{...field}
|
|
||||||
options={switchLogicOperatorOptions}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="relative w-1 flex-1 before:absolute before:w-[1px] before:bg-input-border before:top-0 before:bottom-36 before:left-10"></div>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
<ConditionCards
|
|
||||||
name={name}
|
|
||||||
removeParent={remove}
|
|
||||||
parentIndex={index}
|
|
||||||
parentLength={fields.length}
|
|
||||||
></ConditionCards>
|
|
||||||
</section>
|
|
||||||
</FormContainer>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<BlockButton
|
|
||||||
onClick={() =>
|
|
||||||
append({
|
|
||||||
logical_operator: SwitchLogicOperatorOptions[0],
|
|
||||||
[ItemKey]: [
|
|
||||||
{
|
|
||||||
operator: switchOperatorOptions[0].value,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
to: [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('common.add')}
|
|
||||||
</BlockButton>
|
|
||||||
</FormWrapper>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(SwitchForm);
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { initialSwitchValues } from '../../constant';
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return initialSwitchValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
}, [node]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import { ISwitchCondition } from '@/interfaces/database/agent';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Manually triggered form updates are synchronized to the canvas
|
|
||||||
console.log('🚀 ~ useWatchFormChange ~ values:', form?.formState.isDirty);
|
|
||||||
if (id) {
|
|
||||||
values = form?.getValues() || {};
|
|
||||||
let nextValues: any = {
|
|
||||||
...values,
|
|
||||||
conditions:
|
|
||||||
values?.conditions?.map((x: ISwitchCondition) => ({ ...x })) ?? [], // Changing the form value with useFieldArray does not change the array reference
|
|
||||||
};
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -40,7 +40,10 @@ const TokenizerForm = ({ node }: INextOperatorForm) => {
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<RAGFlowFormItem name="search_method" label={t('search_method')}>
|
<RAGFlowFormItem
|
||||||
|
name="search_method"
|
||||||
|
label={t('dataflow.searchMethod')}
|
||||||
|
>
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={SearchMethodOptions}
|
options={SearchMethodOptions}
|
||||||
@ -52,7 +55,7 @@ const TokenizerForm = ({ node }: INextOperatorForm) => {
|
|||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
<SliderInputFormField
|
<SliderInputFormField
|
||||||
name="filename_embd_weight"
|
name="filename_embd_weight"
|
||||||
label="filename_embd_weight"
|
label={t('dataflow.filenameEmbdWeight')}
|
||||||
max={0.5}
|
max={0.5}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
></SliderInputFormField>
|
></SliderInputFormField>
|
||||||
|
|||||||
@ -1,168 +0,0 @@
|
|||||||
import { Collapse } from '@/components/collapse';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from '@/components/ui/form';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { FormTooltip } from '@/components/ui/tooltip';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { Plus } from 'lucide-react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
|
||||||
import { BeginQuery, INextOperatorForm } from '../../interface';
|
|
||||||
import { ParameterDialog } from '../begin-form/parameter-dialog';
|
|
||||||
import { QueryTable } from '../begin-form/query-table';
|
|
||||||
import { useEditQueryRecord } from '../begin-form/use-edit-query';
|
|
||||||
import { Output } from '../components/output';
|
|
||||||
import { useValues } from './use-values';
|
|
||||||
import { useWatchFormChange } from './use-watch-change';
|
|
||||||
|
|
||||||
function UserFillUpForm({ node }: INextOperatorForm) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const values = useValues(node);
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
enable_tips: z.boolean().optional(),
|
|
||||||
tips: z.string().trim().optional(),
|
|
||||||
inputs: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
key: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
value: z.string(),
|
|
||||||
optional: z.boolean(),
|
|
||||||
name: z.string(),
|
|
||||||
options: z.array(z.union([z.number(), z.string(), z.boolean()])),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: values,
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useWatchFormChange(node?.id, form);
|
|
||||||
|
|
||||||
const inputs: BeginQuery[] = useWatch({
|
|
||||||
control: form.control,
|
|
||||||
name: 'inputs',
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputList = inputs?.map((item) => ({
|
|
||||||
title: item.name,
|
|
||||||
type: item.type,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const {
|
|
||||||
ok,
|
|
||||||
currentRecord,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
showModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
handleDeleteRecord,
|
|
||||||
} = useEditQueryRecord({
|
|
||||||
form,
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="px-5 space-y-5">
|
|
||||||
<Form {...form}>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'enable_tips'}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('flow.openingSwitchTip')}>
|
|
||||||
{t('flow.guidingQuestion')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'tips'}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel tooltip={t('chat.setAnOpenerTip')}>
|
|
||||||
{t('flow.msg')}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea
|
|
||||||
rows={5}
|
|
||||||
{...field}
|
|
||||||
placeholder={t('common.pleaseInput')}
|
|
||||||
></Textarea>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Create a hidden field to make Form instance record this */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name={'inputs'}
|
|
||||||
render={() => <div></div>}
|
|
||||||
/>
|
|
||||||
<Collapse
|
|
||||||
title={
|
|
||||||
<div>
|
|
||||||
{t('flow.input')}
|
|
||||||
<FormTooltip tooltip={t('flow.beginInputTip')}></FormTooltip>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
rightContent={
|
|
||||||
<Button
|
|
||||||
variant={'ghost'}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
showModal();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<QueryTable
|
|
||||||
data={inputs}
|
|
||||||
showModal={showModal}
|
|
||||||
deleteRecord={handleDeleteRecord}
|
|
||||||
></QueryTable>
|
|
||||||
</Collapse>
|
|
||||||
|
|
||||||
{visible && (
|
|
||||||
<ParameterDialog
|
|
||||||
hideModal={hideModal}
|
|
||||||
initialValue={currentRecord}
|
|
||||||
otherThanCurrentQuery={otherThanCurrentQuery}
|
|
||||||
submit={ok}
|
|
||||||
></ParameterDialog>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(UserFillUpForm);
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { initialUserFillUpValues } from '../../constant';
|
|
||||||
import { buildBeginInputListFromObject } from '../begin-form/utils';
|
|
||||||
|
|
||||||
export function useValues(node?: RAGFlowNodeType) {
|
|
||||||
const values = useMemo(() => {
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
|
|
||||||
if (isEmpty(formData)) {
|
|
||||||
return initialUserFillUpValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputs = buildBeginInputListFromObject(formData?.inputs);
|
|
||||||
|
|
||||||
return { ...(formData || {}), inputs };
|
|
||||||
}, [node?.data?.form]);
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import { omit } from 'lodash';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
|
||||||
import { BeginQuery } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
function transferInputsArrayToObject(inputs: BeginQuery[] = []) {
|
|
||||||
return inputs.reduce<Record<string, Omit<BeginQuery, 'key'>>>((pre, cur) => {
|
|
||||||
pre[cur.key] = omit(cur, 'key');
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWatchFormChange(id?: string, form?: UseFormReturn) {
|
|
||||||
let values = useWatch({ control: form?.control });
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// TODO: This should only be executed when the form changes
|
|
||||||
if (id) {
|
|
||||||
values = form?.getValues() || {};
|
|
||||||
|
|
||||||
const inputs = transferInputsArrayToObject(values.inputs);
|
|
||||||
|
|
||||||
const nextValues = {
|
|
||||||
...values,
|
|
||||||
inputs,
|
|
||||||
outputs: inputs,
|
|
||||||
};
|
|
||||||
|
|
||||||
updateNodeForm(id, nextValues);
|
|
||||||
}
|
|
||||||
}, [form?.formState.isDirty, id, updateNodeForm, values]);
|
|
||||||
}
|
|
||||||
@ -2,7 +2,7 @@ import { useSetModalState } from '@/hooks/common-hooks';
|
|||||||
import { NodeMouseHandler } from '@xyflow/react';
|
import { NodeMouseHandler } from '@xyflow/react';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { Operator } from '../constant';
|
import { BeginId, Operator } from '../constant';
|
||||||
import useGraphStore from '../store';
|
import useGraphStore from '../store';
|
||||||
import { useCacheChatLog } from './use-cache-chat-log';
|
import { useCacheChatLog } from './use-cache-chat-log';
|
||||||
import { useGetBeginNodeDataInputs } from './use-get-begin-query';
|
import { useGetBeginNodeDataInputs } from './use-get-begin-query';
|
||||||
@ -13,7 +13,6 @@ export const useShowFormDrawer = () => {
|
|||||||
clickedNodeId: clickNodeId,
|
clickedNodeId: clickNodeId,
|
||||||
setClickedNodeId,
|
setClickedNodeId,
|
||||||
getNode,
|
getNode,
|
||||||
setClickedToolId,
|
|
||||||
} = useGraphStore((state) => state);
|
} = useGraphStore((state) => state);
|
||||||
const {
|
const {
|
||||||
visible: formDrawerVisible,
|
visible: formDrawerVisible,
|
||||||
@ -23,16 +22,14 @@ export const useShowFormDrawer = () => {
|
|||||||
|
|
||||||
const handleShow = useCallback(
|
const handleShow = useCallback(
|
||||||
(e: React.MouseEvent<Element>, nodeId: string) => {
|
(e: React.MouseEvent<Element>, nodeId: string) => {
|
||||||
const tool = get(e.target, 'dataset.tool');
|
|
||||||
// TODO: Operator type judgment should be used
|
// TODO: Operator type judgment should be used
|
||||||
if (nodeId.startsWith(Operator.Tool) && !tool) {
|
if (nodeId === BeginId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setClickedNodeId(nodeId);
|
setClickedNodeId(nodeId);
|
||||||
setClickedToolId(tool);
|
|
||||||
showFormDrawer();
|
showFormDrawer();
|
||||||
},
|
},
|
||||||
[setClickedNodeId, setClickedToolId, showFormDrawer],
|
[setClickedNodeId, showFormDrawer],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { IconFont } from '@/components/icon-font';
|
import { IconFont } from '@/components/icon-font';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
|
Blocks,
|
||||||
FileChartColumnIncreasing,
|
FileChartColumnIncreasing,
|
||||||
Grid3x3,
|
Heading,
|
||||||
HousePlus,
|
HousePlus,
|
||||||
ListMinus,
|
ListMinus,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -14,26 +15,15 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const OperatorIconMap = {
|
export const OperatorIconMap = {
|
||||||
[Operator.Retrieval]: 'KR',
|
|
||||||
[Operator.Begin]: 'house-plus',
|
[Operator.Begin]: 'house-plus',
|
||||||
[Operator.Categorize]: 'a-QuestionClassification',
|
|
||||||
[Operator.Message]: 'reply',
|
|
||||||
[Operator.Iteration]: 'loop',
|
|
||||||
[Operator.Switch]: 'condition',
|
|
||||||
[Operator.Code]: 'code-set',
|
|
||||||
[Operator.Agent]: 'agent-ai',
|
|
||||||
[Operator.UserFillUp]: 'await',
|
|
||||||
[Operator.StringTransform]: 'a-textprocessing',
|
|
||||||
[Operator.Note]: 'notebook-pen',
|
[Operator.Note]: 'notebook-pen',
|
||||||
[Operator.ExeSQL]: 'executesql-0',
|
|
||||||
[Operator.Invoke]: 'httprequest-0',
|
|
||||||
[Operator.Email]: 'sendemail-0',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SVGIconMap = {
|
export const SVGIconMap = {
|
||||||
[Operator.Parser]: FileChartColumnIncreasing,
|
[Operator.Parser]: FileChartColumnIncreasing,
|
||||||
[Operator.Chunker]: Grid3x3,
|
|
||||||
[Operator.Tokenizer]: ListMinus,
|
[Operator.Tokenizer]: ListMinus,
|
||||||
|
[Operator.Splitter]: Blocks,
|
||||||
|
[Operator.HierarchicalMerger]: Heading,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Empty = () => {
|
const Empty = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user