# AiForm
**Also known as:** SchemaForm
## Description
BitAiForm renders a form the model proposed, for the user to check and submit. Rather than calling a tool with arguments it guessed, a model describes a form - as fields, or as the same JSON Schema it already uses for tool arguments - the user corrects it, and the corrected values are what execute. Every field is a core input (BitTextField, BitNumberField, BitCheckbox, BitDropdown, BitDatePicker, BitTimePicker), nested objects become groups and arrays lists the user adds to, and validation happens here rather than in the model. Fields appear as the schema streams in, and what the user typed is kept.
## Parameters
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Busy | `bool` | false | Turns the actions off while something else is in flight - the submission being saved, a reply being generated - while the inputs stay editable. A press that still reaches a button submits or cancels nothing. |
| Classes | `BitAiFormClassStyles?` | null | Custom CSS classes for different parts of the form. |
| Definition | `BitAiFormDefinition` | null | The form to render (required): its title, description, fields and button labels. Each field is seeded once with its Value; a definition handed in again - rebuilt as a schema streams in - keeps what the user typed in every field whose control did not change (a bound, a choice or a nested field streaming in changes nothing typed, and a prefill streaming in fills a scalar field only while it still holds the previous prefill, so what the user typed wins; an object or a list is seeded again with a new prefill), seeds the new ones, and drops the values of fields that are gone. |
| OnCancel | `EventCallback` | | The callback raised when the user presses Cancel. The button is shown only while it is set, and unless the CancelLabel of the definition is an empty string. |
| OnSubmit | `EventCallback<IReadOnlyDictionary<string, string?>>` | | The callback raised with the collected values when the user submits a valid form: a copy of the flat values, as invariant text keyed by field path (name, venue.city, tags[0], speakers[1].name). A form with errors shows them on its fields and raises nothing. See OnSubmitJson for the typed, nested shape. |
| OnSubmitJson | `EventCallback<JsonElement>` | | The callback raised with the values as JSON in the shape of the schema the form came from (BitAiFormDefinition.ToJson): nested objects and arrays rebuilt from the paths, numbers and booleans encoded as such. Raised after OnSubmit. |
| Styles | `BitAiFormClassStyles?` | null | Custom CSS styles for different parts of the form. |
| Submitted | `bool` | false | Whether the form has already been submitted, which replaces the actions with a confirmation and stops any further submission - set it once the submission has been handled, so the turn reads as done. |
| AriaLabel | `string?` | null | Gets or sets the accessible label for the component, used by assistive technologies. |
| Class | `string?` | null | Gets or sets the CSS class name(s) to apply to the rendered element. |
| Dir | `BitDir?` | null | Gets or sets the text directionality for the component's content. |
| Disabled | `bool` | false | Gets or sets a value indicating whether the component is disabled and cannot respond to user interaction. |
| ForceAnimation | `bool` | false | Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested. |
| HtmlAttributes | `Dictionary<string, object>` | new Dictionary<string, object>() | Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. An attribute written on the component wins over an entry of the same name. |
| Id | `string?` | null | Gets or sets the unique identifier for the component's root element. |
| Style | `string?` | null | Gets or sets the CSS style string to apply to the rendered element. |
| TabIndex | `string?` | null | Gets or sets the tab order index for the component when navigating with the keyboard. |
| Visibility | `BitVisibility` | BitVisibility.Visible | Gets or sets the visibility state (visible, hidden, or collapsed) of the component. |
## Public Members
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Values | `IReadOnlyDictionary<string, string?>` | | The values as they currently stand, keyed by field path, valid or not - what the user has typed so far, read without submitting (through a @ref). |
| UniqueId | `Guid` | Guid.NewGuid() | Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed. |
| RootElement | `ElementReference` | | Gets the reference to the root HTML element associated with this component. |
## Enums
### BitAiFieldType Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Text | 0 | A single-line text box (BitTextField); a string with no format. |
| MultilineText | 1 | A multi-line text box; format textarea, or an object or array too deep to follow. |
| Number | 2 | A numeric input (BitNumberField); type integer or number. |
| Boolean | 3 | A checkbox (BitCheckbox); type boolean. |
| Select | 4 | A dropdown over Options (BitDropdown); an enum, or a oneOf / anyOf of const values. |
| Date | 5 | A date picker (BitDatePicker); format date. |
| DateTime | 6 | A date and time picker; format date-time. |
| Email | 7 | An email input; format email or idn-email. |
| Url | 8 | A URL input; format uri, url, uri-reference or iri. |
| Password | 9 | A masked input; format password. |
| Time | 10 | A time-of-day picker (BitTimePicker); format time. |
| Object | 11 | A group of nested fields, from an object with properties. |
| Array | 12 | A list the user adds to and removes from: scalar Items as a list of inputs, object Items as repeatable groups. |
### BitVisibility Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Visible | 0 | The content of the component is visible. |
| Hidden | 1 | The content of the component is hidden, but the space it takes on the page remains (visibility:hidden). |
| Collapsed | 2 | The component is hidden (display:none). |
### BitDir Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Ltr | 0 | Ltr (left to right) is to be used for languages that are written from the left to the right (like English). |
| Rtl | 1 | Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic). |
| Auto | 2 | Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element. |
## Sub Classes
### BitAiFormClassStyles Properties
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Root | `string?` | null | Custom CSS classes/styles for the root element of the BitAiForm. |
| Title | `string?` | null | Custom CSS classes/styles for the title of the BitAiForm. |
| Description | `string?` | null | Custom CSS classes/styles for the description under the title of the BitAiForm. |
| Field | `string?` | null | Custom CSS classes/styles for the wrapper of each single field (one input) of the BitAiForm. |
| Group | `string?` | null | Custom CSS classes/styles for each group of fields (the fieldset of a nested object or a list) of the BitAiForm. |
| Legend | `string?` | null | Custom CSS classes/styles for the legend of each group of fields of the BitAiForm. |
| Hint | `string?` | null | Custom CSS classes/styles for the help text of each group of fields of the BitAiForm. |
| Item | `string?` | null | Custom CSS classes/styles for each entry of a list of the BitAiForm. |
| ItemBody | `string?` | null | Custom CSS classes/styles for the fields of each entry of a list of the BitAiForm. |
| RemoveButton | `BitButtonClassStyles?` | null | Custom CSS classes/styles for the button removing an entry of a list of the BitAiForm. |
| AddButton | `BitButtonClassStyles?` | null | Custom CSS classes/styles for the button adding an entry to a list of the BitAiForm. |
| Error | `string?` | null | Custom CSS classes/styles for the error messages the BitAiForm renders itself: the one of a list, and the one of a checkbox. The other inputs show their errors through their own ErrorMessage. |
| Actions | `string?` | null | Custom CSS classes/styles for the actions of the BitAiForm. |
| CancelButton | `BitButtonClassStyles?` | null | Custom CSS classes/styles for the cancel button of the BitAiForm. |
| SubmitButton | `BitButtonClassStyles?` | null | Custom CSS classes/styles for the submit button of the BitAiForm. |
| Submitted | `string?` | null | Custom CSS classes/styles for the confirmation shown in place of the actions once the BitAiForm is submitted. |
### BitAiFormDefinition Properties
A form the model asked for, which the user fills in and submits back: a record of its fields and labels, built in code or from a JSON Schema.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Fields | `IReadOnlyList<BitAiFormField>` | [] | The fields, in display order. |
| Title | `string?` | null | A heading above the form. |
| Description | `string?` | null | A line of explanation under the heading. |
| SubmitLabel | `string?` | null | The text of the submit button; the Submit text of the AI options' string table while it is null, so a form a model proposes is labelled in the language of the app. |
| CancelLabel | `string?` | null | The text of the cancel button; the Cancel text of the string table while it is null, and no cancel button at all when it is an empty string. The button is shown only on a form with an OnCancel. |
| FromJsonSchema(schema, values?) | `BitAiFormDefinition (static)` | | Builds a form from a JSON Schema object (its title and description become the form's): type - including ["string", "null"], an anyOf with a null alternative or nullable: true, which make a field optional -, format (date, date-time, time, email / idn-email, uri / url / uri-reference / iri, password, textarea), enum with enumNames or x-enumNames, oneOf / anyOf of const values (labelled by their titles), const and readOnly (read-only fields), default, title, description, required, minimum / maximum, exclusiveMinimum / exclusiveMaximum (as numbers, or draft 4's flags), multipleOf, integer, minLength / maxLength, pattern, examples (a placeholder), nested object properties, array items with minItems / maxItems, and local $refs into $defs or definitions (a title or description beside the $ref wins). Objects and arrays nested deeper than 8 levels are edited as JSON text. The optional values, keyed by property name, prefill it ahead of the defaults. Never throws on a malformed schema: a keyword of the wrong kind is ignored and an unknown shape falls back to a text box. |
| ToJson(values) | `JsonElement` | | Builds JSON in the shape of the form from the flat values a BitAiForm collects - nested objects and arrays rebuilt from the paths, numbers and booleans encoded as such (by each field's JsonType) - with one member per field that has a value, and a required field written even when it is empty (as null, or an empty object or array). What OnSubmitJson is handed. |
| Path(parent, name) | `string (static)` | | The path of a member: its name at the root (parent null), else parent.name - venue.city. The keys of the flat values of OnSubmit and Values. |
| IndexPath(arrayPath, index) | `string (static)` | | The path of one entry of a list, by its zero-based index: tags[0], speakers[1] (and speakers[1].name for a member of it). |
| CountItems(values, arrayPath) | `int (static)` | | How many entries a list holds in the flat values: the highest index found under its path, plus one. For an app reading a list out of the values of OnSubmit or Values. |
### BitAiFormField Properties
One field of a form: a record created as new BitAiFormField(name, label?) with its other members set by initializer.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Name | `string` | | The key the value is submitted under. |
| Label | `string?` | null | The visible label. Falls back to the name written as a label (DisplayLabel): firstName reads First Name. |
| Type | `BitAiFieldType` | BitAiFieldType.Text | The control the field renders as. |
| Description | `string?` | null | Help text under the control (under the legend of a group). |
| Placeholder | `string?` | null | Placeholder text inside the control. |
| Value | `string?` | null | The initial value, which a model may prefill, as invariant text. For an Array field, the JSON text of the initial array. A Boolean field without one starts, and is submitted, as false - the unchecked box it shows. |
| Required | `bool` | false | Whether the field must be filled in (an Array field: hold at least one entry). A checkbox is never required to be checked. |
| ReadOnly | `bool` | false | Whether the field is shown but cannot be edited, as for a schema const or readOnly. A read-only list can be neither added to nor removed from. |
| Options | `IReadOnlyList<string>?` | null | The choices of a Select field, as the values that are submitted. A value outside them fails validation. |
| OptionLabels | `IReadOnlyList<string>?` | null | Display labels for Options, in the same order, when they differ from the values. |
| Minimum | `double?` | null | The smallest value a number accepts ("must be at least"); also the lowest value of its spin buttons. |
| Maximum | `double?` | null | The largest value a number accepts ("must be at most"); also the highest value of its spin buttons. |
| ExclusiveMinimum | `double?` | null | A value a number must be strictly greater than ("must be greater than"); the lowest value of its spin buttons when there is no Minimum. |
| ExclusiveMaximum | `double?` | null | A value a number must be strictly less than ("must be less than"); the highest value of its spin buttons when there is no Maximum. |
| MultipleOf | `double?` | null | A step a number must be a multiple of, which is also the step of its spin buttons. |
| Integer | `bool` | false | Whether a number accepts whole numbers only (a schema integer); its spin buttons step by 1. |
| MinLength | `int?` | null | The fewest characters a text, email, URL or password value may have ("must be at least N characters"). |
| MaxLength | `int?` | null | The most characters a text, email, URL or password value may have; the input stops taking more. |
| MinItems | `int?` | null | The fewest entries an Array field may hold ("needs at least N items"). |
| MaxItems | `int?` | null | The most entries an Array field may hold; its add button turns off at the most. |
| Pattern | `string?` | null | A regular expression the value must match, run under a timeout: one that takes too long fails the field. |
| Fields | `IReadOnlyList<BitAiFormField>?` | null | The nested fields of an Object field. |
| Items | `BitAiFormField?` | null | The template of each entry of an Array field: a scalar field for a list of inputs, an Object field for repeatable groups. |
| JsonType | `string?` | null | The JSON Schema primitive the value is submitted as by ToJson - string, integer, number, boolean, object or array; null means string. |
| DisplayLabel | `string` | | The label shown for the field: Label, else the name written as a label. |
### BitAiStrings (form) Properties
The texts of the form, members of the BitAiStrings of the options in force. Every member is virtual: a subclass overrides the ones it words differently and is handed as the Strings of the BitAiUiOptions - of a BitAiOptionsProvider, or of ConfigureUi (see /components/ai/optionsprovider#strings). The label in a message is the field's DisplayLabel - for an entry of a list of values, the list's label and the entry's place (Tags 2).
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Submit / Cancel | `string` | "Submit" / "Cancel" | The texts of the two buttons while the definition gives no SubmitLabel / CancelLabel. |
| Submitted | `string` | "Submitted" | The confirmation shown in place of the actions once Submitted is set. |
| AddItem | `string` | "Add item" | The text of the button adding an entry to a list. |
| ListItem(label, index) | `string` | "{label} {index}" | The name of an entry of a list (one-based): the accessible name of its group or input, and the label in its messages. |
| RemoveItem(label, index) | `string` | "Remove {label} {index}" | The name and tooltip of the button removing an entry. |
| FieldRequired(label) | `string` | "{label} is required." | A required field left empty, a required list with no entry, or an entry of a list of values left empty. |
| FieldTooShort(label, minimum) | `string` | "{label} must be at least {minimum} characters." | A text shorter than MinLength. |
| FieldTooLong(label, maximum) | `string` | "{label} must be at most {maximum} characters." | A text longer than MaxLength. |
| FieldInvalidFormat(label) | `string` | "{label} is not in the expected format." | A value that does not match the Pattern (or whose match timed out), or a choice the Options do not offer. |
| FieldNotANumber(label) | `string` | "{label} must be a number." | A number field holding something that is not a finite number. |
| FieldNotAnInteger(label) | `string` | "{label} must be a whole number." | A fraction in an Integer field. |
| FieldBelowMinimum(label, minimum) | `string` | "{label} must be at least {minimum}." | A number below Minimum. |
| FieldAboveMaximum(label, maximum) | `string` | "{label} must be at most {maximum}." | A number above Maximum. |
| FieldNotAboveMinimum(label, minimum) | `string` | "{label} must be greater than {minimum}." | A number not above ExclusiveMinimum. |
| FieldNotBelowMaximum(label, maximum) | `string` | "{label} must be less than {maximum}." | A number not below ExclusiveMaximum. |
| FieldNotAMultipleOf(label, step) | `string` | "{label} must be a multiple of {step}." | A number that is not a multiple of MultipleOf. |
| FieldNotAnEmail(label) | `string` | "{label} must be an email address." | An Email field without exactly one @ between two parts, or with a space. |
| FieldNotAUrl(label) | `string` | "{label} must be a web address." | A Url field that is not an absolute http or https address with a host (javascript:, mailto: and a bare example.com all fail). |
| FieldNotADate(label) | `string` | "{label} must be a valid date or time." | A Date, DateTime or Time field holding something that does not parse as one (a prefill a model got wrong). |
| FieldTooFewItems(label, minimum) | `string` | "{label} needs at least {minimum} item(s)." | A list with fewer entries than MinItems. |
| FieldTooManyItems(label, maximum) | `string` | "{label} can have at most {maximum} item(s)." | A list with more entries than MaxItems - one prefilled past it, since the add button stops there. |
## CSS Variables
Public CSS custom properties read off the component's root. Set one on `:root` to restyle every instance, on any ancestor to restyle the ones inside it, or on the `Style` of one instance to restyle it and every instance nested inside it, which inherits the value too - a parameter changes one alone, as does the real CSS property on its `Style` / `Styles` when that property does not inherit (`opacity`); an inherited one (`color`, `font-family`) reaches the nested instances that do not set it themselves.
| Name | Default Value | Description |
| :--- | :------------ | :---------- |
| `--bit-AiForm-background` | var(--bit-clr-bg-sec) | Background of the form. |
| `--bit-AiForm-border-color` | var(--bit-clr-brd-pri) | Border color of the form. |
| `--bit-AiForm-border-width` | var(--bit-shp-brd-width) | Border width of the form. |
| `--bit-AiForm-radius` | var(--bit-shp-radius-surface) | Corner radius of the form. |
| `--bit-AiForm-padding` | var(--bit-spa-card-md) | Inset of the form. |
| `--bit-AiForm-gap` | spacing(1.5) | Room between the title, the fields and the actions, and between the fields of a group. |
| `--bit-AiForm-title-color` | var(--bit-clr-fg-pri) | Color of the title. |
| `--bit-AiForm-title-font-size` | var(--bit-tpg-fs-md) | Size of the title. |
| `--bit-AiForm-title-font-weight` | var(--bit-tpg-fw-semibold) | Weight of the title. |
| `--bit-AiForm-description-color` | var(--bit-clr-fg-sec) | Color of the description and of the help text of a group of fields. |
| `--bit-AiForm-description-font-size` | var(--bit-tpg-fs-xs) | Size of the description and of the help text of a group. |
| `--bit-AiForm-group-border-color` | var(--bit-clr-brd-pri) | Border color of a group of fields (a nested object, a list). |
| `--bit-AiForm-group-radius` | var(--bit-shp-radius-control) | Corner radius of a group of fields. |
| `--bit-AiForm-group-padding` | spacing(1.5) | Inset of a group of fields. |
| `--bit-AiForm-legend-color` | var(--bit-clr-fg-pri) | Color of the legend of a group of fields. |
| `--bit-AiForm-legend-font-size` | var(--bit-tpg-fs-sm) | Size of the legend of a group of fields. |
| `--bit-AiForm-required-color` | var(--bit-clr-req) | Color of the marker of a required list (the inputs mark themselves). |
| `--bit-AiForm-error-color` | var(--bit-clr-err) | Color of the border of an invalid list and of the error messages the form renders itself. |
| `--bit-AiForm-error-font-size` | var(--bit-tpg-fs-2xs) | Size of the error messages the form renders itself. |
| `--bit-AiForm-actions-gap` | spacing(1) | Room between the actions. |
| `--bit-AiForm-actions-justify` | flex-end | How the actions, and the confirmation that replaces them, sit along their row. |
## Examples
\n**Basic**:
```razor
@if (contactValues is not null)
{
? contactValues;
```
\n**Field types**:
```razor
@if (typesValues is not null)
{
? typesValues;
```
\n**JSON Schema**:
```razor
```
```csharp
private static JsonElement Parse(string json) => JsonDocument.Parse(json).RootElement;
private readonly BitAiFormDefinition bookingForm = BitAiFormDefinition.FromJsonSchema(Parse("""
{
"title": "Book a table",
"description": "Check the details I inferred before I book.",
"type": "object",
"required": ["restaurant", "guests", "date"],
"properties": {
"restaurant": { "type": "string", "title": "Restaurant" },
"guests": { "type": "integer", "title": "Guests", "minimum": 1, "maximum": 12, "default": 2 },
"date": { "type": "string", "format": "date", "title": "Date" },
"time": { "type": "string", "format": "time", "title": "Time" },
"seating": { "type": "string", "enum": ["inside", "terrace"], "enumNames": ["Inside", "On the terrace"] },
"highChair": { "type": "boolean", "title": "A high chair" },
"notes": { "type": "string", "format": "textarea", "title": "Notes for the restaurant" }
}
}
"""), Parse("""{ "restaurant": "Lucca", "date": "2026-10-23" }"""));
private JsonElement? bookingJson;
```
\n**Schema keywords**:
```razor
```
```csharp
private readonly BitAiFormDefinition seatForm = BitAiFormDefinition.FromJsonSchema(JsonDocument.Parse("""
{
"title": "Reserve a seat",
"type": "object",
"$defs": {
"contact": {
"type": "object",
"title": "Contact",
"required": ["name"],
"properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" } }
}
},
"required": ["event", "ticket", "phone"],
"properties": {
"event": { "const": "BlazorConf 2026", "title": "Event" },
"ticket": {
"title": "Ticket",
"oneOf": [
{ "const": "standard", "title": "Standard - $99" },
{ "const": "vip", "title": "VIP - $249" }
]
},
"seat": { "type": "string", "title": "Seat", "readOnly": true, "default": "B12" },
"arrival": { "type": "string", "format": "date-time", "title": "Arrival" },
"pin": { "type": "string", "format": "password", "title": "Door PIN" },
"phone": { "type": ["string", "null"], "title": "Phone", "examples": ["+1 555 0100"] },
"contact": { "$ref": "#/$defs/contact", "description": "Who we write to about the seat." }
}
}
""").RootElement);
private JsonElement? seatJson;
```
\n**Validation**:
```razor
@if (signUpValues is not null)
{
? signUpValues;
```
\n**Nested objects & lists**:
```razor
```
```csharp
private readonly BitAiFormDefinition eventForm = BitAiFormDefinition.FromJsonSchema(JsonDocument.Parse("""
{
"title": "Plan an event",
"type": "object",
"properties": {
"event": { "type": "string", "title": "Event" },
"venue": {
"type": "object",
"title": "Venue",
"description": "Where it takes place.",
"required": ["city"],
"properties": { "city": { "type": "string" }, "room": { "type": "string" } }
},
"tags": { "type": "array", "title": "Tags", "items": { "type": "string" }, "maxItems": 4, "default": ["blazor"] },
"speakers": {
"type": "array",
"title": "Speakers",
"minItems": 1,
"items": {
"type": "object",
"required": ["name"],
"properties": { "name": { "type": "string" }, "minutes": { "type": "integer", "minimum": 5 } }
}
}
}
}
""").RootElement);
private JsonElement? eventJson;
```
\n**Streaming schema**:
```razor
@implements IAsyncDisposable
Book a table for Friday
@if (ProposedForm is { } proposed)
{
}
```
```csharp
// A scripted transport that streams the JSON Schema of a booking form (see the next tab).
private readonly BitAiChatSession proposalSession = new(AiDemoInteractiveTransports.ProposesForm());
private BitAiRenderScheduler? renderScheduler;
private bool proposalSubmitted;
protected override void OnInitialized()
{
// The session says so on every streamed delta; the scheduler turns any number of those into one render per 50 ms.
renderScheduler = new(() => InvokeAsync(StateHasChanged));
proposalSession.StateChanged += renderScheduler.Request;
}
// Rebuilt from the schema received so far on every render; the form keeps whatever the user already typed.
private BitAiFormDefinition? ProposedForm =>
proposalSession.Conversation.LastMessage?.PartsOf().FirstOrDefault()?.Value is { } schema
? BitAiFormDefinition.FromJsonSchema(schema)
: null;
private async Task ProposeBookingAsync()
{
proposalSubmitted = false;
await proposalSession.SubmitAsync("Book a table for Friday");
}
public async ValueTask DisposeAsync()
{
if (renderScheduler is not null) await renderScheduler.DisposeAsync();
await proposalSession.DisposeAsync();
}
```
`AiDemoInteractiveTransports.cs`:
```csharp
public static IBitAiTransport ProposesForm() =>
new AiScriptedTransport((request, token) => AiScript.Stream(token, async yielder =>
{
await yielder(new BitAiMessageStartedEvent());
const string intro = "I can book that. Check the details first, since I inferred two of them.\n\n";
await foreach (var evt in AiScript.Write(intro, 4, 12, token)) await yielder(evt);
// The two details the model inferred go in as the defaults the form starts from: two guests, and the coming Friday.
var today = DateTime.Today;
var friday = today.AddDays(((int)DayOfWeek.Friday - (int)today.DayOfWeek + 7) % 7);
var date = friday.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
var schema = $$"""
{
"title": "Book a table",
"type": "object",
"required": ["restaurant", "guests", "date"],
"properties": {
"restaurant": { "type": "string", "title": "Restaurant" },
"guests": { "type": "integer", "title": "Guests", "minimum": 1, "maximum": 12, "default": 2 },
"date": { "type": "string", "format": "date", "title": "Date", "default": "{{date}}" },
"seating": { "type": "string", "enum": ["Inside", "Terrace", "No preference"] },
"notes": { "type": "string", "format": "textarea", "title": "Notes for the restaurant" }
}
}
""";
// Thirty characters at a time: the schema is incomplete until the last slice.
foreach (var slice in AiScript.Slice(schema, 30))
{
await AiScript.Pause(50, token);
await yielder(new BitAiStructuredDataDeltaEvent(slice) { SchemaName = "booking_form" });
}
await yielder(new BitAiMessageCompletedEvent());
}));
```
\n**Actions & state**:
```razor
Read the values
Start over
```
```csharp
private readonly BitAiFormDefinition contactForm = new()
{
Title = "Contact us",
Description = "We answer within a working day.",
Fields =
[
new("name") { Required = true, Placeholder = "Ada Lovelace" },
new("email") { Type = BitAiFieldType.Email, Required = true },
new("message") { Type = BitAiFieldType.MultilineText, Description = "What can we help with?" },
],
};
private readonly BitAiFormDefinition feedbackForm = new()
{
Title = "Rate the answer",
SubmitLabel = "Send feedback",
CancelLabel = "Not now",
Fields =
[
new("rating") { Type = BitAiFieldType.Select, Required = true, Options = ["5", "4", "3", "2", "1"], OptionLabels = ["Excellent", "Good", "Fair", "Poor", "Wrong"] },
new("comment") { Type = BitAiFieldType.MultilineText, Placeholder = "What would have made it better?" },
],
};
private BitAiForm feedbackFormRef = default!;
private bool feedbackBusy;
private bool feedbackSubmitted;
private string feedbackStatus = string.Empty;
private async Task SendFeedbackAsync(IReadOnlyDictionary values)
{
feedbackBusy = true;
feedbackStatus = "Sending...";
await Task.Delay(1500); // stands in for the call that stores the feedback
feedbackBusy = false;
feedbackSubmitted = true;
feedbackStatus = $"Sent: a rating of {values["rating"]}.";
}
private void CancelFeedback() => feedbackStatus = "Cancelled: nothing was sent.";
private void ReadValues() =>
feedbackStatus = "So far: " + string.Join(", ", feedbackFormRef.Values.Select(v => $"{v.Key} = {v.Value ?? "(empty)"}"));
private void StartOver()
{
feedbackSubmitted = false;
feedbackStatus = string.Empty;
}
```
\n**Cascading parameters**:
```razor
```
```csharp
private readonly BitAiFormDefinition contactForm = new()
{
Title = "Contact us",
Description = "We answer within a working day.",
Fields =
[
new("name") { Required = true, Placeholder = "Ada Lovelace" },
new("email") { Type = BitAiFieldType.Email, Required = true },
new("message") { Type = BitAiFieldType.MultilineText, Description = "What can we help with?" },
],
};
private readonly BitAiFormDefinition feedbackForm = new()
{
Title = "Rate the answer",
SubmitLabel = "Send feedback",
CancelLabel = "Not now",
Fields =
[
new("rating") { Type = BitAiFieldType.Select, Required = true, Options = ["5", "4", "3", "2", "1"], OptionLabels = ["Excellent", "Good", "Fair", "Poor", "Wrong"] },
new("comment") { Type = BitAiFieldType.MultilineText, Placeholder = "What would have made it better?" },
],
};
private readonly BitAiFormParams[] formParams =
[
new() { Styles = new() { Root = "border-radius: 1rem", Title = "color: var(--bit-clr-pri)", SubmitButton = new() { Root = "border-radius: 1rem" } } },
];
```
\n**Style & Class**:
```razor
```
```csharp
private readonly BitAiFormDefinition contactForm = new()
{
Title = "Contact us",
Description = "We answer within a working day.",
Fields =
[
new("name") { Required = true, Placeholder = "Ada Lovelace" },
new("email") { Type = BitAiFieldType.Email, Required = true },
new("message") { Type = BitAiFieldType.MultilineText, Description = "What can we help with?" },
],
};
private readonly BitAiFormDefinition eventForm = BitAiFormDefinition.FromJsonSchema(JsonDocument.Parse("""
{
"title": "Plan an event",
"type": "object",
"properties": {
"event": { "type": "string", "title": "Event" },
"venue": {
"type": "object",
"title": "Venue",
"description": "Where it takes place.",
"required": ["city"],
"properties": { "city": { "type": "string" }, "room": { "type": "string" } }
},
"tags": { "type": "array", "title": "Tags", "items": { "type": "string" }, "maxItems": 4, "default": ["blazor"] },
"speakers": {
"type": "array",
"title": "Speakers",
"minItems": 1,
"items": {
"type": "object",
"required": ["name"],
"properties": { "name": { "type": "string" }, "minutes": { "type": "integer", "minimum": 5 } }
}
}
}
}
""").RootElement);
```
\n**RTL**:
```razor
```
```csharp
private readonly BitAiFormDefinition persianForm = new()
{
Title = "تماس با ما",
Description = "در کمتر از یک روز کاری پاسخ میدهیم.",
SubmitLabel = "ارسال",
CancelLabel = "انصراف",
Fields =
[
new("name", "نام") { Required = true },
new("subscribe", "عضویت در خبرنامه") { Type = BitAiFieldType.Boolean },
new("message", "پیام") { Type = BitAiFieldType.MultilineText },
],
};
```
-
@foreach (var (key, value) in contactValues)
{
- @key: @value }
-
@foreach (var (key, value) in typesValues)
{
- @key: @value }
Valid: @string.Join(", ", signUpValues.Select(v => $"{v.Key} = {v.Value}"))
}
```
```csharp
private readonly BitAiFormDefinition signUpForm = BitAiFormDefinition.FromJsonSchema(JsonDocument.Parse("""
{
"title": "Create an account",
"type": "object",
"required": ["username", "age"],
"properties": {
"username": { "type": "string", "minLength": 3, "maxLength": 16, "pattern": "^[a-z0-9_]+$", "description": "3 to 16 lowercase letters, digits or underscores." },
"age": { "type": "integer", "minimum": 18, "maximum": 120 },
"email": { "type": "string", "format": "email" },
"website": { "type": "string", "format": "uri" },
"donation": { "type": "number", "exclusiveMinimum": 0, "exclusiveMaximum": 1000, "multipleOf": 5, "description": "Optional, in steps of 5, under 1000." }
}
}
""").RootElement) with { SubmitLabel = "Sign up" };
private IReadOnlyDictionary@feedbackStatus