mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
Feat: Fixed an issue where dragged operators within an iteration were not associated with the iteration. #10866 (#10969)
### What problem does this PR solve? Feat: Fixed an issue where dragged operators within an iteration were not associated with the iteration. #10866 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1724,6 +1724,7 @@ Important structured information may include: names, dates, locations, events, k
|
||||
},
|
||||
structuredOutput: {
|
||||
configuration: 'Configuration',
|
||||
structuredOutput: 'Structured output',
|
||||
},
|
||||
},
|
||||
llmTools: {
|
||||
|
||||
@ -1605,6 +1605,7 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
|
||||
switchPromptMessage: '提示词将发生变化,请确认是否放弃已有提示词?',
|
||||
structuredOutput: {
|
||||
configuration: '配置',
|
||||
structuredOutput: '结构化输出',
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
NodeTypes,
|
||||
Position,
|
||||
ReactFlow,
|
||||
ReactFlowInstance,
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { NotebookPen } from 'lucide-react';
|
||||
@ -27,11 +28,7 @@ import {
|
||||
} from '../context';
|
||||
|
||||
import FormSheet from '../form-sheet/next';
|
||||
import {
|
||||
useHandleDrop,
|
||||
useSelectCanvasData,
|
||||
useValidateConnection,
|
||||
} from '../hooks';
|
||||
import { useSelectCanvasData, useValidateConnection } from '../hooks';
|
||||
import { useAddNode } from '../hooks/use-add-node';
|
||||
import { useBeforeDelete } from '../hooks/use-before-delete';
|
||||
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||
@ -124,8 +121,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
} = useSelectCanvasData();
|
||||
const isValidConnection = useValidateConnection();
|
||||
|
||||
const { onDrop, onDragOver, setReactFlowInstance, reactFlowInstance } =
|
||||
useHandleDrop();
|
||||
const [reactFlowInstance, setReactFlowInstance] =
|
||||
useState<ReactFlowInstance<any, any>>();
|
||||
|
||||
const {
|
||||
onNodeClick,
|
||||
@ -204,7 +201,6 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
onMove,
|
||||
nodeId,
|
||||
} = useConnectionDrag(
|
||||
reactFlowInstance,
|
||||
originalOnConnect,
|
||||
showModal,
|
||||
hideModal,
|
||||
@ -214,6 +210,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
removePlaceholderNode,
|
||||
clearActiveDropdown,
|
||||
checkAndRemoveExistingPlaceholder,
|
||||
reactFlowInstance,
|
||||
);
|
||||
|
||||
const onPaneClick = useCallback(() => {
|
||||
@ -286,11 +283,9 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
onConnect={handleConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onDrop={onDrop}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectEnd={onConnectEnd}
|
||||
onMove={onMove}
|
||||
onDragOver={onDragOver}
|
||||
onNodeClick={onNodeClick}
|
||||
onPaneClick={onPaneClick}
|
||||
onInit={setReactFlowInstance}
|
||||
|
||||
@ -803,6 +803,7 @@ export const RestrictedUpstreamMap = {
|
||||
[Operator.HierarchicalMerger]: [Operator.Begin],
|
||||
[Operator.Tokenizer]: [Operator.Begin],
|
||||
[Operator.Extractor]: [Operator.Begin],
|
||||
[Operator.File]: [Operator.Begin],
|
||||
};
|
||||
|
||||
export const NodeMap = {
|
||||
|
||||
@ -7,6 +7,7 @@ import { cn } from '@/lib/utils';
|
||||
import { get, isEmpty, isPlainObject } from 'lodash';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { PropsWithChildren, ReactNode, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { JsonSchemaDataType, VariableType } from '../../constant';
|
||||
import { useGetStructuredOutputByValue } from '../../hooks/use-build-structured-output';
|
||||
import {
|
||||
@ -27,6 +28,7 @@ export function StructuredOutputSecondaryMenu({
|
||||
click,
|
||||
type,
|
||||
}: StructuredOutputSecondaryMenuProps) {
|
||||
const { t } = useTranslation();
|
||||
const filterStructuredOutput = useGetStructuredOutputByValue();
|
||||
const structuredOutput = filterStructuredOutput(data.value);
|
||||
|
||||
@ -113,7 +115,9 @@ export function StructuredOutputSecondaryMenu({
|
||||
)}
|
||||
>
|
||||
<section className="p-2">
|
||||
<div className="p-1">{data?.parentLabel} structured output:</div>
|
||||
<div className="p-1">
|
||||
{t('flow.structuredOutput.structuredOutput')}
|
||||
</div>
|
||||
{renderAgentStructuredOutput(structuredOutput, data)}
|
||||
</section>
|
||||
</HoverCardContent>
|
||||
|
||||
@ -9,7 +9,6 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { FormTooltip } from '@/components/ui/tooltip';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Plus } from 'lucide-react';
|
||||
@ -22,6 +21,7 @@ import { ParameterDialog } from '../begin-form/parameter-dialog';
|
||||
import { QueryTable } from '../begin-form/query-table';
|
||||
import { useEditQueryRecord } from '../begin-form/use-edit-query';
|
||||
import { Output } from '../components/output';
|
||||
import { PromptEditor } from '../components/prompt-editor';
|
||||
import { useValues } from './use-values';
|
||||
import { useWatchFormChange } from './use-watch-change';
|
||||
|
||||
@ -108,11 +108,7 @@ function UserFillUpForm({ node }: INextOperatorForm) {
|
||||
{t('flow.msg')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={5}
|
||||
{...field}
|
||||
placeholder={t('common.pleaseInput')}
|
||||
></Textarea>
|
||||
<PromptEditor value={field.value} onChange={field.onChange} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@ -1,72 +1,14 @@
|
||||
import {
|
||||
Connection,
|
||||
Edge,
|
||||
getOutgoers,
|
||||
Node,
|
||||
Position,
|
||||
ReactFlowInstance,
|
||||
} from '@xyflow/react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Connection, Edge, getOutgoers } from '@xyflow/react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
// import { shallow } from 'zustand/shallow';
|
||||
import { settledModelVariableMap } from '@/constants/knowledge';
|
||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { humanId } from 'human-id';
|
||||
import { get, lowerFirst, omit } from 'lodash';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
initialAgentValues,
|
||||
initialAkShareValues,
|
||||
initialArXivValues,
|
||||
initialBaiduFanyiValues,
|
||||
initialBaiduValues,
|
||||
initialBeginValues,
|
||||
initialBingValues,
|
||||
initialCategorizeValues,
|
||||
initialCodeValues,
|
||||
initialCrawlerValues,
|
||||
initialDeepLValues,
|
||||
initialDuckValues,
|
||||
initialEmailValues,
|
||||
initialExeSqlValues,
|
||||
initialGithubValues,
|
||||
initialGoogleScholarValues,
|
||||
initialGoogleValues,
|
||||
initialInvokeValues,
|
||||
initialIterationValues,
|
||||
initialJin10Values,
|
||||
initialKeywordExtractValues,
|
||||
initialMessageValues,
|
||||
initialNoteValues,
|
||||
initialPubMedValues,
|
||||
initialQWeatherValues,
|
||||
initialRelevantValues,
|
||||
initialRetrievalValues,
|
||||
initialRewriteQuestionValues,
|
||||
initialSearXNGValues,
|
||||
initialStringTransformValues,
|
||||
initialSwitchValues,
|
||||
initialTavilyExtractValues,
|
||||
initialTavilyValues,
|
||||
initialTuShareValues,
|
||||
initialUserFillUpValues,
|
||||
initialWaitingDialogueValues,
|
||||
initialWenCaiValues,
|
||||
initialWikipediaValues,
|
||||
initialYahooFinanceValues,
|
||||
NodeMap,
|
||||
Operator,
|
||||
RestrictedUpstreamMap,
|
||||
} from './constant';
|
||||
import { Operator, RestrictedUpstreamMap } from './constant';
|
||||
import useGraphStore, { RFState } from './store';
|
||||
import {
|
||||
buildCategorizeObjectFromList,
|
||||
generateNodeNamesWithIncreasingIndex,
|
||||
getNodeDragHandle,
|
||||
getRelativePositionToIterationNode,
|
||||
replaceIdWithText,
|
||||
} from './utils';
|
||||
import { buildCategorizeObjectFromList, replaceIdWithText } from './utils';
|
||||
|
||||
const selector = (state: RFState) => ({
|
||||
nodes: state.nodes,
|
||||
@ -86,72 +28,6 @@ export const useSelectCanvasData = () => {
|
||||
return useGraphStore(selector);
|
||||
};
|
||||
|
||||
export const useInitializeOperatorParams = () => {
|
||||
const llmId = useFetchModelId();
|
||||
|
||||
const initialFormValuesMap = useMemo(() => {
|
||||
return {
|
||||
[Operator.Begin]: initialBeginValues,
|
||||
[Operator.Retrieval]: initialRetrievalValues,
|
||||
[Operator.Categorize]: { ...initialCategorizeValues, llm_id: llmId },
|
||||
[Operator.Relevant]: { ...initialRelevantValues, llm_id: llmId },
|
||||
[Operator.RewriteQuestion]: {
|
||||
...initialRewriteQuestionValues,
|
||||
llm_id: llmId,
|
||||
},
|
||||
[Operator.Message]: initialMessageValues,
|
||||
[Operator.KeywordExtract]: {
|
||||
...initialKeywordExtractValues,
|
||||
llm_id: llmId,
|
||||
},
|
||||
[Operator.DuckDuckGo]: initialDuckValues,
|
||||
[Operator.Baidu]: initialBaiduValues,
|
||||
[Operator.Wikipedia]: initialWikipediaValues,
|
||||
[Operator.PubMed]: initialPubMedValues,
|
||||
[Operator.ArXiv]: initialArXivValues,
|
||||
[Operator.Google]: initialGoogleValues,
|
||||
[Operator.Bing]: initialBingValues,
|
||||
[Operator.GoogleScholar]: initialGoogleScholarValues,
|
||||
[Operator.DeepL]: initialDeepLValues,
|
||||
[Operator.SearXNG]: initialSearXNGValues,
|
||||
[Operator.GitHub]: initialGithubValues,
|
||||
[Operator.BaiduFanyi]: initialBaiduFanyiValues,
|
||||
[Operator.QWeather]: initialQWeatherValues,
|
||||
[Operator.ExeSQL]: { ...initialExeSqlValues, llm_id: llmId },
|
||||
[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,
|
||||
[Operator.Email]: initialEmailValues,
|
||||
[Operator.Iteration]: initialIterationValues,
|
||||
[Operator.IterationStart]: initialIterationValues,
|
||||
[Operator.Code]: initialCodeValues,
|
||||
[Operator.WaitingDialogue]: initialWaitingDialogueValues,
|
||||
[Operator.Agent]: { ...initialAgentValues, llm_id: llmId },
|
||||
[Operator.TavilySearch]: initialTavilyValues,
|
||||
[Operator.TavilyExtract]: initialTavilyExtractValues,
|
||||
[Operator.Tool]: {},
|
||||
[Operator.UserFillUp]: initialUserFillUpValues,
|
||||
[Operator.StringTransform]: initialStringTransformValues,
|
||||
[Operator.Placeholder]: {},
|
||||
};
|
||||
}, [llmId]);
|
||||
|
||||
const initializeOperatorParams = useCallback(
|
||||
(operatorName: Operator) => {
|
||||
return initialFormValuesMap[operatorName];
|
||||
},
|
||||
[initialFormValuesMap],
|
||||
);
|
||||
|
||||
return initializeOperatorParams;
|
||||
};
|
||||
|
||||
export const useHandleDrag = () => {
|
||||
const handleDragStart = useCallback(
|
||||
(operatorId: string) => (ev: React.DragEvent<HTMLDivElement>) => {
|
||||
@ -173,91 +49,6 @@ export const useGetNodeName = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useHandleDrop = () => {
|
||||
const addNode = useGraphStore((state) => state.addNode);
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
const [reactFlowInstance, setReactFlowInstance] =
|
||||
useState<ReactFlowInstance<any, any>>();
|
||||
const initializeOperatorParams = useInitializeOperatorParams();
|
||||
const getNodeName = useGetNodeName();
|
||||
|
||||
const onDragOver = useCallback((event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
}, []);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const type = event.dataTransfer.getData('application/@xyflow/react');
|
||||
|
||||
// check if the dropped element is valid
|
||||
if (typeof type === 'undefined' || !type) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
|
||||
// and you don't need to subtract the reactFlowBounds.left/top anymore
|
||||
// details: https://@xyflow/react.dev/whats-new/2023-11-10
|
||||
const position = reactFlowInstance?.screenToFlowPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
const newNode: Node<any> = {
|
||||
id: `${type}:${humanId()}`,
|
||||
type: NodeMap[type as Operator] || 'ragNode',
|
||||
position: position || {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
data: {
|
||||
label: `${type}`,
|
||||
name: generateNodeNamesWithIncreasingIndex(getNodeName(type), nodes),
|
||||
form: initializeOperatorParams(type as Operator),
|
||||
},
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
dragHandle: getNodeDragHandle(type),
|
||||
};
|
||||
|
||||
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: {},
|
||||
},
|
||||
parentId: newNode.id,
|
||||
extent: 'parent',
|
||||
};
|
||||
addNode(newNode);
|
||||
addNode(iterationStartNode);
|
||||
} else {
|
||||
const subNodeOfIteration = getRelativePositionToIterationNode(
|
||||
nodes,
|
||||
position,
|
||||
);
|
||||
if (subNodeOfIteration) {
|
||||
newNode.parentId = subNodeOfIteration.parentId;
|
||||
newNode.position = subNodeOfIteration.position;
|
||||
newNode.extent = 'parent';
|
||||
}
|
||||
addNode(newNode);
|
||||
}
|
||||
},
|
||||
[reactFlowInstance, getNodeName, nodes, initializeOperatorParams, addNode],
|
||||
);
|
||||
|
||||
return { onDrop, onDragOver, setReactFlowInstance, reactFlowInstance };
|
||||
};
|
||||
|
||||
export const useHandleFormValuesChange = (
|
||||
operatorName: Operator,
|
||||
id?: string,
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { Connection, OnConnectEnd, Position } from '@xyflow/react';
|
||||
import {
|
||||
Connection,
|
||||
OnConnectEnd,
|
||||
OnConnectStart,
|
||||
Position,
|
||||
ReactFlowInstance,
|
||||
} from '@xyflow/react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useDropdownManager } from '../canvas/context';
|
||||
import { Operator, PREVENT_CLOSE_DELAY } from '../constant';
|
||||
@ -14,7 +20,6 @@ interface ConnectionStartParams {
|
||||
* Responsible for handling connection drag start and end logic
|
||||
*/
|
||||
export const useConnectionDrag = (
|
||||
reactFlowInstance: any,
|
||||
onConnect: (connection: Connection) => void,
|
||||
showModal: () => void,
|
||||
hideModal: () => void,
|
||||
@ -27,6 +32,7 @@ export const useConnectionDrag = (
|
||||
removePlaceholderNode: () => void,
|
||||
clearActiveDropdown: () => void,
|
||||
checkAndRemoveExistingPlaceholder: () => void,
|
||||
reactFlowInstance?: ReactFlowInstance<any, any>,
|
||||
) => {
|
||||
// Reference for whether connection is established
|
||||
const isConnectedRef = useRef(false);
|
||||
@ -43,7 +49,7 @@ export const useConnectionDrag = (
|
||||
/**
|
||||
* Connection start handler function
|
||||
*/
|
||||
const onConnectStart = useCallback((event: any, params: any) => {
|
||||
const onConnectStart: OnConnectStart = useCallback((event, params) => {
|
||||
isConnectedRef.current = false;
|
||||
|
||||
// Record mouse start position to detect click vs drag
|
||||
@ -141,16 +147,16 @@ export const useConnectionDrag = (
|
||||
},
|
||||
[
|
||||
setDropdownPosition,
|
||||
addCanvasNode,
|
||||
setCreatedPlaceholderRef,
|
||||
reactFlowInstance,
|
||||
calculateDropdownPosition,
|
||||
setActiveDropdown,
|
||||
showModal,
|
||||
checkAndRemoveExistingPlaceholder,
|
||||
addCanvasNode,
|
||||
reactFlowInstance,
|
||||
removePlaceholderNode,
|
||||
hideModal,
|
||||
clearActiveDropdown,
|
||||
setCreatedPlaceholderRef,
|
||||
calculateDropdownPosition,
|
||||
setActiveDropdown,
|
||||
showModal,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ReactFlowInstance } from '@xyflow/react';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
DROPDOWN_HORIZONTAL_OFFSET,
|
||||
@ -9,7 +10,9 @@ import {
|
||||
* Dropdown position calculation Hook
|
||||
* Responsible for calculating dropdown menu position relative to placeholder node
|
||||
*/
|
||||
export const useDropdownPosition = (reactFlowInstance: any) => {
|
||||
export const useDropdownPosition = (
|
||||
reactFlowInstance?: ReactFlowInstance<any, any>,
|
||||
) => {
|
||||
/**
|
||||
* Calculate dropdown menu position
|
||||
* @param clientX Mouse click screen X coordinate
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { pick } from 'lodash';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { Operator } from '../constant';
|
||||
import useGraphStore from '../store';
|
||||
@ -113,7 +114,7 @@ export const usePlaceholderManager = (reactFlowInstance: any) => {
|
||||
if (newNode) {
|
||||
updateNode({
|
||||
...newNode,
|
||||
position: placeholderNode.position,
|
||||
...pick(placeholderNode, ['position', 'parentId', 'extent']),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user