Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/beeai-sdk-py/examples/request_form_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
FormRender,
MultiSelectField,
OptionItem,
SingleSelectField,
TextField,
)
from beeai_sdk.server import Server
Expand Down Expand Up @@ -45,6 +46,15 @@ async def request_form_agent(
TextField(id="text_field", label="Text Field", col_span=1),
DateField(id="date_field", label="Date Field", col_span=1),
FileField(id="file_field", label="File Field", accept=["*/*"], col_span=2),
SingleSelectField(
id="singleselect_field",
label="Single-Select Field",
options=[
OptionItem(id="option1", label="Option 1"),
OptionItem(id="option2", label="Option 2"),
],
col_span=2,
),
MultiSelectField(
id="multiselect_field",
label="Multi-Select Field",
Expand Down
30 changes: 28 additions & 2 deletions apps/beeai-sdk-py/src/beeai_sdk/a2a/extensions/ui/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ class OptionItem(BaseModel):
label: str


class SingleSelectField(BaseField):
type: Literal["singleselect"] = "singleselect"
options: list[OptionItem]
default_value: str | None = None

@model_validator(mode="after")
def default_value_validator(self):
if self.default_value:
valid_values = {opt.id for opt in self.options}
if self.default_value not in valid_values:
raise ValueError(f"Invalid default_value: {self.default_value}. Must be one of {valid_values}")
return self


class MultiSelectField(BaseField):
type: Literal["multiselect"] = "multiselect"
options: list[OptionItem]
Expand All @@ -73,7 +87,7 @@ class CheckboxField(BaseField):
default_value: bool = False


FormField = TextField | DateField | FileField | MultiSelectField | CheckboxField
FormField = TextField | DateField | FileField | SingleSelectField | MultiSelectField | CheckboxField


class FormRender(BaseModel):
Expand Down Expand Up @@ -106,6 +120,11 @@ class FileFieldValue(BaseModel):
value: list[FileInfo] | None = None


class SingleSelectFieldValue(BaseModel):
type: Literal["singleselect"] = "singleselect"
value: str | None = None


class MultiSelectFieldValue(BaseModel):
type: Literal["multiselect"] = "multiselect"
value: list[str] | None = None
Expand All @@ -116,7 +135,14 @@ class CheckboxFieldValue(BaseModel):
value: bool | None = None


FormFieldValue = TextFieldValue | DateFieldValue | FileFieldValue | MultiSelectFieldValue | CheckboxFieldValue
FormFieldValue = (
TextFieldValue
| DateFieldValue
| FileFieldValue
| SingleSelectFieldValue
| MultiSelectFieldValue
| CheckboxFieldValue
)


class FormResponse(BaseModel):
Expand Down
29 changes: 28 additions & 1 deletion apps/beeai-sdk-ts/src/client/a2a/extensions/ui/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ const fileFieldValue = z.object({
.nullish(),
});

const singleSelectField = baseField.extend({
type: z.literal('singleselect'),
options: z
.array(
z.object({
id: z.string().nonempty(),
label: z.string().nonempty(),
}),
)
.nonempty(),
default_value: z.string().nullish(),
});

const singleSelectFieldValue = z.object({
type: singleSelectField.shape.type,
value: z.string().nullish(),
});

const multiSelectField = baseField.extend({
type: z.literal('multiselect'),
options: z
Expand Down Expand Up @@ -86,7 +104,14 @@ const checkboxFieldValue = z.object({
value: z.boolean().nullish(),
});

const fieldSchema = z.discriminatedUnion('type', [textField, dateField, fileField, multiSelectField, checkboxField]);
const fieldSchema = z.discriminatedUnion('type', [
textField,
dateField,
fileField,
singleSelectField,
multiSelectField,
checkboxField,
]);

const renderSchema = z.object({
id: z.string().nonempty(),
Expand All @@ -105,6 +130,7 @@ const responseSchema = z.object({
textFieldValue,
dateFieldValue,
fileFieldValue,
singleSelectFieldValue,
multiSelectFieldValue,
checkboxFieldValue,
]),
Expand All @@ -114,6 +140,7 @@ const responseSchema = z.object({
export type TextField = z.infer<typeof textField>;
export type DateField = z.infer<typeof dateField>;
export type FileField = z.infer<typeof fileField>;
export type SingleSelectField = z.infer<typeof singleSelectField>;
export type MultiSelectField = z.infer<typeof multiSelectField>;
export type CheckboxField = z.infer<typeof checkboxField>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export function ImportAgentsModal({ onRequestClose, ...modalProps }: ModalProps)
{actionField.value === 'update_provider' && providersToUpdate && (
<Select
id={`${id}:provider`}
size="lg"
labelText="Select agent to update"
{...register('providerId', { required: true, disabled: isPending })}
>
Expand Down
2 changes: 2 additions & 0 deletions apps/beeai-ui/src/modules/form/components/FormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DateField } from './fields/DateField';
import { FileField } from './fields/FileField';
import { FileFieldValue } from './fields/FileFieldValue';
import { MultiSelectField } from './fields/MultiSelectField';
import { SingleSelectField } from './fields/SingleSelectField';
import { TextField } from './fields/TextField';
import classes from './FormField.module.scss';

Expand All @@ -31,6 +32,7 @@ export function FormField({ field, value }: Props) {
.with({ type: 'file', value: P.nonNullable }, ({ value }) => <FileFieldValue field={field} value={value} />)
.otherwise(() => <FileField field={field} />),
)
.with({ type: 'singleselect' }, (field) => <SingleSelectField field={field} />)
.with({ type: 'multiselect' }, (field) => <MultiSelectField field={field} />)
.with({ type: 'checkbox' }, (field) => <CheckboxField field={field} />)
.exhaustive();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { Select, SelectItem } from '@carbon/react';
import type { SingleSelectField } from 'beeai-sdk';
import { useFormContext } from 'react-hook-form';

import type { ValuesOfField } from '#modules/form/types.ts';

interface Props {
field: SingleSelectField;
}

export function SingleSelectField({ field }: Props) {
const { id, label, required, options } = field;

const { register } = useFormContext<ValuesOfField<SingleSelectField>>();

const inputProps = register(`${id}.value`, { required: Boolean(required) });

return (
<Select id={id} size="lg" labelText={label} {...inputProps}>
{options.map(({ id, label }) => (
<SelectItem key={id} text={label} value={id} />
))}
</Select>
);
}
1 change: 1 addition & 0 deletions apps/beeai-ui/src/modules/form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function getDefaultValues(fields: FormField[]) {
.with(
{ type: 'text' },
{ type: 'date' },
{ type: 'singleselect' },
{ type: 'multiselect' },
{ type: 'checkbox' },
({ id, type, default_value }) => [id, { type, value: default_value }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
color: $text-secondary;
}
textarea,
:global(.cds--date-picker__input) {
:global(.cds--date-picker__input),
:global(.cds--select-input) {
color: $text-primary;
background-color: transparent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@
*/

import { Button } from '@carbon/react';
import type { CheckboxField, DateField, FileField, FormField, MultiSelectField, TextField } from 'beeai-sdk';
import type {
CheckboxField,
DateField,
FileField,
FormField,
MultiSelectField,
SingleSelectField,
TextField,
} from 'beeai-sdk';
import { useMemo } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
Expand Down Expand Up @@ -79,6 +87,7 @@ function FormValueRenderer({ field }: { field: FieldWithValue }) {
{match(field)
.with({ type: 'text' }, { type: 'date' }, ({ value }) => value)
.with({ type: 'checkbox' }, ({ value }) => (value ? 'yes' : 'no'))
.with({ type: 'singleselect' }, ({ value }) => value)
.with({ type: 'multiselect' }, ({ value }) => value?.join(', '))
.with({ type: 'file' }, ({ value }) => value?.map(({ name }) => name).join(', '))
.otherwise(() => null)}
Expand All @@ -91,5 +100,6 @@ type FieldWithValue =
| FieldWithValueMapper<TextField>
| FieldWithValueMapper<DateField>
| FieldWithValueMapper<CheckboxField>
| FieldWithValueMapper<SingleSelectField>
| FieldWithValueMapper<MultiSelectField>
| FieldWithValueMapper<FileField>;
1 change: 0 additions & 1 deletion apps/beeai-ui/src/styles/components/_date-picker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

.cds--date-picker {
.cds--date-picker__input {
background-color: $background;
&:disabled {
background-color: $field;
&,
Expand Down
1 change: 1 addition & 0 deletions apps/beeai-ui/src/styles/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
@forward 'radio-button';
@forward '@carbon/styles/scss/components/search';
@forward '@carbon/styles/scss/components/select';
@forward 'select';
@forward '@carbon/styles/scss/components/skeleton-styles';
@forward 'skeleton-styles';
// @forward '@carbon/styles/scss/components/slider';
Expand Down
13 changes: 6 additions & 7 deletions apps/beeai-ui/src/styles/components/_input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@

.cds--text-area,
.cds--text-input,
.cds--date-picker__input {
.cds--date-picker__input,
.cds--select-input {
border: 1px solid $border-subtle-00;
border-radius: $border-radius;
background-color: $layer;
&,
.cds--modal & {
background-color: transparent;
}
background-color: $background;
&:focus-within {
outline: 2px solid $background-inverse;
}
}

.cds--text-input:disabled {
.cds--text-input:disabled,
.cds--select-input:disabled,
.cds--select-input:hover:disabled {
border-block-end-color: $border-subtle-00;
}

Expand Down
1 change: 1 addition & 0 deletions apps/beeai-ui/src/styles/components/_notification.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
inset: 0;
border-block: 1px solid $support-error-30;
border-inline-end: 1px solid $support-error-30;
pointer-events: none;
}
}

Expand Down
18 changes: 18 additions & 0 deletions apps/beeai-ui/src/styles/components/_select.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
* SPDX-License-Identifier: Apache-2.0
*/

@use 'styles/common' as *;

.cds--select-input,
.cds--select--inline .cds--select-input {
&:hover,
&:hover:focus {
background-color: $background;
}
}

.cds--select--inline .cds--select-input {
border: none;
}
22 changes: 22 additions & 0 deletions docs/build-agents/forms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ Here's what you need to know to add form capabilities to your agent:

- **TextField/DateField**: Returns `str | None`
- **FileField**: Returns `list[FileInfo] | None` where each `FileInfo` has `uri`, `name`, and `mime_type`
- **SingleSelectField**: Returns `str | None` (selected option ID)
- **MultiSelectField**: Returns `list[str] | None` (list of selected option IDs)
- **CheckboxField**: Returns `bool | None`

Expand Down Expand Up @@ -236,6 +237,27 @@ FileField(
)
```

### SingleSelectField
Single-select dropdown fields for choosing single option from a list.

```python
from beeai_sdk.a2a.extensions.ui.form import OptionItem, SingleSelectField

SingleSelectField(
id="contact_method",
label="Preferred Contact Method",
col_span=2,
required=False,
options=[
OptionItem(id="email", label="Email"),
OptionItem(id="phone", label="Phone"),
OptionItem(id="sms", label="SMS"),
OptionItem(id="none", label="Do not contact")
],
default_value="email"
)
```

### MultiSelectField
Multi-select dropdown fields for choosing multiple options from a list.

Expand Down