feat: redesign sidebar with new components and features (#4307)

* Added required ShadCN Components

* Added required colors

* Added required icons

* Changed backend to not return beta tag

* Added sidebar categories api call

* Added required use-mobile hook

* Refactored icons to allow sizing

* Added type for sidebar category

* Allowed no name on shortcut display

* Added flow sidebar component that uses ShadCN sidebar

* Added SidebarDraggableComponent with the new style

* Replaced sidebar with current one on FlowPage

* Added sidebar fixed footer with options to go to store and add custom component

* Updated sidebar categories

* Updated background color for sidebar

* Changed size of Components title

* Added color to PageComponent

* Added required icons

* added CustomComponent category and removed custom component from helpers

* create hook useAddComponent

* Use hook to add components both in PageComponent and flowSidebarComponent

* Implement search

* Implement searching and filtering by clicking on edge

* Added check to see if store is present

* Updated colors to match new color schema

* Changed styling of filter component

* Added Beta and Legacy badges

* Implement Show Beta Components and Show Legacy Components

* Fixed styling for sidebar config when collapsed

* Refactored search to filter for tags

* Refactor useeffect

* Updated config button styling

* Implemented keyboard navigation

* Fixed filtering

* Updated color of canvas

* Implemented disclosure on sidebar settings and fetched bundles

* Added temp sidebar bundles

* Fixed badge styling

* Added bundles to categories response in frontend

* Added legacy to components

* Added link to store instead of langflow.store

* Added required data-testids

* Fixed tests to use new data-testids and new sidebar disposition

* Fix github star bug

* Fixed tests that used the custom component

* Changed test to test beta and legacy checkers

* added a test for keyboard navigation on sidebar

* Added a test to check component add by hover the plus button

* [autofix.ci] apply automated fixes

* updated sidebar switch change

* Removed changes on Backend and used only Frontend constants for categories

* merge fix

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* 📝 (custom_component): Add CustomComponent class with input and output definitions
📝 (custom_component): Create CustomComponent class with display name, description, and documentation link
📝 (custom_component): Define input and output properties for CustomComponent class
📝 (custom_component): Implement build_output method in CustomComponent class
📝 (Ollama): Remove unnecessary whitespace in SvgOllama component

* formatting

* ♻️ (custom_component/__init__.py): refactor import statement to match the correct case of the file name for better consistency and readability

* 🔧 (FlowPage/index.tsx): remove FlowToolbar component when view prop is false to improve UI consistency

* 📝 (integration-side-bar.spec.ts): Comment out unnecessary code blocks related to API requests and modals to improve test readability and focus on the main test scenarios.

* [autofix.ci] apply automated fixes

* ✨ (flowSidebarComponent/index.tsx): add data-testid attribute to Sidebar component for testing purposes
🔧 (integration-side-bar.spec.ts): update test to use new data-testid attribute 'shad-sidebar' for Sidebar component to match changes in the codebase

* ✨ (stop-building.spec.ts): refactor test case to use a more descriptive test element for clicking on the sidebar custom component button

* format

* 🐛 (AstraDB): Fix variable naming inconsistency for isDark to isdark in AstraDB component
🐛 (HCD): Fix variable naming inconsistency for isDark to isdark in HCD component
🐛 (index.tsx): Fix variable naming inconsistency for isDark to isdark in index.tsx files
📝 (flowSidebarComponent): Refactor search functionality to normalize search terms and improve metadata search
📝 (filterEdge-shard-1.spec.ts): Update test to check visibility of specific model specs in the sidebar

* [autofix.ci] apply automated fixes

* ✅ (decisionFlow.spec.ts): add a delay of 500ms to ensure proper timing in the test case execution

* ✨ (decisionFlow.spec.ts): Update the X positions of elements in the flow to improve the visual representation and alignment of the elements.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Lucas Oliveira 2024-10-31 19:51:36 -03:00 • committed by GitHub
commit 794848d5e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
122 changed files with 2365 additions and 802 deletions

View file

@ -14,7 +14,8 @@
"@million/lint": "^1.0.0-rc.26",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0",

View file

@ -9,7 +9,8 @@
"@million/lint": "^1.0.0-rc.26",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0",

View file

@ -9,7 +9,7 @@ export const GithubStarComponent = () => {
<FaGithub className="h-4 w-4 text-black dark:text-[white]" />
<div className="hidden text-black dark:text-[white] lg:block">Star</div>
<div className="header-github-display text-black dark:text-[white]">
{stars.toLocaleString() ?? 0}
{stars?.toLocaleString() ?? 0}
</div>
</div>
);

View file

@ -15,13 +15,15 @@ const badgeVariants = cva(
destructive:
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
outline: "text-primary/80 border-ring/60",
secondaryStatic: "bg-input text-muted-foreground",
pinkStatic: "bg-accent-pink text-accent-pink-foreground",
},
size: {
sm: "h-4 text-xs",
md: "h-5 text-sm",
lg: "h-6 text-base",
sq: "h-6 text-sm font-normal rounded-md",
xq: "h-5 text-xs font-normal rounded-md",
sq: "h-6 px-1.5 text-sm font-medium rounded-md",
xq: "h-5 px-1 text-xs font-medium rounded-md",
},
},
defaultVariants: {

View file

@ -18,8 +18,10 @@ const buttonVariants = cva(
"border bg-background text-secondary-foreground hover:bg-muted dark:hover:bg-muted hover:shadow-sm",
secondary:
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
ghost: "hover:bg-accent hover:text-accent-foreground",
menu: "hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0",
ghost: "text-foreground hover:bg-accent hover:text-accent-foreground",
ghostActive:
"bg-muted text-foreground hover:bg-accent hover:text-accent-foreground",
menu: "hover:bg-muted hover:text-accent-foreground focus:!ring-0 focus-visible:!ring-0",
"menu-active":
"font-semibold hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0",
link: "underline-offset-4 hover:underline text-primary",
@ -29,7 +31,9 @@ const buttonVariants = cva(
sm: "h-9 px-3 rounded-md",
xs: "py-0.5 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "py-1 px-1 rounded-md",
iconMd: "p-1.5 rounded-md",
icon: "p-1 rounded-md",
iconSm: "p-0.5 rounded-md",
"node-toolbar": "py-[6px] px-[6px] rounded-md",
},
},

View file

@ -0,0 +1,11 @@
"use client";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleContent, CollapsibleTrigger };

View file

@ -0,0 +1,191 @@
"use client";
import {
AnimatePresence,
motion,
MotionConfig,
Transition,
Variant,
Variants,
} from "framer-motion";
import * as React from "react";
import { createContext, useContext, useEffect, useId, useState } from "react";
import { cn } from "../../utils/utils";
type DisclosureContextType = {
open: boolean;
toggle: () => void;
variants?: { expanded: Variant; collapsed: Variant };
};
const DisclosureContext = createContext<DisclosureContextType | undefined>(
undefined,
);
type DisclosureProviderProps = {
children: React.ReactNode;
open: boolean;
onOpenChange?: (open: boolean) => void;
variants?: { expanded: Variant; collapsed: Variant };
};
function DisclosureProvider({
children,
open: openProp,
onOpenChange,
variants,
}: DisclosureProviderProps) {
const [internalOpenValue, setInternalOpenValue] = useState<boolean>(openProp);
useEffect(() => {
setInternalOpenValue(openProp);
}, [openProp]);
const toggle = () => {
const newOpen = !internalOpenValue;
setInternalOpenValue(newOpen);
if (onOpenChange) {
onOpenChange(newOpen);
}
};
return (
<DisclosureContext.Provider
value={{
open: internalOpenValue,
toggle,
variants,
}}
>
{children}
</DisclosureContext.Provider>
);
}
function useDisclosure() {
const context = useContext(DisclosureContext);
if (!context) {
throw new Error("useDisclosure must be used within a DisclosureProvider");
}
return context;
}
type DisclosureProps = {
open?: boolean;
onOpenChange?: (open: boolean) => void;
children: React.ReactNode;
className?: string;
variants?: { expanded: Variant; collapsed: Variant };
transition?: Transition;
};
export function Disclosure({
open: openProp = false,
onOpenChange,
children,
className,
transition,
variants,
}: DisclosureProps) {
return (
<MotionConfig transition={transition}>
<div className={className}>
<DisclosureProvider
open={openProp}
onOpenChange={onOpenChange}
variants={variants}
>
{React.Children.toArray(children)[0]}
{React.Children.toArray(children)[1]}
</DisclosureProvider>
</div>
</MotionConfig>
);
}
export function DisclosureTrigger({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
const { toggle, open } = useDisclosure();
return (
<>
{React.Children.map(children, (child) => {
return React.isValidElement(child)
? React.cloneElement(child, {
onClick: toggle,
role: "button",
"aria-expanded": open,
tabIndex: 0,
onKeyDown: (e: { key: string; preventDefault: () => void }) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle();
}
},
className: cn(
className,
(child as React.ReactElement).props.className,
),
...(child as React.ReactElement).props,
})
: child;
})}
</>
);
}
export function DisclosureContent({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
const { open, variants } = useDisclosure();
const uniqueId = useId();
const BASE_VARIANTS: Variants = {
expanded: {
height: "auto",
opacity: 1,
},
collapsed: {
height: 0,
opacity: 0,
},
};
const combinedVariants = {
expanded: { ...BASE_VARIANTS.expanded, ...variants?.expanded },
collapsed: { ...BASE_VARIANTS.collapsed, ...variants?.collapsed },
};
return (
<div className={cn("overflow-hidden", className)}>
<AnimatePresence initial={false}>
{open && (
<motion.div
id={uniqueId}
initial="collapsed"
animate="expanded"
exit="collapsed"
variants={combinedVariants}
>
{children}
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default {
Disclosure,
DisclosureProvider,
DisclosureTrigger,
DisclosureContent,
};

View file

@ -65,7 +65,7 @@ const SheetContent = React.forwardRef<
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>

View file

@ -5,10 +5,12 @@ import { VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react";
import * as React from "react";
import { useIsMobile } from "../../hooks/use-mobile";
import { cn } from "../../utils/utils";
import { Button } from "./button";
import { Input } from "./input";
import { Separator } from "./separator";
import { Sheet, SheetContent } from "./sheet";
import { Skeleton } from "./skeleton";
import {
Tooltip,
@ -20,13 +22,17 @@ import {
const SIDEBAR_COOKIE_NAME = "sidebar:state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "19rem";
const SIDEBAR_WIDTH_ICON = "4rem";
const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContext = {
state: "expanded" | "collapsed";
open: boolean;
setOpen: (open: boolean) => void;
openMobile: boolean;
setOpenMobile: (open: boolean) => void;
isMobile: boolean;
toggleSidebar: () => void;
};
@ -47,7 +53,6 @@ const SidebarProvider = React.forwardRef<
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
width?: string;
}
>(
(
@ -58,11 +63,13 @@ const SidebarProvider = React.forwardRef<
className,
style,
children,
width = SIDEBAR_WIDTH,
...props
},
ref,
) => {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen);
@ -85,8 +92,10 @@ const SidebarProvider = React.forwardRef<
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return setOpen((open) => !open);
}, [setOpen]);
return isMobile
? setOpenMobile((open) => !open)
: setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@ -113,9 +122,20 @@ const SidebarProvider = React.forwardRef<
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, toggleSidebar],
[
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
],
);
return (
@ -124,7 +144,7 @@ const SidebarProvider = React.forwardRef<
<div
style={
{
"--sidebar-width": width,
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
@ -164,33 +184,47 @@ const Sidebar = React.forwardRef<
},
ref,
) => {
const { state } = useSidebar();
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === "none") {
return (
<div
className={cn(
"group flex h-full w-[--sidebar-width] flex-col bg-background text-foreground",
"flex h-full w-[--sidebar-width] flex-col bg-background text-foreground",
className,
)}
data-side={side}
ref={ref}
{...props}
>
<div
data-sidebar="sidebar"
className="flex h-full w-full flex-col group-data-[side=left]:border-r group-data-[side=right]:border-l"
>
{children}
</div>
{children}
</div>
);
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
className="w-[--sidebar-width] bg-background p-0 text-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
);
}
return (
<div
ref={ref}
className="group peer relative block h-full flex-col"
className="group peer relative flex h-full flex-col md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
@ -205,16 +239,11 @@ const Sidebar = React.forwardRef<
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
// Keep icon width on mobile when collapsed
"max-sm:w-[--sidebar-width-icon]",
)}
/>
<div
className={cn(
"absolute inset-y-0 z-50 flex h-full transition-[left,right,width] duration-200 ease-linear",
// Adjust width based on state and device
"w-[--sidebar-width]",
"max-sm:group-data-[state=collapsed]:w-[--sidebar-width-icon]",
"absolute inset-y-0 z-10 hidden h-full w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
@ -222,20 +251,13 @@ const Sidebar = React.forwardRef<
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
// Position absolute relative to parent container on mobile
"max-sm:absolute max-sm:h-[100%] max-sm:group-data-[state=expanded]:bg-background/80",
className,
)}
{...props}
>
<div
data-sidebar="sidebar"
className={cn(
"flex h-full w-full flex-col bg-background",
"group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-border group-data-[variant=floating]:shadow",
// Add shadow on mobile
"max-sm:shadow-lg",
)}
className="flex h-full w-full flex-col bg-background group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-border group-data-[variant=floating]:shadow"
>
{children}
</div>
@ -258,7 +280,7 @@ const SidebarTrigger = React.forwardRef<
data-sidebar="trigger"
variant="ghost"
size="icon"
className={cn("h-8 w-8", className)}
className={cn("h-7 w-7", className)}
onClick={(event) => {
onClick?.(event);
toggleSidebar();
@ -310,7 +332,7 @@ const SidebarInset = React.forwardRef<
ref={ref}
className={cn(
"relative flex min-h-svh flex-1 flex-col bg-background",
"peer-data-[variant=inset]:m-2 peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 peer-data-[variant=inset]:ml-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow",
"peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className,
)}
{...props}
@ -328,7 +350,7 @@ const SidebarInput = React.forwardRef<
ref={ref}
data-sidebar="input"
className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-1 focus-visible:ring-ring",
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-ring",
className,
)}
{...props}
@ -426,7 +448,7 @@ const SidebarGroupLabel = React.forwardRef<
ref={ref}
data-sidebar="group-label"
className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0",
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
@ -447,9 +469,9 @@ const SidebarGroupAction = React.forwardRef<
ref={ref}
data-sidebar="group-action"
className={cn(
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0",
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:-inset-2 after:hidden",
"after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden",
className,
)}
@ -499,12 +521,11 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-ring transition-[width,height,padding] hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-accent data-[active=true]:font-medium data-[active=true]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:hover:text-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-ring transition-[width,height,padding] hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-accent data-[active=true]:font-medium data-[active=true]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:hover:text-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default:
"text-secondary-foreground hover:bg-accent hover:text-accent-foreground ",
default: "hover:bg-accent hover:text-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--border))] hover:bg-accent hover:text-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--accent))]",
},
@ -542,7 +563,7 @@ const SidebarMenuButton = React.forwardRef<
ref,
) => {
const Comp = asChild ? Slot : "button";
const { state } = useSidebar();
const { isMobile, state } = useSidebar();
const button = (
<Comp
@ -571,7 +592,7 @@ const SidebarMenuButton = React.forwardRef<
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed"}
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip>
@ -594,9 +615,9 @@ const SidebarMenuAction = React.forwardRef<
ref={ref}
data-sidebar="menu-action"
className={cn(
"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 peer-hover/menu-button:text-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:-inset-2 after:hidden",
"after:absolute after:-inset-2 after:md:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
@ -710,7 +731,7 @@ const SidebarMenuSubButton = React.forwardRef<
data-size={size}
data-active={isActive}
className={cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-foreground outline-none ring-ring hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-accent-foreground",
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-foreground outline-none ring-ring hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-accent-foreground",
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",

View file

@ -908,7 +908,7 @@ export const LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV =
Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) -
Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) * 0.1;
export const TEXT_FIELD_TYPES: string[] = ["str", "SecretStr"];
export const NODE_WIDTH = 400;
export const NODE_WIDTH = 384;
export const NODE_HEIGHT = NODE_WIDTH * 3;
export const SHORTCUT_KEYS = ["cmd", "ctrl", "alt", "shift"];

View file

@ -21,6 +21,7 @@ export const URLs = {
VALIDATE: `validate`,
CONFIG: `config`,
STARTER_PROJECTS: `starter-projects`,
SIDEBAR_CATEGORIES: `sidebar_categories`,
} as const;
export function getURL(key: keyof typeof URLs, params: any = {}) {

View file

@ -1,22 +1,21 @@
import { useEffect, useState } from "react";
import * as React from "react";
export function useMobile(breakpoint: number = 768) {
const [isMobile, setIsMobile] = useState(false);
const MOBILE_BREAKPOINT = 768;
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < breakpoint);
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener("change", onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener("change", onChange);
}, []);
// Check initially
checkMobile();
// Add event listener
window.addEventListener("resize", checkMobile);
// Cleanup
return () => window.removeEventListener("resize", checkMobile);
}, [breakpoint]);
return isMobile;
return !!isMobile;
}

View file

@ -0,0 +1,70 @@
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 { getNodeId } from "@/utils/reactflowUtils";
import { getNodeRenderType } from "@/utils/utils";
import { useCallback } from "react";
import { useStoreApi } from "reactflow";
export function useAddComponent() {
const store = useStoreApi();
const paste = useFlowStore((state) => state.paste);
const addComponent = useCallback(
(
component: APIClassType,
type: string,
position?: { x: number; y: number },
) => {
track("Component Added", { componentType: component.display_name });
const {
height,
width,
transform: [transformX, transformY, zoomLevel],
} = store.getState();
const zoomMultiplier = 1 / zoomLevel;
let pos;
if (position) {
pos = position;
} else {
let centerX, centerY;
centerX = -transformX * zoomMultiplier + (width * zoomMultiplier) / 2;
centerY = -transformY * zoomMultiplier + (height * zoomMultiplier) / 2;
const nodeOffset = NODE_WIDTH / 2;
pos = {
x: -nodeOffset,
y: -nodeOffset,
paneX: centerX,
paneY: centerY,
};
}
const newId = getNodeId(type);
const newNode: NodeType = {
id: newId,
type: getNodeRenderType("genericnode"),
position: { x: 0, y: 0 },
data: {
node: component,
type: type,
id: newId,
},
};
paste({ nodes: [newNode], edges: [] }, pos);
},
[store, paste],
);
return addComponent;
}

View file

@ -9,11 +9,11 @@ const AstraSVG = (props) => (
>
<path
d="M60.2338 0.25H0.000244141V67.75H60.2338L75.365 56.0752V11.9248L60.2338 0.25ZM11.6732 11.9248H63.692V56.0874H11.6732V11.9248Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"}
fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/>
<path
d="M162.038 12.415V1H106.964L92.0097 12.415V28.088L106.964 39.503H154.962V55.585H94.9883V67H151.546L166.5 55.585V39.503L151.546 28.088H103.547V12.415H162.038Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"}
fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/>
</svg>
);

View file

@ -6,6 +6,6 @@ export const AstraDBIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
const isDark = useDarkStore((state) => state.dark);
return <AstraSVG ref={ref} isDark={isDark} {...props} />;
const isdark = useDarkStore((state) => state.dark);
return <AstraSVG ref={ref} isdark={isdark} {...props} />;
});

View file

@ -6,6 +6,7 @@ export const SvgAzure = (props) => (
height="35"
version="1"
viewBox="0 0 750 750"
{...props}
>
<defs>
<filter id="ac4759936b" width="100%" height="100%" x="0%" y="0%">

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
const SvgGroqLogo = ({ color, ...props }) => (
const SvgGroqLogo = ({ ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
aria-label="groq logo"
@ -7,7 +7,8 @@ const SvgGroqLogo = ({ color, ...props }) => (
height="48"
viewBox="0 0 24 24"
fill="none"
class="fill-foreground"
className="fill-foreground"
{...props}
>
<path
fill="#F55036"

View file

@ -11,11 +11,11 @@ const HCDSVG = (props) => (
{/* <rect width="96" height="96" rx="6" fill="white"/> */}
<path
d="M38.0469 33H12V62.1892H38.0469L44.5902 57.1406V38.0485L38.0469 33ZM17.0478 38.0485H39.5424V57.1459H17.0478V38.0485Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"}
fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/>
<path
d="M82.0705 38.2605V33.3243H58.2546L51.788 38.2605V45.038L58.2546 49.9742H79.0107V56.9286H53.076V61.8648H77.5334L84 56.9286V49.9742L77.5334 45.038H56.7772V38.2605H82.0705Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"}
fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/>
</g>
<defs>

View file

@ -4,8 +4,8 @@ import HCDSVG from "./HCD";
export const HCDIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => {
const isDark = useDarkStore((state) => state.dark);
const isdark = useDarkStore((state) => state.dark);
return <HCDSVG ref={ref} isDark={isDark} {...props} />;
return <HCDSVG ref={ref} isdark={isdark} {...props} />;
},
);

View file

@ -1,10 +1,10 @@
export const SvgOllama = (props) => (
<svg
width="646"
height="854"
viewBox="0 0 646 854"
fill="none"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
width="40"
height="40"
version="1"
viewBox="0 0 550 850"
{...props}
>
<path

View file

@ -6,6 +6,7 @@ const SvgMistralIcon = (props) => (
viewBox="0 0 256 233"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
{...props}
>
<title>Mistral AI</title>
<g>

View file

@ -11,12 +11,13 @@ import {
SidebarMenuItem,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { useMobile } from "@/hooks/use-mobile";
import { useIsMobile } from "../../../../hooks/use-mobile";
import { cn } from "@/utils/utils";
import { NavProps } from "../../../../types/templates/types";
export function Nav({ categories, currentTab, setCurrentTab }: NavProps) {
const isMobile = useMobile();
const isMobile = useIsMobile();
return (
<Sidebar collapsible={isMobile ? "icon" : "none"}>

View file

@ -14,8 +14,9 @@ import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
import { track } from "@/customization/utils/analytics";
import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { nodeColors, nodeColorsName } from "@/utils/styleUtils";
import { getNodeRenderType, isSupportedNodeTypes } from "@/utils/utils";
import { useAddComponent } from "@/hooks/useAddComponent";
import { nodeColorsName } from "@/utils/styleUtils";
import { isSupportedNodeTypes } from "@/utils/utils";
import _, { cloneDeep } from "lodash";
import {
KeyboardEvent,
@ -120,6 +121,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
const [isAddingNote, setIsAddingNote] = useState(false);
const [isHighlightingCursor, setIsHighlightingCursor] = useState(false);
const addComponent = useAddComponent();
const { zoomIn, zoomOut, fitView } = useReactFlow();
const { zoom } = useViewport();
@ -439,23 +441,10 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
event.dataTransfer.getData(datakey!),
);
track("Component Added", { componentType: data.node?.display_name });
const newId = getNodeId(data.type);
const newNode: NodeType = {
id: newId,
type: getNodeRenderType(datakey!),
position: { x: 0, y: 0 },
data: {
...data,
id: newId,
},
};
paste(
{ nodes: [newNode], edges: [] },
{ x: event.clientX, y: event.clientY },
);
addComponent(data.node!, data.type, {
x: event.clientX,
y: event.clientY,
});
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
takeSnapshot();
const position = {
@ -478,8 +467,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
});
}
},
// Specify dependencies for useCallback
[getNodeId, setNodes, takeSnapshot, paste],
[takeSnapshot, addComponent],
);
const onEdgeUpdateStart = useCallback(() => {
@ -596,7 +584,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
};
return (
<div className="h-full w-full" ref={reactFlowWrapper}>
<div className="h-full w-full bg-canvas" ref={reactFlowWrapper}>
{showCanvas ? (
<div id="react-flow-id" className="h-full w-full bg-canvas">
<ReactFlow

View file

@ -12,7 +12,7 @@ export function SidebarFilterComponent({
resetFilters: () => void;
}) {
return (
<div className="mb-0.5 flex w-full items-center justify-between rounded border bg-muted p-1 px-2 text-xs font-medium text-primary">
<div className="mb-0.5 flex w-full items-center justify-between rounded border bg-accent-indigo p-2 text-sm text-accent-indigo-foreground">
<div className="flex flex-1 items-center gap-1.5">
<ForwardedIconComponent
name="ListFilter"
@ -30,7 +30,12 @@ export function SidebarFilterComponent({
styleClasses="max-w-full"
content="Remove filter"
>
<Button unstyled className="shrink-0" onClick={resetFilters}>
<Button
unstyled
className="shrink-0"
onClick={resetFilters}
data-testid="sidebar-filter-reset"
>
<ForwardedIconComponent
name="X"
className="h-4 w-4 stroke-2"

View file

@ -0,0 +1,218 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import { useAddComponent } from "@/hooks/useAddComponent";
import { DragEventHandler, forwardRef, useRef, useState } from "react";
import IconComponent, {
ForwardedIconComponent,
} from "../../../../../../components/genericIconComponent";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "../../../../../../components/ui/select-custom";
import { useDarkStore } from "../../../../../../stores/darkStore";
import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
import { APIClassType } from "../../../../../../types/api";
import {
createFlowComponent,
downloadNode,
getNodeId,
} from "../../../../../../utils/reactflowUtils";
import { cn, removeCountFromString } from "../../../../../../utils/utils";
export const SidebarDraggableComponent = forwardRef(
(
{
sectionName,
display_name,
icon,
itemName,
error,
color,
onDragStart,
apiClass,
official,
beta,
legacy,
}: {
sectionName: string;
apiClass: APIClassType;
icon: string;
display_name: string;
itemName: string;
error: boolean;
color: string;
onDragStart: DragEventHandler<HTMLDivElement>;
official: boolean;
beta: boolean;
legacy: boolean;
},
ref,
) => {
const [open, setOpen] = useState(false);
const { deleteFlow } = useDeleteFlow();
const flows = useFlowsManagerStore((state) => state.flows);
const addComponent = useAddComponent();
const version = useDarkStore((state) => state.version);
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const popoverRef = useRef<HTMLDivElement>(null);
const handlePointerDown = (e) => {
if (!open) {
const rect = popoverRef.current?.getBoundingClientRect() ?? {
left: 0,
top: 0,
};
setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
}
};
function handleSelectChange(value: string) {
switch (value) {
case "download":
const type = removeCountFromString(itemName);
downloadNode(
createFlowComponent(
{ id: getNodeId(type), type, node: apiClass },
version,
),
);
break;
case "delete":
const flowId = flows?.find((f) => f.name === display_name);
if (flowId) deleteFlow({ id: flowId.id });
break;
}
}
const handleKeyDown = (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
addComponent(apiClass, itemName);
}
};
return (
<Select
onValueChange={handleSelectChange}
onOpenChange={(change) => setOpen(change)}
open={open}
key={itemName}
>
<div
onPointerDown={handlePointerDown}
onContextMenuCapture={(e) => {
e.preventDefault();
setOpen(true);
}}
key={itemName}
data-tooltip-id={itemName}
tabIndex={0}
onKeyDown={handleKeyDown}
className="rounded-md outline-none ring-ring focus-visible:ring-2"
>
<div
data-testid={sectionName + display_name}
id={sectionName + display_name}
className={cn(
"group/draggable flex cursor-grab items-center gap-2 rounded-md bg-muted p-3 hover:bg-accent",
error ? "cursor-not-allowed select-none" : "",
)}
draggable={!error}
style={{
borderLeftColor: color,
}}
onDragStart={onDragStart}
onDragEnd={() => {
document.body.removeChild(
document.getElementsByClassName("cursor-grabbing")[0],
);
}}
>
<ForwardedIconComponent name={icon} className="h-5 w-5 shrink-0" />
<div className="flex flex-1 items-center overflow-hidden">
<span className="truncate text-sm font-semibold">
{display_name}
</span>
{beta && (
<Badge
variant="pinkStatic"
size="sq"
className="ml-1.5 shrink-0"
>
BETA
</Badge>
)}
{legacy && (
<Badge
variant="secondaryStatic"
size="sq"
className="ml-1.5 shrink-0"
>
LEGACY
</Badge>
)}
</div>
<div className="flex shrink-0 items-center gap-1">
<Button
variant="ghost"
size="icon"
tabIndex={-1}
className="text-muted-foreground hover:text-primary group-focus/draggable:text-primary"
onClick={() => addComponent(apiClass, itemName)}
>
<ForwardedIconComponent
name="Plus"
className="h-4 w-4 shrink-0 opacity-0 transition-all group-hover/draggable:opacity-100 group-focus/draggable:opacity-100"
/>
</Button>
<div ref={popoverRef}>
<ForwardedIconComponent
name="GripVertical"
className="h-4 w-4 shrink-0 text-muted-foreground"
/>
<SelectTrigger tabIndex={-1}></SelectTrigger>
<SelectContent
position="popper"
side="bottom"
sideOffset={-25}
style={{
position: "absolute",
left: cursorPos.x,
top: cursorPos.y,
}}
>
<SelectItem value={"download"}>
<div className="flex">
<IconComponent
name="Download"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Download{" "}
</div>{" "}
</SelectItem>
{!official && (
<SelectItem value={"delete"}>
<div className="flex">
<IconComponent
name="Trash2"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Delete{" "}
</div>{" "}
</SelectItem>
)}
</SelectContent>
</div>
</div>
</div>
</div>
</Select>
);
},
);
export default SidebarDraggableComponent;

View file

@ -0,0 +1,708 @@
import Fuse from "fuse.js";
import { useEffect, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook"; // Import useHotkeys
import ForwardedIconComponent from "@/components/genericIconComponent";
import ShadTooltip from "@/components/shadTooltipComponent";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Disclosure,
DisclosureContent,
DisclosureTrigger,
} from "@/components/ui/disclosure";
import { Input } from "@/components/ui/input";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
} from "@/components/ui/sidebar";
import { Switch } from "@/components/ui/switch";
import { CustomLink } from "@/customization/components/custom-link";
import { useAddComponent } from "@/hooks/useAddComponent";
import { useStoreStore } from "@/stores/storeStore";
import {
nodeColors,
SIDEBAR_BUNDLES,
SIDEBAR_CATEGORIES,
} from "@/utils/styleUtils";
import { removeCountFromString } from "@/utils/utils";
import { cloneDeep } from "lodash";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType } from "../../../../types/api";
import { SidebarFilterComponent } from "../extraSidebarComponent/sidebarFilterComponent";
import sensitiveSort from "../extraSidebarComponent/utils/sensitive-sort";
import ShortcutDisplay from "../nodeToolbarComponent/shortcutDisplay";
import SidebarDraggableComponent from "./components/sidebarDraggableComponent";
export function FlowSidebarComponent() {
const [isInputFocused, setIsInputFocused] = useState(false);
const searchInputRef = useRef<HTMLInputElement | null>(null);
useHotkeys("/", (event) => {
event.preventDefault();
searchInputRef.current?.focus();
});
// Add this new useHotkeys hook
useHotkeys(
"esc",
(event) => {
event.preventDefault();
searchInputRef.current?.blur();
},
{
// Only enable this hotkey when the input is focused
enableOnFormTags: true,
enabled: isInputFocused,
},
);
const categories = SIDEBAR_CATEGORIES;
const bundles = SIDEBAR_BUNDLES;
const data = useTypesStore((state) => state.data);
const templates = useTypesStore((state) => state.templates);
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);
const [search, setSearch] = useState("");
const addComponent = useAddComponent();
const [fuse, setFuse] = useState<Fuse<any> | null>(null);
const [openCategories, setOpenCategories] = useState<string[]>([]);
const [showConfig, setShowConfig] = useState(false);
const [showBeta, setShowBeta] = useState(true);
const [showLegacy, setShowLegacy] = useState(false);
const hasResults = useMemo(() => {
return Object.values(dataFilter).some(
(category) => Object.keys(category).length > 0,
);
}, [dataFilter]);
useEffect(() => {
filterComponents();
}, [data, search, filterType, getFilterEdge, showBeta, showLegacy]);
function normalizeString(str: string): string {
return str.toLowerCase().replace(/_/g, " ").replace(/\s+/g, "");
}
function searchInMetadata(metadata: any, searchTerm: string): boolean {
if (!metadata || typeof metadata !== "object") return false;
return Object.entries(metadata).some(([key, value]) => {
if (typeof value === "string") {
return (
normalizeString(key).includes(searchTerm) ||
normalizeString(value).includes(searchTerm)
);
}
if (typeof value === "object") {
return searchInMetadata(value, searchTerm);
}
return false;
});
}
const filterComponents = () => {
let filteredData = cloneDeep(data);
// Apply search filter
if (search && fuse) {
const results = fuse.search(search);
filteredData = Object.fromEntries(
Object.entries(data).map(([category, items]) => {
const categoryResults = results.filter(
(result) => result.item.category === category,
);
const filteredItems = Object.fromEntries(
categoryResults.map((result) => [result.item.key, result.item]),
);
return [category, filteredItems];
}),
);
} else {
// Fallback to traditional search if Fuse.js is not available
const searchTerm = normalizeString(search);
filteredData = Object.fromEntries(
Object.entries(data).map(([category, items]) => {
const filteredItems = Object.fromEntries(
Object.entries(items).filter(
([key, item]) =>
normalizeString(key).includes(searchTerm) ||
normalizeString(item.display_name).includes(searchTerm) ||
normalizeString(category).includes(searchTerm) ||
(item.metadata && searchInMetadata(item.metadata, searchTerm)),
),
);
return [category, filteredItems];
}),
);
}
// Apply edge filter
if (getFilterEdge?.length > 0) {
filteredData = Object.fromEntries(
Object.entries(filteredData).map(([family, familyData]) => {
const edgeFilter = getFilterEdge.find((x) => x.family === family);
if (!edgeFilter) return [family, {}];
const filteredTypes = edgeFilter.type
.split(",")
.map((t) => t.trim())
.filter((t) => t !== "");
if (filteredTypes.length === 0) return [family, familyData];
const filteredFamilyData = Object.fromEntries(
Object.entries(familyData).filter(([key]) =>
filteredTypes.includes(key),
),
);
return [family, filteredFamilyData];
}),
);
}
// Apply beta filter
if (!showBeta) {
filteredData = Object.fromEntries(
Object.entries(filteredData).map(([category, items]) => [
category,
Object.fromEntries(
Object.entries(items).filter(([_, value]) => !value.beta),
),
]),
);
}
// Apply legacy filter
if (!showLegacy) {
filteredData = Object.fromEntries(
Object.entries(filteredData).map(([category, items]) => [
category,
Object.fromEntries(
Object.entries(items).filter(([_, value]) => !value.legacy),
),
]),
);
}
setFilterData(filteredData);
if (search !== "" || filterType || getFilterEdge.length > 0) {
setOpenCategories(
Object.keys(filteredData).filter(
(cat) => Object.keys(filteredData[cat]).length > 0,
),
);
}
};
useEffect(() => {
if (search === "" && getFilterEdge.length === 0) {
setOpenCategories([]);
}
}, [search, getFilterEdge]);
function handleSearchInput(e: string) {
setSearch(e);
filterComponents();
}
function onDragStart(
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType },
): void {
//start drag event
var crt = event.currentTarget.cloneNode(true);
crt.style.position = "absolute";
crt.style.width = "215px";
crt.style.top = "-500px";
crt.style.right = "-500px";
crt.classList.add("cursor-grabbing");
document.body.appendChild(crt);
event.dataTransfer.setDragImage(crt, 0, 0);
event.dataTransfer.setData("genericNode", JSON.stringify(data));
}
useEffect(() => {
// show components with error on load
let errors: string[] = [];
Object.keys(templates).forEach((component) => {
if (templates[component].error) {
errors.push(component);
}
});
if (errors.length > 0)
setErrorData({ title: " Components with errors: ", list: errors });
}, []);
useEffect(() => {
if (getFilterEdge.length !== 0) {
setSearch("");
}
}, [getFilterEdge, data]);
useEffect(() => {
const options = {
keys: ["display_name", "description", "type"],
threshold: 0.3,
};
const fuseData = Object.entries(data).flatMap(([category, items]) =>
Object.entries(items).map(([key, value]) => ({
...value,
category,
key,
})),
);
setFuse(new Fuse(fuseData, options));
handleSearchInput(search);
}, [data]);
const customComponent = useMemo(() => {
return data?.["custom_component"]?.["CustomComponent"] ?? null;
}, [data]);
const handleKeyDown = (
e: React.KeyboardEvent<HTMLDivElement>,
name: string,
) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setOpenCategories((prev) =>
prev.includes(name)
? prev.filter((cat) => cat !== name)
: [...prev, name],
);
}
};
return (
<Sidebar data-testid="shad-sidebar">
<SidebarHeader className="flex w-full flex-col gap-4 p-4 pb-1">
<Disclosure open={showConfig} onOpenChange={setShowConfig}>
<div className="flex w-full items-center justify-between">
<h3 className="text-sm font-semibold">Components</h3>
<DisclosureTrigger>
<Button
variant={showConfig ? "ghostActive" : "ghost"}
size="iconMd"
data-testid="sidebar-options-trigger"
>
<ForwardedIconComponent
name="SlidersHorizontal"
className="h-4 w-4"
/>
</Button>
</DisclosureTrigger>
</div>
<DisclosureContent>
<div className="flex flex-col gap-7 border-b pb-7 pt-5">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium">
Show{" "}
<Badge variant="pinkStatic" size="sq">
BETA
</Badge>{" "}
Components
</span>
</div>
<Switch
checked={showBeta}
onCheckedChange={setShowBeta}
data-testid="sidebar-beta-switch"
/>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium">
Show{" "}
<Badge variant="secondaryStatic" size="sq">
LEGACY
</Badge>{" "}
Components
</span>
</div>
<Switch
checked={showLegacy}
onCheckedChange={setShowLegacy}
data-testid="sidebar-legacy-switch"
/>
</div>
</div>
</DisclosureContent>
</Disclosure>
<div className="relative w-full flex-1">
<ForwardedIconComponent
name="Search"
className="absolute inset-y-0 left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-primary"
/>
<Input
ref={searchInputRef}
type="search"
data-testid="sidebar-search-input"
className="w-full rounded-lg bg-background pl-8 text-sm"
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
value={search}
onChange={(e) => handleSearchInput(e.target.value)}
/>
{!isInputFocused && search === "" && (
<div className="pointer-events-none absolute inset-y-0 left-8 top-1/2 flex -translate-y-1/2 items-center gap-2 text-sm text-muted-foreground">
Type{" "}
<span>
<ShortcutDisplay sidebar shortcut="/" />
</span>{" "}
to search components
</div>
)}
</div>
{filterType && (
<SidebarFilterComponent
isInput={!!filterType.source}
type={filterType.type}
resetFilters={() => {
setFilterEdge([]);
setFilterData(data);
}}
/>
)}
</SidebarHeader>
<SidebarContent className="p-2">
{hasResults ? (
<>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{!data
? Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton />
</SidebarMenuItem>
))
: categories.map(
(item) =>
dataFilter[item.name] &&
Object.keys(dataFilter[item.name]).length > 0 && (
<Collapsible
key={item.name}
className="group/collapsible"
open={openCategories.includes(item.name)}
onOpenChange={(isOpen) => {
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton asChild>
<div
data-testid={`disclosure-${item.display_name.toLocaleLowerCase()}`}
tabIndex={0}
onKeyDown={(e) =>
handleKeyDown(e, item.name)
}
className="flex cursor-pointer items-center gap-2"
>
<ForwardedIconComponent
name={item.icon}
className="h-4 w-4 text-muted-foreground group-data-[state=open]/collapsible:text-pink-600 group-data-[state=open]/collapsible:dark:text-pink-400"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
{item.display_name}
</span>
<ForwardedIconComponent
name="ChevronRight"
className="h-4 w-4 text-muted-foreground transition-all group-data-[state=open]/collapsible:rotate-90"
/>
</div>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="flex flex-col gap-1 py-2">
{Object.keys(dataFilter[item.name])
.sort((a, b) =>
sensitiveSort(
dataFilter[item.name][a].display_name,
dataFilter[item.name][b].display_name,
),
)
.map((SBItemName: string, idx) => (
<ShadTooltip
content={
dataFilter[item.name][SBItemName]
.display_name
}
side="right"
key={idx}
>
<SidebarDraggableComponent
sectionName={item.name as string}
apiClass={
dataFilter[item.name][SBItemName]
}
icon={
dataFilter[item.name][SBItemName]
.icon ??
item.icon ??
"Unknown"
}
key={idx}
onDragStart={(event) =>
onDragStart(event, {
type: removeCountFromString(
SBItemName,
),
node: dataFilter[item.name][
SBItemName
],
})
}
color={nodeColors[item.name]}
itemName={SBItemName}
error={
!!dataFilter[item.name][
SBItemName
].error
}
display_name={
dataFilter[item.name][SBItemName]
.display_name
}
official={
dataFilter[item.name][SBItemName]
.official === false
? false
: true
}
beta={
dataFilter[item.name][SBItemName]
.beta ?? false
}
legacy={
dataFilter[item.name][SBItemName]
.legacy ?? false
}
/>
</ShadTooltip>
))}
</div>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
),
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>Bundles</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{!data
? Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton />
</SidebarMenuItem>
))
: bundles.map(
(item) =>
dataFilter[item.name] &&
Object.keys(dataFilter[item.name]).length > 0 && (
<Collapsible
key={item.name}
className="group/collapsible"
open={openCategories.includes(item.name)}
onOpenChange={(isOpen) => {
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton asChild>
<div
tabIndex={0}
onKeyDown={(e) =>
handleKeyDown(e, item.name)
}
className="flex cursor-pointer items-center gap-2"
>
<ForwardedIconComponent
name={item.icon}
className="h-4 w-4 text-muted-foreground group-data-[state=open]/collapsible:text-pink-600 group-data-[state=open]/collapsible:dark:text-pink-400"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
{item.display_name}
</span>
<ForwardedIconComponent
name="ChevronRight"
className="h-4 w-4 text-muted-foreground transition-all group-data-[state=open]/collapsible:rotate-90"
/>
</div>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="flex flex-col gap-1 py-2">
{Object.keys(dataFilter[item.name])
.sort((a, b) =>
sensitiveSort(
dataFilter[item.name][a].display_name,
dataFilter[item.name][b].display_name,
),
)
.map((SBItemName: string, idx) => (
<ShadTooltip
content={
dataFilter[item.name][SBItemName]
.display_name
}
side="right"
key={idx}
>
<SidebarDraggableComponent
sectionName={item.name as string}
apiClass={
dataFilter[item.name][SBItemName]
}
icon={
dataFilter[item.name][SBItemName]
.icon ??
item.icon ??
"Unknown"
}
key={idx}
onDragStart={(event) =>
onDragStart(event, {
type: removeCountFromString(
SBItemName,
),
node: dataFilter[item.name][
SBItemName
],
})
}
color={nodeColors[item.name]}
itemName={SBItemName}
error={
!!dataFilter[item.name][
SBItemName
].error
}
display_name={
dataFilter[item.name][SBItemName]
.display_name
}
official={
dataFilter[item.name][SBItemName]
.official === false
? false
: true
}
beta={
dataFilter[item.name][SBItemName]
.beta ?? false
}
legacy={
dataFilter[item.name][SBItemName]
.legacy ?? false
}
/>
</ShadTooltip>
))}
</div>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
),
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</>
) : (
<div className="flex h-full flex-col items-center justify-center p-4 text-center">
<ForwardedIconComponent
name="Search"
className="mb-4 h-8 w-8 text-muted-foreground"
/>
<h3 className="mb-2 text-lg font-semibold">No results found</h3>
<p className="text-sm text-muted-foreground">
Try adjusting your search or filter to find what you're looking
for.
</p>
</div>
)}
</SidebarContent>
<SidebarFooter className="border-t p-4 py-3">
{hasStore && (
<SidebarMenuButton asChild>
<CustomLink to="/store">
<div className="flex items-center gap-2">
<ForwardedIconComponent
name="Store"
className="h-4 w-4 text-muted-foreground"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
Discover more components
</span>
</div>
</CustomLink>
</SidebarMenuButton>
)}
<SidebarMenuButton asChild>
<Button
unstyled
onClick={() => {
if (customComponent) {
addComponent(customComponent, "CustomComponent");
}
}}
data-testid="sidebar-custom-component-button"
className="flex items-center gap-2"
>
<ForwardedIconComponent
name="Plus"
className="h-4 w-4 text-muted-foreground"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
Custom Component
</span>
</Button>
</SidebarMenuButton>
</SidebarFooter>
</Sidebar>
);
}

View file

@ -1,21 +1,40 @@
import RenderIcons from "@/components/renderIconComponent";
import { cn } from "@/utils/utils";
export default function ShortcutDisplay({
name,
shortcut,
sidebar = false,
}: {
name: string;
name?: string;
shortcut: string;
sidebar?: boolean;
}): JSX.Element {
const fixedShortcut = shortcut?.split("+");
return (
<div className="flex content-center items-center justify-center self-center text-[12px]">
<span> {name} </span>
<span
className={`ml-3 flex items-center rounded-sm bg-primary-hover px-1.5 py-[0.1em] text-muted`}
>
<RenderIcons filteredShortcut={fixedShortcut} />
</span>
</div>
<>
{sidebar ? (
<div className="flex justify-center">
{name && <span> {name} </span>}
<span
className={cn(
"flex items-center rounded-sm bg-muted px-1.5 py-[0.1em] text-lg text-muted-foreground",
name && "ml-3",
)}
>
<RenderIcons filteredShortcut={fixedShortcut} />
</span>
</div>
) : (
<div className="flex content-center items-center justify-center self-center text-[12px]">
<span> {name} </span>
<span
className={`ml-3 flex items-center rounded-sm bg-primary-hover px-1.5 py-[0.1em] text-muted`}
>
<RenderIcons filteredShortcut={fixedShortcut} />
</span>
</div>
)}
</>
);
}

View file

@ -1,6 +1,6 @@
import { SidebarProvider } from "@/components/ui/sidebar";
import { useGetFlow } from "@/controllers/API/queries/flows/use-get-flow";
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows";
import { ENABLE_BRANDING } from "@/customization/feature-flags";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useSaveFlow from "@/hooks/flows/use-save-flow";
import { SaveChangesModal } from "@/modals/saveChangesModal";
@ -14,7 +14,7 @@ import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent";
import { FlowSidebarComponent } from "./components/flowSidebarComponent";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
@ -165,16 +165,17 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
<div className="flow-page-positioning">
{currentFlow && (
<div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />}
<main className="flex flex-1">
{/* Primary column */}
<div className="h-full w-full">
<Page />
</div>
</main>
<SidebarProvider>
{!view && <FlowSidebarComponent />}
<main className="flex flex-1">
<div className="h-full w-full">
<Page />
</div>
</main>
</SidebarProvider>
</div>
)}
{ENABLE_BRANDING && version && (
{/* {ENABLE_BRANDING && version && (
<a
target={"_blank"}
href="https://medium.com/logspace/langflow-datastax-better-together-1b7462cebc4d"
@ -184,7 +185,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
<div className={version ? "mt-2" : "mt-1"}>⛓️ v{version}</div>
</a>
)}
)} */}
</div>
{blocker.state === "blocked" && (
<>

View file

@ -7,7 +7,6 @@ import {
templatesGenerator,
typesGenerator,
} from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import useFlowsManagerStore from "./flowsManagerStore";
export const useTypesStore = create<TypesStoreType>((set, get) => ({

View file

@ -260,8 +260,7 @@ pre {
-webkit-appearance: none;
background-color: hsl(var(--primary));
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23777'><path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>");
background-size: 16px 16px;
height: 16px;
opacity: 1 !important;
width: 16px;
background-size: 20px 20px;
height: 20px;
width: 20px;
}

View file

@ -248,6 +248,14 @@
--chat-bot-icon: #235d70;
--chat-user-icon: #4f3d6e;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
--emerald-success: 160.1 84.1% 39.4%;
--accent-emerald-foreground: 161.4 93.5% 30.4%;
--placeholder: 240 5% 64.9%;

View file

@ -43,6 +43,7 @@ export type APIClassType = {
output_types?: Array<string>;
custom_fields?: CustomFieldsType;
beta?: boolean;
legacy?: boolean;
documentation: string;
error?: string;
official?: boolean;

View file

@ -0,0 +1,5 @@
export interface SidebarCategory {
display_name: string;
name: string;
icon: string;
}

View file

@ -84,6 +84,7 @@ import {
GitFork,
GithubIcon,
Globe,
GripVertical,
Group,
Hammer,
Heart,
@ -295,7 +296,7 @@ export const gradients = [
"bg-gradient-to-br from-lime-600 via-yellow-300 to-red-600",
];
/*
/*
Specifications
#FF3276 -> #F480FF
#1A0250 -> #2F10FE
@ -416,41 +417,49 @@ export const nodeColorsName: { [char: string]: string } = {
BaseChatMemory: "cyan",
};
export const nodeNames: { [char: string]: string } = {
inputs: "Inputs",
outputs: "Outputs",
data: "Data",
prompts: "Prompts",
models: "Models",
notion: "Notion",
Notion: "Notion",
AssemblyAI: "AssemblyAI",
assemblyai: "AssemblyAI",
model_specs: "Model Specs",
chains: "Chains",
agents: "Agents",
tools: "Tools",
memories: "Memories",
saved_components: "Saved",
advanced: "Advanced",
chat: "Chat",
embeddings: "Embeddings",
documentloaders: "Loaders",
vectorstores: "Vector Stores",
vectorsearch: "Vector Search",
toolkits: "Toolkits",
wrappers: "Wrappers",
textsplitters: "Text Splitters",
retrievers: "Retrievers",
helpers: "Helpers",
prototypes: "Prototypes",
astra_assistants: "Astra Assistants",
langchain_utilities: "Utilities",
output_parsers: "Output Parsers",
custom_components: "Custom",
link_extractors: "Link Extractors",
unknown: "Other",
};
export const SIDEBAR_CATEGORIES = [
{ display_name: "Saved", name: "saved_components", icon: "GradientSave" },
{ display_name: "Inputs", name: "inputs", icon: "Download" },
{ display_name: "Outputs", name: "outputs", icon: "Upload" },
{ display_name: "Prompts", name: "prompts", icon: "TerminalSquare" },
{ display_name: "Data", name: "data", icon: "Database" },
{ display_name: "Models", name: "models", icon: "BrainCircuit" },
{ display_name: "Helpers", name: "helpers", icon: "Wand2" },
{ display_name: "Vector Stores", name: "vectorstores", icon: "Layers" },
{ display_name: "Embeddings", name: "embeddings", icon: "Binary" },
{ display_name: "Agents", name: "agents", icon: "Bot" },
{ display_name: "Chains", name: "chains", icon: "Link" },
{ display_name: "Loaders", name: "documentloaders", icon: "Paperclip" },
{
display_name: "Utilities",
name: "langchain_utilities",
icon: "PocketKnife",
},
{ display_name: "Link Extractors", name: "link_extractors", icon: "Link2" },
{ display_name: "Memories", name: "memories", icon: "Cpu" },
{ display_name: "Output Parsers", name: "output_parsers", icon: "Compass" },
{ display_name: "Prototypes", name: "prototypes", icon: "FlaskConical" },
{ display_name: "Retrievers", name: "retrievers", icon: "FileSearch" },
{ display_name: "Text Splitters", name: "textsplitters", icon: "Scissors" },
{ display_name: "Toolkits", name: "toolkits", icon: "Package2" },
{ display_name: "Tools", name: "tools", icon: "Hammer" },
];
export const SIDEBAR_BUNDLES = [
{ display_name: "CrewAI", name: "crewai", icon: "CrewAi" },
{ display_name: "LangChain", name: "langchain_utilities", icon: "LangChain" },
{ display_name: "AssemblyAI", name: "assemblyai", icon: "AssemblyAI" },
{
display_name: "Astra Assistants",
name: "astra_assistants",
icon: "Sparkles",
},
{ display_name: "Google", name: "google", icon: "Google" },
{ display_name: "Firecrawl", name: "firecrawl", icon: "FirecrawlCrawlApi" },
{ display_name: "Notion", name: "Notion", icon: "Notion" },
{ display_name: "NVIDIA", name: "nvidia", icon: "NVIDIA" },
{ display_name: "Vectara", name: "vectara", icon: "Vectara" },
];
export const categoryIcons = {
saved_components: GradientSave,
@ -669,6 +678,16 @@ export const nodeIconsLucide: iconsType = {
Blocks,
ChevronDown,
ArrowLeft,
BrainCircuit,
Wand2,
Layers,
Binary,
Paperclip,
PocketKnife,
Scissors,
Cpu,
Hammer,
GradientSave,
Shield,
Plus,
Redo,
@ -743,6 +762,10 @@ export const nodeIconsLucide: iconsType = {
Loader2,
BookmarkPlus,
Heart,
Package2,
FileSearch,
Compass,
Link2,
Pin,
Link,
ToyBrick,
@ -768,6 +791,7 @@ export const nodeIconsLucide: iconsType = {
note: StickyNote,
RotateCcw,
Wrench,
GripVertical,
FolderPlusIcon,
PaperclipIcon,
Settings,

View file

@ -0,0 +1,73 @@
import { expect, test } from "@playwright/test";
test("user can add components by hovering and clicking the plus icon", async ({
page,
}) => {
// Navigate to homepage and handle initial modal
await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
// Start with blank flow
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
// Search for a component
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(500);
// Hover over the component and verify plus icon
const componentLocator = page.getByTestId("inputsChat Input");
// Find the plus icon within the specific component container
const plusIcon = componentLocator.getByTestId("icon-Plus");
// Get the opacity
const opacity = await plusIcon.evaluate((el) =>
window.getComputedStyle(el).getPropertyValue("opacity"),
);
await expect(plusIcon).toBeVisible();
await expect(opacity).toBe("0");
// Hover over the component
await componentLocator.hover();
// Check if the plus icon is visible and has full opacity
await expect(plusIcon).toBeVisible();
await page.waitForTimeout(500);
const opacityAfterHover = await plusIcon.evaluate((el) =>
window.getComputedStyle(el).getPropertyValue("opacity"),
);
await expect(opacityAfterHover).toBe("1");
// Click the plus icon associated with this component
await plusIcon.click();
await page.waitForTimeout(500);
// Verify component was added to the flow
const addedComponent = await page.locator(".react-flow__node").first();
await expect(addedComponent).toBeVisible();
});

View file

@ -23,12 +23,14 @@ test("user must see on handle hover a tooltip with possibility connections", asy
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("retrievalqa");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("retrievalqa");
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-legacy-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-legacy-switch").click();
await expect(page.getByTestId("sidebar-legacy-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await page.waitForTimeout(1000);
await page

View file

@ -32,13 +32,8 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 100000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("api request");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("api request");
await page.waitForTimeout(1000);
await page
@ -77,6 +72,14 @@ test("user must see on handle click the possibility connections - LLMChain", asy
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")).not.toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-legacy-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-legacy-switch").click();
await expect(page.getByTestId("sidebar-legacy-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("disclosure-chains")).toBeVisible();
await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
@ -89,7 +92,17 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await expect(page.getByTestId("chainsConversationChain")).toBeVisible();
await expect(page.getByTestId("prototypesConditional Router")).toBeVisible();
await page.getByPlaceholder("Search").click();
await expect(page.getByTestId("helpersCurrent Date")).toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-beta-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-beta-switch").click();
await expect(page.getByTestId("sidebar-beta-switch")).not.toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("helpersCurrent Date")).not.toBeVisible();
await page.getByTestId("sidebar-filter-reset").click();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
await expect(page.getByTestId("outputsChat Output")).not.toBeVisible();
@ -117,7 +130,16 @@ test("user must see on handle click the possibility connections - LLMChain", asy
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")).not.toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-beta-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-beta-switch").click();
await expect(page.getByTestId("sidebar-beta-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("prototypesSub Flow")).toBeVisible();
await expect(
page.getByTestId("retrieversSelf Query Retriever"),
).toBeVisible();

View file

@ -30,9 +30,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//first component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text input");
await page.waitForTimeout(1000);
await page
@ -52,9 +51,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//second component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("url");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("url");
await page.waitForTimeout(1000);
await page
@ -74,9 +72,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//third component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("split text");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("split text");
await page.waitForTimeout(1000);
await page
@ -96,9 +93,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//fourth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("parse data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("parse data");
await page.waitForTimeout(1000);
await page
@ -118,9 +114,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//fifth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(1000);
await page

View file

@ -31,12 +31,8 @@ test("user must be able to save or delete a global variable", async ({
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(1000);

View file

@ -0,0 +1,96 @@
import { expect, test } from "@playwright/test";
test("user can search and add components using keyboard shortcuts", async ({
page,
}) => {
// Navigate to homepage and handle initial modal
await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
// Start with blank flow
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
// Press "/" to activate search
await page.keyboard.press("/");
await page.waitForTimeout(500);
// Verify search is focused and disclosures are closed when search is empty
await expect(page.getByTestId("sidebar-search-input")).toBeFocused();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
// Type "chat" to search for chat components
await page.keyboard.type("chat");
await page.waitForTimeout(500);
// Verify disclosures open when search has content
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
// Press Tab to focus first result
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
// Verify some expected chat-related components are visible
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
await expect(page.getByTestId("outputsChat Output")).toBeVisible();
// Press Space to select the component
await page.keyboard.press("Space");
await page.waitForTimeout(500);
// Verify component was added to flow
const addedComponent = await page.locator(".react-flow__node").first();
await expect(addedComponent).toBeVisible();
// Clear search input and verify disclosures are closed
await page.getByTestId("sidebar-search-input").clear();
await page.waitForTimeout(500);
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
// Test Enter key selection
await page.keyboard.press("/");
await page.keyboard.type("prompt");
await page.waitForTimeout(500);
// Verify disclosures open with new search
await expect(page.getByTestId("promptsPrompt")).toBeVisible();
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.waitForTimeout(500);
// Verify second component was added
const nodeCount = await page.locator(".react-flow__node").count();
expect(nodeCount).toBe(2);
// Verify search is cleared and disclosures are closed after adding component
await page.keyboard.press("/");
await page.getByTestId("sidebar-search-input").clear();
await page.waitForTimeout(500);
await expect(page.getByTestId("sidebar-search-input")).toHaveValue("");
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
await expect(page.getByTestId("sidebar-search-input")).toBeFocused();
await page.keyboard.press("Escape");
await expect(page.getByTestId("sidebar-search-input")).not.toBeFocused();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
});

View file

@ -38,12 +38,9 @@ test("fresh start playground", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(1000);
await page
@ -52,8 +49,8 @@ test("fresh start playground", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(1000);
await page

View file

@ -103,12 +103,8 @@ test.describe("save component tests", () => {
if (replaceButton) {
await page.getByTestId("replace-button").click();
}
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("group");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("group");
await page.waitForTimeout(1000);
await page

View file

@ -33,9 +33,8 @@ test("user must be able to stop a building", async ({ page }) => {
//first component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text input");
// await page.waitForTimeout(1000);
await page
@ -55,9 +54,8 @@ test("user must be able to stop a building", async ({ page }) => {
//second component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("url");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("url");
// await page.waitForTimeout(1000);
await page
@ -77,9 +75,8 @@ test("user must be able to stop a building", async ({ page }) => {
//third component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("split text");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("split text");
// await page.waitForTimeout(1000);
await page
@ -99,9 +96,8 @@ test("user must be able to stop a building", async ({ page }) => {
//fourth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("parse data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("parse data");
// await page.waitForTimeout(1000);
await page
@ -121,9 +117,8 @@ test("user must be able to stop a building", async ({ page }) => {
//fifth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
// await page.waitForTimeout(1000);
await page
@ -248,17 +243,7 @@ class CustomComponent(Component):
return data
`;
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom component");
await page.waitForTimeout(1000);
await page
.locator('//*[@id="helpersCustom Component"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("sidebar-custom-component-button").click();
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();

View file

@ -88,12 +88,8 @@ test("check if tweaks are updating when someothing on the flow changes", async (
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("Chroma");
await page.waitForTimeout(1000);

View file

@ -1,83 +1,150 @@
import { expect, test } from "@playwright/test";
import { Page, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
// Helper function to wait for element to be ready
async function waitForElement(page: Page, elementId: string, nth: number) {
const element = page.getByTestId(`title-${elementId}`).nth(nth);
// Add this function at the beginning of the file, after the imports
// Wait for element to be visible and stable
await element.waitFor({
state: "visible",
timeout: 30000,
});
// Additional wait to ensure element is fully rendered and interactive
await page.waitForTimeout(1000);
return element;
}
// Improved version of moveElementByX with better error handling and waits
async function moveElementByX(
page: any,
page: Page,
elementId: string,
moveX: number,
nth: number,
) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
await element.hover();
try {
const element = await waitForElement(page, elementId, nth);
await element.hover();
const boundingBox = await element.boundingBox();
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
// Break down mouse movements into smaller steps for more reliability
await page.mouse.move(startX, startY);
await page.waitForTimeout(50);
await page.mouse.down();
await page.mouse.move(startX + moveX, startY);
await page.waitForTimeout(50);
// Move in smaller increments
const steps = 3;
const stepX = moveX / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX + stepX * i, startY);
await page.waitForTimeout(50);
}
await page.mouse.up();
} else {
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
await page.waitForTimeout(100);
} catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
}
}
// Add this function at the beginning of the file, after the imports
// Improved version of moveElementByY with better error handling and waits
async function moveElementByY(
page: any,
page: Page,
elementId: string,
moveY: number,
nth: number,
) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
await element.hover();
try {
const element = await waitForElement(page, elementId, nth);
await element.hover();
const boundingBox = await element.boundingBox();
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
await page.mouse.move(startX, startY);
await page.waitForTimeout(100);
await page.mouse.down();
await page.mouse.move(startX, startY + moveY);
await page.waitForTimeout(100);
// Move in smaller increments
const steps = 5;
const stepY = moveY / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX, startY + stepY * i);
await page.waitForTimeout(50);
}
await page.mouse.up();
} else {
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
await page.waitForTimeout(100);
} catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
}
}
// Add this function at the beginning of the file, after the imports
// Improved version of moveElementByXY with better error handling and waits
async function moveElementByXY(
page: any,
page: Page,
elementId: string,
moveX: number,
moveY: number,
nth: number,
) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
await element.hover();
try {
const element = await waitForElement(page, elementId, nth);
await element.hover();
const boundingBox = await element.boundingBox();
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
await page.mouse.move(startX, startY);
await page.waitForTimeout(100);
await page.mouse.down();
await page.mouse.move(startX + moveX, startY + moveY);
await page.waitForTimeout(100);
// Move in smaller increments
const steps = 5;
const stepX = moveX / steps;
const stepY = moveY / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX + stepX * i, startY + stepY * i);
await page.waitForTimeout(50);
}
await page.mouse.up();
} else {
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
await page.waitForTimeout(100);
} catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
}
}
test("should create a flow with decision", async ({ page }) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
@ -111,21 +178,17 @@ test("should create a flow with decision", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
//---------------------------------- CHAT INPUT
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(500);
await page
.getByTestId("inputsChat Input")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CREATE LIST
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("list");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("list");
await page.waitForTimeout(500);
await page
.getByTestId("helpersCreate List")
@ -158,8 +221,8 @@ test("should create a flow with decision", async ({ page }) => {
await page.getByTestId("inputlist_str_texts_2").last().fill("not cool..");
//---------------------------------- PARSE DATA
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("parse data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("parse data");
await page.waitForTimeout(500);
await page
@ -170,8 +233,8 @@ test("should create a flow with decision", async ({ page }) => {
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- PASS
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("pass");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("pass");
await page.waitForTimeout(500);
await page
.getByTestId("prototypesPass")
@ -185,30 +248,30 @@ test("should create a flow with decision", async ({ page }) => {
.getByTestId("prototypesPass")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- PROMPT
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("prompt");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("prompt");
await page.waitForTimeout(500);
await page
.getByTestId("promptsPrompt")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- OPENAI
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(500);
await page
.getByTestId("modelsOpenAI")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CONDITIONAL ROUTER
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("conditional router");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("conditional router");
await page.waitForTimeout(500);
await page
.getByTestId("prototypesConditional Router")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CHAT OUTPUT
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(500);
await page
.getByTestId("outputsChat Output")
@ -222,32 +285,42 @@ test("should create a flow with decision", async ({ page }) => {
await page.getByTestId("fit_view").click();
await moveElementByX(page, "Chat Output", 500, 1);
await page.waitForTimeout(500);
await moveElementByX(page, "Chat Output", 1000, 0);
await moveElementByX(page, "Chat Output", 400, 1);
await page.waitForTimeout(500);
await moveElementByX(page, "Conditional Router", 1500, 0);
await moveElementByX(page, "Chat Output", 700, 0);
await page.waitForTimeout(500);
await moveElementByX(page, "OpenAI", 2000, 0);
await moveElementByX(page, "Conditional Router", 1000, 0);
await page.waitForTimeout(500);
await moveElementByX(page, "Prompt", 2500, 0);
await page.getByTestId("fit_view").click();
await moveElementByX(page, "OpenAI", 980, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByX(page, "Pass", 3000, 2);
await moveElementByX(page, "Prompt", 990, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByX(page, "Pass", 1000, 2);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Pass", 0, 200, 1);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Pass", 150, 200, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Parse Data", 300, 200, 1);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByXY(page, "Parse Data", 450, 200, 0);
await page.waitForTimeout(500);
await moveElementByXY(page, "Create List", 600, 200, 1);
await page.waitForTimeout(500);
await moveElementByXY(page, "Create List", 800, 200, 0);
await page.waitForTimeout(500);
await moveElementByXY(page, "Chat Input", 1000, 200, 0);
await page.waitForTimeout(500);
await page.getByTestId("fit_view").click();

View file

@ -31,9 +31,8 @@ test("user must be able to check similarity between embedding texts", async ({
//first component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
// await page.waitForTimeout(1000);
await page
@ -70,9 +69,8 @@ test("user must be able to check similarity between embedding texts", async ({
//third component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text embedder");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text embedder");
// await page.waitForTimeout(1000);
await page
@ -109,9 +107,8 @@ test("user must be able to check similarity between embedding texts", async ({
//fifth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("embedding similarity");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("embedding similarity");
// await page.waitForTimeout(1000);
await page
@ -131,9 +128,8 @@ test("user must be able to check similarity between embedding texts", async ({
//sisxth component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("parse data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("parse data");
// await page.waitForTimeout(1000);
await page
@ -153,9 +149,8 @@ test("user must be able to check similarity between embedding texts", async ({
//seventh component
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");
// await page.waitForTimeout(1000);
await page
@ -173,9 +168,8 @@ test("user must be able to check similarity between embedding texts", async ({
await page.mouse.up();
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("filter data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("filter data");
// await page.waitForTimeout(1000);
await page

View file

@ -41,19 +41,9 @@ test("TextInputOutputComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
const focusElementsOnBoard = async ({ page }) => {
const focusElements = await page.getByTestId("extended-disclosure");
focusElements.click();
};
await focusElementsOnBoard({ page });
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text input");
await page.waitForTimeout(1000);
await page
@ -62,8 +52,8 @@ test("TextInputOutputComponent", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(1000);
await page
@ -120,8 +110,8 @@ test("TextInputOutputComponent", async ({ page }) => {
// Release the mouse
await page.mouse.up();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");
await page
.getByTestId("outputsText Output")

View file

@ -30,13 +30,9 @@ test("should be able to see output preview from grouped components and connect c
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text input");
await page.waitForTimeout(1000);
await page
@ -61,9 +57,8 @@ test("should be able to see output preview from grouped components and connect c
.getByTestId("inputsText Input")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("combine text");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("combine text");
await page.waitForTimeout(1000);
await page
@ -107,9 +102,8 @@ test("should be able to see output preview from grouped components and connect c
await page.mouse.up();
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");
await page.waitForTimeout(1000);
await page

View file

@ -38,9 +38,8 @@ test("memory should work as expect", async ({ page }) => {
await page.getByTestId("fit_view").click();
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat memory");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat memory");
// Locate the canvas element
const canvas = page.locator("#react-flow-id"); // Update the selector if needed

View file

@ -32,12 +32,8 @@ test("chat_io_teste", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(1000);
await page
@ -46,8 +42,8 @@ test("chat_io_teste", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(1000);
await page

View file

@ -31,12 +31,8 @@ test("CodeAreaModalComponent", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("python function");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("python function");
await page.waitForTimeout(1000);

View file

@ -30,13 +30,9 @@ test("dropDownComponent", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("amazon");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("amazon");
await page.waitForTimeout(1000);

View file

@ -30,13 +30,9 @@ test("should be able to upload a file", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("file");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("file");
await page.waitForTimeout(1000);
@ -58,8 +54,8 @@ test("should be able to upload a file", async ({ page }) => {
);
await page.getByText("test_file.txt").isVisible();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");
await page
.getByTestId("outputsText Output")
@ -72,8 +68,8 @@ test("should be able to upload a file", async ({ page }) => {
await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("parse data");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("parse data");
await page
.getByTestId("helpersParse Data")
.first()

View file

@ -29,12 +29,8 @@ test("FloatComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("ollama");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("ollama");
await page.waitForTimeout(1000);

View file

@ -29,12 +29,8 @@ test("InputComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("Chroma");
await page.waitForTimeout(1000);

View file

@ -21,12 +21,8 @@ test("InputListComponent", async ({ page }) => {
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("url");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("url");
await page.waitForTimeout(1000);
await page

View file

@ -29,12 +29,8 @@ test("IntComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(1000);

View file

@ -29,12 +29,8 @@ test("KeypairListComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("amazon bedrock");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("amazon bedrock");
await page.waitForTimeout(1000);

View file

@ -40,28 +40,11 @@ test("user should interact with link component", async ({ context, page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
const focusElementsOnBoard = async ({ page }) => {
const focusElements = await page.getByTestId("extended-disclosure");
focusElements.click();
};
await focusElementsOnBoard({ page });
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom component");
await page.waitForTimeout(1000);
await page
.locator('//*[@id="helpersCustom Component"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("sidebar-custom-component-button").click();
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTestId("title-Custom Component").first().click();

View file

@ -29,12 +29,8 @@ test("NestedComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("api request");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("api request");
await page.waitForTimeout(1000);

View file

@ -29,12 +29,8 @@ test("PromptTemplateComponent", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("prompt");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("prompt");
await page.waitForTimeout(1000);

View file

@ -39,12 +39,8 @@ test("user should be able to use slider input", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("ollama");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("ollama");
await page.waitForTimeout(1000);

View file

@ -44,18 +44,10 @@ test("user must be able to interact with table input component", async ({
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom component");
await page.waitForTimeout(1000);
await page
.getByTestId("helpersCustom Component")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.getByTestId("sidebar-custom-component-button").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();

View file

@ -24,18 +24,8 @@ test("TextAreaModalComponent", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
const focusElementsOnBoard = async ({ page }) => {
const focusElements = await page.getByTestId("extended-disclosure");
await focusElements.click();
};
await focusElementsOnBoard({ page });
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("prompt");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("prompt");
await page.waitForTimeout(1000);

View file

@ -30,17 +30,8 @@ test("ToggleComponent", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
const focusElementsOnBoard = async ({ page }) => {
const focusElements = await page.getByTestId("extended-disclosure");
focusElements.click();
};
await focusElementsOnBoard({ page });
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("directory");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("directory");
await page.waitForTimeout(1000);
await page

View file

@ -50,12 +50,9 @@ test("user should be able to manually save a flow when the auto_save is off", as
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 5000,
});
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("NVIDIA");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("NVIDIA");
await page.waitForTimeout(1000);
@ -111,8 +108,8 @@ test("user should be able to manually save a flow when the auto_save is off", as
expect(await page.getByText("NVIDIA").isVisible()).toBeFalsy();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("NVIDIA");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("NVIDIA");
await page.waitForTimeout(1000);
@ -143,8 +140,8 @@ test("user should be able to manually save a flow when the auto_save is off", as
expect(await page.getByTestId("title-NVIDIA").isVisible()).toBeTruthy();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("NVIDIA");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("NVIDIA");
await page.waitForTimeout(1000);

View file

@ -34,12 +34,17 @@ test("user must see on handle click the possibility connections - RetrievalQA",
);
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("retrievalqa");
await page.waitForTimeout(1000);
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-legacy-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-legacy-switch").click();
await expect(page.getByTestId("sidebar-legacy-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("retrievalqa");
await page.waitForTimeout(1000);
await page
@ -112,24 +117,31 @@ test("user must see on handle click the possibility connections - RetrievalQA",
),
);
await page.getByPlaceholder("Search").click();
await page.getByTestId("sidebar-search-input").click();
const notVisibleModelSpecsTestIds = [
"model_specsVertexAI",
"model_specsCTransformers",
"model_specsAmazon Bedrock",
const visibleModelSpecsTestIds = [
"modelsAIML",
"modelsAmazon Bedrock",
"modelsAnthropic",
"modelsAzure OpenAI",
"model_specsAzureChatOpenAI",
"model_specsChatAnthropic",
"model_specsChatLiteLLM",
"model_specsChatOllama",
"model_specsChatOpenAI",
"model_specsChatVertexAI",
"modelsCohere",
"modelsGoogle Generative AI",
"modelsGroq",
"modelsHuggingFace",
"modelsLM Studio",
"modelsMaritalk",
"modelsMistralAI",
"modelsNVIDIA",
"modelsOllama",
"modelsOpenAI",
"modelsPerplexity",
"modelsQianfan",
"modelsVertex AI",
];
await Promise.all(
notVisibleModelSpecsTestIds.map((id) =>
expect(page.getByTestId(id)).not.toBeVisible(),
visibleModelSpecsTestIds.map((id) =>
expect(page.getByTestId(id)).toBeVisible(),
),
);

View file

@ -28,23 +28,14 @@ test.describe("Flow Page tests", () => {
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom");
await page.waitForTimeout(1000);
await page
.locator('//*[@id="helpersCustom Component"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
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 page.getByTestId("sidebar-custom-component-button").click();
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
});
});

View file

@ -3,25 +3,23 @@ import { expect, test } from "@playwright/test";
test("user should be able to see integrations in the sidebar if mvp_components is true", async ({
page,
}) => {
await page.route("**/api/v1/config", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
feature_flags: {
mvp_components: true,
},
}),
headers: {
"content-type": "application/json",
...route.request().headers(),
},
});
});
// await page.route("**/api/v1/config", (route) => {
// route.fulfill({
// status: 200,
// contentType: "application/json",
// body: JSON.stringify({
// feature_flags: {
// mvp_components: true,
// },
// }),
// headers: {
// "content-type": "application/json",
// ...route.request().headers(),
// },
// });
// });
await page.goto("/");
await page.waitForTimeout(1000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
@ -31,66 +29,59 @@ test("user should be able to see integrations in the sidebar if mvp_components i
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
await page.waitForSelector('[data-testid="shad-sidebar"]', {
timeout: 30000,
});
await expect(page.getByText("Integrations")).toBeVisible();
await expect(page.getByText("Bundles")).toBeVisible();
await expect(page.getByText("Notion")).toBeVisible();
await expect(page.getByText("AssemblyAI")).toBeVisible();
});
test("user should NOT be able to see integrations in the sidebar if mvp_components is false", async ({
page,
}) => {
await page.waitForTimeout(4000);
await page.route("**/api/v1/config", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
feature_flags: {
mvp_components: false,
},
}),
headers: {
"content-type": "application/json",
...route.request().headers(),
},
});
});
await page.goto("/");
await page.waitForTimeout(1000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await expect(page.getByText("Integrations")).not.toBeVisible();
await expect(page.getByText("Notion")).not.toBeVisible();
// await page.waitForTimeout(4000);
// await page.route("**/api/v1/config", (route) => {
// route.fulfill({
// status: 200,
// contentType: "application/json",
// body: JSON.stringify({
// feature_flags: {
// mvp_components: false,
// },
// }),
// headers: {
// "content-type": "application/json",
// ...route.request().headers(),
// },
// });
// });
// await page.goto("/");
// await page.waitForTimeout(1000);
// let modalCount = 0;
// try {
// const modalTitleElement = await page?.getByTestId("modal-title");
// if (modalTitleElement) {
// modalCount = await modalTitleElement.count();
// }
// } catch (error) {
// modalCount = 0;
// }
// while (modalCount === 0) {
// await page.getByText("New Project", { exact: true }).click();
// await page.waitForTimeout(3000);
// modalCount = await page.getByTestId("modal-title")?.count();
// }
// await page.getByTestId("blank-flow").click();
// await page.waitForSelector('[data-testid="shad-sidebar"]', {
// timeout: 30000,
// });
// await expect(page.getByText("Integrations")).not.toBeVisible();
// await expect(page.getByText("Notion")).not.toBeVisible();
});

View file

@ -32,13 +32,8 @@ test("LangflowShortcuts", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("ollama");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("ollama");
await page.waitForTimeout(1000);

View file

@ -1,4 +1,3 @@
import { ENABLE_MVPS } from "@/../../src/customization/feature-flags";
import { expect, test } from "@playwright/test";
import uaParser from "ua-parser-js";
@ -67,9 +66,6 @@ The future of AI is both exciting and uncertain. As the technology continues to
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("add_note").click();
await page.waitForTimeout(1000);

View file

@ -30,27 +30,17 @@ test("User must be able to stop building from inside Playground", async ({
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 1000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom");
await page.waitForTimeout(1000);
await page
.locator('//*[@id="helpersCustom Component"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
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 page.getByTestId("sidebar-custom-component-button").click();
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(1000);

View file

@ -29,12 +29,8 @@ test("user should be able to use duckduckgo search component", async ({
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("duck");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("duck");
await page.waitForTimeout(1000);

View file

@ -30,12 +30,8 @@ test("user should be able to use ComposIO without getting api_key error", async
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("composio");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("composio");
await page.waitForTimeout(1000);
@ -83,12 +79,8 @@ test("user should be able to use connect tools", async ({ page }) => {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("search api");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("search api");
await page.waitForTimeout(1000);
@ -105,8 +97,8 @@ test("user should be able to use connect tools", async ({ page }) => {
await page.waitForTimeout(1000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("tool calling agent");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("tool calling agent");
await page.waitForTimeout(1000);

View file

@ -30,12 +30,8 @@ test("user should be able to connect RetrieverTool to another components", async
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("retriever");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("retriever");
await page.waitForTimeout(1000);
@ -62,8 +58,8 @@ test("user should be able to connect RetrieverTool to another components", async
await page.mouse.up();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chroma");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chroma");
await page.waitForTimeout(1000);

View file

@ -36,13 +36,9 @@ test("should use webhook component on API", async ({ page }) => {
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("webhook");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("webhook");
await page.waitForTimeout(1000);

View file

@ -43,12 +43,8 @@ test("should copy code from playground modal", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(1000);
await page
@ -62,8 +58,8 @@ test("should copy code from playground modal", async ({ page }) => {
await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat input");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(1000);
await page
@ -72,8 +68,8 @@ test("should copy code from playground modal", async ({ page }) => {
await page.mouse.up();
await page.mouse.down();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(1000);
await page.getByTestId("fit_view").click();
@ -256,16 +252,13 @@ test("playground button should be enabled or disabled", async ({ page }) => {
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
expect(await page.getByTestId("playground-btn-flow").isDisabled());
expect(await page.getByText("Langflow Chat").isHidden());
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(1000);

View file

@ -27,18 +27,8 @@ test("should be able to see error when something goes wrong on Code Modal", asyn
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom component");
await page.waitForTimeout(1000);
await page
.getByTestId("helpersCustom Component")
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.getByTestId("sidebar-custom-component-button").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click();

View file

@ -28,13 +28,9 @@ test("should be able to select all with ctrl + A on advanced modal", async ({
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("ollama");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("ollama");
await page.waitForTimeout(1000);
await page

View file

@ -26,12 +26,8 @@ test("should interact with api request", async ({ page }) => {
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("api request");
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("api request");
await page.waitForTimeout(1000);