diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor index cacd77d2..dc1f27e8 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor @@ -403,6 +403,70 @@ else } break; + case AssistantComponentType.DATE_PICKER: + if (component is AssistantDatePicker assistantDatePicker) + { + var datePicker = assistantDatePicker; + var format = datePicker.GetDateFormat(); + + + + + } + break; + case AssistantComponentType.DATE_RANGE_PICKER: + if (component is AssistantDateRangePicker assistantDateRangePicker) + { + var dateRangePicker = assistantDateRangePicker; + var format = dateRangePicker.GetDateFormat(); + + + + + } + break; + case AssistantComponentType.TIME_PICKER: + if (component is AssistantTimePicker assistantTimePicker) + { + var timePicker = assistantTimePicker; + var format = timePicker.GetTimeFormat(); + + + + + } + break; } ; diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs index fa193f4f..d576df94 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs @@ -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 private readonly Dictionary webContentFields = new(); private readonly Dictionary fileContentFields = new(); private readonly Dictionary colorPickerFields = new(); + private readonly Dictionary datePickerFields = new(); + private readonly Dictionary dateRangePickerFields = new(); + private readonly Dictionary timePickerFields = new(); private readonly Dictionary imageCache = new(); private readonly HashSet executingButtonActions = []; private readonly HashSet 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 { 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 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 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 return; } + if (this.datePickerFields.ContainsKey(fieldName)) + { + if (value.TryRead(out var dateValue)) + this.datePickerFields[fieldName] = dateValue ?? string.Empty; + else + this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType); + return; + } + + if (this.dateRangePickerFields.ContainsKey(fieldName)) + { + if (value.TryRead(out var dateRangeValue)) + this.dateRangePickerFields[fieldName] = dateRangeValue ?? string.Empty; + else + this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType); + return; + } + + if (this.timePickerFields.ContainsKey(fieldName)) + { + if (value.TryRead(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(out var webContentValue)) @@ -549,6 +613,15 @@ public partial class AssistantDynamic : AssistantBaseCore 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 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 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); + } + } } diff --git a/app/MindWork AI Studio/Plugins/assistants/README.md b/app/MindWork AI Studio/Plugins/assistants/README.md index af0cb8b3..d985098e 100644 --- a/app/MindWork AI Studio/Plugins/assistants/README.md +++ b/app/MindWork AI Studio/Plugins/assistants/README.md @@ -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."](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 ` - `, 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: ``` -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 LLM’s 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 LLM’s 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 ` - ` 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 = { [""] = { - Type = "", + Type = "", Label = "", UserPrompt = "" }, @@ -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. diff --git a/app/MindWork AI Studio/Plugins/assistants/plugin.lua b/app/MindWork AI Studio/Plugins/assistants/plugin.lua index 94681438..14097de6 100644 --- a/app/MindWork AI Studio/Plugins/assistants/plugin.lua +++ b/app/MindWork AI Studio/Plugins/assistants/plugin.lua @@ -343,6 +343,53 @@ ASSISTANT = { ["UserPrompt"] = "", } }, + { + ["Type"] = "DATE_PICKER", + ["Props"] = { + ["Name"] = "", -- required + ["Label"] = "", -- required + ["Value"] = "2026-03-16", -- optional initial value + ["Placeholder"] = "YYYY-MM-DD", + ["HelperText"] = "", + ["DateFormat"] = "yyyy-MM-dd", + ["PickerVariant"] = "", + ["UserPrompt"] = "", + ["Class"] = "", + ["Style"] = "", + } + }, + { + ["Type"] = "DATE_RANGE_PICKER", + ["Props"] = { + ["Name"] = "", -- required + ["Label"] = "", -- required + ["Value"] = "2026-03-16 - 2026-03-20", -- optional initial range + ["PlaceholderStart"] = "Start date", + ["PlaceholderEnd"] = "End date", + ["HelperText"] = "", + ["DateFormat"] = "yyyy-MM-dd", + ["PickerVariant"] = "", + ["UserPrompt"] = "", + ["Class"] = "", + ["Style"] = "", + } + }, + { + ["Type"] = "TIME_PICKER", + ["Props"] = { + ["Name"] = "", -- required + ["Label"] = "", -- required + ["Value"] = "14:30", -- optional initial time + ["Placeholder"] = "HH:mm", + ["HelperText"] = "", + ["TimeFormat"] = "HH:mm", + ["AmPm"] = false, + ["PickerVariant"] = "", + ["UserPrompt"] = "", + ["Class"] = "", + ["Style"] = "", + } + }, } }, } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantComponentFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantComponentFactory.cs index 12f15968..73366af2 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantComponentFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/AssistantComponentFactory.cs @@ -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: diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentPropHelper.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentPropHelper.cs index 969d5771..758dedd7 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentPropHelper.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentPropHelper.cs @@ -72,4 +72,5 @@ internal static class AssistantComponentPropHelper public static Wrap? GetWrap(string value) => Enum.TryParse(value, out var wrap) ? wrap : null; public static StretchItems? GetStretching(string value) => Enum.TryParse(value, out var stretch) ? stretch : null; public static Breakpoint GetBreakpoint(string value, Breakpoint fallback) => Enum.TryParse(value, out var breakpoint) ? breakpoint : fallback; + public static PickerVariant GetPickerVariant(string pickerValue, PickerVariant fallback) => Enum.TryParse(pickerValue, out var variant) ? variant : fallback; } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentType.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentType.cs index bc9ae0ca..f65a2a92 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentType.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantComponentType.cs @@ -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, diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantDatePicker.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantDatePicker.cs new file mode 100644 index 00000000..d5d12d11 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantDatePicker.cs @@ -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 Props { get; set; } = new(); + public override List 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; +} diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantDateRangePicker.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantDateRangePicker.cs new file mode 100644 index 00000000..13470a89 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantDateRangePicker.cs @@ -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 Props { get; set; } = new(); + public override List 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; +} diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTimePicker.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTimePicker.cs new file mode 100644 index 00000000..f261a203 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTimePicker.cs @@ -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 Props { get; set; } = new(); + public override List 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"; + } +} diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs index 00247bff..41506895 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs @@ -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"]