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:
parent
e965fd3b7f
commit
794848d5e9
122 changed files with 2365 additions and 802 deletions
3
src/frontend/package-lock.json
generated
3
src/frontend/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
|
|
|
|||
11
src/frontend/src/components/ui/collapsible.tsx
Normal file
11
src/frontend/src/components/ui/collapsible.tsx
Normal 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 };
|
||||
191
src/frontend/src/components/ui/disclosure.tsx
Normal file
191
src/frontend/src/components/ui/disclosure.tsx
Normal 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,
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"];
|
||||
|
|
|
|||
|
|
@ -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 = {}) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
70
src/frontend/src/hooks/useAddComponent.ts
Normal file
70
src/frontend/src/hooks/useAddComponent.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"}>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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) => ({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export type APIClassType = {
|
|||
output_types?: Array<string>;
|
||||
custom_fields?: CustomFieldsType;
|
||||
beta?: boolean;
|
||||
legacy?: boolean;
|
||||
documentation: string;
|
||||
error?: string;
|
||||
official?: boolean;
|
||||
|
|
|
|||
5
src/frontend/src/types/sidebar/index.ts
Normal file
5
src/frontend/src/types/sidebar/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export interface SidebarCategory {
|
||||
display_name: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
73
src/frontend/tests/core/features/componentHoverAdd.spec.ts
Normal file
73
src/frontend/tests/core/features/componentHoverAdd.spec.ts
Normal 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();
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue