From 277cc01cbf81bde9825c6f385f81e5846b649267 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 10 Apr 2026 16:31:13 +0200 Subject: [PATCH] Add hash-based acceptance to track content changes --- .../Components/MandatoryInfoDisplay.razor | 20 +++++++++++---- .../Components/MandatoryInfoDisplay.razor.cs | 25 +++++++++++++++++++ .../Layout/MainLayout.razor.cs | 3 ++- .../Plugins/configuration/plugin.lua | 2 ++ .../Settings/DataModel/DataMandatoryInfo.cs | 13 +++++++++- .../DataModel/DataMandatoryInfoAcceptance.cs | 5 ++++ 6 files changed, 61 insertions(+), 7 deletions(-) diff --git a/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor b/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor index 8c53f702..24d529ab 100644 --- a/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor +++ b/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor @@ -7,18 +7,28 @@ @if (this.ShowAcceptanceMetadata) { - @if (this.Acceptance is null) + @if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.MISSING) { @T("This mandatory info has not been accepted yet.") } - else if (!string.Equals(this.Acceptance.AcceptedVersion, this.Info.VersionText, StringComparison.Ordinal)) + else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.VERSION_CHANGED) { - @T("The current version has not been accepted yet.") + @T("A new version of the terms is available. Please review it again.")
- @T("Last accepted version"): @this.Acceptance.AcceptedVersion + @T("Last accepted version"): @this.Acceptance!.AcceptedVersion +
+ @T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u") +
+ } + else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.CONTENT_CHANGED) + { + + @T("Please review this text again. The content was changed.") +
+ @T("Last accepted version"): @this.Acceptance!.AcceptedVersion
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
@@ -26,7 +36,7 @@ else { - @T("Accepted version"): @this.Acceptance.AcceptedVersion + @T("Accepted version"): @this.Acceptance!.AcceptedVersion
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
diff --git a/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor.cs b/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor.cs index fa69c994..a9d97cde 100644 --- a/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor.cs +++ b/app/MindWork AI Studio/Components/MandatoryInfoDisplay.razor.cs @@ -6,6 +6,14 @@ namespace AIStudio.Components; public partial class MandatoryInfoDisplay { + private enum MandatoryInfoAcceptanceStatus + { + MISSING, + VERSION_CHANGED, + CONTENT_CHANGED, + ACCEPTED, + } + [Parameter] public DataMandatoryInfo Info { get; set; } = new(); @@ -14,4 +22,21 @@ public partial class MandatoryInfoDisplay [Parameter] public bool ShowAcceptanceMetadata { get; set; } + + private MandatoryInfoAcceptanceStatus AcceptanceStatus + { + get + { + if (this.Acceptance is null) + return MandatoryInfoAcceptanceStatus.MISSING; + + if (!string.Equals(this.Acceptance.AcceptedVersion, this.Info.VersionText, StringComparison.Ordinal)) + return MandatoryInfoAcceptanceStatus.VERSION_CHANGED; + + if (!string.Equals(this.Acceptance.AcceptedHash, this.Info.GetAcceptanceHash(), StringComparison.Ordinal)) + return MandatoryInfoAcceptanceStatus.CONTENT_CHANGED; + + return MandatoryInfoAcceptanceStatus.ACCEPTED; + } + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index da13385d..3d8d6b2a 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -418,7 +418,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan .Where(info => { var acceptance = this.SettingsManager.ConfigurationData.MandatoryInformation.FindAcceptance(info.Id); - return acceptance is null || acceptance.AcceptedVersion != info.VersionText; + return acceptance is null || !string.Equals(acceptance.AcceptedHash, info.GetAcceptanceHash(), StringComparison.Ordinal); }); } @@ -441,6 +441,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan { InfoId = info.Id, AcceptedVersion = info.VersionText, + AcceptedHash = info.GetAcceptanceHash(), AcceptedAtUtc = DateTimeOffset.UtcNow, EnterpriseConfigurationPluginId = info.EnterpriseConfigurationPluginId, }; diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 3faa4c82..552d6462 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -267,6 +267,8 @@ CONFIG["CHAT_TEMPLATES"] = {} CONFIG["DOCUMENT_ANALYSIS_POLICIES"] = {} -- Mandatory infos that users must explicitly accept before using AI Studio: +-- AI Studio asks users again when Version, Title, or Markdown change. +-- Changing Version additionally allows the UI to communicate that a new version is available. CONFIG["MANDATORY_INFOS"] = {} -- An example mandatory info: diff --git a/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs b/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs index 167f9dc3..a633e2d3 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs @@ -1,3 +1,6 @@ +using System.Security.Cryptography; +using System.Text; + using Lua; namespace AIStudio.Settings.DataModel; @@ -22,7 +25,8 @@ public sealed record DataMandatoryInfo public string Title { get; private init; } = string.Empty; /// - /// The configured version string. When it changes, the user must accept the text again. + /// The configured version string shown to the user. A changed version triggers re-acceptance + /// and allows the UI to distinguish a new version from a content-only change. /// public string VersionText { get; private init; } = string.Empty; @@ -41,6 +45,13 @@ public sealed record DataMandatoryInfo /// public string RejectButtonText { get; private init; } = string.Empty; + public string GetAcceptanceHash() + { + var content = $"Version:{this.VersionText}\nTitle:{this.Title}\nMarkdown:{this.Markdown}"; + var hash = SHA256.HashData(Encoding.UTF8.GetBytes(content)); + return Convert.ToHexString(hash); + } + public static bool TryParseConfiguration(int idx, LuaTable table, Guid configPluginId, out DataMandatoryInfo mandatoryInfo) { mandatoryInfo = new DataMandatoryInfo(); diff --git a/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfoAcceptance.cs b/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfoAcceptance.cs index 765816c7..e24969d0 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfoAcceptance.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfoAcceptance.cs @@ -12,6 +12,11 @@ public sealed record DataMandatoryInfoAcceptance /// public string AcceptedVersion { get; init; } = string.Empty; + /// + /// The accepted hash of the mandatory info content. + /// + public string AcceptedHash { get; init; } = string.Empty; + /// /// The UTC time of the acceptance. ///