Added sharing, importing, and deleting for language plugins

This commit is contained in:
Thorsten Sommer 2026-08-09 15:47:14 +02:00
parent 2445f59f3a
commit 459fc977c8
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
11 changed files with 302 additions and 210 deletions

View File

@ -3193,18 +3193,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] =
-- You have selected {0} preview features. -- You have selected {0} preview features.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features."
-- The '{0}' configuration plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T2048783766"] = "The '{0}' configuration plugin has been successfully removed."
-- Delete Configuration Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T3395354991"] = "Delete Configuration Plugin"
-- The configuration plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T4140893414"] = "The configuration plugin '{0}' could not be deleted: {1}"
-- Delete configuration plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T459830575"] = "Delete configuration plugin"
-- Preselected provider -- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider"
@ -3292,6 +3280,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "L
-- Issues -- Issues
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues"
-- Do you really want to delete the language plugin '{0}'? This permanently deletes its local plugin files. When it is your chosen language, AI Studio returns to choosing the language automatically.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T1744561175"] = "Do you really want to delete the language plugin '{0}'? This permanently deletes its local plugin files. When it is your chosen language, AI Studio returns to choosing the language automatically."
-- Delete language plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2707495447"] = "Delete language plugin"
-- The plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2738963920"] = "The plugin '{0}' could not be deleted: {1}"
-- Delete Language Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2990518039"] = "Delete Language Plugin"
-- Delete Configuration Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T3395354991"] = "Delete Configuration Plugin"
-- The plugin '{0}' has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T3476138264"] = "The plugin '{0}' has been successfully removed."
-- Delete configuration plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T459830575"] = "Delete configuration plugin"
-- Your Pandoc installation meets the requirements. -- Your Pandoc installation meets the requirements.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements."
@ -9805,24 +9814,21 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1195382910"]
-- The assistant plugin directory is outside the local assistant plugin directory. -- The assistant plugin directory is outside the local assistant plugin directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1211881977"] = "The assistant plugin directory is outside the local assistant plugin directory." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1211881977"] = "The assistant plugin directory is outside the local assistant plugin directory."
-- Only configuration plugins can be deleted this way.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1214012118"] = "Only configuration plugins can be deleted this way."
-- Only assistant plugins can be edited. -- Only assistant plugins can be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1288328479"] = "Only assistant plugins can be edited." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1288328479"] = "Only assistant plugins can be edited."
-- The assistant cannot be deleted while background work is still running. -- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1318944584"] = "The assistant cannot be deleted while background work is still running." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1318944584"] = "The assistant cannot be deleted while background work is still running."
-- Plugins deployed by your organization cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1348456011"] = "Plugins deployed by your organization cannot be deleted."
-- The resolved plugin directory is outside the plugin directory. -- The resolved plugin directory is outside the plugin directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1559620698"] = "The resolved plugin directory is outside the plugin directory." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1559620698"] = "The resolved plugin directory is outside the plugin directory."
-- Please select a plugin archive with the extension .mwplugin or .zip. -- Please select a plugin archive with the extension .mwplugin or .zip.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1809137998"] = "Please select a plugin archive with the extension .mwplugin or .zip." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1809137998"] = "Please select a plugin archive with the extension .mwplugin or .zip."
-- Internal configuration plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1811106891"] = "Internal configuration plugins cannot be deleted."
-- The selected plugin archive does not exist. -- The selected plugin archive does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1821013825"] = "The selected plugin archive does not exist." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1821013825"] = "The selected plugin archive does not exist."
@ -9835,36 +9841,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2134532120"]
-- The assistant plugin directory does not exist. -- The assistant plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2148384567"] = "The assistant plugin directory does not exist." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2148384567"] = "The assistant plugin directory does not exist."
-- The plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2221093487"] = "The plugin directory does not exist."
-- Unexpected error: {0} -- Unexpected error: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2350673880"] = "Unexpected error: {0}" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2350673880"] = "Unexpected error: {0}"
-- The generated assistant plugin uses the ID of another installed plugin. -- The generated assistant plugin uses the ID of another installed plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2441747251"] = "The generated assistant plugin uses the ID of another installed plugin." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2441747251"] = "The generated assistant plugin uses the ID of another installed plugin."
-- The configuration plugin directory is outside the plugins directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2613041269"] = "The configuration plugin directory is outside the plugins directory."
-- The assistant plugin has no local directory. -- The assistant plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2682912892"] = "The assistant plugin has no local directory." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2682912892"] = "The assistant plugin has no local directory."
-- The AI Studio data directory is not initialized yet. -- The AI Studio data directory is not initialized yet.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2712481762"] = "The AI Studio data directory is not initialized yet." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2712481762"] = "The AI Studio data directory is not initialized yet."
-- Only configuration and language plugins can be deleted this way.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2850672167"] = "Only configuration and language plugins can be deleted this way."
-- Only assistant plugins can be deleted. -- Only assistant plugins can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2864597027"] = "Only assistant plugins can be deleted." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2864597027"] = "Only assistant plugins can be deleted."
-- Only assistant, configuration, and language plugins can be imported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2909113247"] = "Only assistant, configuration, and language plugins can be imported."
-- The plugin directory is outside the plugins directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2911446827"] = "The plugin directory is outside the plugins directory."
-- The generated plugin is not an assistant plugin. Issue: {0} -- The generated plugin is not an assistant plugin. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2955055168"] = "The generated plugin is not an assistant plugin. Issue: {0}" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2955055168"] = "The generated plugin is not an assistant plugin. Issue: {0}"
-- The configuration plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3073748821"] = "The configuration plugin directory does not exist."
-- Only assistant and configuration plugins can be imported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3166787637"] = "Only assistant and configuration plugins can be imported."
-- Your organization has disabled importing plugins. -- Your organization has disabled importing plugins.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3212529834"] = "Your organization has disabled importing plugins." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3212529834"] = "Your organization has disabled importing plugins."
-- The plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3284289028"] = "The plugin has no local directory."
-- The plugin archive must contain exactly one plugin.lua file. -- The plugin archive must contain exactly one plugin.lua file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3355918609"] = "The plugin archive must contain exactly one plugin.lua file." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3355918609"] = "The plugin archive must contain exactly one plugin.lua file."
@ -9877,6 +9889,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3634046009"]
-- Config Server managed assistant plugins cannot be deleted. -- Config Server managed assistant plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3751820312"] = "Config Server managed assistant plugins cannot be deleted." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3751820312"] = "Config Server managed assistant plugins cannot be deleted."
-- Plugins shipped with AI Studio cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3841213017"] = "Plugins shipped with AI Studio cannot be deleted."
-- Only assistants generated by the Assistant Builder can be deleted. -- Only assistants generated by the Assistant Builder can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3940247198"] = "Only assistants generated by the Assistant Builder can be deleted." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3940247198"] = "Only assistants generated by the Assistant Builder can be deleted."
@ -9901,12 +9916,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T584770023"] =
-- The edited assistant plugin must keep the same plugin ID. -- The edited assistant plugin must keep the same plugin ID.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T693124809"] = "The edited assistant plugin must keep the same plugin ID." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T693124809"] = "The edited assistant plugin must keep the same plugin ID."
-- The configuration plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T719938122"] = "The configuration plugin has no local directory."
-- Configuration plugins deployed by your organization cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T75764577"] = "Configuration plugins deployed by your organization cannot be deleted."
-- Internal assistant plugins cannot be edited. -- Internal assistant plugins cannot be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T816339833"] = "Internal assistant plugins cannot be edited." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T816339833"] = "Internal assistant plugins cannot be edited."

View File

@ -1,75 +0,0 @@
using AIStudio.Dialogs;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components;
/// <summary>
/// Lets users remove a configuration plugin they placed locally.
/// </summary>
/// <remarks>
/// Configuration plugins have no activation switch, so without this action a local configuration
/// could only be removed from the data directory by hand. Configurations deployed by an organization
/// stay untouched: the action does not appear for them.
/// </remarks>
public partial class ConfigurationPluginDeleteAction : MSGComponentBase
{
[Parameter, EditorRequired]
public IAvailablePlugin Plugin { get; set; } = null!;
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private PluginInstallService PluginInstallService { get; init; } = null!;
[Inject]
private ILogger<ConfigurationPluginDeleteAction> Logger { get; init; } = null!;
private bool isDeleting;
private bool CanDelete => PluginInstallService.CanDeleteInstalledConfiguration(this.Plugin);
private async Task DeleteConfigurationPluginAsync()
{
if (!this.CanDelete || this.isDeleting)
return;
// The deletion reaches beyond the plugin directory, so we show what it takes with it:
var dialogParameters = new DialogParameters<ConfigurationPluginDeleteDialog>
{
{ x => x.PluginName, this.Plugin.Name },
{ x => x.Summary, this.PluginInstallService.BuildConfigurationDeleteSummary(this.Plugin) },
};
var dialogReference = await this.DialogService.ShowAsync<ConfigurationPluginDeleteDialog>(this.T("Delete Configuration Plugin"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
this.isDeleting = true;
await this.InvokeAsync(this.StateHasChanged);
try
{
var result = await this.PluginInstallService.DeleteInstalledConfigurationAsync(this.Plugin, CancellationToken.None);
if (!result.Success)
{
this.Logger.LogError("Failed to delete configuration plugin '{PluginName}' ({PluginId}) from '{PluginDirectory}' with issue '{Issue}'.", result.PluginName, result.PluginId, result.PluginDirectory, result.Issue);
await this.MessageBus.SendError(new(Icons.Material.Filled.DeleteForever, string.Format(this.T("The configuration plugin '{0}' could not be deleted: {1}"), this.Plugin.Name, result.Issue)));
return;
}
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.Check, string.Format(this.T("The '{0}' configuration plugin has been successfully removed."), result.PluginName)));
}
finally
{
this.isDeleting = false;
await this.InvokeAsync(this.StateHasChanged);
}
}
}

View File

@ -2,12 +2,12 @@
@if (this.CanDelete) @if (this.CanDelete)
{ {
<MudTooltip Text="@T("Delete configuration plugin")"> <MudTooltip Text="@this.Tooltip">
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" <MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
Color="Color.Error" Color="Color.Error"
Variant="Variant.Text" Variant="Variant.Text"
Size="Size.Medium" Size="Size.Medium"
Disabled="@this.isDeleting" Disabled="@this.isDeleting"
OnClick="@this.DeleteConfigurationPluginAsync" /> OnClick="@this.DeleteLocalPluginAsync" />
</MudTooltip> </MudTooltip>
} }

View File

@ -0,0 +1,102 @@
using AIStudio.Dialogs;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components;
/// <summary>
/// Lets users remove a configuration or language plugin they installed or placed themselves.
/// </summary>
/// <remarks>
/// Without this action, such a plugin could only be removed from the data directory by hand. That is
/// especially painful for configuration plugins, which have no activation switch at all. Plugins
/// shipped with AI Studio and plugins deployed by an organization stay untouched: the action does
/// not appear for them.
/// </remarks>
public partial class LocalPluginDeleteAction : MSGComponentBase
{
[Parameter, EditorRequired]
public IAvailablePlugin Plugin { get; set; } = null!;
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private PluginInstallService PluginInstallService { get; init; } = null!;
[Inject]
private ILogger<LocalPluginDeleteAction> Logger { get; init; } = null!;
private bool isDeleting;
private bool CanDelete => PluginInstallService.CanDeleteLocalPlugin(this.Plugin);
private string Tooltip => this.Plugin.Type is PluginType.CONFIGURATION
? this.T("Delete configuration plugin")
: this.T("Delete language plugin");
private async Task DeleteLocalPluginAsync()
{
if (!this.CanDelete || this.isDeleting)
return;
if (!await this.ConfirmDeletionAsync())
return;
this.isDeleting = true;
await this.InvokeAsync(this.StateHasChanged);
try
{
var result = await this.PluginInstallService.DeleteLocalPluginAsync(this.Plugin, CancellationToken.None);
if (!result.Success)
{
this.Logger.LogError("Failed to delete {PluginType} plugin '{PluginName}' ({PluginId}) from '{PluginDirectory}' with issue '{Issue}'.", this.Plugin.Type, result.PluginName, result.PluginId, result.PluginDirectory, result.Issue);
await this.MessageBus.SendError(new(Icons.Material.Filled.DeleteForever, string.Format(this.T("The plugin '{0}' could not be deleted: {1}"), this.Plugin.Name, result.Issue)));
return;
}
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.Check, string.Format(this.T("The plugin '{0}' has been successfully removed."), result.PluginName)));
}
finally
{
this.isDeleting = false;
await this.InvokeAsync(this.StateHasChanged);
}
}
/// <summary>
/// Asks the user before the deletion. A configuration gets the dialog listing its consequences,
/// because removing it also removes the providers and settings it brought. A language plugin
/// only owns its own files, so a plain confirmation is enough.
/// </summary>
private async Task<bool> ConfirmDeletionAsync()
{
if (this.Plugin.Type is PluginType.CONFIGURATION)
{
var configurationParameters = new DialogParameters<ConfigurationPluginDeleteDialog>
{
{ x => x.PluginName, this.Plugin.Name },
{ x => x.Summary, this.PluginInstallService.BuildConfigurationDeleteSummary(this.Plugin) },
};
var configurationDialog = await this.DialogService.ShowAsync<ConfigurationPluginDeleteDialog>(this.T("Delete Configuration Plugin"), configurationParameters, DialogOptions.FULLSCREEN);
return await configurationDialog.Result is { Canceled: false };
}
var parameters = new DialogParameters<ConfirmDialog>
{
{
x => x.Message,
string.Format(this.T("Do you really want to delete the language plugin '{0}'? This permanently deletes its local plugin files. When it is your chosen language, AI Studio returns to choosing the language automatically."), this.Plugin.Name)
},
};
var dialog = await this.DialogService.ShowAsync<ConfirmDialog>(this.T("Delete Language Plugin"), parameters, DialogOptions.FULLSCREEN);
return await dialog.Result is { Canceled: false };
}
}

View File

@ -139,7 +139,7 @@
@if (context is IAvailablePlugin availablePlugin) @if (context is IAvailablePlugin availablePlugin)
{ {
<AssistantPluginDeleteAction Plugin="@availablePlugin" /> <AssistantPluginDeleteAction Plugin="@availablePlugin" />
<ConfigurationPluginDeleteAction Plugin="@availablePlugin" /> <LocalPluginDeleteAction Plugin="@availablePlugin" />
} }
</MudStack> </MudStack>
</MudStack> </MudStack>

View File

@ -236,11 +236,16 @@ public partial class Plugins : MSGComponentBase
} }
/// <summary> /// <summary>
/// Sharing is limited to assistant plugins because the import accepts only those. Otherwise, /// The plugin types users may share. This list has to match what the import accepts, otherwise
/// users would create archives nobody can install. Widen this once the import supports more /// users would create archives nobody can install.
/// plugin types.
/// </summary> /// </summary>
private static bool CanSharePlugin(IAvailablePlugin plugin) => plugin is { IsInternal: false, IsManagedByConfigServer: false, Type: PluginType.ASSISTANT } && !string.IsNullOrWhiteSpace(plugin.LocalPath); private static readonly PluginType[] SHAREABLE_PLUGIN_TYPES = [PluginType.ASSISTANT, PluginType.CONFIGURATION, PluginType.LANGUAGE];
/// <summary>
/// Checks whether a plugin may be shared or exported as an archive. Plugins shipped with
/// AI Studio and plugins deployed by an organization stay with their owner.
/// </summary>
private static bool CanSharePlugin(IAvailablePlugin plugin) => plugin is { IsInternal: false, IsManagedByConfigServer: false } && SHAREABLE_PLUGIN_TYPES.Contains(plugin.Type) && !string.IsNullOrWhiteSpace(plugin.LocalPath);
/// <summary> /// <summary>
/// Highlights the plugin table while the user drags a file over the page, so it is visible /// Highlights the plugin table while the user drags a file over the page, so it is visible

View File

@ -3195,18 +3195,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] =
-- You have selected {0} preview features. -- You have selected {0} preview features.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "Sie haben {0} Vorschaufunktionen ausgewählt." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "Sie haben {0} Vorschaufunktionen ausgewählt."
-- The '{0}' configuration plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T2048783766"] = "Das Konfigurations-Plugin „{0}“ wurde erfolgreich entfernt."
-- Delete Configuration Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T3395354991"] = "Konfigurations-Plugin löschen"
-- The configuration plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T4140893414"] = "Das Konfigurations-Plugin „{0}“ konnte nicht gelöscht werden: {1}"
-- Delete configuration plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T459830575"] = "Konfigurations-Plugin löschen"
-- Preselected provider -- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Vorausgewählter Anbieter" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Vorausgewählter Anbieter"
@ -3294,6 +3282,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "L
-- Issues -- Issues
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Probleme" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Probleme"
-- Do you really want to delete the language plugin '{0}'? This permanently deletes its local plugin files. When it is your chosen language, AI Studio returns to choosing the language automatically.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T1744561175"] = "Möchten Sie das Sprach-Plugin „{0}“ wirklich löschen? Dadurch werden die lokalen Plugin-Dateien dauerhaft gelöscht. Wenn es Ihre ausgewählte Sprache ist, kehrt AI Studio zur automatischen Sprachauswahl zurück."
-- Delete language plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2707495447"] = "Sprach-Plugin löschen"
-- The plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2738963920"] = "Das Plugin „{0}“ konnte nicht gelöscht werden: {1}"
-- Delete Language Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2990518039"] = "Sprach-Plugin löschen"
-- Delete Configuration Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T3395354991"] = "Konfigurations-Plugin löschen"
-- The plugin '{0}' has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T3476138264"] = "Das Plugin „{0}“ wurde erfolgreich entfernt."
-- Delete configuration plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T459830575"] = "Konfigurations-Plugin löschen"
-- Your Pandoc installation meets the requirements. -- Your Pandoc installation meets the requirements.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Ihre Pandoc-Installation erfüllt die Anforderungen." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Ihre Pandoc-Installation erfüllt die Anforderungen."
@ -9807,24 +9816,21 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1195382910"]
-- The assistant plugin directory is outside the local assistant plugin directory. -- The assistant plugin directory is outside the local assistant plugin directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1211881977"] = "Das Assistenten-Plugin-Verzeichnis befindet sich außerhalb des lokalen Assistenten-Plugin-Verzeichnisses." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1211881977"] = "Das Assistenten-Plugin-Verzeichnis befindet sich außerhalb des lokalen Assistenten-Plugin-Verzeichnisses."
-- Only configuration plugins can be deleted this way.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1214012118"] = "Nur Konfigurations-Plugins können auf diese Weise gelöscht werden."
-- Only assistant plugins can be edited. -- Only assistant plugins can be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1288328479"] = "Nur Assistant-Plugins können bearbeitet werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1288328479"] = "Nur Assistant-Plugins können bearbeitet werden."
-- The assistant cannot be deleted while background work is still running. -- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1318944584"] = "Der Assistent kann nicht gelöscht werden, solange noch Hintergrundaktivitäten ausgeführt werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1318944584"] = "Der Assistent kann nicht gelöscht werden, solange noch Hintergrundaktivitäten ausgeführt werden."
-- Plugins deployed by your organization cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1348456011"] = "Von Ihrer Organisation bereitgestellte Plugins können nicht gelöscht werden."
-- The resolved plugin directory is outside the plugin directory. -- The resolved plugin directory is outside the plugin directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1559620698"] = "Das ermittelte Plugin-Verzeichnis befindet sich außerhalb des Plugin-Verzeichnisses." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1559620698"] = "Das ermittelte Plugin-Verzeichnis befindet sich außerhalb des Plugin-Verzeichnisses."
-- Please select a plugin archive with the extension .mwplugin or .zip. -- Please select a plugin archive with the extension .mwplugin or .zip.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1809137998"] = "Bitte wählen Sie ein Plugin-Archiv mit der Dateiendung .mwplugin oder .zip aus." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1809137998"] = "Bitte wählen Sie ein Plugin-Archiv mit der Dateiendung .mwplugin oder .zip aus."
-- Internal configuration plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1811106891"] = "Interne Konfigurations-Plugins können nicht gelöscht werden."
-- The selected plugin archive does not exist. -- The selected plugin archive does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1821013825"] = "Das ausgewählte Plugin-Archiv existiert nicht." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1821013825"] = "Das ausgewählte Plugin-Archiv existiert nicht."
@ -9837,36 +9843,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2134532120"]
-- The assistant plugin directory does not exist. -- The assistant plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2148384567"] = "Das Verzeichnis für das Assistenten-Plugin existiert nicht." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2148384567"] = "Das Verzeichnis für das Assistenten-Plugin existiert nicht."
-- The plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2221093487"] = "Das Plugin-Verzeichnis existiert nicht."
-- Unexpected error: {0} -- Unexpected error: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2350673880"] = "Unerwarteter Fehler: {0}" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2350673880"] = "Unerwarteter Fehler: {0}"
-- The generated assistant plugin uses the ID of another installed plugin. -- The generated assistant plugin uses the ID of another installed plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2441747251"] = "Das generierte Assistenten-Plugin verwendet die ID eines anderen installierten Plugins." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2441747251"] = "Das generierte Assistenten-Plugin verwendet die ID eines anderen installierten Plugins."
-- The configuration plugin directory is outside the plugins directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2613041269"] = "Das Konfigurations-Plugin-Verzeichnis befindet sich außerhalb des Plugin-Verzeichnisses."
-- The assistant plugin has no local directory. -- The assistant plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2682912892"] = "Das Assistenten-Plugin hat kein lokales Verzeichnis." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2682912892"] = "Das Assistenten-Plugin hat kein lokales Verzeichnis."
-- The AI Studio data directory is not initialized yet. -- The AI Studio data directory is not initialized yet.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2712481762"] = "Das Datenverzeichnis von AI Studio ist noch nicht initialisiert." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2712481762"] = "Das Datenverzeichnis von AI Studio ist noch nicht initialisiert."
-- Only configuration and language plugins can be deleted this way.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2850672167"] = "Nur Konfigurations- und Sprach-Plugins können auf diese Weise gelöscht werden."
-- Only assistant plugins can be deleted. -- Only assistant plugins can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2864597027"] = "Nur Assistant-Plugins können gelöscht werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2864597027"] = "Nur Assistant-Plugins können gelöscht werden."
-- Only assistant, configuration, and language plugins can be imported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2909113247"] = "Es können nur Assistenten-, Konfigurations- und Sprach-Plugins importiert werden."
-- The plugin directory is outside the plugins directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2911446827"] = "Das Plugin-Verzeichnis befindet sich außerhalb des Plugins-Verzeichnisses."
-- The generated plugin is not an assistant plugin. Issue: {0} -- The generated plugin is not an assistant plugin. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2955055168"] = "Das generierte Plugin ist kein Assistenten-Plugin. Problem: {0}" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2955055168"] = "Das generierte Plugin ist kein Assistenten-Plugin. Problem: {0}"
-- The configuration plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3073748821"] = "Das Konfigurations-Plugin-Verzeichnis existiert nicht."
-- Only assistant and configuration plugins can be imported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3166787637"] = "Es können nur Assistenten- und Konfigurations-Plugins importiert werden."
-- Your organization has disabled importing plugins. -- Your organization has disabled importing plugins.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3212529834"] = "Ihre Organisation hat das Importieren von Plugins deaktiviert." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3212529834"] = "Ihre Organisation hat das Importieren von Plugins deaktiviert."
-- The plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3284289028"] = "Das Plugin hat kein lokales Verzeichnis."
-- The plugin archive must contain exactly one plugin.lua file. -- The plugin archive must contain exactly one plugin.lua file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3355918609"] = "Das Plugin-Archiv muss genau eine plugin.lua-Datei enthalten." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3355918609"] = "Das Plugin-Archiv muss genau eine plugin.lua-Datei enthalten."
@ -9879,6 +9891,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3634046009"]
-- Config Server managed assistant plugins cannot be deleted. -- Config Server managed assistant plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3751820312"] = "Von einem Config-Server verwaltete Assistenten-Plugins können nicht gelöscht werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3751820312"] = "Von einem Config-Server verwaltete Assistenten-Plugins können nicht gelöscht werden."
-- Plugins shipped with AI Studio cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3841213017"] = "Mit AI Studio ausgelieferte Plugins können nicht gelöscht werden."
-- Only assistants generated by the Assistant Builder can be deleted. -- Only assistants generated by the Assistant Builder can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3940247198"] = "Nur mit dem Assistant Builder erstellte Assistenten können gelöscht werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3940247198"] = "Nur mit dem Assistant Builder erstellte Assistenten können gelöscht werden."
@ -9903,12 +9918,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T584770023"] =
-- The edited assistant plugin must keep the same plugin ID. -- The edited assistant plugin must keep the same plugin ID.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T693124809"] = "Das bearbeitete Assistant-Plugin muss dieselbe Plugin-ID beibehalten." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T693124809"] = "Das bearbeitete Assistant-Plugin muss dieselbe Plugin-ID beibehalten."
-- The configuration plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T719938122"] = "Das Konfigurations-Plugin hat kein lokales Verzeichnis."
-- Configuration plugins deployed by your organization cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T75764577"] = "Von Ihrer Organisation bereitgestellte Konfigurations-Plugins können nicht gelöscht werden."
-- Internal assistant plugins cannot be edited. -- Internal assistant plugins cannot be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T816339833"] = "Interne Assistenten-Plugins können nicht bearbeitet werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T816339833"] = "Interne Assistenten-Plugins können nicht bearbeitet werden."

View File

@ -3195,18 +3195,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] =
-- You have selected {0} preview features. -- You have selected {0} preview features.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features."
-- The '{0}' configuration plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T2048783766"] = "The '{0}' configuration plugin has been successfully removed."
-- Delete Configuration Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T3395354991"] = "Delete Configuration Plugin"
-- The configuration plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T4140893414"] = "The configuration plugin '{0}' could not be deleted: {1}"
-- Delete configuration plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPLUGINDELETEACTION::T459830575"] = "Delete configuration plugin"
-- Preselected provider -- Preselected provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider"
@ -3294,6 +3282,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "L
-- Issues -- Issues
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues"
-- Do you really want to delete the language plugin '{0}'? This permanently deletes its local plugin files. When it is your chosen language, AI Studio returns to choosing the language automatically.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T1744561175"] = "Do you really want to delete the language plugin '{0}'? This permanently deletes its local plugin files. When it is your chosen language, AI Studio returns to choosing the language automatically."
-- Delete language plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2707495447"] = "Delete language plugin"
-- The plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2738963920"] = "The plugin '{0}' could not be deleted: {1}"
-- Delete Language Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T2990518039"] = "Delete Language Plugin"
-- Delete Configuration Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T3395354991"] = "Delete Configuration Plugin"
-- The plugin '{0}' has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T3476138264"] = "The plugin '{0}' has been successfully removed."
-- Delete configuration plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::LOCALPLUGINDELETEACTION::T459830575"] = "Delete configuration plugin"
-- Your Pandoc installation meets the requirements. -- Your Pandoc installation meets the requirements.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T1167365374"] = "Your Pandoc installation meets the requirements."
@ -9807,24 +9816,21 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1195382910"]
-- The assistant plugin directory is outside the local assistant plugin directory. -- The assistant plugin directory is outside the local assistant plugin directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1211881977"] = "The assistant plugin directory is outside the local assistant plugin directory." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1211881977"] = "The assistant plugin directory is outside the local assistant plugin directory."
-- Only configuration plugins can be deleted this way.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1214012118"] = "Only configuration plugins can be deleted this way."
-- Only assistant plugins can be edited. -- Only assistant plugins can be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1288328479"] = "Only assistant plugins can be edited." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1288328479"] = "Only assistant plugins can be edited."
-- The assistant cannot be deleted while background work is still running. -- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1318944584"] = "The assistant cannot be deleted while background work is still running." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1318944584"] = "The assistant cannot be deleted while background work is still running."
-- Plugins deployed by your organization cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1348456011"] = "Plugins deployed by your organization cannot be deleted."
-- The resolved plugin directory is outside the plugin directory. -- The resolved plugin directory is outside the plugin directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1559620698"] = "The resolved plugin directory is outside the plugin directory." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1559620698"] = "The resolved plugin directory is outside the plugin directory."
-- Please select a plugin archive with the extension .mwplugin or .zip. -- Please select a plugin archive with the extension .mwplugin or .zip.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1809137998"] = "Please select a plugin archive with the extension .mwplugin or .zip." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1809137998"] = "Please select a plugin archive with the extension .mwplugin or .zip."
-- Internal configuration plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1811106891"] = "Internal configuration plugins cannot be deleted."
-- The selected plugin archive does not exist. -- The selected plugin archive does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1821013825"] = "The selected plugin archive does not exist." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T1821013825"] = "The selected plugin archive does not exist."
@ -9837,36 +9843,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2134532120"]
-- The assistant plugin directory does not exist. -- The assistant plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2148384567"] = "The assistant plugin directory does not exist." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2148384567"] = "The assistant plugin directory does not exist."
-- The plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2221093487"] = "The plugin directory does not exist."
-- Unexpected error: {0} -- Unexpected error: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2350673880"] = "Unexpected error: {0}" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2350673880"] = "Unexpected error: {0}"
-- The generated assistant plugin uses the ID of another installed plugin. -- The generated assistant plugin uses the ID of another installed plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2441747251"] = "The generated assistant plugin uses the ID of another installed plugin." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2441747251"] = "The generated assistant plugin uses the ID of another installed plugin."
-- The configuration plugin directory is outside the plugins directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2613041269"] = "The configuration plugin directory is outside the plugins directory."
-- The assistant plugin has no local directory. -- The assistant plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2682912892"] = "The assistant plugin has no local directory." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2682912892"] = "The assistant plugin has no local directory."
-- The AI Studio data directory is not initialized yet. -- The AI Studio data directory is not initialized yet.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2712481762"] = "The AI Studio data directory is not initialized yet." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2712481762"] = "The AI Studio data directory is not initialized yet."
-- Only configuration and language plugins can be deleted this way.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2850672167"] = "Only configuration and language plugins can be deleted this way."
-- Only assistant plugins can be deleted. -- Only assistant plugins can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2864597027"] = "Only assistant plugins can be deleted." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2864597027"] = "Only assistant plugins can be deleted."
-- Only assistant, configuration, and language plugins can be imported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2909113247"] = "Only assistant, configuration, and language plugins can be imported."
-- The plugin directory is outside the plugins directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2911446827"] = "The plugin directory is outside the plugins directory."
-- The generated plugin is not an assistant plugin. Issue: {0} -- The generated plugin is not an assistant plugin. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2955055168"] = "The generated plugin is not an assistant plugin. Issue: {0}" UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T2955055168"] = "The generated plugin is not an assistant plugin. Issue: {0}"
-- The configuration plugin directory does not exist.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3073748821"] = "The configuration plugin directory does not exist."
-- Only assistant and configuration plugins can be imported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3166787637"] = "Only assistant and configuration plugins can be imported."
-- Your organization has disabled importing plugins. -- Your organization has disabled importing plugins.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3212529834"] = "Your organization has disabled importing plugins." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3212529834"] = "Your organization has disabled importing plugins."
-- The plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3284289028"] = "The plugin has no local directory."
-- The plugin archive must contain exactly one plugin.lua file. -- The plugin archive must contain exactly one plugin.lua file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3355918609"] = "The plugin archive must contain exactly one plugin.lua file." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3355918609"] = "The plugin archive must contain exactly one plugin.lua file."
@ -9879,6 +9891,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3634046009"]
-- Config Server managed assistant plugins cannot be deleted. -- Config Server managed assistant plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3751820312"] = "Config Server managed assistant plugins cannot be deleted." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3751820312"] = "Config Server managed assistant plugins cannot be deleted."
-- Plugins shipped with AI Studio cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3841213017"] = "Plugins shipped with AI Studio cannot be deleted."
-- Only assistants generated by the Assistant Builder can be deleted. -- Only assistants generated by the Assistant Builder can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3940247198"] = "Only assistants generated by the Assistant Builder can be deleted." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T3940247198"] = "Only assistants generated by the Assistant Builder can be deleted."
@ -9903,12 +9918,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T584770023"] =
-- The edited assistant plugin must keep the same plugin ID. -- The edited assistant plugin must keep the same plugin ID.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T693124809"] = "The edited assistant plugin must keep the same plugin ID." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T693124809"] = "The edited assistant plugin must keep the same plugin ID."
-- The configuration plugin has no local directory.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T719938122"] = "The configuration plugin has no local directory."
-- Configuration plugins deployed by your organization cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T75764577"] = "Configuration plugins deployed by your organization cannot be deleted."
-- Internal assistant plugins cannot be edited. -- Internal assistant plugins cannot be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T816339833"] = "Internal assistant plugins cannot be edited." UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PLUGININSTALLSERVICE::T816339833"] = "Internal assistant plugins cannot be edited."

View File

@ -1,4 +1,5 @@
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Media; using AIStudio.Tools.Media;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants; using AIStudio.Tools.PluginSystem.Assistants;
@ -13,9 +14,18 @@ public sealed partial class PluginInstallService
public static bool CanDeleteInstalledAssistant(IAvailablePlugin plugin) => string.IsNullOrWhiteSpace(GetAssistantDeletionEligibilityIssue(plugin)); public static bool CanDeleteInstalledAssistant(IAvailablePlugin plugin) => string.IsNullOrWhiteSpace(GetAssistantDeletionEligibilityIssue(plugin));
/// <summary> /// <summary>
/// Checks whether a plugin is a local configuration plugin that users may delete. /// The plugin types users may remove through the user interface, besides their own assistants.
/// </summary> /// </summary>
public static bool CanDeleteInstalledConfiguration(IAvailablePlugin plugin) => string.IsNullOrWhiteSpace(GetConfigurationDeletionEligibilityIssue(plugin)); /// <remarks>
/// Assistants have their own path, because only those generated by the Assistant Builder may be
/// deleted and because a running assistant must not be pulled away from under a user.
/// </remarks>
private static readonly PluginType[] DELETABLE_LOCAL_PLUGIN_TYPES = [PluginType.CONFIGURATION, PluginType.LANGUAGE];
/// <summary>
/// Checks whether a plugin is a local configuration or language plugin that users may delete.
/// </summary>
public static bool CanDeleteLocalPlugin(IAvailablePlugin plugin) => string.IsNullOrWhiteSpace(GetLocalPluginDeletionEligibilityIssue(plugin));
/// <summary> /// <summary>
/// Collects what deleting a local configuration plugin removes besides the plugin directory. /// Collects what deleting a local configuration plugin removes besides the plugin directory.
@ -140,54 +150,69 @@ public sealed partial class PluginInstallService
} }
/// <summary> /// <summary>
/// Deletes a local configuration plugin directory. /// Deletes a local configuration or language plugin directory.
/// The directory gets moved to a backup dir outside the plugin root so the plugin loader cannot /// The directory gets moved to a backup dir outside the plugin root so the plugin loader cannot
/// discover it during reload. On failure, the directory is restored. /// discover it during reload. On failure, the directory is restored.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// We do not remove the providers, data sources, chat templates, profiles, or locked settings of /// For a configuration plugin, we do not remove its providers, data sources, chat templates,
/// the plugin ourselves. The reload does that: it recognizes them as left over once their /// profiles, or locked settings ourselves. The reload does that: it recognizes them as left over
/// configuration plugin is gone, and it also deletes the related secrets from the OS keyring. /// once their configuration plugin is gone, and it also deletes the related secrets from the OS
/// keyring.
/// </remarks> /// </remarks>
/// <param name="plugin">Configuration plugin metadata.</param> /// <param name="plugin">Plugin metadata of the configuration or language plugin.</param>
/// <param name="token">Cancellation token for the plugin reload.</param> /// <param name="token">Cancellation token for settings storage and plugin reload.</param>
/// <returns> /// <returns>
/// Delete result that contains success state, deleted plugin metadata, the original plugin directory, /// Delete result that contains success state, deleted plugin metadata, the original plugin directory,
/// and a user-facing issue when deletion failed. /// and a user-facing issue when deletion failed.
/// </returns> /// </returns>
public async Task<PluginDeleteResult> DeleteInstalledConfigurationAsync(IAvailablePlugin plugin, CancellationToken token) public async Task<PluginDeleteResult> DeleteLocalPluginAsync(IAvailablePlugin plugin, CancellationToken token)
{ {
var eligibilityIssue = GetConfigurationDeletionEligibilityIssue(plugin); var eligibilityIssue = GetLocalPluginDeletionEligibilityIssue(plugin);
if (!string.IsNullOrEmpty(eligibilityIssue)) if (!string.IsNullOrEmpty(eligibilityIssue))
return DeleteError(plugin, plugin.LocalPath, eligibilityIssue); return DeleteError(plugin, plugin.LocalPath, eligibilityIssue);
await this.installSemaphore.WaitAsync(token); await this.installSemaphore.WaitAsync(token);
var pluginDirectory = plugin.LocalPath; var pluginDirectory = plugin.LocalPath;
var backupDirectory = string.Empty; var backupDirectory = string.Empty;
var wasChosenLanguage = false;
try try
{ {
// Check again under the semaphore: another operation might have changed the plugin state // Check again under the semaphore: another operation might have changed the plugin state
// while we were waiting: // while we were waiting:
eligibilityIssue = GetConfigurationDeletionEligibilityIssue(plugin); eligibilityIssue = GetLocalPluginDeletionEligibilityIssue(plugin);
if (!string.IsNullOrEmpty(eligibilityIssue)) if (!string.IsNullOrEmpty(eligibilityIssue))
return DeleteError(plugin, pluginDirectory, eligibilityIssue); return DeleteError(plugin, pluginDirectory, eligibilityIssue);
backupDirectory = CreateDeleteBackupDirectory(plugin, "configuration"); backupDirectory = CreateDeleteBackupDirectory(plugin, plugin.Type is PluginType.LANGUAGE ? "language" : "configuration");
Directory.CreateDirectory(Path.GetDirectoryName(backupDirectory)!); Directory.CreateDirectory(Path.GetDirectoryName(backupDirectory)!);
Directory.Move(pluginDirectory, backupDirectory); Directory.Move(pluginDirectory, backupDirectory);
//
// When the user had chosen this language plugin, the app would silently fall back to
// English while the settings still point to the deleted plugin. We return the language
// choice to automatic instead, so the settings stay truthful:
//
wasChosenLanguage = plugin.Type is PluginType.LANGUAGE && this.settingsManager.ConfigurationData.App.LanguagePluginId == plugin.Id;
if (wasChosenLanguage)
{
this.settingsManager.ConfigurationData.App.LanguageBehavior = LangBehavior.AUTO;
this.settingsManager.ConfigurationData.App.LanguagePluginId = Guid.Empty;
await this.settingsManager.StoreSettings();
}
await PluginFactory.LoadAll(token); await PluginFactory.LoadAll(token);
TryDeleteDirectory(backupDirectory, "configuration plugin delete backup", this.logger); TryDeleteDirectory(backupDirectory, "local plugin delete backup", this.logger);
this.logger.LogInformation($"Deleted configuration plugin '{plugin.Name}' ({plugin.Id}) from '{pluginDirectory}'."); this.logger.LogInformation($"Deleted {plugin.Type} plugin '{plugin.Name}' ({plugin.Id}) from '{pluginDirectory}'.");
return new(true, plugin.Id, plugin.Name, pluginDirectory, string.Empty); return new(true, plugin.Id, plugin.Name, pluginDirectory, string.Empty);
} }
catch (Exception e) catch (Exception e)
{ {
this.logger.LogError(e, $"Failed to delete configuration plugin '{plugin.Name}' ({plugin.Id}) from '{pluginDirectory}'."); this.logger.LogError(e, $"Failed to delete {plugin.Type} plugin '{plugin.Name}' ({plugin.Id}) from '{pluginDirectory}'.");
await this.TryRestoreDeletedConfigurationPluginAsync(plugin, pluginDirectory, backupDirectory, token); await this.TryRestoreDeletedLocalPluginAsync(plugin, pluginDirectory, backupDirectory, wasChosenLanguage, token);
return DeleteError(plugin, pluginDirectory, string.Format(TB("Unexpected error: {0}"), e.Message)); return DeleteError(plugin, pluginDirectory, string.Format(TB("Unexpected error: {0}"), e.Message));
} }
finally finally
@ -231,16 +256,16 @@ public sealed partial class PluginInstallService
: TB("The assistant plugin directory does not exist."); : TB("The assistant plugin directory does not exist.");
} }
private static string GetConfigurationDeletionEligibilityIssue(IAvailablePlugin plugin) private static string GetLocalPluginDeletionEligibilityIssue(IAvailablePlugin plugin)
{ {
if (plugin.Type is not PluginType.CONFIGURATION) if (!DELETABLE_LOCAL_PLUGIN_TYPES.Contains(plugin.Type))
return TB("Only configuration plugins can be deleted this way."); return TB("Only configuration and language plugins can be deleted this way.");
if (plugin.IsInternal) if (plugin.IsInternal)
return TB("Internal configuration plugins cannot be deleted."); return TB("Plugins shipped with AI Studio cannot be deleted.");
if (string.IsNullOrWhiteSpace(plugin.LocalPath)) if (string.IsNullOrWhiteSpace(plugin.LocalPath))
return TB("The configuration plugin has no local directory."); return TB("The plugin has no local directory.");
// //
// We decide by the plugin path, not by IsManagedByConfigServer. That value comes from the // We decide by the plugin path, not by IsManagedByConfigServer. That value comes from the
@ -249,14 +274,14 @@ public sealed partial class PluginInstallService
// is exactly the situation this deletion is meant to resolve. // is exactly the situation this deletion is meant to resolve.
// //
if (PluginFactory.IsEnterpriseConfigurationPath(plugin.LocalPath)) if (PluginFactory.IsEnterpriseConfigurationPath(plugin.LocalPath))
return TB("Configuration plugins deployed by your organization cannot be deleted."); return TB("Plugins deployed by your organization cannot be deleted.");
if (!PluginFactory.IsInsidePluginsRoot(plugin.LocalPath)) if (!PluginFactory.IsInsidePluginsRoot(plugin.LocalPath))
return TB("The configuration plugin directory is outside the plugins directory."); return TB("The plugin directory is outside the plugins directory.");
return Directory.Exists(plugin.LocalPath) return Directory.Exists(plugin.LocalPath)
? string.Empty ? string.Empty
: TB("The configuration plugin directory does not exist."); : TB("The plugin directory does not exist.");
} }
private static string CreateDeleteBackupDirectory(IAvailablePlugin plugin, string pluginKind) private static string CreateDeleteBackupDirectory(IAvailablePlugin plugin, string pluginKind)
@ -290,19 +315,26 @@ public sealed partial class PluginInstallService
} }
} }
private async Task TryRestoreDeletedConfigurationPluginAsync(IAvailablePlugin plugin, string pluginDirectory, string backupDirectory, CancellationToken token) private async Task TryRestoreDeletedLocalPluginAsync(IAvailablePlugin plugin, string pluginDirectory, string backupDirectory, bool wasChosenLanguage, CancellationToken token)
{ {
try try
{ {
if (!Directory.Exists(pluginDirectory) && Directory.Exists(backupDirectory)) if (!Directory.Exists(pluginDirectory) && Directory.Exists(backupDirectory))
Directory.Move(backupDirectory, pluginDirectory); Directory.Move(backupDirectory, pluginDirectory);
if (wasChosenLanguage)
{
this.settingsManager.ConfigurationData.App.LanguageBehavior = LangBehavior.MANUAL;
this.settingsManager.ConfigurationData.App.LanguagePluginId = plugin.Id;
await this.settingsManager.StoreSettings();
}
// The reload restores everything the plugin configured, because it is back in place: // The reload restores everything the plugin configured, because it is back in place:
await PluginFactory.LoadAll(token); await PluginFactory.LoadAll(token);
} }
catch (Exception restoreException) catch (Exception restoreException)
{ {
this.logger.LogError(restoreException, $"Failed to restore configuration plugin '{plugin.Name}' ({plugin.Id}) after a failed delete."); this.logger.LogError(restoreException, $"Failed to restore {plugin.Type} plugin '{plugin.Name}' ({plugin.Id}) after a failed delete.");
} }
} }
} }

View File

@ -10,7 +10,7 @@ public sealed partial class PluginInstallService
/// <summary> /// <summary>
/// The plugin types a user may import from an archive. /// The plugin types a user may import from an archive.
/// </summary> /// </summary>
private static readonly PluginType[] IMPORTABLE_PLUGIN_TYPES = [PluginType.ASSISTANT, PluginType.CONFIGURATION]; private static readonly PluginType[] IMPORTABLE_PLUGIN_TYPES = [PluginType.ASSISTANT, PluginType.CONFIGURATION, PluginType.LANGUAGE];
/// <summary> /// <summary>
/// Installs a plugin archive that contains exactly one <c>plugin.lua</c> file. /// Installs a plugin archive that contains exactly one <c>plugin.lua</c> file.
@ -55,7 +55,7 @@ public sealed partial class PluginInstallService
pluginDirectory, pluginDirectory,
pluginCode.Trim(), pluginCode.Trim(),
IMPORTABLE_PLUGIN_TYPES, IMPORTABLE_PLUGIN_TYPES,
TB("Only assistant and configuration plugins can be imported."), TB("Only assistant, configuration, and language plugins can be imported."),
TB("The imported plugin is invalid. Issue: {0}"), TB("The imported plugin is invalid. Issue: {0}"),
TB("The imported plugin uses the ID of another installed plugin."), TB("The imported plugin uses the ID of another installed plugin."),
token); token);

View File

@ -8,7 +8,8 @@
- Added an option for organizations to disable importing, sharing, and exporting plugins. - Added an option for organizations to disable importing, sharing, and exporting plugins.
- Added the option to import configuration plugins from a file. Before installing, AI Studio shows what the configuration sets up: which LLM providers and data sources it adds and where each of them sends your data, plus how many settings it takes control of. A configuration takes effect right away and has no on/off switch, so please install one only when you trust its source. You can remove it again at any time. - Added the option to import configuration plugins from a file. Before installing, AI Studio shows what the configuration sets up: which LLM providers and data sources it adds and where each of them sends your data, plus how many settings it takes control of. A configuration takes effect right away and has no on/off switch, so please install one only when you trust its source. You can remove it again at any time.
- Added a separate option for organizations to disable importing configuration plugins. Organizations can now let people import assistants while keeping configurations to their IT department. - Added a separate option for organizations to disable importing configuration plugins. Organizations can now let people import assistants while keeping configurations to their IT department.
- Added a delete button for configuration plugins you placed yourself. Until now, such a plugin could only be removed from the data directory by hand, because configuration plugins have no on/off switch. Before deleting, AI Studio lists what disappears with it, such as providers, data sources, and settings that return to their default. Configurations deployed by your IT department cannot be deleted. - Added sharing and importing for language plugins. When you work on a translation for a new language, you can now hand your current state to testers or to us with one click, and they can install it just as easily.
- Added a delete button for configuration and language plugins you installed or placed yourself. Until now, such a plugin could only be removed from the data directory by hand, which was especially painful for configurations, because they have no on/off switch. Before deleting a configuration, AI Studio lists what disappears with it, such as providers, data sources, and settings that return to their default. Plugins shipped with AI Studio and plugins deployed by your IT department cannot be deleted.
- Added a priority for configuration plugins. Organizations that deploy several configurations can now decide which one wins: a configuration with a higher priority overrides the settings and providers of a lower one. This allows a company-wide base configuration that each department refines for itself. - Added a priority for configuration plugins. Organizations that deploy several configurations can now decide which one wins: a configuration with a higher priority overrides the settings and providers of a lower one. This allows a company-wide base configuration that each department refines for itself.
- Improved how your organization's configuration behaves when a configuration plugin is present but cannot be loaded, e.g. because of an error in the plugin. Such a plugin still manages your app, so its settings, providers, data sources, profiles, and chat templates now stay in place instead of being removed. - Improved how your organization's configuration behaves when a configuration plugin is present but cannot be loaded, e.g. because of an error in the plugin. Such a plugin still manages your app, so its settings, providers, data sources, profiles, and chat templates now stay in place instead of being removed.
- Changed how approvals for assistant plugins combine when your organization deploys several configurations. They now add up, so a department can approve additional assistant plugins without repeating the approvals of the company-wide configuration. Previously, the last configuration replaced all earlier approvals, which silently required a new security check for those assistants. - Changed how approvals for assistant plugins combine when your organization deploys several configurations. They now add up, so a department can approve additional assistant plugins without repeating the approvals of the company-wide configuration. Previously, the last configuration replaced all earlier approvals, which silently required a new security check for those assistants.