From b1d2f3bd954c4d563d90815cf16764e1ba0c0e29 Mon Sep 17 00:00:00 2001 From: Lluis Agusti Date: Mon, 23 Feb 2026 16:26:54 +0800 Subject: [PATCH 1/3] fix(frontend/copilot): always-visible credentials, inputs, and login prompts in copilot tools Extract blocking UI (credentials picker, agent details/inputs, login prompts) out of collapsible accordions in RunAgent and RunBlock tools so users can't accidentally hide actionable requirements. - Credentials and input forms render standalone, always visible - Add titled boxes ("Block credentials", "Agent credentials", "Block inputs") - Unified "Proceed" button enabled only when all requirements are satisfied - Primary variant for "Add credential" button when no credentials exist - Update styleguide with mock credential providers for both scenarios - Extract need_login and agent_details outside accordion in RunAgent Co-Authored-By: Claude Opus 4.6 --- .../(platform)/copilot/styleguide/page.tsx | 250 ++++++++++++++---- .../copilot/tools/CreateAgent/CreateAgent.tsx | 26 +- .../copilot/tools/EditAgent/EditAgent.tsx | 28 +- .../copilot/tools/RunAgent/RunAgent.tsx | 55 ++-- .../SetupRequirementsCard.tsx | 57 ++-- .../copilot/tools/RunBlock/RunBlock.tsx | 19 +- .../SetupRequirementsCard.tsx | 209 ++++++--------- .../CredentialsFlatView.tsx | 2 +- .../renderers/InputRenderer/FormRenderer.tsx | 12 +- 9 files changed, 393 insertions(+), 265 deletions(-) diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/styleguide/page.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/styleguide/page.tsx index 8a35f939ca25..fc3941b409db 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/styleguide/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/styleguide/page.tsx @@ -11,6 +11,11 @@ import { MessageResponse, } from "@/components/ai-elements/message"; import { Text } from "@/components/atoms/Text/Text"; +import { + CredentialsProvidersContext, + type CredentialsProviderData, + type CredentialsProvidersContextType, +} from "@/providers/agent-credentials/credentials-provider"; import { CopilotChatActionsProvider } from "../components/CopilotChatActionsProvider/CopilotChatActionsProvider"; import { CreateAgentTool } from "../tools/CreateAgent/CreateAgent"; import { EditAgentTool } from "../tools/EditAgent/EditAgent"; @@ -97,6 +102,65 @@ function uid() { return `sg-${++_id}`; } +// --------------------------------------------------------------------------- +// Mock credential providers for setup-requirements demos +// --------------------------------------------------------------------------- + +const noop = () => Promise.reject(new Error("Styleguide mock")); + +function makeMockProvider( + provider: string, + providerName: string, + savedCredentials: CredentialsProviderData["savedCredentials"] = [], +): CredentialsProviderData { + return { + provider, + providerName, + savedCredentials, + isSystemProvider: false, + oAuthCallback: noop as CredentialsProviderData["oAuthCallback"], + mcpOAuthCallback: noop as CredentialsProviderData["mcpOAuthCallback"], + createAPIKeyCredentials: + noop as CredentialsProviderData["createAPIKeyCredentials"], + createUserPasswordCredentials: + noop as CredentialsProviderData["createUserPasswordCredentials"], + createHostScopedCredentials: + noop as CredentialsProviderData["createHostScopedCredentials"], + deleteCredentials: noop as CredentialsProviderData["deleteCredentials"], + }; +} + +/** + * Provider context where the user already has saved credentials + * so the credential picker shows a selection list. + */ +const MOCK_PROVIDERS_WITH_CREDENTIALS: CredentialsProvidersContextType = { + google: makeMockProvider("google", "Google", [ + { + id: "cred-google-1", + provider: "google", + type: "oauth2", + title: "work@company.com", + scopes: ["email", "calendar"], + }, + { + id: "cred-google-2", + provider: "google", + type: "oauth2", + title: "personal@gmail.com", + scopes: ["email", "calendar"], + }, + ]), +}; + +/** + * Provider context where the user has NO saved credentials, + * so the credential picker shows an "add new" flow. + */ +const MOCK_PROVIDERS_WITHOUT_CREDENTIALS: CredentialsProvidersContextType = { + openweathermap: makeMockProvider("openweathermap", "OpenWeatherMap"), +}; + // --------------------------------------------------------------------------- // Page // --------------------------------------------------------------------------- @@ -554,45 +618,80 @@ export default function StyleguidePage() { /> - - + + + + + + + + + }} + /> + @@ -849,34 +948,71 @@ export default function StyleguidePage() { /> - - + + + }} + /> + + + + + + + diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx index 7741647f7051..e59f7c2c823d 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx @@ -16,7 +16,6 @@ import { ContentCardDescription, ContentCodeBlock, ContentGrid, - ContentHint, ContentMessage, } from "../../components/ToolAccordion/AccordionContent"; import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion"; @@ -93,9 +92,7 @@ function getAccordionMeta(output: CreateAgentToolOutput) { ) { return { icon, - title: - "Creating agent, this may take a few minutes. Play while you wait.", - expanded: true, + title: output.message || "Agent creation started", }; } return { @@ -169,16 +166,21 @@ export function CreateAgentTool({ part }: Props) { /> + {isStreaming && ( + } + title="Creating agent, this may take a few minutes. Play while you wait." + expanded + > + + + + + )} + {hasExpandableContent && output && ( - {isOperating && ( - - - - This could take a few minutes — play while you wait! - - - )} + {isOperating && {output.message}} {isAgentSavedOutput(output) && (
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/EditAgent/EditAgent.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/EditAgent/EditAgent.tsx index 40bccd6c6154..ceb6b679bee0 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/EditAgent/EditAgent.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/EditAgent/EditAgent.tsx @@ -4,12 +4,10 @@ import { WarningDiamondIcon } from "@phosphor-icons/react"; import type { ToolUIPart } from "ai"; import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions"; import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation"; -import { OrbitLoader } from "../../components/OrbitLoader/OrbitLoader"; import { ContentCardDescription, ContentCodeBlock, ContentGrid, - ContentHint, ContentLink, ContentMessage, } from "../../components/ToolAccordion/AccordionContent"; @@ -81,9 +79,8 @@ function getAccordionMeta(output: EditAgentToolOutput): { isOperationInProgressOutput(output) ) { return { - icon: , - title: "Editing agent, this may take a few minutes. Play while you wait.", - expanded: true, + icon, + title: output.message || "Agent editing started", }; } return { @@ -148,16 +145,21 @@ export function EditAgentTool({ part }: Props) { />
+ {isStreaming && ( + } + title="Editing agent, this may take a few minutes. Play while you wait." + expanded + > + + + + + )} + {hasExpandableContent && output && ( - {isOperating && ( - - - - This could take a few minutes — play while you wait! - - - )} + {isOperating && {output.message}} {isAgentSavedOutput(output) && ( diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/RunAgent.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/RunAgent.tsx index 835c04d5a0ad..7e2b7d23462f 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/RunAgent.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/RunAgent.tsx @@ -47,14 +47,25 @@ export function RunAgentTool({ part }: Props) { const isError = part.state === "output-error" || (!!output && isRunAgentErrorOutput(output)); + const isOutputAvailable = part.state === "output-available" && !!output; + + const setupRequirementsOutput = + isOutputAvailable && isRunAgentSetupRequirementsOutput(output) + ? output + : null; + + const agentDetailsOutput = + isOutputAvailable && isRunAgentAgentDetailsOutput(output) ? output : null; + + const needLoginOutput = + isOutputAvailable && isRunAgentNeedLoginOutput(output) ? output : null; + const hasExpandableContent = - part.state === "output-available" && - !!output && - (isRunAgentExecutionStartedOutput(output) || - isRunAgentAgentDetailsOutput(output) || - isRunAgentSetupRequirementsOutput(output) || - isRunAgentNeedLoginOutput(output) || - isRunAgentErrorOutput(output)); + isOutputAvailable && + !setupRequirementsOutput && + !agentDetailsOutput && + !needLoginOutput && + (isRunAgentExecutionStartedOutput(output) || isRunAgentErrorOutput(output)); return (
@@ -81,24 +92,30 @@ export function RunAgentTool({ part }: Props) { )} + {setupRequirementsOutput && ( +
+ +
+ )} + + {agentDetailsOutput && ( +
+ +
+ )} + + {needLoginOutput && ( +
+ {needLoginOutput.message} +
+ )} + {hasExpandableContent && output && ( {isRunAgentExecutionStartedOutput(output) && ( )} - {isRunAgentAgentDetailsOutput(output) && ( - - )} - - {isRunAgentSetupRequirementsOutput(output) && ( - - )} - - {isRunAgentNeedLoginOutput(output) && ( - {output.message} - )} - {isRunAgentErrorOutput(output) && } )} diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/components/SetupRequirementsCard/SetupRequirementsCard.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/components/SetupRequirementsCard/SetupRequirementsCard.tsx index c6d116e62ab4..f0c1c8985d59 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/components/SetupRequirementsCard/SetupRequirementsCard.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunAgent/components/SetupRequirementsCard/SetupRequirementsCard.tsx @@ -1,10 +1,11 @@ "use client"; -import { useState } from "react"; -import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView"; +import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse"; import { Button } from "@/components/atoms/Button/Button"; +import { Text } from "@/components/atoms/Text/Text"; +import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView"; import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; -import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse"; +import { useState } from "react"; import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions"; import { ContentBadge, @@ -38,10 +39,13 @@ export function SetupRequirementsCard({ output }: Props) { setInputCredentials((prev) => ({ ...prev, [key]: value })); } - const isAllComplete = - credentialFields.length > 0 && + const needsCredentials = credentialFields.length > 0; + const isAllCredentialsComplete = + needsCredentials && [...requiredCredentials].every((key) => !!inputCredentials[key]); + const canProceed = !hasSent && isAllCredentialsComplete; + function handleProceed() { setHasSent(true); onSend( @@ -53,25 +57,20 @@ export function SetupRequirementsCard({ output }: Props) {
{output.message} - {credentialFields.length > 0 && ( + {needsCredentials && (
- - {isAllComplete && !hasSent && ( - - )} + + Agent credentials + +
+ +
)} @@ -100,6 +99,18 @@ export function SetupRequirementsCard({ output }: Props) {
)} + + {needsCredentials && ( + + )} ); } diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/RunBlock.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/RunBlock.tsx index 6e2cbe90d74d..d1c0ce6b0a96 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/RunBlock.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/RunBlock.tsx @@ -39,12 +39,19 @@ export function RunBlockTool({ part }: Props) { const isError = part.state === "output-error" || (!!output && isRunBlockErrorOutput(output)); + const setupRequirementsOutput = + part.state === "output-available" && + output && + isRunBlockSetupRequirementsOutput(output) + ? output + : null; + const hasExpandableContent = part.state === "output-available" && !!output && + !setupRequirementsOutput && (isRunBlockBlockOutput(output) || isRunBlockDetailsOutput(output) || - isRunBlockSetupRequirementsOutput(output) || isRunBlockErrorOutput(output)); return ( @@ -57,6 +64,12 @@ export function RunBlockTool({ part }: Props) { /> + {setupRequirementsOutput && ( +
+ +
+ )} + {hasExpandableContent && output && ( {isRunBlockBlockOutput(output) && } @@ -65,10 +78,6 @@ export function RunBlockTool({ part }: Props) { )} - {isRunBlockSetupRequirementsOutput(output) && ( - - )} - {isRunBlockErrorOutput(output) && } )} diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/components/SetupRequirementsCard/SetupRequirementsCard.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/components/SetupRequirementsCard/SetupRequirementsCard.tsx index e98fea28504a..4d02de84e441 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/components/SetupRequirementsCard/SetupRequirementsCard.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/RunBlock/components/SetupRequirementsCard/SetupRequirementsCard.tsx @@ -6,15 +6,9 @@ import { Text } from "@/components/atoms/Text/Text"; import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView"; import { FormRenderer } from "@/components/renderers/InputRenderer/FormRenderer"; import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; -import { AnimatePresence, motion } from "framer-motion"; import { useState } from "react"; import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions"; -import { - ContentBadge, - ContentCardDescription, - ContentCardTitle, - ContentMessage, -} from "../../../../components/ToolAccordion/AccordionContent"; +import { ContentMessage } from "../../../../components/ToolAccordion/AccordionContent"; import { buildExpectedInputsSchema, coerceCredentialFields, @@ -31,10 +25,8 @@ export function SetupRequirementsCard({ output }: Props) { const [inputCredentials, setInputCredentials] = useState< Record >({}); - const [hasSentCredentials, setHasSentCredentials] = useState(false); - - const [showInputForm, setShowInputForm] = useState(false); const [inputValues, setInputValues] = useState>({}); + const [hasSent, setHasSent] = useState(false); const { credentialFields, requiredCredentials } = coerceCredentialFields( output.setup_info.user_readiness?.missing_credentials, @@ -50,27 +42,45 @@ export function SetupRequirementsCard({ output }: Props) { setInputCredentials((prev) => ({ ...prev, [key]: value })); } + const needsCredentials = credentialFields.length > 0; const isAllCredentialsComplete = - credentialFields.length > 0 && + needsCredentials && [...requiredCredentials].every((key) => !!inputCredentials[key]); - function handleProceedCredentials() { - setHasSentCredentials(true); - onSend( - "I've configured the required credentials. Please re-run the block now.", - ); - } + const needsInputs = inputSchema !== null; + const requiredInputNames = expectedInputs + .filter((i) => i.required) + .map((i) => i.name); + const isAllInputsComplete = + needsInputs && + requiredInputNames.every((name) => { + const v = inputValues[name]; + return v !== undefined && v !== null && v !== ""; + }); + + const canRun = + !hasSent && + (!needsCredentials || isAllCredentialsComplete) && + (!needsInputs || isAllInputsComplete); + + function handleRun() { + setHasSent(true); + + if (needsInputs) { + const nonEmpty = Object.fromEntries( + Object.entries(inputValues).filter( + ([, v]) => v !== undefined && v !== null && v !== "", + ), + ); + onSend( + `I've configured the required credentials. Run the block with these inputs: ${JSON.stringify(nonEmpty, null, 2)}`, + ); + } else { + onSend( + "I've configured the required credentials. Please re-run the block now.", + ); + } - function handleRunWithInputs() { - const nonEmpty = Object.fromEntries( - Object.entries(inputValues).filter( - ([, v]) => v !== undefined && v !== null && v !== "", - ), - ); - onSend( - `Run the block with these inputs: ${JSON.stringify(nonEmpty, null, 2)}`, - ); - setShowInputForm(false); setInputValues({}); } @@ -78,119 +88,54 @@ export function SetupRequirementsCard({ output }: Props) {
{output.message} - {credentialFields.length > 0 && ( + {needsCredentials && (
- - {isAllCredentialsComplete && !hasSentCredentials && ( - - )} + + Block credentials + +
+ +
)} {inputSchema && ( -
- +
+ + Block inputs + + setInputValues(v.formData ?? {})} + uiSchema={{ + "ui:submitButtonOptions": { norender: true }, + }} + initialValues={inputValues} + formContext={{ + showHandles: false, + size: "small", + }} + />
)} - - {showInputForm && inputSchema && ( - -
- Block inputs - setInputValues(v.formData ?? {})} - uiSchema={{ - "ui:submitButtonOptions": { norender: true }, - }} - initialValues={inputValues} - formContext={{ - showHandles: false, - size: "small", - }} - /> -
- - -
-
-
- )} -
- - {expectedInputs.length > 0 && !inputSchema && ( -
- - Expected inputs - -
- {expectedInputs.map((input) => ( -
-
- - {input.title} - - - {input.required ? "Required" : "Optional"} - -
- - {input.name} • {input.type} - {input.description ? ` \u2022 ${input.description}` : ""} - -
- ))} -
-
+ {(needsCredentials || needsInputs) && ( + )}
); diff --git a/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsFlatView/CredentialsFlatView.tsx b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsFlatView/CredentialsFlatView.tsx index 4d220a535975..9457ae573241 100644 --- a/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsFlatView/CredentialsFlatView.tsx +++ b/autogpt_platform/frontend/src/components/contextual/CredentialsInput/components/CredentialsFlatView/CredentialsFlatView.tsx @@ -119,7 +119,7 @@ export function CredentialsFlatView({ ) : ( !readOnly && (
); -}; +} From 4778cd6b5875691eadac61fb9ae9ed82e90f84e0 Mon Sep 17 00:00:00 2001 From: Lluis Agusti Date: Mon, 23 Feb 2026 17:24:29 +0800 Subject: [PATCH 3/3] fix(frontend/copilot): guard against undefined output.message in CreateAgent Co-Authored-By: Claude Opus 4.6 --- .../app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx index 2a1d9c268070..bcf1f2df6506 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/tools/CreateAgent/CreateAgent.tsx @@ -180,7 +180,9 @@ export function CreateAgentTool({ part }: Props) { {hasExpandableContent && output && ( - {isOperating && {output.message}} + {isOperating && output.message && ( + {output.message} + )} {isAgentSavedOutput(output) && (