advanced layout: included the layout option in the rendering and documented them

This commit is contained in:
krut_ni 2026-03-10 18:57:48 +01:00
parent 5106800399
commit edc717cf24
No known key found for this signature in database
GPG Key ID: A5C0151B4DDB172C
6 changed files with 425 additions and 137 deletions

View File

@ -2,6 +2,7 @@
@using AIStudio.Components @using AIStudio.Components
@using AIStudio.Settings @using AIStudio.Settings
@using AIStudio.Tools.PluginSystem.Assistants.DataModel @using AIStudio.Tools.PluginSystem.Assistants.DataModel
@using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogDynamic> @inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogDynamic>
@foreach (var component in this.RootComponent!.Children) @foreach (var component in this.RootComponent!.Children)
@ -133,6 +134,55 @@
</MudButtonGroup> </MudButtonGroup>
} }
break; break;
case AssistantComponentType.LAYOUT_GRID:
if (component is AssistantGrid assistantGrid)
{
var grid = assistantGrid;
<MudGrid Justify="@(AssistantComponentPropHelper.GetJustify(grid.Justify) ?? Justify.FlexStart)"
Spacing="@grid.Spacing"
Class="@grid.Class"
Style="@this.GetOptionalStyle(grid.Style)">
@this.RenderChildren(grid.Children)
</MudGrid>
}
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;
<MudPaper Elevation="@paper.Elevation"
Outlined="@paper.IsOutlined"
Square="@paper.IsSquare"
Class="@paper.Class"
Style="@this.BuildPaperStyle(paper)">
@this.RenderChildren(paper.Children)
</MudPaper>
}
break;
case AssistantComponentType.LAYOUT_STACK:
if (component is AssistantStack assistantStack)
{
var stack = assistantStack;
<MudStack Row="@stack.IsRow"
Reverse="@stack.IsReverse"
Breakpoint="@AssistantComponentPropHelper.GetBreakpoint(stack.Breakpoint, Breakpoint.None)"
AlignItems="@(AssistantComponentPropHelper.GetAlignment(stack.Align) ?? AlignItems.Stretch)"
Justify="@(AssistantComponentPropHelper.GetJustify(stack.Justify) ?? Justify.FlexStart)"
StretchItems="@(AssistantComponentPropHelper.GetStretching(stack.Stretch) ?? StretchItems.None)"
Wrap="@(AssistantComponentPropHelper.GetWrap(stack.Wrap) ?? Wrap.Wrap)"
Spacing="@stack.Spacing"
Class="@stack.Class"
Style="@this.GetOptionalStyle(this.GetRawStyle(stack))">
@this.RenderChildren(stack.Children)
</MudStack>
}
break;
case AssistantComponentType.PROVIDER_SELECTION: case AssistantComponentType.PROVIDER_SELECTION:
if (component is AssistantProviderSelection providerSelection) if (component is AssistantProviderSelection providerSelection)
{ {
@ -244,4 +294,70 @@
break; break;
} }
</text>; </text>;
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<string> 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<MudItem>(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<string> styles, string key, string value)
{
if (!string.IsNullOrWhiteSpace(value))
styles.Add($"{key}: {value.Trim().TrimEnd(';')}");
}
} }

View File

@ -76,52 +76,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
var rootComponent = this.RootComponent; var rootComponent = this.RootComponent;
if (rootComponent is not null) if (rootComponent is not null)
{ {
foreach (var component in rootComponent.Children) this.InitializeComponentState(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;
}
}
} }
base.OnInitialized(); base.OnInitialized();
@ -277,32 +232,7 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
var meta = new LuaTable(); var meta = new LuaTable();
var rootComponent = this.RootComponent; var rootComponent = this.RootComponent;
if (rootComponent is not null) if (rootComponent is not null)
{ this.AddMetaEntries(meta, rootComponent.Children);
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;
}
}
}
input["meta"] = meta; input["meta"] = meta;
@ -343,89 +273,50 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
if (rootComponent is null) if (rootComponent is null)
return prompt; return prompt;
foreach (var component in rootComponent.Children) return this.CollectUserPromptFallback(rootComponent.Children);
}
private void InitializeComponentState(IEnumerable<IAssistantComponent> components)
{
foreach (var component in components)
{ {
var userInput = string.Empty;
var userDecision = false;
switch (component.Type) switch (component.Type)
{ {
case AssistantComponentType.TEXT_AREA: case AssistantComponentType.TEXT_AREA:
if (component is AssistantTextArea textArea) if (component is AssistantTextArea textArea && !this.inputFields.ContainsKey(textArea.Name))
{ this.inputFields.Add(textArea.Name, textArea.PrefillText);
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; break;
case AssistantComponentType.DROPDOWN: case AssistantComponentType.DROPDOWN:
if (component is AssistantDropdown dropdown) if (component is AssistantDropdown dropdown && !this.dropdownFields.ContainsKey(dropdown.Name))
{ this.dropdownFields.Add(dropdown.Name, dropdown.Default.Value);
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; break;
case AssistantComponentType.SWITCH: case AssistantComponentType.SWITCH:
if (component is AssistantSwitch switchComponent) if (component is AssistantSwitch switchComponent && !this.switchFields.ContainsKey(switchComponent.Name))
{ this.switchFields.Add(switchComponent.Name, switchComponent.Value);
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; break;
case AssistantComponentType.WEB_CONTENT_READER: case AssistantComponentType.WEB_CONTENT_READER:
if (component is AssistantWebContentReader webContent && if (component is AssistantWebContentReader webContent && !this.webContentFields.ContainsKey(webContent.Name))
this.webContentFields.TryGetValue(webContent.Name, out var webState))
{ {
if (!string.IsNullOrWhiteSpace(webContent.UserPrompt)) this.webContentFields.Add(webContent.Name, new WebContentState
{ {
prompt += $"{Environment.NewLine}context:{Environment.NewLine}{webContent.UserPrompt}{Environment.NewLine}---{Environment.NewLine}"; Preselect = webContent.Preselect,
} PreselectContentCleanerAgent = webContent.PreselectContentCleanerAgent,
});
if (!string.IsNullOrWhiteSpace(webState.Content))
{
prompt += $"user prompt:{Environment.NewLine}{webState.Content}";
}
} }
break; break;
case AssistantComponentType.FILE_CONTENT_READER: case AssistantComponentType.FILE_CONTENT_READER:
if (component is AssistantFileContentReader fileContent && if (component is AssistantFileContentReader fileContent && !this.fileContentFields.ContainsKey(fileContent.Name))
this.fileContentFields.TryGetValue(fileContent.Name, out var fileState)) this.fileContentFields.Add(fileContent.Name, new FileContentState());
{
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; break;
case AssistantComponentType.COLOR_PICKER: case AssistantComponentType.COLOR_PICKER:
if (component is AssistantColorPicker colorPicker) if (component is AssistantColorPicker assistantColorPicker && !this.colorPickerFields.ContainsKey(assistantColorPicker.Name))
{ this.colorPickerFields.Add(assistantColorPicker.Name, assistantColorPicker.Placeholder);
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; break;
default:
prompt += $"{userInput}{Environment.NewLine}";
break;
}
} }
return prompt; if (component.Children.Count > 0)
this.InitializeComponentState(component.Children);
}
} }
private static string MergeClass(string customClass, string fallback) private static string MergeClass(string customClass, string fallback)
@ -571,4 +462,109 @@ public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
var time = this.AddUserRequest(await this.CollectUserPromptAsync()); var time = this.AddUserRequest(await this.CollectUserPromptAsync());
await this.AddAIResponseAsync(time); await this.AddAIResponseAsync(time);
} }
private void AddMetaEntries(LuaTable meta, IEnumerable<IAssistantComponent> 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<IAssistantComponent> 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;
}
} }

View File

@ -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`. - `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 `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`. - `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`. - `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.
@ -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. 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 ## 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: 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 ### `SWITCH` reference
- Use `Type = "SWITCH"` to render a boolean toggle. - Use `Type = "SWITCH"` to render a boolean toggle.
- Required props: - Required props:

View File

@ -168,6 +168,48 @@ ASSISTANT = {
-- BUTTON_ELEMENTS -- BUTTON_ELEMENTS
} }
}, },
{
["Type"] = "LAYOUT_STACK",
["Props"] = {
["Name"] = "exampleStack",
["IsRow"] = true,
["Align"] = "Center",
["Justify"] = "SpaceBetween",
["Wrap"] = "Wrap",
["Spacing"] = 2,
["Class"] = "<optional MudBlazor or css classes>",
["Style"] = "<optional css styles>",
},
["Children"] = {
-- CHILDREN
}
},
{
["Type"] = "LAYOUT_PAPER",
["Props"] = {
["Name"] = "examplePaper",
["Elevation"] = 2,
["Width"] = "100%",
["Class"] = "pa-4 mb-3",
["Style"] = "<optional css styles>",
},
["Children"] = {
-- CHILDREN
}
},
{
["Type"] = "LAYOUT_GRID",
["Props"] = {
["Name"] = "exampleGrid",
["Justify"] = "FlexStart",
["Spacing"] = 2,
["Class"] = "<optional MudBlazor or css classes>",
["Style"] = "<optional css styles>",
},
["Children"] = {
-- CHILDREN
}
},
{ {
["Type"] = "PROVIDER_SELECTION", -- required ["Type"] = "PROVIDER_SELECTION", -- required
["Props"] = { ["Props"] = {

View File

@ -89,7 +89,7 @@ public static class ComponentPropSpecs
[AssistantComponentType.LAYOUT_PAPER] = new( [AssistantComponentType.LAYOUT_PAPER] = new(
required: ["Name"], required: ["Name"],
optional: [ optional: [
"Height", "MaxHeight", "MinHeight", "Width", "MaxWidth", "MinWidth", "Elevation", "Height", "MaxHeight", "MinHeight", "Width", "MaxWidth", "MinWidth",
"IsOutlined", "IsSquare", "Class", "Style" "IsOutlined", "IsSquare", "Class", "Style"
] ]
), ),

View File

@ -1,4 +1,5 @@
using AIStudio.Tools.PluginSystem.Assistants.DataModel; using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout;
using Lua; using Lua;
namespace AIStudio.Tools.PluginSystem.Assistants; 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; return true;
} }