-
- {trigger || }
-
+ {showTrigger && (
+
+ {trigger || }
+
+ )}
{renderContent()}
diff --git a/autogpt_platform/frontend/src/components/contextual/PublishAgentModal/usePublishAgentModal.ts b/autogpt_platform/frontend/src/components/contextual/PublishAgentModal/usePublishAgentModal.ts
index f83698d8e7..0f8a819c6e 100644
--- a/autogpt_platform/frontend/src/components/contextual/PublishAgentModal/usePublishAgentModal.ts
+++ b/autogpt_platform/frontend/src/components/contextual/PublishAgentModal/usePublishAgentModal.ts
@@ -30,6 +30,7 @@ export interface Props {
onStateChange?: (state: PublishState) => void;
preSelectedAgentId?: string;
preSelectedAgentVersion?: number;
+ showTrigger?: boolean;
}
export function usePublishAgentModal({
diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/Navbar.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/Navbar.tsx
index 1441cbfb65..c5e9cabd63 100644
--- a/autogpt_platform/frontend/src/components/layout/Navbar/Navbar.tsx
+++ b/autogpt_platform/frontend/src/components/layout/Navbar/Navbar.tsx
@@ -1,13 +1,157 @@
+"use client";
+
+import { useGetV2GetUserProfile } from "@/app/api/__generated__/endpoints/store/store";
+import { okData } from "@/app/api/helpers";
+import { IconAutoGPTLogo, IconType } from "@/components/__legacy__/ui/icons";
+import { PreviewBanner } from "@/components/layout/Navbar/components/PreviewBanner/PreviewBanner";
+import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
+import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { environment } from "@/services/environment";
+import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
+import { AccountMenu } from "./components/AccountMenu/AccountMenu";
+import { AgentActivityDropdown } from "./components/AgentActivityDropdown/AgentActivityDropdown";
+import { LoginButton } from "./components/LoginButton";
+import { MobileNavBar } from "./components/MobileNavbar/MobileNavBar";
+import { NavbarLink } from "./components/NavbarLink";
+import { NavbarLoading } from "./components/NavbarLoading";
+import { Wallet } from "./components/Wallet/Wallet";
+import { getAccountMenuItems, loggedInLinks, loggedOutLinks } from "./helpers";
-import { NavbarView } from "./components/NavbarView";
-import { getNavbarAccountData } from "./data";
-
-export async function Navbar() {
- const { isLoggedIn } = await getNavbarAccountData();
+export function Navbar() {
+ const { user, isLoggedIn, isUserLoading } = useSupabase();
+ const breakpoint = useBreakpoint();
+ const isSmallScreen = breakpoint === "sm" || breakpoint === "base";
+ const dynamicMenuItems = getAccountMenuItems(user?.role);
+ const isChatEnabled = useGetFlag(Flag.CHAT);
const previewBranchName = environment.getPreviewStealingDev();
+ const { data: profile, isLoading: isProfileLoading } = useGetV2GetUserProfile(
+ {
+ query: {
+ select: okData,
+ enabled: isLoggedIn && !!user,
+ // Include user ID in query key to ensure cache invalidation when user changes
+ queryKey: ["/api/store/profile", user?.id],
+ },
+ },
+ );
+
+ const isLoadingProfile = isProfileLoading || isUserLoading;
+
+ const shouldShowPreviewBanner = Boolean(isLoggedIn && previewBranchName);
+
+ const actualLoggedInLinks =
+ isChatEnabled === true
+ ? loggedInLinks.concat([{ name: "Chat", href: "/chat" }])
+ : loggedInLinks;
+
+ if (isUserLoading) {
+ return
;
+ }
+
return (
-
+ <>
+
+ {shouldShowPreviewBanner && previewBranchName ? (
+
+ ) : null}
+
+
+ {/* Mobile Navbar - Adjust positioning */}
+ <>
+ {isLoggedIn && isSmallScreen ? (
+
+
+ {
+ if (link.name === "Chat" && !isChatEnabled) {
+ return null;
+ }
+
+ return {
+ icon:
+ link.name === "Marketplace"
+ ? IconType.Marketplace
+ : link.name === "Library"
+ ? IconType.Library
+ : link.name === "Build"
+ ? IconType.Builder
+ : link.name === "Chat"
+ ? IconType.Chat
+ : link.name === "Monitor"
+ ? IconType.Library
+ : IconType.LayoutDashboard,
+ text: link.name,
+ href: link.href,
+ };
+ })
+ .filter((item) => item !== null) as Array<{
+ icon: IconType;
+ text: string;
+ href: string;
+ }>,
+ },
+ ...dynamicMenuItems,
+ ]}
+ userEmail={profile?.name}
+ avatarSrc={profile?.avatar_url ?? ""}
+ />
+
+ ) : null}
+ >
+ >
);
}
diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/AccountMenu/components/AccountLogoutOption.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/AccountMenu/components/AccountLogoutOption.tsx
index b0061ec2c9..570f05ca89 100644
--- a/autogpt_platform/frontend/src/components/layout/Navbar/components/AccountMenu/components/AccountLogoutOption.tsx
+++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/AccountMenu/components/AccountLogoutOption.tsx
@@ -6,45 +6,42 @@ import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { cn } from "@/lib/utils";
import * as Sentry from "@sentry/nextjs";
import { useRouter } from "next/navigation";
-import { useState } from "react";
+import { useTransition } from "react";
export function AccountLogoutOption() {
- const [isLoggingOut, setIsLoggingOut] = useState(false);
+ const [isPending, startTransition] = useTransition();
const supabase = useSupabase();
const router = useRouter();
const { toast } = useToast();
- async function handleLogout() {
- setIsLoggingOut(true);
- try {
- await supabase.logOut();
- router.push("/login");
- } catch (e) {
- Sentry.captureException(e);
- toast({
- title: "Error logging out",
- description:
- "Something went wrong when logging out. Please try again. If the problem persists, please contact support.",
- variant: "destructive",
- });
- } finally {
- setTimeout(() => {
- setIsLoggingOut(false);
- }, 3000);
- }
+ function handleLogout() {
+ startTransition(async () => {
+ try {
+ await supabase.logOut();
+ router.replace("/login");
+ } catch (e) {
+ Sentry.captureException(e);
+ toast({
+ title: "Error logging out",
+ description:
+ "Something went wrong when logging out. Please try again. If the problem persists, please contact support.",
+ variant: "destructive",
+ });
+ }
+ });
}
return (
- {isLoggingOut ? (
+ {isPending ? (
) : (
<>
diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLoading.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLoading.tsx
index 42362d24d4..322574fdb0 100644
--- a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLoading.tsx
+++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLoading.tsx
@@ -5,16 +5,15 @@ export function NavbarLoading() {
return (
);
diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarView.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarView.tsx
deleted file mode 100644
index 863b9f601f..0000000000
--- a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarView.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-"use client";
-
-import { useGetV2GetUserProfile } from "@/app/api/__generated__/endpoints/store/store";
-import { IconAutoGPTLogo, IconType } from "@/components/__legacy__/ui/icons";
-import { PreviewBanner } from "@/components/layout/Navbar/components/PreviewBanner/PreviewBanner";
-import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
-import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
-import { useMemo } from "react";
-import { okData } from "@/app/api/helpers";
-import { getAccountMenuItems, loggedInLinks, loggedOutLinks } from "../helpers";
-import { AccountMenu } from "./AccountMenu/AccountMenu";
-import { AgentActivityDropdown } from "./AgentActivityDropdown/AgentActivityDropdown";
-import { LoginButton } from "./LoginButton";
-import { MobileNavBar } from "./MobileNavbar/MobileNavBar";
-import { NavbarLink } from "./NavbarLink";
-import { Wallet } from "./Wallet/Wallet";
-interface NavbarViewProps {
- isLoggedIn: boolean;
- previewBranchName?: string | null;
-}
-
-export function NavbarView({ isLoggedIn, previewBranchName }: NavbarViewProps) {
- const { user } = useSupabase();
- const breakpoint = useBreakpoint();
- const isSmallScreen = breakpoint === "sm" || breakpoint === "base";
- const dynamicMenuItems = getAccountMenuItems(user?.role);
- const isChatEnabled = useGetFlag(Flag.CHAT);
-
- const { data: profile, isLoading: isProfileLoading } = useGetV2GetUserProfile(
- {
- query: {
- select: okData,
- enabled: isLoggedIn && !!user,
- // Include user ID in query key to ensure cache invalidation when user changes
- queryKey: ["/api/store/profile", user?.id],
- },
- },
- );
-
- const { isUserLoading } = useSupabase();
- const isLoadingProfile = isProfileLoading || isUserLoading;
-
- const linksWithChat = useMemo(() => {
- const chatLink = { name: "Chat", href: "/chat" };
- return isChatEnabled ? [...loggedInLinks, chatLink] : loggedInLinks;
- }, [isChatEnabled]);
-
- const shouldShowPreviewBanner = Boolean(isLoggedIn && previewBranchName);
-
- return (
- <>
-
- {shouldShowPreviewBanner && previewBranchName ? (
-
- ) : null}
-
-
- {/* Mobile Navbar - Adjust positioning */}
- <>
- {isLoggedIn && isSmallScreen ? (
-
-
- ({
- icon:
- link.name === "Marketplace"
- ? IconType.Marketplace
- : link.name === "Library"
- ? IconType.Library
- : link.name === "Build"
- ? IconType.Builder
- : link.name === "Chat"
- ? IconType.Chat
- : link.name === "Monitor"
- ? IconType.Library
- : IconType.LayoutDashboard,
- text: link.name,
- href: link.href,
- })),
- },
- ...dynamicMenuItems,
- ]}
- userEmail={profile?.name}
- avatarSrc={profile?.avatar_url ?? ""}
- />
-
- ) : null}
- >
- >
- );
-}
diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/data.ts b/autogpt_platform/frontend/src/components/layout/Navbar/data.ts
deleted file mode 100644
index 0d07cef78b..0000000000
--- a/autogpt_platform/frontend/src/components/layout/Navbar/data.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { prefetchGetV2GetUserProfileQuery } from "@/app/api/__generated__/endpoints/store/store";
-import { getQueryClient } from "@/lib/react-query/queryClient";
-import { getServerUser } from "@/lib/supabase/server/getServerUser";
-
-export async function getNavbarAccountData() {
- const { user } = await getServerUser();
- const isLoggedIn = Boolean(user);
- const queryClient = getQueryClient();
-
- if (!isLoggedIn) {
- return {
- profile: null,
- isLoggedIn,
- };
- }
- try {
- await prefetchGetV2GetUserProfileQuery(queryClient);
- } catch (error) {
- console.error("Error fetching profile:", error);
- }
-
- return {
- isLoggedIn,
- };
-}
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/FormRenderer.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/FormRenderer.tsx
similarity index 83%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/FormRenderer.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/FormRenderer.tsx
index 53df5f9a37..50137aa04f 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/FormRenderer.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/FormRenderer.tsx
@@ -1,27 +1,17 @@
-import { BlockUIType } from "@/app/(platform)/build/components/types";
-import Form from "@rjsf/core";
import { RJSFSchema } from "@rjsf/utils";
-import { fields } from "./fields";
-import { templates } from "./templates";
-import { widgets } from "./widgets";
import { preprocessInputSchema } from "./utils/input-schema-pre-processor";
import { useMemo } from "react";
import { customValidator } from "./utils/custom-validator";
import { isLlmModelFieldSchema } from "./fields/LlmModelField/LlmModelField";
-
-type FormContextType = {
- nodeId?: string;
- uiType?: BlockUIType;
- showHandles?: boolean;
- size?: "small" | "medium" | "large";
-};
+import Form from "./registry";
+import { ExtendedFormContextType } from "./types";
type FormRendererProps = {
jsonSchema: RJSFSchema;
handleChange: (formData: any) => void;
uiSchema: any;
initialValues: any;
- formContext: FormContextType;
+ formContext: ExtendedFormContextType;
};
export const FormRenderer = ({
@@ -43,18 +33,16 @@ export const FormRenderer = ({
return mergeUiSchema(uiSchema, llmModelUiSchema);
}, [uiSchema, llmModelUiSchema]);
return (
-
+
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/AnyOfField.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/AnyOfField.tsx
new file mode 100644
index 0000000000..3eb5b45a5e
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/AnyOfField.tsx
@@ -0,0 +1,86 @@
+import { FieldProps, getUiOptions, getWidget } from "@rjsf/utils";
+import { AnyOfFieldTitle } from "./components/AnyOfFieldTitle";
+import { isEmpty } from "lodash";
+import { useAnyOfField } from "./useAnyOfField";
+import { getHandleId, updateUiOption } from "../../helpers";
+import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
+import { ANY_OF_FLAG } from "../../constants";
+
+export const AnyOfField = (props: FieldProps) => {
+ const { registry, schema } = props;
+ const { fields } = registry;
+ const { SchemaField: _SchemaField } = fields;
+ const { nodeId } = registry.formContext;
+
+ const { isInputConnected } = useEdgeStore();
+
+ const uiOptions = getUiOptions(props.uiSchema, props.globalUiOptions);
+
+ const Widget = getWidget({ type: "string" }, "select", registry.widgets);
+
+ const {
+ handleOptionChange,
+ enumOptions,
+ selectedOption,
+ optionSchema,
+ field_id,
+ } = useAnyOfField(props);
+
+ const handleId = getHandleId({
+ uiOptions,
+ id: field_id + ANY_OF_FLAG,
+ schema: schema,
+ });
+
+ const updatedUiSchema = updateUiOption(props.uiSchema, {
+ handleId: handleId,
+ label: false,
+ fromAnyOf: true,
+ });
+
+ const isHandleConnected = isInputConnected(nodeId, handleId);
+
+ const optionsSchemaField =
+ (optionSchema && optionSchema.type !== "null" && (
+ <_SchemaField
+ {...props}
+ schema={optionSchema}
+ uiSchema={updatedUiSchema}
+ />
+ )) ||
+ null;
+
+ const selector = (
+
= 0 ? selectedOption : undefined}
+ options={{ enumOptions }}
+ registry={registry}
+ placeholder={props.placeholder}
+ autocomplete={props.autocomplete}
+ className="-ml-1 h-[22px] w-fit gap-1 px-1 pl-2 text-xs font-medium"
+ autofocus={props.autofocus}
+ label=""
+ hideLabel={true}
+ readonly={props.readonly}
+ />
+ );
+
+ return (
+
+
+ {!isHandleConnected && optionsSchemaField}
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/components/AnyOfFieldTitle.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/components/AnyOfFieldTitle.tsx
new file mode 100644
index 0000000000..bd8aadc6a5
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/components/AnyOfFieldTitle.tsx
@@ -0,0 +1,78 @@
+import {
+ descriptionId,
+ FieldProps,
+ getTemplate,
+ getUiOptions,
+ titleId,
+} from "@rjsf/utils";
+import { shouldShowTypeSelector } from "../helpers";
+import { useIsArrayItem } from "../../array/context/array-item-context";
+import { cleanUpHandleId } from "../../../helpers";
+import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
+import { Text } from "@/components/atoms/Text/Text";
+import { isOptionalType } from "../../../utils/schema-utils";
+import { getTypeDisplayInfo } from "@/app/(platform)/build/components/FlowEditor/nodes/helpers";
+import { cn } from "@/lib/utils";
+
+interface customFieldProps extends FieldProps {
+ selector: JSX.Element;
+}
+
+export const AnyOfFieldTitle = (props: customFieldProps) => {
+ const { uiSchema, schema, required, name, registry, fieldPathId, selector } =
+ props;
+ const { isInputConnected } = useEdgeStore();
+ const { nodeId } = registry.formContext;
+
+ const uiOptions = getUiOptions(uiSchema);
+ const TitleFieldTemplate = getTemplate(
+ "TitleFieldTemplate",
+ registry,
+ uiOptions,
+ );
+ const DescriptionFieldTemplate = getTemplate(
+ "DescriptionFieldTemplate",
+ registry,
+ uiOptions,
+ );
+
+ const title_id = titleId(fieldPathId ?? "");
+ const description_id = descriptionId(fieldPathId ?? "");
+
+ const isArrayItem = useIsArrayItem();
+
+ const handleId = cleanUpHandleId(uiOptions.handleId);
+ const isHandleConnected = isInputConnected(nodeId, handleId);
+
+ const { isOptional, type } = isOptionalType(schema); // If we have something like int | null = we will treat it as optional int
+ const { displayType, colorClass } = getTypeDisplayInfo(type);
+
+ const shouldShowSelector =
+ shouldShowTypeSelector(schema) && !isArrayItem && !isHandleConnected;
+ const shoudlShowType = isHandleConnected || (isOptional && type);
+
+ return (
+
+
+ {shoudlShowType && (
+
+ {isOptional ? `(${displayType})` : "(any)"}
+
+ )}
+ {shouldShowSelector && selector}
+
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/helpers.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/helpers.ts
new file mode 100644
index 0000000000..0a18bbf4d7
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/helpers.ts
@@ -0,0 +1,61 @@
+import { RJSFSchema, StrictRJSFSchema } from "@rjsf/utils";
+
+const TYPE_PRIORITY = [
+ "string",
+ "number",
+ "integer",
+ "boolean",
+ "array",
+ "object",
+] as const;
+
+export function getDefaultTypeIndex(options: StrictRJSFSchema[]): number {
+ for (const preferredType of TYPE_PRIORITY) {
+ const index = options.findIndex((opt) => opt.type === preferredType);
+ if (index >= 0) return index;
+ }
+
+ const nonNullIndex = options.findIndex((opt) => opt.type !== "null");
+ return nonNullIndex >= 0 ? nonNullIndex : 0;
+}
+
+/**
+ * Determines if a type selector should be shown for an anyOf schema
+ * Returns false for simple optional types (type | null)
+ * Returns true for complex anyOf (3+ types or multiple non-null types)
+ */
+export function shouldShowTypeSelector(
+ schema: RJSFSchema | undefined,
+): boolean {
+ const anyOf = schema?.anyOf;
+ if (!anyOf || !Array.isArray(anyOf) || anyOf.length === 0) {
+ return false;
+ }
+
+ if (anyOf.length === 2 && anyOf.some((opt: any) => opt.type === "null")) {
+ return false;
+ }
+
+ return anyOf.length >= 3;
+}
+
+export function isSimpleOptional(schema: RJSFSchema | undefined): boolean {
+ const anyOf = schema?.anyOf;
+ return (
+ Array.isArray(anyOf) &&
+ anyOf.length === 2 &&
+ anyOf.some((opt: any) => opt.type === "null")
+ );
+}
+
+export function getOptionalType(
+ schema: RJSFSchema | undefined,
+): string | undefined {
+ if (!isSimpleOptional(schema)) {
+ return undefined;
+ }
+
+ const anyOf = schema?.anyOf;
+ const nonNullOption = anyOf?.find((opt: any) => opt.type !== "null");
+ return nonNullOption ? (nonNullOption as any).type : undefined;
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/useAnyOfField.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/useAnyOfField.ts
new file mode 100644
index 0000000000..15825902b6
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/anyof/useAnyOfField.ts
@@ -0,0 +1,96 @@
+import { FieldProps, getFirstMatchingOption, mergeSchemas } from "@rjsf/utils";
+import { useRef, useState } from "react";
+import validator from "@rjsf/validator-ajv8";
+import { getDefaultTypeIndex } from "./helpers";
+import { cleanUpHandleId } from "../../helpers";
+import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
+
+export const useAnyOfField = (props: FieldProps) => {
+ const { registry, schema, options, onChange, formData } = props;
+ const { schemaUtils } = registry;
+
+ const getInitialOption = () => {
+ if (formData !== undefined && formData !== null) {
+ const option = getFirstMatchingOption(
+ validator,
+ formData,
+ options,
+ schema,
+ );
+ return option !== undefined ? option : getDefaultTypeIndex(options);
+ }
+ return getDefaultTypeIndex(options);
+ };
+
+ const [selectedOption, setSelectedOption] =
+ useState(getInitialOption());
+ const retrievedOptions = useRef(
+ options.map((opt: any) => schemaUtils.retrieveSchema(opt, formData)),
+ );
+
+ const option =
+ selectedOption >= 0
+ ? retrievedOptions.current[selectedOption] || null
+ : null;
+ let optionSchema: any | undefined | null;
+
+ // adding top level required to each option schema
+ if (option) {
+ const { required } = schema;
+ optionSchema = required
+ ? (mergeSchemas({ required }, option) as any)
+ : option;
+ }
+
+ const field_id = props.fieldPathId.$id;
+
+ const handleOptionChange = (option?: string) => {
+ const intOption = option !== undefined ? parseInt(option, 10) : -1;
+ if (intOption === selectedOption) return;
+
+ const newOption =
+ intOption >= 0 ? retrievedOptions.current[intOption] : undefined;
+ const oldOption =
+ selectedOption >= 0
+ ? retrievedOptions.current[selectedOption]
+ : undefined;
+
+ // When we change the option, we need to clean the form data
+ let newFormData = schemaUtils.sanitizeDataForNewSchema(
+ newOption,
+ oldOption,
+ formData,
+ );
+
+ const handlePrefix = cleanUpHandleId(field_id);
+ console.log("handlePrefix", handlePrefix);
+ useEdgeStore
+ .getState()
+ .removeEdgesByHandlePrefix(registry.formContext.nodeId, handlePrefix);
+
+ // We have cleaned the form data, now we need to get the default form state of new selected option
+ if (newOption) {
+ newFormData = schemaUtils.getDefaultFormState(
+ newOption,
+ newFormData,
+ "excludeObjectChildren",
+ ) as any;
+ }
+
+ setSelectedOption(intOption);
+ onChange(newFormData, props.fieldPathId.path, undefined, field_id);
+ };
+
+ const enumOptions = retrievedOptions.current.map((option, index) => ({
+ value: index,
+ label: option.type,
+ }));
+
+ return {
+ handleOptionChange,
+ enumOptions,
+ selectedOption,
+ optionSchema,
+ field_id,
+ };
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArrayFieldItemTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArrayFieldItemTemplate.tsx
new file mode 100644
index 0000000000..f6b1583b14
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArrayFieldItemTemplate.tsx
@@ -0,0 +1,34 @@
+import {
+ ArrayFieldItemTemplateProps,
+ getTemplate,
+ getUiOptions,
+} from "@rjsf/utils";
+
+export default function ArrayFieldItemTemplate(
+ props: ArrayFieldItemTemplateProps,
+) {
+ const { children, buttonsProps, hasToolbar, uiSchema, registry } = props;
+ const uiOptions = getUiOptions(uiSchema);
+ const ArrayFieldItemButtonsTemplate = getTemplate(
+ "ArrayFieldItemButtonsTemplate",
+ registry,
+ uiOptions,
+ );
+
+ return (
+
+
+
+
+ {hasToolbar && (
+
+ )}
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArrayFieldTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArrayFieldTemplate.tsx
new file mode 100644
index 0000000000..7b3f60b5c8
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArrayFieldTemplate.tsx
@@ -0,0 +1,105 @@
+import {
+ ArrayFieldTemplateProps,
+ buttonId,
+ getTemplate,
+ getUiOptions,
+} from "@rjsf/utils";
+import { getHandleId, updateUiOption } from "../../helpers";
+
+export default function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
+ const {
+ canAdd,
+ disabled,
+ fieldPathId,
+ uiSchema,
+ items,
+ optionalDataControl,
+ onAddClick,
+ readonly,
+ registry,
+ required,
+ schema,
+ title,
+ } = props;
+
+ const uiOptions = getUiOptions(uiSchema);
+
+ const ArrayFieldDescriptionTemplate = getTemplate(
+ "ArrayFieldDescriptionTemplate",
+ registry,
+ uiOptions,
+ );
+ const ArrayFieldTitleTemplate = getTemplate(
+ "ArrayFieldTitleTemplate",
+ registry,
+ uiOptions,
+ );
+ const showOptionalDataControlInTitle = !readonly && !disabled;
+
+ const {
+ ButtonTemplates: { AddButton },
+ } = registry.templates;
+
+ const { fromAnyOf } = uiOptions;
+
+ const handleId = getHandleId({
+ uiOptions,
+ id: fieldPathId.$id,
+ schema: schema,
+ });
+ const updatedUiSchema = updateUiOption(uiSchema, {
+ handleId: handleId,
+ });
+
+ return (
+
+
+
+ {!fromAnyOf && (
+
+ )}
+
+ {!showOptionalDataControlInTitle ? optionalDataControl : undefined}
+ {items}
+ {canAdd && (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArraySchemaField.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArraySchemaField.tsx
new file mode 100644
index 0000000000..b926b71f9a
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/ArraySchemaField.tsx
@@ -0,0 +1,29 @@
+import { FieldProps, getUiOptions } from "@rjsf/utils";
+import { getHandleId, updateUiOption } from "../../helpers";
+import { ARRAY_ITEM_FLAG } from "../../constants";
+
+const ArraySchemaField = (props: FieldProps) => {
+ const { index, registry, fieldPathId } = props;
+ const { SchemaField } = registry.fields;
+
+ const uiOptions = getUiOptions(props.uiSchema);
+
+ const handleId = getHandleId({
+ uiOptions,
+ id: fieldPathId.$id,
+ schema: props.schema,
+ });
+ const updatedUiSchema = updateUiOption(props.uiSchema, {
+ handleId: handleId + ARRAY_ITEM_FLAG,
+ });
+
+ return (
+
+ );
+};
+
+export default ArraySchemaField;
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/context/array-item-context.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/context/array-item-context.tsx
new file mode 100644
index 0000000000..d4da5b9ea4
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/context/array-item-context.tsx
@@ -0,0 +1,33 @@
+import React, { createContext, useContext } from "react";
+
+interface ArrayItemContextValue {
+ isArrayItem: boolean;
+ arrayItemHandleId: string;
+}
+
+const ArrayItemContext = createContext({
+ isArrayItem: false,
+ arrayItemHandleId: "",
+});
+
+export const ArrayItemProvider: React.FC<{
+ children: React.ReactNode;
+ arrayItemHandleId: string;
+}> = ({ children, arrayItemHandleId }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const useIsArrayItem = (): boolean => {
+ // here this will be true if field is inside an array
+ const context = useContext(ArrayItemContext);
+ return context.isArrayItem;
+};
+
+export const useArrayItemHandleId = (): string => {
+ const context = useContext(ArrayItemContext);
+ return context.arrayItemHandleId;
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/helpers.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/helpers.ts
new file mode 100644
index 0000000000..96e74e1381
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/helpers.ts
@@ -0,0 +1,3 @@
+export const generateArrayItemHandleId = (id: string) => {
+ return `array-item-${id}`;
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/index.ts
new file mode 100644
index 0000000000..eb496d3d6d
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/array/index.ts
@@ -0,0 +1,7 @@
+export { default as ArrayFieldTemplate } from "./ArrayFieldTemplate";
+export { default as ArrayFieldItemTemplate } from "./ArrayFieldItemTemplate";
+export { default as ArraySchemaField } from "./ArraySchemaField";
+export {
+ ArrayItemProvider,
+ useIsArrayItem,
+} from "./context/array-item-context";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/base-registry.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/base-registry.ts
new file mode 100644
index 0000000000..253e13cbea
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/base-registry.ts
@@ -0,0 +1,71 @@
+import {
+ RegistryFieldsType,
+ RegistryWidgetsType,
+ TemplatesType,
+} from "@rjsf/utils";
+import { AnyOfField } from "./anyof/AnyOfField";
+import {
+ ArrayFieldItemTemplate,
+ ArrayFieldTemplate,
+ ArraySchemaField,
+} from "./array";
+import {
+ ObjectFieldTemplate,
+ OptionalDataControlsTemplate,
+ WrapIfAdditionalTemplate,
+} from "./object";
+import { DescriptionField, FieldTemplate, TitleField } from "./standard";
+import { AddButton, CopyButton, RemoveButton } from "./standard/buttons";
+import {
+ CheckboxWidget,
+ DateTimeWidget,
+ DateWidget,
+ FileWidget,
+ GoogleDrivePickerWidget,
+ SelectWidget,
+ TextWidget,
+ TimeWidget,
+} from "./standard/widgets";
+
+const NoButton = () => null;
+
+export function generateBaseFields(): RegistryFieldsType {
+ return {
+ AnyOfField,
+ ArraySchemaField,
+ };
+}
+
+export function generateBaseTemplates(): Partial {
+ return {
+ ArrayFieldItemTemplate,
+ ArrayFieldTemplate,
+ ButtonTemplates: {
+ AddButton,
+ CopyButton,
+ MoveDownButton: NoButton,
+ MoveUpButton: NoButton,
+ RemoveButton,
+ SubmitButton: NoButton,
+ },
+ DescriptionFieldTemplate: DescriptionField,
+ FieldTemplate,
+ ObjectFieldTemplate,
+ OptionalDataControlsTemplate,
+ TitleFieldTemplate: TitleField,
+ WrapIfAdditionalTemplate,
+ };
+}
+
+export function generateBaseWidgets(): RegistryWidgetsType {
+ return {
+ TextWidget,
+ SelectWidget,
+ CheckboxWidget,
+ FileWidget,
+ DateWidget,
+ TimeWidget,
+ DateTimeWidget,
+ GoogleDrivePickerWidget,
+ };
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/index.ts
new file mode 100644
index 0000000000..cfd0a86fa4
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/index.ts
@@ -0,0 +1,5 @@
+export * from "./array";
+export * from "./object";
+export * from "./standard";
+export * from "./standard/widgets";
+export * from "./standard/buttons";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/ObjectFieldTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/ObjectFieldTemplate.tsx
new file mode 100644
index 0000000000..80d7682bbc
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/ObjectFieldTemplate.tsx
@@ -0,0 +1,122 @@
+import {
+ ADDITIONAL_PROPERTY_FLAG,
+ buttonId,
+ canExpand,
+ descriptionId,
+ getTemplate,
+ getUiOptions,
+ ObjectFieldTemplateProps,
+ titleId,
+} from "@rjsf/utils";
+import { getHandleId, updateUiOption } from "../../helpers";
+import React from "react";
+
+export default function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
+ const {
+ description,
+ title,
+ properties,
+ required,
+ uiSchema,
+ fieldPathId,
+ schema,
+ formData,
+ optionalDataControl,
+ onAddProperty,
+ disabled,
+ readonly,
+ registry,
+ } = props;
+ const uiOptions = getUiOptions(uiSchema);
+
+ const TitleFieldTemplate = getTemplate(
+ "TitleFieldTemplate",
+ registry,
+ uiOptions,
+ );
+
+ const DescriptionFieldTemplate = getTemplate(
+ "DescriptionFieldTemplate",
+ registry,
+ uiOptions,
+ );
+ const showOptionalDataControlInTitle = !readonly && !disabled;
+
+ const {
+ ButtonTemplates: { AddButton },
+ } = registry.templates;
+
+ const additional = ADDITIONAL_PROPERTY_FLAG in schema;
+
+ const handleId = getHandleId({
+ uiOptions,
+ id: fieldPathId.$id,
+ schema,
+ });
+
+ const updatedUiSchema = updateUiOption(uiSchema, {
+ handleId: handleId,
+ });
+
+ return (
+ <>
+
+ {title && !additional && (
+
+ )}
+ {description && (
+
+ )}
+
+
+
+ {!showOptionalDataControlInTitle ? optionalDataControl : undefined}
+
+ {/* I have cloned it - so i could pass updated uiSchema to the nested children */}
+ {properties.map((element: any, index: number) => {
+ const clonedContent = React.cloneElement(element.content, {
+ ...element.content.props,
+ uiSchema: updateUiOption(element.content.props.uiSchema, {
+ handleId: handleId,
+ }),
+ });
+
+ return (
+
+ );
+ })}
+ {canExpand(schema, uiSchema, formData) ? (
+
+ ) : null}
+
+ >
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/OptionalDataControlsTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/OptionalDataControlsTemplate.tsx
new file mode 100644
index 0000000000..dc53c6ce5b
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/OptionalDataControlsTemplate.tsx
@@ -0,0 +1,35 @@
+import { OptionalDataControlsTemplateProps } from "@rjsf/utils";
+import { PlusCircle } from "lucide-react";
+
+import { IconButton, RemoveButton } from "../standard/buttons";
+
+export default function OptionalDataControlsTemplate(
+ props: OptionalDataControlsTemplateProps,
+) {
+ const { id, registry, label, onAddClick, onRemoveClick } = props;
+ if (onAddClick) {
+ return (
+ }
+ size="small"
+ />
+ );
+ } else if (onRemoveClick) {
+ return (
+
+ );
+ }
+ return {label};
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/WrapIfAdditionalTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/WrapIfAdditionalTemplate.tsx
new file mode 100644
index 0000000000..97478e9eaf
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/WrapIfAdditionalTemplate.tsx
@@ -0,0 +1,114 @@
+import {
+ ADDITIONAL_PROPERTY_FLAG,
+ buttonId,
+ getTemplate,
+ getUiOptions,
+ titleId,
+ WrapIfAdditionalTemplateProps,
+} from "@rjsf/utils";
+
+import { Input } from "@/components/atoms/Input/Input";
+import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
+
+export default function WrapIfAdditionalTemplate(
+ props: WrapIfAdditionalTemplateProps,
+) {
+ const {
+ classNames,
+ style,
+ children,
+ disabled,
+ id,
+ label,
+ onRemoveProperty,
+ onKeyRenameBlur,
+ readonly,
+ required,
+ schema,
+ uiSchema,
+ registry,
+ } = props;
+ const { templates, formContext } = registry;
+ const uiOptions = getUiOptions(uiSchema);
+ // Button templates are not overridden in the uiSchema
+ const { RemoveButton } = templates.ButtonTemplates;
+ const { isInputConnected } = useEdgeStore();
+
+ const additional = ADDITIONAL_PROPERTY_FLAG in schema;
+ const { nodeId } = formContext;
+ const handleId = uiOptions.handleId;
+
+ const TitleFieldTemplate = getTemplate(
+ "TitleFieldTemplate",
+ registry,
+ uiOptions,
+ );
+
+ if (!additional) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ const keyId = `${id}-key`;
+ const generateObjectPropertyTitleId = (id: string, label: string) => {
+ return id.replace(`_${label}`, `_#_${label}`);
+ };
+ const title_id = generateObjectPropertyTitleId(id, label);
+
+ const handleBlur = (e: React.FocusEvent) => {
+ if (e.target.value == "") {
+ onRemoveProperty();
+ } else {
+ onKeyRenameBlur(e);
+ }
+ };
+
+ const isHandleConnected = isInputConnected(nodeId, handleId);
+
+ return (
+ <>
+
+
+ {!isHandleConnected && (
+
+ )}
+ {!isHandleConnected && (
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/index.ts
new file mode 100644
index 0000000000..84bf8baa16
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/object/index.ts
@@ -0,0 +1,3 @@
+export { default as ObjectFieldTemplate } from "./ObjectFieldTemplate";
+export { default as WrapIfAdditionalTemplate } from "./WrapIfAdditionalTemplate";
+export { default as OptionalDataControlsTemplate } from "./OptionalDataControlsTemplate";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/DescriptionField.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/DescriptionField.tsx
new file mode 100644
index 0000000000..65b5e4be43
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/DescriptionField.tsx
@@ -0,0 +1,32 @@
+import { DescriptionFieldProps } from "@rjsf/utils";
+import { RichDescription } from "@rjsf/core";
+import { InfoIcon } from "@phosphor-icons/react";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/atoms/Tooltip/BaseTooltip";
+
+export default function DescriptionField(props: DescriptionFieldProps) {
+ const { id, description, registry, uiSchema } = props;
+ if (!description) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/FieldError.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/FieldError.tsx
new file mode 100644
index 0000000000..e25a9bc80f
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/FieldError.tsx
@@ -0,0 +1,27 @@
+import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
+import { Text } from "@/components/atoms/Text/Text";
+
+export const FieldError = ({
+ nodeId,
+ fieldId,
+}: {
+ nodeId: string;
+ fieldId: string;
+}) => {
+ const nodeErrors = useNodeStore((state) => {
+ const node = state.nodes.find((n) => n.id === nodeId);
+ return node?.data?.errors;
+ });
+ const fieldError =
+ nodeErrors?.[fieldId] || nodeErrors?.[fieldId.replace(/_%_/g, ".")] || null;
+
+ return (
+
+ {fieldError && (
+
+ {fieldError}
+
+ )}
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/FieldTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/FieldTemplate.tsx
new file mode 100644
index 0000000000..d912d9d06e
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/FieldTemplate.tsx
@@ -0,0 +1,131 @@
+import {
+ ADDITIONAL_PROPERTY_FLAG,
+ FieldTemplateProps,
+ getTemplate,
+ getUiOptions,
+ titleId,
+} from "@rjsf/utils";
+
+import { isAnyOfChild, isAnyOfSchema } from "../../utils/schema-utils";
+import {
+ cleanUpHandleId,
+ getHandleId,
+ isPartOfAnyOf,
+ updateUiOption,
+} from "../../helpers";
+
+import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
+import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
+import { FieldError } from "./FieldError";
+
+export default function FieldTemplate(props: FieldTemplateProps) {
+ const {
+ id,
+ children,
+ displayLabel,
+ description,
+ rawDescription,
+ label,
+ hidden,
+ required,
+ schema,
+ uiSchema,
+ registry,
+ classNames,
+ style,
+ disabled,
+ onKeyRename,
+ onKeyRenameBlur,
+ onRemoveProperty,
+ readonly,
+ } = props;
+ const { nodeId } = registry.formContext;
+
+ const { isInputConnected } = useEdgeStore();
+ const showAdvanced = useNodeStore(
+ (state) => state.nodeAdvancedStates[registry.formContext.nodeId ?? ""],
+ );
+
+ if (hidden) {
+ return {children}
;
+ }
+
+ const uiOptions = getUiOptions(uiSchema);
+ const TitleFieldTemplate = getTemplate(
+ "TitleFieldTemplate",
+ registry,
+ uiOptions,
+ );
+ const WrapIfAdditionalTemplate = getTemplate(
+ "WrapIfAdditionalTemplate",
+ registry,
+ uiOptions,
+ );
+
+ const additional = ADDITIONAL_PROPERTY_FLAG in schema;
+
+ const handleId = getHandleId({
+ uiOptions,
+ id: id,
+ schema: schema,
+ });
+ const updatedUiSchema = updateUiOption(uiSchema, {
+ handleId: handleId,
+ });
+ const isHandleConnected = isInputConnected(nodeId, cleanUpHandleId(handleId));
+
+ const shouldDisplayLabel =
+ displayLabel ||
+ (schema.type === "boolean" && !isAnyOfChild(uiSchema as any));
+ const shouldShowTitleSection = !isAnyOfSchema(schema) && !additional;
+ const shouldShowChildren = isAnyOfSchema(schema) || !isHandleConnected;
+
+ const isAdvancedField = (schema as any).advanced === true;
+ if (!showAdvanced && isAdvancedField && !isHandleConnected) {
+ return null;
+ }
+
+ const marginBottom =
+ isPartOfAnyOf({ uiOptions }) || isAnyOfSchema(schema) ? 0 : 16;
+
+ return (
+
+
+ {shouldShowTitleSection && (
+
+ {shouldDisplayLabel && (
+
+ )}
+ {shouldDisplayLabel && rawDescription && {description}}
+
+ )}
+ {shouldShowChildren && children}
+
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/TitleField.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/TitleField.tsx
new file mode 100644
index 0000000000..378ffa7f34
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/TitleField.tsx
@@ -0,0 +1,55 @@
+import {
+ ADDITIONAL_PROPERTY_FLAG,
+ descriptionId,
+ getUiOptions,
+ TitleFieldProps,
+} from "@rjsf/utils";
+
+import { Text } from "@/components/atoms/Text/Text";
+import { getTypeDisplayInfo } from "@/app/(platform)/build/components/FlowEditor/nodes/helpers";
+import { isAnyOfSchema } from "../../utils/schema-utils";
+import { cn } from "@/lib/utils";
+import { isArrayItem } from "../../helpers";
+import { InputNodeHandle } from "@/app/(platform)/build/components/FlowEditor/handlers/NodeHandle";
+
+export default function TitleField(props: TitleFieldProps) {
+ const { id, title, required, schema, registry, uiSchema } = props;
+ const { nodeId, showHandles } = registry.formContext;
+ const uiOptions = getUiOptions(uiSchema);
+
+ const isAnyOf = isAnyOfSchema(schema);
+ const { displayType, colorClass } = getTypeDisplayInfo(schema);
+ const description_id = descriptionId(id);
+
+ const additional = ADDITIONAL_PROPERTY_FLAG in schema;
+ const isArrayItemFlag = isArrayItem({ uiOptions });
+ const smallText = isArrayItemFlag || additional;
+
+ const showHandle = uiOptions.showHandles ?? showHandles;
+ return (
+
+ {showHandle !== false && (
+
+ )}
+
+ {title}
+
+
+ {required ? "*" : null}
+
+ {!isAnyOf && (
+
+ ({displayType})
+
+ )}
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/AddButton.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/AddButton.tsx
new file mode 100644
index 0000000000..901716f56a
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/AddButton.tsx
@@ -0,0 +1,27 @@
+import { IconButtonProps, TranslatableString } from "@rjsf/utils";
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/atoms/Button/Button";
+import { PlusIcon } from "@phosphor-icons/react";
+
+export default function AddButton({
+ registry,
+ className,
+ uiSchema: _uiSchema,
+ ...props
+}: IconButtonProps) {
+ const { translateString } = registry;
+ return (
+
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/IconButton.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/IconButton.tsx
new file mode 100644
index 0000000000..4a7f011b82
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/IconButton.tsx
@@ -0,0 +1,101 @@
+import {
+ FormContextType,
+ IconButtonProps,
+ RJSFSchema,
+ StrictRJSFSchema,
+ TranslatableString,
+} from "@rjsf/utils";
+import { ChevronDown, ChevronUp, Copy } from "lucide-react";
+import type { VariantProps } from "class-variance-authority";
+
+import { Button } from "@/components/atoms/Button/Button";
+import { extendedButtonVariants } from "@/components/atoms/Button/helpers";
+import { TrashIcon } from "@phosphor-icons/react";
+import { cn } from "@/lib/utils";
+import { Text } from "@/components/atoms/Text/Text";
+
+export type AutogptIconButtonProps<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any,
+> = IconButtonProps & VariantProps;
+
+export default function IconButton(props: AutogptIconButtonProps) {
+ const {
+ icon,
+ className,
+ uiSchema: _uiSchema,
+ registry: _registry,
+ iconType: _iconType,
+ ...otherProps
+ } = props;
+
+ return (
+
+ );
+}
+
+export function CopyButton(props: AutogptIconButtonProps) {
+ const {
+ registry: { translateString },
+ } = props;
+ return (
+ }
+ />
+ );
+}
+
+export function MoveDownButton(props: AutogptIconButtonProps) {
+ const {
+ registry: { translateString },
+ } = props;
+ return (
+ }
+ />
+ );
+}
+
+export function MoveUpButton(props: AutogptIconButtonProps) {
+ const {
+ registry: { translateString },
+ } = props;
+ return (
+ }
+ />
+ );
+}
+
+export function RemoveButton(props: AutogptIconButtonProps) {
+ const {
+ registry: { translateString },
+ } = props;
+ return (
+ }
+ />
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/index.ts
new file mode 100644
index 0000000000..f083306249
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/buttons/index.ts
@@ -0,0 +1,8 @@
+export { default as AddButton } from "./AddButton";
+export {
+ default as IconButton,
+ CopyButton,
+ RemoveButton,
+ MoveUpButton,
+ MoveDownButton,
+} from "./IconButton";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/errors/ErrorList.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/errors/ErrorList.tsx
new file mode 100644
index 0000000000..39175ef13e
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/errors/ErrorList.tsx
@@ -0,0 +1,24 @@
+import { ErrorListProps, TranslatableString } from "@rjsf/utils";
+import { AlertCircle } from "lucide-react";
+
+import {
+ Alert,
+ AlertDescription,
+ AlertTitle,
+} from "@/components/molecules/Alert/Alert";
+
+export default function ErrorList(props: ErrorListProps) {
+ const { errors, registry } = props;
+ const { translateString } = registry;
+ return (
+
+
+ {translateString(TranslatableString.ErrorsLabel)}
+
+ {errors.map((error, i: number) => {
+ return • {error.stack};
+ })}
+
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/errors/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/errors/index.ts
new file mode 100644
index 0000000000..ccecd3c8c6
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/errors/index.ts
@@ -0,0 +1 @@
+export { default as ErrorList } from "./ErrorList";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/helpers.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/helpers.ts
new file mode 100644
index 0000000000..327a0e06b6
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/helpers.ts
@@ -0,0 +1,76 @@
+import { RJSFSchema } from "@rjsf/utils";
+
+export function parseFieldPath(
+ rootSchema: RJSFSchema,
+ id: string,
+ additional: boolean,
+ idSeparator: string = "_%_",
+): { path: string[]; typeHints: string[] } {
+ const segments = id.split(idSeparator).filter(Boolean);
+ const typeHints: string[] = [];
+
+ let currentSchema = rootSchema;
+
+ for (let i = 0; i < segments.length; i++) {
+ const segment = segments[i];
+ const isNumeric = /^\d+$/.test(segment);
+
+ if (isNumeric) {
+ typeHints.push("array");
+ } else {
+ if (additional) {
+ typeHints.push("object-key");
+ } else {
+ typeHints.push("object-property");
+ }
+ currentSchema = (currentSchema.properties?.[segment] as RJSFSchema) || {};
+ }
+ }
+
+ return { path: segments, typeHints };
+}
+
+// This helper work is simple - it just help us to convert rjsf id to our backend compatible id
+// Example : List[dict] = agpt_%_List_0_dict__title -> List_$_0_#_dict
+// We remove the prefix and suffix and then we split id by our custom delimiter (_%_)
+// then add _$_ delimiter for array and _#_ delimiter for object-key
+// and for normal property we add . delimiter
+
+export function getHandleId(
+ rootSchema: RJSFSchema,
+ id: string,
+ additional: boolean,
+ idSeparator: string = "_%_",
+): string {
+ const idPrefix = "agpt_%_";
+ const idSuffix = "__title";
+
+ if (id.startsWith(idPrefix)) {
+ id = id.slice(idPrefix.length);
+ }
+ if (id.endsWith(idSuffix)) {
+ id = id.slice(0, -idSuffix.length);
+ }
+
+ const { path, typeHints } = parseFieldPath(
+ rootSchema,
+ id,
+ additional,
+ idSeparator,
+ );
+
+ return path
+ .map((seg, i) => {
+ const type = typeHints[i];
+ if (type === "array") {
+ return `_$_${seg}`;
+ }
+ if (type === "object-key") {
+ return `_${seg}`; // we haven't added _#_ delimiter for object-key because it's already added in the id - check WrapIfAdditionalTemplate.tsx
+ }
+
+ return `.${seg}`;
+ })
+ .join("")
+ .slice(1);
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/index.ts
new file mode 100644
index 0000000000..870753151c
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/index.ts
@@ -0,0 +1,3 @@
+export { default as FieldTemplate } from "./FieldTemplate";
+export { default as TitleField } from "./TitleField";
+export { default as DescriptionField } from "./DescriptionField";
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/SwitchWidget.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/CheckboxInput/CheckBoxWidget.tsx
similarity index 87%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/SwitchWidget.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/CheckboxInput/CheckBoxWidget.tsx
index d15ec18a9a..ff93528492 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/SwitchWidget.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/CheckboxInput/CheckBoxWidget.tsx
@@ -1,8 +1,9 @@
import { WidgetProps } from "@rjsf/utils";
import { Switch } from "@/components/atoms/Switch/Switch";
-export function SwitchWidget(props: WidgetProps) {
+export function CheckboxWidget(props: WidgetProps) {
const { value = false, onChange, disabled, readonly, autofocus, id } = props;
+
return (
{
+export const DateWidget = (props: WidgetProps) => {
const {
value,
onChange,
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateInput/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateInput/index.ts
new file mode 100644
index 0000000000..3ba465c4f4
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateInput/index.ts
@@ -0,0 +1 @@
+export { DateWidget } from "./DateWidget";
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/DateTimeInputWidget.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateTimeInput/DateTimeWidget.tsx
similarity index 91%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/DateTimeInputWidget.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateTimeInput/DateTimeWidget.tsx
index 2e85a610b5..50f6e378fb 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/DateTimeInputWidget.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateTimeInput/DateTimeWidget.tsx
@@ -1,7 +1,7 @@
import { WidgetProps } from "@rjsf/utils";
import { DateTimeInput } from "@/components/atoms/DateTimeInput/DateTimeInput";
-export const DateTimeInputWidget = (props: WidgetProps) => {
+export const DateTimeWidget = (props: WidgetProps) => {
const {
value,
onChange,
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateTimeInput/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateTimeInput/index.ts
new file mode 100644
index 0000000000..bf7c084f5a
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/DateTimeInput/index.ts
@@ -0,0 +1 @@
+export { DateTimeWidget } from "./DateTimeWidget";
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/FileWidget.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/FileInput/FileWidget.tsx
similarity index 100%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/FileWidget.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/FileInput/FileWidget.tsx
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/FileInput/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/FileInput/index.ts
new file mode 100644
index 0000000000..e23b50bfd0
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/FileInput/index.ts
@@ -0,0 +1 @@
+export { FileWidget } from "./FileWidget";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/GoogleDrivePicker/GoogleDrivePicketWidget.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/GoogleDrivePicker/GoogleDrivePicketWidget.tsx
new file mode 100644
index 0000000000..45ed9611cf
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/GoogleDrivePicker/GoogleDrivePicketWidget.tsx
@@ -0,0 +1,55 @@
+import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
+import { GoogleDrivePickerInput } from "@/components/contextual/GoogleDrivePicker/GoogleDrivePickerInput";
+import { getFieldErrorKey } from "@/components/renderers/InputRenderer/utils/helpers";
+import type { GoogleDrivePickerConfig } from "@/lib/autogpt-server-api/types";
+import { cn } from "@/lib/utils";
+import { WidgetProps } from "@rjsf/utils";
+
+function hasGoogleDrivePickerConfig(
+ schema: unknown,
+): schema is { google_drive_picker_config?: GoogleDrivePickerConfig } {
+ return (
+ typeof schema === "object" &&
+ schema !== null &&
+ "google_drive_picker_config" in schema
+ );
+}
+
+export function GoogleDrivePickerWidget(props: WidgetProps) {
+ const { onChange, disabled, readonly, value, schema, id, formContext } =
+ props;
+ const { nodeId } = formContext || {};
+
+ const nodeErrors = useNodeStore((state) => {
+ const node = state.nodes.find((n) => n.id === nodeId);
+ return node?.data?.errors;
+ });
+
+ const fieldErrorKey = getFieldErrorKey(id ?? "");
+ const fieldError =
+ nodeErrors?.[fieldErrorKey] ||
+ nodeErrors?.[fieldErrorKey.replace(/_/g, ".")] ||
+ nodeErrors?.[fieldErrorKey.replace(/\./g, "_")] ||
+ undefined;
+
+ const config: GoogleDrivePickerConfig = hasGoogleDrivePickerConfig(schema)
+ ? schema.google_drive_picker_config || {}
+ : {};
+
+ function handleChange(newValue: unknown) {
+ onChange(newValue);
+ }
+
+ return (
+
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/GoogleDrivePicker/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/GoogleDrivePicker/index.ts
new file mode 100644
index 0000000000..c0608ac9fe
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/GoogleDrivePicker/index.ts
@@ -0,0 +1 @@
+export { GoogleDrivePickerWidget } from "./GoogleDrivePicketWidget";
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/SelectWidget.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/SelectInput/SelectWidget.tsx
similarity index 87%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/SelectWidget.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/SelectInput/SelectWidget.tsx
index db68a1628c..894004db40 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/SelectWidget.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/SelectInput/SelectWidget.tsx
@@ -14,8 +14,16 @@ import {
} from "@/components/__legacy__/ui/multiselect";
export const SelectWidget = (props: WidgetProps) => {
- const { options, value, onChange, disabled, readonly, id, formContext } =
- props;
+ const {
+ options,
+ value,
+ onChange,
+ disabled,
+ readonly,
+ className,
+ id,
+ formContext,
+ } = props;
const enumOptions = options.enumOptions || [];
const type = mapJsonSchemaTypeToInputType(props.schema);
const { size = "small" } = formContext || {};
@@ -36,7 +44,7 @@ export const SelectWidget = (props: WidgetProps) => {
- {enumOptions?.map((option) => (
+ {enumOptions?.map((option: any) => (
{option.label}
@@ -56,12 +64,13 @@ export const SelectWidget = (props: WidgetProps) => {
value={value ?? ""}
onValueChange={onChange}
options={
- enumOptions?.map((option) => ({
+ enumOptions?.map((option: any) => ({
value: option.value,
label: option.label,
})) || []
}
wrapperClassName="!mb-0 "
+ className={className}
/>
);
};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/SelectInput/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/SelectInput/index.ts
new file mode 100644
index 0000000000..9a64291abc
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/SelectInput/index.ts
@@ -0,0 +1 @@
+export { SelectWidget } from "./SelectWidget";
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/InputExpanderModal.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/TextInputExpanderModal.tsx
similarity index 100%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/InputExpanderModal.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/TextInputExpanderModal.tsx
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/TextInputWidget.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/TextWidget.tsx
similarity index 91%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/TextInputWidget.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/TextWidget.tsx
index d9fea28a8d..33a55581c7 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TextInputWidget/TextInputWidget.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/TextWidget.tsx
@@ -14,15 +14,12 @@ import {
TooltipTrigger,
} from "@/components/atoms/Tooltip/BaseTooltip";
import { BlockUIType } from "@/lib/autogpt-server-api/types";
-import { InputExpanderModal } from "./InputExpanderModal";
import { ArrowsOutIcon } from "@phosphor-icons/react";
+import { InputExpanderModal } from "./TextInputExpanderModal";
-export const TextInputWidget = (props: WidgetProps) => {
- const { schema, formContext } = props;
- const { uiType, size = "small" } = formContext as {
- uiType: BlockUIType;
- size?: string;
- };
+export default function TextWidget(props: WidgetProps) {
+ const { schema, placeholder, registry } = props;
+ const { size, uiType } = registry.formContext;
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -122,7 +119,7 @@ export const TextInputWidget = (props: WidgetProps) => {
wrapperClassName="mb-0 flex-1"
value={props.value ?? ""}
onChange={handleChange}
- placeholder={schema.placeholder || config.placeholder}
+ placeholder={placeholder || config.placeholder}
required={props.required}
disabled={props.disabled}
className={showExpandButton ? "pr-8" : ""}
@@ -152,8 +149,8 @@ export const TextInputWidget = (props: WidgetProps) => {
title={schema.title || "Edit value"}
description={schema.description || ""}
defaultValue={props.value ?? ""}
- placeholder={schema.placeholder || config.placeholder}
+ placeholder={placeholder || config.placeholder}
/>
>
);
-};
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/index.ts
new file mode 100644
index 0000000000..102db07ade
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TextInput/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./TextWidget";
+export { InputExpanderModal } from "./TextInputExpanderModal";
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TimeInputWidget.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TimeInput/TimeWidget.tsx
similarity index 91%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TimeInputWidget.tsx
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TimeInput/TimeWidget.tsx
index 032c33e62c..152aae7298 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/TimeInputWidget.tsx
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TimeInput/TimeWidget.tsx
@@ -1,7 +1,7 @@
import { WidgetProps } from "@rjsf/utils";
import { TimeInput } from "@/components/atoms/TimeInput/TimeInput";
-export const TimeInputWidget = (props: WidgetProps) => {
+export const TimeWidget = (props: WidgetProps) => {
const { value, onChange, disabled, readonly, placeholder, id, formContext } =
props;
const { size = "small" } = formContext || {};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TimeInput/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TimeInput/index.ts
new file mode 100644
index 0000000000..488e184c08
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/TimeInput/index.ts
@@ -0,0 +1 @@
+export { TimeWidget } from "./TimeWidget";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/index.ts
new file mode 100644
index 0000000000..f68117fa83
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/base/standard/widgets/index.ts
@@ -0,0 +1,8 @@
+export { CheckboxWidget } from "./CheckboxInput";
+export { DateWidget } from "./DateInput";
+export { DateTimeWidget } from "./DateTimeInput";
+export { FileWidget } from "./FileInput";
+export { GoogleDrivePickerWidget } from "./GoogleDrivePicker";
+export { SelectWidget } from "./SelectInput";
+export { default as TextWidget } from "./TextInput";
+export { TimeWidget } from "./TimeInput";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/constants.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/constants.ts
new file mode 100644
index 0000000000..144d850a00
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/constants.ts
@@ -0,0 +1,8 @@
+export const ANY_OF_FLAG = "__anyOf";
+export const ARRAY_FLAG = "__array";
+export const OBJECT_FLAG = "__object";
+export const KEY_PAIR_FLAG = "__keyPair";
+export const TITLE_FLAG = "__title";
+export const ARRAY_ITEM_FLAG = "__arrayItem";
+export const ID_PREFIX = "agpt_@_";
+export const ID_PREFIX_ARRAY = "agpt_%_";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/CredentialField.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/CredentialField.tsx
new file mode 100644
index 0000000000..f814fba93f
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/CredentialField.tsx
@@ -0,0 +1,73 @@
+import React, { useMemo } from "react";
+import { FieldProps, getUiOptions } from "@rjsf/utils";
+import {
+ BlockIOCredentialsSubSchema,
+ CredentialsMetaInput,
+} from "@/lib/autogpt-server-api";
+import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/NewAgentLibraryView/components/modals/CredentialsInputs/CredentialsInputs";
+import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
+import { useShallow } from "zustand/react/shallow";
+import { CredentialFieldTitle } from "./components/CredentialFieldTitle";
+
+export const CredentialsField = (props: FieldProps) => {
+ const { formData, onChange, schema, registry, fieldPathId } = props;
+
+ const formContext = registry.formContext;
+ const uiOptions = getUiOptions(props.uiSchema);
+ const nodeId = formContext?.nodeId;
+
+ // Get sibling inputs (hardcoded values) from the node store
+ const hardcodedValues = useNodeStore(
+ useShallow((state) => (nodeId ? state.getHardCodedValues(nodeId) : {})),
+ );
+
+ const handleChange = (newValue: any) => {
+ onChange(newValue, fieldPathId?.path);
+ };
+
+ const handleSelectCredentials = (credentialsMeta?: CredentialsMetaInput) => {
+ if (credentialsMeta) {
+ handleChange({
+ id: credentialsMeta.id,
+ provider: credentialsMeta.provider,
+ title: credentialsMeta.title,
+ type: credentialsMeta.type,
+ });
+ } else {
+ handleChange(undefined);
+ }
+ };
+
+ // Convert formData to CredentialsMetaInput format
+ const selectedCredentials: CredentialsMetaInput | undefined = useMemo(
+ () =>
+ formData?.id
+ ? {
+ id: formData.id,
+ provider: formData.provider,
+ title: formData.title,
+ type: formData.type,
+ }
+ : undefined,
+ [formData?.id, formData?.provider, formData?.title, formData?.type],
+ );
+
+ return (
+
+
+
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/components/CredentialFieldTitle.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/components/CredentialFieldTitle.tsx
new file mode 100644
index 0000000000..ca14c8a4ce
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/components/CredentialFieldTitle.tsx
@@ -0,0 +1,66 @@
+import {
+ getTemplate,
+ UiSchema,
+ Registry,
+ RJSFSchema,
+ FieldPathId,
+ titleId,
+ descriptionId,
+} from "@rjsf/utils";
+import { getCredentialProviderFromSchema, toDisplayName } from "../helpers";
+import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
+import { BlockIOCredentialsSubSchema } from "@/lib/autogpt-server-api";
+import { updateUiOption } from "../../../helpers";
+import { uiSchema } from "@/app/(platform)/build/components/FlowEditor/nodes/uiSchema";
+
+export const CredentialFieldTitle = (props: {
+ registry: Registry;
+ uiOptions: UiSchema;
+ schema: RJSFSchema;
+ fieldPathId: FieldPathId;
+}) => {
+ const { registry, uiOptions, schema, fieldPathId } = props;
+ const { nodeId } = registry.formContext;
+
+ const TitleFieldTemplate = getTemplate(
+ "TitleFieldTemplate",
+ registry,
+ uiOptions,
+ );
+
+ const DescriptionFieldTemplate = getTemplate(
+ "DescriptionFieldTemplate",
+ registry,
+ uiOptions,
+ );
+
+ const credentialProvider = toDisplayName(
+ getCredentialProviderFromSchema(
+ useNodeStore.getState().getHardCodedValues(nodeId),
+ schema as BlockIOCredentialsSubSchema,
+ ) ?? "",
+ );
+
+ const updatedUiSchema = updateUiOption(uiSchema, {
+ showHandles: false,
+ });
+
+ return (
+
+
+
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/helpers.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/helpers.ts
similarity index 100%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/helpers.ts
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/CredentialField/helpers.ts
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/GoogleDrivePickerField/GoogleDrivePickerField.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/GoogleDrivePickerField/GoogleDrivePickerField.tsx
new file mode 100644
index 0000000000..51c5806ae5
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/GoogleDrivePickerField/GoogleDrivePickerField.tsx
@@ -0,0 +1,21 @@
+import { GoogleDrivePickerInput } from "@/components/contextual/GoogleDrivePicker/GoogleDrivePickerInput";
+import { GoogleDrivePickerConfig } from "@/lib/autogpt-server-api";
+import { FieldProps, getUiOptions } from "@rjsf/utils";
+
+export const GoogleDrivePickerField = (props: FieldProps) => {
+ const { schema, uiSchema, onChange, fieldPathId, formData } = props;
+ const uiOptions = getUiOptions(uiSchema);
+ const config: GoogleDrivePickerConfig = schema.google_drive_picker_config;
+
+ return (
+
+ onChange(value, fieldPathId.path)}
+ className={uiOptions.className}
+ showRemoveButton={true}
+ />
+
+ );
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/custom-registry.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/custom-registry.ts
new file mode 100644
index 0000000000..91850e3f10
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/custom/custom-registry.ts
@@ -0,0 +1,52 @@
+import { FieldProps, RJSFSchema, RegistryFieldsType } from "@rjsf/utils";
+import { CredentialsField } from "./CredentialField/CredentialField";
+import { GoogleDrivePickerField } from "./GoogleDrivePickerField/GoogleDrivePickerField";
+
+export interface CustomFieldDefinition {
+ id: string;
+ matcher: (schema: any) => boolean;
+ component: (props: FieldProps) => JSX.Element | null;
+}
+
+export const CUSTOM_FIELDS: CustomFieldDefinition[] = [
+ {
+ id: "custom/credential_field",
+ matcher: (schema: any) => {
+ return (
+ typeof schema === "object" &&
+ schema !== null &&
+ "credentials_provider" in schema
+ );
+ },
+ component: CredentialsField,
+ },
+ {
+ id: "custom/google_drive_picker_field",
+ matcher: (schema: any) => {
+ return (
+ "google_drive_picker_config" in schema ||
+ ("format" in schema && schema.format === "google-drive-picker")
+ );
+ },
+ component: GoogleDrivePickerField,
+ },
+];
+
+export function findCustomFieldId(schema: any): string | null {
+ for (const field of CUSTOM_FIELDS) {
+ if (field.matcher(schema)) {
+ return field.id;
+ }
+ }
+ return null;
+}
+
+export function generateCustomFields(): RegistryFieldsType {
+ return CUSTOM_FIELDS.reduce(
+ (acc, field) => {
+ acc[field.id] = field.component;
+ return acc;
+ },
+ {} as Record,
+ );
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/docs/HEIRARCHY.md b/autogpt_platform/frontend/src/components/renderers/InputRenderer/docs/HEIRARCHY.md
new file mode 100644
index 0000000000..45b4276946
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/docs/HEIRARCHY.md
@@ -0,0 +1,291 @@
+# Input Renderer 2 - Hierarchy
+
+## Flow Overview
+
+```
+FormRenderer2 → Form (RJSF) → ObjectFieldTemplate → FieldTemplate → Widget/Field
+```
+
+---
+
+## Component Layers
+
+### 1. Root (FormRenderer2)
+
+- Entry point
+- Preprocesses schema
+- Passes to RJSF Form
+
+### 2. Form (registry/Form.tsx)
+
+- RJSF themed form
+- Combines: templates + widgets + fields
+
+### 3. Templates (decide layout/structure)
+
+| Template | When Used |
+| -------------------------- | ------------------------------------------- |
+| `ObjectFieldTemplate` | `type: "object"` |
+| `ArrayFieldTemplate` | `type: "array"` |
+| `FieldTemplate` | Wraps every field (title, errors, children) |
+| `ArrayFieldItemTemplate` | Each array item |
+| `WrapIfAdditionalTemplate` | Additional properties in objects |
+
+### 4. Fields (custom rendering logic)
+
+| Field | When Used |
+| ------------------ | ---------------------------- |
+| `AnyOfField` | `anyOf` or `oneOf` in schema |
+| `ArraySchemaField` | Array type handling |
+
+### 5. Widgets (actual input elements)
+
+| Widget | Input Type |
+| ---------------- | ----------------------- |
+| `TextWidget` | string, number, integer |
+| `SelectWidget` | enum, anyOf selector |
+| `CheckboxWidget` | boolean |
+| `FileWidget` | file upload |
+| `DateWidget` | date |
+| `TimeWidget` | time |
+| `DateTimeWidget` | datetime |
+
+---
+
+## Your Schema Hierarchy
+
+```
+Root (type: object)
+└── ObjectFieldTemplate
+ │
+ ├── name (string, required)
+ │ └── FieldTemplate → TextWidget
+ │
+ ├── value (anyOf)
+ │ └── FieldTemplate → AnyOfField
+ │ └── Selector dropdown + selected type:
+ │ ├── String → TextWidget
+ │ ├── Number → TextWidget
+ │ ├── Integer → TextWidget
+ │ ├── Boolean → CheckboxWidget
+ │ ├── Array → ArrayFieldTemplate → items
+ │ ├── Object → ObjectFieldTemplate
+ │ └── Null → nothing
+ │
+ ├── title (anyOf: string | null)
+ │ └── FieldTemplate → AnyOfField
+ │ └── String → TextWidget OR Null → nothing
+ │
+ ├── description (anyOf: string | null)
+ │ └── FieldTemplate → AnyOfField
+ │ └── String → TextWidget OR Null → nothing
+ │
+ ├── placeholder_values (array of strings)
+ │ └── FieldTemplate → ArrayFieldTemplate
+ │ └── ArrayFieldItemTemplate (per item)
+ │ └── TextWidget
+ │
+ ├── advanced (boolean)
+ │ └── FieldTemplate → CheckboxWidget
+ │
+ └── secret (boolean)
+ └── FieldTemplate → CheckboxWidget
+```
+
+---
+
+## Nested Examples (up to 3 levels)
+
+### Simple Array (strings)
+
+```json
+{ "tags": { "type": "array", "items": { "type": "string" } } }
+```
+
+```
+Level 1: ObjectFieldTemplate (root)
+└── Level 2: FieldTemplate → ArrayFieldTemplate
+ └── Level 3: ArrayFieldItemTemplate → TextWidget
+```
+
+### Array of Objects
+
+```json
+{
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "age": { "type": "integer" }
+ }
+ }
+ }
+}
+```
+
+```
+Level 1: ObjectFieldTemplate (root)
+└── Level 2: FieldTemplate → ArrayFieldTemplate
+ └── Level 3: ArrayFieldItemTemplate → ObjectFieldTemplate
+ ├── FieldTemplate → TextWidget (name)
+ └── FieldTemplate → TextWidget (age)
+```
+
+### Nested Object (3 levels)
+
+```json
+{
+ "config": {
+ "type": "object",
+ "properties": {
+ "database": {
+ "type": "object",
+ "properties": {
+ "host": { "type": "string" },
+ "port": { "type": "integer" }
+ }
+ }
+ }
+ }
+}
+```
+
+```
+Level 1: ObjectFieldTemplate (root)
+└── config
+ └── Level 2: FieldTemplate → ObjectFieldTemplate
+ └── database
+ └── Level 3: FieldTemplate → ObjectFieldTemplate
+ ├── FieldTemplate → TextWidget (host)
+ └── FieldTemplate → TextWidget (port)
+```
+
+### Array of Arrays (nested array)
+
+```json
+{
+ "matrix": {
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items": { "type": "number" }
+ }
+ }
+}
+```
+
+```
+Level 1: ObjectFieldTemplate (root)
+└── Level 2: FieldTemplate → ArrayFieldTemplate
+ └── Level 3: ArrayFieldItemTemplate → ArrayFieldTemplate
+ └── ArrayFieldItemTemplate → TextWidget
+```
+
+### Complex: Object → Array → Object
+
+```json
+{
+ "company": {
+ "type": "object",
+ "properties": {
+ "departments": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "budget": { "type": "number" }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+```
+Level 1: ObjectFieldTemplate (root)
+└── company
+ └── Level 2: FieldTemplate → ObjectFieldTemplate
+ └── departments
+ └── Level 3: FieldTemplate → ArrayFieldTemplate
+ └── ArrayFieldItemTemplate → ObjectFieldTemplate
+ ├── FieldTemplate → TextWidget (name)
+ └── FieldTemplate → TextWidget (budget)
+```
+
+### anyOf inside Array
+
+```json
+{
+ "items": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ { "type": "string" },
+ { "type": "object", "properties": { "id": { "type": "string" } } }
+ ]
+ }
+ }
+}
+```
+
+```
+Level 1: ObjectFieldTemplate (root)
+└── Level 2: FieldTemplate → ArrayFieldTemplate
+ └── Level 3: ArrayFieldItemTemplate → AnyOfField
+ └── Selector + selected:
+ ├── String → TextWidget
+ └── Object → ObjectFieldTemplate
+ └── FieldTemplate → TextWidget (id)
+```
+
+---
+
+## Nesting Pattern Summary
+
+| Parent Type | Child Wrapper |
+| ----------- | ----------------------------------------------- |
+| object | `ObjectFieldTemplate` → `FieldTemplate` |
+| array | `ArrayFieldTemplate` → `ArrayFieldItemTemplate` |
+| anyOf | `AnyOfField` → selected schema's template |
+| primitive | `Widget` (leaf - no children) |
+
+**Pattern:** Each level adds FieldTemplate wrapper except array items (use ArrayFieldItemTemplate)
+
+---
+
+## Key Points
+
+1. **FieldTemplate wraps everything** - handles title, description, errors
+2. **anyOf = AnyOfField** - shows dropdown to pick type, then renders selected schema
+3. **ObjectFieldTemplate loops properties** - each property gets FieldTemplate
+4. **ArrayFieldTemplate loops items** - each item gets ArrayFieldItemTemplate
+5. **Widgets are leaf nodes** - actual input controls user interacts with
+6. **Nesting repeats the pattern** - object/array/anyOf can contain object/array/anyOf recursively
+
+---
+
+## Decision Flow
+
+```
+Schema Type?
+├── object → ObjectFieldTemplate → loop properties
+├── array → ArrayFieldTemplate → loop items
+├── anyOf/oneOf → AnyOfField → selector + selected schema
+└── primitive (string/number/boolean) → Widget
+```
+
+---
+
+## Template Wrapping Order
+
+```
+ObjectFieldTemplate (root)
+└── FieldTemplate (per property)
+ └── WrapIfAdditionalTemplate (if additionalProperties)
+ └── TitleField + DescriptionField + children
+ └── Widget OR nested Template/Field
+```
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/helpers.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/helpers.ts
new file mode 100644
index 0000000000..f3302fcb85
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/helpers.ts
@@ -0,0 +1,276 @@
+import {
+ RJSFSchema,
+ UIOptionsType,
+ StrictRJSFSchema,
+ FormContextType,
+ ADDITIONAL_PROPERTY_FLAG,
+} from "@rjsf/utils";
+
+import {
+ ANY_OF_FLAG,
+ ARRAY_ITEM_FLAG,
+ ID_PREFIX,
+ ID_PREFIX_ARRAY,
+ KEY_PAIR_FLAG,
+ OBJECT_FLAG,
+} from "./constants";
+import { PathSegment } from "./types";
+
+export function updateUiOption>(
+ uiSchema: T | undefined,
+ options: Record,
+): T & { "ui:options": Record } {
+ return {
+ ...(uiSchema || {}),
+ "ui:options": {
+ ...uiSchema?.["ui:options"],
+ ...options,
+ },
+ } as T & { "ui:options": Record };
+}
+
+export const cleanUpHandleId = (handleId: string) => {
+ let newHandleId = handleId;
+ if (handleId.includes(ANY_OF_FLAG)) {
+ newHandleId = newHandleId.replace(ANY_OF_FLAG, "");
+ }
+ if (handleId.includes(ARRAY_ITEM_FLAG)) {
+ newHandleId = newHandleId.replace(ARRAY_ITEM_FLAG, "");
+ }
+ if (handleId.includes(KEY_PAIR_FLAG)) {
+ newHandleId = newHandleId.replace(KEY_PAIR_FLAG, "");
+ }
+ if (handleId.includes(OBJECT_FLAG)) {
+ newHandleId = newHandleId.replace(OBJECT_FLAG, "");
+ }
+ if (handleId.includes(ID_PREFIX_ARRAY)) {
+ newHandleId = newHandleId.replace(ID_PREFIX_ARRAY, "");
+ }
+ if (handleId.includes(ID_PREFIX)) {
+ newHandleId = newHandleId.replace(ID_PREFIX, "");
+ }
+ return newHandleId;
+};
+
+export const isArrayItem = <
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any,
+>({
+ uiOptions,
+}: {
+ uiOptions: UIOptionsType;
+}) => {
+ return uiOptions.handleId?.endsWith(ARRAY_ITEM_FLAG);
+};
+
+export const isKeyValuePair = ({ schema }: { schema: RJSFSchema }) => {
+ return ADDITIONAL_PROPERTY_FLAG in schema;
+};
+
+export const isNormal = <
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any,
+>({
+ uiOptions,
+}: {
+ uiOptions: UIOptionsType;
+}) => {
+ return uiOptions.handleId === undefined;
+};
+
+export const isPartOfAnyOf = <
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any,
+>({
+ uiOptions,
+}: {
+ uiOptions: UIOptionsType;
+}) => {
+ return uiOptions.handleId?.endsWith(ANY_OF_FLAG);
+};
+export const isObjectProperty = <
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any,
+>({
+ uiOptions,
+ schema,
+}: {
+ uiOptions: UIOptionsType;
+ schema: RJSFSchema;
+}) => {
+ return (
+ !isArrayItem({ uiOptions }) &&
+ !isKeyValuePair({ schema }) &&
+ !isNormal({ uiOptions }) &&
+ !isPartOfAnyOf({ uiOptions })
+ );
+};
+
+export const getHandleId = <
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any,
+>({
+ id,
+ schema,
+ uiOptions,
+}: {
+ id: string;
+ schema: RJSFSchema;
+ uiOptions: UIOptionsType;
+}) => {
+ const parentHandleId = uiOptions.handleId;
+
+ if (isNormal({ uiOptions })) {
+ return id;
+ }
+
+ if (isPartOfAnyOf({ uiOptions })) {
+ return parentHandleId + ANY_OF_FLAG;
+ }
+
+ if (isKeyValuePair({ schema })) {
+ const key = id.split("_%_").at(-1);
+ let prefix = "";
+ if (parentHandleId) {
+ prefix = parentHandleId;
+ } else {
+ prefix = id.split("_%_").slice(0, -1).join("_%_");
+ }
+
+ const handleId = `${prefix}_#_${key}`;
+ return handleId + KEY_PAIR_FLAG;
+ }
+
+ if (isArrayItem({ uiOptions })) {
+ const index = id.split("_%_").at(-1);
+ const prefix = id.split("_%_").slice(0, -1).join("_%_");
+ const handleId = `${prefix}_$_${index}`;
+ return handleId + ARRAY_ITEM_FLAG;
+ }
+
+ if (isObjectProperty({ uiOptions, schema })) {
+ const key = id.split("_%_").at(-1);
+ const prefix = id.split("_%_").slice(0, -1).join("_%_");
+ const handleId = `${prefix}_@_${key}`;
+ return handleId + OBJECT_FLAG;
+ }
+ return parentHandleId;
+};
+
+export function isCredentialFieldSchema(schema: any): boolean {
+ return (
+ typeof schema === "object" &&
+ schema !== null &&
+ "credentials_provider" in schema
+ );
+}
+
+export function parseHandleIdToPath(handleId: string): PathSegment[] {
+ const cleanedId = cleanUpHandleId(handleId);
+ const segments: PathSegment[] = [];
+ const parts = cleanedId.split(/(_#_|_@_|_\$_|\.)/);
+
+ let currentType: "property" | "item" | "additional" | "normal" = "normal";
+
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i];
+
+ if (part === "_#_") {
+ currentType = "additional";
+ } else if (part === "_@_") {
+ currentType = "property";
+ } else if (part === "_$_") {
+ currentType = "item";
+ } else if (part === ".") {
+ currentType = "normal";
+ } else if (part) {
+ const isNumeric = /^\d+$/.test(part);
+ if (currentType === "item" && isNumeric) {
+ segments.push({
+ key: part,
+ type: "item",
+ index: parseInt(part, 10),
+ });
+ } else {
+ segments.push({
+ key: part,
+ type: currentType,
+ });
+ }
+ currentType = "normal";
+ }
+ }
+
+ return segments;
+}
+
+/**
+ * Ensure a path exists in an object, creating intermediate objects/arrays as needed
+ * Returns true if any modifications were made
+ */
+export function ensurePathExists(
+ obj: Record,
+ segments: PathSegment[],
+): boolean {
+ if (segments.length === 0) return false;
+
+ let current = obj;
+ let modified = false;
+
+ for (let i = 0; i < segments.length; i++) {
+ const segment = segments[i];
+ const isLast = i === segments.length - 1;
+ const nextSegment = segments[i + 1];
+
+ const getDefaultValue = () => {
+ if (isLast) {
+ return "";
+ }
+ if (nextSegment?.type === "item") {
+ return [];
+ }
+ return {};
+ };
+
+ if (segment.type === "item" && segment.index !== undefined) {
+ if (!Array.isArray(current)) {
+ return modified;
+ }
+
+ while (current.length <= segment.index) {
+ current.push(isLast ? "" : {});
+ modified = true;
+ }
+
+ if (!isLast) {
+ if (
+ current[segment.index] === undefined ||
+ current[segment.index] === null
+ ) {
+ current[segment.index] = getDefaultValue();
+ modified = true;
+ }
+ current = current[segment.index];
+ }
+ } else {
+ if (!(segment.key in current)) {
+ current[segment.key] = getDefaultValue();
+ modified = true;
+ } else if (!isLast && current[segment.key] === undefined) {
+ current[segment.key] = getDefaultValue();
+ modified = true;
+ }
+
+ if (!isLast) {
+ current = current[segment.key];
+ }
+ }
+ }
+
+ return modified;
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/index.ts
new file mode 100644
index 0000000000..c25af0b231
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/index.ts
@@ -0,0 +1,3 @@
+export { FormRenderer } from "./FormRenderer";
+export { default as Form } from "./registry";
+export type { ExtendedFormContextType } from "./types";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/Form.tsx b/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/Form.tsx
new file mode 100644
index 0000000000..5bf720a994
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/Form.tsx
@@ -0,0 +1,23 @@
+import { ComponentType } from "react";
+import { FormProps, withTheme, ThemeProps } from "@rjsf/core";
+import {
+ generateBaseFields,
+ generateBaseTemplates,
+ generateBaseWidgets,
+} from "../base/base-registry";
+import { generateCustomFields } from "../custom/custom-registry";
+
+export function generateForm(): ComponentType {
+ const theme: ThemeProps = {
+ templates: generateBaseTemplates(),
+ widgets: generateBaseWidgets(),
+ fields: {
+ ...generateBaseFields(),
+ ...generateCustomFields(),
+ },
+ };
+
+ return withTheme(theme);
+}
+
+export default generateForm();
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/index.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/index.ts
new file mode 100644
index 0000000000..641c2a97d6
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/index.ts
@@ -0,0 +1,10 @@
+export { default, generateForm } from "./Form";
+export {
+ generateBaseFields,
+ generateBaseTemplates,
+ generateBaseWidgets,
+} from "../base/base-registry";
+export {
+ generateCustomFields,
+ findCustomFieldId,
+} from "../custom/custom-registry";
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/types.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/types.ts
new file mode 100644
index 0000000000..cab84afcc3
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/registry/types.ts
@@ -0,0 +1,7 @@
+import { BlockUIType } from "@/app/(platform)/build/components/types";
+
+export type ExtraContext = {
+ nodeId?: string;
+ uiType?: BlockUIType;
+ size?: "small" | "medium" | "large";
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/types.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/types.ts
new file mode 100644
index 0000000000..af2e8b7866
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/types.ts
@@ -0,0 +1,15 @@
+import { BlockUIType } from "@/lib/autogpt-server-api/types";
+import { FormContextType } from "@rjsf/utils";
+
+export interface ExtendedFormContextType extends FormContextType {
+ nodeId?: string;
+ uiType?: BlockUIType;
+ showHandles?: boolean;
+ size?: "small" | "medium" | "large";
+}
+
+export type PathSegment = {
+ key: string;
+ type: "property" | "item" | "additional" | "normal";
+ index?: number;
+};
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/custom-validator.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/custom-validator.ts
similarity index 100%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/utils/custom-validator.ts
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/custom-validator.ts
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/helpers.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/helpers.ts
similarity index 100%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/utils/helpers.ts
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/helpers.ts
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/input-schema-pre-processor.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/input-schema-pre-processor.ts
similarity index 91%
rename from autogpt_platform/frontend/src/components/renderers/input-renderer/utils/input-schema-pre-processor.ts
rename to autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/input-schema-pre-processor.ts
index ffbcbf52b2..dad95251ed 100644
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/input-schema-pre-processor.ts
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/input-schema-pre-processor.ts
@@ -1,9 +1,11 @@
import { RJSFSchema } from "@rjsf/utils";
+import { findCustomFieldId } from "../custom/custom-registry";
/**
* Pre-processes the input schema to ensure all properties have a type defined.
* If a property doesn't have a type, it assigns a union of all supported JSON Schema types.
*/
+
export function preprocessInputSchema(schema: RJSFSchema): RJSFSchema {
if (!schema || typeof schema !== "object") {
return schema;
@@ -19,6 +21,12 @@ export function preprocessInputSchema(schema: RJSFSchema): RJSFSchema {
if (property && typeof property === "object") {
const processedProperty = { ...property };
+ // adding $id for custom field
+ const customFieldId = findCustomFieldId(processedProperty);
+ if (customFieldId) {
+ processedProperty.$id = customFieldId;
+ }
+
// Only add type if no type is defined AND no anyOf/oneOf/allOf is present
if (
!processedProperty.type &&
@@ -32,7 +40,7 @@ export function preprocessInputSchema(schema: RJSFSchema): RJSFSchema {
{ type: "integer" },
{ type: "boolean" },
{ type: "array", items: { type: "string" } },
- { type: "object" },
+ { type: "object", title: "Object", additionalProperties: true },
{ type: "null" },
];
}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/rjsf-utils.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/rjsf-utils.ts
new file mode 100644
index 0000000000..365058cebd
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/rjsf-utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/schema-utils.ts b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/schema-utils.ts
new file mode 100644
index 0000000000..b1cfd37967
--- /dev/null
+++ b/autogpt_platform/frontend/src/components/renderers/InputRenderer/utils/schema-utils.ts
@@ -0,0 +1,35 @@
+import { getUiOptions, RJSFSchema, UiSchema } from "@rjsf/utils";
+
+export function isAnyOfSchema(schema: RJSFSchema | undefined): boolean {
+ return Array.isArray(schema?.anyOf) && schema!.anyOf.length > 0;
+}
+
+export const isAnyOfChild = (
+ uiSchema: UiSchema | undefined,
+): boolean => {
+ const uiOptions = getUiOptions(uiSchema);
+ return uiOptions.label === false;
+};
+
+export function isOptionalType(schema: RJSFSchema | undefined): {
+ isOptional: boolean;
+ type?: any;
+} {
+ if (
+ !Array.isArray(schema?.anyOf) ||
+ schema!.anyOf.length !== 2 ||
+ !schema!.anyOf.some((opt: any) => opt.type === "null")
+ ) {
+ return { isOptional: false };
+ }
+
+ const nonNullType = schema!.anyOf?.find((opt: any) => opt.type !== "null");
+
+ return {
+ isOptional: true,
+ type: nonNullType,
+ };
+}
+export function isAnyOfSelector(name: string) {
+ return name.includes("anyof_select");
+}
diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/ARCHITECTURE_INPUT_RENDERER.md b/autogpt_platform/frontend/src/components/renderers/input-renderer/ARCHITECTURE_INPUT_RENDERER.md
deleted file mode 100644
index 7ae3d2b546..0000000000
--- a/autogpt_platform/frontend/src/components/renderers/input-renderer/ARCHITECTURE_INPUT_RENDERER.md
+++ /dev/null
@@ -1,938 +0,0 @@
-# Input-Renderer Architecture Documentation
-
-## Overview
-
-The Input-Renderer is a **JSON Schema-based form generation system** built on top of **React JSON Schema Form (RJSF)**. It dynamically creates form inputs for block nodes in the FlowEditor based on JSON schemas defined in the backend.
-
-This system allows blocks to define their input requirements declaratively, and the frontend automatically generates appropriate UI components.
-
----
-
-## High-Level Architecture
-
-```
-┌─────────────────────────────────────────────────────────┐
-│ FormRenderer │
-│ (Entry point, wraps RJSF Form) │
-└─────────────────────┬───────────────────────────────────┘
- │
- ┌─────────▼─────────┐
- │ RJSF Core │
- │ │
- └───────┬───────────┘
- │
- ┌───────────┼───────────┬──────────────┐
- │ │ │ │
- ┌────▼────┐ ┌───▼────┐ ┌────▼─────┐ ┌────▼────┐
- │ Fields │ │Templates│ │ Widgets │ │ Schemas │
- └─────────┘ └─────────┘ └──────────┘ └─────────┘
- │ │ │ │
- │ │ │ │
- Handles Wrapper Actual JSON Schema
- complex layouts input (from backend)
- types & labels components
-```
-
----
-
-## What is RJSF (React JSON Schema Form)?
-
-**RJSF** is a library that generates React forms from JSON Schema definitions. It follows a specific hierarchy to render forms:
-
-### **RJSF Rendering Flow:**
-
-```
-1. JSON Schema (defines data structure)
- ↓
-2. Schema Field (decides which Field component to use)
- ↓
-3. Field Component (handles specific type logic)
- ↓
-4. Field Template (wraps field with label, description)
- ↓
-5. Widget (actual input element - TextInput, Select, etc.)
-```
-
-### **Example Flow:**
-
-```json
-// JSON Schema
-{
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "title": "Name"
- }
- }
-}
-```
-
-**Becomes:**
-
-```
-SchemaField (detects "string" type)
- ↓
-StringField (default RJSF field)
- ↓
-FieldTemplate (adds label "Name")
- ↓
-TextWidget (renders )
-```
-
----
-
-## Core Components of Input-Renderer
-
-### 1. **FormRenderer** (`FormRenderer.tsx`)
-
-The main entry point that wraps RJSF `` component.
-
-```typescript
-export const FormRenderer = ({
- jsonSchema, // JSON Schema from backend
- handleChange, // Callback when form changes
- uiSchema, // UI customization
- initialValues, // Pre-filled values
- formContext, // Extra context (nodeId, uiType, etc.)
-}: FormRendererProps) => {
- const preprocessedSchema = preprocessInputSchema(jsonSchema);
-
- return (
-
- );
-};
-```
-
-**Key Props:**
-
-- **`fields`** - Custom components for complex types (anyOf, credentials, objects)
-- **`templates`** - Layout wrappers (FieldTemplate, ArrayFieldTemplate)
-- **`widgets`** - Actual input components (TextInput, Select, FileWidget)
-- **`formContext`** - Shared data (nodeId, showHandles, size)
-
----
-
-### 2. **Schema Pre-Processing** (`utils/input-schema-pre-processor.ts`)
-
-Before rendering, schemas are transformed to ensure RJSF compatibility.
-
-**Purpose:**
-
-- Add missing `type` fields (prevents RJSF errors)
-- Recursively process nested objects and arrays
-- Normalize inconsistent schemas from backend
-
-**Example:**
-
-```typescript
-// Backend schema (missing type)
-{
- "properties": {
- "value": {} // No type defined!
- }
-}
-
-// After pre-processing
-{
- "properties": {
- "value": {
- "anyOf": [
- { "type": "string" },
- { "type": "number" },
- { "type": "boolean" },
- // ... all possible types
- ]
- }
- }
-}
-```
-
-**Why?** RJSF requires explicit types. Without this, it would crash or render incorrectly.
-
----
-
-## The Three Pillars: Fields, Templates, Widgets
-
-### **A. Fields** (`fields/`)
-
-Fields handle **complex type logic** that goes beyond simple inputs.
-
-**Registered Fields:**
-
-```typescript
-export const fields: RegistryFieldsType = {
- AnyOfField: AnyOfField, // Handles anyOf/oneOf
- credentials: CredentialsField, // OAuth/API key handling
- ObjectField: ObjectField, // Free-form objects
-};
-```
-
-#### **1. AnyOfField** (`fields/AnyOfField/AnyOfField.tsx`)
-
-Handles schemas with multiple possible types (union types).
-
-**When Used:**
-
-```json
-{
- "anyOf": [{ "type": "string" }, { "type": "number" }, { "type": "boolean" }]
-}
-```
-
-**Rendering:**
-
-```
-┌─────────────────────────────────────┐
-│ Parameter Name (string) ▼ │ ← Type selector dropdown
-├─────────────────────────────────────┤
-│ [Text Input] │ ← Widget for selected type
-└─────────────────────────────────────┘
-```
-
-**Features:**
-
-- Type selector dropdown
-- Nullable types (with toggle switch)
-- Recursive rendering (can contain arrays, objects)
-- Connection-aware (hides input when connected)
-
-**Special Case: Nullable Types**
-
-```json
-{
- "anyOf": [{ "type": "string" }, { "type": "null" }]
-}
-```
-
-**Renders as:**
-
-```
-┌─────────────────────────────────────┐
-│ Parameter Name (string | null) [✓] │ ← Toggle switch
-├─────────────────────────────────────┤
-│ [Text Input] (only if enabled) │
-└─────────────────────────────────────┘
-```
-
----
-
-#### **2. CredentialsField** (`fields/CredentialField/CredentialField.tsx`)
-
-Handles authentication credentials (OAuth, API Keys, Passwords).
-
-**When Used:**
-
-```json
-{
- "type": "object",
- "credentials": {
- "provider": "google",
- "scopes": ["email", "profile"]
- }
-}
-```
-
-**Flow:**
-
-```
-1. Renders SelectCredential dropdown
- ↓
-2. User selects existing credential OR clicks "Add New"
- ↓
-3. Modal opens (OAuthModal/APIKeyModal/PasswordModal)
- ↓
-4. User authorizes/enters credentials
- ↓
-5. Credential saved to backend
- ↓
-6. Dropdown shows selected credential
-```
-
-**Credential Types:**
-
-- **OAuth** - 3rd party authorization (Google, GitHub, etc.)
-- **API Key** - Simple key-based auth
-- **Password** - Username/password pairs
-
----
-
-#### **3. ObjectField** (`fields/ObjectField.tsx`)
-
-Handles free-form objects (key-value pairs).
-
-**When Used:**
-
-```json
-{
- "type": "object",
- "additionalProperties": true // Free-form
-}
-```
-
-vs
-
-```json
-{
- "type": "object",
- "properties": {
- "name": { "type": "string" } // Fixed schema
- }
-}
-```
-
-**Behavior:**
-
-- **Fixed schema** → Uses default RJSF rendering
-- **Free-form** → Uses ObjectEditorWidget (JSON editor)
-
----
-
-### **B. Templates** (`templates/`)
-
-Templates control **layout and wrapping** of fields.
-
-#### **1. FieldTemplate** (`templates/FieldTemplate.tsx`)
-
-Wraps every field with label, type indicator, and connection handle.
-
-**Rendering Structure:**
-
-```
-┌────────────────────────────────────────┐
-│ ○ Label (type) ⓘ │ ← Handle + Label + Type + Info icon
-├────────────────────────────────────────┤
-│ [Actual Input Widget] │ ← The input itself
-└────────────────────────────────────────┘
-```
-
-**Responsibilities:**
-
-- Shows/hides input based on connection status
-- Renders connection handle (NodeHandle)
-- Displays type information
-- Shows tooltip with description
-- Handles "advanced" field visibility
-- Formats credential field labels
-
-**Key Logic:**
-
-```typescript
-// Hide input if connected
-{(isAnyOf || !isConnected) && (
- {children}
-)}
-
-// Show handle for most fields
-{shouldShowHandle && (
-
-)}
-```
-
-**Context-Aware Behavior:**
-
-- Inside `AnyOfField` → No handle (parent handles it)
-- Credential field → Special label formatting
-- Array item → Uses parent handle
-- INPUT/OUTPUT/WEBHOOK blocks → Different handle positioning
-
----
-
-#### **2. ArrayFieldTemplate** (`templates/ArrayFieldTemplate.tsx`)
-
-Wraps array fields to use custom ArrayEditorWidget.
-
-**Simple Wrapper:**
-
-```typescript
-function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
- const { items, canAdd, onAddClick, nodeId } = props;
-
- return (
-
- );
-}
-```
-
----
-
-### **C. Widgets** (`widgets/`)
-
-Widgets are **actual input components** - the final rendered HTML elements.
-
-**Registered Widgets:**
-
-```typescript
-export const widgets: RegistryWidgetsType = {
- TextWidget: TextInputWidget, //
- SelectWidget: SelectWidget, //