mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
### What problem does this PR solve? Feat: Add loop operator node. #10427 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
18
web/src/components/bool-segmented.tsx
Normal file
18
web/src/components/bool-segmented.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { omit } from 'lodash';
|
||||
import { Segmented, SegmentedProps } from './ui/segmented';
|
||||
|
||||
export function BoolSegmented({ ...props }: Omit<SegmentedProps, 'options'>) {
|
||||
return (
|
||||
<Segmented
|
||||
options={
|
||||
[
|
||||
{ value: true, label: 'True' },
|
||||
{ value: false, label: 'False' },
|
||||
] as any
|
||||
}
|
||||
sizeType="sm"
|
||||
itemClassName="justify-center flex-1"
|
||||
{...omit(props, 'options')}
|
||||
></Segmented>
|
||||
);
|
||||
}
|
||||
24
web/src/components/logical-operator.tsx
Normal file
24
web/src/components/logical-operator.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { useBuildSwitchLogicOperatorOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { RAGFlowFormItem } from './ragflow-form';
|
||||
import { RAGFlowSelect } from './ui/select';
|
||||
|
||||
type LogicalOperatorProps = { name: string };
|
||||
|
||||
export function LogicalOperator({ name }: LogicalOperatorProps) {
|
||||
const switchLogicOperatorOptions = useBuildSwitchLogicOperatorOptions();
|
||||
|
||||
return (
|
||||
<div className="relative min-w-14">
|
||||
<RAGFlowFormItem
|
||||
name={name}
|
||||
className="absolute top-1/2 -translate-y-1/2 right-1 left-0 z-10 bg-bg-base"
|
||||
>
|
||||
<RAGFlowSelect
|
||||
options={switchLogicOperatorOptions}
|
||||
triggerClassName="w-full text-xs px-1 py-0 h-6"
|
||||
></RAGFlowSelect>
|
||||
</RAGFlowFormItem>
|
||||
<div className="absolute border-l border-y w-5 right-0 top-4 bottom-4 rounded-l-lg"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -17,15 +17,13 @@ import { Input } from '@/components/ui/input';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { SwitchLogicOperator, SwitchOperatorOptions } from '@/constants/agent';
|
||||
import { useBuildSwitchOperatorOptions } from '@/hooks/logic-hooks/use-build-operator-options';
|
||||
import { useBuildSwitchLogicOperatorOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request';
|
||||
import { PromptEditor } from '@/pages/agent/form/components/prompt-editor';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RAGFlowFormItem } from '../ragflow-form';
|
||||
import { RAGFlowSelect } from '../ui/select';
|
||||
import { LogicalOperator } from '../logical-operator';
|
||||
|
||||
export function MetadataFilterConditions({
|
||||
kbIds,
|
||||
@ -44,8 +42,6 @@ export function MetadataFilterConditions({
|
||||
|
||||
const switchOperatorOptions = useBuildSwitchOperatorOptions();
|
||||
|
||||
const switchLogicOperatorOptions = useBuildSwitchLogicOperatorOptions();
|
||||
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name,
|
||||
control: form.control,
|
||||
@ -53,14 +49,16 @@ export function MetadataFilterConditions({
|
||||
|
||||
const add = useCallback(
|
||||
(key: string) => () => {
|
||||
form.setValue(logic, SwitchLogicOperator.And);
|
||||
if (fields.length === 1) {
|
||||
form.setValue(logic, SwitchLogicOperator.And);
|
||||
}
|
||||
append({
|
||||
key,
|
||||
value: '',
|
||||
op: SwitchOperatorOptions[0].value,
|
||||
});
|
||||
},
|
||||
[append, form, logic],
|
||||
[append, fields.length, form, logic],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -85,20 +83,7 @@ export function MetadataFilterConditions({
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<section className="flex">
|
||||
{fields.length > 1 && (
|
||||
<div className="relative min-w-14">
|
||||
<RAGFlowFormItem
|
||||
name={logic}
|
||||
className="absolute top-1/2 -translate-y-1/2 right-1 left-0 z-10 bg-bg-base"
|
||||
>
|
||||
<RAGFlowSelect
|
||||
options={switchLogicOperatorOptions}
|
||||
triggerClassName="w-full text-xs px-1 py-0 h-6"
|
||||
></RAGFlowSelect>
|
||||
</RAGFlowFormItem>
|
||||
<div className="absolute border-l border-y w-5 right-0 top-4 bottom-4 rounded-l-lg"></div>
|
||||
</div>
|
||||
)}
|
||||
{fields.length > 1 && <LogicalOperator name={logic}></LogicalOperator>}
|
||||
<div className="space-y-5 flex-1">
|
||||
{fields.map((field, index) => {
|
||||
const typeField = `${name}.${index}.key`;
|
||||
|
||||
@ -75,7 +75,6 @@ export enum Operator {
|
||||
Message = 'Message',
|
||||
Relevant = 'Relevant',
|
||||
RewriteQuestion = 'RewriteQuestion',
|
||||
KeywordExtract = 'KeywordExtract',
|
||||
DuckDuckGo = 'DuckDuckGo',
|
||||
Wikipedia = 'Wikipedia',
|
||||
PubMed = 'PubMed',
|
||||
@ -84,14 +83,10 @@ export enum Operator {
|
||||
Bing = 'Bing',
|
||||
GoogleScholar = 'GoogleScholar',
|
||||
GitHub = 'GitHub',
|
||||
QWeather = 'QWeather',
|
||||
ExeSQL = 'ExeSQL',
|
||||
Switch = 'Switch',
|
||||
WenCai = 'WenCai',
|
||||
AkShare = 'AkShare',
|
||||
YahooFinance = 'YahooFinance',
|
||||
Jin10 = 'Jin10',
|
||||
TuShare = 'TuShare',
|
||||
Note = 'Note',
|
||||
Crawler = 'Crawler',
|
||||
Invoke = 'Invoke',
|
||||
@ -118,6 +113,9 @@ export enum Operator {
|
||||
Splitter = 'Splitter',
|
||||
HierarchicalMerger = 'HierarchicalMerger',
|
||||
Extractor = 'Extractor',
|
||||
Loop = 'Loop',
|
||||
LoopStart = 'LoopItem',
|
||||
ExitLoop = 'ExitLoop',
|
||||
}
|
||||
|
||||
export enum ComparisonOperator {
|
||||
|
||||
@ -1170,8 +1170,13 @@ Example: Virtual Hosted Style`,
|
||||
addField: 'Add option',
|
||||
addMessage: 'Add message',
|
||||
loop: 'Loop',
|
||||
loopTip:
|
||||
loopDescription:
|
||||
'Loop is the upper limit of the number of loops of the current component, when the number of loops exceeds the value of loop, it means that the component can not complete the current task, please re-optimize agent',
|
||||
exitLoop: 'Exit loop',
|
||||
exitLoopDescription: `Equivalent to "break". This node has no configuration items. When the loop body reaches this node, the loop terminates.`,
|
||||
loopVariables: 'Loop Variables',
|
||||
maximumLoopCount: 'Maximum loop count',
|
||||
loopTerminationCondition: 'Loop termination condition',
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
key: 'Key',
|
||||
@ -1655,9 +1660,8 @@ This delimiter is used to split the input text into several text pieces echo of
|
||||
variableAssignerDescription:
|
||||
'This component performs operations on Data objects, including extracting, filtering, and editing keys and values in the Data.',
|
||||
variableAggregator: 'Variable aggregator',
|
||||
variableAggregatorDescription: `This process aggregates variables from multiple branches into a single variable to achieve unified configuration for downstream nodes.
|
||||
|
||||
The variable aggregation node (originally the variable assignment node) is a crucial node in the workflow. It is responsible for integrating the output results of different branches, ensuring that regardless of which branch is executed, its result can be referenced and accessed through a unified variable. This is extremely useful in multi-branch scenarios, as it maps variables with the same function across different branches to a single output variable, avoiding redundant definitions in downstream nodes.`,
|
||||
variableAggregatorDescription: `
|
||||
This process aggregates variables from multiple branches into a single variable to achieve unified configuration for downstream nodes.`,
|
||||
inputVariables: 'Input variables',
|
||||
runningHintText: 'is running...🕞',
|
||||
openingSwitch: 'Opening switch',
|
||||
@ -1886,10 +1890,10 @@ Important structured information may include: names, dates, locations, events, k
|
||||
overwrite: 'Overwritten By',
|
||||
clear: 'Clear',
|
||||
set: 'Set',
|
||||
'+=': 'Add',
|
||||
'-=': 'Subtract',
|
||||
'*=': 'Multiply',
|
||||
'/=': 'Divide',
|
||||
add: 'Add',
|
||||
subtract: 'Subtract',
|
||||
multiply: 'Multiply',
|
||||
divide: 'Divide',
|
||||
append: 'Append',
|
||||
extend: 'Extend',
|
||||
removeFirst: 'Remove first',
|
||||
|
||||
@ -1102,9 +1102,14 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
messageMsg: '请输入消息或删除此字段。',
|
||||
addField: '新增字段',
|
||||
addMessage: '新增消息',
|
||||
loop: '循环上限',
|
||||
loopTip:
|
||||
loop: '循环',
|
||||
loopDescription:
|
||||
'loop为当前组件循环次数上限,当循环次数超过loop的值时,说明组件不能完成当前任务,请重新优化agent',
|
||||
exitLoop: '退出循环',
|
||||
exitLoopDescription: `等同于 "break"。此节点没有配置项。当循环体到达此节点时,循环终止。`,
|
||||
loopVariables: '循环变量',
|
||||
maximumLoopCount: '最大循环次数',
|
||||
loopTerminationCondition: '循环终止条件',
|
||||
yes: '是',
|
||||
no: '否',
|
||||
key: '键',
|
||||
@ -1499,7 +1504,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
contentTip: 'content: 邮件内容(可选)',
|
||||
jsonUploadTypeErrorMessage: '请上传json文件',
|
||||
jsonUploadContentErrorMessage: 'json 文件错误',
|
||||
iteration: '循环',
|
||||
iteration: '迭代',
|
||||
iterationDescription: `该组件负责迭代生成新的内容,对列表对象执行多次步骤直至输出所有结果。`,
|
||||
delimiterTip: `该分隔符用于将输入文本分割成几个文本片段,每个文本片段的回显将作为每次迭代的输入项。`,
|
||||
delimiterOptions: {
|
||||
@ -1545,8 +1550,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
variableAssignerDescription:
|
||||
'此组件对数据对象执行操作,包括提取、筛选和编辑数据中的键和值。',
|
||||
variableAggregator: '变量聚合',
|
||||
variableAggregatorDescription: `将多路分支的变量聚合为一个变量,以实现下游节点统一配置。
|
||||
变量聚合节点(原变量赋值节点)是工作流程中的一个关键节点,它负责整合不同分支的输出结果,确保无论哪个分支被执行,其结果都能通过一个统一的变量来引用和访问。这在多分支的情况下非常有用,可将不同分支下相同作用的变量映射为一个输出变量,避免下游节点重复定义。`,
|
||||
variableAggregatorDescription: `该过程将来自多个分支的变量聚合到一个变量中,以实现下游节点的统一配置。`,
|
||||
inputVariables: '输入变量',
|
||||
addVariable: '新增变量',
|
||||
runningHintText: '正在运行中...🕞',
|
||||
|
||||
@ -56,12 +56,14 @@ import { BeginNode } from './node/begin-node';
|
||||
import { CategorizeNode } from './node/categorize-node';
|
||||
import { DataOperationsNode } from './node/data-operations-node';
|
||||
import { NextStepDropdown } from './node/dropdown/next-step-dropdown';
|
||||
import { ExitLoopNode } from './node/exit-loop-node';
|
||||
import { ExtractorNode } from './node/extractor-node';
|
||||
import { FileNode } from './node/file-node';
|
||||
import { InvokeNode } from './node/invoke-node';
|
||||
import { IterationNode, IterationStartNode } from './node/iteration-node';
|
||||
import { KeywordNode } from './node/keyword-node';
|
||||
import { ListOperationsNode } from './node/list-operations-node';
|
||||
import { LoopNode, LoopStartNode } from './node/loop-node';
|
||||
import { MessageNode } from './node/message-node';
|
||||
import NoteNode from './node/note-node';
|
||||
import ParserNode from './node/parser-node';
|
||||
@ -105,6 +107,9 @@ export const nodeTypes: NodeTypes = {
|
||||
listOperationsNode: ListOperationsNode,
|
||||
variableAssignerNode: VariableAssignerNode,
|
||||
variableAggregatorNode: VariableAggregatorNode,
|
||||
loopNode: LoopNode,
|
||||
loopStartNode: LoopStartNode,
|
||||
exitLoopNode: ExitLoopNode,
|
||||
};
|
||||
|
||||
const edgeTypes = {
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
import OperateDropdown from '@/components/operate-dropdown';
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import { Flex, MenuProps } from 'antd';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Operator } from '../../constant';
|
||||
import { useDuplicateNode } from '../../hooks';
|
||||
import useGraphStore from '../../store';
|
||||
|
||||
interface IProps {
|
||||
id: string;
|
||||
iconFontColor?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const NodeDropdown = ({ id, iconFontColor, label }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||
const deleteIterationNodeById = useGraphStore(
|
||||
(store) => store.deleteIterationNodeById,
|
||||
);
|
||||
|
||||
const deleteNode = useCallback(() => {
|
||||
if (label === Operator.Iteration) {
|
||||
deleteIterationNodeById(id);
|
||||
} else {
|
||||
deleteNodeById(id);
|
||||
}
|
||||
}, [label, deleteIterationNodeById, id, deleteNodeById]);
|
||||
|
||||
const duplicateNode = useDuplicateNode();
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: '2',
|
||||
onClick: () => duplicateNode(id, label),
|
||||
label: (
|
||||
<Flex justify={'space-between'}>
|
||||
{t('common.copy')}
|
||||
<CopyOutlined />
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<OperateDropdown
|
||||
iconFontSize={22}
|
||||
height={14}
|
||||
deleteItem={deleteNode}
|
||||
items={items}
|
||||
needsDeletionValidation={false}
|
||||
iconFontColor={iconFontColor}
|
||||
></OperateDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeDropdown;
|
||||
@ -21,11 +21,23 @@ function OperatorAccordionTrigger({ children }: PropsWithChildren) {
|
||||
export function AccordionOperators({
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
nodeId,
|
||||
}: {
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
nodeId?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { getOperatorTypeFromId, getParentIdById } = useGraphStore(
|
||||
(state) => state,
|
||||
);
|
||||
|
||||
const exitLoopList = useMemo(() => {
|
||||
if (getOperatorTypeFromId(getParentIdById(nodeId)) === Operator.Loop) {
|
||||
return [Operator.ExitLoop];
|
||||
}
|
||||
return [];
|
||||
}, [getOperatorTypeFromId, getParentIdById, nodeId]);
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
@ -62,6 +74,8 @@ export function AccordionOperators({
|
||||
operators={[
|
||||
Operator.Switch,
|
||||
Operator.Iteration,
|
||||
Operator.Loop,
|
||||
...exitLoopList,
|
||||
Operator.Categorize,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
|
||||
@ -73,6 +73,7 @@ export function InnerNextStepDropdown({
|
||||
<AccordionOperators
|
||||
isCustomDropdown={true}
|
||||
mousePosition={position}
|
||||
nodeId={nodeId}
|
||||
></AccordionOperators>
|
||||
)}
|
||||
</OnNodeCreatedContext.Provider>
|
||||
@ -101,9 +102,11 @@ export function InnerNextStepDropdown({
|
||||
</DropdownMenuLabel>
|
||||
<HideModalContext.Provider value={hideModal}>
|
||||
{isPipeline ? (
|
||||
<PipelineAccordionOperators></PipelineAccordionOperators>
|
||||
<PipelineAccordionOperators
|
||||
nodeId={nodeId}
|
||||
></PipelineAccordionOperators>
|
||||
) : (
|
||||
<AccordionOperators></AccordionOperators>
|
||||
<AccordionOperators nodeId={nodeId}></AccordionOperators>
|
||||
)}
|
||||
</HideModalContext.Provider>
|
||||
</DropdownMenuContent>
|
||||
|
||||
23
web/src/pages/agent/canvas/node/exit-loop-node.tsx
Normal file
23
web/src/pages/agent/canvas/node/exit-loop-node.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { BaseNode } from '@/interfaces/database/agent';
|
||||
import { NodeProps } from '@xyflow/react';
|
||||
import { LeftEndHandle } from './handle';
|
||||
import NodeHeader from './node-header';
|
||||
import { NodeWrapper } from './node-wrapper';
|
||||
import { ToolBar } from './toolbar';
|
||||
|
||||
export function ExitLoopNode({ id, data, selected }: NodeProps<BaseNode<any>>) {
|
||||
return (
|
||||
<ToolBar
|
||||
selected={selected}
|
||||
id={id}
|
||||
label={data.label}
|
||||
showRun={false}
|
||||
showCopy={false}
|
||||
>
|
||||
<NodeWrapper selected={selected}>
|
||||
<LeftEndHandle></LeftEndHandle>
|
||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
}
|
||||
@ -56,7 +56,7 @@ export function InnerIterationNode({
|
||||
);
|
||||
}
|
||||
|
||||
function InnerIterationStartNode({
|
||||
export function InnerIterationStartNode({
|
||||
isConnectable = true,
|
||||
id,
|
||||
selected,
|
||||
|
||||
75
web/src/pages/agent/canvas/node/labeled-group-node.tsx
Normal file
75
web/src/pages/agent/canvas/node/labeled-group-node.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { Panel, type NodeProps, type PanelPosition } from '@xyflow/react';
|
||||
import { type ComponentProps, type ReactNode } from 'react';
|
||||
|
||||
import { BaseNode } from '@/components/xyflow/base-node';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
/* GROUP NODE Label ------------------------------------------------------- */
|
||||
|
||||
export type GroupNodeLabelProps = ComponentProps<'div'>;
|
||||
|
||||
export function GroupNodeLabel({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: GroupNodeLabelProps) {
|
||||
return (
|
||||
<div className="h-full w-full" {...props}>
|
||||
<div
|
||||
className={cn(
|
||||
'text-card-foreground bg-secondary w-fit p-2 text-xs',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export type GroupNodeProps = Partial<NodeProps> & {
|
||||
label?: ReactNode;
|
||||
position?: PanelPosition;
|
||||
};
|
||||
|
||||
/* GROUP NODE -------------------------------------------------------------- */
|
||||
|
||||
export function LabeledGroupNode({
|
||||
label = '',
|
||||
position,
|
||||
...props
|
||||
}: GroupNodeProps) {
|
||||
const getLabelClassName = (position?: PanelPosition) => {
|
||||
switch (position) {
|
||||
case 'top-left':
|
||||
return 'rounded-br-sm';
|
||||
case 'top-center':
|
||||
return 'rounded-b-sm';
|
||||
case 'top-right':
|
||||
return 'rounded-bl-sm';
|
||||
case 'bottom-left':
|
||||
return 'rounded-tr-sm';
|
||||
case 'bottom-right':
|
||||
return 'rounded-tl-sm';
|
||||
case 'bottom-center':
|
||||
return 'rounded-t-sm';
|
||||
default:
|
||||
return 'rounded-br-sm';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseNode
|
||||
className="bg-opacity-50 h-full overflow-hidden rounded-sm"
|
||||
{...props}
|
||||
>
|
||||
<Panel className="m-0 p-0" position={position}>
|
||||
{label && (
|
||||
<GroupNodeLabel className={getLabelClassName(position)}>
|
||||
{label}
|
||||
</GroupNodeLabel>
|
||||
)}
|
||||
</Panel>
|
||||
</BaseNode>
|
||||
);
|
||||
}
|
||||
16
web/src/pages/agent/canvas/node/loop-node.tsx
Normal file
16
web/src/pages/agent/canvas/node/loop-node.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { BaseNode } from '@/interfaces/database/agent';
|
||||
import { NodeProps } from '@xyflow/react';
|
||||
import { memo } from 'react';
|
||||
import { InnerIterationNode, InnerIterationStartNode } from './iteration-node';
|
||||
|
||||
export function InnerLoopNode({ ...props }: NodeProps<BaseNode<any>>) {
|
||||
return <InnerIterationNode {...props}></InnerIterationNode>;
|
||||
}
|
||||
|
||||
export const LoopNode = memo(InnerLoopNode);
|
||||
|
||||
export function InnerLoopStartNode({ ...props }: NodeProps<BaseNode<any>>) {
|
||||
return <InnerIterationStartNode {...props}></InnerIterationStartNode>;
|
||||
}
|
||||
|
||||
export const LoopStartNode = memo(InnerLoopStartNode);
|
||||
@ -23,7 +23,7 @@ function InnerRetrievalNode({
|
||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||
|
||||
const { getLabel } = useGetVariableLabelOrTypeByValue(id);
|
||||
const { getLabel } = useGetVariableLabelOrTypeByValue({ nodeId: id });
|
||||
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
|
||||
@ -27,7 +27,7 @@ const ConditionBlock = ({
|
||||
nodeId,
|
||||
}: { condition: ISwitchCondition } & { nodeId: string }) => {
|
||||
const items = condition?.items ?? [];
|
||||
const { getLabel } = useGetVariableLabelOrTypeByValue(nodeId);
|
||||
const { getLabel } = useGetVariableLabelOrTypeByValue({ nodeId });
|
||||
|
||||
const renderOperatorIcon = useCallback((operator?: string) => {
|
||||
const item = SwitchOperatorOptions.find((x) => x.value === operator);
|
||||
|
||||
@ -58,7 +58,7 @@ export function ToolBar({
|
||||
const deleteNode: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
if (label === Operator.Iteration) {
|
||||
if ([Operator.Iteration, Operator.Loop].includes(label as Operator)) {
|
||||
deleteIterationNodeById(id);
|
||||
} else {
|
||||
deleteNodeById(id);
|
||||
|
||||
@ -61,7 +61,6 @@ export const AgentOperatorList = [
|
||||
Operator.Categorize,
|
||||
Operator.Message,
|
||||
Operator.RewriteQuestion,
|
||||
Operator.KeywordExtract,
|
||||
Operator.Switch,
|
||||
Operator.Iteration,
|
||||
Operator.WaitingDialogue,
|
||||
@ -79,10 +78,6 @@ export const DataOperationsOperatorOptions = [
|
||||
|
||||
export const SwitchElseTo = 'end_cpn_ids';
|
||||
|
||||
const initialQueryBaseValues = {
|
||||
query: [],
|
||||
};
|
||||
|
||||
export const initialRetrievalValues = {
|
||||
query: AgentGlobalsSysQueryWithBrace,
|
||||
top_n: 8,
|
||||
@ -139,11 +134,6 @@ export const initialMessageValues = {
|
||||
content: [''],
|
||||
};
|
||||
|
||||
export const initialKeywordExtractValues = {
|
||||
...initialLlmBaseValues,
|
||||
top_n: 3,
|
||||
...initialQueryBaseValues,
|
||||
};
|
||||
export const initialDuckValues = {
|
||||
top_n: 10,
|
||||
channel: Channel.Text,
|
||||
@ -275,14 +265,6 @@ export const initialGithubValues = {
|
||||
},
|
||||
};
|
||||
|
||||
export const initialQWeatherValues = {
|
||||
web_apikey: 'xxx',
|
||||
type: 'weather',
|
||||
user_type: 'free',
|
||||
time_period: 'now',
|
||||
...initialQueryBaseValues,
|
||||
};
|
||||
|
||||
export const initialExeSqlValues = {
|
||||
sql: '',
|
||||
db_type: 'mysql',
|
||||
@ -331,8 +313,6 @@ export const initialWenCaiValues = {
|
||||
},
|
||||
};
|
||||
|
||||
export const initialAkShareValues = { top_n: 10, ...initialQueryBaseValues };
|
||||
|
||||
export const initialYahooFinanceValues = {
|
||||
stock_code: '',
|
||||
info: true,
|
||||
@ -349,22 +329,6 @@ export const initialYahooFinanceValues = {
|
||||
},
|
||||
};
|
||||
|
||||
export const initialJin10Values = {
|
||||
type: 'flash',
|
||||
secret_key: 'xxx',
|
||||
flash_type: '1',
|
||||
contain: '',
|
||||
filter: '',
|
||||
...initialQueryBaseValues,
|
||||
};
|
||||
|
||||
export const initialTuShareValues = {
|
||||
token: 'xxx',
|
||||
src: 'eastmoney',
|
||||
start_date: '2024-01-01 09:00:00',
|
||||
...initialQueryBaseValues,
|
||||
};
|
||||
|
||||
export const initialNoteValues = {
|
||||
text: '',
|
||||
};
|
||||
@ -624,6 +588,13 @@ export const initialVariableAssignerValues = {};
|
||||
|
||||
export const initialVariableAggregatorValues = { outputs: {}, groups: [] };
|
||||
|
||||
export const initialLoopValues = {
|
||||
loop_variables: [],
|
||||
loop_termination_condition: [],
|
||||
maximum_loop_count: 10,
|
||||
outputs: {},
|
||||
};
|
||||
|
||||
export const CategorizeAnchorPointPositions = [
|
||||
{ top: 1, right: 34 },
|
||||
{ top: 8, right: 18 },
|
||||
@ -659,11 +630,6 @@ export const RestrictedUpstreamMap = {
|
||||
Operator.RewriteQuestion,
|
||||
Operator.Relevant,
|
||||
],
|
||||
[Operator.KeywordExtract]: [
|
||||
Operator.Begin,
|
||||
Operator.Message,
|
||||
Operator.Relevant,
|
||||
],
|
||||
[Operator.DuckDuckGo]: [Operator.Begin, Operator.Retrieval],
|
||||
[Operator.Wikipedia]: [Operator.Begin, Operator.Retrieval],
|
||||
[Operator.PubMed]: [Operator.Begin, Operator.Retrieval],
|
||||
@ -672,15 +638,11 @@ export const RestrictedUpstreamMap = {
|
||||
[Operator.Bing]: [Operator.Begin, Operator.Retrieval],
|
||||
[Operator.GoogleScholar]: [Operator.Begin, Operator.Retrieval],
|
||||
[Operator.GitHub]: [Operator.Begin, Operator.Retrieval],
|
||||
[Operator.QWeather]: [Operator.Begin, Operator.Retrieval],
|
||||
[Operator.SearXNG]: [Operator.Begin, Operator.Retrieval],
|
||||
[Operator.ExeSQL]: [Operator.Begin],
|
||||
[Operator.Switch]: [Operator.Begin],
|
||||
[Operator.WenCai]: [Operator.Begin],
|
||||
[Operator.AkShare]: [Operator.Begin],
|
||||
[Operator.YahooFinance]: [Operator.Begin],
|
||||
[Operator.Jin10]: [Operator.Begin],
|
||||
[Operator.TuShare]: [Operator.Begin],
|
||||
[Operator.Crawler]: [Operator.Begin],
|
||||
[Operator.Note]: [],
|
||||
[Operator.Invoke]: [Operator.Begin],
|
||||
@ -706,6 +668,9 @@ export const RestrictedUpstreamMap = {
|
||||
[Operator.Tokenizer]: [Operator.Begin],
|
||||
[Operator.Extractor]: [Operator.Begin],
|
||||
[Operator.File]: [Operator.Begin],
|
||||
[Operator.Loop]: [Operator.Begin],
|
||||
[Operator.LoopStart]: [Operator.Begin],
|
||||
[Operator.ExitLoop]: [Operator.Begin],
|
||||
};
|
||||
|
||||
export const NodeMap = {
|
||||
@ -715,7 +680,6 @@ export const NodeMap = {
|
||||
[Operator.Message]: 'messageNode',
|
||||
[Operator.Relevant]: 'relevantNode',
|
||||
[Operator.RewriteQuestion]: 'rewriteNode',
|
||||
[Operator.KeywordExtract]: 'keywordNode',
|
||||
[Operator.DuckDuckGo]: 'ragNode',
|
||||
[Operator.Wikipedia]: 'ragNode',
|
||||
[Operator.PubMed]: 'ragNode',
|
||||
@ -724,15 +688,11 @@ export const NodeMap = {
|
||||
[Operator.Bing]: 'ragNode',
|
||||
[Operator.GoogleScholar]: 'ragNode',
|
||||
[Operator.GitHub]: 'ragNode',
|
||||
[Operator.QWeather]: 'ragNode',
|
||||
[Operator.SearXNG]: 'ragNode',
|
||||
[Operator.ExeSQL]: 'ragNode',
|
||||
[Operator.Switch]: 'switchNode',
|
||||
[Operator.WenCai]: 'ragNode',
|
||||
[Operator.AkShare]: 'ragNode',
|
||||
[Operator.YahooFinance]: 'ragNode',
|
||||
[Operator.Jin10]: 'ragNode',
|
||||
[Operator.TuShare]: 'ragNode',
|
||||
[Operator.Note]: 'noteNode',
|
||||
[Operator.Crawler]: 'ragNode',
|
||||
[Operator.Invoke]: 'ragNode',
|
||||
@ -758,6 +718,9 @@ export const NodeMap = {
|
||||
[Operator.ListOperations]: 'listOperationsNode',
|
||||
[Operator.VariableAssigner]: 'variableAssignerNode',
|
||||
[Operator.VariableAggregator]: 'variableAggregatorNode',
|
||||
[Operator.Loop]: 'loopNode',
|
||||
[Operator.LoopStart]: 'loopStartNode',
|
||||
[Operator.ExitLoop]: 'exitLoopNode',
|
||||
};
|
||||
|
||||
export enum BeginQueryType {
|
||||
@ -891,3 +854,82 @@ export const ArrayFields = [
|
||||
TypesWithArray.ArrayString,
|
||||
TypesWithArray.ArrayObject,
|
||||
];
|
||||
|
||||
export enum InputMode {
|
||||
Constant = 'constant',
|
||||
Variable = 'variable',
|
||||
}
|
||||
|
||||
export enum LoopTerminationComparisonOperator {
|
||||
Contains = ComparisonOperator.Contains,
|
||||
NotContains = ComparisonOperator.NotContains,
|
||||
StartWith = ComparisonOperator.StartWith,
|
||||
EndWith = ComparisonOperator.EndWith,
|
||||
Is = 'is',
|
||||
IsNot = 'is not',
|
||||
}
|
||||
|
||||
export const LoopTerminationStringComparisonOperator = [
|
||||
LoopTerminationComparisonOperator.Contains,
|
||||
LoopTerminationComparisonOperator.NotContains,
|
||||
LoopTerminationComparisonOperator.StartWith,
|
||||
LoopTerminationComparisonOperator.EndWith,
|
||||
LoopTerminationComparisonOperator.Is,
|
||||
LoopTerminationComparisonOperator.IsNot,
|
||||
ComparisonOperator.Empty,
|
||||
ComparisonOperator.NotEmpty,
|
||||
];
|
||||
|
||||
export const LoopTerminationBooleanComparisonOperator = [
|
||||
LoopTerminationComparisonOperator.Is,
|
||||
LoopTerminationComparisonOperator.IsNot,
|
||||
ComparisonOperator.Empty,
|
||||
ComparisonOperator.NotEmpty,
|
||||
];
|
||||
// object or object array
|
||||
export const LoopTerminationObjectComparisonOperator = [
|
||||
ComparisonOperator.Empty,
|
||||
ComparisonOperator.NotEmpty,
|
||||
];
|
||||
|
||||
// string array or number array
|
||||
export const LoopTerminationStringArrayComparisonOperator = [
|
||||
LoopTerminationComparisonOperator.Contains,
|
||||
LoopTerminationComparisonOperator.NotContains,
|
||||
ComparisonOperator.Empty,
|
||||
ComparisonOperator.NotEmpty,
|
||||
];
|
||||
|
||||
export const LoopTerminationBooleanArrayComparisonOperator = [
|
||||
LoopTerminationComparisonOperator.Is,
|
||||
LoopTerminationComparisonOperator.IsNot,
|
||||
ComparisonOperator.Empty,
|
||||
ComparisonOperator.NotEmpty,
|
||||
];
|
||||
|
||||
export const LoopTerminationNumberComparisonOperator = [
|
||||
ComparisonOperator.Equal,
|
||||
ComparisonOperator.NotEqual,
|
||||
ComparisonOperator.GreatThan,
|
||||
ComparisonOperator.LessThan,
|
||||
ComparisonOperator.GreatEqual,
|
||||
ComparisonOperator.LessEqual,
|
||||
ComparisonOperator.Empty,
|
||||
ComparisonOperator.NotEmpty,
|
||||
];
|
||||
|
||||
export const LoopTerminationStringComparisonOperatorMap = {
|
||||
[TypesWithArray.String]: LoopTerminationStringComparisonOperator,
|
||||
[TypesWithArray.Number]: LoopTerminationNumberComparisonOperator,
|
||||
[TypesWithArray.Boolean]: LoopTerminationBooleanComparisonOperator,
|
||||
[TypesWithArray.Object]: LoopTerminationObjectComparisonOperator,
|
||||
[TypesWithArray.ArrayString]: LoopTerminationStringArrayComparisonOperator,
|
||||
[TypesWithArray.ArrayNumber]: LoopTerminationStringArrayComparisonOperator,
|
||||
[TypesWithArray.ArrayBoolean]: LoopTerminationBooleanArrayComparisonOperator,
|
||||
[TypesWithArray.ArrayObject]: LoopTerminationObjectComparisonOperator,
|
||||
};
|
||||
|
||||
export enum AgentVariableType {
|
||||
Begin = 'begin',
|
||||
Conversation = 'conversation',
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Operator } from '../constant';
|
||||
import AgentForm from '../form/agent-form';
|
||||
import AkShareForm from '../form/akshare-form';
|
||||
import ArXivForm from '../form/arxiv-form';
|
||||
import BeginForm from '../form/begin-form';
|
||||
import BingForm from '../form/bing-form';
|
||||
@ -19,13 +18,11 @@ 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 Jin10Form from '../form/jin10-form';
|
||||
import KeywordExtractForm from '../form/keyword-extract-form';
|
||||
import ListOperationsForm from '../form/list-operations-form';
|
||||
import LoopForm from '../form/loop-form';
|
||||
import MessageForm from '../form/message-form';
|
||||
import ParserForm from '../form/parser-form';
|
||||
import PubMedForm from '../form/pubmed-form';
|
||||
import QWeatherForm from '../form/qweather-form';
|
||||
import RelevantForm from '../form/relevant-form';
|
||||
import RetrievalForm from '../form/retrieval-form/next';
|
||||
import RewriteQuestionForm from '../form/rewrite-question-form';
|
||||
@ -37,7 +34,6 @@ import TavilyExtractForm from '../form/tavily-extract-form';
|
||||
import TavilyForm from '../form/tavily-form';
|
||||
import TokenizerForm from '../form/tokenizer-form';
|
||||
import ToolForm from '../form/tool-form';
|
||||
import TuShareForm from '../form/tushare-form';
|
||||
import UserFillUpForm from '../form/user-fill-up-form';
|
||||
import VariableAggregatorForm from '../form/variable-aggregator-form';
|
||||
import VariableAssignerForm from '../form/variable-assigner-form';
|
||||
@ -76,9 +72,6 @@ export const FormConfigMap = {
|
||||
[Operator.DuckDuckGo]: {
|
||||
component: DuckDuckGoForm,
|
||||
},
|
||||
[Operator.KeywordExtract]: {
|
||||
component: KeywordExtractForm,
|
||||
},
|
||||
[Operator.Wikipedia]: {
|
||||
component: WikipediaForm,
|
||||
},
|
||||
@ -100,9 +93,6 @@ export const FormConfigMap = {
|
||||
[Operator.GitHub]: {
|
||||
component: GithubForm,
|
||||
},
|
||||
[Operator.QWeather]: {
|
||||
component: QWeatherForm,
|
||||
},
|
||||
[Operator.ExeSQL]: {
|
||||
component: ExeSQLForm,
|
||||
},
|
||||
@ -112,18 +102,9 @@ export const FormConfigMap = {
|
||||
[Operator.WenCai]: {
|
||||
component: WenCaiForm,
|
||||
},
|
||||
[Operator.AkShare]: {
|
||||
component: AkShareForm,
|
||||
},
|
||||
[Operator.YahooFinance]: {
|
||||
component: YahooFinanceForm,
|
||||
},
|
||||
[Operator.Jin10]: {
|
||||
component: Jin10Form,
|
||||
},
|
||||
[Operator.TuShare]: {
|
||||
component: TuShareForm,
|
||||
},
|
||||
[Operator.Crawler]: {
|
||||
component: CrawlerForm,
|
||||
},
|
||||
@ -191,8 +172,13 @@ export const FormConfigMap = {
|
||||
[Operator.VariableAssigner]: {
|
||||
component: VariableAssignerForm,
|
||||
},
|
||||
|
||||
[Operator.VariableAggregator]: {
|
||||
component: VariableAggregatorForm,
|
||||
},
|
||||
[Operator.Loop]: {
|
||||
component: LoopForm,
|
||||
},
|
||||
[Operator.ExitLoop]: {
|
||||
component: () => <></>,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import { TopNFormField } from '@/components/top-n-item';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
const AkShareForm = ({ form, node }: INextOperatorForm) => {
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<TopNFormField max={99}></TopNFormField>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AkShareForm;
|
||||
@ -1,127 +0,0 @@
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
|
||||
import { PropsWithChildren, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBuildVariableOptions } from '../../hooks/use-get-begin-query';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
interface IProps {
|
||||
node?: RAGFlowNodeType;
|
||||
}
|
||||
|
||||
enum VariableType {
|
||||
Reference = 'reference',
|
||||
Input = 'input',
|
||||
}
|
||||
|
||||
const getVariableName = (type: string) =>
|
||||
type === VariableType.Reference ? 'component_id' : 'value';
|
||||
|
||||
const DynamicVariableForm = ({ node }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const valueOptions = useBuildVariableOptions(node?.id, node?.parentId);
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
const options = [
|
||||
{ value: VariableType.Reference, label: t('flow.reference') },
|
||||
{ value: VariableType.Input, label: t('flow.text') },
|
||||
];
|
||||
|
||||
const handleTypeChange = useCallback(
|
||||
(name: number) => () => {
|
||||
setTimeout(() => {
|
||||
form.setFieldValue(['query', name, 'component_id'], undefined);
|
||||
form.setFieldValue(['query', name, 'value'], undefined);
|
||||
}, 0);
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
return (
|
||||
<Form.List name="query">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
<Flex key={key} gap={10} align={'baseline'}>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'type']}
|
||||
className={styles.variableType}
|
||||
>
|
||||
<Select
|
||||
options={options}
|
||||
onChange={handleTypeChange(name)}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
<Form.Item noStyle dependencies={[name, 'type']}>
|
||||
{({ getFieldValue }) => {
|
||||
const type = getFieldValue(['query', name, 'type']);
|
||||
return (
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, getVariableName(type)]}
|
||||
className={styles.variableValue}
|
||||
>
|
||||
{type === VariableType.Reference ? (
|
||||
<Select
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
options={valueOptions}
|
||||
></Select>
|
||||
) : (
|
||||
<Input placeholder={t('common.pleaseInput')} />
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||
</Flex>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add({ type: VariableType.Reference })}
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
className={styles.addButton}
|
||||
>
|
||||
{t('flow.addVariable')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
);
|
||||
};
|
||||
|
||||
export function FormCollapse({
|
||||
children,
|
||||
title,
|
||||
}: PropsWithChildren<{ title: string }>) {
|
||||
return (
|
||||
<Collapse
|
||||
className={styles.dynamicInputVariable}
|
||||
defaultActiveKey={['1']}
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: <span className={styles.title}>{title}</span>,
|
||||
children,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const DynamicInputVariable = ({ node }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<FormCollapse title={t('flow.input')}>
|
||||
<DynamicVariableForm node={node}></DynamicVariableForm>
|
||||
</FormCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicInputVariable;
|
||||
@ -1,135 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { SideDown } from '@/assets/icon/next-icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBuildVariableOptions } from '../../hooks/use-get-begin-query';
|
||||
|
||||
interface IProps {
|
||||
node?: RAGFlowNodeType;
|
||||
}
|
||||
|
||||
enum VariableType {
|
||||
Reference = 'reference',
|
||||
Input = 'input',
|
||||
}
|
||||
|
||||
const getVariableName = (type: string) =>
|
||||
type === VariableType.Reference ? 'component_id' : 'value';
|
||||
|
||||
export function DynamicVariableForm({ node }: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name: 'query',
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const valueOptions = useBuildVariableOptions(node?.id, node?.parentId);
|
||||
|
||||
const options = [
|
||||
{ value: VariableType.Reference, label: t('flow.reference') },
|
||||
{ value: VariableType.Input, label: t('flow.text') },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => {
|
||||
const typeField = `query.${index}.type`;
|
||||
const typeValue = form.watch(typeField);
|
||||
return (
|
||||
<div key={field.id} className="flex items-center gap-1">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={typeField}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-2/5">
|
||||
<FormDescription />
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
options={options}
|
||||
onChange={(val) => {
|
||||
field.onChange(val);
|
||||
form.resetField(`query.${index}.value`);
|
||||
form.resetField(`query.${index}.component_id`);
|
||||
}}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`query.${index}.${getVariableName(typeValue)}`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormDescription />
|
||||
<FormControl>
|
||||
{typeValue === VariableType.Reference ? (
|
||||
<RAGFlowSelect
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
{...field}
|
||||
options={valueOptions}
|
||||
></RAGFlowSelect>
|
||||
) : (
|
||||
<Input placeholder={t('common.pleaseInput')} {...field} />
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Trash2
|
||||
className="cursor-pointer mx-3 size-4 text-colors-text-functional-danger"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<Button onClick={append} className="mt-4" variant={'outline'} size={'sm'}>
|
||||
<Plus />
|
||||
{t('flow.addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DynamicInputVariable({ node }: IProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<CollapsibleTrigger className="flex justify-between w-full pb-2">
|
||||
<span className="font-bold text-2xl text-colors-text-neutral-strong">
|
||||
{t('flow.input')}
|
||||
</span>
|
||||
<Button variant={'icon'} size={'icon'}>
|
||||
<SideDown />
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<DynamicVariableForm node={node}></DynamicVariableForm>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
@ -192,7 +192,7 @@ export default function VariablePickerMenuPlugin({
|
||||
|
||||
const [queryString, setQueryString] = React.useState<string | null>('');
|
||||
|
||||
let options = useFilterQueryVariableOptionsByTypes(types);
|
||||
let options = useFilterQueryVariableOptionsByTypes({ types });
|
||||
|
||||
if (baseOptions) {
|
||||
options = baseOptions as typeof options;
|
||||
|
||||
@ -20,7 +20,7 @@ export function QueryVariableList({
|
||||
const form = useFormContext();
|
||||
const name = 'query';
|
||||
|
||||
let options = useFilterQueryVariableOptionsByTypes(types);
|
||||
let options = useFilterQueryVariableOptionsByTypes({ types });
|
||||
|
||||
const secondOptions = flatOptions(options);
|
||||
|
||||
|
||||
@ -9,7 +9,10 @@ import { ReactNode } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { JsonSchemaDataType } from '../../constant';
|
||||
import { useFilterQueryVariableOptionsByTypes } from '../../hooks/use-get-begin-query';
|
||||
import {
|
||||
BuildQueryVariableOptions,
|
||||
useFilterQueryVariableOptionsByTypes,
|
||||
} from '../../hooks/use-get-begin-query';
|
||||
import { GroupedSelectWithSecondaryMenu } from './select-with-secondary-menu';
|
||||
|
||||
type QueryVariableProps = {
|
||||
@ -21,7 +24,7 @@ type QueryVariableProps = {
|
||||
onChange?: (value: string) => void;
|
||||
pureQuery?: boolean;
|
||||
value?: string;
|
||||
};
|
||||
} & BuildQueryVariableOptions;
|
||||
|
||||
export function QueryVariable({
|
||||
name = 'query',
|
||||
@ -32,11 +35,17 @@ export function QueryVariable({
|
||||
onChange,
|
||||
pureQuery = false,
|
||||
value,
|
||||
nodeIds = [],
|
||||
variablesExceptOperatorOutputs,
|
||||
}: QueryVariableProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
|
||||
const finalOptions = useFilterQueryVariableOptionsByTypes(types);
|
||||
const finalOptions = useFilterQueryVariableOptionsByTypes({
|
||||
types,
|
||||
nodeIds,
|
||||
variablesExceptOperatorOutputs,
|
||||
});
|
||||
|
||||
const renderWidget = (
|
||||
value?: string,
|
||||
|
||||
@ -49,7 +49,7 @@ export function VariableTable({
|
||||
nodeId,
|
||||
}: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const { getLabel } = useGetVariableLabelOrTypeByValue(nodeId!);
|
||||
const { getLabel } = useGetVariableLabelOrTypeByValue({ nodeId: nodeId! });
|
||||
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
import { FormContainer } from '@/components/form-container';
|
||||
import { KeyInput } from '@/components/key-input';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { BlockButton, Button } from '@/components/ui/button';
|
||||
import {
|
||||
FormControl,
|
||||
@ -11,13 +10,17 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Operator } from '@/constants/agent';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { X } from 'lucide-react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBuildSubNodeOutputOptions } from './use-build-options';
|
||||
import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query';
|
||||
import useGraphStore from '../../store';
|
||||
import { QueryVariable } from '../components/query-variable';
|
||||
|
||||
interface IProps {
|
||||
node?: RAGFlowNodeType;
|
||||
@ -26,28 +29,22 @@ interface IProps {
|
||||
export function DynamicOutputForm({ node }: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const options = useBuildSubNodeOutputOptions(node?.id);
|
||||
const { nodes } = useGraphStore((state) => state);
|
||||
|
||||
const childNodeIds = nodes
|
||||
.filter(
|
||||
(x) =>
|
||||
x.parentId === node?.id &&
|
||||
x.data.label !== Operator.IterationStart &&
|
||||
!isEmpty(x.data?.form?.outputs),
|
||||
)
|
||||
.map((x) => x.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 { getType } = useGetVariableLabelOrTypeByValue({
|
||||
nodeIds: childNodeIds,
|
||||
});
|
||||
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name: name,
|
||||
@ -77,25 +74,15 @@ export function DynamicOutputForm({ node }: IProps) {
|
||||
)}
|
||||
/>
|
||||
<Separator className="w-3 text-text-secondary" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
<QueryVariable
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
hideLabel
|
||||
className="w-2/5"
|
||||
onChange={(val) => {
|
||||
form.setValue(typeField, `Array<${getType(val)}>`);
|
||||
}}
|
||||
nodeIds={childNodeIds}
|
||||
></QueryVariable>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={typeField}
|
||||
|
||||
@ -9,7 +9,6 @@ import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output } from '../components/output';
|
||||
import { QueryVariable } from '../components/query-variable';
|
||||
import { DynamicOutput } from './dynamic-output';
|
||||
import { DynamicVariables } from './dynamic-variables';
|
||||
import { OutputArray } from './interface';
|
||||
import { useValues } from './use-values';
|
||||
import { useWatchFormChange } from './use-watch-form-change';
|
||||
@ -53,7 +52,6 @@ function IterationForm({ node }: INextOperatorForm) {
|
||||
name="items_ref"
|
||||
types={ArrayFields as any[]}
|
||||
></QueryVariable>
|
||||
<DynamicVariables name="variables" label="Variables"></DynamicVariables>
|
||||
<DynamicOutput node={node}></DynamicOutput>
|
||||
<Output list={outputList}></Output>
|
||||
</FormWrapper>
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import { buildOutputOptions } from '@/utils/canvas-util';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { Operator } from '../../constant';
|
||||
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,145 +0,0 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Form, Input, Select } from 'antd';
|
||||
import { useMemo } from 'react';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
import {
|
||||
Jin10CalendarDatashapeOptions,
|
||||
Jin10CalendarTypeOptions,
|
||||
Jin10FlashTypeOptions,
|
||||
Jin10SymbolsDatatypeOptions,
|
||||
Jin10SymbolsTypeOptions,
|
||||
Jin10TypeOptions,
|
||||
} from '../../options';
|
||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
||||
|
||||
const Jin10Form = ({ onValuesChange, form, node }: IOperatorForm) => {
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
const jin10TypeOptions = useMemo(() => {
|
||||
return Jin10TypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`jin10TypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const jin10FlashTypeOptions = useMemo(() => {
|
||||
return Jin10FlashTypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`jin10FlashTypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const jin10CalendarTypeOptions = useMemo(() => {
|
||||
return Jin10CalendarTypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`jin10CalendarTypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const jin10CalendarDatashapeOptions = useMemo(() => {
|
||||
return Jin10CalendarDatashapeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`jin10CalendarDatashapeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const jin10SymbolsTypeOptions = useMemo(() => {
|
||||
return Jin10SymbolsTypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`jin10SymbolsTypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const jin10SymbolsDatatypeOptions = useMemo(() => {
|
||||
return Jin10SymbolsDatatypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`jin10SymbolsDatatypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<Form
|
||||
name="basic"
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
onValuesChange={onValuesChange}
|
||||
layout={'vertical'}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<Form.Item label={t('type')} name={'type'} initialValue={'flash'}>
|
||||
<Select options={jin10TypeOptions}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('secretKey')} name={'secret_key'}>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
<Form.Item noStyle dependencies={['type']}>
|
||||
{({ getFieldValue }) => {
|
||||
const type = getFieldValue('type');
|
||||
switch (type) {
|
||||
case 'flash':
|
||||
return (
|
||||
<>
|
||||
<Form.Item label={t('flashType')} name={'flash_type'}>
|
||||
<Select options={jin10FlashTypeOptions}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('contain')} name={'contain'}>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('filter')} name={'filter'}>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'calendar':
|
||||
return (
|
||||
<>
|
||||
<Form.Item label={t('calendarType')} name={'calendar_type'}>
|
||||
<Select options={jin10CalendarTypeOptions}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('calendarDatashape')}
|
||||
name={'calendar_datashape'}
|
||||
>
|
||||
<Select options={jin10CalendarDatashapeOptions}></Select>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'symbols':
|
||||
return (
|
||||
<>
|
||||
<Form.Item label={t('symbolsType')} name={'symbols_type'}>
|
||||
<Select options={jin10SymbolsTypeOptions}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('symbolsDatatype')}
|
||||
name={'symbols_datatype'}
|
||||
>
|
||||
<Select options={jin10SymbolsDatatypeOptions}></Select>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'news':
|
||||
return (
|
||||
<>
|
||||
<Form.Item label={t('contain')} name={'contain'}>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('filter')} name={'filter'}>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default Jin10Form;
|
||||
@ -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,33 +1,27 @@
|
||||
import { BoolSegmented } from '@/components/bool-segmented';
|
||||
import { KeyInput } from '@/components/key-input';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
import { Editor, loader } from '@monaco-editor/react';
|
||||
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
||||
import { X } from 'lucide-react';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { TypesWithArray } from '../../constant';
|
||||
import { buildConversationVariableSelectOptions } from '../../utils';
|
||||
import { InputMode, TypesWithArray } from '../../constant';
|
||||
import {
|
||||
InputModeOptions,
|
||||
buildConversationVariableSelectOptions,
|
||||
} from '../../utils';
|
||||
import { DynamicFormHeader } from '../components/dynamic-fom-header';
|
||||
import { QueryVariable } from '../components/query-variable';
|
||||
import { useInitializeConditions } from './use-watch-form-change';
|
||||
|
||||
loader.config({ paths: { vs: '/vs' } });
|
||||
|
||||
enum InputMode {
|
||||
Constant = 'constant',
|
||||
Variable = 'variable',
|
||||
}
|
||||
|
||||
const InputModeOptions = buildOptions(InputMode);
|
||||
|
||||
type SelectKeysProps = {
|
||||
name: string;
|
||||
label: ReactNode;
|
||||
@ -35,42 +29,15 @@ type SelectKeysProps = {
|
||||
keyField?: string;
|
||||
valueField?: string;
|
||||
operatorField?: string;
|
||||
nodeId?: string;
|
||||
};
|
||||
|
||||
type RadioGroupProps = React.ComponentProps<typeof RadioGroupPrimitive.Root>;
|
||||
|
||||
type RadioButtonProps = Partial<
|
||||
Omit<RadioGroupProps, 'onValueChange'> & {
|
||||
onChange: RadioGroupProps['onValueChange'];
|
||||
}
|
||||
>;
|
||||
|
||||
function RadioButton({ value, onChange }: RadioButtonProps) {
|
||||
return (
|
||||
<RadioGroup
|
||||
defaultValue="yes"
|
||||
className="flex"
|
||||
value={value}
|
||||
onValueChange={onChange}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem value="yes" id="r1" />
|
||||
<Label htmlFor="r1">Yes</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<RadioGroupItem value="no" id="r2" />
|
||||
<Label htmlFor="r2">No</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const VariableTypeOptions = buildConversationVariableSelectOptions();
|
||||
|
||||
const modeField = 'mode';
|
||||
const modeField = 'input_mode';
|
||||
|
||||
const ConstantValueMap = {
|
||||
[TypesWithArray.Boolean]: 'yes',
|
||||
[TypesWithArray.Boolean]: true,
|
||||
[TypesWithArray.Number]: 0,
|
||||
[TypesWithArray.String]: '',
|
||||
[TypesWithArray.ArrayBoolean]: '[]',
|
||||
@ -85,8 +52,9 @@ export function DynamicVariables({
|
||||
label,
|
||||
tooltip,
|
||||
keyField = 'variable',
|
||||
valueField = 'parameter',
|
||||
operatorField = 'operator',
|
||||
valueField = 'value',
|
||||
operatorField = 'type',
|
||||
nodeId,
|
||||
}: SelectKeysProps) {
|
||||
const form = useFormContext();
|
||||
const isDarkTheme = useIsDarkTheme();
|
||||
@ -96,6 +64,9 @@ export function DynamicVariables({
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const { initializeVariableRelatedConditions } =
|
||||
useInitializeConditions(nodeId);
|
||||
|
||||
const initializeValue = useCallback(
|
||||
(mode: string, variableType: string, valueFieldAlias: string) => {
|
||||
if (mode === InputMode.Variable) {
|
||||
@ -112,23 +83,27 @@ export function DynamicVariables({
|
||||
(mode: string, valueFieldAlias: string, operatorFieldAlias: string) => {
|
||||
const variableType = form.getValues(operatorFieldAlias);
|
||||
initializeValue(mode, variableType, valueFieldAlias);
|
||||
// if (mode === InputMode.Variable) {
|
||||
// form.setValue(valueFieldAlias, '');
|
||||
// } else {
|
||||
// const val = ConstantValueMap[variableType as TypesWithArray];
|
||||
// form.setValue(valueFieldAlias, val);
|
||||
// }
|
||||
},
|
||||
[form, initializeValue],
|
||||
);
|
||||
|
||||
const handleVariableTypeChange = useCallback(
|
||||
(variableType: string, valueFieldAlias: string, modeFieldAlias: string) => {
|
||||
(
|
||||
variableType: string,
|
||||
valueFieldAlias: string,
|
||||
modeFieldAlias: string,
|
||||
keyFieldAlias: string,
|
||||
) => {
|
||||
const mode = form.getValues(modeFieldAlias);
|
||||
|
||||
initializeVariableRelatedConditions(
|
||||
form.getValues(keyFieldAlias),
|
||||
variableType,
|
||||
);
|
||||
|
||||
initializeValue(mode, variableType, valueFieldAlias);
|
||||
},
|
||||
[form, initializeValue],
|
||||
[form, initializeValue, initializeVariableRelatedConditions],
|
||||
);
|
||||
|
||||
const renderParameter = useCallback(
|
||||
@ -138,7 +113,7 @@ export function DynamicVariables({
|
||||
|
||||
if (mode === InputMode.Constant) {
|
||||
if (logicalOperator === TypesWithArray.Boolean) {
|
||||
return <RadioButton></RadioButton>;
|
||||
return <BoolSegmented></BoolSegmented>;
|
||||
}
|
||||
|
||||
if (logicalOperator === TypesWithArray.Number) {
|
||||
@ -211,6 +186,7 @@ export function DynamicVariables({
|
||||
val,
|
||||
valueFieldAlias,
|
||||
modeFieldAlias,
|
||||
keyFieldAlias,
|
||||
);
|
||||
field.onChange(val);
|
||||
}}
|
||||
52
web/src/pages/agent/form/loop-form/index.tsx
Normal file
52
web/src/pages/agent/form/loop-form/index.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { FormLayout } from '@/constants/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { memo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { initialLoopValues } from '../../constant';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { DynamicVariables } from './dynamic-variables';
|
||||
import { LoopTerminationCondition } from './loop-termination-condition';
|
||||
import { FormSchema, LoopFormSchemaType } from './schema';
|
||||
import { useFormValues } from './use-values';
|
||||
import { useWatchFormChange } from './use-watch-form-change';
|
||||
|
||||
function LoopForm({ node }: INextOperatorForm) {
|
||||
const defaultValues = useFormValues(initialLoopValues, node);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm<LoopFormSchemaType>({
|
||||
defaultValues: defaultValues,
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<DynamicVariables
|
||||
name="loop_variables"
|
||||
label={t('flow.loopVariables')}
|
||||
nodeId={node?.id}
|
||||
></DynamicVariables>
|
||||
<LoopTerminationCondition
|
||||
label={t('flow.loopTerminationCondition')}
|
||||
nodeId={node?.id}
|
||||
></LoopTerminationCondition>
|
||||
<SliderInputFormField
|
||||
min={1}
|
||||
max={100}
|
||||
name="maximum_loop_count"
|
||||
label={t('flow.maximumLoopCount')}
|
||||
layout={FormLayout.Vertical}
|
||||
></SliderInputFormField>
|
||||
</FormWrapper>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(LoopForm);
|
||||
@ -0,0 +1,316 @@
|
||||
import { BoolSegmented } from '@/components/bool-segmented';
|
||||
import { LogicalOperator } from '@/components/logical-operator';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { ComparisonOperator, SwitchLogicOperator } from '@/constants/agent';
|
||||
import { loader } from '@monaco-editor/react';
|
||||
import { toLower } from 'lodash';
|
||||
import { X } from 'lucide-react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import {
|
||||
AgentVariableType,
|
||||
InputMode,
|
||||
JsonSchemaDataType,
|
||||
} from '../../constant';
|
||||
import { useFilterChildNodeIds } from '../../hooks/use-filter-child-node-ids';
|
||||
import { useGetVariableLabelOrTypeByValue } from '../../hooks/use-get-begin-query';
|
||||
import { InputModeOptions } from '../../utils';
|
||||
import { DynamicFormHeader } from '../components/dynamic-fom-header';
|
||||
import { QueryVariable } from '../components/query-variable';
|
||||
import { LoopFormSchemaType } from './schema';
|
||||
import { useBuildLogicalOptions } from './use-build-logical-options';
|
||||
import {
|
||||
ConditionKeyType,
|
||||
ConditionModeType,
|
||||
ConditionOperatorType,
|
||||
ConditionValueType,
|
||||
useInitializeConditions,
|
||||
} from './use-watch-form-change';
|
||||
|
||||
loader.config({ paths: { vs: '/vs' } });
|
||||
|
||||
const VariablesExceptOperatorOutputs = [AgentVariableType.Conversation];
|
||||
|
||||
type LoopTerminationConditionProps = {
|
||||
label: ReactNode;
|
||||
tooltip?: string;
|
||||
keyField?: string;
|
||||
valueField?: string;
|
||||
operatorField?: string;
|
||||
modeField?: string;
|
||||
nodeId?: string;
|
||||
};
|
||||
|
||||
const EmptyFields = [ComparisonOperator.Empty, ComparisonOperator.NotEmpty];
|
||||
|
||||
const LogicalOperatorFieldName = 'logical_operator';
|
||||
|
||||
const name = 'loop_termination_condition';
|
||||
|
||||
export function LoopTerminationCondition({
|
||||
label,
|
||||
tooltip,
|
||||
keyField = 'variable',
|
||||
valueField = 'value',
|
||||
operatorField = 'operator',
|
||||
modeField = 'input_mode',
|
||||
nodeId,
|
||||
}: LoopTerminationConditionProps) {
|
||||
const form = useFormContext<LoopFormSchemaType>();
|
||||
const childNodeIds = useFilterChildNodeIds(nodeId);
|
||||
|
||||
const nodeIds = useMemo(() => {
|
||||
if (!nodeId) return [];
|
||||
return [nodeId, ...childNodeIds];
|
||||
}, [childNodeIds, nodeId]);
|
||||
|
||||
const { getType } = useGetVariableLabelOrTypeByValue({
|
||||
nodeIds: nodeIds,
|
||||
variablesExceptOperatorOutputs: VariablesExceptOperatorOutputs,
|
||||
});
|
||||
|
||||
const {
|
||||
initializeConditionMode,
|
||||
initializeConditionOperator,
|
||||
initializeConditionValue,
|
||||
} = useInitializeConditions(nodeId);
|
||||
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name: name,
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const { buildLogicalOptions } = useBuildLogicalOptions();
|
||||
|
||||
const getVariableType = useCallback(
|
||||
(keyFieldName: ConditionKeyType) => {
|
||||
const key = form.getValues(keyFieldName);
|
||||
return toLower(getType(key));
|
||||
},
|
||||
[form, getType],
|
||||
);
|
||||
|
||||
const initializeMode = useCallback(
|
||||
(modeFieldAlias: ConditionModeType, keyFieldAlias: ConditionKeyType) => {
|
||||
const keyType = getVariableType(keyFieldAlias);
|
||||
|
||||
initializeConditionMode(modeFieldAlias, keyType);
|
||||
},
|
||||
[getVariableType, initializeConditionMode],
|
||||
);
|
||||
|
||||
const initializeValue = useCallback(
|
||||
(valueFieldAlias: ConditionValueType, keyFieldAlias: ConditionKeyType) => {
|
||||
const keyType = getVariableType(keyFieldAlias);
|
||||
|
||||
initializeConditionValue(valueFieldAlias, keyType);
|
||||
},
|
||||
[getVariableType, initializeConditionValue],
|
||||
);
|
||||
|
||||
const handleVariableChange = useCallback(
|
||||
(
|
||||
operatorFieldAlias: ConditionOperatorType,
|
||||
valueFieldAlias: ConditionValueType,
|
||||
keyFieldAlias: ConditionKeyType,
|
||||
modeFieldAlias: ConditionModeType,
|
||||
) => {
|
||||
return () => {
|
||||
initializeConditionOperator(
|
||||
operatorFieldAlias,
|
||||
getVariableType(keyFieldAlias),
|
||||
);
|
||||
|
||||
initializeMode(modeFieldAlias, keyFieldAlias);
|
||||
|
||||
initializeValue(valueFieldAlias, keyFieldAlias);
|
||||
};
|
||||
},
|
||||
[
|
||||
getVariableType,
|
||||
initializeConditionOperator,
|
||||
initializeMode,
|
||||
initializeValue,
|
||||
],
|
||||
);
|
||||
|
||||
const handleOperatorChange = useCallback(
|
||||
(
|
||||
valueFieldAlias: ConditionValueType,
|
||||
keyFieldAlias: ConditionKeyType,
|
||||
modeFieldAlias: ConditionModeType,
|
||||
) => {
|
||||
initializeMode(modeFieldAlias, keyFieldAlias);
|
||||
initializeValue(valueFieldAlias, keyFieldAlias);
|
||||
},
|
||||
[initializeMode, initializeValue],
|
||||
);
|
||||
|
||||
const handleModeChange = useCallback(
|
||||
(mode: string, valueFieldAlias: ConditionValueType) => {
|
||||
form.setValue(valueFieldAlias, mode === InputMode.Constant ? 0 : '', {
|
||||
shouldDirty: true,
|
||||
});
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const renderParameterPanel = useCallback(
|
||||
(
|
||||
keyFieldName: ConditionKeyType,
|
||||
valueFieldAlias: ConditionValueType,
|
||||
modeFieldAlias: ConditionModeType,
|
||||
operatorFieldAlias: ConditionOperatorType,
|
||||
) => {
|
||||
const type = getVariableType(keyFieldName);
|
||||
const mode = form.getValues(modeFieldAlias);
|
||||
const operator = form.getValues(operatorFieldAlias);
|
||||
|
||||
if (EmptyFields.includes(operator as ComparisonOperator)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === JsonSchemaDataType.Number) {
|
||||
return (
|
||||
<section className="flex items-center gap-1">
|
||||
<RAGFlowFormItem name={modeFieldAlias}>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
value={field.value}
|
||||
onChange={(val) => {
|
||||
handleModeChange(val, valueFieldAlias);
|
||||
field.onChange(val);
|
||||
}}
|
||||
options={InputModeOptions}
|
||||
></SelectWithSearch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
<Separator className="w-2" />
|
||||
{mode === InputMode.Constant ? (
|
||||
<RAGFlowFormItem name={valueFieldAlias}>
|
||||
<Input type="number" />
|
||||
</RAGFlowFormItem>
|
||||
) : (
|
||||
<QueryVariable
|
||||
types={[JsonSchemaDataType.Number]}
|
||||
hideLabel
|
||||
pureQuery
|
||||
name={valueFieldAlias}
|
||||
className="flex-1 min-w-0"
|
||||
></QueryVariable>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === JsonSchemaDataType.Boolean) {
|
||||
return (
|
||||
<RAGFlowFormItem name={valueFieldAlias} className="w-full">
|
||||
<BoolSegmented></BoolSegmented>
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RAGFlowFormItem name={valueFieldAlias} className="w-full">
|
||||
<Input />
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
},
|
||||
[form, getVariableType, handleModeChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="space-y-2">
|
||||
<DynamicFormHeader
|
||||
label={label}
|
||||
tooltip={tooltip}
|
||||
onClick={() => {
|
||||
if (fields.length === 1) {
|
||||
form.setValue(LogicalOperatorFieldName, SwitchLogicOperator.And);
|
||||
}
|
||||
append({ [keyField]: '', [valueField]: '' });
|
||||
}}
|
||||
></DynamicFormHeader>
|
||||
<section className="flex">
|
||||
{fields.length > 1 && (
|
||||
<LogicalOperator name={LogicalOperatorFieldName}></LogicalOperator>
|
||||
)}
|
||||
<div className="space-y-5 flex-1 min-w-0">
|
||||
{fields.map((field, index) => {
|
||||
const keyFieldAlias =
|
||||
`${name}.${index}.${keyField}` as ConditionKeyType;
|
||||
const valueFieldAlias =
|
||||
`${name}.${index}.${valueField}` as ConditionValueType;
|
||||
const operatorFieldAlias =
|
||||
`${name}.${index}.${operatorField}` as ConditionOperatorType;
|
||||
const modeFieldAlias =
|
||||
`${name}.${index}.${modeField}` as ConditionModeType;
|
||||
|
||||
return (
|
||||
<section key={field.id} className="flex gap-2">
|
||||
<div className="flex-1 space-y-3 min-w-0">
|
||||
<div className="flex items-center">
|
||||
<QueryVariable
|
||||
name={keyFieldAlias}
|
||||
hideLabel
|
||||
className="flex-1 min-w-0"
|
||||
onChange={handleVariableChange(
|
||||
operatorFieldAlias,
|
||||
valueFieldAlias,
|
||||
keyFieldAlias,
|
||||
modeFieldAlias,
|
||||
)}
|
||||
nodeIds={nodeIds}
|
||||
variablesExceptOperatorOutputs={
|
||||
VariablesExceptOperatorOutputs
|
||||
}
|
||||
></QueryVariable>
|
||||
|
||||
<Separator className="w-2" />
|
||||
|
||||
<RAGFlowFormItem
|
||||
name={operatorFieldAlias}
|
||||
className="w-1/3"
|
||||
>
|
||||
{({ onChange, value }) => (
|
||||
<SelectWithSearch
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
handleOperatorChange(
|
||||
valueFieldAlias,
|
||||
keyFieldAlias,
|
||||
modeFieldAlias,
|
||||
);
|
||||
onChange(val);
|
||||
}}
|
||||
options={buildLogicalOptions(
|
||||
getVariableType(keyFieldAlias),
|
||||
)}
|
||||
></SelectWithSearch>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
</div>
|
||||
{renderParameterPanel(
|
||||
keyFieldAlias,
|
||||
valueFieldAlias,
|
||||
modeFieldAlias,
|
||||
operatorFieldAlias,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
||||
<X />
|
||||
</Button>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
24
web/src/pages/agent/form/loop-form/schema.ts
Normal file
24
web/src/pages/agent/form/loop-form/schema.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FormSchema = z.object({
|
||||
loop_variables: z.array(
|
||||
z.object({
|
||||
variable: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
value: z.string().or(z.number()).or(z.boolean()).optional(),
|
||||
input_mode: z.string(),
|
||||
}),
|
||||
),
|
||||
logical_operator: z.string(),
|
||||
loop_termination_condition: z.array(
|
||||
z.object({
|
||||
variable: z.string().optional(),
|
||||
operator: z.string().optional(),
|
||||
value: z.string().or(z.number()).or(z.boolean()).optional(),
|
||||
input_mode: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
maximum_loop_count: z.number(),
|
||||
});
|
||||
|
||||
export type LoopFormSchemaType = z.infer<typeof FormSchema>;
|
||||
@ -0,0 +1,27 @@
|
||||
import { SwitchOperatorOptions } from '@/constants/agent';
|
||||
import { camelCase, toLower } from 'lodash';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LoopTerminationStringComparisonOperatorMap } from '../../constant';
|
||||
|
||||
export function useBuildLogicalOptions() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const buildLogicalOptions = useCallback(
|
||||
(type: string) => {
|
||||
return LoopTerminationStringComparisonOperatorMap[
|
||||
toLower(type) as keyof typeof LoopTerminationStringComparisonOperatorMap
|
||||
]?.map((x) => ({
|
||||
label: t(
|
||||
`flow.switchOperatorOptions.${camelCase(SwitchOperatorOptions.find((y) => y.value === x)?.label || x)}`,
|
||||
),
|
||||
value: x,
|
||||
}));
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
return {
|
||||
buildLogicalOptions,
|
||||
};
|
||||
}
|
||||
20
web/src/pages/agent/form/loop-form/use-values.ts
Normal file
20
web/src/pages/agent/form/loop-form/use-values.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { isEmpty, omit } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function useFormValues(
|
||||
defaultValues: Record<string, any>,
|
||||
node?: RAGFlowNodeType,
|
||||
) {
|
||||
const values = useMemo(() => {
|
||||
const formData = node?.data?.form;
|
||||
|
||||
if (isEmpty(formData)) {
|
||||
return omit(defaultValues, 'outputs');
|
||||
}
|
||||
|
||||
return omit(formData, 'outputs');
|
||||
}, [defaultValues, node?.data?.form]);
|
||||
|
||||
return values;
|
||||
}
|
||||
116
web/src/pages/agent/form/loop-form/use-watch-form-change.ts
Normal file
116
web/src/pages/agent/form/loop-form/use-watch-form-change.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { JsonSchemaDataType } from '@/constants/agent';
|
||||
import { buildVariableValue } from '@/utils/canvas-util';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { UseFormReturn, useFormContext, useWatch } from 'react-hook-form';
|
||||
import { InputMode } from '../../constant';
|
||||
import { IOutputs } from '../../interface';
|
||||
import useGraphStore from '../../store';
|
||||
import { LoopFormSchemaType } from './schema';
|
||||
import { useBuildLogicalOptions } from './use-build-logical-options';
|
||||
|
||||
export function useWatchFormChange(
|
||||
id?: string,
|
||||
form?: UseFormReturn<LoopFormSchemaType>,
|
||||
) {
|
||||
let values = useWatch({ control: form?.control });
|
||||
const { replaceNodeForm } = useGraphStore((state) => state);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
let nextValues = {
|
||||
...values,
|
||||
outputs: values.loop_variables?.reduce((pre, cur) => {
|
||||
const variable = cur.variable;
|
||||
if (variable) {
|
||||
pre[variable] = {
|
||||
type: cur.type,
|
||||
value: '',
|
||||
};
|
||||
}
|
||||
return pre;
|
||||
}, {} as IOutputs),
|
||||
};
|
||||
|
||||
replaceNodeForm(id, nextValues);
|
||||
}
|
||||
}, [form?.formState.isDirty, id, replaceNodeForm, values]);
|
||||
}
|
||||
|
||||
type ConditionPrefixType = `loop_termination_condition.${number}.`;
|
||||
export type ConditionKeyType = `${ConditionPrefixType}variable`;
|
||||
export type ConditionModeType = `${ConditionPrefixType}input_mode`;
|
||||
export type ConditionValueType = `${ConditionPrefixType}value`;
|
||||
export type ConditionOperatorType = `${ConditionPrefixType}operator`;
|
||||
export function useInitializeConditions(id?: string) {
|
||||
const form = useFormContext<LoopFormSchemaType>();
|
||||
const { buildLogicalOptions } = useBuildLogicalOptions();
|
||||
|
||||
const initializeConditionMode = useCallback(
|
||||
(modeFieldAlias: ConditionModeType, keyType: string) => {
|
||||
if (keyType === JsonSchemaDataType.Number) {
|
||||
form.setValue(modeFieldAlias, InputMode.Constant, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const initializeConditionValue = useCallback(
|
||||
(valueFieldAlias: ConditionValueType, keyType: string) => {
|
||||
let initialValue: string | boolean | number = '';
|
||||
|
||||
if (keyType === JsonSchemaDataType.Number) {
|
||||
initialValue = 0;
|
||||
} else if (keyType === JsonSchemaDataType.Boolean) {
|
||||
initialValue = true;
|
||||
}
|
||||
|
||||
form.setValue(valueFieldAlias, initialValue, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const initializeConditionOperator = useCallback(
|
||||
(operatorFieldAlias: ConditionOperatorType, keyType: string) => {
|
||||
const logicalOptions = buildLogicalOptions(keyType);
|
||||
|
||||
form.setValue(operatorFieldAlias, logicalOptions?.at(0)?.value, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
},
|
||||
[buildLogicalOptions, form],
|
||||
);
|
||||
|
||||
const initializeVariableRelatedConditions = useCallback(
|
||||
(variable: string, variableType: string) => {
|
||||
form?.getValues('loop_termination_condition').forEach((x, idx) => {
|
||||
if (variable && x.variable === buildVariableValue(variable, id)) {
|
||||
const prefix: ConditionPrefixType = `loop_termination_condition.${idx}.`;
|
||||
initializeConditionMode(`${prefix}input_mode`, variableType);
|
||||
initializeConditionValue(`${prefix}value`, variableType);
|
||||
initializeConditionOperator(`${prefix}operator`, variableType);
|
||||
}
|
||||
});
|
||||
},
|
||||
[
|
||||
form,
|
||||
id,
|
||||
initializeConditionMode,
|
||||
initializeConditionOperator,
|
||||
initializeConditionValue,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
initializeVariableRelatedConditions,
|
||||
initializeConditionMode,
|
||||
initializeConditionValue,
|
||||
initializeConditionOperator,
|
||||
};
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import {
|
||||
QWeatherLangOptions,
|
||||
QWeatherTimePeriodOptions,
|
||||
QWeatherTypeOptions,
|
||||
QWeatherUserTypeOptions,
|
||||
} from '../../options';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
enum FormFieldName {
|
||||
Type = 'type',
|
||||
UserType = 'user_type',
|
||||
}
|
||||
|
||||
const QWeatherForm = ({ form, node }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
const typeValue = form.watch(FormFieldName.Type);
|
||||
|
||||
const qWeatherLangOptions = useMemo(() => {
|
||||
return QWeatherLangOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherLangOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const qWeatherTypeOptions = useMemo(() => {
|
||||
return QWeatherTypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherTypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const qWeatherUserTypeOptions = useMemo(() => {
|
||||
return QWeatherUserTypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherUserTypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const getQWeatherTimePeriodOptions = useCallback(() => {
|
||||
let options = QWeatherTimePeriodOptions;
|
||||
const userType = form.getValues(FormFieldName.UserType);
|
||||
if (userType === 'free') {
|
||||
options = options.slice(0, 3);
|
||||
}
|
||||
return options.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherTimePeriodOptions.${x}`),
|
||||
}));
|
||||
}, [form, t]);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="web_apikey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.webApiKey')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lang"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.lang')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={qWeatherLangOptions}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={FormFieldName.Type}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.type')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={qWeatherTypeOptions}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={FormFieldName.UserType}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.userType')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={qWeatherUserTypeOptions}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{typeValue === 'weather' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'time_period'}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.timePeriod')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={getQWeatherTimePeriodOptions()}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default QWeatherForm;
|
||||
@ -1,5 +1,4 @@
|
||||
import { Operator } from '../../constant';
|
||||
import AkShareForm from '../akshare-form';
|
||||
import ArXivForm from './arxiv-form';
|
||||
import BingForm from './bing-form';
|
||||
import CrawlerForm from './crawler-form';
|
||||
@ -29,7 +28,6 @@ export const ToolFormConfigMap = {
|
||||
[Operator.GoogleScholar]: GoogleScholarForm,
|
||||
[Operator.GitHub]: GithubForm,
|
||||
[Operator.ExeSQL]: ExeSQLForm,
|
||||
[Operator.AkShare]: AkShareForm,
|
||||
[Operator.YahooFinance]: YahooFinanceForm,
|
||||
[Operator.Crawler]: CrawlerForm,
|
||||
[Operator.Email]: EmailForm,
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { DatePicker, DatePickerProps, Form, Input, Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
import { TuShareSrcOptions } from '../../options';
|
||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
||||
|
||||
const DateTimePicker = ({
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
onChange?: (val: number | undefined) => void;
|
||||
value?: number | undefined;
|
||||
}) => {
|
||||
const handleChange: DatePickerProps['onChange'] = useCallback(
|
||||
(val: any) => {
|
||||
const nextVal = val?.format('YYYY-MM-DD HH:mm:ss');
|
||||
onChange?.(nextVal ? nextVal : undefined);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
// The value needs to be converted into a string and saved to the backend
|
||||
const nextValue = useMemo(() => {
|
||||
if (value) {
|
||||
return dayjs(value);
|
||||
}
|
||||
return undefined;
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
showTime
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
onChange={handleChange}
|
||||
value={nextValue}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TuShareForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
const tuShareSrcOptions = useMemo(() => {
|
||||
return TuShareSrcOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`tuShareSrcOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<Form
|
||||
name="basic"
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
onValuesChange={onValuesChange}
|
||||
layout={'vertical'}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<Form.Item
|
||||
label={t('token')}
|
||||
name={'token'}
|
||||
tooltip={'Get from https://tushare.pro/'}
|
||||
>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('src')} name={'src'}>
|
||||
<Select options={tuShareSrcOptions}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('startDate')} name={'start_date'}>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('endDate')} name={'end_date'}>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('keyword')} name={'keyword'}>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default TuShareForm;
|
||||
@ -1,7 +1,7 @@
|
||||
import { BoolSegmented } from '@/components/bool-segmented';
|
||||
import JsonEditor from '@/components/json-edit';
|
||||
import { BlockButton, Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Segmented } from '@/components/ui/segmented';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Trash2, X } from 'lucide-react';
|
||||
@ -15,7 +15,7 @@ export const useObjectFields = () => {
|
||||
(field: FieldValues, className?: string) => {
|
||||
const fieldValue = field.value ? true : false;
|
||||
return (
|
||||
<Segmented
|
||||
<BoolSegmented
|
||||
options={
|
||||
[
|
||||
{ value: true, label: 'True' },
|
||||
@ -27,7 +27,7 @@ export const useObjectFields = () => {
|
||||
onChange={field.onChange}
|
||||
className={className}
|
||||
itemClassName="justify-center flex-1"
|
||||
></Segmented>
|
||||
></BoolSegmented>
|
||||
);
|
||||
},
|
||||
[],
|
||||
|
||||
@ -10,7 +10,6 @@ import {
|
||||
NodeMap,
|
||||
Operator,
|
||||
initialAgentValues,
|
||||
initialAkShareValues,
|
||||
initialArXivValues,
|
||||
initialBeginValues,
|
||||
initialBingValues,
|
||||
@ -29,14 +28,12 @@ import {
|
||||
initialInvokeValues,
|
||||
initialIterationStartValues,
|
||||
initialIterationValues,
|
||||
initialJin10Values,
|
||||
initialKeywordExtractValues,
|
||||
initialListOperationsValues,
|
||||
initialLoopValues,
|
||||
initialMessageValues,
|
||||
initialNoteValues,
|
||||
initialParserValues,
|
||||
initialPubMedValues,
|
||||
initialQWeatherValues,
|
||||
initialRelevantValues,
|
||||
initialRetrievalValues,
|
||||
initialRewriteQuestionValues,
|
||||
@ -47,7 +44,6 @@ import {
|
||||
initialTavilyExtractValues,
|
||||
initialTavilyValues,
|
||||
initialTokenizerValues,
|
||||
initialTuShareValues,
|
||||
initialUserFillUpValues,
|
||||
initialVariableAggregatorValues,
|
||||
initialVariableAssignerValues,
|
||||
@ -68,6 +64,63 @@ function isBottomSubAgent(type: string, position: Position) {
|
||||
type === Operator.Tool
|
||||
);
|
||||
}
|
||||
|
||||
const GroupStartNodeMap = {
|
||||
[Operator.Iteration]: {
|
||||
id: `${Operator.IterationStart}:${humanId()}`,
|
||||
type: 'iterationStartNode',
|
||||
position: { x: 50, y: 100 },
|
||||
data: {
|
||||
label: Operator.IterationStart,
|
||||
name: Operator.IterationStart,
|
||||
form: initialIterationStartValues,
|
||||
},
|
||||
extent: 'parent' as 'parent',
|
||||
},
|
||||
[Operator.Loop]: {
|
||||
id: `${Operator.LoopStart}:${humanId()}`,
|
||||
type: 'loopStartNode',
|
||||
position: { x: 50, y: 100 },
|
||||
data: {
|
||||
label: Operator.LoopStart,
|
||||
name: Operator.LoopStart,
|
||||
form: {},
|
||||
},
|
||||
extent: 'parent' as 'parent',
|
||||
},
|
||||
};
|
||||
|
||||
function useAddGroupNode() {
|
||||
const { addEdge, addNode } = useGraphStore((state) => state);
|
||||
|
||||
const addGroupNode = useCallback(
|
||||
(operatorType: string, newNode: Node<any>, nodeId?: string) => {
|
||||
newNode.width = 500;
|
||||
newNode.height = 250;
|
||||
|
||||
const startNode: Node<any> =
|
||||
GroupStartNodeMap[operatorType as keyof typeof GroupStartNodeMap];
|
||||
|
||||
startNode.parentId = newNode.id;
|
||||
|
||||
addNode(newNode);
|
||||
addNode(startNode);
|
||||
|
||||
if (nodeId) {
|
||||
addEdge({
|
||||
source: nodeId,
|
||||
target: newNode.id,
|
||||
sourceHandle: NodeHandleId.Start,
|
||||
targetHandle: NodeHandleId.End,
|
||||
});
|
||||
}
|
||||
return newNode.id;
|
||||
},
|
||||
[addEdge, addNode],
|
||||
);
|
||||
|
||||
return { addGroupNode };
|
||||
}
|
||||
export const useInitializeOperatorParams = () => {
|
||||
const llmId = useFetchModelId();
|
||||
|
||||
@ -82,10 +135,6 @@ export const useInitializeOperatorParams = () => {
|
||||
llm_id: llmId,
|
||||
},
|
||||
[Operator.Message]: initialMessageValues,
|
||||
[Operator.KeywordExtract]: {
|
||||
...initialKeywordExtractValues,
|
||||
llm_id: llmId,
|
||||
},
|
||||
[Operator.DuckDuckGo]: initialDuckValues,
|
||||
[Operator.Wikipedia]: initialWikipediaValues,
|
||||
[Operator.PubMed]: initialPubMedValues,
|
||||
@ -95,14 +144,10 @@ export const useInitializeOperatorParams = () => {
|
||||
[Operator.GoogleScholar]: initialGoogleScholarValues,
|
||||
[Operator.SearXNG]: initialSearXNGValues,
|
||||
[Operator.GitHub]: initialGithubValues,
|
||||
[Operator.QWeather]: initialQWeatherValues,
|
||||
[Operator.ExeSQL]: initialExeSqlValues,
|
||||
[Operator.Switch]: initialSwitchValues,
|
||||
[Operator.WenCai]: initialWenCaiValues,
|
||||
[Operator.AkShare]: initialAkShareValues,
|
||||
[Operator.YahooFinance]: initialYahooFinanceValues,
|
||||
[Operator.Jin10]: initialJin10Values,
|
||||
[Operator.TuShare]: initialTuShareValues,
|
||||
[Operator.Note]: initialNoteValues,
|
||||
[Operator.Crawler]: initialCrawlerValues,
|
||||
[Operator.Invoke]: initialInvokeValues,
|
||||
@ -133,6 +178,9 @@ export const useInitializeOperatorParams = () => {
|
||||
[Operator.ListOperations]: initialListOperationsValues,
|
||||
[Operator.VariableAssigner]: initialVariableAssignerValues,
|
||||
[Operator.VariableAggregator]: initialVariableAggregatorValues,
|
||||
[Operator.Loop]: initialLoopValues,
|
||||
[Operator.LoopStart]: {},
|
||||
[Operator.ExitLoop]: {},
|
||||
};
|
||||
}, [llmId]);
|
||||
|
||||
@ -311,6 +359,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
const { addChildEdge } = useAddChildEdge();
|
||||
const { addToolNode } = useAddToolNode();
|
||||
const { resizeIterationNode } = useResizeIterationNode();
|
||||
const { addGroupNode } = useAddGroupNode();
|
||||
// const [reactFlowInstance, setReactFlowInstance] =
|
||||
// useState<ReactFlowInstance<any, any>>();
|
||||
|
||||
@ -376,33 +425,8 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
}
|
||||
}
|
||||
|
||||
if (type === Operator.Iteration) {
|
||||
newNode.width = 500;
|
||||
newNode.height = 250;
|
||||
const iterationStartNode: Node<any> = {
|
||||
id: `${Operator.IterationStart}:${humanId()}`,
|
||||
type: 'iterationStartNode',
|
||||
position: { x: 50, y: 100 },
|
||||
// draggable: false,
|
||||
data: {
|
||||
label: Operator.IterationStart,
|
||||
name: Operator.IterationStart,
|
||||
form: initialIterationStartValues,
|
||||
},
|
||||
parentId: newNode.id,
|
||||
extent: 'parent',
|
||||
};
|
||||
addNode(newNode);
|
||||
addNode(iterationStartNode);
|
||||
if (nodeId) {
|
||||
addEdge({
|
||||
source: nodeId,
|
||||
target: newNode.id,
|
||||
sourceHandle: NodeHandleId.Start,
|
||||
targetHandle: NodeHandleId.End,
|
||||
});
|
||||
}
|
||||
return newNode.id;
|
||||
if ([Operator.Iteration, Operator.Loop].includes(type as Operator)) {
|
||||
return addGroupNode(type, newNode, nodeId);
|
||||
} else if (
|
||||
type === Operator.Agent &&
|
||||
params.position === Position.Bottom
|
||||
@ -456,6 +480,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
[
|
||||
addChildEdge,
|
||||
addEdge,
|
||||
addGroupNode,
|
||||
addNode,
|
||||
addToolNode,
|
||||
calculateNewlyBackChildPosition,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { buildNodeOutputOptions } from '@/utils/canvas-util';
|
||||
import { buildUpstreamNodeOutputOptions } from '@/utils/canvas-util';
|
||||
import { useMemo } from 'react';
|
||||
import { Operator } from '../constant';
|
||||
import OperatorIcon from '../operator-icon';
|
||||
@ -9,7 +9,7 @@ export function useBuildNodeOutputOptions(nodeId?: string) {
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
|
||||
return useMemo(() => {
|
||||
return buildNodeOutputOptions({
|
||||
return buildUpstreamNodeOutputOptions({
|
||||
nodes,
|
||||
edges,
|
||||
nodeId,
|
||||
|
||||
10
web/src/pages/agent/hooks/use-filter-child-node-ids.ts
Normal file
10
web/src/pages/agent/hooks/use-filter-child-node-ids.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { filterChildNodeIds } from '@/utils/canvas-util';
|
||||
import useGraphStore from '../store';
|
||||
|
||||
export function useFilterChildNodeIds(nodeId?: string) {
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
|
||||
const childNodeIds = filterChildNodeIds(nodes, nodeId);
|
||||
|
||||
return childNodeIds ?? [];
|
||||
}
|
||||
@ -1,15 +1,21 @@
|
||||
import { AgentGlobals, AgentStructuredOutputField } from '@/constants/agent';
|
||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { buildNodeOutputOptions, isAgentStructured } from '@/utils/canvas-util';
|
||||
import {
|
||||
buildNodeOutputOptions,
|
||||
buildOutputOptions,
|
||||
buildUpstreamNodeOutputOptions,
|
||||
isAgentStructured,
|
||||
} from '@/utils/canvas-util';
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty, toLower } from 'lodash';
|
||||
import { flatten, isEmpty, toLower } from 'lodash';
|
||||
import get from 'lodash/get';
|
||||
import { MessageSquareCode } from 'lucide-react';
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
AgentDialogueMode,
|
||||
AgentVariableType,
|
||||
BeginId,
|
||||
BeginQueryType,
|
||||
JsonSchemaDataType,
|
||||
@ -85,20 +91,39 @@ export const useGetBeginNodeDataQueryIsSafe = () => {
|
||||
return isBeginNodeDataQuerySafe;
|
||||
};
|
||||
|
||||
export function useBuildNodeOutputOptions(nodeId?: string) {
|
||||
export function useBuildUpstreamNodeOutputOptions(nodeId?: string) {
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
|
||||
return useMemo(() => {
|
||||
return buildNodeOutputOptions({
|
||||
return buildUpstreamNodeOutputOptions({
|
||||
nodes,
|
||||
edges,
|
||||
nodeId,
|
||||
Icon: ({ name }) => <OperatorIcon name={name as Operator}></OperatorIcon>,
|
||||
});
|
||||
}, [edges, nodeId, nodes]);
|
||||
}
|
||||
|
||||
export function useBuildParentOutputOptions(parentId?: string) {
|
||||
const { getNode, getOperatorTypeFromId } = useGraphStore((state) => state);
|
||||
const parentNode = getNode(parentId);
|
||||
|
||||
const parentType = getOperatorTypeFromId(parentId);
|
||||
|
||||
if (
|
||||
parentType &&
|
||||
[Operator.Loop].includes(parentType as Operator) &&
|
||||
parentNode
|
||||
) {
|
||||
const options = buildOutputOptions(parentNode);
|
||||
if (options) {
|
||||
return [options];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// exclude nodes with branches
|
||||
const ExcludedNodes = [
|
||||
Operator.Categorize,
|
||||
@ -120,7 +145,7 @@ function transferToVariableType(type: string) {
|
||||
return type;
|
||||
}
|
||||
|
||||
export function useBuildBeginVariableOptions() {
|
||||
export function useBuildBeginDynamicVariableOptions() {
|
||||
const inputs = useSelectBeginNodeDataInputs();
|
||||
|
||||
const options = useMemo(() => {
|
||||
@ -144,6 +169,30 @@ export function useBuildBeginVariableOptions() {
|
||||
|
||||
const Env = 'env.';
|
||||
|
||||
export function useBuildGlobalWithBeginVariableOptions() {
|
||||
const { data } = useFetchAgent();
|
||||
const dynamicBeginOptions = useBuildBeginDynamicVariableOptions();
|
||||
const globals = data?.dsl?.globals ?? {};
|
||||
const globalOptions = Object.entries(globals)
|
||||
.filter(([key]) => !key.startsWith(Env))
|
||||
.map(([key, value]) => ({
|
||||
label: key,
|
||||
value: key,
|
||||
icon: <OperatorIcon name={Operator.Begin} className="block" />,
|
||||
parentLabel: <span>{t('flow.beginInput')}</span>,
|
||||
type: Array.isArray(value)
|
||||
? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '<file>' : ''}`
|
||||
: typeof value,
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
...dynamicBeginOptions[0],
|
||||
options: [...(dynamicBeginOptions[0]?.options ?? []), ...globalOptions],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function useBuildConversationVariableOptions() {
|
||||
const { data } = useFetchAgent();
|
||||
|
||||
@ -175,55 +224,88 @@ export function useBuildConversationVariableOptions() {
|
||||
}
|
||||
|
||||
export const useBuildVariableOptions = (nodeId?: string, parentId?: string) => {
|
||||
const nodeOutputOptions = useBuildNodeOutputOptions(nodeId);
|
||||
const parentNodeOutputOptions = useBuildNodeOutputOptions(parentId);
|
||||
const beginOptions = useBuildBeginVariableOptions();
|
||||
const upstreamNodeOutputOptions = useBuildUpstreamNodeOutputOptions(nodeId);
|
||||
const parentNodeOutputOptions = useBuildParentOutputOptions(parentId);
|
||||
const parentUpstreamNodeOutputOptions =
|
||||
useBuildUpstreamNodeOutputOptions(parentId);
|
||||
|
||||
const options = useMemo(() => {
|
||||
return [...beginOptions, ...nodeOutputOptions, ...parentNodeOutputOptions];
|
||||
}, [beginOptions, nodeOutputOptions, parentNodeOutputOptions]);
|
||||
return [
|
||||
...upstreamNodeOutputOptions,
|
||||
...parentNodeOutputOptions,
|
||||
...parentUpstreamNodeOutputOptions,
|
||||
];
|
||||
}, [
|
||||
upstreamNodeOutputOptions,
|
||||
parentNodeOutputOptions,
|
||||
parentUpstreamNodeOutputOptions,
|
||||
]);
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
export function useBuildQueryVariableOptions(n?: RAGFlowNodeType) {
|
||||
const { data } = useFetchAgent();
|
||||
export type BuildQueryVariableOptions = {
|
||||
nodeIds?: string[];
|
||||
variablesExceptOperatorOutputs?: AgentVariableType[];
|
||||
};
|
||||
|
||||
export function useBuildQueryVariableOptions({
|
||||
n,
|
||||
nodeIds = [],
|
||||
variablesExceptOperatorOutputs, // Variables other than operator output variables
|
||||
}: {
|
||||
n?: RAGFlowNodeType;
|
||||
} & BuildQueryVariableOptions = {}) {
|
||||
const node = useContext(AgentFormContext) || n;
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
|
||||
const options = useBuildVariableOptions(node?.id, node?.parentId);
|
||||
|
||||
const conversationOptions = useBuildConversationVariableOptions();
|
||||
|
||||
const globalWithBeginVariableOptions =
|
||||
useBuildGlobalWithBeginVariableOptions();
|
||||
|
||||
const AgentVariableOptionsMap = {
|
||||
[AgentVariableType.Begin]: globalWithBeginVariableOptions,
|
||||
[AgentVariableType.Conversation]: conversationOptions,
|
||||
};
|
||||
|
||||
const nextOptions = useMemo(() => {
|
||||
const globals = data?.dsl?.globals ?? {};
|
||||
const globalOptions = Object.entries(globals)
|
||||
.filter(([key]) => !key.startsWith(Env))
|
||||
.map(([key, value]) => ({
|
||||
label: key,
|
||||
value: key,
|
||||
icon: <OperatorIcon name={Operator.Begin} className="block" />,
|
||||
parentLabel: <span>{t('flow.beginInput')}</span>,
|
||||
type: Array.isArray(value)
|
||||
? `${VariableType.Array}${key === AgentGlobals.SysFiles ? '<file>' : ''}`
|
||||
: typeof value,
|
||||
}));
|
||||
return [
|
||||
...globalWithBeginVariableOptions,
|
||||
...conversationOptions,
|
||||
...options,
|
||||
];
|
||||
}, [conversationOptions, globalWithBeginVariableOptions, options]);
|
||||
|
||||
// Which options are entirely under external control?
|
||||
if (!isEmpty(nodeIds) || !isEmpty(variablesExceptOperatorOutputs)) {
|
||||
const nodeOutputOptions = buildNodeOutputOptions({ nodes, nodeIds });
|
||||
|
||||
const variablesExceptOperatorOutputsOptions =
|
||||
variablesExceptOperatorOutputs?.map((x) => AgentVariableOptionsMap[x]) ??
|
||||
[];
|
||||
|
||||
return [
|
||||
{
|
||||
...options[0],
|
||||
options: [...options[0]?.options, ...globalOptions],
|
||||
},
|
||||
...options.slice(1),
|
||||
...conversationOptions,
|
||||
...flatten(variablesExceptOperatorOutputsOptions),
|
||||
...nodeOutputOptions,
|
||||
];
|
||||
}, [conversationOptions, data?.dsl?.globals, options]);
|
||||
|
||||
}
|
||||
return nextOptions;
|
||||
}
|
||||
|
||||
export function useFilterQueryVariableOptionsByTypes(
|
||||
types?: JsonSchemaDataType[],
|
||||
) {
|
||||
const nextOptions = useBuildQueryVariableOptions();
|
||||
export function useFilterQueryVariableOptionsByTypes({
|
||||
types,
|
||||
nodeIds = [],
|
||||
variablesExceptOperatorOutputs,
|
||||
}: {
|
||||
types?: JsonSchemaDataType[];
|
||||
} & BuildQueryVariableOptions) {
|
||||
const nextOptions = useBuildQueryVariableOptions({
|
||||
nodeIds,
|
||||
variablesExceptOperatorOutputs,
|
||||
});
|
||||
|
||||
const filteredOptions = useMemo(() => {
|
||||
return !isEmpty(types)
|
||||
@ -294,7 +376,7 @@ export function useBuildComponentIdAndBeginOptions(
|
||||
parentId?: string,
|
||||
) {
|
||||
const componentIdOptions = useBuildComponentIdOptions(nodeId, parentId);
|
||||
const beginOptions = useBuildBeginVariableOptions();
|
||||
const beginOptions = useBuildBeginDynamicVariableOptions();
|
||||
|
||||
return [...beginOptions, ...componentIdOptions];
|
||||
}
|
||||
@ -323,9 +405,19 @@ export function flatOptions(options: DefaultOptionType[]) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function useFlattenQueryVariableOptions(nodeId?: string) {
|
||||
export function useFlattenQueryVariableOptions({
|
||||
nodeId,
|
||||
nodeIds = [],
|
||||
variablesExceptOperatorOutputs,
|
||||
}: {
|
||||
nodeId?: string;
|
||||
} & BuildQueryVariableOptions = {}) {
|
||||
const { getNode } = useGraphStore((state) => state);
|
||||
const nextOptions = useBuildQueryVariableOptions(getNode(nodeId));
|
||||
const nextOptions = useBuildQueryVariableOptions({
|
||||
n: getNode(nodeId),
|
||||
nodeIds,
|
||||
variablesExceptOperatorOutputs,
|
||||
});
|
||||
|
||||
const flattenOptions = useMemo(() => {
|
||||
return flatOptions(nextOptions);
|
||||
@ -334,8 +426,18 @@ export function useFlattenQueryVariableOptions(nodeId?: string) {
|
||||
return flattenOptions;
|
||||
}
|
||||
|
||||
export function useGetVariableLabelOrTypeByValue(nodeId?: string) {
|
||||
const flattenOptions = useFlattenQueryVariableOptions(nodeId);
|
||||
export function useGetVariableLabelOrTypeByValue({
|
||||
nodeId,
|
||||
nodeIds = [],
|
||||
variablesExceptOperatorOutputs,
|
||||
}: {
|
||||
nodeId?: string;
|
||||
} & BuildQueryVariableOptions = {}) {
|
||||
const flattenOptions = useFlattenQueryVariableOptions({
|
||||
nodeId,
|
||||
nodeIds,
|
||||
variablesExceptOperatorOutputs,
|
||||
});
|
||||
const findAgentStructuredOutputTypeByValue =
|
||||
useFindAgentStructuredOutputTypeByValue();
|
||||
const findAgentStructuredOutputLabel =
|
||||
|
||||
@ -14,6 +14,7 @@ export const useShowFormDrawer = () => {
|
||||
setClickedNodeId,
|
||||
getNode,
|
||||
setClickedToolId,
|
||||
getOperatorTypeFromId,
|
||||
} = useGraphStore((state) => state);
|
||||
const {
|
||||
visible: formDrawerVisible,
|
||||
@ -25,14 +26,20 @@ export const useShowFormDrawer = () => {
|
||||
(e: React.MouseEvent<Element>, nodeId: string) => {
|
||||
const tool = get(e.target, 'dataset.tool');
|
||||
// TODO: Operator type judgment should be used
|
||||
if (nodeId.startsWith(Operator.Tool) && !tool) {
|
||||
const operatorType = getOperatorTypeFromId(nodeId);
|
||||
if (
|
||||
(operatorType === Operator.Tool && !tool) ||
|
||||
[Operator.LoopStart, Operator.ExitLoop].includes(
|
||||
operatorType as Operator,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setClickedNodeId(nodeId);
|
||||
setClickedToolId(tool);
|
||||
showFormDrawer();
|
||||
},
|
||||
[setClickedNodeId, setClickedToolId, showFormDrawer],
|
||||
[getOperatorTypeFromId, setClickedNodeId, setClickedToolId, showFormDrawer],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -41,3 +41,11 @@ export type IInputs = {
|
||||
prologue: string;
|
||||
mode: string;
|
||||
};
|
||||
|
||||
export type IOutputs = Record<
|
||||
string,
|
||||
{
|
||||
type?: string;
|
||||
value?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
@ -14,7 +14,12 @@ import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.s
|
||||
|
||||
import { IconFontFill } from '@/components/icon-font';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { FileCode, HousePlus } from 'lucide-react';
|
||||
import {
|
||||
FileCode,
|
||||
HousePlus,
|
||||
Infinity as InfinityIcon,
|
||||
LogOut,
|
||||
} from 'lucide-react';
|
||||
import { Operator } from './constant';
|
||||
|
||||
interface IProps {
|
||||
@ -60,6 +65,8 @@ export const SVGIconMap = {
|
||||
};
|
||||
export const LucideIconMap = {
|
||||
[Operator.DataOperations]: FileCode,
|
||||
[Operator.Loop]: InfinityIcon,
|
||||
[Operator.ExitLoop]: LogOut,
|
||||
};
|
||||
|
||||
const Empty = () => {
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
} from '@/interfaces/database/agent';
|
||||
import { DSLComponents, RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { buildSelectOptions } from '@/utils/component-util';
|
||||
import { removeUselessFieldsFromValues } from '@/utils/form';
|
||||
import { buildOptions, removeUselessFieldsFromValues } from '@/utils/form';
|
||||
import { Edge, Node, XYPosition } from '@xyflow/react';
|
||||
import { FormInstance, FormListFieldData } from 'antd';
|
||||
import { humanId } from 'human-id';
|
||||
@ -27,6 +27,7 @@ import {
|
||||
CategorizeAnchorPointPositions,
|
||||
FileType,
|
||||
FileTypeSuffixMap,
|
||||
InputMode,
|
||||
NoCopyOperatorsList,
|
||||
NoDebugOperatorsList,
|
||||
NodeHandleId,
|
||||
@ -772,3 +773,5 @@ export function getArrayElementType(type: string) {
|
||||
export function buildConversationVariableSelectOptions() {
|
||||
return buildSelectOptions(Object.values(TypesWithArray));
|
||||
}
|
||||
|
||||
export const InputModeOptions = buildOptions(InputMode);
|
||||
|
||||
@ -4,10 +4,11 @@ import {
|
||||
Operator,
|
||||
} from '@/constants/agent';
|
||||
import { BaseNode } from '@/interfaces/database/agent';
|
||||
import OperatorIcon from '@/pages/agent/operator-icon';
|
||||
|
||||
import { Edge } from '@xyflow/react';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { ComponentType, ReactNode } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export function filterAllUpstreamNodeIds(edges: Edge[], nodeIds: string[]) {
|
||||
return nodeIds.reduce<string[]>((pre, nodeId) => {
|
||||
@ -29,13 +30,21 @@ export function filterAllUpstreamNodeIds(edges: Edge[], nodeIds: string[]) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function filterChildNodeIds(nodes: BaseNode[], nodeId?: string) {
|
||||
return nodes.filter((x) => x.parentId === nodeId).map((x) => x.id);
|
||||
}
|
||||
|
||||
export function isAgentStructured(id?: string, label?: string) {
|
||||
return (
|
||||
label === AgentStructuredOutputField && id?.startsWith(`${Operator.Agent}:`)
|
||||
);
|
||||
}
|
||||
|
||||
export function buildOutputOptions(
|
||||
export function buildVariableValue(value: string, nodeId?: string) {
|
||||
return `${nodeId}@${value}`;
|
||||
}
|
||||
|
||||
export function buildSecondaryOutputOptions(
|
||||
outputs: Record<string, any> = {},
|
||||
nodeId?: string,
|
||||
parentLabel?: string | ReactNode,
|
||||
@ -43,7 +52,7 @@ export function buildOutputOptions(
|
||||
) {
|
||||
return Object.keys(outputs).map((x) => ({
|
||||
label: x,
|
||||
value: `${nodeId}@${x}`,
|
||||
value: buildVariableValue(x, nodeId),
|
||||
parentLabel,
|
||||
icon,
|
||||
type: isAgentStructured(nodeId, x)
|
||||
@ -52,40 +61,63 @@ export function buildOutputOptions(
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildOutputOptions(x: BaseNode) {
|
||||
return {
|
||||
label: x.data.name,
|
||||
value: x.id,
|
||||
title: x.data.name,
|
||||
options: buildSecondaryOutputOptions(
|
||||
x.data.form.outputs,
|
||||
x.id,
|
||||
x.data.name,
|
||||
<OperatorIcon name={x.data.label as Operator} />,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildNodeOutputOptions({
|
||||
nodes, // all nodes
|
||||
nodeIds, // Need to obtain the output node IDs
|
||||
}: {
|
||||
nodes: BaseNode[];
|
||||
nodeIds: string[];
|
||||
}) {
|
||||
const nodeWithOutputList = nodes.filter(
|
||||
(x) => nodeIds.some((y) => y === x.id) && !isEmpty(x.data?.form?.outputs),
|
||||
);
|
||||
|
||||
return nodeWithOutputList.map((x) => buildOutputOptions(x));
|
||||
}
|
||||
|
||||
export function buildUpstreamNodeOutputOptions({
|
||||
nodes,
|
||||
edges,
|
||||
nodeId,
|
||||
Icon,
|
||||
}: {
|
||||
nodes: BaseNode[];
|
||||
edges: Edge[];
|
||||
nodeId?: string;
|
||||
Icon: ComponentType<{ name: string }>;
|
||||
}) {
|
||||
if (!nodeId) {
|
||||
return [];
|
||||
}
|
||||
const upstreamIds = filterAllUpstreamNodeIds(edges, [nodeId]);
|
||||
|
||||
return buildNodeOutputOptions({ nodes, nodeIds: upstreamIds });
|
||||
}
|
||||
|
||||
export function buildChildOutputOptions({
|
||||
nodes,
|
||||
nodeId,
|
||||
}: {
|
||||
nodes: BaseNode[];
|
||||
nodeId?: string;
|
||||
}) {
|
||||
const nodeWithOutputList = nodes.filter(
|
||||
(x) =>
|
||||
upstreamIds.some((y) => y === x.id) && !isEmpty(x.data?.form?.outputs),
|
||||
(x) => x.parentId === nodeId && !isEmpty(x.data?.form?.outputs),
|
||||
);
|
||||
|
||||
return nodeWithOutputList
|
||||
.filter((x) => x.id !== nodeId)
|
||||
.map((x) => ({
|
||||
label: x.data.name,
|
||||
value: x.id,
|
||||
title: x.data.name,
|
||||
options: buildOutputOptions(
|
||||
x.data.form.outputs,
|
||||
x.id,
|
||||
x.data.name,
|
||||
<Icon name={x.data.name} />,
|
||||
),
|
||||
}));
|
||||
return nodeWithOutputList.map((x) => buildOutputOptions(x));
|
||||
}
|
||||
|
||||
export function getStructuredDatatype(value: Record<string, any> | unknown) {
|
||||
|
||||
Reference in New Issue
Block a user