mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 12:07:22 +00:00
Added an optional notice for AI-generated content (#884)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
f5a257270e
commit
249fc778a2
26 files changed
+406
-46
No files matched your search
@@ -80,7 +80,7 @@
|
||||
|
||||
@if (this.ShowResult && !this.ShowEntireChatThread && this.ResultingContentBlock?.Content != null)
|
||||
{
|
||||
<ContentBlockComponent Role="@(this.ResultingContentBlock.Role)" Type="@(this.ResultingContentBlock.ContentType)" Time="@(this.ResultingContentBlock.Time)" Content="@this.ResultingContentBlock.Content" ExportTitle="@TB("Export result")" ExportFileName="@this.ExportFileName"/>
|
||||
<ContentBlockComponent Role="@(this.ResultingContentBlock.Role)" Type="@(this.ResultingContentBlock.ContentType)" Time="@(this.ResultingContentBlock.Time)" Content="@this.ResultingContentBlock.Content" ExportTitle="@TB("Export result")" ExportFileName="@this.ExportFileName" AddsAIGeneratedContentDisclosure="@this.AddsAIGeneratedContentDisclosure"/>
|
||||
}
|
||||
|
||||
@if(this.ShowResult && this.ShowEntireChatThread && this.ChatThread is not null)
|
||||
@@ -89,7 +89,7 @@
|
||||
{
|
||||
@if (block is { HideFromUser: false, Content: not null })
|
||||
{
|
||||
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content" ExportTitle="@TB("Export result")" ExportFileName="@this.ExportFileName"/>
|
||||
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content" ExportTitle="@TB("Export result")" ExportFileName="@this.ExportFileName" AddsAIGeneratedContentDisclosure="@this.AddsAIGeneratedContentDisclosure"/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected AIGeneratedContentDisclosureService DisclosureService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ToolRegistry ToolRegistry { get; init; } = null!;
|
||||
|
||||
@@ -108,7 +111,17 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
protected virtual bool ShowSendTo => true;
|
||||
|
||||
protected virtual bool ShowCopyResult => true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether "Copy result", and copying or exporting the answers this assistant shows, end them with the
|
||||
/// notice that AI generated them, when that is switched on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An assistant whose result is meant for a program or another model rather than for a reader turns
|
||||
/// this off, because one sentence more would change what the result does.
|
||||
/// </remarks>
|
||||
protected virtual bool AddsAIGeneratedContentDisclosure => true;
|
||||
|
||||
protected virtual bool ShowReset => true;
|
||||
|
||||
protected virtual string? SendToChatVisibleUserPromptPrefix => null;
|
||||
@@ -652,7 +665,11 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
|
||||
protected async Task CopyToClipboard()
|
||||
{
|
||||
await this.RustService.CopyText2Clipboard(this.Result2Copy());
|
||||
var result = this.Result2Copy();
|
||||
if (this.AddsAIGeneratedContentDisclosure)
|
||||
result = this.DisclosureService.AddDisclosureToMarkdown(result);
|
||||
|
||||
await this.RustService.CopyText2Clipboard(result);
|
||||
}
|
||||
|
||||
private ChatThread CreateSendToChatThread()
|
||||
|
||||
+8
-2
@@ -228,6 +228,12 @@ public partial class AssistantBatchProcessing
|
||||
{
|
||||
try
|
||||
{
|
||||
//
|
||||
// Only the file of a document ends with the notice that AI generated it, when that is
|
||||
// switched on. The table of results and a CSV file keep the answer as it is: there,
|
||||
// the notice would repeat in every row and get in the way of whoever processes the table.
|
||||
//
|
||||
var resultText = this.DisclosureService.AddDisclosureToMarkdown(aiAnswer);
|
||||
var resultFilePath = Path.Join(resolvedOutputDirectory, this.CreateResultFileName(fileResult.FileName));
|
||||
if (this.resultFileFormat.UsesPandoc())
|
||||
{
|
||||
@@ -235,14 +241,14 @@ public partial class AssistantBatchProcessing
|
||||
// Pandoc reports a failure instead of throwing, because one document which
|
||||
// cannot be converted must not end a run over hundreds of them:
|
||||
//
|
||||
if (!await PandocExport.ConvertAsync(this.RustService, aiAnswer, resultFilePath, this.resultFileFormat, token))
|
||||
if (!await PandocExport.ConvertAsync(this.RustService, resultText, resultFilePath, this.resultFileFormat, token))
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("Was not able to convert the answer into the chosen file format."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
await File.WriteAllTextAsync(resultFilePath, aiAnswer, this.resultFileFormat.ToFileEncoding(), CancellationToken.None);
|
||||
await File.WriteAllTextAsync(resultFilePath, resultText, this.resultFileFormat.ToFileEncoding(), CancellationToken.None);
|
||||
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.DONE, Path.GetFileName(resultFilePath));
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ public partial class AssistantBuilder : AssistantBaseCore<NoSettingsPanel>
|
||||
|
||||
protected override bool ShowCopyResult => this.step is BuilderStep.DONE;
|
||||
|
||||
// The result is the Lua code of a plugin, and a notice below it would make that code invalid:
|
||||
protected override bool AddsAIGeneratedContentDisclosure => false;
|
||||
|
||||
protected override bool HasSettingsPanel => false;
|
||||
|
||||
protected override Func<string> Result2Copy => () => !string.IsNullOrWhiteSpace(this.generatedLuaAssistant) ? this.generatedLuaAssistant : this.generatedAssistantSpec;
|
||||
|
||||
@@ -114,6 +114,11 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
private string searchString = string.Empty;
|
||||
private Guid selectedLanguagePluginId;
|
||||
private ILanguagePlugin? selectedLanguagePlugin;
|
||||
|
||||
/// <summary>
|
||||
/// The English text of every key, as the I18N collector wrote it into allTexts.lua.
|
||||
/// </summary>
|
||||
private Dictionary<string, string> sourceContent = [];
|
||||
private Dictionary<string, string> addedContent = [];
|
||||
private Dictionary<string, string> removedContent = [];
|
||||
private Dictionary<string, string> localizedContent = [];
|
||||
@@ -127,6 +132,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
private static readonly AssistantSessionStateKey<string> SEARCH_STRING_STATE_KEY = new(nameof(searchString));
|
||||
private static readonly AssistantSessionStateKey<Guid> SELECTED_LANGUAGE_PLUGIN_ID_STATE_KEY = new(nameof(selectedLanguagePluginId));
|
||||
private static readonly AssistantSessionStateKey<ILanguagePlugin?> SELECTED_LANGUAGE_PLUGIN_STATE_KEY = new(nameof(selectedLanguagePlugin));
|
||||
private static readonly AssistantSessionStateKey<Dictionary<string, string>> SOURCE_CONTENT_STATE_KEY = new(nameof(sourceContent));
|
||||
private static readonly AssistantSessionStateKey<Dictionary<string, string>> ADDED_CONTENT_STATE_KEY = new(nameof(addedContent));
|
||||
private static readonly AssistantSessionStateKey<Dictionary<string, string>> REMOVED_CONTENT_STATE_KEY = new(nameof(removedContent));
|
||||
private static readonly AssistantSessionStateKey<Dictionary<string, string>> LOCALIZED_CONTENT_STATE_KEY = new(nameof(localizedContent));
|
||||
@@ -143,6 +149,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
state.Set(SEARCH_STRING_STATE_KEY, this.searchString);
|
||||
state.Set(SELECTED_LANGUAGE_PLUGIN_ID_STATE_KEY, this.selectedLanguagePluginId);
|
||||
state.Set(SELECTED_LANGUAGE_PLUGIN_STATE_KEY, this.selectedLanguagePlugin);
|
||||
state.SetDictionary(SOURCE_CONTENT_STATE_KEY, this.sourceContent);
|
||||
state.SetDictionary(ADDED_CONTENT_STATE_KEY, this.addedContent);
|
||||
state.SetDictionary(REMOVED_CONTENT_STATE_KEY, this.removedContent);
|
||||
state.SetDictionary(LOCALIZED_CONTENT_STATE_KEY, this.localizedContent);
|
||||
@@ -160,6 +167,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
state.Restore(SEARCH_STRING_STATE_KEY, value => this.searchString = value);
|
||||
state.Restore(SELECTED_LANGUAGE_PLUGIN_ID_STATE_KEY, value => this.selectedLanguagePluginId = value);
|
||||
state.Restore(SELECTED_LANGUAGE_PLUGIN_STATE_KEY, value => this.selectedLanguagePlugin = value);
|
||||
state.RestoreDictionary(SOURCE_CONTENT_STATE_KEY, this.sourceContent);
|
||||
state.RestoreDictionary(ADDED_CONTENT_STATE_KEY, this.addedContent);
|
||||
state.RestoreDictionary(REMOVED_CONTENT_STATE_KEY, this.removedContent);
|
||||
state.RestoreDictionary(LOCALIZED_CONTENT_STATE_KEY, this.localizedContent);
|
||||
@@ -275,6 +283,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
var newI18NContent = pluginLanguage.Content;
|
||||
|
||||
var currentI18NContent = this.selectedLanguagePlugin.Content;
|
||||
this.sourceContent = newI18NContent.ToDictionary();
|
||||
this.addedContent = newI18NContent.ExceptBy(currentI18NContent.Keys, n => n.Key).ToDictionary();
|
||||
this.removedContent = currentI18NContent.ExceptBy(newI18NContent.Keys, n => n.Key).ToDictionary();
|
||||
this.localizationPossible = true;
|
||||
@@ -354,7 +363,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
var removedContentSnapshot = this.removedContent.ToArray();
|
||||
var removedContentKeys = removedContentSnapshot.Select(keyValuePair => keyValuePair.Key).ToHashSet(StringComparer.Ordinal);
|
||||
var selectedLanguageContentSnapshot = this.selectedLanguagePlugin.Content.ToArray();
|
||||
var baseLanguageContentSnapshot = PluginFactory.BaseLanguage.Content.ToArray();
|
||||
var sourceContentSnapshot = this.sourceContent.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value, StringComparer.Ordinal);
|
||||
|
||||
this.localizedContent.Clear();
|
||||
this.activeSystemPromptLanguage = this.SystemPromptLanguage();
|
||||
@@ -398,22 +407,12 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
return;
|
||||
|
||||
//
|
||||
// Phase 2: Create the Lua code. We want to use the base language
|
||||
// for the comments, though:
|
||||
// Phase 2: Create the Lua code. Each comment names the English text, which
|
||||
// allTexts.lua holds for every key of the result. The English language plugin
|
||||
// would not do: a text which is missing there, e.g., because somebody added it
|
||||
// to this language by hand, would get its translation as a comment instead.
|
||||
//
|
||||
var commentContent = addedContentSnapshot.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value, StringComparer.Ordinal);
|
||||
foreach (var keyValuePair in baseLanguageContentSnapshot)
|
||||
{
|
||||
if (this.CancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (removedContentKeys.Contains(keyValuePair.Key))
|
||||
continue;
|
||||
|
||||
commentContent.TryAdd(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
this.Phase2CreateLuaCode(commentContent);
|
||||
this.Phase2CreateLuaCode(sourceContentSnapshot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -4948,6 +4948,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T148008546"]
|
||||
-- Select the desired behavior for the navigation bar.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1555038969"] = "Select the desired behavior for the navigation bar."
|
||||
|
||||
-- When enabled, AI answers which you copy or export end with a notice that they were generated using artificial intelligence (AI), in the language of the app. The same holds for the files of the Batch Processing Assistant. Code blocks, single files or tables taken from an answer, visual briefings, and the results of the Prompt Optimizer and the Assistant Builder stay unchanged. Rules on transparency, such as the EU AI Act, may require such a notice, for example, when you publish a text.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1594053136"] = "When enabled, AI answers which you copy or export end with a notice that they were generated using artificial intelligence (AI), in the language of the app. The same holds for the files of the Batch Processing Assistant. Code blocks, single files or tables taken from an answer, visual briefings, and the results of the Prompt Optimizer and the Assistant Builder stay unchanged. Rules on transparency, such as the EU AI Act, may require such a notice, for example, when you publish a text."
|
||||
|
||||
-- Color theme
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1599198973"] = "Color theme"
|
||||
|
||||
@@ -5008,6 +5011,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"]
|
||||
-- Update installation method
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"] = "Update installation method"
|
||||
|
||||
-- AI-generated content gets no notice
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2533412202"] = "AI-generated content gets no notice"
|
||||
|
||||
-- AI-generated content gets a notice
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2544374612"] = "AI-generated content gets a notice"
|
||||
|
||||
-- Language
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language"
|
||||
|
||||
@@ -5029,6 +5038,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2918560776"]
|
||||
-- Enter one host pattern per line. Exact hosts such as data.intra.example.org and one-label wildcards such as *.intra.example.org are supported. Cloud provider endpoints built into AI Studio, such as OpenAI, Google, etc., never use these additional root certificates.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2960110864"] = "Enter one host pattern per line. Exact hosts such as data.intra.example.org and one-label wildcards such as *.intra.example.org are supported. Cloud provider endpoints built into AI Studio, such as OpenAI, Google, etc., never use these additional root certificates."
|
||||
|
||||
-- Add a notice to AI-generated content?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3070682674"] = "Add a notice to AI-generated content?"
|
||||
|
||||
-- Save energy?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] = "Save energy?"
|
||||
|
||||
@@ -13132,6 +13144,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS:
|
||||
-- Mail content
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T841590371"] = "Mail content"
|
||||
|
||||
-- This content was generated using artificial intelligence (AI).
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::AIGENERATEDCONTENTDISCLOSURESERVICE::T3440957850"] = "This content was generated using artificial intelligence (AI)."
|
||||
|
||||
-- The revised assistant plugin asks for tools this AI Studio does not have: '{0}'. Please try again.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T1002777578"] = "The revised assistant plugin asks for tools this AI Studio does not have: '{0}'. Please try again."
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
|
||||
|
||||
protected override Func<string> Result2Copy => () => this.optimizedPrompt;
|
||||
|
||||
// The optimized prompt goes on to a model instead of a reader, and a notice would become part of the prompt:
|
||||
protected override bool AddsAIGeneratedContentDisclosure => false;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new ButtonData
|
||||
|
||||
+4
@@ -129,6 +129,10 @@ public partial class VisualBriefingAssistant
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// The export is the version which the integrity check just verified, byte for byte. That is
|
||||
// why it gets no notice that AI generated it: any change would make it a version nobody checked.
|
||||
//
|
||||
await using var source = verified.Value.Stream;
|
||||
await using var destination = new FileStream(response.SaveFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 65_536, true);
|
||||
await source.CopyToAsync(destination);
|
||||
|
||||
Reference in new issue
Block a user