mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend): ask for credentials in onboarding agent run (#11146)
## Changes 🏗️ <img width="800" height="852" alt="Screenshot_2025-10-13_at_19 20 47" src="https://github.com/user-attachments/assets/2fc150b9-1053-4e25-9018-24bcc2d93b43" /> <img width="800" height="669" alt="Screenshot 2025-10-13 at 19 23 41" src="https://github.com/user-attachments/assets/9078b04e-0f65-42f3-ac4a-d2f3daa91215" /> - Onboarding “Run” step now renders required credentials (e.g., Google OAuth) and includes them in execution. - Run button remains disabled until required inputs and credentials are provided. - Logic extracted and strongly typed; removed any usage. ## Checklist 📋 ### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [ ] I have tested my changes according to the test plan ( _once merged in dev..._ ) - [ ] Select an onboarding agent that requires Google OAuth: - [ ] Credentials selector appears. - [ ] After selecting/signing in, “Run agent” enables. - [ ]Run succeeds and navigates to the next step. ### For configuration changes: None
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import type { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import type {
|
||||
BlockIOCredentialsSubSchema,
|
||||
CredentialsMetaInput,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import type { InputValues } from "./types";
|
||||
|
||||
export function computeInitialAgentInputs(
|
||||
agent: GraphMeta | null,
|
||||
existingInputs?: InputValues | null,
|
||||
): InputValues {
|
||||
const properties = agent?.input_schema?.properties || {};
|
||||
const result: InputValues = {};
|
||||
|
||||
Object.entries(properties).forEach(([key, subSchema]) => {
|
||||
if (
|
||||
existingInputs &&
|
||||
key in existingInputs &&
|
||||
existingInputs[key] != null
|
||||
) {
|
||||
result[key] = existingInputs[key];
|
||||
return;
|
||||
}
|
||||
// GraphIOSubSchema.default is typed as string, but server may return other primitives
|
||||
const def = (subSchema as unknown as { default?: string | number }).default;
|
||||
result[key] = def ?? "";
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getAgentCredentialsInputFields(agent: GraphMeta | null) {
|
||||
const hasNoInputs =
|
||||
!agent?.credentials_input_schema ||
|
||||
typeof agent.credentials_input_schema !== "object" ||
|
||||
!("properties" in agent.credentials_input_schema) ||
|
||||
!agent.credentials_input_schema.properties;
|
||||
|
||||
if (hasNoInputs) return {};
|
||||
|
||||
return agent.credentials_input_schema.properties;
|
||||
}
|
||||
|
||||
export function areAllCredentialsSet(
|
||||
fields: Record<string, BlockIOCredentialsSubSchema>,
|
||||
inputs: Record<string, CredentialsMetaInput | undefined>,
|
||||
) {
|
||||
const required = Object.keys(fields || {});
|
||||
return required.every((k) => Boolean(inputs[k]));
|
||||
}
|
||||
|
||||
type IsRunDisabledParams = {
|
||||
agent: GraphMeta | null;
|
||||
isRunning: boolean;
|
||||
agentInputs: InputValues | null | undefined;
|
||||
credentialsRequired: boolean;
|
||||
credentialsSatisfied: boolean;
|
||||
};
|
||||
|
||||
export function isRunDisabled({
|
||||
agent,
|
||||
isRunning,
|
||||
agentInputs,
|
||||
credentialsRequired,
|
||||
credentialsSatisfied,
|
||||
}: IsRunDisabledParams) {
|
||||
const hasEmptyInput = Object.values(agentInputs || {}).some(
|
||||
(value) => String(value).trim() === "",
|
||||
);
|
||||
|
||||
if (hasEmptyInput) return true;
|
||||
if (!agent) return true;
|
||||
if (isRunning) return true;
|
||||
if (credentialsRequired && !credentialsSatisfied) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getSchemaDefaultCredentials(
|
||||
schema: BlockIOCredentialsSubSchema,
|
||||
): CredentialsMetaInput | undefined {
|
||||
return schema.default as CredentialsMetaInput | undefined;
|
||||
}
|
||||
|
||||
export function sanitizeCredentials(
|
||||
map: Record<string, CredentialsMetaInput | undefined>,
|
||||
): Record<string, CredentialsMetaInput> {
|
||||
const sanitized: Record<string, CredentialsMetaInput> = {};
|
||||
for (const [key, value] of Object.entries(map)) {
|
||||
if (value) sanitized[key] = value;
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
@@ -13,13 +13,24 @@ import {
|
||||
} from "@/components/__legacy__/ui/card";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { GraphMeta, StoreAgentDetails } from "@/lib/autogpt-server-api";
|
||||
import type { InputValues } from "./types";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Play } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RunAgentInputs } from "@/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentInputs/RunAgentInputs";
|
||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||
import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/AgentRunsView/components/CredentialsInputs/CredentialsInputs";
|
||||
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
|
||||
import {
|
||||
areAllCredentialsSet,
|
||||
computeInitialAgentInputs,
|
||||
getAgentCredentialsInputFields,
|
||||
isRunDisabled,
|
||||
getSchemaDefaultCredentials,
|
||||
sanitizeCredentials,
|
||||
} from "./helpers";
|
||||
|
||||
export default function Page() {
|
||||
const { state, updateState, setStep } = useOnboarding(
|
||||
@@ -30,13 +41,16 @@ export default function Page() {
|
||||
const [agent, setAgent] = useState<GraphMeta | null>(null);
|
||||
const [storeAgent, setStoreAgent] = useState<StoreAgentDetails | null>(null);
|
||||
const [runningAgent, setRunningAgent] = useState(false);
|
||||
const [inputCredentials, setInputCredentials] = useState<
|
||||
Record<string, CredentialsMetaInput | undefined>
|
||||
>({});
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const api = useBackendAPI();
|
||||
|
||||
useEffect(() => {
|
||||
setStep(5);
|
||||
}, [setStep]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!state?.selectedStoreListingVersionId) {
|
||||
@@ -49,40 +63,36 @@ export default function Page() {
|
||||
});
|
||||
api
|
||||
.getGraphMetaByStoreListingVersionID(state.selectedStoreListingVersionId)
|
||||
.then((agent) => {
|
||||
setAgent(agent);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const update: { [key: string]: any } = {};
|
||||
// Set default values from schema
|
||||
Object.entries(agent.input_schema.properties).forEach(
|
||||
([key, value]) => {
|
||||
// Skip if already set
|
||||
if (state.agentInput && state.agentInput[key]) {
|
||||
update[key] = state.agentInput[key];
|
||||
return;
|
||||
}
|
||||
update[key] = value.type !== "null" ? value.default || "" : "";
|
||||
},
|
||||
.then((meta) => {
|
||||
setAgent(meta);
|
||||
const update = computeInitialAgentInputs(
|
||||
meta,
|
||||
(state.agentInput as unknown as InputValues) || null,
|
||||
);
|
||||
updateState({
|
||||
agentInput: update,
|
||||
});
|
||||
updateState({ agentInput: update });
|
||||
});
|
||||
}, [api, setAgent, updateState, state?.selectedStoreListingVersionId]);
|
||||
|
||||
const setAgentInput = useCallback(
|
||||
(key: string, value: string) => {
|
||||
updateState({
|
||||
agentInput: {
|
||||
...state?.agentInput,
|
||||
[key]: value,
|
||||
},
|
||||
});
|
||||
},
|
||||
[state?.agentInput, updateState],
|
||||
const agentCredentialsInputFields = getAgentCredentialsInputFields(agent);
|
||||
|
||||
const credentialsRequired =
|
||||
Object.keys(agentCredentialsInputFields || {}).length > 0;
|
||||
|
||||
const allCredentialsAreSet = areAllCredentialsSet(
|
||||
agentCredentialsInputFields,
|
||||
inputCredentials,
|
||||
);
|
||||
|
||||
const runAgent = useCallback(async () => {
|
||||
function setAgentInput(key: string, value: string) {
|
||||
updateState({
|
||||
agentInput: {
|
||||
...state?.agentInput,
|
||||
[key]: value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function runAgent() {
|
||||
if (!agent) {
|
||||
return;
|
||||
}
|
||||
@@ -95,6 +105,7 @@ export default function Page() {
|
||||
libraryAgent.graph_id,
|
||||
libraryAgent.graph_version,
|
||||
state?.agentInput || {},
|
||||
sanitizeCredentials(inputCredentials),
|
||||
);
|
||||
updateState({
|
||||
onboardingAgentExecutionId: runID,
|
||||
@@ -111,7 +122,7 @@ export default function Page() {
|
||||
});
|
||||
setRunningAgent(false);
|
||||
}
|
||||
}, [api, agent, router, state?.agentInput, storeAgent, updateState, toast]);
|
||||
}
|
||||
|
||||
const runYourAgent = (
|
||||
<div className="ml-[104px] w-[481px] pl-5">
|
||||
@@ -221,6 +232,30 @@ export default function Page() {
|
||||
<span className="mt-4 text-base font-normal leading-normal text-zinc-600">
|
||||
When you're done, click <b>Run Agent</b>.
|
||||
</span>
|
||||
{Object.entries(agentCredentialsInputFields || {}).map(
|
||||
([key, inputSubSchema]) => (
|
||||
<div key={key} className="mt-4">
|
||||
<CredentialsInput
|
||||
schema={inputSubSchema}
|
||||
selectedCredentials={
|
||||
inputCredentials[key] ??
|
||||
getSchemaDefaultCredentials(inputSubSchema)
|
||||
}
|
||||
onSelectCredentials={(value) =>
|
||||
setInputCredentials((prev) => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}))
|
||||
}
|
||||
siblingInputs={
|
||||
(state?.agentInput || undefined) as
|
||||
| Record<string, any>
|
||||
| undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
<Card className="agpt-box mt-4">
|
||||
<CardHeader>
|
||||
<CardTitle className="font-poppins text-lg">Input</CardTitle>
|
||||
@@ -250,13 +285,14 @@ export default function Page() {
|
||||
variant="violet"
|
||||
className="mt-8 w-[136px]"
|
||||
loading={runningAgent}
|
||||
disabled={
|
||||
Object.values(state?.agentInput || {}).some(
|
||||
(value) => String(value).trim() === "",
|
||||
) ||
|
||||
!agent ||
|
||||
runningAgent
|
||||
}
|
||||
disabled={isRunDisabled({
|
||||
agent,
|
||||
isRunning: runningAgent,
|
||||
agentInputs:
|
||||
(state?.agentInput as unknown as InputValues) || null,
|
||||
credentialsRequired,
|
||||
credentialsSatisfied: allCredentialsAreSet,
|
||||
})}
|
||||
onClick={runAgent}
|
||||
icon={<Play className="mr-2" size={18} />}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export type InputPrimitive = string | number;
|
||||
export type InputValues = Record<string, InputPrimitive>;
|
||||
Reference in New Issue
Block a user