diff --git a/src/frontend/src/App.css b/src/frontend/src/App.css
index 419fdfeef..6a91177c6 100644
--- a/src/frontend/src/App.css
+++ b/src/frontend/src/App.css
@@ -145,6 +145,10 @@ body {
transition-duration: 150ms;
}
+.react-flow__edge.running .react-flow__edge-path {
+ stroke: var(--status-blue) !important;
+}
+
.ag-react-container {
width: 100%;
height: 100%;
diff --git a/src/frontend/src/CustomEdges/index.tsx b/src/frontend/src/CustomEdges/index.tsx
new file mode 100644
index 000000000..8ac7e31bd
--- /dev/null
+++ b/src/frontend/src/CustomEdges/index.tsx
@@ -0,0 +1,33 @@
+import useFlowStore from "@/stores/flowStore";
+import { BaseEdge, EdgeProps, getBezierPath, Position } from "reactflow";
+
+export function DefaultEdge({
+ sourceHandleId,
+ source,
+ sourceX,
+ sourceY,
+ target,
+ targetHandleId,
+ targetX,
+ targetY,
+ ...props
+}: EdgeProps) {
+ const getNode = useFlowStore((state) => state.getNode);
+
+ const sourceNode = getNode(source);
+ const targetNode = getNode(target);
+
+ const sourceXNew = (sourceNode?.position.x ?? 0) + (sourceNode?.width ?? 0);
+ const targetXNew = targetNode?.position.x ?? 0;
+
+ const [edgePath] = getBezierPath({
+ sourceX: sourceXNew,
+ sourceY,
+ sourcePosition: Position.Right,
+ targetPosition: Position.Left,
+ targetX: targetXNew,
+ targetY,
+ });
+
+ return ;
+}
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/HandleTooltipComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/HandleTooltipComponent/index.tsx
index 58e28ff3a..0d6210fbb 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/HandleTooltipComponent/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/HandleTooltipComponent/index.tsx
@@ -1,30 +1,65 @@
-import { TOOLTIP_EMPTY } from "../../../../constants/constants";
-import useFlowStore from "../../../../stores/flowStore";
-import { useTypesStore } from "../../../../stores/typesStore";
-import { NodeType } from "../../../../types/flow";
-import { groupByFamily } from "../../../../utils/utils";
-import TooltipRenderComponent from "../tooltipRenderComponent";
+import { convertTestName } from "@/components/storeCardComponent/utils/convert-test-name";
-export default function HandleTooltips({
- left,
+export default function HandleTooltipComponent({
+ isInput,
tooltipTitle,
+ colors,
+ isConnecting,
+ isCompatible,
+ isSameNode,
}: {
- left: boolean;
- nodes: NodeType[];
+ isInput: boolean;
+ colors: string[];
tooltipTitle: string;
+ isConnecting: boolean;
+ isCompatible: boolean;
+ isSameNode: boolean;
}) {
- const myData = useTypesStore((state) => state.data);
- const nodes = useFlowStore((state) => state.nodes);
-
- let groupedObj: any = groupByFamily(myData, tooltipTitle!, left, nodes!);
-
- if (groupedObj && groupedObj.length > 0) {
- //@ts-ignore
- return groupedObj.map((item, index) => {
- return ;
- });
- } else {
- //@ts-ignore
- return {TOOLTIP_EMPTY};
- }
+ const tooltips = tooltipTitle.split("\n");
+ const plural = tooltips.length > 1 ? "s" : "";
+ return (
+
+ {isSameNode ? (
+ "Can't connect to the same node"
+ ) : (
+
+ {isConnecting ? (
+ isCompatible ? (
+
+ Connect{" "}
+ to
+
+ ) : (
+
Incompatible with
+ )
+ ) : (
+
+ {isInput ? `Input${plural}` : `Output${plural}`}:{" "}
+
+ )}
+ {tooltips.map((word, index) => (
+
+ {word}
+
+ ))}
+ {isConnecting &&
{isInput ? `input` : `output`}}
+
+ )}
+ {!isConnecting && (
+
+
+ Drag to connect compatible {!isInput ? "inputs" : "outputs"}
+
+
+ Select to filter compatible {!isInput ? "inputs" : "outputs"}{" "}
+ and components
+
+
+ )}
+
+ );
}
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx
index e44ea2147..bd7b104ea 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeInputField/index.tsx
@@ -81,6 +81,7 @@ export default function NodeInputField({
setFilterEdge={setFilterEdge}
showNode={showNode}
testIdComplement={`${data?.type?.toLowerCase()}-${showNode ? "shownode" : "noshownode"}`}
+ nodeId={data.id}
/>
);
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx
index 96a7dabb1..e7d52d07c 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputfield/index.tsx
@@ -110,6 +110,7 @@ export default function NodeOutputField({
id={id}
title={title}
edges={edges}
+ nodeId={data.id}
myData={myData}
colors={colors}
setFilterEdge={setFilterEdge}
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx
index fbf2232de..0e393b357 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx
@@ -1,3 +1,6 @@
+import { useDarkStore } from "@/stores/darkStore";
+import useFlowStore from "@/stores/flowStore";
+import { useMemo, useState } from "react";
import { Handle, Position } from "reactflow";
import ShadTooltip from "../../../../components/shadTooltipComponent";
import {
@@ -5,7 +8,7 @@ import {
scapedJSONStringfy,
} from "../../../../utils/reactflowUtils";
import { classNames, cn, groupByFamily } from "../../../../utils/utils";
-import HandleTooltips from "../HandleTooltipComponent";
+import HandleTooltipComponent from "../HandleTooltipComponent";
export default function HandleRenderComponent({
left,
@@ -20,6 +23,7 @@ export default function HandleRenderComponent({
setFilterEdge,
showNode,
testIdComplement,
+ nodeId,
}: {
left: boolean;
nodes: any;
@@ -33,17 +37,168 @@ export default function HandleRenderComponent({
setFilterEdge: any;
showNode: any;
testIdComplement?: string;
+ nodeId: string;
}) {
+ const setHandleDragging = useFlowStore((state) => state.setHandleDragging);
+ const setFilterType = useFlowStore((state) => state.setFilterType);
+ const handleDragging = useFlowStore((state) => state.handleDragging);
+ const filterType = useFlowStore((state) => state.filterType);
+ const dark = useDarkStore((state) => state.dark);
+
+ const onConnect = useFlowStore((state) => state.onConnect);
+
+ const handleMouseUp = () => {
+ setHandleDragging(undefined);
+ document.removeEventListener("mouseup", handleMouseUp);
+ };
+
+ const myId = useMemo(
+ () => scapedJSONStringfy(proxy ? { ...id, proxy } : id),
+ [id, proxy],
+ );
+
+ const getConnection = useMemo(
+ () =>
+ (semiConnection: {
+ source: string | undefined;
+ sourceHandle: string | undefined;
+ target: string | undefined;
+ targetHandle: string | undefined;
+ }) => ({
+ source: semiConnection.source ?? nodeId,
+ sourceHandle: semiConnection.sourceHandle ?? myId,
+ target: semiConnection.target ?? nodeId,
+ targetHandle: semiConnection.targetHandle ?? myId,
+ }),
+ [nodeId, myId],
+ );
+
+ const sameDraggingNode = useMemo(
+ () => (!left ? handleDragging?.target : handleDragging?.source) === nodeId,
+ [left, handleDragging, nodeId],
+ );
+
+ const ownDraggingHandle = useMemo(
+ () =>
+ handleDragging &&
+ (left ? handleDragging?.target : handleDragging?.source) &&
+ (left ? handleDragging.targetHandle : handleDragging.sourceHandle) ===
+ myId,
+ [handleDragging, left, myId],
+ );
+
+ const sameFilterNode = useMemo(
+ () => (!left ? filterType?.target : filterType?.source) === nodeId,
+ [left, filterType, nodeId],
+ );
+
+ const ownFilterHandle = useMemo(
+ () =>
+ filterType &&
+ (left ? filterType?.target : filterType?.source) === nodeId &&
+ (left ? filterType.targetHandle : filterType.sourceHandle) === myId,
+ [filterType, left, myId],
+ );
+
+ const sameNode = useMemo(
+ () => sameDraggingNode || sameFilterNode,
+ [sameDraggingNode, sameFilterNode],
+ );
+ const ownHandle = useMemo(
+ () => ownDraggingHandle || ownFilterHandle,
+ [ownDraggingHandle, ownFilterHandle],
+ );
+
+ const draggingOpenHandle = useMemo(
+ () =>
+ handleDragging &&
+ (left ? handleDragging.source : handleDragging.target) &&
+ !ownDraggingHandle
+ ? isValidConnection(getConnection(handleDragging), nodes, edges)
+ : false,
+ [handleDragging, left, ownDraggingHandle, getConnection, nodes, edges],
+ );
+
+ const filterOpenHandle = useMemo(
+ () =>
+ filterType &&
+ (left ? filterType.source : filterType.target) &&
+ !ownFilterHandle
+ ? isValidConnection(getConnection(filterType), nodes, edges)
+ : false,
+ [filterType, left, ownFilterHandle, getConnection, nodes, edges],
+ );
+
+ const openHandle = useMemo(
+ () => filterOpenHandle || draggingOpenHandle,
+ [filterOpenHandle, draggingOpenHandle],
+ );
+ const filterPresent = useMemo(
+ () => handleDragging || filterType,
+ [handleDragging, filterType],
+ );
+
+ const currentFilter = useMemo(
+ () =>
+ left
+ ? {
+ targetHandle: myId,
+ target: nodeId,
+ source: undefined,
+ sourceHandle: undefined,
+ type: tooltipTitle,
+ color: colors[0],
+ }
+ : {
+ sourceHandle: myId,
+ source: nodeId,
+ target: undefined,
+ targetHandle: undefined,
+ type: tooltipTitle,
+ color: colors[0],
+ },
+ [left, myId, nodeId, tooltipTitle, colors],
+ );
+
+ const handleColor = useMemo(
+ () =>
+ filterPresent && !(openHandle || ownHandle)
+ ? dark
+ ? "conic-gradient(#374151 0deg 360deg)"
+ : "conic-gradient(#cbd5e1 0deg 360deg)"
+ : "conic-gradient(" +
+ colors
+ .concat(colors[0])
+ .map(
+ (color, index) =>
+ color +
+ " " +
+ ((360 / colors.length) * index - 360 / (colors.length * 4)) +
+ "deg " +
+ ((360 / colors.length) * index + 360 / (colors.length * 4)) +
+ "deg",
+ )
+ .join(" ,") +
+ ")",
+ [filterPresent, openHandle, ownHandle, dark, colors],
+ );
+
+ const [openTooltip, setOpenTooltip] = useState(false);
return (
}
side={left ? "left" : "right"}
@@ -54,47 +209,73 @@ export default function HandleRenderComponent({
}`}
type={left ? "target" : "source"}
position={left ? Position.Left : Position.Right}
- key={scapedJSONStringfy(proxy ? { ...id, proxy } : id)}
- id={scapedJSONStringfy(proxy ? { ...id, proxy } : id)}
+ key={myId}
+ id={myId}
isValidConnection={(connection) =>
isValidConnection(connection, nodes, edges)
}
className={classNames(
- left ? "-ml-0.5" : "-mr-0.5",
- "z-20 h-3 w-3 rounded-full border-none bg-background",
+ `group/handle z-20 h-6 w-6 rounded-full border-none bg-transparent transition-all`,
)}
- style={{
- background:
- "conic-gradient(" +
- colors
- .concat(colors[0])
- .map(
- (color, index) =>
- color +
- " " +
- ((360 / colors.length) * index -
- 360 / (colors.length * 4)) +
- "deg " +
- ((360 / colors.length) * index +
- 360 / (colors.length * 4)) +
- "deg",
- )
- .join(" ,") +
- ")",
- WebkitMaskImage: "radial-gradient(transparent 40%, black 44%)",
- maskImage: "radial-gradient(transparent 40%, black 44%)",
- }}
onClick={() => {
setFilterEdge(groupByFamily(myData, tooltipTitle!, left, nodes!));
+ setFilterType(currentFilter);
+ if (filterOpenHandle && filterType) {
+ onConnect(getConnection(filterType));
+ setFilterType(undefined);
+ setFilterEdge([]);
+ }
}}
- />
+ onMouseUp={() => {
+ setOpenTooltip(false);
+ }}
+ onContextMenu={(event) => {
+ event.preventDefault();
+ }}
+ onMouseDown={(event) => {
+ if (event.button === 0) {
+ setHandleDragging(currentFilter);
+ document.addEventListener("mouseup", handleMouseUp);
+ }
+ }}
+ >
+
+
+
+
-
);
}
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/tooltipRenderComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/tooltipRenderComponent/index.tsx
deleted file mode 100644
index ed2760161..000000000
--- a/src/frontend/src/CustomNodes/GenericNode/components/tooltipRenderComponent/index.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import React from "react";
-import {
- INPUT_HANDLER_HOVER,
- OUTPUT_HANDLER_HOVER,
-} from "../../../../constants/constants";
-import {
- nodeColors,
- nodeIconsLucide,
- nodeNames,
-} from "../../../../utils/styleUtils";
-import { classNames } from "../../../../utils/utils";
-
-const TooltipRenderComponent = ({ item, index, left }) => {
- const Icon = nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
-
- return (
-
- {index === 0 && (
-
{left ? INPUT_HANDLER_HOVER : OUTPUT_HANDLER_HOVER}
- )}
-
0 ? "mt-2 flex items-center" : "mt-3 flex items-center",
- )}
- >
-
-
-
-
- {nodeNames[item.family] ?? "Other"}{" "}
- {item?.display_name && item?.display_name?.length > 0 ? (
-
- {" "}
- {item.display_name === "" ? "" : " - "}
- {item.display_name.split(", ").length > 2
- ? item.display_name.split(", ").map((el, index) => (
-
-
- {index === item.display_name.split(", ").length - 1
- ? el
- : (el += `, `)}
-
-
- ))
- : item.display_name}
-
- ) : (
-
- {" "}
- {item.type === "" ? "" : " - "}
- {item.type.split(", ").length > 2
- ? item.type.split(", ").map((el, index) => (
-
-
- {index === item.type.split(", ").length - 1
- ? el
- : (el += `, `)}
-
-
- ))
- : item.type}
-
- )}
-
-
-
- );
-};
-
-export default TooltipRenderComponent;
diff --git a/src/frontend/src/components/shadTooltipComponent/index.tsx b/src/frontend/src/components/shadTooltipComponent/index.tsx
index 1b76ab325..387f24e30 100644
--- a/src/frontend/src/components/shadTooltipComponent/index.tsx
+++ b/src/frontend/src/components/shadTooltipComponent/index.tsx
@@ -9,12 +9,19 @@ export default function ShadTooltip({
children,
styleClasses,
delayDuration = 500,
+ open,
+ setOpen,
}: ShadToolTipType): JSX.Element {
return content ? (
-
+
{children}
-
{title}
+
{title}
{beta && (
BETA
diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
index 49a4a7a27..32b1fe7ef 100644
--- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
@@ -17,6 +17,7 @@ import { nodeIconsLucide } from "../../../../utils/styleUtils";
import ParentDisclosureComponent from "../ParentDisclosureComponent";
import { SidebarCategoryComponent } from "./SidebarCategoryComponent";
+import { SidebarFilterComponent } from "./sidebarFilterComponent";
import { sortKeys } from "./utils";
export default function ExtraSidebar(): JSX.Element {
@@ -25,6 +26,7 @@ export default function ExtraSidebar(): JSX.Element {
const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const hasStore = useStoreStore((state) => state.hasStore);
+ const filterType = useFlowStore((state) => state.filterType);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [dataFilter, setFilterData] = useState(data);
@@ -222,8 +224,18 @@ export default function ExtraSidebar(): JSX.Element {
-
-
Components
+
+ Components
+ {filterType && (
+ {
+ setFilterEdge([]);
+ setFilterData(data);
+ }}
+ />
+ )}
diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sidebarFilterComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sidebarFilterComponent/index.tsx
new file mode 100644
index 000000000..7b1dcbb69
--- /dev/null
+++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sidebarFilterComponent/index.tsx
@@ -0,0 +1,40 @@
+import ForwardedIconComponent from "@/components/genericIconComponent";
+import ShadTooltip from "@/components/shadTooltipComponent";
+import { Button } from "@/components/ui/button";
+
+export function SidebarFilterComponent({
+ isInput,
+ type,
+ resetFilters,
+}: {
+ isInput: boolean;
+ type: string;
+ resetFilters: () => void;
+}) {
+ return (
+
+
+
+
+ {isInput ? "Input" : "Output"}: {type}
+
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts
index 6fa47d093..cdc910a8e 100644
--- a/src/frontend/src/stores/flowStore.ts
+++ b/src/frontend/src/stores/flowStore.ts
@@ -431,6 +431,9 @@ const useFlowStore = create
((set, get) => ({
});
},
setFilterEdge: (newState) => {
+ if (newState.length === 0) {
+ set({ filterType: undefined });
+ }
set({ getFilterEdge: newState });
},
getFilterEdge: [],
@@ -469,8 +472,6 @@ const useFlowStore = create((set, get) => ({
targetHandle: scapeJSONParse(connection.targetHandle!),
sourceHandle: scapeJSONParse(connection.sourceHandle!),
},
- // style: { stroke: "#555" },
- // className: "stroke-foreground stroke-connection",
},
oldEdges,
);
@@ -528,6 +529,7 @@ const useFlowStore = create((set, get) => ({
get().updateBuildStatus(ids, BuildStatus.ERROR);
throw new Error("Invalid components");
}
+ get().updateEdgesRunningByNodes(nodes, true);
}
function handleBuildUpdate(
vertexBuildData: VertexBuildTypeAPI,
@@ -631,6 +633,10 @@ const useFlowStore = create((set, get) => ({
});
}
}
+ get().updateEdgesRunningByNodes(
+ get().nodes.map((n) => n.id),
+ false,
+ );
get().setIsBuilding(false);
get().setLockChat(false);
},
@@ -653,6 +659,10 @@ const useFlowStore = create((set, get) => ({
title:
"There are outdated components in the flow. The error could be related to them.",
});
+ get().updateEdgesRunningByNodes(
+ get().nodes.map((n) => n.id),
+ false,
+ );
setErrorData({ list, title });
get().setIsBuilding(false);
get().setLockChat(false);
@@ -662,7 +672,7 @@ const useFlowStore = create((set, get) => ({
// reference is the id of the vertex or the id of the parent in a group node
.map((element) => element.reference)
.filter(Boolean) as string[];
- useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
+ get().updateBuildStatus(idList, BuildStatus.BUILDING);
},
onValidateNodes: validateSubgraph,
nodes: get().nodes || undefined,
@@ -680,6 +690,17 @@ const useFlowStore = create((set, get) => ({
viewport: get().reactFlowInstance?.getViewport()!,
};
},
+ updateEdgesRunningByNodes: (ids: string[], running: boolean) => {
+ const edges = get().edges;
+ const newEdges = edges.map((edge) => {
+ if (ids.includes(edge.source) && ids.includes(edge.target)) {
+ edge.animated = running;
+ edge.className = running ? "running" : "";
+ }
+ return edge;
+ });
+ set({ edges: newEdges });
+ },
updateVerticesBuild: (
vertices: {
verticesIds: string[];
@@ -762,6 +783,15 @@ const useFlowStore = create((set, get) => ({
setBuildController: (controller) => {
set({ buildController: controller });
},
+ handleDragging: undefined,
+ setHandleDragging: (handleDragging) => {
+ set({ handleDragging });
+ },
+
+ filterType: undefined,
+ setFilterType: (filterType) => {
+ set({ filterType });
+ },
}));
export default useFlowStore;
diff --git a/src/frontend/src/style/applies.css b/src/frontend/src/style/applies.css
index ab6643500..3977bee7a 100644
--- a/src/frontend/src/style/applies.css
+++ b/src/frontend/src/style/applies.css
@@ -231,7 +231,7 @@
@apply fill-chat-trigger-disabled stroke-chat-trigger-disabled stroke-1;
}
.parent-disclosure-arrangement {
- @apply flex w-full select-none items-center justify-between bg-background px-3 py-1;
+ @apply flex w-full select-none items-center justify-between bg-background px-5 py-3;
}
.components-disclosure-arrangement {
@apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2;
@@ -240,9 +240,6 @@
/* different color than the non child */
@apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2;
}
- .parent-disclosure-title {
- @apply p-2 px-2 text-sm font-medium;
- }
.components-disclosure-title {
@apply flex items-center text-sm text-primary;
}
diff --git a/src/frontend/src/style/classes.css b/src/frontend/src/style/classes.css
index e12bee424..224e2a05d 100644
--- a/src/frontend/src/style/classes.css
+++ b/src/frontend/src/style/classes.css
@@ -169,6 +169,26 @@ textarea[class^="ag-"]:focus {
cursor: grabbing !important;
}
+.react-flow__handle-right {
+ right: 0 !important;
+ transform: translate(50%, -50%) !important;
+}
+
+.react-flow__handle-left {
+ left: 0 !important;
+ transform: translate(-50%, -50%) !important;
+}
+
+.react-flow__handle-right {
+ right: 0 !important;
+ transform: translate(50%, -50%) !important;
+}
+
+.react-flow__handle-left {
+ left: 0 !important;
+ transform: translate(-50%, -50%) !important;
+}
+
.react-flow__node-noteNode:not(.selected) {
z-index: -1 !important;
}
diff --git a/src/frontend/src/style/index.css b/src/frontend/src/style/index.css
index d75a76a9e..287afead6 100644
--- a/src/frontend/src/style/index.css
+++ b/src/frontend/src/style/index.css
@@ -57,6 +57,8 @@
--hover: #f2f4f5;
--disabled-run: #6366f1;
+ --filter-foreground: #4f46e5;
+ --filter-background: #eef2ff;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923de;
--build-trigger: #dc735b;
@@ -114,6 +116,9 @@
--destructive: 0 60% 25%; /* hsl(0 60% 25%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
+ --filter-foreground: #eef2ff;
+ --filter-background: #4e46e599;
+
--ring: 216 24% 30%; /* hsl(216 24% 30%) */
--radius: 0.5rem;
diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts
index 5374d9b2e..ee0f13e3e 100644
--- a/src/frontend/src/types/components/index.ts
+++ b/src/frontend/src/types/components/index.ts
@@ -339,6 +339,8 @@ export type ShadTooltipProps = {
style?: string;
};
export type ShadToolTipType = {
+ open?: boolean;
+ setOpen?: (open: boolean) => void;
content?: ReactNode | null;
side?: "top" | "right" | "bottom" | "left";
asChild?: boolean;
diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts
index ac4705d8e..3515b7fc1 100644
--- a/src/frontend/src/types/zustand/flow/index.ts
+++ b/src/frontend/src/types/zustand/flow/index.ts
@@ -180,6 +180,52 @@ export type FlowStoreType = {
edges?: Edge[];
viewport?: Viewport;
}) => void;
+ handleDragging:
+ | {
+ source: string | undefined;
+ sourceHandle: string | undefined;
+ target: string | undefined;
+ targetHandle: string | undefined;
+ type: string;
+ color: string;
+ }
+ | undefined;
+ setHandleDragging: (
+ data:
+ | {
+ source: string | undefined;
+ sourceHandle: string | undefined;
+ target: string | undefined;
+ targetHandle: string | undefined;
+ type: string;
+ color: string;
+ }
+ | undefined,
+ ) => void;
+
+ filterType:
+ | {
+ source: string | undefined;
+ sourceHandle: string | undefined;
+ target: string | undefined;
+ targetHandle: string | undefined;
+ type: string;
+ color: string;
+ }
+ | undefined;
+ setFilterType: (
+ data:
+ | {
+ source: string | undefined;
+ sourceHandle: string | undefined;
+ target: string | undefined;
+ targetHandle: string | undefined;
+ type: string;
+ color: string;
+ }
+ | undefined,
+ ) => void;
+ updateEdgesRunningByNodes: (ids: string[], running: boolean) => void;
stopBuilding: () => void;
buildController: AbortController;
setBuildController: (controller: AbortController) => void;
diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts
index dff2e8436..737e948db 100644
--- a/src/frontend/src/utils/reactflowUtils.ts
+++ b/src/frontend/src/utils/reactflowUtils.ts
@@ -215,6 +215,9 @@ export function isValidConnection(
nodes: Node[],
edges: Edge[],
) {
+ if (source === target) {
+ return false;
+ }
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
if (
diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts
index 9fe290ed1..fad9dd6df 100644
--- a/src/frontend/src/utils/styleUtils.ts
+++ b/src/frontend/src/utils/styleUtils.ts
@@ -8,6 +8,7 @@ import {
AlertTriangle,
ArrowBigUp,
ArrowLeft,
+ ArrowRight,
ArrowUpToLine,
Bell,
Binary,
@@ -63,6 +64,7 @@ import {
FileText,
FileType2,
FileUp,
+ Filter,
FlaskConical,
FolderIcon,
FolderPlus,
@@ -87,6 +89,7 @@ import {
Layers,
Link,
Link2,
+ ListFilter,
Loader2,
Lock,
LogIn,
@@ -401,10 +404,12 @@ export const nodeIconsLucide: iconsType = {
GoogleSearchRun: GoogleIcon,
Google: GoogleIcon,
GoogleGenerativeAI: GoogleGenerativeAIIcon,
+ ArrowRight,
Groq: GroqIcon,
HCD: HCDIcon,
HNLoader: HackerNewsIcon,
Unstructured: UnstructuredIcon,
+ Filter: Filter,
HuggingFaceHub: HuggingFaceIcon,
HuggingFace: HuggingFaceIcon,
HuggingFaceEmbeddings: HuggingFaceIcon,
@@ -414,6 +419,7 @@ export const nodeIconsLucide: iconsType = {
Meta: MetaIcon,
CheckCheck,
Midjorney: MidjourneyIcon,
+ ListFilter,
MongoDBAtlasVectorSearch: MongoDBIcon,
MongoDB: MongoDBIcon,
MongoDBChatMessageHistory: MongoDBIcon,
diff --git a/src/frontend/tailwind.config.mjs b/src/frontend/tailwind.config.mjs
index a3502d458..201f55b20 100644
--- a/src/frontend/tailwind.config.mjs
+++ b/src/frontend/tailwind.config.mjs
@@ -97,9 +97,15 @@ const config = {
"status-gray": "var(--status-gray)",
"success-background": "var(--success-background)",
"success-foreground": "var(--success-foreground)",
- "beta-background": "var(--beta-background)",
- "beta-foreground": "var(--beta-foreground)",
- "beta-foreground-soft": "var(--beta-foreground-soft)",
+ filter: {
+ foreground: "var(--filter-foreground)",
+ background: "var(--filter-background)",
+ },
+ beta: {
+ background: "var(--beta-background)",
+ foreground: "var(--beta-foreground)",
+ "foreground-soft": "var(--beta-foreground-soft)",
+ },
"chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)",
ice: "var(--ice)",
diff --git a/src/frontend/tests/core/features/filterEdge-shard-0.spec.ts b/src/frontend/tests/core/features/filterEdge-shard-0.spec.ts
index 790f01ed3..233dac0ee 100644
--- a/src/frontend/tests/core/features/filterEdge-shard-0.spec.ts
+++ b/src/frontend/tests/core/features/filterEdge-shard-0.spec.ts
@@ -54,28 +54,21 @@ test("user must see on handle hover a tooltip with possibility connections", asy
}
await visibleElementHandle.hover().then(async () => {
- const testIds = [
- "available-output-inputs",
- "available-output-chains",
- "available-output-textsplitters",
- "available-output-retrievers",
- "available-output-prototypes",
- "available-output-embeddings",
- "available-output-data",
- "available-output-vectorstores",
- "available-output-memories",
- "available-output-models",
- "available-output-outputs",
- "available-output-agents",
- "available-output-helpers",
- ];
+ await expect(
+ page.getByText("Drag to connect compatible inputs").first(),
+ ).toBeVisible();
- await Promise.all(
- testIds.map((id) => expect(page.getByTestId(id).first()).toBeVisible()),
- );
+ await expect(
+ page
+ .getByText("Select to filter compatible inputs and components")
+ .first(),
+ ).toBeVisible();
- await page.getByTestId("icon-X").click();
- await page.waitForTimeout(500);
+ await expect(page.getByText("Output:").first()).toBeVisible();
+
+ await expect(
+ page.getByTestId("output-tooltip-message").first(),
+ ).toBeVisible();
});
await page.getByTitle("fit view").click();
@@ -96,13 +89,20 @@ test("user must see on handle hover a tooltip with possibility connections", asy
await visibleElementHandle.hover().then(async () => {
await expect(
- page.getByTestId("available-input-models").first(),
+ page.getByText("Drag to connect compatible outputs").first(),
).toBeVisible();
- await page.waitForTimeout(1000);
- await page.getByTestId("icon-Search").click();
+ await expect(
+ page
+ .getByText("Select to filter compatible outputs and components")
+ .first(),
+ ).toBeVisible();
- await page.waitForTimeout(500);
+ await expect(page.getByText("Input:").first()).toBeVisible();
+
+ await expect(
+ page.getByTestId("input-tooltip-languagemodel").first(),
+ ).toBeVisible();
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
@@ -121,16 +121,21 @@ test("user must see on handle hover a tooltip with possibility connections", asy
}
await visibleElementHandle.hover().then(async () => {
- await page.waitForTimeout(2500);
-
await expect(
- page.getByTestId("available-input-retrievers").first(),
- ).toBeVisible();
- await expect(
- page.getByTestId("available-input-vectorstores").first(),
+ page.getByText("Drag to connect compatible outputs").first(),
).toBeVisible();
- await page.waitForTimeout(500);
+ await expect(
+ page
+ .getByText("Select to filter compatible outputs and components")
+ .first(),
+ ).toBeVisible();
+
+ await expect(page.getByText("Input:").first()).toBeVisible();
+
+ await expect(
+ page.getByTestId("input-tooltip-retriever").first(),
+ ).toBeVisible();
});
await page.getByTitle("fit view").click();
@@ -151,7 +156,19 @@ test("user must see on handle hover a tooltip with possibility connections", asy
await visibleElementHandle.hover().then(async () => {
await expect(
- page.getByTestId("available-input-helpers").first(),
+ page.getByText("Drag to connect compatible outputs").first(),
+ ).toBeVisible();
+
+ await expect(
+ page
+ .getByText("Select to filter compatible outputs and components")
+ .first(),
+ ).toBeVisible();
+
+ await expect(page.getByText("Input:").first()).toBeVisible();
+
+ await expect(
+ page.getByTestId("input-tooltip-basechatmemory").first(),
).toBeVisible();
});
});
diff --git a/src/frontend/tests/core/features/filterSidebar.spec.ts b/src/frontend/tests/core/features/filterSidebar.spec.ts
index 8091c0b72..405a6b3fb 100644
--- a/src/frontend/tests/core/features/filterSidebar.spec.ts
+++ b/src/frontend/tests/core/features/filterSidebar.spec.ts
@@ -54,99 +54,86 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await page.getByTestId("handle-apirequest-shownode-urls-left").click();
- let disclosureTestIds = [
- "disclosure-inputs",
- "disclosure-outputs",
- "disclosure-prompts",
- "disclosure-models",
- "disclosure-helpers",
- "disclosure-agents",
- "disclosure-chains",
- "disclosure-prototypes",
- ];
+ await page.waitForTimeout(500);
- let specificTestIds = [
- "inputsChat Input",
- "outputsChat Output",
- "promptsPrompt",
- "modelsAmazon Bedrock",
- "helpersChat Memory",
- "agentsCSVAgent",
- "chainsConversationChain",
- "prototypesConditional Router",
- ];
+ expect(await page.getByTestId("icon-ListFilter")).toBeVisible();
- await Promise.all(
- disclosureTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()),
- );
+ await page
+ .getByTestId("icon-X")
+ .first()
+ .hover()
+ .then(async () => {
+ await page
+ .getByText("Remove filter", {
+ exact: false,
+ })
+ .first()
+ .isVisible();
+ });
- await Promise.all(
- specificTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()),
- );
+ await expect(page.getByTestId("disclosure-inputs")).toBeVisible();
+ await expect(page.getByTestId("disclosure-outputs")).toBeVisible();
+ await expect(page.getByTestId("disclosure-prompts")).toBeVisible();
+ await expect(page.getByTestId("disclosure-models")).toBeVisible();
+ await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
+ await expect(page.getByTestId("disclosure-agents")).toBeVisible();
+ await expect(page.getByTestId("disclosure-chains")).toBeVisible();
+ await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
+
+ await expect(page.getByTestId("inputsChat Input")).toBeVisible();
+ await expect(page.getByTestId("outputsChat Output")).toBeVisible();
+ await expect(page.getByTestId("promptsPrompt")).toBeVisible();
+ await expect(page.getByTestId("modelsAmazon Bedrock")).toBeVisible();
+ await expect(page.getByTestId("helpersChat Memory")).toBeVisible();
+ await expect(page.getByTestId("agentsCSVAgent")).toBeVisible();
+ await expect(page.getByTestId("chainsConversationChain")).toBeVisible();
+ await expect(page.getByTestId("prototypesConditional Router")).toBeVisible();
await page.getByPlaceholder("Search").click();
- let notVisibleTestIds = [
- "inputsChat Input",
- "outputsChat Output",
- "promptsPrompt",
- "modelsAmazon Bedrock",
- "helpersChat Memory",
- "agentsTool Calling Agent",
- "chainsConversationChain",
- "prototypesConditional Router",
- ];
-
- await Promise.all(
- notVisibleTestIds.map((id) =>
- expect(page.getByTestId(id)).not.toBeVisible(),
- ),
- );
+ await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
+ await expect(page.getByTestId("outputsChat Output")).not.toBeVisible();
+ await expect(page.getByTestId("promptsPrompt")).not.toBeVisible();
+ await expect(page.getByTestId("modelsAmazon Bedrock")).not.toBeVisible();
+ await expect(page.getByTestId("helpersChat Memory")).not.toBeVisible();
+ await expect(page.getByTestId("agentsTool Calling Agent")).not.toBeVisible();
+ await expect(page.getByTestId("chainsConversationChain")).not.toBeVisible();
+ await expect(
+ page.getByTestId("prototypesConditional Router"),
+ ).not.toBeVisible();
await page.getByTestId("handle-apirequest-shownode-headers-left").click();
- disclosureTestIds = [
- "disclosure-data",
- "disclosure-helpers",
- "disclosure-vector stores",
- "disclosure-utilities",
- "disclosure-prototypes",
- "disclosure-retrievers",
- "disclosure-tools",
- ];
+ await expect(page.getByTestId("disclosure-data")).toBeVisible();
+ await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
+ await expect(page.getByTestId("disclosure-vector stores")).toBeVisible();
+ await expect(page.getByTestId("disclosure-utilities")).toBeVisible();
+ await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
+ await expect(page.getByTestId("disclosure-retrievers")).toBeVisible();
+ await expect(page.getByTestId("disclosure-embeddings")).toBeVisible();
+ await expect(page.getByTestId("disclosure-tools")).toBeVisible();
- specificTestIds = [
- "dataAPI Request",
- "helpersChat Memory",
- "vectorstoresAstra DB",
- "toolsSearch API",
- "prototypesSub Flow",
- "retrieversSelf Query Retriever",
- ];
+ await expect(page.getByTestId("dataAPI Request")).toBeVisible();
+ await expect(page.getByTestId("helpersChat Memory")).toBeVisible();
+ await expect(page.getByTestId("vectorstoresAstra DB")).toBeVisible();
+ await expect(page.getByTestId("toolsSearch API")).toBeVisible();
+ await expect(page.getByTestId("prototypesSub Flow")).toBeVisible();
+ await expect(
+ page.getByTestId("retrieversSelf Query Retriever"),
+ ).toBeVisible();
+ await expect(page.getByTestId("helpersSplit Text")).toBeVisible();
+ await expect(page.getByTestId("toolsSearch API")).toBeVisible();
- await Promise.all(
- disclosureTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()),
- );
+ await page.getByTestId("icon-X").first().click();
- await Promise.all(
- specificTestIds.map((id) => expect(page.getByTestId(id)).toBeVisible()),
- );
-
- await page.getByPlaceholder("Search").click();
-
- notVisibleTestIds = [
- "dataAPI Request",
- "helpersChat Memory",
- "vectorstoresAstra DB",
- "toolsSearch API",
- "prototypesSub Flow",
- "retrieversSelf Query Retriever",
- "textsplittersCharacterTextSplitter",
- ];
-
- await Promise.all(
- notVisibleTestIds.map((id) =>
- expect(page.getByTestId(id)).not.toBeVisible(),
- ),
- );
+ await expect(page.getByTestId("dataAPI Request")).not.toBeVisible();
+ await expect(page.getByTestId("helpersChat Memory")).not.toBeVisible();
+ await expect(page.getByTestId("vectorstoresAstra DB")).not.toBeVisible();
+ await expect(page.getByTestId("toolsSearch API")).not.toBeVisible();
+ await expect(page.getByTestId("prototypesSub Flow")).not.toBeVisible();
+ await expect(
+ page.getByTestId("retrieversSelf Query Retriever"),
+ ).not.toBeVisible();
+ await expect(page.getByTestId("helpersSplit Text")).not.toBeVisible();
+ await expect(page.getByTestId("toolsSearch API")).not.toBeVisible();
});
diff --git a/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts b/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts
index 3f2a4142b..bf6554ada 100644
--- a/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts
+++ b/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts
@@ -1,6 +1,4 @@
import { expect, test } from "@playwright/test";
-import * as dotenv from "dotenv";
-import path from "path";
test("should be able to move flow from folder, rename it and be displayed on correct folder", async ({
page,
diff --git a/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts b/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts
index 1cb1d2b54..4ac3987a8 100644
--- a/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts
+++ b/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts
@@ -1,6 +1,6 @@
import { expect, test } from "@playwright/test";
-test("should be able to see output preview from grouped components", async ({
+test("should be able to see output preview from grouped components and connect components with a single click", async ({
page,
}) => {
await page.goto("/");
@@ -51,7 +51,7 @@ test("should be able to see output preview from grouped components", async ({
.hover()
.then(async () => {
await page.mouse.down();
- await page.mouse.move(-1000, 500);
+ await page.mouse.move(-600, 300);
await page.waitForTimeout(400);
});
@@ -71,7 +71,7 @@ test("should be able to see output preview from grouped components", async ({
.hover()
.then(async () => {
await page.mouse.down();
- await page.mouse.move(-1000, 800);
+ await page.mouse.move(-600, 300);
await page.waitForTimeout(400);
});
@@ -86,7 +86,7 @@ test("should be able to see output preview from grouped components", async ({
.hover()
.then(async () => {
await page.mouse.down();
- await page.mouse.move(-800, 800);
+ await page.mouse.move(-600, 300);
await page.waitForTimeout(400);
});
@@ -101,7 +101,7 @@ test("should be able to see output preview from grouped components", async ({
.hover()
.then(async () => {
await page.mouse.down();
- await page.mouse.move(-200, 800);
+ await page.mouse.move(-600, 300);
await page.waitForTimeout(200);
});
@@ -117,7 +117,7 @@ test("should be able to see output preview from grouped components", async ({
.hover()
.then(async () => {
await page.mouse.down();
- await page.mouse.move(-200, 500);
+ await page.mouse.move(-600, 300);
});
await page.mouse.up();
@@ -134,13 +134,118 @@ test("should be able to see output preview from grouped components", async ({
const elementCombineTextOutput0 = await page
.getByTestId("handle-combinetext-shownode-combined text-right")
.nth(0);
- await elementCombineTextOutput0.hover();
- await page.mouse.down();
+ await elementCombineTextOutput0.click();
+
+ const blockedHandle = await page
+ .getByTestId("gradient-handle-textinput-shownode-text-right")
+ .nth(2);
+ const secondBlockedHandle = await page
+ .getByTestId("gradient-handle-combinetext-shownode-combined text-right")
+ .nth(2);
+ const thirdBlockedHandle = await page
+ .getByTestId("gradient-handle-textoutput-shownode-text-right")
+ .nth(0);
+
+ const hasGradient = await blockedHandle?.evaluate((el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ style.backgroundImage.includes("conic-gradient") &&
+ style.backgroundImage.includes("rgb(203, 213, 225)")
+ );
+ });
+
+ await page.waitForTimeout(500);
+
+ const secondHasGradient = await secondBlockedHandle?.evaluate((el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ style.backgroundImage.includes("conic-gradient") &&
+ style.backgroundImage.includes("rgb(203, 213, 225)")
+ );
+ });
+
+ await page.waitForTimeout(500);
+
+ const thirdHasGradient = await thirdBlockedHandle?.evaluate((el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ style.backgroundImage.includes("conic-gradient") &&
+ style.backgroundImage.includes("rgb(203, 213, 225)")
+ );
+ });
+
+ await page.waitForTimeout(500);
+
+ expect(hasGradient).toBe(true);
+ expect(secondHasGradient).toBe(true);
+ expect(thirdHasGradient).toBe(true);
+
+ const unlockedHandle = await page
+ .getByTestId("gradient-handle-textinput-shownode-text-left")
+ .last();
+ const secondUnlockedHandle = await page
+ .getByTestId("gradient-handle-combinetext-shownode-second text-left")
+ .last();
+ const thirdUnlockedHandle = await page
+ .getByTestId("gradient-handle-combinetext-shownode-second text-left")
+ .first();
+ const fourthUnlockedHandle = await page
+ .getByTestId("gradient-handle-textoutput-shownode-text-left")
+ .first();
+
+ const hasGradientUnlocked = await unlockedHandle?.evaluate((el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ style.backgroundImage.includes("conic-gradient") &&
+ style.backgroundImage.includes("rgb(79, 70, 229)")
+ );
+ });
+
+ await page.waitForTimeout(500);
+
+ const secondHasGradientUnlocked = await secondUnlockedHandle?.evaluate(
+ (el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ style.backgroundImage.includes("conic-gradient") &&
+ style.backgroundImage.includes("rgb(79, 70, 229)")
+ );
+ },
+ );
+
+ await page.waitForTimeout(500);
+
+ const thirdHasGradientLocked = await thirdUnlockedHandle?.evaluate((el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ style.backgroundImage.includes("conic-gradient") &&
+ style.backgroundImage.includes("rgb(203, 213, 225)")
+ );
+ });
+
+ await page.waitForTimeout(500);
+
+ const fourthHasGradientUnlocked = await fourthUnlockedHandle?.evaluate(
+ (el) => {
+ const style = window.getComputedStyle(el);
+ return (
+ style.backgroundImage.includes("conic-gradient") &&
+ style.backgroundImage.includes("rgb(79, 70, 229)")
+ );
+ },
+ );
+
+ await page.waitForTimeout(500);
+
+ expect(hasGradientUnlocked).toBe(true);
+ expect(secondHasGradientUnlocked).toBe(true);
+ expect(thirdHasGradientLocked).toBe(true);
+ expect(fourthHasGradientUnlocked).toBe(true);
+
const elementCombineTextInput1 = await page
.getByTestId("handle-combinetext-shownode-first text-left")
.nth(1);
- await elementCombineTextInput1.hover();
- await page.mouse.up();
+ await elementCombineTextInput1.click();
await page
.getByTestId("title-Combine Text")
@@ -157,68 +262,60 @@ test("should be able to see output preview from grouped components", async ({
const elementTextOutput0 = await page
.getByTestId("handle-textinput-shownode-text-right")
.nth(0);
- await elementTextOutput0.hover();
- await page.mouse.down();
+ await elementTextOutput0.click();
const elementGroupInput0 = await page.getByTestId(
"handle-groupnode-shownode-first text-left",
);
-
- await elementGroupInput0.hover();
- await page.mouse.up();
+ await elementGroupInput0.click();
//connection 3
const elementTextOutput1 = await page
.getByTestId("handle-textinput-shownode-text-right")
.nth(2);
- await elementTextOutput1.hover();
- await page.mouse.down();
+ await elementTextOutput1.click();
const elementGroupInput1 = await page
.getByTestId("handle-groupnode-shownode-second text-left")
.nth(1);
-
- await elementGroupInput1.hover();
- await page.mouse.up();
+ await elementGroupInput1.click();
//connection 4
const elementGroupOutput = await page
.getByTestId("handle-groupnode-shownode-combined text-right")
.nth(0);
- await elementGroupOutput.hover();
- await page.mouse.down();
+ await elementGroupOutput.click();
const elementTextOutputInput = await page
.getByTestId("handle-textoutput-shownode-text-left")
.nth(0);
- await elementTextOutputInput.hover();
- await page.mouse.up();
+ await elementTextOutputInput.click();
await page.getByTestId("textarea_str_input_value").nth(0).fill(randomName);
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(500);
await page
.getByTestId("textarea_str_input_value")
.nth(1)
.fill(secondRandomName);
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(500);
await page
.getByPlaceholder("Type something...", { exact: true })
.nth(6)
.fill(thirdRandomName);
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(500);
await page
.getByPlaceholder("Type something...", { exact: true })
.nth(3)
.fill("-");
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(500);
await page
.getByPlaceholder("Type something...", { exact: true })
.nth(4)
.fill("-");
- await page.waitForTimeout(3000);
+ await page.waitForTimeout(500);
await page.getByTestId("button_run_text output").last().click();
@@ -227,13 +324,13 @@ test("should be able to see output preview from grouped components", async ({
await page.getByText("built successfully").last().click({
timeout: 15000,
});
- await page.waitForTimeout(3000);
+ await page.waitForTimeout(500);
expect(
await page.getByTestId("output-inspection-combined text").first(),
).not.toBeDisabled();
await page.getByTestId("output-inspection-combined text").first().click();
- await page.waitForTimeout(1000);
+ await page.waitForTimeout(500);
await page.getByText("Component Output").isVisible();