From 0e63f9369648785022f76d0cd58113d1e880f16b Mon Sep 17 00:00:00 2001
From: Peer Hogeterp <20603780+peerschuett@users.noreply.github.com>
Date: Thu, 24 Sep 2026 17:20:54 +0200
Subject: [PATCH 1/5] first version of the import function
---
.../DocumentAnalysisAssistant.razor | 1 +
.../DocumentAnalysisAssistant.razor.cs | 42 +++-
.../Assistants/I18N/allTexts.lua | 132 ++++++++++
.../Components/DataSourceManagement.razor | 25 +-
.../Components/DataSourceManagement.razor.cs | 20 +-
.../Settings/SettingsPanelEmbeddings.razor | 5 +-
.../Settings/SettingsPanelEmbeddings.razor.cs | 23 +-
.../Settings/SettingsPanelProviders.razor | 5 +-
.../Settings/SettingsPanelProviders.razor.cs | 25 +-
.../Settings/SettingsPanelTranscription.razor | 5 +-
.../SettingsPanelTranscription.razor.cs | 23 +-
.../Dialogs/ChatTemplateDialog.razor | 21 +-
.../Dialogs/ChatTemplateDialog.razor.cs | 104 +++++++-
.../ConfigurationSnippetImportDialog.razor | 18 ++
.../ConfigurationSnippetImportDialog.razor.cs | 58 +++++
.../Dialogs/DataSourceERI_V1Dialog.razor | 13 +-
.../Dialogs/DataSourceERI_V1Dialog.razor.cs | 80 +++++-
.../DocumentAnalysisPolicyDialog.razor | 45 ++++
.../DocumentAnalysisPolicyDialog.razor.cs | 134 ++++++++++
.../Dialogs/EmbeddingProviderDialog.razor | 7 +
.../Dialogs/EmbeddingProviderDialog.razor.cs | 53 ++++
.../Dialogs/ProfileDialog.razor | 2 +-
.../Dialogs/ProfileDialog.razor.cs | 33 ++-
.../Dialogs/ProviderDialog.razor | 7 +
.../Dialogs/ProviderDialog.razor.cs | 54 ++++
.../Settings/SettingsDialogChatTemplate.razor | 11 +-
.../SettingsDialogChatTemplate.razor.cs | 27 +-
.../Settings/SettingsDialogProfiles.razor | 11 +-
.../Settings/SettingsDialogProfiles.razor.cs | 25 +-
.../Dialogs/TranscriptionProviderDialog.razor | 7 +
.../TranscriptionProviderDialog.razor.cs | 46 ++++
.../Plugins/configuration/plugin.lua | 14 ++
.../Settings/DataModel/DataApp.cs | 23 ++
.../PluginSystem/ConfigurationImportFields.cs | 90 +++++++
.../ConfigurationSnippetImportValidation.cs | 135 ++++++++++
.../ConfigurationSnippetParser.cs | 235 ++++++++++++++++++
.../Tools/PluginSystem/PluginConfiguration.cs | 9 +
.../wwwroot/changelog/v26.9.1.md | 3 +
documentation/Enterprise IT.md | 10 +
39 files changed, 1535 insertions(+), 46 deletions(-)
create mode 100644 app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor
create mode 100644 app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor.cs
create mode 100644 app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor
create mode 100644 app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs
create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs
create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs
create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs
diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor
index b43a5cb7..9eb13f21 100644
--- a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor
+++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor
@@ -47,6 +47,7 @@ else
@T("Add policy")
+
@T("Delete this policy")
diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs
index e69dbc12..3e31c052 100644
--- a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs
+++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs
@@ -9,6 +9,7 @@ using AIStudio.Settings.DataModel;
using AIStudio.Tools.AssistantSessions;
using Microsoft.AspNetCore.Components;
+using LuaTable = Lua.LuaTable;
using SharedTools;
@@ -244,7 +245,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore this.AddPolicy(null);
+
+ private async Task ImportPolicy()
+ {
+ if (this.ArePolicyControlsDisabled || !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
+ return;
+ var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "DOCUMENT_ANALYSIS_POLICIES", T("Import document analysis policy"));
+ if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
+ await this.AddPolicy(table);
+ }
+
+ private async Task AddPolicy(LuaTable? importedConfiguration)
+ {
+ if (this.ArePolicyControlsDisabled)
+ return;
+
+ if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
+ return;
+
+ var parameters = new DialogParameters();
+ if (importedConfiguration is not null)
+ parameters.Add(x => x.ImportedConfiguration, importedConfiguration);
+ var dialogReference = await this.DialogService.ShowAsync(T("Add policy"), parameters, DialogOptions.FULLSCREEN);
+ var result = await dialogReference.Result;
+ if (result is null || result.Canceled || result.Data is not DataDocumentAnalysisPolicy policy ||
+ (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES")))
+ return;
+
+ var addedPolicy = policy with
+ {
+ Num = this.SettingsManager.ConfigurationData.NextDocumentAnalysisPolicyNum++,
+ };
+ this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Add(addedPolicy);
+ await this.SettingsManager.StoreSettings();
+ this.SelectedPolicyChanged(addedPolicy);
+ }
+
+ private async Task AddInitialPolicy()
{
if (this.ArePolicyControlsDisabled)
return;
diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index a36ed2f4..c69fc6c5 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -1102,6 +1102,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
+-- Import
+UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1463683828"] = "Import"
+
-- Only the tools selected here can be used by the AI for an analysis with this policy. Every tool still has to meet the confidence requirements of the selected provider, so a tool may remain unavailable even when this policy permits it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1692505801"] = "Only the tools selected here can be used by the AI for an analysis with this policy. Every tool still has to meet the confidence requirements of the selected provider, so a tool may remain unavailable even when this policy permits it."
@@ -1216,6 +1219,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- A policy with this name already exists. Please choose a different name.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3584374593"] = "A policy with this name already exists. Please choose a different name."
+-- Import document analysis policy
+UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3800115485"] = "Import document analysis policy"
+
-- Load analysis rules from document
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3813558135"] = "Load analysis rules from document"
@@ -3919,6 +3925,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T750361472"] = "Can
-- External Data (ERI-Server v1)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T774473996"] = "External Data (ERI-Server v1)"
+-- Import ERI v1 Data Source
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T777621786"] = "Import ERI v1 Data Source"
+
-- Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T782820095"] = "Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}"
@@ -4846,6 +4855,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELDATASOURCES::T4761
-- Embedding Result
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1387042335"] = "Embedding Result"
+-- Import
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1463683828"] = "Import"
+
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
@@ -4879,6 +4891,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T21748
-- Model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2189814010"] = "Model"
+-- Import Embedding Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T229678442"] = "Import Embedding Provider"
+
-- Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2419962612"] = "Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'."
@@ -4948,12 +4963,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T401
-- This provider is trusted by your organization for data source security checks.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1298650849"] = "This provider is trusted by your organization for data source security checks."
+-- Import
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1463683828"] = "Import"
+
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1469573738"] = "Delete"
-- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Uses the provider-configured model"
+-- Import LLM Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T178720520"] = "Import LLM Provider"
+
-- Add Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider"
@@ -5050,6 +5071,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T10
-- Edit Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1317362918"] = "Edit Transcription Provider"
+-- Import
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1463683828"] = "Import"
+
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1469573738"] = "Delete"
@@ -5092,6 +5116,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T58
-- This transcription provider is trusted by your organization for data source security checks.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T601264181"] = "This transcription provider is trusted by your organization for data source security checks."
+-- Import Transcription Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T682006405"] = "Import Transcription Provider"
+
-- This transcription provider is managed by your organization. You can set your own API key.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T690752279"] = "This transcription provider is managed by your organization. You can set your own API key."
@@ -5722,6 +5749,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1396308587"] = "The cha
-- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options."
+-- Enter an absolute path to an existing local file before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1482137067"] = "Enter an absolute path to an existing local file before saving."
+
-- Please enter a name for the chat template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Please enter a name for the chat template."
@@ -5740,6 +5770,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "The chat
-- Profile Usage
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile Usage"
+-- Relink attachment: {0}
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Relink attachment: {0}"
+
-- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs."
@@ -5800,6 +5833,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you
-- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here."
+-- Relink the missing attachment '{0}' to an existing local file before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3262119677"] = "Relink the missing attachment '{0}' to an existing local file before saving."
+
-- No, chats keep the data source options from your chat options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "No, chats keep the data source options from your chat options"
@@ -5953,12 +5989,30 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T767586087"
-- {0} embedding provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T818101181"] = "{0} embedding provider"
+-- Import
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T1463683828"] = "Import"
+
+-- Configuration snippet
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T3867536704"] = "Configuration snippet"
+
+-- Import is locked by your organization.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T4101825749"] = "Import is locked by your organization."
+
+-- Copy one exported configuration snippet from the item's Export configuration control and paste it below. You can review and change the filled form before saving a new local item.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T4214407984"] = "Copy one exported configuration snippet from the item's Export configuration control and paste it below. You can review and change the filled form before saving a new local item."
+
+-- Cancel
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T900713019"] = "Cancel"
+
-- No
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T1642511898"] = "No"
-- Yes
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T3013883440"] = "Yes"
+-- The imported retrieval process '{0}' is unavailable. Select another process before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERI_V1DIALOG::T1759490982"] = "The imported retrieval process '{0}' is unavailable. Select another process before saving."
+
-- How many matches do you want at most per query?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERI_V1DIALOG::T1827669611"] = "How many matches do you want at most per query?"
@@ -6532,6 +6586,72 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T854110894
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T900713019"] = "Cancel"
+-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
+
+-- Hide the policy definition when distributed via configuration plugin?
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1875622568"] = "Hide the policy definition when distributed via configuration plugin?"
+
+-- No profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2028602035"] = "No profile"
+
+-- Load output rules from document
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2168201568"] = "Load output rules from document"
+
+-- Preselect a profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2322771068"] = "Preselect a profile"
+
+-- The name of your policy must be between 6 and 60 characters long.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2435013256"] = "The name of your policy must be between 6 and 60 characters long."
+
+-- Preselect a provider
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2440815970"] = "Preselect a provider"
+
+-- Add
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2646845972"] = "Add"
+
+-- Policy name
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2879019438"] = "Policy name"
+
+-- Analysis rules
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3108719748"] = "Analysis rules"
+
+-- Tools this policy permits
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T31356122"] = "Tools this policy permits"
+
+-- The description of your policy must be between 32 and 512 characters long.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3285636934"] = "The description of your policy must be between 32 and 512 characters long."
+
+-- A policy with this name already exists. Please choose a different name.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3584374593"] = "A policy with this name already exists. Please choose a different name."
+
+-- Use app default profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3587225583"] = "Use app default profile"
+
+-- No provider
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3740605451"] = "No provider"
+
+-- Load analysis rules from document
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3813558135"] = "Load analysis rules from document"
+
+-- Output rules
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3918193587"] = "Output rules"
+
+-- Please provide a name for your policy. This name will be used to identify the policy in AI Studio.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T4040507702"] = "Please provide a name for your policy. This name will be used to identify the policy in AI Studio."
+
+-- Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T652187065"] = "Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis."
+
+-- Policy description
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T748735777"] = "Policy description"
+
+-- Would you like to protect this policy so that you cannot accidentally edit or delete it?
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T80597472"] = "Would you like to protect this policy so that you cannot accidentally edit or delete it?"
+
+-- Cancel
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T900713019"] = "Cancel"
+
-- Please wait while we load the content of your file. Depending on the file type and size, this may take a moment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTCHECKDIALOG::T1205126512"] = "Please wait while we load the content of your file. Depending on the file type and size, this may take a moment."
@@ -7852,6 +7972,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T11721
-- Copy attachments into plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1345613295"] = "Copy attachments into plugin"
+-- Import
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1463683828"] = "Import"
+
-- Delete
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1469573738"] = "Delete"
@@ -7873,6 +7996,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T23198
-- This chat template preselects data sources which exist on this machine only: {0}. They cannot be rolled out, so a chat started with this template elsewhere begins without them. Do you want to export the template anyway?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2563631533"] = "This chat template preselects data sources which exist on this machine only: {0}. They cannot be rolled out, so a chat started with this template elsewhere begins without them. Do you want to export the template anyway?"
+-- Import Chat Template
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T263213584"] = "Import Chat Template"
+
-- Chat Template Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T275026390"] = "Chat Template Name"
@@ -8194,9 +8320,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T711745239"
-- Edit Profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1143111468"] = "Edit Profile"
+-- Import Profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1215374025"] = "Import Profile"
+
-- No profiles configured yet.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1433534732"] = "No profiles configured yet."
+-- Import
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1463683828"] = "Import"
+
-- Delete
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1469573738"] = "Delete"
diff --git a/app/MindWork AI Studio/Components/DataSourceManagement.razor b/app/MindWork AI Studio/Components/DataSourceManagement.razor
index 7d2010a0..5402f773 100644
--- a/app/MindWork AI Studio/Components/DataSourceManagement.razor
+++ b/app/MindWork AI Studio/Components/DataSourceManagement.razor
@@ -105,14 +105,17 @@
}
-
-
- @T("External Data (ERI-Server v1)")
-
-
- @T("Local Directory")
-
-
- @T("Local File")
-
-
\ No newline at end of file
+
+
+
+ @T("External Data (ERI-Server v1)")
+
+
+ @T("Local Directory")
+
+
+ @T("Local File")
+
+
+
+
diff --git a/app/MindWork AI Studio/Components/DataSourceManagement.razor.cs b/app/MindWork AI Studio/Components/DataSourceManagement.razor.cs
index 2aaacbb7..195d32fe 100644
--- a/app/MindWork AI Studio/Components/DataSourceManagement.razor.cs
+++ b/app/MindWork AI Studio/Components/DataSourceManagement.razor.cs
@@ -6,6 +6,7 @@ using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
+using Lua;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
@@ -170,8 +171,20 @@ public partial class DataSourceManagement : MSGComponentBase
await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
}
- private async Task AddDataSource(DataSourceType type)
+ private async Task ImportERIDataSource()
{
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DATA_SOURCES"))
+ return;
+ var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "DATA_SOURCES", T("Import ERI v1 Data Source"));
+ if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DATA_SOURCES"))
+ await this.AddDataSource(DataSourceType.ERI_V1, table);
+ }
+
+ private async Task AddDataSource(DataSourceType type, LuaTable? importedConfiguration = null)
+ {
+ if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DATA_SOURCES"))
+ return;
+
IDataSource? addedDataSource = null;
switch (type)
{
@@ -214,10 +227,13 @@ public partial class DataSourceManagement : MSGComponentBase
{
{ x => x.IsEditing, false },
};
+ if (importedConfiguration is not null)
+ eriDialogParameters.Add(x => x.ImportedConfiguration, importedConfiguration);
var eriDialogReference = await this.DialogService.ShowAsync(T("Add ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN);
var eriDialogResult = await eriDialogReference.Result;
- if (eriDialogResult is null || eriDialogResult.Canceled)
+ if (eriDialogResult is null || eriDialogResult.Canceled ||
+ (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DATA_SOURCES")))
return;
var eriDataSource = (DataSourceERI_V1)eriDialogResult.Data!;
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
index 31db52cb..47ca405d 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
@@ -93,6 +93,9 @@
}
-
+
+
+
+
}
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs
index a48b3f7d..4a83f8a4 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs
@@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
+using Lua;
+
namespace AIStudio.Components.Settings;
public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
@@ -53,16 +55,33 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
#endregion
- private async Task AddEmbeddingProvider()
+ private Task AddEmbeddingProvider() => this.AddEmbeddingProvider(null);
+
+ private async Task ImportEmbeddingProvider()
{
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("EMBEDDING_PROVIDERS"))
+ return;
+ var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "EMBEDDING_PROVIDERS", T("Import Embedding Provider"));
+ if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("EMBEDDING_PROVIDERS"))
+ await this.AddEmbeddingProvider(table);
+ }
+
+ private async Task AddEmbeddingProvider(LuaTable? importedConfiguration)
+ {
+ if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("EMBEDDING_PROVIDERS"))
+ return;
+
var dialogParameters = new DialogParameters
{
{ x => x.IsEditing, false },
};
+ if (importedConfiguration is not null)
+ dialogParameters.Add(x => x.ImportedConfiguration, importedConfiguration);
var dialogReference = await this.DialogService.ShowAsync(T("Add Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
- if (dialogResult is null || dialogResult.Canceled)
+ if (dialogResult is null || dialogResult.Canceled ||
+ (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("EMBEDDING_PROVIDERS")))
return;
var addedEmbedding = (EmbeddingProvider)dialogResult.Data!;
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor
index 7fd0d9da..3e016d5d 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor
@@ -78,5 +78,8 @@
}
-
+
+
+
+
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs
index 64464e24..cb1873f3 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs
@@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
+using Lua;
+
namespace AIStudio.Components.Settings;
public partial class SettingsPanelProviders : SettingsPanelProviderBase
@@ -39,17 +41,34 @@ public partial class SettingsPanelProviders : SettingsPanelProviderBase
#endregion
- [SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "Managing the provider list is the purpose of this settings panel. Reading providers goes through the settings manager, but adding, editing, and removing them stays here on purpose.")]
- private async Task AddLLMProvider()
+ private Task AddLLMProvider() => this.AddLLMProvider(null);
+
+ private async Task ImportProvider()
{
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("LLM_PROVIDERS"))
+ return;
+ var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "LLM_PROVIDERS", T("Import LLM Provider"));
+ if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("LLM_PROVIDERS"))
+ await this.AddLLMProvider(table);
+ }
+
+ [SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "Managing the provider list is the purpose of this settings panel. Reading providers goes through the settings manager, but adding, editing, and removing them stays here on purpose.")]
+ private async Task AddLLMProvider(LuaTable? importedConfiguration)
+ {
+ if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("LLM_PROVIDERS"))
+ return;
+
var dialogParameters = new DialogParameters
{
{ x => x.IsEditing, false },
};
+ if (importedConfiguration is not null)
+ dialogParameters.Add(x => x.ImportedConfiguration, importedConfiguration);
var dialogReference = await this.DialogService.ShowAsync(T("Add LLM Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
- if (dialogResult is null || dialogResult.Canceled)
+ if (dialogResult is null || dialogResult.Canceled ||
+ (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("LLM_PROVIDERS")))
return;
var addedProvider = (AIStudio.Settings.Provider)dialogResult.Data!;
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor
index 4ad4488f..1d864162 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor
@@ -83,6 +83,9 @@
}
-
+
+
+
+
}
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs
index 1db25379..3eff0a1d 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor.cs
@@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
+using Lua;
+
namespace AIStudio.Components.Settings;
public partial class SettingsPanelTranscription : SettingsPanelProviderBase
@@ -47,16 +49,33 @@ public partial class SettingsPanelTranscription : SettingsPanelProviderBase
#endregion
- private async Task AddTranscriptionProvider()
+ private Task AddTranscriptionProvider() => this.AddTranscriptionProvider(null);
+
+ private async Task ImportTranscriptionProvider()
{
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("TRANSCRIPTION_PROVIDERS"))
+ return;
+ var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "TRANSCRIPTION_PROVIDERS", T("Import Transcription Provider"));
+ if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("TRANSCRIPTION_PROVIDERS"))
+ await this.AddTranscriptionProvider(table);
+ }
+
+ private async Task AddTranscriptionProvider(LuaTable? importedConfiguration)
+ {
+ if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("TRANSCRIPTION_PROVIDERS"))
+ return;
+
var dialogParameters = new DialogParameters
{
{ x => x.IsEditing, false },
};
+ if (importedConfiguration is not null)
+ dialogParameters.Add(x => x.ImportedConfiguration, importedConfiguration);
var dialogReference = await this.DialogService.ShowAsync(T("Add Transcription Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
- if (dialogResult is null || dialogResult.Canceled)
+ if (dialogResult is null || dialogResult.Canceled ||
+ (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("TRANSCRIPTION_PROVIDERS")))
return;
var addedTranscription = (TranscriptionProvider)dialogResult.Data!;
diff --git a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
index c26d4872..182dc73c 100644
--- a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
@@ -4,6 +4,25 @@
+ @if (!this.IsEditing && !this.IsReadOnly)
+ {
+ @if (!string.IsNullOrWhiteSpace(this.importReferenceIssue))
+ {
+ @this.importReferenceIssue
+ }
+ @if (!string.IsNullOrWhiteSpace(this.relinkIssue))
+ {
+ @this.relinkIssue
+ }
+ @for (var index = 0; index < this.attachmentsToRelink.Count; index++)
+ {
+ var currentIndex = index;
+
+ }
+ }
@* A drop anywhere in this dialog belongs to the dialog, not to the page behind it: *@
@* The name for the drop state is given although nobody uses it: the messages of this dialog
are listed in a table, whose rows would otherwise ask for the same name. *@
@@ -265,4 +284,4 @@
}
}
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
index 942d2727..3b090e82 100644
--- a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
@@ -2,6 +2,10 @@ using AIStudio.Chat;
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
+using AIStudio.Tools.PluginSystem;
+using AIStudio.Tools.ToolCallingSystem;
+
+using Lua;
using Microsoft.AspNetCore.Components;
@@ -9,6 +13,9 @@ namespace AIStudio.Dialogs;
public partial class ChatTemplateDialog : MSGComponentBase
{
+ [Parameter]
+ public LuaTable? ImportedConfiguration { get; set; }
+
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -81,6 +88,9 @@ public partial class ChatTemplateDialog : MSGComponentBase
[Inject]
private ILogger Logger { get; init; } = null!;
+ [Inject]
+ private ToolRegistry ToolRegistry { get; init; } = null!;
+
private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new();
///
@@ -91,6 +101,9 @@ public partial class ChatTemplateDialog : MSGComponentBase
private bool dataIsValid;
private List dataExampleConversation = [];
private HashSet fileAttachments = [];
+ private List<(string OriginalPath, string ReplacementPath)> attachmentsToRelink = [];
+ private string relinkIssue = string.Empty;
+ private string importReferenceIssue = string.Empty;
private bool preselectTools;
private HashSet selectedToolIds = new(StringComparer.Ordinal);
private bool preselectDataSources;
@@ -153,6 +166,15 @@ public partial class ChatTemplateDialog : MSGComponentBase
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
+ if (firstRender && this.ImportedConfiguration is not null)
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
+ this.MudDialog.Cancel();
+ else
+ await this.ImportConfiguration(this.ImportedConfiguration);
+ this.StateHasChanged();
+ }
+
await base.OnAfterRenderAsync(firstRender);
}
@@ -176,6 +198,72 @@ public partial class ChatTemplateDialog : MSGComponentBase
IsEnterpriseConfiguration = false,
};
+ private async Task ImportConfiguration(LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ ConfigurationImportFields.String(table, "Name");
+ ConfigurationImportFields.String(table, "SystemPrompt");
+ ConfigurationImportFields.String(table, "PredefinedUserPrompt", required: false);
+ ConfigurationImportFields.Bool(table, "AllowProfileUsage");
+ var messages = ConfigurationImportFields.Table(table, "ExampleConversation");
+ for (var index = 1; index <= messages.ArrayLength; index++)
+ {
+ if (messages[index].Type is not LuaValueType.Table || !messages[index].TryRead(out var message))
+ throw new FormatException("An example conversation entry is not a table.");
+ ConfigurationImportFields.Enum(message, "Role");
+ if (string.IsNullOrWhiteSpace(ConfigurationImportFields.String(message, "Content")))
+ throw new FormatException("An example conversation message is empty.");
+ }
+ if (table.TryGetValue("ToolIds", out _))
+ ConfigurationImportFields.Strings(table, "ToolIds");
+ if (table.TryGetValue("DataSourceOptions", out _))
+ {
+ var options = ConfigurationImportFields.Table(table, "DataSourceOptions");
+ ConfigurationImportFields.Bool(options, "DisableDataSources");
+ ConfigurationImportFields.Bool(options, "AutomaticDataSourceSelection");
+ ConfigurationImportFields.Bool(options, "AutomaticValidation");
+ if (options.TryGetValue("PreselectedDataSourceIds", out _))
+ ConfigurationImportFields.Strings(options, "PreselectedDataSourceIds");
+ }
+ if (!ChatTemplate.TryParseChatTemplateTable(0, table, Guid.Empty, string.Empty, out var parsed) || parsed is not ChatTemplate template)
+ throw new FormatException("The chat template fields are malformed.");
+ var paths = ConfigurationImportFields.Strings(table, "FileAttachments");
+ var validAttachments = new HashSet();
+ var toRelink = new List<(string OriginalPath, string ReplacementPath)>();
+ foreach (var path in paths)
+ {
+ if (ConfigurationImportFields.IsExistingLocalFile(path))
+ validAttachments.Add(FileAttachment.FromPath(path));
+ else
+ toRelink.Add((path, string.Empty));
+ }
+
+ this.DataName = template.Name;
+ this.DataSystemPrompt = template.SystemPrompt;
+ this.PredefinedUserPrompt = template.PredefinedUserPrompt;
+ this.AllowProfileUsage = template.AllowProfileUsage;
+ this.dataExampleConversation = template.ExampleConversation.Select(block => block.DeepClone()).ToList();
+ this.fileAttachments = validAttachments;
+ this.attachmentsToRelink = toRelink;
+ this.preselectTools = template.ToolIds is not null;
+ this.selectedToolIds = template.ToolIds is null ? new(StringComparer.Ordinal) : new(template.ToolIds, StringComparer.Ordinal);
+ this.preselectDataSources = template.DataSourceOptions is not null;
+ this.templateDataSourceOptions = template.DataSourceOptions?.CreateCopy() ?? new DataSourceOptions { DisableDataSources = false };
+ this.importReferenceIssue = await this.BuildImportReferenceIssue();
+ this.form.ResetValidation();
+ }
+
+ private async Task BuildImportReferenceIssue()
+ {
+ var missingSources = this.templateDataSourceOptions.PreselectedDataSourceIds
+ .Where(id => this.SettingsManager.ConfigurationData.DataSources.All(source => source.Id != id)).ToList();
+ var availableToolIds = (await this.ToolRegistry.GetCatalogAsync(AIStudio.Tools.Components.CHAT))
+ .Select(item => item.Definition.Id).ToHashSet(StringComparer.Ordinal);
+ var missingTools = this.selectedToolIds.Where(id => !availableToolIds.Contains(id)).ToList();
+ var missing = missingSources.Select(id => $"data source {id}").Concat(missingTools.Select(id => $"tool {id}")).ToList();
+ return missing.Count == 0 ? string.Empty : $"Unavailable references: {string.Join(", ", missing)}. Review the selection before saving.";
+ }
+
private void SetSelectedToolIds(HashSet toolIds) => this.selectedToolIds = toolIds;
private void RemoveMessage(ContentBlock item)
@@ -271,9 +359,23 @@ public partial class ChatTemplateDialog : MSGComponentBase
private async Task Store()
{
+ if (this.ImportedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
+ return;
+
if (this.IsReadOnly)
return;
+ this.relinkIssue = string.Empty;
+ foreach (var (originalPath, replacementPath) in this.attachmentsToRelink)
+ {
+ if (!ConfigurationImportFields.IsExistingLocalFile(replacementPath))
+ {
+ this.relinkIssue = string.Format(T("Relink the missing attachment '{0}' to an existing local file before saving."), originalPath);
+ return;
+ }
+ this.fileAttachments.Add(FileAttachment.FromPath(replacementPath));
+ }
+
await this.form.Validate();
// When the data is not valid, we don't store it:
@@ -329,4 +431,4 @@ public partial class ChatTemplateDialog : MSGComponentBase
}
private void Cancel() => this.MudDialog.Cancel();
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor b/app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor
new file mode 100644
index 00000000..507ea0bf
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor
@@ -0,0 +1,18 @@
+@inherits MSGComponentBase
+
+
+
+
+ @T("Copy one exported configuration snippet from the item's Export configuration control and paste it below. You can review and change the filled form before saving a new local item.")
+
+
+ @if (!string.IsNullOrWhiteSpace(this.issue))
+ {
+ @this.issue
+ }
+
+
+ @T("Cancel")
+ @this.ImportLabel
+
+
diff --git a/app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor.cs
new file mode 100644
index 00000000..c6b97ad5
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/ConfigurationSnippetImportDialog.razor.cs
@@ -0,0 +1,58 @@
+using AIStudio.Components;
+using AIStudio.Tools.PluginSystem;
+
+using Lua;
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Dialogs;
+
+public partial class ConfigurationSnippetImportDialog : MSGComponentBase
+{
+ [CascadingParameter]
+ private IMudDialogInstance MudDialog { get; set; } = null!;
+
+ [Parameter]
+ public string Section { get; set; } = string.Empty;
+
+ [Parameter]
+ public string ImportLabel { get; set; } = string.Empty;
+
+ private string snippet = string.Empty;
+ private string issue = string.Empty;
+
+ private void Import()
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet(this.Section))
+ {
+ this.issue = T("Import is locked by your organization.");
+ return;
+ }
+
+ if (!ConfigurationSnippetParser.TryParse(this.snippet, this.Section, out var table, out this.issue))
+ return;
+
+ try
+ {
+ ConfigurationSnippetImportValidation.Validate(this.Section, table);
+ this.MudDialog.Close(DialogResult.Ok(table));
+ }
+ catch (FormatException exception)
+ {
+ this.issue = exception.Message;
+ }
+ }
+
+ private void Cancel() => this.MudDialog.Cancel();
+
+ public static async Task ShowAsync(IDialogService service, string section, string title)
+ {
+ var parameters = new DialogParameters
+ {
+ { x => x.Section, section },
+ { x => x.ImportLabel, title },
+ };
+ var dialog = await service.ShowAsync(title, parameters, DialogOptions.FULLSCREEN);
+ var result = await dialog.Result;
+ return result is { Canceled: false, Data: LuaTable table } ? table : null;
+ }
+}
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
index 3a3d4b00..a16077af 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
@@ -5,6 +5,17 @@
+ @if (!this.IsEditing)
+ {
+ @if (!string.IsNullOrWhiteSpace(this.importCredentialIssue))
+ {
+ @this.importCredentialIssue
+ }
+ @if (!string.IsNullOrWhiteSpace(this.importRetrievalIssue))
+ {
+ @this.importRetrievalIssue
+ }
+ }
@* ReSharper disable once CSharpWarnings::CS8974 *@
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
index 8bec772a..26d27855 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
@@ -4,8 +4,11 @@ using AIStudio.Settings.DataModel;
using AIStudio.Tools.ERIClient;
using AIStudio.Tools.ERIClient.DataModel;
using AIStudio.Tools.Services;
+using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Validation;
+using Lua;
+
using Microsoft.AspNetCore.Components;
using RetrievalInfo = AIStudio.Tools.ERIClient.DataModel.RetrievalInfo;
@@ -15,6 +18,9 @@ namespace AIStudio.Dialogs;
public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
{
+ [Parameter]
+ public LuaTable? ImportedConfiguration { get; set; }
+
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -43,6 +49,8 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
private bool dataIsValid;
private string[] dataIssues = [];
private string dataSecretStorageIssue = string.Empty;
+ private string importCredentialIssue = string.Empty;
+ private string importRetrievalIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
private List availableAuthMethods = [];
private DataSourceSecurity dataSecurityPolicy;
@@ -143,6 +151,15 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
// We don't want to show validation errors when the user opens the dialog.
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
+
+ if (firstRender && this.ImportedConfiguration is not null)
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DATA_SOURCES"))
+ this.MudDialog.Cancel();
+ else
+ await this.ImportConfiguration(this.ImportedConfiguration);
+ this.StateHasChanged();
+ }
await base.OnAfterRenderAsync(firstRender);
}
@@ -176,6 +193,53 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
MaxMatches = this.dataMaxMatches,
};
}
+
+ private Task ImportConfiguration(LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ if (ConfigurationImportFields.String(table, "Type") != "ERI_V1")
+ throw new FormatException("This data source is not an ERI v1 data source.");
+ var name = ConfigurationImportFields.String(table, "Name");
+ var hostname = ConfigurationImportFields.String(table, "Hostname");
+ var port = ConfigurationImportFields.Int(table, "Port");
+ if (port is < 1 or > 65535)
+ throw new FormatException("The 'Port' field must be between 1 and 65535.");
+ var authMethod = ConfigurationImportFields.Enum(table, "AuthMethod");
+ if (authMethod is AuthMethod.KERBEROS)
+ throw new FormatException("Kerberos data sources cannot be imported from configuration snippets.");
+ var securityPolicy = ConfigurationImportFields.Enum(table, "SecurityPolicy");
+ var retrievalId = ConfigurationImportFields.String(table, "SelectedRetrievalId");
+ var maxMatches = ConfigurationImportFields.Int(table, "MaxMatches", 10);
+ if (maxMatches is < 1 or > ushort.MaxValue)
+ throw new FormatException("The 'MaxMatches' field is outside the allowed range.");
+ var secretName = authMethod switch
+ {
+ AuthMethod.TOKEN => "Token",
+ AuthMethod.USERNAME_PASSWORD => "Password",
+ _ => string.Empty,
+ };
+ var secret = string.Empty;
+ var credentialIssue = string.Empty;
+ if (!string.IsNullOrEmpty(secretName))
+ secret = ConfigurationImportFields.Credential(table, secretName, out credentialIssue);
+ var username = ConfigurationImportFields.String(table, "Username", required: false);
+
+ this.dataName = name;
+ this.dataHostname = hostname;
+ this.dataPort = port;
+ this.dataAuthMethod = authMethod;
+ this.dataUsername = username;
+ this.dataSecurityPolicy = securityPolicy;
+ this.dataSelectedRetrievalProcess = this.dataSelectedRetrievalProcess with { Id = retrievalId };
+ this.dataMaxMatches = (ushort)maxMatches;
+ this.dataSecret = secret;
+ this.importCredentialIssue = credentialIssue;
+ this.importRetrievalIssue = string.Empty;
+ this.connectionTested = false;
+ this.connectionSuccessfulTested = false;
+ this.form.ResetValidation();
+ return Task.CompletedTask;
+ }
private bool IsConnectionEncrypted() => this.dataHostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase);
@@ -251,6 +315,17 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
}
this.availableRetrievalProcesses = retrievalInfoRequest.Data ?? [];
+ if (!string.IsNullOrWhiteSpace(this.dataSelectedRetrievalProcess.Id))
+ {
+ var importedRetrieval = this.availableRetrievalProcesses.FirstOrDefault(item => item.Id == this.dataSelectedRetrievalProcess.Id);
+ if (importedRetrieval != default)
+ this.dataSelectedRetrievalProcess = importedRetrieval;
+ else
+ {
+ this.importRetrievalIssue = string.Format(T("The imported retrieval process '{0}' is unavailable. Select another process before saving."), this.dataSelectedRetrievalProcess.Id);
+ this.dataSelectedRetrievalProcess = default;
+ }
+ }
this.connectionTested = true;
this.connectionSuccessfulTested = true;
@@ -304,6 +379,9 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
private async Task Store()
{
+ if (this.ImportedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DATA_SOURCES"))
+ return;
+
await this.form.Validate();
var testConnectionValidation = this.dataSourceValidation.ValidateTestedConnection();
@@ -337,4 +415,4 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
}
private void Cancel() => this.MudDialog.Cancel();
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor
new file mode 100644
index 00000000..72ef518c
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor
@@ -0,0 +1,45 @@
+@using AIStudio.Provider
+@using AIStudio.Settings
+@using AIStudio.Settings.DataModel
+@inherits MSGComponentBase
+
+
+
+ @if (!string.IsNullOrWhiteSpace(this.referenceIssue))
+ {
+ @this.referenceIssue
+ }
+
+
+
+
+
+
+
+ @T("No provider")
+ @foreach (var provider in this.SettingsManager.GetAllProviders())
+ {
+ @provider.InstanceName
+ }
+
+
+ @T("Use app default profile")
+ @T("No profile")
+ @foreach (var profile in this.SettingsManager.ConfigurationData.Profiles)
+ {
+ @profile.Name
+ }
+
+
+
+
+
+
+
+
+
+
+ @T("Cancel")
+ @T("Add")
+
+
diff --git a/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs
new file mode 100644
index 00000000..8f03e171
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs
@@ -0,0 +1,134 @@
+using AIStudio.Components;
+using AIStudio.Provider;
+using AIStudio.Settings;
+using AIStudio.Settings.DataModel;
+using AIStudio.Tools.PluginSystem;
+using AIStudio.Tools.ToolCallingSystem;
+
+using Lua;
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Dialogs;
+
+public partial class DocumentAnalysisPolicyDialog : MSGComponentBase
+{
+ [Parameter]
+ public LuaTable? ImportedConfiguration { get; set; }
+
+ [CascadingParameter]
+ private IMudDialogInstance MudDialog { get; set; } = null!;
+
+ [Inject]
+ private ToolRegistry ToolRegistry { get; init; } = null!;
+
+ private MudForm form = null!;
+ private bool isValid;
+ private string[] issues = [];
+ private string referenceIssue = string.Empty;
+ private string name = string.Empty;
+ private string description = string.Empty;
+ private string analysisRules = string.Empty;
+ private string outputRules = string.Empty;
+ private ConfidenceLevel minimumConfidence = ConfidenceLevel.NONE;
+ private HashSet allowedToolIds = new(StringComparer.Ordinal);
+ private string providerId = string.Empty;
+ private string profileId = Profile.NO_PROFILE.Id;
+ private bool hideDefinition;
+ private bool isProtected;
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender && this.ImportedConfiguration is not null)
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
+ this.MudDialog.Cancel();
+ else
+ await this.ImportConfiguration(this.ImportedConfiguration);
+ this.StateHasChanged();
+ }
+
+ await base.OnAfterRenderAsync(firstRender);
+ }
+
+ private async Task ImportConfiguration(LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ var importedName = ConfigurationImportFields.String(table, "PolicyName");
+ var importedDescription = ConfigurationImportFields.String(table, "PolicyDescription");
+ var importedAnalysisRules = ConfigurationImportFields.String(table, "AnalysisRules");
+ var importedOutputRules = ConfigurationImportFields.String(table, "OutputRules");
+ var confidence = ConfigurationImportFields.Enum(table, "MinimumProviderConfidence");
+ var toolIds = ConfigurationImportFields.Strings(table, "AllowedToolIds");
+ var importedProviderId = ConfigurationImportFields.String(table, "PreselectedProvider", required: false);
+ var importedProfileId = ConfigurationImportFields.String(table, "PreselectedProfile", required: false);
+ var hide = ConfigurationImportFields.Bool(table, "HidePolicyDefinition");
+
+ this.name = importedName;
+ this.description = importedDescription;
+ this.analysisRules = importedAnalysisRules;
+ this.outputRules = importedOutputRules;
+ this.minimumConfidence = confidence;
+ this.allowedToolIds = new(toolIds, StringComparer.Ordinal);
+ this.providerId = importedProviderId;
+ this.profileId = importedProfileId;
+ this.hideDefinition = hide;
+
+ var missing = new List();
+ if (!string.IsNullOrWhiteSpace(this.providerId) && this.SettingsManager.GetAllProviders().All(provider => provider.Id != this.providerId))
+ missing.Add($"provider {this.providerId}");
+ if (!string.IsNullOrWhiteSpace(this.profileId) && this.profileId != Profile.NO_PROFILE.Id && this.SettingsManager.ConfigurationData.Profiles.All(profile => profile.Id != this.profileId))
+ missing.Add($"profile {this.profileId}");
+ var availableToolIds = (await this.ToolRegistry.GetCatalogAsync(AIStudio.Tools.Components.DOCUMENT_ANALYSIS_ASSISTANT))
+ .Select(item => item.Definition.Id).ToHashSet(StringComparer.Ordinal);
+ missing.AddRange(this.allowedToolIds.Where(id => !availableToolIds.Contains(id)).Select(id => $"tool {id}"));
+ this.referenceIssue = missing.Count == 0 ? string.Empty : $"Unavailable references: {string.Join(", ", missing)}. Review the selections before saving.";
+ this.form.ResetValidation();
+ }
+
+ private async Task Store()
+ {
+ if (this.ImportedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
+ return;
+
+ await this.form.Validate();
+ if (!this.isValid)
+ return;
+
+ this.MudDialog.Close(DialogResult.Ok(new DataDocumentAnalysisPolicy
+ {
+ Id = Guid.NewGuid().ToString(),
+ PolicyName = this.name.Trim(),
+ PolicyDescription = this.description.Trim(),
+ AnalysisRules = this.analysisRules.Trim(),
+ OutputRules = this.outputRules.Trim(),
+ MinimumProviderConfidence = this.minimumConfidence,
+ AllowedToolIds = new(this.allowedToolIds, StringComparer.Ordinal),
+ PreselectedProvider = this.providerId,
+ PreselectedProfile = this.profileId,
+ HidePolicyDefinition = this.hideDefinition,
+ IsProtected = this.isProtected,
+ }));
+ }
+
+ private string? ValidateName(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return T("Please provide a name for your policy. This name will be used to identify the policy in AI Studio.");
+ if (value.Length is < 6 or > 60)
+ return T("The name of your policy must be between 6 and 60 characters long.");
+ if (this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Any(policy => policy.PolicyName == value))
+ return T("A policy with this name already exists. Please choose a different name.");
+ return null;
+ }
+
+ private string? ValidateDescription(string value) => value.Length is < 32 or > 512
+ ? T("The description of your policy must be between 32 and 512 characters long.") : null;
+
+ private string? ValidateAnalysisRules(string value) => string.IsNullOrWhiteSpace(value)
+ ? T("Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.") : null;
+
+ private string? ValidateOutputRules(string value) => string.IsNullOrWhiteSpace(value)
+ ? T("Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.") : null;
+
+ private void Cancel() => this.MudDialog.Cancel();
+}
diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor
index 566e0640..26357d1e 100644
--- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor
@@ -5,6 +5,13 @@
+ @if (!this.IsEditing)
+ {
+ @if (!string.IsNullOrWhiteSpace(this.importCredentialIssue))
+ {
+ @this.importCredentialIssue
+ }
+ }
@if (this.IsEnterpriseConfiguration)
{
diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
index 765e7aa5..b290481b 100644
--- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs
@@ -4,8 +4,11 @@ using AIStudio.Provider.HuggingFace;
using AIStudio.Settings;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
+using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Validation;
+using Lua;
+
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Host = AIStudio.Provider.SelfHosted.Host;
@@ -14,6 +17,9 @@ namespace AIStudio.Dialogs;
public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
{
+ [Parameter]
+ public LuaTable? ImportedConfiguration { get; set; }
+
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -134,6 +140,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private string dataAPIKey = string.Empty;
private bool dataHadStoredAPIKeyOnLoad;
private string dataAPIKeyStorageIssue = string.Empty;
+ private string importCredentialIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
private string dataLoadingModelsIssue = string.Empty;
private bool dataConfiguredModelIsNotOffered;
@@ -190,6 +197,40 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
HFInferenceProvider = this.HFInferenceProviderId,
};
}
+
+ private Task ImportConfiguration(LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ var modelTable = ConfigurationImportFields.Table(table, "Model");
+ var model = new Model(ConfigurationImportFields.String(modelTable, "Id"), ConfigurationImportFields.String(modelTable, "DisplayName"));
+ var name = ConfigurationImportFields.String(table, "Name");
+ var provider = ConfigurationImportFields.Enum(table, "UsedLLMProvider");
+ var host = ConfigurationImportFields.Enum(table, "Host");
+ var hostname = ConfigurationImportFields.String(table, "Hostname");
+ var hfProvider = table.TryGetValue("HFInferenceProvider", out _)
+ ? ConfigurationImportFields.Enum(table, "HFInferenceProvider") : HFInferenceProvider.NONE;
+ var tokenizerPath = ConfigurationImportFields.String(table, "TokenizerPath", required: false);
+ var tokenLimit = ConfigurationImportFields.Int(table, "TokenLimit", EmbeddingProvider.DEFAULT_TOKEN_LIMIT);
+ var batchSize = ConfigurationImportFields.Int(table, "EmbeddingBatchSize", EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE);
+ var credential = ConfigurationImportFields.Credential(table, "APIKey", out var credentialIssue);
+
+ this.DataName = name;
+ this.DataLLMProvider = provider;
+ this.DataHost = host;
+ this.DataHostname = hostname;
+ this.HFInferenceProviderId = hfProvider;
+ this.DataModel = model;
+ this.dataFilePath = tokenizerPath;
+ this.DataTokenLimit = tokenLimit;
+ this.DataEmbeddingBatchSize = batchSize;
+ this.showExpertSettings = !string.IsNullOrWhiteSpace(tokenizerPath) || tokenLimit != EmbeddingProvider.DEFAULT_TOKEN_LIMIT || batchSize != EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
+ this.dataAPIKey = credential;
+ this.importCredentialIssue = credentialIssue;
+ if (this.availableModels.All(candidate => candidate.Id != model.Id))
+ this.availableModels.Add(model);
+ this.form.ResetValidation();
+ return Task.CompletedTask;
+ }
#region Overrides of ComponentBase
@@ -247,6 +288,15 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
// We don't want to show validation errors when the user opens the dialog.
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
+
+ if (firstRender && this.ImportedConfiguration is not null)
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("EMBEDDING_PROVIDERS"))
+ this.MudDialog.Cancel();
+ else
+ await this.ImportConfiguration(this.ImportedConfiguration);
+ this.StateHasChanged();
+ }
await base.OnAfterRenderAsync(firstRender);
}
@@ -267,6 +317,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
private async Task Store()
{
+ if (this.ImportedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("EMBEDDING_PROVIDERS"))
+ return;
+
this.dataStoreWasAttempted = true;
await this.dataTokenizerValidationTask;
await this.form.Validate();
diff --git a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor
index b72d3135..c62103ec 100644
--- a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor
@@ -99,4 +99,4 @@
}
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs
index ba2dfff8..2e6dc0ac 100644
--- a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs
@@ -1,5 +1,8 @@
using AIStudio.Components;
using AIStudio.Settings;
+using AIStudio.Tools.PluginSystem;
+
+using Lua;
using Microsoft.AspNetCore.Components;
@@ -7,6 +10,9 @@ namespace AIStudio.Dialogs;
public partial class ProfileDialog : MSGComponentBase
{
+ [Parameter]
+ public LuaTable? ImportedConfiguration { get; set; }
+
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -78,6 +84,19 @@ public partial class ProfileDialog : MSGComponentBase
IsEnterpriseConfiguration = false,
};
+ private Task ImportConfiguration(LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ var name = ConfigurationImportFields.String(table, "Name");
+ var needToKnow = ConfigurationImportFields.String(table, "NeedToKnow");
+ var actions = ConfigurationImportFields.String(table, "Actions");
+ this.DataName = name;
+ this.DataNeedToKnow = needToKnow;
+ this.DataActions = actions;
+ this.form.ResetValidation();
+ return Task.CompletedTask;
+ }
+
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
@@ -104,6 +123,15 @@ public partial class ProfileDialog : MSGComponentBase
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
+ if (firstRender && this.ImportedConfiguration is not null)
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("PROFILES"))
+ this.MudDialog.Cancel();
+ else
+ await this.ImportConfiguration(this.ImportedConfiguration);
+ this.StateHasChanged();
+ }
+
await base.OnAfterRenderAsync(firstRender);
}
@@ -111,6 +139,9 @@ public partial class ProfileDialog : MSGComponentBase
private async Task Store()
{
+ if (this.ImportedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("PROFILES"))
+ return;
+
if (this.IsReadOnly)
return;
@@ -165,4 +196,4 @@ public partial class ProfileDialog : MSGComponentBase
}
private void Cancel() => this.MudDialog.Cancel();
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
index be647902..87a94410 100644
--- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
@@ -4,6 +4,13 @@
@inherits MSGComponentBase
+ @if (!this.IsEditing)
+ {
+ @if (!string.IsNullOrWhiteSpace(this.importCredentialIssue))
+ {
+ @this.importCredentialIssue
+ }
+ }
@if (this.IsEnterpriseConfiguration)
{
diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
index 26666691..114110ea 100644
--- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
@@ -10,8 +10,11 @@ using AIStudio.Tools.Rust;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Services;
+using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Validation;
+using Lua;
+
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@@ -24,6 +27,9 @@ namespace AIStudio.Dialogs;
///
public partial class ProviderDialog : MSGComponentBase, ISecretId
{
+ [Parameter]
+ public LuaTable? ImportedConfiguration { get; set; }
+
private enum ReasoningOverrideMode
{
AUTOMATIC,
@@ -154,6 +160,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private bool dataHadStoredAPIKeyOnLoad;
private string dataManuallyModel = string.Empty;
private string dataAPIKeyStorageIssue = string.Empty;
+ private string importCredentialIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
private string dataLoadingModelsIssue = string.Empty;
private string dataFilePath = string.Empty;
@@ -232,6 +239,41 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
return this.DataModel;
}
+ private Task ImportConfiguration(LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ var modelTable = ConfigurationImportFields.Table(table, "Model");
+ var model = new Model(ConfigurationImportFields.String(modelTable, "Id"), ConfigurationImportFields.String(modelTable, "DisplayName"));
+ var name = ConfigurationImportFields.String(table, "InstanceName");
+ var provider = ConfigurationImportFields.Enum(table, "UsedLLMProvider");
+ var host = ConfigurationImportFields.Enum(table, "Host");
+ var hostname = ConfigurationImportFields.String(table, "Hostname");
+ var hfProvider = table.TryGetValue("HFInferenceProvider", out _)
+ ? ConfigurationImportFields.Enum(table, "HFInferenceProvider") : HFInferenceProvider.NONE;
+ var tokenizerPath = ConfigurationImportFields.String(table, "TokenizerPath", required: false);
+ var additionalParameters = ConfigurationImportFields.String(table, "AdditionalJsonApiParameters", required: false);
+ var credential = ConfigurationImportFields.Credential(table, "APIKey", out var credentialIssue);
+ var overrides = ProviderCapabilityOverrides.TryParseFromLuaTable(0, table, Guid.Empty, this.Logger);
+
+ this.DataInstanceName = name;
+ this.DataLLMProvider = provider;
+ this.DataHost = host;
+ this.DataHostname = hostname;
+ this.HFInferenceProviderId = hfProvider;
+ this.DataModel = model;
+ this.dataManuallyModel = model.Id;
+ this.dataFilePath = tokenizerPath;
+ this.AdditionalJsonApiParameters = additionalParameters;
+ this.capabilityOverrides = overrides ?? new();
+ this.showExpertSettings = this.capabilityOverrides.HasOverrides || !string.IsNullOrWhiteSpace(this.dataFilePath) || !string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters);
+ this.dataAPIKey = credential;
+ this.importCredentialIssue = credentialIssue;
+ if (this.availableModels.All(candidate => candidate.Id != model.Id))
+ this.availableModels.Add(model);
+ this.form.ResetValidation();
+ return Task.CompletedTask;
+ }
+
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
@@ -297,6 +339,15 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
// We don't want to show validation errors when the user opens the dialog.
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
+
+ if (firstRender && this.ImportedConfiguration is not null)
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("LLM_PROVIDERS"))
+ this.MudDialog.Cancel();
+ else
+ await this.ImportConfiguration(this.ImportedConfiguration);
+ this.StateHasChanged();
+ }
await base.OnAfterRenderAsync(firstRender);
}
@@ -317,6 +368,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private async Task Store()
{
+ if (this.ImportedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("LLM_PROVIDERS"))
+ return;
+
this.dataStoreWasAttempted = true;
await this.dataTokenizerValidationTask;
await this.form.Validate();
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor
index 305f25e5..b6060a17 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor
@@ -81,13 +81,16 @@
}
-
- @T("Add Chat Template")
-
+
+
+ @T("Add Chat Template")
+
+
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs
index b941beab..d66ab0f0 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs
@@ -2,6 +2,8 @@ using AIStudio.Chat;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
+using Lua;
+
namespace AIStudio.Dialogs.Settings;
public partial class SettingsDialogChatTemplate : SettingsDialogBase
@@ -26,14 +28,30 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
#endregion
- private async Task AddChatTemplate()
+ private Task AddChatTemplate() => this.AddChatTemplate(null);
+
+ private async Task ImportChatTemplate()
{
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
+ return;
+ var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "CHAT_TEMPLATES", T("Import Chat Template"));
+ if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
+ await this.AddChatTemplate(table);
+ }
+
+ private async Task AddChatTemplate(LuaTable? importedConfiguration)
+ {
+ if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
+ return;
+
var dialogParameters = new DialogParameters
{
{ x => x.IsEditing, false },
};
+ if (importedConfiguration is not null)
+ dialogParameters.Add(x => x.ImportedConfiguration, importedConfiguration);
- if (this.CreateTemplateFromExistingChatThread)
+ if (this.CreateTemplateFromExistingChatThread && importedConfiguration is null)
{
dialogParameters.Add(x => x.CreateFromExistingChatThread, this.CreateTemplateFromExistingChatThread);
dialogParameters.Add(x => x.ExistingChatThread, this.ExistingChatThread);
@@ -41,7 +59,8 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
var dialogReference = await this.DialogService.ShowAsync(T("Add Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
- if (dialogResult is null || dialogResult.Canceled)
+ if (dialogResult is null || dialogResult.Canceled ||
+ (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES")))
return;
var addedChatTemplate = (ChatTemplate)dialogResult.Data!;
@@ -229,4 +248,4 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
if (!string.IsNullOrWhiteSpace(luaCode))
await this.RustService.CopyText2Clipboard(luaCode);
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor
index f84a170b..6b24a70a 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor
@@ -64,13 +64,16 @@
}
-
- @T("Add Profile")
-
+
+
+ @T("Add Profile")
+
+
+
@T("Close")
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs
index 531583a0..f274eeb7 100644
--- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs
@@ -1,19 +1,38 @@
using AIStudio.Settings;
+using Lua;
+
namespace AIStudio.Dialogs.Settings;
public partial class SettingsDialogProfiles : SettingsDialogBase
{
- private async Task AddProfile()
+ private Task AddProfile() => this.AddProfile(null);
+
+ private async Task ImportProfile()
{
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("PROFILES"))
+ return;
+ var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "PROFILES", T("Import Profile"));
+ if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("PROFILES"))
+ await this.AddProfile(table);
+ }
+
+ private async Task AddProfile(LuaTable? importedConfiguration)
+ {
+ if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("PROFILES"))
+ return;
+
var dialogParameters = new DialogParameters
{
{ x => x.IsEditing, false },
};
+ if (importedConfiguration is not null)
+ dialogParameters.Add(x => x.ImportedConfiguration, importedConfiguration);
var dialogReference = await this.DialogService.ShowAsync(T("Add Profile"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
- if (dialogResult is null || dialogResult.Canceled)
+ if (dialogResult is null || dialogResult.Canceled ||
+ (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("PROFILES")))
return;
var addedProfile = (Profile)dialogResult.Data!;
@@ -95,4 +114,4 @@ public partial class SettingsDialogProfiles : SettingsDialogBase
await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED);
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor b/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor
index 003129b2..ed1f8164 100644
--- a/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor
@@ -5,6 +5,13 @@
+ @if (!this.IsEditing)
+ {
+ @if (!string.IsNullOrWhiteSpace(this.importCredentialIssue))
+ {
+ @this.importCredentialIssue
+ }
+ }
@if (this.IsEnterpriseConfiguration)
{
diff --git a/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor.cs
index dd463a06..72a7a605 100644
--- a/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/TranscriptionProviderDialog.razor.cs
@@ -3,8 +3,11 @@ using AIStudio.Provider;
using AIStudio.Provider.HuggingFace;
using AIStudio.Settings;
using AIStudio.Tools.Services;
+using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Validation;
+using Lua;
+
using Microsoft.AspNetCore.Components;
using Host = AIStudio.Provider.SelfHosted.Host;
@@ -13,6 +16,9 @@ namespace AIStudio.Dialogs;
public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
{
+ [Parameter]
+ public LuaTable? ImportedConfiguration { get; set; }
+
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -107,6 +113,7 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
private string dataAPIKey = string.Empty;
private bool dataHadStoredAPIKeyOnLoad;
private string dataAPIKeyStorageIssue = string.Empty;
+ private string importCredentialIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
private string dataLoadingModelsIssue = string.Empty;
@@ -153,6 +160,33 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
HFInferenceProvider = this.HFInferenceProviderId,
};
}
+
+ private Task ImportConfiguration(LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ var modelTable = ConfigurationImportFields.Table(table, "Model");
+ var model = new Model(ConfigurationImportFields.String(modelTable, "Id"), ConfigurationImportFields.String(modelTable, "DisplayName"));
+ var name = ConfigurationImportFields.String(table, "Name");
+ var provider = ConfigurationImportFields.Enum(table, "UsedLLMProvider");
+ var host = ConfigurationImportFields.Enum(table, "Host");
+ var hostname = ConfigurationImportFields.String(table, "Hostname");
+ var hfProvider = table.TryGetValue("HFInferenceProvider", out _)
+ ? ConfigurationImportFields.Enum(table, "HFInferenceProvider") : HFInferenceProvider.NONE;
+ var credential = ConfigurationImportFields.Credential(table, "APIKey", out var credentialIssue);
+
+ this.DataName = name;
+ this.DataLLMProvider = provider;
+ this.DataHost = host;
+ this.DataHostname = hostname;
+ this.HFInferenceProviderId = hfProvider;
+ this.DataModel = model;
+ this.dataAPIKey = credential;
+ this.importCredentialIssue = credentialIssue;
+ if (this.availableModels.All(candidate => candidate.Id != model.Id))
+ this.availableModels.Add(model);
+ this.form.ResetValidation();
+ return Task.CompletedTask;
+ }
#region Overrides of ComponentBase
@@ -205,6 +239,15 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
// We don't want to show validation errors when the user opens the dialog.
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
+
+ if (firstRender && this.ImportedConfiguration is not null)
+ {
+ if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("TRANSCRIPTION_PROVIDERS"))
+ this.MudDialog.Cancel();
+ else
+ await this.ImportConfiguration(this.ImportedConfiguration);
+ this.StateHasChanged();
+ }
await base.OnAfterRenderAsync(firstRender);
}
@@ -225,6 +268,9 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
private async Task Store()
{
+ if (this.ImportedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("TRANSCRIPTION_PROVIDERS"))
+ return;
+
await this.form.Validate();
this.dataAPIKeyStorageIssue = string.Empty;
diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua
index 61590c55..150c2ab5 100644
--- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua
@@ -395,6 +395,20 @@ CONFIG["SETTINGS"] = {}
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddEmbeddingProvider"] = false
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddTranscriptionProvider"] = false
+-- Control whether users may paste exported configuration snippets to create local items.
+-- The master setting and the matching item setting must both be true. Provider imports
+-- also require DataApp.AllowUserToAddProvider and the matching provider Add setting.
+-- Blocked Import buttons remain visible with a lock. These settings do not affect
+-- plugin archive imports or exports.
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportConfigurationSnippets"] = false
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportProfile"] = false
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportLLMProvider"] = false
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportEmbeddingProvider"] = false
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportTranscriptionProvider"] = false
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportChatTemplate"] = false
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportERIDataSource"] = false
+-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportDocumentAnalysisPolicy"] = false
+
-- Configure the user permission to import plugin archives from disk.
-- When set to false, the import button on the plugins page stays visible but is disabled.
-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportPlugins"] = false
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
index fd485d76..298cf00f 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
@@ -181,6 +181,29 @@ public sealed class DataApp(Expression>? configSelection = n
///
public bool AllowUserToAddTranscriptionProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddTranscriptionProvider, true);
+ /// Should the user be allowed to import exported configuration snippets?
+ public bool AllowUserToImportConfigurationSnippets { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportConfigurationSnippets, true);
+
+ public bool AllowUserToImportProfile { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportProfile, true);
+ public bool AllowUserToImportLLMProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportLLMProvider, true);
+ public bool AllowUserToImportEmbeddingProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportEmbeddingProvider, true);
+ public bool AllowUserToImportTranscriptionProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportTranscriptionProvider, true);
+ public bool AllowUserToImportChatTemplate { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportChatTemplate, true);
+ public bool AllowUserToImportERIDataSource { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportERIDataSource, true);
+ public bool AllowUserToImportDocumentAnalysisPolicy { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportDocumentAnalysisPolicy, true);
+
+ public bool CanImportConfigurationSnippet(string section) => this.AllowUserToImportConfigurationSnippets && (section switch
+ {
+ "PROFILES" => this.AllowUserToImportProfile,
+ "LLM_PROVIDERS" => this.AllowUserToImportLLMProvider && this.AllowUserToAddProvider && this.AllowUserToAddLLMProvider,
+ "EMBEDDING_PROVIDERS" => this.AllowUserToImportEmbeddingProvider && this.AllowUserToAddProvider && this.AllowUserToAddEmbeddingProvider,
+ "TRANSCRIPTION_PROVIDERS" => this.AllowUserToImportTranscriptionProvider && this.AllowUserToAddProvider && this.AllowUserToAddTranscriptionProvider,
+ "CHAT_TEMPLATES" => this.AllowUserToImportChatTemplate,
+ "DATA_SOURCES" => this.AllowUserToImportERIDataSource,
+ "DOCUMENT_ANALYSIS_POLICIES" => this.AllowUserToImportDocumentAnalysisPolicy,
+ _ => false,
+ });
+
///
/// Should the user be allowed to import plugin archives from disk?
///
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs
new file mode 100644
index 00000000..18811ff8
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs
@@ -0,0 +1,90 @@
+using AIStudio.Tools;
+
+using Lua;
+
+namespace AIStudio.Tools.PluginSystem;
+
+public static class ConfigurationImportFields
+{
+ public static void ValidateExportId(LuaTable table)
+ {
+ if (!Guid.TryParse(String(table, "Id"), out _))
+ throw new FormatException("The exported item has an invalid ID.");
+ }
+
+ public static string String(LuaTable table, string name, bool required = true)
+ {
+ if (table.TryGetValue(name, out var value))
+ {
+ if (value.Type is LuaValueType.String && value.TryRead(out var text))
+ return text;
+ throw new FormatException($"The '{name}' field must be a string.");
+ }
+ if (!required)
+ return string.Empty;
+ throw new FormatException($"The '{name}' field must be a string.");
+ }
+
+ public static LuaTable Table(LuaTable table, string name)
+ {
+ if (table.TryGetValue(name, out var value) && value.Type is LuaValueType.Table && value.TryRead(out var nested))
+ return nested;
+ throw new FormatException($"The '{name}' field must be a table.");
+ }
+
+ public static T Enum(LuaTable table, string name) where T : struct, Enum
+ {
+ var text = String(table, name);
+ if (System.Enum.TryParse(text, true, out var result) && System.Enum.IsDefined(result))
+ return result;
+ throw new FormatException($"The '{name}' field has an unknown value.");
+ }
+
+ public static bool Bool(LuaTable table, string name, bool fallback = false)
+ {
+ if (!table.TryGetValue(name, out var value))
+ return fallback;
+ if (value.Type is LuaValueType.Boolean && value.TryRead(out var result))
+ return result;
+ throw new FormatException($"The '{name}' field must be true or false.");
+ }
+
+ public static int Int(LuaTable table, string name, int fallback = 0)
+ {
+ if (!table.TryGetValue(name, out var value))
+ return fallback;
+ if (value.Type is LuaValueType.Number && value.TryRead(out var number) && number >= int.MinValue && number <= int.MaxValue && number == Math.Truncate(number))
+ return (int)number;
+ throw new FormatException($"The '{name}' field must be a whole number.");
+ }
+
+ public static List Strings(LuaTable table, string name)
+ {
+ var nested = Table(table, name);
+ var result = new List();
+ for (var i = 1; i <= nested.ArrayLength; i++)
+ {
+ if (nested[i].Type is not LuaValueType.String || !nested[i].TryRead(out var text) || string.IsNullOrWhiteSpace(text))
+ throw new FormatException($"The '{name}' field contains an invalid entry.");
+ result.Add(text);
+ }
+ return result;
+ }
+
+ public static bool IsExistingLocalFile(string path) => Path.IsPathFullyQualified(path) && File.Exists(path);
+
+ public static string Credential(LuaTable table, string name, out string issue, EnterpriseEncryption? decryptionService = null)
+ {
+ issue = string.Empty;
+ var encrypted = String(table, name, required: false);
+ if (string.IsNullOrEmpty(encrypted))
+ return string.Empty;
+ if (!EnterpriseEncryption.IsEncrypted(encrypted))
+ throw new FormatException($"The '{name}' field must contain an ENC:v1 credential.");
+ var encryption = decryptionService ?? PluginFactory.EnterpriseEncryption;
+ if (encryption?.IsAvailable == true && encryption.TryDecrypt(encrypted, out var decrypted))
+ return decrypted;
+ issue = "The embedded credential could not be decrypted on this device. Enter your own credential before saving.";
+ return string.Empty;
+ }
+}
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs
new file mode 100644
index 00000000..6e8a5a32
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs
@@ -0,0 +1,135 @@
+using AIStudio.Chat;
+using AIStudio.Provider;
+using AIStudio.Provider.HuggingFace;
+using AIStudio.Settings;
+using AIStudio.Settings.DataModel;
+using AIStudio.Tools.ERIClient.DataModel;
+
+using Lua;
+
+using Host = AIStudio.Provider.SelfHosted.Host;
+
+namespace AIStudio.Tools.PluginSystem;
+
+/// Checks the fields used by the creation forms before leaving the paste dialog.
+public static class ConfigurationSnippetImportValidation
+{
+ public static void Validate(string section, LuaTable table)
+ {
+ ConfigurationImportFields.ValidateExportId(table);
+ switch (section)
+ {
+ case "PROFILES":
+ ConfigurationImportFields.String(table, "Name");
+ ConfigurationImportFields.String(table, "NeedToKnow");
+ ConfigurationImportFields.String(table, "Actions");
+ break;
+ case "LLM_PROVIDERS":
+ case "EMBEDDING_PROVIDERS":
+ case "TRANSCRIPTION_PROVIDERS":
+ ValidateProvider(section, table);
+ break;
+ case "CHAT_TEMPLATES":
+ ValidateChatTemplate(table);
+ break;
+ case "DATA_SOURCES":
+ ValidateERIDataSource(table);
+ break;
+ case "DOCUMENT_ANALYSIS_POLICIES":
+ ConfigurationImportFields.String(table, "PolicyName");
+ ConfigurationImportFields.String(table, "PolicyDescription");
+ ConfigurationImportFields.String(table, "AnalysisRules");
+ ConfigurationImportFields.String(table, "OutputRules");
+ ConfigurationImportFields.Enum(table, "MinimumProviderConfidence");
+ ConfigurationImportFields.Strings(table, "AllowedToolIds");
+ ConfigurationImportFields.String(table, "PreselectedProvider", required: false);
+ ConfigurationImportFields.String(table, "PreselectedProfile", required: false);
+ ConfigurationImportFields.Bool(table, "HidePolicyDefinition");
+ break;
+ default:
+ throw new FormatException("This configuration section cannot be imported here.");
+ }
+ }
+
+ private static void ValidateProvider(string section, LuaTable table)
+ {
+ var model = ConfigurationImportFields.Table(table, "Model");
+ ConfigurationImportFields.String(model, "Id");
+ ConfigurationImportFields.String(model, "DisplayName");
+ ConfigurationImportFields.String(table, section == "LLM_PROVIDERS" ? "InstanceName" : "Name");
+ ConfigurationImportFields.Enum(table, "UsedLLMProvider");
+ ConfigurationImportFields.Enum(table, "Host");
+ ConfigurationImportFields.String(table, "Hostname");
+ if (table.TryGetValue("HFInferenceProvider", out _))
+ ConfigurationImportFields.Enum(table, "HFInferenceProvider");
+ if (section != "TRANSCRIPTION_PROVIDERS")
+ ConfigurationImportFields.String(table, "TokenizerPath", required: false);
+ if (section == "LLM_PROVIDERS")
+ ConfigurationImportFields.String(table, "AdditionalJsonApiParameters", required: false);
+ if (section == "EMBEDDING_PROVIDERS")
+ {
+ ConfigurationImportFields.Int(table, "TokenLimit", EmbeddingProvider.DEFAULT_TOKEN_LIMIT);
+ ConfigurationImportFields.Int(table, "EmbeddingBatchSize", EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE);
+ }
+ ConfigurationImportFields.Credential(table, "APIKey", out _);
+ }
+
+ private static void ValidateChatTemplate(LuaTable table)
+ {
+ ConfigurationImportFields.String(table, "Name");
+ ConfigurationImportFields.String(table, "SystemPrompt");
+ ConfigurationImportFields.String(table, "PredefinedUserPrompt", required: false);
+ ConfigurationImportFields.Bool(table, "AllowProfileUsage");
+ var messages = ConfigurationImportFields.Table(table, "ExampleConversation");
+ for (var index = 1; index <= messages.ArrayLength; index++)
+ {
+ if (messages[index].Type is not LuaValueType.Table || !messages[index].TryRead(out var message))
+ throw new FormatException("An example conversation entry is not a table.");
+ ConfigurationImportFields.Enum(message, "Role");
+ if (string.IsNullOrWhiteSpace(ConfigurationImportFields.String(message, "Content")))
+ throw new FormatException("An example conversation message is empty.");
+ }
+ if (table.TryGetValue("ToolIds", out _))
+ ConfigurationImportFields.Strings(table, "ToolIds");
+ if (table.TryGetValue("DataSourceOptions", out _))
+ {
+ var options = ConfigurationImportFields.Table(table, "DataSourceOptions");
+ ConfigurationImportFields.Bool(options, "DisableDataSources");
+ ConfigurationImportFields.Bool(options, "AutomaticDataSourceSelection");
+ ConfigurationImportFields.Bool(options, "AutomaticValidation");
+ if (options.TryGetValue("PreselectedDataSourceIds", out _))
+ ConfigurationImportFields.Strings(options, "PreselectedDataSourceIds");
+ }
+ if (!ChatTemplate.TryParseChatTemplateTable(0, table, Guid.Empty, string.Empty, out _))
+ throw new FormatException("The chat template fields are malformed.");
+ ConfigurationImportFields.Strings(table, "FileAttachments");
+ }
+
+ private static void ValidateERIDataSource(LuaTable table)
+ {
+ if (ConfigurationImportFields.String(table, "Type") != "ERI_V1")
+ throw new FormatException("This data source is not an ERI v1 data source.");
+ ConfigurationImportFields.String(table, "Name");
+ ConfigurationImportFields.String(table, "Hostname");
+ var port = ConfigurationImportFields.Int(table, "Port");
+ if (port is < 1 or > 65535)
+ throw new FormatException("The 'Port' field must be between 1 and 65535.");
+ var authMethod = ConfigurationImportFields.Enum(table, "AuthMethod");
+ if (authMethod is AuthMethod.KERBEROS)
+ throw new FormatException("Kerberos data sources cannot be imported from configuration snippets.");
+ ConfigurationImportFields.Enum(table, "SecurityPolicy");
+ ConfigurationImportFields.String(table, "SelectedRetrievalId");
+ var maxMatches = ConfigurationImportFields.Int(table, "MaxMatches", 10);
+ if (maxMatches is < 1 or > ushort.MaxValue)
+ throw new FormatException("The 'MaxMatches' field is outside the allowed range.");
+ var secretName = authMethod switch
+ {
+ AuthMethod.TOKEN => "Token",
+ AuthMethod.USERNAME_PASSWORD => "Password",
+ _ => string.Empty,
+ };
+ if (!string.IsNullOrEmpty(secretName))
+ ConfigurationImportFields.Credential(table, secretName, out _);
+ ConfigurationImportFields.String(table, "Username", required: false);
+ }
+}
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs
new file mode 100644
index 00000000..f036bb64
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs
@@ -0,0 +1,235 @@
+using System.Globalization;
+using System.Text;
+
+using Lua;
+
+namespace AIStudio.Tools.PluginSystem;
+
+/// Reads one exported configuration assignment as data. No Lua state is created.
+public sealed class ConfigurationSnippetParser
+{
+ private readonly string source;
+ private int position;
+
+ private ConfigurationSnippetParser(string source) => this.source = source;
+
+ public static bool TryParse(string source, string expectedSection, out LuaTable table, out string issue)
+ {
+ table = new LuaTable();
+ issue = string.Empty;
+ if (string.IsNullOrWhiteSpace(source) || source.Length > 1_000_000)
+ {
+ issue = "Paste one exported configuration snippet (up to 1 MB).";
+ return false;
+ }
+
+ try
+ {
+ var parser = new ConfigurationSnippetParser(source);
+ parser.ExpectWord("CONFIG");
+ var section = parser.ReadBracketedString();
+ if (section != expectedSection)
+ throw new FormatException($"This is a {section} snippet. Paste a {expectedSection} snippet here.");
+
+ parser.Expect('[');
+ parser.Expect('#');
+ parser.ExpectWord("CONFIG");
+ if (parser.ReadBracketedString() != expectedSection)
+ throw new FormatException("The configuration section names do not match.");
+ parser.Expect('+');
+ parser.Expect('1');
+ parser.Expect(']');
+ parser.Expect('=');
+ table = parser.ReadTable(0);
+ parser.SkipTrivia();
+ if (parser.position != source.Length)
+ throw new FormatException("The snippet must contain exactly one table assignment and no executable code.");
+ return true;
+ }
+ catch (FormatException exception)
+ {
+ issue = exception.Message;
+ return false;
+ }
+ }
+
+ private LuaTable ReadTable(int depth)
+ {
+ if (depth > 32)
+ throw new FormatException("The snippet contains too many nested tables.");
+ this.Expect('{');
+ var table = new LuaTable();
+ var arrayIndex = 1;
+ var names = new HashSet(StringComparer.Ordinal);
+ while (true)
+ {
+ this.SkipTrivia();
+ if (this.Take('}'))
+ return table;
+
+ if (this.Take('['))
+ {
+ var key = this.ReadString();
+ this.Expect(']');
+ this.Expect('=');
+ if (!names.Add(key))
+ throw new FormatException($"The field '{key}' occurs more than once.");
+ table[key] = this.ReadValue(depth + 1);
+ }
+ else
+ table[arrayIndex++] = this.ReadValue(depth + 1);
+
+ this.SkipTrivia();
+ if (this.Take('}'))
+ return table;
+ if (!this.Take(',') && !this.Take(';'))
+ throw new FormatException($"Expected a comma or closing brace at character {this.position + 1}.");
+ }
+ }
+
+ private LuaValue ReadValue(int depth)
+ {
+ this.SkipTrivia();
+ if (this.Peek() == '{')
+ return this.ReadTable(depth);
+ if (this.Peek() is '"' or '\'' || this.Peek() == '[')
+ return this.ReadString();
+ if (this.TakeWord("true"))
+ return true;
+ if (this.TakeWord("false"))
+ return false;
+ if (this.TakeWord("nil"))
+ return LuaValue.Nil;
+
+ var start = this.position;
+ if (this.Peek() == '-')
+ this.position++;
+ while (char.IsAsciiDigit(this.Peek()))
+ this.position++;
+ if (this.Peek() == '.')
+ {
+ this.position++;
+ while (char.IsAsciiDigit(this.Peek()))
+ this.position++;
+ }
+ if (this.position > start && double.TryParse(this.source[start..this.position], NumberStyles.Float, CultureInfo.InvariantCulture, out var number) && double.IsFinite(number))
+ return number;
+ throw new FormatException($"Only literal values are allowed at character {start + 1}; executable Lua is not accepted.");
+ }
+
+ private string ReadBracketedString()
+ {
+ this.Expect('[');
+ var result = this.ReadString();
+ this.Expect(']');
+ return result;
+ }
+
+ private string ReadString()
+ {
+ this.SkipTrivia();
+ var quote = this.Peek();
+ if (quote == '[')
+ {
+ this.position++;
+ var equalsStart = this.position;
+ while (this.Peek() == '=')
+ this.position++;
+ var equals = this.source[equalsStart..this.position];
+ this.Expect('[');
+ if (this.Peek() is '\r' or '\n')
+ {
+ if (this.Take('\r'))
+ this.Take('\n');
+ else
+ this.position++;
+ }
+ var end = this.source.IndexOf("]" + equals + "]", this.position, StringComparison.Ordinal);
+ if (end < 0)
+ throw new FormatException("Unterminated long string.");
+ var value = this.source[this.position..end];
+ this.position = end + equals.Length + 2;
+ return value;
+ }
+ if (quote is not ('"' or '\''))
+ throw new FormatException($"Expected a quoted string at character {this.position + 1}.");
+ this.position++;
+ var builder = new StringBuilder();
+ while (this.position < this.source.Length)
+ {
+ var c = this.source[this.position++];
+ if (c == quote)
+ return builder.ToString();
+ if (c is '\r' or '\n')
+ throw new FormatException("A quoted string contains an unescaped newline.");
+ if (c != '\\')
+ {
+ builder.Append(c);
+ continue;
+ }
+ if (this.position == this.source.Length)
+ break;
+ c = this.source[this.position++];
+ builder.Append(c switch
+ {
+ 'n' => '\n', 'r' => '\r', 't' => '\t', 'a' => '\a', 'b' => '\b', 'f' => '\f', 'v' => '\v',
+ '\\' => '\\', '"' => '"', '\'' => '\'',
+ _ => throw new FormatException($"Unsupported string escape \\{c}."),
+ });
+ }
+ throw new FormatException("Unterminated quoted string.");
+ }
+
+ private void SkipTrivia()
+ {
+ while (this.position < this.source.Length)
+ {
+ if (char.IsWhiteSpace(this.source[this.position]))
+ {
+ this.position++;
+ continue;
+ }
+ if (this.source.AsSpan(this.position).StartsWith("--"))
+ {
+ this.position += 2;
+ while (this.position < this.source.Length && this.source[this.position] != '\n')
+ this.position++;
+ continue;
+ }
+ break;
+ }
+ }
+
+ private char Peek() => this.position < this.source.Length ? this.source[this.position] : '\0';
+
+ private bool Take(char c)
+ {
+ this.SkipTrivia();
+ if (this.Peek() != c)
+ return false;
+ this.position++;
+ return true;
+ }
+
+ private void Expect(char c)
+ {
+ if (!this.Take(c))
+ throw new FormatException($"Expected '{c}' at character {this.position + 1}.");
+ }
+
+ private bool TakeWord(string word)
+ {
+ this.SkipTrivia();
+ if (!this.source.AsSpan(this.position).StartsWith(word) ||
+ (this.position + word.Length < this.source.Length && (char.IsLetterOrDigit(this.source[this.position + word.Length]) || this.source[this.position + word.Length] == '_')))
+ return false;
+ this.position += word.Length;
+ return true;
+ }
+
+ private void ExpectWord(string word)
+ {
+ if (!this.TakeWord(word))
+ throw new FormatException($"Expected '{word}' at character {this.position + 1}.");
+ }
+}
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
index ea02f556..bb763e6f 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
@@ -250,6 +250,15 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Config: allow the user to add transcription providers?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddTranscriptionProvider, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportConfigurationSnippets, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportProfile, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportLLMProvider, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportEmbeddingProvider, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportTranscriptionProvider, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportChatTemplate, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportERIDataSource, this.Id, settingsTable, dryRun);
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportDocumentAnalysisPolicy, this.Id, settingsTable, dryRun);
+
// Config: allow the user to import plugin archives?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportPlugins, this.Id, settingsTable, dryRun);
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md
index e7affb11..83ed9be8 100644
--- a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md
+++ b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md
@@ -1,4 +1,7 @@
# v26.9.1, build 256 (2026-09-xx xx:xx UTC)
+- Added labeled import buttons beside Add for your providers, profiles, chat templates, ERI v1 data sources, and document analysis policies. Paste an exported configuration snippet, review the filled form, and save it as your own item.
+- Added controls for organizations to allow or lock configuration snippet imports by item type. A locked Import button stays visible, and plugin archive imports keep their own controls.
+- Added a way to relink missing file attachments when you create a chat template from an exported configuration.
- Added a way to copy an entire chat, either with the button in the chat toolbar or next to the chat in the chat list. The copy opens right away so you can continue in it, while the original conversation stays exactly as it was. Many thanks to Peer Hogeterp (`peerschuett`) and Jens Erler (`j-erler`) for this feature.
- Added a way to roll a chat back to an earlier AI response. The response you pick stays, and every message after it is removed permanently, together with the attachments of those messages.
- Added a way to save a single code block of an answer. When an answer holds a web page, a LaTeX document, or a Markdown text, the export menu now offers that block as a file of its own.
diff --git a/documentation/Enterprise IT.md b/documentation/Enterprise IT.md
index 186d59ed..c6ab12ae 100644
--- a/documentation/Enterprise IT.md
+++ b/documentation/Enterprise IT.md
@@ -619,6 +619,16 @@ Some exports ask a question first: a provider with an API key offers to include
Handing a whole plugin to a colleague is a different thing: that is the **Share** function on the plugins page, which writes a `.mwplugin` archive and is governed by its own organization setting rather than by the administration settings.
+### Importing one exported item for personal use
+
+Each parent screen has a button naming the item to import beside **Add** for profiles, LLM providers, embedding providers, transcription providers, chat templates, and document analysis policies. The data source screen has **Import ERI v1 Data Source** beside **Add Data Source**. Copy an exported snippet from the item's **Export configuration** control, paste one snippet of the matching type into the import dialog, and choose its item-specific import button. AI Studio reads the table as data; it does not run Lua code. The existing creation form then opens with its fields filled in. Review them and choose **Add** to create the item. Canceling either dialog leaves nothing saved. Tool Settings snippets cannot be imported this way.
+
+The imported item receives a new ID and the next local number. It belongs to you, even if the snippet came from an organization configuration. Existing provider, profile, tool, and data source references are kept; unavailable references are flagged so you can review them. Chat template attachments must point to existing local files. Relink any missing or relative paths in the creation dialog before saving.
+
+If an exported provider or ERI source contains an `ENC:v1` credential, AI Studio can fill it only on a device with the matching enterprise encryption secret. Otherwise, enter your own key, token, or password. A credential is stored in the local operating system secret store only when you save the item. Exporting still requires **Show administration settings**.
+
+All eight snippet import settings default to `true`. `DataApp.AllowUserToImportConfigurationSnippets` is the master switch; `DataApp.AllowUserToImportProfile`, `DataApp.AllowUserToImportLLMProvider`, `DataApp.AllowUserToImportEmbeddingProvider`, `DataApp.AllowUserToImportTranscriptionProvider`, `DataApp.AllowUserToImportChatTemplate`, `DataApp.AllowUserToImportERIDataSource`, and `DataApp.AllowUserToImportDocumentAnalysisPolicy` control the seven item types. The master and matching item setting must both allow the import. Provider imports also require `DataApp.AllowUserToAddProvider` and the matching provider Add permission. A blocked Import button stays visible with a lock. AI Studio checks these permissions when opening the import dialog and again before saving. Plugin archive import permissions are independent.
+
## Encrypted API Keys
You can include encrypted API keys in your configuration plugins for cloud providers (like OpenAI, Anthropic) or secured on-premise models. This feature provides obfuscation to prevent casual exposure of API keys in configuration files.
From ad6b700978160d49754e65fdeb9c0786ec65116a Mon Sep 17 00:00:00 2001
From: Peer Hogeterp <20603780+peerschuett@users.noreply.github.com>
Date: Thu, 24 Sep 2026 18:06:25 +0200
Subject: [PATCH 2/5] First review
---
.../DocumentAnalysisAssistant.razor.cs | 24 ++++-----
.../Dialogs/ChatTemplateDialog.razor | 13 +++--
.../Dialogs/ChatTemplateDialog.razor.cs | 51 ++++++++-----------
.../Dialogs/DataSourceERI_V1Dialog.razor.cs | 10 +---
.../DocumentAnalysisPolicyDialog.razor.cs | 8 +--
.../PluginSystem/ConfigurationImportFields.cs | 37 ++++++++++----
.../ConfigurationSnippetImportValidation.cs | 18 ++++---
.../ConfigurationSnippetParser.cs | 42 ++++++++-------
.../wwwroot/changelog/v26.9.1.md | 2 +-
documentation/Enterprise IT.md | 2 +-
10 files changed, 108 insertions(+), 99 deletions(-)
diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs
index 3e31c052..97343190 100644
--- a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs
+++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs
@@ -245,7 +245,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore this.AddPolicy(null);
-
private async Task ImportPolicy()
{
if (this.ArePolicyControlsDisabled || !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
return;
var table = await ConfigurationSnippetImportDialog.ShowAsync(this.DialogService, "DOCUMENT_ANALYSIS_POLICIES", T("Import document analysis policy"));
if (table is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
- await this.AddPolicy(table);
+ await this.AddImportedPolicy(table);
}
- private async Task AddPolicy(LuaTable? importedConfiguration)
+ private async Task AddImportedPolicy(LuaTable importedConfiguration)
{
- if (this.ArePolicyControlsDisabled)
+ if (this.ArePolicyControlsDisabled || !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
return;
- if (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
- return;
-
- var parameters = new DialogParameters();
- if (importedConfiguration is not null)
- parameters.Add(x => x.ImportedConfiguration, importedConfiguration);
+ var parameters = new DialogParameters
+ {
+ { x => x.ImportedConfiguration, importedConfiguration },
+ };
var dialogReference = await this.DialogService.ShowAsync(T("Add policy"), parameters, DialogOptions.FULLSCREEN);
var result = await dialogReference.Result;
if (result is null || result.Canceled || result.Data is not DataDocumentAnalysisPolicy policy ||
- (importedConfiguration is not null && !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES")))
+ !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("DOCUMENT_ANALYSIS_POLICIES"))
return;
var addedPolicy = policy with
@@ -521,7 +517,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore
+
+
+
+
+
+
}
}
@* A drop anywhere in this dialog belongs to the dialog, not to the page behind it: *@
diff --git a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
index 3b090e82..5006381a 100644
--- a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
@@ -200,33 +200,9 @@ public partial class ChatTemplateDialog : MSGComponentBase
private async Task ImportConfiguration(LuaTable table)
{
- ConfigurationImportFields.ValidateExportId(table);
- ConfigurationImportFields.String(table, "Name");
- ConfigurationImportFields.String(table, "SystemPrompt");
- ConfigurationImportFields.String(table, "PredefinedUserPrompt", required: false);
- ConfigurationImportFields.Bool(table, "AllowProfileUsage");
- var messages = ConfigurationImportFields.Table(table, "ExampleConversation");
- for (var index = 1; index <= messages.ArrayLength; index++)
- {
- if (messages[index].Type is not LuaValueType.Table || !messages[index].TryRead(out var message))
- throw new FormatException("An example conversation entry is not a table.");
- ConfigurationImportFields.Enum(message, "Role");
- if (string.IsNullOrWhiteSpace(ConfigurationImportFields.String(message, "Content")))
- throw new FormatException("An example conversation message is empty.");
- }
- if (table.TryGetValue("ToolIds", out _))
- ConfigurationImportFields.Strings(table, "ToolIds");
- if (table.TryGetValue("DataSourceOptions", out _))
- {
- var options = ConfigurationImportFields.Table(table, "DataSourceOptions");
- ConfigurationImportFields.Bool(options, "DisableDataSources");
- ConfigurationImportFields.Bool(options, "AutomaticDataSourceSelection");
- ConfigurationImportFields.Bool(options, "AutomaticValidation");
- if (options.TryGetValue("PreselectedDataSourceIds", out _))
- ConfigurationImportFields.Strings(options, "PreselectedDataSourceIds");
- }
+ ConfigurationSnippetImportValidation.Validate("CHAT_TEMPLATES", table);
if (!ChatTemplate.TryParseChatTemplateTable(0, table, Guid.Empty, string.Empty, out var parsed) || parsed is not ChatTemplate template)
- throw new FormatException("The chat template fields are malformed.");
+ throw new FormatException(T("The chat template fields are malformed."));
var paths = ConfigurationImportFields.Strings(table, "FileAttachments");
var validAttachments = new HashSet();
var toRelink = new List<(string OriginalPath, string ReplacementPath)>();
@@ -260,8 +236,18 @@ public partial class ChatTemplateDialog : MSGComponentBase
var availableToolIds = (await this.ToolRegistry.GetCatalogAsync(AIStudio.Tools.Components.CHAT))
.Select(item => item.Definition.Id).ToHashSet(StringComparer.Ordinal);
var missingTools = this.selectedToolIds.Where(id => !availableToolIds.Contains(id)).ToList();
- var missing = missingSources.Select(id => $"data source {id}").Concat(missingTools.Select(id => $"tool {id}")).ToList();
- return missing.Count == 0 ? string.Empty : $"Unavailable references: {string.Join(", ", missing)}. Review the selection before saving.";
+ var missing = missingSources.Select(ConfigurationImportFields.MissingDataSourceReference)
+ .Concat(missingTools.Select(ConfigurationImportFields.MissingToolReference)).ToList();
+ return ConfigurationImportFields.UnavailableReferencesIssue(missing);
+ }
+
+ private void UpdateRelinkPath(int index, string path) => this.attachmentsToRelink[index] = (this.attachmentsToRelink[index].OriginalPath, path);
+
+ private void RemoveAttachmentToRelink(int index)
+ {
+ this.attachmentsToRelink.RemoveAt(index);
+ if (this.attachmentsToRelink.Count == 0)
+ this.relinkIssue = string.Empty;
}
private void SetSelectedToolIds(HashSet toolIds) => this.selectedToolIds = toolIds;
@@ -365,15 +351,16 @@ public partial class ChatTemplateDialog : MSGComponentBase
if (this.IsReadOnly)
return;
+ // Only check the relinked attachments here. They are added right before closing, so that a
+ // failed save does not leave a path behind which the user changes afterward:
this.relinkIssue = string.Empty;
foreach (var (originalPath, replacementPath) in this.attachmentsToRelink)
{
if (!ConfigurationImportFields.IsExistingLocalFile(replacementPath))
{
- this.relinkIssue = string.Format(T("Relink the missing attachment '{0}' to an existing local file before saving."), originalPath);
+ this.relinkIssue = string.Format(T("Relink the missing attachment '{0}' to an existing local file or remove it before saving."), originalPath);
return;
}
- this.fileAttachments.Add(FileAttachment.FromPath(replacementPath));
}
await this.form.Validate();
@@ -386,6 +373,10 @@ public partial class ChatTemplateDialog : MSGComponentBase
if (this.isInlineEditOnGoing)
return;
+ foreach (var (_, replacementPath) in this.attachmentsToRelink)
+ this.fileAttachments.Add(FileAttachment.FromPath(replacementPath));
+ this.attachmentsToRelink.Clear();
+
// Use the data model to store the chat template.
// We just return this data to the parent component:
var addedChatTemplateSettings = this.CreateChatTemplateSettings();
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
index 26d27855..5827274f 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
@@ -196,22 +196,14 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
private Task ImportConfiguration(LuaTable table)
{
- ConfigurationImportFields.ValidateExportId(table);
- if (ConfigurationImportFields.String(table, "Type") != "ERI_V1")
- throw new FormatException("This data source is not an ERI v1 data source.");
+ ConfigurationSnippetImportValidation.Validate("DATA_SOURCES", table);
var name = ConfigurationImportFields.String(table, "Name");
var hostname = ConfigurationImportFields.String(table, "Hostname");
var port = ConfigurationImportFields.Int(table, "Port");
- if (port is < 1 or > 65535)
- throw new FormatException("The 'Port' field must be between 1 and 65535.");
var authMethod = ConfigurationImportFields.Enum(table, "AuthMethod");
- if (authMethod is AuthMethod.KERBEROS)
- throw new FormatException("Kerberos data sources cannot be imported from configuration snippets.");
var securityPolicy = ConfigurationImportFields.Enum(table, "SecurityPolicy");
var retrievalId = ConfigurationImportFields.String(table, "SelectedRetrievalId");
var maxMatches = ConfigurationImportFields.Int(table, "MaxMatches", 10);
- if (maxMatches is < 1 or > ushort.MaxValue)
- throw new FormatException("The 'MaxMatches' field is outside the allowed range.");
var secretName = authMethod switch
{
AuthMethod.TOKEN => "Token",
diff --git a/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs
index 8f03e171..464bd02f 100644
--- a/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor.cs
@@ -75,13 +75,13 @@ public partial class DocumentAnalysisPolicyDialog : MSGComponentBase
var missing = new List();
if (!string.IsNullOrWhiteSpace(this.providerId) && this.SettingsManager.GetAllProviders().All(provider => provider.Id != this.providerId))
- missing.Add($"provider {this.providerId}");
+ missing.Add(ConfigurationImportFields.MissingProviderReference(this.providerId));
if (!string.IsNullOrWhiteSpace(this.profileId) && this.profileId != Profile.NO_PROFILE.Id && this.SettingsManager.ConfigurationData.Profiles.All(profile => profile.Id != this.profileId))
- missing.Add($"profile {this.profileId}");
+ missing.Add(ConfigurationImportFields.MissingProfileReference(this.profileId));
var availableToolIds = (await this.ToolRegistry.GetCatalogAsync(AIStudio.Tools.Components.DOCUMENT_ANALYSIS_ASSISTANT))
.Select(item => item.Definition.Id).ToHashSet(StringComparer.Ordinal);
- missing.AddRange(this.allowedToolIds.Where(id => !availableToolIds.Contains(id)).Select(id => $"tool {id}"));
- this.referenceIssue = missing.Count == 0 ? string.Empty : $"Unavailable references: {string.Join(", ", missing)}. Review the selections before saving.";
+ missing.AddRange(this.allowedToolIds.Where(id => !availableToolIds.Contains(id)).Select(ConfigurationImportFields.MissingToolReference));
+ this.referenceIssue = ConfigurationImportFields.UnavailableReferencesIssue(missing);
this.form.ResetValidation();
}
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs
index 18811ff8..349df939 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationImportFields.cs
@@ -6,10 +6,12 @@ namespace AIStudio.Tools.PluginSystem;
public static class ConfigurationImportFields
{
+ private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationImportFields).Namespace, nameof(ConfigurationImportFields));
+
public static void ValidateExportId(LuaTable table)
{
if (!Guid.TryParse(String(table, "Id"), out _))
- throw new FormatException("The exported item has an invalid ID.");
+ throw new FormatException(TB("The exported item has an invalid ID."));
}
public static string String(LuaTable table, string name, bool required = true)
@@ -18,18 +20,18 @@ public static class ConfigurationImportFields
{
if (value.Type is LuaValueType.String && value.TryRead(out var text))
return text;
- throw new FormatException($"The '{name}' field must be a string.");
+ throw new FormatException(string.Format(TB("The '{0}' field must be a string."), name));
}
if (!required)
return string.Empty;
- throw new FormatException($"The '{name}' field must be a string.");
+ throw new FormatException(string.Format(TB("The '{0}' field must be a string."), name));
}
public static LuaTable Table(LuaTable table, string name)
{
if (table.TryGetValue(name, out var value) && value.Type is LuaValueType.Table && value.TryRead(out var nested))
return nested;
- throw new FormatException($"The '{name}' field must be a table.");
+ throw new FormatException(string.Format(TB("The '{0}' field must be a table."), name));
}
public static T Enum(LuaTable table, string name) where T : struct, Enum
@@ -37,7 +39,7 @@ public static class ConfigurationImportFields
var text = String(table, name);
if (System.Enum.TryParse(text, true, out var result) && System.Enum.IsDefined(result))
return result;
- throw new FormatException($"The '{name}' field has an unknown value.");
+ throw new FormatException(string.Format(TB("The '{0}' field has an unknown value."), name));
}
public static bool Bool(LuaTable table, string name, bool fallback = false)
@@ -46,7 +48,7 @@ public static class ConfigurationImportFields
return fallback;
if (value.Type is LuaValueType.Boolean && value.TryRead(out var result))
return result;
- throw new FormatException($"The '{name}' field must be true or false.");
+ throw new FormatException(string.Format(TB("The '{0}' field must be true or false."), name));
}
public static int Int(LuaTable table, string name, int fallback = 0)
@@ -55,7 +57,7 @@ public static class ConfigurationImportFields
return fallback;
if (value.Type is LuaValueType.Number && value.TryRead(out var number) && number >= int.MinValue && number <= int.MaxValue && number == Math.Truncate(number))
return (int)number;
- throw new FormatException($"The '{name}' field must be a whole number.");
+ throw new FormatException(string.Format(TB("The '{0}' field must be a whole number."), name));
}
public static List Strings(LuaTable table, string name)
@@ -65,7 +67,7 @@ public static class ConfigurationImportFields
for (var i = 1; i <= nested.ArrayLength; i++)
{
if (nested[i].Type is not LuaValueType.String || !nested[i].TryRead(out var text) || string.IsNullOrWhiteSpace(text))
- throw new FormatException($"The '{name}' field contains an invalid entry.");
+ throw new FormatException(string.Format(TB("The '{0}' field contains an invalid entry."), name));
result.Add(text);
}
return result;
@@ -73,6 +75,19 @@ public static class ConfigurationImportFields
public static bool IsExistingLocalFile(string path) => Path.IsPathFullyQualified(path) && File.Exists(path);
+ /// Lists the given references as a warning, or returns an empty text when nothing is missing.
+ public static string UnavailableReferencesIssue(IReadOnlyCollection missing) => missing.Count == 0
+ ? string.Empty
+ : string.Format(TB("Unavailable references: {0}. Review the selections before saving."), string.Join(", ", missing));
+
+ public static string MissingProviderReference(string id) => string.Format(TB("provider {0}"), id);
+
+ public static string MissingProfileReference(string id) => string.Format(TB("profile {0}"), id);
+
+ public static string MissingToolReference(string id) => string.Format(TB("tool {0}"), id);
+
+ public static string MissingDataSourceReference(string id) => string.Format(TB("data source {0}"), id);
+
public static string Credential(LuaTable table, string name, out string issue, EnterpriseEncryption? decryptionService = null)
{
issue = string.Empty;
@@ -80,11 +95,11 @@ public static class ConfigurationImportFields
if (string.IsNullOrEmpty(encrypted))
return string.Empty;
if (!EnterpriseEncryption.IsEncrypted(encrypted))
- throw new FormatException($"The '{name}' field must contain an ENC:v1 credential.");
+ throw new FormatException(string.Format(TB("The '{0}' field must contain an ENC:v1 credential."), name));
var encryption = decryptionService ?? PluginFactory.EnterpriseEncryption;
if (encryption?.IsAvailable == true && encryption.TryDecrypt(encrypted, out var decrypted))
return decrypted;
- issue = "The embedded credential could not be decrypted on this device. Enter your own credential before saving.";
+ issue = TB("The embedded credential could not be decrypted on this device. Enter your own credential before saving.");
return string.Empty;
}
-}
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs
index 6e8a5a32..d3d077f3 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetImportValidation.cs
@@ -14,6 +14,8 @@ namespace AIStudio.Tools.PluginSystem;
/// Checks the fields used by the creation forms before leaving the paste dialog.
public static class ConfigurationSnippetImportValidation
{
+ private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationSnippetImportValidation).Namespace, nameof(ConfigurationSnippetImportValidation));
+
public static void Validate(string section, LuaTable table)
{
ConfigurationImportFields.ValidateExportId(table);
@@ -47,7 +49,7 @@ public static class ConfigurationSnippetImportValidation
ConfigurationImportFields.Bool(table, "HidePolicyDefinition");
break;
default:
- throw new FormatException("This configuration section cannot be imported here.");
+ throw new FormatException(TB("This configuration section cannot be imported here."));
}
}
@@ -84,10 +86,10 @@ public static class ConfigurationSnippetImportValidation
for (var index = 1; index <= messages.ArrayLength; index++)
{
if (messages[index].Type is not LuaValueType.Table || !messages[index].TryRead(out var message))
- throw new FormatException("An example conversation entry is not a table.");
+ throw new FormatException(TB("An example conversation entry is not a table."));
ConfigurationImportFields.Enum(message, "Role");
if (string.IsNullOrWhiteSpace(ConfigurationImportFields.String(message, "Content")))
- throw new FormatException("An example conversation message is empty.");
+ throw new FormatException(TB("An example conversation message is empty."));
}
if (table.TryGetValue("ToolIds", out _))
ConfigurationImportFields.Strings(table, "ToolIds");
@@ -101,27 +103,27 @@ public static class ConfigurationSnippetImportValidation
ConfigurationImportFields.Strings(options, "PreselectedDataSourceIds");
}
if (!ChatTemplate.TryParseChatTemplateTable(0, table, Guid.Empty, string.Empty, out _))
- throw new FormatException("The chat template fields are malformed.");
+ throw new FormatException(TB("The chat template fields are malformed."));
ConfigurationImportFields.Strings(table, "FileAttachments");
}
private static void ValidateERIDataSource(LuaTable table)
{
if (ConfigurationImportFields.String(table, "Type") != "ERI_V1")
- throw new FormatException("This data source is not an ERI v1 data source.");
+ throw new FormatException(TB("This data source is not an ERI v1 data source."));
ConfigurationImportFields.String(table, "Name");
ConfigurationImportFields.String(table, "Hostname");
var port = ConfigurationImportFields.Int(table, "Port");
if (port is < 1 or > 65535)
- throw new FormatException("The 'Port' field must be between 1 and 65535.");
+ throw new FormatException(TB("The 'Port' field must be between 1 and 65535."));
var authMethod = ConfigurationImportFields.Enum(table, "AuthMethod");
if (authMethod is AuthMethod.KERBEROS)
- throw new FormatException("Kerberos data sources cannot be imported from configuration snippets.");
+ throw new FormatException(TB("Kerberos data sources cannot be imported from configuration snippets."));
ConfigurationImportFields.Enum(table, "SecurityPolicy");
ConfigurationImportFields.String(table, "SelectedRetrievalId");
var maxMatches = ConfigurationImportFields.Int(table, "MaxMatches", 10);
if (maxMatches is < 1 or > ushort.MaxValue)
- throw new FormatException("The 'MaxMatches' field is outside the allowed range.");
+ throw new FormatException(TB("The 'MaxMatches' field is outside the allowed range."));
var secretName = authMethod switch
{
AuthMethod.TOKEN => "Token",
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs
index f036bb64..d1e0dd74 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationSnippetParser.cs
@@ -13,13 +13,15 @@ public sealed class ConfigurationSnippetParser
private ConfigurationSnippetParser(string source) => this.source = source;
+ private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationSnippetParser).Namespace, nameof(ConfigurationSnippetParser));
+
public static bool TryParse(string source, string expectedSection, out LuaTable table, out string issue)
{
table = new LuaTable();
issue = string.Empty;
if (string.IsNullOrWhiteSpace(source) || source.Length > 1_000_000)
{
- issue = "Paste one exported configuration snippet (up to 1 MB).";
+ issue = TB("Paste one exported configuration snippet (up to 1 MB).");
return false;
}
@@ -29,13 +31,13 @@ public sealed class ConfigurationSnippetParser
parser.ExpectWord("CONFIG");
var section = parser.ReadBracketedString();
if (section != expectedSection)
- throw new FormatException($"This is a {section} snippet. Paste a {expectedSection} snippet here.");
+ throw new FormatException(string.Format(TB("This is a {0} snippet. Paste a {1} snippet here."), section, expectedSection));
parser.Expect('[');
parser.Expect('#');
parser.ExpectWord("CONFIG");
if (parser.ReadBracketedString() != expectedSection)
- throw new FormatException("The configuration section names do not match.");
+ throw new FormatException(TB("The configuration section names do not match."));
parser.Expect('+');
parser.Expect('1');
parser.Expect(']');
@@ -43,7 +45,7 @@ public sealed class ConfigurationSnippetParser
table = parser.ReadTable(0);
parser.SkipTrivia();
if (parser.position != source.Length)
- throw new FormatException("The snippet must contain exactly one table assignment and no executable code.");
+ throw new FormatException(TB("The snippet must contain exactly one table assignment and no executable code."));
return true;
}
catch (FormatException exception)
@@ -56,7 +58,7 @@ public sealed class ConfigurationSnippetParser
private LuaTable ReadTable(int depth)
{
if (depth > 32)
- throw new FormatException("The snippet contains too many nested tables.");
+ throw new FormatException(TB("The snippet contains too many nested tables."));
this.Expect('{');
var table = new LuaTable();
var arrayIndex = 1;
@@ -67,13 +69,15 @@ public sealed class ConfigurationSnippetParser
if (this.Take('}'))
return table;
- if (this.Take('['))
+ // "[[" or "[=" opens a long string, which is an array value rather than a bracketed key:
+ if (this.Peek() == '[' && !this.IsLongStringStart())
{
+ this.position++;
var key = this.ReadString();
this.Expect(']');
this.Expect('=');
if (!names.Add(key))
- throw new FormatException($"The field '{key}' occurs more than once.");
+ throw new FormatException(string.Format(TB("The field '{0}' occurs more than once."), key));
table[key] = this.ReadValue(depth + 1);
}
else
@@ -83,7 +87,7 @@ public sealed class ConfigurationSnippetParser
if (this.Take('}'))
return table;
if (!this.Take(',') && !this.Take(';'))
- throw new FormatException($"Expected a comma or closing brace at character {this.position + 1}.");
+ throw new FormatException(string.Format(TB("Expected a comma or closing brace at character {0}."), this.position + 1));
}
}
@@ -114,9 +118,11 @@ public sealed class ConfigurationSnippetParser
}
if (this.position > start && double.TryParse(this.source[start..this.position], NumberStyles.Float, CultureInfo.InvariantCulture, out var number) && double.IsFinite(number))
return number;
- throw new FormatException($"Only literal values are allowed at character {start + 1}; executable Lua is not accepted.");
+ throw new FormatException(string.Format(TB("Only literal values are allowed at character {0}; executable Lua is not accepted."), start + 1));
}
+ private bool IsLongStringStart() => this.position + 1 < this.source.Length && this.source[this.position + 1] is '[' or '=';
+
private string ReadBracketedString()
{
this.Expect('[');
@@ -136,7 +142,9 @@ public sealed class ConfigurationSnippetParser
while (this.Peek() == '=')
this.position++;
var equals = this.source[equalsStart..this.position];
- this.Expect('[');
+ if (this.Peek() != '[')
+ throw new FormatException(string.Format(TB("Expected '{0}' at character {1}."), '[', this.position + 1));
+ this.position++;
if (this.Peek() is '\r' or '\n')
{
if (this.Take('\r'))
@@ -146,13 +154,13 @@ public sealed class ConfigurationSnippetParser
}
var end = this.source.IndexOf("]" + equals + "]", this.position, StringComparison.Ordinal);
if (end < 0)
- throw new FormatException("Unterminated long string.");
+ throw new FormatException(TB("Unterminated long string."));
var value = this.source[this.position..end];
this.position = end + equals.Length + 2;
return value;
}
if (quote is not ('"' or '\''))
- throw new FormatException($"Expected a quoted string at character {this.position + 1}.");
+ throw new FormatException(string.Format(TB("Expected a quoted string at character {0}."), this.position + 1));
this.position++;
var builder = new StringBuilder();
while (this.position < this.source.Length)
@@ -161,7 +169,7 @@ public sealed class ConfigurationSnippetParser
if (c == quote)
return builder.ToString();
if (c is '\r' or '\n')
- throw new FormatException("A quoted string contains an unescaped newline.");
+ throw new FormatException(TB("A quoted string contains an unescaped newline."));
if (c != '\\')
{
builder.Append(c);
@@ -174,10 +182,10 @@ public sealed class ConfigurationSnippetParser
{
'n' => '\n', 'r' => '\r', 't' => '\t', 'a' => '\a', 'b' => '\b', 'f' => '\f', 'v' => '\v',
'\\' => '\\', '"' => '"', '\'' => '\'',
- _ => throw new FormatException($"Unsupported string escape \\{c}."),
+ _ => throw new FormatException(string.Format(TB("Unsupported string escape sequence: {0}"), "\\" + c)),
});
}
- throw new FormatException("Unterminated quoted string.");
+ throw new FormatException(TB("Unterminated quoted string."));
}
private void SkipTrivia()
@@ -214,7 +222,7 @@ public sealed class ConfigurationSnippetParser
private void Expect(char c)
{
if (!this.Take(c))
- throw new FormatException($"Expected '{c}' at character {this.position + 1}.");
+ throw new FormatException(string.Format(TB("Expected '{0}' at character {1}."), c, this.position + 1));
}
private bool TakeWord(string word)
@@ -230,6 +238,6 @@ public sealed class ConfigurationSnippetParser
private void ExpectWord(string word)
{
if (!this.TakeWord(word))
- throw new FormatException($"Expected '{word}' at character {this.position + 1}.");
+ throw new FormatException(string.Format(TB("Expected '{0}' at character {1}."), word, this.position + 1));
}
}
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md
index 83ed9be8..f32bbc5b 100644
--- a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md
+++ b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md
@@ -1,7 +1,7 @@
# v26.9.1, build 256 (2026-09-xx xx:xx UTC)
- Added labeled import buttons beside Add for your providers, profiles, chat templates, ERI v1 data sources, and document analysis policies. Paste an exported configuration snippet, review the filled form, and save it as your own item.
- Added controls for organizations to allow or lock configuration snippet imports by item type. A locked Import button stays visible, and plugin archive imports keep their own controls.
-- Added a way to relink missing file attachments when you create a chat template from an exported configuration.
+- Added a way to relink or remove missing file attachments when you create a chat template from an exported configuration.
- Added a way to copy an entire chat, either with the button in the chat toolbar or next to the chat in the chat list. The copy opens right away so you can continue in it, while the original conversation stays exactly as it was. Many thanks to Peer Hogeterp (`peerschuett`) and Jens Erler (`j-erler`) for this feature.
- Added a way to roll a chat back to an earlier AI response. The response you pick stays, and every message after it is removed permanently, together with the attachments of those messages.
- Added a way to save a single code block of an answer. When an answer holds a web page, a LaTeX document, or a Markdown text, the export menu now offers that block as a file of its own.
diff --git a/documentation/Enterprise IT.md b/documentation/Enterprise IT.md
index c6ab12ae..de3ae27f 100644
--- a/documentation/Enterprise IT.md
+++ b/documentation/Enterprise IT.md
@@ -623,7 +623,7 @@ Handing a whole plugin to a colleague is a different thing: that is the **Share*
Each parent screen has a button naming the item to import beside **Add** for profiles, LLM providers, embedding providers, transcription providers, chat templates, and document analysis policies. The data source screen has **Import ERI v1 Data Source** beside **Add Data Source**. Copy an exported snippet from the item's **Export configuration** control, paste one snippet of the matching type into the import dialog, and choose its item-specific import button. AI Studio reads the table as data; it does not run Lua code. The existing creation form then opens with its fields filled in. Review them and choose **Add** to create the item. Canceling either dialog leaves nothing saved. Tool Settings snippets cannot be imported this way.
-The imported item receives a new ID and the next local number. It belongs to you, even if the snippet came from an organization configuration. Existing provider, profile, tool, and data source references are kept; unavailable references are flagged so you can review them. Chat template attachments must point to existing local files. Relink any missing or relative paths in the creation dialog before saving.
+The imported item receives a new ID and the next local number. It belongs to you, even if the snippet came from an organization configuration. Existing provider, profile, tool, and data source references are kept; unavailable references are flagged so you can review them. Chat template attachments must point to existing local files. Relink or remove any missing or relative paths in the creation dialog before saving.
If an exported provider or ERI source contains an `ENC:v1` credential, AI Studio can fill it only on a device with the matching enterprise encryption secret. Otherwise, enter your own key, token, or password. A credential is stored in the local operating system secret store only when you save the item. Exporting still requires **Show administration settings**.
From 73f48e7bcd584a12e72243654cf15b6297080316 Mon Sep 17 00:00:00 2001
From: Peer Hogeterp <20603780+peerschuett@users.noreply.github.com>
Date: Fri, 25 Sep 2026 12:41:55 +0200
Subject: [PATCH 3/5] Finding small errors
---
.../Assistants/I18N/allTexts.lua | 150 +++++++++---
.../Settings/SettingsPanelEmbeddings.razor | 4 +-
.../Settings/SettingsPanelProviders.razor | 4 +-
.../Settings/SettingsPanelTranscription.razor | 4 +-
.../Dialogs/ChatTemplateDialog.razor | 46 ++--
.../Dialogs/ChatTemplateDialog.razor.cs | 25 +-
.../plugin.lua | 228 ++++++++++++++++++
.../plugin.lua | 228 ++++++++++++++++++
8 files changed, 627 insertions(+), 62 deletions(-)
diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index c69fc6c5..9287ec8a 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -1102,9 +1102,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
--- Import
-UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1463683828"] = "Import"
-
-- Only the tools selected here can be used by the AI for an analysis with this policy. Every tool still has to meet the confidence requirements of the selected provider, so a tool may remain unavailable even when this policy permits it.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1692505801"] = "Only the tools selected here can be used by the AI for an analysis with this policy. Every tool still has to meet the confidence requirements of the selected provider, so a tool may remain unavailable even when this policy permits it."
@@ -4855,9 +4852,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELDATASOURCES::T4761
-- Embedding Result
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1387042335"] = "Embedding Result"
--- Import
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1463683828"] = "Import"
-
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
@@ -4963,9 +4957,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T401
-- This provider is trusted by your organization for data source security checks.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1298650849"] = "This provider is trusted by your organization for data source security checks."
--- Import
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1463683828"] = "Import"
-
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1469573738"] = "Delete"
@@ -5071,9 +5062,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T10
-- Edit Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1317362918"] = "Edit Transcription Provider"
--- Import
-UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1463683828"] = "Import"
-
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1469573738"] = "Delete"
@@ -5749,9 +5737,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1396308587"] = "The cha
-- The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1442266827"] = "The same goes for your data. A chat template may bring its own data source options, which includes leaving the choice of sources to the AI. Without them, those chats start with the data source options from your chat options."
--- Enter an absolute path to an existing local file before saving.
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1482137067"] = "Enter an absolute path to an existing local file before saving."
-
-- Please enter a name for the chat template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T1548747185"] = "Please enter a name for the chat template."
@@ -5800,6 +5785,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579080722"] = "No, pro
-- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with."
+-- Enter an absolute path to an existing local file, or remove this attachment.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2603688456"] = "Enter an absolute path to an existing local file, or remove this attachment."
+
-- Predefined User Input
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Predefined User Input"
@@ -5833,15 +5821,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you
-- Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3227981830"] = "Using some chat templates in tandem with profiles might cause issues. Therefore, you might prohibit the usage of profiles here."
--- Relink the missing attachment '{0}' to an existing local file before saving.
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3262119677"] = "Relink the missing attachment '{0}' to an existing local file before saving."
-
-- No, chats keep the data source options from your chat options
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "No, chats keep the data source options from your chat options"
-- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says."
+-- Some attachments in this chat template are missing. Relink or remove them before adding the template.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3342669429"] = "Some attachments in this chat template are missing. Relink or remove them before adding the template."
+
-- Add a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Add a message"
@@ -5851,6 +5839,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Close"
-- Unsupported content type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Unsupported content type"
+-- Relink the missing attachment '{0}' to an existing local file or remove it before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3582611137"] = "Relink the missing attachment '{0}' to an existing local file or remove it before saving."
+
-- What system prompt do you want to use?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3652587353"] = "What system prompt do you want to use?"
@@ -5890,6 +5881,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Create
-- Enter a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Enter a message"
+-- The chat template fields are malformed.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T539301384"] = "The chat template fields are malformed."
+
-- Data Sources
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T558345131"] = "Data Sources"
@@ -5899,6 +5893,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T628396066"] = "System P
-- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself."
+-- Remove this attachment
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Remove this attachment"
+
-- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use."
@@ -5989,9 +5986,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T767586087"
-- {0} embedding provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T818101181"] = "{0} embedding provider"
--- Import
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T1463683828"] = "Import"
-
-- Configuration snippet
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T3867536704"] = "Configuration snippet"
@@ -7972,9 +7966,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T11721
-- Copy attachments into plugin
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1345613295"] = "Copy attachments into plugin"
--- Import
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1463683828"] = "Import"
-
-- Delete
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T1469573738"] = "Delete"
@@ -8326,9 +8317,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T121537402
-- No profiles configured yet.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1433534732"] = "No profiles configured yet."
--- Import
-UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1463683828"] = "Import"
-
-- Delete
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1469573738"] = "Delete"
@@ -11716,6 +11704,114 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
-- The plugin code changed after the last security audit. The stored result no longer matches the current code, so this assistant plugin must be audited again before it may be enabled or used.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T995107927"] = "The plugin code changed after the last security audit. The stored result no longer matches the current code, so this assistant plugin must be audited again before it may be enabled or used."
+-- The embedded credential could not be decrypted on this device. Enter your own credential before saving.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1017166692"] = "The embedded credential could not be decrypted on this device. Enter your own credential before saving."
+
+-- tool {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1561005241"] = "tool {0}"
+
+-- provider {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1867642302"] = "provider {0}"
+
+-- The '{0}' field must be a string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T224834942"] = "The '{0}' field must be a string."
+
+-- The exported item has an invalid ID.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T2640740755"] = "The exported item has an invalid ID."
+
+-- The '{0}' field has an unknown value.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T2930445640"] = "The '{0}' field has an unknown value."
+
+-- data source {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3277968944"] = "data source {0}"
+
+-- The '{0}' field must be a whole number.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3419687389"] = "The '{0}' field must be a whole number."
+
+-- profile {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3445783292"] = "profile {0}"
+
+-- The '{0}' field must be true or false.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3600915840"] = "The '{0}' field must be true or false."
+
+-- Unavailable references: {0}. Review the selections before saving.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3637224236"] = "Unavailable references: {0}. Review the selections before saving."
+
+-- The '{0}' field must contain an ENC:v1 credential.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T4261018324"] = "The '{0}' field must contain an ENC:v1 credential."
+
+-- The '{0}' field must be a table.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T432554775"] = "The '{0}' field must be a table."
+
+-- The '{0}' field contains an invalid entry.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T948874113"] = "The '{0}' field contains an invalid entry."
+
+-- The 'Port' field must be between 1 and 65535.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T1817689659"] = "The 'Port' field must be between 1 and 65535."
+
+-- The 'MaxMatches' field is outside the allowed range.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T2009159518"] = "The 'MaxMatches' field is outside the allowed range."
+
+-- This configuration section cannot be imported here.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T2657742500"] = "This configuration section cannot be imported here."
+
+-- An example conversation message is empty.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T3131294453"] = "An example conversation message is empty."
+
+-- An example conversation entry is not a table.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T3912378391"] = "An example conversation entry is not a table."
+
+-- Kerberos data sources cannot be imported from configuration snippets.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T4019138476"] = "Kerberos data sources cannot be imported from configuration snippets."
+
+-- The chat template fields are malformed.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T539301384"] = "The chat template fields are malformed."
+
+-- This data source is not an ERI v1 data source.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T724971220"] = "This data source is not an ERI v1 data source."
+
+-- Only literal values are allowed at character {0}; executable Lua is not accepted.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1200702179"] = "Only literal values are allowed at character {0}; executable Lua is not accepted."
+
+-- Expected a quoted string at character {0}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "Expected a quoted string at character {0}."
+
+-- Unterminated long string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1583706986"] = "Unterminated long string."
+
+-- The configuration section names do not match.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1700453124"] = "The configuration section names do not match."
+
+-- The snippet contains too many nested tables.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1872841947"] = "The snippet contains too many nested tables."
+
+-- Expected '{0}' at character {1}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2092509274"] = "Expected '{0}' at character {1}."
+
+-- Unsupported string escape sequence: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2686740263"] = "Unsupported string escape sequence: {0}"
+
+-- Paste one exported configuration snippet (up to 1 MB).
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2743278967"] = "Paste one exported configuration snippet (up to 1 MB)."
+
+-- Expected a comma or closing brace at character {0}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3466555352"] = "Expected a comma or closing brace at character {0}."
+
+-- This is a {0} snippet. Paste a {1} snippet here.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3577683445"] = "This is a {0} snippet. Paste a {1} snippet here."
+
+-- Unterminated quoted string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3882373828"] = "Unterminated quoted string."
+
+-- A quoted string contains an unescaped newline.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T4168367785"] = "A quoted string contains an unescaped newline."
+
+-- The field '{0}' occurs more than once.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T560836334"] = "The field '{0}' occurs more than once."
+
+-- The snippet must contain exactly one table assignment and no executable code.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T873986754"] = "The snippet must contain exactly one table assignment and no executable code."
+
-- The table AUTHORS does not exist or is using an invalid syntax.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T1068328139"] = "The table AUTHORS does not exist or is using an invalid syntax."
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
index 47ca405d..9a49ee4a 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor
@@ -94,8 +94,8 @@
}
-
-
+
+
}
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor
index 3e016d5d..80d28d1d 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor
@@ -79,7 +79,7 @@
}
-
-
+
+
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor
index 1d864162..dd93cfde 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelTranscription.razor
@@ -84,8 +84,8 @@
}
-
-
+
+
}
diff --git a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
index 056e19e1..27564e09 100644
--- a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor
@@ -10,23 +10,6 @@
{
@this.importReferenceIssue
}
- @if (!string.IsNullOrWhiteSpace(this.relinkIssue))
- {
- @this.relinkIssue
- }
- @for (var index = 0; index < this.attachmentsToRelink.Count; index++)
- {
- var currentIndex = index;
-
-
-
-
-
-
- }
}
@* A drop anywhere in this dialog belongs to the dialog, not to the page behind it: *@
@* The name for the drop state is given although nobody uses it: the messages of this dialog
@@ -118,6 +101,33 @@
@T("You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template.")
+ @if (!this.IsEditing && !this.IsReadOnly)
+ {
+ @if (this.HasUnresolvedAttachments)
+ {
+
+ @T("Some attachments in this chat template are missing. Relink or remove them before adding the template.")
+
+ }
+ @if (!string.IsNullOrWhiteSpace(this.relinkIssue))
+ {
+ @this.relinkIssue
+ }
+ @for (var index = 0; index < this.attachmentsToRelink.Count; index++)
+ {
+ var currentIndex = index;
+ var attachment = this.attachmentsToRelink[index];
+
+
+
+
+
+
+ }
+ }
+
@if (this.IsEditing)
{
@T("Update")
diff --git a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
index 5006381a..63db3244 100644
--- a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs
@@ -103,6 +103,7 @@ public partial class ChatTemplateDialog : MSGComponentBase
private HashSet fileAttachments = [];
private List<(string OriginalPath, string ReplacementPath)> attachmentsToRelink = [];
private string relinkIssue = string.Empty;
+ private bool HasUnresolvedAttachments => this.attachmentsToRelink.Any(attachment => !ConfigurationImportFields.IsExistingLocalFile(attachment.ReplacementPath));
private string importReferenceIssue = string.Empty;
private bool preselectTools;
private HashSet selectedToolIds = new(StringComparer.Ordinal);
@@ -156,6 +157,9 @@ public partial class ChatTemplateDialog : MSGComponentBase
this.DataName = this.ExistingChatThread.Name;
}
+ if (this.ImportedConfiguration is not null && this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
+ await this.ImportConfiguration(this.ImportedConfiguration);
+
await base.OnInitializedAsync();
}
@@ -166,14 +170,9 @@ public partial class ChatTemplateDialog : MSGComponentBase
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
- if (firstRender && this.ImportedConfiguration is not null)
- {
- if (!this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
- this.MudDialog.Cancel();
- else
- await this.ImportConfiguration(this.ImportedConfiguration);
- this.StateHasChanged();
- }
+ if (firstRender && this.ImportedConfiguration is not null &&
+ !this.SettingsManager.ConfigurationData.App.CanImportConfigurationSnippet("CHAT_TEMPLATES"))
+ this.MudDialog.Cancel();
await base.OnAfterRenderAsync(firstRender);
}
@@ -226,7 +225,6 @@ public partial class ChatTemplateDialog : MSGComponentBase
this.preselectDataSources = template.DataSourceOptions is not null;
this.templateDataSourceOptions = template.DataSourceOptions?.CreateCopy() ?? new DataSourceOptions { DisableDataSources = false };
this.importReferenceIssue = await this.BuildImportReferenceIssue();
- this.form.ResetValidation();
}
private async Task BuildImportReferenceIssue()
@@ -241,12 +239,17 @@ public partial class ChatTemplateDialog : MSGComponentBase
return ConfigurationImportFields.UnavailableReferencesIssue(missing);
}
- private void UpdateRelinkPath(int index, string path) => this.attachmentsToRelink[index] = (this.attachmentsToRelink[index].OriginalPath, path);
+ private void UpdateRelinkPath(int index, string? path)
+ {
+ this.attachmentsToRelink[index] = (this.attachmentsToRelink[index].OriginalPath, path ?? string.Empty);
+ if (!this.HasUnresolvedAttachments)
+ this.relinkIssue = string.Empty;
+ }
private void RemoveAttachmentToRelink(int index)
{
this.attachmentsToRelink.RemoveAt(index);
- if (this.attachmentsToRelink.Count == 0)
+ if (!this.HasUnresolvedAttachments)
this.relinkIssue = string.Empty;
}
diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
index 6d292049..60577a17 100644
--- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
@@ -1218,6 +1218,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- A policy with this name already exists. Please choose a different name.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3584374593"] = "Ein Regelwerk mit diesem Namen existiert bereits. Bitte wählen Sie einen anderen Namen."
+-- Import document analysis policy
+UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3800115485"] = "Regelwerk importieren"
+
-- Load analysis rules from document
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3813558135"] = "Regeln für die Analyse aus einem Dokument laden"
@@ -3921,6 +3924,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T750361472"] = "Die
-- External Data (ERI-Server v1)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T774473996"] = "Externe Daten (ERI-Server v1)"
+-- Import ERI v1 Data Source
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T777621786"] = "ERI-v1-Datenquelle importieren"
+
-- Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T782820095"] = "Diese ERI-Datenquelle kann nicht exportiert werden, da kein Geheimnis für die Authentifizierung konfiguriert ist. Das Problem war: {0}"
@@ -4881,6 +4887,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T21748
-- Model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2189814010"] = "Modell"
+-- Import Embedding Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T229678442"] = "Einbettung importieren"
+
-- Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2419962612"] = "Einbettungen sind eine Methode, um Wörter, Sätze, ganze Dokumente oder sogar Bilder und Videos als digitale Fingerabdrücke darzustellen. So wie jeder Mensch einen einzigartigen Fingerabdruck hat, erzeugen Einbettungs-Modelle einzigartige digitale Muster, die die Bedeutung und Eigenschaften der von ihnen analysierten Inhalte erfassen. Wenn zwei Dinge sich in ihrer Bedeutung oder ihrem Inhalt ähneln, sehen auch ihre digitalen Fingerabdrücke sehr ähnlich aus. Zum Beispiel wären die Fingerabdrücke für „glücklich“ und „freudig“ einander ähnlicher als die für „glücklich“ und „traurig“."
@@ -4956,6 +4965,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T146957
-- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell"
+-- Import LLM Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T178720520"] = "Anbieter importieren"
+
-- Add Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Anbieter hinzufügen"
@@ -5094,6 +5106,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T58
-- This transcription provider is trusted by your organization for data source security checks.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T601264181"] = "Ihre Organisation vertraut diesem Anbieter für Transkriptionen bei der Sicherheitsprüfung von Datenquellen."
+-- Import Transcription Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T682006405"] = "Anbieter für Transkriptionen importieren"
+
-- This transcription provider is managed by your organization. You can set your own API key.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T690752279"] = "Dieser Anbieter für Transkriptionen wird von Ihrer Organisation verwaltet. Sie können Ihren eigenen API-Schlüssel festlegen."
@@ -5742,6 +5757,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "Der Name
-- Profile Usage
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profilnutzung"
+-- Relink attachment: {0}
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Anhang erneut verknüpfen: {0}"
+
-- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Fügen Sie Nachrichten einer Beispiel-Konversation hinzu (Nutzereingabe, gefolgt von einer Antwort des Assistenten), um das gewünschte Interaktionsmuster zu demonstrieren. Diese Beispiele helfen der KI, Ihre Erwartungen zu verstehen, indem Sie das korrekte Format, den Stil und den Inhalt von Antworten zeigen, bevor tatsächliche Nutzereingaben erfolgen."
@@ -5769,6 +5787,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579080722"] = "Nein, d
-- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "Sie können eine Nachricht vordefinieren, die als Benutzereingabe verwendet wird, wenn Sie diese Chat-Vorlage verwenden. Diese Nachricht könnte beispielsweise eine Vorlage für eine strukturierte Nachricht sein, für die diese Chat-Vorlage entwickelt wurde."
+-- Enter an absolute path to an existing local file, or remove this attachment.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2603688456"] = "Geben Sie einen vollständigen Pfad zu einer vorhandenen lokalen Datei ein, oder entfernen Sie diesen Anhang."
+
-- Predefined User Input
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Vordefinierte Benutzereingabe"
@@ -5808,6 +5829,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "Nein, C
-- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "Eine Chat-Vorlage kann festlegen, mit welchen Werkzeugen ein Chat startet. Ohne eine solche Festlegung starten diese Chats mit den Werkzeugen, die Sie in den Chat-Optionen als Standard ausgewählt haben. Legen Sie es fest und wählen dann nichts aus, ist das eine andere Aussage: Solche Chats starten ohne jedes Werkzeug, ganz gleich, was Ihr Standard vorsieht."
+-- Some attachments in this chat template are missing. Relink or remove them before adding the template.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3342669429"] = "Einige Anhänge in dieser Chatvorlage fehlen. Verknüpfen Sie sie erneut oder entfernen Sie sie, bevor Sie die Vorlage hinzufügen."
+
-- Add a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Nachricht hinzufügen"
@@ -5817,6 +5841,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Schlie
-- Unsupported content type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Nicht unterstützter Inhaltstyp"
+-- Relink the missing attachment '{0}' to an existing local file or remove it before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3582611137"] = "Verknüpfen Sie den fehlenden Anhang '{0}' erneut mit einer vorhandenen Datei auf Ihrem Computer oder entfernen Sie ihn vor dem Speichern."
+
-- What system prompt do you want to use?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3652587353"] = "Welchen System-Prompt möchten Sie verwenden?"
@@ -5856,6 +5883,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Erstell
-- Enter a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Nachricht eingeben"
+-- The chat template fields are malformed.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T539301384"] = "Die Felder der Chat-Vorlage sind fehlerhaft."
+
-- Data Sources
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T558345131"] = "Datenquellen"
@@ -5865,6 +5895,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T628396066"] = "System-P
-- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "Ein mit dieser Vorlage gestarteter Chat beginnt mit diesen Datenquellen und Optionen. Alles davon lässt sich im Chat selbst weiterhin ändern."
+-- Remove this attachment
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Diesen Anhang entfernen"
+
-- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "Die Auswahl lässt sich im Chat weiterhin ändern, und jedes Werkzeug muss die Vertrauensanforderungen des verwendeten Anbieters erfüllen."
@@ -5955,12 +5988,27 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T767586087"
-- {0} embedding provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T818101181"] = "{0} Anbieter für Einbettungen"
+-- Configuration snippet
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T3867536704"] = "Konfigurationsauszug"
+
+-- Import is locked by your organization.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T4101825749"] = "Der Import ist von Ihrer Organisation gesperrt."
+
+-- Copy one exported configuration snippet from the item's Export configuration control and paste it below. You can review and change the filled form before saving a new local item.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T4214407984"] = "Kopiere einen exportierten Konfigurationsausschnitt aus dem Steuerelement „Konfiguration exportieren“ des Elements und füge ihn unten ein. Du kannst das ausgefüllte Formular überprüfen und ändern, bevor du ein neues lokales Element speicherst."
+
+-- Cancel
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T900713019"] = "Abbrechen"
+
-- No
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T1642511898"] = "Nein"
-- Yes
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T3013883440"] = "Ja"
+-- The imported retrieval process '{0}' is unavailable. Select another process before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERI_V1DIALOG::T1759490982"] = "Der importierte Abrufprozess '{0}' ist nicht verfügbar. Bitte wählen Sie vor dem Speichern einen anderen Prozess aus."
+
-- How many matches do you want at most per query?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERI_V1DIALOG::T1827669611"] = "Wie viele Treffer möchten Sie maximal pro Abfrage erhalten?"
@@ -6534,6 +6582,72 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T854110894
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T900713019"] = "Abbrechen"
+-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1291179736"] = "Below is a clear set of **analysis rules** you can use to instruct an AI on how to analyze documents. --- ## Document Analysis Rules ### 1. Purpose of Analysis The AI must analyze each document by extracting the most important information, identifying its meaning, and presenting the results in a clear, structured, and useful way. --- ### 2. Focus on the Document Content The AI must base its analysis only on the information contained in the document. It should: - Use facts, statements, and data from the document. - Not invent missing information. - Clearly mark uncertainty if something is unclear. --- ### 3. Identify the Document Type The AI should first determine what kind of document it is, for example: - Report - Contract - Email - Article - Presentation - Form - Policy - Invoice - Manual - Legal document If the type cannot be determined, the AI should state that it is uncertain. --- ### 4. Extract Key Information The AI should identify the most important elements of the document, such as: - Title or main topic - Author or sender, if available - Date or timeline - Purpose of the document - Main points or arguments - Key entities, such as people, organizations, places, or products - Important numbers, dates, deadlines, or references - Any required actions or recommendations --- ### 5. Summarize the Document The AI should provide a short and clear summary that captures the main meaning of the document. The summary should: - Be concise - Stay factual - Include only the most important information - Avoid unnecessary details --- ### 6. Identify Structure and Organization The AI should analyze how the document is organized, including: - Sections or headings - Logical flow of information - Presence of tables, lists, or attachments - Whether the structure helps the reader understand the content --- ### 7. Analyze Tone and Style The AI should evaluate the writing style and tone of the document. Possible tone categories include: - Formal - Informal - Technical - Legal - Persuasive - Neutral - Urgent - Instructional The AI should also note if the tone is appropriate for the document type and audience. --- ### 8. Assess Clarity and Quality The AI should evaluate how clear and understandable the document is. It should consider: - Whether the main message is easy to understand - Whether important information is missing or unclear - Whether the language is precise or vague - Whether the document contains contradictions or errors - Whether the formatting supports easy reading --- ### 9. Detect Risks, Issues, or Gaps The AI should point out anything that may be important, risky, incomplete, or inconsistent. For example: - Missing dates or signatures - Unclear obligations - Conflicting statements - Undefined terms - Potentially incorrect data - Missing explanations - Ambiguous instructions --- ### 10. Identify Actions, Decisions, or Requirements If the document asks the reader to do something, the AI must identify those actions clearly. It should extract: - Required tasks - Deadlines - Responsibilities - Approval requests - Conditions - Next steps --- ### 11. Compare Information If Multiple Documents Are Given If more than one document is provided, the AI should compare them and identify: - Similarities - Differences - Contradictions - Repeated information - New or updated content --- ### 12. Present Results in a Structured Format The AI should return its analysis in a clear structure, for example: **1. Document type** **2. Main purpose** **3. Summary** **4. Key information** **5. Important points** **6. Missing or unclear items** **7. Risks or issues** **8. Suggested actions** **9. Conclusion** If the user requests a different format, the AI should follow that format. --- ### 13. Use Simple and Understandable Language The AI should explain the document in a way that is easy to understand, even for non-experts. It should: - Avoid unnecessary technical language - Explain complex terms if needed - Make the analysis useful for regular users --- ### 14. Be Objective and Neutral The AI should analyze the document without adding personal opinions, unless the user explicitly asks for evaluation or recommendation. It should: - Stay factual - Separate facts from interpretation - Avoid exaggeration or assumptions --- ### 15. Handle Unclear or Low-Quality Input Carefully If the document is incomplete, unreadable, or unclear, the AI should: - Explain what is missing or unclear - Describe what can still be understood - Suggest what additional information is needed It should not pretend to understand something that is not supported by the document. --- ## Optional Standard Output Format The AI may respond using this structure: ```text 1. Document Overview 2. Main Purpose 3. Key Points 4. Important Data 5. Unclear or Missing Information 6. Risks or Issues 7. Recommended Actions 8. Final Summary ``` --- If you want, I can also turn this into: 1. a **system prompt** for an AI assistant, 2. a **checklist for document review**, or 3. a **shorter version for practical use**."
+
+-- Hide the policy definition when distributed via configuration plugin?
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1875622568"] = "Richtliniendefinition ausblenden, wenn sie über das Konfigurations-Plugin verteilt wird?"
+
+-- No profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2028602035"] = "Kein Profil"
+
+-- Load output rules from document
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2168201568"] = "Ausgaberegeln aus Dokument laden"
+
+-- Preselect a profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2322771068"] = "Profil vorauswählen"
+
+-- The name of your policy must be between 6 and 60 characters long.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2435013256"] = "Der Name deiner Richtlinie muss zwischen 6 und 60 Zeichen lang sein."
+
+-- Preselect a provider
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2440815970"] = "Anbieter vorab auswählen"
+
+-- Add
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2646845972"] = "Hinzufügen"
+
+-- Policy name
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2879019438"] = "Richtlinienname"
+
+-- Analysis rules
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3108719748"] = "Analyseregeln"
+
+-- Tools this policy permits
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T31356122"] = "Von dieser Richtlinie erlaubte Tools"
+
+-- The description of your policy must be between 32 and 512 characters long.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3285636934"] = "Die Beschreibung Ihrer Richtlinie muss zwischen 32 und 512 Zeichen lang sein."
+
+-- A policy with this name already exists. Please choose a different name.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3584374593"] = "Eine Richtlinie mit diesem Namen ist bereits vorhanden. Bitte wählen Sie einen anderen Namen."
+
+-- Use app default profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3587225583"] = "App-Standardprofil verwenden"
+
+-- No provider
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3740605451"] = "Kein Anbieter"
+
+-- Load analysis rules from document
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3813558135"] = "Analyse-Regeln aus Dokument laden"
+
+-- Output rules
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3918193587"] = "- Gib nur die Übersetzung zurück, ohne zusätzliche Kommentare. - Keine Erklärungen, Beispiele oder Hinweise. - Behalte Platzhalter, Variablen, HTML, Markdown und Sonderzeichen genau bei. - Nutze einfache, verständliche deutsche Sprache. - Übersetze UI-Elemente kurz, eindeutig und passend zum Kontext. - Korrigiere Rechtschreibung und Grammatik, ohne den Sinn zu ändern. - Erfinde keine neuen Informationen und ergänze keine Inhalte."
+
+-- Please provide a name for your policy. This name will be used to identify the policy in AI Studio.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T4040507702"] = "Bitte geben Sie einen Namen für Ihre Richtlinie an. Dieser Name wird zur Identifizierung der Richtlinie in AI Studio verwendet."
+
+-- Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T652187065"] = "Output rules for the analysis/localization task: 1. Return only the requested output. - Do not add introductions, explanations, comments, notes, or conclusions. - If the task is translation, output only the translated text. 2. Use German as the target language. - Use German from Germany. - Keep the language simple and easy to understand for non-technical users. - Prefer natural, common German wording over literal translation. - Avoid unnecessary technical jargon. 3. Translate faithfully without adding new information. - Preserve the original meaning, intent, and tone. - Do not add features, explanations, warnings, benefits, or context that are not present in the source text. 4. Correct obvious spelling and grammar issues in the source text. - Fix only clear mistakes. - Do not change the meaning while correcting. 5. Preserve placeholders, variables, and code elements. - Keep all placeholders unchanged, for example: `{name}`, `{{variable}}`, `%s`, ``, `#tag`, `[[link]]`. - Keep formatting syntax unchanged when present, such as Markdown, HTML, JSON keys, or line breaks. - Do not translate variable names, function names, API names, file paths, or identifiers. 6. Preserve brand names and product names. - Keep proper names unchanged unless there is a known localized form. - Examples: “MindWork AI Studio”, “AI Studio”, “macOS”, “Windows”, “Linux”. 7. Match the UI context and intended function. - Buttons and menu labels should be short, clear, and actionable. - Headings should be concise. - Descriptions should sound helpful and easy to understand. - If the original text is a label, do not turn it into a sentence unless the original implies a full sentence. 8. Keep tone and style consistent. - Use a friendly, professional, and neutral tone suitable for general users. - If an address form is needed, prefer formal “Sie” unless the source clearly uses casual language or the app context requires “Du”. 9. Preserve length where practical. - Try to keep the translated text within a similar character length as the source, especially for UI elements like buttons, tabs, and labels. - Shorten wording if needed without losing meaning. 10. Follow German writing conventions. - Use correct German capitalization. - Use German punctuation when appropriate. - Remove unnecessary spaces before punctuation only if the source style allows it. - Keep quotation marks, dashes, and special characters in a way that fits German conventions unless they are technical elements. 11. Do not alter the structure unless required by grammar. - If the source contains bullet points, numbered items, line breaks, or multiple sentences, preserve that structure in the output. - Do not merge or split content unless needed for natural German. 12. If the source is unclear, ambiguous, or incomplete, choose the safest direct interpretation. - Do not invent missing information. - If a text cannot be translated meaningfully, return a minimal, faithful translation based only on the available source."
+
+-- Policy description
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T748735777"] = "Richtlinienbeschreibung"
+
+-- Would you like to protect this policy so that you cannot accidentally edit or delete it?
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T80597472"] = "Möchtest du diese Richtlinie schützen, damit du sie nicht versehentlich bearbeiten oder löschen kannst?"
+
+-- Cancel
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T900713019"] = "Abbrechen"
+
-- Please wait while we load the content of your file. Depending on the file type and size, this may take a moment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTCHECKDIALOG::T1205126512"] = "Bitte warten Sie, während wir den Inhalt Ihrer Datei laden. Je nach Dateityp und -größe kann dies einen Moment dauern."
@@ -7875,6 +7989,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T23198
-- This chat template preselects data sources which exist on this machine only: {0}. They cannot be rolled out, so a chat started with this template elsewhere begins without them. Do you want to export the template anyway?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2563631533"] = "Diese Chat-Vorlage wählt Datenquellen vorab aus, die es nur auf diesem Rechner gibt: {0}. Solche Quellen lassen sich nicht bereitstellen; ein Chat, der auf einem anderen Rechner mit dieser Vorlage startet, beginnt daher ohne sie. Möchten Sie die Vorlage trotzdem exportieren?"
+-- Import Chat Template
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T263213584"] = "Chat-Vorlage importieren"
+
-- Chat Template Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T275026390"] = "Name der Chat-Vorlage"
@@ -8196,6 +8313,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T711745239"
-- Edit Profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1143111468"] = "Profil bearbeiten"
+-- Import Profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1215374025"] = "Profil importieren"
+
-- No profiles configured yet.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1433534732"] = "Noch keine Profile eingerichtet."
@@ -11586,6 +11706,114 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
-- The plugin code changed after the last security audit. The stored result no longer matches the current code, so this assistant plugin must be audited again before it may be enabled or used.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T995107927"] = "Der Plugin-Code wurde nach der letzten Sicherheitsprüfung geändert. Das gespeicherte Ergebnis stimmt nicht mehr mit dem aktuellen Code überein, daher muss dieses Assistenten-Plugin erneut geprüft werden, bevor es aktiviert oder verwendet werden darf."
+-- The embedded credential could not be decrypted on this device. Enter your own credential before saving.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1017166692"] = "Die eingebetteten Zugangsdaten konnten auf diesem Gerät nicht entschlüsselt werden. Geben Sie Ihre eigenen Zugangsdaten vor dem Speichern ein."
+
+-- tool {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1561005241"] = "Werkzeug {0}"
+
+-- provider {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1867642302"] = "Anbieter {0}"
+
+-- The '{0}' field must be a string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T224834942"] = "Das Feld „{0}“ muss eine Zeichenfolge sein."
+
+-- The exported item has an invalid ID.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T2640740755"] = "Das exportierte Element hat eine ungültige ID."
+
+-- The '{0}' field has an unknown value.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T2930445640"] = "Das Feld '{0}' hat einen unbekannten Wert."
+
+-- data source {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3277968944"] = "Datenquelle {0}"
+
+-- The '{0}' field must be a whole number.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3419687389"] = "Das Feld „{0}“ muss eine ganze Zahl sein."
+
+-- profile {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3445783292"] = "Profil {0}"
+
+-- The '{0}' field must be true or false.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3600915840"] = "Das Feld „{0}“ muss wahr oder falsch sein."
+
+-- Unavailable references: {0}. Review the selections before saving.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3637224236"] = "Nicht verfügbare Verweise: {0}. Überprüfen Sie die Auswahl, bevor Sie speichern."
+
+-- The '{0}' field must contain an ENC:v1 credential.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T4261018324"] = "Das Feld '{0}' muss einen ENC:v1-Anmeldedatensatz enthalten."
+
+-- The '{0}' field must be a table.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T432554775"] = "Das Feld '{0}' muss eine Tabelle sein."
+
+-- The '{0}' field contains an invalid entry.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T948874113"] = "Das Feld „{0}“ enthält einen ungültigen Eintrag."
+
+-- The 'Port' field must be between 1 and 65535.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T1817689659"] = "Das Feld „Port“ muss zwischen 1 und 65535 liegen."
+
+-- The 'MaxMatches' field is outside the allowed range.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T2009159518"] = "Das Feld „MaxMatches“ liegt außerhalb des zulässigen Bereichs."
+
+-- This configuration section cannot be imported here.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T2657742500"] = "Dieser Konfigurationsbereich kann hier nicht importiert werden."
+
+-- An example conversation message is empty.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T3131294453"] = "Eine Nachricht einer Beispielkonversation ist leer."
+
+-- An example conversation entry is not a table.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T3912378391"] = "Ein Beispiel-Gesprächseintrag ist keine Tabelle."
+
+-- Kerberos data sources cannot be imported from configuration snippets.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T4019138476"] = "Kerberos-Datenquellen können nicht aus Konfigurations-Snippets importiert werden."
+
+-- The chat template fields are malformed.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T539301384"] = "Die Felder der Chat-Vorlage sind fehlerhaft."
+
+-- This data source is not an ERI v1 data source.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T724971220"] = "Diese Datenquelle ist keine ERI-v1-Datenquelle."
+
+-- Only literal values are allowed at character {0}; executable Lua is not accepted.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1200702179"] = "An Zeichen {0} sind nur Literalwerte erlaubt; ausführbarer Lua-Code wird nicht akzeptiert."
+
+-- Expected a quoted string at character {0}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "Es wurde ein Text in Anführungszeichen bei Zeichen {0} erwartet."
+
+-- Unterminated long string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1583706986"] = "Nicht abgeschlossene lange Zeichenkette."
+
+-- The configuration section names do not match.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1700453124"] = "Die Namen der Konfigurationsbereiche stimmen nicht überein."
+
+-- The snippet contains too many nested tables.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1872841947"] = "Der Ausschnitt enthält zu viele verschachtelte Tabellen."
+
+-- Expected '{0}' at character {1}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2092509274"] = "„{0}“ an Zeichenposition {1} erwartet."
+
+-- Unsupported string escape sequence: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2686740263"] = "Nicht unterstützte Escape-Sequenz in der Zeichenfolge: {0}"
+
+-- Paste one exported configuration snippet (up to 1 MB).
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2743278967"] = ""
+
+-- Expected a comma or closing brace at character {0}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3466555352"] = "An Position {0} wurde ein Komma oder eine schließende geschweifte Klammer erwartet."
+
+-- This is a {0} snippet. Paste a {1} snippet here.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3577683445"] = "Dies ist ein {0}-Snippet. Fügen Sie hier ein {1}-Snippet ein."
+
+-- Unterminated quoted string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3882373828"] = "Zeichenkette in Anführungszeichen nicht abgeschlossen."
+
+-- A quoted string contains an unescaped newline.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T4168367785"] = "Ein String in Anführungszeichen enthält einen nicht maskierten Zeilenumbruch."
+
+-- The field '{0}' occurs more than once.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T560836334"] = "Das Feld „{0}“ kommt mehr als einmal vor."
+
+-- The snippet must contain exactly one table assignment and no executable code.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T873986754"] = "table: {}"
+
-- The table AUTHORS does not exist or is using an invalid syntax.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T1068328139"] = "Die Tabelle AUTHORS existiert nicht oder verwendet eine ungültige Syntax."
diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
index 93d915e8..46455c27 100644
--- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
@@ -1218,6 +1218,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
-- A policy with this name already exists. Please choose a different name.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3584374593"] = "A policy with this name already exists. Please choose a different name."
+-- Import document analysis policy
+UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3800115485"] = "Import document analysis policy"
+
-- Load analysis rules from document
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3813558135"] = "Load analysis rules from document"
@@ -3921,6 +3924,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T750361472"] = "Can
-- External Data (ERI-Server v1)
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T774473996"] = "External Data (ERI-Server v1)"
+-- Import ERI v1 Data Source
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T777621786"] = "Import ERI v1 Data Source"
+
-- Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCEMANAGEMENT::T782820095"] = "Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}"
@@ -4881,6 +4887,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T21748
-- Model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2189814010"] = "Model"
+-- Import Embedding Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T229678442"] = "Import Embedding Provider"
+
-- Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2419962612"] = "Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'."
@@ -4956,6 +4965,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T146957
-- Uses the provider-configured model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Uses the provider-configured model"
+-- Import LLM Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T178720520"] = "Import LLM Provider"
+
-- Add Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider"
@@ -5094,6 +5106,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T58
-- This transcription provider is trusted by your organization for data source security checks.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T601264181"] = "This transcription provider is trusted by your organization for data source security checks."
+-- Import Transcription Provider
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T682006405"] = "Import Transcription Provider"
+
-- This transcription provider is managed by your organization. You can set your own API key.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T690752279"] = "This transcription provider is managed by your organization. You can set your own API key."
@@ -5742,6 +5757,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T204496403"] = "The chat
-- Profile Usage
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile Usage"
+-- Relink attachment: {0}
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2234793579"] = "Relink attachment: {0}"
+
-- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs."
@@ -5769,6 +5787,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579080722"] = "No, pro
-- You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2579208700"] = "You might want to predefine a first message that will be copied into the user prompt, when you use this chat template. This message could for example be a blueprint for a structured message that this chat template is defined to work with."
+-- Enter an absolute path to an existing local file, or remove this attachment.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2603688456"] = "Enter an absolute path to an existing local file, or remove this attachment."
+
-- Predefined User Input
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2607897066"] = "Predefined User Input"
@@ -5808,6 +5829,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3268470871"] = "No, cha
-- A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3329415439"] = "A chat template may decide which tools a chat starts with. Without such a decision, those chats start with the tools you chose as your default in the chat options. Deciding and then picking nothing is a different statement: such chats start with no tool at all, no matter what your default says."
+-- Some attachments in this chat template are missing. Relink or remove them before adding the template.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3342669429"] = "Some attachments in this chat template are missing. Relink or remove them before adding the template."
+
-- Add a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3372872324"] = "Add a message"
@@ -5817,6 +5841,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3448155331"] = "Close"
-- Unsupported content type
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3570316759"] = "Unsupported content type"
+-- Relink the missing attachment '{0}' to an existing local file or remove it before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3582611137"] = "Relink the missing attachment '{0}' to an existing local file or remove it before saving."
+
-- What system prompt do you want to use?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3652587353"] = "What system prompt do you want to use?"
@@ -5856,6 +5883,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T4199560726"] = "Create
-- Enter a message
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T446374405"] = "Enter a message"
+-- The chat template fields are malformed.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T539301384"] = "The chat template fields are malformed."
+
-- Data Sources
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T558345131"] = "Data Sources"
@@ -5865,6 +5895,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T628396066"] = "System P
-- A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T650711085"] = "A chat started with this template begins with these data sources and options. All of it stays changeable in the chat itself."
+-- Remove this attachment
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T70791272"] = "Remove this attachment"
+
-- The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T722788866"] = "The selection stays changeable in the chat, and every tool still has to meet the confidence requirements of the provider in use."
@@ -5955,12 +5988,27 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T767586087"
-- {0} embedding provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONPLUGINDELETEDIALOG::T818101181"] = "{0} embedding provider"
+-- Configuration snippet
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T3867536704"] = "Configuration snippet"
+
+-- Import is locked by your organization.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T4101825749"] = "Import is locked by your organization."
+
+-- Copy one exported configuration snippet from the item's Export configuration control and paste it below. You can review and change the filled form before saving a new local item.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T4214407984"] = "Copy one exported configuration snippet from the item's Export configuration control and paste it below. You can review and change the filled form before saving a new local item."
+
+-- Cancel
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIGURATIONSNIPPETIMPORTDIALOG::T900713019"] = "Cancel"
+
-- No
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T1642511898"] = "No"
-- Yes
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T3013883440"] = "Yes"
+-- The imported retrieval process '{0}' is unavailable. Select another process before saving.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERI_V1DIALOG::T1759490982"] = "The imported retrieval process '{0}' is unavailable. Select another process before saving."
+
-- How many matches do you want at most per query?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERI_V1DIALOG::T1827669611"] = "How many matches do you want at most per query?"
@@ -6534,6 +6582,72 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T854110894
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T900713019"] = "Cancel"
+-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
+
+-- Hide the policy definition when distributed via configuration plugin?
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1875622568"] = "Hide the policy definition when distributed via configuration plugin?"
+
+-- No profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2028602035"] = "No profile"
+
+-- Load output rules from document
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2168201568"] = "Load output rules from document"
+
+-- Preselect a profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2322771068"] = "Preselect a profile"
+
+-- The name of your policy must be between 6 and 60 characters long.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2435013256"] = "The name of your policy must be between 6 and 60 characters long."
+
+-- Preselect a provider
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2440815970"] = "Preselect a provider"
+
+-- Add
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2646845972"] = "Add"
+
+-- Policy name
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T2879019438"] = "Policy name"
+
+-- Analysis rules
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3108719748"] = "Analysis rules"
+
+-- Tools this policy permits
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T31356122"] = "Tools this policy permits"
+
+-- The description of your policy must be between 32 and 512 characters long.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3285636934"] = "The description of your policy must be between 32 and 512 characters long."
+
+-- A policy with this name already exists. Please choose a different name.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3584374593"] = "A policy with this name already exists. Please choose a different name."
+
+-- Use app default profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3587225583"] = "Use app default profile"
+
+-- No provider
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3740605451"] = "No provider"
+
+-- Load analysis rules from document
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3813558135"] = "Load analysis rules from document"
+
+-- Output rules
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3918193587"] = "Output rules"
+
+-- Please provide a name for your policy. This name will be used to identify the policy in AI Studio.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T4040507702"] = "Please provide a name for your policy. This name will be used to identify the policy in AI Studio."
+
+-- Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T652187065"] = "Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis."
+
+-- Policy description
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T748735777"] = "Policy description"
+
+-- Would you like to protect this policy so that you cannot accidentally edit or delete it?
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T80597472"] = "Would you like to protect this policy so that you cannot accidentally edit or delete it?"
+
+-- Cancel
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T900713019"] = "Cancel"
+
-- Please wait while we load the content of your file. Depending on the file type and size, this may take a moment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTCHECKDIALOG::T1205126512"] = "Please wait while we load the content of your file. Depending on the file type and size, this may take a moment."
@@ -7875,6 +7989,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T23198
-- This chat template preselects data sources which exist on this machine only: {0}. They cannot be rolled out, so a chat started with this template elsewhere begins without them. Do you want to export the template anyway?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T2563631533"] = "This chat template preselects data sources which exist on this machine only: {0}. They cannot be rolled out, so a chat started with this template elsewhere begins without them. Do you want to export the template anyway?"
+-- Import Chat Template
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T263213584"] = "Import Chat Template"
+
-- Chat Template Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T275026390"] = "Chat Template Name"
@@ -8196,6 +8313,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T711745239"
-- Edit Profile
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1143111468"] = "Edit Profile"
+-- Import Profile
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1215374025"] = "Import Profile"
+
-- No profiles configured yet.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGPROFILES::T1433534732"] = "No profiles configured yet."
@@ -11586,6 +11706,114 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECUR
-- The plugin code changed after the last security audit. The stored result no longer matches the current code, so this assistant plugin must be audited again before it may be enabled or used.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::ASSISTANTS::PLUGINASSISTANTSECURITYRESOLVER::T995107927"] = "The plugin code changed after the last security audit. The stored result no longer matches the current code, so this assistant plugin must be audited again before it may be enabled or used."
+-- The embedded credential could not be decrypted on this device. Enter your own credential before saving.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1017166692"] = "The embedded credential could not be decrypted on this device. Enter your own credential before saving."
+
+-- tool {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1561005241"] = "tool {0}"
+
+-- provider {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T1867642302"] = "provider {0}"
+
+-- The '{0}' field must be a string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T224834942"] = "The '{0}' field must be a string."
+
+-- The exported item has an invalid ID.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T2640740755"] = "The exported item has an invalid ID."
+
+-- The '{0}' field has an unknown value.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T2930445640"] = "The '{0}' field has an unknown value."
+
+-- data source {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3277968944"] = "data source {0}"
+
+-- The '{0}' field must be a whole number.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3419687389"] = "The '{0}' field must be a whole number."
+
+-- profile {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3445783292"] = "profile {0}"
+
+-- The '{0}' field must be true or false.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3600915840"] = "The '{0}' field must be true or false."
+
+-- Unavailable references: {0}. Review the selections before saving.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T3637224236"] = "Unavailable references: {0}. Review the selections before saving."
+
+-- The '{0}' field must contain an ENC:v1 credential.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T4261018324"] = "The '{0}' field must contain an ENC:v1 credential."
+
+-- The '{0}' field must be a table.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T432554775"] = "The '{0}' field must be a table."
+
+-- The '{0}' field contains an invalid entry.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONIMPORTFIELDS::T948874113"] = "The '{0}' field contains an invalid entry."
+
+-- The 'Port' field must be between 1 and 65535.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T1817689659"] = "The 'Port' field must be between 1 and 65535."
+
+-- The 'MaxMatches' field is outside the allowed range.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T2009159518"] = "The 'MaxMatches' field is outside the allowed range."
+
+-- This configuration section cannot be imported here.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T2657742500"] = "This configuration section cannot be imported here."
+
+-- An example conversation message is empty.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T3131294453"] = "An example conversation message is empty."
+
+-- An example conversation entry is not a table.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T3912378391"] = "An example conversation entry is not a table."
+
+-- Kerberos data sources cannot be imported from configuration snippets.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T4019138476"] = "Kerberos data sources cannot be imported from configuration snippets."
+
+-- The chat template fields are malformed.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T539301384"] = "The chat template fields are malformed."
+
+-- This data source is not an ERI v1 data source.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETIMPORTVALIDATION::T724971220"] = "This data source is not an ERI v1 data source."
+
+-- Only literal values are allowed at character {0}; executable Lua is not accepted.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1200702179"] = "Only literal values are allowed at character {0}; executable Lua is not accepted."
+
+-- Expected a quoted string at character {0}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1235695745"] = "Expected a quoted string at character {0}."
+
+-- Unterminated long string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1583706986"] = "Unterminated long string."
+
+-- The configuration section names do not match.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1700453124"] = "The configuration section names do not match."
+
+-- The snippet contains too many nested tables.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T1872841947"] = "The snippet contains too many nested tables."
+
+-- Expected '{0}' at character {1}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2092509274"] = "Expected '{0}' at character {1}."
+
+-- Unsupported string escape sequence: {0}
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2686740263"] = "Unsupported string escape sequence: {0}"
+
+-- Paste one exported configuration snippet (up to 1 MB).
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T2743278967"] = "Paste one exported configuration snippet (up to 1 MB)."
+
+-- Expected a comma or closing brace at character {0}.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3466555352"] = "Expected a comma or closing brace at character {0}."
+
+-- This is a {0} snippet. Paste a {1} snippet here.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3577683445"] = "This is a {0} snippet. Paste a {1} snippet here."
+
+-- Unterminated quoted string.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T3882373828"] = "Unterminated quoted string."
+
+-- A quoted string contains an unescaped newline.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T4168367785"] = "A quoted string contains an unescaped newline."
+
+-- The field '{0}' occurs more than once.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T560836334"] = "The field '{0}' occurs more than once."
+
+-- The snippet must contain exactly one table assignment and no executable code.
+UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::CONFIGURATIONSNIPPETPARSER::T873986754"] = "The snippet must contain exactly one table assignment and no executable code."
+
-- The table AUTHORS does not exist or is using an invalid syntax.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T1068328139"] = "The table AUTHORS does not exist or is using an invalid syntax."
From 2e288b554092230a50f72d32073dbc5ebbace22a Mon Sep 17 00:00:00 2001
From: Peer Hogeterp <20603780+peerschuett@users.noreply.github.com>
Date: Fri, 25 Sep 2026 12:55:01 +0200
Subject: [PATCH 4/5] Fixing minor bugs
---
app/MindWork AI Studio/Assistants/I18N/allTexts.lua | 12 ++++++++++++
.../Dialogs/DocumentAnalysisPolicyDialog.razor | 4 ++--
.../plugin.lua | 12 ++++++++++++
.../plugin.lua | 12 ++++++++++++
4 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index 9287ec8a..4d991cdc 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -6580,9 +6580,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T854110894
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T900713019"] = "Cancel"
+-- No, the policy can be edited
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1286595725"] = "No, the policy can be edited"
+
-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
+-- Yes, protect this policy
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1762380857"] = "Yes, protect this policy"
+
-- Hide the policy definition when distributed via configuration plugin?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1875622568"] = "Hide the policy definition when distributed via configuration plugin?"
@@ -6613,6 +6619,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3108719748"]
-- Tools this policy permits
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T31356122"] = "Tools this policy permits"
+-- No, show the policy definition
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3166091879"] = "No, show the policy definition"
+
-- The description of your policy must be between 32 and 512 characters long.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3285636934"] = "The description of your policy must be between 32 and 512 characters long."
@@ -6646,6 +6655,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T80597472"] =
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T900713019"] = "Cancel"
+-- Yes, hide the policy definition
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T940701960"] = "Yes, hide the policy definition"
+
-- Please wait while we load the content of your file. Depending on the file type and size, this may take a moment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTCHECKDIALOG::T1205126512"] = "Please wait while we load the content of your file. Depending on the file type and size, this may take a moment."
diff --git a/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor
index 72ef518c..5d00a1ff 100644
--- a/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/DocumentAnalysisPolicyDialog.razor
@@ -12,7 +12,7 @@
-
+
@@ -30,7 +30,7 @@
@profile.Name
}
-
+
diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
index 60577a17..ba871e7c 100644
--- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
@@ -6582,9 +6582,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T854110894
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T900713019"] = "Abbrechen"
+-- No, the policy can be edited
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1286595725"] = "Nein, die Richtlinie kann bearbeitet werden."
+
-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1291179736"] = "Below is a clear set of **analysis rules** you can use to instruct an AI on how to analyze documents. --- ## Document Analysis Rules ### 1. Purpose of Analysis The AI must analyze each document by extracting the most important information, identifying its meaning, and presenting the results in a clear, structured, and useful way. --- ### 2. Focus on the Document Content The AI must base its analysis only on the information contained in the document. It should: - Use facts, statements, and data from the document. - Not invent missing information. - Clearly mark uncertainty if something is unclear. --- ### 3. Identify the Document Type The AI should first determine what kind of document it is, for example: - Report - Contract - Email - Article - Presentation - Form - Policy - Invoice - Manual - Legal document If the type cannot be determined, the AI should state that it is uncertain. --- ### 4. Extract Key Information The AI should identify the most important elements of the document, such as: - Title or main topic - Author or sender, if available - Date or timeline - Purpose of the document - Main points or arguments - Key entities, such as people, organizations, places, or products - Important numbers, dates, deadlines, or references - Any required actions or recommendations --- ### 5. Summarize the Document The AI should provide a short and clear summary that captures the main meaning of the document. The summary should: - Be concise - Stay factual - Include only the most important information - Avoid unnecessary details --- ### 6. Identify Structure and Organization The AI should analyze how the document is organized, including: - Sections or headings - Logical flow of information - Presence of tables, lists, or attachments - Whether the structure helps the reader understand the content --- ### 7. Analyze Tone and Style The AI should evaluate the writing style and tone of the document. Possible tone categories include: - Formal - Informal - Technical - Legal - Persuasive - Neutral - Urgent - Instructional The AI should also note if the tone is appropriate for the document type and audience. --- ### 8. Assess Clarity and Quality The AI should evaluate how clear and understandable the document is. It should consider: - Whether the main message is easy to understand - Whether important information is missing or unclear - Whether the language is precise or vague - Whether the document contains contradictions or errors - Whether the formatting supports easy reading --- ### 9. Detect Risks, Issues, or Gaps The AI should point out anything that may be important, risky, incomplete, or inconsistent. For example: - Missing dates or signatures - Unclear obligations - Conflicting statements - Undefined terms - Potentially incorrect data - Missing explanations - Ambiguous instructions --- ### 10. Identify Actions, Decisions, or Requirements If the document asks the reader to do something, the AI must identify those actions clearly. It should extract: - Required tasks - Deadlines - Responsibilities - Approval requests - Conditions - Next steps --- ### 11. Compare Information If Multiple Documents Are Given If more than one document is provided, the AI should compare them and identify: - Similarities - Differences - Contradictions - Repeated information - New or updated content --- ### 12. Present Results in a Structured Format The AI should return its analysis in a clear structure, for example: **1. Document type** **2. Main purpose** **3. Summary** **4. Key information** **5. Important points** **6. Missing or unclear items** **7. Risks or issues** **8. Suggested actions** **9. Conclusion** If the user requests a different format, the AI should follow that format. --- ### 13. Use Simple and Understandable Language The AI should explain the document in a way that is easy to understand, even for non-experts. It should: - Avoid unnecessary technical language - Explain complex terms if needed - Make the analysis useful for regular users --- ### 14. Be Objective and Neutral The AI should analyze the document without adding personal opinions, unless the user explicitly asks for evaluation or recommendation. It should: - Stay factual - Separate facts from interpretation - Avoid exaggeration or assumptions --- ### 15. Handle Unclear or Low-Quality Input Carefully If the document is incomplete, unreadable, or unclear, the AI should: - Explain what is missing or unclear - Describe what can still be understood - Suggest what additional information is needed It should not pretend to understand something that is not supported by the document. --- ## Optional Standard Output Format The AI may respond using this structure: ```text 1. Document Overview 2. Main Purpose 3. Key Points 4. Important Data 5. Unclear or Missing Information 6. Risks or Issues 7. Recommended Actions 8. Final Summary ``` --- If you want, I can also turn this into: 1. a **system prompt** for an AI assistant, 2. a **checklist for document review**, or 3. a **shorter version for practical use**."
+-- Yes, protect this policy
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1762380857"] = "Ja, diese Richtlinie schützen"
+
-- Hide the policy definition when distributed via configuration plugin?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1875622568"] = "Richtliniendefinition ausblenden, wenn sie über das Konfigurations-Plugin verteilt wird?"
@@ -6615,6 +6621,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3108719748"]
-- Tools this policy permits
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T31356122"] = "Von dieser Richtlinie erlaubte Tools"
+-- No, show the policy definition
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3166091879"] = "Nein, Definition der Richtlinie anzeigen"
+
-- The description of your policy must be between 32 and 512 characters long.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3285636934"] = "Die Beschreibung Ihrer Richtlinie muss zwischen 32 und 512 Zeichen lang sein."
@@ -6648,6 +6657,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T80597472"] =
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T900713019"] = "Abbrechen"
+-- Yes, hide the policy definition
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T940701960"] = "Ja, Richtliniendefinition ausblenden"
+
-- Please wait while we load the content of your file. Depending on the file type and size, this may take a moment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTCHECKDIALOG::T1205126512"] = "Bitte warten Sie, während wir den Inhalt Ihrer Datei laden. Je nach Dateityp und -größe kann dies einen Moment dauern."
diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
index 46455c27..1781c049 100644
--- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
@@ -6582,9 +6582,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T854110894
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DIRECTCHATLAUNCHERSETTINGSDIALOG::T900713019"] = "Cancel"
+-- No, the policy can be edited
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1286595725"] = "No, the policy can be edited"
+
-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
+-- Yes, protect this policy
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1762380857"] = "Yes, protect this policy"
+
-- Hide the policy definition when distributed via configuration plugin?
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T1875622568"] = "Hide the policy definition when distributed via configuration plugin?"
@@ -6615,6 +6621,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3108719748"]
-- Tools this policy permits
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T31356122"] = "Tools this policy permits"
+-- No, show the policy definition
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3166091879"] = "No, show the policy definition"
+
-- The description of your policy must be between 32 and 512 characters long.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T3285636934"] = "The description of your policy must be between 32 and 512 characters long."
@@ -6648,6 +6657,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T80597472"] =
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T900713019"] = "Cancel"
+-- Yes, hide the policy definition
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTANALYSISPOLICYDIALOG::T940701960"] = "Yes, hide the policy definition"
+
-- Please wait while we load the content of your file. Depending on the file type and size, this may take a moment.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DOCUMENTCHECKDIALOG::T1205126512"] = "Please wait while we load the content of your file. Depending on the file type and size, this may take a moment."
From 003922efffc83a6f06f6bd93c6f6f0b057fa8f75 Mon Sep 17 00:00:00 2001
From: Peer Hogeterp <20603780+peerschuett@users.noreply.github.com>
Date: Fri, 25 Sep 2026 13:04:24 +0200
Subject: [PATCH 5/5] Final review
---
.../Dialogs/DataSourceERI_V1Dialog.razor | 2 +-
.../Dialogs/DataSourceERI_V1Dialog.razor.cs | 17 ++++++++++++++---
.../Settings/EmbeddingProvider.cs | 2 +-
app/MindWork AI Studio/Settings/Provider.cs | 2 +-
4 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
index a16077af..7893ac54 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
@@ -113,7 +113,7 @@
@if (this.availableRetrievalProcesses.Count > 0)
{
-
+
@foreach (var retrievalProcess in this.availableRetrievalProcesses)
{
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
index 5827274f..2ad1b370 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
@@ -51,6 +51,7 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
private string dataSecretStorageIssue = string.Empty;
private string importCredentialIssue = string.Empty;
private string importRetrievalIssue = string.Empty;
+ private string importedRetrievalId = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
private List availableAuthMethods = [];
private DataSourceSecurity dataSecurityPolicy;
@@ -227,12 +228,15 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
this.dataSecret = secret;
this.importCredentialIssue = credentialIssue;
this.importRetrievalIssue = string.Empty;
+ this.importedRetrievalId = retrievalId;
this.connectionTested = false;
this.connectionSuccessfulTested = false;
this.form.ResetValidation();
return Task.CompletedTask;
}
+ private void ClearImportRetrievalIssue() => this.importRetrievalIssue = string.Empty;
+
private bool IsConnectionEncrypted() => this.dataHostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase);
private bool IsConnectionPossible()
@@ -307,16 +311,23 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
}
this.availableRetrievalProcesses = retrievalInfoRequest.Data ?? [];
- if (!string.IsNullOrWhiteSpace(this.dataSelectedRetrievalProcess.Id))
+ // Only the first successful test after an import resolves the imported retrieval ID;
+ // afterward, the selection belongs to the user:
+ if (!string.IsNullOrWhiteSpace(this.importedRetrievalId))
{
- var importedRetrieval = this.availableRetrievalProcesses.FirstOrDefault(item => item.Id == this.dataSelectedRetrievalProcess.Id);
+ var importedRetrieval = this.availableRetrievalProcesses.FirstOrDefault(item => item.Id == this.importedRetrievalId);
if (importedRetrieval != default)
+ {
this.dataSelectedRetrievalProcess = importedRetrieval;
+ this.importRetrievalIssue = string.Empty;
+ }
else
{
- this.importRetrievalIssue = string.Format(T("The imported retrieval process '{0}' is unavailable. Select another process before saving."), this.dataSelectedRetrievalProcess.Id);
+ this.importRetrievalIssue = string.Format(T("The imported retrieval process '{0}' is unavailable. Select another process before saving."), this.importedRetrievalId);
this.dataSelectedRetrievalProcess = default;
}
+
+ this.importedRetrievalId = string.Empty;
}
this.connectionTested = true;
diff --git a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs
index 8aa411b1..7180c1d9 100644
--- a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs
+++ b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs
@@ -256,7 +256,7 @@ public sealed record EmbeddingProvider(
["Name"] = "{{LuaTools.EscapeLuaString(this.Name)}}",
["UsedLLMProvider"] = "{{this.UsedLLMProvider}}",
- ["TokenizerPath"] = "{{this.TokenizerPath}}",
+ ["TokenizerPath"] = "{{LuaTools.EscapeLuaString(this.TokenizerPath)}}",
["TokenLimit"] = {{this.EffectiveTokenLimit}},
["EmbeddingBatchSize"] = {{this.EffectiveEmbeddingBatchSize}},
diff --git a/app/MindWork AI Studio/Settings/Provider.cs b/app/MindWork AI Studio/Settings/Provider.cs
index cc153d21..f42ea61e 100644
--- a/app/MindWork AI Studio/Settings/Provider.cs
+++ b/app/MindWork AI Studio/Settings/Provider.cs
@@ -279,7 +279,7 @@ public sealed record Provider(
["InstanceName"] = "{{LuaTools.EscapeLuaString(this.InstanceName)}}",
["UsedLLMProvider"] = "{{this.UsedLLMProvider}}",
- ["TokenizerPath"] = "{{this.TokenizerPath}}",
+ ["TokenizerPath"] = "{{LuaTools.EscapeLuaString(this.TokenizerPath)}}",
["Host"] = "{{this.Host}}",
["Hostname"] = "{{LuaTools.EscapeLuaString(this.Hostname)}}",