feat(blocks): Add support for mutually exclusive input fields (#8856)
- resolves part of #8731 ### Changes - Introduced `mutually_exclusive` parameter in `SchemaField` to manage input exclusivity. - Implemented logic in `NodeGenericInputField` to disable inputs based on mutual exclusivity. - Updated related components to support the new `disabled` state for inputs. - Enhanced `BlockIOSubSchemaMeta` to include `mutually_exclusive` property. > Currently, I’m disabling the input from the same group (I haven’t added any frontend validation to prevent users from bypassing it). https://github.com/user-attachments/assets/71fb9fe4-943b-4724-8acb-6aed2232ed6b --------- Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
This commit is contained in:
parent
f588b69484
commit
2fe6eb1df1
|
@ -90,6 +90,7 @@ class BlockSchema(BaseModel):
|
|||
}
|
||||
elif isinstance(obj, list):
|
||||
return [ref_to_dict(item) for item in obj]
|
||||
|
||||
return obj
|
||||
|
||||
cls.cached_jsonschema = cast(dict[str, Any], ref_to_dict(model))
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
BlockIONumberSubSchema,
|
||||
BlockIOBooleanSubSchema,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import React, { FC, useCallback, useEffect, useState } from "react";
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Switch } from "./ui/switch";
|
||||
import {
|
||||
|
@ -326,13 +326,26 @@ export const NodeGenericInputField: FC<{
|
|||
}
|
||||
}
|
||||
|
||||
if ("oneOf" in propSchema) {
|
||||
// At the time of writing, this isn't used in the backend -> no impl. needed
|
||||
console.error(
|
||||
`Unsupported 'oneOf' in schema for '${propKey}'!`,
|
||||
propSchema,
|
||||
if (
|
||||
"oneOf" in propSchema &&
|
||||
propSchema.oneOf &&
|
||||
"discriminator" in propSchema &&
|
||||
propSchema.discriminator
|
||||
) {
|
||||
return (
|
||||
<NodeOneOfDiscriminatorField
|
||||
nodeId={nodeId}
|
||||
propKey={propKey}
|
||||
propSchema={propSchema}
|
||||
currentValue={currentValue}
|
||||
errors={errors}
|
||||
connections={connections}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
/>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!("type" in propSchema)) {
|
||||
|
@ -451,6 +464,132 @@ export const NodeGenericInputField: FC<{
|
|||
}
|
||||
};
|
||||
|
||||
const NodeOneOfDiscriminatorField: FC<{
|
||||
nodeId: string;
|
||||
propKey: string;
|
||||
propSchema: any;
|
||||
currentValue?: any;
|
||||
errors: { [key: string]: string | undefined };
|
||||
connections: any;
|
||||
handleInputChange: (key: string, value: any) => void;
|
||||
handleInputClick: (key: string) => void;
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({
|
||||
nodeId,
|
||||
propKey,
|
||||
propSchema,
|
||||
currentValue,
|
||||
errors,
|
||||
connections,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
const discriminator = propSchema.discriminator;
|
||||
|
||||
const discriminatorProperty = discriminator.propertyName;
|
||||
|
||||
const variantOptions = useMemo(() => {
|
||||
const oneOfVariants = propSchema.oneOf || [];
|
||||
|
||||
return oneOfVariants
|
||||
.map((variant: any) => {
|
||||
const variantDiscValue =
|
||||
variant.properties?.[discriminatorProperty]?.const;
|
||||
|
||||
return {
|
||||
value: variantDiscValue,
|
||||
schema: variant,
|
||||
};
|
||||
})
|
||||
.filter((v: any) => v.value != null);
|
||||
}, [discriminatorProperty, propSchema.oneOf]);
|
||||
|
||||
const currentVariant = variantOptions.find(
|
||||
(opt: any) => currentValue?.[discriminatorProperty] === opt.value,
|
||||
);
|
||||
|
||||
const [chosenType, setChosenType] = useState<string>(
|
||||
currentVariant?.value || "",
|
||||
);
|
||||
|
||||
const handleVariantChange = (newType: string) => {
|
||||
setChosenType(newType);
|
||||
const chosenVariant = variantOptions.find(
|
||||
(opt: any) => opt.value === newType,
|
||||
);
|
||||
if (chosenVariant) {
|
||||
const initialValue = {
|
||||
[discriminatorProperty]: newType,
|
||||
};
|
||||
handleInputChange(propKey, initialValue);
|
||||
}
|
||||
};
|
||||
|
||||
const chosenVariantSchema = variantOptions.find(
|
||||
(opt: any) => opt.value === chosenType,
|
||||
)?.schema;
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col space-y-2", className)}>
|
||||
<Select value={chosenType || ""} onValueChange={handleVariantChange}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Select a type..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{variantOptions.map((opt: any) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
{beautifyString(opt.value)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{chosenVariantSchema && (
|
||||
<div className={cn(className, "w-full flex-col")}>
|
||||
{Object.entries(chosenVariantSchema.properties).map(
|
||||
([someKey, childSchema]) => {
|
||||
if (someKey === "discriminator") {
|
||||
return null;
|
||||
}
|
||||
const childKey = propKey ? `${propKey}.${someKey}` : someKey;
|
||||
return (
|
||||
<div
|
||||
key={childKey}
|
||||
className="flex w-full flex-row justify-between space-y-2"
|
||||
>
|
||||
<span className="mr-2 mt-3 dark:text-gray-300">
|
||||
{(childSchema as BlockIOSubSchema).title ||
|
||||
beautifyString(someKey)}
|
||||
</span>
|
||||
<NodeGenericInputField
|
||||
nodeId={nodeId}
|
||||
key={propKey}
|
||||
propKey={childKey}
|
||||
propSchema={childSchema as BlockIOSubSchema}
|
||||
currentValue={
|
||||
currentValue ? currentValue[someKey] : undefined
|
||||
}
|
||||
errors={errors}
|
||||
connections={connections}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
displayName={
|
||||
chosenVariantSchema.title || beautifyString(someKey)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeCredentialsInput: FC<{
|
||||
selfKey: string;
|
||||
value: any;
|
||||
|
@ -849,7 +988,7 @@ const NodeStringInput: FC<{
|
|||
placeholder={
|
||||
schema?.placeholder || `Enter ${beautifyString(displayName)}`
|
||||
}
|
||||
className="pr-8 read-only:cursor-pointer read-only:text-gray-500 dark:text-white"
|
||||
className="pr-8 read-only:cursor-pointer read-only:text-gray-500"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
Loading…
Reference in New Issue