mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(frontend): add support for user password credentials in new FlowEditor (#11122)
- depends on https://github.com/Significant-Gravitas/AutoGPT/pull/11107 In this PR, I’ve added a way to add a username and password as credentials on new builder. https://github.com/user-attachments/assets/b896ea62-6a6d-487c-99a3-727cef4ad9a5 ### Changes 🏗️ - Introduced PasswordCredentialsModal to handle user password credentials. - Updated useCredentialField to support user password type. - Refactored APIKeyCredentialsModal to remove unnecessary onSuccess prop. - Enhanced the CredentialsField component to conditionally render the new password modal based on supported credential types. ### Checklist 📋 #### For code changes: - [x] Ability to add username and password correctly. - [x] The username and password are visible in the credentials list after adding it.
This commit is contained in:
@@ -6,6 +6,7 @@ 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";
|
||||
|
||||
export const CredentialsField = (props: FieldProps) => {
|
||||
const { formData = {}, onChange, required: _required, schema } = props;
|
||||
@@ -14,6 +15,7 @@ export const CredentialsField = (props: FieldProps) => {
|
||||
isCredentialListLoading,
|
||||
supportsApiKey,
|
||||
supportsOAuth2,
|
||||
supportsUserPassword,
|
||||
credentialsExists,
|
||||
} = useCredentialField({
|
||||
credentialSchema: schema as BlockIOCredentialsSubSchema,
|
||||
@@ -22,6 +24,7 @@ export const CredentialsField = (props: FieldProps) => {
|
||||
const setField = (key: string, value: any) =>
|
||||
onChange({ ...formData, [key]: value });
|
||||
|
||||
// This is to set the latest credential as the default one [currently, latest means last one in the list of credentials]
|
||||
useEffect(() => {
|
||||
if (!isCredentialListLoading && credentials.length > 0 && !formData.id) {
|
||||
const latestCredential = credentials[credentials.length - 1];
|
||||
@@ -29,10 +32,6 @@ export const CredentialsField = (props: FieldProps) => {
|
||||
}
|
||||
}, [isCredentialListLoading, credentials, formData.id]);
|
||||
|
||||
const handleCredentialCreated = (credentialId: string) => {
|
||||
setField("id", credentialId);
|
||||
};
|
||||
|
||||
if (isCredentialListLoading) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -59,12 +58,17 @@ export const CredentialsField = (props: FieldProps) => {
|
||||
{supportsApiKey && (
|
||||
<APIKeyCredentialsModal
|
||||
schema={schema as BlockIOCredentialsSubSchema}
|
||||
onSuccess={handleCredentialCreated}
|
||||
/>
|
||||
)}
|
||||
{supportsOAuth2 && (
|
||||
<OAuthCredentialModal provider={schema.credentials_provider[0]} />
|
||||
)}
|
||||
{supportsUserPassword && (
|
||||
<PasswordCredentialsModal
|
||||
schema={schema as BlockIOCredentialsSubSchema}
|
||||
provider={schema.credentials_provider[0]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,11 +14,9 @@ import { Text } from "@/components/atoms/Text/Text";
|
||||
|
||||
type Props = {
|
||||
schema: BlockIOCredentialsSubSchema;
|
||||
|
||||
onSuccess: (credentialId: string) => void;
|
||||
};
|
||||
|
||||
export function APIKeyCredentialsModal({ schema, onSuccess }: Props) {
|
||||
export function APIKeyCredentialsModal({ schema }: Props) {
|
||||
const {
|
||||
form,
|
||||
isLoading,
|
||||
@@ -27,7 +25,7 @@ export function APIKeyCredentialsModal({ schema, onSuccess }: Props) {
|
||||
provider,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
} = useAPIKeyCredentialsModal({ schema, onSuccess });
|
||||
} = useAPIKeyCredentialsModal({ schema });
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { APIKeyCredentials } from "@/app/api/__generated__/models/aPIKeyCredentials";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { PostV1CreateCredentials201 } from "@/app/api/__generated__/models/postV1CreateCredentials201";
|
||||
import { useState } from "react";
|
||||
|
||||
export type APIKeyFormValues = {
|
||||
@@ -20,12 +19,10 @@ export type APIKeyFormValues = {
|
||||
|
||||
type useAPIKeyCredentialsModalType = {
|
||||
schema: BlockIOCredentialsSubSchema;
|
||||
onSuccess: (credentialId: string) => void;
|
||||
};
|
||||
|
||||
export function useAPIKeyCredentialsModal({
|
||||
schema,
|
||||
onSuccess,
|
||||
}: useAPIKeyCredentialsModalType): {
|
||||
form: UseFormReturn<APIKeyFormValues>;
|
||||
isLoading: boolean;
|
||||
@@ -42,9 +39,7 @@ export function useAPIKeyCredentialsModal({
|
||||
const { mutateAsync: createCredentials, isPending: isCreatingCredentials } =
|
||||
usePostV1CreateCredentials({
|
||||
mutation: {
|
||||
onSuccess: async (response) => {
|
||||
const credentialId = (response.data as PostV1CreateCredentials201)
|
||||
?.id;
|
||||
onSuccess: async () => {
|
||||
form.reset();
|
||||
setIsOpen(false);
|
||||
toast({
|
||||
@@ -56,10 +51,6 @@ export function useAPIKeyCredentialsModal({
|
||||
await queryClient.refetchQueries({
|
||||
queryKey: getGetV1ListCredentialsQueryKey(),
|
||||
});
|
||||
|
||||
if (credentialId && onSuccess) {
|
||||
onSuccess(credentialId);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
import { Form, FormField } from "@/components/__legacy__/ui/form";
|
||||
import { BlockIOCredentialsSubSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { usePasswordCredentialModal } from "./usePasswordCredentialModal";
|
||||
import { toDisplayName } from "../../helpers";
|
||||
import { UserIcon } from "@phosphor-icons/react";
|
||||
|
||||
type Props = {
|
||||
schema: BlockIOCredentialsSubSchema;
|
||||
provider: string;
|
||||
};
|
||||
|
||||
export function PasswordCredentialsModal({ schema, provider }: Props) {
|
||||
const {
|
||||
credentials,
|
||||
isCredentialListLoading,
|
||||
form,
|
||||
onSubmit,
|
||||
open,
|
||||
setOpen,
|
||||
} = usePasswordCredentialModal({ schema });
|
||||
if (!credentials || isCredentialListLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={`Add new username & password for ${toDisplayName(provider)}`}
|
||||
controlled={{
|
||||
isOpen: open,
|
||||
set: (isOpen) => {
|
||||
if (!isOpen) setOpen(false);
|
||||
},
|
||||
}}
|
||||
onClose={() => setOpen(false)}
|
||||
styling={{
|
||||
maxWidth: "25rem",
|
||||
}}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-2 pt-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="username"
|
||||
label="Username"
|
||||
type="text"
|
||||
placeholder="Enter username..."
|
||||
size="small"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
placeholder="Enter password..."
|
||||
size="small"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="title"
|
||||
label="Name"
|
||||
type="text"
|
||||
placeholder="Enter a name for this user login..."
|
||||
size="small"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" size="small" className="min-w-68">
|
||||
Save & use this user login
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-fit"
|
||||
size="small"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<UserIcon className="size-4" />
|
||||
Add username & password
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useState } from "react";
|
||||
import { useCredentialField } from "../../useCredentialField";
|
||||
import z from "zod";
|
||||
import { BlockIOCredentialsSubSchema } from "@/lib/autogpt-server-api";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
getGetV1ListCredentialsQueryKey,
|
||||
usePostV1CreateCredentials,
|
||||
} from "@/app/api/__generated__/endpoints/integrations/integrations";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
type usePasswordCredentialModalType = {
|
||||
schema: BlockIOCredentialsSubSchema;
|
||||
};
|
||||
|
||||
export const usePasswordCredentialModal = ({
|
||||
schema,
|
||||
}: usePasswordCredentialModalType) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
const { credentials, isCredentialListLoading } = useCredentialField({
|
||||
credentialSchema: schema,
|
||||
});
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().min(1, "Username is required"),
|
||||
password: z.string().min(1, "Password is required"),
|
||||
title: z.string().min(1, "Name is required"),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
title: "",
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: createCredentials } = usePostV1CreateCredentials({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
form.reset();
|
||||
setOpen(false);
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Credentials created successfully",
|
||||
variant: "default",
|
||||
});
|
||||
|
||||
await queryClient.refetchQueries({
|
||||
queryKey: getGetV1ListCredentialsQueryKey(),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
createCredentials({
|
||||
provider: schema.credentials_provider[0],
|
||||
data: {
|
||||
provider: schema.credentials_provider[0],
|
||||
type: "user_password",
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
title: values.title,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
credentials,
|
||||
isCredentialListLoading,
|
||||
onSubmit,
|
||||
open,
|
||||
setOpen,
|
||||
};
|
||||
};
|
||||
@@ -23,6 +23,8 @@ export const useCredentialField = ({
|
||||
|
||||
const supportsApiKey = credentialSchema.credentials_types.includes("api_key");
|
||||
const supportsOAuth2 = credentialSchema.credentials_types.includes("oauth2");
|
||||
const supportsUserPassword =
|
||||
credentialSchema.credentials_types.includes("user_password");
|
||||
|
||||
const credentialProviders = credentialSchema.credentials_provider;
|
||||
const { credentials: filteredCredentials, exists: credentialsExists } =
|
||||
@@ -33,6 +35,7 @@ export const useCredentialField = ({
|
||||
isCredentialListLoading,
|
||||
supportsApiKey,
|
||||
supportsOAuth2,
|
||||
supportsUserPassword,
|
||||
credentialsExists,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user