2026-02-24 10:31:16 +00:00
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
|
|
|
|
using AIStudio.Dialogs.Settings;
|
2025-09-30 19:53:56 +00:00
|
|
|
using AIStudio.Tools.PluginSystem;
|
2026-02-23 14:01:00 +00:00
|
|
|
using AIStudio.Settings;
|
2025-09-30 19:53:56 +00:00
|
|
|
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!;
|
2025-11-10 16:01:49 +00:00
|
|
|
|
|
|
|
|
protected override string Title => this.title;
|
|
|
|
|
protected override string Description => this.description;
|
|
|
|
|
protected override string SystemPrompt => this.systemPrompt;
|
|
|
|
|
protected override bool AllowProfiles => this.allowProfiles;
|
2026-02-23 14:01:00 +00:00
|
|
|
protected override bool ShowProfileSelection => this.showFooterProfileSelection;
|
2025-11-10 16:01:49 +00:00
|
|
|
protected override string SubmitText => this.submitText;
|
|
|
|
|
protected override Func<Task> SubmitAction => this.Submit;
|
|
|
|
|
public override Tools.Components Component { get; }
|
2025-09-30 19:53:56 +00:00
|
|
|
|
|
|
|
|
private string? inputText;
|
|
|
|
|
private string title = string.Empty;
|
|
|
|
|
private string description = string.Empty;
|
|
|
|
|
private string systemPrompt = string.Empty;
|
|
|
|
|
private bool allowProfiles = true;
|
2025-11-10 16:01:49 +00:00
|
|
|
private string submitText = string.Empty;
|
|
|
|
|
private string selectedTargetLanguage = string.Empty;
|
|
|
|
|
private string customTargetLanguage = string.Empty;
|
2026-02-23 14:01:00 +00:00
|
|
|
private bool showFooterProfileSelection = true;
|
2025-11-10 16:01:49 +00:00
|
|
|
|
|
|
|
|
private Dictionary<string, string> inputFields = new();
|
2025-11-11 14:57:15 +00:00
|
|
|
private Dictionary<string, string> dropdownFields = new();
|
2025-11-11 18:06:44 +00:00
|
|
|
private Dictionary<string, bool> switchFields = new();
|
2026-02-23 15:13:28 +00:00
|
|
|
private Dictionary<string, WebContentState> webContentFields = new();
|
2026-02-23 15:33:24 +00:00
|
|
|
private Dictionary<string, FileContentState> fileContentFields = new();
|
2026-02-24 10:31:16 +00:00
|
|
|
private readonly Dictionary<string, string> imageCache = new();
|
|
|
|
|
private string pluginPath = string.Empty;
|
|
|
|
|
const string PLUGIN_SCHEME = "plugin://";
|
2025-11-10 16:01:49 +00:00
|
|
|
|
2025-09-30 19:53:56 +00:00
|
|
|
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;
|
2025-11-10 16:01:49 +00:00
|
|
|
this.submitText = assistantPlugin.SubmitText;
|
2025-09-30 19:53:56 +00:00
|
|
|
this.allowProfiles = assistantPlugin.AllowProfiles;
|
2026-02-23 14:01:00 +00:00
|
|
|
this.showFooterProfileSelection = !assistantPlugin.HasEmbeddedProfileSelection;
|
2026-02-24 10:31:16 +00:00
|
|
|
this.pluginPath = assistantPlugin.PluginPath;
|
2025-09-30 19:53:56 +00:00
|
|
|
}
|
2025-11-10 16:01:49 +00:00
|
|
|
|
|
|
|
|
foreach (var component in this.RootComponent!.Children)
|
|
|
|
|
{
|
|
|
|
|
switch (component.Type)
|
|
|
|
|
{
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.TEXT_AREA:
|
2025-11-10 16:01:49 +00:00
|
|
|
if (component is AssistantTextArea textArea)
|
|
|
|
|
{
|
2025-11-11 13:51:34 +00:00
|
|
|
this.inputFields.Add(textArea.Name, textArea.PrefillText);
|
2025-11-10 16:01:49 +00:00
|
|
|
}
|
|
|
|
|
break;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.DROPDOWN:
|
2025-11-11 14:57:15 +00:00
|
|
|
if (component is AssistantDropdown dropdown)
|
|
|
|
|
{
|
|
|
|
|
this.dropdownFields.Add(dropdown.Name, dropdown.Default.Value);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.SWITCH:
|
2025-11-11 18:06:44 +00:00
|
|
|
if (component is AssistantSwitch switchComponent)
|
|
|
|
|
{
|
|
|
|
|
this.switchFields.Add(switchComponent.Name, switchComponent.Value);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.WEB_CONTENT_READER:
|
2026-02-23 15:13:28 +00:00
|
|
|
if (component is AssistantWebContentReader webContent)
|
|
|
|
|
{
|
|
|
|
|
this.webContentFields.Add(webContent.Name, new WebContentState
|
|
|
|
|
{
|
|
|
|
|
Preselect = webContent.Preselect,
|
|
|
|
|
PreselectContentCleanerAgent = webContent.PreselectContentCleanerAgent,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.FILE_CONTENT_READER:
|
2026-02-23 15:33:24 +00:00
|
|
|
if (component is AssistantFileContentReader fileContent)
|
|
|
|
|
{
|
|
|
|
|
this.fileContentFields.Add(fileContent.Name, new FileContentState());
|
|
|
|
|
}
|
|
|
|
|
break;
|
2025-11-10 16:01:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-30 19:53:56 +00:00
|
|
|
base.OnInitialized();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void ResetForm()
|
|
|
|
|
{
|
2025-11-10 16:01:49 +00:00
|
|
|
foreach (var entry in this.inputFields)
|
|
|
|
|
{
|
|
|
|
|
this.inputFields[entry.Key] = string.Empty;
|
|
|
|
|
}
|
2026-02-23 15:13:28 +00:00
|
|
|
foreach (var entry in this.webContentFields)
|
|
|
|
|
{
|
|
|
|
|
entry.Value.Content = string.Empty;
|
|
|
|
|
entry.Value.AgentIsRunning = false;
|
|
|
|
|
}
|
2026-02-23 15:33:24 +00:00
|
|
|
foreach (var entry in this.fileContentFields)
|
|
|
|
|
{
|
|
|
|
|
entry.Value.Content = string.Empty;
|
|
|
|
|
}
|
2025-09-30 19:53:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override bool MightPreselectValues()
|
|
|
|
|
{
|
2025-11-10 16:01:49 +00:00
|
|
|
Console.WriteLine("throw new NotImplementedException();");
|
|
|
|
|
return false;
|
2025-09-30 19:53:56 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 10:31:16 +00:00
|
|
|
private string ResolveImageSource(AssistantImage image)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(image.Src))
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
|
|
if (this.imageCache.TryGetValue(image.Src, out var cached) && !string.IsNullOrWhiteSpace(cached))
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
|
|
var resolved = image.Src;
|
|
|
|
|
|
|
|
|
|
if (resolved.StartsWith(PLUGIN_SCHEME, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(this.pluginPath))
|
|
|
|
|
{
|
|
|
|
|
var relative = resolved[PLUGIN_SCHEME.Length..].TrimStart('/', '\\').Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
|
|
|
|
|
var filePath = Path.Join(this.pluginPath, relative);
|
|
|
|
|
if (File.Exists(filePath))
|
|
|
|
|
{
|
|
|
|
|
var mime = GetImageMimeType(filePath);
|
|
|
|
|
var data = Convert.ToBase64String(File.ReadAllBytes(filePath));
|
|
|
|
|
resolved = $"data:{mime};base64,{data}";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
resolved = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (Uri.TryCreate(resolved, UriKind.Absolute, out var uri))
|
|
|
|
|
{
|
|
|
|
|
if (uri.Scheme is not ("http" or "https" or "data"))
|
|
|
|
|
resolved = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
resolved = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.imageCache[image.Src] = resolved;
|
|
|
|
|
return resolved;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetImageMimeType(string path)
|
|
|
|
|
{
|
|
|
|
|
var extension = Path.GetExtension(path).TrimStart('.').ToLowerInvariant();
|
|
|
|
|
return extension switch
|
|
|
|
|
{
|
|
|
|
|
"svg" => "image/svg+xml",
|
|
|
|
|
"png" => "image/png",
|
|
|
|
|
"jpg" => "image/jpeg",
|
|
|
|
|
"jpeg" => "image/jpeg",
|
|
|
|
|
"gif" => "image/gif",
|
|
|
|
|
"webp" => "image/webp",
|
|
|
|
|
"bmp" => "image/bmp",
|
|
|
|
|
_ => "image/png",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 16:01:49 +00:00
|
|
|
private string? ValidateCustomLanguage(string value) => string.Empty;
|
|
|
|
|
|
|
|
|
|
private string CollectUserPrompt()
|
|
|
|
|
{
|
|
|
|
|
var prompt = string.Empty;
|
2025-11-11 13:51:34 +00:00
|
|
|
|
|
|
|
|
foreach (var component in this.RootComponent!.Children)
|
2025-11-10 16:01:49 +00:00
|
|
|
{
|
2025-11-11 13:51:34 +00:00
|
|
|
var userInput = string.Empty;
|
2025-11-11 18:06:44 +00:00
|
|
|
var userDecision = false;
|
2025-11-11 13:51:34 +00:00
|
|
|
switch (component.Type)
|
|
|
|
|
{
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.TEXT_AREA:
|
2025-11-11 13:51:34 +00:00
|
|
|
if (component is AssistantTextArea textArea)
|
|
|
|
|
{
|
2025-11-11 18:06:44 +00:00
|
|
|
prompt += $"context:{Environment.NewLine}{textArea.UserPrompt}{Environment.NewLine}---{Environment.NewLine}";
|
2025-11-11 13:51:34 +00:00
|
|
|
if (this.inputFields.TryGetValue(textArea.Name, out userInput))
|
|
|
|
|
{
|
|
|
|
|
prompt += $"user prompt:{Environment.NewLine}{userInput}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.DROPDOWN:
|
2025-11-11 14:57:15 +00:00
|
|
|
if (component is AssistantDropdown dropdown)
|
|
|
|
|
{
|
2025-11-11 18:06:44 +00:00
|
|
|
prompt += $"{Environment.NewLine}context:{Environment.NewLine}{dropdown.UserPrompt}{Environment.NewLine}---{Environment.NewLine}";
|
2025-11-11 14:57:15 +00:00
|
|
|
if (this.dropdownFields.TryGetValue(dropdown.Name, out userInput))
|
|
|
|
|
{
|
|
|
|
|
prompt += $"user prompt:{Environment.NewLine}{userInput}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.SWITCH:
|
2025-11-11 18:06:44 +00:00
|
|
|
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;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.WEB_CONTENT_READER:
|
2026-02-23 15:13:28 +00:00
|
|
|
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;
|
2026-02-24 10:39:17 +00:00
|
|
|
case AssistantComponentType.FILE_CONTENT_READER:
|
2026-02-23 15:33:24 +00:00
|
|
|
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;
|
2025-11-11 13:51:34 +00:00
|
|
|
default:
|
|
|
|
|
prompt += $"{userInput}{Environment.NewLine}";
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-11-10 16:01:49 +00:00
|
|
|
}
|
2025-11-11 14:57:15 +00:00
|
|
|
|
2025-11-10 16:01:49 +00:00
|
|
|
return prompt;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 14:01:00 +00:00
|
|
|
private string? ValidateProfileSelection(AssistantProfileSelection profileSelection, Profile profile)
|
|
|
|
|
{
|
|
|
|
|
if (profile == default || profile == Profile.NO_PROFILE)
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(profileSelection.ValidationMessage))
|
|
|
|
|
return profileSelection.ValidationMessage;
|
|
|
|
|
|
|
|
|
|
return this.T("Please select one of your profiles.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 16:01:49 +00:00
|
|
|
private async Task Submit()
|
|
|
|
|
{
|
|
|
|
|
this.CreateChatThread();
|
2025-11-11 14:57:15 +00:00
|
|
|
var time = this.AddUserRequest(this.CollectUserPrompt());
|
2025-11-10 16:01:49 +00:00
|
|
|
await this.AddAIResponseAsync(time);
|
|
|
|
|
}
|
2026-02-23 15:33:24 +00:00
|
|
|
|
2026-02-23 14:01:00 +00:00
|
|
|
}
|