mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-08-21 01:52:57 +00:00
Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
be11efed67 | ||
|
7d10bb00f7 | ||
|
9ca2860079 |
17
.junie/guidelines.md
Normal file
17
.junie/guidelines.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Project Guidelines
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
- The repository and the app consist of a Rust project in the `runtime` folder and a .NET solution in the `app` folder.
|
||||||
|
- The .NET solution then contains 4 .NET projects:
|
||||||
|
- `Build Script` is not required for running the app; instead, it contains the build script for creating new releases, for example.
|
||||||
|
- `MindWork AI Studio` contains the actual app code.
|
||||||
|
- `SharedTools` contains types that are needed in the build script and in the app, for example.
|
||||||
|
- `SourceCodeRules` is a Roslyn analyzer project. It contains analyzers and code fixes that we use to enforce code style rules within the team.
|
||||||
|
|
||||||
|
## Changelogs
|
||||||
|
- There is a changelog in Markdown format for each version.
|
||||||
|
- All changelogs are located in the folder `app/MindWork AI Studio/wwwroot/changelog`.
|
||||||
|
- These changelogs are intended for end users, not for developers.
|
||||||
|
- Therefore, we don't mention all changes in the changelog: changes that end users wouldn't understand remain unmentioned. For complex refactorings, for example, we mention a generic point that the code quality has been improved to enhance future maintenance.
|
||||||
|
- The changelog is always written in US English.
|
||||||
|
- The changelog doesn't mention bug fixes if the bug was never shipped and users don't know about it.
|
@ -5,6 +5,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FNV/@EntryIndexedValue">FNV</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FNV/@EntryIndexedValue">FNV</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GWDG/@EntryIndexedValue">GWDG</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GWDG/@EntryIndexedValue">GWDG</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HF/@EntryIndexedValue">HF</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HF/@EntryIndexedValue">HF</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IERI/@EntryIndexedValue">IERI</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
|
||||||
@ -20,6 +21,7 @@
|
|||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=huggingface/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=huggingface/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=ieri/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@ -384,7 +384,17 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
|||||||
|
|
||||||
protected override void DisposeResources()
|
protected override void DisposeResources()
|
||||||
{
|
{
|
||||||
this.formChangeTimer.Dispose();
|
try
|
||||||
|
{
|
||||||
|
this.formChangeTimer.Stop();
|
||||||
|
this.formChangeTimer.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
base.DisposeResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -3415,6 +3415,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T32678
|
|||||||
-- Close
|
-- Close
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Close"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Close"
|
||||||
|
|
||||||
|
-- This template is managed by your organization.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "This template is managed by your organization."
|
||||||
|
|
||||||
-- Edit Chat Template
|
-- Edit Chat Template
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template"
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ public sealed record ChatThread
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatThread.SelectedChatTemplate);
|
var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatThread.SelectedChatTemplate);
|
||||||
if(chatTemplate == default)
|
if(chatTemplate == null)
|
||||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -327,7 +327,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate)
|
private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate)
|
||||||
{
|
{
|
||||||
this.currentChatTemplate = chatTemplate;
|
this.currentChatTemplate = chatTemplate;
|
||||||
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt))
|
||||||
|
this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
|
||||||
|
|
||||||
if(this.ChatThread is null)
|
if(this.ChatThread is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -435,7 +437,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
DataSourceOptions = this.earlyDataSourceOptions,
|
DataSourceOptions = this.earlyDataSourceOptions,
|
||||||
Name = this.ExtractThreadName(this.userInput),
|
Name = this.ExtractThreadName(this.userInput),
|
||||||
Seed = this.RNG.Next(),
|
Seed = this.RNG.Next(),
|
||||||
Blocks = this.currentChatTemplate == default ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||||
@ -673,7 +675,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
ChatId = Guid.NewGuid(),
|
ChatId = Guid.NewGuid(),
|
||||||
Name = string.Empty,
|
Name = string.Empty,
|
||||||
Seed = this.RNG.Next(),
|
Seed = this.RNG.Next(),
|
||||||
Blocks = this.currentChatTemplate == default ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,9 +815,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
// Try to select the chat template:
|
// Try to select the chat template:
|
||||||
if (!string.IsNullOrWhiteSpace(chatChatTemplate))
|
if (!string.IsNullOrWhiteSpace(chatChatTemplate))
|
||||||
{
|
{
|
||||||
this.currentChatTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate);
|
var selectedTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate);
|
||||||
if(this.currentChatTemplate == default)
|
this.currentChatTemplate = selectedTemplate ?? ChatTemplate.NO_CHAT_TEMPLATE;
|
||||||
this.currentChatTemplate = ChatTemplate.NO_CHAT_TEMPLATE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,4 +92,23 @@ public partial class ConfigurationText : ConfigurationBaseCore
|
|||||||
await this.SettingsManager.StoreSettings();
|
await this.SettingsManager.StoreSettings();
|
||||||
await this.InformAboutChange();
|
await this.InformAboutChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Overrides of MSGComponentBase
|
||||||
|
|
||||||
|
protected override void DisposeResources()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.timer.Stop();
|
||||||
|
this.timer.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
base.DisposeResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ using Timer = System.Timers.Timer;
|
|||||||
|
|
||||||
namespace AIStudio.Components;
|
namespace AIStudio.Components;
|
||||||
|
|
||||||
public partial class DebouncedTextField : MudComponentBase
|
public partial class DebouncedTextField : MudComponentBase, IDisposable
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Label { get; set; } = string.Empty;
|
public string Label { get; set; } = string.Empty;
|
||||||
@ -50,12 +50,15 @@ public partial class DebouncedTextField : MudComponentBase
|
|||||||
|
|
||||||
private readonly Timer debounceTimer = new();
|
private readonly Timer debounceTimer = new();
|
||||||
private string text = string.Empty;
|
private string text = string.Empty;
|
||||||
|
private string lastParameterText = string.Empty;
|
||||||
|
private bool isInitialized;
|
||||||
|
|
||||||
#region Overrides of ComponentBase
|
#region Overrides of ComponentBase
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
this.text = this.Text;
|
this.text = this.Text;
|
||||||
|
this.lastParameterText = this.Text;
|
||||||
this.debounceTimer.AutoReset = false;
|
this.debounceTimer.AutoReset = false;
|
||||||
this.debounceTimer.Interval = this.DebounceTime.TotalMilliseconds;
|
this.debounceTimer.Interval = this.DebounceTime.TotalMilliseconds;
|
||||||
this.debounceTimer.Elapsed += (_, _) =>
|
this.debounceTimer.Elapsed += (_, _) =>
|
||||||
@ -66,9 +69,32 @@ public partial class DebouncedTextField : MudComponentBase
|
|||||||
this.InvokeAsync(() => this.WhenTextCanged(this.text));
|
this.InvokeAsync(() => this.WhenTextCanged(this.text));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.isInitialized = true;
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
// Ensure the timer uses the latest debouncing interval:
|
||||||
|
if (!this.isInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(Math.Abs(this.debounceTimer.Interval - this.DebounceTime.TotalMilliseconds) > 1)
|
||||||
|
this.debounceTimer.Interval = this.DebounceTime.TotalMilliseconds;
|
||||||
|
|
||||||
|
// Only sync when the parent's parameter actually changed since the last change:
|
||||||
|
if (this.Text != this.lastParameterText)
|
||||||
|
{
|
||||||
|
this.text = this.Text;
|
||||||
|
this.lastParameterText = this.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debounceTimer.Stop();
|
||||||
|
this.debounceTimer.Start();
|
||||||
|
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void OnTextChanged(string value)
|
private void OnTextChanged(string value)
|
||||||
@ -77,4 +103,21 @@ public partial class DebouncedTextField : MudComponentBase
|
|||||||
this.debounceTimer.Stop();
|
this.debounceTimer.Stop();
|
||||||
this.debounceTimer.Start();
|
this.debounceTimer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.debounceTimer.Stop();
|
||||||
|
this.debounceTimer.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
@ -54,6 +54,9 @@ public partial class SettingsPanelProviders : SettingsPanelBase
|
|||||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||||
private async Task EditLLMProvider(AIStudio.Settings.Provider provider)
|
private async Task EditLLMProvider(AIStudio.Settings.Provider provider)
|
||||||
{
|
{
|
||||||
|
if (provider.IsEnterpriseConfiguration)
|
||||||
|
return;
|
||||||
|
|
||||||
var dialogParameters = new DialogParameters<ProviderDialog>
|
var dialogParameters = new DialogParameters<ProviderDialog>
|
||||||
{
|
{
|
||||||
{ x => x.DataNum, provider.Num },
|
{ x => x.DataNum, provider.Num },
|
||||||
|
@ -129,6 +129,9 @@ public partial class ChatTemplateDialog : MSGComponentBase
|
|||||||
PredefinedUserPrompt = this.PredefinedUserPrompt,
|
PredefinedUserPrompt = this.PredefinedUserPrompt,
|
||||||
ExampleConversation = this.dataExampleConversation,
|
ExampleConversation = this.dataExampleConversation,
|
||||||
AllowProfileUsage = this.AllowProfileUsage,
|
AllowProfileUsage = this.AllowProfileUsage,
|
||||||
|
|
||||||
|
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||||
|
IsEnterpriseConfiguration = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
private void RemoveMessage(ContentBlock item)
|
private void RemoveMessage(ContentBlock item)
|
||||||
|
@ -31,14 +31,23 @@
|
|||||||
<MudTd>@context.Num</MudTd>
|
<MudTd>@context.Num</MudTd>
|
||||||
<MudTd>@context.Name</MudTd>
|
<MudTd>@context.Name</MudTd>
|
||||||
<MudTd>
|
<MudTd>
|
||||||
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
|
@if (context.IsEnterpriseConfiguration)
|
||||||
<MudTooltip Text="@T("Edit")">
|
{
|
||||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditChatTemplate(context)"/>
|
<MudTooltip Text="@T("This template is managed by your organization.")">
|
||||||
|
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
<MudTooltip Text="@T("Delete")">
|
}
|
||||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteChatTemplate(context)"/>
|
else
|
||||||
</MudTooltip>
|
{
|
||||||
</MudStack>
|
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
|
||||||
|
<MudTooltip Text="@T("Edit")">
|
||||||
|
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditChatTemplate(context)"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="@T("Delete")">
|
||||||
|
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteChatTemplate(context)"/>
|
||||||
|
</MudTooltip>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
</MudTd>
|
</MudTd>
|
||||||
</RowTemplate>
|
</RowTemplate>
|
||||||
</MudTable>
|
</MudTable>
|
||||||
|
@ -53,6 +53,9 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
|
|||||||
|
|
||||||
private async Task EditChatTemplate(ChatTemplate chatTemplate)
|
private async Task EditChatTemplate(ChatTemplate chatTemplate)
|
||||||
{
|
{
|
||||||
|
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
|
||||||
|
return;
|
||||||
|
|
||||||
var dialogParameters = new DialogParameters<ChatTemplateDialog>
|
var dialogParameters = new DialogParameters<ChatTemplateDialog>
|
||||||
{
|
{
|
||||||
{ x => x.DataNum, chatTemplate.Num },
|
{ x => x.DataNum, chatTemplate.Num },
|
||||||
|
@ -100,6 +100,23 @@ public partial class Chat : MSGComponentBase
|
|||||||
|
|
||||||
#region Overrides of MSGComponentBase
|
#region Overrides of MSGComponentBase
|
||||||
|
|
||||||
|
protected override void DisposeResources()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.splitterSaveTimer.Stop();
|
||||||
|
this.splitterSaveTimer.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
base.DisposeResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||||
{
|
{
|
||||||
switch (triggeredEvent)
|
switch (triggeredEvent)
|
||||||
@ -111,6 +128,4 @@ public partial class Chat : MSGComponentBase
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
@ -152,4 +152,23 @@ public partial class Writer : MSGComponentBase
|
|||||||
this.suggestion = string.Join(' ', words.Skip(1));
|
this.suggestion = string.Join(' ', words.Skip(1));
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Overrides of MSGComponentBase
|
||||||
|
|
||||||
|
protected override void DisposeResources()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.typeTimer.Stop();
|
||||||
|
this.typeTimer.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
base.DisposeResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
@ -69,3 +69,27 @@ CONFIG["SETTINGS"] = {}
|
|||||||
-- Configure the user permission to add providers:
|
-- Configure the user permission to add providers:
|
||||||
-- Allowed values are: true, false
|
-- Allowed values are: true, false
|
||||||
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false
|
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false
|
||||||
|
|
||||||
|
-- Example chat templates for this configuration:
|
||||||
|
CONFIG["CHAT_TEMPLATES"] = {}
|
||||||
|
|
||||||
|
-- A simple example chat template:
|
||||||
|
CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||||
|
["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||||
|
["Name"] = "<user-friendly name of the chat template>",
|
||||||
|
["SystemPrompt"] = "You are <Company Name>'s helpful AI assistant for <Department Name>. Your task is ...",
|
||||||
|
["PredefinedUserPrompt"] = "Please help me with ...",
|
||||||
|
["AllowProfileUsage"] = true,
|
||||||
|
["ExampleConversation"] = {
|
||||||
|
{
|
||||||
|
-- Allowed values are: USER, AI, SYSTEM
|
||||||
|
["Role"] = "USER",
|
||||||
|
["Content"] = "Hello! Can you help me with a quick task?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
-- Allowed values are: USER, AI, SYSTEM
|
||||||
|
["Role"] = "AI",
|
||||||
|
["Content"] = "Of course. What do you need?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3417,6 +3417,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T32678
|
|||||||
-- Close
|
-- Close
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Schließen"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Schließen"
|
||||||
|
|
||||||
|
-- This template is managed by your organization.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "Diesee Vorlage wird von Ihrer Organisation verwaltet."
|
||||||
|
|
||||||
-- Edit Chat Template
|
-- Edit Chat Template
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Chat-Vorlage bearbeiten"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Chat-Vorlage bearbeiten"
|
||||||
|
|
||||||
@ -4584,7 +4587,7 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Letztes Änderungsproto
|
|||||||
-- Choose the provider and model best suited for your current task.
|
-- Choose the provider and model best suited for your current task.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Wählen Sie den Anbieter und das Modell aus, die am besten zu ihrer aktuellen Aufgabe passen."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Wählen Sie den Anbieter und das Modell aus, die am besten zu ihrer aktuellen Aufgabe passen."
|
||||||
|
|
||||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2900280782"] = "Sie sind an keinen einzelnen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu ihren Bedürfnissen passt. Derzeit unterstützen wir OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face und selbst gehostete Modelle mit vLLM, llama.cpp, ollama, LM Studio, Groq oder Fireworks. Für Wissenschaftler und Mitarbeiter von Forschungseinrichtungen unterstützen wir auch die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Anmeldungen wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen und viele internationale Universitäten verfügbar."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2900280782"] = "Sie sind an keinen einzelnen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu ihren Bedürfnissen passt. Derzeit unterstützen wir OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face und selbst gehostete Modelle mit vLLM, llama.cpp, ollama, LM Studio, Groq oder Fireworks. Für Wissenschaftler und Mitarbeiter von Forschungseinrichtungen unterstützen wir auch die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Anmeldungen wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen und viele internationale Universitäten verfügbar."
|
||||||
|
|
||||||
-- Quick Start Guide
|
-- Quick Start Guide
|
||||||
|
@ -3417,6 +3417,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T32678
|
|||||||
-- Close
|
-- Close
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Close"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Close"
|
||||||
|
|
||||||
|
-- This template is managed by your organization.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "This template is managed by your organization."
|
||||||
|
|
||||||
-- Edit Chat Template
|
-- Edit Chat Template
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template"
|
||||||
|
|
||||||
@ -4584,7 +4587,7 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Last Changelog"
|
|||||||
-- Choose the provider and model best suited for your current task.
|
-- Choose the provider and model best suited for your current task.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Choose the provider and model best suited for your current task."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Choose the provider and model best suited for your current task."
|
||||||
|
|
||||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2900280782"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2900280782"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities."
|
||||||
|
|
||||||
-- Quick Start Guide
|
-- Quick Start Guide
|
||||||
|
@ -3,8 +3,12 @@ using AIStudio.Tools.PluginSystem;
|
|||||||
|
|
||||||
namespace AIStudio.Settings;
|
namespace AIStudio.Settings;
|
||||||
|
|
||||||
public readonly record struct ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, string PredefinedUserPrompt, List<ContentBlock> ExampleConversation, bool AllowProfileUsage)
|
public record ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, string PredefinedUserPrompt, List<ContentBlock> ExampleConversation, bool AllowProfileUsage, bool IsEnterpriseConfiguration = false, Guid EnterpriseConfigurationPluginId = default)
|
||||||
{
|
{
|
||||||
|
public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate));
|
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate));
|
||||||
|
|
||||||
public static readonly ChatTemplate NO_CHAT_TEMPLATE = new()
|
public static readonly ChatTemplate NO_CHAT_TEMPLATE = new()
|
||||||
@ -16,6 +20,8 @@ public readonly record struct ChatTemplate(uint Num, string Id, string Name, str
|
|||||||
Num = uint.MaxValue,
|
Num = uint.MaxValue,
|
||||||
ExampleConversation = [],
|
ExampleConversation = [],
|
||||||
AllowProfileUsage = true,
|
AllowProfileUsage = true,
|
||||||
|
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||||
|
IsEnterpriseConfiguration = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
#region Overrides of ValueType
|
#region Overrides of ValueType
|
||||||
|
@ -270,11 +270,11 @@ public sealed class SettingsManager
|
|||||||
public ChatTemplate GetPreselectedChatTemplate(Tools.Components component)
|
public ChatTemplate GetPreselectedChatTemplate(Tools.Components component)
|
||||||
{
|
{
|
||||||
var preselection = component.PreselectedChatTemplate(this);
|
var preselection = component.PreselectedChatTemplate(this);
|
||||||
if (preselection != default)
|
if (preselection != ChatTemplate.NO_CHAT_TEMPLATE)
|
||||||
return preselection;
|
return preselection;
|
||||||
|
|
||||||
preselection = this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedChatTemplate);
|
preselection = this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedChatTemplate);
|
||||||
return preselection != default ? preselection : ChatTemplate.NO_CHAT_TEMPLATE;
|
return preselection ?? ChatTemplate.NO_CHAT_TEMPLATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider)
|
public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider)
|
||||||
|
@ -133,8 +133,8 @@ public static class ComponentsExtensions
|
|||||||
|
|
||||||
public static ChatTemplate PreselectedChatTemplate(this Components component, SettingsManager settingsManager) => component switch
|
public static ChatTemplate PreselectedChatTemplate(this Components component, SettingsManager settingsManager) => component switch
|
||||||
{
|
{
|
||||||
Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedChatTemplate) : default,
|
Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedChatTemplate) ?? ChatTemplate.NO_CHAT_TEMPLATE : ChatTemplate.NO_CHAT_TEMPLATE,
|
||||||
|
|
||||||
_ => default,
|
_ => ChatTemplate.NO_CHAT_TEMPLATE,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Settings;
|
using AIStudio.Settings;
|
||||||
|
using AIStudio.Chat;
|
||||||
|
|
||||||
using Lua;
|
using Lua;
|
||||||
|
|
||||||
@ -14,6 +15,13 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
private static readonly ILogger<PluginConfiguration> LOGGER = Program.LOGGER_FACTORY.CreateLogger<PluginConfiguration>();
|
private static readonly ILogger<PluginConfiguration> LOGGER = Program.LOGGER_FACTORY.CreateLogger<PluginConfiguration>();
|
||||||
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||||
|
|
||||||
|
private readonly List<PluginConfigurationObject> configObjects = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<PluginConfigurationObject> ConfigObjects => this.configObjects;
|
||||||
|
|
||||||
public async Task InitializeAsync(bool dryRun)
|
public async Task InitializeAsync(bool dryRun)
|
||||||
{
|
{
|
||||||
if(!this.TryProcessConfiguration(dryRun, out var issue))
|
if(!this.TryProcessConfiguration(dryRun, out var issue))
|
||||||
@ -29,11 +37,13 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to initialize the UI text content of the plugin.
|
/// Tries to initialize the UI text content of the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
|
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
|
||||||
/// <param name="message">The error message, when the UI text content could not be read.</param>
|
/// <param name="message">The error message, when the UI text content could not be read.</param>
|
||||||
/// <returns>True, when the UI text content could be read successfully.</returns>
|
/// <returns>True, when the UI text content could be read successfully.</returns>
|
||||||
private bool TryProcessConfiguration(bool dryRun, out string message)
|
private bool TryProcessConfiguration(bool dryRun, out string message)
|
||||||
{
|
{
|
||||||
|
this.configObjects.Clear();
|
||||||
|
|
||||||
// Ensure that the main CONFIG table exists and is a valid Lua table:
|
// Ensure that the main CONFIG table exists and is a valid Lua table:
|
||||||
if (!this.state.Environment["CONFIG"].TryRead<LuaTable>(out var mainTable))
|
if (!this.state.Environment["CONFIG"].TryRead<LuaTable>(out var mainTable))
|
||||||
{
|
{
|
||||||
@ -59,7 +69,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun);
|
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Configured providers
|
// Configured providers:
|
||||||
//
|
//
|
||||||
if (!mainTable.TryGetValue("LLM_PROVIDERS", out var providersValue) || !providersValue.TryRead<LuaTable>(out var providersTable))
|
if (!mainTable.TryGetValue("LLM_PROVIDERS", out var providersValue) || !providersValue.TryRead<LuaTable>(out var providersTable))
|
||||||
{
|
{
|
||||||
@ -94,6 +104,17 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
// The iterating variable is immutable, so we need to create a local copy:
|
// The iterating variable is immutable, so we need to create a local copy:
|
||||||
var provider = configuredProvider;
|
var provider = configuredProvider;
|
||||||
|
|
||||||
|
// Store this provider in the config object list:
|
||||||
|
this.configObjects.Add(new()
|
||||||
|
{
|
||||||
|
ConfigPluginId = this.Id,
|
||||||
|
Id = Guid.Parse(provider.Id),
|
||||||
|
Type = PluginConfigurationObjectType.LLM_PROVIDER,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dryRun)
|
||||||
|
continue;
|
||||||
|
|
||||||
var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id);
|
var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id);
|
||||||
if (providerIndex > -1)
|
if (providerIndex > -1)
|
||||||
{
|
{
|
||||||
@ -109,8 +130,65 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
SETTINGS_MANAGER.ConfigurationData.Providers.Add(provider);
|
SETTINGS_MANAGER.ConfigurationData.Providers.Add(provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning restore MWAIS0001
|
#pragma warning restore MWAIS0001
|
||||||
|
|
||||||
|
//
|
||||||
|
// Configured chat templates:
|
||||||
|
//
|
||||||
|
if (mainTable.TryGetValue("CHAT_TEMPLATES", out var templatesValue) && templatesValue.TryRead<LuaTable>(out var templatesTable))
|
||||||
|
{
|
||||||
|
var numberTemplates = templatesTable.ArrayLength;
|
||||||
|
var configuredTemplates = new List<ChatTemplate>(numberTemplates);
|
||||||
|
for (var i = 1; i <= numberTemplates; i++)
|
||||||
|
{
|
||||||
|
var templateLuaTableValue = templatesTable[i];
|
||||||
|
if (!templateLuaTableValue.TryRead<LuaTable>(out var templateLuaTable))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The CHAT_TEMPLATES table at index {i} is not a valid table.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.TryReadChatTemplateTable(i, templateLuaTable, out var template) && template != ChatTemplate.NO_CHAT_TEMPLATE)
|
||||||
|
configuredTemplates.Add(template);
|
||||||
|
else
|
||||||
|
LOGGER.LogWarning($"The CHAT_TEMPLATES table at index {i} does not contain a valid chat template configuration.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply configured chat templates to the system settings:
|
||||||
|
foreach (var configuredTemplate in configuredTemplates)
|
||||||
|
{
|
||||||
|
// The iterating variable is immutable, so we need to create a local copy:
|
||||||
|
var template = configuredTemplate;
|
||||||
|
|
||||||
|
// Store this provider in the config object list:
|
||||||
|
this.configObjects.Add(new()
|
||||||
|
{
|
||||||
|
ConfigPluginId = this.Id,
|
||||||
|
Id = Guid.Parse(template.Id),
|
||||||
|
Type = PluginConfigurationObjectType.CHAT_TEMPLATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dryRun)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id);
|
||||||
|
if (tplIndex > -1)
|
||||||
|
{
|
||||||
|
// Case: The template already exists, we update it:
|
||||||
|
var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex];
|
||||||
|
template = template with { Num = existingTemplate.Num };
|
||||||
|
SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Case: The template does not exist, we add it:
|
||||||
|
template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ };
|
||||||
|
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,4 +272,96 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
|||||||
model = new(id, displayName);
|
model = new(id, displayName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryReadChatTemplateTable(int idx, LuaTable table, out ChatTemplate template)
|
||||||
|
{
|
||||||
|
template = ChatTemplate.NO_CHAT_TEMPLATE;
|
||||||
|
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid ID. The ID must be a valid GUID.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead<string>(out var name))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid name.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table.TryGetValue("SystemPrompt", out var sysPromptValue) || !sysPromptValue.TryRead<string>(out var systemPrompt))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid system prompt.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var predefinedUserPrompt = string.Empty;
|
||||||
|
if (table.TryGetValue("PredefinedUserPrompt", out var preUserValue) && preUserValue.TryRead<string>(out var preUser))
|
||||||
|
predefinedUserPrompt = preUser;
|
||||||
|
|
||||||
|
var allowProfileUsage = false;
|
||||||
|
if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow))
|
||||||
|
allowProfileUsage = allow;
|
||||||
|
|
||||||
|
template = new()
|
||||||
|
{
|
||||||
|
Num = 0,
|
||||||
|
Id = id.ToString(),
|
||||||
|
Name = name,
|
||||||
|
SystemPrompt = systemPrompt,
|
||||||
|
PredefinedUserPrompt = predefinedUserPrompt,
|
||||||
|
ExampleConversation = ParseExampleConversation(idx, table),
|
||||||
|
AllowProfileUsage = allowProfileUsage,
|
||||||
|
IsEnterpriseConfiguration = true,
|
||||||
|
EnterpriseConfigurationPluginId = this.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ContentBlock> ParseExampleConversation(int idx, LuaTable table)
|
||||||
|
{
|
||||||
|
var exampleConversation = new List<ContentBlock>();
|
||||||
|
if (!table.TryGetValue("ExampleConversation", out var exConvValue) || !exConvValue.TryRead<LuaTable>(out var exConvTable))
|
||||||
|
return exampleConversation;
|
||||||
|
|
||||||
|
var numBlocks = exConvTable.ArrayLength;
|
||||||
|
for (var j = 1; j <= numBlocks; j++)
|
||||||
|
{
|
||||||
|
var blockValue = exConvTable[j];
|
||||||
|
if (!blockValue.TryRead<LuaTable>(out var blockTable))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} is not a valid table.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blockTable.TryGetValue("Role", out var roleValue) || !roleValue.TryRead<string>(out var roleText) || !Enum.TryParse<ChatRole>(roleText, true, out var parsedRole))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid role.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blockTable.TryGetValue("Content", out var contentValue) || !contentValue.TryRead<string>(out var content))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid content message.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(content))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} contains an empty content message.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
exampleConversation.Add(new ContentBlock
|
||||||
|
{
|
||||||
|
Time = DateTimeOffset.UtcNow,
|
||||||
|
Role = parsedRole,
|
||||||
|
Content = new ContentText { Text = content },
|
||||||
|
ContentType = ContentType.TEXT,
|
||||||
|
HideFromUser = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return exampleConversation;
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
namespace AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents metadata for a configuration object from a configuration plugin. These are
|
||||||
|
/// complex objects such as configured LLM providers, chat templates, etc.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record PluginConfigurationObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The id of the configuration plugin to which this configuration object belongs.
|
||||||
|
/// </summary>
|
||||||
|
public required Guid ConfigPluginId { get; init; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The id of the configuration object, e.g., the id of a chat template.
|
||||||
|
/// </summary>
|
||||||
|
public required Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the configuration object.
|
||||||
|
/// </summary>
|
||||||
|
public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
namespace AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
|
public enum PluginConfigurationObjectType
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
UNKNOWN,
|
||||||
|
|
||||||
|
PROFILE,
|
||||||
|
DATA_SOURCE,
|
||||||
|
LLM_PROVIDER,
|
||||||
|
CHAT_TEMPLATE,
|
||||||
|
EMBEDDING_PROVIDER,
|
||||||
|
}
|
@ -40,6 +40,8 @@ public static partial class PluginFactory
|
|||||||
if (!await PLUGIN_LOAD_SEMAPHORE.WaitAsync(0, cancellationToken))
|
if (!await PLUGIN_LOAD_SEMAPHORE.WaitAsync(0, cancellationToken))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var configObjectList = new List<PluginConfigurationObject>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LOG.LogInformation("Start loading plugins.");
|
LOG.LogInformation("Start loading plugins.");
|
||||||
@ -112,7 +114,8 @@ public static partial class PluginFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start or restart all plugins:
|
// Start or restart all plugins:
|
||||||
await RestartAllPlugins(cancellationToken);
|
var configObjects = await RestartAllPlugins(cancellationToken);
|
||||||
|
configObjectList.AddRange(configObjects);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -149,9 +152,51 @@ public static partial class PluginFactory
|
|||||||
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider);
|
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider);
|
||||||
wasConfigurationChanged = true;
|
wasConfigurationChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!configObjectList.Any(configObject =>
|
||||||
|
configObject.Type is PluginConfigurationObjectType.LLM_PROVIDER &&
|
||||||
|
configObject.ConfigPluginId == providerSourcePluginId &&
|
||||||
|
configObject.Id.ToString() == configuredProvider.Id))
|
||||||
|
{
|
||||||
|
LOG.LogWarning($"The configured LLM provider '{configuredProvider.InstanceName}' (id={configuredProvider.Id}) is not present in the configuration plugin anymore. Removing the provider from the settings.");
|
||||||
|
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider);
|
||||||
|
wasConfigurationChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore MWAIS0001
|
#pragma warning restore MWAIS0001
|
||||||
|
|
||||||
|
//
|
||||||
|
// Check chat templates:
|
||||||
|
//
|
||||||
|
var configuredTemplates = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.ToList();
|
||||||
|
foreach (var configuredTemplate in configuredTemplates)
|
||||||
|
{
|
||||||
|
if(!configuredTemplate.IsEnterpriseConfiguration)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var templateSourcePluginId = configuredTemplate.EnterpriseConfigurationPluginId;
|
||||||
|
if(templateSourcePluginId == Guid.Empty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var templateSourcePlugin = AVAILABLE_PLUGINS.FirstOrDefault(plugin => plugin.Id == templateSourcePluginId);
|
||||||
|
if(templateSourcePlugin is null)
|
||||||
|
{
|
||||||
|
LOG.LogWarning($"The configured chat template '{configuredTemplate.Name}' (id={configuredTemplate.Id}) is based on a plugin that is not available anymore. Removing the chat template from the settings.");
|
||||||
|
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate);
|
||||||
|
wasConfigurationChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!configObjectList.Any(configObject =>
|
||||||
|
configObject.Type is PluginConfigurationObjectType.CHAT_TEMPLATE &&
|
||||||
|
configObject.ConfigPluginId == templateSourcePluginId &&
|
||||||
|
configObject.Id.ToString() == configuredTemplate.Id))
|
||||||
|
{
|
||||||
|
LOG.LogWarning($"The configured chat template '{configuredTemplate.Name}' (id={configuredTemplate.Id}) is not present in the configuration plugin anymore. Removing the chat template from the settings.");
|
||||||
|
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate);
|
||||||
|
wasConfigurationChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// Check all possible settings:
|
// Check all possible settings:
|
||||||
|
@ -11,9 +11,10 @@ public static partial class PluginFactory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyCollection<PluginBase> RunningPlugins => RUNNING_PLUGINS;
|
public static IReadOnlyCollection<PluginBase> RunningPlugins => RUNNING_PLUGINS;
|
||||||
|
|
||||||
private static async Task RestartAllPlugins(CancellationToken cancellationToken = default)
|
private static async Task<List<PluginConfigurationObject>> RestartAllPlugins(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
LOG.LogInformation("Try to start or restart all plugins.");
|
LOG.LogInformation("Try to start or restart all plugins.");
|
||||||
|
var configObjects = new List<PluginConfigurationObject>();
|
||||||
RUNNING_PLUGINS.Clear();
|
RUNNING_PLUGINS.Clear();
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -65,7 +66,12 @@ public static partial class PluginFactory
|
|||||||
{
|
{
|
||||||
if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin) || availablePlugin.Type == PluginType.CONFIGURATION)
|
if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin) || availablePlugin.Type == PluginType.CONFIGURATION)
|
||||||
if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin)
|
if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin)
|
||||||
|
{
|
||||||
|
if (plugin is PluginConfiguration configPlugin)
|
||||||
|
configObjects.AddRange(configPlugin.ConfigObjects);
|
||||||
|
|
||||||
RUNNING_PLUGINS.Add(plugin);
|
RUNNING_PLUGINS.Add(plugin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -75,6 +81,7 @@ public static partial class PluginFactory
|
|||||||
|
|
||||||
// Inform all components that the plugins have been reloaded or started:
|
// Inform all components that the plugins have been reloaded or started:
|
||||||
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.PLUGINS_RELOADED);
|
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.PLUGINS_RELOADED);
|
||||||
|
return configObjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<PluginBase> Start(IAvailablePlugin meta, CancellationToken cancellationToken = default)
|
private static async Task<PluginBase> Start(IAvailablePlugin meta, CancellationToken cancellationToken = default)
|
||||||
|
@ -1 +1,5 @@
|
|||||||
# v0.9.51, build 226 (2025-08-xx xx:xx UTC)
|
# v0.9.51, build 226 (2025-08-xx xx:xx UTC)
|
||||||
|
- Added support for predefined chat templates in configuration plugins to help enterprises roll out consistent templates across the organization.
|
||||||
|
- Improved memory usage in several areas of the app.
|
||||||
|
- Improved plugin management for configuration plugins so that hot reload detects when a provider or chat template has been removed.
|
||||||
|
- Fixed a bug in various assistants where some text fields were not reset when resetting.
|
||||||
|
Loading…
Reference in New Issue
Block a user