Added support for background running assistant sessions

This commit is contained in:
Thorsten Sommer 2026-07-02 16:24:33 +02:00
parent 2ccf1d0617
commit 947db222a0
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
38 changed files with 1912 additions and 24 deletions

View File

@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Agenda;
@ -185,6 +186,85 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string inputWhoIsPresenting = string.Empty;
private readonly List<string> contentLines = [];
private static readonly AssistantSessionStateKey<string> INPUT_TOPIC_STATE_KEY = new(nameof(inputTopic));
private static readonly AssistantSessionStateKey<string> INPUT_NAME_STATE_KEY = new(nameof(inputName));
private static readonly AssistantSessionStateKey<string> INPUT_CONTENT_STATE_KEY = new(nameof(inputContent));
private static readonly AssistantSessionStateKey<string> INPUT_DURATION_STATE_KEY = new(nameof(inputDuration));
private static readonly AssistantSessionStateKey<string> INPUT_START_TIME_STATE_KEY = new(nameof(inputStartTime));
private static readonly AssistantSessionStateKey<HashSet<string>> SELECTED_FOCI_STATE_KEY = new(nameof(selectedFoci));
private static readonly AssistantSessionStateKey<HashSet<string>> JUST_BRIEFLY_STATE_KEY = new(nameof(justBriefly));
private static readonly AssistantSessionStateKey<string> INPUT_OBJECTIVE_STATE_KEY = new(nameof(inputObjective));
private static readonly AssistantSessionStateKey<string> INPUT_MODERATOR_STATE_KEY = new(nameof(inputModerator));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<bool> INTRODUCE_PARTICIPANTS_STATE_KEY = new(nameof(introduceParticipants));
private static readonly AssistantSessionStateKey<bool> IS_MEETING_VIRTUAL_STATE_KEY = new(nameof(isMeetingVirtual));
private static readonly AssistantSessionStateKey<string> INPUT_LOCATION_STATE_KEY = new(nameof(inputLocation));
private static readonly AssistantSessionStateKey<bool> GOING_TO_DINNER_STATE_KEY = new(nameof(goingToDinner));
private static readonly AssistantSessionStateKey<bool> DOING_SOCIAL_ACTIVITY_STATE_KEY = new(nameof(doingSocialActivity));
private static readonly AssistantSessionStateKey<bool> NEED_TO_ARRIVE_AND_DEPART_STATE_KEY = new(nameof(needToArriveAndDepart));
private static readonly AssistantSessionStateKey<int> DURATION_LUNCH_BREAK_STATE_KEY = new(nameof(durationLunchBreak));
private static readonly AssistantSessionStateKey<int> DURATION_BREAKS_STATE_KEY = new(nameof(durationBreaks));
private static readonly AssistantSessionStateKey<bool> ACTIVE_PARTICIPATION_STATE_KEY = new(nameof(activeParticipation));
private static readonly AssistantSessionStateKey<NumberParticipants> NUMBER_PARTICIPANTS_STATE_KEY = new(nameof(numberParticipants));
private static readonly AssistantSessionStateKey<string> INPUT_WHO_IS_PRESENTING_STATE_KEY = new(nameof(inputWhoIsPresenting));
private static readonly AssistantSessionStateKey<List<string>> CONTENT_LINES_STATE_KEY = new(nameof(contentLines));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_TOPIC_STATE_KEY, this.inputTopic);
state.Set(INPUT_NAME_STATE_KEY, this.inputName);
state.Set(INPUT_CONTENT_STATE_KEY, this.inputContent);
state.Set(INPUT_DURATION_STATE_KEY, this.inputDuration);
state.Set(INPUT_START_TIME_STATE_KEY, this.inputStartTime);
state.SetHashSet(SELECTED_FOCI_STATE_KEY, this.selectedFoci);
state.SetHashSet(JUST_BRIEFLY_STATE_KEY, this.justBriefly);
state.Set(INPUT_OBJECTIVE_STATE_KEY, this.inputObjective);
state.Set(INPUT_MODERATOR_STATE_KEY, this.inputModerator);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(INTRODUCE_PARTICIPANTS_STATE_KEY, this.introduceParticipants);
state.Set(IS_MEETING_VIRTUAL_STATE_KEY, this.isMeetingVirtual);
state.Set(INPUT_LOCATION_STATE_KEY, this.inputLocation);
state.Set(GOING_TO_DINNER_STATE_KEY, this.goingToDinner);
state.Set(DOING_SOCIAL_ACTIVITY_STATE_KEY, this.doingSocialActivity);
state.Set(NEED_TO_ARRIVE_AND_DEPART_STATE_KEY, this.needToArriveAndDepart);
state.Set(DURATION_LUNCH_BREAK_STATE_KEY, this.durationLunchBreak);
state.Set(DURATION_BREAKS_STATE_KEY, this.durationBreaks);
state.Set(ACTIVE_PARTICIPATION_STATE_KEY, this.activeParticipation);
state.Set(NUMBER_PARTICIPANTS_STATE_KEY, this.numberParticipants);
state.Set(INPUT_WHO_IS_PRESENTING_STATE_KEY, this.inputWhoIsPresenting);
state.SetList(CONTENT_LINES_STATE_KEY, this.contentLines);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_TOPIC_STATE_KEY, value => this.inputTopic = value);
state.Restore(INPUT_NAME_STATE_KEY, value => this.inputName = value);
state.Restore(INPUT_CONTENT_STATE_KEY, value => this.inputContent = value);
state.Restore(INPUT_DURATION_STATE_KEY, value => this.inputDuration = value);
state.Restore(INPUT_START_TIME_STATE_KEY, value => this.inputStartTime = value);
state.Restore(SELECTED_FOCI_STATE_KEY, value => this.selectedFoci = value);
state.Restore(JUST_BRIEFLY_STATE_KEY, value => this.justBriefly = value);
state.Restore(INPUT_OBJECTIVE_STATE_KEY, value => this.inputObjective = value);
state.Restore(INPUT_MODERATOR_STATE_KEY, value => this.inputModerator = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(INTRODUCE_PARTICIPANTS_STATE_KEY, value => this.introduceParticipants = value);
state.Restore(IS_MEETING_VIRTUAL_STATE_KEY, value => this.isMeetingVirtual = value);
state.Restore(INPUT_LOCATION_STATE_KEY, value => this.inputLocation = value);
state.Restore(GOING_TO_DINNER_STATE_KEY, value => this.goingToDinner = value);
state.Restore(DOING_SOCIAL_ACTIVITY_STATE_KEY, value => this.doingSocialActivity = value);
state.Restore(NEED_TO_ARRIVE_AND_DEPART_STATE_KEY, value => this.needToArriveAndDepart = value);
state.Restore(DURATION_LUNCH_BREAK_STATE_KEY, value => this.durationLunchBreak = value);
state.Restore(DURATION_BREAKS_STATE_KEY, value => this.durationBreaks = value);
state.Restore(ACTIVE_PARTICIPATION_STATE_KEY, value => this.activeParticipation = value);
state.Restore(NUMBER_PARTICIPANTS_STATE_KEY, value => this.numberParticipants = value);
state.Restore(INPUT_WHO_IS_PRESENTING_STATE_KEY, value => this.inputWhoIsPresenting = value);
state.RestoreList(CONTENT_LINES_STATE_KEY, this.contentLines);
}
#region Overrides of ComponentBase

View File

@ -41,7 +41,7 @@
<MudButton Disabled="@(this.SubmitDisabled || this.isProcessing)" Variant="Variant.Filled" OnClick="@(async () => await this.Start())" Style="@this.SubmitButtonStyle">
@this.SubmitText
</MudButton>
@if (this.isProcessing && this.CancellationTokenSource is not null)
@if (this.isProcessing)
{
<MudTooltip Text="@TB("Stop generation")">
<MudIconButton Variant="Variant.Filled" Icon="@Icons.Material.Filled.Stop" Color="Color.Error" OnClick="@(async () => await this.CancelStreaming())"/>

View File

@ -2,6 +2,7 @@ using AIStudio.Chat;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
@ -36,6 +37,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
[Inject]
private MudTheme ColorTheme { get; init; } = null!;
[Inject]
protected AssistantSessionService AssistantSessionService { get; init; } = null!;
protected abstract string Title { get; }
@ -119,12 +123,35 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected ChatThread? ChatThread;
protected IContent? LastUserPrompt;
protected CancellationTokenSource? CancellationTokenSource;
private static readonly AssistantSessionStateKey<AIStudio.Settings.Provider> PROVIDER_SETTINGS_STATE_KEY = new(nameof(ProviderSettings));
private static readonly AssistantSessionStateKey<bool> INPUT_IS_VALID_STATE_KEY = new(nameof(InputIsValid));
private static readonly AssistantSessionStateKey<Profile> CURRENT_PROFILE_STATE_KEY = new(nameof(CurrentProfile));
private static readonly AssistantSessionStateKey<ChatTemplate> CURRENT_CHAT_TEMPLATE_STATE_KEY = new(nameof(CurrentChatTemplate));
private static readonly AssistantSessionStateKey<ChatThread?> CHAT_THREAD_STATE_KEY = new(nameof(ChatThread));
private static readonly AssistantSessionStateKey<IContent?> LAST_USER_PROMPT_STATE_KEY = new(nameof(LastUserPrompt));
private static readonly AssistantSessionStateKey<ContentBlock?> RESULTING_CONTENT_BLOCK_STATE_KEY = new(nameof(resultingContentBlock));
private static readonly AssistantSessionStateKey<string[]> INPUT_ISSUES_STATE_KEY = new(nameof(inputIssues));
private static readonly AssistantSessionStateKey<bool> IS_PROCESSING_STATE_KEY = new(nameof(isProcessing));
private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));
private ContentBlock? resultingContentBlock;
private string[] inputIssues = [];
private bool isProcessing;
private bool isDisposed;
private AssistantSessionKey assistantSessionKey;
private Guid? assistantSessionId;
/// <summary>
/// Gets whether the Blazor component instance has already been disposed.
/// </summary>
protected bool IsAssistantComponentDisposed => this.isDisposed;
/// <summary>
/// Gets the assistant-specific identifier used to distinguish session slots.
/// </summary>
protected virtual string AssistantSessionInstanceId => this.GetType().FullName ?? this.Component.ToString();
#region Overrides of ComponentBase
@ -150,6 +177,8 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.ProviderSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
this.CurrentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.CurrentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
this.assistantSessionKey = new(this.Component, this.AssistantSessionInstanceId);
await this.AttachAssistantSessionIfAvailable();
}
protected override async Task OnParametersSetAsync()
@ -191,12 +220,63 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
private async Task Start()
{
using (this.CancellationTokenSource = new())
var activeSession = this.AssistantSessionService.TryGetSnapshot(this.assistantSessionKey);
if (activeSession?.IsActive ?? false)
{
await this.AttachAssistantSession(activeSession, restoreClientOnlyContent: true);
return;
}
this.CancellationTokenSource = new();
this.isProcessing = true;
var startedSession = this.AssistantSessionService.TryBegin(this.assistantSessionKey, this.Title, this.CancellationTokenSource, this.ChatThread, this.CaptureAssistantSessionState());
if (startedSession.IsActive is not true || startedSession.Key != this.assistantSessionKey)
{
this.CancellationTokenSource.Dispose();
this.CancellationTokenSource = null;
return;
}
this.assistantSessionId = startedSession.SessionId;
await this.RefreshAssistantUIAsync();
var sessionStatus = AssistantSessionStatus.COMPLETED;
var errorMessage = string.Empty;
try
{
await this.SubmitAction();
if (this.CancellationTokenSource?.IsCancellationRequested ?? false)
sessionStatus = AssistantSessionStatus.CANCELED;
}
catch (OperationCanceledException)
{
sessionStatus = AssistantSessionStatus.CANCELED;
}
catch (ProviderRequestException e)
{
sessionStatus = AssistantSessionStatus.FAILED;
errorMessage = e.UserMessage;
this.Logger.LogError(e, "The provider request failed for assistant '{AssistantTitle}'. Status={StatusCode}, Reason='{ReasonPhrase}', Body='{ResponseBody}'", this.Title, e.StatusCode, e.ReasonPhrase, e.ResponseBody);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, e.UserMessage));
}
catch (Exception e)
{
sessionStatus = AssistantSessionStatus.FAILED;
errorMessage = e.Message;
this.Logger.LogError(e, "The assistant session '{AssistantTitle}' failed.", this.Title);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, string.Format(this.TB("The assistant failed. The message is: '{0}'"), e.Message)));
}
finally
{
this.isProcessing = false;
var sessionCancellationTokenSource = this.CancellationTokenSource;
this.CancellationTokenSource = null;
if (this.assistantSessionId is { } sessionId)
await this.AssistantSessionService.CompleteAsync(this.assistantSessionKey, sessionId, sessionStatus, errorMessage, this.ChatThread, this.CaptureAssistantSessionState());
sessionCancellationTokenSource?.Dispose();
await this.RefreshAssistantUIAsync();
}
this.CancellationTokenSource = null;
}
private void TriggerFormChange(FormFieldChangedEventArgs _)
@ -224,7 +304,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
Array.Resize(ref this.inputIssues, this.inputIssues.Length + 1);
this.inputIssues[^1] = issue;
this.InputIsValid = false;
this.StateHasChanged();
_ = this.RefreshAssistantUIAsync();
}
/// <summary>
@ -234,7 +314,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
{
this.inputIssues = [];
this.InputIsValid = true;
this.StateHasChanged();
_ = this.RefreshAssistantUIAsync();
}
protected void CreateChatThread()
@ -310,6 +390,18 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
InitialRemoteWait = true,
};
aiText.StreamingEvent = async () =>
{
await this.CheckpointAssistantSession();
await this.RefreshAssistantUIAsync();
};
aiText.StreamingDone = async () =>
{
await this.CheckpointAssistantSession();
await this.RefreshAssistantUIAsync();
};
this.resultingContentBlock = new ContentBlock
{
Time = time,
@ -326,7 +418,8 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
}
this.isProcessing = true;
this.StateHasChanged();
await this.CheckpointAssistantSession();
await this.RefreshAssistantUIAsync();
try
{
@ -353,8 +446,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
}
finally
{
this.isProcessing = false;
this.StateHasChanged();
this.isProcessing = this.assistantSessionId is not null && (this.AssistantSessionService.TryGetSnapshot(this.assistantSessionKey)?.IsActive ?? false);
await this.CheckpointAssistantSession();
await this.RefreshAssistantUIAsync();
if(manageCancellationLocally)
{
@ -366,9 +460,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
private async Task CancelStreaming()
{
if (this.CancellationTokenSource is not null)
if(!this.CancellationTokenSource.IsCancellationRequested)
await this.CancellationTokenSource.CancelAsync();
await this.AssistantSessionService.CancelAsync(this.assistantSessionKey);
}
protected async Task CopyToClipboard()
@ -434,10 +526,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
await this.DialogService.ShowAsync<TSettings>(null, dialogParameters, DialogOptions.FULLSCREEN);
}
protected Task SendToAssistant(Tools.Components destination, SendToButton sendToButton)
protected async Task SendToAssistant(Tools.Components destination, SendToButton sendToButton)
{
if (!this.CanSendToAssistant(destination))
return Task.CompletedTask;
return;
var contentToSend = sendToButton == default ? string.Empty : sendToButton.UseResultingContentBlockData switch
{
@ -450,6 +542,16 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
};
var sendToData = destination.GetData();
if (destination is not Tools.Components.CHAT && this.AssistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && snapshot.Key.Component == destination))
{
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);
return;
}
if (destination is not Tools.Components.CHAT)
await this.AssistantSessionService.ClearInactiveSessionsForComponentAsync(destination);
switch (destination)
{
case Tools.Components.CHAT:
@ -469,7 +571,6 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
}
this.NavigationManager.NavigateTo(sendToData.Route);
return Task.CompletedTask;
}
private bool CanSendToAssistant(Tools.Components component)
@ -482,6 +583,11 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
private async Task InnerResetForm()
{
if (this.AssistantSessionService.TryGetSnapshot(this.assistantSessionKey)?.IsActive ?? false)
return;
await this.AssistantSessionService.ClearAsync(this.assistantSessionKey);
this.assistantSessionId = null;
this.resultingContentBlock = null;
this.ProviderSettings = Settings.Provider.NONE;
@ -495,7 +601,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.inputIssues = [];
this.Form?.ResetValidation();
this.StateHasChanged();
await this.RefreshAssistantUIAsync();
this.Form?.ResetValidation();
}
@ -515,6 +621,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected override void DisposeResources()
{
this.isDisposed = true;
try
{
this.formChangeTimer.Stop();
@ -529,4 +636,153 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
}
#endregion
}
#region Assistant sessions
/// <summary>
/// Stores the current assistant UI and chat state in the active assistant session.
/// </summary>
/// <returns>A task that completes after the checkpoint was stored and published.</returns>
private Task CheckpointAssistantSession()
{
if (this.assistantSessionId is null)
return Task.CompletedTask;
return this.AssistantSessionService.CheckpointAsync(this.assistantSessionKey, this.assistantSessionId.Value, this.Title, this.ChatThread, this.CaptureAssistantSessionState());
}
/// <summary>
/// Allows derived assistants to restore client-only UI after a session was attached.
/// </summary>
/// <param name="snapshot">The assistant session snapshot that was attached.</param>
/// <returns>A task that completes after derived UI restore work has finished.</returns>
protected virtual Task OnAssistantSessionAttachedAsync(AssistantSessionSnapshot snapshot) => Task.CompletedTask;
/// <summary>
/// Handles assistant session change events for the current assistant instance.
/// </summary>
/// <typeparam name="T">The message payload type.</typeparam>
/// <param name="sendingComponent">The component that sent the message, if any.</param>
/// <param name="triggeredEvent">The event that was triggered.</param>
/// <param name="data">The message payload.</param>
/// <returns>A task that completes after the message was processed.</returns>
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
switch (triggeredEvent)
{
case Event.ASSISTANT_SESSION_CHANGED:
case Event.ASSISTANT_SESSION_FINISHED:
if (data is AssistantSessionSnapshot snapshot && snapshot.Key == this.assistantSessionKey)
await this.AttachAssistantSession(snapshot, restoreClientOnlyContent: triggeredEvent is Event.ASSISTANT_SESSION_FINISHED);
break;
}
}
/// <summary>
/// Attaches the component to an existing assistant session if one is available.
/// </summary>
/// <returns>A task that completes after the session was attached.</returns>
private async Task AttachAssistantSessionIfAvailable()
{
var snapshot = this.AssistantSessionService.TryGetSnapshot(this.assistantSessionKey);
if (snapshot is null)
return;
await this.AttachAssistantSession(snapshot, restoreClientOnlyContent: true);
}
/// <summary>
/// Applies an assistant session snapshot to this component instance.
/// </summary>
/// <param name="snapshot">The snapshot to attach.</param>
/// <param name="restoreClientOnlyContent">Whether derived assistants should restore client-only UI state.</param>
/// <returns>A task that completes after the component was refreshed.</returns>
private async Task AttachAssistantSession(AssistantSessionSnapshot snapshot, bool restoreClientOnlyContent)
{
this.assistantSessionId = snapshot.SessionId;
this.ImportAssistantSessionState(snapshot.State);
this.ChatThread = snapshot.ChatThread ?? this.ChatThread;
this.isProcessing = snapshot.IsActive;
if (!snapshot.IsActive)
this.CancellationTokenSource = null;
if (restoreClientOnlyContent)
await this.OnAssistantSessionAttachedAsync(snapshot);
await this.RefreshAssistantUIAsync();
}
/// <summary>
/// Refreshes the component when it is still mounted.
/// </summary>
/// <returns>A task that completes after the renderer was notified.</returns>
private async Task RefreshAssistantUIAsync()
{
if (this.isDisposed)
return;
try
{
await this.InvokeAsync(this.StateHasChanged);
}
catch (InvalidOperationException)
{
// The component may already have left the renderer while a background session is finishing.
}
}
/// <summary>
/// Captures the base assistant state and assistant-specific typed state values for session restore.
/// </summary>
/// <returns>A dictionary containing the current assistant state.</returns>
private Dictionary<string, IAssistantSessionSnapshotField> CaptureAssistantSessionState()
{
var state = new AssistantSessionStateWriter();
state.Set(PROVIDER_SETTINGS_STATE_KEY, this.ProviderSettings);
state.Set(INPUT_IS_VALID_STATE_KEY, this.InputIsValid);
state.Set(CURRENT_PROFILE_STATE_KEY, this.CurrentProfile);
state.Set(CURRENT_CHAT_TEMPLATE_STATE_KEY, this.CurrentChatTemplate);
state.Set(CHAT_THREAD_STATE_KEY, this.ChatThread);
state.Set(LAST_USER_PROMPT_STATE_KEY, this.LastUserPrompt);
state.Set(RESULTING_CONTENT_BLOCK_STATE_KEY, this.resultingContentBlock);
state.Set(INPUT_ISSUES_STATE_KEY, this.inputIssues);
state.Set(IS_PROCESSING_STATE_KEY, this.isProcessing);
this.CaptureCustomAssistantSessionState(state);
return state.ToDictionary();
}
/// <summary>
/// Captures assistant-specific state values.
/// </summary>
/// <param name="state">The typed state writer to update.</param>
protected virtual void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state) { }
/// <summary>
/// Restores the base assistant state and assistant-specific typed state values from a session snapshot.
/// </summary>
/// <param name="state">The captured assistant state to import.</param>
private void ImportAssistantSessionState(IReadOnlyDictionary<string, IAssistantSessionSnapshotField> state)
{
var reader = new AssistantSessionStateReader(state, this.Title);
reader.Restore(PROVIDER_SETTINGS_STATE_KEY, value => this.ProviderSettings = value);
reader.Restore(INPUT_IS_VALID_STATE_KEY, value => this.InputIsValid = value);
reader.Restore(CURRENT_PROFILE_STATE_KEY, value => this.CurrentProfile = value);
reader.Restore(CURRENT_CHAT_TEMPLATE_STATE_KEY, value => this.CurrentChatTemplate = value);
reader.Restore(CHAT_THREAD_STATE_KEY, value => this.ChatThread = value);
reader.Restore(LAST_USER_PROMPT_STATE_KEY, value => this.LastUserPrompt = value);
reader.Restore(RESULTING_CONTENT_BLOCK_STATE_KEY, value => this.resultingContentBlock = value);
reader.Restore(INPUT_ISSUES_STATE_KEY, value => this.inputIssues = value);
reader.Restore(IS_PROCESSING_STATE_KEY, value => this.isProcessing = value);
this.RestoreCustomAssistantSessionState(reader);
}
/// <summary>
/// Restores assistant-specific state values.
/// </summary>
/// <param name="state">The typed state reader to read from.</param>
protected virtual void RestoreCustomAssistantSessionState(AssistantSessionStateReader state) { }
#endregion
}

View File

@ -3,6 +3,7 @@ using System.Text;
using AIStudio.Chat;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.BiasDay;
@ -66,6 +67,25 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore<SettingsDialogAss
private Bias biasOfTheDay = BiasCatalog.NONE;
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
private string customTargetLanguage = string.Empty;
private static readonly AssistantSessionStateKey<Bias> BIAS_OF_THE_DAY_STATE_KEY = new(nameof(biasOfTheDay));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(BIAS_OF_THE_DAY_STATE_KEY, this.biasOfTheDay);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(BIAS_OF_THE_DAY_STATE_KEY, value => this.biasOfTheDay = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
private string? ValidateTargetLanguage(CommonLanguages language)
{

View File

@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Coding;
@ -59,6 +60,28 @@ public partial class AssistantCoding : AssistantBaseCore<SettingsDialogCoding>
private bool provideCompilerMessages;
private string compilerMessages = string.Empty;
private string questions = string.Empty;
private static readonly AssistantSessionStateKey<List<CodingContext>> CODING_CONTEXTS_STATE_KEY = new(nameof(codingContexts));
private static readonly AssistantSessionStateKey<bool> PROVIDE_COMPILER_MESSAGES_STATE_KEY = new(nameof(provideCompilerMessages));
private static readonly AssistantSessionStateKey<string> COMPILER_MESSAGES_STATE_KEY = new(nameof(compilerMessages));
private static readonly AssistantSessionStateKey<string> QUESTIONS_STATE_KEY = new(nameof(questions));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.SetList(CODING_CONTEXTS_STATE_KEY, this.codingContexts);
state.Set(PROVIDE_COMPILER_MESSAGES_STATE_KEY, this.provideCompilerMessages);
state.Set(COMPILER_MESSAGES_STATE_KEY, this.compilerMessages);
state.Set(QUESTIONS_STATE_KEY, this.questions);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.RestoreList(CODING_CONTEXTS_STATE_KEY, this.codingContexts);
state.Restore(PROVIDE_COMPILER_MESSAGES_STATE_KEY, value => this.provideCompilerMessages = value);
state.Restore(COMPILER_MESSAGES_STATE_KEY, value => this.compilerMessages = value);
state.Restore(QUESTIONS_STATE_KEY, value => this.questions = value);
}
#region Overrides of ComponentBase

View File

@ -7,6 +7,7 @@ using AIStudio.Dialogs.Settings;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.AssistantSessions;
using Microsoft.AspNetCore.Components;
@ -279,6 +280,55 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
private ProfilePreselection policyPreselectedProfile = ProfilePreselection.NoProfile;
private HashSet<FileAttachment> loadedDocumentPaths = [];
private readonly List<ConfigurationSelectData<string>> availableLLMProviders = new();
private static readonly AssistantSessionStateKey<DataDocumentAnalysisPolicy?> SELECTED_POLICY_STATE_KEY = new(nameof(selectedPolicy));
private static readonly AssistantSessionStateKey<bool> POLICY_IS_PROTECTED_STATE_KEY = new(nameof(policyIsProtected));
private static readonly AssistantSessionStateKey<bool> POLICY_HIDE_POLICY_DEFINITION_STATE_KEY = new(nameof(policyHidePolicyDefinition));
private static readonly AssistantSessionStateKey<bool> POLICY_DEFINITION_EXPANDED_STATE_KEY = new(nameof(policyDefinitionExpanded));
private static readonly AssistantSessionStateKey<string> POLICY_NAME_STATE_KEY = new(nameof(policyName));
private static readonly AssistantSessionStateKey<string> POLICY_DESCRIPTION_STATE_KEY = new(nameof(policyDescription));
private static readonly AssistantSessionStateKey<string> POLICY_ANALYSIS_RULES_STATE_KEY = new(nameof(policyAnalysisRules));
private static readonly AssistantSessionStateKey<string> POLICY_OUTPUT_RULES_STATE_KEY = new(nameof(policyOutputRules));
private static readonly AssistantSessionStateKey<ConfidenceLevel> POLICY_MINIMUM_PROVIDER_CONFIDENCE_STATE_KEY = new(nameof(policyMinimumProviderConfidence));
private static readonly AssistantSessionStateKey<string> POLICY_PRESELECTED_PROVIDER_ID_STATE_KEY = new(nameof(policyPreselectedProviderId));
private static readonly AssistantSessionStateKey<ProfilePreselection> POLICY_PRESELECTED_PROFILE_STATE_KEY = new(nameof(policyPreselectedProfile));
private static readonly AssistantSessionStateKey<HashSet<FileAttachment>> LOADED_DOCUMENT_PATHS_STATE_KEY = new(nameof(loadedDocumentPaths));
private static readonly AssistantSessionStateKey<List<ConfigurationSelectData<string>>> AVAILABLE_LLM_PROVIDERS_STATE_KEY = new(nameof(availableLLMProviders));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(SELECTED_POLICY_STATE_KEY, this.selectedPolicy);
state.Set(POLICY_IS_PROTECTED_STATE_KEY, this.policyIsProtected);
state.Set(POLICY_HIDE_POLICY_DEFINITION_STATE_KEY, this.policyHidePolicyDefinition);
state.Set(POLICY_DEFINITION_EXPANDED_STATE_KEY, this.policyDefinitionExpanded);
state.Set(POLICY_NAME_STATE_KEY, this.policyName);
state.Set(POLICY_DESCRIPTION_STATE_KEY, this.policyDescription);
state.Set(POLICY_ANALYSIS_RULES_STATE_KEY, this.policyAnalysisRules);
state.Set(POLICY_OUTPUT_RULES_STATE_KEY, this.policyOutputRules);
state.Set(POLICY_MINIMUM_PROVIDER_CONFIDENCE_STATE_KEY, this.policyMinimumProviderConfidence);
state.Set(POLICY_PRESELECTED_PROVIDER_ID_STATE_KEY, this.policyPreselectedProviderId);
state.Set(POLICY_PRESELECTED_PROFILE_STATE_KEY, this.policyPreselectedProfile);
state.SetHashSet(LOADED_DOCUMENT_PATHS_STATE_KEY, this.loadedDocumentPaths);
state.SetList(AVAILABLE_LLM_PROVIDERS_STATE_KEY, this.availableLLMProviders);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(SELECTED_POLICY_STATE_KEY, value => this.selectedPolicy = value);
state.Restore(POLICY_IS_PROTECTED_STATE_KEY, value => this.policyIsProtected = value);
state.Restore(POLICY_HIDE_POLICY_DEFINITION_STATE_KEY, value => this.policyHidePolicyDefinition = value);
state.Restore(POLICY_DEFINITION_EXPANDED_STATE_KEY, value => this.policyDefinitionExpanded = value);
state.Restore(POLICY_NAME_STATE_KEY, value => this.policyName = value);
state.Restore(POLICY_DESCRIPTION_STATE_KEY, value => this.policyDescription = value);
state.Restore(POLICY_ANALYSIS_RULES_STATE_KEY, value => this.policyAnalysisRules = value);
state.Restore(POLICY_OUTPUT_RULES_STATE_KEY, value => this.policyOutputRules = value);
state.Restore(POLICY_MINIMUM_PROVIDER_CONFIDENCE_STATE_KEY, value => this.policyMinimumProviderConfidence = value);
state.Restore(POLICY_PRESELECTED_PROVIDER_ID_STATE_KEY, value => this.policyPreselectedProviderId = value);
state.Restore(POLICY_PRESELECTED_PROFILE_STATE_KEY, value => this.policyPreselectedProfile = value);
state.RestoreHashSet(LOADED_DOCUMENT_PATHS_STATE_KEY, this.loadedDocumentPaths);
state.RestoreList(AVAILABLE_LLM_PROVIDERS_STATE_KEY, this.availableLLMProviders);
}
private bool IsNoPolicySelectedOrProtected => this.selectedPolicy is null || this.selectedPolicy.IsProtected;
@ -515,7 +565,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
break;
}
return Task.CompletedTask;
return base.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
}
#endregion

View File

@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
@ -27,6 +28,11 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
// Reuse chat-level provider filtering/preselection instead of NONE.
protected override Tools.Components Component => Tools.Components.CHAT;
/// <summary>
/// Gets the plugin ID as the assistant session instance ID.
/// </summary>
protected override string AssistantSessionInstanceId => this.assistantPlugin is null ? base.AssistantSessionInstanceId : this.assistantPlugin.Id.ToString();
private string title = string.Empty;
private string description = string.Empty;
private string systemPrompt = string.Empty;
@ -44,6 +50,61 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
private string securityMessage = string.Empty;
private bool isSecurityBlocked;
private const string ASSISTANT_QUERY_KEY = "assistantId";
private static readonly AssistantSessionStateKey<string> TITLE_STATE_KEY = new(nameof(title));
private static readonly AssistantSessionStateKey<string> DESCRIPTION_STATE_KEY = new(nameof(description));
private static readonly AssistantSessionStateKey<string> SYSTEM_PROMPT_STATE_KEY = new(nameof(systemPrompt));
private static readonly AssistantSessionStateKey<bool> ALLOW_PROFILES_STATE_KEY = new(nameof(allowProfiles));
private static readonly AssistantSessionStateKey<string> SUBMIT_TEXT_STATE_KEY = new(nameof(submitText));
private static readonly AssistantSessionStateKey<bool> SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY = new(nameof(showFooterProfileSelection));
private static readonly AssistantSessionStateKey<PluginAssistants?> ASSISTANT_PLUGIN_STATE_KEY = new(nameof(assistantPlugin));
private static readonly AssistantSessionStateKey<AssistantState> ASSISTANT_STATE_STATE_KEY = new(nameof(assistantState));
private static readonly AssistantSessionStateKey<Dictionary<string, string>> IMAGE_CACHE_STATE_KEY = new(nameof(imageCache));
private static readonly AssistantSessionStateKey<HashSet<string>> EXECUTING_BUTTON_ACTIONS_STATE_KEY = new(nameof(executingButtonActions));
private static readonly AssistantSessionStateKey<HashSet<string>> EXECUTING_SWITCH_ACTIONS_STATE_KEY = new(nameof(executingSwitchActions));
private static readonly AssistantSessionStateKey<string> PLUGIN_PATH_STATE_KEY = new(nameof(pluginPath));
private static readonly AssistantSessionStateKey<PluginAssistantAudit?> AUDIT_STATE_KEY = new(nameof(audit));
private static readonly AssistantSessionStateKey<string> SECURITY_MESSAGE_STATE_KEY = new(nameof(securityMessage));
private static readonly AssistantSessionStateKey<bool> IS_SECURITY_BLOCKED_STATE_KEY = new(nameof(isSecurityBlocked));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(TITLE_STATE_KEY, this.title);
state.Set(DESCRIPTION_STATE_KEY, this.description);
state.Set(SYSTEM_PROMPT_STATE_KEY, this.systemPrompt);
state.Set(ALLOW_PROFILES_STATE_KEY, this.allowProfiles);
state.Set(SUBMIT_TEXT_STATE_KEY, this.submitText);
state.Set(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, this.showFooterProfileSelection);
state.Set(ASSISTANT_PLUGIN_STATE_KEY, this.assistantPlugin);
state.Set(ASSISTANT_STATE_STATE_KEY, this.assistantState.Clone());
state.SetDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
state.SetHashSet(EXECUTING_BUTTON_ACTIONS_STATE_KEY, this.executingButtonActions);
state.SetHashSet(EXECUTING_SWITCH_ACTIONS_STATE_KEY, this.executingSwitchActions);
state.Set(PLUGIN_PATH_STATE_KEY, this.pluginPath);
state.Set(AUDIT_STATE_KEY, this.audit);
state.Set(SECURITY_MESSAGE_STATE_KEY, this.securityMessage);
state.Set(IS_SECURITY_BLOCKED_STATE_KEY, this.isSecurityBlocked);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(TITLE_STATE_KEY, value => this.title = value);
state.Restore(DESCRIPTION_STATE_KEY, value => this.description = value);
state.Restore(SYSTEM_PROMPT_STATE_KEY, value => this.systemPrompt = value);
state.Restore(ALLOW_PROFILES_STATE_KEY, value => this.allowProfiles = value);
state.Restore(SUBMIT_TEXT_STATE_KEY, value => this.submitText = value);
state.Restore(SHOW_FOOTER_PROFILE_SELECTION_STATE_KEY, value => this.showFooterProfileSelection = value);
state.Restore(ASSISTANT_PLUGIN_STATE_KEY, value => this.assistantPlugin = value);
state.Restore(ASSISTANT_STATE_STATE_KEY, value => this.assistantState.CopyFrom(value));
state.RestoreDictionary(IMAGE_CACHE_STATE_KEY, this.imageCache);
state.RestoreHashSet(EXECUTING_BUTTON_ACTIONS_STATE_KEY, this.executingButtonActions);
state.RestoreHashSet(EXECUTING_SWITCH_ACTIONS_STATE_KEY, this.executingSwitchActions);
state.Restore(PLUGIN_PATH_STATE_KEY, value => this.pluginPath = value);
state.Restore(AUDIT_STATE_KEY, value => this.audit = value);
state.Restore(SECURITY_MESSAGE_STATE_KEY, value => this.securityMessage = value);
state.Restore(IS_SECURITY_BLOCKED_STATE_KEY, value => this.isSecurityBlocked = value);
}
#region Implementation of AssistantBase

View File

@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.EMail;
@ -78,6 +79,46 @@ public partial class AssistantEMail : AssistantBaseCore<SettingsDialogWritingEMa
private string customTargetLanguage = string.Empty;
private bool provideHistory;
private string inputHistory = string.Empty;
private static readonly AssistantSessionStateKey<WritingStyles> SELECTED_WRITING_STYLE_STATE_KEY = new(nameof(selectedWritingStyle));
private static readonly AssistantSessionStateKey<string> INPUT_GREETING_STATE_KEY = new(nameof(inputGreeting));
private static readonly AssistantSessionStateKey<string> INPUT_BULLET_POINTS_STATE_KEY = new(nameof(inputBulletPoints));
private static readonly AssistantSessionStateKey<List<string>> BULLET_POINTS_LINES_STATE_KEY = new(nameof(bulletPointsLines));
private static readonly AssistantSessionStateKey<HashSet<string>> SELECTED_FOCI_STATE_KEY = new(nameof(selectedFoci));
private static readonly AssistantSessionStateKey<string> INPUT_NAME_STATE_KEY = new(nameof(inputName));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<bool> PROVIDE_HISTORY_STATE_KEY = new(nameof(provideHistory));
private static readonly AssistantSessionStateKey<string> INPUT_HISTORY_STATE_KEY = new(nameof(inputHistory));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(SELECTED_WRITING_STYLE_STATE_KEY, this.selectedWritingStyle);
state.Set(INPUT_GREETING_STATE_KEY, this.inputGreeting);
state.Set(INPUT_BULLET_POINTS_STATE_KEY, this.inputBulletPoints);
state.SetList(BULLET_POINTS_LINES_STATE_KEY, this.bulletPointsLines);
state.SetHashSet(SELECTED_FOCI_STATE_KEY, this.selectedFoci);
state.Set(INPUT_NAME_STATE_KEY, this.inputName);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(PROVIDE_HISTORY_STATE_KEY, this.provideHistory);
state.Set(INPUT_HISTORY_STATE_KEY, this.inputHistory);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(SELECTED_WRITING_STYLE_STATE_KEY, value => this.selectedWritingStyle = value);
state.Restore(INPUT_GREETING_STATE_KEY, value => this.inputGreeting = value);
state.Restore(INPUT_BULLET_POINTS_STATE_KEY, value => this.inputBulletPoints = value);
state.RestoreList(BULLET_POINTS_LINES_STATE_KEY, this.bulletPointsLines);
state.Restore(SELECTED_FOCI_STATE_KEY, value => this.selectedFoci = value);
state.Restore(INPUT_NAME_STATE_KEY, value => this.inputName = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(PROVIDE_HISTORY_STATE_KEY, value => this.provideHistory = value);
state.Restore(INPUT_HISTORY_STATE_KEY, value => this.inputHistory = value);
}
#region Overrides of ComponentBase

View File

@ -5,6 +5,7 @@ using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Dialogs.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.AssistantSessions;
using Microsoft.AspNetCore.Components;
@ -449,6 +450,88 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
private bool writeToFilesystem;
private string baseDirectory = string.Empty;
private List<string> previouslyGeneratedFiles = new();
private static readonly AssistantSessionStateKey<DataERIServer?> SELECTED_ERI_SERVER_STATE_KEY = new(nameof(selectedERIServer));
private static readonly AssistantSessionStateKey<bool> AUTO_SAVE_STATE_KEY = new(nameof(autoSave));
private static readonly AssistantSessionStateKey<string> SERVER_NAME_STATE_KEY = new(nameof(serverName));
private static readonly AssistantSessionStateKey<string> SERVER_DESCRIPTION_STATE_KEY = new(nameof(serverDescription));
private static readonly AssistantSessionStateKey<ERIVersion> SELECTED_ERI_VERSION_STATE_KEY = new(nameof(selectedERIVersion));
private static readonly AssistantSessionStateKey<string?> ERI_SPECIFICATION_STATE_KEY = new(nameof(eriSpecification));
private static readonly AssistantSessionStateKey<ProgrammingLanguages> SELECTED_PROGRAMMING_LANGUAGE_STATE_KEY = new(nameof(selectedProgrammingLanguage));
private static readonly AssistantSessionStateKey<string> OTHER_PROGRAMMING_LANGUAGE_STATE_KEY = new(nameof(otherProgrammingLanguage));
private static readonly AssistantSessionStateKey<DataSources> SELECTED_DATA_SOURCE_STATE_KEY = new(nameof(selectedDataSource));
private static readonly AssistantSessionStateKey<string> OTHER_DATA_SOURCE_STATE_KEY = new(nameof(otherDataSource));
private static readonly AssistantSessionStateKey<string> DATA_SOURCE_PRODUCT_NAME_STATE_KEY = new(nameof(dataSourceProductName));
private static readonly AssistantSessionStateKey<string> DATA_SOURCE_HOSTNAME_STATE_KEY = new(nameof(dataSourceHostname));
private static readonly AssistantSessionStateKey<int?> DATA_SOURCE_PORT_STATE_KEY = new(nameof(dataSourcePort));
private static readonly AssistantSessionStateKey<bool> USER_TYPED_PORT_STATE_KEY = new(nameof(userTypedPort));
private static readonly AssistantSessionStateKey<HashSet<Auth>> SELECTED_AUTHENTICATION_METHODS_STATE_KEY = new(nameof(selectedAuthenticationMethods));
private static readonly AssistantSessionStateKey<string> AUTH_DESCRIPTION_STATE_KEY = new(nameof(authDescription));
private static readonly AssistantSessionStateKey<OperatingSystem> SELECTED_OPERATING_SYSTEM_STATE_KEY = new(nameof(selectedOperatingSystem));
private static readonly AssistantSessionStateKey<AllowedLLMProviders> ALLOWED_LLM_PROVIDERS_STATE_KEY = new(nameof(allowedLLMProviders));
private static readonly AssistantSessionStateKey<List<EmbeddingInfo>> EMBEDDINGS_STATE_KEY = new(nameof(embeddings));
private static readonly AssistantSessionStateKey<List<RetrievalInfo>> RETRIEVAL_PROCESSES_STATE_KEY = new(nameof(retrievalProcesses));
private static readonly AssistantSessionStateKey<string> ADDITIONAL_LIBRARIES_STATE_KEY = new(nameof(additionalLibraries));
private static readonly AssistantSessionStateKey<bool> WRITE_TO_FILESYSTEM_STATE_KEY = new(nameof(writeToFilesystem));
private static readonly AssistantSessionStateKey<string> BASE_DIRECTORY_STATE_KEY = new(nameof(baseDirectory));
private static readonly AssistantSessionStateKey<List<string>> PREVIOUSLY_GENERATED_FILES_STATE_KEY = new(nameof(previouslyGeneratedFiles));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(SELECTED_ERI_SERVER_STATE_KEY, this.selectedERIServer);
state.Set(AUTO_SAVE_STATE_KEY, this.autoSave);
state.Set(SERVER_NAME_STATE_KEY, this.serverName);
state.Set(SERVER_DESCRIPTION_STATE_KEY, this.serverDescription);
state.Set(SELECTED_ERI_VERSION_STATE_KEY, this.selectedERIVersion);
state.Set(ERI_SPECIFICATION_STATE_KEY, this.eriSpecification);
state.Set(SELECTED_PROGRAMMING_LANGUAGE_STATE_KEY, this.selectedProgrammingLanguage);
state.Set(OTHER_PROGRAMMING_LANGUAGE_STATE_KEY, this.otherProgrammingLanguage);
state.Set(SELECTED_DATA_SOURCE_STATE_KEY, this.selectedDataSource);
state.Set(OTHER_DATA_SOURCE_STATE_KEY, this.otherDataSource);
state.Set(DATA_SOURCE_PRODUCT_NAME_STATE_KEY, this.dataSourceProductName);
state.Set(DATA_SOURCE_HOSTNAME_STATE_KEY, this.dataSourceHostname);
state.Set(DATA_SOURCE_PORT_STATE_KEY, this.dataSourcePort);
state.Set(USER_TYPED_PORT_STATE_KEY, this.userTypedPort);
state.SetHashSet(SELECTED_AUTHENTICATION_METHODS_STATE_KEY, this.selectedAuthenticationMethods);
state.Set(AUTH_DESCRIPTION_STATE_KEY, this.authDescription);
state.Set(SELECTED_OPERATING_SYSTEM_STATE_KEY, this.selectedOperatingSystem);
state.Set(ALLOWED_LLM_PROVIDERS_STATE_KEY, this.allowedLLMProviders);
state.SetList(EMBEDDINGS_STATE_KEY, this.embeddings);
state.SetList(RETRIEVAL_PROCESSES_STATE_KEY, this.retrievalProcesses);
state.Set(ADDITIONAL_LIBRARIES_STATE_KEY, this.additionalLibraries);
state.Set(WRITE_TO_FILESYSTEM_STATE_KEY, this.writeToFilesystem);
state.Set(BASE_DIRECTORY_STATE_KEY, this.baseDirectory);
state.SetList(PREVIOUSLY_GENERATED_FILES_STATE_KEY, this.previouslyGeneratedFiles);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(SELECTED_ERI_SERVER_STATE_KEY, value => this.selectedERIServer = value);
state.Restore(AUTO_SAVE_STATE_KEY, value => this.autoSave = value);
state.Restore(SERVER_NAME_STATE_KEY, value => this.serverName = value);
state.Restore(SERVER_DESCRIPTION_STATE_KEY, value => this.serverDescription = value);
state.Restore(SELECTED_ERI_VERSION_STATE_KEY, value => this.selectedERIVersion = value);
state.Restore(ERI_SPECIFICATION_STATE_KEY, value => this.eriSpecification = value);
state.Restore(SELECTED_PROGRAMMING_LANGUAGE_STATE_KEY, value => this.selectedProgrammingLanguage = value);
state.Restore(OTHER_PROGRAMMING_LANGUAGE_STATE_KEY, value => this.otherProgrammingLanguage = value);
state.Restore(SELECTED_DATA_SOURCE_STATE_KEY, value => this.selectedDataSource = value);
state.Restore(OTHER_DATA_SOURCE_STATE_KEY, value => this.otherDataSource = value);
state.Restore(DATA_SOURCE_PRODUCT_NAME_STATE_KEY, value => this.dataSourceProductName = value);
state.Restore(DATA_SOURCE_HOSTNAME_STATE_KEY, value => this.dataSourceHostname = value);
state.Restore(DATA_SOURCE_PORT_STATE_KEY, value => this.dataSourcePort = value);
state.Restore(USER_TYPED_PORT_STATE_KEY, value => this.userTypedPort = value);
state.Restore(SELECTED_AUTHENTICATION_METHODS_STATE_KEY, value => this.selectedAuthenticationMethods = value);
state.Restore(AUTH_DESCRIPTION_STATE_KEY, value => this.authDescription = value);
state.Restore(SELECTED_OPERATING_SYSTEM_STATE_KEY, value => this.selectedOperatingSystem = value);
state.Restore(ALLOWED_LLM_PROVIDERS_STATE_KEY, value => this.allowedLLMProviders = value);
state.RestoreList(EMBEDDINGS_STATE_KEY, this.embeddings);
state.RestoreList(RETRIEVAL_PROCESSES_STATE_KEY, this.retrievalProcesses);
state.Restore(ADDITIONAL_LIBRARIES_STATE_KEY, value => this.additionalLibraries = value);
state.Restore(WRITE_TO_FILESYSTEM_STATE_KEY, value => this.writeToFilesystem = value);
state.Restore(BASE_DIRECTORY_STATE_KEY, value => this.baseDirectory = value);
state.RestoreList(PREVIOUSLY_GENERATED_FILES_STATE_KEY, this.previouslyGeneratedFiles);
}
private bool AreServerPresetsBlocked => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions;

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.GrammarSpelling;
@ -84,6 +85,28 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore<SettingsDialog
private CommonLanguages selectedTargetLanguage;
private string customTargetLanguage = string.Empty;
private string correctedText = string.Empty;
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_STATE_KEY = new(nameof(inputText));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<string> CORRECTED_TEXT_STATE_KEY = new(nameof(correctedText));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_TEXT_STATE_KEY, this.inputText);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(CORRECTED_TEXT_STATE_KEY, this.correctedText);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_TEXT_STATE_KEY, value => this.inputText = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(CORRECTED_TEXT_STATE_KEY, value => this.correctedText = value);
}
private string? ValidateText(string text)
{
@ -127,6 +150,13 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore<SettingsDialog
var time = this.AddUserRequest(this.inputText);
this.correctedText = await this.AddAIResponseAsync(time);
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.correctedText);
if (!this.IsAssistantComponentDisposed)
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.correctedText);
}
protected override async Task OnAssistantSessionAttachedAsync(AssistantSessionSnapshot snapshot)
{
if (!snapshot.IsActive && !string.IsNullOrWhiteSpace(this.inputText) && !string.IsNullOrWhiteSpace(this.correctedText))
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.correctedText);
}
}

View File

@ -2,6 +2,7 @@ using System.Diagnostics;
using System.Text;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.PluginSystem;
using Microsoft.Extensions.FileProviders;
@ -117,6 +118,52 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
private Dictionary<string, string> removedContent = [];
private Dictionary<string, string> localizedContent = [];
private StringBuilder finalLuaCode = new();
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<bool> IS_LOADING_STATE_KEY = new(nameof(isLoading));
private static readonly AssistantSessionStateKey<string> LOADING_ISSUE_STATE_KEY = new(nameof(loadingIssue));
private static readonly AssistantSessionStateKey<bool> LOCALIZATION_POSSIBLE_STATE_KEY = new(nameof(localizationPossible));
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>> 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));
private static readonly AssistantSessionStateKey<string> FINAL_LUA_CODE_STATE_KEY = new(nameof(finalLuaCode));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(IS_LOADING_STATE_KEY, this.isLoading);
state.Set(LOADING_ISSUE_STATE_KEY, this.loadingIssue);
state.Set(LOCALIZATION_POSSIBLE_STATE_KEY, this.localizationPossible);
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(ADDED_CONTENT_STATE_KEY, this.addedContent);
state.SetDictionary(REMOVED_CONTENT_STATE_KEY, this.removedContent);
state.SetDictionary(LOCALIZED_CONTENT_STATE_KEY, this.localizedContent);
state.SetStringBuilder(FINAL_LUA_CODE_STATE_KEY, this.finalLuaCode);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(IS_LOADING_STATE_KEY, value => this.isLoading = value);
state.Restore(LOADING_ISSUE_STATE_KEY, value => this.loadingIssue = value);
state.Restore(LOCALIZATION_POSSIBLE_STATE_KEY, value => this.localizationPossible = value);
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(ADDED_CONTENT_STATE_KEY, this.addedContent);
state.RestoreDictionary(REMOVED_CONTENT_STATE_KEY, this.removedContent);
state.RestoreDictionary(LOCALIZED_CONTENT_STATE_KEY, this.localizedContent);
state.RestoreStringBuilder(FINAL_LUA_CODE_STATE_KEY, this.finalLuaCode);
}
#region Overrides of AssistantBase<SettingsDialogI18N>

View File

@ -310,6 +310,12 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T180921696"] = "Reset"
-- Please select a provider.
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T1809312323"] = "Please select a provider."
-- The assistant failed. The message is: '{0}'
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T1833836281"] = "The assistant failed. The message is: '{0}'"
-- 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."
-- Assistant - {0}
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T3043922"] = "Assistant - {0}"

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.IconFinder;
@ -56,6 +57,22 @@ public partial class AssistantIconFinder : AssistantBaseCore<SettingsDialogIconF
private string inputContext = string.Empty;
private IconSources selectedIconSource;
private static readonly AssistantSessionStateKey<string> INPUT_CONTEXT_STATE_KEY = new(nameof(inputContext));
private static readonly AssistantSessionStateKey<IconSources> SELECTED_ICON_SOURCE_STATE_KEY = new(nameof(selectedIconSource));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_CONTEXT_STATE_KEY, this.inputContext);
state.Set(SELECTED_ICON_SOURCE_STATE_KEY, this.selectedIconSource);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_CONTEXT_STATE_KEY, value => this.inputContext = value);
state.Restore(SELECTED_ICON_SOURCE_STATE_KEY, value => this.selectedIconSource = value);
}
#region Overrides of ComponentBase

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.JobPosting;
@ -128,6 +129,49 @@ public partial class AssistantJobPostings : AssistantBaseCore<SettingsDialogJobP
private string inputCountryLegalFramework = string.Empty;
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
private string customTargetLanguage = string.Empty;
private static readonly AssistantSessionStateKey<string> INPUT_MANDATORY_INFORMATION_STATE_KEY = new(nameof(inputMandatoryInformation));
private static readonly AssistantSessionStateKey<string> INPUT_JOB_DESCRIPTION_STATE_KEY = new(nameof(inputJobDescription));
private static readonly AssistantSessionStateKey<string> INPUT_QUALIFICATIONS_STATE_KEY = new(nameof(inputQualifications));
private static readonly AssistantSessionStateKey<string> INPUT_RESPONSIBILITIES_STATE_KEY = new(nameof(inputResponsibilities));
private static readonly AssistantSessionStateKey<string> INPUT_COMPANY_NAME_STATE_KEY = new(nameof(inputCompanyName));
private static readonly AssistantSessionStateKey<string> INPUT_ENTRY_DATE_STATE_KEY = new(nameof(inputEntryDate));
private static readonly AssistantSessionStateKey<string> INPUT_VALID_UNTIL_STATE_KEY = new(nameof(inputValidUntil));
private static readonly AssistantSessionStateKey<string> INPUT_WORK_LOCATION_STATE_KEY = new(nameof(inputWorkLocation));
private static readonly AssistantSessionStateKey<string> INPUT_COUNTRY_LEGAL_FRAMEWORK_STATE_KEY = new(nameof(inputCountryLegalFramework));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_MANDATORY_INFORMATION_STATE_KEY, this.inputMandatoryInformation);
state.Set(INPUT_JOB_DESCRIPTION_STATE_KEY, this.inputJobDescription);
state.Set(INPUT_QUALIFICATIONS_STATE_KEY, this.inputQualifications);
state.Set(INPUT_RESPONSIBILITIES_STATE_KEY, this.inputResponsibilities);
state.Set(INPUT_COMPANY_NAME_STATE_KEY, this.inputCompanyName);
state.Set(INPUT_ENTRY_DATE_STATE_KEY, this.inputEntryDate);
state.Set(INPUT_VALID_UNTIL_STATE_KEY, this.inputValidUntil);
state.Set(INPUT_WORK_LOCATION_STATE_KEY, this.inputWorkLocation);
state.Set(INPUT_COUNTRY_LEGAL_FRAMEWORK_STATE_KEY, this.inputCountryLegalFramework);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_MANDATORY_INFORMATION_STATE_KEY, value => this.inputMandatoryInformation = value);
state.Restore(INPUT_JOB_DESCRIPTION_STATE_KEY, value => this.inputJobDescription = value);
state.Restore(INPUT_QUALIFICATIONS_STATE_KEY, value => this.inputQualifications = value);
state.Restore(INPUT_RESPONSIBILITIES_STATE_KEY, value => this.inputResponsibilities = value);
state.Restore(INPUT_COMPANY_NAME_STATE_KEY, value => this.inputCompanyName = value);
state.Restore(INPUT_ENTRY_DATE_STATE_KEY, value => this.inputEntryDate = value);
state.Restore(INPUT_VALID_UNTIL_STATE_KEY, value => this.inputValidUntil = value);
state.Restore(INPUT_WORK_LOCATION_STATE_KEY, value => this.inputWorkLocation = value);
state.Restore(INPUT_COUNTRY_LEGAL_FRAMEWORK_STATE_KEY, value => this.inputCountryLegalFramework = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.LegalCheck;
@ -59,6 +60,31 @@ public partial class AssistantLegalCheck : AssistantBaseCore<SettingsDialogLegal
private bool isAgentRunning;
private string inputLegalDocument = string.Empty;
private string inputQuestions = string.Empty;
private static readonly AssistantSessionStateKey<bool> SHOW_WEB_CONTENT_READER_STATE_KEY = new(nameof(showWebContentReader));
private static readonly AssistantSessionStateKey<bool> USE_CONTENT_CLEANER_AGENT_STATE_KEY = new(nameof(useContentCleanerAgent));
private static readonly AssistantSessionStateKey<bool> IS_AGENT_RUNNING_STATE_KEY = new(nameof(isAgentRunning));
private static readonly AssistantSessionStateKey<string> INPUT_LEGAL_DOCUMENT_STATE_KEY = new(nameof(inputLegalDocument));
private static readonly AssistantSessionStateKey<string> INPUT_QUESTIONS_STATE_KEY = new(nameof(inputQuestions));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(SHOW_WEB_CONTENT_READER_STATE_KEY, this.showWebContentReader);
state.Set(USE_CONTENT_CLEANER_AGENT_STATE_KEY, this.useContentCleanerAgent);
state.Set(IS_AGENT_RUNNING_STATE_KEY, this.isAgentRunning);
state.Set(INPUT_LEGAL_DOCUMENT_STATE_KEY, this.inputLegalDocument);
state.Set(INPUT_QUESTIONS_STATE_KEY, this.inputQuestions);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(SHOW_WEB_CONTENT_READER_STATE_KEY, value => this.showWebContentReader = value);
state.Restore(USE_CONTENT_CLEANER_AGENT_STATE_KEY, value => this.useContentCleanerAgent = value);
state.Restore(IS_AGENT_RUNNING_STATE_KEY, value => this.isAgentRunning = value);
state.Restore(INPUT_LEGAL_DOCUMENT_STATE_KEY, value => this.inputLegalDocument = value);
state.Restore(INPUT_QUESTIONS_STATE_KEY, value => this.inputQuestions = value);
}
#region Overrides of ComponentBase

View File

@ -1,5 +1,6 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.MyTasks;
@ -59,6 +60,25 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
private string inputText = string.Empty;
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
private string customTargetLanguage = string.Empty;
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_STATE_KEY = new(nameof(inputText));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_TEXT_STATE_KEY, this.inputText);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_TEXT_STATE_KEY, value => this.inputText = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase

View File

@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
using Microsoft.AspNetCore.Components;
#if !DEBUG
@ -176,6 +177,67 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
private string recStructureMarkers = string.Empty;
private string recRoleDefinition = string.Empty;
private string recLanguageChoice = string.Empty;
private static readonly AssistantSessionStateKey<string> INPUT_PROMPT_STATE_KEY = new(nameof(inputPrompt));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<string> IMPORTANT_ASPECTS_STATE_KEY = new(nameof(importantAspects));
private static readonly AssistantSessionStateKey<bool> USE_CUSTOM_PROMPT_GUIDE_STATE_KEY = new(nameof(useCustomPromptGuide));
private static readonly AssistantSessionStateKey<HashSet<FileAttachment>> CUSTOM_PROMPT_GUIDE_FILES_STATE_KEY = new(nameof(customPromptGuideFiles));
private static readonly AssistantSessionStateKey<string> CURRENT_CUSTOM_PROMPT_GUIDE_PATH_STATE_KEY = new(nameof(currentCustomPromptGuidePath));
private static readonly AssistantSessionStateKey<string> CUSTOM_PROMPTING_GUIDELINE_CONTENT_STATE_KEY = new(nameof(customPromptingGuidelineContent));
private static readonly AssistantSessionStateKey<bool> IS_LOADING_CUSTOM_PROMPT_GUIDE_STATE_KEY = new(nameof(isLoadingCustomPromptGuide));
private static readonly AssistantSessionStateKey<bool> HAS_UPDATED_DEFAULT_RECOMMENDATIONS_STATE_KEY = new(nameof(hasUpdatedDefaultRecommendations));
private static readonly AssistantSessionStateKey<string> OPTIMIZED_PROMPT_STATE_KEY = new(nameof(optimizedPrompt));
private static readonly AssistantSessionStateKey<string> REC_CLARITY_DIRECTNESS_STATE_KEY = new(nameof(recClarityDirectness));
private static readonly AssistantSessionStateKey<string> REC_EXAMPLES_CONTEXT_STATE_KEY = new(nameof(recExamplesContext));
private static readonly AssistantSessionStateKey<string> REC_SEQUENTIAL_STEPS_STATE_KEY = new(nameof(recSequentialSteps));
private static readonly AssistantSessionStateKey<string> REC_STRUCTURE_MARKERS_STATE_KEY = new(nameof(recStructureMarkers));
private static readonly AssistantSessionStateKey<string> REC_ROLE_DEFINITION_STATE_KEY = new(nameof(recRoleDefinition));
private static readonly AssistantSessionStateKey<string> REC_LANGUAGE_CHOICE_STATE_KEY = new(nameof(recLanguageChoice));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_PROMPT_STATE_KEY, this.inputPrompt);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(IMPORTANT_ASPECTS_STATE_KEY, this.importantAspects);
state.Set(USE_CUSTOM_PROMPT_GUIDE_STATE_KEY, this.useCustomPromptGuide);
state.SetHashSet(CUSTOM_PROMPT_GUIDE_FILES_STATE_KEY, this.customPromptGuideFiles);
state.Set(CURRENT_CUSTOM_PROMPT_GUIDE_PATH_STATE_KEY, this.currentCustomPromptGuidePath);
state.Set(CUSTOM_PROMPTING_GUIDELINE_CONTENT_STATE_KEY, this.customPromptingGuidelineContent);
state.Set(IS_LOADING_CUSTOM_PROMPT_GUIDE_STATE_KEY, this.isLoadingCustomPromptGuide);
state.Set(HAS_UPDATED_DEFAULT_RECOMMENDATIONS_STATE_KEY, this.hasUpdatedDefaultRecommendations);
state.Set(OPTIMIZED_PROMPT_STATE_KEY, this.optimizedPrompt);
state.Set(REC_CLARITY_DIRECTNESS_STATE_KEY, this.recClarityDirectness);
state.Set(REC_EXAMPLES_CONTEXT_STATE_KEY, this.recExamplesContext);
state.Set(REC_SEQUENTIAL_STEPS_STATE_KEY, this.recSequentialSteps);
state.Set(REC_STRUCTURE_MARKERS_STATE_KEY, this.recStructureMarkers);
state.Set(REC_ROLE_DEFINITION_STATE_KEY, this.recRoleDefinition);
state.Set(REC_LANGUAGE_CHOICE_STATE_KEY, this.recLanguageChoice);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_PROMPT_STATE_KEY, value => this.inputPrompt = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(IMPORTANT_ASPECTS_STATE_KEY, value => this.importantAspects = value);
state.Restore(USE_CUSTOM_PROMPT_GUIDE_STATE_KEY, value => this.useCustomPromptGuide = value);
state.RestoreHashSet(CUSTOM_PROMPT_GUIDE_FILES_STATE_KEY, this.customPromptGuideFiles);
state.Restore(CURRENT_CUSTOM_PROMPT_GUIDE_PATH_STATE_KEY, value => this.currentCustomPromptGuidePath = value);
state.Restore(CUSTOM_PROMPTING_GUIDELINE_CONTENT_STATE_KEY, value => this.customPromptingGuidelineContent = value);
state.Restore(IS_LOADING_CUSTOM_PROMPT_GUIDE_STATE_KEY, value => this.isLoadingCustomPromptGuide = value);
state.Restore(HAS_UPDATED_DEFAULT_RECOMMENDATIONS_STATE_KEY, value => this.hasUpdatedDefaultRecommendations = value);
state.Restore(OPTIMIZED_PROMPT_STATE_KEY, value => this.optimizedPrompt = value);
state.Restore(REC_CLARITY_DIRECTNESS_STATE_KEY, value => this.recClarityDirectness = value);
state.Restore(REC_EXAMPLES_CONTEXT_STATE_KEY, value => this.recExamplesContext = value);
state.Restore(REC_SEQUENTIAL_STEPS_STATE_KEY, value => this.recSequentialSteps = value);
state.Restore(REC_STRUCTURE_MARKERS_STATE_KEY, value => this.recStructureMarkers = value);
state.Restore(REC_ROLE_DEFINITION_STATE_KEY, value => this.recRoleDefinition = value);
state.Restore(REC_LANGUAGE_CHOICE_STATE_KEY, value => this.recLanguageChoice = value);
}
private bool ShowUpdatedPromptGuidelinesIndicator => !this.useCustomPromptGuide && this.hasUpdatedDefaultRecommendations;
private bool CanPreviewCustomPromptGuide => this.useCustomPromptGuide && this.customPromptGuideFiles.Count > 0;

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.RewriteImprove;
@ -91,6 +92,34 @@ public partial class AssistantRewriteImprove : AssistantBaseCore<SettingsDialogR
private string rewrittenText = string.Empty;
private WritingStyles selectedWritingStyle;
private SentenceStructure selectedSentenceStructure;
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_STATE_KEY = new(nameof(inputText));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<string> REWRITTEN_TEXT_STATE_KEY = new(nameof(rewrittenText));
private static readonly AssistantSessionStateKey<WritingStyles> SELECTED_WRITING_STYLE_STATE_KEY = new(nameof(selectedWritingStyle));
private static readonly AssistantSessionStateKey<SentenceStructure> SELECTED_SENTENCE_STRUCTURE_STATE_KEY = new(nameof(selectedSentenceStructure));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_TEXT_STATE_KEY, this.inputText);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(REWRITTEN_TEXT_STATE_KEY, this.rewrittenText);
state.Set(SELECTED_WRITING_STYLE_STATE_KEY, this.selectedWritingStyle);
state.Set(SELECTED_SENTENCE_STRUCTURE_STATE_KEY, this.selectedSentenceStructure);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_TEXT_STATE_KEY, value => this.inputText = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(REWRITTEN_TEXT_STATE_KEY, value => this.rewrittenText = value);
state.Restore(SELECTED_WRITING_STYLE_STATE_KEY, value => this.selectedWritingStyle = value);
state.Restore(SELECTED_SENTENCE_STRUCTURE_STATE_KEY, value => this.selectedSentenceStructure = value);
}
private string? ValidateText(string text)
{
@ -134,6 +163,13 @@ public partial class AssistantRewriteImprove : AssistantBaseCore<SettingsDialogR
var time = this.AddUserRequest(this.inputText);
this.rewrittenText = await this.AddAIResponseAsync(time);
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.rewrittenText);
if (!this.IsAssistantComponentDisposed)
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.rewrittenText);
}
protected override async Task OnAssistantSessionAttachedAsync(AssistantSessionSnapshot snapshot)
{
if (!snapshot.IsActive && !string.IsNullOrWhiteSpace(this.inputText) && !string.IsNullOrWhiteSpace(this.rewrittenText))
await this.JsRuntime.GenerateAndShowDiff(this.inputText, this.rewrittenText);
}
}

View File

@ -1,6 +1,7 @@
using System.Text;
using AIStudio.Chat;
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.SlideBuilder;
@ -197,6 +198,58 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
private int calculatedNumberOfSlides;
private string importantAspects = string.Empty;
private HashSet<FileAttachment> loadedDocumentPaths = [];
private static readonly AssistantSessionStateKey<string> INPUT_TITLE_STATE_KEY = new(nameof(inputTitle));
private static readonly AssistantSessionStateKey<string> INPUT_CONTENT_STATE_KEY = new(nameof(inputContent));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<AudienceProfile> SELECTED_AUDIENCE_PROFILE_STATE_KEY = new(nameof(selectedAudienceProfile));
private static readonly AssistantSessionStateKey<AudienceAgeGroup> SELECTED_AUDIENCE_AGE_GROUP_STATE_KEY = new(nameof(selectedAudienceAgeGroup));
private static readonly AssistantSessionStateKey<AudienceOrganizationalLevel> SELECTED_AUDIENCE_ORGANIZATIONAL_LEVEL_STATE_KEY = new(nameof(selectedAudienceOrganizationalLevel));
private static readonly AssistantSessionStateKey<AudienceExpertise> SELECTED_AUDIENCE_EXPERTISE_STATE_KEY = new(nameof(selectedAudienceExpertise));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<int> NUMBER_OF_SHEETS_STATE_KEY = new(nameof(numberOfSheets));
private static readonly AssistantSessionStateKey<int> NUMBER_OF_BULLET_POINTS_STATE_KEY = new(nameof(numberOfBulletPoints));
private static readonly AssistantSessionStateKey<int> TIME_SPECIFICATION_STATE_KEY = new(nameof(timeSpecification));
private static readonly AssistantSessionStateKey<int> CALCULATED_NUMBER_OF_SLIDES_STATE_KEY = new(nameof(calculatedNumberOfSlides));
private static readonly AssistantSessionStateKey<string> IMPORTANT_ASPECTS_STATE_KEY = new(nameof(importantAspects));
private static readonly AssistantSessionStateKey<HashSet<FileAttachment>> LOADED_DOCUMENT_PATHS_STATE_KEY = new(nameof(loadedDocumentPaths));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_TITLE_STATE_KEY, this.inputTitle);
state.Set(INPUT_CONTENT_STATE_KEY, this.inputContent);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(SELECTED_AUDIENCE_PROFILE_STATE_KEY, this.selectedAudienceProfile);
state.Set(SELECTED_AUDIENCE_AGE_GROUP_STATE_KEY, this.selectedAudienceAgeGroup);
state.Set(SELECTED_AUDIENCE_ORGANIZATIONAL_LEVEL_STATE_KEY, this.selectedAudienceOrganizationalLevel);
state.Set(SELECTED_AUDIENCE_EXPERTISE_STATE_KEY, this.selectedAudienceExpertise);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(NUMBER_OF_SHEETS_STATE_KEY, this.numberOfSheets);
state.Set(NUMBER_OF_BULLET_POINTS_STATE_KEY, this.numberOfBulletPoints);
state.Set(TIME_SPECIFICATION_STATE_KEY, this.timeSpecification);
state.Set(CALCULATED_NUMBER_OF_SLIDES_STATE_KEY, this.calculatedNumberOfSlides);
state.Set(IMPORTANT_ASPECTS_STATE_KEY, this.importantAspects);
state.SetHashSet(LOADED_DOCUMENT_PATHS_STATE_KEY, this.loadedDocumentPaths);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_TITLE_STATE_KEY, value => this.inputTitle = value);
state.Restore(INPUT_CONTENT_STATE_KEY, value => this.inputContent = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(SELECTED_AUDIENCE_PROFILE_STATE_KEY, value => this.selectedAudienceProfile = value);
state.Restore(SELECTED_AUDIENCE_AGE_GROUP_STATE_KEY, value => this.selectedAudienceAgeGroup = value);
state.Restore(SELECTED_AUDIENCE_ORGANIZATIONAL_LEVEL_STATE_KEY, value => this.selectedAudienceOrganizationalLevel = value);
state.Restore(SELECTED_AUDIENCE_EXPERTISE_STATE_KEY, value => this.selectedAudienceExpertise = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(NUMBER_OF_SHEETS_STATE_KEY, value => this.numberOfSheets = value);
state.Restore(NUMBER_OF_BULLET_POINTS_STATE_KEY, value => this.numberOfBulletPoints = value);
state.Restore(TIME_SPECIFICATION_STATE_KEY, value => this.timeSpecification = value);
state.Restore(CALCULATED_NUMBER_OF_SLIDES_STATE_KEY, value => this.calculatedNumberOfSlides = value);
state.Restore(IMPORTANT_ASPECTS_STATE_KEY, value => this.importantAspects = value);
state.RestoreHashSet(LOADED_DOCUMENT_PATHS_STATE_KEY, this.loadedDocumentPaths);
}
#region Overrides of ComponentBase

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Synonym;
@ -103,6 +104,28 @@ public partial class AssistantSynonyms : AssistantBaseCore<SettingsDialogSynonym
private string inputContext = string.Empty;
private CommonLanguages selectedLanguage;
private string customTargetLanguage = string.Empty;
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_STATE_KEY = new(nameof(inputText));
private static readonly AssistantSessionStateKey<string> INPUT_CONTEXT_STATE_KEY = new(nameof(inputContext));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_LANGUAGE_STATE_KEY = new(nameof(selectedLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(INPUT_TEXT_STATE_KEY, this.inputText);
state.Set(INPUT_CONTEXT_STATE_KEY, this.inputContext);
state.Set(SELECTED_LANGUAGE_STATE_KEY, this.selectedLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(INPUT_TEXT_STATE_KEY, value => this.inputText = value);
state.Restore(INPUT_CONTEXT_STATE_KEY, value => this.inputContext = value);
state.Restore(SELECTED_LANGUAGE_STATE_KEY, value => this.selectedLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.TextSummarizer;
@ -72,6 +73,43 @@ public partial class AssistantTextSummarizer : AssistantBaseCore<SettingsDialogT
private Complexity selectedComplexity;
private string expertInField = string.Empty;
private string importantAspects = string.Empty;
private static readonly AssistantSessionStateKey<bool> SHOW_WEB_CONTENT_READER_STATE_KEY = new(nameof(showWebContentReader));
private static readonly AssistantSessionStateKey<bool> USE_CONTENT_CLEANER_AGENT_STATE_KEY = new(nameof(useContentCleanerAgent));
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_STATE_KEY = new(nameof(inputText));
private static readonly AssistantSessionStateKey<bool> IS_AGENT_RUNNING_STATE_KEY = new(nameof(isAgentRunning));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
private static readonly AssistantSessionStateKey<Complexity> SELECTED_COMPLEXITY_STATE_KEY = new(nameof(selectedComplexity));
private static readonly AssistantSessionStateKey<string> EXPERT_IN_FIELD_STATE_KEY = new(nameof(expertInField));
private static readonly AssistantSessionStateKey<string> IMPORTANT_ASPECTS_STATE_KEY = new(nameof(importantAspects));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(SHOW_WEB_CONTENT_READER_STATE_KEY, this.showWebContentReader);
state.Set(USE_CONTENT_CLEANER_AGENT_STATE_KEY, this.useContentCleanerAgent);
state.Set(INPUT_TEXT_STATE_KEY, this.inputText);
state.Set(IS_AGENT_RUNNING_STATE_KEY, this.isAgentRunning);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
state.Set(SELECTED_COMPLEXITY_STATE_KEY, this.selectedComplexity);
state.Set(EXPERT_IN_FIELD_STATE_KEY, this.expertInField);
state.Set(IMPORTANT_ASPECTS_STATE_KEY, this.importantAspects);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(SHOW_WEB_CONTENT_READER_STATE_KEY, value => this.showWebContentReader = value);
state.Restore(USE_CONTENT_CLEANER_AGENT_STATE_KEY, value => this.useContentCleanerAgent = value);
state.Restore(INPUT_TEXT_STATE_KEY, value => this.inputText = value);
state.Restore(IS_AGENT_RUNNING_STATE_KEY, value => this.isAgentRunning = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
state.Restore(SELECTED_COMPLEXITY_STATE_KEY, value => this.selectedComplexity = value);
state.Restore(EXPERT_IN_FIELD_STATE_KEY, value => this.expertInField = value);
state.Restore(IMPORTANT_ASPECTS_STATE_KEY, value => this.importantAspects = value);
}
#region Overrides of ComponentBase

View File

@ -1,4 +1,5 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Tools.AssistantSessions;
namespace AIStudio.Assistants.Translation;
@ -79,6 +80,40 @@ public partial class AssistantTranslation : AssistantBaseCore<SettingsDialogTran
private string inputTextLastTranslation = string.Empty;
private CommonLanguages selectedTargetLanguage;
private string customTargetLanguage = string.Empty;
private static readonly AssistantSessionStateKey<bool> SHOW_WEB_CONTENT_READER_STATE_KEY = new(nameof(showWebContentReader));
private static readonly AssistantSessionStateKey<bool> USE_CONTENT_CLEANER_AGENT_STATE_KEY = new(nameof(useContentCleanerAgent));
private static readonly AssistantSessionStateKey<bool> LIVE_TRANSLATION_STATE_KEY = new(nameof(liveTranslation));
private static readonly AssistantSessionStateKey<bool> IS_AGENT_RUNNING_STATE_KEY = new(nameof(isAgentRunning));
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_STATE_KEY = new(nameof(inputText));
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_LAST_TRANSLATION_STATE_KEY = new(nameof(inputTextLastTranslation));
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
{
state.Set(SHOW_WEB_CONTENT_READER_STATE_KEY, this.showWebContentReader);
state.Set(USE_CONTENT_CLEANER_AGENT_STATE_KEY, this.useContentCleanerAgent);
state.Set(LIVE_TRANSLATION_STATE_KEY, this.liveTranslation);
state.Set(IS_AGENT_RUNNING_STATE_KEY, this.isAgentRunning);
state.Set(INPUT_TEXT_STATE_KEY, this.inputText);
state.Set(INPUT_TEXT_LAST_TRANSLATION_STATE_KEY, this.inputTextLastTranslation);
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
{
state.Restore(SHOW_WEB_CONTENT_READER_STATE_KEY, value => this.showWebContentReader = value);
state.Restore(USE_CONTENT_CLEANER_AGENT_STATE_KEY, value => this.useContentCleanerAgent = value);
state.Restore(LIVE_TRANSLATION_STATE_KEY, value => this.liveTranslation = value);
state.Restore(IS_AGENT_RUNNING_STATE_KEY, value => this.isAgentRunning = value);
state.Restore(INPUT_TEXT_STATE_KEY, value => this.inputText = value);
state.Restore(INPUT_TEXT_LAST_TRANSLATION_STATE_KEY, value => this.inputTextLastTranslation = value);
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
}
#region Overrides of ComponentBase

View File

@ -1,5 +1,6 @@
using AIStudio.Dialogs.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.AssistantSessions;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
@ -31,6 +32,12 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
[Parameter]
public Tools.Components Component { get; set; } = Tools.Components.NONE;
/// <summary>
/// Gets or sets the optional assistant session instance ID represented by this block.
/// </summary>
[Parameter]
public string AssistantSessionInstanceId { get; set; } = string.Empty;
[Parameter]
public PreviewFeatures RequiredPreviewFeature { get; set; } = PreviewFeatures.NONE;
@ -39,6 +46,9 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private AssistantSessionService AssistantSessionService { get; init; } = null!;
private async Task OpenSettingsDialog()
{
@ -50,7 +60,7 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
await this.DialogService.ShowAsync<TSettings>(T("Open Settings"), dialogParameters, DialogOptions.FULLSCREEN);
}
private string BorderColor => this.SettingsManager.IsDarkMode switch
private string BorderColor => this.HasActiveSession ? this.ColorTheme.GetCurrentPalette(this.SettingsManager).Warning.Value : this.SettingsManager.IsDarkMode switch
{
true => this.ColorTheme.GetCurrentPalette(this.SettingsManager).GrayLight,
false => this.ColorTheme.GetCurrentPalette(this.SettingsManager).Primary.Value,
@ -61,4 +71,27 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
private bool IsVisible => this.SettingsManager.IsAssistantVisible(this.Component, assistantName: this.Name, requiredPreviewFeature: this.RequiredPreviewFeature);
private bool HasSettingsPanel => typeof(TSettings) != typeof(NoSettingsPanel);
}
/// <summary>
/// Gets whether this block represents an active assistant session.
/// </summary>
private bool HasActiveSession => string.IsNullOrWhiteSpace(this.AssistantSessionInstanceId)
? this.AssistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && snapshot.Key.Component == this.Component)
: this.AssistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && snapshot.Key.InstanceId == this.AssistantSessionInstanceId);
/// <summary>
/// Refreshes the block when assistant session activity changes.
/// </summary>
/// <typeparam name="T">The message payload type.</typeparam>
/// <param name="sendingComponent">The component that sent the message, if any.</param>
/// <param name="triggeredEvent">The event that was triggered.</param>
/// <param name="data">The message payload.</param>
/// <returns>A task that completes after the message was processed.</returns>
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
if (triggeredEvent is Event.ASSISTANT_SESSION_CHANGED or Event.ASSISTANT_SESSION_FINISHED)
this.StateHasChanged();
return base.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
}
}

View File

@ -2,6 +2,7 @@ using AIStudio.Dialogs;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.AIJobs;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
@ -30,6 +31,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
[Inject]
private AIJobService AIJobService { get; init; } = null!;
[Inject]
private AssistantSessionService AssistantSessionService { get; init; } = null!;
[Inject]
private ISnackbar Snackbar { get; init; } = null!;
@ -102,7 +106,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
Event.INSTALL_UPDATE, Event.STARTUP_COMPLETED, Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED,
Event.CHAT_GENERATION_CHANGED,
Event.CHAT_GENERATION_CHANGED, Event.ASSISTANT_SESSION_CHANGED, Event.ASSISTANT_SESSION_FINISHED,
]);
// Set the snackbar for the update service:
@ -228,6 +232,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
case Event.AI_JOB_CHANGED:
case Event.AI_JOB_FINISHED:
case Event.CHAT_GENERATION_CHANGED:
case Event.ASSISTANT_SESSION_CHANGED:
case Event.ASSISTANT_SESSION_FINISHED:
this.LoadNavItems();
this.StateHasChanged();
break;
@ -344,7 +350,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
yield return new(T("Home"), Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true);
yield return new(T("Chat"), this.AIJobService.HasActiveJobs ? Icons.Material.Filled.Chat : Icons.Material.Outlined.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false);
yield return new(T("Assistants"), Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false);
yield return new(T("Assistants"), this.AssistantSessionService.HasActiveSessions ? Icons.Material.Filled.Apps : Icons.Material.Outlined.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false);
if (PreviewFeatures.PRE_WRITER_MODE_2024.IsEnabled(this.SettingsManager))
yield return new(T("Writer"), Icons.Material.Filled.Create, palette.DarkLighten, palette.GrayLight, Routes.WRITER, false);

View File

@ -47,6 +47,7 @@
Description="@T(assistantPlugin.Description)"
Icon="@Icons.Material.Filled.FindInPage"
Disabled="@(!securityState.CanStartAssistant)"
AssistantSessionInstanceId="@assistantPlugin.Id.ToString()"
Link="@($"{Routes.ASSISTANT_DYNAMIC}?assistantId={assistantPlugin.Id}")">
<SecurityBadge>
<AssistantPluginSecurityCard Plugin="@assistantPlugin" Compact="@true" />

View File

@ -3,6 +3,7 @@ using AIStudio.Agents.AssistantAudit;
using AIStudio.Settings;
using AIStudio.Tools.Databases;
using AIStudio.Tools.AIJobs;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
using AIStudio.Tools.Services;
@ -130,6 +131,7 @@ internal sealed class Program
builder.Services.AddSingleton<SettingsManager>();
builder.Services.AddSingleton<ThreadSafeRandom>();
builder.Services.AddSingleton<AIJobService>();
builder.Services.AddSingleton<AssistantSessionService>();
builder.Services.AddSingleton<VoiceRecordingAvailabilityService>();
builder.Services.AddSingleton<DataSourceService>();
builder.Services.AddScoped<PandocAvailabilityService>();

View File

@ -0,0 +1,34 @@
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Identifies one logical assistant session slot.
/// </summary>
public readonly record struct AssistantSessionKey
{
/// <summary>
/// Initializes a new assistant session key.
/// </summary>
/// <param name="component">The application component the assistant belongs to.</param>
/// <param name="instanceId">The assistant-specific instance ID, such as a component type or plugin ID.</param>
public AssistantSessionKey(Components component, string instanceId)
{
this.Component = component;
this.InstanceId = instanceId;
}
/// <summary>
/// Gets the application component the assistant belongs to.
/// </summary>
public Components Component { get; init; }
/// <summary>
/// Gets the assistant-specific instance ID, such as a component type or plugin ID.
/// </summary>
public string InstanceId { get; init; }
/// <summary>
/// Converts the key into a compact diagnostic string.
/// </summary>
/// <returns>The component and instance ID joined by a colon.</returns>
public override string ToString() => $"{this.Component}:{this.InstanceId}";
}

View File

@ -0,0 +1,320 @@
using System.Collections.Concurrent;
using AIStudio.Chat;
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Keeps assistant sessions alive while their Blazor components are not mounted.
/// </summary>
/// <param name="messageBus">The message bus used to publish assistant session changes.</param>
public sealed class AssistantSessionService(MessageBus messageBus)
{
/// <summary>
/// Mutable runtime state owned exclusively by <see cref="AssistantSessionService"/>.
/// </summary>
/// <remarks>
/// This type intentionally exists in addition to <see cref="AssistantSessionSnapshot"/>.
/// It holds runtime-only data such as the cancellation token source and lock, while
/// snapshots are copied DTOs for UI components and message bus payloads.
/// </remarks>
private sealed class AssistantSessionState
{
/// <summary>
/// Identifies this concrete run of an assistant session.
/// </summary>
public required Guid SessionId { get; init; }
/// <summary>
/// Identifies the assistant and logical session slot this run belongs to.
/// </summary>
public required AssistantSessionKey Key { get; init; }
/// <summary>
/// Cancels the active assistant run.
/// </summary>
public required CancellationTokenSource CancellationTokenSource { get; init; }
/// <summary>
/// Stores when the session run started.
/// </summary>
public required DateTimeOffset StartedAt { get; init; }
/// <summary>
/// Stores when the session state was last changed.
/// </summary>
public DateTimeOffset UpdatedAt { get; set; }
/// <summary>
/// Stores when the session reached a terminal state.
/// </summary>
public DateTimeOffset? FinishedAt { get; set; }
/// <summary>
/// Stores the user-visible assistant title.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// Stores the current lifecycle state of the session.
/// </summary>
public AssistantSessionStatus Status { get; set; }
/// <summary>
/// Stores the user-visible error message for failed sessions.
/// </summary>
public string ErrorMessage { get; set; } = string.Empty;
/// <summary>
/// Stores the current assistant chat thread, including streamed output.
/// </summary>
public ChatThread? ChatThread { get; set; }
/// <summary>
/// Stores the assistant component state captured from the running UI instance.
/// </summary>
public Dictionary<string, IAssistantSessionSnapshotField> State { get; set; } = new(StringComparer.Ordinal);
/// <summary>
/// Guards mutable fields while snapshots are created or updates are applied.
/// </summary>
public readonly Lock SyncRoot = new();
}
/// <summary>
/// Stores one assistant session per session key.
/// </summary>
private readonly ConcurrentDictionary<AssistantSessionKey, AssistantSessionState> sessions = new();
/// <summary>
/// Gets whether at least one assistant session is still active.
/// </summary>
public bool HasActiveSessions => this.sessions.Values.Any(session => session.Status is AssistantSessionStatus.RUNNING or AssistantSessionStatus.CANCELING);
/// <summary>
/// Gets copied snapshots for all known assistant sessions.
/// </summary>
/// <returns>Session snapshots ordered by newest update first.</returns>
public IReadOnlyCollection<AssistantSessionSnapshot> GetSnapshots()
{
return this.sessions.Values
.Select(CreateSnapshot)
.OrderByDescending(snapshot => snapshot.UpdatedAt)
.ToList();
}
/// <summary>
/// Tries to get the current snapshot for an assistant session key.
/// </summary>
/// <param name="key">The assistant session key to look up.</param>
/// <returns>The current snapshot, or <c>null</c> when no session exists.</returns>
public AssistantSessionSnapshot? TryGetSnapshot(AssistantSessionKey key)
{
return this.sessions.TryGetValue(key, out var session) ? CreateSnapshot(session) : null;
}
/// <summary>
/// Starts a new assistant session when no active session exists for the key.
/// </summary>
/// <param name="key">The assistant session key.</param>
/// <param name="title">The user-visible assistant title.</param>
/// <param name="cancellationTokenSource">The cancellation token source owned by the new runtime session.</param>
/// <param name="chatThread">The current assistant chat thread, if one already exists.</param>
/// <param name="state">The initial assistant component state.</param>
/// <returns>The new session snapshot, or the existing active session snapshot.</returns>
public AssistantSessionSnapshot TryBegin(AssistantSessionKey key, string title, CancellationTokenSource cancellationTokenSource, ChatThread? chatThread, Dictionary<string, IAssistantSessionSnapshotField> state)
{
if (this.sessions.TryGetValue(key, out var existing) && existing.Status is AssistantSessionStatus.RUNNING or AssistantSessionStatus.CANCELING)
return CreateSnapshot(existing);
var now = DateTimeOffset.Now;
var session = new AssistantSessionState
{
SessionId = Guid.NewGuid(),
Key = key,
CancellationTokenSource = cancellationTokenSource,
StartedAt = now,
UpdatedAt = now,
Title = title,
Status = AssistantSessionStatus.RUNNING,
ChatThread = chatThread,
State = state,
};
this.sessions[key] = session;
_ = this.NotifyChangedAsync(session);
return CreateSnapshot(session);
}
/// <summary>
/// Updates a running assistant session with the latest UI and chat state.
/// </summary>
/// <param name="key">The assistant session key.</param>
/// <param name="sessionId">The concrete run ID that is allowed to write the checkpoint.</param>
/// <param name="title">The current user-visible assistant title.</param>
/// <param name="chatThread">The current assistant chat thread.</param>
/// <param name="state">The current assistant component state.</param>
public async Task CheckpointAsync(AssistantSessionKey key, Guid sessionId, string title, ChatThread? chatThread, Dictionary<string, IAssistantSessionSnapshotField> state)
{
if (!this.sessions.TryGetValue(key, out var session))
return;
lock (session.SyncRoot)
{
if (session.SessionId != sessionId)
return;
session.Title = title;
session.ChatThread = chatThread;
session.State = state;
session.UpdatedAt = DateTimeOffset.Now;
}
await this.NotifyChangedAsync(session);
}
/// <summary>
/// Requests cancellation for an active assistant session.
/// </summary>
/// <param name="key">The assistant session key to cancel.</param>
public async Task CancelAsync(AssistantSessionKey key)
{
if (!this.sessions.TryGetValue(key, out var session))
return;
lock (session.SyncRoot)
{
if (session.Status is not AssistantSessionStatus.RUNNING)
return;
session.Status = AssistantSessionStatus.CANCELING;
session.UpdatedAt = DateTimeOffset.Now;
}
try
{
if (!session.CancellationTokenSource.IsCancellationRequested)
await session.CancellationTokenSource.CancelAsync();
}
catch (ObjectDisposedException)
{
return;
}
await this.NotifyChangedAsync(session);
}
/// <summary>
/// Moves an assistant session into a terminal state and publishes completion.
/// </summary>
/// <param name="key">The assistant session key.</param>
/// <param name="sessionId">The concrete run ID that is allowed to complete the session.</param>
/// <param name="status">The terminal status to store.</param>
/// <param name="errorMessage">The user-visible error message for failed sessions.</param>
/// <param name="chatThread">The final assistant chat thread.</param>
/// <param name="state">The final assistant component state.</param>
public async Task CompleteAsync(AssistantSessionKey key, Guid sessionId, AssistantSessionStatus status, string errorMessage, ChatThread? chatThread, Dictionary<string, IAssistantSessionSnapshotField> state)
{
if (!this.sessions.TryGetValue(key, out var session))
return;
lock (session.SyncRoot)
{
if (session.SessionId != sessionId)
return;
session.Status = status;
session.ErrorMessage = errorMessage;
session.ChatThread = chatThread;
session.State = state;
session.UpdatedAt = DateTimeOffset.Now;
session.FinishedAt = session.UpdatedAt;
}
await this.NotifyChangedAsync(session);
await messageBus.SendMessage(null, Event.ASSISTANT_SESSION_FINISHED, CreateSnapshot(session));
try
{
session.CancellationTokenSource.Dispose();
}
catch
{
// ignore
}
}
/// <summary>
/// Clears an inactive assistant session.
/// </summary>
/// <param name="key">The assistant session key to clear.</param>
public async Task ClearAsync(AssistantSessionKey key)
{
if (!this.sessions.TryGetValue(key, out var session))
return;
if (session.Status is AssistantSessionStatus.RUNNING or AssistantSessionStatus.CANCELING)
return;
lock (session.SyncRoot)
{
session.Status = AssistantSessionStatus.NONE;
session.ChatThread = null;
session.State = new(StringComparer.Ordinal);
session.UpdatedAt = DateTimeOffset.Now;
session.FinishedAt = session.UpdatedAt;
}
this.sessions.TryRemove(key, out _);
await messageBus.SendMessage(null, Event.ASSISTANT_SESSION_CHANGED, CreateSnapshot(session));
}
/// <summary>
/// Clears all inactive sessions for a component.
/// </summary>
/// <param name="component">The component whose inactive sessions should be cleared.</param>
public async Task ClearInactiveSessionsForComponentAsync(Components component)
{
var matchingKeys = this.sessions
.Where(pair => pair.Key.Component == component && pair.Value.Status is not AssistantSessionStatus.RUNNING and not AssistantSessionStatus.CANCELING)
.Select(pair => pair.Key)
.ToList();
foreach (var key in matchingKeys)
await this.ClearAsync(key);
}
/// <summary>
/// Publishes an assistant session change event.
/// </summary>
/// <param name="session">The runtime session whose copied snapshot should be published.</param>
private async Task NotifyChangedAsync(AssistantSessionState session)
{
await messageBus.SendMessage(null, Event.ASSISTANT_SESSION_CHANGED, CreateSnapshot(session));
}
/// <summary>
/// Creates a copied, external snapshot from the internal runtime state.
/// </summary>
/// <param name="session">The runtime session to copy.</param>
/// <returns>A snapshot safe to send to UI components.</returns>
private static AssistantSessionSnapshot CreateSnapshot(AssistantSessionState session)
{
lock (session.SyncRoot)
{
return new()
{
SessionId = session.SessionId,
Key = session.Key,
Title = session.Title,
Status = session.Status,
StartedAt = session.StartedAt,
UpdatedAt = session.UpdatedAt,
FinishedAt = session.FinishedAt,
ErrorMessage = session.ErrorMessage,
ChatThread = session.ChatThread,
State = new Dictionary<string, IAssistantSessionSnapshotField>(session.State, StringComparer.Ordinal),
};
}
}
}

View File

@ -0,0 +1,68 @@
using AIStudio.Chat;
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Immutable-style view of an assistant session for UI consumers and message bus events.
/// </summary>
/// <remarks>
/// The service creates snapshots by copying its internal runtime state. Consumers must
/// treat the contained chat thread and state objects as read-only views of the session.
/// </remarks>
public sealed record AssistantSessionSnapshot
{
/// <summary>
/// Identifies the concrete run represented by this snapshot.
/// </summary>
public required Guid SessionId { get; init; }
/// <summary>
/// Identifies the assistant and logical session slot represented by this snapshot.
/// </summary>
public required AssistantSessionKey Key { get; init; }
/// <summary>
/// Gets the user-visible assistant title.
/// </summary>
public required string Title { get; init; }
/// <summary>
/// Gets the current lifecycle status.
/// </summary>
public required AssistantSessionStatus Status { get; init; }
/// <summary>
/// Gets when the session run started.
/// </summary>
public required DateTimeOffset StartedAt { get; init; }
/// <summary>
/// Gets when the session was last changed.
/// </summary>
public required DateTimeOffset UpdatedAt { get; init; }
/// <summary>
/// Gets when the session reached a terminal state.
/// </summary>
public DateTimeOffset? FinishedAt { get; init; }
/// <summary>
/// Gets the user-visible error message for failed sessions.
/// </summary>
public string ErrorMessage { get; init; } = string.Empty;
/// <summary>
/// Gets the assistant chat thread captured for this session.
/// </summary>
public ChatThread? ChatThread { get; init; }
/// <summary>
/// Gets the assistant component state captured for this session.
/// </summary>
public IReadOnlyDictionary<string, IAssistantSessionSnapshotField> State { get; init; } = new Dictionary<string, IAssistantSessionSnapshotField>();
/// <summary>
/// Gets whether the session is still running or canceling.
/// </summary>
public bool IsActive => this.Status is AssistantSessionStatus.RUNNING or AssistantSessionStatus.CANCELING;
}

View File

@ -0,0 +1,45 @@
// ReSharper disable MemberCanBePrivate.Global
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Stores one typed value in an assistant session snapshot.
/// </summary>
/// <typeparam name="T">The captured value type.</typeparam>
public sealed record AssistantSessionSnapshotField<T> : IAssistantSessionSnapshotField
{
/// <summary>
/// Initializes a new typed snapshot field.
/// </summary>
/// <param name="value">The captured value.</param>
public AssistantSessionSnapshotField(T value)
{
this.Value = value;
}
/// <summary>
/// Gets the captured value.
/// </summary>
public T Value { get; }
/// <inheritdoc />
public Type ValueType => typeof(T);
/// <inheritdoc />
public bool TryRead<TValue>(out TValue? value)
{
if (this.Value is TValue typedValue)
{
value = typedValue;
return true;
}
if (this.Value is null && default(TValue) is null)
{
value = default;
return true;
}
value = default;
return false;
}
}

View File

@ -0,0 +1,28 @@
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Identifies a typed assistant session state value.
/// </summary>
/// <typeparam name="T">The value type stored for this key.</typeparam>
public readonly record struct AssistantSessionStateKey<T>
{
/// <summary>
/// Initializes a new assistant session state key.
/// </summary>
/// <param name="name">The stable dictionary name used in assistant session snapshots.</param>
public AssistantSessionStateKey(string name)
{
this.Name = name;
}
/// <summary>
/// Gets the stable dictionary name used in assistant session snapshots.
/// </summary>
public string Name { get; }
/// <summary>
/// Returns the stable dictionary name.
/// </summary>
/// <returns>The stable dictionary name.</returns>
public override string ToString() => this.Name;
}

View File

@ -0,0 +1,111 @@
using System.Text;
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Restores typed assistant session state values from a snapshot.
/// </summary>
/// <param name="fields">The captured snapshot fields.</param>
/// <param name="assistantTitle">The user-visible assistant title.</param>
public sealed class AssistantSessionStateReader(IReadOnlyDictionary<string, IAssistantSessionSnapshotField> fields, string assistantTitle)
{
private static readonly ILogger<AssistantSessionStateReader> LOG = Program.LOGGER_FACTORY.CreateLogger<AssistantSessionStateReader>();
/// <summary>
/// Restores a typed value when it exists in the snapshot.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="apply">The action that applies the restored value.</param>
public void Restore<T>(AssistantSessionStateKey<T> key, Action<T> apply)
{
if (this.TryRead(key, out var value))
apply(value!);
}
/// <summary>
/// Restores a list into an existing list instance.
/// </summary>
/// <typeparam name="T">The list item type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="target">The existing list to update.</param>
public void RestoreList<T>(AssistantSessionStateKey<List<T>> key, List<T> target)
{
this.Restore(key, values =>
{
target.Clear();
target.AddRange(values);
});
}
/// <summary>
/// Restores a hash set into an existing hash set instance.
/// </summary>
/// <typeparam name="T">The set item type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="target">The existing hash set to update.</param>
public void RestoreHashSet<T>(AssistantSessionStateKey<HashSet<T>> key, HashSet<T> target)
{
this.Restore(key, values =>
{
target.Clear();
target.UnionWith(values);
});
}
/// <summary>
/// Restores a dictionary into an existing dictionary instance.
/// </summary>
/// <typeparam name="TKey">The dictionary key type.</typeparam>
/// <typeparam name="TValue">The dictionary value type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="target">The existing dictionary to update.</param>
public void RestoreDictionary<TKey, TValue>(AssistantSessionStateKey<Dictionary<TKey, TValue>> key, Dictionary<TKey, TValue> target) where TKey : notnull
{
this.Restore(key, values =>
{
target.Clear();
foreach (var (itemKey, itemValue) in values)
target[itemKey] = itemValue;
});
}
/// <summary>
/// Restores text into an existing string builder instance.
/// </summary>
/// <param name="key">The typed state key.</param>
/// <param name="target">The existing string builder to update.</param>
public void RestoreStringBuilder(AssistantSessionStateKey<string> key, StringBuilder target)
{
this.Restore(key, value =>
{
target.Clear();
target.Append(value);
});
}
/// <summary>
/// Tries to read a typed value from the snapshot.
/// </summary>
/// <typeparam name="T">The requested value type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="value">The restored value when reading succeeds.</param>
/// <returns><c>true</c> when a value exists and matches the requested type; otherwise, <c>false</c>.</returns>
private bool TryRead<T>(AssistantSessionStateKey<T> key, out T? value)
{
value = default;
if (!fields.TryGetValue(key.Name, out var field))
return false;
if (field.TryRead(out value))
return true;
LOG.LogWarning(
"Could not restore assistant session field '{FieldStateKey}' for assistant '{AssistantTitle}'. ExpectedType='{ExpectedType}', CapturedValueType='{CapturedValueType}'. The current value is kept.",
key.Name,
assistantTitle,
typeof(T).FullName,
field.ValueType.FullName);
return false;
}
}

View File

@ -0,0 +1,78 @@
using System.Text;
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Collects typed assistant session state values for a snapshot.
/// </summary>
public sealed class AssistantSessionStateWriter
{
/// <summary>
/// Stores captured fields by their stable dictionary names.
/// </summary>
private readonly Dictionary<string, IAssistantSessionSnapshotField> fields = new(StringComparer.Ordinal);
/// <summary>
/// Stores a typed state value.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="value">The captured value.</param>
public void Set<T>(AssistantSessionStateKey<T> key, T value)
{
this.fields[key.Name] = new AssistantSessionSnapshotField<T>(value);
}
/// <summary>
/// Stores a list copy.
/// </summary>
/// <typeparam name="T">The list item type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="values">The values to copy.</param>
public void SetList<T>(AssistantSessionStateKey<List<T>> key, IEnumerable<T> values)
{
this.Set(key, values.ToList());
}
/// <summary>
/// Stores a hash set copy.
/// </summary>
/// <typeparam name="T">The set item type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="values">The values to copy.</param>
public void SetHashSet<T>(AssistantSessionStateKey<HashSet<T>> key, IEnumerable<T> values)
{
this.Set(key, values.ToHashSet());
}
/// <summary>
/// Stores a dictionary copy.
/// </summary>
/// <typeparam name="TKey">The dictionary key type.</typeparam>
/// <typeparam name="TValue">The dictionary value type.</typeparam>
/// <param name="key">The typed state key.</param>
/// <param name="values">The values to copy.</param>
public void SetDictionary<TKey, TValue>(AssistantSessionStateKey<Dictionary<TKey, TValue>> key, IDictionary<TKey, TValue> values) where TKey : notnull
{
this.Set(key, new Dictionary<TKey, TValue>(values));
}
/// <summary>
/// Stores the current text from a string builder.
/// </summary>
/// <param name="key">The typed state key.</param>
/// <param name="value">The string builder to read.</param>
public void SetStringBuilder(AssistantSessionStateKey<string> key, StringBuilder value)
{
this.Set(key, value.ToString());
}
/// <summary>
/// Returns the captured fields as a dictionary.
/// </summary>
/// <returns>A copied dictionary containing the captured fields.</returns>
public Dictionary<string, IAssistantSessionSnapshotField> ToDictionary()
{
return new(this.fields, StringComparer.Ordinal);
}
}

View File

@ -0,0 +1,37 @@
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Describes the lifecycle state of an assistant session.
/// </summary>
public enum AssistantSessionStatus
{
/// <summary>
/// No session state is available.
/// </summary>
NONE,
/// <summary>
/// The assistant session is running.
/// </summary>
RUNNING,
/// <summary>
/// Cancellation was requested and the assistant is shutting down.
/// </summary>
CANCELING,
/// <summary>
/// The assistant session completed successfully.
/// </summary>
COMPLETED,
/// <summary>
/// The assistant session was canceled.
/// </summary>
CANCELED,
/// <summary>
/// The assistant session failed.
/// </summary>
FAILED,
}

View File

@ -0,0 +1,20 @@
namespace AIStudio.Tools.AssistantSessions;
/// <summary>
/// Provides a typed value stored in an assistant session snapshot.
/// </summary>
public interface IAssistantSessionSnapshotField
{
/// <summary>
/// Gets the type used when the value was captured.
/// </summary>
Type ValueType { get; }
/// <summary>
/// Tries to read the captured value as the requested type.
/// </summary>
/// <typeparam name="T">The requested value type.</typeparam>
/// <param name="value">The typed value when reading succeeds.</param>
/// <returns><c>true</c> when the captured value matches <typeparamref name="T"/>; otherwise, <c>false</c>.</returns>
bool TryRead<T>(out T? value);
}

View File

@ -144,6 +144,16 @@ public enum Event
/// Notifies receivers that chat generation state changed.
/// </summary>
CHAT_GENERATION_CHANGED,
/// <summary>
/// Notifies receivers that an assistant session changed.
/// </summary>
ASSISTANT_SESSION_CHANGED,
/// <summary>
/// Notifies receivers that an assistant session finished.
/// </summary>
ASSISTANT_SESSION_FINISHED,
// Workspace events:
/// <summary>

View File

@ -30,6 +30,49 @@ public sealed class AssistantState
this.Times.Clear();
}
/// <summary>
/// Copies all dynamic assistant state values from another state instance.
/// </summary>
/// <param name="other">The state instance to copy from.</param>
public void CopyFrom(AssistantState other)
{
this.Clear();
CopyDictionary(other.Text, this.Text);
CopyDictionary(other.SingleSelect, this.SingleSelect);
CopyDictionary(other.MultiSelect, this.MultiSelect);
CopyDictionary(other.Booleans, this.Booleans);
CopyDictionary(other.WebContent, this.WebContent);
CopyDictionary(other.FileContent, this.FileContent);
CopyDictionary(other.Colors, this.Colors);
CopyDictionary(other.Dates, this.Dates);
CopyDictionary(other.DateRanges, this.DateRanges);
CopyDictionary(other.Times, this.Times);
}
/// <summary>
/// Creates a copy of the dynamic assistant state.
/// </summary>
/// <returns>A copied assistant state instance.</returns>
public AssistantState Clone()
{
var clone = new AssistantState();
clone.CopyFrom(this);
return clone;
}
/// <summary>
/// Copies all entries from one dictionary into another dictionary.
/// </summary>
/// <typeparam name="TKey">The dictionary key type.</typeparam>
/// <typeparam name="TValue">The dictionary value type.</typeparam>
/// <param name="source">The source dictionary.</param>
/// <param name="target">The target dictionary.</param>
private static void CopyDictionary<TKey, TValue>(Dictionary<TKey, TValue> source, Dictionary<TKey, TValue> target) where TKey : notnull
{
foreach (var (key, value) in source)
target[key] = value;
}
public bool TryApplyValue(string fieldName, LuaValue value, out string expectedType)
{
expectedType = string.Empty;

View File

@ -1,2 +1,3 @@
# v26.6.3, build 243 (2026-06-xx xx:xx UTC)
- Improved assistants so running tasks can continue when you leave the assistant and return later.
- Improved the chat experience by automatically focusing the message composer again when it becomes available. Thanks, Dominic Neuburg (`donework`), for the contribution.