From a2afd69af28cee7ff7404c9636a88ea310b605c3 Mon Sep 17 00:00:00 2001 From: j-erler Date: Sat, 26 Sep 2026 01:09:54 +0200 Subject: [PATCH] Added organization-wide management for data backup and restore Co-Authored-By: Claude Opus 5.5 --- .../Settings/SettingsPanelDataBackup.razor | 8 ++---- .../Settings/SettingsPanelDataBackup.razor.cs | 28 +++++++++++++++++-- .../Plugins/configuration/plugin.lua | 11 ++++++++ .../Settings/DataModel/DataApp.cs | 18 ++++++++++++ .../Tools/PluginSystem/PluginConfiguration.cs | 6 ++++ .../wwwroot/changelog/v26.9.1.md | 1 + 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor index 6a4b12ea..f10066e2 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor @@ -81,9 +81,7 @@ } else { - - @string.Format(T("Export {0} chats"), this.SelectedChatCount) - + } } @@ -133,9 +131,7 @@ - - @T("Choose archive") - + @if (!string.IsNullOrWhiteSpace(this.selectedArchivePath)) { diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor.cs index 3fa6163d..d0c2b43c 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataBackup.razor.cs @@ -53,7 +53,18 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase private int SelectedChatCount => this.workspaceSelections.Where(selection => selection.IsSelected).Sum(selection => selection.ChatCount) + (this.includeTemporaryChats ? this.temporaryChatCount : 0); - private bool CanImport => this.importPreview is { Success: true, Manifest.TotalChatCount: > 0 }; + private bool AllowChatExport => this.SettingsManager.ConfigurationData.App.AllowUserToExportChats; + + private bool AllowChatImport => this.SettingsManager.ConfigurationData.App.AllowUserToImportChats; + + /// + /// Whether the chosen archive can be imported now. + /// + /// + /// The permission is asked here again and not only when choosing the archive: a + /// configuration plugin may take it away while a chosen archive waits to be imported. + /// + private bool CanImport => this.importPreview is { Success: true, Manifest.TotalChatCount: > 0 } && this.AllowChatImport; private double ProgressPercentage => this.progress.TotalChats is 0 ? 0d : this.progress.ProcessedChats * 100d / this.progress.TotalChats; @@ -159,7 +170,7 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase private async Task StartExportAsync() { - if (this.isBusy || !this.IsAnythingSelected) + if (this.isBusy || !this.IsAnythingSelected || !this.AllowChatExport) return; this.isBusy = true; @@ -184,6 +195,9 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase this.progress = new(0, this.SelectedChatCount); this.cancellationTokenSource = new(); + // Show the progress and the cancel button before the first chat is written: + await this.InvokeAsync(this.StateHasChanged); + // Compressing on the UI thread would keep the progress bar and the cancel // button from updating for the whole run: var token = this.cancellationTokenSource.Token; @@ -199,6 +213,10 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase this.isProcessing = false; this.DisposeCancellation(); this.isBusy = false; + + // The lockable button calls this as a plain function, not as an event of this + // component, so nothing renders the result unless we do: + await this.InvokeAsync(this.StateHasChanged); } } @@ -243,7 +261,7 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase private async Task ChooseArchiveAsync() { - if (this.isBusy) + if (this.isBusy || !this.AllowChatImport) return; this.isBusy = true; @@ -260,6 +278,10 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase finally { this.isBusy = false; + + // The lockable button calls this as a plain function, not as an event of this + // component, so nothing renders the preview unless we do: + await this.InvokeAsync(this.StateHasChanged); } } diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index c7f3d6a0..022ee5d0 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -411,6 +411,17 @@ CONFIG["SETTINGS"] = {} -- On Linux, this button exports the plugin archive instead of using a native share sheet. -- CONFIG["SETTINGS"]["DataApp.AllowUserToSharePlugins"] = false +-- Configure the user permission to export chats into an archive file, which the data backup +-- in the settings writes. When set to false, the export button stays visible but is disabled. +-- Chats often hold confidential content, and an archive is a single file which can leave the +-- computer on any stick or cloud drive. +-- CONFIG["SETTINGS"]["DataApp.AllowUserToExportChats"] = false + +-- Configure the user permission to import chats from an archive file. When set to false, the +-- button to choose an archive stays visible but is disabled. An archive brings along the +-- locations of the documents its chats name, and those are read again when a chat continues. +-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportChats"] = false + -- Configure whether administration settings are visible in the UI: -- CONFIG["SETTINGS"]["DataApp.ShowAdminSettings"] = true diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs index fd485d76..b0d41651 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs @@ -201,6 +201,24 @@ public sealed class DataApp(Expression>? configSelection = n /// public bool AllowUserToSharePlugins { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToSharePlugins, true); + /// + /// Should the user be allowed to export chats into an archive file? + /// + /// + /// Chats often hold confidential content, and an archive is a single file which can leave + /// the computer on any stick or cloud drive. + /// + public bool AllowUserToExportChats { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToExportChats, true); + + /// + /// Should the user be allowed to import chats from an archive file? + /// + /// + /// An archive brings along everything its chats name, including the locations of attached + /// documents, which are read again when such a chat continues. + /// + public bool AllowUserToImportChats { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportChats, true); + /// /// Should administration settings be visible in the UI? /// diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index ea02f556..aa2f1bbf 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -259,6 +259,12 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: allow the user to share or export plugins? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToSharePlugins, this.Id, settingsTable, dryRun); + // Config: allow the user to export chats into an archive? + ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToExportChats, this.Id, settingsTable, dryRun); + + // Config: allow the user to import chats from an archive? + ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportChats, this.Id, settingsTable, dryRun); + // Config: show administration settings? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowAdminSettings, 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 016c788b..aa514de0 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md @@ -43,6 +43,7 @@ - Added data backup and restore for your chats to the settings. You write the chats of the workspaces you choose into a single archive file, which you can keep as a backup or take to another computer, and you import that file again whenever you need your chats back. - Added chat backups that leave your attached documents where they are. When you back up chats in the settings, documents such as PDF or Word files are not copied into the archive. Your chats keep the place where each document is stored, so the documents stay usable wherever that place can be reached. - Added transcripts to your chat backups when you want them. When you back up chats in the settings which hold transcripts of audio or video files, AI Studio asks whether the transcripts go into the archive, because they exist nowhere else. +- Added organization-wide management for chat backups. IT departments can prevent you and your colleagues from exporting chats into an archive file, from importing chats from one, or both. - Improved loading web content in the assistants: it now uses the same reader as the Read Web Page tool, which extracts the main content of a page more reliably and skips navigation and boilerplate. Pages from your own network, including local servers, keep working as before. When a page cannot be read, AI Studio now says why instead of leaving the field empty. - Improved the app icon. The previous one was generated by an image model; the new one was created based on it and keeps the familiar green landscape with the chat bubble. Because it is now a vector drawing, it stays sharp everywhere it appears: in your taskbar or dock, in the window list, and on the start screen while AI Studio is loading. - Improved how AI Studio works out what a model can do. Every model family now stands on its own, together with the page it was read from, and our build refuses rules which contradict each other or name no source. That way, mistakes are caught before they ever reach you.