Added an optional notice for AI-generated content (#884)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Sabrina-devopsandThorsten Sommer authored and GitHub committed 2026-10-16 20:42:04 +02:00
1 parent f5a257270e
commit 249fc778a2
26 files changed
+406 -46

No files matched your search

@@ -123,13 +123,27 @@ public partial class ContentBlockComponent : MSGComponentBase
/// </remarks>
[Parameter]
public string? ExportFileName { get; set; }
/// <summary>
/// Whether copying or exporting an AI answer of this block ends it with the notice that AI
/// generated it, when that is switched on.
/// </summary>
/// <remarks>
/// An assistant whose answer is meant for another model rather than for a reader, such as the
/// prompt optimizer, turns this off, the same way as for its "Copy result" button.
/// </remarks>
[Parameter]
public bool AddsAIGeneratedContentDisclosure { get; set; } = true;
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private RustService RustService { get; init; } = null!;
[Inject]
private AIGeneratedContentDisclosureService DisclosureService { get; init; } = null!;
[Inject]
private IJSRuntime JsRuntime { get; init; } = null!;
@@ -165,6 +179,11 @@ public partial class ContentBlockComponent : MSGComponentBase
/// </remarks>
private bool CanExport => this.Content is { InitialRemoteWait: false, IsStreaming: false } && this.Content.TryGetMarkdownText(out _);
/// <summary>
/// Whether this block leaves AI Studio with the notice that AI generated it, when that is switched on.
/// </summary>
private bool GetsAIGeneratedContentDisclosure => this.Role is ChatRole.AI && this.AddsAIGeneratedContentDisclosure;
/// <summary>
/// The files this block holds, tables and code blocks, so that the export menu can offer each of them.
/// </summary>
@@ -755,7 +774,7 @@ public partial class ContentBlockComponent : MSGComponentBase
}
/// <summary>
/// Exports the entire message.
/// Exports the entire message, ending with the notice that AI generated it when that is switched on.
/// </summary>
private async Task ExportDocument(FileExportFormat format)
{
@@ -765,10 +784,11 @@ public partial class ContentBlockComponent : MSGComponentBase
// The format itself knows who writes it, so we do not have to keep a list of formats
// here which would fall out of sync with the one in FileExportFormatExtensions.
//
var disclosureService = this.GetsAIGeneratedContentDisclosure ? this.DisclosureService : null;
if (format.UsesPandoc())
await PandocExport.ToDocument(this.RustService, this.PandocAvailability, this.EffectiveExportTitle, format, this.Content, this.ExportFileName);
await PandocExport.ToDocument(this.RustService, this.PandocAvailability, disclosureService, this.EffectiveExportTitle, format, this.Content, this.ExportFileName);
else if (this.Content.TryGetExportMarkdown(out var markdown))
await PlainFileExport.ToFile(this.RustService, this.EffectiveExportTitle, format, markdown, this.ExportFileName);
await PlainFileExport.ToFile(this.RustService, this.EffectiveExportTitle, format, disclosureService?.AddDisclosureToMarkdown(markdown) ?? markdown, this.ExportFileName);
}
catch (ArgumentOutOfRangeException e)
{
@@ -780,6 +800,11 @@ public partial class ContentBlockComponent : MSGComponentBase
/// Exports one file out of the message, along with the sources the answer rests on wherever its
/// format has room for them.
/// </summary>
/// <remarks>
/// Unlike the entire message, the file never gets the notice that AI generated it. It is meant for
/// a program as often as for a reader, a table or a web page for instance, and one sentence more
/// would break most of them.
/// </remarks>
private async Task ExportFile(MessageFile file)
{
try