mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Add tailwind-scrollbar-hide and implement block menu UI
The commit adds a new block menu UI component with sidebar navigation, integration chips, and scrollable content areas. It includes tailwind- scrollbar-hide for better UI experience and custom CSS for scroll containers. The implementation features different content sections for blocks categorized by type (input, action, output) and supports search functionality.
This commit is contained in:
@@ -82,6 +82,7 @@
|
||||
"react-shepherd": "^6.1.8",
|
||||
"recharts": "^2.15.3",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-scrollbar-hide": "^2.0.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^11.1.0",
|
||||
"zod": "^3.24.4"
|
||||
|
||||
@@ -154,3 +154,11 @@ input[type="number"]::-webkit-inner-spin-button {
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none; /* IE and Edge: hide scrollbar */
|
||||
transition: scrollbar-width 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import PrimaryActionBar from "@/components/PrimaryActionButton";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { useCopyPaste } from "../hooks/useCopyPaste";
|
||||
import { CronScheduler } from "./cronScheduler";
|
||||
import { BlockMenu } from "./builder/block-menu/BlockMenu";
|
||||
|
||||
// This is for the history, this is the minimum distance a block must move before it is logged
|
||||
// It helps to prevent spamming the history with small movements especially when pressing on a input in a block
|
||||
@@ -136,6 +137,10 @@ const FlowEditor: React.FC<{
|
||||
// State to control if save popover should be pinned open
|
||||
const [pinSavePopover, setPinSavePopover] = useState(false);
|
||||
|
||||
const [blockMenuSelected, setBlockMenuSelected] = useState<
|
||||
"save" | "block" | ""
|
||||
>("");
|
||||
|
||||
const runnerUIRef = useRef<RunnerUIWrapperRef>(null);
|
||||
|
||||
const [openCron, setOpenCron] = useState(false);
|
||||
@@ -623,12 +628,12 @@ const FlowEditor: React.FC<{
|
||||
const editorControls: Control[] = [
|
||||
{
|
||||
label: "Undo",
|
||||
icon: <IconUndo2 />,
|
||||
icon: <IconUndo2 className="h-5 w-5" strokeWidth={2} />,
|
||||
onClick: handleUndo,
|
||||
},
|
||||
{
|
||||
label: "Redo",
|
||||
icon: <IconRedo2 />,
|
||||
icon: <IconRedo2 className="h-5 w-5" strokeWidth={2} />,
|
||||
onClick: handleRedo,
|
||||
},
|
||||
];
|
||||
@@ -676,15 +681,13 @@ const FlowEditor: React.FC<{
|
||||
<Controls />
|
||||
<Background className="dark:bg-slate-800" />
|
||||
<ControlPanel
|
||||
className="absolute z-10"
|
||||
controls={editorControls}
|
||||
topChildren={
|
||||
<BlocksControl
|
||||
pinBlocksPopover={pinBlocksPopover} // Pass the state to BlocksControl
|
||||
blocks={availableNodes}
|
||||
<BlockMenu
|
||||
pinBlocksPopover={pinBlocksPopover}
|
||||
addBlock={addNode}
|
||||
flows={availableFlows}
|
||||
nodes={nodes}
|
||||
blockMenuSelected={blockMenuSelected}
|
||||
setBlockMenuSelected={setBlockMenuSelected}
|
||||
/>
|
||||
}
|
||||
botChildren={
|
||||
@@ -697,6 +700,8 @@ const FlowEditor: React.FC<{
|
||||
agentName={agentName}
|
||||
onNameChange={setAgentName}
|
||||
pinSavePopover={pinSavePopover}
|
||||
blockMenuSelected={blockMenuSelected}
|
||||
setBlockMenuSelected={setBlockMenuSelected}
|
||||
/>
|
||||
}
|
||||
></ControlPanel>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// BLOCK MENU TODO: Currently when i click on the control panel button, if it is already open, then it needs to close, currently its not happening
|
||||
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import ControlPanelButton from "@/components/builder/block-menu/ControlPanelButton";
|
||||
import { ToyBrick } from "lucide-react";
|
||||
import BlockMenuContent from "./BlockMenuContent";
|
||||
|
||||
interface BlockMenuProps {
|
||||
addBlock: (
|
||||
id: string,
|
||||
name: string,
|
||||
hardcodedValues: Record<string, any>,
|
||||
) => void;
|
||||
pinBlocksPopover: boolean;
|
||||
blockMenuSelected: "save" | "block" | "";
|
||||
setBlockMenuSelected: React.Dispatch<
|
||||
React.SetStateAction<"" | "save" | "block">
|
||||
>;
|
||||
}
|
||||
|
||||
export const BlockMenu: React.FC<BlockMenuProps> = ({
|
||||
addBlock,
|
||||
pinBlocksPopover,
|
||||
blockMenuSelected,
|
||||
setBlockMenuSelected,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handlingOnOpen = (newOpen: boolean) => {
|
||||
if (!pinBlocksPopover) {
|
||||
setOpen(newOpen);
|
||||
setBlockMenuSelected(newOpen ? "block" : "");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={pinBlocksPopover ? true : open}
|
||||
onOpenChange={handlingOnOpen}
|
||||
>
|
||||
<PopoverTrigger className="hover:cursor-pointer">
|
||||
<ControlPanelButton
|
||||
data-id="blocks-control-popover-trigger"
|
||||
data-testid="blocks-control-blocks-button"
|
||||
selected={blockMenuSelected === "block"}
|
||||
className="rounded-none"
|
||||
>
|
||||
<ToyBrick className="h-5 w-6" strokeWidth={2} />
|
||||
</ControlPanelButton>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
side="right"
|
||||
align="start"
|
||||
sideOffset={16}
|
||||
className="absolute h-[75vh] w-[46.625rem] overflow-hidden rounded-[1rem] border-none p-0 shadow-[0_2px_6px_0_rgba(0,0,0,0.05)]"
|
||||
data-id="blocks-control-popover-content"
|
||||
>
|
||||
<BlockMenuContent />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import BlockMenuSearchBar from "./BlockMenuSearchBar";
|
||||
import BlockMenuSearch from "./search-and-filter//BlockMenuSearch";
|
||||
import BlockMenuDefault from "./default/BlockMenuDefault";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const BlockMenuContent: React.FC = () => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
{/* Search Bar */}
|
||||
<BlockMenuSearchBar
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
/>
|
||||
|
||||
<Separator className="h-[1px] w-full text-zinc-300" />
|
||||
|
||||
{/* Content */}
|
||||
{/* BLOCK MENU TODO : search after 3 characters */}
|
||||
{searchQuery ? <BlockMenuSearch /> : <BlockMenuDefault />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockMenuContent;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Search } from "lucide-react";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
interface BlockMenuSearchBarProps {
|
||||
setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
|
||||
searchQuery: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const BlockMenuSearchBar: React.FC<BlockMenuSearchBarProps> = ({
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
className = "",
|
||||
}) => {
|
||||
const inputRef = useRef(null);
|
||||
return (
|
||||
<div className="flex min-h-[3.5625rem] items-center gap-2.5 px-4">
|
||||
<Search className="h-6 w-6 text-zinc-700" strokeWidth={2} />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
setSearchQuery(e.target.value);
|
||||
}}
|
||||
placeholder={"Blocks, Agents, Integrations or Keywords..."}
|
||||
className={cn(
|
||||
"m-0 border-none p-0 font-sans text-base font-normal text-zinc-800 shadow-none outline-none placeholder:text-zinc-400 focus:shadow-none focus:outline-none focus:ring-0",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockMenuSearchBar;
|
||||
@@ -1,32 +0,0 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
import { LucideIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
selected?: boolean;
|
||||
icon?: LucideIcon;
|
||||
}
|
||||
|
||||
const ControlPanel: React.FC<Props> = ({
|
||||
selected = false,
|
||||
icon: Icon,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"flex h-[4.25rem] w-[4.25rem] items-center justify-center whitespace-normal bg-white p-[1.38rem] text-zinc-800 shadow-none hover:cursor-pointer hover:bg-zinc-100 hover:text-zinc-950 focus:ring-0",
|
||||
selected &&
|
||||
"bg-violet-50 text-violet-700 hover:cursor-default hover:bg-violet-50 hover:text-violet-700",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{Icon && <Icon className="h-6 w-6" strokeWidth={2} />}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlPanel;
|
||||
@@ -0,0 +1,35 @@
|
||||
// BLOCK MENU TODO: We need a disable state in this, currently it's not in design.
|
||||
|
||||
// Using div instead of button, because it's only for design purposes. We will use this to give design to PopoverTrigger.
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
selected?: boolean;
|
||||
children?: React.ReactNode; // For icon purpose
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const ControlPanelButton: React.FC<Props> = ({
|
||||
selected = false,
|
||||
children,
|
||||
disabled,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-[4.25rem] w-[4.25rem] items-center justify-center whitespace-normal bg-white p-[1.38rem] text-zinc-800 shadow-none hover:cursor-pointer hover:bg-zinc-100 hover:text-zinc-950 focus:ring-0",
|
||||
selected &&
|
||||
"bg-violet-50 text-violet-700 hover:cursor-default hover:bg-violet-50 hover:text-violet-700 active:bg-violet-50 active:text-violet-700",
|
||||
disabled && className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlPanelButton;
|
||||
@@ -22,15 +22,16 @@ const IntegrationChip: React.FC<Props> = ({
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{icon_url && (
|
||||
<Image
|
||||
src={icon_url}
|
||||
alt="integration-icon"
|
||||
className="h-9 w-9"
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
)}
|
||||
<div className="relative h-9 w-9 rounded-[0.5rem] bg-transparent">
|
||||
{icon_url && (
|
||||
<Image
|
||||
src={icon_url}
|
||||
alt="integration-icon"
|
||||
fill
|
||||
className="w-full object-contain"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="font-sans text-sm font-normal leading-[1.375rem] text-zinc-800">
|
||||
{name}
|
||||
</span>
|
||||
|
||||
@@ -20,7 +20,7 @@ const MenuItem: React.FC<Props> = ({
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"flex h-[2.375rem] w-full justify-between whitespace-normal rounded-[0.5rem] bg-transparent p-2 pl-3 shadow-none hover:bg-transparent focus:ring-0",
|
||||
"flex h-[2.375rem] w-full min-w-52 justify-between whitespace-normal rounded-[0.5rem] bg-transparent p-2 pl-3 shadow-none hover:cursor-pointer hover:bg-transparent focus:ring-0",
|
||||
selected && "bg-zinc-100 hover:bg-zinc-100",
|
||||
className,
|
||||
)}
|
||||
@@ -31,7 +31,7 @@ const MenuItem: React.FC<Props> = ({
|
||||
</span>
|
||||
{number && (
|
||||
<span className="font-sans text-sm font-normal leading-[1.375rem] text-zinc-600">
|
||||
{number}+
|
||||
{number > 100 ? "100+" : number}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -15,7 +15,7 @@ const SearchHistoryChip: React.FC<Props> = ({
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
"h-[2.375rem] space-x-1 whitespace-normal rounded-[1.5rem] bg-zinc-50 py-[0.44rem] pl-[0.38rem] pr-[0.62rem] shadow-none hover:bg-zinc-100 focus:ring-0 active:border active:border-zinc-300 active:bg-zinc-100",
|
||||
"h-[2.25rem] space-x-1 rounded-[1.5rem] bg-zinc-50 p-[0.375rem] pr-[0.625rem] shadow-none hover:bg-zinc-100 focus:ring-0 active:border active:border-zinc-300 active:bg-zinc-100",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const ActionBlocksContent: React.FC = () => {
|
||||
return <div className="h-full w-full">ActionBlocksContent</div>;
|
||||
};
|
||||
|
||||
export default ActionBlocksContent;
|
||||
@@ -0,0 +1,122 @@
|
||||
// BLOCK MENU TODO: Currently I have hide the scrollbar, but need to add better designed custom scroller
|
||||
|
||||
import React from "react";
|
||||
import Block from "../Block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const AllBlocksContent: React.FC = () => {
|
||||
return (
|
||||
<div className="h-full w-full space-y-3 px-4">
|
||||
{/* AI Category */}
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
|
||||
AI
|
||||
</p>
|
||||
<span className="rounded-full bg-zinc-100 px-[0.375rem] font-sans text-sm leading-[1.375rem] text-zinc-600">
|
||||
10
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="px-0 font-sans text-sm leading-[1.375rem] text-zinc-600 underline hover:text-zinc-800"
|
||||
>
|
||||
see all
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="h-[1px] w-full text-zinc-300" />
|
||||
|
||||
{/* Basic Category */}
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
|
||||
Basic
|
||||
</p>
|
||||
<span className="rounded-full bg-zinc-100 px-[0.375rem] font-sans text-sm leading-[1.375rem] text-zinc-600">
|
||||
6
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="px-0 font-sans text-sm leading-[1.375rem] text-zinc-600 underline hover:text-zinc-800"
|
||||
>
|
||||
see all
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="h-[1px] w-full text-zinc-300" />
|
||||
|
||||
{/* Communincation Category */}
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
|
||||
Communincation
|
||||
</p>
|
||||
<span className="rounded-full bg-zinc-100 px-[0.375rem] font-sans text-sm leading-[1.375rem] text-zinc-600">
|
||||
6
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="px-0 font-sans text-sm leading-[1.375rem] text-zinc-600 underline hover:text-zinc-800"
|
||||
>
|
||||
see all
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="h-[1px] w-full text-zinc-300" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllBlocksContent;
|
||||
@@ -0,0 +1,36 @@
|
||||
// BLOCK MENU TODO: Fix scrollbar in all states
|
||||
|
||||
import React, { useState } from "react";
|
||||
import BlockMenuSidebar from "./BlockMenuSidebar";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import BlockMenuDefaultContent from "./BlockMenuDefaultContent";
|
||||
|
||||
export type DefaultStateType =
|
||||
| "suggestion"
|
||||
| "all_blocks"
|
||||
| "input_blocks"
|
||||
| "action_blocks"
|
||||
| "output_blocks"
|
||||
| "integrations"
|
||||
| "marketplace_agents"
|
||||
| "my_agents";
|
||||
|
||||
const BlockMenuDefault: React.FC = () => {
|
||||
const [defaultState, setDefaultState] =
|
||||
useState<DefaultStateType>("suggestion");
|
||||
return (
|
||||
<div className="flex flex-1 overflow-y-auto">
|
||||
{/* Left sidebar */}
|
||||
<BlockMenuSidebar
|
||||
defaultState={defaultState}
|
||||
setDefaultState={setDefaultState}
|
||||
/>
|
||||
|
||||
<Separator className="h-full w-[1px] text-zinc-300" />
|
||||
|
||||
<BlockMenuDefaultContent defaultState={defaultState} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockMenuDefault;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { DefaultStateType } from "./BlockMenuDefault";
|
||||
import SuggestionContent from "./SuggestionContent";
|
||||
import AllBlocksContent from "./AllBlocksContent";
|
||||
import InputBlocksContent from "./InputBlocksContent";
|
||||
import ActionBlocksContent from "./ActionBlocksContent";
|
||||
import OutputBlocksContent from "./OutputBlocksContent";
|
||||
import IntegrationsContent from "./IntegrationsContent";
|
||||
import MarketplaceAgentsContent from "./MarketplaceAgentsContent";
|
||||
import MyAgentsContent from "./MyAgentsContent";
|
||||
|
||||
interface BlockMenuDefaultContentProps {
|
||||
defaultState: DefaultStateType;
|
||||
}
|
||||
|
||||
const BlockMenuDefaultContent: React.FC<BlockMenuDefaultContentProps> = ({
|
||||
defaultState,
|
||||
}) => {
|
||||
return (
|
||||
<div className="scrollbar-hide h-full flex-1 overflow-y-auto pt-4">
|
||||
{defaultState == "suggestion" && <SuggestionContent />}
|
||||
{defaultState == "all_blocks" && <AllBlocksContent />}
|
||||
{defaultState == "input_blocks" && <InputBlocksContent />}
|
||||
{defaultState == "action_blocks" && <ActionBlocksContent />}
|
||||
{defaultState == "output_blocks" && <OutputBlocksContent />}
|
||||
{defaultState == "integrations" && <IntegrationsContent />}
|
||||
{defaultState == "marketplace_agents" && <MarketplaceAgentsContent />}
|
||||
{defaultState == "my_agents" && <MyAgentsContent />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockMenuDefaultContent;
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from "react";
|
||||
import MenuItem from "../MenuItem";
|
||||
import { DefaultStateType } from "./BlockMenuDefault";
|
||||
|
||||
interface BlockMenuSidebarProps {
|
||||
defaultState: DefaultStateType;
|
||||
setDefaultState: React.Dispatch<React.SetStateAction<DefaultStateType>>;
|
||||
}
|
||||
|
||||
const BlockMenuSidebar: React.FC<BlockMenuSidebarProps> = ({
|
||||
defaultState,
|
||||
setDefaultState,
|
||||
}) => {
|
||||
// BLOCK MENU TODO: We need to fetch the number of Blocks/Integrations/Agents when opening the menu.
|
||||
// Alternatively, this might depend on the strategy we plan in the future.
|
||||
// We'll add a loading state based on the future plan.
|
||||
|
||||
return (
|
||||
<div className="space-y-2 p-4">
|
||||
<MenuItem
|
||||
name={"Suggestion"}
|
||||
selected={defaultState == "suggestion"}
|
||||
onClick={() => setDefaultState("suggestion")}
|
||||
/>
|
||||
<MenuItem
|
||||
name={"All blocks"}
|
||||
number={103}
|
||||
selected={defaultState == "all_blocks"}
|
||||
onClick={() => setDefaultState("all_blocks")}
|
||||
/>
|
||||
<div className="ml-[0.5365rem] border-l border-black/10 pl-[0.75rem]">
|
||||
<MenuItem
|
||||
name={"Input blocks"}
|
||||
number={12}
|
||||
selected={defaultState == "input_blocks"}
|
||||
onClick={() => setDefaultState("input_blocks")}
|
||||
/>
|
||||
<MenuItem
|
||||
name={"Action blocks"}
|
||||
number={40}
|
||||
selected={defaultState == "action_blocks"}
|
||||
onClick={() => setDefaultState("action_blocks")}
|
||||
/>
|
||||
<MenuItem
|
||||
name={"Output blocks"}
|
||||
number={6}
|
||||
selected={defaultState == "output_blocks"}
|
||||
onClick={() => setDefaultState("output_blocks")}
|
||||
/>
|
||||
</div>
|
||||
<MenuItem
|
||||
name={"Integrations"}
|
||||
number={24}
|
||||
selected={defaultState == "integrations"}
|
||||
onClick={() => setDefaultState("integrations")}
|
||||
/>
|
||||
<MenuItem
|
||||
name={"Marketplace Agents"}
|
||||
number={103}
|
||||
selected={defaultState == "marketplace_agents"}
|
||||
onClick={() => setDefaultState("marketplace_agents")}
|
||||
/>
|
||||
<MenuItem
|
||||
name={"My Agents"}
|
||||
number={6}
|
||||
selected={defaultState == "my_agents"}
|
||||
onClick={() => setDefaultState("my_agents")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockMenuSidebar;
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import Block from "../Block";
|
||||
|
||||
const InputBlocksContent: React.FC = () => {
|
||||
return (
|
||||
<div className="h-full w-full space-y-3 px-4">
|
||||
<Block title="Date Input" description="Input a date into your agent." />
|
||||
<Block
|
||||
title="Dropdown input"
|
||||
description="Give your users the ability to select from a dropdown menu"
|
||||
/>
|
||||
<Block title="File upload" description="Upload a file to your agent" />
|
||||
<Block
|
||||
title="Text input"
|
||||
description="Allow users to select multiple options using checkboxes"
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Add to list"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputBlocksContent;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const IntegrationsContent: React.FC = () => {
|
||||
return <div className="h-full w-full">IntegerationsContent</div>;
|
||||
};
|
||||
|
||||
export default IntegrationsContent;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const MarketplaceAgentsContent: React.FC = () => {
|
||||
return <div className="h-full w-full">MarketplaceAgentsContent</div>;
|
||||
};
|
||||
|
||||
export default MarketplaceAgentsContent;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const MyAgentsContent: React.FC = () => {
|
||||
return <div className="h-full w-full">MyAgentsContent</div>;
|
||||
};
|
||||
|
||||
export default MyAgentsContent;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const OutputBlocksContent: React.FC = () => {
|
||||
return <div className="h-full w-full">OutputBlocksContent</div>;
|
||||
};
|
||||
|
||||
export default OutputBlocksContent;
|
||||
@@ -0,0 +1,83 @@
|
||||
import React from "react";
|
||||
import SearchHistoryChip from "../SearchHistoryChip";
|
||||
import IntegrationChip from "../IntegrationChip";
|
||||
import Block from "../Block";
|
||||
|
||||
const SuggestionContent: React.FC = () => {
|
||||
return (
|
||||
<div className="h-full w-full space-y-6">
|
||||
{/* Recent Searches */}
|
||||
<div className="space-y-2.5">
|
||||
<p className="px-4 font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
|
||||
Recent searches
|
||||
</p>
|
||||
<div className="scrollbar-hide flex flex-nowrap gap-2 overflow-x-auto">
|
||||
<SearchHistoryChip content="image generator" className="ml-4" />
|
||||
<SearchHistoryChip content="deepfake" />
|
||||
<SearchHistoryChip content="competitor analysis" />
|
||||
<SearchHistoryChip content="image generator" />
|
||||
<SearchHistoryChip content="deepfake" />
|
||||
<SearchHistoryChip content="competitor analysis" />
|
||||
<SearchHistoryChip content="image generator" />
|
||||
<SearchHistoryChip content="deepfake" />
|
||||
<SearchHistoryChip content="competitor analysis" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Integrations */}
|
||||
<div className="space-y-2.5 px-4">
|
||||
<p className="font-sans text-xs font-medium leading-[1.25rem] text-zinc-500">
|
||||
Integrations
|
||||
</p>
|
||||
<div className="grid grid-cols-3 grid-rows-2 gap-2">
|
||||
<IntegrationChip icon_url="/integrations/x.png" name="Twitter" />
|
||||
<IntegrationChip icon_url="/integrations/github.png" name="Github" />
|
||||
<IntegrationChip
|
||||
icon_url="/integrations/hubspot.png"
|
||||
name="Hubspot"
|
||||
/>
|
||||
<IntegrationChip
|
||||
icon_url="/integrations/discord.png"
|
||||
name="Discord"
|
||||
/>
|
||||
<IntegrationChip icon_url="/integrations/medium.png" name="Medium" />
|
||||
<IntegrationChip
|
||||
icon_url="/integrations/todoist.png"
|
||||
name="Todoist"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top blocks */}
|
||||
<div className="space-y-2.5 px-4 pb-4">
|
||||
<p className="font-sans text-sm font-medium leading-[1.375rem] text-zinc-800">
|
||||
Top blocks
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<Block
|
||||
title="Find in Dictionary"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Find in Dictionary"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Find in Dictionary"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Find in Dictionary"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
<Block
|
||||
title="Find in Dictionary"
|
||||
description="Enables your agent to chat with users in natural language."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SuggestionContent;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
const BlockMenuSearch: React.FC = () => {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2>Filter Block Menu</h2>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockMenuSearch;
|
||||
@@ -0,0 +1,3 @@
|
||||
// Default state data
|
||||
|
||||
// All blocks
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
import ControlPanelButton from "@/components/builder/block-menu/ControlPanelButton";
|
||||
|
||||
/**
|
||||
* Represents a control element for the ControlPanel Component.
|
||||
@@ -27,6 +21,7 @@ interface ControlPanelProps {
|
||||
controls: Control[];
|
||||
topChildren?: React.ReactNode;
|
||||
botChildren?: React.ReactNode;
|
||||
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -45,42 +40,31 @@ export const ControlPanel = ({
|
||||
className,
|
||||
}: ControlPanelProps) => {
|
||||
return (
|
||||
<Card className={cn("m-4 mt-24 w-14 dark:bg-slate-900", className)}>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col items-center gap-3 rounded-xl py-3">
|
||||
{topChildren}
|
||||
<Separator className="dark:bg-slate-700" />
|
||||
{controls.map((control, index) => (
|
||||
<Tooltip key={index} delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => control.onClick()}
|
||||
data-id={`control-button-${index}`}
|
||||
data-testid={`blocks-control-${control.label.toLowerCase()}-button`}
|
||||
disabled={control.disabled || false}
|
||||
className="dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
|
||||
>
|
||||
{control.icon}
|
||||
<span className="sr-only">{control.label}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="right"
|
||||
className="dark:bg-slate-800 dark:text-slate-100"
|
||||
>
|
||||
{control.label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
))}
|
||||
<Separator className="dark:bg-slate-700" />
|
||||
{botChildren}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<section
|
||||
className={cn(
|
||||
"absolute left-4 top-24 z-10 w-[4.25rem] overflow-hidden rounded-[1rem] border-none bg-white p-0 shadow-[0_1px_5px_0_rgba(0,0,0,0.1)]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center rounded-[1rem] p-0">
|
||||
{topChildren}
|
||||
<Separator className="text-[#E1E1E1]" />
|
||||
{controls.map((control, index) => (
|
||||
<ControlPanelButton
|
||||
key={index}
|
||||
onClick={() => control.onClick()}
|
||||
data-id={`control-button-${index}`}
|
||||
data-testid={`blocks-control-${control.label.toLowerCase()}-button`}
|
||||
disabled={control.disabled || false}
|
||||
className="rounded-none"
|
||||
>
|
||||
{control.icon}
|
||||
</ControlPanelButton>
|
||||
))}
|
||||
<Separator className="text-[#E1E1E1]" />
|
||||
{botChildren}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
export default ControlPanel;
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import ControlPanelButton from "@/components/builder/block-menu/ControlPanelButton";
|
||||
|
||||
interface SaveControlProps {
|
||||
agentMeta: GraphMeta | null;
|
||||
@@ -26,6 +27,11 @@ interface SaveControlProps {
|
||||
onNameChange: (name: string) => void;
|
||||
onDescriptionChange: (description: string) => void;
|
||||
pinSavePopover: boolean;
|
||||
|
||||
blockMenuSelected: "save" | "block" | "";
|
||||
setBlockMenuSelected: React.Dispatch<
|
||||
React.SetStateAction<"" | "save" | "block">
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,6 +54,8 @@ export const SaveControl = ({
|
||||
onNameChange,
|
||||
agentDescription,
|
||||
onDescriptionChange,
|
||||
blockMenuSelected,
|
||||
setBlockMenuSelected,
|
||||
pinSavePopover,
|
||||
}: SaveControlProps) => {
|
||||
/**
|
||||
@@ -82,27 +90,29 @@ export const SaveControl = ({
|
||||
}, [handleSave, toast]);
|
||||
|
||||
return (
|
||||
<Popover open={pinSavePopover ? true : undefined}>
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
data-id="save-control-popover-trigger"
|
||||
data-testid="blocks-control-save-button"
|
||||
name="Save"
|
||||
>
|
||||
<IconSave className="dark:text-gray-300" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">Save</TooltipContent>
|
||||
</Tooltip>
|
||||
<Popover
|
||||
open={pinSavePopover ? true : undefined}
|
||||
onOpenChange={(open) => open || setBlockMenuSelected("")}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<ControlPanelButton
|
||||
data-id="save-control-popover-trigger"
|
||||
data-testid="blocks-control-save-button"
|
||||
selected={blockMenuSelected === "save"}
|
||||
onClick={() => {
|
||||
setBlockMenuSelected("save");
|
||||
}}
|
||||
className="rounded-none"
|
||||
>
|
||||
<IconSave className="h-5 w-5" strokeWidth={2} />
|
||||
</ControlPanelButton>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
side="right"
|
||||
sideOffset={15}
|
||||
sideOffset={16}
|
||||
align="start"
|
||||
className="w-[17rem] rounded-xl border-none p-0 shadow-none md:w-[30rem]"
|
||||
data-id="save-control-popover-content"
|
||||
>
|
||||
<Card className="border-none shadow-none dark:bg-slate-900">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
import scrollbarHide from "tailwind-scrollbar-hide";
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
@@ -144,7 +145,7 @@ const config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
plugins: [require("tailwindcss-animate"), scrollbarHide],
|
||||
} satisfies Config;
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1222,7 +1222,6 @@
|
||||
|
||||
"@fastify/otel@https://codeload.github.com/getsentry/fastify-otel/tar.gz/ae3088d65e286bdc94ac5d722573537d6a6671bb":
|
||||
version "0.8.0"
|
||||
uid "1632d3df7ebf8cd86996a50e9e42721aea05b39c"
|
||||
resolved "https://codeload.github.com/getsentry/fastify-otel/tar.gz/ae3088d65e286bdc94ac5d722573537d6a6671bb#1632d3df7ebf8cd86996a50e9e42721aea05b39c"
|
||||
dependencies:
|
||||
"@opentelemetry/core" "^1.30.1"
|
||||
@@ -11368,6 +11367,11 @@ tailwind-merge@^2.6.0:
|
||||
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.6.0.tgz#ac5fb7e227910c038d458f396b7400d93a3142d5"
|
||||
integrity sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==
|
||||
|
||||
tailwind-scrollbar-hide@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tailwind-scrollbar-hide/-/tailwind-scrollbar-hide-2.0.0.tgz#2d25a3ba383cc7ec1bd516a4416759bb3428f88b"
|
||||
integrity sha512-lqiIutHliEiODwBRHy4G2+Tcayo2U7+3+4frBmoMETD72qtah+XhOk5XcPzC1nJvXhXUdfl2ajlMhUc2qC6CIg==
|
||||
|
||||
tailwindcss-animate@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz#318b692c4c42676cc9e67b19b78775742388bef4"
|
||||
|
||||
Reference in New Issue
Block a user