mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-26 00:46:52 +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:
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user