mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend+backend): prefill block inputs and hide advanced in CoPilot setup card
Backend: - get_inputs_from_schema() now accepts input_data to populate each field's value with what CoPilot already provided, and includes the advanced flag from the schema so the frontend can hide non-essential fields. Frontend: - SetupRequirementsCard prefills form inputs from backend-provided values instead of showing empty forms - Advanced fields hidden by default with "Show advanced fields" toggle (matching builder behaviour) - siblingInputs built from both input values and discriminator_values so the host pattern modal can extract the host from the URL - extractInitialValues() populates form state from prefilled values
This commit is contained in:
@@ -48,27 +48,41 @@ logger = logging.getLogger(__name__)
|
||||
def get_inputs_from_schema(
|
||||
input_schema: dict[str, Any],
|
||||
exclude_fields: set[str] | None = None,
|
||||
input_data: dict[str, Any] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Extract input field info from JSON schema."""
|
||||
"""Extract input field info from JSON schema.
|
||||
|
||||
When *input_data* is provided, each field's ``value`` key is populated
|
||||
with the value the CoPilot already supplied — so the frontend can
|
||||
prefill the form instead of showing empty inputs. Fields marked
|
||||
``advanced`` in the schema are flagged so the frontend can hide them
|
||||
by default (matching the builder behaviour).
|
||||
"""
|
||||
if not isinstance(input_schema, dict):
|
||||
return []
|
||||
|
||||
exclude = exclude_fields or set()
|
||||
properties = input_schema.get("properties", {})
|
||||
required = set(input_schema.get("required", []))
|
||||
provided = input_data or {}
|
||||
|
||||
return [
|
||||
{
|
||||
results: list[dict[str, Any]] = []
|
||||
for name, schema in properties.items():
|
||||
if name in exclude:
|
||||
continue
|
||||
entry: dict[str, Any] = {
|
||||
"name": name,
|
||||
"title": schema.get("title", name),
|
||||
"type": schema.get("type", "string"),
|
||||
"description": schema.get("description", ""),
|
||||
"required": name in required,
|
||||
"default": schema.get("default"),
|
||||
"advanced": schema.get("advanced", False),
|
||||
}
|
||||
for name, schema in properties.items()
|
||||
if name not in exclude
|
||||
]
|
||||
if name in provided:
|
||||
entry["value"] = provided[name]
|
||||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
||||
async def execute_block(
|
||||
@@ -446,7 +460,9 @@ async def prepare_block_for_execution(
|
||||
requirements={
|
||||
"credentials": missing_creds_list,
|
||||
"inputs": get_inputs_from_schema(
|
||||
input_schema, exclude_fields=credentials_fields
|
||||
input_schema,
|
||||
exclude_fields=credentials_fields,
|
||||
input_data=input_data,
|
||||
),
|
||||
"execution_modes": ["immediate"],
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import { ContentMessage } from "../../../../components/ToolAccordion/AccordionContent";
|
||||
import {
|
||||
@@ -14,18 +14,13 @@ import {
|
||||
buildSiblingInputsFromCredentials,
|
||||
coerceCredentialFields,
|
||||
coerceExpectedInputs,
|
||||
extractInitialValues,
|
||||
} from "./helpers";
|
||||
|
||||
interface Props {
|
||||
output: SetupRequirementsResponse;
|
||||
/** Override the message sent to the chat when the user clicks Proceed after connecting credentials.
|
||||
* Defaults to "Please re-run this step now." */
|
||||
retryInstruction?: string;
|
||||
/** Override the label shown above the credentials section.
|
||||
* Defaults to "Credentials". */
|
||||
credentialsLabel?: string;
|
||||
/** Called after Proceed is clicked so the parent can persist the dismissed state
|
||||
* across remounts (avoids re-enabling the Proceed button on remount). */
|
||||
onComplete?: () => void;
|
||||
}
|
||||
|
||||
@@ -40,22 +35,38 @@ export function SetupRequirementsCard({
|
||||
const [inputCredentials, setInputCredentials] = useState<
|
||||
Record<string, CredentialsMetaInput | undefined>
|
||||
>({});
|
||||
const [inputValues, setInputValues] = useState<Record<string, unknown>>({});
|
||||
const [hasSent, setHasSent] = useState(false);
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
|
||||
const { credentialFields, requiredCredentials } = coerceCredentialFields(
|
||||
output.setup_info.user_readiness?.missing_credentials,
|
||||
);
|
||||
|
||||
const siblingInputs = buildSiblingInputsFromCredentials(
|
||||
output.setup_info.user_readiness?.missing_credentials,
|
||||
);
|
||||
|
||||
const expectedInputs = coerceExpectedInputs(
|
||||
(output.setup_info.requirements as Record<string, unknown>)?.inputs,
|
||||
);
|
||||
|
||||
const inputSchema = buildExpectedInputsSchema(expectedInputs);
|
||||
const initialValues = useMemo(
|
||||
() => extractInitialValues(expectedInputs),
|
||||
[expectedInputs],
|
||||
);
|
||||
|
||||
const [inputValues, setInputValues] =
|
||||
useState<Record<string, unknown>>(initialValues);
|
||||
|
||||
const hasAdvancedFields = expectedInputs.some((i) => i.advanced);
|
||||
const inputSchema = buildExpectedInputsSchema(expectedInputs, showAdvanced);
|
||||
|
||||
// Build siblingInputs for credential modal host prefill.
|
||||
// Prefer discriminator_values from the credential response, but also
|
||||
// include values from input_data (e.g. url field) so the host pattern
|
||||
// can be extracted even when discriminator_values is empty.
|
||||
const siblingInputs = useMemo(() => {
|
||||
const fromCreds = buildSiblingInputsFromCredentials(
|
||||
output.setup_info.user_readiness?.missing_credentials,
|
||||
);
|
||||
return { ...inputValues, ...fromCreds };
|
||||
}, [output.setup_info.user_readiness?.missing_credentials, inputValues]);
|
||||
|
||||
function handleCredentialChange(key: string, value?: CredentialsMetaInput) {
|
||||
setInputCredentials((prev) => ({ ...prev, [key]: value }));
|
||||
@@ -68,10 +79,10 @@ export function SetupRequirementsCard({
|
||||
|
||||
const needsInputs = inputSchema !== null;
|
||||
const requiredInputNames = expectedInputs
|
||||
.filter((i) => i.required)
|
||||
.filter((i) => i.required && !i.advanced)
|
||||
.map((i) => i.name);
|
||||
const isAllInputsComplete =
|
||||
needsInputs &&
|
||||
!needsInputs ||
|
||||
requiredInputNames.every((name) => {
|
||||
const v = inputValues[name];
|
||||
return v !== undefined && v !== null && v !== "";
|
||||
@@ -82,8 +93,7 @@ export function SetupRequirementsCard({
|
||||
}
|
||||
|
||||
const canRun =
|
||||
(!needsCredentials || isAllCredentialsComplete) &&
|
||||
(!needsInputs || isAllInputsComplete);
|
||||
(!needsCredentials || isAllCredentialsComplete) && isAllInputsComplete;
|
||||
|
||||
function handleRun() {
|
||||
setHasSent(true);
|
||||
@@ -138,7 +148,9 @@ export function SetupRequirementsCard({
|
||||
<FormRenderer
|
||||
jsonSchema={inputSchema}
|
||||
className="mb-3 mt-3"
|
||||
handleChange={(v) => setInputValues(v.formData ?? {})}
|
||||
handleChange={(v) =>
|
||||
setInputValues((prev) => ({ ...prev, ...(v.formData ?? {}) }))
|
||||
}
|
||||
uiSchema={{
|
||||
"ui:submitButtonOptions": { norender: true },
|
||||
}}
|
||||
@@ -148,6 +160,15 @@ export function SetupRequirementsCard({
|
||||
size: "small",
|
||||
}}
|
||||
/>
|
||||
{hasAdvancedFields && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-muted-foreground underline"
|
||||
onClick={() => setShowAdvanced((v) => !v)}
|
||||
>
|
||||
{showAdvanced ? "Hide advanced fields" : "Show advanced fields"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -110,21 +110,19 @@ export function buildSiblingInputsFromCredentials(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function coerceExpectedInputs(rawInputs: unknown): Array<{
|
||||
interface ExpectedInput {
|
||||
name: string;
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
}> {
|
||||
advanced: boolean;
|
||||
value?: unknown;
|
||||
}
|
||||
|
||||
export function coerceExpectedInputs(rawInputs: unknown): ExpectedInput[] {
|
||||
if (!Array.isArray(rawInputs)) return [];
|
||||
const results: Array<{
|
||||
name: string;
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
}> = [];
|
||||
const results: ExpectedInput[] = [];
|
||||
|
||||
rawInputs.forEach((value, index) => {
|
||||
if (!value || typeof value !== "object") return;
|
||||
@@ -144,15 +142,13 @@ export function coerceExpectedInputs(rawInputs: unknown): Array<{
|
||||
? input.description.trim()
|
||||
: undefined;
|
||||
const required = Boolean(input.required);
|
||||
const advanced = Boolean(input.advanced);
|
||||
|
||||
const item: {
|
||||
name: string;
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
} = { name, title, type, required };
|
||||
const item: ExpectedInput = { name, title, type, required, advanced };
|
||||
if (description) item.description = description;
|
||||
if (input.value !== undefined && input.value !== null) {
|
||||
item.value = input.value;
|
||||
}
|
||||
results.push(item);
|
||||
});
|
||||
|
||||
@@ -162,17 +158,20 @@ export function coerceExpectedInputs(rawInputs: unknown): Array<{
|
||||
/**
|
||||
* Build an RJSF schema from expected inputs so they can be rendered
|
||||
* as a dynamic form via FormRenderer.
|
||||
*
|
||||
* When ``showAdvanced`` is false (default), fields marked ``advanced``
|
||||
* are excluded — matching the builder behaviour where advanced fields
|
||||
* are hidden behind a toggle.
|
||||
*/
|
||||
export function buildExpectedInputsSchema(
|
||||
expectedInputs: Array<{
|
||||
name: string;
|
||||
title: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
}>,
|
||||
expectedInputs: ExpectedInput[],
|
||||
showAdvanced = false,
|
||||
): RJSFSchema | null {
|
||||
if (expectedInputs.length === 0) return null;
|
||||
const visible = showAdvanced
|
||||
? expectedInputs
|
||||
: expectedInputs.filter((i) => !i.advanced);
|
||||
|
||||
if (visible.length === 0) return null;
|
||||
|
||||
const TYPE_MAP: Record<string, string> = {
|
||||
string: "string",
|
||||
@@ -189,12 +188,14 @@ export function buildExpectedInputsSchema(
|
||||
const properties: Record<string, Record<string, unknown>> = {};
|
||||
const required: string[] = [];
|
||||
|
||||
for (const input of expectedInputs) {
|
||||
properties[input.name] = {
|
||||
for (const input of visible) {
|
||||
const prop: Record<string, unknown> = {
|
||||
type: TYPE_MAP[input.type.toLowerCase()] ?? "string",
|
||||
title: input.title,
|
||||
...(input.description ? { description: input.description } : {}),
|
||||
};
|
||||
if (input.description) prop.description = input.description;
|
||||
if (input.value !== undefined) prop.default = input.value;
|
||||
properties[input.name] = prop;
|
||||
if (input.required) required.push(input.name);
|
||||
}
|
||||
|
||||
@@ -204,3 +205,19 @@ export function buildExpectedInputsSchema(
|
||||
...(required.length > 0 ? { required } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract initial form values from expected inputs that have a
|
||||
* prefilled ``value`` from the backend.
|
||||
*/
|
||||
export function extractInitialValues(
|
||||
expectedInputs: ExpectedInput[],
|
||||
): Record<string, unknown> {
|
||||
const values: Record<string, unknown> = {};
|
||||
for (const input of expectedInputs) {
|
||||
if (input.value !== undefined && input.value !== null) {
|
||||
values[input.name] = input.value;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user