From edc717cf240f9bfa332faaa47dc9419f371c63dc Mon Sep 17 00:00:00 2001 From: krut_ni Date: Tue, 10 Mar 2026 18:57:48 +0100 Subject: [PATCH] advanced layout: included the layout option in the rendering and documented them --- .../Assistants/Dynamic/AssistantDynamic.razor | 116 ++++++++ .../Dynamic/AssistantDynamic.razor.cs | 266 +++++++++--------- .../Plugins/assistants/README.md | 125 +++++++- .../Plugins/assistants/plugin.lua | 42 +++ .../DataModel/ComponentPropSpecs.cs | 2 +- .../Assistants/PluginAssistants.cs | 11 + 6 files changed, 425 insertions(+), 137 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor index aeccc50d..5ac6359a 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor @@ -2,6 +2,7 @@ @using AIStudio.Components @using AIStudio.Settings @using AIStudio.Tools.PluginSystem.Assistants.DataModel +@using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout @inherits AssistantBaseCore @foreach (var component in this.RootComponent!.Children) @@ -133,6 +134,55 @@ } break; + case AssistantComponentType.LAYOUT_GRID: + if (component is AssistantGrid assistantGrid) + { + var grid = assistantGrid; + + @this.RenderChildren(grid.Children) + + } + break; + case AssistantComponentType.LAYOUT_ITEM: + if (component is AssistantItem assistantItem) + { + @this.RenderLayoutItem(assistantItem) + } + break; + case AssistantComponentType.LAYOUT_PAPER: + if (component is AssistantPaper assistantPaper) + { + var paper = assistantPaper; + + @this.RenderChildren(paper.Children) + + } + break; + case AssistantComponentType.LAYOUT_STACK: + if (component is AssistantStack assistantStack) + { + var stack = assistantStack; + + @this.RenderChildren(stack.Children) + + } + break; case AssistantComponentType.PROVIDER_SELECTION: if (component is AssistantProviderSelection providerSelection) { @@ -244,4 +294,70 @@ break; } ; + + private string? GetRawStyle(IAssistantComponent component) + { + if (!component.Props.TryGetValue("Style", out var rawStyle) || rawStyle is null) + return null; + + return rawStyle as string ?? rawStyle.ToString(); + } + + private string? BuildPaperStyle(AssistantPaper paper) + { + List styles = []; + + this.AddStyle(styles, "height", paper.Height); + this.AddStyle(styles, "max-height", paper.MaxHeight); + this.AddStyle(styles, "min-height", paper.MinHeight); + this.AddStyle(styles, "width", paper.Width); + this.AddStyle(styles, "max-width", paper.MaxWidth); + this.AddStyle(styles, "min-width", paper.MinWidth); + + var customStyle = this.GetRawStyle(paper); + if (!string.IsNullOrWhiteSpace(customStyle)) + styles.Add(customStyle.Trim().TrimEnd(';')); + + return styles.Count == 0 ? null : string.Join("; ", styles); + } + + private RenderFragment RenderLayoutItem(AssistantItem item) => builder => + { + builder.OpenComponent(0); + + if (item.Xs.HasValue) + builder.AddAttribute(1, "xs", item.Xs.Value); + + if (item.Sm.HasValue) + builder.AddAttribute(2, "sm", item.Sm.Value); + + if (item.Md.HasValue) + builder.AddAttribute(3, "md", item.Md.Value); + + if (item.Lg.HasValue) + builder.AddAttribute(4, "lg", item.Lg.Value); + + if (item.Xl.HasValue) + builder.AddAttribute(5, "xl", item.Xl.Value); + + if (item.Xxl.HasValue) + builder.AddAttribute(6, "xxl", item.Xxl.Value); + + var itemClass = item.Class; + if (!string.IsNullOrWhiteSpace(itemClass)) + builder.AddAttribute(7, nameof(MudItem.Class), itemClass); + + var itemStyle = this.GetOptionalStyle(item.Style); + if (!string.IsNullOrWhiteSpace(itemStyle)) + builder.AddAttribute(8, nameof(MudItem.Style), itemStyle); + + builder.AddAttribute(9, nameof(MudItem.ChildContent), this.RenderChildren(item.Children)); + builder.CloseComponent(); + }; + + private void AddStyle(List styles, string key, string value) + { + if (!string.IsNullOrWhiteSpace(value)) + styles.Add($"{key}: {value.Trim().TrimEnd(';')}"); + } } diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs index f2fac9c0..f3abcd7f 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs @@ -76,52 +76,7 @@ public partial class AssistantDynamic : AssistantBaseCore var rootComponent = this.RootComponent; if (rootComponent is not null) { - foreach (var component in rootComponent.Children) - { - switch (component.Type) - { - case AssistantComponentType.TEXT_AREA: - if (component is AssistantTextArea textArea) - { - this.inputFields.Add(textArea.Name, textArea.PrefillText); - } - break; - case AssistantComponentType.DROPDOWN: - if (component is AssistantDropdown dropdown) - { - this.dropdownFields.Add(dropdown.Name, dropdown.Default.Value); - } - break; - case AssistantComponentType.SWITCH: - if (component is AssistantSwitch switchComponent) - { - this.switchFields.Add(switchComponent.Name, switchComponent.Value); - } - break; - case AssistantComponentType.WEB_CONTENT_READER: - if (component is AssistantWebContentReader webContent) - { - this.webContentFields.Add(webContent.Name, new WebContentState - { - Preselect = webContent.Preselect, - PreselectContentCleanerAgent = webContent.PreselectContentCleanerAgent, - }); - } - break; - case AssistantComponentType.FILE_CONTENT_READER: - if (component is AssistantFileContentReader fileContent) - { - this.fileContentFields.Add(fileContent.Name, new FileContentState()); - } - break; - case AssistantComponentType.COLOR_PICKER: - if (component is AssistantColorPicker assistantColorPicker) - { - this.colorPickerFields.Add(assistantColorPicker.Name, assistantColorPicker.Placeholder); - } - break; - } - } + this.InitializeComponentState(rootComponent.Children); } base.OnInitialized(); @@ -277,32 +232,7 @@ public partial class AssistantDynamic : AssistantBaseCore var meta = new LuaTable(); var rootComponent = this.RootComponent; if (rootComponent is not null) - { - foreach (var component in rootComponent.Children) - { - switch (component) - { - case AssistantTextArea textArea: - this.AddMetaEntry(meta, textArea.Name, component.Type, textArea.Label, textArea.UserPrompt); - break; - case AssistantDropdown dropdown: - this.AddMetaEntry(meta, dropdown.Name, component.Type, dropdown.Label, dropdown.UserPrompt); - break; - case AssistantSwitch switchComponent: - this.AddMetaEntry(meta, switchComponent.Name, component.Type, switchComponent.Label, switchComponent.UserPrompt); - break; - case AssistantWebContentReader webContent: - this.AddMetaEntry(meta, webContent.Name, component.Type, null, webContent.UserPrompt); - break; - case AssistantFileContentReader fileContent: - this.AddMetaEntry(meta, fileContent.Name, component.Type, null, fileContent.UserPrompt); - break; - case AssistantColorPicker colorPicker: - this.AddMetaEntry(meta, colorPicker.Name, component.Type, colorPicker.Label, colorPicker.UserPrompt); - break; - } - } - } + this.AddMetaEntries(meta, rootComponent.Children); input["meta"] = meta; @@ -343,89 +273,50 @@ public partial class AssistantDynamic : AssistantBaseCore if (rootComponent is null) return prompt; - foreach (var component in rootComponent.Children) + return this.CollectUserPromptFallback(rootComponent.Children); + } + + private void InitializeComponentState(IEnumerable components) + { + foreach (var component in components) { - var userInput = string.Empty; - var userDecision = false; switch (component.Type) { case AssistantComponentType.TEXT_AREA: - if (component is AssistantTextArea textArea) - { - prompt += $"context:{Environment.NewLine}{textArea.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; - if (this.inputFields.TryGetValue(textArea.Name, out userInput)) - { - prompt += $"user prompt:{Environment.NewLine}{userInput}"; - } - } + if (component is AssistantTextArea textArea && !this.inputFields.ContainsKey(textArea.Name)) + this.inputFields.Add(textArea.Name, textArea.PrefillText); break; case AssistantComponentType.DROPDOWN: - if (component is AssistantDropdown dropdown) - { - prompt += $"{Environment.NewLine}context:{Environment.NewLine}{dropdown.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; - if (this.dropdownFields.TryGetValue(dropdown.Name, out userInput)) - { - prompt += $"user prompt:{Environment.NewLine}{userInput}"; - } - } + if (component is AssistantDropdown dropdown && !this.dropdownFields.ContainsKey(dropdown.Name)) + this.dropdownFields.Add(dropdown.Name, dropdown.Default.Value); break; case AssistantComponentType.SWITCH: - if (component is AssistantSwitch switchComponent) - { - prompt += $"{Environment.NewLine}context:{Environment.NewLine}{switchComponent.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; - if (this.switchFields.TryGetValue(switchComponent.Name, out userDecision)) - { - prompt += $"user decision:{Environment.NewLine}{userDecision}"; - } - } + if (component is AssistantSwitch switchComponent && !this.switchFields.ContainsKey(switchComponent.Name)) + this.switchFields.Add(switchComponent.Name, switchComponent.Value); break; case AssistantComponentType.WEB_CONTENT_READER: - if (component is AssistantWebContentReader webContent && - this.webContentFields.TryGetValue(webContent.Name, out var webState)) + if (component is AssistantWebContentReader webContent && !this.webContentFields.ContainsKey(webContent.Name)) { - if (!string.IsNullOrWhiteSpace(webContent.UserPrompt)) + this.webContentFields.Add(webContent.Name, new WebContentState { - prompt += $"{Environment.NewLine}context:{Environment.NewLine}{webContent.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; - } - - if (!string.IsNullOrWhiteSpace(webState.Content)) - { - prompt += $"user prompt:{Environment.NewLine}{webState.Content}"; - } + Preselect = webContent.Preselect, + PreselectContentCleanerAgent = webContent.PreselectContentCleanerAgent, + }); } break; case AssistantComponentType.FILE_CONTENT_READER: - if (component is AssistantFileContentReader fileContent && - this.fileContentFields.TryGetValue(fileContent.Name, out var fileState)) - { - if (!string.IsNullOrWhiteSpace(fileContent.UserPrompt)) - { - prompt += $"{Environment.NewLine}context:{Environment.NewLine}{fileContent.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; - } - - if (!string.IsNullOrWhiteSpace(fileState.Content)) - { - prompt += $"user prompt:{Environment.NewLine}{fileState.Content}"; - } - } + if (component is AssistantFileContentReader fileContent && !this.fileContentFields.ContainsKey(fileContent.Name)) + this.fileContentFields.Add(fileContent.Name, new FileContentState()); break; case AssistantComponentType.COLOR_PICKER: - if (component is AssistantColorPicker colorPicker) - { - prompt += $"context:{Environment.NewLine}{colorPicker.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; - if (this.inputFields.TryGetValue(colorPicker.Name, out userInput)) - { - prompt += $"user prompt:{Environment.NewLine}{userInput}"; - } - } - break; - default: - prompt += $"{userInput}{Environment.NewLine}"; + if (component is AssistantColorPicker assistantColorPicker && !this.colorPickerFields.ContainsKey(assistantColorPicker.Name)) + this.colorPickerFields.Add(assistantColorPicker.Name, assistantColorPicker.Placeholder); break; } - } - return prompt; + if (component.Children.Count > 0) + this.InitializeComponentState(component.Children); + } } private static string MergeClass(string customClass, string fallback) @@ -571,4 +462,109 @@ public partial class AssistantDynamic : AssistantBaseCore var time = this.AddUserRequest(await this.CollectUserPromptAsync()); await this.AddAIResponseAsync(time); } + + private void AddMetaEntries(LuaTable meta, IEnumerable components) + { + foreach (var component in components) + { + switch (component) + { + case AssistantTextArea textArea: + this.AddMetaEntry(meta, textArea.Name, component.Type, textArea.Label, textArea.UserPrompt); + break; + case AssistantDropdown dropdown: + this.AddMetaEntry(meta, dropdown.Name, component.Type, dropdown.Label, dropdown.UserPrompt); + break; + case AssistantSwitch switchComponent: + this.AddMetaEntry(meta, switchComponent.Name, component.Type, switchComponent.Label, switchComponent.UserPrompt); + break; + case AssistantWebContentReader webContent: + this.AddMetaEntry(meta, webContent.Name, component.Type, null, webContent.UserPrompt); + break; + case AssistantFileContentReader fileContent: + this.AddMetaEntry(meta, fileContent.Name, component.Type, null, fileContent.UserPrompt); + break; + case AssistantColorPicker colorPicker: + this.AddMetaEntry(meta, colorPicker.Name, component.Type, colorPicker.Label, colorPicker.UserPrompt); + break; + } + + if (component.Children.Count > 0) + this.AddMetaEntries(meta, component.Children); + } + } + + private string CollectUserPromptFallback(IEnumerable components) + { + var prompt = string.Empty; + + foreach (var component in components) + { + var userInput = string.Empty; + var userDecision = false; + + switch (component.Type) + { + case AssistantComponentType.TEXT_AREA: + if (component is AssistantTextArea textArea) + { + prompt += $"context:{Environment.NewLine}{textArea.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; + if (this.inputFields.TryGetValue(textArea.Name, out userInput)) + prompt += $"user prompt:{Environment.NewLine}{userInput}"; + } + break; + case AssistantComponentType.DROPDOWN: + if (component is AssistantDropdown dropdown) + { + prompt += $"{Environment.NewLine}context:{Environment.NewLine}{dropdown.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; + if (this.dropdownFields.TryGetValue(dropdown.Name, out userInput)) + prompt += $"user prompt:{Environment.NewLine}{userInput}"; + } + break; + case AssistantComponentType.SWITCH: + if (component is AssistantSwitch switchComponent) + { + prompt += $"{Environment.NewLine}context:{Environment.NewLine}{switchComponent.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; + if (this.switchFields.TryGetValue(switchComponent.Name, out userDecision)) + prompt += $"user decision:{Environment.NewLine}{userDecision}"; + } + break; + case AssistantComponentType.WEB_CONTENT_READER: + if (component is AssistantWebContentReader webContent && + this.webContentFields.TryGetValue(webContent.Name, out var webState)) + { + if (!string.IsNullOrWhiteSpace(webContent.UserPrompt)) + prompt += $"{Environment.NewLine}context:{Environment.NewLine}{webContent.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; + + if (!string.IsNullOrWhiteSpace(webState.Content)) + prompt += $"user prompt:{Environment.NewLine}{webState.Content}"; + } + break; + case AssistantComponentType.FILE_CONTENT_READER: + if (component is AssistantFileContentReader fileContent && + this.fileContentFields.TryGetValue(fileContent.Name, out var fileState)) + { + if (!string.IsNullOrWhiteSpace(fileContent.UserPrompt)) + prompt += $"{Environment.NewLine}context:{Environment.NewLine}{fileContent.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; + + if (!string.IsNullOrWhiteSpace(fileState.Content)) + prompt += $"user prompt:{Environment.NewLine}{fileState.Content}"; + } + break; + case AssistantComponentType.COLOR_PICKER: + if (component is AssistantColorPicker colorPicker) + { + prompt += $"context:{Environment.NewLine}{colorPicker.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; + if (this.colorPickerFields.TryGetValue(colorPicker.Name, out userInput)) + prompt += $"user prompt:{Environment.NewLine}{userInput}"; + } + break; + } + + if (component.Children.Count > 0) + prompt += this.CollectUserPromptFallback(component.Children); + } + + return prompt; + } } diff --git a/app/MindWork AI Studio/Plugins/assistants/README.md b/app/MindWork AI Studio/Plugins/assistants/README.md index 8800b004..15e0d1d7 100644 --- a/app/MindWork AI Studio/Plugins/assistants/README.md +++ b/app/MindWork AI Studio/Plugins/assistants/README.md @@ -13,6 +13,10 @@ Supported types (matching the Blazor UI components): - `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_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`. +- `LAYOUT_PAPER`: renders a `MudPaper`; may include `Elevation`, `Height`, `MaxHeight`, `MinHeight`, `Width`, `MaxWidth`, `MinWidth`, `IsOutlined`, `IsSquare`, `Class`, `Style`. +- `LAYOUT_STACK`: renders a `MudStack`; may include `IsRow`, `IsReverse`, `Breakpoint`, `Align`, `Justify`, `Stretch`, `Wrap`, `Spacing`, `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`. - `PROVIDER_SELECTION` / `PROFILE_SELECTION`: hooks into the shared provider/profile selectors. @@ -24,7 +28,7 @@ Supported types (matching the Blazor UI components): 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. ## Prompt Assembly -Each component exposes a `UserPrompt` string. When the assistant runs, `AssistantDynamic` iterates over `RootComponent.Children` and, for each component that has a prompt, emits: +Each component exposes a `UserPrompt` string. When the assistant runs, `AssistantDynamic` recursively iterates over the component tree and, for each component that has a prompt, emits: ``` context: @@ -280,6 +284,125 @@ Example: } ``` +### `LAYOUT_GRID` reference +- Use `Type = "LAYOUT_GRID"` to render a MudBlazor grid container. +- Required props: + - `Name`: unique identifier for the layout node. +- Required structure: + - `Children`: array of `LAYOUT_ITEM` component tables. Other child component types are ignored. +- Optional props: + - `Justify`: one of the MudBlazor `Justify` enum names such as `FlexStart`, `Center`, `SpaceBetween`; omitted values fall back to `FlexStart`. + - `Spacing`: integer spacing between grid items; omitted values fall back to `6`. + - `Class`, `Style`: forwarded to the rendered `MudGrid` for layout/styling. + +Example: +```lua +{ + ["Type"] = "LAYOUT_GRID", + ["Props"] = { + ["Name"] = "mainGrid", + ["Justify"] = "FlexStart", + ["Spacing"] = 2 + }, + ["Children"] = { + { + ["Type"] = "LAYOUT_ITEM", + ["Props"] = { + ["Name"] = "leftItem", + ["Xs"] = 12, + ["Md"] = 6 + } + }, + { + ["Type"] = "LAYOUT_ITEM", + ["Props"] = { + ["Name"] = "rightItem", + ["Xs"] = 12, + ["Md"] = 6 + } + } + } +} +``` + +### `LAYOUT_ITEM` reference +- Use `Type = "LAYOUT_ITEM"` to render a MudBlazor grid item. +- Required props: + - `Name`: unique identifier for the layout node. +- Intended parent: + - Use this component inside `LAYOUT_GRID`. +- Optional props: + - `Xs`, `Sm`, `Md`, `Lg`, `Xl`, `Xxl`: integer breakpoint widths. Omit a breakpoint to leave it unset. + - `Class`, `Style`: forwarded to the rendered `MudItem` for layout/styling. +- `Children` may contain any other assistant components you want to place inside the item. + +Example: +```lua +{ + ["Type"] = "LAYOUT_ITEM", + ["Props"] = { + ["Name"] = "contentColumn", + ["Xs"] = 12, + ["Lg"] = 8 + } +} +``` + +### `LAYOUT_PAPER` reference +- Use `Type = "LAYOUT_PAPER"` to render a MudBlazor paper container. +- Required props: + - `Name`: unique identifier for the layout node. +- Optional props: + - `Elevation`: integer elevation; omitted values fall back to `1`. + - `Height`, `MaxHeight`, `MinHeight`, `Width`, `MaxWidth`, `MinWidth`: CSS size values such as `100%`, `24rem`, `50vh`. + - `IsOutlined`: defaults to `false`; toggles outlined mode. + - `IsSquare`: defaults to `false`; removes rounded corners. + - `Class`, `Style`: forwarded to the rendered `MudPaper` for layout/styling. +- `Children` may contain any other assistant components you want to wrap. + +Example: +```lua +{ + ["Type"] = "LAYOUT_PAPER", + ["Props"] = { + ["Name"] = "contentPaper", + ["Elevation"] = 2, + ["Width"] = "100%", + ["IsOutlined"] = true + } +} +``` + +### `LAYOUT_STACK` reference +- Use `Type = "LAYOUT_STACK"` to render a MudBlazor stack layout. +- Required props: + - `Name`: unique identifier for the layout node. +- Optional props: + - `IsRow`: defaults to `false`; renders items horizontally. + - `IsReverse`: defaults to `false`; reverses the visual order. + - `Breakpoint`: one of the MudBlazor `Breakpoint` enum names such as `Sm`, `Md`, `Lg`; omitted values fall back to `None`. + - `Align`: one of the MudBlazor `AlignItems` enum names such as `Start`, `Center`, `Stretch`; omitted values fall back to `Stretch`. + - `Justify`: one of the MudBlazor `Justify` enum names such as `FlexStart`, `Center`, `SpaceBetween`; omitted values fall back to `FlexStart`. + - `Stretch`: one of the MudBlazor `StretchItems` enum names such as `None`, `Start`, `End`, `Stretch`; omitted values fall back to `None`. + - `Wrap`: one of the MudBlazor `Wrap` enum names such as `Wrap`, `NoWrap`, `WrapReverse`; omitted values fall back to `Wrap`. + - `Spacing`: integer spacing between child components; omitted values fall back to `3`. + - `Class`, `Style`: forwarded to the rendered `MudStack` for layout/styling. +- `Children` may contain any other assistant components you want to arrange. + +Example: +```lua +{ + ["Type"] = "LAYOUT_STACK", + ["Props"] = { + ["Name"] = "toolbarRow", + ["IsRow"] = true, + ["Align"] = "Center", + ["Justify"] = "SpaceBetween", + ["Spacing"] = 2 + } +} +``` + ### `SWITCH` reference - Use `Type = "SWITCH"` to render a boolean toggle. - Required props: diff --git a/app/MindWork AI Studio/Plugins/assistants/plugin.lua b/app/MindWork AI Studio/Plugins/assistants/plugin.lua index 084e90d2..49028340 100644 --- a/app/MindWork AI Studio/Plugins/assistants/plugin.lua +++ b/app/MindWork AI Studio/Plugins/assistants/plugin.lua @@ -168,6 +168,48 @@ ASSISTANT = { -- BUTTON_ELEMENTS } }, + { + ["Type"] = "LAYOUT_STACK", + ["Props"] = { + ["Name"] = "exampleStack", + ["IsRow"] = true, + ["Align"] = "Center", + ["Justify"] = "SpaceBetween", + ["Wrap"] = "Wrap", + ["Spacing"] = 2, + ["Class"] = "", + ["Style"] = "", + }, + ["Children"] = { + -- CHILDREN + } + }, + { + ["Type"] = "LAYOUT_PAPER", + ["Props"] = { + ["Name"] = "examplePaper", + ["Elevation"] = 2, + ["Width"] = "100%", + ["Class"] = "pa-4 mb-3", + ["Style"] = "", + }, + ["Children"] = { + -- CHILDREN + } + }, + { + ["Type"] = "LAYOUT_GRID", + ["Props"] = { + ["Name"] = "exampleGrid", + ["Justify"] = "FlexStart", + ["Spacing"] = 2, + ["Class"] = "", + ["Style"] = "", + }, + ["Children"] = { + -- CHILDREN + } + }, { ["Type"] = "PROVIDER_SELECTION", -- required ["Props"] = { 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 fcafc9b2..5e86b9a6 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/ComponentPropSpecs.cs @@ -89,7 +89,7 @@ public static class ComponentPropSpecs [AssistantComponentType.LAYOUT_PAPER] = new( required: ["Name"], optional: [ - "Height", "MaxHeight", "MinHeight", "Width", "MaxWidth", "MinWidth", + "Elevation", "Height", "MaxHeight", "MinHeight", "Width", "MaxWidth", "MinWidth", "IsOutlined", "IsSquare", "Class", "Style" ] ), diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs index 204c274c..68f6525b 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs @@ -1,4 +1,5 @@ using AIStudio.Tools.PluginSystem.Assistants.DataModel; +using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout; using Lua; namespace AIStudio.Tools.PluginSystem.Assistants; @@ -298,6 +299,16 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType } } + if (component is AssistantGrid grid) + { + var invalidChildren = grid.Children.Where(child => child.Type != AssistantComponentType.LAYOUT_ITEM).ToList(); + if (invalidChildren.Count > 0) + { + LOGGER.LogWarning("Assistant plugin '{PluginName}' LAYOUT_GRID contains non-LAYOUT_ITEM children. Only LAYOUT_ITEM children are supported and invalid children are ignored.", this.Name); + grid.Children = grid.Children.Where(child => child.Type == AssistantComponentType.LAYOUT_ITEM).ToList(); + } + } + return true; }