mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 00:53:37 +00:00
Warn before exporting a chat template with local data sources
This commit is contained in:
parent
52c0f3aa89
commit
9bd0ff24cf
@ -132,6 +132,9 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
|
||||
if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
if (!await this.ConfirmExportOfLocalDataSources(chatTemplate))
|
||||
return;
|
||||
|
||||
await this.CopyChatTemplateLuaToClipboard(chatTemplate);
|
||||
}
|
||||
|
||||
@ -145,10 +148,14 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
|
||||
|
||||
if (chatTemplate.FileAttachments.Count == 0)
|
||||
{
|
||||
// That way asks about the local data sources itself, so we must not ask twice:
|
||||
await this.ExportChatTemplateWithSharedAttachmentPaths(chatTemplate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await this.ConfirmExportOfLocalDataSources(chatTemplate))
|
||||
return;
|
||||
|
||||
this.isPluginDirectoryDialogOpen = true;
|
||||
try
|
||||
{
|
||||
@ -164,6 +171,35 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks whether to export a template although it preselects data sources of this machine.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The export writes the preselected data source IDs unchanged, which is what makes a template
|
||||
/// usable across an organization — but a local file or folder exists here and nowhere else, so
|
||||
/// its ID points at nothing on the machine reading the plugin. Nothing breaks, the chat simply
|
||||
/// starts without that source, and that is precisely why it has to be said beforehand: nobody
|
||||
/// would notice it afterwards. Exporting anyway is a fair choice, because the rest of the
|
||||
/// template is worth rolling out.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template about to be exported.</param>
|
||||
/// <returns>True when the export may go ahead.</returns>
|
||||
private async Task<bool> ConfirmExportOfLocalDataSources(ChatTemplate chatTemplate)
|
||||
{
|
||||
var localDataSourceNames = ChatTemplate.GetPreselectedLocalDataSourceNames(chatTemplate, this.SettingsManager.ConfigurationData.DataSources);
|
||||
if (localDataSourceNames.Count == 0)
|
||||
return true;
|
||||
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, string.Format(T("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?"), string.Join(", ", localDataSourceNames)) },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Export Chat Template"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
return dialogResult is { Canceled: false };
|
||||
}
|
||||
|
||||
private async Task CopyChatTemplateLuaToClipboard(ChatTemplate chatTemplate)
|
||||
{
|
||||
if (!chatTemplate.TryExportAsConfigurationSection(out var luaCode, out var issue))
|
||||
|
||||
@ -145,6 +145,32 @@ public record ChatTemplate(
|
||||
return (templateOptions.CreateCopy(), launcherOptions is not null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Names the preselected data sources which exist on this machine only.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Such a source is a sensible choice inside a chat and a dead end in an export: its ID travels
|
||||
/// into the plugin unchanged, and on the machine which reads that plugin it points at nothing.
|
||||
/// Only ERI sources describe something the whole organization can reach, which is why they are
|
||||
/// also the only ones the app offers an export for.<br/><br/>
|
||||
/// IDs which match no configured source at all are left out. Those are covered by the note the
|
||||
/// export writes above the data source IDs anyway, and the name to warn about is missing.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template about to be exported.</param>
|
||||
/// <param name="configuredDataSources">The data sources configured on this machine.</param>
|
||||
/// <returns>The names of the preselected local data sources, in the order they are configured in.</returns>
|
||||
public static IReadOnlyList<string> GetPreselectedLocalDataSourceNames(ChatTemplate chatTemplate, IEnumerable<IDataSource> configuredDataSources)
|
||||
{
|
||||
if (chatTemplate.DataSourceOptions is not { PreselectedDataSourceIds.Count: > 0 } options)
|
||||
return [];
|
||||
|
||||
var preselectedIds = options.PreselectedDataSourceIds.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
return configuredDataSources
|
||||
.Where(source => source is IInternalDataSource && preselectedIds.Contains(source.Id))
|
||||
.Select(source => source.Name)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static bool TryParseChatTemplateTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject template)
|
||||
{
|
||||
template = NO_CHAT_TEMPLATE;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
using Lua;
|
||||
using Lua.Standard;
|
||||
@ -14,6 +15,8 @@ namespace AIStudio.Tests.Settings;
|
||||
/// template says nothing" and "this template says none" survives. Both end up as an empty
|
||||
/// selection in the chat on a fresh installation, so a mistake here stays invisible until somebody
|
||||
/// sets a default tool for their chats -- and then quietly hands out a tool the template ruled out.
|
||||
/// The last tests cover what the export says out loud before it runs, for the same reason: a data
|
||||
/// source which cannot be rolled out goes unnoticed on the machine reading the plugin.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class ChatTemplateConfigurationTests
|
||||
@ -183,6 +186,55 @@ public sealed class ChatTemplateConfigurationTests
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LocalDataSourcesOfATemplateAreNamedBeforeItIsExported()
|
||||
{
|
||||
var template = NewTemplate() with
|
||||
{
|
||||
DataSourceOptions = new()
|
||||
{
|
||||
DisableDataSources = false,
|
||||
PreselectedDataSourceIds = ["11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222"],
|
||||
},
|
||||
};
|
||||
|
||||
var localNames = ChatTemplate.GetPreselectedLocalDataSourceNames(template, ConfiguredDataSources());
|
||||
|
||||
Assert.That(localNames, Is.EqualTo(new[] { "Meeting notes" }), "Only the local source can be named: its ID means nothing on the machine which reads the exported plugin, while the ERI source points at something the whole organization reaches.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ATemplateWithoutLocalDataSourcesIsExportedWithoutAQuestion()
|
||||
{
|
||||
var eriOnly = NewTemplate() with
|
||||
{
|
||||
DataSourceOptions = new() { DisableDataSources = false, PreselectedDataSourceIds = ["22222222-2222-2222-2222-222222222222"] },
|
||||
};
|
||||
|
||||
var agentic = NewTemplate() with
|
||||
{
|
||||
DataSourceOptions = new() { DisableDataSources = false, AutomaticDataSourceSelection = true },
|
||||
};
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(ChatTemplate.GetPreselectedLocalDataSourceNames(eriOnly, ConfiguredDataSources()), Is.Empty);
|
||||
Assert.That(ChatTemplate.GetPreselectedLocalDataSourceNames(agentic, ConfiguredDataSources()), Is.Empty, "An agent picks the sources per message, so this template names none to begin with.");
|
||||
Assert.That(ChatTemplate.GetPreselectedLocalDataSourceNames(NewTemplate(), ConfiguredDataSources()), Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnIdWhichMatchesNoDataSourceIsNotReportedAsALocalOne()
|
||||
{
|
||||
var template = NewTemplate() with
|
||||
{
|
||||
DataSourceOptions = new() { DisableDataSources = false, PreselectedDataSourceIds = ["99999999-9999-9999-9999-999999999999"] },
|
||||
};
|
||||
|
||||
Assert.That(ChatTemplate.GetPreselectedLocalDataSourceNames(template, ConfiguredDataSources()), Is.Empty, "There is no name to warn about, and the note above the exported IDs already tells the admin to check them.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A template with the parts every export needs, and nothing said about tools or data sources.
|
||||
/// </summary>
|
||||
@ -198,6 +250,15 @@ public sealed class ChatTemplateConfigurationTests
|
||||
AllowProfileUsage = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// One data source of each kind: a local one, which cannot be rolled out, and an ERI one, which can.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<IDataSource> ConfiguredDataSources() =>
|
||||
[
|
||||
new DataSourceLocalFile { Id = "11111111-1111-1111-1111-111111111111", Name = "Meeting notes", Type = DataSourceType.LOCAL_FILE },
|
||||
new DataSourceERI_V1 { Id = "22222222-2222-2222-2222-222222222222", Name = "Intranet", Type = DataSourceType.ERI_V1 },
|
||||
];
|
||||
|
||||
private static async Task<ChatTemplate> ExportAndReadBackAsync(ChatTemplate template)
|
||||
{
|
||||
Assert.That(template.TryExportAsConfigurationSection(out var luaCode, out var issue), Is.True, issue);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user