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

@@ -107,7 +107,7 @@
</MudMenu>
</MudTooltip>
}
<MudCopyClipboardButton Content="@this.Content" Type="@this.Type" Size="Size.Medium"/>
<MudCopyClipboardButton Content="@this.Content" Type="@this.Type" Size="Size.Medium" IsAIGenerated="@this.GetsAIGeneratedContentDisclosure"/>
</div>
</CardHeaderActions>
</MudCardHeader>
@@ -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
@@ -113,19 +113,5 @@ public static class IContentExtensions
/// <param name="sources">The source list as SourceExtensions.ToExportMarkdown writes it, or an
/// empty string when there are no sources.</param>
/// <returns>The text followed by its sources.</returns>
private static string AppendSources(string markdown, string sources)
{
if (sources.Length == 0)
return markdown;
if (markdown.Length == 0)
return sources;
//
// The blank line is not cosmetic: it ends a paragraph, a list, a table, or a block quote, so
// that the heading of the source list stands on its own instead of being pulled into the
// last block of the answer.
//
return $"{Markdown.CloseOpenCodeFence(markdown)}{Environment.NewLine}{Environment.NewLine}{sources}";
}
private static string AppendSources(string markdown, string sources) => Markdown.AppendBlock(markdown, sources);
}