mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-09 14:25:25 -05:00
Compare commits
2 Commits
fix/execut
...
abhi-9711/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ccf337714 | ||
|
|
df092941e4 |
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useMemo } from "react";
|
import React, { useState, useMemo, useRef, useEffect } from "react";
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -53,6 +53,123 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [selectedBlockIndex, setSelectedBlockIndex] = useState<number>(-1);
|
||||||
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const blockCardsRef = useRef<HTMLDivElement[]>([]);
|
||||||
|
const [isInputFocused, setIsInputFocused] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
// Check if Ctrl+B or Cmd+B was pressed
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === "b") {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsOpen(true);
|
||||||
|
// Focus on the search input after a brief delay to ensure the popover is open
|
||||||
|
setTimeout(() => {
|
||||||
|
searchInputRef.current?.focus();
|
||||||
|
setSelectedBlockIndex(-1);
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Reset selected block index when search query changes
|
||||||
|
setSelectedBlockIndex(-1);
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Focus on the search input when popover opens
|
||||||
|
if (isOpen) {
|
||||||
|
setTimeout(() => {
|
||||||
|
searchInputRef.current?.focus();
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const handleInputFocus = () => {
|
||||||
|
setIsInputFocused(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputBlur = () => {
|
||||||
|
setIsInputFocused(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyNavigation = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (isInputFocused) {
|
||||||
|
// When Enter is pressed in search input, select the first block
|
||||||
|
if (filteredAvailableBlocks.length > 0) {
|
||||||
|
setSelectedBlockIndex(0);
|
||||||
|
setIsInputFocused(false);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
selectedBlockIndex >= 0 &&
|
||||||
|
selectedBlockIndex < filteredAvailableBlocks.length
|
||||||
|
) {
|
||||||
|
// When Enter is pressed on a selected block, add that block
|
||||||
|
const block = filteredAvailableBlocks[selectedBlockIndex];
|
||||||
|
if (!block.notAvailable) {
|
||||||
|
addBlock(block.id, block.name, block?.hardcodedValues || {});
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isInputFocused && filteredAvailableBlocks.length > 0) {
|
||||||
|
// Move from input to first block
|
||||||
|
setSelectedBlockIndex(0);
|
||||||
|
setIsInputFocused(false);
|
||||||
|
} else {
|
||||||
|
// Navigate blocks downward
|
||||||
|
setSelectedBlockIndex((prev) =>
|
||||||
|
Math.min(prev + 1, filteredAvailableBlocks.length - 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (selectedBlockIndex <= 0) {
|
||||||
|
// Go back to input when at first block
|
||||||
|
setSelectedBlockIndex(-1);
|
||||||
|
setIsInputFocused(true);
|
||||||
|
searchInputRef.current?.focus();
|
||||||
|
} else {
|
||||||
|
// Navigate blocks upward
|
||||||
|
setSelectedBlockIndex((prev) => Math.max(prev - 1, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Scroll selected block into view
|
||||||
|
if (selectedBlockIndex >= 0 && blockCardsRef.current[selectedBlockIndex]) {
|
||||||
|
blockCardsRef.current[selectedBlockIndex].scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedBlockIndex]);
|
||||||
|
|
||||||
|
// Function to focus on the newly added block
|
||||||
|
const handleAddBlock = (
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
hardcodedValues: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
addBlock(id, name, hardcodedValues);
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const graphHasWebhookNodes = nodes.some(
|
const graphHasWebhookNodes = nodes.some(
|
||||||
(n) => n.data.uiType == BlockUIType.WEBHOOK,
|
(n) => n.data.uiType == BlockUIType.WEBHOOK,
|
||||||
@@ -129,6 +246,8 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||||||
const resetFilters = React.useCallback(() => {
|
const resetFilters = React.useCallback(() => {
|
||||||
setSearchQuery("");
|
setSearchQuery("");
|
||||||
setSelectedCategory(null);
|
setSelectedCategory(null);
|
||||||
|
setSelectedBlockIndex(-1);
|
||||||
|
setIsInputFocused(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Extract unique categories from blocks
|
// Extract unique categories from blocks
|
||||||
@@ -143,8 +262,11 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
open={pinBlocksPopover ? true : undefined}
|
open={pinBlocksPopover ? true : isOpen}
|
||||||
onOpenChange={(open) => open || resetFilters()}
|
onOpenChange={(open) => {
|
||||||
|
setIsOpen(open);
|
||||||
|
if (!open) resetFilters();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip delayDuration={500}>
|
<Tooltip delayDuration={500}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -169,6 +291,7 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||||||
align="start"
|
align="start"
|
||||||
className="absolute -top-3 w-[17rem] rounded-xl border-none p-0 shadow-none md:w-[30rem]"
|
className="absolute -top-3 w-[17rem] rounded-xl border-none p-0 shadow-none md:w-[30rem]"
|
||||||
data-id="blocks-control-popover-content"
|
data-id="blocks-control-popover-content"
|
||||||
|
onKeyDown={handleKeyNavigation}
|
||||||
>
|
>
|
||||||
<Card className="p-3 pb-0 dark:bg-slate-900">
|
<Card className="p-3 pb-0 dark:bg-slate-900">
|
||||||
<CardHeader className="flex flex-col gap-x-8 gap-y-1 p-3 px-2">
|
<CardHeader className="flex flex-col gap-x-8 gap-y-1 p-3 px-2">
|
||||||
@@ -190,8 +313,13 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||||||
placeholder="Search blocks"
|
placeholder="Search blocks"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
onFocus={handleInputFocus}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
className="rounded-lg px-8 py-5 dark:bg-slate-800 dark:text-white"
|
className="rounded-lg px-8 py-5 dark:bg-slate-800 dark:text-white"
|
||||||
data-id="blocks-control-search-input"
|
data-id="blocks-control-search-input"
|
||||||
|
ref={searchInputRef}
|
||||||
|
autoFocus
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
@@ -222,19 +350,27 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
|||||||
className="h-[60vh] w-full"
|
className="h-[60vh] w-full"
|
||||||
data-id="blocks-control-scroll-area"
|
data-id="blocks-control-scroll-area"
|
||||||
>
|
>
|
||||||
{filteredAvailableBlocks.map((block) => (
|
{filteredAvailableBlocks.map((block, index) => (
|
||||||
<Card
|
<Card
|
||||||
key={block.uiKey || block.id}
|
key={block.uiKey || block.id}
|
||||||
|
ref={(el) => {
|
||||||
|
if (el) blockCardsRef.current[index] = el;
|
||||||
|
}}
|
||||||
className={`m-2 my-4 flex h-20 shadow-none dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-700 ${
|
className={`m-2 my-4 flex h-20 shadow-none dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-700 ${
|
||||||
block.notAvailable
|
block.notAvailable
|
||||||
? "cursor-not-allowed opacity-50"
|
? "cursor-not-allowed opacity-50"
|
||||||
: "cursor-pointer hover:shadow-lg"
|
: "cursor-pointer hover:shadow-lg"
|
||||||
}`}
|
} ${selectedBlockIndex === index ? "border-2 border-blue-500 dark:border-blue-400" : ""}`}
|
||||||
data-id={`block-card-${block.id}`}
|
data-id={`block-card-${block.id}`}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
!block.notAvailable &&
|
if (!block.notAvailable) {
|
||||||
addBlock(block.id, block.name, block?.hardcodedValues || {})
|
handleAddBlock(
|
||||||
}
|
block.id,
|
||||||
|
block.name,
|
||||||
|
block?.hardcodedValues || {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
title={block.notAvailable ?? undefined}
|
title={block.notAvailable ?? undefined}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user