Added a Send to menu to every chat message and kept restrictions when sending content on (#1124)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-10-28 13:28:30 +01:00
1 parent d06688ca08
commit bf54b8cd0d
73 files changed
+1923 -390

No files matched your search

@@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Agenda;
@@ -266,16 +267,9 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
state.RestoreList(CONTENT_LINES_STATE_KEY, this.contentLines);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_AGENDA_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputContent = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputContent = payload.Text;
#endregion
@@ -1,5 +1,4 @@
@using AIStudio.Chat
@using AIStudio.Settings.DataModel
@inherits AssistantLowerBase
@typeparam TSettings
@@ -128,7 +127,7 @@
{
@if (this.ShowSendTo && this.VisibleSendToAssistants.Count > 0)
{
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded" MaxHeight="@MenuDefaults.MAX_HEIGHT">
@foreach (var assistant in this.VisibleSendToAssistants)
{
<MudMenuItem OnClick="@(async () => await this.SendToAssistant(assistant, new()))">
@@ -160,7 +159,7 @@
case SendToButton sendToButton:
@if (this.VisibleSendToAssistants.Count > 0)
{
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded" MaxHeight="@MenuDefaults.MAX_HEIGHT">
@foreach (var assistant in this.VisibleSendToAssistants)
{
<MudMenuItem OnClick="@(async () => await this.SendToAssistant(assistant, sendToButton))">
@@ -200,7 +199,7 @@
@* No selection where the assistant's own rules already name the tools: *@
@if (this.SettingsManager.AreToolsEnabled() && this.AssistantManagedToolIds is null && this.SettingsManager.IsToolSelectionVisible(this.Component))
{
<ToolSelection Component="@this.Component" LLMProvider="@this.ProviderSettings" RequiredOutboundDataRestriction="@(this.ChatThread?.RequiredOutboundDataRestriction ?? OutboundDataRequirement.NONE)" SelectedToolIds="@this.SelectedToolIds" SelectedToolIdsChanged="@this.SelectedToolIdsChanged" Disabled="@this.IsProcessing" />
<ToolSelection Component="@this.Component" LLMProvider="@this.ProviderSettings" RequiredOutboundDataRestriction="@this.CurrentRestrictions.OutboundData" SelectedToolIds="@this.SelectedToolIds" SelectedToolIdsChanged="@this.SelectedToolIdsChanged" Disabled="@this.IsProcessing" />
}
<MudSpacer />
@@ -4,6 +4,7 @@ using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AIJobs;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Media;
using AIStudio.Tools.Services;
@@ -48,6 +49,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
[Inject]
protected AssistantSessionService AssistantSessionService { get; init; } = null!;
[Inject]
private AssistantLaunchService AssistantLaunchService { get; init; } = null!;
/// <summary>
/// Gets the job service used to run assistant-created chats independently from the assistant UI.
/// </summary>
@@ -162,6 +166,44 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected virtual ChatThread ConvertToChatThread => this.CreateSendToChatThread();
/// <summary>
/// Takes over a text which another assistant or a chat sent to this assistant.
/// </summary>
/// <remarks>
/// Every assistant which content may be sent to puts the text into its main field, see
/// ComponentsExtensions.AllowSendTo. It comes before the preselected values, so a field which those only fill
/// when empty keeps the text. An assistant which allows sending to it but takes nothing would leave the text
/// lost without a word, which is why a test checks that each one overrides this.
/// </remarks>
/// <param name="payload">The text sent.</param>
protected virtual void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.Logger.LogError("The assistant '{Component}' allows sending text to it, but it does not take the text.", this.Component);
/// <summary>
/// What the text sent to this assistant demands, because of where it came from.
/// </summary>
/// <remarks>
/// An answer written from mails demands here what it demanded in its chat. The thread of the
/// assistant is created anew with every run, so the restrictions live here and go onto every thread
/// this assistant runs, see AssignRuntimeIdentity, and onto every tool list, see GetRunnableToolIds.
/// They stay until the form is reset, which removes the text as well.
/// </remarks>
private ChatRestrictions transferredRestrictions = ChatRestrictions.NONE;
/// <summary>
/// What everything this assistant holds demands: the text sent to it and what its current thread read.
/// </summary>
private ChatRestrictions CurrentRestrictions => this.ChatThread?.CaptureRestrictions().StricterOf(this.transferredRestrictions) ?? this.transferredRestrictions;
/// <summary>
/// Keeps what the current result demands, before the assistant puts that result into its own input.
/// </summary>
/// <remarks>
/// Call this before clearing the thread: the result was written from what this thread read, and the
/// web addresses in it were written by a model. Both stay with the input until the form is reset.
/// </remarks>
/// <param name="resultText">The result which becomes the new input.</param>
protected void TakeOverRestrictionsOfResult(string resultText) => this.transferredRestrictions = this.CurrentRestrictions.WithWebAddressesWrittenByModelIn(resultText);
private protected virtual RenderFragment? HeaderActions => null;
private protected virtual RenderFragment? AfterResultContent => null;
@@ -213,6 +255,13 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected override async Task OnInitializedAsync()
{
// A text sent here comes first, so that the preselected values below only fill what it left empty:
if (AssistantLaunchService.TakeTextPayload(this.Component) is { } textLaunchPayload)
{
this.transferredRestrictions = textLaunchPayload.Restrictions;
this.ApplyTextLaunchPayload(textLaunchPayload);
}
this.MediaTranscriptionService.StateChanged += this.OnMediaImportStateChanged;
await base.OnInitializedAsync();
@@ -276,25 +325,30 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.Confidence.ShowProviderConfidence ? this.ProviderSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty;
private IReadOnlyList<Tools.Components> VisibleSendToAssistants => Enum.GetValues<AIStudio.Tools.Components>()
.Where(this.CanSendToAssistant)
.OrderBy(component => component.Name().Length)
.ToArray();
private IReadOnlyList<Tools.Components> VisibleSendToAssistants => this.AssistantLaunchService.GetDestinationsFor(this.Component);
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
{
if(provider.UsedLLMProvider == LLMProviders.NONE)
return this.TB("Please select a provider.");
// A provider which nothing else rules out may still be kept from the text sent here, because of where it came from:
if (!this.transferredRestrictions.IsLLMProviderAllowed(provider) && ChatRestrictions.NONE.IsLLMProviderAllowed(provider))
return this.TB("This provider may not see the text sent to this assistant, because of where the text came from. Please select another provider.");
return null;
}
private async Task Start()
{
//
// A provider which may not see the text sent here would fail the run without a word, see
// ContentText.CreateFromProviderAsync, so the user hears about it before the run starts:
//
await this.RefreshProviderSelectionFromConfigurationAsync();
if (this.ProviderSettings == Settings.Provider.NONE)
if (this.ProviderSettings == Settings.Provider.NONE || this.ValidatingProvider(this.ProviderSettings) is not null)
{
await this.ReportMissingProviderAsync();
await this.ReportProviderIssueAsync();
return;
}
@@ -367,16 +421,17 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
}
/// <summary>
/// Tells the user that the assistant cannot start without a provider, together with whatever else is missing.
/// Tells the user that the assistant cannot start with the selected provider, together with whatever else is missing.
/// </summary>
/// <remarks>
/// Most assistants validate their form only in their submit action, which never runs without a
/// provider. Validating here marks the provider selection and lists the other issues as well.
/// The form does not report the missing provider when it holds no provider selection, as with an
/// assistant plugin without one, or when its selection still shows the provider which the refresh
/// at the start has just dropped. Then the issue is added on its own.
/// Either no provider is selected, or the selected one may not see the text sent to this assistant,
/// see ValidatingProvider. Most assistants validate their form only in their submit action, which
/// never runs without a usable provider. Validating here marks the provider selection and lists the
/// other issues as well. The form does not report the issue when it holds no provider selection, as
/// with an assistant plugin without one, or when its selection still shows the provider which the
/// refresh at the start has just dropped. Then the issue is added on its own.
/// </remarks>
private async Task ReportMissingProviderAsync()
private async Task ReportProviderIssueAsync()
{
if (this.Form is not null)
await this.Form.Validate();
@@ -466,16 +521,20 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
/// <summary>
/// Sets who runs the given thread: this assistant's component and, for an assistant plugin, its name.
/// It also puts the restrictions of a text sent to this assistant on the thread.
/// </summary>
/// <remarks>
/// Both decide which tools the thread may use and what its requests to self-hosted servers name. Every
/// way an assistant creates or sends a thread goes through here, so that none of them can forget one.
/// That is why the restrictions of a text sent here go onto the thread here, too: every thread of this
/// assistant may hold that text, its private threads for research included.
/// </remarks>
/// <param name="chatThread">The thread this assistant runs.</param>
protected void AssignRuntimeIdentity(ChatThread chatThread)
{
chatThread.RuntimeComponent = this.Component;
chatThread.RuntimeAssistantName = this.RuntimeAssistantName;
chatThread.RequireRestrictions(this.transferredRestrictions);
}
private Task RefreshProviderSelectionFromConfigurationAsync()
@@ -515,9 +574,12 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
/// the user a tool for good. The same goes for a tool which a mailbox the thread read from
/// keeps back.
/// </remarks>
/// <param name="outboundDataRestriction">Where the thread the tools run in may still send data, see ChatThread.RequiredOutboundDataRestriction.</param>
/// <param name="outboundDataRestriction">Where the thread the tools run in may still send data, see ChatThread.RequiredOutboundDataRestriction. A text sent to this assistant may demand more, and then that counts.</param>
protected HashSet<string> GetRunnableToolIds(OutboundDataRestriction outboundDataRestriction)
{
// Every thread of this assistant may hold the text sent to it, so what that text demands holds whoever asks:
outboundDataRestriction = outboundDataRestriction.StricterOf(this.transferredRestrictions.OutboundData.Restriction);
if (this.AssistantManagedToolIds is not null)
return this.ToolRegistry.FilterToolIdsForProvider(this.ProviderSettings, this.AssistantManagedToolIds, outboundDataRestriction);
@@ -834,7 +896,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected async Task SendToAssistant(Tools.Components destination, SendToButton sendToButton)
{
if (!this.CanSendToAssistant(destination))
if (!this.VisibleSendToAssistants.Contains(destination))
return;
var contentToSend = sendToButton == default ? string.Empty : sendToButton.UseResultingContentBlockData switch
@@ -847,57 +909,33 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
},
};
var sendToData = destination.GetData();
if (destination.HasSingleSessionSlot() && this.AssistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && snapshot.Key.Component == destination))
// The chat starts from the conversation of this assistant, unless it only takes the text for the next message:
if (destination is Tools.Components.CHAT && !sendToButton.SendToChatAsInput)
{
await this.MessageBus.SendWarning(new(Icons.Material.Filled.Apps, this.TB("This assistant is already running. AI Studio opens the running session instead.")));
this.NavigationManager.NavigateTo(sendToData.Route);
var convertedChatThread = this.ConvertToChatThread;
convertedChatThread = convertedChatThread with { SelectedProvider = this.ProviderSettings.Id };
//
// The chat holds what this assistant held, whichever thread the assistant builds it from: some
// assistants start it from a new thread, which has read nothing, or from none at all. The copy
// above is the chat's own, so tightening it leaves the thread of this assistant as it is:
//
convertedChatThread.RequireRestrictions(this.CurrentRestrictions);
// Some assistants start the chat under an id of its own, so what their answers received goes along. A chat without an id received nothing:
if (this.ChatThread is { } assistantChat && convertedChatThread.ChatId != Guid.Empty && assistantChat.ChatId != convertedChatThread.ChatId)
await ChatProvenance.CopyChatAsync(assistantChat.ChatId, convertedChatThread.ChatId);
MessageBus.INSTANCE.DeferMessage(this, Event.SEND_TO_CHAT, new ChatStartRequest(convertedChatThread));
this.NavigationManager.NavigateTo(Routes.CHAT);
return;
}
// Only components with a single session slot may be cleared as a group. The visual briefing
// assistant keys its sessions per briefing, so clearing by component would discard the
// status of every stored briefing instead of the one we are about to open.
if (destination.HasSingleSessionSlot())
await this.AssistantSessionService.ClearInactiveSessionsForComponentAsync(destination);
switch (destination)
{
case Tools.Components.CHAT:
if (sendToButton.SendToChatAsInput)
MessageBus.INSTANCE.DeferMessage(this, Event.SEND_TO_CHAT_INPUT, contentToSend);
else
{
var convertedChatThread = this.ConvertToChatThread;
convertedChatThread = convertedChatThread with { SelectedProvider = this.ProviderSettings.Id };
// Some assistants start the chat under an id of its own, so what their answers received goes along. A chat without an id received nothing:
if (this.ChatThread is { } assistantChat && convertedChatThread.ChatId != Guid.Empty && assistantChat.ChatId != convertedChatThread.ChatId)
await ChatProvenance.CopyChatAsync(assistantChat.ChatId, convertedChatThread.ChatId);
MessageBus.INSTANCE.DeferMessage(this, sendToData.Event, new ChatStartRequest(convertedChatThread));
}
break;
default:
MessageBus.INSTANCE.DeferMessage(this, sendToData.Event, contentToSend);
break;
}
this.NavigationManager.NavigateTo(sendToData.Route);
// A model wrote what an assistant sends, even when it falls back to the input, so it gets filtered, and none of its addresses counts as given:
var payload = await this.AssistantLaunchService.CreateTextPayloadAsync(contentToSend, writtenByModel: true, this.CurrentRestrictions, this.Title);
await this.AssistantLaunchService.LaunchAsync(destination, payload);
}
private bool CanSendToAssistant(Tools.Components component)
{
if (!component.AllowSendTo())
return false;
return this.SettingsManager.IsAssistantVisible(
component,
withLogging: false,
requiredPreviewFeature: component.RequiredPreviewFeature());
}
private async Task InnerResetForm()
{
if ((this.AssistantSessionService.TryGetSnapshot(this.assistantSessionKey)?.IsActive ?? false)
@@ -908,6 +946,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.MediaTranscriptionService.ClearOwnerState(this.CurrentMediaImportOwner);
this.assistantSessionId = null;
this.ClearConversationState();
// The text sent here is gone with the form, and what it demanded goes with it:
this.transferredRestrictions = ChatRestrictions.NONE;
this.ProviderSettings = Settings.Provider.NONE;
await this.JsRuntime.ClearDiv(BEFORE_RESULT_DIV_ID);
@@ -1149,6 +1190,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
state.Set(INPUT_ISSUES_STATE_KEY, this.InputIssues);
state.Set(IS_PROCESSING_STATE_KEY, this.IsProcessing);
state.Set(SELECTED_TOOL_IDS_STATE_KEY, this.SelectedToolIds);
state.Set(TRANSFERRED_RESTRICTIONS_STATE_KEY, this.transferredRestrictions);
this.CaptureCustomAssistantSessionState(state);
return state.ToDictionary();
@@ -1177,6 +1219,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
reader.Restore(INPUT_ISSUES_STATE_KEY, value => this.InputIssues = value);
reader.Restore(IS_PROCESSING_STATE_KEY, value => this.IsProcessing = value);
reader.Restore(SELECTED_TOOL_IDS_STATE_KEY, value => this.SelectedToolIds = this.ToolRegistry.NormalizeSelection(value));
reader.Restore(TRANSFERRED_RESTRICTIONS_STATE_KEY, value => this.transferredRestrictions = value);
this.RestoreCustomAssistantSessionState(reader);
}
@@ -38,6 +38,7 @@ public abstract class AssistantLowerBase : MSGComponentBase
protected static readonly AssistantSessionStateKey<string[]> INPUT_ISSUES_STATE_KEY = new(nameof(InputIssues));
protected static readonly AssistantSessionStateKey<bool> IS_PROCESSING_STATE_KEY = new(nameof(IsProcessing));
protected static readonly AssistantSessionStateKey<HashSet<string>> SELECTED_TOOL_IDS_STATE_KEY = new("SelectedToolIds");
protected static readonly AssistantSessionStateKey<ChatRestrictions> TRANSFERRED_RESTRICTIONS_STATE_KEY = new("TransferredRestrictions");
protected AIStudio.Settings.Provider ProviderSettings = Settings.Provider.NONE;
protected bool InputIsValid;
@@ -2,6 +2,7 @@
using AIStudio.Chat;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Coding;
@@ -139,16 +140,9 @@ public partial class AssistantCoding : AssistantBaseCore<SettingsDialogCoding>
state.Restore(QUESTIONS_STATE_KEY, value => this.questions = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_CODING_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.questions = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.questions = payload.Text;
#endregion
@@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.EMail;
@@ -120,16 +121,9 @@ public partial class AssistantEMail : AssistantBaseCore<SettingsDialogWritingEMa
state.Restore(INPUT_HISTORY_STATE_KEY, value => this.inputHistory = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_EMAIL_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputBulletPoints = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputBulletPoints = payload.Text;
#endregion
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.GrammarSpelling;
@@ -31,7 +32,6 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore<SettingsDialog
[
new SendToButton
{
Self = Tools.Components.GRAMMAR_SPELLING_ASSISTANT,
UseResultingContentBlockData = false,
GetText = () => string.IsNullOrWhiteSpace(this.correctedText) ? this.inputText : this.correctedText
},
@@ -68,16 +68,9 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore<SettingsDialog
return false;
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_GRAMMAR_SPELLING_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputText = payload.Text;
#endregion
@@ -331,8 +331,8 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T1840311560"] = "Export re
-- The media transcription was canceled.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T241403726"] = "The media transcription was canceled."
-- This assistant is already running. AI Studio opens the running session instead.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T2575715765"] = "This assistant is already running. AI Studio opens the running session instead."
-- This provider may not see the text sent to this assistant, because of where the text came from. Please select another provider.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T268581738"] = "This provider may not see the text sent to this assistant, because of where the text came from. Please select another provider."
-- Assistant - {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T3043922"] = "Assistant - {0}"
@@ -3454,6 +3454,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGASSISTANT::
-- Please provide a custom protection level.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGASSISTANT::T799692129"] = "Please provide a custom protection level."
-- This provider may not see the text sent to this briefing, because of where the text came from. Please select another provider.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGASSISTANT::T869841458"] = "This provider may not see the text sent to this briefing, because of where the text came from. Please select another provider."
-- Please provide a briefing name.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGASSISTANT::T902674552"] = "Please provide a briefing name."
@@ -3601,6 +3604,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGFAILUREEXTE
-- The assembled briefing did not pass the security validation.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGFAILUREEXTENSIONS::T295498807"] = "The assembled briefing did not pass the security validation."
-- The selected provider may not see the content of this briefing. Please select another provider.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGFAILUREEXTENSIONS::T300791290"] = "The selected provider may not see the content of this briefing. Please select another provider."
-- The charts of the model response did not match the planned briefing elements. Please try again or select another model.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::VISUALBRIEFING::VISUALBRIEFINGFAILUREEXTENSIONS::T3326200304"] = "The charts of the model response did not match the planned briefing elements. Please try again or select another model."
@@ -3772,6 +3778,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, kee
-- No tool calls
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4224149521"] = "No tool calls"
-- Send to ...
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4242312602"] = "Send to ..."
-- No arguments
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T931993614"] = "No arguments"
@@ -12694,26 +12703,26 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::MAILBOXTRANSPORTSECURITYEXTENSIO
-- Unknown encryption
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::MAILBOXTRANSPORTSECURITYEXTENSIONS::T685301366"] = "Unknown encryption"
-- This chat found content in a data source which has been removed since, while its setting 'Where a chat may send data after reading this data source' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T1103975982"] = "This chat found content in a data source which has been removed since, while its setting 'Where a chat may send data after reading this data source' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."
-- This chat holds content from your Confluence wiki, found while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T1689639786"] = "This chat holds content from your Confluence wiki, found while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again."
-- This chat read an internal web page on '{0}' while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T1307134700"] = "This chat read an internal web page on '{0}' while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."
-- This chat holds content from the mailbox '{0}', read while its setting 'Where a chat may send data after reading mails' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T1878172266"] = "This chat holds content from the mailbox '{0}', read while its setting 'Where a chat may send data after reading mails' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again."
-- This chat searched your Confluence wiki while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T1719013859"] = "This chat searched your Confluence wiki while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."
-- This chat holds content from a data source which has been removed since, found while its setting 'Where a chat may send data after reading this data source' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T2154420164"] = "This chat holds content from a data source which has been removed since, found while its setting 'Where a chat may send data after reading this data source' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again."
-- This chat found content in the data source '{0}' while its setting 'Where a chat may send data after reading this data source' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T2391737974"] = "This chat found content in the data source '{0}' while its setting 'Where a chat may send data after reading this data source' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."
-- This chat holds content from a mailbox which has been removed since, read while its setting 'Where a chat may send data after reading mails' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T2474328188"] = "This chat holds content from a mailbox which has been removed since, read while its setting 'Where a chat may send data after reading mails' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again."
-- This chat read mails from the mailbox '{0}' while its setting 'Where a chat may send data after reading mails' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T3290545516"] = "This chat read mails from the mailbox '{0}' while its setting 'Where a chat may send data after reading mails' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."
-- This chat holds content from an internal web page on '{0}', read while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T3062480014"] = "This chat holds content from an internal web page on '{0}', read while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again."
-- This chat read internal content while the chat was restricted to '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T3482648272"] = "This chat read internal content while the chat was restricted to '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."
-- This chat holds content from the data source '{0}', found while its setting 'Where a chat may send data after reading this data source' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T3148548820"] = "This chat holds content from the data source '{0}', found while its setting 'Where a chat may send data after reading this data source' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again."
-- This chat read mails from a mailbox which has been removed since, while its setting 'Where a chat may send data after reading mails' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T996427866"] = "This chat read mails from a mailbox which has been removed since, while its setting 'Where a chat may send data after reading mails' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."
-- This chat holds internal content, read while a chat was restricted to '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again.
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATAREQUIREMENTEXTENSIONS::T348053469"] = "This chat holds internal content, read while a chat was restricted to '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat without this content can use it again."
-- Unknown restriction
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::OUTBOUNDDATARESTRICTIONEXTENSIONS::T1376332431"] = "Unknown restriction"
@@ -12859,6 +12868,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::ASSISTANTCATEGORYEXTENSIONS::T4193824894"] = "
-- Learning
UI_TEXT_CONTENT["AISTUDIO::TOOLS::ASSISTANTCATEGORYEXTENSIONS::T755590027"] = "Learning"
-- This assistant is already running. AI Studio opens the running session instead.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::ASSISTANTLAUNCH::ASSISTANTLAUNCHSERVICE::T2575715765"] = "This assistant is already running. AI Studio opens the running session instead."
-- Unnamed chat
UI_TEXT_CONTENT["AISTUDIO::TOOLS::ASSISTANTLAUNCH::ASSISTANTLAUNCHSERVICE::T3310482275"] = "Unnamed chat"
-- SSO (Kerberos)
UI_TEXT_CONTENT["AISTUDIO::TOOLS::AUTHMETHODSV1EXTENSIONS::T268552140"] = "SSO (Kerberos)"
@@ -15610,6 +15625,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS:
-- Data source description
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T1553588912"] = "Data source description"
-- AI answer
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T2066071095"] = "AI answer"
-- Web content
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SECURITY::PROMPTINJECTIONSOURCEKINDEXTENSIONS::T2626468388"] = "Web content"
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.IconFinder;
@@ -74,16 +75,9 @@ public partial class AssistantIconFinder : AssistantBaseCore<SettingsDialogIconF
state.Restore(SELECTED_ICON_SOURCE_STATE_KEY, value => this.selectedIconSource = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_ICON_FINDER_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputContext = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputContext = payload.Text;
#endregion
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.JobPosting;
@@ -173,16 +174,9 @@ public partial class AssistantJobPostings : AssistantBaseCore<SettingsDialogJobP
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_JOB_POSTING_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputJobDescription = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputJobDescription = payload.Text;
#endregion
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.LegalCheck;
@@ -91,16 +92,9 @@ public partial class AssistantLegalCheck : AssistantBaseCore<SettingsDialogLegal
state.Restore(INPUT_QUESTIONS_STATE_KEY, value => this.inputQuestions = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_LEGAL_CHECK_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputQuestions = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputQuestions = payload.Text;
#endregion
@@ -1,6 +1,7 @@
using AIStudio.Chat;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.MyTasks;
@@ -135,16 +136,9 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_MY_TASKS_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputText = payload.Text;
#endregion
@@ -3,6 +3,7 @@ using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
@@ -110,7 +111,6 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
},
new SendToButton
{
Self = Tools.Components.PROMPT_OPTIMIZER_ASSISTANT,
UseResultingContentBlockData = false,
SendToChatAsInput = true,
GetText = () => string.IsNullOrWhiteSpace(this.optimizedPrompt) ? this.inputPrompt : this.optimizedPrompt,
@@ -163,13 +163,11 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
this.ResetGuidelineSummaryToDefault();
this.hasUpdatedDefaultRecommendations = false;
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_PROMPT_OPTIMIZER_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputPrompt = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputPrompt = payload.Text;
private string inputPrompt = string.Empty;
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
private string customTargetLanguage = string.Empty;
@@ -460,6 +458,7 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
if (!this.CanImproveFurther)
return Task.CompletedTask;
this.TakeOverRestrictionsOfResult(this.optimizedPrompt);
this.inputPrompt = this.optimizedPrompt;
this.ResetOutput();
this.ClearConversationState();
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.RewriteImprove;
@@ -32,7 +33,6 @@ public partial class AssistantRewriteImprove : AssistantBaseCore<SettingsDialogR
[
new SendToButton
{
Self = Tools.Components.REWRITE_ASSISTANT,
UseResultingContentBlockData = false,
GetText = () => string.IsNullOrWhiteSpace(this.rewrittenText) ? this.inputText : this.rewrittenText,
},
@@ -73,16 +73,9 @@ public partial class AssistantRewriteImprove : AssistantBaseCore<SettingsDialogR
return false;
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_REWRITE_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputText = payload.Text;
#endregion
@@ -40,7 +40,8 @@ public partial class AssistantSWOTAnalysis
if (token.IsCancellationRequested)
return;
// The research runs in a thread of its own, which has read nothing yet:
// The research runs in a thread of its own, which has read nothing yet. A text sent to this
// assistant may still demand more, and GetRunnableToolIds holds the research to that:
var runnableToolIds = this.GetRunnableToolIds(OutboundDataRestriction.UNRESTRICTED);
if (runnableToolIds.Count > 0)
{
@@ -1,5 +1,6 @@
using AIStudio.Chat;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Security;
using AIStudio.Tools.Services;
@@ -160,16 +161,9 @@ public partial class AssistantSWOTAnalysis : AssistantBaseCore<SettingsDialogSWO
state.Restore(SHARED_RESEARCH_CONTEXT_STATE_KEY, value => this.sharedResearchContext = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_SWOT_ANALYSIS_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputText = payload.Text;
#endregion
@@ -2,6 +2,7 @@
using AIStudio.Chat;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.PowerPoint;
using AIStudio.Tools.Security;
@@ -399,16 +400,9 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
this.StartCountingPictureLibrary();
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_SLIDE_BUILDER_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputContent = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputContent = payload.Text;
#endregion
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Synonym;
@@ -127,16 +128,9 @@ public partial class AssistantSynonyms : AssistantBaseCore<SettingsDialogSynonym
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_SYNONYMS_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputContext = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputContext = payload.Text;
#endregion
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.TextSummarizer;
@@ -116,16 +117,9 @@ public partial class AssistantTextSummarizer : AssistantBaseCore<SettingsDialogT
state.Restore(IMPORTANT_ASPECTS_STATE_KEY, value => this.importantAspects = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_TEXT_SUMMARIZER_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputText = payload.Text;
#endregion
@@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Translation;
@@ -120,16 +121,9 @@ public partial class AssistantTranslation : AssistantBaseCore<SettingsDialogTran
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase
#region Overrides of AssistantBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.TakeDeferredMessages<string>(Event.SEND_TO_TRANSLATION_ASSISTANT).LastOrDefault();
if (deferredContent is not null)
this.inputText = deferredContent;
await base.OnInitializedAsync();
}
protected override void ApplyTextLaunchPayload(TextLaunchPayload payload) => this.inputText = payload.Text;
#endregion
@@ -19,6 +19,7 @@ internal sealed class StructuredLlmStageRunner(
/// <typeparam name="T">The strict response type.</typeparam>
/// <param name="provider">The selected provider configuration.</param>
/// <param name="profile">The selected user profile.</param>
/// <param name="restrictions">What the content of the briefing demands, see VisualBriefingManifest.Restrictions.</param>
/// <param name="systemContract">The stage-specific system contract.</param>
/// <param name="prompt">The user prompt containing stage inputs.</param>
/// <param name="attachments">The first-turn attachments.</param>
@@ -31,6 +32,7 @@ internal sealed class StructuredLlmStageRunner(
public async Task<StructuredLlmStageResult<T>> RunAsync<T>(
ProviderSettings provider,
Profile profile,
ChatRestrictions restrictions,
string systemContract,
string prompt,
IReadOnlyList<FileAttachment> attachments,
@@ -79,6 +81,10 @@ internal sealed class StructuredLlmStageRunner(
],
};
// The thread of the stage holds what the briefing holds. The orchestrator checked the provider before the
// build started; the provider call checks it against the thread once more:
thread.RequireRestrictions(restrictions);
VisualBriefingContractIssue? repairIssue = null;
for (var attempt = 1; attempt <= 2; attempt++)
{
@@ -1,5 +1,6 @@
using System.Text.Json;
using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Provider;
using AIStudio.Tools.Media;
@@ -58,7 +59,13 @@ public partial class VisualBriefingAssistant
/// <summary>
/// Defines <c>CreateBriefingAsync</c> for the visual briefing feature.
/// </summary>
private async Task CreateBriefingAsync()
private Task CreateBriefingAsync() => this.CreateBriefingAsync(ChatRestrictions.NONE);
/// <summary>
/// Creates a briefing which holds content with the given restrictions, see VisualBriefingManifest.Restrictions.
/// </summary>
/// <param name="restrictions">What the content of the briefing demands.</param>
private async Task CreateBriefingAsync(ChatRestrictions restrictions)
{
var defaults = this.SettingsManager.ConfigurationData.VisualBriefing;
var defaultProvider = this.SettingsManager.GetPreselectedProvider(ComponentKind.VISUAL_BRIEFING_ASSISTANT);
@@ -79,7 +86,7 @@ public partial class VisualBriefingAssistant
OptimizeImages = defaults.OptimizeImages,
};
var briefing = await this.Store.CreateAsync(suggestedName, string.Empty, settings);
var briefing = await this.Store.CreateAsync(suggestedName, string.Empty, settings, restrictions);
await this.ReloadListAsync(briefing.BriefingId);
}
@@ -1,3 +1,4 @@
using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.Rust;
@@ -109,10 +110,21 @@ public partial class VisualBriefingAssistant
private string? ValidateProjectName(string name) => string.IsNullOrWhiteSpace(name) ? T("Please provide a briefing name.") : null;
/// <summary>Validates the selected generation provider.</summary>
private string? ValidateProvider(ProviderSettings value) =>
value == ProviderSettings.NONE || value.UsedLLMProvider is LLMProviders.NONE
? T("Please select a provider.")
: null;
/// <remarks>
/// A provider which may not see a text sent to this briefing is reported here, before the build starts. A provider the
/// organization prohibited is not: it fails for every briefing alike, and the build reports it.
/// </remarks>
private string? ValidateProvider(ProviderSettings value)
{
if (value == ProviderSettings.NONE || value.UsedLLMProvider is LLMProviders.NONE)
return T("Please select a provider.");
var restrictions = this.selectedBriefing?.Restrictions ?? ChatRestrictions.NONE;
if (!restrictions.IsLLMProviderAllowed(value) && ChatRestrictions.NONE.IsLLMProviderAllowed(value))
return T("This provider may not see the text sent to this briefing, because of where the text came from. Please select another provider.");
return null;
}
/// <summary>Validates the free-form target language when Other is selected.</summary>
private string? ValidateCustomTargetLanguage(string language) =>
@@ -1,6 +1,8 @@
using AIStudio.Chat;
using AIStudio.Components;
using AIStudio.Dialogs;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantLaunch;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Services;
@@ -152,20 +154,18 @@ public partial class VisualBriefingAssistant : MSGComponentBase
return;
}
this.ApplyFilters([], [Event.SEND_TO_VISUAL_BRIEFING_ASSISTANT, Event.CONFIGURATION_CHANGED]);
this.ApplyFilters([], [Event.CONFIGURATION_CHANGED]);
this.MediaTranscriptionService.StateChanged += this.MediaStateChanged;
this.BuildProgressService.Changed += this.BuildProgressChanged;
await this.ReloadListAsync();
await this.ConsumePendingMediaOutcomesAsync();
this.MonitorSourceStatusAsync(this.sourceMonitorCancellation.Token).Observe($"{nameof(VisualBriefingAssistant)}: monitoring the source status");
var deferredInstruction = this.MessageBus.TakeDeferredMessages<string>(Event.SEND_TO_VISUAL_BRIEFING_ASSISTANT).LastOrDefault();
if (!string.IsNullOrWhiteSpace(deferredInstruction))
if (AssistantLaunchService.TakeTextPayload(ComponentKind.VISUAL_BRIEFING_ASSISTANT) is { } textLaunchPayload && !string.IsNullOrWhiteSpace(textLaunchPayload.Text))
{
if (this.selectedBriefing is null)
await this.CreateBriefingAsync();
this.editor.Instruction = deferredInstruction;
// A text sent here starts a briefing of its own, which holds what the text demands. The briefing
// open last belongs to other work, and its instruction would be gone:
await this.CreateBriefingAsync(textLaunchPayload.Restrictions);
this.editor.Instruction = textLaunchPayload.Text;
await this.SaveCurrentAsync();
}
@@ -235,17 +235,6 @@ public partial class VisualBriefingAssistant : MSGComponentBase
/// </summary>
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
if (triggeredEvent is Event.SEND_TO_VISUAL_BRIEFING_ASSISTANT && data is string text)
{
if (this.selectedBriefing is null)
await this.CreateBriefingAsync();
this.editor.Instruction = text;
await this.SaveCurrentAsync();
this.StateHasChanged();
return;
}
if (triggeredEvent is Event.CONFIGURATION_CHANGED)
{
// The spellchecking setting might have changed. Since this page is not re-parameterized
@@ -1,3 +1,4 @@
using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.Rust;
@@ -233,6 +234,28 @@ internal sealed partial class VisualBriefingBuildOrchestrator
"No provider is selected.");
}
/// <summary>
/// Ensures the selected provider may see what the briefing holds.
/// </summary>
/// <remarks>
/// A text sent to the briefing demands what it demanded where it came from, see VisualBriefingManifest.Restrictions,
/// and a provider the organization prohibited may see nothing at all. The provider call of each stage would refuse
/// such a provider as well, but only once the build runs, and the stage would fail without telling why.
/// </remarks>
/// <param name="manifest">The briefing manifest.</param>
/// <param name="provider">The provider.</param>
internal static void ValidateProviderMaySeeContent(VisualBriefingManifest manifest, ProviderSettings provider)
{
if (manifest.Restrictions.IsLLMProviderAllowed(provider))
return;
throw new VisualBriefingBuildException(
VisualBriefingFailureCode.PROVIDER_NOT_ALLOWED,
VisualBriefingBuildStage.SOURCE_PREPARATION,
"The selected provider may not see the content of this briefing.",
$"ProviderFamily={provider.UsedLLMProvider}; RequiredConfidence={manifest.Restrictions.ProviderConfidence}; DataSecurity={manifest.Restrictions.DataSecurity}.");
}
/// <summary>
/// Ensures content-generating builds have at least one source-material file.
/// </summary>
@@ -114,6 +114,7 @@ internal sealed partial class VisualBriefingBuildOrchestrator
try
{
ValidateProvider(provider);
ValidateProviderMaySeeContent(manifest, provider);
ValidateSourceMaterial(manifest, mode);
var parentContext = await this.LoadParentContextAsync(manifest, mode, parentRevisionId, token);
VisualBriefingEvidenceArtifact? reusableEvidence = null;
@@ -37,7 +37,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR
await store.SaveBuildAsync(build, token);
progressService.Publish(build);
var run = await stageRunner.RunAsync<VisualBriefingContentResponse>(provider, profile, BuildSystemContract(),
var run = await stageRunner.RunAsync<VisualBriefingContentResponse>(provider, profile, manifest.Restrictions, BuildSystemContract(),
BuildPrompt(manifest, evidence, plan), [], VisualBriefingBuildStage.CONTENT, build.OperationId, build.BuildId,
response => this.ValidateResponseAndProject(manifest, plan, evidence, response), token);
@@ -38,7 +38,7 @@ internal sealed class VisualBriefingEvidenceStage(StructuredLlmStageRunner stage
progressService.Publish(build);
var run = await stageRunner.RunAsync<VisualBriefingEvidenceResponse>(
provider, profile, BuildSystemContract(), BuildPrompt(manifest, preparedSources), preparedSources.Attachments, VisualBriefingBuildStage.EVIDENCE,
provider, profile, manifest.Restrictions, BuildSystemContract(), BuildPrompt(manifest, preparedSources), preparedSources.Attachments, VisualBriefingBuildStage.EVIDENCE,
build.OperationId, build.BuildId, response => VisualBriefingValidation.ValidateEvidence(manifest, response), token);
stage.Attempts = run.Attempts;
@@ -18,6 +18,11 @@ public enum VisualBriefingFailureCode
/// </summary>
PROVIDER_NOT_SELECTED,
/// <summary>
/// The selected provider may not see the content of the briefing.
/// </summary>
PROVIDER_NOT_ALLOWED,
/// <summary>
/// The selected model lacks a required capability.
/// </summary>
@@ -83,6 +83,7 @@ internal static class VisualBriefingFailureExtensions
private static string ToUserMessage(this VisualBriefingFailureCode code) => code switch
{
VisualBriefingFailureCode.PROVIDER_NOT_SELECTED => TB("This briefing has no provider selected. Please select a provider before you generate a briefing."),
VisualBriefingFailureCode.PROVIDER_NOT_ALLOWED => TB("The selected provider may not see the content of this briefing. Please select another provider."),
VisualBriefingFailureCode.MODEL_CAPABILITY_MISSING => TB("The selected model lacks a capability this briefing needs. Please select another model."),
VisualBriefingFailureCode.SOURCE_UNREACHABLE => TB("A source of this briefing can no longer be reached. Please relink or remove the affected source."),
VisualBriefingFailureCode.TRANSCRIPT_UNAVAILABLE => TB("A media transcript of this briefing is missing or outdated. Please transcribe the affected media again."),
@@ -1,3 +1,5 @@
using AIStudio.Chat;
namespace AIStudio.Assistants.VisualBriefing;
/// <summary>
@@ -40,6 +42,18 @@ public sealed class VisualBriefingManifest
/// </summary>
public VisualBriefingLocalSettings Settings { get; set; } = new();
/// <summary>
/// What the text sent to this briefing demands, because of where it came from.
/// </summary>
/// <remarks>
/// An answer written from mails demands here what it demanded in its chat, and every stage of every build
/// holds to it. The restrictions are set when the briefing is created for the text, and they never loosen:
/// the text may still be part of the instruction, and of the versions built from it. They live here rather
/// than in the settings, which the editor replaces with every save. A briefing nothing was sent to, or one
/// written before this property existed, demands nothing.
/// </remarks>
public ChatRestrictions Restrictions { get; init; } = ChatRestrictions.NONE;
/// <summary>
/// Defines <c>Sources</c> for the visual briefing feature.
/// </summary>
@@ -42,7 +42,7 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn
await store.SaveBuildAsync(build, token);
progressService.Publish(build);
var run = await stageRunner.RunAsync<VisualBriefingPlanResponse>(provider, profile, BuildSystemContract(), BuildPrompt(manifest, evidence),
var run = await stageRunner.RunAsync<VisualBriefingPlanResponse>(provider, profile, manifest.Restrictions, BuildSystemContract(), BuildPrompt(manifest, evidence),
[], VisualBriefingBuildStage.PLAN, build.OperationId, build.BuildId, response => VisualBriefingValidation.ValidatePlan(evidence, response), token);
stage.Attempts = run.Attempts;
@@ -42,7 +42,7 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s
await store.SaveBuildAsync(build, token);
progressService.Publish(build);
var run = await stageRunner.RunAsync<VisualBriefingDesignResponse>(provider, profile, BuildSystemContract(),
var run = await stageRunner.RunAsync<VisualBriefingDesignResponse>(provider, profile, manifest.Restrictions, BuildSystemContract(),
BuildPrompt(manifest, plan, parentPresentation), [], VisualBriefingBuildStage.DESIGN, build.OperationId, build.BuildId,
response => ValidateDesign(manifest, plan, content, response), token);
@@ -1,5 +1,7 @@
using System.Text.Json;
using AIStudio.Chat;
namespace AIStudio.Assistants.VisualBriefing;
public sealed partial class VisualBriefingStore
@@ -294,10 +296,14 @@ public sealed partial class VisualBriefingStore
/// <summary>
/// Defines <c>CreateAsync</c> for the visual briefing feature.
/// </summary>
/// <remarks>
/// The restrictions are set here only, see VisualBriefingManifest.Restrictions: nothing later on loosens them.
/// </remarks>
public async Task<VisualBriefingManifest> CreateAsync(
string name,
string author,
VisualBriefingLocalSettings settings,
ChatRestrictions restrictions,
Guid? briefingId = null,
CancellationToken token = default)
{
@@ -330,6 +336,7 @@ public sealed partial class VisualBriefingStore
CreatedAtUtc = now,
ModifiedAtUtc = now,
Settings = settings,
Restrictions = restrictions,
};
await this.StoreManifestAtomicAsync(manifest, token);
@@ -455,6 +462,8 @@ public sealed partial class VisualBriefingStore
/// </summary>
private async Task StoreManifestAtomicAsync(VisualBriefingManifest manifest, CancellationToken token)
{
// What is written now holds everything of the current version, the restrictions among them, which an older AI Studio cannot read:
manifest.ManifestVersion = VisualBriefingVersions.MANIFEST;
var json = JsonSerializer.Serialize(manifest, JSON_OPTIONS);
await WriteTextAtomicAsync(this.ManifestPath(manifest.BriefingId), json, overwrite: true, token);
}
@@ -1,6 +1,8 @@
using System.Text;
using System.Text.Json;
using AIStudio.Chat;
namespace AIStudio.Assistants.VisualBriefing;
public sealed partial class VisualBriefingStore
@@ -339,6 +341,10 @@ public sealed partial class VisualBriefingStore
export.Name,
export.Author,
SettingsFromExport(export),
// An export is a file outside AI Studio, which carries neither the instruction nor any restrictions.
// A briefing which exists here already keeps its own:
ChatRestrictions.NONE,
export.BriefingId,
token);
}
@@ -440,6 +446,9 @@ public sealed partial class VisualBriefingStore
parts.ExportManifest.Name,
parts.ExportManifest.Author,
SettingsFromExport(parts.ExportManifest),
// An export is a file outside AI Studio, which carries neither the instruction nor any restrictions:
ChatRestrictions.NONE,
copyId,
token);
@@ -9,7 +9,11 @@ public static class VisualBriefingVersions
public const int ARTIFACT = 1;
/// <summary>Gets the project manifest contract version.</summary>
public const int MANIFEST = 1;
/// <remarks>
/// Version 2 added the restrictions of a text sent to the briefing. An older version of AI Studio cannot
/// read them, so it tells the user that the briefing needs a newer version instead of ignoring them.
/// </remarks>
public const int MANIFEST = 2;
/// <summary>Gets the canonical data schema version.</summary>
public const int SCHEMA = 2;