diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs index 551b4f4c..051f8995 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs @@ -194,9 +194,9 @@ public partial class AssistantDynamic : AssistantBaseCore private LuaTable BuildPromptInput() { - var input = new LuaTable(); + var state = new LuaTable(); var rootComponent = this.RootComponent; - input["state"] = rootComponent is not null + state = rootComponent is not null ? this.assistantState.ToLuaTable(rootComponent.Children) : new LuaTable(); @@ -207,9 +207,9 @@ public partial class AssistantDynamic : AssistantBaseCore ["Actions"] = this.currentProfile.Actions, ["Num"] = this.currentProfile.Num, }; - input["profile"] = profile; + state["profile"] = profile; - return input; + return state; } private string CollectUserPromptFallback() @@ -308,24 +308,54 @@ public partial class AssistantDynamic : AssistantBaseCore private void ApplyActionResult(LuaTable result, AssistantComponentType sourceType) { - if (!result.TryGetValue("fields", out var fieldsValue)) + if (!result.TryGetValue("state", out var statesValue)) return; - if (!fieldsValue.TryRead(out var fieldsTable)) + if (!statesValue.TryRead(out var stateTable)) { - this.Logger.LogWarning("Assistant {ComponentType} callback returned a non-table 'fields' value. The result is ignored.", sourceType); + this.Logger.LogWarning($"Assistant {sourceType} callback returned a non-table 'state' value. The result is ignored."); return; } - foreach (var pair in fieldsTable) + foreach (var component in stateTable) { - if (!pair.Key.TryRead(out var fieldName) || string.IsNullOrWhiteSpace(fieldName)) + if (!component.Key.TryRead(out var componentName) || string.IsNullOrWhiteSpace(componentName)) continue; - this.TryApplyFieldUpdate(fieldName, pair.Value, sourceType); + if (!component.Value.TryRead(out var componentUpdate)) + { + this.Logger.LogWarning($"Assistant {sourceType} callback returned a non-table update for '{componentName}'. The result is ignored."); + continue; + } + + this.TryApplyComponentUpdate(componentName, componentUpdate, sourceType); } } + private void TryApplyComponentUpdate(string componentName, LuaTable componentUpdate, AssistantComponentType sourceType) + { + if (componentUpdate.TryGetValue("Value", out var value)) + this.TryApplyFieldUpdate(componentName, value, sourceType); + + if (!componentUpdate.TryGetValue("Props", out var propsValue)) + return; + + if (!propsValue.TryRead(out var propsTable)) + { + this.Logger.LogWarning($"Assistant {sourceType} callback returned a non-table 'Props' value for '{componentName}'. The props update is ignored."); + return; + } + + var rootComponent = this.RootComponent; + if (rootComponent is null || !TryFindNamedComponent(rootComponent.Children, componentName, out var component)) + { + this.Logger.LogWarning($"Assistant {sourceType} callback tried to update props of unknown component '{componentName}'. The props update is ignored."); + return; + } + + this.ApplyPropUpdates(component, propsTable, sourceType); + } + private void TryApplyFieldUpdate(string fieldName, LuaValue value, AssistantComponentType sourceType) { if (this.assistantState.TryApplyValue(fieldName, value, out var expectedType)) @@ -340,6 +370,51 @@ public partial class AssistantDynamic : AssistantBaseCore this.Logger.LogWarning($"Assistant {sourceType} callback tried to update unknown field '{fieldName}'. The value is ignored."); } + private void ApplyPropUpdates(IAssistantComponent component, LuaTable propsTable, AssistantComponentType sourceType) + { + var propSpec = ComponentPropSpecs.SPECS.GetValueOrDefault(component.Type); + + foreach (var prop in propsTable) + { + if (!prop.Key.TryRead(out var propName) || string.IsNullOrWhiteSpace(propName)) + continue; + + if (propSpec is not null && propSpec.NonWriteable.Contains(propName, StringComparer.Ordinal)) + { + this.Logger.LogWarning($"Assistant {sourceType} callback tried to update non-writeable prop '{propName}' on component '{GetComponentName(component)}'. The value is ignored."); + continue; + } + + if (!AssistantLuaConversion.TryReadScalarOrStructuredValue(prop.Value, out var convertedValue)) + { + this.Logger.LogWarning($"Assistant {sourceType} callback returned an unsupported value for prop '{propName}' on component '{GetComponentName(component)}'. The props update is ignored."); + continue; + } + + component.Props[propName] = convertedValue; + } + } + + private static bool TryFindNamedComponent(IEnumerable components, string componentName, out IAssistantComponent component) + { + foreach (var candidate in components) + { + if (candidate is INamedAssistantComponent named && string.Equals(named.Name, componentName, StringComparison.Ordinal)) + { + component = candidate; + return true; + } + + if (candidate.Children.Count > 0 && TryFindNamedComponent(candidate.Children, componentName, out component)) + return true; + } + + component = null!; + return false; + } + + private static string GetComponentName(IAssistantComponent component) => component is INamedAssistantComponent named ? named.Name : component.Type.ToString(); + private EventCallback> CreateMultiselectDropdownChangedCallback(string fieldName) => EventCallback.Factory.Create>(this, values => { diff --git a/app/MindWork AI Studio/Plugins/assistants/README.md b/app/MindWork AI Studio/Plugins/assistants/README.md index 6074030b..eb80e279 100644 --- a/app/MindWork AI Studio/Plugins/assistants/README.md +++ b/app/MindWork AI Studio/Plugins/assistants/README.md @@ -24,9 +24,9 @@ This folder keeps the Lua manifest (`plugin.lua`) that defines a custom assistan - [Advanced Prompt Assembly - BuildPrompt()](#advanced-prompt-assembly---buildprompt) - [Interface](#interface) - [`input` table shape](#input-table-shape) - - [Using `meta` inside BuildPrompt](#using-meta-inside-buildprompt) - - [Example: iterate all fields with labels and include their values](#example-iterate-all-fields-with-labels-and-include-their-values) - - [Example: handle types differently](#example-handle-types-differently) + - [Using component metadata inside BuildPrompt](#using-component-metadata-inside-buildprompt) + - [Example: build a prompt from two fields](#example-build-a-prompt-from-two-fields) + - [Example: reuse a label from `Props`](#example-reuse-a-label-from-props) - [Using `profile` inside BuildPrompt](#using-profile-inside-buildprompt) - [Example: Add user profile context to the prompt](#example-add-user-profile-context-to-the-prompt) - [Advanced Layout Options](#advanced-layout-options) @@ -147,7 +147,7 @@ More information on rendered components can be found [here](https://www.mudblazo ### `TEXT_AREA` reference - Use `Type = "TEXT_AREA"` to render a MudBlazor text input or textarea. - Required props: - - `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`. + - `Name`: unique state key used in prompt assembly and `BuildPrompt(input)`. - `Label`: visible field label. - Optional props: - `HelperText`: helper text rendered below the input. @@ -190,7 +190,7 @@ More information on rendered components can be found [here](https://www.mudblazo ### `DROPDOWN` reference - Use `Type = "DROPDOWN"` to render a MudBlazor select field. - Required props: - - `Name`: unique state key used in prompt assembly, button actions, and `BuildPrompt(input.fields)`. + - `Name`: unique state key used in prompt assembly, button actions, and `BuildPrompt(input)`. - `Label`: visible field label. - `Default`: dropdown item table with the shape `{ ["Value"] = "", ["Display"] = "" }`. - `Items`: array of dropdown item tables with the same shape as `Default`. @@ -211,8 +211,8 @@ More information on rendered components can be found [here](https://www.mudblazo - `Value`: the internal raw value stored in component state and passed to prompt building. - `Display`: the visible label shown to the user in the menu and selection text. - Behavior notes: - - For single-select dropdowns, `input.fields.` is a single raw value such as `germany`. - - For multiselect dropdowns, `input.fields.` is an array-like Lua table of raw values. + - For single-select dropdowns, `input..Value` is a single raw value such as `germany`. + - For multiselect dropdowns, `input..Value` is an array-like Lua table of raw values. - The UI shows the `Display` text, while prompt assembly and `BuildPrompt(input)` receive the raw `Value`. - `Default` should usually also exist in `Items`. If it is missing there, the runtime currently still renders it as an available option. @@ -272,13 +272,20 @@ More information on rendered components can be found [here](https://www.mudblazo #### `Action(input)` interface - 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: +- Each named component is available as `input.` and exposes: + - `Type`: component type such as `TEXT_AREA` or `SWITCH` + - `Value`: current component value + - `Props`: readable component props +- To update component state, return a table with a `state` table. +- `state` keys must reference existing component `Name` values. +- Each component update may include: + - `Value`: updates the current state value + - `Props`: partial prop updates for writable props +- Supported `Value` write targets: - `TEXT_AREA`, single-select `DROPDOWN`, `WEB_CONTENT_READER`, `FILE_CONTENT_READER`, `COLOR_PICKER`, `DATE_PICKER`, `DATE_RANGE_PICKER`, `TIME_PICKER`: string values - multiselect `DROPDOWN`: array-like Lua table of strings - `SWITCH`: boolean values -- Unknown field names and wrong value types are ignored and logged. +- Unknown component names, wrong value types, unsupported prop values, and non-writeable props are ignored and logged. #### Example Button component ```lua @@ -296,8 +303,8 @@ More information on rendered components can be found [here](https://www.mudblazo ["IconColor"] = "Inherit", ["IconSize"] = "Medium", ["Action"] = function(input) - local email = input.fields.emailContent or "" - local translate = input.fields.translateEmail or false + local email = input.emailContent and input.emailContent.Value or "" + local translate = input.translateEmail and input.translateEmail.Value or false local output = email if translate then @@ -305,8 +312,10 @@ More information on rendered components can be found [here](https://www.mudblazo end return { - fields = { - outputTextField = output + state = { + outputTextField = { + Value = output + } } } end, @@ -330,8 +339,10 @@ More information on rendered components can be found [here](https://www.mudblazo ["StartIcon"] = "Icons.Material.Filled.Refresh", ["Action"] = function(input) return { - fields = { - outputTextField = "Preview refreshed at " .. Timestamp() + state = { + outputTextField = { + Value = "Preview refreshed at " .. Timestamp() + } } } end @@ -374,8 +385,10 @@ More information on rendered components can be found [here](https://www.mudblazo ["Text"] = "Build output", ["Action"] = function(input) return { - fields = { - outputBuffer = input.fields.emailContent or "" + state = { + outputBuffer = { + Value = input.emailContent and input.emailContent.Value or "" + } } } end, @@ -388,7 +401,8 @@ More information on rendered components can be found [here](https://www.mudblazo ["Name"] = "logColor", ["Text"] = "Log color", ["Action"] = function(input) - LogError("ColorPicker value: " .. tostring(input.fields.colorPicker or "")) + local colorValue = input.colorPicker and input.colorPicker.Value or "" + LogError("ColorPicker value: " .. colorValue) return nil end, ["EndIcon"] = "Icons.Material.Filled.BugReport" @@ -402,11 +416,11 @@ More information on rendered components can be found [here](https://www.mudblazo ### `SWITCH` reference - Use `Type = "SWITCH"` to render a boolean toggle. - Required props: - - `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`. + - `Name`: unique state key used in prompt assembly and `BuildPrompt(input)`. - `Value`: initial boolean state (`true` or `false`). - Optional props: - `Label`: If set, renders the switch inside an outlines Box, otherwise renders it raw. Visible label for the switch field. - - `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]`. + - `OnChanged`: Lua callback invoked after the switch value changes. It receives the same `input` table as `BUTTON.Action(input)` and may return `{ state = { ... } }` to update component state. The new switch value is already reflected in `input..Value`. - `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`. @@ -427,10 +441,12 @@ More information on rendered components can be found [here](https://www.mudblazo ["Label"] = "Include summary", ["Value"] = true, ["OnChanged"] = function(input) - local includeSummary = input.fields.IncludeSummary or false + local includeSummary = input.IncludeSummary and input.IncludeSummary.Value or false return { - fields = { - SummaryMode = includeSummary and "short-summary" or "no-summary" + state = { + SummaryMode = { + Value = includeSummary and "short-summary" or "no-summary" + } } } end, @@ -452,7 +468,7 @@ More information on rendered components can be found [here](https://www.mudblazo ### `COLOR_PICKER` reference - Use `Type = "COLOR_PICKER"` to render a MudBlazor color picker. - Required props: - - `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`. + - `Name`: unique state key used in prompt assembly and `BuildPrompt(input)`. - `Label`: visible field label. - Optional props: - `Placeholder`: default color hex string (e.g. `#FF10FF`) or initial hint text. @@ -485,7 +501,7 @@ More information on rendered components can be found [here](https://www.mudblazo ### `DATE_PICKER` reference - Use `Type = "DATE_PICKER"` to render a MudBlazor date picker. - Required props: - - `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`. + - `Name`: unique state key used in prompt assembly and `BuildPrompt(input)`. - `Label`: visible field label. - Optional props: - `Value`: initial date string. Use the same format as `DateFormat`; default recommendation is `yyyy-MM-dd`. @@ -520,7 +536,7 @@ More information on rendered components can be found [here](https://www.mudblazo ### `DATE_RANGE_PICKER` reference - Use `Type = "DATE_RANGE_PICKER"` to render a MudBlazor date range picker. - Required props: - - `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`. + - `Name`: unique state key used in prompt assembly and `BuildPrompt(input)`. - `Label`: visible field label. - Optional props: - `Value`: initial range string using ` - `, for example `2026-03-01 - 2026-03-31`. @@ -557,7 +573,7 @@ More information on rendered components can be found [here](https://www.mudblazo ### `TIME_PICKER` reference - Use `Type = "TIME_PICKER"` to render a MudBlazor time picker. - Required props: - - `Name`: unique state key used in prompt assembly and `BuildPrompt(input.fields)`. + - `Name`: unique state key used in prompt assembly and `BuildPrompt(input)`. - `Label`: visible field label. - Optional props: - `Value`: initial time string. Use the same format as `TimeFormat`; default recommendations are `HH:mm` or `hh:mm tt`. @@ -613,34 +629,24 @@ If you want full control over prompt composition, define `ASSISTANT.BuildPrompt` --- ### `input` table shape The function receives a single `input` Lua table with: -- `input.fields`: values keyed by component `Name` - - Text area, single-select dropdown, and readers are strings - - Multiselect dropdown is an array-like Lua table of strings - - Switch is a boolean - - Color picker is the selected color as a string - - Date picker is the selected date as a string - - Date range picker is the selected range as a single string in ` - ` format - - Time picker is the selected time as a string -- `input.meta`: per-component metadata keyed by component `Name` +- `input.`: one entry per named component - `Type` (string, e.g. `TEXT_AREA`, `DROPDOWN`, `SWITCH`, `COLOR_PICKER`, `DATE_PICKER`, `DATE_RANGE_PICKER`, `TIME_PICKER`) - - `Label` (string, when provided) - - `UserPrompt` (string, when provided) + - `Value` (current component value) + - `Props` (readable component props) - `input.profile`: selected profile data - `Name`, `NeedToKnow`, `Actions`, `Num` - When no profile is selected, values match the built-in "Use no profile" entry + - `profile` is a reserved key in the input table ``` input = { - fields = { - [""] = "", - ... - }, - meta = { - [""] = { - Type = "", + [""] = { + Type = "", + Value = "", + Props = { + Name = "", Label = "", UserPrompt = "" - }, - ... + } }, profile = { Name = "", @@ -654,47 +660,66 @@ input = { ``` --- -### Using `meta` inside BuildPrompt -`input.meta` is useful when you want to dynamically build the prompt based on component type or reuse existing UI text (labels/user prompts). +### Using component metadata inside BuildPrompt +`input..Type` and `input..Props` are useful when you want to build prompts from a few specific fields without depending on the default `UserPrompt` assembly. -#### Example: iterate all fields with labels and include their values +#### Example: build a prompt from two fields ```lua ASSISTANT.BuildPrompt = function(input) + local topic = input.Topic and input.Topic.Value or "" + local includeSummary = input.IncludeSummary and input.IncludeSummary.Value or false + local parts = {} - for name, value in pairs(input.fields) do - local meta = input.meta[name] - if meta and meta.Label and value ~= "" then - table.insert(parts, meta.Label .. ": " .. tostring(value)) - end + if topic ~= "" then + table.insert(parts, "Topic: " .. topic) end + + if includeSummary then + table.insert(parts, "Add a short summary at the end.") + end + return table.concat(parts, "\n") end ``` -#### Example: handle types differently +#### Example: reuse a label from `Props` ```lua ASSISTANT.BuildPrompt = function(input) - local parts = {} - for name, meta in pairs(input.meta) do - local value = input.fields[name] - if meta.Type == "SWITCH" then - table.insert(parts, name .. ": " .. tostring(value)) - elseif meta.Type == "COLOR_PICKER" and value and value ~= "" then - table.insert(parts, name .. ": " .. value) - elseif meta.Type == "DATE_PICKER" and value and value ~= "" then - table.insert(parts, name .. ": " .. value) - elseif meta.Type == "DATE_RANGE_PICKER" and value and value ~= "" then - table.insert(parts, name .. ": " .. value) - elseif meta.Type == "TIME_PICKER" and value and value ~= "" then - table.insert(parts, name .. ": " .. value) - elseif value and value ~= "" then - table.insert(parts, name .. ": " .. value) - end + local main = input.Main + if not main then + return "" end - return table.concat(parts, "\n") + + local label = main.Props and main.Props.Label or "Main" + local value = main.Value or "" + return label .. ": " .. value end ``` ---- +--- + +### Callback result shape +Callbacks may return a partial state update: + +```lua +return { + state = { + [""] = { + Value = "", + Props = { + -- optional writable prop updates + } + } + } +} +``` + +- `Value` is optional +- `Props` is optional +- `Props` updates are partial +- non-writeable props are ignored and logged + +--- + ### Using `profile` inside BuildPrompt Profiles are optional user context (e.g., "NeedToKnow" and "Actions"). You can inject this directly into the user prompt if you want the LLM to always see it. @@ -707,7 +732,7 @@ ASSISTANT.BuildPrompt = function(input) table.insert(parts, input.profile.NeedToKnow) table.insert(parts, "") end - table.insert(parts, input.fields.Main or "") + table.insert(parts, input.Main and input.Main.Value or "") return table.concat(parts, "\n") end ``` @@ -1012,14 +1037,14 @@ The assistant runtime exposes basic logging helpers to Lua. Use them to debug cu - `LogDebug(message)` - `LogInfo(message)` -- `LogWarn(message)` +- `LogWarning(message)` - `LogError(message)` #### Example: Use Logging in lua functions ```lua ASSISTANT.BuildPrompt = function(input) LogInfo("BuildPrompt called") - return input.fields.Text or "" + return input.Text and input.Text.Value or "" end ``` --- diff --git a/app/MindWork AI Studio/Plugins/assistants/plugin.lua b/app/MindWork AI Studio/Plugins/assistants/plugin.lua index ea9ecc7f..2f97b61f 100644 --- a/app/MindWork AI Studio/Plugins/assistants/plugin.lua +++ b/app/MindWork AI Studio/Plugins/assistants/plugin.lua @@ -147,8 +147,8 @@ ASSISTANT = { ["IconColor"] = "", -- color of start and end icons on text buttons. Defaults to Inherit ["IconSize"] = "", -- 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 email = input.emailContent and input.emailContent.Value or "" + local translate = input.translateEmail and input.translateEmail.Value or false local output = email if translate then @@ -156,8 +156,10 @@ ASSISTANT = { end return { - fields = { - outputBuffer = output + state = { + outputBuffer = { + Value = output + } } } end, diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantState.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantState.cs index 0ff6584b..5676d0b3 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantState.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/DataModel/AssistantState.cs @@ -1,4 +1,3 @@ -using System.Collections; using AIStudio.Assistants.Dynamic; using Lua; @@ -159,6 +158,7 @@ public sealed class AssistantState { target[named.Name] = new LuaTable { + ["Type"] = Enum.GetName(component.Type) ?? string.Empty, ["Value"] = component is IStatefulAssistantComponent ? this.ReadValueForLua(named.Name) : LuaValue.Nil, ["Props"] = this.CreatePropsTable(component), }; @@ -176,7 +176,7 @@ public sealed class AssistantState if (this.SingleSelect.TryGetValue(name, out var singleSelectValue)) return singleSelectValue; if (this.MultiSelect.TryGetValue(name, out var multiSelectValue)) - return CreateLuaArray(multiSelectValue.OrderBy(static value => value, StringComparer.Ordinal)); + return AssistantLuaConversion.CreateLuaArray(multiSelectValue.OrderBy(static value => value, StringComparer.Ordinal)); if (this.Bools.TryGetValue(name, out var boolValue)) return boolValue; if (this.WebContent.TryGetValue(name, out var webContentValue)) @@ -210,138 +210,13 @@ public sealed class AssistantState if (!component.Props.TryGetValue(key, out var value)) continue; - if (!TryWriteLuaValue(table, key, value)) + if (!AssistantLuaConversion.TryWriteAssistantValue(table, key, value)) continue; } return table; } - private static bool TryWriteLuaValue(LuaTable table, string key, object? value) - { - if (value is null or LuaFunction) - return false; - - switch (value) - { - case LuaValue { Type: not LuaValueType.Nil } luaValue: - table[key] = luaValue; - return true; - case LuaTable luaTable: - table[key] = luaTable; - return true; - case string stringValue: - table[key] = (LuaValue)stringValue; - return true; - case bool boolValue: - table[key] = (LuaValue)boolValue; - return true; - case byte byteValue: - table[key] = (LuaValue)byteValue; - return true; - case sbyte sbyteValue: - table[key] = (LuaValue)sbyteValue; - return true; - case short shortValue: - table[key] = (LuaValue)shortValue; - return true; - case ushort ushortValue: - table[key] = (LuaValue)ushortValue; - return true; - case int intValue: - table[key] = (LuaValue)intValue; - return true; - case uint uintValue: - table[key] = (LuaValue)uintValue; - return true; - case long longValue: - table[key] = (LuaValue)longValue; - return true; - case ulong ulongValue: - table[key] = (LuaValue)ulongValue; - return true; - case float floatValue: - table[key] = (LuaValue)floatValue; - return true; - case double doubleValue: - table[key] = (LuaValue)doubleValue; - return true; - case decimal decimalValue: - table[key] = (LuaValue)(double)decimalValue; - return true; - case Enum enumValue: - table[key] = enumValue.ToString() ?? string.Empty; - return true; - case AssistantDropdownItem dropdownItem: - table[key] = CreateDropdownItemTable(dropdownItem); - return true; - case IEnumerable dropdownItems: - table[key] = CreateLuaArray(dropdownItems.Select(CreateDropdownItemTable)); - return true; - case IEnumerable listItems: - table[key] = CreateLuaArray(listItems.Select(CreateListItemTable)); - return true; - case IEnumerable strings: - table[key] = CreateLuaArray(strings); - return true; - default: - return false; - } - } - - private static LuaTable CreateDropdownItemTable(AssistantDropdownItem item) => - new() - { - ["Value"] = item.Value, - ["Display"] = item.Display, - }; - - private static LuaTable CreateListItemTable(AssistantListItem item) - { - var table = new LuaTable - { - ["Type"] = item.Type, - ["Text"] = item.Text, - ["Icon"] = item.Icon, - ["IconColor"] = item.IconColor, - }; - - if (!string.IsNullOrWhiteSpace(item.Href)) - table["Href"] = item.Href; - - return table; - } - - private static LuaTable CreateLuaArray(IEnumerable values) - { - var luaArray = new LuaTable(); - var index = 1; - - foreach (var value in values) - luaArray[index++] = value switch - { - null => LuaValue.Nil, - LuaValue luaValue => luaValue, - LuaTable luaTable => luaTable, - string stringValue => (LuaValue)stringValue, - bool boolValue => (LuaValue)boolValue, - byte byteValue => (LuaValue)byteValue, - sbyte sbyteValue => (LuaValue)sbyteValue, - short shortValue => (LuaValue)shortValue, - ushort ushortValue => (LuaValue)ushortValue, - int intValue => (LuaValue)intValue, - uint uintValue => (LuaValue)uintValue, - long longValue => (LuaValue)longValue, - ulong ulongValue => (LuaValue)ulongValue, - float floatValue => (LuaValue)floatValue, - double doubleValue => (LuaValue)doubleValue, - decimal decimalValue => (LuaValue)(double)decimalValue, - _ => LuaValue.Nil, - }; - - return luaArray; - } - private static HashSet ReadStringValues(LuaTable values) { var parsedValues = new HashSet(StringComparer.Ordinal); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs index f9ef85e9..b5043d21 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/Assistants/PluginAssistants.cs @@ -399,141 +399,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType return true; } - return this.TryConvertLuaValue(val, out result); - } - - private bool TryConvertLuaValue(LuaValue val, out object result) - { - if (val.TryRead(out var s)) - { - result = s; - return true; - } - if (val.TryRead(out var b)) - { - result = b; - return true; - } - if (val.TryRead(out var d)) - { - result = d; - return true; - } - - if (val.TryRead(out var table) && this.TryParseDropdownItem(table, out var item)) - { - result = item; - return true; - } - - if (val.TryRead(out var listTable) && this.TryParseDropdownItemList(listTable, out var itemList)) - { - result = itemList; - return true; - } - - if (val.TryRead(out var listItemListTable) && this.TryParseListItemList(listItemListTable, out var listItemList)) - { - result = listItemList; - return true; - } - - result = null!; - return false; - } - - private bool TryParseDropdownItem(LuaTable table, out AssistantDropdownItem item) - { - item = new AssistantDropdownItem(); - - if (!table.TryGetValue("Value", out var valueVal) || !valueVal.TryRead(out var value)) - return false; - - if (!table.TryGetValue("Display", out var displayVal) || !displayVal.TryRead(out var display)) - return false; - - item.Value = value; - item.Display = display; - return true; - } - - - private bool TryParseDropdownItemList(LuaTable table, out List items) - { - items = new List(); - - var length = table.ArrayLength; - for (var i = 1; i <= length; i++) - { - var value = table[i]; - - if (value.TryRead(out var subTable) && this.TryParseDropdownItem(subTable, out var item)) - { - items.Add(item); - } - else - { - items = null!; - return false; - } - } - - return true; - } - - private bool TryParseListItem(LuaTable table, out AssistantListItem item) - { - item = new AssistantListItem(); - - if (!table.TryGetValue("Text", out var textVal) || !textVal.TryRead(out var text)) - return false; - - if (!table.TryGetValue("Type", out var typeVal) || !typeVal.TryRead(out var type)) - return false; - - table.TryGetValue("Icon", out var iconVal); - iconVal.TryRead(out var icon); - icon ??= string.Empty; - - table.TryGetValue("IconColor", out var iconColorVal); - iconColorVal.TryRead(out var iconColor); - iconColor ??= string.Empty; - - - item.Text = text; - item.Type = type; - item.Icon = icon; - item.IconColor = iconColor; - - if (table.TryGetValue("Href", out var hrefVal) && hrefVal.TryRead(out var href)) - { - item.Href = href; - } - - return true; - } - - private bool TryParseListItemList(LuaTable table, out List items) - { - items = new List(); - - var length = table.ArrayLength; - for (var i = 1; i <= length; i++) - { - var value = table[i]; - - if (value.TryRead(out var subTable) && this.TryParseListItem(subTable, out var item)) - { - items.Add(item); - } - else - { - items = null!; - return false; - } - } - - return true; + return AssistantLuaConversion.TryReadScalarOrStructuredValue(val, out result); } private void RegisterLuaHelpers()