mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-03-29 11:51:37 +00:00
Button now supports IconButton; Switch supports OnChange hook now
This commit is contained in:
parent
d05dd35d20
commit
0d57f1883c
@ -129,22 +129,47 @@
|
||||
if (component is AssistantButton assistantButton)
|
||||
{
|
||||
var button = assistantButton;
|
||||
<div>
|
||||
<MudButton Variant="@button.GetButtonVariant()"
|
||||
Color="@AssistantComponentPropHelper.GetColor(button.Color, Color.Default)"
|
||||
OnClick="@(() => this.ExecuteButtonActionAsync(button))"
|
||||
Size="@AssistantComponentPropHelper.GetComponentSize(button.Size, Size.Medium)"
|
||||
FullWidth="@button.IsFullWidth"
|
||||
StartIcon="@AssistantComponentPropHelper.GetIconSvg(button.StartIcon)"
|
||||
EndIcon="@AssistantComponentPropHelper.GetIconSvg(button.EndIcon)"
|
||||
IconColor="@AssistantComponentPropHelper.GetColor(button.IconColor, Color.Inherit)"
|
||||
IconSize="@AssistantComponentPropHelper.GetComponentSize(button.IconSize, Size.Medium)"
|
||||
Disabled="@this.IsButtonActionRunning(button.Name)"
|
||||
Class='@MergeClass(button.Class, "mb-3")'
|
||||
Style="@this.GetOptionalStyle(button.Style)">
|
||||
@button.Text
|
||||
</MudButton>
|
||||
</div>
|
||||
var icon = AssistantComponentPropHelper.GetIconSvg(button.StartIcon);
|
||||
var iconColor = AssistantComponentPropHelper.GetColor(button.IconColor, Color.Inherit);
|
||||
var color = AssistantComponentPropHelper.GetColor(button.Color, Color.Default);
|
||||
var size = AssistantComponentPropHelper.GetComponentSize(button.Size, Size.Medium);
|
||||
var iconSize = AssistantComponentPropHelper.GetComponentSize(button.IconSize, Size.Medium);
|
||||
var variant = button.GetButtonVariant();
|
||||
var disabled = this.IsButtonActionRunning(button.Name);
|
||||
var buttonClass = MergeClass(button.Class, "mb-3");
|
||||
var style = this.GetOptionalStyle(button.Style);
|
||||
|
||||
if (!button.IsIconButton)
|
||||
{
|
||||
<div>
|
||||
<MudButton Variant="@variant"
|
||||
Color="@color"
|
||||
OnClick="@(() => this.ExecuteButtonActionAsync(button))"
|
||||
Size="@size"
|
||||
FullWidth="@button.IsFullWidth"
|
||||
StartIcon="@icon"
|
||||
EndIcon="@AssistantComponentPropHelper.GetIconSvg(button.EndIcon)"
|
||||
IconColor="@iconColor"
|
||||
IconSize="@iconSize"
|
||||
Disabled="@disabled"
|
||||
Class="@buttonClass"
|
||||
Style="@style">
|
||||
@button.Text
|
||||
</MudButton>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton Icon="@icon"
|
||||
Color="@color"
|
||||
Variant="@variant"
|
||||
Size="@size"
|
||||
OnClick="@(() => this.ExecuteButtonActionAsync(button))"
|
||||
Disabled="@disabled"
|
||||
Class="@buttonClass"
|
||||
Style="@style" />
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case AssistantComponentType.BUTTON_GROUP:
|
||||
@ -281,16 +306,17 @@
|
||||
{
|
||||
var assistantSwitch = switchComponent;
|
||||
var currentValue = this.switchFields[assistantSwitch.Name];
|
||||
<MudField Label="@assistantSwitch.Label" Variant="Variant.Outlined" Class="mb-3" Disabled="@assistantSwitch.Disabled">
|
||||
var disabled = assistantSwitch.Disabled || this.IsSwitchActionRunning(assistantSwitch.Name);
|
||||
<MudField Label="@assistantSwitch.Label" Variant="Variant.Outlined" Class="mb-3" Disabled="@disabled">
|
||||
<MudSwitch T="bool"
|
||||
Value="@currentValue"
|
||||
ValueChanged="@((bool value) => this.switchFields[assistantSwitch.Name] = value)"
|
||||
ValueChanged="@((bool value) => this.ExecuteSwitchChangedAsync(assistantSwitch, value))"
|
||||
LabelPlacement="@assistantSwitch.GetLabelPlacement()"
|
||||
Color="@assistantSwitch.GetColor(assistantSwitch.CheckedColor)"
|
||||
UncheckedColor="@assistantSwitch.GetColor(assistantSwitch.UncheckedColor)"
|
||||
ThumbIcon="@assistantSwitch.GetIconSvg()"
|
||||
ThumbIconColor="@assistantSwitch.GetColor(assistantSwitch.IconColor)"
|
||||
Disabled="@assistantSwitch.Disabled"
|
||||
Disabled="@disabled"
|
||||
Class="@assistantSwitch.Class"
|
||||
Style="@this.GetOptionalStyle(assistantSwitch.Style)">
|
||||
@(currentValue ? assistantSwitch.LabelOn : assistantSwitch.LabelOff)
|
||||
|
||||
@ -50,6 +50,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
private readonly Dictionary<string, string> colorPickerFields = 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";
|
||||
@ -348,6 +349,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
private string? GetOptionalStyle(string? style) => string.IsNullOrWhiteSpace(style) ? null : style;
|
||||
|
||||
private bool IsButtonActionRunning(string buttonName) => this.executingButtonActions.Contains(buttonName);
|
||||
private bool IsSwitchActionRunning(string switchName) => this.executingSwitchActions.Contains(switchName);
|
||||
|
||||
private async Task ExecuteButtonActionAsync(AssistantButton button)
|
||||
{
|
||||
@ -363,7 +365,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
var cancellationToken = this.cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||
var result = await this.assistantPlugin.TryInvokeButtonActionAsync(button, input, cancellationToken);
|
||||
if (result is not null)
|
||||
this.ApplyButtonActionResult(result);
|
||||
this.ApplyActionResult(result, AssistantComponentType.BUTTON);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -372,14 +374,45 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyButtonActionResult(LuaTable result)
|
||||
private async Task ExecuteSwitchChangedAsync(AssistantSwitch switchComponent, bool value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(switchComponent.Name))
|
||||
return;
|
||||
|
||||
this.switchFields[switchComponent.Name] = value;
|
||||
|
||||
if (this.assistantPlugin is null || switchComponent.OnChanged is null)
|
||||
{
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.executingSwitchActions.Add(switchComponent.Name))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var input = this.BuildPromptInput();
|
||||
var cancellationToken = this.cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||
var result = await this.assistantPlugin.TryInvokeSwitchChangedAsync(switchComponent, input, cancellationToken);
|
||||
if (result is not null)
|
||||
this.ApplyActionResult(result, AssistantComponentType.SWITCH);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.executingSwitchActions.Remove(switchComponent.Name);
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyActionResult(LuaTable result, AssistantComponentType sourceType)
|
||||
{
|
||||
if (!result.TryGetValue("fields", out var fieldsValue))
|
||||
return;
|
||||
|
||||
if (!fieldsValue.TryRead<LuaTable>(out var fieldsTable))
|
||||
{
|
||||
this.Logger.LogWarning("Assistant BUTTON action returned a non-table 'fields' value. The result is ignored.");
|
||||
this.Logger.LogWarning("Assistant {ComponentType} callback returned a non-table 'fields' value. The result is ignored.", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -388,18 +421,18 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
if (!pair.Key.TryRead<string>(out var fieldName) || string.IsNullOrWhiteSpace(fieldName))
|
||||
continue;
|
||||
|
||||
this.TryApplyFieldUpdate(fieldName, pair.Value);
|
||||
this.TryApplyFieldUpdate(fieldName, pair.Value, sourceType);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryApplyFieldUpdate(string fieldName, LuaValue value)
|
||||
private void TryApplyFieldUpdate(string fieldName, LuaValue value, AssistantComponentType sourceType)
|
||||
{
|
||||
if (this.inputFields.ContainsKey(fieldName))
|
||||
{
|
||||
if (value.TryRead<string>(out var textValue))
|
||||
this.inputFields[fieldName] = textValue ?? string.Empty;
|
||||
else
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -408,7 +441,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
if (value.TryRead<string>(out var dropdownValue))
|
||||
this.dropdownFields[fieldName] = dropdownValue ?? string.Empty;
|
||||
else
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -419,7 +452,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
else if (value.TryRead<string>(out var singleDropdownValue))
|
||||
this.multiselectDropdownFields[fieldName] = string.IsNullOrWhiteSpace(singleDropdownValue) ? [] : [singleDropdownValue];
|
||||
else
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string[]");
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string[]", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -428,7 +461,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
if (value.TryRead<bool>(out var boolValue))
|
||||
this.switchFields[fieldName] = boolValue;
|
||||
else
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "boolean");
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "boolean", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -437,7 +470,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
if (value.TryRead<string>(out var colorValue))
|
||||
this.colorPickerFields[fieldName] = colorValue ?? string.Empty;
|
||||
else
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -446,7 +479,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
if (value.TryRead<string>(out var webContentValue))
|
||||
webContentState.Content = webContentValue ?? string.Empty;
|
||||
else
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -455,16 +488,16 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
||||
if (value.TryRead<string>(out var fileContentValue))
|
||||
fileContentState.Content = fileContentValue ?? string.Empty;
|
||||
else
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||
this.LogFieldUpdateTypeMismatch(fieldName, "string", sourceType);
|
||||
return;
|
||||
}
|
||||
|
||||
this.Logger.LogWarning("Assistant BUTTON action tried to update unknown field '{FieldName}'. The value is ignored.", fieldName);
|
||||
this.Logger.LogWarning("Assistant {ComponentType} callback tried to update unknown field '{FieldName}'. The value is ignored.", sourceType, fieldName);
|
||||
}
|
||||
|
||||
private void LogFieldUpdateTypeMismatch(string fieldName, string expectedType)
|
||||
private void LogFieldUpdateTypeMismatch(string fieldName, string expectedType, AssistantComponentType sourceType)
|
||||
{
|
||||
this.Logger.LogWarning("Assistant BUTTON action tried to write an invalid value to '{FieldName}'. Expected {ExpectedType}.", fieldName, expectedType);
|
||||
this.Logger.LogWarning("Assistant {ComponentType} callback tried to write an invalid value to '{FieldName}'. Expected {ExpectedType}.", sourceType, fieldName, expectedType);
|
||||
}
|
||||
|
||||
private EventCallback<HashSet<string>> CreateMultiselectDropdownChangedCallback(string fieldName) =>
|
||||
|
||||
@ -90,7 +90,7 @@ ASSISTANT = {
|
||||
|
||||
- `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`.
|
||||
- `BUTTON`: invokes a Lua callback; `Props` must include `Name`, `Text`, `Action`, and may include `Variant`, `Color`, `IsFullWidth`, `Size`, `StartIcon`, `EndIcon`, `IconColor`, `IconSize`, `Class`, `Style`.
|
||||
- `BUTTON`: invokes a Lua callback; `Props` must include `Name`, `Text`, `Action`, and may include `IsIconButton`, `Variant`, `Color`, `IsFullWidth`, `Size`, `StartIcon`, `EndIcon`, `IconColor`, `IconSize`, `Class`, `Style`. Use this for stateless actions, including icon-only action buttons.
|
||||
- `BUTTON_GROUP`: groups multiple `BUTTON` children in a `MudButtonGroup`; `Children` must contain only `BUTTON` components and `Props` may include `Variant`, `Color`, `Size`, `OverrideStyles`, `Vertical`, `DropShadow`, `Class`, `Style`.
|
||||
- `LAYOUT_GRID`: renders a `MudGrid`; `Children` must contain only `LAYOUT_ITEM` components and `Props` may include `Justify`, `Spacing`, `Class`, `Style`.
|
||||
- `LAYOUT_ITEM`: renders a `MudItem`; use it inside `LAYOUT_GRID` and configure breakpoints with `Xs`, `Sm`, `Md`, `Lg`, `Xl`, `Xxl`, plus optional `Class`, `Style`.
|
||||
@ -98,7 +98,7 @@ ASSISTANT = {
|
||||
- `LAYOUT_STACK`: renders a `MudStack`; may include `IsRow`, `IsReverse`, `Breakpoint`, `Align`, `Justify`, `Stretch`, `Wrap`, `Spacing`, `Class`, `Style`.
|
||||
- `LAYOUT_ACCORDION`: renders a `MudExpansionPanels`; may include `AllowMultiSelection`, `IsDense`, `HasOutline`, `IsSquare`, `Elevation`, `HasSectionPaddings`, `Class`, `Style`.
|
||||
- `LAYOUT_ACCORDION_SECTION`: renders a `MudExpansionPanel`; requires `Name`, `HeaderText`, and may include `IsDisabled`, `IsExpanded`, `IsDense`, `HasInnerPadding`, `HideIcon`, `HeaderIcon`, `HeaderColor`, `HeaderTypo`, `HeaderAlign`, `MaxHeight`, `ExpandIcon`, `Class`, `Style`.
|
||||
- `SWITCH`: boolean option; requires `Name`, `Label`, `Value`, and may include `Disabled`, `UserPrompt`, `LabelOn`, `LabelOff`, `LabelPlacement`, `Icon`, `IconColor`, `CheckedColor`, `UncheckedColor`, `Class`, `Style`.
|
||||
- `SWITCH`: boolean option; requires `Name`, `Label`, `Value`, and may include `OnChanged`, `Disabled`, `UserPrompt`, `LabelOn`, `LabelOff`, `LabelPlacement`, `Icon`, `IconColor`, `CheckedColor`, `UncheckedColor`, `Class`, `Style`.
|
||||
- `COLOR_PICKER`: color input based on `MudColorPicker`; requires `Name`, `Label`, and may include `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style`.
|
||||
- `PROVIDER_SELECTION` / `PROFILE_SELECTION`: hooks into the shared provider/profile selectors.
|
||||
- `WEB_CONTENT_READER`: renders `ReadWebContent`; include `Name`, `UserPrompt`, `Preselect`, `PreselectContentCleanerAgent`.
|
||||
@ -108,28 +108,28 @@ ASSISTANT = {
|
||||
|
||||
Images referenced via the `plugin://` scheme must exist in the plugin directory (e.g., `assets/example.png`). Drop the file there and point `Src` at it. The component will read the file at runtime, encode it as Base64, and render it inside the assistant UI.
|
||||
|
||||
| Component | Required Props | Optional Props | Renders |
|
||||
|----------------------------|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `TEXT_AREA` | `Name`, `Label` | `HelperText`, `HelperTextOnFocus`, `Adornment`, `AdornmentIcon`, `AdornmentText`, `AdornmentColor`, `Counter`, `MaxLength`, `IsImmediate`, `UserPrompt`, `PrefillText`, `IsSingleLine`, `ReadOnly`, `Class`, `Style` | [MudTextField](https://www.mudblazor.com/components/textfield) |
|
||||
| `DROPDOWN` | `Name`, `Label`, `Default`, `Items` | `IsMultiselect`, `HasSelectAll`, `SelectAllText`, `HelperText`, `OpenIcon`, `CloseIcon`, `IconColor`, `IconPositon`, `Variant`, `ValueType`, `UserPrompt` | [MudSelect](https://www.mudblazor.com/components/select) |
|
||||
| `BUTTON` | `Name`, `Text`, `Action` | `Variant`, `Color`, `IsFullWidth`, `Size`, `StartIcon`, `EndIcon`, `IconColor`, `IconSize`, `Class`, `Style` | [MudButton](https://www.mudblazor.com/components/button) |
|
||||
| `SWITCH` | `Name`, `Label`, `Value` | `Disabled`, `UserPrompt`, `LabelOn`, `LabelOff`, `LabelPlacement`, `Icon`, `IconColor`, `CheckedColor`, `UncheckedColor`, `Class`, `Style` | [MudSwitch](https://www.mudblazor.com/components/switch) |
|
||||
| `PROVIDER_SELECTION` | `None` | `None` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ProviderSelection.razor) |
|
||||
| `PROFILE_SELECTION` | `None` | `None` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ProfileSelection.razor) |
|
||||
| `FILE_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadFileContent.razor) |
|
||||
| `WEB_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadWebContent.razor) |
|
||||
| `COLOR_PICKER` | `Name`, `Label` | `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style` | [MudColorPicker](https://www.mudblazor.com/components/colorpicker) |
|
||||
| `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) |
|
||||
| `IMAGE` | `Src` | `Alt`, `Caption`,`Src` | [MudImage](https://www.mudblazor.com/components/image) |
|
||||
| `BUTTON_GROUP` | `None` | `Variant`, `Color`, `Size`, `OverrideStyles`, `Vertical`, `DropShadow`, `Class`, `Style` | [MudButtonGroup](https://www.mudblazor.com/components/buttongroup) |
|
||||
| `LAYOUT_PAPER` | `None` | `Elevation`, `Height`, `MaxHeight`, `MinHeight`, `Width`, `MaxWidth`, `MinWidth`, `IsOutlined`, `IsSquare`, `Class`, `Style` | [MudPaper](https://www.mudblazor.com/components/paper) |
|
||||
| `LAYOUT_ITEM` | `None` | `Xs`, `Sm`, `Md`, `Lg`, `Xl`, `Xxl`, `Class`, `Style` | [MudItem](https://www.mudblazor.com/api/MudItem) |
|
||||
| `LAYOUT_STACK` | `None` | `IsRow`, `IsReverse`, `Breakpoint`, `Align`, `Justify`, `Stretch`, `Wrap`, `Spacing`, `Class`, `Style` | [MudStack](https://www.mudblazor.com/components/stack) |
|
||||
| `LAYOUT_GRID` | `None` | `Justify`, `Spacing`, `Class`, `Style` | [MudGrid](https://www.mudblazor.com/components/grid) |
|
||||
| `LAYOUT_ACCORDION` | `None` | `AllowMultiSelection`, `IsDense`, `HasOutline`, `IsSquare`, `Elevation`, `HasSectionPaddings`, `Class`, `Style` | [MudExpansionPanels](https://www.mudblazor.com/components/expansionpanels) |
|
||||
| `LAYOUT_ACCORDION_SECTION` | `Name`, `HeaderText` | `IsDisabled`, `IsExpanded`, `IsDense`, `HasInnerPadding`, `HideIcon`, `HeaderIcon`, `HeaderColor`, `HeaderTypo`, `HeaderAlign`, `MaxHeight`, `ExpandIcon`, `Class`, `Style` | [MudExpansionPanel](https://www.mudblazor.com/components/expansionpanels) |
|
||||
| Component | Required Props | Optional Props | Renders |
|
||||
|----------------------------|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `TEXT_AREA` | `Name`, `Label` | `HelperText`, `HelperTextOnFocus`, `Adornment`, `AdornmentIcon`, `AdornmentText`, `AdornmentColor`, `Counter`, `MaxLength`, `IsImmediate`, `UserPrompt`, `PrefillText`, `IsSingleLine`, `ReadOnly`, `Class`, `Style` | [MudTextField](https://www.mudblazor.com/components/textfield) |
|
||||
| `DROPDOWN` | `Name`, `Label`, `Default`, `Items` | `IsMultiselect`, `HasSelectAll`, `SelectAllText`, `HelperText`, `OpenIcon`, `CloseIcon`, `IconColor`, `IconPositon`, `Variant`, `ValueType`, `UserPrompt` | [MudSelect](https://www.mudblazor.com/components/select) |
|
||||
| `BUTTON` | `Name`, `Text`, `Action` | `IsIconButton`, `Variant`, `Color`, `IsFullWidth`, `Size`, `StartIcon`, `EndIcon`, `IconColor`, `IconSize`, `Class`, `Style` | [MudButton](https://www.mudblazor.com/components/button) / [MudIconButton](https://www.mudblazor.com/components/button#icon-button) |
|
||||
| `SWITCH` | `Name`, `Label`, `Value` | `OnChanged`, `Disabled`, `UserPrompt`, `LabelOn`, `LabelOff`, `LabelPlacement`, `Icon`, `IconColor`, `CheckedColor`, `UncheckedColor`, `Class`, `Style` | [MudSwitch](https://www.mudblazor.com/components/switch) |
|
||||
| `PROVIDER_SELECTION` | `None` | `None` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ProviderSelection.razor) |
|
||||
| `PROFILE_SELECTION` | `None` | `None` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ProfileSelection.razor) |
|
||||
| `FILE_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadFileContent.razor) |
|
||||
| `WEB_CONTENT_READER` | `Name` | `UserPrompt` | [`internal`](https://github.com/MindWorkAI/AI-Studio/blob/main/app/MindWork%20AI%20Studio/Components/ReadWebContent.razor) |
|
||||
| `COLOR_PICKER` | `Name`, `Label` | `Placeholder`, `ShowAlpha`, `ShowToolbar`, `ShowModeSwitch`, `PickerVariant`, `UserPrompt`, `Class`, `Style` | [MudColorPicker](https://www.mudblazor.com/components/colorpicker) |
|
||||
| `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) |
|
||||
| `IMAGE` | `Src` | `Alt`, `Caption`,`Src` | [MudImage](https://www.mudblazor.com/components/image) |
|
||||
| `BUTTON_GROUP` | `None` | `Variant`, `Color`, `Size`, `OverrideStyles`, `Vertical`, `DropShadow`, `Class`, `Style` | [MudButtonGroup](https://www.mudblazor.com/components/buttongroup) |
|
||||
| `LAYOUT_PAPER` | `None` | `Elevation`, `Height`, `MaxHeight`, `MinHeight`, `Width`, `MaxWidth`, `MinWidth`, `IsOutlined`, `IsSquare`, `Class`, `Style` | [MudPaper](https://www.mudblazor.com/components/paper) |
|
||||
| `LAYOUT_ITEM` | `None` | `Xs`, `Sm`, `Md`, `Lg`, `Xl`, `Xxl`, `Class`, `Style` | [MudItem](https://www.mudblazor.com/api/MudItem) |
|
||||
| `LAYOUT_STACK` | `None` | `IsRow`, `IsReverse`, `Breakpoint`, `Align`, `Justify`, `Stretch`, `Wrap`, `Spacing`, `Class`, `Style` | [MudStack](https://www.mudblazor.com/components/stack) |
|
||||
| `LAYOUT_GRID` | `None` | `Justify`, `Spacing`, `Class`, `Style` | [MudGrid](https://www.mudblazor.com/components/grid) |
|
||||
| `LAYOUT_ACCORDION` | `None` | `AllowMultiSelection`, `IsDense`, `HasOutline`, `IsSquare`, `Elevation`, `HasSectionPaddings`, `Class`, `Style` | [MudExpansionPanels](https://www.mudblazor.com/components/expansionpanels) |
|
||||
| `LAYOUT_ACCORDION_SECTION` | `Name`, `HeaderText` | `IsDisabled`, `IsExpanded`, `IsDense`, `HasInnerPadding`, `HideIcon`, `HeaderIcon`, `HeaderColor`, `HeaderTypo`, `HeaderAlign`, `MaxHeight`, `ExpandIcon`, `Class`, `Style` | [MudExpansionPanel](https://www.mudblazor.com/components/expansionpanels) |
|
||||
More information on rendered components can be found [here](https://www.mudblazor.com/docs/overview).
|
||||
|
||||
## Component References
|
||||
@ -239,18 +239,23 @@ More information on rendered components can be found [here](https://www.mudblazo
|
||||
|
||||
### `BUTTON` reference
|
||||
- Use `Type = "BUTTON"` to render a clickable action button.
|
||||
- `BUTTON` is the only action-button component in the assistant plugin API. Keep plugin authoring simple by treating it as one concept with two visual modes:
|
||||
- default button mode: text button, optionally with start/end icons
|
||||
- icon-button mode: set `IsIconButton = true` to render the action as an icon-only button
|
||||
- Do not model persistent on/off state with `BUTTON`. For boolean toggles, use `SWITCH`. The plugin API intentionally does not expose a separate `TOGGLE_BUTTON` component.
|
||||
- Required props:
|
||||
- `Name`: unique identifier used to track execution state and logging.
|
||||
- `Text`: visible button label.
|
||||
- `Text`: button label used for standard buttons. Keep providing it for icon buttons too so the manifest stays self-describing.
|
||||
- `Action`: Lua function called on button click.
|
||||
- Optional props:
|
||||
- `IsIconButton`: defaults to `false`; when `true`, renders the action as a `MudIconButton` using `StartIcon` as the icon glyph.
|
||||
- `Variant`: one of the MudBlazor `Variant` enum names such as `Filled`, `Outlined`, `Text`; omitted values fall back to `Filled`.
|
||||
- `Color`: one of the MudBlazor `Color` enum names such as `Default`, `Primary`, `Secondary`, `Info`; omitted values fall back to `Default`.
|
||||
- `IsFullWidth`: defaults to `false`; when `true`, the button expands to the available width.
|
||||
- `Size`: one of the MudBlazor `Size` enum names such as `Small`, `Medium`, `Large`; omitted values fall back to `Medium`.
|
||||
- `StartIcon`: MudBlazor icon identifier string rendered before the button text.
|
||||
- `StartIcon`: MudBlazor icon identifier string rendered before the button text, or used as the icon itself when `IsIconButton = true`.
|
||||
- `EndIcon`: MudBlazor icon identifier string rendered after the button text.
|
||||
- `IconColor`: one of the MudBlazor `Color` enum names; omitted values fall back to `Inherit`.
|
||||
- `IconColor`: one of the MudBlazor `Color` enum names for text-button icons; omitted values fall back to `Inherit`.
|
||||
- `IconSize`: one of the MudBlazor `Size` enum names; omitted values fall back to `Medium`.
|
||||
- `Class`, `Style`: forwarded to the rendered component for layout/styling.
|
||||
|
||||
@ -300,6 +305,29 @@ More information on rendered components can be found [here](https://www.mudblazo
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Example Icon-Button action
|
||||
```lua
|
||||
{
|
||||
["Type"] = "BUTTON",
|
||||
["Props"] = {
|
||||
["Name"] = "refreshPreview",
|
||||
["Text"] = "Refresh preview",
|
||||
["IsIconButton"] = true,
|
||||
["Variant"] = "Outlined",
|
||||
["Color"] = "Primary",
|
||||
["Size"] = "Medium",
|
||||
["StartIcon"] = "Icons.Material.Filled.Refresh",
|
||||
["Action"] = function(input)
|
||||
return {
|
||||
fields = {
|
||||
outputTextField = "Preview refreshed at " .. Timestamp()
|
||||
}
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
### `BUTTON_GROUP` reference
|
||||
@ -314,7 +342,7 @@ More information on rendered components can be found [here](https://www.mudblazo
|
||||
- `Vertical`: defaults to `false`; when `true`, buttons are rendered vertically instead of horizontally.
|
||||
- `DropShadow`: defaults to `true`; controls the group shadow.
|
||||
- `Class`, `Style`: forwarded to the rendered `MudButtonGroup` for layout/styling.
|
||||
- Child buttons use the existing `BUTTON` props and behavior, including Lua `Action(input)`.
|
||||
- Child buttons use the existing `BUTTON` props and behavior, including Lua `Action(input)`. That includes `IsIconButton = true` when you want an icon-only action inside the group.
|
||||
|
||||
#### Example Button-Group component
|
||||
```lua
|
||||
@ -368,6 +396,7 @@ More information on rendered components can be found [here](https://www.mudblazo
|
||||
- `Label`: visible label for the switch field.
|
||||
- `Value`: initial boolean state (`true` or `false`).
|
||||
- Optional props:
|
||||
- `OnChanged`: Lua callback invoked after the switch value changes. It receives the same `input` table as `BUTTON.Action(input)` and may return `{ fields = { ... } }` to update component state. The new switch value is already reflected in `input.fields[Name]`.
|
||||
- `Disabled`: defaults to `false`; disables user interaction while still allowing the value to be included in prompt assembly.
|
||||
- `UserPrompt`: prompt context text for this field.
|
||||
- `LabelOn`: text shown when the switch value is `true`.
|
||||
@ -387,6 +416,14 @@ More information on rendered components can be found [here](https://www.mudblazo
|
||||
["Name"] = "IncludeSummary",
|
||||
["Label"] = "Include summary",
|
||||
["Value"] = true,
|
||||
["OnChanged"] = function(input)
|
||||
local includeSummary = input.fields.IncludeSummary or false
|
||||
return {
|
||||
fields = {
|
||||
SummaryMode = includeSummary and "short-summary" or "no-summary"
|
||||
}
|
||||
}
|
||||
end,
|
||||
["Disabled"] = false,
|
||||
["UserPrompt"] = "Decide whether the final answer should include a short summary.",
|
||||
["LabelOn"] = "Summary enabled",
|
||||
|
||||
@ -116,6 +116,9 @@ ASSISTANT = {
|
||||
["Name"] = "<unique identifier of this component>", -- required
|
||||
["Label"] = "<heading of your component>", -- required
|
||||
["Value"] = true, -- initial switch state
|
||||
["OnChanged"] = function(input) -- optional; same input and return contract as BUTTON.Action(input)
|
||||
return nil
|
||||
end,
|
||||
["Disabled"] = false, -- if true, disables user interaction but the value can still be used in the user prompt (use for presentation purposes)
|
||||
["UserPrompt"] = "<direct input of instructions, questions, or tasks by a user>",
|
||||
["LabelOn"] = "<text if state is true>",
|
||||
@ -133,14 +136,15 @@ ASSISTANT = {
|
||||
["Type"] = "BUTTON",
|
||||
["Props"] = {
|
||||
["Name"] = "buildEmailOutput",
|
||||
["Text"] = "Build email output",
|
||||
["Text"] = "Build email output", -- keep this even for icon-only buttons so the manifest stays readable
|
||||
["IsIconButton"] = false, -- when true, renders an icon-only action button using StartIcon
|
||||
["Size"] = "<Small|Medium|Large>", -- size of the button. Defaults to Medium
|
||||
["Variant"] = "<Filled|Outlined|Text>", -- display variation to use. Defaults to Text
|
||||
["Color"] = "<Dark|Error|Info|Inherit|Primary|Secondary|Success|Surface|Tertiary|Transparent|Warning>", -- color of the button. Defaults to Default
|
||||
["IsFullWidth"] = false, -- ignores sizing and renders a long full width button. Defaults to false
|
||||
["StartIcon"] = "Icons.Material.Filled.ArrowRight", -- icon displayed before the text. Defaults to null
|
||||
["StartIcon"] = "Icons.Material.Filled.ArrowRight", -- icon displayed before the text, or the main icon for icon-only buttons. Defaults to null
|
||||
["EndIcon"] = "Icons.Material.Filled.ArrowLeft", -- icon displayed after the text. Defaults to null
|
||||
["IconColor"] = "<Dark|Error|Info|Inherit|Primary|Secondary|Success|Surface|Tertiary|Transparent|Warning>", -- color of start and end icons. Defaults to Inherit
|
||||
["IconColor"] = "<Dark|Error|Info|Inherit|Primary|Secondary|Success|Surface|Tertiary|Transparent|Warning>", -- color of start and end icons on text buttons. Defaults to Inherit
|
||||
["IconSize"] = "<Small|Medium|Large>", -- size of icons. Defaults to null. When null, the value of ["Size"] is used
|
||||
["Action"] = function(input)
|
||||
local email = input.fields.emailContent or ""
|
||||
|
||||
@ -19,6 +19,12 @@ public sealed class AssistantButton : AssistantComponentBase
|
||||
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Text));
|
||||
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Text), value);
|
||||
}
|
||||
|
||||
public bool IsIconButton
|
||||
{
|
||||
get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.IsIconButton), false);
|
||||
set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.IsIconButton), value);
|
||||
}
|
||||
|
||||
public LuaFunction? Action
|
||||
{
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
using AIStudio.Tools.PluginSystem.Assistants.Icons;
|
||||
using Lua;
|
||||
|
||||
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
|
||||
|
||||
internal sealed class AssistantSwitch : AssistantComponentBase
|
||||
public sealed class AssistantSwitch : AssistantComponentBase
|
||||
{
|
||||
public override AssistantComponentType Type => AssistantComponentType.SWITCH;
|
||||
public override Dictionary<string, object> Props { get; set; } = new();
|
||||
@ -37,6 +38,12 @@ internal sealed class AssistantSwitch : AssistantComponentBase
|
||||
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.UserPrompt));
|
||||
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.UserPrompt), value);
|
||||
}
|
||||
|
||||
public LuaFunction? OnChanged
|
||||
{
|
||||
get => this.Props.TryGetValue(nameof(this.OnChanged), out var value) && value is LuaFunction onChanged ? onChanged : null;
|
||||
set => AssistantComponentPropHelper.WriteObject(this.Props, nameof(this.OnChanged), value);
|
||||
}
|
||||
|
||||
public string LabelOn
|
||||
{
|
||||
|
||||
@ -20,7 +20,7 @@ public static class ComponentPropSpecs
|
||||
[AssistantComponentType.BUTTON] = new(
|
||||
required: ["Name", "Text", "Action"],
|
||||
optional: [
|
||||
"Variant", "Color", "IsFullWidth", "Size",
|
||||
"IsIconButton", "Variant", "Color", "IsFullWidth", "Size",
|
||||
"StartIcon", "EndIcon", "IconColor", "IconSize", "Class", "Style"
|
||||
]
|
||||
),
|
||||
@ -46,7 +46,7 @@ public static class ComponentPropSpecs
|
||||
[AssistantComponentType.SWITCH] = new(
|
||||
required: ["Name", "Label", "Value"],
|
||||
optional: [
|
||||
"LabelOn", "LabelOff", "LabelPlacement", "Icon", "IconColor", "UserPrompt",
|
||||
"OnChanged", "LabelOn", "LabelOff", "LabelPlacement", "Icon", "IconColor", "UserPrompt",
|
||||
"CheckedColor", "UncheckedColor", "Disabled", "Class", "Style",
|
||||
]
|
||||
),
|
||||
|
||||
@ -146,13 +146,23 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
|
||||
public async Task<LuaTable?> TryInvokeButtonActionAsync(AssistantButton button, LuaTable input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (button.Action is null)
|
||||
return await this.TryInvokeComponentCallbackAsync(button.Action, AssistantComponentType.BUTTON, button.Name, input, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<LuaTable?> TryInvokeSwitchChangedAsync(AssistantSwitch switchComponent, LuaTable input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await this.TryInvokeComponentCallbackAsync(switchComponent.OnChanged, AssistantComponentType.SWITCH, switchComponent.Name, input, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<LuaTable?> TryInvokeComponentCallbackAsync(LuaFunction? callback, AssistantComponentType componentType, string componentName, LuaTable input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (callback is null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var results = await this.state.CallAsync(button.Action, [input]);
|
||||
var results = await this.state.CallAsync(callback, [input]);
|
||||
if (results.Length == 0)
|
||||
return null;
|
||||
|
||||
@ -162,12 +172,12 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
if (results[0].TryRead<LuaTable>(out var updateTable))
|
||||
return updateTable;
|
||||
|
||||
LOGGER.LogWarning("Assistant plugin '{PluginName}' BUTTON '{ButtonName}' returned a non-table value. The result is ignored.", this.Name, button.Name);
|
||||
LOGGER.LogWarning("Assistant plugin '{PluginName}' {ComponentType} '{ComponentName}' callback returned a non-table value. The result is ignored.", this.Name, componentType, componentName);
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.LogError(e, "Assistant plugin '{PluginName}' BUTTON '{ButtonName}' action failed to execute.", this.Name, button.Name);
|
||||
LOGGER.LogError(e, "Assistant plugin '{PluginName}' {ComponentType} '{ComponentName}' callback failed to execute.", this.Name, componentType, componentName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user