mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-03-29 20:51:37 +00:00
buttons now support lua functions as actions, allowing plugin devs to exectute them on their own
This commit is contained in:
parent
a32a5354e9
commit
2ccb72c77d
@ -85,6 +85,27 @@
|
|||||||
Style="@assistantDropdown.Style"/>
|
Style="@assistantDropdown.Style"/>
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case AssistantComponentType.BUTTON:
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
break;
|
||||||
case AssistantComponentType.PROVIDER_SELECTION:
|
case AssistantComponentType.PROVIDER_SELECTION:
|
||||||
if (component is AssistantProviderSelection providerSelection)
|
if (component is AssistantProviderSelection providerSelection)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
|
|||||||
private readonly Dictionary<string, FileContentState> fileContentFields = new();
|
private readonly Dictionary<string, FileContentState> fileContentFields = new();
|
||||||
private readonly Dictionary<string, string> colorPickerFields = new();
|
private readonly Dictionary<string, string> colorPickerFields = new();
|
||||||
private readonly Dictionary<string, string> imageCache = new();
|
private readonly Dictionary<string, string> imageCache = new();
|
||||||
|
private readonly HashSet<string> executingButtonActions = [];
|
||||||
private string pluginPath = string.Empty;
|
private string pluginPath = string.Empty;
|
||||||
private const string PLUGIN_SCHEME = "plugin://";
|
private const string PLUGIN_SCHEME = "plugin://";
|
||||||
private const string ASSISTANT_QUERY_KEY = "assistantId";
|
private const string ASSISTANT_QUERY_KEY = "assistantId";
|
||||||
@ -441,6 +443,115 @@ 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 bool IsButtonActionRunning(string buttonName) => this.executingButtonActions.Contains(buttonName);
|
||||||
|
|
||||||
|
private async Task ExecuteButtonActionAsync(AssistantButton button)
|
||||||
|
{
|
||||||
|
if (this.assistantPlugin is null || button.Action is null || string.IsNullOrWhiteSpace(button.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!this.executingButtonActions.Add(button.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var input = this.BuildPromptInput();
|
||||||
|
var cancellationToken = this.cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
|
var result = await this.assistantPlugin.TryInvokeButtonActionAsync(button, input, cancellationToken);
|
||||||
|
if (result is not null)
|
||||||
|
this.ApplyButtonActionResult(result);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.executingButtonActions.Remove(button.Name);
|
||||||
|
await this.InvokeAsync(this.StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyButtonActionResult(LuaTable result)
|
||||||
|
{
|
||||||
|
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.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pair in fieldsTable)
|
||||||
|
{
|
||||||
|
if (!pair.Key.TryRead<string>(out var fieldName) || string.IsNullOrWhiteSpace(fieldName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.TryApplyFieldUpdate(fieldName, pair.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryApplyFieldUpdate(string fieldName, LuaValue value)
|
||||||
|
{
|
||||||
|
if (this.inputFields.ContainsKey(fieldName))
|
||||||
|
{
|
||||||
|
if (value.TryRead<string>(out var textValue))
|
||||||
|
this.inputFields[fieldName] = textValue ?? string.Empty;
|
||||||
|
else
|
||||||
|
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.dropdownFields.ContainsKey(fieldName))
|
||||||
|
{
|
||||||
|
if (value.TryRead<string>(out var dropdownValue))
|
||||||
|
this.dropdownFields[fieldName] = dropdownValue ?? string.Empty;
|
||||||
|
else
|
||||||
|
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.switchFields.ContainsKey(fieldName))
|
||||||
|
{
|
||||||
|
if (value.TryRead<bool>(out var boolValue))
|
||||||
|
this.switchFields[fieldName] = boolValue;
|
||||||
|
else
|
||||||
|
this.LogFieldUpdateTypeMismatch(fieldName, "boolean");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.colorPickerFields.ContainsKey(fieldName))
|
||||||
|
{
|
||||||
|
if (value.TryRead<string>(out var colorValue))
|
||||||
|
this.colorPickerFields[fieldName] = colorValue ?? string.Empty;
|
||||||
|
else
|
||||||
|
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.webContentFields.TryGetValue(fieldName, out var webContentState))
|
||||||
|
{
|
||||||
|
if (value.TryRead<string>(out var webContentValue))
|
||||||
|
webContentState.Content = webContentValue ?? string.Empty;
|
||||||
|
else
|
||||||
|
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fileContentFields.TryGetValue(fieldName, out var fileContentState))
|
||||||
|
{
|
||||||
|
if (value.TryRead<string>(out var fileContentValue))
|
||||||
|
fileContentState.Content = fileContentValue ?? string.Empty;
|
||||||
|
else
|
||||||
|
this.LogFieldUpdateTypeMismatch(fieldName, "string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Logger.LogWarning("Assistant BUTTON action tried to update unknown field '{FieldName}'. The value is ignored.", fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogFieldUpdateTypeMismatch(string fieldName, string expectedType)
|
||||||
|
{
|
||||||
|
this.Logger.LogWarning("Assistant BUTTON action tried to write an invalid value to '{FieldName}'. Expected {ExpectedType}.", fieldName, expectedType);
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ Supported types (matching the Blazor UI components):
|
|||||||
|
|
||||||
- `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`.
|
- `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`.
|
||||||
|
- `BUTTON`: invokes a Lua callback; `Props` must include `Name`, `Text`, `Action`, and may include `Variant`, `Color`, `IsFullWidth`, `Size`, `StartIcon`, `EndIcon`, `IconColor`, `IconSize`, `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 `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`.
|
- `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.
|
- `PROVIDER_SELECTION` / `PROFILE_SELECTION`: hooks into the shared provider/profile selectors.
|
||||||
@ -157,6 +158,69 @@ Example:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `BUTTON` reference
|
||||||
|
- Use `Type = "BUTTON"` to render a clickable action button.
|
||||||
|
- Required props:
|
||||||
|
- `Name`: unique identifier used to track execution state and logging.
|
||||||
|
- `Text`: visible button label.
|
||||||
|
- `Action`: Lua function called on button click.
|
||||||
|
- Optional props:
|
||||||
|
- `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.
|
||||||
|
- `EndIcon`: MudBlazor icon identifier string rendered after the button text.
|
||||||
|
- `IconColor`: one of the MudBlazor `Color` enum names; 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.
|
||||||
|
|
||||||
|
#### `Action(input)` contract
|
||||||
|
- The function receives the same `input` structure as `ASSISTANT.BuildPrompt(input)`.
|
||||||
|
- Return `nil` for no state update.
|
||||||
|
- To update component state, return a table with a `fields` table.
|
||||||
|
- `fields` keys must reference existing component `Name` values.
|
||||||
|
- Supported write targets:
|
||||||
|
- `TEXT_AREA`, `DROPDOWN`, `WEB_CONTENT_READER`, `FILE_CONTENT_READER`, `COLOR_PICKER`: string values
|
||||||
|
- `SWITCH`: boolean values
|
||||||
|
- Unknown field names and wrong value types are ignored and logged.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
["Type"] = "BUTTON",
|
||||||
|
["Props"] = {
|
||||||
|
["Name"] = "buildEmailOutput",
|
||||||
|
["Text"] = "Build output",
|
||||||
|
["Variant"] = "Filled",
|
||||||
|
["Color"] = "Primary",
|
||||||
|
["IsFullWidth"] = false,
|
||||||
|
["Size"] = "Medium",
|
||||||
|
["StartIcon"] = "Icons.Material.Filled.AutoFixHigh",
|
||||||
|
["EndIcon"] = "Icons.Material.Filled.ArrowForward",
|
||||||
|
["IconColor"] = "Inherit",
|
||||||
|
["IconSize"] = "Medium",
|
||||||
|
["Action"] = function(input)
|
||||||
|
local email = input.fields.emailContent or ""
|
||||||
|
local translate = input.fields.translateEmail or false
|
||||||
|
local output = email
|
||||||
|
|
||||||
|
if translate then
|
||||||
|
output = output .. "\n\nTranslate this email."
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields = {
|
||||||
|
outputBuffer = output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
["Class"] = "mb-3",
|
||||||
|
["Style"] = "min-width: 12rem;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `SWITCH` reference
|
### `SWITCH` reference
|
||||||
- Use `Type = "SWITCH"` to render a boolean toggle.
|
- Use `Type = "SWITCH"` to render a boolean toggle.
|
||||||
- Required props:
|
- Required props:
|
||||||
|
|||||||
@ -120,6 +120,38 @@ ASSISTANT = {
|
|||||||
["Style"] = "<optional css styles>",
|
["Style"] = "<optional css styles>",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
["Type"] = "BUTTON",
|
||||||
|
["Props"] = {
|
||||||
|
["Name"] = "buildEmailOutput",
|
||||||
|
["Text"] = "Build email output",
|
||||||
|
["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
|
||||||
|
["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
|
||||||
|
["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 ""
|
||||||
|
local translate = input.fields.translateEmail or false
|
||||||
|
local output = email
|
||||||
|
|
||||||
|
if translate then
|
||||||
|
output = output .. "\n\nTranslate this email."
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields = {
|
||||||
|
outputBuffer = output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
["Class"] = "<optional MudBlazor or css classes>",
|
||||||
|
["Style"] = "<optional css styles>",
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
["Type"] = "PROVIDER_SELECTION", -- required
|
["Type"] = "PROVIDER_SELECTION", -- required
|
||||||
["Props"] = {
|
["Props"] = {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
|
using Lua;
|
||||||
|
|
||||||
internal sealed class AssistantButton : AssistantComponentBase
|
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
|
||||||
|
|
||||||
|
public sealed class AssistantButton : AssistantComponentBase
|
||||||
{
|
{
|
||||||
public override AssistantComponentType Type => AssistantComponentType.BUTTON;
|
public override AssistantComponentType Type => AssistantComponentType.BUTTON;
|
||||||
public override Dictionary<string, object> Props { get; set; } = new();
|
public override Dictionary<string, object> Props { get; set; } = new();
|
||||||
@ -11,16 +13,65 @@ internal sealed class AssistantButton : AssistantComponentBase
|
|||||||
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Name));
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Name));
|
||||||
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Name), value);
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Name), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Text
|
public string Text
|
||||||
{
|
{
|
||||||
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Text));
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Text));
|
||||||
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Text), value);
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Text), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Action
|
public LuaFunction? Action
|
||||||
{
|
{
|
||||||
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Action));
|
get => this.Props.TryGetValue(nameof(this.Action), out var value) && value is LuaFunction action ? action : null;
|
||||||
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Action), value);
|
set => AssistantComponentPropHelper.WriteObject(this.Props, nameof(this.Action), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Variant
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Variant));
|
||||||
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Variant), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Color
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Color));
|
||||||
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Color), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFullWidth
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadBool(this.Props, nameof(this.IsFullWidth), false);
|
||||||
|
set => AssistantComponentPropHelper.WriteBool(this.Props, nameof(this.IsFullWidth), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StartIcon
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.StartIcon));
|
||||||
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.StartIcon), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EndIcon
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.EndIcon));
|
||||||
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.EndIcon), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string IconColor
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.IconColor));
|
||||||
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.IconColor), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string IconSize
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.IconSize));
|
||||||
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.IconSize), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Size
|
||||||
|
{
|
||||||
|
get => AssistantComponentPropHelper.ReadString(this.Props, nameof(this.Size));
|
||||||
|
set => AssistantComponentPropHelper.WriteString(this.Props, nameof(this.Size), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Class
|
public string Class
|
||||||
@ -34,4 +85,7 @@ internal sealed class AssistantButton : 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 Variant GetButtonVariant() => Enum.TryParse<Variant>(this.Variant, out var variant) ? variant : MudBlazor.Variant.Filled;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using AIStudio.Tools.PluginSystem.Assistants.Icons;
|
||||||
|
|
||||||
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
|
namespace AIStudio.Tools.PluginSystem.Assistants.DataModel;
|
||||||
|
|
||||||
internal static class AssistantComponentPropHelper
|
internal static class AssistantComponentPropHelper
|
||||||
@ -49,4 +51,16 @@ internal static class AssistantComponentPropHelper
|
|||||||
{
|
{
|
||||||
props[key] = value;
|
props[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void WriteObject(Dictionary<string, object> props, string key, object? value)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
props.Remove(key);
|
||||||
|
else
|
||||||
|
props[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MudBlazor.Color GetColor(string value, Color fallback) => Enum.TryParse<MudBlazor.Color>(value, out var color) ? color : fallback;
|
||||||
|
public static string GetIconSvg(string value) => MudBlazorIconRegistry.TryGetSvg(value, out var svg) ? svg : string.Empty;
|
||||||
|
public static Size GetComponentSize(string value, Size fallback) => Enum.TryParse<Size>(value, out var size) ? size : fallback;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,10 @@ public static class ComponentPropSpecs
|
|||||||
),
|
),
|
||||||
[AssistantComponentType.BUTTON] = new(
|
[AssistantComponentType.BUTTON] = new(
|
||||||
required: ["Name", "Text", "Action"],
|
required: ["Name", "Text", "Action"],
|
||||||
optional: ["Class", "Style"]
|
optional: [
|
||||||
|
"Variant", "Color", "IsFullWidth", "Size",
|
||||||
|
"StartIcon", "EndIcon", "IconColor", "IconSize", "Class", "Style"
|
||||||
|
]
|
||||||
),
|
),
|
||||||
[AssistantComponentType.DROPDOWN] = new(
|
[AssistantComponentType.DROPDOWN] = new(
|
||||||
required: ["Name", "Label", "Default", "Items"],
|
required: ["Name", "Label", "Default", "Items"],
|
||||||
|
|||||||
@ -143,6 +143,34 @@ 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 null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
var results = await this.state.CallAsync(button.Action, [input]);
|
||||||
|
if (results.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (results[0].Type is LuaValueType.Nil)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
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);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.LogError(e, "Assistant plugin '{PluginName}' BUTTON '{ButtonName}' action failed to execute.", this.Name, button.Name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses the root <c>FORM</c> component and start to parse its required children (main ui components)
|
/// Parses the root <c>FORM</c> component and start to parse its required children (main ui components)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -280,7 +308,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
|||||||
LOGGER.LogWarning($"Component {type} missing required prop '{key}'.");
|
LOGGER.LogWarning($"Component {type} missing required prop '{key}'.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this.TryConvertLuaValue(luaVal, out var dotNetVal))
|
if (!this.TryConvertComponentPropValue(type, key, luaVal, out var dotNetVal))
|
||||||
{
|
{
|
||||||
LOGGER.LogWarning($"Component {type}: prop '{key}' has wrong type.");
|
LOGGER.LogWarning($"Component {type}: prop '{key}' has wrong type.");
|
||||||
return false;
|
return false;
|
||||||
@ -293,7 +321,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
|||||||
if (!propsTable.TryGetValue(key, out var luaVal))
|
if (!propsTable.TryGetValue(key, out var luaVal))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!this.TryConvertLuaValue(luaVal, out var dotNetVal))
|
if (!this.TryConvertComponentPropValue(type, key, luaVal, out var dotNetVal))
|
||||||
{
|
{
|
||||||
LOGGER.LogWarning($"Component {type}: optional prop '{key}' has wrong type, skipping.");
|
LOGGER.LogWarning($"Component {type}: optional prop '{key}' has wrong type, skipping.");
|
||||||
continue;
|
continue;
|
||||||
@ -303,6 +331,17 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryConvertComponentPropValue(AssistantComponentType type, string key, LuaValue val, out object result)
|
||||||
|
{
|
||||||
|
if (type == AssistantComponentType.BUTTON && key == "Action" && val.TryRead<LuaFunction>(out var action))
|
||||||
|
{
|
||||||
|
result = action;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.TryConvertLuaValue(val, out result);
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryConvertLuaValue(LuaValue val, out object result)
|
private bool TryConvertLuaValue(LuaValue val, out object result)
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user