WIP: implementing time, date and date range pickers

This commit is contained in:
krut_ni 2026-03-16 20:06:50 +01:00
parent 5894147238
commit c4ec10748a
No known key found for this signature in database
GPG Key ID: A5C0151B4DDB172C
11 changed files with 716 additions and 4 deletions

View File

@ -403,6 +403,70 @@ else
</MudItem> </MudItem>
} }
break; break;
case AssistantComponentType.DATE_PICKER:
if (component is AssistantDatePicker assistantDatePicker)
{
var datePicker = assistantDatePicker;
var format = datePicker.GetDateFormat();
<MudPaper Class="d-flex" Elevation="0">
<MudDatePicker Date="@this.ParseDatePickerValue(this.datePickerFields[datePicker.Name], format)"
DateChanged="@((DateTime? value) => this.SetDatePickerValue(datePicker.Name, value, format))"
Label="@datePicker.Label"
Placeholder="@datePicker.Placeholder"
HelperText="@datePicker.HelperText"
DateFormat="@format"
PickerVariant="@AssistantComponentPropHelper.GetPickerVariant(datePicker.PickerVariant, PickerVariant.Static)"
Variant="Variant.Outlined"
Class='@MergeClass(datePicker.Class, "mb-3")'
Style="@this.GetOptionalStyle(datePicker.Style)"
/>
</MudPaper>
}
break;
case AssistantComponentType.DATE_RANGE_PICKER:
if (component is AssistantDateRangePicker assistantDateRangePicker)
{
var dateRangePicker = assistantDateRangePicker;
var format = dateRangePicker.GetDateFormat();
<MudPaper Class="d-flex" Elevation="0">
<MudDateRangePicker DateRange="@this.ParseDateRangePickerValue(this.dateRangePickerFields[dateRangePicker.Name], format)"
DateRangeChanged="@(value => this.SetDateRangePickerValue(dateRangePicker.Name, value, format))"
Label="@dateRangePicker.Label"
PlaceholderStart="@dateRangePicker.PlaceholderStart"
PlaceholderEnd="@dateRangePicker.PlaceholderEnd"
HelperText="@dateRangePicker.HelperText"
DateFormat="@format"
PickerVariant="@AssistantComponentPropHelper.GetPickerVariant(dateRangePicker.PickerVariant, PickerVariant.Static)"
Variant="Variant.Outlined"
Class='@MergeClass(dateRangePicker.Class, "mb-3")'
Style="@this.GetOptionalStyle(dateRangePicker.Style)"
/>
</MudPaper>
}
break;
case AssistantComponentType.TIME_PICKER:
if (component is AssistantTimePicker assistantTimePicker)
{
var timePicker = assistantTimePicker;
var format = timePicker.GetTimeFormat();
<MudPaper Class="d-flex" Elevation="0">
<MudTimePicker Time="@this.ParseTimePickerValue(this.timePickerFields[timePicker.Name], format)"
TimeChanged="@((TimeSpan? value) => this.SetTimePickerValue(timePicker.Name, value, format))"
Label="@timePicker.Label"
Placeholder="@timePicker.Placeholder"
HelperText="@timePicker.HelperText"
TimeFormat="@format"
AmPm="@timePicker.AmPm"
PickerVariant="@AssistantComponentPropHelper.GetPickerVariant(timePicker.PickerVariant, PickerVariant.Static)"
Variant="Variant.Outlined"
Class='@MergeClass(timePicker.Class, "mb-3")'
Style="@this.GetOptionalStyle(timePicker.Style)"/>
</MudPaper>
}
break;
} }
</text>; </text>;

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -45,12 +46,18 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
private readonly Dictionary<string, WebContentState> webContentFields = new(); private readonly Dictionary<string, WebContentState> webContentFields = new();
private readonly Dictionary<string, FileContentState> fileContentFields = new(); private readonly Dictionary<string, FileContentState> fileContentFields = new();
private readonly Dictionary<string, string> colorPickerFields = new(); private readonly Dictionary<string, string> colorPickerFields = new();
private readonly Dictionary<string, string> datePickerFields = new();
private readonly Dictionary<string, string> dateRangePickerFields = new();
private readonly Dictionary<string, string> timePickerFields = new();
private readonly Dictionary<string, string> imageCache = new(); private readonly Dictionary<string, string> imageCache = new();
private readonly HashSet<string> executingButtonActions = []; private readonly HashSet<string> executingButtonActions = [];
private readonly HashSet<string> executingSwitchActions = []; private readonly HashSet<string> executingSwitchActions = [];
private string pluginPath = string.Empty; private string pluginPath = string.Empty;
private const string PLUGIN_SCHEME = "plugin://"; private const string PLUGIN_SCHEME = "plugin://";
private const string ASSISTANT_QUERY_KEY = "assistantId"; private const string ASSISTANT_QUERY_KEY = "assistantId";
private static readonly CultureInfo INVARIANT_CULTURE = CultureInfo.InvariantCulture;
private static readonly string[] FALLBACK_DATE_FORMATS = ["yyyy-MM-dd", "dd.MM.yyyy", "MM/dd/yyyy"];
private static readonly string[] FALLBACK_TIME_FORMATS = ["HH:mm", "HH:mm:ss", "hh:mm tt", "h:mm tt"];
protected override void OnInitialized() protected override void OnInitialized()
{ {
@ -136,6 +143,18 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
{ {
this.colorPickerFields[entry.Key] = string.Empty; this.colorPickerFields[entry.Key] = string.Empty;
} }
foreach (var entry in this.datePickerFields)
{
this.datePickerFields[entry.Key] = string.Empty;
}
foreach (var entry in this.dateRangePickerFields)
{
this.dateRangePickerFields[entry.Key] = string.Empty;
}
foreach (var entry in this.timePickerFields)
{
this.timePickerFields[entry.Key] = string.Empty;
}
} }
protected override bool MightPreselectValues() protected override bool MightPreselectValues()
@ -229,6 +248,12 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
fields[entry.Key] = entry.Value.Content ?? string.Empty; fields[entry.Key] = entry.Value.Content ?? string.Empty;
foreach (var entry in this.colorPickerFields) foreach (var entry in this.colorPickerFields)
fields[entry.Key] = entry.Value ?? string.Empty; fields[entry.Key] = entry.Value ?? string.Empty;
foreach (var entry in this.datePickerFields)
fields[entry.Key] = entry.Value ?? string.Empty;
foreach (var entry in this.dateRangePickerFields)
fields[entry.Key] = entry.Value ?? string.Empty;
foreach (var entry in this.timePickerFields)
fields[entry.Key] = entry.Value ?? string.Empty;
input["fields"] = fields; input["fields"] = fields;
@ -325,6 +350,18 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
if (component is AssistantColorPicker assistantColorPicker && !this.colorPickerFields.ContainsKey(assistantColorPicker.Name)) if (component is AssistantColorPicker assistantColorPicker && !this.colorPickerFields.ContainsKey(assistantColorPicker.Name))
this.colorPickerFields.Add(assistantColorPicker.Name, assistantColorPicker.Placeholder); this.colorPickerFields.Add(assistantColorPicker.Name, assistantColorPicker.Placeholder);
break; break;
case AssistantComponentType.DATE_PICKER:
if (component is AssistantDatePicker datePicker && !this.datePickerFields.ContainsKey(datePicker.Name))
this.datePickerFields.Add(datePicker.Name, datePicker.Value);
break;
case AssistantComponentType.DATE_RANGE_PICKER:
if (component is AssistantDateRangePicker dateRangePicker && !this.dateRangePickerFields.ContainsKey(dateRangePicker.Name))
this.dateRangePickerFields.Add(dateRangePicker.Name, dateRangePicker.Value);
break;
case AssistantComponentType.TIME_PICKER:
if (component is AssistantTimePicker timePicker && !this.timePickerFields.ContainsKey(timePicker.Name))
this.timePickerFields.Add(timePicker.Name, timePicker.Value);
break;
} }
if (component.Children.Count > 0) if (component.Children.Count > 0)
@ -473,6 +510,33 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
return; return;
} }
if (this.datePickerFields.ContainsKey(fieldName))
{
if (value.TryRead<string>(out var dateValue))
this.datePickerFields[fieldName] = dateValue ?? string.Empty;
else
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
return;
}
if (this.dateRangePickerFields.ContainsKey(fieldName))
{
if (value.TryRead<string>(out var dateRangeValue))
this.dateRangePickerFields[fieldName] = dateRangeValue ?? string.Empty;
else
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
return;
}
if (this.timePickerFields.ContainsKey(fieldName))
{
if (value.TryRead<string>(out var timeValue))
this.timePickerFields[fieldName] = timeValue ?? string.Empty;
else
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
return;
}
if (this.webContentFields.TryGetValue(fieldName, out var webContentState)) if (this.webContentFields.TryGetValue(fieldName, out var webContentState))
{ {
if (value.TryRead<string>(out var webContentValue)) if (value.TryRead<string>(out var webContentValue))
@ -549,6 +613,15 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
case AssistantColorPicker colorPicker: case AssistantColorPicker colorPicker:
this.AddMetaEntry(meta, colorPicker.Name, component.Type, colorPicker.Label, colorPicker.UserPrompt); this.AddMetaEntry(meta, colorPicker.Name, component.Type, colorPicker.Label, colorPicker.UserPrompt);
break; break;
case AssistantDatePicker datePicker:
this.AddMetaEntry(meta, datePicker.Name, component.Type, datePicker.Label, datePicker.UserPrompt);
break;
case AssistantDateRangePicker dateRangePicker:
this.AddMetaEntry(meta, dateRangePicker.Name, component.Type, dateRangePicker.Label, dateRangePicker.UserPrompt);
break;
case AssistantTimePicker timePicker:
this.AddMetaEntry(meta, timePicker.Name, component.Type, timePicker.Label, timePicker.UserPrompt);
break;
} }
if (component.Children.Count > 0) if (component.Children.Count > 0)
@ -623,6 +696,30 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
prompt += $"user prompt:{Environment.NewLine}{userInput}"; prompt += $"user prompt:{Environment.NewLine}{userInput}";
} }
break; break;
case AssistantComponentType.DATE_PICKER:
if (component is AssistantDatePicker datePicker)
{
prompt += $"context:{Environment.NewLine}{datePicker.UserPrompt}{Environment.NewLine}---{Environment.NewLine}";
if (this.datePickerFields.TryGetValue(datePicker.Name, out userInput))
prompt += $"user prompt:{Environment.NewLine}{userInput}";
}
break;
case AssistantComponentType.DATE_RANGE_PICKER:
if (component is AssistantDateRangePicker dateRangePicker)
{
prompt += $"context:{Environment.NewLine}{dateRangePicker.UserPrompt}{Environment.NewLine}---{Environment.NewLine}";
if (this.dateRangePickerFields.TryGetValue(dateRangePicker.Name, out userInput))
prompt += $"user prompt:{Environment.NewLine}{userInput}";
}
break;
case AssistantComponentType.TIME_PICKER:
if (component is AssistantTimePicker timePicker)
{
prompt += $"context:{Environment.NewLine}{timePicker.UserPrompt}{Environment.NewLine}---{Environment.NewLine}";
if (this.timePickerFields.TryGetValue(timePicker.Name, out userInput))
prompt += $"user prompt:{Environment.NewLine}{userInput}";
}
break;
} }
if (component.Children.Count > 0) if (component.Children.Count > 0)
@ -663,4 +760,124 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
return parsedValues; return parsedValues;
} }
private DateTime? ParseDatePickerValue(string? value, string? format)
{
if (string.IsNullOrWhiteSpace(value))
return null;
if (TryParseDate(value, format, out var parsedDate))
return parsedDate;
return null;
}
private void SetDatePickerValue(string fieldName, DateTime? value, string? format)
{
this.datePickerFields[fieldName] = value.HasValue ? FormatDate(value.Value, format) : string.Empty;
}
private DateRange? ParseDateRangePickerValue(string? value, string? format)
{
if (string.IsNullOrWhiteSpace(value))
return null;
var parts = value.Split(" - ", 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
return null;
if (!TryParseDate(parts[0], format, out var start) || !TryParseDate(parts[1], format, out var end))
return null;
return new DateRange(start, end);
}
private void SetDateRangePickerValue(string fieldName, DateRange? value, string? format)
{
if (value?.Start is null || value.End is null)
{
this.dateRangePickerFields[fieldName] = string.Empty;
return;
}
this.dateRangePickerFields[fieldName] = $"{FormatDate(value.Start.Value, format)} - {FormatDate(value.End.Value, format)}";
}
private TimeSpan? ParseTimePickerValue(string? value, string? format)
{
if (string.IsNullOrWhiteSpace(value))
return null;
if (TryParseTime(value, format, out var parsedTime))
return parsedTime;
return null;
}
private void SetTimePickerValue(string fieldName, TimeSpan? value, string? format)
{
this.timePickerFields[fieldName] = value.HasValue ? FormatTime(value.Value, format) : string.Empty;
}
private static bool TryParseDate(string value, string? format, out DateTime parsedDate)
{
if (!string.IsNullOrWhiteSpace(format) &&
DateTime.TryParseExact(value, format, INVARIANT_CULTURE, DateTimeStyles.AllowWhiteSpaces, out parsedDate))
{
return true;
}
if (DateTime.TryParseExact(value, FALLBACK_DATE_FORMATS, INVARIANT_CULTURE, DateTimeStyles.AllowWhiteSpaces, out parsedDate))
return true;
return DateTime.TryParse(value, INVARIANT_CULTURE, DateTimeStyles.AllowWhiteSpaces, out parsedDate);
}
private static bool TryParseTime(string value, string? format, out TimeSpan parsedTime)
{
if (!string.IsNullOrWhiteSpace(format) &&
DateTime.TryParseExact(value, format, INVARIANT_CULTURE, DateTimeStyles.AllowWhiteSpaces, out var dateTime))
{
parsedTime = dateTime.TimeOfDay;
return true;
}
if (DateTime.TryParseExact(value, FALLBACK_TIME_FORMATS, INVARIANT_CULTURE, DateTimeStyles.AllowWhiteSpaces, out dateTime))
{
parsedTime = dateTime.TimeOfDay;
return true;
}
if (TimeSpan.TryParse(value, INVARIANT_CULTURE, out parsedTime))
return true;
parsedTime = default;
return false;
}
private static string FormatDate(DateTime value, string? format)
{
try
{
return value.ToString(string.IsNullOrWhiteSpace(format) ? "yyyy-MM-dd" : format, INVARIANT_CULTURE);
}
catch (FormatException)
{
return value.ToString("yyyy-MM-dd", INVARIANT_CULTURE);
}
}
private static string FormatTime(TimeSpan value, string? format)
{
var dateTime = DateTime.Today.Add(value);
try
{
return dateTime.ToString(string.IsNullOrWhiteSpace(format) ? "HH:mm" : format, INVARIANT_CULTURE);
}
catch (FormatException)
{
return dateTime.ToString("HH:mm", INVARIANT_CULTURE);
}
}
} }

View File

@ -17,6 +17,9 @@ This folder keeps the Lua manifest (`plugin.lua`) that defines a custom assistan
- [`BUTTON_GROUP` reference](#button_group-reference) - [`BUTTON_GROUP` reference](#button_group-reference)
- [`SWITCH` reference](#switch-reference) - [`SWITCH` reference](#switch-reference)
- [`COLOR_PICKER` reference](#color_picker-reference) - [`COLOR_PICKER` reference](#color_picker-reference)
- [`DATE_PICKER` reference](#date_picker-reference)
- [`DATE_RANGE_PICKER` reference](#date_range_picker-reference)
- [`TIME_PICKER` reference](#time_picker-reference)
- [Prompt Assembly - UserPrompt Property](#prompt-assembly---userprompt-property) - [Prompt Assembly - UserPrompt Property](#prompt-assembly---userprompt-property)
- [Advanced Prompt Assembly - BuildPrompt()](#advanced-prompt-assembly---buildprompt) - [Advanced Prompt Assembly - BuildPrompt()](#advanced-prompt-assembly---buildprompt)
- [Interface](#interface) - [Interface](#interface)
@ -100,6 +103,9 @@ ASSISTANT = {
- `LAYOUT_ACCORDION_SECTION`: renders a `MudExpansionPanel`; requires `Name`, `HeaderText`, and may include `IsDisabled`, `IsExpanded`, `IsDense`, `HasInnerPadding`, `HideIcon`, `HeaderIcon`, `HeaderColor`, `HeaderTypo`, `HeaderAlign`, `MaxHeight`, `ExpandIcon`, `Class`, `Style`. - `LAYOUT_ACCORDION_SECTION`: renders a `MudExpansionPanel`; requires `Name`, `HeaderText`, and may include `IsDisabled`, `IsExpanded`, `IsDense`, `HasInnerPadding`, `HideIcon`, `HeaderIcon`, `HeaderColor`, `HeaderTypo`, `HeaderAlign`, `MaxHeight`, `ExpandIcon`, `Class`, `Style`.
- `SWITCH`: boolean option; requires `Name`, `Label`, `Value`, and may include `OnChanged`, `Disabled`, `UserPrompt`, `LabelOn`, `LabelOff`, `LabelPlacement`, `Icon`, `IconColor`, `CheckedColor`, `UncheckedColor`, `Class`, `Style`. - `SWITCH`: boolean option; requires `Name`, `Label`, `Value`, and may include `OnChanged`, `Disabled`, `UserPrompt`, `LabelOn`, `LabelOff`, `LabelPlacement`, `Icon`, `IconColor`, `CheckedColor`, `UncheckedColor`, `Class`, `Style`.
- `COLOR_PICKER`: color input based on `MudColorPicker`; requires `Name`, `Label`, and may include `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style`. - `COLOR_PICKER`: color input based on `MudColorPicker`; requires `Name`, `Label`, and may include `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style`.
- `DATE_PICKER`: date input based on `MudDatePicker`; requires `Name`, `Label`, and may include `Value`, `Placeholder`, `HelperText`, `DateFormat`, `PickerVariant`, `UserPrompt`, `Class`, `Style`.
- `DATE_RANGE_PICKER`: date range input based on `MudDateRangePicker`; requires `Name`, `Label`, and may include `Value`, `PlaceholderStart`, `PlaceholderEnd`, `HelperText`, `DateFormat`, `PickerVariant`, `UserPrompt`, `Class`, `Style`.
- `TIME_PICKER`: time input based on `MudTimePicker`; requires `Name`, `Label`, and may include `Value`, `Placeholder`, `HelperText`, `TimeFormat`, `AmPm`, `PickerVariant`, `UserPrompt`, `Class`, `Style`.
- `PROVIDER_SELECTION` / `PROFILE_SELECTION`: hooks into the shared provider/profile selectors. - `PROVIDER_SELECTION` / `PROFILE_SELECTION`: hooks into the shared provider/profile selectors.
- `WEB_CONTENT_READER`: renders `ReadWebContent`; include `Name`, `UserPrompt`, `Preselect`, `PreselectContentCleanerAgent`. - `WEB_CONTENT_READER`: renders `ReadWebContent`; include `Name`, `UserPrompt`, `Preselect`, `PreselectContentCleanerAgent`.
- `FILE_CONTENT_READER`: renders `ReadFileContent`; include `Name`, `UserPrompt`. - `FILE_CONTENT_READER`: renders `ReadFileContent`; include `Name`, `UserPrompt`.
@ -119,6 +125,9 @@ Images referenced via the `plugin://` scheme must exist in the plugin directory
| `FILE_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadFileContent.razor) | | `FILE_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadFileContent.razor) |
| `WEB_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadWebContent.razor) | | `WEB_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadWebContent.razor) |
| `COLOR_PICKER` | `Name`, `Label` | `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style` | [MudColorPicker](https://www.mudblazor.com/components/colorpicker) | | `COLOR_PICKER` | `Name`, `Label` | `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style` | [MudColorPicker](https://www.mudblazor.com/components/colorpicker) |
| `DATE_PICKER` | `Name`, `Label` | `Value`, `Placeholder`, `HelperText`, `DateFormat`, `PickerVariant`, `UserPrompt`, `Class`, `Style` | [MudDatePicker](https://www.mudblazor.com/components/datepicker) |
| `DATE_RANGE_PICKER` | `Name`, `Label` | `Value`, `PlaceholderStart`, `PlaceholderEnd`, `HelperText`, `DateFormat`, `PickerVariant`, `UserPrompt`, `Class`, `Style` | [MudDateRangePicker](https://www.mudblazor.com/components/daterangepicker) |
| `TIME_PICKER` | `Name`, `Label` | `Value`, `Placeholder`, `HelperText`, `TimeFormat`, `AmPm`, `PickerVariant`, `UserPrompt`, `Class`, `Style` | [MudTimePicker](https://www.mudblazor.com/components/timepicker) |
| `HEADING` | `Text` | `Level` | [MudText Typo="Typo.<h2\|h3\|h4\|h5\|h6>"](https://www.mudblazor.com/components/typography) | | `HEADING` | `Text` | `Level` | [MudText Typo="Typo.<h2\|h3\|h4\|h5\|h6>"](https://www.mudblazor.com/components/typography) |
| `TEXT` | `Content` | `None` | [MudText Typo="Typo.body1"](https://www.mudblazor.com/components/typography) | | `TEXT` | `Content` | `None` | [MudText Typo="Typo.body1"](https://www.mudblazor.com/components/typography) |
| `LIST` | `Type`, `Text` | `Href` | [MudList](https://www.mudblazor.com/componentss/list) | | `LIST` | `Type`, `Text` | `Href` | [MudList](https://www.mudblazor.com/componentss/list) |
@ -265,7 +274,7 @@ More information on rendered components can be found [here](https://www.mudblazo
- To update component state, return a table with a `fields` table. - To update component state, return a table with a `fields` table.
- `fields` keys must reference existing component `Name` values. - `fields` keys must reference existing component `Name` values.
- Supported write targets: - Supported write targets:
- `TEXT_AREA`, single-select `DROPDOWN`, `WEB_CONTENT_READER`, `FILE_CONTENT_READER`, `COLOR_PICKER`: string values - `TEXT_AREA`, single-select `DROPDOWN`, `WEB_CONTENT_READER`, `FILE_CONTENT_READER`, `COLOR_PICKER`, `DATE_PICKER`, `DATE_RANGE_PICKER`, `TIME_PICKER`: string values
- multiselect `DROPDOWN`: array-like Lua table of strings - multiselect `DROPDOWN`: array-like Lua table of strings
- `SWITCH`: boolean values - `SWITCH`: boolean values
- Unknown field names and wrong value types are ignored and logged. - Unknown field names and wrong value types are ignored and logged.
@ -470,6 +479,109 @@ More information on rendered components can be found [here](https://www.mudblazo
} }
``` ```
---
### `DATE_PICKER` reference
- Use `Type = "DATE_PICKER"` to render a MudBlazor date picker.
- Required props:
- `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`.
- `Label`: visible field label.
- Optional props:
- `Value`: initial date string. Use the same format as `DateFormat`; default recommendation is `yyyy-MM-dd`.
- `Placeholder`: hint text shown before a date is selected.
- `HelperText`: helper text rendered below the picker.
- `DateFormat`: output and parsing format; defaults to `yyyy-MM-dd`.
- `PickerVariant`: one of `Dialog`, `Inline`, `Static`; invalid or omitted values fall back to `Dialog`.
- `UserPrompt`: prompt context text for the selected date.
- `Class`, `Style`: forwarded to the rendered component for layout/styling.
#### Example DatePicker component
```lua
{
["Type"] = "DATE_PICKER",
["Props"] = {
["Name"] = "deadline",
["Label"] = "Deadline",
["Value"] = "2026-03-31",
["Placeholder"] = "YYYY-MM-DD",
["HelperText"] = "Pick the target completion date.",
["DateFormat"] = "yyyy-MM-dd",
["PickerVariant"] = "Dialog",
["UserPrompt"] = "Use this as the relevant deadline."
}
}
```
---
### `DATE_RANGE_PICKER` reference
- Use `Type = "DATE_RANGE_PICKER"` to render a MudBlazor date range picker.
- Required props:
- `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`.
- `Label`: visible field label.
- Optional props:
- `Value`: initial range string using `<start> - <end>`, for example `2026-03-01 - 2026-03-31`.
- `PlaceholderStart`: hint text for the start date input.
- `PlaceholderEnd`: hint text for the end date input.
- `HelperText`: helper text rendered below the picker.
- `DateFormat`: output and parsing format for both dates; defaults to `yyyy-MM-dd`.
- `PickerVariant`: one of `Dialog`, `Inline`, `Static`; invalid or omitted values fall back to `Dialog`.
- `UserPrompt`: prompt context text for the selected date range.
- `Class`, `Style`: forwarded to the rendered component for layout/styling.
#### Example DateRangePicker component
```lua
{
["Type"] = "DATE_RANGE_PICKER",
["Props"] = {
["Name"] = "travelWindow",
["Label"] = "Travel window",
["Value"] = "2026-06-01 - 2026-06-07",
["PlaceholderStart"] = "Start date",
["PlaceholderEnd"] = "End date",
["HelperText"] = "Select the full period.",
["DateFormat"] = "yyyy-MM-dd",
["PickerVariant"] = "Dialog",
["UserPrompt"] = "Use this as the allowed date range."
}
}
```
---
### `TIME_PICKER` reference
- Use `Type = "TIME_PICKER"` to render a MudBlazor time picker.
- Required props:
- `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`.
- `Label`: visible field label.
- Optional props:
- `Value`: initial time string. Use the same format as `TimeFormat`; default recommendations are `HH:mm` or `hh:mm tt`.
- `Placeholder`: hint text shown before a time is selected.
- `HelperText`: helper text rendered below the picker.
- `TimeFormat`: output and parsing format; defaults to `HH:mm`, or `hh:mm tt` when `AmPm = true`.
- `AmPm`: defaults to `false`; toggles 12-hour mode.
- `PickerVariant`: one of `Dialog`, `Inline`, `Static`; invalid or omitted values fall back to `Dialog`.
- `UserPrompt`: prompt context text for the selected time.
- `Class`, `Style`: forwarded to the rendered component for layout/styling.
#### Example TimePicker component
```lua
{
["Type"] = "TIME_PICKER",
["Props"] = {
["Name"] = "meetingTime",
["Label"] = "Meeting time",
["Value"] = "14:30",
["Placeholder"] = "HH:mm",
["HelperText"] = "Pick the preferred meeting time.",
["TimeFormat"] = "HH:mm",
["AmPm"] = false,
["PickerVariant"] = "Dialog",
["UserPrompt"] = "Use this as the preferred time."
}
}
```
## Prompt Assembly - UserPrompt Property ## Prompt Assembly - UserPrompt Property
Each component exposes a `UserPrompt` string. When the assistant runs, `AssistantDynamic` recursively iterates over the component tree and, for each component that has a prompt, emits: Each component exposes a `UserPrompt` string. When the assistant runs, `AssistantDynamic` recursively iterates over the component tree and, for each component that has a prompt, emits:
@ -481,7 +593,7 @@ user prompt:
<value extracted from the component> <value extracted from the component>
``` ```
For switches the “value” is the boolean `true/false`; for readers it is the fetched/selected content; for color pickers it is the selected color text (for example `#FFAA00` or `rgba(...)`, depending on the picker mode). Always provide a meaningful `UserPrompt` so the final concatenated prompt remains coherent from the LLMs perspective. For switches the “value” is the boolean `true/false`; for readers it is the fetched/selected content; for color pickers it is the selected color text (for example `#FFAA00` or `rgba(...)`, depending on the picker mode); for date and time pickers it is the formatted date, date range, or time string. Always provide a meaningful `UserPrompt` so the final concatenated prompt remains coherent from the LLMs perspective.
## Advanced Prompt Assembly - BuildPrompt() ## Advanced Prompt Assembly - BuildPrompt()
If you want full control over prompt composition, define `ASSISTANT.BuildPrompt` as a Lua function. When present, AI Studio calls it and uses its return value as the final user prompt. The default prompt assembly is skipped. If you want full control over prompt composition, define `ASSISTANT.BuildPrompt` as a Lua function. When present, AI Studio calls it and uses its return value as the final user prompt. The default prompt assembly is skipped.
@ -499,8 +611,11 @@ The function receives a single `input` Lua table with:
- Multiselect dropdown is an array-like Lua table of strings - Multiselect dropdown is an array-like Lua table of strings
- Switch is a boolean - Switch is a boolean
- Color picker is the selected color as a string - Color picker is the selected color as a string
- Date picker is the selected date as a string
- Date range picker is the selected range as a single string in `<start> - <end>` format
- Time picker is the selected time as a string
- `input.meta`: per-component metadata keyed by component `Name` - `input.meta`: per-component metadata keyed by component `Name`
- `Type` (string, e.g. `TEXT_AREA`, `DROPDOWN`, `SWITCH`, `COLOR_PICKER`) - `Type` (string, e.g. `TEXT_AREA`, `DROPDOWN`, `SWITCH`, `COLOR_PICKER`, `DATE_PICKER`, `DATE_RANGE_PICKER`, `TIME_PICKER`)
- `Label` (string, when provided) - `Label` (string, when provided)
- `UserPrompt` (string, when provided) - `UserPrompt` (string, when provided)
- `input.profile`: selected profile data - `input.profile`: selected profile data
@ -514,7 +629,7 @@ input = {
}, },
meta = { meta = {
["<Name>"] = { ["<Name>"] = {
Type = "<TEXT_AREA|DROPDOWN|SWITCH|WEB_CONTENT_READER|FILE_CONTENT_READER|COLOR_PICKER>", Type = "<TEXT_AREA|DROPDOWN|SWITCH|WEB_CONTENT_READER|FILE_CONTENT_READER|COLOR_PICKER|DATE_PICKER|DATE_RANGE_PICKER|TIME_PICKER>",
Label = "<string?>", Label = "<string?>",
UserPrompt = "<string?>" UserPrompt = "<string?>"
}, },
@ -559,6 +674,12 @@ ASSISTANT.BuildPrompt = function(input)
table.insert(parts, name .. ": " .. tostring(value)) table.insert(parts, name .. ": " .. tostring(value))
elseif meta.Type == "COLOR_PICKER" and value and value ~= "" then elseif meta.Type == "COLOR_PICKER" and value and value ~= "" then
table.insert(parts, name .. ": " .. value) table.insert(parts, name .. ": " .. value)
elseif meta.Type == "DATE_PICKER" and value and value ~= "" then
table.insert(parts, name .. ": " .. value)
elseif meta.Type == "DATE_RANGE_PICKER" and value and value ~= "" then
table.insert(parts, name .. ": " .. value)
elseif meta.Type == "TIME_PICKER" and value and value ~= "" then
table.insert(parts, name .. ": " .. value)
elseif value and value ~= "" then elseif value and value ~= "" then
table.insert(parts, name .. ": " .. value) table.insert(parts, name .. ": " .. value)
end end
@ -875,6 +996,10 @@ Use `LAYOUT_ACCORDION` as the outer wrapper and put the actual content into one
- [Bitwise Operations Library](https://www.lua.org/manual/5.2/manual.html#6.7) - [Bitwise Operations Library](https://www.lua.org/manual/5.2/manual.html#6.7)
--- ---
> **Warning:** some common lua functions might not be available in this lua environment. Examples are:
> 1. `tostring()`
> 2. `pairs()`\\`ipairs()`
### Logging helpers ### Logging helpers
The assistant runtime exposes basic logging helpers to Lua. Use them to debug custom prompt building. The assistant runtime exposes basic logging helpers to Lua. Use them to debug custom prompt building.

View File

@ -343,6 +343,53 @@ ASSISTANT = {
["UserPrompt"] = "<help text reminding the user what kind of file they should load>", ["UserPrompt"] = "<help text reminding the user what kind of file they should load>",
} }
}, },
{
["Type"] = "DATE_PICKER",
["Props"] = {
["Name"] = "<unique identifier of this component>", -- required
["Label"] = "<heading of your component>", -- required
["Value"] = "2026-03-16", -- optional initial value
["Placeholder"] = "YYYY-MM-DD",
["HelperText"] = "<optional help text rendered under the picker>",
["DateFormat"] = "yyyy-MM-dd",
["PickerVariant"] = "<Dialog|Inline|Static>",
["UserPrompt"] = "<prompt context for the selected date>",
["Class"] = "<optional MudBlazor or css classes>",
["Style"] = "<optional css styles>",
}
},
{
["Type"] = "DATE_RANGE_PICKER",
["Props"] = {
["Name"] = "<unique identifier of this component>", -- required
["Label"] = "<heading of your component>", -- required
["Value"] = "2026-03-16 - 2026-03-20", -- optional initial range
["PlaceholderStart"] = "Start date",
["PlaceholderEnd"] = "End date",
["HelperText"] = "<optional help text rendered under the picker>",
["DateFormat"] = "yyyy-MM-dd",
["PickerVariant"] = "<Dialog|Inline|Static>",
["UserPrompt"] = "<prompt context for the selected date range>",
["Class"] = "<optional MudBlazor or css classes>",
["Style"] = "<optional css styles>",
}
},
{
["Type"] = "TIME_PICKER",
["Props"] = {
["Name"] = "<unique identifier of this component>", -- required
["Label"] = "<heading of your component>", -- required
["Value"] = "14:30", -- optional initial time
["Placeholder"] = "HH:mm",
["HelperText"] = "<optional help text rendered under the picker>",
["TimeFormat"] = "HH:mm",
["AmPm"] = false,
["PickerVariant"] = "<Dialog|Inline|Static>",
["UserPrompt"] = "<prompt context for the selected time>",
["Class"] = "<optional MudBlazor or css classes>",
["Style"] = "<optional css styles>",
}
},
} }
}, },
} }

View File

@ -44,6 +44,12 @@ public class AssistantComponentFactory
return new AssistantImage { Props = props, Children = children }; return new AssistantImage { Props = props, Children = children };
case AssistantComponentType.COLOR_PICKER: case AssistantComponentType.COLOR_PICKER:
return new AssistantColorPicker { Props = props, Children = children }; return new AssistantColorPicker { Props = props, Children = children };
case AssistantComponentType.DATE_PICKER:
return new AssistantDatePicker { Props = props, Children = children };
case AssistantComponentType.DATE_RANGE_PICKER:
return new AssistantDateRangePicker { Props = props, Children = children };
case AssistantComponentType.TIME_PICKER:
return new AssistantTimePicker { Props = props, Children = children };
case AssistantComponentType.LAYOUT_ITEM: case AssistantComponentType.LAYOUT_ITEM:
return new AssistantItem { Props = props, Children = children }; return new AssistantItem { Props = props, Children = children };
case AssistantComponentType.LAYOUT_GRID: case AssistantComponentType.LAYOUT_GRID:

View File

@ -72,4 +72,5 @@ internal static class AssistantComponentPropHelper
public static Wrap? GetWrap(string value) => Enum.TryParse<Wrap>(value, out var wrap) ? wrap : null; public static Wrap? GetWrap(string value) => Enum.TryParse<Wrap>(value, out var wrap) ? wrap : null;
public static StretchItems? GetStretching(string value) => Enum.TryParse<StretchItems>(value, out var stretch) ? stretch : null; public static StretchItems? GetStretching(string value) => Enum.TryParse<StretchItems>(value, out var stretch) ? stretch : null;
public static Breakpoint GetBreakpoint(string value, Breakpoint fallback) => Enum.TryParse<Breakpoint>(value, out var breakpoint) ? breakpoint : fallback; public static Breakpoint GetBreakpoint(string value, Breakpoint fallback) => Enum.TryParse<Breakpoint>(value, out var breakpoint) ? breakpoint : fallback;
public static PickerVariant GetPickerVariant(string pickerValue, PickerVariant fallback) => Enum.TryParse<PickerVariant>(pickerValue, out var variant) ? variant : fallback;
} }

View File

@ -17,6 +17,9 @@ public enum AssistantComponentType
FILE_CONTENT_READER, FILE_CONTENT_READER,
IMAGE, IMAGE,
COLOR_PICKER, COLOR_PICKER,
DATE_PICKER,
DATE_RANGE_PICKER,
TIME_PICKER,
LAYOUT_ITEM, LAYOUT_ITEM,
LAYOUT_GRID, LAYOUT_GRID,
LAYOUT_PAPER, LAYOUT_PAPER,

View File

@ -0,0 +1,70 @@
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
internal sealed class AssistantDatePicker : AssistantComponentBase
{
public override AssistantComponentType Type => AssistantComponentType.DATE_PICKER;
public override Dictionary<string, object> Props { get; set; } = new();
public override List<IAssistantComponent> Children { get; set; } = new();
public string Name
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Name));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Name), value);
}
public string Label
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Label));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Label), value);
}
public string Value
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Value));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Value), value);
}
public string Placeholder
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Placeholder));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Placeholder), value);
}
public string HelperText
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.HelperText));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.HelperText), value);
}
public string DateFormat
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.DateFormat));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.DateFormat), value);
}
public string PickerVariant
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.PickerVariant));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.PickerVariant), value);
}
public string UserPrompt
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.UserPrompt));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.UserPrompt), value);
}
public string Class
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Class));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Class), value);
}
public string Style
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value);
}
public string GetDateFormat() => string.IsNullOrWhiteSpace(this.DateFormat) ? "yyyy-MM-dd" : this.DateFormat;
}

View File

@ -0,0 +1,76 @@
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
internal sealed class AssistantDateRangePicker : AssistantComponentBase
{
public override AssistantComponentType Type => AssistantComponentType.DATE_RANGE_PICKER;
public override Dictionary<string, object> Props { get; set; } = new();
public override List<IAssistantComponent> Children { get; set; } = new();
public string Name
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Name));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Name), value);
}
public string Label
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Label));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Label), value);
}
public string Value
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Value));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Value), value);
}
public string PlaceholderStart
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.PlaceholderStart));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.PlaceholderStart), value);
}
public string PlaceholderEnd
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.PlaceholderEnd));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.PlaceholderEnd), value);
}
public string HelperText
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.HelperText));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.HelperText), value);
}
public string DateFormat
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.DateFormat));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.DateFormat), value);
}
public string PickerVariant
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.PickerVariant));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.PickerVariant), value);
}
public string UserPrompt
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.UserPrompt));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.UserPrompt), value);
}
public string Class
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Class));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Class), value);
}
public string Style
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value);
}
public string GetDateFormat() => string.IsNullOrWhiteSpace(this.DateFormat) ? "yyyy-MM-dd" : this.DateFormat;
}

View File

@ -0,0 +1,82 @@
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
internal sealed class AssistantTimePicker : AssistantComponentBase
{
public override AssistantComponentType Type => AssistantComponentType.TIME_PICKER;
public override Dictionary<string, object> Props { get; set; } = new();
public override List<IAssistantComponent> Children { get; set; } = new();
public string Name
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Name));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Name), value);
}
public string Label
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Label));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Label), value);
}
public string Value
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Value));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Value), value);
}
public string Placeholder
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Placeholder));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Placeholder), value);
}
public string HelperText
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.HelperText));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.HelperText), value);
}
public string TimeFormat
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.TimeFormat));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.TimeFormat), value);
}
public bool AmPm
{
get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.AmPm), false);
set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.AmPm), value);
}
public string PickerVariant
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.PickerVariant));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.PickerVariant), value);
}
public string UserPrompt
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.UserPrompt));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.UserPrompt), value);
}
public string Class
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Class));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Class), value);
}
public string Style
{
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value);
}
public string GetTimeFormat()
{
if (!string.IsNullOrWhiteSpace(this.TimeFormat))
return this.TimeFormat;
return this.AmPm ? "hh:mm tt" : "HH:mm";
}
}

View File

@ -81,6 +81,27 @@ public static class ComponentPropSpecs
"PickerVariant", "UserPrompt", "Class", "Style" "PickerVariant", "UserPrompt", "Class", "Style"
] ]
), ),
[AssistantComponentType.DATE_PICKER] = new(
required: ["Name", "Label"],
optional: [
"Value", "Placeholder", "HelperText", "DateFormat",
"PickerVariant", "UserPrompt", "Class", "Style"
]
),
[AssistantComponentType.DATE_RANGE_PICKER] = new(
required: ["Name", "Label"],
optional: [
"Value", "PlaceholderStart", "PlaceholderEnd", "HelperText", "DateFormat",
"PickerVariant", "UserPrompt", "Class", "Style"
]
),
[AssistantComponentType.TIME_PICKER] = new(
required: ["Name", "Label"],
optional: [
"Value", "Placeholder", "HelperText", "TimeFormat", "AmPm",
"PickerVariant", "UserPrompt", "Class", "Style"
]
),
[AssistantComponentType.LAYOUT_ITEM] = new( [AssistantComponentType.LAYOUT_ITEM] = new(
required: ["Name"], required: ["Name"],
optional: ["Xs", "Sm", "Md", "Lg", "Xl", "Xxl", "Class", "Style"] optional: ["Xs", "Sm", "Md", "Lg", "Xl", "Xxl", "Class", "Style"]