mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-05-23 02:52:14 +00:00
Added support for exporting chat templates & profiles (#772)
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
parent
277309cd19
commit
c08f9e2ea1
@ -10,6 +10,8 @@ using AIStudio.Settings.DataModel;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using SharedTools;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Assistants.DocumentAnalysis;
|
||||
@ -747,16 +749,12 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
||||
return $$"""
|
||||
CONFIG["DOCUMENT_ANALYSIS_POLICIES"][#CONFIG["DOCUMENT_ANALYSIS_POLICIES"]+1] = {
|
||||
["Id"] = "{{id}}",
|
||||
["PolicyName"] = "{{this.selectedPolicy.PolicyName.Trim()}}",
|
||||
["PolicyDescription"] = "{{this.selectedPolicy.PolicyDescription.Trim()}}",
|
||||
["PolicyName"] = {{LuaTools.ToLuaStringLiteral(this.selectedPolicy.PolicyName.Trim())}},
|
||||
["PolicyDescription"] = {{LuaTools.ToLuaStringLiteral(this.selectedPolicy.PolicyDescription.Trim())}},
|
||||
|
||||
["AnalysisRules"] = [===[
|
||||
{{this.selectedPolicy.AnalysisRules.Trim()}}
|
||||
]===],
|
||||
["AnalysisRules"] = {{LuaTools.ToLuaStringLiteral(this.selectedPolicy.AnalysisRules.Trim(), forceLongString: true)}},
|
||||
|
||||
["OutputRules"] = [===[
|
||||
{{this.selectedPolicy.OutputRules.Trim()}}
|
||||
]===],
|
||||
["OutputRules"] = {{LuaTools.ToLuaStringLiteral(this.selectedPolicy.OutputRules.Trim(), forceLongString: true)}},
|
||||
|
||||
-- Optional: minimum provider confidence required for this policy.
|
||||
-- Allowed values are: NONE, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
|
||||
|
||||
@ -4750,6 +4750,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T582516016"] =
|
||||
-- Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, our templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1172171653"] = "Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, our templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them."
|
||||
|
||||
-- Copy attachments into plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1345613295"] = "Copy attachments into plugin"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1469573738"] = "Delete"
|
||||
|
||||
@ -4759,6 +4762,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T15483
|
||||
-- Note: This advanced feature is designed for users familiar with prompt engineering concepts. Furthermore, you have to make sure yourself that your chosen provider supports the use of assistant prompts.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1909110760"] = "Note: This advanced feature is designed for users familiar with prompt engineering concepts. Furthermore, you have to make sure yourself that your chosen provider supports the use of assistant prompts."
|
||||
|
||||
-- Use shared attachment paths
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2054531878"] = "Use shared attachment paths"
|
||||
|
||||
-- No chat templates configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2319860307"] = "No chat templates configured yet."
|
||||
|
||||
@ -4777,6 +4783,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T34481
|
||||
-- This template is managed by your organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "This template is managed by your organization."
|
||||
|
||||
-- Select configuration plugin folder
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576816894"] = "Select configuration plugin folder"
|
||||
|
||||
-- Edit Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template"
|
||||
|
||||
@ -4789,6 +4798,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T38650
|
||||
-- Delete Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T4025180906"] = "Delete Chat Template"
|
||||
|
||||
-- Export Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T491504763"] = "Export Chat Template"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T975426229"] = "Export configuration"
|
||||
|
||||
-- Which programming language should be preselected for added contexts?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T1073540083"] = "Which programming language should be preselected for added contexts?"
|
||||
|
||||
@ -5224,6 +5239,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T55364659"
|
||||
-- Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T56359901"] = "Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile."
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T975426229"] = "Export configuration"
|
||||
|
||||
-- Preselect the target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROMPTOPTIMIZER::T1417990312"] = "Preselect the target language"
|
||||
|
||||
@ -6553,9 +6571,27 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T39077128
|
||||
-- Model as configured by whisper.cpp
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp"
|
||||
|
||||
-- Cannot export this chat template because example message {0} is not a text message.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T1861800849"] = "Cannot export this chat template because example message {0} is not a text message."
|
||||
|
||||
-- Cannot export this chat template because example message {0} uses a role that is not supported by configuration plugins.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T2407395493"] = "Cannot export this chat template because example message {0} uses a role that is not supported by configuration plugins."
|
||||
|
||||
-- Please select a valid configuration plugin folder. The folder must contain a plugin.lua file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T2542895569"] = "Please select a valid configuration plugin folder. The folder must contain a plugin.lua file."
|
||||
|
||||
-- Cannot package the chat template attachments. The issue was: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T3635593138"] = "Cannot package the chat template attachments. The issue was: {0}"
|
||||
|
||||
-- Cannot package the attachment '{0}' because the file does not exist.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4121340492"] = "Cannot package the attachment '{0}' because the file does not exist."
|
||||
|
||||
-- Use no chat template
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Use no chat template"
|
||||
|
||||
-- Cannot export this chat template because example message {0} is empty.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T477540958"] = "Cannot export this chat template because example message {0} is empty."
|
||||
|
||||
-- Navigation never expands, but there are tooltips
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1095779033"] = "Navigation never expands, but there are tooltips"
|
||||
|
||||
|
||||
@ -18,6 +18,9 @@ public abstract class SettingsDialogBase : MSGComponentBase
|
||||
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
protected readonly List<ConfigurationSelectData<string>> AvailableLLMProviders = new();
|
||||
protected readonly List<ConfigurationSelectData<string>> AvailableEmbeddingProviders = new();
|
||||
|
||||
@ -43,6 +43,28 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditChatTemplate(context))"/>
|
||||
</MudTooltip>
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
@if (context.FileAttachments.Count == 0)
|
||||
{
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportChatTemplateWithSharedAttachmentPaths(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudMenu Icon="@Icons.Material.Filled.Dataset" Color="Color.Info" Variant="Variant.Text">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Link" OnClick="@(() => this.ExportChatTemplateWithSharedAttachmentPaths(context))">
|
||||
@T("Use shared attachment paths")
|
||||
</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Folder" OnClick="@(() => this.ExportChatTemplateWithPackagedAttachments(context))">
|
||||
@T("Copy attachments into plugin")
|
||||
</MudMenuItem>
|
||||
</MudMenu>
|
||||
</MudTooltip>
|
||||
}
|
||||
}
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteChatTemplate(context))"/>
|
||||
</MudTooltip>
|
||||
|
||||
@ -98,4 +98,66 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
|
||||
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task ExportChatTemplateWithSharedAttachmentPaths(ChatTemplate chatTemplate)
|
||||
{
|
||||
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
return;
|
||||
|
||||
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
await this.CopyChatTemplateLuaToClipboard(chatTemplate);
|
||||
}
|
||||
|
||||
private async Task ExportChatTemplateWithPackagedAttachments(ChatTemplate chatTemplate)
|
||||
{
|
||||
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
return;
|
||||
|
||||
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
if (chatTemplate.FileAttachments.Count == 0)
|
||||
{
|
||||
await this.ExportChatTemplateWithSharedAttachmentPaths(chatTemplate);
|
||||
return;
|
||||
}
|
||||
|
||||
var pluginDirectoryResponse = await this.RustService.SelectDirectory(T("Select configuration plugin folder"));
|
||||
if (pluginDirectoryResponse.UserCancelled)
|
||||
return;
|
||||
|
||||
await this.CopyPackagedChatTemplateLuaToClipboard(chatTemplate, pluginDirectoryResponse.SelectedDirectory);
|
||||
}
|
||||
|
||||
private async Task CopyChatTemplateLuaToClipboard(ChatTemplate chatTemplate)
|
||||
{
|
||||
if (!chatTemplate.TryExportAsConfigurationSection(out var luaCode, out var issue))
|
||||
{
|
||||
await this.DialogService.ShowMessageBox(
|
||||
T("Export Chat Template"),
|
||||
issue,
|
||||
T("Close"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(luaCode))
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
|
||||
}
|
||||
|
||||
private async Task CopyPackagedChatTemplateLuaToClipboard(ChatTemplate chatTemplate, string pluginDirectory)
|
||||
{
|
||||
if (!chatTemplate.TryExportAsConfigurationSectionWithPackagedAttachments(pluginDirectory, out var luaCode, out var issue))
|
||||
{
|
||||
await this.DialogService.ShowMessageBox(
|
||||
T("Export Chat Template"),
|
||||
issue,
|
||||
T("Close"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(luaCode))
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
|
||||
}
|
||||
}
|
||||
@ -3,15 +3,10 @@ using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.ERIClient.DataModel;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Dialogs.Settings;
|
||||
|
||||
public partial class SettingsDialogDataSources : SettingsDialogBase
|
||||
{
|
||||
[Inject]
|
||||
private ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
private string GetEmbeddingName(IDataSource dataSource)
|
||||
{
|
||||
if(dataSource is IInternalDataSource internalDataSource)
|
||||
|
||||
@ -42,6 +42,12 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditProfile(context)"/>
|
||||
</MudTooltip>
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="() => this.ExportProfile(context)"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteProfile(context)"/>
|
||||
</MudTooltip>
|
||||
|
||||
@ -49,6 +49,19 @@ public partial class SettingsDialogProfiles : SettingsDialogBase
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task ExportProfile(Profile profile)
|
||||
{
|
||||
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
return;
|
||||
|
||||
if (profile == Profile.NO_PROFILE || profile.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
var luaCode = profile.ExportAsConfigurationSection();
|
||||
if (!string.IsNullOrWhiteSpace(luaCode))
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
|
||||
}
|
||||
|
||||
private async Task DeleteProfile(Profile profile)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
|
||||
@ -298,7 +298,8 @@ CONFIG["CHAT_TEMPLATES"] = {}
|
||||
-- ["AllowProfileUsage"] = true,
|
||||
-- -- Optional: Pre-attach files that will be automatically included when using this template.
|
||||
-- -- These files will be loaded when the user selects this chat template.
|
||||
-- -- Note: File paths must be absolute paths and accessible to all users.
|
||||
-- -- Note: File paths can be absolute paths that are accessible to all users, or relative paths
|
||||
-- -- inside this plugin folder, for example "attachments/00000000-0000-0000-0000-000000000001/Guidelines.pdf".
|
||||
-- ["FileAttachments"] = {
|
||||
-- "G:\\Company\\Documents\\Guidelines.pdf",
|
||||
-- "G:\\Company\\Documents\\CompanyPolicies.docx"
|
||||
|
||||
@ -4752,6 +4752,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T582516016"] =
|
||||
-- Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, chat templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1172171653"] = "Passen Sie ihre KI-Erfahrung mit Chat-Vorlagen an. Egal, ob Sie mit Prompt-Engineering experimentieren, einfach einen eigenen System-Prompt im normalen Chat verwenden oder einen spezialisierten Assistenten erstellen möchten – mit Chat-Vorlagen haben Sie die volle Kontrolle. Ähnlich wie in den Playgrounds gängiger KI-Anbieter können Sie eigene System-Prompts festlegen und bei unterstützenden Anbietern auch Assistenten-Prompts nutzen."
|
||||
|
||||
-- Copy attachments into plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1345613295"] = "Anhänge in das Plugin kopieren"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1469573738"] = "Löschen"
|
||||
|
||||
@ -4761,6 +4764,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T15483
|
||||
-- Note: This advanced feature is designed for users familiar with prompt engineering concepts. Furthermore, you have to make sure yourself that your chosen provider supports the use of assistant prompts.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1909110760"] = "Hinweis: Diese fortgeschrittene Funktion richtet sich an Nutzer, die mit den Grundlagen des Prompt Engineerings vertraut sind. Außerdem müssen Sie selbst sicherstellen, dass Ihr gewählter Anbieter die Verwendung von Assistenten-Prompts unterstützt."
|
||||
|
||||
-- Use shared attachment paths
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2054531878"] = "Gemeinsame Pfade für Anhänge verwenden"
|
||||
|
||||
-- No chat templates configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2319860307"] = "Noch keine Chat-Vorlagen konfiguriert."
|
||||
|
||||
@ -4779,6 +4785,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T34481
|
||||
-- This template is managed by your organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "Diesee Vorlage wird von Ihrer Organisation verwaltet."
|
||||
|
||||
-- Select configuration plugin folder
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576816894"] = "Konfigurationsordner für Plugins auswählen"
|
||||
|
||||
-- Edit Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Chat-Vorlage bearbeiten"
|
||||
|
||||
@ -4788,9 +4797,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T38241
|
||||
-- Actions
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3865031940"] = "Aktionen"
|
||||
|
||||
-- Export
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3898821075"] = "Exportieren"
|
||||
|
||||
-- Delete Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T4025180906"] = "Chat-Vorlage löschen"
|
||||
|
||||
-- Export Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T491504763"] = "Chat-Vorlage exportieren"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T975426229"] = "Konfiguration exportieren"
|
||||
|
||||
-- Which programming language should be preselected for added contexts?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T1073540083"] = "Welche Programmiersprache soll für hinzugefügte Kontexte vorausgewählt werden?"
|
||||
|
||||
@ -5226,6 +5244,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T55364659"
|
||||
-- Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T56359901"] = "Sind Sie Projektleiter in einer Forschungseinrichtung? Dann möchten Sie vielleicht ein Profil für ihre Projektmanagement-Aktivitäten anlegen, eines für ihre wissenschaftliche Arbeit und ein weiteres Profil, wenn Sie Programmcode schreiben müssen. In diesen Profilen können Sie festhalten, wie viel Erfahrung Sie haben oder welche Methoden Sie bevorzugen oder nicht gerne verwenden. Später können Sie dann auswählen, wann und wo Sie jedes Profil nutzen möchten."
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T975426229"] = "Konfiguration exportieren"
|
||||
|
||||
-- Preselect the target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROMPTOPTIMIZER::T1417990312"] = "Zielsprache vorwählen"
|
||||
|
||||
@ -6555,9 +6576,27 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T39077128
|
||||
-- Model as configured by whisper.cpp
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Modell wie in whisper.cpp konfiguriert"
|
||||
|
||||
-- Cannot export this chat template because example message {0} is not a text message.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T1861800849"] = "Diese Chatvorlage kann nicht exportiert werden, da die Beispielnachricht {0} keine Textnachricht ist."
|
||||
|
||||
-- Cannot export this chat template because example message {0} uses a role that is not supported by configuration plugins.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T2407395493"] = "Diese Chat-Vorlage kann nicht exportiert werden, da die Beispielnachricht {0} eine Rolle verwendet, die von Konfigurations-Plugins nicht unterstützt wird."
|
||||
|
||||
-- Please select a valid configuration plugin folder. The folder must contain a plugin.lua file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T2542895569"] = "Bitte wählen Sie einen gültigen Konfigurations-Plug-in-Ordner aus. Der Ordner muss eine Datei „plugin.lua“ enthalten."
|
||||
|
||||
-- Cannot package the chat template attachments. The issue was: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T3635593138"] = "Die Anhänge der Chat-Vorlage können nicht verpackt werden. Das Problem war: {0}"
|
||||
|
||||
-- Cannot package the attachment '{0}' because the file does not exist.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4121340492"] = "Der Anhang „{0}“ kann nicht gepackt werden, da die Datei nicht existiert."
|
||||
|
||||
-- Use no chat template
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Keine Chat-Vorlage verwenden"
|
||||
|
||||
-- Cannot export this chat template because example message {0} is empty.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T477540958"] = "Diese Chatvorlage kann nicht exportiert werden, da die Beispielnachricht {0} leer ist."
|
||||
|
||||
-- Navigation never expands, but there are tooltips
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1095779033"] = "Die Navigationsleiste wird nie ausgeklappt, aber es gibt Tooltips"
|
||||
|
||||
|
||||
@ -4752,6 +4752,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T582516016"] =
|
||||
-- Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, chat templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1172171653"] = "Customize your AI experience with chat templates. Whether you want to experiment with prompt engineering, simply use a custom system prompt in the standard chat interface, or create a specialized assistant, chat templates give you full control. Similar to common AI companies' playgrounds, you can define your own system prompts and leverage assistant prompts for providers that support them."
|
||||
|
||||
-- Copy attachments into plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1345613295"] = "Copy attachments into plugin"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1469573738"] = "Delete"
|
||||
|
||||
@ -4761,6 +4764,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T15483
|
||||
-- Note: This advanced feature is designed for users familiar with prompt engineering concepts. Furthermore, you have to make sure yourself that your chosen provider supports the use of assistant prompts.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1909110760"] = "Note: This advanced feature is designed for users familiar with prompt engineering concepts. Furthermore, you have to make sure yourself that your chosen provider supports the use of assistant prompts."
|
||||
|
||||
-- Use shared attachment paths
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2054531878"] = "Use shared attachment paths"
|
||||
|
||||
-- No chat templates configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2319860307"] = "No chat templates configured yet."
|
||||
|
||||
@ -4779,6 +4785,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T34481
|
||||
-- This template is managed by your organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "This template is managed by your organization."
|
||||
|
||||
-- Select configuration plugin folder
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576816894"] = "Select configuration plugin folder"
|
||||
|
||||
-- Edit Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template"
|
||||
|
||||
@ -4788,9 +4797,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T38241
|
||||
-- Actions
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3865031940"] = "Actions"
|
||||
|
||||
-- Export
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3898821075"] = "Export"
|
||||
|
||||
-- Delete Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T4025180906"] = "Delete Chat Template"
|
||||
|
||||
-- Export Chat Template
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T491504763"] = "Export Chat Template"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T975426229"] = "Export configuration"
|
||||
|
||||
-- Which programming language should be preselected for added contexts?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T1073540083"] = "Which programming language should be preselected for added contexts?"
|
||||
|
||||
@ -5226,6 +5244,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T55364659"
|
||||
-- Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T56359901"] = "Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile."
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T975426229"] = "Export configuration"
|
||||
|
||||
-- Preselect the target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROMPTOPTIMIZER::T1417990312"] = "Preselect the target language"
|
||||
|
||||
@ -6555,9 +6576,27 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T39077128
|
||||
-- Model as configured by whisper.cpp
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp"
|
||||
|
||||
-- Cannot export this chat template because example message {0} is not a text message.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T1861800849"] = "Cannot export this chat template because example message {0} is not a text message."
|
||||
|
||||
-- Cannot export this chat template because example message {0} uses a role that is not supported by configuration plugins.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T2407395493"] = "Cannot export this chat template because example message {0} uses a role that is not supported by configuration plugins."
|
||||
|
||||
-- Please select a valid configuration plugin folder. The folder must contain a plugin.lua file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T2542895569"] = "Please select a valid configuration plugin folder. The folder must contain a plugin.lua file."
|
||||
|
||||
-- Cannot package the chat template attachments. The issue was: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T3635593138"] = "Cannot package the chat template attachments. The issue was: {0}"
|
||||
|
||||
-- Cannot package the attachment '{0}' because the file does not exist.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4121340492"] = "Cannot package the attachment '{0}' because the file does not exist."
|
||||
|
||||
-- Use no chat template
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Use no chat template"
|
||||
|
||||
-- Cannot export this chat template because example message {0} is empty.
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T477540958"] = "Cannot export this chat template because example message {0} is empty."
|
||||
|
||||
-- Navigation never expands, but there are tooltips
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1095779033"] = "Navigation never expands, but there are tooltips"
|
||||
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Lua;
|
||||
using SharedTools;
|
||||
|
||||
using LuaTable = Lua.LuaTable;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
@ -17,6 +21,8 @@ public record ChatTemplate(
|
||||
bool IsEnterpriseConfiguration = false,
|
||||
Guid EnterpriseConfigurationPluginId = default) : ConfigurationBaseObject
|
||||
{
|
||||
private const string ATTACHMENTS_DIRECTORY = "attachments";
|
||||
|
||||
public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], [], false)
|
||||
{
|
||||
}
|
||||
@ -73,8 +79,8 @@ public record ChatTemplate(
|
||||
|
||||
return this.SystemPrompt;
|
||||
}
|
||||
|
||||
public static bool TryParseChatTemplateTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject template)
|
||||
|
||||
public static bool TryParseChatTemplateTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject template)
|
||||
{
|
||||
template = NO_CHAT_TEMPLATE;
|
||||
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
|
||||
@ -103,7 +109,7 @@ public record ChatTemplate(
|
||||
if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow))
|
||||
allowProfileUsage = allow;
|
||||
|
||||
var fileAttachments = ParseFileAttachments(idx, table);
|
||||
var fileAttachments = ParseFileAttachments(idx, table, pluginPath);
|
||||
|
||||
template = new ChatTemplate
|
||||
{
|
||||
@ -169,7 +175,7 @@ public record ChatTemplate(
|
||||
return exampleConversation;
|
||||
}
|
||||
|
||||
private static List<FileAttachment> ParseFileAttachments(int idx, LuaTable table)
|
||||
private static List<FileAttachment> ParseFileAttachments(int idx, LuaTable table, string pluginPath)
|
||||
{
|
||||
var fileAttachments = new List<FileAttachment>();
|
||||
if (!table.TryGetValue("FileAttachments", out var fileAttValue) || !fileAttValue.TryRead<LuaTable>(out var fileAttTable))
|
||||
@ -185,9 +191,227 @@ public record ChatTemplate(
|
||||
continue;
|
||||
}
|
||||
|
||||
fileAttachments.Add(FileAttachment.FromPath(filePath));
|
||||
if (TryResolveFileAttachmentPath(idx, attachmentNum, filePath, pluginPath, out var resolvedFilePath))
|
||||
fileAttachments.Add(FileAttachment.FromPath(resolvedFilePath));
|
||||
}
|
||||
|
||||
return fileAttachments;
|
||||
}
|
||||
|
||||
private static bool TryResolveFileAttachmentPath(int idx, int attachmentNum, string filePath, string pluginPath, out string resolvedFilePath)
|
||||
{
|
||||
resolvedFilePath = filePath;
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
LOGGER.LogWarning("The FileAttachments entry {AttachmentNum} in chat template {IdxChatTemplate} is empty.", attachmentNum, idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Path.IsPathFullyQualified(filePath))
|
||||
return true;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pluginPath))
|
||||
{
|
||||
LOGGER.LogWarning("The relative FileAttachments entry {AttachmentNum} in chat template {IdxChatTemplate} cannot be resolved because the plugin path is unknown.", attachmentNum, idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
var pluginRoot = Path.GetFullPath(pluginPath);
|
||||
var relativePath = filePath
|
||||
.Replace('/', Path.DirectorySeparatorChar)
|
||||
.Replace('\\', Path.DirectorySeparatorChar);
|
||||
|
||||
if (relativePath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries).Any(segment => segment == ".."))
|
||||
{
|
||||
LOGGER.LogWarning("The relative FileAttachments entry {AttachmentNum} in chat template {IdxChatTemplate} contains '..' path segments and will be ignored.", attachmentNum, idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
var combinedPath = Path.GetFullPath(Path.Combine(pluginRoot, relativePath));
|
||||
var pluginRootWithSeparator = pluginRoot.EndsWith(Path.DirectorySeparatorChar)
|
||||
? pluginRoot
|
||||
: pluginRoot + Path.DirectorySeparatorChar;
|
||||
var comparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
|
||||
if (!combinedPath.StartsWith(pluginRootWithSeparator, comparison))
|
||||
{
|
||||
LOGGER.LogWarning("The relative FileAttachments entry {AttachmentNum} in chat template {IdxChatTemplate} points outside of the plugin folder and will be ignored.", attachmentNum, idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
resolvedFilePath = combinedPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryExportAsConfigurationSection(out string luaCode, out string issue) => this.TryExportAsConfigurationSection(null, Guid.NewGuid().ToString(), out luaCode, out issue);
|
||||
|
||||
private bool TryExportAsConfigurationSection(IReadOnlyList<string>? fileAttachmentPaths, string exportId, out string luaCode, out string issue)
|
||||
{
|
||||
luaCode = string.Empty;
|
||||
issue = string.Empty;
|
||||
if (!this.TryBuildExampleConversationLua(out var exampleConversationLua, out issue))
|
||||
return false;
|
||||
|
||||
return this.TryExportAsConfigurationSection(fileAttachmentPaths, exportId, exampleConversationLua, out luaCode, out issue);
|
||||
}
|
||||
|
||||
private bool TryExportAsConfigurationSection(IReadOnlyList<string>? fileAttachmentPaths, string exportId, string exampleConversationLua, out string luaCode, out string issue)
|
||||
{
|
||||
issue = string.Empty;
|
||||
var fileAttachmentsLua = this.BuildFileAttachmentsLua(fileAttachmentPaths);
|
||||
luaCode = $$"""
|
||||
CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||
["Id"] = "{{LuaTools.EscapeLuaString(exportId)}}",
|
||||
["Name"] = {{LuaTools.ToLuaStringLiteral(this.Name)}},
|
||||
["SystemPrompt"] = {{LuaTools.ToLuaStringLiteral(this.SystemPrompt)}},
|
||||
["PredefinedUserPrompt"] = {{LuaTools.ToLuaStringLiteral(this.PredefinedUserPrompt)}},
|
||||
["AllowProfileUsage"] = {{this.AllowProfileUsage.ToString().ToLowerInvariant()}},
|
||||
["FileAttachments"] = {{fileAttachmentsLua}},
|
||||
["ExampleConversation"] = {{exampleConversationLua}},
|
||||
}
|
||||
""";
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryExportAsConfigurationSectionWithPackagedAttachments(string pluginDirectory, out string luaCode, out string issue)
|
||||
{
|
||||
luaCode = string.Empty;
|
||||
issue = string.Empty;
|
||||
var exportId = Guid.NewGuid().ToString();
|
||||
|
||||
if (!this.TryBuildExampleConversationLua(out var exampleConversationLua, out issue))
|
||||
return false;
|
||||
|
||||
if (this.FileAttachments.Count == 0)
|
||||
return this.TryExportAsConfigurationSection(null, exportId, exampleConversationLua, out luaCode, out issue);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pluginDirectory) || !File.Exists(Path.Combine(pluginDirectory, "plugin.lua")))
|
||||
{
|
||||
issue = TB("Please select a valid configuration plugin folder. The folder must contain a plugin.lua file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var sourcePaths = new List<string>();
|
||||
foreach (var attachment in this.FileAttachments)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(attachment.FilePath) || !File.Exists(attachment.FilePath))
|
||||
{
|
||||
issue = string.Format(TB("Cannot package the attachment '{0}' because the file does not exist."), attachment.FileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
sourcePaths.Add(attachment.FilePath);
|
||||
}
|
||||
|
||||
var targetDirectory = Path.Combine(pluginDirectory, ATTACHMENTS_DIRECTORY, exportId);
|
||||
var relativeAttachmentPaths = new List<string>();
|
||||
var usedFileNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
foreach (var sourcePath in sourcePaths)
|
||||
{
|
||||
var targetFileName = CreateUniqueAttachmentFileName(sourcePath, usedFileNames);
|
||||
var targetPath = Path.Combine(targetDirectory, targetFileName);
|
||||
File.Copy(sourcePath, targetPath, overwrite: false);
|
||||
relativeAttachmentPaths.Add($"{ATTACHMENTS_DIRECTORY}/{exportId}/{targetFileName}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(targetDirectory))
|
||||
Directory.Delete(targetDirectory, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Keep the original packaging error as the user-facing issue.
|
||||
}
|
||||
|
||||
issue = string.Format(TB("Cannot package the chat template attachments. The issue was: {0}"), e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.TryExportAsConfigurationSection(relativeAttachmentPaths, exportId, exampleConversationLua, out luaCode, out issue);
|
||||
}
|
||||
|
||||
private bool TryBuildExampleConversationLua(out string luaTable, out string issue)
|
||||
{
|
||||
luaTable = "{}";
|
||||
issue = string.Empty;
|
||||
if (this.ExampleConversation.Count == 0)
|
||||
return true;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("{");
|
||||
for (var i = 0; i < this.ExampleConversation.Count; i++)
|
||||
{
|
||||
var block = this.ExampleConversation[i];
|
||||
if (block.Role is not ChatRole.USER and not ChatRole.AI)
|
||||
{
|
||||
issue = string.Format(TB("Cannot export this chat template because example message {0} uses a role that is not supported by configuration plugins."), i + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block.Content is not ContentText textContent)
|
||||
{
|
||||
issue = string.Format(TB("Cannot export this chat template because example message {0} is not a text message."), i + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(textContent.Text))
|
||||
{
|
||||
issue = string.Format(TB("Cannot export this chat template because example message {0} is empty."), i + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.AppendLine(" {");
|
||||
builder.AppendLine($" [\"Role\"] = \"{block.Role}\",");
|
||||
builder.AppendLine($" [\"Content\"] = {LuaTools.ToLuaStringLiteral(textContent.Text)},");
|
||||
builder.AppendLine(" },");
|
||||
}
|
||||
|
||||
builder.Append(" }");
|
||||
luaTable = builder.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
private string BuildFileAttachmentsLua(IReadOnlyList<string>? fileAttachmentPaths)
|
||||
{
|
||||
var paths = fileAttachmentPaths ?? this.FileAttachments.Select(attachment => attachment.FilePath).ToList();
|
||||
if (paths.Count == 0)
|
||||
return "{}";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("{");
|
||||
foreach (var path in paths)
|
||||
builder.AppendLine($" \"{LuaTools.EscapeLuaString(path)}\",");
|
||||
|
||||
builder.Append(" }");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string CreateUniqueAttachmentFileName(string sourcePath, HashSet<string> usedFileNames)
|
||||
{
|
||||
var fileName = SanitizeFileName(Path.GetFileName(sourcePath));
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
fileName = "attachment";
|
||||
|
||||
var extension = Path.GetExtension(fileName);
|
||||
var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
|
||||
var candidate = fileName;
|
||||
var counter = 2;
|
||||
while (!usedFileNames.Add(candidate))
|
||||
candidate = $"{nameWithoutExtension}-{counter++}{extension}";
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string fileName)
|
||||
{
|
||||
foreach (var invalidChar in Path.GetInvalidFileNameChars())
|
||||
fileName = fileName.Replace(invalidChar, '_');
|
||||
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
@ -8,10 +8,11 @@ using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.RAG;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Lua;
|
||||
using SharedTools;
|
||||
|
||||
using ChatThread = AIStudio.Chat.ChatThread;
|
||||
using ContentType = AIStudio.Tools.ERIClient.DataModel.ContentType;
|
||||
using LuaTable = Lua.LuaTable;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
|
||||
@ -3,9 +3,10 @@ using System.Text.Json.Serialization;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Lua;
|
||||
using SharedTools;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
using LuaTable = Lua.LuaTable;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using Lua;
|
||||
|
||||
using SharedTools;
|
||||
|
||||
using LuaTable = Lua.LuaTable;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
@ -132,4 +135,20 @@ public record Profile(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the profile configuration as a Lua configuration section.
|
||||
/// </summary>
|
||||
/// <returns>A Lua configuration section string.</returns>
|
||||
public string ExportAsConfigurationSection()
|
||||
{
|
||||
return $$"""
|
||||
CONFIG["PROFILES"][#CONFIG["PROFILES"]+1] = {
|
||||
["Id"] = "{{Guid.NewGuid().ToString()}}",
|
||||
["Name"] = {{LuaTools.ToLuaStringLiteral(this.Name)}},
|
||||
["NeedToKnow"] = {{LuaTools.ToLuaStringLiteral(this.NeedToKnow)}},
|
||||
["Actions"] = {{LuaTools.ToLuaStringLiteral(this.Actions)}},
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
@ -4,9 +4,10 @@ using AIStudio.Provider;
|
||||
using AIStudio.Provider.HuggingFace;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Lua;
|
||||
using SharedTools;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
using LuaTable = Lua.LuaTable;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
|
||||
@ -3,9 +3,10 @@ using System.Text.Json.Serialization;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Lua;
|
||||
using SharedTools;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
using LuaTable = Lua.LuaTable;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
public static class LuaTools
|
||||
{
|
||||
public static string EscapeLuaString(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return string.Empty;
|
||||
|
||||
return value
|
||||
.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\n", "\\n");
|
||||
}
|
||||
}
|
||||
@ -185,7 +185,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, x => x.NextEmbeddingNum, mainTable, this.Id, ref this.configObjects, dryRun);
|
||||
|
||||
// Handle configured chat templates:
|
||||
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun);
|
||||
PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun, this.PluginPath);
|
||||
|
||||
// Handle configured data sources:
|
||||
PluginConfigurationObject.TryParseDataSources(mainTable, this.Id, ref this.configObjects, dryRun);
|
||||
|
||||
@ -52,6 +52,7 @@ public sealed record PluginConfigurationObject
|
||||
/// This parameter is passed by reference.</param>
|
||||
/// <param name="dryRun">Specifies whether to perform the operation as a dry run, where changes
|
||||
/// are not persisted.</param>
|
||||
/// <param name="pluginPath">An optional parameter specifying the file path of the plugin, used for relative paths in the Lua table.</param>
|
||||
/// <returns>Returns true if parsing succeeds and configuration objects are added
|
||||
/// to the list; otherwise, false.</returns>
|
||||
public static bool TryParse<TClass>(
|
||||
@ -61,7 +62,8 @@ public sealed record PluginConfigurationObject
|
||||
LuaTable mainTable,
|
||||
Guid configPluginId,
|
||||
ref List<PluginConfigurationObject> configObjects,
|
||||
bool dryRun
|
||||
bool dryRun,
|
||||
string pluginPath = ""
|
||||
) where TClass : ConfigurationBaseObject
|
||||
{
|
||||
var luaTableName = configObjectType switch
|
||||
@ -104,7 +106,7 @@ public sealed record PluginConfigurationObject
|
||||
var (wasParsingSuccessful, configObject) = configObjectType switch
|
||||
{
|
||||
PluginConfigurationObjectType.LLM_PROVIDER => (Settings.Provider.TryParseProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Settings.Provider.NONE, configurationObject),
|
||||
PluginConfigurationObjectType.CHAT_TEMPLATE => (ChatTemplate.TryParseChatTemplateTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != ChatTemplate.NO_CHAT_TEMPLATE, configurationObject),
|
||||
PluginConfigurationObjectType.CHAT_TEMPLATE => (ChatTemplate.TryParseChatTemplateTable(i, luaObjectTable, configPluginId, pluginPath, out var configurationObject) && configurationObject != ChatTemplate.NO_CHAT_TEMPLATE, configurationObject),
|
||||
PluginConfigurationObjectType.PROFILE => (Profile.TryParseProfileTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Profile.NO_PROFILE, configurationObject),
|
||||
PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER => (TranscriptionProvider.TryParseTranscriptionProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != TranscriptionProvider.NONE, configurationObject),
|
||||
PluginConfigurationObjectType.EMBEDDING_PROVIDER => (EmbeddingProvider.TryParseEmbeddingProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != EmbeddingProvider.NONE, configurationObject),
|
||||
|
||||
@ -326,7 +326,11 @@ public static partial class PluginFactory
|
||||
return new PluginLanguage(isInternal, state, type);
|
||||
|
||||
case PluginType.CONFIGURATION:
|
||||
var configPlug = new PluginConfiguration(isInternal, state, type);
|
||||
var configPlug = new PluginConfiguration(isInternal, state, type)
|
||||
{
|
||||
PluginPath = pluginPath ?? string.Empty
|
||||
};
|
||||
|
||||
await configPlug.InitializeAsync(true);
|
||||
return configPlug;
|
||||
|
||||
|
||||
@ -6,9 +6,11 @@ public sealed partial class RustService
|
||||
{
|
||||
public async Task<DirectorySelectionResponse> SelectDirectory(string title, string? initialDirectory = null)
|
||||
{
|
||||
PreviousDirectory? previousDirectory = initialDirectory is null ? null : new (initialDirectory);
|
||||
var encodedTitle = Uri.EscapeDataString(title);
|
||||
var result = await this.http.PostAsJsonAsync($"/select/directory?title={encodedTitle}", previousDirectory, this.jsonRustSerializerOptions);
|
||||
var result = initialDirectory is null
|
||||
? await this.http.PostAsync($"/select/directory?title={encodedTitle}", null)
|
||||
: await this.http.PostAsJsonAsync($"/select/directory?title={encodedTitle}", new PreviousDirectory(initialDirectory), this.jsonRustSerializerOptions);
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to select a directory: '{result.StatusCode}'");
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
# v26.5.5, build 240 (2026-05-xx xx:xx UTC)
|
||||
- Released the voice recording and transcription for all users. You no longer need to enable a preview feature to configure transcription providers, select a transcription provider, or use dictation.
|
||||
- Added export options for profiles and chat templates, including an option to package chat template attachments into configuration plugins.
|
||||
- Added support for organization-managed ERI servers in configuration plugins, so admins can preconfigure external data sources for users.
|
||||
- Added an export option for ERI server data sources, so admins can create configuration plugin snippets without writing the Lua code manually.
|
||||
- Added an option to configure the timeout setting for all requests. This is useful when you have a slow network connection, or you have to work with slow AI servers. It is also possible to configure this timeout for an entire organization using configuration plugins.
|
||||
|
||||
@ -2,9 +2,49 @@ namespace SharedTools;
|
||||
|
||||
public static class LuaTools
|
||||
{
|
||||
public static string EscapeLuaString(string value)
|
||||
public static string EscapeLuaString(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return string.Empty;
|
||||
|
||||
// Replace backslashes with double backslashes and escape double quotes:
|
||||
return value.Replace("\\", @"\\").Replace("\"", "\\\"");
|
||||
return value
|
||||
.Replace("\\", @"\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\n", "\\n");
|
||||
}
|
||||
|
||||
public static string ToLuaStringLiteral(string? value, bool forceLongString = false, int longStringLengthThreshold = 80)
|
||||
{
|
||||
value ??= string.Empty;
|
||||
if (!forceLongString &&
|
||||
value.Length <= longStringLengthThreshold &&
|
||||
!value.Contains('\n') &&
|
||||
!value.Contains('\r'))
|
||||
return $"\"{EscapeLuaString(value)}\"";
|
||||
|
||||
return $"{CreateLongStringOpeningDelimiter(value)}{value}{CreateLongStringClosingDelimiter(value)}";
|
||||
}
|
||||
|
||||
private static string CreateLongStringOpeningDelimiter(string value)
|
||||
{
|
||||
var equals = CreateLongStringEquals(value);
|
||||
return $"[{equals}[";
|
||||
}
|
||||
|
||||
private static string CreateLongStringClosingDelimiter(string value)
|
||||
{
|
||||
var equals = CreateLongStringEquals(value);
|
||||
return $"]{equals}]";
|
||||
}
|
||||
|
||||
private static string CreateLongStringEquals(string value)
|
||||
{
|
||||
var equalsCount = 3;
|
||||
while (value.Contains($"]{new string('=', equalsCount)}]"))
|
||||
equalsCount++;
|
||||
|
||||
return new string('=', equalsCount);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user