From 7d6d41aa87b2d08579132669b6d6b95f505d1ff2 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:40:13 -0300 Subject: [PATCH] refactor: update ReactFlow to v12 (#5317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added xyflow and updated imports * Fix changing node.width to node.measured.width * Updated NodeType to follow new API * Fixed note node data type * Created AllNodeType to contain note node type * Changed flow types to follow new type from reactflow 12 * Updated flowStore to work with new types * Updated flowStore and reactFlowUtils to work with custom edge type * Updated updateAllNodes to use new type * Updated PageComponent to follow new names for node dragging and edge reconnect * Made selected prop be optional * Changed reactFlowInstance to have nodes and edges type * Updated updateAllComponent to follow new types * Updated ReactFlowUtils with new types * Updated reactFlowUtils type * Updated all reactFlowUtils to be generic * Updated handleRenderComponent with Connection type * Updated node description and name with null checks for names * Added check if node is genericNode on nodeOutputField * Updated note node type * Updated nodestatus with selected null check * Updated notenode with new node type * Update NodeType imports to be AllNodeType * Fix more lint issues * Fixed react flow button css * โœจ (freeze.spec.ts): add zoomOut utility function to handle zooming out in tests for better code reusability and readability ๐Ÿ“ (decisionFlow.spec.ts): import zoomOut utility function to handle zooming out in tests for better code reusability and readability ๐Ÿ“ (zoom-out.ts): create zoomOut utility function to handle zooming out in tests for better code reusability and readability * ๐Ÿ› (generalBugs-shard-10.spec.ts): fix test to wait for the element with class "border-ring-frozen" to be visible before asserting its count * ๐Ÿ“ (generalBugs-shard-10.spec.ts): remove unnecessary locator click on element with id 'react-flow-id' to improve test reliability and maintainability * ๐Ÿ“ (SelectionMenuComponent): add data-testid attribute to differentiate error-group-node and group-node components ๐Ÿ“ (similarity.spec.ts): refactor dragTo calls to use targetPosition for better accuracy, replace zoom_out clicks with zoomOut function, updateOldComponents function to handle outdated components ๐Ÿ“ (generalBugs-shard-5.spec.ts): replace zoom_out clicks with zoomOut function, add waitForSelector for group-node before clicking, remove unnecessary mouse interactions ๐Ÿ“ (intComponent.spec.ts): replace zoom_out clicks with zoomOut function, click on div-generic-node component ๐Ÿ“ (keyPairListComponent.spec.ts): click on div-generic-node component, adjustScreenView function call ๐Ÿ“ (nestedComponent.spec.ts): remove unnecessary click on react-flow-id element ๐Ÿ“ (generalBugs-shard-7.spec.ts): replace zoom_out clicks with zoomOut function * โœจ (stop-building.spec.ts): Add new utility functions to improve code modularity and readability ๐Ÿ”ง (stop-building.spec.ts): Refactor drag and drop operations to use utility functions for better maintainability ๐Ÿ”ง (stop-building.spec.ts): Refactor zoom out operations to use utility function for consistency ๐Ÿ”ง (stop-building.spec.ts): Refactor component positioning operations to use utility functions for clarity ๐Ÿ”ง (stop-building.spec.ts): Refactor outdated components and filled API keys handling to use utility functions for reusability ๐Ÿ”ง (stop-building.spec.ts): Refactor fit view operation to use utility function for consistency * โœจ (generalBugs-shard-9.spec.ts): refactor test to use initialGPTsetup function for GPT setup instead of manual steps to improve code readability and maintainability * โœจ (store-shard-2.spec.ts): Increase timeout for clicking "api-key-button-store" to 200000ms for better test stability โœจ (deleteComponents.spec.ts, deleteFlows.spec.ts, store-shard-1.spec.ts, store-shard-3.spec.ts): Increase timeout for clicking "api-key-button-store" to 200000ms for better test stability โœจ (store-shard-1.spec.ts, store-shard-3.spec.ts): Remove unnecessary waitForSelector and add timeout of 200000ms for clicking "api-key-button-store" for better test stability * โœจ (fileUploadComponent.spec.ts): update dragTo method calls with targetPosition option to specify the position of the drag action * โœจ (decisionFlow.spec.ts): add explicit wait for an element to be attached before interacting with it to improve test reliability ๐Ÿ› (sticky-notes.spec.ts): fix test by adding keyboard press to simulate pressing the Escape key to close a modal before checking text length * โœ… (sticky-notes.spec.ts): update assertion to use 'toHaveCount' matcher for improved test readability and reliability * Added function to make controls horizontal * โœจ (sticky-notes.spec.ts): update selector for textMarkdown to improve test reliability and readability --------- Co-authored-by: cristhianzl --- src/frontend/package-lock.json | 31 ++ src/frontend/package.json | 1 + src/frontend/src/App.css | 3 + src/frontend/src/App.tsx | 2 +- src/frontend/src/CustomEdges/index.tsx | 5 +- .../components/NodeDescription/index.tsx | 8 +- .../GenericNode/components/NodeName/index.tsx | 8 +- .../components/NodeOutputfield/index.tsx | 5 +- .../components/NodeStatus/index.tsx | 15 +- .../handleRenderComponent/index.tsx | 6 +- .../src/CustomNodes/GenericNode/index.tsx | 4 +- .../NoteNode/NoteToolbarComponent/index.tsx | 31 +- .../src/CustomNodes/NoteNode/index.tsx | 8 +- .../helpers/process-node-advanced-fields.ts | 4 +- .../hooks/use-handle-new-value.tsx | 6 +- .../hooks/use-handle-node-class.tsx | 6 +- .../hooks/use-update-all-nodes.tsx | 6 +- .../core/canvasControlsComponent/index.tsx | 8 +- .../components/tweakComponent/index.tsx | 4 +- .../components/tweaksComponent/index.tsx | 4 +- .../core/csvOutputComponent/index.tsx | 4 +- .../core/flowToolbarComponent/index.tsx | 2 +- .../tableAdvancedToggleCellRender/index.tsx | 3 +- .../components/tableNodeCellRender/index.tsx | 3 +- src/frontend/src/contexts/index.tsx | 2 +- src/frontend/src/controllers/API/index.ts | 2 +- .../queries/flows/use-patch-update-flow.ts | 2 +- .../API/queries/flows/use-post-add-flow.ts | 2 +- .../use-get-starter-projects.ts | 3 +- .../vertex/use-post-retrieve-vertex-order.tsx | 2 +- src/frontend/src/hooks/flows/use-save-flow.ts | 2 +- src/frontend/src/hooks/useAddComponent.ts | 6 +- .../IOModal/components/IOFieldView/index.tsx | 6 +- src/frontend/src/modals/IOModal/index.tsx | 8 +- .../utils/get-nodes-with-default-value.ts | 6 +- .../ConnectionLineComponent/index.tsx | 2 +- .../components/PageComponent/index.tsx | 47 +- .../SelectionMenuComponent/index.tsx | 4 +- .../components/UpdateAllComponents/index.tsx | 6 +- .../components/nodeToolbarComponent/index.tsx | 2 +- .../MainPage/utils/get-template-style.ts | 5 +- .../EditShortcutButton/index.tsx | 13 +- src/frontend/src/stores/flowStore.ts | 87 ++-- src/frontend/src/types/api/index.ts | 2 +- src/frontend/src/types/components/index.ts | 13 +- src/frontend/src/types/flow/index.ts | 45 +- src/frontend/src/types/tabs/index.ts | 2 +- .../src/types/utils/reactflowUtils.ts | 14 +- src/frontend/src/types/zustand/flow/index.ts | 39 +- .../src/types/zustand/tweaks/index.ts | 10 +- src/frontend/src/utils/buildUtils.ts | 2 +- src/frontend/src/utils/layoutUtils.ts | 11 +- src/frontend/src/utils/reactflowUtils.ts | 476 +++++++++--------- src/frontend/src/utils/storeUtils.ts | 2 +- src/frontend/src/utils/utils.ts | 11 +- .../tests/core/features/freeze.spec.ts | 75 +-- .../tests/core/features/stop-building.spec.ts | 90 +--- .../tests/core/features/store-shard-2.spec.ts | 8 +- .../core/integrations/decisionFlow.spec.ts | 12 +- .../core/integrations/similarity.spec.ts | 108 +--- .../regression/generalBugs-shard-5.spec.ts | 21 +- .../regression/generalBugs-shard-9.spec.ts | 38 +- .../core/unit/fileUploadComponent.spec.ts | 15 +- .../tests/core/unit/intComponent.spec.ts | 12 +- .../core/unit/keyPairListComponent.spec.ts | 5 +- .../tests/core/unit/nestedComponent.spec.ts | 1 - .../features/deleteComponents.spec.ts | 4 +- .../extended/features/deleteFlows.spec.ts | 4 +- .../extended/features/sticky-notes.spec.ts | 7 +- .../extended/features/store-shard-1.spec.ts | 8 +- .../extended/features/store-shard-3.spec.ts | 10 +- .../regression/generalBugs-shard-10.spec.ts | 5 +- .../regression/generalBugs-shard-7.spec.ts | 5 +- src/frontend/tests/utils/zoom-out.ts | 7 + 74 files changed, 686 insertions(+), 760 deletions(-) create mode 100644 src/frontend/tests/utils/zoom-out.ts diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 8343ea503..ed0fdb811 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -36,6 +36,7 @@ "@tailwindcss/line-clamp": "^0.4.4", "@tanstack/react-query": "^5.49.2", "@types/axios": "^0.14.0", + "@xyflow/react": "^12.3.6", "ace-builds": "^1.35.0", "ag-grid-community": "^32.0.2", "ag-grid-react": "^32.0.2", @@ -6238,6 +6239,36 @@ "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==", "license": "MIT" }, + "node_modules/@xyflow/react": { + "version": "12.3.6", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.3.6.tgz", + "integrity": "sha512-9GS+cz8hDZahpvTrVCmySAEgKUL8oN4b2q1DluHrKtkqhAMWfH2s7kblhbM4Y4Y4SUnH2lt4drXKZ/4/Lot/2Q==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.47", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.47.tgz", + "integrity": "sha512-aUXJPIvsCFxGX70ccRG8LPsR+A8ExYXfh/noYNpqn8udKerrLdSHxMG2VsvUrQ1PGex10fOpbJwFU4A+I/Xv8w==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index bbf27b4d3..6809e059f 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -31,6 +31,7 @@ "@tailwindcss/line-clamp": "^0.4.4", "@tanstack/react-query": "^5.49.2", "@types/axios": "^0.14.0", + "@xyflow/react": "^12.3.6", "ace-builds": "^1.35.0", "ag-grid-community": "^32.0.2", "ag-grid-react": "^32.0.2", diff --git a/src/frontend/src/App.css b/src/frontend/src/App.css index d45d358ba..37a3b6848 100644 --- a/src/frontend/src/App.css +++ b/src/frontend/src/App.css @@ -137,6 +137,9 @@ body { stroke: var(--selected) !important; stroke-width: 2px !important; } +.react-flow__controls-button svg { + fill: none !important; +} .react-flow__edge .react-flow__edge-path { transition: color; diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index a58342799..9c7a659e5 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,6 +1,6 @@ +import "@xyflow/react/dist/style.css"; import { Suspense } from "react"; import { RouterProvider } from "react-router-dom"; -import "reactflow/dist/style.css"; import { LoadingPage } from "./pages/LoadingPage"; import router from "./routes"; diff --git a/src/frontend/src/CustomEdges/index.tsx b/src/frontend/src/CustomEdges/index.tsx index 8ac7e31bd..e4db9bcb7 100644 --- a/src/frontend/src/CustomEdges/index.tsx +++ b/src/frontend/src/CustomEdges/index.tsx @@ -1,5 +1,5 @@ import useFlowStore from "@/stores/flowStore"; -import { BaseEdge, EdgeProps, getBezierPath, Position } from "reactflow"; +import { BaseEdge, EdgeProps, getBezierPath, Position } from "@xyflow/react"; export function DefaultEdge({ sourceHandleId, @@ -17,7 +17,8 @@ export function DefaultEdge({ const sourceNode = getNode(source); const targetNode = getNode(target); - const sourceXNew = (sourceNode?.position.x ?? 0) + (sourceNode?.width ?? 0); + const sourceXNew = + (sourceNode?.position.x ?? 0) + (sourceNode?.measured?.width ?? 0); const targetXNew = targetNode?.position.x ?? 0; const [edgePath] = getBezierPath({ diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx index 450b64381..271bfac55 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeDescription/index.tsx @@ -18,7 +18,7 @@ export default function NodeDescription({ style, }: { description?: string; - selected: boolean; + selected?: boolean; nodeId: string; emptyPlaceholder?: string; placeholderClassName?: string; @@ -28,7 +28,9 @@ export default function NodeDescription({ style?: React.CSSProperties; }) { const [inputDescription, setInputDescription] = useState(false); - const [nodeDescription, setNodeDescription] = useState(description); + const [nodeDescription, setNodeDescription] = useState( + description ?? "", + ); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const setNode = useFlowStore((state) => state.setNode); const overflowRef = useRef(null); @@ -56,7 +58,7 @@ export default function NodeDescription({ }, [selected]); useEffect(() => { - setNodeDescription(description); + setNodeDescription(description ?? ""); }, [description]); const MemoizedMarkdown = memo(Markdown); diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx index 15bafebf7..e00d01122 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx @@ -15,7 +15,7 @@ export default function NodeName({ beta, }: { display_name?: string; - selected: boolean; + selected?: boolean; nodeId: string; showNode: boolean; validationStatus: VertexBuildTypeAPI | null; @@ -23,7 +23,7 @@ export default function NodeName({ beta: boolean; }) { const [inputName, setInputName] = useState(false); - const [nodeName, setNodeName] = useState(display_name); + const [nodeName, setNodeName] = useState(display_name ?? ""); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const setNode = useFlowStore((state) => state.setNode); useEffect(() => { @@ -33,7 +33,7 @@ export default function NodeName({ }, [selected]); useEffect(() => { - setNodeName(display_name); + setNodeName(display_name ?? ""); }, [display_name]); return inputName ? ( @@ -54,7 +54,7 @@ export default function NodeName({ }, })); } else { - setNodeName(display_name); + setNodeName(display_name ?? ""); } }} value={nodeName} diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx index 60e6e9347..4e9ad0c03 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx @@ -1,7 +1,7 @@ import { ICON_STROKE_WIDTH } from "@/constants/constants"; +import { useUpdateNodeInternals } from "@xyflow/react"; import { cloneDeep } from "lodash"; import { memo, useCallback, useEffect, useMemo, useRef } from "react"; -import { useUpdateNodeInternals } from "reactflow"; import { default as IconComponent } from "../../../../components/common/genericIconComponent"; import ShadTooltip from "../../../../components/common/shadTooltipComponent"; import { Button } from "../../../../components/ui/button"; @@ -201,7 +201,8 @@ function NodeOutputField({ const handleUpdateOutputHide = useCallback( (value?: boolean) => { setNode(data.id, (oldNode) => { - const newNode = cloneDeep(oldNode); + if (oldNode.type !== "genericNode") return oldNode; + let newNode = cloneDeep(oldNode); newNode.data = { ...newNode.data, node: { diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx index 3104fafef..60a480338 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeStatus/index.tsx @@ -5,13 +5,7 @@ import useValidationStatusString from "@/CustomNodes/hooks/use-validation-status import ShadTooltip from "@/components/common/shadTooltipComponent"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { - ICON_STROKE_WIDTH, - RUN_TIMESTAMP_PREFIX, - STATUS_BUILD, - STATUS_BUILDING, - STATUS_INACTIVE, -} from "@/constants/constants"; +import { ICON_STROKE_WIDTH } from "@/constants/constants"; import { BuildStatus } from "@/constants/enums"; import { track } from "@/customization/utils/analytics"; import { useDarkStore } from "@/stores/darkStore"; @@ -43,7 +37,7 @@ export default function NodeStatus({ }: { nodeId: string; display_name: string; - selected: boolean; + selected?: boolean; setBorderColor: (color: string) => void; frozen?: boolean; showNode: boolean; @@ -101,8 +95,7 @@ export default function NodeStatus({ return cn(frozen ? frozenClass : className, updateClass); }; const getNodeBorderClassName = ( - selected: boolean, - showNode: boolean, + selected: boolean | undefined, buildStatus: BuildStatus | undefined, validationStatus: VertexBuildTypeAPI | null, ) => { @@ -119,7 +112,7 @@ export default function NodeStatus({ useEffect(() => { setBorderColor( - getNodeBorderClassName(selected, showNode, buildStatus, validationStatus), + getNodeBorderClassName(selected, buildStatus, validationStatus), ); }, [ selected, diff --git a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx index 79ab3c78e..92c453e3f 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx @@ -1,7 +1,7 @@ import { useDarkStore } from "@/stores/darkStore"; import useFlowStore from "@/stores/flowStore"; +import { Connection, Handle, Position } from "@xyflow/react"; import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { Handle, Position } from "reactflow"; import ShadTooltip from "../../../../components/common/shadTooltipComponent"; import { isValidConnection, @@ -413,7 +413,9 @@ const HandleRenderComponent = memo(function HandleRenderComponent({ type={left ? "target" : "source"} position={left ? Position.Left : Position.Right} id={myId} - isValidConnection={validateConnection} + isValidConnection={(connection) => + isValidConnection(connection as Connection, nodes, edges) + } className={cn( `group/handle z-50 transition-all`, !showNode && "no-show", diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 2795f3dd9..4015ca102 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -1,9 +1,9 @@ import ForwardedIconComponent from "@/components/common/genericIconComponent"; import ShadTooltip from "@/components/common/shadTooltipComponent"; import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code"; +import { useUpdateNodeInternals } from "@xyflow/react"; import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import { useUpdateNodeInternals } from "reactflow"; import { Button } from "../../components/ui/button"; import { TOOLTIP_HIDDEN_OUTPUTS, @@ -65,7 +65,7 @@ function GenericNode({ selected, }: { data: NodeDataType; - selected: boolean; + selected?: boolean; xPos?: number; yPos?: number; }): JSX.Element { diff --git a/src/frontend/src/CustomNodes/NoteNode/NoteToolbarComponent/index.tsx b/src/frontend/src/CustomNodes/NoteNode/NoteToolbarComponent/index.tsx index c1bb0746b..23b737036 100644 --- a/src/frontend/src/CustomNodes/NoteNode/NoteToolbarComponent/index.tsx +++ b/src/frontend/src/CustomNodes/NoteNode/NoteToolbarComponent/index.tsx @@ -10,7 +10,7 @@ import useAlertStore from "@/stores/alertStore"; import useFlowStore from "@/stores/flowStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore"; import { useShortcutsStore } from "@/stores/shortcuts"; -import { noteDataType } from "@/types/flow"; +import { NoteDataType } from "@/types/flow"; import { classNames, cn, openInNewTab } from "@/utils/utils"; import { cloneDeep } from "lodash"; import { memo, useCallback, useMemo } from "react"; @@ -22,7 +22,7 @@ const NoteToolbarComponent = memo(function NoteToolbarComponent({ data, bgColor, }: { - data: noteDataType; + data: NoteDataType; bgColor: string; }) { const setNoticeData = useAlertStore((state) => state.setNoticeData); @@ -69,21 +69,18 @@ const NoteToolbarComponent = memo(function NoteToolbarComponent({ setLastCopiedSelection({ nodes: cloneDeep(node), edges: [] }); break; case "duplicate": - const targetNode = nodes.find((node) => node.id === data.id); - if (targetNode) { - paste( - { - nodes: [targetNode], - edges: [], - }, - { - x: 50, - y: 10, - paneX: targetNode.position.x, - paneY: targetNode.position.y, - }, - ); - } + paste( + { + nodes: [nodes.find((node) => node.id === data.id)!], + edges: [], + }, + { + x: 50, + y: 10, + paneX: nodes.find((node) => node.id === data.id)?.position.x, + paneY: nodes.find((node) => node.id === data.id)?.position.y, + }, + ); break; } }, diff --git a/src/frontend/src/CustomNodes/NoteNode/index.tsx b/src/frontend/src/CustomNodes/NoteNode/index.tsx index 349b1e33d..0519f2214 100644 --- a/src/frontend/src/CustomNodes/NoteNode/index.tsx +++ b/src/frontend/src/CustomNodes/NoteNode/index.tsx @@ -5,18 +5,18 @@ import { NOTE_NODE_MIN_HEIGHT, NOTE_NODE_MIN_WIDTH, } from "@/constants/constants"; -import { noteDataType } from "@/types/flow"; +import { NoteDataType } from "@/types/flow"; import { cn } from "@/utils/utils"; +import { NodeResizer } from "@xyflow/react"; import { useEffect, useMemo, useRef, useState } from "react"; -import { NodeResizer } from "reactflow"; import NodeDescription from "../GenericNode/components/NodeDescription"; import NoteToolbarComponent from "./NoteToolbarComponent"; function NoteNode({ data, selected, }: { - data: noteDataType; - selected: boolean; + data: NoteDataType; + selected?: boolean; }) { const bgColor = Object.keys(COLOR_OPTIONS).find( diff --git a/src/frontend/src/CustomNodes/helpers/process-node-advanced-fields.ts b/src/frontend/src/CustomNodes/helpers/process-node-advanced-fields.ts index b3c448fcc..ad64a091b 100644 --- a/src/frontend/src/CustomNodes/helpers/process-node-advanced-fields.ts +++ b/src/frontend/src/CustomNodes/helpers/process-node-advanced-fields.ts @@ -1,10 +1,10 @@ import { APIClassType } from "@/types/api"; +import { EdgeType } from "@/types/flow"; import { cloneDeep } from "lodash"; -import { Edge } from "reactflow"; export function processNodeAdvancedFields( resData: APIClassType, - edges: Edge[], + edges: EdgeType[], nodeId: string, ) { let newNode = cloneDeep(resData); diff --git a/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx index aa91819b0..cf024ef23 100644 --- a/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx @@ -4,10 +4,10 @@ import useAlertStore from "@/stores/alertStore"; import useFlowStore from "@/stores/flowStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore"; import { APIClassType, InputFieldType } from "@/types/api"; -import { NodeType } from "@/types/flow"; +import { AllNodeType } from "@/types/flow"; +import { useUpdateNodeInternals } from "@xyflow/react"; import { cloneDeep } from "lodash"; import { useCallback, useMemo } from "react"; -import { useUpdateNodeInternals } from "reactflow"; import { mutateTemplate } from "../helpers/mutate-template"; export type handleOnNewValueType = ( @@ -29,7 +29,7 @@ const useHandleOnNewValue = ({ name: string; setNode?: ( id: string, - update: NodeType | ((oldState: NodeType) => NodeType), + update: AllNodeType | ((oldState: AllNodeType) => AllNodeType), ) => void; }) => { const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); diff --git a/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx index baf78a781..829da1857 100644 --- a/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx @@ -1,13 +1,13 @@ import useFlowStore from "@/stores/flowStore"; -import { NodeType } from "@/types/flow"; +import { AllNodeType } from "@/types/flow"; +import { useUpdateNodeInternals } from "@xyflow/react"; import { cloneDeep } from "lodash"; -import { useUpdateNodeInternals } from "reactflow"; const useHandleNodeClass = ( nodeId: string, setMyNode?: ( id: string, - update: NodeType | ((oldState: NodeType) => NodeType), + update: AllNodeType | ((oldState: AllNodeType) => AllNodeType), ) => void, ) => { const setNode = setMyNode ?? useFlowStore((state) => state.setNode); diff --git a/src/frontend/src/CustomNodes/hooks/use-update-all-nodes.tsx b/src/frontend/src/CustomNodes/hooks/use-update-all-nodes.tsx index b507cb4d0..9be164173 100644 --- a/src/frontend/src/CustomNodes/hooks/use-update-all-nodes.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-update-all-nodes.tsx @@ -1,7 +1,7 @@ +import { AllNodeType } from "@/types/flow"; import { cloneDeep } from "lodash"; import { useCallback } from "react"; import { APIClassType } from "../../types/api"; -import { NodeType } from "../../types/flow"; export type UpdateNodesType = { nodeId: string; @@ -12,7 +12,9 @@ export type UpdateNodesType = { }; const useUpdateAllNodes = ( - setNodes: (callback: (oldNodes: NodeType[]) => NodeType[]) => void, + setNodes: ( + update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]), + ) => void, updateNodeInternals: (nodeId: string) => void, ) => { const updateAllNodes = useCallback( diff --git a/src/frontend/src/components/core/canvasControlsComponent/index.tsx b/src/frontend/src/components/core/canvasControlsComponent/index.tsx index 5df5a5631..53cf7658c 100644 --- a/src/frontend/src/components/core/canvasControlsComponent/index.tsx +++ b/src/frontend/src/components/core/canvasControlsComponent/index.tsx @@ -4,8 +4,6 @@ import useSaveFlow from "@/hooks/flows/use-save-flow"; import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowStore from "@/stores/flowStore"; import { cn } from "@/utils/utils"; -import { cloneDeep } from "lodash"; -import { useEffect } from "react"; import { ControlButton, Panel, @@ -13,7 +11,9 @@ import { useStore, useStoreApi, type ReactFlowState, -} from "reactflow"; +} from "@xyflow/react"; +import { cloneDeep } from "lodash"; +import { useEffect } from "react"; import { shallow } from "zustand/shallow"; type CustomControlButtonProps = { @@ -106,7 +106,7 @@ const CanvasControls = ({ children }) => { return ( {/* Zoom In */} diff --git a/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx b/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx index 0d0176043..efc62cc3c 100644 --- a/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx +++ b/src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx @@ -2,7 +2,7 @@ import AccordionComponent from "@/components/common/accordionComponent"; import ShadTooltip from "@/components/common/shadTooltipComponent"; import { EditNodeComponent } from "@/modals/editNodeModal/components/editNodeComponent"; import { APIClassType } from "@/types/api"; -import { NodeType } from "@/types/flow"; +import { AllNodeType } from "@/types/flow"; import { customStringify } from "@/utils/reactflowUtils"; import { useEffect, useState } from "react"; @@ -11,7 +11,7 @@ export function TweakComponent({ node, }: { open: boolean; - node: NodeType; + node: AllNodeType; }) { const [nodeClass, setNodeClass] = useState( node.data?.node, diff --git a/src/frontend/src/components/core/codeTabsComponent/components/tweaksComponent/index.tsx b/src/frontend/src/components/core/codeTabsComponent/components/tweaksComponent/index.tsx index 9078bda1d..4005e63ce 100644 --- a/src/frontend/src/components/core/codeTabsComponent/components/tweaksComponent/index.tsx +++ b/src/frontend/src/components/core/codeTabsComponent/components/tweaksComponent/index.tsx @@ -1,12 +1,12 @@ import { useTweaksStore } from "@/stores/tweaksStore"; -import { NodeType } from "@/types/flow"; +import { AllNodeType } from "@/types/flow"; import { TweakComponent } from "../tweakComponent"; export function TweaksComponent({ open }: { open: boolean }) { const nodes = useTweaksStore((state) => state.nodes); return (
- {nodes?.map((node: NodeType, i) => ( + {nodes?.map((node: AllNodeType, i) => (
diff --git a/src/frontend/src/components/core/csvOutputComponent/index.tsx b/src/frontend/src/components/core/csvOutputComponent/index.tsx index 0ce8bfd38..2b2cd213a 100644 --- a/src/frontend/src/components/core/csvOutputComponent/index.tsx +++ b/src/frontend/src/components/core/csvOutputComponent/index.tsx @@ -1,3 +1,4 @@ +import { AllNodeType } from "@/types/flow"; import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid import "ag-grid-community/styles/ag-theme-balham.css"; // Optional Theme applied to the grid import { useEffect, useMemo, useState } from "react"; @@ -8,7 +9,6 @@ import { } from "../../../constants/constants"; import { useDarkStore } from "../../../stores/darkStore"; import { VertexBuildTypeAPI } from "../../../types/api"; -import { NodeType } from "../../../types/flow"; import ForwardedIconComponent from "../../common/genericIconComponent"; import Loading from "../../ui/loading"; import TableComponent from "../parameterRenderComponent/components/tableComponent"; @@ -18,7 +18,7 @@ function CsvOutputComponent({ csvNode, flowPool, }: { - csvNode: NodeType; + csvNode: AllNodeType; flowPool: VertexBuildTypeAPI; }) { const csvNodeArtifacts = flowPool?.data?.artifacts?.repr; diff --git a/src/frontend/src/components/core/flowToolbarComponent/index.tsx b/src/frontend/src/components/core/flowToolbarComponent/index.tsx index 88211cb74..cc96c5881 100644 --- a/src/frontend/src/components/core/flowToolbarComponent/index.tsx +++ b/src/frontend/src/components/core/flowToolbarComponent/index.tsx @@ -5,9 +5,9 @@ import { ENABLE_LANGFLOW_STORE, } from "@/customization/feature-flags"; import { track } from "@/customization/utils/analytics"; +import { Panel } from "@xyflow/react"; import { useEffect, useMemo, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import { Panel } from "reactflow"; import ApiModal from "../../../modals/apiModal"; import ShareModal from "../../../modals/shareModal"; import useFlowStore from "../../../stores/flowStore"; diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableAdvancedToggleCellRender/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableAdvancedToggleCellRender/index.tsx index 48f1c9b37..36e88062a 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableAdvancedToggleCellRender/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableAdvancedToggleCellRender/index.tsx @@ -1,6 +1,7 @@ import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value"; import ShadTooltip from "@/components/common/shadTooltipComponent"; import useFlowStore from "@/stores/flowStore"; +import { APIClassType } from "@/types/api"; import { isTargetHandleConnected } from "@/utils/reactflowUtils"; import { CustomCellRendererProps } from "ag-grid-react"; import ToggleShadComponent from "../../../toggleShadComponent"; @@ -20,7 +21,7 @@ export default function TableAdvancedToggleCellRender({ ); const { handleOnNewValue } = useHandleOnNewValue({ - node: node?.data.node, + node: node?.data.node as APIClassType, nodeId, name: parameterId, }); diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx index a8949b292..7d0832f69 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender/index.tsx @@ -3,6 +3,7 @@ import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class"; import { ParameterRenderComponent } from "@/components/core/parameterRenderComponent"; import useFlowStore from "@/stores/flowStore"; import { useTweaksStore } from "@/stores/tweaksStore"; +import { APIClassType } from "@/types/api"; import { isTargetHandleConnected } from "@/utils/reactflowUtils"; import { CustomCellRendererProps } from "ag-grid-react"; @@ -25,7 +26,7 @@ export default function TableNodeCellRender({ ); const { handleOnNewValue } = useHandleOnNewValue({ - node: node?.data.node, + node: node?.data.node as APIClassType, nodeId, name: parameterId, setNode: isTweaks ? setNode : undefined, diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 8ad3aa7e1..b935fb0ed 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -1,8 +1,8 @@ import { GradientWrapper } from "@/components/common/GradientWrapper"; import { CustomWrapper } from "@/customization/custom-wrapper"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactFlowProvider } from "@xyflow/react"; import { ReactNode } from "react"; -import { ReactFlowProvider } from "reactflow"; import { TooltipProvider } from "../components/ui/tooltip"; import { ApiInterceptor } from "../controllers/API/api"; import { AuthProvider } from "./authContext"; diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 3882fa0e0..5d4f112d0 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -1,5 +1,5 @@ +import { Edge, Node, ReactFlowJsonObject } from "@xyflow/react"; import { AxiosRequestConfig, AxiosResponse } from "axios"; -import { Edge, Node, ReactFlowJsonObject } from "reactflow"; import { BASE_URL_API } from "../../constants/constants"; import { api } from "../../controllers/API/api"; import { diff --git a/src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts b/src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts index af5280546..49923662d 100644 --- a/src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts +++ b/src/frontend/src/controllers/API/queries/flows/use-patch-update-flow.ts @@ -1,6 +1,6 @@ import { useMutationFunctionType } from "@/types/api"; import { UseMutationResult } from "@tanstack/react-query"; -import { ReactFlowJsonObject } from "reactflow"; +import { ReactFlowJsonObject } from "@xyflow/react"; import { api } from "../../api"; import { getURL } from "../../helpers/constants"; import { UseRequestProcessor } from "../../services/request-processor"; diff --git a/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts b/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts index ff8d17b89..687c23ba4 100644 --- a/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts +++ b/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts @@ -1,7 +1,7 @@ import { useFolderStore } from "@/stores/foldersStore"; import { useMutationFunctionType } from "@/types/api"; import { UseMutationResult } from "@tanstack/react-query"; -import { ReactFlowJsonObject } from "reactflow"; +import { ReactFlowJsonObject } from "@xyflow/react"; import { api } from "../../api"; import { getURL } from "../../helpers/constants"; import { UseRequestProcessor } from "../../services/request-processor"; diff --git a/src/frontend/src/controllers/API/queries/starter-projects/use-get-starter-projects.ts b/src/frontend/src/controllers/API/queries/starter-projects/use-get-starter-projects.ts index 4e2b27911..69a9cf2a8 100644 --- a/src/frontend/src/controllers/API/queries/starter-projects/use-get-starter-projects.ts +++ b/src/frontend/src/controllers/API/queries/starter-projects/use-get-starter-projects.ts @@ -1,4 +1,3 @@ -import { useDarkStore } from "@/stores/darkStore"; import { useQueryFunctionType } from "@/types/api"; import { api } from "../../api"; import { getURL } from "../../helpers/constants"; @@ -24,7 +23,7 @@ interface IApiQueryResponse { export const useGetStarterProjectsQuery: useQueryFunctionType< undefined, IApiQueryResponse -> = (_, options) => { +> = (options) => { const { query } = UseRequestProcessor(); const getStarterProjectsFn = async () => { diff --git a/src/frontend/src/controllers/API/queries/vertex/use-post-retrieve-vertex-order.tsx b/src/frontend/src/controllers/API/queries/vertex/use-post-retrieve-vertex-order.tsx index a757ed6cf..5f4f9bb85 100644 --- a/src/frontend/src/controllers/API/queries/vertex/use-post-retrieve-vertex-order.tsx +++ b/src/frontend/src/controllers/API/queries/vertex/use-post-retrieve-vertex-order.tsx @@ -1,6 +1,6 @@ import { useMutationFunctionType } from "@/types/api"; +import { ReactFlowJsonObject } from "@xyflow/react"; import { AxiosRequestConfig } from "axios"; -import { ReactFlowJsonObject } from "reactflow"; import { api } from "../../api"; import { getURL } from "../../helpers/constants"; import { UseRequestProcessor } from "../../services/request-processor"; diff --git a/src/frontend/src/hooks/flows/use-save-flow.ts b/src/frontend/src/hooks/flows/use-save-flow.ts index 6ff7ac554..df08aad5a 100644 --- a/src/frontend/src/hooks/flows/use-save-flow.ts +++ b/src/frontend/src/hooks/flows/use-save-flow.ts @@ -5,7 +5,7 @@ import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowStore from "@/stores/flowStore"; import { FlowType } from "@/types/flow"; import { customStringify } from "@/utils/reactflowUtils"; -import { ReactFlowJsonObject } from "reactflow"; +import { ReactFlowJsonObject } from "@xyflow/react"; const useSaveFlow = () => { const flows = useFlowsManagerStore((state) => state.flows); diff --git a/src/frontend/src/hooks/useAddComponent.ts b/src/frontend/src/hooks/useAddComponent.ts index 924b5ba60..a18e553c0 100644 --- a/src/frontend/src/hooks/useAddComponent.ts +++ b/src/frontend/src/hooks/useAddComponent.ts @@ -2,11 +2,11 @@ import { NODE_WIDTH } from "@/constants/constants"; import { track } from "@/customization/utils/analytics"; import useFlowStore from "@/stores/flowStore"; import { APIClassType } from "@/types/api"; -import { NodeType } from "@/types/flow"; +import { AllNodeType } from "@/types/flow"; import { getNodeId } from "@/utils/reactflowUtils"; import { getNodeRenderType } from "@/utils/utils"; +import { useStoreApi } from "@xyflow/react"; import { useCallback } from "react"; -import { useStoreApi } from "reactflow"; export function useAddComponent() { const store = useStoreApi(); @@ -50,7 +50,7 @@ export function useAddComponent() { const newId = getNodeId(type); - const newNode: NodeType = { + const newNode: AllNodeType = { id: newId, type: getNodeRenderType("genericnode"), position: { x: 0, y: 0 }, diff --git a/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx b/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx index 972c2ba80..820b5c201 100644 --- a/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx +++ b/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx @@ -1,5 +1,5 @@ import useHandleNewValue from "@/CustomNodes/hooks/use-handle-new-value"; -import { NodeType } from "@/types/flow"; +import { AllNodeType } from "@/types/flow"; import { cloneDeep } from "lodash"; import { useState } from "react"; import ImageViewer from "../../../../components/common/ImageViewer"; @@ -35,7 +35,9 @@ export default function IOFieldView({ const nodes = useFlowStore((state) => state.nodes); const setNode = useFlowStore((state) => state.setNode); const flowPool = useFlowStore((state) => state.flowPool); - const node: NodeType | undefined = nodes.find((node) => node.id === fieldId); + const node: AllNodeType | undefined = nodes.find( + (node) => node.id === fieldId, + ); const flowPoolNode = (flowPool[node!.id] ?? [])[ (flowPool[node!.id]?.length ?? 1) - 1 ]; diff --git a/src/frontend/src/modals/IOModal/index.tsx b/src/frontend/src/modals/IOModal/index.tsx index 8ffc9b73f..325bbd337 100644 --- a/src/frontend/src/modals/IOModal/index.tsx +++ b/src/frontend/src/modals/IOModal/index.tsx @@ -1,12 +1,10 @@ +import AccordionComponent from "@/components/common/accordionComponent"; import { useDeleteMessages, useGetMessagesQuery, } from "@/controllers/API/queries/messages"; import { useUtilityStore } from "@/stores/utilityStore"; -import { someFlowTemplateFields } from "@/utils/reactflowUtils"; import { useEffect, useState } from "react"; -import ShortUniqueId from "short-unique-id"; -import AccordionComponent from "../../components/accordionComponent"; import IconComponent from "../../components/common/genericIconComponent"; import ShadTooltip from "../../components/common/shadTooltipComponent"; import { Badge } from "../../components/ui/badge"; @@ -24,7 +22,7 @@ import useFlowStore from "../../stores/flowStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { useMessagesStore } from "../../stores/messagesStore"; import { IOModalPropsType } from "../../types/components"; -import { NodeType } from "../../types/flow"; +import { AllNodeType } from "../../types/flow"; import { cn } from "../../utils/utils"; import BaseModal from "../baseModal"; import IOFieldView from "./components/IOFieldView"; @@ -168,7 +166,7 @@ export default function IOModal({ // refetch(); setLockChat(false); if (chatInput) { - setNode(chatInput.id, (node: NodeType) => { + setNode(chatInput.id, (node: AllNodeType) => { const newNode = { ...node }; newNode.data.node!.template["input_value"].value = chatValue; diff --git a/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts b/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts index 04588f107..92f0041ab 100644 --- a/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts +++ b/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts @@ -1,9 +1,9 @@ -import { NodeType } from "@/types/flow"; +import { AllNodeType } from "@/types/flow"; import { cloneDeep } from "lodash"; import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants"; -export const getNodesWithDefaultValue = (nodes: NodeType[]) => { - const filteredNodes: NodeType[] = []; +export const getNodesWithDefaultValue = (nodes: AllNodeType[]) => { + const filteredNodes: AllNodeType[] = []; nodes.forEach((node) => { if (node?.data?.node?.template && node?.type === "genericNode") { diff --git a/src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx index a225c0404..8a0cfe990 100644 --- a/src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx @@ -1,5 +1,5 @@ import useFlowStore from "@/stores/flowStore"; -import { ConnectionLineComponentProps } from "reactflow"; +import { ConnectionLineComponentProps } from "@xyflow/react"; const ConnectionLineComponent = ({ fromX, diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 6f6e64e85..75bed98d4 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -20,6 +20,17 @@ import useUploadFlow from "@/hooks/flows/use-upload-flow"; import { useAddComponent } from "@/hooks/useAddComponent"; import { nodeColorsName } from "@/utils/styleUtils"; import { cn, isSupportedNodeTypes } from "@/utils/utils"; +import { + Background, + Connection, + Edge, + OnNodeDrag, + OnSelectionChangeParams, + Panel, + ReactFlow, + reconnectEdge, + SelectionDragHandler, +} from "@xyflow/react"; import _, { cloneDeep } from "lodash"; import { KeyboardEvent, @@ -30,16 +41,6 @@ import { useState, } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import ReactFlow, { - Background, - Connection, - Edge, - NodeDragHandler, - OnSelectionChangeParams, - Panel, - SelectionDragHandler, - updateEdge, -} from "reactflow"; import GenericNode from "../../../../CustomNodes/GenericNode"; import { INVALID_SELECTION_ERROR_ALERT, @@ -53,7 +54,7 @@ import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import { useShortcutsStore } from "../../../../stores/shortcuts"; import { useTypesStore } from "../../../../stores/typesStore"; import { APIClassType } from "../../../../types/api"; -import { NodeType } from "../../../../types/flow"; +import { AllNodeType, EdgeType, NoteNodeType } from "../../../../types/flow"; import { generateFlow, generateNodeFromFlow, @@ -323,14 +324,14 @@ export default function Page({ view }: { view?: boolean }): JSX.Element { [takeSnapshot, onConnect], ); - const onNodeDragStart: NodeDragHandler = useCallback(() => { + const onNodeDragStart: OnNodeDrag = useCallback(() => { // ๐Ÿ‘‡ make dragging a node undoable takeSnapshot(); // ๐Ÿ‘‰ you can place your event handlers here }, [takeSnapshot]); - const onNodeDragStop: NodeDragHandler = useCallback(() => { + const onNodeDragStop: OnNodeDrag = useCallback(() => { // ๐Ÿ‘‡ make moving the canvas undoable autoSaveFlow(); updateCurrentFlow({ nodes }); @@ -405,12 +406,14 @@ export default function Page({ view }: { view?: boolean }): JSX.Element { }, []); const onEdgeUpdate = useCallback( - (oldEdge: Edge, newConnection: Connection) => { + (oldEdge: EdgeType, newConnection: Connection) => { if (isValidConnection(newConnection, nodes, edges)) { edgeUpdateSuccessful.current = true; - oldEdge.data.targetHandle = scapeJSONParse(newConnection.targetHandle!); - oldEdge.data.sourceHandle = scapeJSONParse(newConnection.sourceHandle!); - setEdges((els) => updateEdge(oldEdge, newConnection, els)); + oldEdge.data = { + targetHandle: scapeJSONParse(newConnection.targetHandle!), + sourceHandle: scapeJSONParse(newConnection.sourceHandle!), + }; + setEdges((els) => reconnectEdge(oldEdge, newConnection, els)); } }, [setEdges], @@ -472,7 +475,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element { }; const newId = getNodeId(data.type); - const newNode: NodeType = { + const newNode: NoteNodeType = { id: newId, type: "noteNode", position: position || { x: 0, y: 0 }, @@ -521,7 +524,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
{showCanvas ? (
- nodes={nodes} edges={edges} onNodesChange={onNodesChange} @@ -530,9 +533,9 @@ export default function Page({ view }: { view?: boolean }): JSX.Element { disableKeyboardA11y={true} onInit={setReactFlowInstance} nodeTypes={nodeTypes} - onEdgeUpdate={onEdgeUpdate} - onEdgeUpdateStart={onEdgeUpdateStart} - onEdgeUpdateEnd={onEdgeUpdateEnd} + onReconnect={onEdgeUpdate} + onReconnectStart={onEdgeUpdateStart} + onReconnectEnd={onEdgeUpdateEnd} onNodeDragStart={onNodeDragStart} onSelectionDragStart={onSelectionDragStart} onSelectionEnd={onSelectionEnd} diff --git a/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx index f548481da..aa3b1a80f 100644 --- a/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx @@ -1,6 +1,6 @@ +import { NodeToolbar } from "@xyflow/react"; import { useEffect, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import { NodeToolbar } from "reactflow"; import ShadTooltip from "../../../../components/common/shadTooltipComponent"; import { Button } from "../../../../components/ui/button"; import { GradientGroup } from "../../../../icons/GradientSparkles"; @@ -80,6 +80,7 @@ export default function SelectionMenu({ }`} onClick={onClick} disabled={disable} + data-testid="error-group-node" > { const node = nodes.find((n) => n.id === nodeId); - if (!node) return Promise.resolve(); + if (!node || node.type !== "genericNode") return Promise.resolve(); const thisNodeTemplate = templates[node.data.type]?.template; if (!thisNodeTemplate?.code) return Promise.resolve(); @@ -46,7 +46,7 @@ export default function UpdateAllComponents() { return new Promise((resolve) => { validateComponentCode({ code: currentCode, - frontend_node: node.data.node, + frontend_node: node.data.node!, }) .then(({ data: resData, type }) => { if (resData && type) { diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index e8dbe9148..84e5f1d7d 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -10,9 +10,9 @@ import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-t import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex"; import useAddFlow from "@/hooks/flows/use-add-flow"; import { APIClassType } from "@/types/api"; +import { useUpdateNodeInternals } from "@xyflow/react"; import _, { cloneDeep } from "lodash"; import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useUpdateNodeInternals } from "reactflow"; import IconComponent from "../../../../components/common/genericIconComponent"; import { Select, diff --git a/src/frontend/src/pages/MainPage/utils/get-template-style.ts b/src/frontend/src/pages/MainPage/utils/get-template-style.ts index 21523216d..2eecf47c8 100644 --- a/src/frontend/src/pages/MainPage/utils/get-template-style.ts +++ b/src/frontend/src/pages/MainPage/utils/get-template-style.ts @@ -6,7 +6,10 @@ export const useGetTemplateStyle = ( flowData: FlowType, ): { getIcon: () => string } => { const getIcon = () => { - if (flowData.is_component) { + if ( + flowData.is_component && + flowData.data?.nodes[0].type === "genericNode" + ) { const dataType = flowData.data?.nodes[0].data.type; const isGroup = !!flowData.data?.nodes[0].data.node?.flow; const icon = flowData.data?.nodes[0].data.node?.icon; diff --git a/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/EditShortcutButton/index.tsx b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/EditShortcutButton/index.tsx index df6012a1a..e5e12c255 100644 --- a/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/EditShortcutButton/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/EditShortcutButton/index.tsx @@ -19,7 +19,11 @@ export default function EditShortcutButton({ }: { children: JSX.Element; shortcut: string[]; - defaultShortcuts: Array<{ name: string; shortcut: string }>; + defaultShortcuts: Array<{ + name: string; + shortcut: string; + display_name: string; + }>; open: boolean; setOpen: (bool: boolean) => void; disable?: boolean; @@ -64,10 +68,15 @@ export default function EditShortcutButton({ if (s.name === shortcut[0]) { return { name: s.name, + display_name: s.display_name, shortcut: fixCombination.join("").toLowerCase(), }; } - return { name: s.name, shortcut: s.shortcut }; + return { + name: s.name, + display_name: s.display_name, + shortcut: s.shortcut, + }; }); const shortcutName = toCamelCase(shortcut[0]); setUniqueShortcut(shortcutName, fixCombination.join("").toLowerCase()); diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index 0b53ec3b9..dbf51ce11 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -4,16 +4,15 @@ import { } from "@/constants/constants"; import { track } from "@/customization/utils/analytics"; import { brokenEdgeMessage } from "@/utils/utils"; -import { cloneDeep, zip } from "lodash"; import { - Edge, EdgeChange, Node, NodeChange, addEdge, applyEdgeChanges, applyNodeChanges, -} from "reactflow"; +} from "@xyflow/react"; +import { cloneDeep, zip } from "lodash"; import { create } from "zustand"; import { FLOW_BUILD_SUCCESS_ALERT, @@ -23,8 +22,9 @@ import { BuildStatus } from "../constants/enums"; import { VertexBuildTypeAPI } from "../types/api"; import { ChatInputType, ChatOutputType } from "../types/chat"; import { + AllNodeType, + EdgeType, NodeDataType, - NodeType, sourceHandleType, targetHandleType, } from "../types/flow"; @@ -68,16 +68,19 @@ const useFlowStore = create((set, get) => ({ let outdatedNodes: string[] = []; const templates = useTypesStore.getState().templates; for (let i = 0; i < nodes.length; i++) { - const currentCode = templates[nodes[i].data?.type]?.template?.code?.value; - const thisNodesCode = nodes[i].data?.node!.template?.code?.value; - if ( - currentCode && - thisNodesCode && - currentCode !== thisNodesCode && - !nodes[i].data?.node?.edited && - !componentsToIgnoreUpdate.includes(nodes[i].data?.type) - ) { - outdatedNodes.push(nodes[i].id); + let node = nodes[i]; + if (node.type === "genericNode") { + const currentCode = templates[node.data?.type]?.template?.code?.value; + const thisNodesCode = node.data?.node!.template?.code?.value; + if ( + currentCode && + thisNodesCode && + currentCode !== thisNodesCode && + !node.data?.node?.edited && + !componentsToIgnoreUpdate.includes(node.data?.type) + ) { + outdatedNodes.push(node.id); + } } } set({ componentsToUpdate: outdatedNodes }); @@ -122,10 +125,13 @@ const useFlowStore = create((set, get) => ({ set({ flowPool }); }, updateToolMode: (nodeId: string, toolMode: boolean) => { - get().setNode(nodeId, (node) => ({ - ...node, - data: { ...node.data, node: { ...node.data.node, tool_mode: toolMode } }, - })); + get().setNode(nodeId, (node) => { + let newNode = cloneDeep(node); + if (newNode.type === "genericNode") { + newNode.data.node!.tool_mode = toolMode; + } + return newNode; + }); }, updateFreezeStatus: (nodeIds: string[], freeze: boolean) => { get().setNodes((oldNodes) => { @@ -223,12 +229,12 @@ const useFlowStore = create((set, get) => ({ setReactFlowInstance: (newState) => { set({ reactFlowInstance: newState }); }, - onNodesChange: (changes: NodeChange[]) => { + onNodesChange: (changes: NodeChange[]) => { set({ nodes: applyNodeChanges(changes, get().nodes), }); }, - onEdgesChange: (changes: EdgeChange[]) => { + onEdgesChange: (changes: EdgeChange[]) => { set({ edges: applyEdgeChanges(changes, get().edges), }); @@ -264,7 +270,7 @@ const useFlowStore = create((set, get) => ({ }, setNode: ( id: string, - change: Node | ((oldState: Node) => Node), + change: AllNodeType | ((oldState: AllNodeType) => AllNodeType), isUserChange: boolean = true, callback?: () => void, ) => { @@ -305,8 +311,8 @@ const useFlowStore = create((set, get) => ({ }, deleteNode: (nodeId) => { const { filteredNodes, deletedNode } = get().nodes.reduce<{ - filteredNodes: Node[]; - deletedNode: Node | null; + filteredNodes: AllNodeType[]; + deletedNode: AllNodeType | null; }>( (acc, node) => { const isMatch = @@ -362,7 +368,7 @@ const useFlowStore = create((set, get) => ({ let minimumX = Infinity; let minimumY = Infinity; let idsMap = {}; - let newNodes: Node[] = get().nodes; + let newNodes: AllNodeType[] = get().nodes; let newEdges = get().edges; selection.nodes.forEach((node: Node) => { if (node.position.y < minimumY) { @@ -380,15 +386,15 @@ const useFlowStore = create((set, get) => ({ y: position.y, }); - selection.nodes.forEach((node: NodeType) => { + selection.nodes.forEach((node: AllNodeType) => { // Generate a unique node ID let newId = getNodeId(node.data.type); idsMap[node.id] = newId; - // Create a new node object - const newNode: NodeType = { + // Create a new node object with the correct type + const newNode = { id: newId, - type: node.type, + type: node.type as "genericNode" | "noteNode", position: { x: insidePosition.x + node.position!.x - minimumX, y: insidePosition.y + node.position!.y - minimumY, @@ -397,7 +403,8 @@ const useFlowStore = create((set, get) => ({ ...cloneDeep(node.data), id: newId, }, - }; + } as AllNodeType; + updateGroupRecursion( newNode, selection.edges, @@ -412,7 +419,7 @@ const useFlowStore = create((set, get) => ({ }); get().setNodes(newNodes); - selection.edges.forEach((edge: Edge) => { + selection.edges.forEach((edge: EdgeType) => { let source = idsMap[edge.source]; let target = idsMap[edge.target]; const sourceHandleObject: sourceHandleType = scapeJSONParse( @@ -424,7 +431,6 @@ const useFlowStore = create((set, get) => ({ }); sourceHandleObject.id = source; - edge.data.sourceHandle = sourceHandleObject; const targetHandleObject: targetHandleType = scapeJSONParse( edge.targetHandle!, ); @@ -433,7 +439,12 @@ const useFlowStore = create((set, get) => ({ id: target, }); targetHandleObject.id = target; - edge.data.targetHandle = targetHandleObject; + + edge.data = { + sourceHandle: sourceHandleObject, + targetHandle: targetHandleObject, + }; + let id = getHandleId(source, sourceHandle, target, targetHandle); newEdges = addEdge( { @@ -516,7 +527,7 @@ const useFlowStore = create((set, get) => ({ // isIoOut = outputTypes.has(sourceType); // } - let newEdges: Edge[] = []; + let newEdges: EdgeType[] = []; get().setEdges((oldEdges) => { newEdges = addEdge( { @@ -733,7 +744,10 @@ const useFlowStore = create((set, get) => ({ const edges = get().edges; const newEdges = edges.map((edge) => { - if (idList.includes(edge.data.targetHandle.id)) { + if ( + edge.data?.targetHandle && + idList.includes(edge.data.targetHandle.id ?? "") + ) { edge.className = "ran"; } return edge; @@ -759,7 +773,10 @@ const useFlowStore = create((set, get) => ({ updateEdgesRunningByNodes: (ids: string[], running: boolean) => { const edges = get().edges; const newEdges = edges.map((edge) => { - if (ids.includes(edge.data.sourceHandle.id)) { + if ( + edge.data?.sourceHandle && + ids.includes(edge.data.sourceHandle.id ?? "") + ) { edge.animated = running; edge.className = running ? "running" : ""; } else { diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 6b59ef875..8f1415ec5 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -4,7 +4,7 @@ import { UseQueryOptions, UseQueryResult, } from "@tanstack/react-query"; -import { Edge, Node, Viewport } from "reactflow"; +import { Edge, Node, Viewport } from "@xyflow/react"; import { ChatInputType, ChatOutputType } from "../chat"; import { FlowType } from "../flow"; //kind and class are just representative names to represent the actual structure of the object received by the API diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index d8c94ec54..ec76d459c 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -1,5 +1,5 @@ +import { ReactFlowJsonObject } from "@xyflow/react"; import { ReactElement, ReactNode } from "react"; -import { ReactFlowJsonObject } from "reactflow"; import { InputOutput } from "../../constants/enums"; import { APIClassType, @@ -8,7 +8,12 @@ import { OutputFieldProxyType, } from "../api"; import { ChatMessageType } from "../chat"; -import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index"; +import { + AllNodeType, + FlowStyleType, + FlowType, + NodeDataType, +} from "../flow/index"; import { sourceHandleType, targetHandleType } from "./../flow/index"; export type InputComponentType = { name?: string; @@ -692,11 +697,11 @@ export type tabsArrayType = { export type codeTabsPropsType = { open?: boolean; - tabs: Array; + tabs: tabsArrayType[]; activeTab: string; setActiveTab: (value: string) => void; isMessage?: boolean; - tweaksNodes?: Array; + tweaksNodes?: AllNodeType[]; activeTweaks?: boolean; setActiveTweaks?: (value: boolean) => void; }; diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts index db740eb44..197c521b1 100644 --- a/src/frontend/src/types/flow/index.ts +++ b/src/frontend/src/types/flow/index.ts @@ -1,4 +1,4 @@ -import { ReactFlowJsonObject, XYPosition } from "reactflow"; +import { Edge, Node, ReactFlowJsonObject } from "@xyflow/react"; import { BuildStatus } from "../../constants/enums"; import { APIClassType } from "../api/index"; @@ -13,7 +13,7 @@ export type PaginatedFlowsType = { export type FlowType = { name: string; id: string; - data: ReactFlowJsonObject | null; + data: ReactFlowJsonObject | null; description: string; endpoint_name?: string | null; style?: FlowStyleType; @@ -33,38 +33,46 @@ export type FlowType = { locked?: boolean | null; }; -export type NodeType = { - id: string; - type?: string; - position: XYPosition; - data: NodeDataType; - selected?: boolean; -}; +export type GenericNodeType = Node; +export type NoteNodeType = Node; -export interface noteClassType - extends Pick { +export type AllNodeType = GenericNodeType | NoteNodeType; +export type SetNodeType = + T extends "genericNode" ? GenericNodeType : NoteNodeType; + +export type noteClassType = Pick< + APIClassType, + "description" | "display_name" | "documentation" | "tool_mode" | "frozen" +> & { template: { - backgroundColor: string; + backgroundColor?: string; [key: string]: any; }; -} +}; -export interface noteDataType - extends Pick { +export type NoteDataType = { showNode?: boolean; type: string; - node?: noteClassType; + node: noteClassType; id: string; -} +}; export type NodeDataType = { showNode?: boolean; type: string; - node?: APIClassType; + node: APIClassType; id: string; output_types?: string[]; selected_output_type?: string; buildStatus?: BuildStatus; }; + +export type EdgeType = Edge; + +export type EdgeDataType = { + sourceHandle: sourceHandleType; + targetHandle: targetHandleType; +}; + // FlowStyleType is the type of the style object that is used to style the // Flow card with an emoji and a color. export type FlowStyleType = { @@ -83,6 +91,7 @@ export type TweaksType = Array< // right side export type sourceHandleType = { + baseClasses?: string[]; dataType: string; id: string; output_types: string[]; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 97693053b..4b8c5d188 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -1,4 +1,4 @@ -import { XYPosition } from "reactflow"; +import { XYPosition } from "@xyflow/react"; import { FlowType, NodeDataType } from "../flow"; type OnChange = (changes: ChangesType[]) => void; diff --git a/src/frontend/src/types/utils/reactflowUtils.ts b/src/frontend/src/types/utils/reactflowUtils.ts index bb1d4c1d1..f3b5480b7 100644 --- a/src/frontend/src/types/utils/reactflowUtils.ts +++ b/src/frontend/src/types/utils/reactflowUtils.ts @@ -1,18 +1,18 @@ -import { Edge } from "reactflow"; -import { FlowType, NodeType } from "../flow"; +import { Edge } from "@xyflow/react"; +import { AllNodeType, EdgeType, FlowType } from "../flow"; export type addEscapedHandleIdsToEdgesType = { - edges: Edge[]; + edges: EdgeType[]; }; export type updateEdgesHandleIdsType = { - nodes: NodeType[]; - edges: Edge[]; + nodes: AllNodeType[]; + edges: EdgeType[]; }; export type generateFlowType = { newFlow: FlowType; removedEdges: Edge[] }; export type findLastNodeType = { - nodes: NodeType[]; - edges: Edge[]; + nodes: AllNodeType[]; + edges: EdgeType[]; }; diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts index b53653cc5..680a142ca 100644 --- a/src/frontend/src/types/zustand/flow/index.ts +++ b/src/frontend/src/types/zustand/flow/index.ts @@ -1,13 +1,12 @@ -import { FlowType } from "@/types/flow"; +import { AllNodeType, EdgeType, FlowType } from "@/types/flow"; import { Connection, - Edge, Node, OnEdgesChange, OnNodesChange, ReactFlowInstance, Viewport, -} from "reactflow"; +} from "@xyflow/react"; import { BuildStatus } from "../../../constants/enums"; import { VertexBuildTypeAPI } from "../../api"; import { ChatInputType, ChatOutputType } from "../../chat"; @@ -60,7 +59,7 @@ export type FlowStoreType = { setComponentsToUpdate: ( update: string[] | ((oldState: string[]) => string[]), ) => void; - updateComponentsToUpdate: (nodes: Node[]) => void; + updateComponentsToUpdate: (nodes: AllNodeType[]) => void; onFlowPage: boolean; setOnFlowPage: (onFlowPage: boolean) => void; flowPool: FlowPoolType; @@ -90,8 +89,10 @@ export type FlowStoreType = { setIsBuilding: (isBuilding: boolean) => void; setPending: (isPending: boolean) => void; resetFlow: (flow: FlowType | undefined) => void; - reactFlowInstance: ReactFlowInstance | null; - setReactFlowInstance: (newState: ReactFlowInstance) => void; + reactFlowInstance: ReactFlowInstance | null; + setReactFlowInstance: ( + newState: ReactFlowInstance, + ) => void; flowState: FlowState | undefined; setFlowState: ( state: @@ -99,19 +100,23 @@ export type FlowStoreType = { | undefined | ((oldState: FlowState | undefined) => FlowState), ) => void; - nodes: Node[]; - edges: Edge[]; - onNodesChange: OnNodesChange; - onEdgesChange: OnEdgesChange; - setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void; - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void; + nodes: AllNodeType[]; + edges: EdgeType[]; + onNodesChange: OnNodesChange; + onEdgesChange: OnEdgesChange; + setNodes: ( + update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]), + ) => void; + setEdges: ( + update: EdgeType[] | ((oldState: EdgeType[]) => EdgeType[]), + ) => void; setNode: ( id: string, - update: Node | ((oldState: Node) => Node), + update: AllNodeType | ((oldState: AllNodeType) => AllNodeType), isUserChange?: boolean, callback?: () => void, ) => void; - getNode: (id: string) => Node | undefined; + getNode: (id: string) => AllNodeType | undefined; deleteNode: (nodeId: string | Array) => void; deleteEdge: (edgeId: string | Array) => void; paste: ( @@ -145,7 +150,7 @@ export type FlowStoreType = { silent?: boolean; session?: string; }) => Promise; - getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport }; + getFlow: () => { nodes: Node[]; edges: EdgeType[]; viewport: Viewport }; updateVerticesBuild: ( vertices: { verticesIds: string[]; @@ -183,8 +188,8 @@ export type FlowStoreType = { edges, viewport, }: { - nodes?: Node[]; - edges?: Edge[]; + nodes?: AllNodeType[]; + edges?: EdgeType[]; viewport?: Viewport; }) => void; handleDragging: diff --git a/src/frontend/src/types/zustand/tweaks/index.ts b/src/frontend/src/types/zustand/tweaks/index.ts index 854cb62b5..018b7cbb5 100644 --- a/src/frontend/src/types/zustand/tweaks/index.ts +++ b/src/frontend/src/types/zustand/tweaks/index.ts @@ -1,21 +1,21 @@ -import { FlowType, NodeType } from "@/types/flow"; +import { AllNodeType, FlowType } from "@/types/flow"; import { GetCodesType } from "@/types/tweaks"; import { tabsArrayType } from "../../components"; export type TweaksStoreType = { activeTweaks: boolean; setActiveTweaks: (activeTweaks: boolean) => void; - nodes: NodeType[]; + nodes: AllNodeType[]; setNodes: ( - update: NodeType[] | ((oldState: NodeType[]) => NodeType[]), + update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]), skipSave?: boolean, ) => void; setNode: ( id: string, - update: NodeType | ((oldState: NodeType) => NodeType), + update: AllNodeType | ((oldState: AllNodeType) => AllNodeType), ) => void; getCodes: GetCodesType; - getNode: (id: string) => NodeType | undefined; + getNode: (id: string) => AllNodeType | undefined; tabs: tabsArrayType[]; initialSetup: ( autoLogin: boolean, diff --git a/src/frontend/src/utils/buildUtils.ts b/src/frontend/src/utils/buildUtils.ts index 76251f998..c4dbe047e 100644 --- a/src/frontend/src/utils/buildUtils.ts +++ b/src/frontend/src/utils/buildUtils.ts @@ -1,9 +1,9 @@ import { BASE_URL_API } from "@/constants/constants"; import { performStreamingRequest } from "@/controllers/API/api"; import { useMessagesStore } from "@/stores/messagesStore"; +import { Edge, Node } from "@xyflow/react"; import { AxiosError } from "axios"; import { flushSync } from "react-dom"; -import { Edge, Node } from "reactflow"; import { BuildStatus } from "../constants/enums"; import { getVerticesOrder, postBuildVertex } from "../controllers/API"; import useAlertStore from "../stores/alertStore"; diff --git a/src/frontend/src/utils/layoutUtils.ts b/src/frontend/src/utils/layoutUtils.ts index 990da635b..eee5dcd77 100644 --- a/src/frontend/src/utils/layoutUtils.ts +++ b/src/frontend/src/utils/layoutUtils.ts @@ -1,8 +1,7 @@ import { NODE_HEIGHT, NODE_WIDTH } from "@/constants/constants"; -import { NodeType } from "@/types/flow"; +import { AllNodeType, EdgeType } from "@/types/flow"; import ELK, { ElkNode } from "elkjs/lib/elk.bundled.js"; import { cloneDeep } from "lodash"; -import { Edge } from "reactflow"; const layoutOptions = { "elk.algorithm": "layered", @@ -11,7 +10,7 @@ const layoutOptions = { "elk.layered.spacing.edgeNodeBetweenLayers": "40", "elk.spacing.nodeNode": "40", "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", - "elk.separateConnectedComponents": true, + "elk.separateConnectedComponents": "true", "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP", "elk.spacing.componentComponent": `${NODE_WIDTH}`, "elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES", @@ -19,7 +18,10 @@ const layoutOptions = { const elk = new ELK(); // uses elkjs to give each node a layouted position -export const getLayoutedNodes = async (nodes: NodeType[], edges: Edge[]) => { +export const getLayoutedNodes = async ( + nodes: AllNodeType[], + edges: EdgeType[], +): Promise => { const graph = { id: "root", layoutOptions, @@ -72,7 +74,6 @@ export const getLayoutedNodes = async (nodes: NodeType[], edges: Edge[]) => { x: layoutedNode?.x ?? 0, y: layoutedNode?.y ?? 0, }, - type: "genericNode", }; }); return layoutedNodes; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index b32994ace..b90fafd78 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -2,7 +2,6 @@ import { getLeftHandleId, getRightHandleId, } from "@/CustomNodes/utils/get-handle-id"; -import { cloneDeep } from "lodash"; import { Connection, Edge, @@ -10,7 +9,8 @@ import { OnSelectionChangeParams, ReactFlowJsonObject, XYPosition, -} from "reactflow"; +} from "@xyflow/react"; +import { cloneDeep } from "lodash"; import ShortUniqueId from "short-unique-id"; import getFieldTitle from "../CustomNodes/utils/get-field-title"; import { @@ -31,9 +31,10 @@ import { OutputFieldType, } from "../types/api"; import { + AllNodeType, + EdgeType, FlowType, NodeDataType, - NodeType, sourceHandleType, targetHandleType, } from "../types/flow"; @@ -51,7 +52,7 @@ export function checkChatInput(nodes: Node[]) { return nodes.some((node) => node.data.type === "ChatInput"); } -export function cleanEdges(nodes: NodeType[], edges: Edge[]) { +export function cleanEdges(nodes: AllNodeType[], edges: EdgeType[]) { let newEdges = cloneDeep(edges); edges.forEach((edge) => { // check if the source and target node still exists @@ -83,31 +84,33 @@ export function cleanEdges(nodes: NodeType[], edges: Edge[]) { if (sourceHandle) { const parsedSourceHandle = scapeJSONParse(sourceHandle); const name = parsedSourceHandle.name; - const output = sourceNode.data.node!.outputs?.find( - (output) => output.name === name, - ); - if (output) { - const outputTypes = - output!.types.length === 1 ? output!.types : [output!.selected!]; + if (sourceNode.type == "genericNode") { + const output = sourceNode.data.node!.outputs?.find( + (output) => output.name === name, + ); + if (output) { + const outputTypes = + output!.types.length === 1 ? output!.types : [output!.selected!]; - const id: sourceHandleType = { - id: sourceNode.data.id, - name: name, - output_types: outputTypes, - dataType: sourceNode.data.type, - }; - if (scapedJSONStringfy(id) !== sourceHandle) { + const id: sourceHandleType = { + id: sourceNode.data.id, + name: name, + output_types: outputTypes, + dataType: sourceNode.data.type, + }; + if (scapedJSONStringfy(id) !== sourceHandle) { + newEdges = newEdges.filter((e) => e.id !== edge.id); + } + } else { newEdges = newEdges.filter((e) => e.id !== edge.id); } - } else { - newEdges = newEdges.filter((e) => e.id !== edge.id); } } }); return newEdges; } -export function detectBrokenEdgesEdges(nodes: NodeType[], edges: Edge[]) { +export function detectBrokenEdgesEdges(nodes: AllNodeType[], edges: Edge[]) { function generateAlertObject(sourceNode, targetNode, edge) { const targetHandleObject: targetHandleType = scapeJSONParse( edge.targetHandle, @@ -175,26 +178,28 @@ export function detectBrokenEdgesEdges(nodes: NodeType[], edges: Edge[]) { if (sourceHandle) { const parsedSourceHandle = scapeJSONParse(sourceHandle); const name = parsedSourceHandle.name; - const output = sourceNode.data.node!.outputs?.find( - (output) => output.name === name, - ); - if (output) { - const outputTypes = - output!.types.length === 1 ? output!.types : [output!.selected!]; + if (sourceNode.type == "genericNode") { + const output = sourceNode.data.node!.outputs?.find( + (output) => output.name === name, + ); + if (output) { + const outputTypes = + output!.types.length === 1 ? output!.types : [output!.selected!]; - const id: sourceHandleType = { - id: sourceNode.data.id, - name: name, - output_types: outputTypes, - dataType: sourceNode.data.type, - }; - if (scapedJSONStringfy(id) !== sourceHandle) { + const id: sourceHandleType = { + id: sourceNode.data.id, + name: name, + output_types: outputTypes, + dataType: sourceNode.data.type, + }; + if (scapedJSONStringfy(id) !== sourceHandle) { + newEdges = newEdges.filter((e) => e.id !== edge.id); + BrokenEdges.push(generateAlertObject(sourceNode, targetNode, edge)); + } + } else { newEdges = newEdges.filter((e) => e.id !== edge.id); BrokenEdges.push(generateAlertObject(sourceNode, targetNode, edge)); } - } else { - newEdges = newEdges.filter((e) => e.id !== edge.id); - BrokenEdges.push(generateAlertObject(sourceNode, targetNode, edge)); } } }); @@ -212,8 +217,8 @@ export function unselectAllNodesEdges(nodes: Node[], edges: Edge[]) { export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, - nodes: Node[], - edges: Edge[], + nodes: AllNodeType[], + edges: EdgeType[], ) { if (source === target) { return false; @@ -249,9 +254,10 @@ export function isValidConnection( export function removeApiKeys(flow: FlowType): FlowType { let cleanFLow = cloneDeep(flow); cleanFLow.data!.nodes.forEach((node) => { - for (const key in node.data.node.template) { - if (node.data.node.template[key].password) { - node.data.node.template[key].value = ""; + if (node.type !== "genericNode") return; + for (const key in node.data.node!.template) { + if (node.data.node!.template[key].password) { + node.data.node!.template[key].value = ""; } } }); @@ -309,14 +315,14 @@ export const processFlows = (DbData: FlowType[], skipUpdate = true) => { return { data: savedComponents, flows: DbData }; }; -export const needsLayout = (nodes: NodeType[]) => { +export const needsLayout = (nodes: AllNodeType[]) => { return nodes.some((node) => !node.position); }; export async function processDataFromFlow( flow: FlowType, refreshIds = true, -): Promise { +): Promise | null> { let data = flow?.data ? flow.data : null; if (data) { processFlowEdges(flow); @@ -336,13 +342,13 @@ export async function processDataFromFlow( } export function updateIds( - { edges, nodes }: { edges: Edge[]; nodes: Node[] }, - selection?: { edges: Edge[]; nodes: Node[] }, + { edges, nodes }: { edges: EdgeType[]; nodes: AllNodeType[] }, + selection?: OnSelectionChangeParams, ) { let idsMap = {}; const selectionIds = selection?.nodes.map((n) => n.id); if (nodes) { - nodes.forEach((node: NodeType) => { + nodes.forEach((node: AllNodeType) => { // Generate a unique node ID let newId = getNodeId(node.data.type); if (selection && !selectionIds?.includes(node.id)) { @@ -353,15 +359,17 @@ export function updateIds( node.data.id = newId; // Add the new node to the list of nodes in state }); - selection?.nodes.forEach((sNode: NodeType) => { - let newId = idsMap[sNode.id]; - sNode.id = newId; - sNode.data.id = newId; + selection?.nodes.forEach((sNode: Node) => { + if (sNode.type === "genericNode") { + let newId = idsMap[sNode.id]; + sNode.id = newId; + sNode.data.id = newId; + } }); } - const concatedEdges = [...edges, ...(selection?.edges ?? [])]; + const concatedEdges = [...edges, ...((selection?.edges as EdgeType[]) ?? [])]; if (concatedEdges) - concatedEdges.forEach((edge: Edge) => { + concatedEdges.forEach((edge: EdgeType) => { edge.source = idsMap[edge.source]; edge.target = idsMap[edge.target]; @@ -396,7 +404,7 @@ export function updateIds( return idsMap; } -export function validateNode(node: NodeType, edges: Edge[]): Array { +export function validateNode(node: AllNodeType, edges: Edge[]): Array { if (!node.data?.node?.template || !Object.keys(node.data.node.template)) { return [ "We've noticed a potential issue with a Component in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!", @@ -412,6 +420,7 @@ export function validateNode(node: NodeType, edges: Edge[]): Array { return Object.keys(template).reduce((errors: Array, t) => { if ( + node.type === "genericNode" && template[t].required && !(template[t].tool_mode && node?.data?.node?.tool_mode) && template[t].show && @@ -457,8 +466,8 @@ export function validateNode(node: NodeType, edges: Edge[]): Array { } export function validateNodes( - nodes: Node[], - edges: Edge[], + nodes: AllNodeType[], + edges: EdgeType[], ): // this returns an array of tuples with the node id and the errors Array<{ id: string; errors: Array }> { if (nodes.length === 0) { @@ -479,7 +488,7 @@ Array<{ id: string; errors: Array }> { return nodeMap.filter((n) => n.errors?.length); } -export function updateEdges(edges: Edge[]) { +export function updateEdges(edges: EdgeType[]) { if (edges) edges.forEach((edge) => { const targetHandleObject: targetHandleType = scapeJSONParse( @@ -506,20 +515,24 @@ export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) { export function addEscapedHandleIdsToEdges({ edges, -}: addEscapedHandleIdsToEdgesType): Edge[] { +}: addEscapedHandleIdsToEdgesType): EdgeType[] { let newEdges = cloneDeep(edges); newEdges.forEach((edge) => { let escapedSourceHandle = edge.sourceHandle; let escapedTargetHandle = edge.targetHandle; if (!escapedSourceHandle) { let sourceHandle = edge.data?.sourceHandle; - escapedSourceHandle = getRightHandleId(sourceHandle); - edge.sourceHandle = escapedSourceHandle; + if (sourceHandle) { + escapedSourceHandle = getRightHandleId(sourceHandle); + edge.sourceHandle = escapedSourceHandle; + } } if (!escapedTargetHandle) { let targetHandle = edge.data?.targetHandle; - escapedTargetHandle = getLeftHandleId(targetHandle); - edge.targetHandle = escapedTargetHandle; + if (targetHandle) { + escapedTargetHandle = getLeftHandleId(targetHandle); + edge.targetHandle = escapedTargetHandle; + } } }); return newEdges; @@ -527,7 +540,7 @@ export function addEscapedHandleIdsToEdges({ export function updateEdgesHandleIds({ edges, nodes, -}: updateEdgesHandleIdsType): Edge[] { +}: updateEdgesHandleIdsType): EdgeType[] { let newEdges = cloneDeep(edges); newEdges.forEach((edge) => { const sourceNodeId = edge.source; @@ -549,7 +562,7 @@ export function updateEdgesHandleIds({ inputTypes: targetNode.data.node!.template[field].input_types, }; } - if (source && sourceNode) { + if (source && sourceNode && sourceNode.type === "genericNode") { const output_types = sourceNode.data.node!.output_types ?? sourceNode.data.node!.base_classes!; @@ -580,61 +593,64 @@ export function updateNewOutput({ nodes, edges }: updateEdgesHandleIdsType) { let newTargetHandle: targetHandleType = scapeJSONParse(edge.targetHandle); const id = newSourceHandle.id; const sourceNodeIndex = newNodes.findIndex((node) => node.id === id); - let sourceNode: NodeType | undefined = undefined; + let sourceNode: AllNodeType | undefined = undefined; if (sourceNodeIndex !== -1) { sourceNode = newNodes[sourceNodeIndex]; } - - let intersection; - //@ts-ignore - if (newSourceHandle.baseClasses) { - if (!newSourceHandle.output_types) { - if (sourceNode?.data.node!.output_types) { - newSourceHandle.output_types = sourceNode?.data.node!.output_types; - } else { - //@ts-ignore - newSourceHandle.output_types = newSourceHandle.baseClasses; + if (sourceNode?.type === "genericNode") { + let intersection; + if (newSourceHandle.baseClasses) { + if (!newSourceHandle.output_types) { + if (sourceNode?.data.node!.output_types) { + newSourceHandle.output_types = + sourceNode?.data.node!.output_types; + } else { + newSourceHandle.output_types = newSourceHandle.baseClasses; + } + } + delete newSourceHandle.baseClasses; + } + if ( + newTargetHandle.inputTypes && + newTargetHandle.inputTypes.length > 0 + ) { + intersection = newSourceHandle.output_types.filter((type) => + newTargetHandle.inputTypes!.includes(type), + ); + } else { + intersection = newSourceHandle.output_types.filter( + (type) => type === newTargetHandle.type, + ); + } + const selected = intersection[0]; + newSourceHandle.name = newSourceHandle.output_types.join(" | "); + newSourceHandle.output_types = [selected]; + if (sourceNode) { + if (!sourceNode.data.node?.outputs) { + sourceNode.data.node!.outputs = []; + } + const types = + sourceNode.data.node!.output_types ?? + sourceNode.data.node!.base_classes!; + if ( + !sourceNode.data.node!.outputs.some( + (output) => output.selected === selected, + ) + ) { + sourceNode.data.node!.outputs.push({ + types, + selected: selected, + name: types.join(" | "), + display_name: types.join(" | "), + }); } } - //@ts-ignore - delete newSourceHandle.baseClasses; - } - if (newTargetHandle.inputTypes && newTargetHandle.inputTypes.length > 0) { - //conjuction subtraction - intersection = newSourceHandle.output_types.filter((type) => - newTargetHandle.inputTypes!.includes(type), - ); - } else { - intersection = newSourceHandle.output_types.filter( - (type) => type === newTargetHandle.type, - ); - } - const selected = intersection[0]; - newSourceHandle.name = newSourceHandle.output_types.join(" | "); - newSourceHandle.output_types = [selected]; - if (sourceNode) { - if (!sourceNode.data.node?.outputs) { - sourceNode.data.node!.outputs = []; - } - const types = - sourceNode.data.node!.output_types ?? - sourceNode.data.node!.base_classes!; - if ( - !sourceNode.data.node!.outputs.some( - (output) => output.selected === selected, - ) - ) { - sourceNode.data.node!.outputs.push({ - types, - selected: selected, - name: types.join(" | "), - display_name: types.join(" | "), - }); - } - } - edge.sourceHandle = scapedJSONStringfy(newSourceHandle); - edge.data.sourceHandle = newSourceHandle; + edge.sourceHandle = scapedJSONStringfy(newSourceHandle); + if (edge.data) { + edge.data.sourceHandle = newSourceHandle; + } + } } }); return { nodes: newNodes, edges: newEdges }; @@ -684,8 +700,8 @@ export function handleOnlyIntegerInput( export function getConnectedNodes( edge: Edge, - nodes: Array, -): Array { + nodes: Array, +): Array { const sourceId = edge.source; const targetId = edge.target; return nodes.filter((node) => node.id === targetId || node.id === sourceId); @@ -796,10 +812,10 @@ export function checkEdgeWithoutEscapedHandleIds(edges: Edge[]): boolean { ); } -export function checkOldNodesOutput(nodes: NodeType[]): boolean { +export function checkOldNodesOutput(nodes: AllNodeType[]): boolean { return nodes.some( (node) => - node.data.node?.outputs === undefined && node.type === "genericNode", + node.type === "genericNode" && node.data.node?.outputs === undefined, ); } @@ -860,8 +876,8 @@ export function getHandleId( export function generateFlow( selection: OnSelectionChangeParams, - nodes: Node[], - edges: Edge[], + nodes: AllNodeType[], + edges: EdgeType[], name: string, ): generateFlowType { const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } }; @@ -872,7 +888,7 @@ export function generateFlow( selection.nodes.some((node) => node.id === edge.target) && selection.nodes.some((node) => node.id === edge.source), ); - newFlowData.nodes = selection.nodes; + newFlowData.nodes = selection.nodes as AllNodeType[]; const newFlow: FlowType = { data: newFlowData, @@ -896,8 +912,11 @@ export function generateFlow( }; } -export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) { - if (!groupNode.data.node!.flow) return []; +export function reconnectEdges( + groupNode: AllNodeType, + excludedEdges: EdgeType[], +) { + if (groupNode.type !== "genericNode" || !groupNode.data.node!.flow) return []; let newEdges = cloneDeep(excludedEdges); const { nodes, edges } = groupNode.data.node!.flow!.data!; const lastNode = findLastNode(groupNode.data.node!.flow!.data!); @@ -905,26 +924,32 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) { (e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id), ); newEdges.forEach((edge) => { + const newSourceHandle: sourceHandleType = scapeJSONParse( + edge.sourceHandle!, + ); + const newTargetHandle: targetHandleType = scapeJSONParse( + edge.targetHandle!, + ); if (lastNode && edge.source === lastNode.id) { edge.source = groupNode.id; - let newSourceHandle: sourceHandleType = scapeJSONParse( - edge.sourceHandle!, - ); newSourceHandle.id = groupNode.id; edge.sourceHandle = scapedJSONStringfy(newSourceHandle); - edge.data.sourceHandle = newSourceHandle; } if (nodes.some((node) => node.id === edge.target)) { const targetNode = nodes.find((node) => node.id === edge.target)!; - const targetHandle: targetHandleType = scapeJSONParse(edge.targetHandle!); - const proxy = { id: targetNode.id, field: targetHandle.fieldName }; - let newTargetHandle: targetHandleType = cloneDeep(targetHandle); + const proxy = { id: targetNode.id, field: newTargetHandle.fieldName }; newTargetHandle.id = groupNode.id; newTargetHandle.proxy = proxy; edge.target = groupNode.id; - newTargetHandle.fieldName = targetHandle.fieldName + "_" + targetNode.id; + newTargetHandle.fieldName = + newTargetHandle.fieldName + "_" + targetNode.id; edge.targetHandle = scapedJSONStringfy(newTargetHandle); - edge.data.targetHandle = newTargetHandle; + } + if (newSourceHandle && newTargetHandle) { + edge.data = { + sourceHandle: newSourceHandle, + targetHandle: newTargetHandle, + }; } }); return newEdges; @@ -1058,7 +1083,7 @@ export function mergeNodeTemplates({ nodes, edges, }: { - nodes: NodeType[]; + nodes: AllNodeType[]; edges: Edge[]; }): APITemplateType { /* this function receives a flow and iterate throw each node @@ -1072,10 +1097,13 @@ export function mergeNodeTemplates({ Object.keys(nodeTemplate) .filter((field_name) => field_name.charAt(0) !== "_") .forEach((key) => { - if (!isTargetHandleConnected(edges, key, nodeTemplate[key], node.id)) { + if ( + node.type === "genericNode" && + !isTargetHandleConnected(edges, key, nodeTemplate[key], node.id) + ) { template[key + "_" + node.id] = nodeTemplate[key]; template[key + "_" + node.id].proxy = { id: node.id, field: key }; - if (node.type === "groupNode") { + if (node.data.type === "GroupNode") { template[key + "_" + node.id].display_name = node.data.node!.flow!.name + " - " + nodeTemplate[key].name; } else { @@ -1152,13 +1180,13 @@ export function generateNodeTemplate(Flow: FlowType) { export function generateNodeFromFlow( flow: FlowType, getNodeId: (type: string) => string, -): NodeType { +): AllNodeType { const { nodes } = flow.data!; const outputNode = cloneDeep(findLastNode(flow.data!)); const position = getMiddlePoint(nodes); let data = cloneDeep(flow); const id = getNodeId("groupComponent"); - const newGroupNode: NodeType = { + const newGroupNode: AllNodeType = { data: { id, type: "GroupNode", @@ -1181,8 +1209,8 @@ export function generateNodeFromFlow( function generateNodeOutputs(flow: FlowType) { const { nodes, edges } = flow.data!; const outputs: Array = []; - nodes.forEach((node: NodeType) => { - if (node.data.node?.outputs) { + nodes.forEach((node: AllNodeType) => { + if (node.type === "genericNode" && node.data.node?.outputs) { const nodeOutputs = node.data.node.outputs; nodeOutputs.forEach((output) => { //filter outputs that are not connected @@ -1190,7 +1218,8 @@ function generateNodeOutputs(flow: FlowType) { !edges.some( (edge) => edge.source === node.id && - (edge.data.sourceHandle as sourceHandleType).name === output.name, + (edge.data?.sourceHandle as sourceHandleType).name === + output.name, ) ) { outputs.push( @@ -1213,44 +1242,6 @@ function generateNodeOutputs(flow: FlowType) { return outputs; } -export function connectedInputNodesOnHandle( - nodeId: string, - handleId: string, - { nodes, edges }: { nodes: NodeType[]; edges: Edge[] }, -) { - const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> = - []; - // return the nodes connected to the input handle of the node - const TargetEdges = edges.filter((e) => e.target === nodeId); - TargetEdges.forEach((edge) => { - if (edge.targetHandle === handleId) { - const sourceNode = nodes.find((n) => n.id === edge.source); - if (sourceNode) { - if (sourceNode.type === "groupNode") { - let lastNode = findLastNode(sourceNode.data.node!.flow!.data!); - while (lastNode && lastNode.type === "groupNode") { - lastNode = findLastNode(lastNode.data.node!.flow!.data!); - } - if (lastNode) { - connectedNodes.push({ - name: sourceNode.data.node!.flow!.name, - id: lastNode.id, - isGroup: true, - }); - } - } else { - connectedNodes.push({ - name: sourceNode.data.type, - id: sourceNode.id, - isGroup: false, - }); - } - } - } - }); - return connectedNodes; -} - export function updateProxyIdsOnTemplate( template: APITemplateType, idsMap: { [key: string]: string }, @@ -1275,15 +1266,15 @@ export function updateProxyIdsOnOutputs( } export function updateEdgesIds( - edges: Edge[], + edges: EdgeType[], idsMap: { [key: string]: string }, ) { edges.forEach((edge) => { - let targetHandle: targetHandleType = edge.data.targetHandle; + let targetHandle: targetHandleType = edge.data!.targetHandle; if (targetHandle.proxy && idsMap[targetHandle.proxy!.id]) { targetHandle.proxy!.id = idsMap[targetHandle.proxy!.id]; } - edge.data.targetHandle = targetHandle; + edge.data!.targetHandle = targetHandle; edge.targetHandle = scapedJSONStringfy(targetHandle); }); } @@ -1312,17 +1303,21 @@ export function expandGroupNode( id: string, flow: FlowType, template: APITemplateType, - nodes: Node[], - edges: Edge[], - setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, - setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, + nodes: AllNodeType[], + edges: EdgeType[], + setNodes: ( + update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]), + ) => void, + setEdges: ( + update: EdgeType[] | ((oldState: EdgeType[]) => EdgeType[]), + ) => void, outputs?: OutputFieldType[], ) { const idsMap = updateIds(flow!.data!); updateProxyIdsOnTemplate(template, idsMap); let flowEdges = edges; updateEdgesIds(flowEdges, idsMap); - const gNodes: NodeType[] = cloneDeep(flow?.data?.nodes!); + const gNodes: AllNodeType[] = cloneDeep(flow?.data?.nodes!); const gEdges = cloneDeep(flow!.data!.edges); // //redirect edges to correct proxy node // let updatedEdges: Edge[] = []; @@ -1406,13 +1401,16 @@ export function expandGroupNode( outputs?.forEach((output) => { let nodeIndex = gNodes.findIndex((n) => n.id === output.proxy!.id); if (nodeIndex !== -1) { - if (gNodes[nodeIndex].data.node?.outputs) { - const nodeOutputIndex = gNodes[nodeIndex].data.node!.outputs!.findIndex( - (o) => o.name === output.proxy?.name, - ); - if (nodeOutputIndex !== -1 && output.selected) { - gNodes[nodeIndex].data.node!.outputs![nodeOutputIndex].selected = - output.selected; + const node = gNodes[nodeIndex]; + if (node.type === "genericNode") { + if (node.data.node?.outputs) { + const nodeOutputIndex = node.data.node!.outputs!.findIndex( + (o) => o.name === output.proxy?.name, + ); + if (nodeOutputIndex !== -1 && output.selected) { + node.data.node!.outputs![nodeOutputIndex].selected = + output.selected; + } } } } @@ -1432,7 +1430,7 @@ export function getGroupStatus( ) { let status = { valid: true, params: SUCCESS_BUILD }; const { nodes } = flow.data!; - const ids = nodes.map((n: NodeType) => n.data.id); + const ids = nodes.map((n: AllNodeType) => n.data.id); ids.forEach((id) => { if (!ssData[id]) { status = ssData[id]; @@ -1487,28 +1485,32 @@ export function updateComponentNameAndType( ) {} export function removeFileNameFromComponents(flow: FlowType) { - flow.data!.nodes.forEach((node: NodeType) => { - Object.keys(node.data.node!.template).forEach((field) => { - if (node.data.node?.template[field].type === "file") { - node.data.node!.template[field].value = ""; + flow.data!.nodes.forEach((node: AllNodeType) => { + if (node.type === "genericNode") { + Object.keys(node.data.node!.template).forEach((field) => { + if (node.data.node?.template[field].type === "file") { + node.data.node!.template[field].value = ""; + } + }); + if (node.data.node?.flow) { + removeFileNameFromComponents(node.data.node.flow); } - }); - if (node.data.node?.flow) { - removeFileNameFromComponents(node.data.node.flow); } }); } export function removeGlobalVariableFromComponents(flow: FlowType) { - flow.data!.nodes.forEach((node: NodeType) => { - Object.keys(node.data.node!.template).forEach((field) => { - if (node.data?.node?.template[field]?.load_from_db) { - node.data.node!.template[field].value = ""; - node.data.node!.template[field].load_from_db = false; + flow.data!.nodes.forEach((node: AllNodeType) => { + if (node.type === "genericNode") { + Object.keys(node.data.node!.template).forEach((field) => { + if (node.data?.node?.template[field]?.load_from_db) { + node.data.node!.template[field].value = ""; + node.data.node!.template[field].load_from_db = false; + } + }); + if (node.data.node?.flow) { + removeGlobalVariableFromComponents(node.data.node.flow); } - }); - if (node.data.node?.flow) { - removeGlobalVariableFromComponents(node.data.node.flow); } }); } @@ -1592,7 +1594,7 @@ export function getRandomDescription(): string { } export const createNewFlow = ( - flowData: ReactFlowJsonObject, + flowData: ReactFlowJsonObject, folderId: string, flow?: FlowType, ) => { @@ -1626,8 +1628,8 @@ export function isOutputType(type: string): boolean { } export function updateGroupRecursion( - groupNode: NodeType, - edges: Edge[], + groupNode: AllNodeType, + edges: EdgeType[], unavailableFields: | { [name: string]: string; @@ -1635,28 +1637,32 @@ export function updateGroupRecursion( | undefined, globalVariablesEntries: string[] | undefined, ) { - updateGlobalVariables( - groupNode.data.node, - unavailableFields, - globalVariablesEntries, - ); - if (groupNode.data.node?.flow) { - groupNode.data.node.flow.data!.nodes.forEach((node) => { - if (node.data.node?.flow) { - updateGroupRecursion( - node, - node.data.node.flow.data!.edges, - unavailableFields, - globalVariablesEntries, - ); - } - }); - let newFlow = groupNode.data.node!.flow; - const idsMap = updateIds(newFlow.data!); - updateProxyIdsOnTemplate(groupNode.data.node!.template, idsMap); - updateProxyIdsOnOutputs(groupNode.data.node.outputs, idsMap); - let flowEdges = edges; - updateEdgesIds(flowEdges, idsMap); + if (groupNode.type === "genericNode") { + updateGlobalVariables( + groupNode.data.node, + unavailableFields, + globalVariablesEntries, + ); + if (groupNode.data.node?.flow) { + groupNode.data.node.flow.data!.nodes.forEach((node) => { + if (node.type === "genericNode") { + if (node.data.node?.flow) { + updateGroupRecursion( + node, + node.data.node.flow.data!.edges, + unavailableFields, + globalVariablesEntries, + ); + } + } + }); + let newFlow = groupNode.data.node!.flow; + const idsMap = updateIds(newFlow.data!); + updateProxyIdsOnTemplate(groupNode.data.node!.template, idsMap); + updateProxyIdsOnOutputs(groupNode.data.node.outputs, idsMap); + let flowEdges = edges; + updateEdgesIds(flowEdges, idsMap); + } } } export function updateGlobalVariables( @@ -1699,10 +1705,10 @@ export function getGroupOutputNodeId( p_name: string, p_node_id: string, ) { - let node: NodeType | undefined = flow.data?.nodes.find( + let node: AllNodeType | undefined = flow.data?.nodes.find( (n) => n.id === p_node_id, ); - if (!node) return; + if (!node || node.type !== "genericNode") return; if (node.data.node?.flow) { let output = node.data.node.outputs?.find((o) => o.name === p_name); if (output && output.proxy) { @@ -1727,7 +1733,7 @@ export function checkOldComponents({ nodes }: { nodes: any[] }) { } export function someFlowTemplateFields( - { nodes }: { nodes: NodeType[] }, + { nodes }: { nodes: AllNodeType[] }, validateFn: (field: InputFieldType) => boolean, ): boolean { return nodes.some((node) => { diff --git a/src/frontend/src/utils/storeUtils.ts b/src/frontend/src/utils/storeUtils.ts index c8391b211..7e6ce0d35 100644 --- a/src/frontend/src/utils/storeUtils.ts +++ b/src/frontend/src/utils/storeUtils.ts @@ -1,5 +1,5 @@ +import { Node } from "@xyflow/react"; import { cloneDeep, uniqueId } from "lodash"; -import { Node } from "reactflow"; import { FlowType, NodeDataType } from "../types/flow"; import { isInputNode, isOutputNode } from "./reactflowUtils"; diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 79963c095..8feb62294 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -20,7 +20,7 @@ import { nodeGroupedObjType, tweakType, } from "../types/components"; -import { NodeDataType, NodeType } from "../types/flow"; +import { AllNodeType, NodeDataType } from "../types/flow"; import { FlowState } from "../types/tabs"; import { isErrorLog } from "../types/utils/typeCheckingUtils"; import { parseString } from "./stringManipulation"; @@ -243,7 +243,7 @@ export function groupByFamily( data: APIDataType, baseClasses: string, left: boolean, - flow?: NodeType[], + flow?: AllNodeType[], ): groupedObjType[] { const baseClassesSet = new Set(baseClasses.split("\n")); let arrOfPossibleInputs: Array<{ @@ -278,7 +278,12 @@ export function groupByFamily( // se existir o flow for (const node of flow) { // para cada node do flow - if (node!.data!.node!.flow || !node!.data!.node!.template) break; // nรฃo faz nada se o node for um group + if ( + node!.type !== "genericNode" || + !node!.data!.node!.flow || + !node!.data!.node!.template + ) + break; // nรฃo faz nada se o node for um group const nodeData = node.data; const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node jรก foi checado diff --git a/src/frontend/tests/core/features/freeze.spec.ts b/src/frontend/tests/core/features/freeze.spec.ts index b6de8e862..58d277c27 100644 --- a/src/frontend/tests/core/features/freeze.spec.ts +++ b/src/frontend/tests/core/features/freeze.spec.ts @@ -1,5 +1,6 @@ import { expect, test } from "@playwright/test"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { zoomOut } from "../../utils/zoom-out"; test( "user must be able to freeze a component", @@ -18,23 +19,14 @@ test( timeout: 1000, }); + await zoomOut(page, 3); + await page .getByTestId("inputsText Input") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 100, y: 100 }, }); - await page.mouse.up(); - - //second component - await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("url"); await page.waitForSelector('[data-testid="dataURL"]', { @@ -43,19 +35,10 @@ test( await page .getByTestId("dataURL") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 300, y: 300 }, }); - await page.mouse.up(); - //third component await page.getByTestId("sidebar-search-input").click(); @@ -66,19 +49,10 @@ test( await page .getByTestId("processingSplit Text") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 350, y: 100 }, }); - await page.mouse.up(); - //fourth component await page.getByTestId("sidebar-search-input").click(); @@ -89,18 +63,11 @@ test( await page .getByTestId("processingParse Data") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 50, y: 300 }, }); - await page.mouse.up(); + await page.getByTestId("zoom_out").click(); //fifth component @@ -112,18 +79,11 @@ test( await page .getByTestId("outputsChat Output") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 600, y: 200 }, }); - await page.mouse.up(); + await page.getByTestId("fit_view").click(); let outdatedComponents = await page .getByTestId("icon-AlertTriangle") @@ -144,6 +104,7 @@ test( } await page.getByTestId("fit_view").click(); + await zoomOut(page, 2); //connection 1 const urlOutput = await page @@ -169,8 +130,6 @@ test( await splitTextInput.hover(); await page.mouse.up(); - await page.getByTestId("fit_view").click(); - //connection 3 const splitTextOutput = await page .getByTestId("handle-splittext-shownode-chunks-right") @@ -195,8 +154,6 @@ test( await chatOutputInput.hover(); await page.mouse.up(); - await page.getByTestId("fit_view").click(); - await page .getByTestId("textarea_str_input_value") .first() diff --git a/src/frontend/tests/core/features/stop-building.spec.ts b/src/frontend/tests/core/features/stop-building.spec.ts index a5f125472..a89ec6597 100644 --- a/src/frontend/tests/core/features/stop-building.spec.ts +++ b/src/frontend/tests/core/features/stop-building.spec.ts @@ -1,6 +1,8 @@ import { test } from "@playwright/test"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - +import { removeOldApiKeys } from "../../utils/remove-old-api-keys"; +import { updateOldComponents } from "../../utils/update-old-components"; +import { zoomOut } from "../../utils/zoom-out"; // TODO: fix this test test( "user must be able to stop a building", @@ -16,18 +18,11 @@ test( await page .getByTestId("inputsText Input") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 0, y: 0 }, }); - await page.mouse.up(); + await zoomOut(page, 3); //second component @@ -36,19 +31,10 @@ test( await page .getByTestId("dataURL") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 100, y: 200 }, }); - await page.mouse.up(); - //third component await page.getByTestId("sidebar-search-input").click(); @@ -56,19 +42,10 @@ test( await page .getByTestId("processingSplit Text") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 300, y: 300 }, }); - await page.mouse.up(); - //fourth component await page.getByTestId("sidebar-search-input").click(); @@ -76,19 +53,10 @@ test( await page .getByTestId("processingParse Data") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 100, y: 400 }, }); - await page.mouse.up(); - //fifth component await page.getByTestId("sidebar-search-input").click(); @@ -96,37 +64,17 @@ test( await page .getByTestId("outputsChat Output") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 600, y: 300 }, }); - await page.mouse.up(); - - let outdatedComponents = await page - .getByTestId("icon-AlertTriangle") - .count(); - - while (outdatedComponents > 0) { - await page.getByTestId("icon-AlertTriangle").first().click(); - outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); - } - - let filledApiKey = await page.getByTestId("remove-icon-badge").count(); - while (filledApiKey > 0) { - await page.getByTestId("remove-icon-badge").first().click(); - await page.waitForTimeout(1000); - filledApiKey = await page.getByTestId("remove-icon-badge").count(); - } + await updateOldComponents(page); + await removeOldApiKeys(page); await page.getByTestId("fit_view").click(); + await zoomOut(page, 2); + //connection 1 const urlOutput = await page .getByTestId("handle-url-shownode-data-right") @@ -151,8 +99,6 @@ test( await splitTextInput.hover(); await page.mouse.up(); - await page.getByTestId("fit_view").click(); - //connection 3 const splitTextOutput = await page .getByTestId("handle-splittext-shownode-chunks-right") diff --git a/src/frontend/tests/core/features/store-shard-2.spec.ts b/src/frontend/tests/core/features/store-shard-2.spec.ts index e3dc90719..e076b7c5b 100644 --- a/src/frontend/tests/core/features/store-shard-2.spec.ts +++ b/src/frontend/tests/core/features/store-shard-2.spec.ts @@ -22,7 +22,9 @@ test( await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); - await page.getByTestId("api-key-button-store").click(); + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); await page .getByPlaceholder("Insert your API Key") @@ -83,7 +85,9 @@ test("should share component with share button", async ({ page }) => { await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); - await page.getByTestId("api-key-button-store").click(); + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); await page .getByPlaceholder("Insert your API Key") diff --git a/src/frontend/tests/core/integrations/decisionFlow.spec.ts b/src/frontend/tests/core/integrations/decisionFlow.spec.ts index 52993eae3..9c850b02a 100644 --- a/src/frontend/tests/core/integrations/decisionFlow.spec.ts +++ b/src/frontend/tests/core/integrations/decisionFlow.spec.ts @@ -2,12 +2,7 @@ import { Page, test } from "@playwright/test"; import * as dotenv from "dotenv"; import path from "path"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -async function zoomOut(page: Page, times: number = 4) { - for (let i = 0; i < times; i++) { - await page.getByTestId("zoom_out").click(); - } -} +import { zoomOut } from "../../utils/zoom-out"; test( "should create a flow with decision", @@ -57,6 +52,11 @@ test( targetPosition: { x: 100, y: 100 }, }); + await page.waitForSelector('[data-testid="input-list-plus-btn_texts-0"]', { + timeout: 3000, + state: "attached", + }); + await page.getByTestId("input-list-plus-btn_texts-0").first().click(); await page.getByTestId("input-list-plus-btn_texts-0").first().click(); await page.getByTestId("input-list-plus-btn_texts-0").first().click(); diff --git a/src/frontend/tests/core/integrations/similarity.spec.ts b/src/frontend/tests/core/integrations/similarity.spec.ts index d0693472e..3b4fedc1c 100644 --- a/src/frontend/tests/core/integrations/similarity.spec.ts +++ b/src/frontend/tests/core/integrations/similarity.spec.ts @@ -1,5 +1,7 @@ import { expect, test } from "@playwright/test"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { updateOldComponents } from "../../utils/update-old-components"; +import { zoomOut } from "../../utils/zoom-out"; test( "user must be able to check similarity between embedding texts", @@ -23,18 +25,11 @@ test( await page .getByText("OpenAI Embeddings", { exact: true }) - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-50, 50); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 0, y: 0 }, }); - await page.mouse.up(); + await zoomOut(page, 5); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("text embedder"); @@ -44,36 +39,18 @@ test( await page .getByTestId("embeddingsText Embedder") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-50, 50); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 100, y: 400 }, }); - await page.mouse.up(); - //fourth component await page .getByTestId("embeddingsText Embedder") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-50, 50); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 300, y: 400 }, }); - await page.mouse.up(); - //fifth component await page.getByTestId("sidebar-search-input").click(); @@ -84,19 +61,10 @@ test( await page .getByTestId("embeddingsEmbedding Similarity") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-50, 50); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 350, y: 100 }, }); - await page.mouse.up(); - //sisxth component await page.getByTestId("sidebar-search-input").click(); @@ -107,19 +75,10 @@ test( await page .getByTestId("processingParse Data") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-50, 50); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 50, y: 100 }, }); - await page.mouse.up(); - //seventh component await page.getByTestId("sidebar-search-input").click(); @@ -130,19 +89,10 @@ test( await page .getByTestId("outputsText Output") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-50, 50); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 500, y: 100 }, }); - await page.mouse.up(); - await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("filter data"); await page.waitForSelector("text=Filter Data", { @@ -151,33 +101,11 @@ test( await page .getByTestId("processingFilter Data") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.getByTestId("zoom_out").click(); - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-50, 50); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 600, y: 200 }, }); - await page.mouse.up(); - - let outdatedComponents = await page - .getByTestId("icon-AlertTriangle") - .count(); - - while (outdatedComponents > 0) { - await page.getByTestId("icon-AlertTriangle").first().click(); - outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); - } - - let filledApiKey = await page.getByTestId("remove-icon-badge").count(); - while (filledApiKey > 0) { - await page.getByTestId("remove-icon-badge").first().click(); - filledApiKey = await page.getByTestId("remove-icon-badge").count(); - } + await updateOldComponents(page); await page.getByTestId("fit_view").click(); diff --git a/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts b/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts index 544bad24d..3a2f12c16 100644 --- a/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts @@ -1,5 +1,6 @@ import { expect, test } from "@playwright/test"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { zoomOut } from "../../utils/zoom-out"; test( "should be able to see output preview from grouped components and connect components with a single click", @@ -23,10 +24,7 @@ test( .getByTestId("inputsText Input") .dragTo(page.locator('//*[@id="react-flow-id"]'), {}); - await page.getByTestId("zoom_out").click(); - await page.getByTestId("zoom_out").click(); - await page.getByTestId("zoom_out").click(); - await page.getByTestId("zoom_out").click(); + await zoomOut(page, 4); await page .getByTestId("inputsText Input") @@ -156,18 +154,21 @@ test( .nth(1); await elementCombineTextInput1.click(); + await page.getByTitle("fit view").click(); + + await zoomOut(page, 2); + await page .getByTestId("title-Combine Text") .first() .click({ modifiers: ["Control"] }); - await page - .getByTestId("title-delimiter") - .last() - .click({ modifiers: ["Control"] }); - await page.getByRole("button", { name: "Group" }).click(); + await page.waitForSelector('[data-testid="group-node"]', { + timeout: 3000, + state: "visible", + }); - await page.getByTitle("fit view").click(); + await page.getByTestId("group-node").click(); //connection 2 const elementTextOutput0 = page diff --git a/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts b/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts index aa363dd14..a41dfa113 100644 --- a/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test"; import * as dotenv from "dotenv"; import path from "path"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - +import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "memory should work as expect", { tag: ["@release"] }, @@ -67,37 +67,13 @@ test( await page .getByTestId("helpersMessage History") .first() - .dragTo(page.locator('//*[@id="react-flow-id"]')); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 300, y: 500 }, + }); - await page.mouse.up(); - await page.mouse.down(); - - await page.getByTestId("fit_view").click(); - - let outdatedComponents = await page - .getByTestId("icon-AlertTriangle") - .count(); - - while (outdatedComponents > 0) { - await page.getByTestId("icon-AlertTriangle").first().click(); - outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); - } - - let filledApiKey = await page.getByTestId("remove-icon-badge").count(); - while (filledApiKey > 0) { - await page.getByTestId("remove-icon-badge").first().click(); - filledApiKey = await page.getByTestId("remove-icon-badge").count(); - } - - const apiKeyInput = page.getByTestId("popover-anchor-input-api_key"); - const isApiKeyInputVisible = await apiKeyInput.isVisible(); - - if (isApiKeyInputVisible) { - await apiKeyInput.fill(process.env.OPENAI_API_KEY ?? ""); - } - - await page.getByTestId("dropdown_str_model_name").click(); - await page.getByTestId("gpt-4o-1-option").click(); + await initialGPTsetup(page, { + skipAdjustScreenView: true, + }); const prompt = ` {context} diff --git a/src/frontend/tests/core/unit/fileUploadComponent.spec.ts b/src/frontend/tests/core/unit/fileUploadComponent.spec.ts index 5991dc327..80f526381 100644 --- a/src/frontend/tests/core/unit/fileUploadComponent.spec.ts +++ b/src/frontend/tests/core/unit/fileUploadComponent.spec.ts @@ -44,9 +44,10 @@ test( await page .getByTestId("outputsChat Output") .first() - .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.mouse.up(); - await page.mouse.down(); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 0, y: 0 }, + }); + await adjustScreenView(page); await page.getByTestId("sidebar-search-input").click(); @@ -54,11 +55,9 @@ test( await page .getByTestId("processingParse Data") .first() - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page.mouse.up(); - await page.mouse.down(); - await adjustScreenView(page); + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 300, y: 400 }, + }); let visibleElementHandle; diff --git a/src/frontend/tests/core/unit/intComponent.spec.ts b/src/frontend/tests/core/unit/intComponent.spec.ts index f18f8f8e7..b39bddc8f 100644 --- a/src/frontend/tests/core/unit/intComponent.spec.ts +++ b/src/frontend/tests/core/unit/intComponent.spec.ts @@ -1,5 +1,6 @@ import { expect, test } from "@playwright/test"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { zoomOut } from "../../utils/zoom-out"; test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { await awaitBootstrapTest(page); @@ -19,16 +20,11 @@ test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { .getByTestId("modelsOpenAI") .first() .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.mouse.up(); - await page.mouse.down(); - await page.waitForSelector('[data-testid="fit_view"]', { - timeout: 100000, - }); await page.getByTestId("fit_view").click(); - await page.getByTestId("zoom_out").click(); - await page.getByTestId("zoom_out").click(); - await page.getByTestId("zoom_out").click(); + await zoomOut(page, 2); + + await page.getByTestId("div-generic-node").click(); await page.getByTestId("more-options-modal").click(); await page.getByTestId("advanced-button-modal").click(); diff --git a/src/frontend/tests/core/unit/keyPairListComponent.spec.ts b/src/frontend/tests/core/unit/keyPairListComponent.spec.ts index 38fb6e03c..96383f0f4 100644 --- a/src/frontend/tests/core/unit/keyPairListComponent.spec.ts +++ b/src/frontend/tests/core/unit/keyPairListComponent.spec.ts @@ -22,10 +22,11 @@ test( await page .getByTestId("modelsAmazon Bedrock") .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.mouse.up(); - await page.mouse.down(); + await adjustScreenView(page); + await page.getByTestId("div-generic-node").click(); + await page.getByTestId("more-options-modal").click(); await page.getByTestId("advanced-button-modal").click(); diff --git a/src/frontend/tests/core/unit/nestedComponent.spec.ts b/src/frontend/tests/core/unit/nestedComponent.spec.ts index 753440898..0e7485e3c 100644 --- a/src/frontend/tests/core/unit/nestedComponent.spec.ts +++ b/src/frontend/tests/core/unit/nestedComponent.spec.ts @@ -23,7 +23,6 @@ test( .getByTestId("dataAPI Request") .first() .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.click('//*[@id="react-flow-id"]'); await adjustScreenView(page); diff --git a/src/frontend/tests/extended/features/deleteComponents.spec.ts b/src/frontend/tests/extended/features/deleteComponents.spec.ts index 6bcaccde8..304c8f17c 100644 --- a/src/frontend/tests/extended/features/deleteComponents.spec.ts +++ b/src/frontend/tests/extended/features/deleteComponents.spec.ts @@ -17,7 +17,9 @@ test( await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); - await page.getByTestId("api-key-button-store").click(); + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); diff --git a/src/frontend/tests/extended/features/deleteFlows.spec.ts b/src/frontend/tests/extended/features/deleteFlows.spec.ts index e85ae7556..d06df2929 100644 --- a/src/frontend/tests/extended/features/deleteFlows.spec.ts +++ b/src/frontend/tests/extended/features/deleteFlows.spec.ts @@ -20,7 +20,9 @@ test( await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); - await page.getByTestId("api-key-button-store").click(); + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); await page .getByPlaceholder("Insert your API Key") diff --git a/src/frontend/tests/extended/features/sticky-notes.spec.ts b/src/frontend/tests/extended/features/sticky-notes.spec.ts index b91d744e2..db1360b7a 100644 --- a/src/frontend/tests/extended/features/sticky-notes.spec.ts +++ b/src/frontend/tests/extended/features/sticky-notes.spec.ts @@ -60,10 +60,13 @@ The future of AI is both exciting and uncertain. As the technology continues to await page.locator(".generic-node-desc-text").last().dblclick(); await page.getByTestId("textarea").fill(noteText); - expect(await page.getByText("2500/2500")).toBeVisible(); + expect(page.getByText("2500/2500")).toHaveCount(1); await targetElement.click(); - const textMarkdown = await page.locator(".markdown").innerText(); + await page.keyboard.press("Escape"); + const textMarkdown = await page + .getByTestId("generic-node-desc") + .innerText(); const textLength = textMarkdown.length; const noteTextLength = noteText.length; diff --git a/src/frontend/tests/extended/features/store-shard-1.spec.ts b/src/frontend/tests/extended/features/store-shard-1.spec.ts index 00b446096..2e709fbf7 100644 --- a/src/frontend/tests/extended/features/store-shard-1.spec.ts +++ b/src/frontend/tests/extended/features/store-shard-1.spec.ts @@ -20,7 +20,9 @@ test.skip( await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); - await page.getByTestId("api-key-button-store").click(); + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); @@ -100,7 +102,9 @@ test.skip( await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); - await page.getByTestId("api-key-button-store").click(); + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); await page .getByPlaceholder("Insert your API Key") diff --git a/src/frontend/tests/extended/features/store-shard-3.spec.ts b/src/frontend/tests/extended/features/store-shard-3.spec.ts index cde143d55..502f5f4ac 100644 --- a/src/frontend/tests/extended/features/store-shard-3.spec.ts +++ b/src/frontend/tests/extended/features/store-shard-3.spec.ts @@ -19,7 +19,9 @@ test( await page.getByTestId("button-store").click(); - await page.getByTestId("api-key-button-store").click(); + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); await page .getByPlaceholder("Insert your API Key") @@ -58,10 +60,10 @@ test("should filter by type", { tag: ["@release"] }, async ({ page }) => { } await page.goto("/"); await page.getByTestId("button-store").click(); - await page.waitForSelector('[data-testid="api-key-button-store"]', { - timeout: 100000, + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, }); - await page.getByTestId("api-key-button-store").click(); + await page .getByPlaceholder("Insert your API Key") .fill(process.env.STORE_API_KEY ?? ""); diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts index b037bd25d..a3c8d1416 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts @@ -71,13 +71,14 @@ test( await page.getByText("Close").last().click(); await page.getByText("Prompt", { exact: true }).click(); + await page.getByTestId("more-options-modal").click(); await page.getByText("Freeze", { exact: true }).last().click(); - await page.locator('//*[@id="react-flow-id"]').click(); + await page.waitForSelector(".border-ring-frozen", { timeout: 3000 }); - expect(page.getByTestId("icon-Snowflake").first()).toBeVisible(); + expect(page.locator(".border-ring-frozen")).toHaveCount(1); await page.locator('//*[@id="react-flow-id"]').click(); diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts index a66436809..603b8c3de 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - +import { zoomOut } from "../../utils/zoom-out"; // TODO: This test might not be needed anymore test( "should be able to select all with ctrl + A on advanced modal", @@ -35,8 +35,7 @@ test( .dragTo(page.locator('//*[@id="react-flow-id"]')); await page.getByTestId("fit_view").click(); - await page.getByTestId("zoom_out").click(); - await page.getByTestId("zoom_out").click(); + await zoomOut(page, 3); await page.waitForSelector('[data-testid="div-generic-node"]', { timeout: 5000, diff --git a/src/frontend/tests/utils/zoom-out.ts b/src/frontend/tests/utils/zoom-out.ts new file mode 100644 index 000000000..85120905d --- /dev/null +++ b/src/frontend/tests/utils/zoom-out.ts @@ -0,0 +1,7 @@ +import { Page } from "@playwright/test"; + +export async function zoomOut(page: Page, times: number = 4) { + for (let i = 0; i < times; i++) { + await page.getByTestId("zoom_out").click(); + } +}