diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor index dcf10de3..4553ebc2 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor @@ -12,7 +12,27 @@ if (component is AssistantTextArea textArea) { var lines = textArea.IsSingleLine ? 1 : 6; - + + } break; case AssistantComponentType.IMAGE: @@ -139,7 +159,7 @@ if (component is AssistantColorPicker assistantColorPicker) { var colorPicker = assistantColorPicker; - var variant = this.GetPickerVariant(colorPicker.PickerVariant); + var variant = colorPicker.GetPickerVariant(); var elevation = variant == PickerVariant.Static ? 6 : 0; var rounded = variant == PickerVariant.Static; diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs index bb206e48..7c302583 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs @@ -441,14 +441,6 @@ public partial class AssistantDynamic : AssistantBaseCore private string? GetOptionalStyle(string? style) => string.IsNullOrWhiteSpace(style) ? null : style; - private PickerVariant GetPickerVariant(string pickerVariant) => pickerVariant.ToLowerInvariant() switch - { - "dialog" => PickerVariant.Dialog, - "static" => PickerVariant.Static, - "inline" => PickerVariant.Inline, - _ => PickerVariant.Static - }; - private string? ValidateProfileSelection(AssistantProfileSelection profileSelection, Profile? profile) { if (profile == default || profile == Profile.NO_PROFILE) diff --git a/app/MindWork AI Studio/Plugins/assistants/README.md b/app/MindWork AI Studio/Plugins/assistants/README.md index aa3e993b..2c10cd2a 100644 --- a/app/MindWork AI Studio/Plugins/assistants/README.md +++ b/app/MindWork AI Studio/Plugins/assistants/README.md @@ -9,7 +9,7 @@ This folder keeps the Lua manifest (`plugin.lua`) that defines a custom assistan Supported types (matching the Blazor UI components): -- `TEXT_AREA`: any user input field with `Name`, `Label`, `UserPrompt`, `PrefillText`, `IsSingleLine`, `ReadOnly`. +- `TEXT_AREA`: user input field based on `MudTextField`; requires `Name`, `Label`, and may include `HelperText`, `HelperTextOnFocus`, `Adornment`, `AdornmentIcon`, `AdornmentText`, `AdornmentColor`, `Counter`, `MaxLength`, `IsImmediate`, `UserPrompt`, `PrefillText`, `IsSingleLine`, `ReadOnly`, `Class`, `Style`. - `DROPDOWN`: selects between variants; `Props` must include `Name`, `Label`, `Default`, `Items`, and optionally `ValueType` plus `UserPrompt`. - `SWITCH`: boolean option; requires `Name`, `Label`, `Value`, `LabelOn`, `LabelOff`, and may include `UserPrompt`. - `COLOR_PICKER`: color input based on `MudColorPicker`; requires `Name`, `Label`, and may include `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style`. @@ -115,6 +115,48 @@ ASSISTANT.BuildPrompt = function(input) end ``` +### `TEXT_AREA` reference +- Use `Type = "TEXT_AREA"` to render a MudBlazor text input or textarea. +- Required props: + - `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`. + - `Label`: visible field label. +- Optional props: + - `HelperText`: helper text rendered below the input. + - `HelperTextOnFocus`: defaults to `false`; show helper text only while the field is focused. + - `Adornment`: one of `Start`, `End`, `None`; invalid or omitted values fall back to `Start`. + - `AdornmentIcon`: MudBlazor icon identifier string for the adornment. + - `AdornmentText`: plain adornment text. Do not set this together with `AdornmentIcon`. + - `AdornmentColor`: one of the MudBlazor `Color` enum names such as `Primary`, `Secondary`, `Warning`; invalid or omitted values fall back to `Default`. + - `Counter`: nullable integer. Omit it to hide the counter entirely. Set `0` to show only the current character count. Set `1` or higher to show `current/max`. + - `MaxLength`: maximum number of characters allowed; defaults to `524288`. + - `IsImmediate`: defaults to `false`; updates the bound value on each input event instead of on blur/change. + - `UserPrompt`: prompt context text for this field. + - `PrefillText`: initial input value. + - `IsSingleLine`: defaults to `false`; render as a one-line input instead of a textarea. + - `ReadOnly`: defaults to `false`; disables editing. + - `Class`, `Style`: forwarded to the rendered component for layout/styling. + +Example: +```lua +{ + ["Type"] = "TEXT_AREA", + ["Props"] = { + ["Name"] = "Budget", + ["Label"] = "Budget", + ["HelperText"] = "Enter the expected amount.", + ["Adornment"] = "Start", + ["AdornmentIcon"] = "Icons.Material.Filled.AttachMoney", + ["AdornmentColor"] = "Success", + ["Counter"] = 0, + ["MaxLength"] = 100, + ["IsImmediate"] = true, + ["UserPrompt"] = "Use this budget information in your answer.", + ["PrefillText"] = "", + ["IsSingleLine"] = true + } +} +``` + ### `COLOR_PICKER` reference - Use `Type = "COLOR_PICKER"` to render a MudBlazor color picker. - Required props: diff --git a/app/MindWork AI Studio/Plugins/assistants/plugin.lua b/app/MindWork AI Studio/Plugins/assistants/plugin.lua index 35b3133f..86ff9300 100644 --- a/app/MindWork AI Studio/Plugins/assistants/plugin.lua +++ b/app/MindWork AI Studio/Plugins/assistants/plugin.lua @@ -70,10 +70,21 @@ ASSISTANT = { ["Props"] = { ["Name"] = "", -- required ["Label"] = "", -- required + ["Adornment"] = "", -- location of the `AdornmentIcon` OR `AdornmentText`; CASE SENSITIV + ["AdornmentIcon"] = "Icons.Material.Filled.AppSettingsAlt", -- The Mudblazor icon displayed for the adornment + ["AdornmentText"] = "", -- The text displayed for the adornment + ["AdornmentColor"] = "", -- the color of AdornmentText or AdornmentIcon; CASE SENSITIVE + ["Counter"] = 0, -- shows a character counter. When 0, the current character count is displayed. When 1 or greater, the character count and this count are displayed. Defaults to `null` + ["MaxLength"] = 100, -- max number of characters allowed, prevents more input characters; use together with the character counter. Defaults to 524,288 + ["HelperText"] = "", + ["IsImmediate"] = false, -- changes the value as soon as input is received. Defaults to false but will be true if counter or maxlength is set to reflect changes + ["HelperTextOnFocus"] = true, -- if true, shows the helping text only when the user focuses on the text area ["UserPrompt"] = "", ["PrefillText"] = "", ["IsSingleLine"] = false, -- if true, shows a text field instead of an area - ["ReadOnly"] = false -- if true, deactivates user input (make sure to provide a PrefillText) + ["ReadOnly"] = false, -- if true, deactivates user input (make sure to provide a PrefillText) + ["Class"] = "", + ["Style"] = "", } }, { @@ -177,7 +188,7 @@ ASSISTANT = { ["ShowAlpha"] = true, -- weather alpha channels are shown ["ShowToolbar"] = true, -- weather the toolbar to toggle between picker, grid or palette is shown ["ShowModeSwitch"] = true, -- weather switch to toggle between RGB(A), HEX or HSL color mode is shown - ["PickerVariant"] = "", + ["PickerVariant"] = "", -- different rendering modes: `Dialog` opens the picker in a modal type screen, `Inline` shows the picker next to the input field and `Static` renders the picker widget directly (default); Case sensitiv ["UserPrompt"] = "", } }, diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantColorPicker.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantColorPicker.cs index 6a7dd7b8..6a8680f7 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantColorPicker.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantColorPicker.cs @@ -64,4 +64,6 @@ internal sealed class AssistantColorPicker : AssistantComponentBase get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style)); set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value); } + + public PickerVariant GetPickerVariant() => Enum.TryParse(this.PickerVariant, out var variant) ? variant : MudBlazor.PickerVariant.Static; } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantSwitch.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantSwitch.cs index bd70f37c..d07b94ac 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantSwitch.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantSwitch.cs @@ -20,8 +20,8 @@ internal sealed class AssistantSwitch : AssistantComponentBase public bool Value { - get => this.Props.TryGetValue(nameof(this.Value), out var val) && val is true; - set => this.Props[nameof(this.Value)] = value; + get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.Value), false); + set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.Value), value); } public string UserPrompt diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs index 0d5177ea..09fb3b5b 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantTextArea.cs @@ -17,6 +17,42 @@ internal sealed class AssistantTextArea : AssistantComponentBase get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Label)); set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Label), value); } + + public string HelperText + { + get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.HelperText)); + set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.HelperText), value); + } + + public bool HelperTextOnFocus + { + get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.HelperTextOnFocus), false); + set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.HelperTextOnFocus), value); + } + + public string Adornment + { + get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Adornment)); + set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Adornment), value); + } + + public string AdornmentIcon + { + get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.AdornmentIcon)); + set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.AdornmentIcon), value); + } + + public string AdornmentText + { + get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.AdornmentText)); + set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.AdornmentText), value); + } + + public string AdornmentColor + { + get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.AdornmentColor)); + set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.AdornmentColor), value); + } public string UserPrompt { @@ -30,16 +66,34 @@ internal sealed class AssistantTextArea : AssistantComponentBase set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.PrefillText), value); } + public int? Counter + { + get => AssistantComponentPropHelper.ReadNullableInt(this.Props, nameof(this.Counter)); + set => AssistantComponentPropHelper.WriteNullableInt(this.Props, nameof(this.Counter), value); + } + + public int MaxLength + { + get => AssistantComponentPropHelper.ReadInt(this.Props, nameof(this.MaxLength), PluginAssistants.TEXT_AREA_MAX_VALUE); + set => AssistantComponentPropHelper.WriteInt(this.Props, nameof(this.MaxLength), value); + } + + public bool IsImmediate + { + get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.IsImmediate)); + set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.IsImmediate), value); + } + public bool IsSingleLine { - get => this.Props.TryGetValue(nameof(this.IsSingleLine), out var val) && val is true; - set => this.Props[nameof(this.IsSingleLine)] = value; + get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.IsSingleLine), false); + set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.IsSingleLine), value); } public bool ReadOnly { - get => this.Props.TryGetValue(nameof(this.ReadOnly), out var val) && val is true; - set => this.Props[nameof(this.ReadOnly)] = value; + get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.ReadOnly), false); + set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.ReadOnly), value); } public string Class @@ -53,4 +107,8 @@ internal sealed class AssistantTextArea : AssistantComponentBase get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style)); set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value); } + + public Adornment GetAdornmentPos() => Enum.TryParse(this.Adornment, out var position) ? position : MudBlazor.Adornment.Start; + + public Color GetAdornmentColor() => Enum.TryParse(this.AdornmentColor, out var color) ? color : Color.Default; } 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 c3e94f6c..4cdbb8c4 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs @@ -11,7 +11,11 @@ public static class ComponentPropSpecs ), [AssistantComponentType.TEXT_AREA] = new( required: ["Name", "Label"], - optional: ["UserPrompt", "PrefillText", "ReadOnly", "IsSingleLine", "Class", "Style"] + optional: [ + "HelperText", "HelperTextOnFocus", "UserPrompt", "PrefillText", + "ReadOnly", "IsSingleLine", "Counter", "MaxLength", "IsImmediate", + "Adornment", "AdornmentIcon", "AdornmentText", "AdornmentColor", "Class", "Style", + ] ), [AssistantComponentType.BUTTON] = new( required: ["Name", "Text", "Action"], diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs index 1f6eaf71..7e8f6997 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs @@ -17,6 +17,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType public bool AllowProfiles { get; private set; } = true; public bool HasEmbeddedProfileSelection { get; private set; } public bool HasCustomPromptBuilder => this.buildPromptFunction is not null; + public const int TEXT_AREA_MAX_VALUE = 524288; private LuaFunction? buildPromptFunction; @@ -240,13 +241,29 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType } component = AssistantComponentFactory.CreateComponent(type, props, children); + + if (component is AssistantTextArea textArea) + { + if (!string.IsNullOrWhiteSpace(textArea.AdornmentIcon) && !string.IsNullOrWhiteSpace(textArea.AdornmentText)) + LOGGER.LogWarning($"Assistant plugin '{this.Name}' TEXT_AREA '{textArea.Name}' defines both '[\"AdornmentIcon\"]' and '[\"AdornmentText\"]', thus both will be ignored by the renderer. You`re only allowed to use either one of them."); + + if (textArea.MaxLength == 0) + { + LOGGER.LogWarning($"Assistant plugin '{this.Name}' TEXT_AREA '{textArea.Name}' defines a MaxLength of `0`. This is not applicable, if you want a readonly Textfield, set the [\"ReadOnly\"] field to `true`. MAXLENGTH IS SET TO DEFAULT {TEXT_AREA_MAX_VALUE}."); + textArea.MaxLength = TEXT_AREA_MAX_VALUE; + } + + if (textArea.MaxLength != 0 && textArea.MaxLength != TEXT_AREA_MAX_VALUE) + textArea.Counter = textArea.MaxLength; + + if (textArea.Counter != null) + textArea.IsImmediate = true; + } + return true; } - private bool TryReadComponentProps( - AssistantComponentType type, - LuaTable propsTable, - out Dictionary props) + private bool TryReadComponentProps(AssistantComponentType type, LuaTable propsTable, out Dictionary props) { props = new Dictionary();