mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
feat(frontend): add "None" option for optional credentials in run dialog
Allow users to skip optional credentials by selecting "None" in the credential dropdown. This properly surfaces the backend optional credentials flag in the run dialog UI. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,7 @@ export function CredentialsInput({
|
||||
siblingInputs,
|
||||
onLoaded,
|
||||
readOnly,
|
||||
isOptional,
|
||||
});
|
||||
|
||||
if (!isLoaded(hookData)) {
|
||||
@@ -112,14 +113,16 @@ export function CredentialsInput({
|
||||
|
||||
{hasCredentialsToShow ? (
|
||||
<>
|
||||
{credentialsToShow.length > 1 && !readOnly ? (
|
||||
{(credentialsToShow.length > 1 || isOptional) && !readOnly ? (
|
||||
<CredentialsSelect
|
||||
credentials={credentialsToShow}
|
||||
provider={provider}
|
||||
displayName={displayName}
|
||||
selectedCredentials={selectedCredential}
|
||||
onSelectCredential={handleCredentialSelect}
|
||||
onClearCredential={() => onSelectCredential(undefined)}
|
||||
readOnly={readOnly}
|
||||
allowNone={isOptional}
|
||||
/>
|
||||
) : (
|
||||
<div className="mb-4 space-y-2">
|
||||
|
||||
@@ -23,7 +23,9 @@ interface Props {
|
||||
displayName: string;
|
||||
selectedCredentials?: CredentialsMetaInput;
|
||||
onSelectCredential: (credentialId: string) => void;
|
||||
onClearCredential?: () => void;
|
||||
readOnly?: boolean;
|
||||
allowNone?: boolean;
|
||||
}
|
||||
|
||||
export function CredentialsSelect({
|
||||
@@ -32,20 +34,30 @@ export function CredentialsSelect({
|
||||
displayName,
|
||||
selectedCredentials,
|
||||
onSelectCredential,
|
||||
onClearCredential,
|
||||
readOnly = false,
|
||||
allowNone = true,
|
||||
}: Props) {
|
||||
// Auto-select first credential if none is selected
|
||||
// Auto-select first credential if none is selected (only if allowNone is false)
|
||||
useEffect(() => {
|
||||
if (!selectedCredentials && credentials.length > 0) {
|
||||
if (!allowNone && !selectedCredentials && credentials.length > 0) {
|
||||
onSelectCredential(credentials[0].id);
|
||||
}
|
||||
}, [selectedCredentials, credentials, onSelectCredential]);
|
||||
}, [allowNone, selectedCredentials, credentials, onSelectCredential]);
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
if (value === "__none__") {
|
||||
onClearCredential?.();
|
||||
} else {
|
||||
onSelectCredential(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-4 w-full">
|
||||
<Select
|
||||
value={selectedCredentials?.id || ""}
|
||||
onValueChange={(value) => onSelectCredential(value)}
|
||||
value={selectedCredentials?.id || (allowNone ? "__none__" : "")}
|
||||
onValueChange={handleValueChange}
|
||||
>
|
||||
<SelectTrigger className="h-auto min-h-12 w-full rounded-medium border-zinc-200 p-0 pr-4 shadow-none">
|
||||
{selectedCredentials ? (
|
||||
@@ -70,6 +82,15 @@ export function CredentialsSelect({
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{allowNone && (
|
||||
<SelectItem key="__none__" value="__none__">
|
||||
<div className="flex items-center gap-2">
|
||||
<Text variant="body" className="tracking-tight text-gray-500">
|
||||
None (skip this credential)
|
||||
</Text>
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
{credentials.map((credential) => (
|
||||
<SelectItem key={credential.id} value={credential.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -22,6 +22,7 @@ type Params = {
|
||||
siblingInputs?: Record<string, any>;
|
||||
onLoaded?: (loaded: boolean) => void;
|
||||
readOnly?: boolean;
|
||||
isOptional?: boolean;
|
||||
};
|
||||
|
||||
export function useCredentialsInput({
|
||||
@@ -31,6 +32,7 @@ export function useCredentialsInput({
|
||||
siblingInputs,
|
||||
onLoaded,
|
||||
readOnly = false,
|
||||
isOptional = false,
|
||||
}: Params) {
|
||||
const [isAPICredentialsModalOpen, setAPICredentialsModalOpen] =
|
||||
useState(false);
|
||||
@@ -99,13 +101,20 @@ export function useCredentialsInput({
|
||||
: null;
|
||||
}, [credentials]);
|
||||
|
||||
// Auto-select the one available credential
|
||||
// Auto-select the one available credential (only if not optional)
|
||||
useEffect(() => {
|
||||
if (readOnly) return;
|
||||
if (isOptional) return; // Don't auto-select when credential is optional
|
||||
if (singleCredential && !selectedCredential) {
|
||||
onSelectCredential(singleCredential);
|
||||
}
|
||||
}, [singleCredential, selectedCredential, onSelectCredential, readOnly]);
|
||||
}, [
|
||||
singleCredential,
|
||||
selectedCredential,
|
||||
onSelectCredential,
|
||||
readOnly,
|
||||
isOptional,
|
||||
]);
|
||||
|
||||
if (
|
||||
!credentials ||
|
||||
|
||||
@@ -87,6 +87,7 @@ export const CredentialsField = (props: FieldProps) => {
|
||||
siblingInputs={hardcodedValues}
|
||||
showTitle={false}
|
||||
readOnly={formContext?.readOnly}
|
||||
isOptional={!isRequired}
|
||||
/>
|
||||
|
||||
{/* Optional credentials toggle - only show in builder canvas, not run dialogs */}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import React from "react";
|
||||
import { FieldProps } from "@rjsf/utils";
|
||||
import { useCredentialField } from "./useCredentialField";
|
||||
import { SelectCredential } from "./SelectCredential";
|
||||
import { Skeleton } from "@/components/__legacy__/ui/skeleton";
|
||||
import { BlockIOCredentialsSubSchema } from "@/lib/autogpt-server-api";
|
||||
import { APIKeyCredentialsModal } from "./models/APIKeyCredentialModal/APIKeyCredentialModal";
|
||||
import { OAuthCredentialModal } from "./models/OAuthCredentialModal/OAuthCredentialModal";
|
||||
import { PasswordCredentialsModal } from "./models/PasswordCredentialModal/PasswordCredentialModal";
|
||||
import { HostScopedCredentialsModal } from "./models/HostScopedCredentialsModal/HostScopedCredentialsModal";
|
||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||
import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
|
||||
export const CredentialsField = (props: FieldProps) => {
|
||||
const {
|
||||
formData = {},
|
||||
onChange,
|
||||
required: _required,
|
||||
schema,
|
||||
formContext,
|
||||
} = props;
|
||||
|
||||
const nodeId = formContext.nodeId;
|
||||
// Only show the optional toggle when editing blocks in the builder canvas
|
||||
const showOptionalToggle = formContext.showOptionalToggle !== false && nodeId;
|
||||
|
||||
const { credentialsOptional, setCredentialsOptional } = useNodeStore(
|
||||
useShallow((state) => ({
|
||||
credentialsOptional: state.getCredentialsOptional(nodeId),
|
||||
setCredentialsOptional: state.setCredentialsOptional,
|
||||
})),
|
||||
);
|
||||
|
||||
const {
|
||||
credentials,
|
||||
isCredentialListLoading,
|
||||
supportsApiKey,
|
||||
supportsOAuth2,
|
||||
supportsUserPassword,
|
||||
supportsHostScoped,
|
||||
credentialsExists,
|
||||
credentialProvider,
|
||||
setCredential,
|
||||
discriminatorValue,
|
||||
} = useCredentialField({
|
||||
credentialSchema: schema as BlockIOCredentialsSubSchema,
|
||||
formData,
|
||||
nodeId,
|
||||
onChange,
|
||||
disableAutoSelect: credentialsOptional,
|
||||
});
|
||||
|
||||
if (isCredentialListLoading) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-8 w-full rounded-xlarge" />
|
||||
<Skeleton className="h-8 w-[30%] rounded-xlarge" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!credentialProvider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{credentialsExists && (
|
||||
<SelectCredential
|
||||
credentials={credentials}
|
||||
value={formData.id || ""}
|
||||
onChange={setCredential}
|
||||
disabled={false}
|
||||
label="Credential"
|
||||
placeholder={
|
||||
credentialsOptional
|
||||
? "Select credential (optional)"
|
||||
: "Select credential"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{supportsApiKey && (
|
||||
<APIKeyCredentialsModal
|
||||
schema={schema as BlockIOCredentialsSubSchema}
|
||||
provider={credentialProvider}
|
||||
/>
|
||||
)}
|
||||
{supportsOAuth2 && (
|
||||
<OAuthCredentialModal provider={credentialProvider} />
|
||||
)}
|
||||
{supportsUserPassword && (
|
||||
<PasswordCredentialsModal provider={credentialProvider} />
|
||||
)}
|
||||
{supportsHostScoped && discriminatorValue && (
|
||||
<HostScopedCredentialsModal
|
||||
schema={schema as BlockIOCredentialsSubSchema}
|
||||
provider={credentialProvider}
|
||||
discriminatorValue={discriminatorValue}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Optional credentials toggle - only show in builder canvas, not run dialogs */}
|
||||
{showOptionalToggle && (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<Switch
|
||||
id={`credentials-optional-${nodeId}`}
|
||||
checked={credentialsOptional}
|
||||
onCheckedChange={(checked) =>
|
||||
setCredentialsOptional(nodeId, checked)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`credentials-optional-${nodeId}`}
|
||||
className="cursor-pointer text-xs text-gray-500"
|
||||
>
|
||||
Optional - skip block if not configured
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user