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>
}
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>;

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@ -45,12 +46,18 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
private readonly Dictionary<string, WebContentState> webContentFields = new();
private readonly Dictionary<string, FileContentState> fileContentFields = 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 HashSet<string> executingButtonActions = [];
private readonly HashSet<string> executingSwitchActions = [];
private string pluginPath = string.Empty;
private const string PLUGIN_SCHEME = "plugin://";
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()
{
@ -136,6 +143,18 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
{
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()
@ -229,6 +248,12 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
fields[entry.Key] = entry.Value.Content ?? string.Empty;
foreach (var entry in this.colorPickerFields)
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;
@ -325,6 +350,18 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
if (component is AssistantColorPicker assistantColorPicker && !this.colorPickerFields.ContainsKey(assistantColorPicker.Name))
this.colorPickerFields.Add(assistantColorPicker.Name, assistantColorPicker.Placeholder);
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)
@ -473,6 +510,33 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
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 (value.TryRead<string>(out var webContentValue))
@ -549,6 +613,15 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
case AssistantColorPicker colorPicker:
this.AddMetaEntry(meta, colorPicker.Name, component.Type, colorPicker.Label, colorPicker.UserPrompt);
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)
@ -623,6 +696,30 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
prompt += $"user prompt:{Environment.NewLine}{userInput}";
}
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)
@ -663,4 +760,124 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
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)
- [`SWITCH` reference](#switch-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)
- [Advanced Prompt Assembly - BuildPrompt()](#advanced-prompt-assembly---buildprompt)
- [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`.
- `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`.
- `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.
- `WEB_CONTENT_READER`: renders `ReadWebContent`; include `Name`, `UserPrompt`, `Preselect`, `PreselectContentCleanerAgent`.
- `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) |
| `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) |
| `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) |
| `TEXT` | `Content` | `None` | [MudText Typo="Typo.body1"](https://www.mudblazor.com/components/typography) |
| `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.
- `fields` keys must reference existing component `Name` values.
- 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
- `SWITCH`: boolean values
- 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
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>
```
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()
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
- Switch is a boolean
- 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`
- `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)
- `UserPrompt` (string, when provided)
- `input.profile`: selected profile data
@ -514,7 +629,7 @@ input = {
},
meta = {
["<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?>",
UserPrompt = "<string?>"
},
@ -559,6 +674,12 @@ ASSISTANT.BuildPrompt = function(input)
table.insert(parts, name .. ": " .. tostring(value))
elseif meta.Type == "COLOR_PICKER" and value and value ~= "" then
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
table.insert(parts, name .. ": " .. value)
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)
---
> **Warning:** some common lua functions might not be available in this lua environment. Examples are:
> 1. `tostring()`
> 2. `pairs()`\\`ipairs()`
### Logging helpers
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>",
}
},
{
["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 };
case AssistantComponentType.COLOR_PICKER:
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:
return new AssistantItem { Props = props, Children = children };
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 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 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,
IMAGE,
COLOR_PICKER,
DATE_PICKER,
DATE_RANGE_PICKER,
TIME_PICKER,
LAYOUT_ITEM,
LAYOUT_GRID,
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"
]
),
[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(
required: ["Name"],
optional: ["Xs", "Sm", "Md", "Lg", "Xl", "Xxl", "Class", "Style"]