diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index 32c4e9e9..dd48db75 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -221,7 +221,7 @@ public abstract partial class AssistantBase : AssistantLowerBase wher this.CancellationTokenSource = new(); this.IsProcessing = true; - var startedSession = await this.AssistantSessionService.TryBeginAsync(this.assistantSessionKey, this.Title, this.CancellationTokenSource, this.ChatThread, this.CaptureAssistantSessionState()); + var startedSession = await this.AssistantSessionService.TryBeginAsync(this.assistantSessionKey, this.Title, this.CancellationTokenSource, this.ChatThread, this.CaptureAssistantSessionState(), this); if (startedSession.IsActive is not true || startedSession.Key != this.assistantSessionKey) { this.CancellationTokenSource.Dispose(); @@ -265,7 +265,11 @@ public abstract partial class AssistantBase : AssistantLowerBase wher 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()); + { + await this.AssistantSessionService.CompleteAsync(this.assistantSessionKey, sessionId, sessionStatus, errorMessage, this.ChatThread, this.CaptureAssistantSessionState(), this); + if (!this.isDisposed) + _ = this.AssistantSessionService.TryTakeInactiveSnapshot(this.assistantSessionKey); + } sessionCancellationTokenSource?.Dispose(); await this.RefreshAssistantUIAsync(); } @@ -452,7 +456,7 @@ public abstract partial class AssistantBase : AssistantLowerBase wher private async Task CancelStreaming() { - await this.AssistantSessionService.CancelAsync(this.assistantSessionKey); + await this.AssistantSessionService.CancelAsync(this.assistantSessionKey, this); } protected async Task CopyToClipboard() @@ -640,7 +644,7 @@ public abstract partial class AssistantBase : AssistantLowerBase wher if (this.assistantSessionId is null) return Task.CompletedTask; - return this.AssistantSessionService.CheckpointAsync(this.assistantSessionKey, this.assistantSessionId.Value, this.Title, this.ChatThread, this.CaptureAssistantSessionState()); + return this.AssistantSessionService.CheckpointAsync(this.assistantSessionKey, this.assistantSessionId.Value, this.Title, this.ChatThread, this.CaptureAssistantSessionState(), this); } /// @@ -667,6 +671,9 @@ public abstract partial class AssistantBase : AssistantLowerBase wher /// A task that completes after the message was processed. protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { + if (ReferenceEquals(sendingComponent, this)) + return; + switch (triggeredEvent) { case Event.ASSISTANT_SESSION_CHANGED: diff --git a/app/MindWork AI Studio/Tools/AssistantSessions/AssistantSessionService.cs b/app/MindWork AI Studio/Tools/AssistantSessions/AssistantSessionService.cs index a5dd20af..e3baa690 100644 --- a/app/MindWork AI Studio/Tools/AssistantSessions/AssistantSessionService.cs +++ b/app/MindWork AI Studio/Tools/AssistantSessions/AssistantSessionService.cs @@ -2,6 +2,8 @@ using System.Collections.Concurrent; using AIStudio.Chat; +using Microsoft.AspNetCore.Components; + namespace AIStudio.Tools.AssistantSessions; /// @@ -148,8 +150,9 @@ public sealed class AssistantSessionService(MessageBus messageBus) /// The cancellation token source owned by the new runtime session. /// The current assistant chat thread, if one already exists. /// The initial assistant component state. + /// The component that initiated the session start. /// The new session snapshot, or the existing active session snapshot. - public async Task TryBeginAsync(AssistantSessionKey key, string title, CancellationTokenSource cancellationTokenSource, ChatThread? chatThread, Dictionary state) + public async Task TryBeginAsync(AssistantSessionKey key, string title, CancellationTokenSource cancellationTokenSource, ChatThread? chatThread, Dictionary state, ComponentBase? sendingComponent = null) { if (this.sessions.TryGetValue(key, out var existing) && existing.Status is AssistantSessionStatus.RUNNING or AssistantSessionStatus.CANCELING) return CreateSnapshot(existing); @@ -170,7 +173,7 @@ public sealed class AssistantSessionService(MessageBus messageBus) this.sessions[key] = session; var snapshot = CreateSnapshot(session); - await this.NotifyChangedAsync(session); + await this.NotifyChangedAsync(session, sendingComponent); return snapshot; } @@ -182,7 +185,8 @@ public sealed class AssistantSessionService(MessageBus messageBus) /// The current user-visible assistant title. /// The current assistant chat thread. /// The current assistant component state. - public async Task CheckpointAsync(AssistantSessionKey key, Guid sessionId, string title, ChatThread? chatThread, Dictionary state) + /// The component that initiated the checkpoint. + public async Task CheckpointAsync(AssistantSessionKey key, Guid sessionId, string title, ChatThread? chatThread, Dictionary state, ComponentBase? sendingComponent = null) { if (!this.sessions.TryGetValue(key, out var session)) return; @@ -198,14 +202,15 @@ public sealed class AssistantSessionService(MessageBus messageBus) session.UpdatedAt = DateTimeOffset.Now; } - await this.NotifyChangedAsync(session); + await this.NotifyChangedAsync(session, sendingComponent); } /// /// Requests cancellation for an active assistant session. /// /// The assistant session key to cancel. - public async Task CancelAsync(AssistantSessionKey key) + /// The component that initiated the cancellation. + public async Task CancelAsync(AssistantSessionKey key, ComponentBase? sendingComponent = null) { if (!this.sessions.TryGetValue(key, out var session)) return; @@ -229,7 +234,7 @@ public sealed class AssistantSessionService(MessageBus messageBus) return; } - await this.NotifyChangedAsync(session); + await this.NotifyChangedAsync(session, sendingComponent); } /// @@ -241,7 +246,8 @@ public sealed class AssistantSessionService(MessageBus messageBus) /// The user-visible error message for failed sessions. /// The final assistant chat thread. /// The final assistant component state. - public async Task CompleteAsync(AssistantSessionKey key, Guid sessionId, AssistantSessionStatus status, string errorMessage, ChatThread? chatThread, Dictionary state) + /// The component that initiated the completion. + public async Task CompleteAsync(AssistantSessionKey key, Guid sessionId, AssistantSessionStatus status, string errorMessage, ChatThread? chatThread, Dictionary state, ComponentBase? sendingComponent = null) { if (!this.sessions.TryGetValue(key, out var session)) return; @@ -259,8 +265,8 @@ public sealed class AssistantSessionService(MessageBus messageBus) session.FinishedAt = session.UpdatedAt; } - await this.NotifyChangedAsync(session); - await messageBus.SendMessage(null, Event.ASSISTANT_SESSION_FINISHED, CreateSnapshot(session)); + await this.NotifyChangedAsync(session, sendingComponent); + await messageBus.SendMessage(sendingComponent, Event.ASSISTANT_SESSION_FINISHED, CreateSnapshot(session)); try { @@ -316,9 +322,10 @@ public sealed class AssistantSessionService(MessageBus messageBus) /// Publishes an assistant session change event. /// /// The runtime session whose copied snapshot should be published. - private async Task NotifyChangedAsync(AssistantSessionState session) + /// The component that initiated the session change. + private async Task NotifyChangedAsync(AssistantSessionState session, ComponentBase? sendingComponent = null) { - await messageBus.SendMessage(null, Event.ASSISTANT_SESSION_CHANGED, CreateSnapshot(session)); + await messageBus.SendMessage(sendingComponent, Event.ASSISTANT_SESSION_CHANGED, CreateSnapshot(session)); } ///