overhauled the textarea component to be as feature complete as possible

This commit is contained in:
krut_ni 2026-03-09 18:56:38 +01:00
parent 867890a46f
commit 9dba7f415e
No known key found for this signature in database
GPG Key ID: A5C0151B4DDB172C
9 changed files with 170 additions and 24 deletions

View File

@ -12,7 +12,27 @@
if (component is AssistantTextArea textArea) if (component is AssistantTextArea textArea)
{ {
var lines = textArea.IsSingleLine ? 1 : 6; var lines = textArea.IsSingleLine ? 1 : 6;
<MudTextField T="string" @bind-Text="@this.inputFields[textArea.Name]" Label="@textArea.Label" ReadOnly="@textArea.ReadOnly" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Variant="Variant.Outlined" Lines="@lines" AutoGrow="@true" MaxLines="12" Class='@MergeClass(textArea.Class, "mb-3")' Style="@this.GetOptionalStyle(textArea.Style)"/>
<MudTextField T="string"
@bind-Text="@this.inputFields[textArea.Name]"
Label="@textArea.Label"
HelperText="@textArea.HelperText"
HelperTextOnFocus="@textArea.HelperTextOnFocus"
ReadOnly="@textArea.ReadOnly"
Counter="@textArea.Counter"
MaxLength="@textArea.MaxLength"
Immediate="@textArea.IsImmediate"
Adornment="@textArea.GetAdornmentPos()"
AdornmentIcon="@textArea.AdornmentIcon"
AdornmentText="@textArea.AdornmentText"
AdornmentColor="@textArea.GetAdornmentColor()"
Variant="Variant.Outlined"
Lines="@lines"
AutoGrow="@true"
MaxLines="12"
Class='@MergeClass(textArea.Class, "mb-3")'
Style="@this.GetOptionalStyle(textArea.Style)"
/>
} }
break; break;
case AssistantComponentType.IMAGE: case AssistantComponentType.IMAGE:
@ -139,7 +159,7 @@
if (component is AssistantColorPicker assistantColorPicker) if (component is AssistantColorPicker assistantColorPicker)
{ {
var colorPicker = assistantColorPicker; var colorPicker = assistantColorPicker;
var variant = this.GetPickerVariant(colorPicker.PickerVariant); var variant = colorPicker.GetPickerVariant();
var elevation = variant == PickerVariant.Static ? 6 : 0; var elevation = variant == PickerVariant.Static ? 6 : 0;
var rounded = variant == PickerVariant.Static; var rounded = variant == PickerVariant.Static;

View File

@ -441,14 +441,6 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
private string? GetOptionalStyle(string? style) => string.IsNullOrWhiteSpace(style) ? null : style; 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) private string? ValidateProfileSelection(AssistantProfileSelection profileSelection, Profile? profile)
{ {
if (profile == default || profile == Profile.NO_PROFILE) if (profile == default || profile == Profile.NO_PROFILE)

View File

@ -9,7 +9,7 @@ This folder keeps the Lua manifest (`plugin.lua`) that defines a custom assistan
Supported types (matching the Blazor UI components): 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`. - `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`. - `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`. - `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 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 ### `COLOR_PICKER` reference
- Use `Type = "COLOR_PICKER"` to render a MudBlazor color picker. - Use `Type = "COLOR_PICKER"` to render a MudBlazor color picker.
- Required props: - Required props:

View File

@ -70,10 +70,21 @@ ASSISTANT = {
["Props"] = { ["Props"] = {
["Name"] = "<unique identifier of this component>", -- required ["Name"] = "<unique identifier of this component>", -- required
["Label"] = "<heading of your component>", -- required ["Label"] = "<heading of your component>", -- required
["Adornment"] = "<Start|End|None>", -- 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"] = "<Dark|Error|Info|Inherit|Primary|Secondary|Success|Surface|Tertiary|Transparent|Warning>", -- 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"] = "<a helping text rendered under the text area to give hints to users>",
["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"] = "<direct input of instructions, questions, or tasks by a user>", ["UserPrompt"] = "<direct input of instructions, questions, or tasks by a user>",
["PrefillText"] = "<text to show in the field initially>", ["PrefillText"] = "<text to show in the field initially>",
["IsSingleLine"] = false, -- if true, shows a text field instead of an area ["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"] = "<MudBlazor or css classes>",
["Style"] = "<css styles>",
} }
}, },
{ {
@ -177,7 +188,7 @@ ASSISTANT = {
["ShowAlpha"] = true, -- weather alpha channels are shown ["ShowAlpha"] = true, -- weather alpha channels are shown
["ShowToolbar"] = true, -- weather the toolbar to toggle between picker, grid or palette is 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 ["ShowModeSwitch"] = true, -- weather switch to toggle between RGB(A), HEX or HSL color mode is shown
["PickerVariant"] = "<DIALOG | INLINE | STATIC (default)>", ["PickerVariant"] = "<Dialog|Inline|Static>", -- 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"] = "<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>",
} }
}, },

View File

@ -64,4 +64,6 @@ internal sealed class AssistantColorPicker : AssistantComponentBase
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style)); get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value); set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value);
} }
public PickerVariant GetPickerVariant() => Enum.TryParse<PickerVariant>(this.PickerVariant, out var variant) ? variant : MudBlazor.PickerVariant.Static;
} }

View File

@ -20,8 +20,8 @@ internal sealed class AssistantSwitch : AssistantComponentBase
public bool Value public bool Value
{ {
get => this.Props.TryGetValue(nameof(this.Value), out var val) && val is true; get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.Value), false);
set => this.Props[nameof(this.Value)] = value; set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.Value), value);
} }
public string UserPrompt public string UserPrompt

View File

@ -17,6 +17,42 @@ internal sealed class AssistantTextArea : AssistantComponentBase
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Label)); get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Label));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Label), value); 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 public string UserPrompt
{ {
@ -30,16 +66,34 @@ internal sealed class AssistantTextArea : AssistantComponentBase
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.PrefillText), value); 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 public bool IsSingleLine
{ {
get => this.Props.TryGetValue(nameof(this.IsSingleLine), out var val) && val is true; get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.IsSingleLine), false);
set => this.Props[nameof(this.IsSingleLine)] = value; set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.IsSingleLine), value);
} }
public bool ReadOnly public bool ReadOnly
{ {
get => this.Props.TryGetValue(nameof(this.ReadOnly), out var val) && val is true; get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.ReadOnly), false);
set => this.Props[nameof(this.ReadOnly)] = value; set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.ReadOnly), value);
} }
public string Class public string Class
@ -53,4 +107,8 @@ internal sealed class AssistantTextArea : AssistantComponentBase
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style)); get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Style));
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value); set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Style), value);
} }
public Adornment GetAdornmentPos() => Enum.TryParse<MudBlazor.Adornment>(this.Adornment, out var position) ? position : MudBlazor.Adornment.Start;
public Color GetAdornmentColor() => Enum.TryParse<Color>(this.AdornmentColor, out var color) ? color : Color.Default;
} }

View File

@ -11,7 +11,11 @@ public static class ComponentPropSpecs
), ),
[AssistantComponentType.TEXT_AREA] = new( [AssistantComponentType.TEXT_AREA] = new(
required: ["Name", "Label"], 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( [AssistantComponentType.BUTTON] = new(
required: ["Name", "Text", "Action"], required: ["Name", "Text", "Action"],

View File

@ -17,6 +17,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
public bool AllowProfiles { get; private set; } = true; public bool AllowProfiles { get; private set; } = true;
public bool HasEmbeddedProfileSelection { get; private set; } public bool HasEmbeddedProfileSelection { get; private set; }
public bool HasCustomPromptBuilder => this.buildPromptFunction is not null; public bool HasCustomPromptBuilder => this.buildPromptFunction is not null;
public const int TEXT_AREA_MAX_VALUE = 524288;
private LuaFunction? buildPromptFunction; private LuaFunction? buildPromptFunction;
@ -240,13 +241,29 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
} }
component = AssistantComponentFactory.CreateComponent(type, props, children); 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; return true;
} }
private bool TryReadComponentProps( private bool TryReadComponentProps(AssistantComponentType type, LuaTable propsTable, out Dictionary<string, object> props)
AssistantComponentType type,
LuaTable propsTable,
out Dictionary<string, object> props)
{ {
props = new Dictionary<string, object>(); props = new Dictionary<string, object>();