Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b9a9f2d
WIP: Working out a data model and an example lua file to turn it into…
nilskruthoff Jul 21, 2025
04c3060
WIP: changed to correct namespaces
Jul 21, 2025
f5c475f
Added a component factory and refactored Dropdowns; reformat getters
Jul 21, 2025
cef643c
WIP: Implementing a parser for the lua data structure
Jul 22, 2025
aba61ff
Merge branch 'refs/heads/main' into 29-add-an-assistant-builder
Sep 23, 2025
7b60c5f
changing the lua file to become a more generic usage example
Sep 29, 2025
c12fa64
removing assistant from the PluginCategory enum
Sep 29, 2025
b42ee50
included the assistant plugin to load during plugin initialization
Sep 29, 2025
5a6c91f
included valid assistant plugins into the running plugins
Sep 30, 2025
3d5831f
added a settings dialog for dynamic assistants
Sep 30, 2025
aa31fa7
included the installed plugin assistants to the assistants page
Sep 30, 2025
09b187d
started with the rendering of dynamic assistants
Sep 30, 2025
488747b
included allowProfiles and system prompt properties to the lua parser
Sep 30, 2025
d37c3f2
fixed dropdown parsing
Sep 30, 2025
2afc8c6
added DynamicAssistantDropdown to accomodate lua structure; included …
Nov 10, 2025
5a3e49d
increased functionality for text area by adding optional properties
Nov 11, 2025
4fd21ad
fixed bug in dropdown that prevented values from changing; finished i…
Nov 11, 2025
0775b03
added a switch component for boolean user inputs
Nov 11, 2025
9374b56
updated example plugin.lua for dynamic assistants
Nov 11, 2025
bc50b37
Added new descriptive heading and text components
Feb 10, 2026
de1cf65
added a descriptive list component
Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
@attribute [Route(Routes.ASSISTANT_DYNAMIC)]
@using AIStudio.Tools.PluginSystem.Assistants.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogDynamic>

@foreach (var component in this.RootComponent!.Children)
{
@switch (component.Type)
{
case AssistantUiCompontentType.TEXT_AREA:
if (component is AssistantTextArea textArea)
{
var lines = textArea.IsSingleLine ? 1 : 6;
<MudTextField T="string" @bind-Text="@this.inputFields[textArea.Name]" Label="@textArea.Label" ReadOnly="@textArea.ReadOnly" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Variant="Variant.Outlined" Lines="@lines" AutoGrow="@true" MaxLines="12" Class="mb-3"/>
}
break;

case AssistantUiCompontentType.DROPDOWN:
if (component is AssistantDropdown assistantDropdown)
{
<DynamicAssistantDropdown Items="@assistantDropdown.Items"
@bind-Value="@this.dropdownFields[assistantDropdown.Name]"
Default="@assistantDropdown.Default"
Label="@assistantDropdown.Label"
Icon="@Icons.Material.Filled.Translate"/>
}
break;
case AssistantUiCompontentType.PROVIDER_SELECTION:
if (component is AssistantProviderSelection providerSelection)
{
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
}
break;
case AssistantUiCompontentType.SWITCH:
if (component is AssistantSwitch assistantSwitch)
{
<MudTextSwitch Label="@assistantSwitch.Label" @bind-Value="@this.switchFields[assistantSwitch.Name]" LabelOn="@assistantSwitch.LabelOn" LabelOff="@assistantSwitch.LabelOff" />
}
break;
case AssistantUiCompontentType.HEADING:
if (component is AssistantHeading assistantHeading)
{
var heading = assistantHeading;
@switch (assistantHeading.Level)
{
case 1:
<MudText Typo="Typo.h4">@heading.Text</MudText>
break;
case 2:
<MudText Typo="Typo.h5">@heading.Text</MudText>
break;
case 3:
<MudText Typo="Typo.h6">@heading.Text</MudText>
break;
default:
<MudText Typo="Typo.h4">@heading.Text</MudText>
break;
}
}
break;
case AssistantUiCompontentType.TEXT:
if (component is AssistantText assistantText)
{
var text = assistantText;
<MudText Typo="Typo.body1" Class="mb-3">@text.Content</MudText>
}
break;
case AssistantUiCompontentType.LIST:
if (component is AssistantList assistantList)
{
var list = assistantList;
<MudList T="string" Class="mb-6">
@foreach (var item in list.Items)
{
@if (item.Type == "LINK")
{
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="@item.Href">@item.Text</MudListItem>
}
else
{
<MudListItem T="string">@item.Text</MudListItem>
}
}
</MudList>
}
break;
}
}
147 changes: 147 additions & 0 deletions app/MindWork AI Studio/Assistants/Dynamic/AssistantDynamic.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using Microsoft.AspNetCore.Components;

namespace AIStudio.Assistants.Dynamic;

public partial class AssistantDynamic : AssistantBaseCore<SettingsDialogDynamic>
{
[Parameter]
public AssistantForm? RootComponent { get; set; } = null!;

protected override string Title => this.title;
protected override string Description => this.description;
protected override string SystemPrompt => this.systemPrompt;
protected override bool AllowProfiles => this.allowProfiles;
protected override string SubmitText => this.submitText;
protected override Func<Task> SubmitAction => this.Submit;
public override Tools.Components Component { get; }

private string? inputText;
private string title = string.Empty;
private string description = string.Empty;
private string systemPrompt = string.Empty;
private bool allowProfiles = true;
private string submitText = string.Empty;
private string selectedTargetLanguage = string.Empty;
private string customTargetLanguage = string.Empty;

private Dictionary<string, string> inputFields = new();
private Dictionary<string, string> dropdownFields = new();
private Dictionary<string, bool> switchFields = new();

protected override void OnInitialized()
{
var guid = Guid.Parse("958312de-a9e7-4666-901f-4d5b61647efb");
var plugin = PluginFactory.RunningPlugins.FirstOrDefault(e => e.Id == guid);
if (plugin is PluginAssistants assistantPlugin)
{
this.RootComponent = assistantPlugin.RootComponent;
this.title = assistantPlugin.AssistantTitle;
this.description = assistantPlugin.AssistantDescription;
this.systemPrompt = assistantPlugin.SystemPrompt;
this.submitText = assistantPlugin.SubmitText;
this.allowProfiles = assistantPlugin.AllowProfiles;
}

foreach (var component in this.RootComponent!.Children)
{
switch (component.Type)
{
case AssistantUiCompontentType.TEXT_AREA:
if (component is AssistantTextArea textArea)
{
this.inputFields.Add(textArea.Name, textArea.PrefillText);
}
break;
case AssistantUiCompontentType.DROPDOWN:
if (component is AssistantDropdown dropdown)
{
this.dropdownFields.Add(dropdown.Name, dropdown.Default.Value);
}
break;
case AssistantUiCompontentType.SWITCH:
if (component is AssistantSwitch switchComponent)
{
this.switchFields.Add(switchComponent.Name, switchComponent.Value);
}
break;
}
}
base.OnInitialized();
}

protected override void ResetForm()
{
foreach (var entry in this.inputFields)
{
this.inputFields[entry.Key] = string.Empty;
}
}

protected override bool MightPreselectValues()
{
Console.WriteLine("throw new NotImplementedException();");
return false;
}

private string? ValidateCustomLanguage(string value) => string.Empty;

private string CollectUserPrompt()
{
var prompt = string.Empty;

foreach (var component in this.RootComponent!.Children)
{
var userInput = string.Empty;
var userDecision = false;
switch (component.Type)
{
case AssistantUiCompontentType.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 AssistantUiCompontentType.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 AssistantUiCompontentType.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;
default:
prompt += $"{userInput}{Environment.NewLine}";
break;
}
}

return prompt;
}

private async Task Submit()
{
this.CreateChatThread();
var time = this.AddUserRequest(this.CollectUserPrompt());
await this.AddAIResponseAsync(time);
}
}
21 changes: 21 additions & 0 deletions app/MindWork AI Studio/Assistants/I18N/allTexts.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5353,6 +5353,27 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T567205144"] = "It seems that Pandoc i
-- The latest Pandoc version was not found, installing version {0} instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PANDOC::T726914939"] = "The latest Pandoc version was not found, installing version {0} instead."

-- The ASSISTANT table does not contain a valid description.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T2080819991"] = "The ASSISTANT table does not contain a valid description."

-- The ASSISTANT table does not contain a the boolean flag to control the allowance of profiles.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T221472268"] = "The ASSISTANT table does not contain a the boolean flag to control the allowance of profiles."

-- Failed to parse the UI render tree.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T2583341941"] = "Failed to parse the UI render tree."

-- The ASSISTANT table does not contain a valid UI section.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3126717084"] = "The ASSISTANT table does not contain a valid UI section."

-- The ASSISTANT table does not contain a valid system prompt.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T3723171842"] = "The ASSISTANT table does not contain a valid system prompt."

-- The ASSISTANT table does not exist or is not a valid table.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T6004146"] = "The ASSISTANT table does not exist or is not a valid table."

-- The ASSISTANT table does not contain a valid title.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTS::T998753547"] = "The ASSISTANT table does not contain a valid title."

-- The table AUTHORS does not exist or is using an invalid syntax.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T1068328139"] = "The table AUTHORS does not exist or is using an invalid syntax."

Expand Down
21 changes: 21 additions & 0 deletions app/MindWork AI Studio/Components/DynamicAssistantDropdown.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<MudStack Row="true" Class="mb-3">
<MudSelect
T="string"
Value="@this.Value"
ValueChanged="@(val => this.OnValueChanged(val))"
Label="@this.Label"
Placeholder="@this.Default.Value"
AdornmentIcon="@this.Icon"
Adornment="Adornment.Start"
Variant="Variant.Outlined"
Margin="Margin.Dense"
MultiSelection="false"
>
@foreach (var item in Items)
{
<MudSelectItem Value="@item.Value">
@item.Display
</MudSelectItem>
}
</MudSelect>
</MudStack>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
using Microsoft.AspNetCore.Components;
using MudBlazor;

namespace AIStudio.Components
{
public partial class DynamicAssistantDropdown : ComponentBase
{
[Parameter]
public List<AssistantDropdownItem> Items { get; set; } = new();

[Parameter]
public AssistantDropdownItem Default { get; set; } = new();

[Parameter]
public string Value { get; set; } = string.Empty;

[Parameter]
public EventCallback<string> ValueChanged { get; set; }

[Parameter]
public string Label { get; set; } = string.Empty;

[Parameter]
public Func<string, string?> ValidateSelection { get; set; } = _ => null;

[Parameter]
public string Icon { get; set; } = Icons.Material.Filled.ArrowDropDown;

private async Task OnValueChanged(string newValue)
{
if (this.Value != newValue)
{
this.Value = newValue;
await this.ValueChanged.InvokeAsync(newValue);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@using AIStudio.Settings
@inherits SettingsDialogBase

<MudDialog>
</MudDialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Microsoft.AspNetCore.Components;

namespace AIStudio.Dialogs.Settings;

public partial class SettingsDialogDynamic : SettingsDialogBase;
11 changes: 11 additions & 0 deletions app/MindWork AI Studio/Pages/Assistants.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@using AIStudio.Dialogs.Settings
@using AIStudio.Settings.DataModel
@using AIStudio.Tools.PluginSystem
@using AIStudio.Tools.PluginSystem.Assistants
@using ReverseMarkdown.Converters
@attribute [Route(Routes.ASSISTANTS)]
@inherits MSGComponentBase

Expand Down Expand Up @@ -31,6 +34,14 @@
<AssistantBlock TSettings="SettingsDialogJobPostings" Name="@T("Job Posting")" Description="@T("Generate a job posting for a given job description.")" Icon="@Icons.Material.Filled.Work" Link="@Routes.ASSISTANT_JOB_POSTING"/>
<AssistantBlock TSettings="SettingsDialogLegalCheck" Name="@T("Legal Check")" Description="@T("Ask a question about a legal document.")" Icon="@Icons.Material.Filled.Gavel" Link="@Routes.ASSISTANT_LEGAL_CHECK"/>
<AssistantBlock TSettings="SettingsDialogIconFinder" Name="@T("Icon Finder")" Description="@T("Use an LLM to find an icon for a given context.")" Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_ICON_FINDER"/>

@foreach (var assistant in PluginFactory.RunningPlugins.Where(e => e.Type == PluginType.ASSISTANT))
{
if (assistant is PluginAssistants assistantPlugin)
{
<AssistantBlock TSettings="SettingsDialogTranslation" Name="@T(assistantPlugin.AssistantTitle)" Description="@T(assistantPlugin.Description)" Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_DYNAMIC"/>
}
}
</MudStack>

<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
Expand Down
1 change: 1 addition & 0 deletions app/MindWork AI Studio/Plugins/assistants/icon.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SVG = [[<svg enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#1f1f1f"><g><path d="M0,0h24v24H0V0z" fill="none"/><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></g></svg>]]
Loading