diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor index d014a3c0..7b729b23 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor @@ -87,22 +87,42 @@ case AssistantComponentType.DROPDOWN: if (component is AssistantDropdown assistantDropdown) { - + if (assistantDropdown.IsMultiselect) + { + + } + else + { + + } } break; case AssistantComponentType.BUTTON: diff --git a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs index cc68282d..fa0d31be 100644 --- a/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs +++ b/app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs @@ -43,6 +43,7 @@ public partial class AssistantDynamic : AssistantBaseCore private readonly Dictionary inputFields = new(); private readonly Dictionary dropdownFields = new(); + private readonly Dictionary> multiselectDropdownFields = new(); private readonly Dictionary switchFields = new(); private readonly Dictionary webContentFields = new(); private readonly Dictionary fileContentFields = new(); @@ -218,6 +219,8 @@ public partial class AssistantDynamic : AssistantBaseCore fields[entry.Key] = entry.Value ?? string.Empty; foreach (var entry in this.dropdownFields) fields[entry.Key] = entry.Value ?? string.Empty; + foreach (var entry in this.multiselectDropdownFields) + fields[entry.Key] = CreateLuaArray(entry.Value); foreach (var entry in this.switchFields) fields[entry.Key] = entry.Value; foreach (var entry in this.webContentFields) @@ -287,8 +290,18 @@ public partial class AssistantDynamic : AssistantBaseCore this.inputFields.Add(textArea.Name, textArea.PrefillText); break; case AssistantComponentType.DROPDOWN: - if (component is AssistantDropdown dropdown && !this.dropdownFields.ContainsKey(dropdown.Name)) - this.dropdownFields.Add(dropdown.Name, dropdown.Default.Display); + if (component is AssistantDropdown dropdown) + { + if (dropdown.IsMultiselect) + { + if (!this.multiselectDropdownFields.ContainsKey(dropdown.Name)) + this.multiselectDropdownFields.Add(dropdown.Name, CreateInitialMultiselectValues(dropdown)); + } + else if (!this.dropdownFields.ContainsKey(dropdown.Name)) + { + this.dropdownFields.Add(dropdown.Name, dropdown.Default.Value); + } + } break; case AssistantComponentType.SWITCH: if (component is AssistantSwitch switchComponent && !this.switchFields.ContainsKey(switchComponent.Name)) @@ -399,6 +412,17 @@ public partial class AssistantDynamic : AssistantBaseCore return; } + if (this.multiselectDropdownFields.ContainsKey(fieldName)) + { + if (value.TryRead(out var multiselectDropdownValue)) + this.multiselectDropdownFields[fieldName] = ReadStringValues(multiselectDropdownValue); + else if (value.TryRead(out var singleDropdownValue)) + this.multiselectDropdownFields[fieldName] = string.IsNullOrWhiteSpace(singleDropdownValue) ? [] : [singleDropdownValue]; + else + this.LogFieldUpdateTypeMismatch(fieldName, "string[]"); + return; + } + if (this.switchFields.ContainsKey(fieldName)) { if (value.TryRead(out var boolValue)) @@ -443,6 +467,12 @@ public partial class AssistantDynamic : AssistantBaseCore this.Logger.LogWarning("Assistant BUTTON action tried to write an invalid value to '{FieldName}'. Expected {ExpectedType}.", fieldName, expectedType); } + private EventCallback> CreateMultiselectDropdownChangedCallback(string fieldName) => + EventCallback.Factory.Create>(this, values => + { + this.multiselectDropdownFields[fieldName] = values; + }); + private string? ValidateProfileSelection(AssistantProfileSelection profileSelection, Profile? profile) { if (profile == default || profile == Profile.NO_PROFILE) @@ -517,7 +547,9 @@ public partial class AssistantDynamic : AssistantBaseCore 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)) + if (dropdown.IsMultiselect && this.multiselectDropdownFields.TryGetValue(dropdown.Name, out var selections)) + prompt += $"user prompt:{Environment.NewLine}{string.Join(Environment.NewLine, selections.OrderBy(static value => value, StringComparer.Ordinal))}"; + else if (this.dropdownFields.TryGetValue(dropdown.Name, out userInput)) prompt += $"user prompt:{Environment.NewLine}{userInput}"; } break; @@ -567,4 +599,36 @@ public partial class AssistantDynamic : AssistantBaseCore return prompt; } + + private static HashSet CreateInitialMultiselectValues(AssistantDropdown dropdown) + { + if (string.IsNullOrWhiteSpace(dropdown.Default.Value)) + return []; + + return [dropdown.Default.Value]; + } + + private static LuaTable CreateLuaArray(IEnumerable values) + { + var luaArray = new LuaTable(); + var index = 1; + + foreach (var value in values.OrderBy(static value => value, StringComparer.Ordinal)) + luaArray[index++] = value; + + return luaArray; + } + + private static HashSet ReadStringValues(LuaTable values) + { + var parsedValues = new HashSet(StringComparer.Ordinal); + + foreach (var entry in values) + { + if (entry.Value.TryRead(out var value) && !string.IsNullOrWhiteSpace(value)) + parsedValues.Add(value); + } + + return parsedValues; + } } diff --git a/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor b/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor index 9ad8880a..8723138f 100644 --- a/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor +++ b/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor @@ -1,27 +1,52 @@ @using AIStudio.Tools.PluginSystem.Assistants.DataModel - - @foreach (var item in Items) - { - - @item.Display - - } - + @if (this.IsMultiselect) + { + + @foreach (var item in Items) + { + + @item.Display + + } + + } + else + { + + @foreach (var item in Items) + { + + @item.Display + + } + + } diff --git a/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor.cs b/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor.cs index 22896792..df59aa3c 100644 --- a/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor.cs +++ b/app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using AIStudio.Tools.PluginSystem.Assistants.DataModel; using Microsoft.AspNetCore.Components; @@ -17,6 +18,10 @@ namespace AIStudio.Components [Parameter] public EventCallback ValueChanged { get; set; } + [Parameter] public HashSet SelectedValues { get; set; } = []; + + [Parameter] public EventCallback> SelectedValuesChanged { get; set; } + [Parameter] public string Label { get; set; } = string.Empty; [Parameter] public string HelperText { get; set; } = string.Empty; @@ -52,6 +57,20 @@ namespace AIStudio.Components } } + private async Task OnSelectedValuesChanged(IEnumerable? newValues) + { + var updatedValues = newValues? + .Where(value => !string.IsNullOrWhiteSpace(value)) + .Select(value => value!) + .ToHashSet(StringComparer.Ordinal) ?? []; + + if (this.SelectedValues.SetEquals(updatedValues)) + return; + + this.SelectedValues = updatedValues; + await this.SelectedValuesChanged.InvokeAsync(updatedValues); + } + private string MergeClasses(string custom, string fallback) { var trimmedCustom = custom?.Trim() ?? string.Empty; @@ -62,4 +81,4 @@ namespace AIStudio.Components return string.IsNullOrEmpty(trimmedFallback) ? trimmedCustom : $"{trimmedCustom} {trimmedFallback}"; } } -} \ No newline at end of file +}