diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json index c48ac18c6..5c45a02b5 100644 --- a/autogpt_platform/frontend/package.json +++ b/autogpt_platform/frontend/package.json @@ -27,6 +27,7 @@ "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.0", + "@radix-ui/react-context-menu": "^2.2.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", diff --git a/autogpt_platform/frontend/src/app/marketplace/page.tsx b/autogpt_platform/frontend/src/app/marketplace/page.tsx index 7cc1f990d..ded8fdf77 100644 --- a/autogpt_platform/frontend/src/app/marketplace/page.tsx +++ b/autogpt_platform/frontend/src/app/marketplace/page.tsx @@ -105,7 +105,7 @@ const AgentCard: React.FC<{ agent: Agent; featured?: boolean }> = ({ return (
diff --git a/autogpt_platform/frontend/src/components/CustomEdge.tsx b/autogpt_platform/frontend/src/components/CustomEdge.tsx index 78b8bf60b..6cf767d32 100644 --- a/autogpt_platform/frontend/src/components/CustomEdge.tsx +++ b/autogpt_platform/frontend/src/components/CustomEdge.tsx @@ -48,9 +48,9 @@ export function CustomEdge({ }>({ beads: [], created: 0, destroyed: 0 }); const { svgPath, length, getPointForT, getTForDistance } = useBezierPath( sourceX - 5, - sourceY, - targetX + 3, - targetY, + sourceY - 5, + targetX - 9, + targetY - 5, ); const { deleteElements } = useReactFlow(); const { visualizeBeads } = useContext(FlowContext) ?? { diff --git a/autogpt_platform/frontend/src/components/CustomNode.tsx b/autogpt_platform/frontend/src/components/CustomNode.tsx index c1a7da136..c08e4a2e9 100644 --- a/autogpt_platform/frontend/src/components/CustomNode.tsx +++ b/autogpt_platform/frontend/src/components/CustomNode.tsx @@ -21,19 +21,20 @@ import { import { beautifyString, cn, setNestedProperty } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; -import { Copy, Trash2 } from "lucide-react"; import { history } from "./history"; import NodeHandle from "./NodeHandle"; import { NodeGenericInputField, NodeTextBoxInput, } from "./node-input-components"; -import SchemaTooltip from "./SchemaTooltip"; import { getPrimaryCategoryColor } from "@/lib/utils"; import { FlowContext } from "./Flow"; import { Badge } from "./ui/badge"; -import DataTable from "./DataTable"; +import NodeOutputs from "./NodeOutputs"; import { IconCoin } from "./ui/icons"; +import * as Separator from "@radix-ui/react-separator"; +import * as ContextMenu from "@radix-ui/react-context-menu"; +import { DotsVerticalIcon, TrashIcon, CopyIcon } from "@radix-ui/react-icons"; type ParsedKey = { key: string; index?: number }; @@ -72,14 +73,19 @@ export type CustomNodeData = { export type CustomNode = Node; -export function CustomNode({ data, id, width, height }: NodeProps) { +export function CustomNode({ + data, + id, + width, + height, + selected, +}: NodeProps) { const [isOutputOpen, setIsOutputOpen] = useState(data.isOutputOpen || false); const [isAdvancedOpen, setIsAdvancedOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [activeKey, setActiveKey] = useState(null); const [inputModalValue, setInputModalValue] = useState(""); const [isOutputModalOpen, setIsOutputModalOpen] = useState(false); - const [isHovered, setIsHovered] = useState(false); const { updateNodeData, deleteElements, addNodes, getNode } = useReactFlow< CustomNode, Edge @@ -165,15 +171,14 @@ export function CustomNode({ data, id, width, height }: NodeProps) { const isAdvanced = propSchema.advanced; return ( (isRequired || isAdvancedOpen || !isAdvanced) && ( -
- +
+ {propSchema.title || beautifyString(propKey)} -
{}}> +
{!isConnected && ( ) { const isAdvanced = propSchema.advanced; return ( (isRequired || isAdvancedOpen || !isAdvanced) && ( -
{}}> +
{propKey !== "value" ? ( - + {propSchema.title || beautifyString(propKey)} ) : ( @@ -233,7 +238,6 @@ export function CustomNode({ data, id, width, height }: NodeProps) { {!isConnected && ( ) { const isAdvanced = propSchema.advanced; return ( (isRequired || isAdvancedOpen || isConnected || !isAdvanced) && ( -
{}}> +
{"credentials_provider" in propSchema ? ( - + Credentials ) : ( @@ -273,7 +277,6 @@ export function CustomNode({ data, id, width, height }: NodeProps) { {!isConnected && ( ) { }; const handleInputClick = (key: string) => { - console.log(`Opening modal for key: ${key}`); + console.debug(`Opening modal for key: ${key}`); setActiveKey(key); const value = getValue(key); setInputModalValue( @@ -421,16 +424,8 @@ export function CustomNode({ data, id, width, height }: NodeProps) { setIsOutputModalOpen(true); }; - const handleHovered = () => { - setIsHovered(true); - }; - - const handleMouseLeave = () => { - setIsHovered(false); - }; - const deleteNode = useCallback(() => { - console.log("Deleting node:", id); + console.debug("Deleting node:", id); // Remove the node deleteElements({ nodes: [{ id }] }); @@ -467,7 +462,7 @@ export function CustomNode({ data, id, width, height }: NodeProps) { history.push({ type: "ADD_NODE", - payload: { node: newNode }, + payload: { node: { ...newNode, ...newNode.data } as CustomNodeData }, undo: () => deleteElements({ nodes: [{ id: newId }] }), redo: () => addNodes(newNode), }); @@ -510,20 +505,53 @@ export function CustomNode({ data, id, width, height }: NodeProps) { "custom-node", "dark-theme", "rounded-xl", - "border", "bg-white/[.9]", - "shadow-md", + "border border-gray-300", + data.uiType === BlockUIType.NOTE ? "w-[300px]" : "w-[500px]", + data.uiType === BlockUIType.NOTE ? "bg-yellow-100" : "bg-white", + selected ? "shadow-2xl" : "", ] .filter(Boolean) .join(" "); const errorClass = - hasConfigErrors || hasOutputError ? "border-red-500 border-2" : ""; + hasConfigErrors || hasOutputError ? "border-red-200 border-2" : ""; - const statusClass = - hasConfigErrors || hasOutputError - ? "failed" - : (data.status?.toLowerCase() ?? ""); + const statusClass = (() => { + if (hasConfigErrors || hasOutputError) return "border-red-200 border-4"; + switch (data.status?.toLowerCase()) { + case "completed": + return "border-green-200 border-4"; + case "running": + return "border-yellow-200 border-4"; + case "failed": + return "border-red-200 border-4"; + case "incomplete": + return "border-purple-200 border-4"; + case "queued": + return "border-cyan-200 border-4"; + default: + return ""; + } + })(); + + const statusBackgroundClass = (() => { + if (hasConfigErrors || hasOutputError) return "bg-red-200"; + switch (data.status?.toLowerCase()) { + case "completed": + return "bg-green-200"; + case "running": + return "bg-yellow-200"; + case "failed": + return "bg-red-200"; + case "incomplete": + return "bg-purple-200"; + case "queued": + return "bg-cyan-200"; + default: + return ""; + } + })(); const hasAdvancedFields = data.inputSchema && @@ -544,115 +572,212 @@ export function CustomNode({ data, id, width, height }: NodeProps) { ), ); - return ( -
-
( +
+ +
+ ); + + const ContextMenuContent = () => ( + + -
-
- {beautifyString( - data.blockType?.replace(/Block$/, "") || data.title, - )} + + Copy + + + + + Delete + + + ); + + const onContextButtonTrigger = (e: React.MouseEvent) => { + e.preventDefault(); + const rect = e.currentTarget.getBoundingClientRect(); + const event = new MouseEvent("contextmenu", { + bubbles: true, + clientX: rect.left + rect.width / 2, + clientY: rect.top + rect.height / 2, + }); + e.currentTarget.dispatchEvent(event); + }; + + const stripeColor = getPrimaryCategoryColor(data.categories); + + const nodeContent = () => ( +
+ {/* Header */} +
+ {/* Color Stripe */} +
+ +
+
+
+ {beautifyString( + data.blockType?.replace(/Block$/, "") || data.title, + )} +
- -
-
- {isHovered && ( - <> - - - + {blockCost && ( +
+ + {" "} + {blockCost.cost_amount}{" "} + credits/{blockCost.cost_type} + +
)}
+ {data.categories.map((category) => ( + + {beautifyString(category.category.toLowerCase())} + + ))} + + +
- {blockCost && ( -
- - {blockCost.cost_amount} credits/{blockCost.cost_type} - -
- )} - {data.uiType !== BlockUIType.NOTE ? ( -
+ {/* Body */} +
+ {/* Input Handles */} + {data.uiType !== BlockUIType.NOTE ? ( +
+
+ {data.inputSchema && + generateInputHandles(data.inputSchema, data.uiType)} +
+
+ ) : (
{data.inputSchema && generateInputHandles(data.inputSchema, data.uiType)}
-
- {data.outputSchema && - generateOutputHandles(data.outputSchema, data.uiType)} -
-
- ) : ( -
- {data.inputSchema && - generateInputHandles(data.inputSchema, data.uiType)} -
- )} - {isOutputOpen && data.uiType !== BlockUIType.NOTE && ( -
- {(data.executionResults?.length ?? 0) > 0 ? ( - <> - + +
+ Advanced + -
- +
+ + )} + {/* Output Handles */} + {data.uiType !== BlockUIType.NOTE && ( + <> + +
+
+ {data.outputSchema && + generateOutputHandles(data.outputSchema, data.uiType)}
- - ) : ( - No outputs yet - )} -
- )} - {data.uiType !== BlockUIType.NOTE && ( -
- - Output - {hasAdvancedFields && ( - <> - - Advanced - - )} - {data.status && ( - + + )} +
+ {/* End Body */} + {/* Footer */} +
+ {/* Display Outputs */} + {isOutputOpen && data.uiType !== BlockUIType.NOTE && ( +
+ {(data.executionResults?.length ?? 0) > 0 ? ( +
+ + +
+ +
+
+ ) : ( +
+ )} +
- {data.status} - - )} -
- )} + + {hasConfigErrors || hasOutputError + ? "Error" + : data.status + ? beautifyString(data.status) + : "Not Run"} + +
+
+ )} +
) { />
); + + return ( + + {nodeContent()} + + ); } diff --git a/autogpt_platform/frontend/src/components/Flow.tsx b/autogpt_platform/frontend/src/components/Flow.tsx index e200a6250..7d7af8c2d 100644 --- a/autogpt_platform/frontend/src/components/Flow.tsx +++ b/autogpt_platform/frontend/src/components/Flow.tsx @@ -44,6 +44,7 @@ import RunnerUIWrapper, { } from "@/components/RunnerUIWrapper"; import PrimaryActionBar from "@/components/PrimaryActionButton"; import { useToast } from "@/components/ui/use-toast"; +import { forceLoad } from "@sentry/nextjs"; // 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 @@ -113,10 +114,20 @@ const FlowEditor: React.FC<{ localStorage.removeItem(TUTORIAL_STORAGE_KEY); router.push(pathname); } else if (!localStorage.getItem(TUTORIAL_STORAGE_KEY)) { - startTutorial(setPinBlocksPopover, setPinSavePopover); + const emptyNodes = (forceRemove: boolean = false) => + forceRemove ? (setNodes([]), setEdges([]), true) : nodes.length === 0; + startTutorial(emptyNodes, setPinBlocksPopover, setPinSavePopover); localStorage.setItem(TUTORIAL_STORAGE_KEY, "yes"); } - }, [availableNodes, router, pathname, params]); + }, [ + availableNodes, + router, + pathname, + params, + setEdges, + setNodes, + nodes.length, + ]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { @@ -423,7 +434,7 @@ const FlowEditor: React.FC<{ history.push({ type: "ADD_NODE", - payload: { node: newNode.data }, + payload: { node: { ...newNode, ...newNode.data } }, undo: () => deleteElements({ nodes: [{ id: newNode.id }] }), redo: () => addNodes(newNode), }); diff --git a/autogpt_platform/frontend/src/components/NavBar.tsx b/autogpt_platform/frontend/src/components/NavBar.tsx index 815543189..cd8de5802 100644 --- a/autogpt_platform/frontend/src/components/NavBar.tsx +++ b/autogpt_platform/frontend/src/components/NavBar.tsx @@ -18,7 +18,7 @@ export async function NavBar() { const { user } = await getServerUser(); return ( -
+
diff --git a/autogpt_platform/frontend/src/components/NodeHandle.tsx b/autogpt_platform/frontend/src/components/NodeHandle.tsx index d059d6567..e42b740d3 100644 --- a/autogpt_platform/frontend/src/components/NodeHandle.tsx +++ b/autogpt_platform/frontend/src/components/NodeHandle.tsx @@ -31,20 +31,27 @@ const NodeHandle: FC = ({ const typeClass = `text-sm ${getTypeTextColor(schema.type || "any")} ${side === "left" ? "text-left" : "text-right"}`; const label = ( -
- - {schema.title || beautifyString(keyName)} +
+ + {schema.title || beautifyString(keyName.toLowerCase())} {isRequired ? "*" : ""} - {typeName[schema.type] || "any"} + + ({typeName[schema.type as keyof typeof typeName] || "any"}) +
); - const dot = ( -
- ); + const Dot = ({ className = "" }) => { + const color = isConnected + ? getTypeBgColor(schema.type || "any") + : "border-gray-300"; + return ( +
+ ); + }; if (side === "left") { return ( @@ -53,10 +60,10 @@ const NodeHandle: FC = ({ type="target" position={Position.Left} id={keyName} - className="background-color: white; border: 2px solid black; width: 15px; height: 15px; border-radius: 50%; bottom: -7px; left: 20%; group -ml-[26px]" + className="-ml-[26px]" >
- {dot} + {label}
@@ -74,7 +81,7 @@ const NodeHandle: FC = ({ >
{label} - {dot} +
diff --git a/autogpt_platform/frontend/src/components/NodeOutputs.tsx b/autogpt_platform/frontend/src/components/NodeOutputs.tsx new file mode 100644 index 000000000..c7b7dc571 --- /dev/null +++ b/autogpt_platform/frontend/src/components/NodeOutputs.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { ContentRenderer } from "./ui/render"; +import { beautifyString } from "@/lib/utils"; +import * as Separator from "@radix-ui/react-separator"; + +type NodeOutputsProps = { + title?: string; + truncateLongData?: boolean; + data: { [key: string]: Array }; +}; + +export default function NodeOutputs({ + title, + truncateLongData, + data, +}: NodeOutputsProps) { + return ( +
+ {title && {title}} + {Object.entries(data).map(([pin, dataArray]) => ( +
+
+ Pin: + {beautifyString(pin)} +
+ +
+ Data: +
+ {dataArray.map((item, index) => ( + + + {index < dataArray.length - 1 && ", "} + + ))} +
+
+
+ ))} +
+ ); +} diff --git a/autogpt_platform/frontend/src/components/OutputModalComponent.tsx b/autogpt_platform/frontend/src/components/OutputModalComponent.tsx index fcf33049f..082bdb022 100644 --- a/autogpt_platform/frontend/src/components/OutputModalComponent.tsx +++ b/autogpt_platform/frontend/src/components/OutputModalComponent.tsx @@ -26,7 +26,7 @@ const OutputModalComponent: FC = ({
Output Data History -
+
{executionResults.map((data, i) => ( <> diff --git a/autogpt_platform/frontend/src/components/PrimaryActionButton.tsx b/autogpt_platform/frontend/src/components/PrimaryActionButton.tsx index cbcf00ac9..ee3c83784 100644 --- a/autogpt_platform/frontend/src/components/PrimaryActionButton.tsx +++ b/autogpt_platform/frontend/src/components/PrimaryActionButton.tsx @@ -59,7 +59,7 @@ const PrimaryActionBar: React.FC = ({ onClick={runButtonOnClick} size="primary" style={{ - background: isRunning ? "#FFB3BA" : "#7544DF", + background: isRunning ? "#DF4444" : "#7544DF", opacity: isDisabled ? 0.5 : 1, }} data-id="primary-action-run-agent" diff --git a/autogpt_platform/frontend/src/components/TallyPopup.tsx b/autogpt_platform/frontend/src/components/TallyPopup.tsx index e36738598..89b02cf49 100644 --- a/autogpt_platform/frontend/src/components/TallyPopup.tsx +++ b/autogpt_platform/frontend/src/components/TallyPopup.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useEffect, useState } from "react"; import { Button } from "./ui/button"; -import { IconMegaphone } from "@/components/ui/icons"; +import { QuestionMarkCircledIcon } from "@radix-ui/react-icons"; import { useRouter } from "next/navigation"; const TallyPopupSimple = () => { @@ -48,17 +48,22 @@ const TallyPopupSimple = () => { }; return ( -
-
diff --git a/autogpt_platform/frontend/src/components/customnode.css b/autogpt_platform/frontend/src/components/customnode.css index 840fd31a9..b361257c7 100644 --- a/autogpt_platform/frontend/src/components/customnode.css +++ b/autogpt_platform/frontend/src/components/customnode.css @@ -4,20 +4,6 @@ transition: border-color 0.3s ease-in-out; } -.custom-node .mb-2 { - display: flex; - justify-content: space-between; - align-items: center; - min-height: 40px; - /* Increased to accommodate larger buttons */ - margin-bottom: 10px; -} - -.custom-node .mb-2 .text-lg { - flex-grow: 1; - margin-right: 10px; -} - /* Existing styles */ .handle-container { display: flex; diff --git a/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx b/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx index 0813d08d9..7ddc7cdd4 100644 --- a/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx +++ b/autogpt_platform/frontend/src/components/edit/control/BlocksControl.tsx @@ -2,7 +2,6 @@ import React, { useState } from "react"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; -import { ToyBrick } from "lucide-react"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { beautifyString } from "@/lib/utils"; @@ -12,11 +11,9 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { Block } from "@/lib/autogpt-server-api"; -import { PlusIcon } from "@radix-ui/react-icons"; +import { MagnifyingGlassIcon, PlusIcon } from "@radix-ui/react-icons"; import { IconToyBrick } from "@/components/ui/icons"; -import SchemaTooltip from "@/components/SchemaTooltip"; import { getPrimaryCategoryColor } from "@/lib/utils"; -import { Badge } from "@/components/ui/badge"; import { Tooltip, TooltipContent, @@ -43,26 +40,28 @@ export const BlocksControl: React.FC = ({ addBlock, pinBlocksPopover, }) => { + const blockList = blocks.sort((a, b) => a.name.localeCompare(b.name)); const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState(null); - const [filteredBlocks, setFilteredBlocks] = useState(blocks); + const [filteredBlocks, setFilteredBlocks] = useState(blockList); const resetFilters = React.useCallback(() => { setSearchQuery(""); setSelectedCategory(null); - setFilteredBlocks(blocks); - }, [blocks]); + setFilteredBlocks(blockList); + }, [blockList]); // Extract unique categories from blocks const categories = Array.from( - new Set( - blocks.flatMap((block) => block.categories.map((cat) => cat.category)), - ), + new Set([ + null, + ...blocks.flatMap((block) => block.categories.map((cat) => cat.category)), + ]), ); React.useEffect(() => { setFilteredBlocks( - blocks.filter( + blockList.filter( (block: Block) => (block.name.toLowerCase().includes(searchQuery.toLowerCase()) || beautifyString(block.name) @@ -72,7 +71,7 @@ export const BlocksControl: React.FC = ({ block.categories.some((cat) => cat.category === selectedCategory)), ), ); - }, [blocks, searchQuery, selectedCategory]); + }, [blockList, searchQuery, selectedCategory]); return ( = ({ side="right" sideOffset={22} align="start" - className="w-[30rem] p-0" + className="absolute -top-3 w-[17rem] rounded-xl border-none p-0 shadow-none md:w-[30rem]" data-id="blocks-control-popover-content" > - - + +
- setSearchQuery(e.target.value)} - data-id="blocks-control-search-input" - /> +
+ + setSearchQuery(e.target.value)} + className="rounded-lg px-8 py-5" + data-id="blocks-control-search-input" + /> +
- {categories.map((category) => ( - - setSelectedCategory( - selectedCategory === category ? null : category, - ) - } - > - {beautifyString(category)} - - ))} + {categories.map((category) => { + const color = getPrimaryCategoryColor([ + { category: category || "All", description: "" }, + ]); + const colorClass = + selectedCategory === category ? `${color}` : ""; + return ( +
+ setSelectedCategory( + selectedCategory === category ? null : category, + ) + } + > + {beautifyString((category || "All").toLowerCase())} +
+ ); + })}
- + {filteredBlocks.map((block) => ( addBlock(block.id, block.name)} > - {/* This div needs to be 10px wide and the same height as the card and be the primary color showing up on top of the card with matching rounded corners */}
-
+
- {beautifyString(block.name)} + {beautifyString(block.name).replace(/ Block$/, "")} - + {block.description}
+ > + +
))} diff --git a/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx b/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx index 8a9476d74..f3db18845 100644 --- a/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx +++ b/autogpt_platform/frontend/src/components/edit/control/ControlPanel.tsx @@ -47,7 +47,7 @@ export const ControlPanel = ({ return ( -
+
{topChildren} {controls.map((control, index) => ( diff --git a/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx b/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx index 0a1820510..030d83c16 100644 --- a/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx +++ b/autogpt_platform/frontend/src/components/edit/control/SaveControl.tsx @@ -76,7 +76,12 @@ export const SaveControl = ({ Save - +
diff --git a/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx b/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx index ff4f398c9..b0572b106 100644 --- a/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx +++ b/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx @@ -114,7 +114,7 @@ function AgentDetailContent({ agent }: { agent: AgentDetailResponse }) { {agent.description}

-
+
diff --git a/autogpt_platform/frontend/src/components/nav/CreditButton.tsx b/autogpt_platform/frontend/src/components/nav/CreditButton.tsx index d80615398..0a8d9d446 100644 --- a/autogpt_platform/frontend/src/components/nav/CreditButton.tsx +++ b/autogpt_platform/frontend/src/components/nav/CreditButton.tsx @@ -1,21 +1,23 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; -import { IconRefresh, IconCoin } from "@/components/ui/icons"; +import { IconRefresh } from "@/components/ui/icons"; import AutoGPTServerAPI from "@/lib/autogpt-server-api"; +const api = new AutoGPTServerAPI(); + export default function CreditButton() { const [credit, setCredit] = useState(null); - const api = new AutoGPTServerAPI(); - const fetchCredit = async () => { + const fetchCredit = useCallback(async () => { const response = await api.getUserCredit(); setCredit(response.credits); - }; + }, []); + useEffect(() => { fetchCredit(); - }, [api]); + }, [fetchCredit]); return ( credit !== null && ( diff --git a/autogpt_platform/frontend/src/components/node-input-components.tsx b/autogpt_platform/frontend/src/components/node-input-components.tsx index 516542b0f..a55fa0902 100644 --- a/autogpt_platform/frontend/src/components/node-input-components.tsx +++ b/autogpt_platform/frontend/src/components/node-input-components.tsx @@ -9,7 +9,6 @@ import { BlockIOStringSubSchema, BlockIONumberSubSchema, BlockIOBooleanSubSchema, - BlockIOCredentialsSubSchema, } from "@/lib/autogpt-server-api/types"; import React, { FC, useCallback, useEffect, useState } from "react"; import { Button } from "./ui/button"; @@ -110,6 +109,7 @@ export const NodeGenericInputField: FC<{ className, displayName, }) => { + className = cn(className, "my-2"); displayName ||= propSchema.title || beautifyString(propKey); if ("allOf" in propSchema) { @@ -322,6 +322,10 @@ const NodeCredentialsInput: FC<{ ); }; +const InputRef = (value: any): ((el: HTMLInputElement | null) => void) => { + return (el) => el && value && (el.value = value); +}; + const NodeKeyValueInput: FC<{ nodeId: string; selfKey: string; @@ -348,7 +352,7 @@ const NodeKeyValueInput: FC<{ const defaultEntries = new Map( Object.entries(entries ?? schema.default ?? {}), ); - const prefix = getEntryKey(""); + const prefix = `${selfKey}_#_`; connections .filter((c) => c.targetHandle.startsWith(prefix)) .map((c) => c.targetHandle.slice(prefix.length)) @@ -368,6 +372,7 @@ const NodeKeyValueInput: FC<{ function updateKeyValuePairs(newPairs: typeof keyValuePairs) { setKeyValuePairs(newPairs); + handleInputChange( selfKey, newPairs.reduce((obj, { key, value }) => ({ ...obj, [key]: value }), {}), @@ -394,27 +399,26 @@ const NodeKeyValueInput: FC<{ } return ( -
- {displayName && {displayName}} +
0 ? "flex flex-col" : "")} + >
{keyValuePairs.map(({ key, value }, index) => (
- {key && ( - - )} + {!isConnected(key) && (
+ ref={InputRef(key ?? "")} + onBlur={(e) => updateKeyValuePairs( keyValuePairs.toSpliced(index, 1, { key: e.target.value, @@ -426,7 +430,7 @@ const NodeKeyValueInput: FC<{ updateKeyValuePairs( keyValuePairs.toSpliced(index, 1, { @@ -455,7 +459,11 @@ const NodeKeyValueInput: FC<{
))}