mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-12 23:52:13 +00:00
Replaced visual briefing special cases with component capabilities & fixed briefing session clearing
This commit is contained in:
parent
832b5e32cc
commit
bb418ab5b1
@ -606,15 +606,17 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
};
|
||||
|
||||
var sendToData = destination.GetData();
|
||||
if (destination is not Tools.Components.CHAT and not Tools.Components.VISUAL_BRIEFING_ASSISTANT &&
|
||||
this.AssistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && snapshot.Key.Component == destination))
|
||||
if (destination.HasSingleSessionSlot() && 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)
|
||||
// Only components with a single session slot may be cleared as a group. The visual briefing
|
||||
// assistant keys its sessions per briefing, so clearing by component would discard the
|
||||
// status of every stored briefing instead of the one we are about to open.
|
||||
if (destination.HasSingleSessionSlot())
|
||||
await this.AssistantSessionService.ClearInactiveSessionsForComponentAsync(destination);
|
||||
|
||||
switch (destination)
|
||||
|
||||
@ -200,6 +200,8 @@ public partial class VisualBriefingAssistant
|
||||
null,
|
||||
new(StringComparer.Ordinal),
|
||||
this);
|
||||
|
||||
this.RetireFinishedSession(sessionKey);
|
||||
this.generatingBriefings.Remove(briefingId);
|
||||
this.StateHasChanged();
|
||||
}
|
||||
@ -290,11 +292,29 @@ public partial class VisualBriefingAssistant
|
||||
finally
|
||||
{
|
||||
await this.AssistantSessionService.CompleteAsync(sessionKey, session.SessionId, terminalStatus, terminalIssue, null, new(StringComparer.Ordinal), this);
|
||||
this.RetireFinishedSession(sessionKey);
|
||||
this.generatingBriefings.Remove(briefingId);
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consumes the finished session of one briefing while this component is still showing it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A briefing session carries no state, because the briefing itself is stored on disk. Its only
|
||||
/// remaining purpose after completion is the indicator on the assistant overview. When the user
|
||||
/// is still on this page, that indicator would be stale, so we retire the session the same way
|
||||
/// <c>AssistantBase</c> does. When the user has navigated away, we keep it so the overview can
|
||||
/// report that a background build has finished.
|
||||
/// </remarks>
|
||||
/// <param name="sessionKey">The session key of the briefing that just finished.</param>
|
||||
private void RetireFinishedSession(AssistantSessionKey sessionKey)
|
||||
{
|
||||
if (!this.isDisposed)
|
||||
_ = this.AssistantSessionService.TryTakeInactiveSnapshot(sessionKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically resumes the selected build that was active when the app stopped.
|
||||
/// </summary>
|
||||
|
||||
@ -190,6 +190,9 @@ public partial class VisualBriefingAssistant : MSGComponentBase
|
||||
/// <summary>Requests validation after conditional form controls have rendered.</summary>
|
||||
private bool formValidationPending;
|
||||
|
||||
/// <summary>Stores whether this component instance has already left the renderer.</summary>
|
||||
private bool isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Defines <c>IsCurrentBusy</c> for the visual briefing feature.
|
||||
/// </summary>
|
||||
@ -237,6 +240,7 @@ public partial class VisualBriefingAssistant : MSGComponentBase
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
this.isDisposed = true;
|
||||
this.sourceMonitorCancellation.Cancel();
|
||||
this.sourceMonitorCancellation.Dispose();
|
||||
this.buildDurationMonitorCancellation.Cancel();
|
||||
|
||||
@ -103,14 +103,29 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
|
||||
private MediaImportOwner CurrentMediaImportOwner => MediaImportOwner.ForAssistant(new AssistantSessionKey(this.Component, this.AssistantSessionInstanceId));
|
||||
|
||||
private MediaImportSnapshot? MediaImportSnapshot => this.Component is Tools.Components.VISUAL_BRIEFING_ASSISTANT
|
||||
? this.MediaTranscriptionService.GetSnapshots().FirstOrDefault(snapshot =>
|
||||
snapshot.Owner.Kind is MediaImportOwnerKind.VISUAL_BRIEFING)
|
||||
: string.IsNullOrWhiteSpace(this.AssistantSessionInstanceId)
|
||||
? this.MediaTranscriptionService.GetSnapshots().FirstOrDefault(snapshot =>
|
||||
snapshot.Owner.Kind is MediaImportOwnerKind.ASSISTANT
|
||||
&& snapshot.Owner.Id.StartsWith($"{this.Component}:", StringComparison.Ordinal))
|
||||
: this.MediaTranscriptionService.GetSnapshot(this.CurrentMediaImportOwner);
|
||||
private MediaImportSnapshot? MediaImportSnapshot => this.MediaTranscriptionService.GetSnapshots()
|
||||
.FirstOrDefault(snapshot => this.OwnedByThisBlock(snapshot.Owner));
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether a media-import owner belongs to the assistant represented by this block.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Owners that persist their own sources are keyed by the stored document rather than by an
|
||||
/// assistant session, so this block aggregates all of them for its component. Without a session
|
||||
/// instance we aggregate every owner of the component, otherwise we match the exact owner.
|
||||
/// </remarks>
|
||||
/// <param name="owner">The media-import owner to test.</param>
|
||||
/// <returns><c>true</c> when this block represents the owner.</returns>
|
||||
private bool OwnedByThisBlock(MediaImportOwner owner)
|
||||
{
|
||||
if (owner.Kind.PersistsOwnSources())
|
||||
return owner.Kind == this.Component.MediaOwnerKind();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.AssistantSessionInstanceId))
|
||||
return owner.Kind is MediaImportOwnerKind.ASSISTANT && owner.Id.StartsWith($"{this.Component}:", StringComparison.Ordinal);
|
||||
|
||||
return owner == this.CurrentMediaImportOwner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assistant session indicator shown on top of the assistant icon.
|
||||
@ -143,13 +158,7 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
|
||||
private void OnMediaImportStateChanged(MediaImportOwner owner)
|
||||
{
|
||||
var matches = this.Component is Tools.Components.VISUAL_BRIEFING_ASSISTANT
|
||||
? owner.Kind is MediaImportOwnerKind.VISUAL_BRIEFING
|
||||
: string.IsNullOrWhiteSpace(this.AssistantSessionInstanceId)
|
||||
? owner.Kind is MediaImportOwnerKind.ASSISTANT && owner.Id.StartsWith($"{this.Component}:", StringComparison.Ordinal)
|
||||
: owner == this.CurrentMediaImportOwner;
|
||||
|
||||
if (matches)
|
||||
if (this.OwnedByThisBlock(owner))
|
||||
_ = this.InvokeAsync(this.StateHasChanged);
|
||||
}
|
||||
|
||||
|
||||
@ -184,7 +184,9 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
private async Task SyncCompletedMediaAttachmentsAsync()
|
||||
{
|
||||
var delivery = this.MediaTranscriptionService.GetPendingDelivery(this.EffectiveMediaImportTarget);
|
||||
var completed = this.EffectiveImportOwner.Kind is MediaImportOwnerKind.VISUAL_BRIEFING
|
||||
// Owners that persist their own sources have already taken the media over when the batch
|
||||
// started, so re-adding the delivered transcripts here would duplicate them.
|
||||
var completed = this.EffectiveImportOwner.Kind.PersistsOwnSources()
|
||||
? Array.Empty<FileAttachment>()
|
||||
: delivery?.Attachments ?? [];
|
||||
var pending = this.OwnerChat?.PendingMediaTranscripts ?? [];
|
||||
@ -493,7 +495,9 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
if (this.OwnerChat is null)
|
||||
this.OwnerChat = await this.EnsureOwnerChatAsync(mediaPaths[0]);
|
||||
|
||||
if (this.EffectiveImportOwner.Kind is MediaImportOwnerKind.VISUAL_BRIEFING)
|
||||
// Owners that persist their own sources show the file right away and keep it next to the
|
||||
// stored document, instead of waiting for the transcription to be delivered back.
|
||||
if (this.EffectiveImportOwner.Kind.PersistsOwnSources())
|
||||
{
|
||||
foreach (var mediaPath in mediaPaths)
|
||||
this.DocumentPaths.Add(FileAttachment.FromPath(mediaPath));
|
||||
|
||||
@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Media;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
namespace AIStudio.Tools;
|
||||
@ -24,6 +25,38 @@ public static class ComponentsExtensions
|
||||
_ => PreviewFeatures.NONE,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether a component owns exactly one assistant session slot, so that a running session
|
||||
/// blocks starting another one and inactive sessions can be cleared as a group.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Components return <c>false</c> for two different reasons. The chat has no assistant sessions
|
||||
/// at all. The visual briefing assistant keys its sessions per briefing, so it owns one slot per
|
||||
/// stored briefing rather than one per component. Both must be excluded from the single-slot
|
||||
/// checks, which is why this is a capability and not a component comparison.
|
||||
/// </remarks>
|
||||
/// <param name="component">The component to look up.</param>
|
||||
/// <returns><c>true</c> when the component owns exactly one session slot.</returns>
|
||||
public static bool HasSingleSessionSlot(this Components component) => component switch
|
||||
{
|
||||
Components.CHAT => false,
|
||||
Components.VISUAL_BRIEFING_ASSISTANT => false,
|
||||
|
||||
_ => true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of media-import owner a component creates for its attachments.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to look up.</param>
|
||||
/// <returns>The media-import owner kind.</returns>
|
||||
public static MediaImportOwnerKind MediaOwnerKind(this Components component) => component switch
|
||||
{
|
||||
Components.VISUAL_BRIEFING_ASSISTANT => MediaImportOwnerKind.VISUAL_BRIEFING,
|
||||
|
||||
_ => MediaImportOwnerKind.ASSISTANT,
|
||||
};
|
||||
|
||||
public static bool AllowSendTo(this Components component) => component switch
|
||||
{
|
||||
Components.NONE => false,
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
namespace AIStudio.Tools.Media;
|
||||
|
||||
/// <summary>Capabilities of a media-import owner kind.</summary>
|
||||
public static class MediaImportOwnerKindExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether the owner stores its own source list and transcripts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Owners that persist their own sources take the attached media over immediately and keep it
|
||||
/// next to the stored document, see <see cref="MediaImportOwnerKind.VISUAL_BRIEFING"/>. The
|
||||
/// attachment control must therefore neither wait for the transcription to finish before showing
|
||||
/// the file, nor deliver the completed transcripts back into its own list afterwards, because
|
||||
/// the owner already holds them. All other owners rely on that delivery instead.
|
||||
/// </remarks>
|
||||
/// <param name="kind">The owner kind to look up.</param>
|
||||
/// <returns><c>true</c> when the owner persists its own sources.</returns>
|
||||
public static bool PersistsOwnSources(this MediaImportOwnerKind kind) => kind is MediaImportOwnerKind.VISUAL_BRIEFING;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user