Add hash-based acceptance to track content changes

This commit is contained in:
Thorsten Sommer 2026-04-10 16:31:13 +02:00
parent 5e38603fea
commit 277cc01cbf
No known key found for this signature in database
GPG Key ID: B0B7E2FC074BF1F5
6 changed files with 61 additions and 7 deletions

View File

@ -7,18 +7,28 @@
@if (this.ShowAcceptanceMetadata)
{
@if (this.Acceptance is null)
@if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.MISSING)
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
@T("This mandatory info has not been accepted yet.")
</MudAlert>
}
else if (!string.Equals(this.Acceptance.AcceptedVersion, this.Info.VersionText, StringComparison.Ordinal))
else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.VERSION_CHANGED)
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
@T("The current version has not been accepted yet.")
@T("A new version of the terms is available. Please review it again.")
<br />
@T("Last accepted version"): @this.Acceptance.AcceptedVersion
@T("Last accepted version"): @this.Acceptance!.AcceptedVersion
<br />
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
</MudAlert>
}
else if (this.AcceptanceStatus is MandatoryInfoAcceptanceStatus.CONTENT_CHANGED)
{
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="@true">
@T("Please review this text again. The content was changed.")
<br />
@T("Last accepted version"): @this.Acceptance!.AcceptedVersion
<br />
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
</MudAlert>
@ -26,7 +36,7 @@
else
{
<MudAlert Severity="Severity.Success" Variant="Variant.Outlined" Dense="@true">
@T("Accepted version"): @this.Acceptance.AcceptedVersion
@T("Accepted version"): @this.Acceptance!.AcceptedVersion
<br />
@T("Accepted at (UTC)"): @this.Acceptance.AcceptedAtUtc.UtcDateTime.ToString("u")
</MudAlert>

View File

@ -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;
}
}
}

View File

@ -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,
};

View File

@ -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:

View File

@ -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;
/// <summary>
/// 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.
/// </summary>
public string VersionText { get; private init; } = string.Empty;
@ -41,6 +45,13 @@ public sealed record DataMandatoryInfo
/// </summary>
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();

View File

@ -12,6 +12,11 @@ public sealed record DataMandatoryInfoAcceptance
/// </summary>
public string AcceptedVersion { get; init; } = string.Empty;
/// <summary>
/// The accepted hash of the mandatory info content.
/// </summary>
public string AcceptedHash { get; init; } = string.Empty;
/// <summary>
/// The UTC time of the acceptance.
/// </summary>