refactor: update ReactFlow to v12 (#5317)

* 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 <cristhian.lousa@gmail.com>
This commit is contained in:
Lucas Oliveira 2024-12-18 16:40:13 -03:00 committed by GitHub
commit 7d6d41aa87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 686 additions and 760 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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;

View file

@ -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";

View file

@ -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({

View file

@ -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<string>(
description ?? "",
);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const setNode = useFlowStore((state) => state.setNode);
const overflowRef = useRef<HTMLDivElement>(null);
@ -56,7 +58,7 @@ export default function NodeDescription({
}, [selected]);
useEffect(() => {
setNodeDescription(description);
setNodeDescription(description ?? "");
}, [description]);
const MemoizedMarkdown = memo(Markdown);

View file

@ -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<string>(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}

View file

@ -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: {

View file

@ -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,

View file

@ -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",

View file

@ -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 {

View file

@ -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;
}
},

View file

@ -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(

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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(

View file

@ -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 (
<Panel
data-testid="canvas_controls"
className="react-flow__controls !m-2 flex gap-1.5 rounded-md border border-secondary-hover bg-background fill-foreground stroke-foreground p-1.5 text-primary shadow [&>button]:border-0 [&>button]:bg-background hover:[&>button]:bg-accent"
className="react-flow__controls !m-2 flex !flex-row gap-1.5 rounded-md border border-secondary-hover bg-background fill-foreground stroke-foreground p-1.5 text-primary shadow [&>button]:border-0 [&>button]:bg-background hover:[&>button]:bg-accent"
position="bottom-left"
>
{/* Zoom In */}

View file

@ -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<APIClassType | undefined>(
node.data?.node,

View file

@ -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 (
<div className="h-full w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll">
{nodes?.map((node: NodeType, i) => (
{nodes?.map((node: AllNodeType, i) => (
<div className="px-3" key={i}>
<TweakComponent open={open} node={node} />
</div>

View file

@ -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;

View file

@ -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";

View file

@ -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,
});

View file

@ -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,

View file

@ -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";

View file

@ -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 {

View file

@ -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";

View file

@ -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";

View file

@ -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 () => {

View file

@ -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";

View file

@ -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);

View file

@ -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 },

View file

@ -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
];

View file

@ -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;

View file

@ -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") {

View file

@ -1,5 +1,5 @@
import useFlowStore from "@/stores/flowStore";
import { ConnectionLineComponentProps } from "reactflow";
import { ConnectionLineComponentProps } from "@xyflow/react";
const ConnectionLineComponent = ({
fromX,

View file

@ -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 {
<div className="h-full w-full bg-canvas" ref={reactFlowWrapper}>
{showCanvas ? (
<div id="react-flow-id" className="h-full w-full bg-canvas">
<ReactFlow
<ReactFlow<AllNodeType, EdgeType>
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}

View file

@ -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"
>
<GradientGroup
strokeWidth={1.5}
@ -100,6 +101,7 @@ export default function SelectionMenu({
}`}
onClick={onClick}
disabled={disable}
data-testid="group-node"
>
<GradientGroup
strokeWidth={1.5}

View file

@ -10,8 +10,8 @@ import useFlowsManagerStore from "@/stores/flowsManagerStore";
import useFlowStore from "@/stores/flowStore";
import { useTypesStore } from "@/stores/typesStore";
import { cn } from "@/utils/utils";
import { useUpdateNodeInternals } from "@xyflow/react";
import { useState } from "react";
import { useUpdateNodeInternals } from "reactflow";
export default function UpdateAllComponents() {
const { componentsToUpdate, nodes, edges, setNodes } = useFlowStore();
@ -36,7 +36,7 @@ export default function UpdateAllComponents() {
const updatePromises = componentsToUpdate.map((nodeId) => {
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) {

View file

@ -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,

View file

@ -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;

View file

@ -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());

View file

@ -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<FlowStoreType>((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<FlowStoreType>((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<FlowStoreType>((set, get) => ({
setReactFlowInstance: (newState) => {
set({ reactFlowInstance: newState });
},
onNodesChange: (changes: NodeChange[]) => {
onNodesChange: (changes: NodeChange<AllNodeType>[]) => {
set({
nodes: applyNodeChanges(changes, get().nodes),
});
},
onEdgesChange: (changes: EdgeChange[]) => {
onEdgesChange: (changes: EdgeChange<EdgeType>[]) => {
set({
edges: applyEdgeChanges(changes, get().edges),
});
@ -264,7 +270,7 @@ const useFlowStore = create<FlowStoreType>((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<FlowStoreType>((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<FlowStoreType>((set, get) => ({
let minimumX = Infinity;
let minimumY = Infinity;
let idsMap = {};
let newNodes: Node<NodeDataType>[] = 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<FlowStoreType>((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<FlowStoreType>((set, get) => ({
...cloneDeep(node.data),
id: newId,
},
};
} as AllNodeType;
updateGroupRecursion(
newNode,
selection.edges,
@ -412,7 +419,7 @@ const useFlowStore = create<FlowStoreType>((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<FlowStoreType>((set, get) => ({
});
sourceHandleObject.id = source;
edge.data.sourceHandle = sourceHandleObject;
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!,
);
@ -433,7 +439,12 @@ const useFlowStore = create<FlowStoreType>((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<FlowStoreType>((set, get) => ({
// isIoOut = outputTypes.has(sourceType);
// }
let newEdges: Edge[] = [];
let newEdges: EdgeType[] = [];
get().setEdges((oldEdges) => {
newEdges = addEdge(
{
@ -733,7 +744,10 @@ const useFlowStore = create<FlowStoreType>((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<FlowStoreType>((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 {

View file

@ -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

View file

@ -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<tabsArrayType>;
tabs: tabsArrayType[];
activeTab: string;
setActiveTab: (value: string) => void;
isMessage?: boolean;
tweaksNodes?: Array<NodeType>;
tweaksNodes?: AllNodeType[];
activeTweaks?: boolean;
setActiveTweaks?: (value: boolean) => void;
};

View file

@ -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<AllNodeType, EdgeType> | 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<NodeDataType, "genericNode">;
export type NoteNodeType = Node<NoteDataType, "noteNode">;
export interface noteClassType
extends Pick<APIClassType, "description" | "display_name" | "documentation"> {
export type AllNodeType = GenericNodeType | NoteNodeType;
export type SetNodeType<T = "genericNode" | "noteNode"> =
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<NodeDataType, "showNode" | "type" | "id"> {
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<EdgeDataType, "default">;
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[];

View file

@ -1,4 +1,4 @@
import { XYPosition } from "reactflow";
import { XYPosition } from "@xyflow/react";
import { FlowType, NodeDataType } from "../flow";
type OnChange<ChangesType> = (changes: ChangesType[]) => void;

View file

@ -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[];
};

View file

@ -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<AllNodeType, EdgeType> | null;
setReactFlowInstance: (
newState: ReactFlowInstance<AllNodeType, EdgeType>,
) => 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<AllNodeType>;
onEdgesChange: OnEdgesChange<EdgeType>;
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<string>) => void;
deleteEdge: (edgeId: string | Array<string>) => void;
paste: (
@ -145,7 +150,7 @@ export type FlowStoreType = {
silent?: boolean;
session?: string;
}) => Promise<void>;
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:

View file

@ -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,

View file

@ -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";

View file

@ -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<AllNodeType[]> => {
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;

View file

@ -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<ReactFlowJsonObject | null> {
): Promise<ReactFlowJsonObject<AllNodeType, EdgeType> | 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<string> {
export function validateNode(node: AllNodeType, edges: Edge[]): Array<string> {
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<string> {
return Object.keys(template).reduce((errors: Array<string>, 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<string> {
}
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<string> }> {
if (nodes.length === 0) {
@ -479,7 +488,7 @@ Array<{ id: string; errors: Array<string> }> {
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<NodeType>,
): Array<NodeType> {
nodes: Array<AllNodeType>,
): Array<AllNodeType> {
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<OutputFieldType> = [];
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<AllNodeType, EdgeType>,
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) => {

View file

@ -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";

View file

@ -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

View file

@ -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()

View file

@ -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")

View file

@ -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")

View file

@ -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();

View file

@ -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();

View file

@ -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

View file

@ -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}

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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);

View file

@ -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 ?? "");

View file

@ -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")

View file

@ -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;

View file

@ -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")

View file

@ -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 ?? "");

View file

@ -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();

View file

@ -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,

View file

@ -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();
}
}