From 427c7eb1d4ff059b8f7b7c320f35a86f932e3881 Mon Sep 17 00:00:00 2001 From: Abhimanyu Yadav <122007096+Abhi1992002@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:35:45 +0530 Subject: [PATCH] feat(frontend): Add dynamic input dialog for agent execution with credential support (#11301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Changes 🏗️ This PR enhances the agent execution functionality by introducing a dynamic input dialog that collects both regular inputs and credentials before running agents. Screenshot 2025-11-03 at 10 16
38 AM #### ✨ New Features - **Dynamic Input Dialog**: Added a new `RunInputDialog` component that automatically detects when agents require inputs or credentials and prompts users before execution - **Credential Management**: Integrated credential input handling directly into the execution flow, supporting various credential types (API keys, OAuth, passwords) - **Enhanced Run Controls**: Improved the `RunGraph` component with better state management and visual feedback for running/stopping agents - **Form Renderer**: Created a new unified `FormRenderer` component for consistent input rendering across the application #### 🔧 Refactoring - **Input Renderer Migration**: Moved input renderer components from FlowEditor-specific location to a shared components directory for better reusability: - Migrated fields (AnyOfField, CredentialField, ObjectField) - Migrated widgets (ArrayEditor, DateInput, SelectWidget, TextInput, etc.) - Migrated templates (FieldTemplate, ArrayFieldTemplate) - **State Management**: Enhanced `graphStore` with schemas for inputs and credentials, including helper methods to check for their presence - **Component Organization**: Restructured BuilderActions components for better modularity #### 🗑️ Cleanup - Removed outdated FlowEditor documentation files (FORM_CREATOR.md, README.md) - Removed deprecated `RunGraph` and `useRunGraph` implementations from FlowEditor - Consolidated duplicate functionality into new shared components #### 🎨 UI/UX Improvements - Added gradient styling to Run/Stop button for better visual appeal - Improved dialog layout with clear sections for Credentials and Inputs - Enhanced form fields with size variants (small, medium, large) for better responsiveness - Added loading states and proper error handling during execution ### Technical Details - The new system automatically detects input requirements from the graph schema - Credentials are handled separately with special UI treatment based on credential type - The dialog only appears when inputs or credentials are actually required - Execution flow: Save graph → Check for inputs/credentials → Show dialog if needed → Execute with provided values ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Create an agent without inputs and verify it runs directly without dialog - [x] Create an agent with input blocks and verify the dialog appears with correct fields - [x] Create an agent requiring credentials and verify credential selection/creation works - [x] Test agent execution with both inputs and credentials - [x] Verify Stop Agent functionality during execution - [x] Test error handling for invalid inputs or missing credentials - [x] Verify that the dialog closes properly after submission - [x] Test that execution state is properly reflected in the UI --- .../BuilderActions/BuilderActions.tsx | 2 +- .../components/RunGraph/RunGraph.tsx | 45 ++ .../components/RunGraph/useRunGraph.ts | 101 +++ .../RunInputDialog/RunInputDialog.tsx | 107 ++++ .../RunInputDialog/useRunInputDialog.ts | 108 ++++ .../BuilderActions/components/RunGraph.tsx | 32 - .../BuilderActions/components/useRunGraph.ts | 62 -- .../build/components/FlowEditor/Flow/Flow.tsx | 2 +- .../components/FlowEditor/Flow/useFlow.ts | 12 + .../FlowEditor/docs/FORM_CREATOR.md | 580 ------------------ .../components/FlowEditor/docs/README.md | 159 ----- .../nodes/CustomNode/StandardNodeBlock.tsx | 4 +- .../nodes/CustomNode/StickyNoteBlock.tsx | 2 +- .../FlowEditor/nodes/FormCreator.tsx | 25 +- .../app/(platform)/build/stores/graphStore.ts | 36 +- .../renderers/input-renderer/FormRenderer.tsx | 51 ++ .../fields/AnyOfField/AnyOfField.tsx | 55 +- .../fields/AnyOfField/useAnyOfField.tsx | 0 .../CredentialField/CredentialField.tsx | 0 .../CredentialField/SelectCredential.tsx | 0 .../fields/CredentialField/helpers.ts | 0 .../APIKeyCredentialModal.tsx | 0 .../useAPIKeyCredentialsModal.ts | 0 .../OAuthCredentialModal.tsx | 0 .../useOAuthCredentialModal.ts | 0 .../PasswordCredentialModal.tsx | 0 .../usePasswordCredentialModal.ts | 0 .../CredentialField/useCredentialField.ts | 0 .../input-renderer}/fields/ObjectField.tsx | 4 +- .../renderers/input-renderer}/fields/index.ts | 0 .../templates/ArrayFieldTemplate.tsx | 4 +- .../templates/FieldTemplate.tsx | 37 +- .../input-renderer}/templates/index.ts | 0 .../utils}/input-schema-pre-processor.ts | 0 .../ArrayEditorWidget}/ArrayEditorContext.tsx | 0 .../ArrayEditorWidget/ArrayEditorWidget.tsx} | 11 +- .../widgets/DateInputWidget.tsx | 18 +- .../widgets/DateTimeInputWidget.tsx | 19 +- .../input-renderer}/widgets/FileWidget.tsx | 0 .../ObjectEditorWidget.tsx} | 20 +- .../input-renderer}/widgets/SelectWidget.tsx | 14 +- .../input-renderer}/widgets/SwitchWidget.tsx | 0 .../widgets/TextInputWidget.tsx | 15 +- .../widgets/TimeInputWidget.tsx | 10 +- .../input-renderer}/widgets/index.ts | 0 45 files changed, 624 insertions(+), 911 deletions(-) rename autogpt_platform/frontend/src/app/(platform)/build/components/{FlowEditor => }/BuilderActions/BuilderActions.tsx (80%) create mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/RunGraph.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts create mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/RunInputDialog.tsx create mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts delete mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/RunGraph.tsx delete mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/useRunGraph.ts delete mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/FORM_CREATOR.md delete mode 100644 autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/README.md create mode 100644 autogpt_platform/frontend/src/components/renderers/input-renderer/FormRenderer.tsx rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/AnyOfField/AnyOfField.tsx (77%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/AnyOfField/useAnyOfField.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/CredentialField.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/SelectCredential.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/helpers.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/models/APIKeyCredentialModal/APIKeyCredentialModal.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/models/APIKeyCredentialModal/useAPIKeyCredentialsModal.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/models/OAuthCredentialModal/OAuthCredentialModal.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/models/OAuthCredentialModal/useOAuthCredentialModal.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/models/PasswordCredentialModal/PasswordCredentialModal.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/models/PasswordCredentialModal/usePasswordCredentialModal.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/CredentialField/useCredentialField.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/ObjectField.tsx (84%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/fields/index.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/templates/ArrayFieldTemplate.tsx (83%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/templates/FieldTemplate.tsx (75%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/templates/index.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/processors => components/renderers/input-renderer/utils}/input-schema-pre-processor.ts (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/components/ArrayEditor => components/renderers/input-renderer/widgets/ArrayEditorWidget}/ArrayEditorContext.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/components/ArrayEditor/ArrayEditor.tsx => components/renderers/input-renderer/widgets/ArrayEditorWidget/ArrayEditorWidget.tsx} (90%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/DateInputWidget.tsx (59%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/DateTimeInputWidget.tsx (58%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/FileWidget.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/components/ObjectEditor/ObjectEditor.tsx => components/renderers/input-renderer/widgets/ObjectEditorWidget/ObjectEditorWidget.tsx} (89%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/SelectWidget.tsx (80%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/SwitchWidget.tsx (100%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/TextInputWidget.tsx (87%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/TimeInputWidget.tsx (61%) rename autogpt_platform/frontend/src/{app/(platform)/build/components/FlowEditor/nodes => components/renderers/input-renderer}/widgets/index.ts (100%) diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/BuilderActions.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/BuilderActions.tsx similarity index 80% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/BuilderActions.tsx rename to autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/BuilderActions.tsx index 88fdceb686..48b2bfd24c 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/BuilderActions.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/BuilderActions.tsx @@ -1,4 +1,4 @@ -import { RunGraph } from "./components/RunGraph"; +import { RunGraph } from "./components/RunGraph/RunGraph"; export const BuilderActions = () => { return ( diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/RunGraph.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/RunGraph.tsx new file mode 100644 index 0000000000..b7e59db9f3 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/RunGraph.tsx @@ -0,0 +1,45 @@ +import { Button } from "@/components/atoms/Button/Button"; +import { PlayIcon } from "lucide-react"; +import { useRunGraph } from "./useRunGraph"; +import { useGraphStore } from "@/app/(platform)/build/stores/graphStore"; +import { useShallow } from "zustand/react/shallow"; +import { StopIcon } from "@phosphor-icons/react"; +import { cn } from "@/lib/utils"; +import { RunInputDialog } from "../RunInputDialog/RunInputDialog"; + +export const RunGraph = () => { + const { + handleRunGraph, + handleStopGraph, + isSaving, + openRunInputDialog, + setOpenRunInputDialog, + } = useRunGraph(); + const isGraphRunning = useGraphStore( + useShallow((state) => state.isGraphRunning), + ); + + return ( + <> + + + + ); +}; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts new file mode 100644 index 0000000000..bbdb44123b --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunGraph/useRunGraph.ts @@ -0,0 +1,101 @@ +import { + usePostV1ExecuteGraphAgent, + usePostV1StopGraphExecution, +} from "@/app/api/__generated__/endpoints/graphs/graphs"; +import { useNewSaveControl } from "../../../NewControlPanel/NewSaveControl/useNewSaveControl"; +import { useToast } from "@/components/molecules/Toast/use-toast"; +import { parseAsInteger, parseAsString, useQueryStates } from "nuqs"; +import { GraphExecutionMeta } from "@/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/use-agent-runs"; +import { useGraphStore } from "@/app/(platform)/build/stores/graphStore"; +import { useShallow } from "zustand/react/shallow"; +import { useState } from "react"; + +export const useRunGraph = () => { + const { onSubmit: onSaveGraph, isLoading: isSaving } = useNewSaveControl({ + showToast: false, + }); + const { toast } = useToast(); + const setIsGraphRunning = useGraphStore( + useShallow((state) => state.setIsGraphRunning), + ); + const hasInputs = useGraphStore(useShallow((state) => state.hasInputs)); + const hasCredentials = useGraphStore( + useShallow((state) => state.hasCredentials), + ); + const [openRunInputDialog, setOpenRunInputDialog] = useState(false); + + const [{ flowID, flowVersion, flowExecutionID }, setQueryStates] = + useQueryStates({ + flowID: parseAsString, + flowVersion: parseAsInteger, + flowExecutionID: parseAsString, + }); + + const { mutateAsync: executeGraph } = usePostV1ExecuteGraphAgent({ + mutation: { + onSuccess: (response) => { + const { id } = response.data as GraphExecutionMeta; + setQueryStates({ + flowExecutionID: id, + }); + }, + onError: (error) => { + setIsGraphRunning(false); + + toast({ + title: (error.detail as string) ?? "An unexpected error occurred.", + description: "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + const { mutateAsync: stopGraph } = usePostV1StopGraphExecution({ + mutation: { + onSuccess: () => { + setIsGraphRunning(false); + }, + onError: (error) => { + toast({ + title: (error.detail as string) ?? "An unexpected error occurred.", + description: "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + const handleRunGraph = async () => { + await onSaveGraph(undefined); + + if (hasInputs() || hasCredentials()) { + setOpenRunInputDialog(true); + } else { + setIsGraphRunning(true); + await executeGraph({ + graphId: flowID ?? "", + graphVersion: flowVersion || null, + data: { inputs: {}, credentials_inputs: {} }, + }); + } + }; + + const handleStopGraph = async () => { + if (!flowExecutionID) { + return; + } + await stopGraph({ + graphId: flowID ?? "", + graphExecId: flowExecutionID, + }); + }; + + return { + handleRunGraph, + handleStopGraph, + isSaving, + openRunInputDialog, + setOpenRunInputDialog, + }; +}; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/RunInputDialog.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/RunInputDialog.tsx new file mode 100644 index 0000000000..7d034cc680 --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/RunInputDialog.tsx @@ -0,0 +1,107 @@ +import { Dialog } from "@/components/molecules/Dialog/Dialog"; +import { RJSFSchema } from "@rjsf/utils"; +import { uiSchema } from "../../../FlowEditor/nodes/uiSchema"; +import { useGraphStore } from "@/app/(platform)/build/stores/graphStore"; +import { Button } from "@/components/atoms/Button/Button"; +import { PlayIcon } from "@phosphor-icons/react"; +import { Text } from "@/components/atoms/Text/Text"; +import { FormRenderer } from "@/components/renderers/input-renderer/FormRenderer"; +import { useRunInputDialog } from "./useRunInputDialog"; + +export const RunInputDialog = ({ + isOpen, + setIsOpen, +}: { + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; +}) => { + const hasInputs = useGraphStore((state) => state.hasInputs); + const hasCredentials = useGraphStore((state) => state.hasCredentials); + const inputSchema = useGraphStore((state) => state.inputSchema); + const credentialsSchema = useGraphStore( + (state) => state.credentialsInputSchema, + ); + + const { + credentialsUiSchema, + handleManualRun, + handleInputChange, + handleCredentialChange, + isExecutingGraph, + } = useRunInputDialog({ setIsOpen }); + + return ( + + +
+ {/* Credentials Section */} + {hasCredentials() && ( +
+
+ + Credentials + +
+
+ handleCredentialChange(v.formData)} + uiSchema={credentialsUiSchema} + initialValues={{}} + formContext={{ + showHandles: false, + size: "large", + }} + /> +
+
+ )} + + {/* Inputs Section */} + {hasInputs() && ( +
+
+ + Inputs + +
+
+ handleInputChange(v.formData)} + uiSchema={uiSchema} + initialValues={{}} + formContext={{ + showHandles: false, + size: "large", + }} + /> +
+
+ )} + + {/* Action Button */} +
+ +
+
+
+
+ ); +}; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts new file mode 100644 index 0000000000..e5858b964a --- /dev/null +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderActions/components/RunInputDialog/useRunInputDialog.ts @@ -0,0 +1,108 @@ +import { useGraphStore } from "@/app/(platform)/build/stores/graphStore"; +import { usePostV1ExecuteGraphAgent } from "@/app/api/__generated__/endpoints/graphs/graphs"; +import { useToast } from "@/components/molecules/Toast/use-toast"; +import { + CredentialsMetaInput, + GraphExecutionMeta, +} from "@/lib/autogpt-server-api"; +import { parseAsInteger, parseAsString, useQueryStates } from "nuqs"; +import { useMemo, useState } from "react"; +import { useShallow } from "zustand/react/shallow"; +import { uiSchema } from "../../../FlowEditor/nodes/uiSchema"; +import { isCredentialFieldSchema } from "@/components/renderers/input-renderer/fields/CredentialField/helpers"; + +export const useRunInputDialog = ({ + setIsOpen, +}: { + setIsOpen: (isOpen: boolean) => void; +}) => { + const credentialsSchema = useGraphStore( + (state) => state.credentialsInputSchema, + ); + const [inputValues, setInputValues] = useState>({}); + const [credentialValues, setCredentialValues] = useState< + Record + >({}); + const [{ flowID, flowVersion }, setQueryStates] = useQueryStates({ + flowExecutionID: parseAsString, + flowID: parseAsString, + flowVersion: parseAsInteger, + }); + const setIsGraphRunning = useGraphStore( + useShallow((state) => state.setIsGraphRunning), + ); + const { toast } = useToast(); + + const { mutateAsync: executeGraph, isPending: isExecutingGraph } = + usePostV1ExecuteGraphAgent({ + mutation: { + onSuccess: (response) => { + const { id } = response.data as GraphExecutionMeta; + setQueryStates({ + flowExecutionID: id, + }); + setIsGraphRunning(false); + }, + onError: (error) => { + setIsGraphRunning(false); + + toast({ + title: (error.detail as string) ?? "An unexpected error occurred.", + description: "An unexpected error occurred.", + variant: "destructive", + }); + }, + }, + }); + + // We are rendering the credentials field differently compared to other fields. + // In the node, we have the field name as "credential" - so our library catches it and renders it differently. + // But here we have a different name, something like `Firecrawl credentials`, so here we are telling the library that this field is a credential field type. + + const credentialsUiSchema = useMemo(() => { + const dynamicUiSchema: any = { ...uiSchema }; + + if (credentialsSchema?.properties) { + Object.keys(credentialsSchema.properties).forEach((fieldName) => { + const fieldSchema = credentialsSchema.properties[fieldName]; + if (isCredentialFieldSchema(fieldSchema)) { + dynamicUiSchema[fieldName] = { + ...dynamicUiSchema[fieldName], + "ui:field": "credentials", + }; + } + }); + } + + return dynamicUiSchema; + }, [credentialsSchema]); + + const handleManualRun = () => { + setIsOpen(false); + setIsGraphRunning(true); + executeGraph({ + graphId: flowID ?? "", + graphVersion: flowVersion || null, + data: { inputs: inputValues, credentials_inputs: credentialValues }, + }); + }; + + const handleInputChange = (inputValues: Record) => { + setInputValues(inputValues); + }; + const handleCredentialChange = ( + credentialValues: Record, + ) => { + setCredentialValues(credentialValues); + }; + + return { + credentialsUiSchema, + inputValues, + credentialValues, + isExecutingGraph, + handleInputChange, + handleCredentialChange, + handleManualRun, + }; +}; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/RunGraph.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/RunGraph.tsx deleted file mode 100644 index e1b56bc347..0000000000 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/RunGraph.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Button } from "@/components/atoms/Button/Button"; -import { PlayIcon } from "lucide-react"; -import { useRunGraph } from "./useRunGraph"; -import { useGraphStore } from "@/app/(platform)/build/stores/graphStore"; -import { useShallow } from "zustand/react/shallow"; -import { StopIcon } from "@phosphor-icons/react"; -import { cn } from "@/lib/utils"; - -export const RunGraph = () => { - const { runGraph, isSaving } = useRunGraph(); - const isGraphRunning = useGraphStore( - useShallow((state) => state.isGraphRunning), - ); - - return ( - - ); -}; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/useRunGraph.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/useRunGraph.ts deleted file mode 100644 index 717628db77..0000000000 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/BuilderActions/components/useRunGraph.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { usePostV1ExecuteGraphAgent } from "@/app/api/__generated__/endpoints/graphs/graphs"; -import { useNewSaveControl } from "../../../NewControlPanel/NewSaveControl/useNewSaveControl"; -import { useToast } from "@/components/molecules/Toast/use-toast"; -import { parseAsInteger, parseAsString, useQueryStates } from "nuqs"; -import { GraphExecutionMeta } from "@/app/(platform)/library/agents/[id]/components/OldAgentLibraryView/use-agent-runs"; -import { useGraphStore } from "@/app/(platform)/build/stores/graphStore"; -import { useShallow } from "zustand/react/shallow"; - -export const useRunGraph = () => { - const { onSubmit: onSaveGraph, isLoading: isSaving } = useNewSaveControl({ - showToast: false, - }); - const { toast } = useToast(); - const setIsGraphRunning = useGraphStore( - useShallow((state) => state.setIsGraphRunning), - ); - const [{ flowID, flowVersion }, setQueryStates] = useQueryStates({ - flowID: parseAsString, - flowVersion: parseAsInteger, - flowExecutionID: parseAsString, - }); - - const { mutateAsync: executeGraph } = usePostV1ExecuteGraphAgent({ - mutation: { - onSuccess: (response) => { - const { id } = response.data as GraphExecutionMeta; - setQueryStates({ - flowExecutionID: id, - }); - }, - onError: (error) => { - setIsGraphRunning(false); - - toast({ - title: (error.detail as string) ?? "An unexpected error occurred.", - description: "An unexpected error occurred.", - variant: "destructive", - }); - }, - }, - }); - - const runGraph = async () => { - setIsGraphRunning(true); - await onSaveGraph(undefined); - - // Todo : We need to save graph which has inputs and credentials inputs - await executeGraph({ - graphId: flowID ?? "", - graphVersion: flowVersion || null, - data: { - inputs: {}, - credentials_inputs: {}, - }, - }); - }; - - return { - runGraph, - isSaving, - }; -}; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/Flow.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/Flow.tsx index caa049bd9e..ba8f7adc2e 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/Flow.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/Flow.tsx @@ -9,7 +9,7 @@ import { CustomNode } from "../nodes/CustomNode/CustomNode"; import { useCustomEdge } from "../edges/useCustomEdge"; import { useFlowRealtime } from "./useFlowRealtime"; import { GraphLoadingBox } from "./components/GraphLoadingBox"; -import { BuilderActions } from "../BuilderActions/BuilderActions"; +import { BuilderActions } from "../../BuilderActions/BuilderActions"; import { RunningBackground } from "./components/RunningBackground"; import { useGraphStore } from "../../../stores/graphStore"; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts index 9696e7777c..d351691954 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/Flow/useFlow.ts @@ -27,6 +27,9 @@ export const useFlow = () => { const setIsGraphRunning = useGraphStore( useShallow((state) => state.setIsGraphRunning), ); + const setGraphSchemas = useGraphStore( + useShallow((state) => state.setGraphSchemas), + ); const [{ flowID, flowVersion, flowExecutionID }] = useQueryStates({ flowID: parseAsString, flowVersion: parseAsInteger, @@ -83,6 +86,14 @@ export const useFlow = () => { }, [nodes, blocks]); useEffect(() => { + // load graph schemas + if (graph) { + setGraphSchemas( + graph.input_schema as Record | null, + graph.credentials_input_schema as Record | null, + ); + } + // adding nodes if (customNodes.length > 0) { useNodeStore.getState().setNodes([]); @@ -128,6 +139,7 @@ export const useFlow = () => { return () => { useNodeStore.getState().setNodes([]); useEdgeStore.getState().setConnections([]); + useGraphStore.getState().reset(); setIsGraphRunning(false); }; }, []); diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/FORM_CREATOR.md b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/FORM_CREATOR.md deleted file mode 100644 index 0443011196..0000000000 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/FORM_CREATOR.md +++ /dev/null @@ -1,580 +0,0 @@ -# Form Creator System - -The Form Creator is a dynamic form generation system built on React JSON Schema Form (RJSF) that automatically creates interactive forms based on JSON schemas. It's the core component that powers the input handling in the FlowEditor. - -## Table of Contents - -- [Architecture Overview](#architecture-overview) -- [How It Works](#how-it-works) -- [Schema Processing](#schema-processing) -- [Widget System](#widget-system) -- [Field System](#field-system) -- [Template System](#template-system) -- [Customization Guide](#customization-guide) -- [Advanced Features](#advanced-features) - -## Architecture Overview - -The Form Creator system consists of several interconnected layers: - -``` -FormCreator -├── Schema Preprocessing -│ └── input-schema-pre-processor.ts -├── Widget System -│ ├── TextInputWidget -│ ├── SelectWidget -│ ├── SwitchWidget -│ └── ... (other widgets) -├── Field System -│ ├── AnyOfField -│ ├── ObjectField -│ └── CredentialsField -├── Template System -│ ├── FieldTemplate -│ └── ArrayFieldTemplate -└── UI Schema - └── uiSchema.ts -``` - -## How It Works - -### 1. **Schema Input** - -The FormCreator receives a JSON schema that defines the structure of the form: - -```typescript -const schema = { - type: "object", - properties: { - message: { - type: "string", - title: "Message", - description: "Enter your message", - }, - count: { - type: "number", - title: "Count", - minimum: 0, - }, - }, -}; -``` - -### 2. **Schema Preprocessing** - -The schema is preprocessed to ensure all properties have proper types: - -```typescript -// Before preprocessing -{ - "properties": { - "name": { "title": "Name" } // No type defined - } -} - -// After preprocessing -// if there is no type - that means it can accept any type -{ - "properties": { - "name": { - "title": "Name", - "anyOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" }, - { "type": "array", "items": { "type": "string" } }, - { "type": "object" }, - { "type": "null" } - ] - } - } -} -``` - -### 3. **Widget Mapping** - -Schema types are mapped to appropriate input widgets: - -```typescript -// Schema type -> Widget mapping -"string" -> TextInputWidget -"number" -> TextInputWidget (with number type) -"boolean" -> SwitchWidget -"array" -> ArrayFieldTemplate -"object" -> ObjectField -"enum" -> SelectWidget -``` - -### 4. **Form Rendering** - -RJSF renders the form using the mapped widgets and templates: - -```typescript -
-``` - -## Schema Processing - -### Input Schema Preprocessor - -The `preprocessInputSchema` function ensures all properties have proper types: - -```typescript -export function preprocessInputSchema(schema: RJSFSchema): RJSFSchema { - // Recursively processes properties - if (processedSchema.properties) { - for (const [key, property] of Object.entries(processedSchema.properties)) { - // Add type if none exists - if ( - !processedProperty.type && - !processedProperty.anyOf && - !processedProperty.oneOf && - !processedProperty.allOf - ) { - processedProperty.anyOf = [ - { type: "string" }, - { type: "number" }, - { type: "integer" }, - { type: "boolean" }, - { type: "array", items: { type: "string" } }, - { type: "object" }, - { type: "null" }, - ]; - } - } - } -} -``` - -### Key Features - -1. **Type Safety**: Ensures all properties have types -2. **Recursive Processing**: Handles nested objects and arrays -3. **Array Item Processing**: Processes array item schemas -4. **Schema Cleanup**: Removes titles and descriptions from root schema - -## Widget System - -Widgets are the actual input components that users interact with. - -### Available Widgets - -#### TextInputWidget - -Handles text, number, password, and textarea inputs: - -```typescript -export const TextInputWidget = (props: WidgetProps) => { - const { schema } = props; - const mapped = mapJsonSchemaTypeToInputType(schema); - - const inputConfig = { - [InputType.TEXT_AREA]: { - htmlType: "textarea", - placeholder: "Enter text...", - handleChange: (v: string) => (v === "" ? undefined : v), - }, - [InputType.PASSWORD]: { - htmlType: "password", - placeholder: "Enter secret text...", - handleChange: (v: string) => (v === "" ? undefined : v), - }, - [InputType.NUMBER]: { - htmlType: "number", - placeholder: "Enter number value...", - handleChange: (v: string) => (v === "" ? undefined : Number(v)), - } - }; - - return ; -}; -``` - -#### SelectWidget - -Handles dropdown and multi-select inputs: - -```typescript -export const SelectWidget = (props: WidgetProps) => { - const { options, value, onChange, schema } = props; - const enumOptions = options.enumOptions || []; - const type = mapJsonSchemaTypeToInputType(schema); - - if (type === InputType.MULTI_SELECT) { - return ; - } - - return - {renderInput(currentTypeOption)} - - ); -}; -``` - -### ObjectField - -Handles free-form object editing: - -```typescript -export const ObjectField = (props: FieldProps) => { - const { schema, formData = {}, onChange, name, idSchema, formContext } = props; - - // Use default field for fixed-schema objects - if (idSchema?.$id === "root" || !isFreeForm) { - return ; - } - - // Use custom ObjectEditor for free-form objects - return ( - - ); -}; -``` - -### Field Registration - -Fields are registered in the fields registry: - -```typescript -export const fields: RegistryFieldsType = { - AnyOfField: AnyOfField, - credentials: CredentialsField, - ObjectField: ObjectField, -}; -``` - -## Template System - -Templates provide custom rendering for form structure elements. - -### FieldTemplate - -Custom field wrapper with connection handles: - -```typescript -const FieldTemplate: React.FC = ({ - id, label, required, description, children, schema, formContext, uiSchema -}) => { - const { isInputConnected } = useEdgeStore(); - const { nodeId } = formContext; - - const fieldKey = generateHandleId(id); - const isConnected = isInputConnected(nodeId, fieldKey); - - return ( -
- {label && schema.type && ( - - )} - {!isConnected &&
{children}
} -
- ); -}; -``` - -### ArrayFieldTemplate - -Custom array editing interface: - -```typescript -function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { - const { items, canAdd, onAddClick, disabled, readonly, formContext, idSchema } = props; - const { nodeId } = formContext; - - return ( - - ); -} -``` - -## Customization Guide - -### Adding a Custom Widget - -1. **Create the Widget Component**: - -```typescript -import { WidgetProps } from "@rjsf/utils"; - -export const MyCustomWidget = (props: WidgetProps) => { - const { value, onChange, schema, disabled, readonly } = props; - - return ( -
- onChange(e.target.value)} - disabled={disabled || readonly} - placeholder={schema.placeholder} - /> -
- ); -}; -``` - -2. **Register the Widget**: - -```typescript -// In widgets/index.ts -export const widgets: RegistryWidgetsType = { - // ... existing widgets - MyCustomWidget: MyCustomWidget, -}; -``` - -3. **Use in Schema**: - -```typescript -const schema = { - type: "object", - properties: { - myField: { - type: "string", - "ui:widget": "MyCustomWidget", - }, - }, -}; -``` - -### Adding a Custom Field - -1. **Create the Field Component**: - -```typescript -import { FieldProps } from "@rjsf/utils"; - -export const MyCustomField = (props: FieldProps) => { - const { schema, formData, onChange, name, idSchema, formContext } = props; - - return ( -
- {/* Custom field implementation */} -
- ); -}; -``` - -2. **Register the Field**: - -```typescript -// In fields/index.ts -export const fields: RegistryFieldsType = { - // ... existing fields - MyCustomField: MyCustomField, -}; -``` - -3. **Use in Schema**: - -```typescript -const schema = { - type: "object", - properties: { - myField: { - type: "string", - "ui:field": "MyCustomField", - }, - }, -}; -``` - -### Customizing Templates - -1. **Create Custom Template**: - -```typescript -const MyCustomFieldTemplate: React.FC = (props) => { - return ( -
- {/* Custom template implementation */} -
- ); -}; -``` - -2. **Register Template**: - -```typescript -// In templates/index.ts -export const templates = { - FieldTemplate: MyCustomFieldTemplate, - // ... other templates -}; -``` - -## Advanced Features - -### Connection State Management - -The Form Creator integrates with the edge store to show/hide input fields based on connection state: - -```typescript -const FieldTemplate = ({ id, children, formContext }) => { - const { isInputConnected } = useEdgeStore(); - const { nodeId } = formContext; - - const fieldKey = generateHandleId(id); - const isConnected = isInputConnected(nodeId, fieldKey); - - return ( -
- - {!isConnected && children} -
- ); -}; -``` - -### Advanced Mode - -Fields can be hidden/shown based on advanced mode: - -```typescript -const FieldTemplate = ({ schema, formContext }) => { - const { nodeId } = formContext; - const showAdvanced = useNodeStore( - (state) => state.nodeAdvancedStates[nodeId] || false - ); - - if (!showAdvanced && schema.advanced === true) { - return null; - } - - return
{/* field content */}
; -}; -``` - -### Array Item Context - -Array items have special context for connection handling: - -```typescript -const ArrayEditor = ({ items, nodeId }) => { - return ( -
- {items?.map((element) => { - const fieldKey = generateHandleId(id, [element.index.toString()], HandleIdType.ARRAY); - const isConnected = isInputConnected(nodeId, fieldKey); - - return ( - - {element.children} - - ); - })} -
- ); -}; -``` - -### Handle ID Generation - -Handle IDs are generated based on field structure: - -```typescript -// Simple field -generateHandleId("message"); // "message" - -// Nested field -generateHandleId("config", ["api_key"]); // "config.api_key" - -// Array item -generateHandleId("items", ["0"]); // "items_$_0" - -// Key-value pair -generateHandleId("headers", ["Authorization"]); // "headers_#_Authorization" -``` diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/README.md b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/README.md deleted file mode 100644 index 0cfbe68723..0000000000 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/docs/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# FlowEditor Component - -The FlowEditor is a powerful visual flow builder component built on top of React Flow that allows users to create, connect, and manage nodes in a visual workflow. It provides a comprehensive form system with dynamic input handling, connection management, and advanced features. - -## Table of Contents - -- [Architecture Overview](#architecture-overview) -- [Store Management](#store-management) - -## Architecture Overview - -The FlowEditor follows a modular architecture with clear separation of concerns: - -``` -FlowEditor/ -├── Flow.tsx # Main component -├── nodes/ # Node-related components -│ ├── CustomNode.tsx # Main node component -│ ├── FormCreator.tsx # Dynamic form generator -│ ├── fields/ # Custom field components -│ ├── widgets/ # Custom input widgets -│ ├── templates/ # RJSF templates -│ └── helpers.ts # Utility functions -├── edges/ # Edge-related components -│ ├── CustomEdge.tsx # Custom edge component -│ ├── useCustomEdge.ts # Edge management hook -│ └── helpers.ts # Edge utilities -├── handlers/ # Connection handles -│ ├── NodeHandle.tsx # Connection handle component -│ └── helpers.ts # Handle utilities -├── components/ # Shared components -│ ├── ArrayEditor/ # Array editing components -│ └── ObjectEditor/ # Object editing components -└── processors/ # Data processors - └── input-schema-pre-processor.ts -``` - -## Store Management - -The FlowEditor uses Zustand for state management with two main stores: - -### NodeStore (`useNodeStore`) - -Manages all node-related state and operations. - -**Key Features:** - -- Node CRUD operations -- Advanced state management per node -- Form data persistence -- Node counter for unique IDs - -**Usage:** - -```typescript -import { useNodeStore } from "../stores/nodeStore"; - -// Get nodes -const nodes = useNodeStore(useShallow((state) => state.nodes)); - -// Add a new node -const addNode = useNodeStore((state) => state.addNode); - -// Update node data -const updateNodeData = useNodeStore((state) => state.updateNodeData); - -// Toggle advanced mode -const setShowAdvanced = useNodeStore((state) => state.setShowAdvanced); -``` - -**Store Methods:** - -- `setNodes(nodes)` - Replace all nodes -- `addNode(node)` - Add a single node -- `addBlock(blockInfo)` - Add node from block info -- `updateNodeData(nodeId, data)` - Update node data -- `onNodesChange(changes)` - Handle node changes from React Flow -- `setShowAdvanced(nodeId, show)` - Toggle advanced mode -- `incrementNodeCounter()` - Get next node ID - -### EdgeStore (`useEdgeStore`) - -Manages all connection-related state and operations. - -**Key Features:** - -- Connection CRUD operations -- Connection validation -- Backend link conversion -- Connection state queries - -**Usage:** - -```typescript -import { useEdgeStore } from "../stores/edgeStore"; - -// Get connections -const connections = useEdgeStore((state) => state.connections); - -// Add connection -const addConnection = useEdgeStore((state) => state.addConnection); - -// Check if input is connected -const isInputConnected = useEdgeStore((state) => state.isInputConnected); -``` - -**Store Methods:** - -- `setConnections(connections)` - Replace all connections -- `addConnection(conn)` - Add a new connection -- `removeConnection(edgeId)` - Remove connection by ID -- `upsertMany(conns)` - Bulk update connections -- `isInputConnected(nodeId, handle)` - Check input connection -- `isOutputConnected(nodeId, handle)` - Check output connection -- `getNodeConnections(nodeId)` - Get all connections for a node -- `getBackendLinks()` - Convert to backend format - -## Form Creator System - -The FormCreator is a dynamic form generator built on React JSON Schema Form (RJSF) that automatically creates forms based on JSON schemas. - -### How It Works - -1. **Schema Processing**: Input schemas are preprocessed to ensure all properties have types -2. **Widget Mapping**: Schema types are mapped to appropriate input widgets -3. **Field Rendering**: Custom fields handle complex data structures -4. **State Management**: Form data is automatically synced with the node store - -### Key Components - -#### FormCreator - -```typescript - -``` - -#### Custom Widgets - -- `TextInputWidget` - Text, number, password inputs -- `SelectWidget` - Dropdown and multi-select -- `SwitchWidget` - Boolean toggles -- `FileWidget` - File upload -- `DateInputWidget` - Date picker -- `TimeInputWidget` - Time picker -- `DateTimeInputWidget` - DateTime picker - -#### Custom Fields - -- `AnyOfField` - Union type handling -- `ObjectField` - Free-form object editing -- `CredentialsField` - API credential management - -#### Templates - -- `FieldTemplate` - Custom field wrapper with handles -- `ArrayFieldTemplate` - Array editing interface diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx index dc7f17e5d3..39b64a6ccd 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/StandardNodeBlock.tsx @@ -2,7 +2,7 @@ import { beautifyString, cn } from "@/lib/utils"; import { CustomNodeData } from "./CustomNode"; import { Text } from "@/components/atoms/Text/Text"; import { FormCreator } from "../FormCreator"; -import { preprocessInputSchema } from "../../processors/input-schema-pre-processor"; +import { preprocessInputSchema } from "@/components/renderers/input-renderer/utils/input-schema-pre-processor"; import { Switch } from "@/components/atoms/Switch/Switch"; import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore"; import { OutputHandler } from "../OutputHandler"; @@ -57,7 +57,7 @@ export const StandardNodeBlock = ({ {/* Input Handles */} -
+
); }, diff --git a/autogpt_platform/frontend/src/app/(platform)/build/stores/graphStore.ts b/autogpt_platform/frontend/src/app/(platform)/build/stores/graphStore.ts index 6a0de2c7ff..7fbd4f480a 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/stores/graphStore.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/stores/graphStore.ts @@ -3,9 +3,43 @@ import { create } from "zustand"; interface GraphStore { isGraphRunning: boolean; setIsGraphRunning: (isGraphRunning: boolean) => void; + + inputSchema: Record | null; + credentialsInputSchema: Record | null; + setGraphSchemas: ( + inputSchema: Record | null, + credentialsInputSchema: Record | null, + ) => void; + + hasInputs: () => boolean; + hasCredentials: () => boolean; + reset: () => void; } -export const useGraphStore = create((set) => ({ +export const useGraphStore = create((set, get) => ({ isGraphRunning: false, + inputSchema: null, + credentialsInputSchema: null, + setIsGraphRunning: (isGraphRunning: boolean) => set({ isGraphRunning }), + + setGraphSchemas: (inputSchema, credentialsInputSchema) => + set({ inputSchema, credentialsInputSchema }), + + hasInputs: () => { + const { inputSchema } = get(); + return Object.keys(inputSchema?.properties ?? {}).length > 0; + }, + + hasCredentials: () => { + const { credentialsInputSchema } = get(); + return Object.keys(credentialsInputSchema?.properties ?? {}).length > 0; + }, + + reset: () => + set({ + isGraphRunning: false, + inputSchema: null, + credentialsInputSchema: null, + }), })); diff --git a/autogpt_platform/frontend/src/components/renderers/input-renderer/FormRenderer.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/FormRenderer.tsx new file mode 100644 index 0000000000..082b48301f --- /dev/null +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/FormRenderer.tsx @@ -0,0 +1,51 @@ +import { BlockUIType } from "@/app/(platform)/build/components/types"; +import validator from "@rjsf/validator-ajv8"; +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"; + +type FormContextType = { + nodeId?: string; + uiType?: BlockUIType; + showHandles?: boolean; + size?: "small" | "medium" | "large"; +}; + +type FormRendererProps = { + jsonSchema: RJSFSchema; + handleChange: (formData: any) => void; + uiSchema: any; + initialValues: any; + formContext: FormContextType; +}; + +export const FormRenderer = ({ + jsonSchema, + handleChange, + uiSchema, + initialValues, + formContext, +}: FormRendererProps) => { + const preprocessedSchema = useMemo(() => { + return preprocessInputSchema(jsonSchema); + }, [jsonSchema]); + return ( +
+ +
+ ); +}; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/AnyOfField/AnyOfField.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/AnyOfField/AnyOfField.tsx similarity index 77% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/AnyOfField/AnyOfField.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/AnyOfField/AnyOfField.tsx index 3a7001272e..a7097e689b 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/AnyOfField/AnyOfField.tsx +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/AnyOfField/AnyOfField.tsx @@ -4,14 +4,17 @@ import { FieldProps, RJSFSchema } from "@rjsf/utils"; import { Text } from "@/components/atoms/Text/Text"; import { Switch } from "@/components/atoms/Switch/Switch"; import { Select } from "@/components/atoms/Select/Select"; -import { InputType, mapJsonSchemaTypeToInputType } from "../../helpers"; +import { + InputType, + mapJsonSchemaTypeToInputType, +} from "@/app/(platform)/build/components/FlowEditor/nodes/helpers"; import { InfoIcon } from "@phosphor-icons/react"; import { useAnyOfField } from "./useAnyOfField"; -import NodeHandle from "../../../handlers/NodeHandle"; +import NodeHandle from "@/app/(platform)/build/components/FlowEditor/handlers/NodeHandle"; import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore"; -import { generateHandleId } from "../../../handlers/helpers"; -import { getTypeDisplayInfo } from "../../helpers"; +import { generateHandleId } from "@/app/(platform)/build/components/FlowEditor/handlers/helpers"; +import { getTypeDisplayInfo } from "@/app/(platform)/build/components/FlowEditor/nodes/helpers"; import merge from "lodash/merge"; import { Tooltip, @@ -19,6 +22,7 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/atoms/Tooltip/BaseTooltip"; +import { cn } from "@/lib/utils"; type TypeOption = { type: string; @@ -46,9 +50,9 @@ export const AnyOfField = ({ const handleId = generateHandleId(idSchema.$id ?? ""); const updatedFormContexrt = { ...formContext, fromAnyOf: true }; - const { nodeId } = updatedFormContexrt; + const { nodeId, showHandles = true } = updatedFormContexrt; const { isInputConnected } = useEdgeStore(); - const isConnected = isInputConnected(nodeId, handleId); + const isConnected = showHandles ? isInputConnected(nodeId, handleId) : false; const { isNullableType, nonNull, @@ -124,15 +128,21 @@ export const AnyOfField = ({ const { displayType, colorClass } = getTypeDisplayInfo(nonNull); return ( -
+
-
- - +
+ {showHandles && ( + + )} + {name.charAt(0).toUpperCase() + name.slice(1)} @@ -147,16 +157,22 @@ export const AnyOfField = ({ /> )}
- {!isConnected && isEnabled && renderInput(nonNull)} +
{!isConnected && isEnabled && renderInput(nonNull)}
); } return ( -
-
- - +
+
+ {showHandles && ( + + )} + {name.charAt(0).toUpperCase() + name.slice(1)} {!isConnected && ( @@ -193,6 +209,7 @@ export const AnyOfField = ({ )}
+ {!isConnected && currentTypeOption && renderInput(currentTypeOption)}
); diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/AnyOfField/useAnyOfField.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/AnyOfField/useAnyOfField.tsx similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/AnyOfField/useAnyOfField.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/AnyOfField/useAnyOfField.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/CredentialField.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/CredentialField.tsx similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/CredentialField.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/CredentialField.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/SelectCredential.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/SelectCredential.tsx similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/SelectCredential.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/SelectCredential.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/helpers.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/helpers.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/helpers.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/helpers.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/APIKeyCredentialModal/APIKeyCredentialModal.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/APIKeyCredentialModal/APIKeyCredentialModal.tsx similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/APIKeyCredentialModal/APIKeyCredentialModal.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/APIKeyCredentialModal/APIKeyCredentialModal.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/APIKeyCredentialModal/useAPIKeyCredentialsModal.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/APIKeyCredentialModal/useAPIKeyCredentialsModal.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/APIKeyCredentialModal/useAPIKeyCredentialsModal.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/APIKeyCredentialModal/useAPIKeyCredentialsModal.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/OAuthCredentialModal/OAuthCredentialModal.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/OAuthCredentialModal/OAuthCredentialModal.tsx similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/OAuthCredentialModal/OAuthCredentialModal.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/OAuthCredentialModal/OAuthCredentialModal.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/OAuthCredentialModal/useOAuthCredentialModal.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/OAuthCredentialModal/useOAuthCredentialModal.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/OAuthCredentialModal/useOAuthCredentialModal.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/OAuthCredentialModal/useOAuthCredentialModal.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/PasswordCredentialModal/PasswordCredentialModal.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/PasswordCredentialModal/PasswordCredentialModal.tsx similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/PasswordCredentialModal/PasswordCredentialModal.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/PasswordCredentialModal/PasswordCredentialModal.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/PasswordCredentialModal/usePasswordCredentialModal.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/PasswordCredentialModal/usePasswordCredentialModal.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/models/PasswordCredentialModal/usePasswordCredentialModal.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/models/PasswordCredentialModal/usePasswordCredentialModal.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/useCredentialField.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/useCredentialField.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/CredentialField/useCredentialField.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/CredentialField/useCredentialField.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/ObjectField.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/ObjectField.tsx similarity index 84% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/ObjectField.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/ObjectField.tsx index ce6ae35331..8189116a4e 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/ObjectField.tsx +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/ObjectField.tsx @@ -1,8 +1,8 @@ import React from "react"; import { FieldProps } from "@rjsf/utils"; import { getDefaultRegistry } from "@rjsf/core"; -import { generateHandleId } from "../../handlers/helpers"; -import { ObjectEditor } from "../../components/ObjectEditor/ObjectEditor"; +import { generateHandleId } from "@/app/(platform)/build/components/FlowEditor/handlers/helpers"; +import { ObjectEditor } from "../widgets/ObjectEditorWidget/ObjectEditorWidget"; export const ObjectField = (props: FieldProps) => { const { diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/index.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/fields/index.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/fields/index.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/fields/index.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/templates/ArrayFieldTemplate.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/templates/ArrayFieldTemplate.tsx similarity index 83% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/templates/ArrayFieldTemplate.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/templates/ArrayFieldTemplate.tsx index 1b28767f25..e9f708de5a 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/templates/ArrayFieldTemplate.tsx +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/templates/ArrayFieldTemplate.tsx @@ -1,6 +1,6 @@ import React from "react"; import { ArrayFieldTemplateProps } from "@rjsf/utils"; -import { ArrayEditor } from "../../components/ArrayEditor/ArrayEditor"; +import { ArrayEditorWidget } from "../widgets/ArrayEditorWidget/ArrayEditorWidget"; function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { const { @@ -15,7 +15,7 @@ function ArrayFieldTemplate(props: ArrayFieldTemplateProps) { const { nodeId } = formContext; return ( - = ({ id: fieldId, @@ -35,7 +35,7 @@ const FieldTemplate: React.FC = ({ uiSchema, }) => { const { isInputConnected } = useEdgeStore(); - const { nodeId } = formContext; + const { nodeId, showHandles = true, size = "small" } = formContext; const showAdvanced = useNodeStore( (state) => state.nodeAdvancedStates[nodeId] ?? false, @@ -55,7 +55,7 @@ const FieldTemplate: React.FC = ({ handleId = arrayFieldHandleId; } - const isConnected = isInputConnected(nodeId, handleId); + const isConnected = showHandles ? isInputConnected(nodeId, handleId) : false; if (!showAdvanced && schema.advanced === true && !isConnected) { return null; @@ -78,11 +78,22 @@ const FieldTemplate: React.FC = ({ return
{children}
; } + // Size-based styling + + const shouldShowHandle = + showHandles && !suppressHandle && !fromAnyOf && !isCredential; + return ( -
+
{label && schema.type && ( )} - {(isAnyOf || !isConnected) &&
{children}
}{" "} + {(isAnyOf || !isConnected) && ( +
{children}
+ )}{" "}
); }; diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/templates/index.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/templates/index.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/templates/index.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/templates/index.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/processors/input-schema-pre-processor.ts b/autogpt_platform/frontend/src/components/renderers/input-renderer/utils/input-schema-pre-processor.ts similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/processors/input-schema-pre-processor.ts rename to autogpt_platform/frontend/src/components/renderers/input-renderer/utils/input-schema-pre-processor.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ArrayEditor/ArrayEditorContext.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/ArrayEditorWidget/ArrayEditorContext.tsx similarity index 100% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ArrayEditor/ArrayEditorContext.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/ArrayEditorWidget/ArrayEditorContext.tsx diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ArrayEditor/ArrayEditor.tsx b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/ArrayEditorWidget/ArrayEditorWidget.tsx similarity index 90% rename from autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ArrayEditor/ArrayEditor.tsx rename to autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/ArrayEditorWidget/ArrayEditorWidget.tsx index 59ed0ee73c..5e00a4be15 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/components/ArrayEditor/ArrayEditor.tsx +++ b/autogpt_platform/frontend/src/components/renderers/input-renderer/widgets/ArrayEditorWidget/ArrayEditorWidget.tsx @@ -1,9 +1,12 @@ import { ArrayFieldTemplateItemType, RJSFSchema } from "@rjsf/utils"; -import { generateHandleId, HandleIdType } from "../../handlers/helpers"; import { ArrayEditorContext } from "./ArrayEditorContext"; import { Button } from "@/components/atoms/Button/Button"; import { PlusIcon, XIcon } from "@phosphor-icons/react"; import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore"; +import { + generateHandleId, + HandleIdType, +} from "@/app/(platform)/build/components/FlowEditor/handlers/helpers"; export interface ArrayEditorProps { items?: ArrayFieldTemplateItemType[]; @@ -15,7 +18,7 @@ export interface ArrayEditorProps { id: string; } -export const ArrayEditor = ({ +export const ArrayEditorWidget = ({ items, nodeId, canAdd, @@ -29,7 +32,7 @@ export const ArrayEditor = ({ return (
-
+
{items?.map((element) => { const arrayFieldHandleId = generateHandleId( fieldId, @@ -59,7 +62,7 @@ export const ArrayEditor = ({