mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-13 06:53:48 +00:00
Fixed the warning about unsaved changes appearing only by chance (#1050)
This commit is contained in:
1 parent
468d28aa94
commit
c43b52d483
11 files changed
+213
-20
No files matched your search
@@ -2106,26 +2106,26 @@ public partial class ChatComponent : MSGComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task<TResult?> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
|
||||
protected override Task<MessageBusAnswer<TResult>> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TPayload : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.HAS_CHAT_UNSAVED_CHANGES:
|
||||
if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
return Task.FromResult((TResult?) (object) false);
|
||||
return Task.FromResult(new MessageBusAnswer<TResult>(true, (TResult) (object) false));
|
||||
|
||||
if (this.IsCurrentChatStreaming)
|
||||
return Task.FromResult((TResult?) (object) false);
|
||||
return Task.FromResult(new MessageBusAnswer<TResult>(true, (TResult) (object) false));
|
||||
|
||||
//
|
||||
// A message the user started counts as unsaved on its own only while there is no chat
|
||||
// to keep it. Otherwise, it is part of the chat: changing it counts as a change of the
|
||||
// chat, and a message which came back from the disk with the chat is saved already.
|
||||
//
|
||||
return Task.FromResult((TResult?)(object)(this.hasUnsavedChanges || (this.ChatThread is null && this.ComposerState.HasVisibleUserDraft)));
|
||||
return Task.FromResult(new MessageBusAnswer<TResult>(true, (TResult)(object)(this.hasUnsavedChanges || (this.ChatThread is null && this.ComposerState.HasVisibleUserDraft))));
|
||||
}
|
||||
|
||||
return Task.FromResult(default(TResult));
|
||||
|
||||
return Task.FromResult(MessageBusAnswer<TResult>.NONE);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -67,7 +67,7 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IAsyncDispo
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
public async Task<MessageBusAnswer<TResult>> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return await this.ProcessIncomingMessageWithResult<TPayload, TResult>(sendingComponent, triggeredEvent, data);
|
||||
}
|
||||
@@ -79,9 +79,13 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IAsyncDispo
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual Task<TResult?> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
/// <summary>
|
||||
/// Answers a question from the message bus. Override this for the questions your component handles,
|
||||
/// and return MessageBusAnswer.NONE for every other one.
|
||||
/// </summary>
|
||||
protected virtual Task<MessageBusAnswer<TResult>> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
return Task.FromResult(MessageBusAnswer<TResult>.NONE);
|
||||
}
|
||||
|
||||
protected async Task SendMessage<T>(Event triggeredEvent, T? data = default)
|
||||
|
||||
@@ -429,9 +429,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
}
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
public Task<MessageBusAnswer<TResult>> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
return Task.FromResult(MessageBusAnswer<TResult>.NONE);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -6,5 +6,9 @@ public interface IMessageBusReceiver
|
||||
{
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data);
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data);
|
||||
/// <summary>
|
||||
/// Answers a question which was asked through MessageBus.SendMessageUseFirstResult.
|
||||
/// </summary>
|
||||
/// <returns>The answer, or MessageBusAnswer.NONE when this receiver does not handle the question.</returns>
|
||||
public Task<MessageBusAnswer<TResult>> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data);
|
||||
}
|
||||
@@ -208,19 +208,42 @@ public sealed class MessageBus
|
||||
return messages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the receivers a question and returns the first answer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every receiver which never narrowed down its events gets the question as well, but only the ones
|
||||
/// which handle it answer. All others return MessageBusAnswer.NONE and are passed over, cf. the remarks
|
||||
/// on MessageBusAnswer for why a bare result cannot express that.
|
||||
/// <br/><br/>
|
||||
/// A question stays within the circuit, i.e., the window, of the sender. The app keeps the circuits of
|
||||
/// reloaded windows around for a long time, cf. the retention settings in Program.cs, and their components
|
||||
/// are still registered: the chat of such an old window must not answer for the one the user is looking
|
||||
/// at. Receivers without a circuit, such as hosted services, serve every window, and a sender without a
|
||||
/// circuit asks all receivers.
|
||||
/// </remarks>
|
||||
/// <param name="sendingComponent">That's you, the sender.</param>
|
||||
/// <param name="triggeredEvent">The question to ask.</param>
|
||||
/// <param name="data">What the question needs to be answered, if anything.</param>
|
||||
/// <returns>The first answer, or the default of TResult when no receiver answers. Asking for unsaved
|
||||
/// changes thus yields false when no chat is open.</returns>
|
||||
public async Task<TResult?> SendMessageUseFirstResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data = default)
|
||||
{
|
||||
var senderCircuit = sendingComponent is IMessageBusReceiver sender && this.receiverCircuits.TryGetValue(sender, out var circuitState) ? circuitState : null;
|
||||
foreach (var (receiver, componentFilter) in this.componentFilters)
|
||||
{
|
||||
if (componentFilter.Length > 0 && sendingComponent is not null && !componentFilter.Contains(sendingComponent))
|
||||
continue;
|
||||
|
||||
if (senderCircuit is not null && this.receiverCircuits.TryGetValue(receiver, out var receiverCircuit) && !ReferenceEquals(receiverCircuit, senderCircuit))
|
||||
continue;
|
||||
|
||||
var eventFilter = this.componentEvents[receiver];
|
||||
if (eventFilter.Length == 0 || eventFilter.Contains(triggeredEvent))
|
||||
{
|
||||
var result = await receiver.ProcessMessageWithResult<TPayload, TResult>(sendingComponent, triggeredEvent, data);
|
||||
if (result is not null)
|
||||
return (TResult) result;
|
||||
var answer = await receiver.ProcessMessageWithResult<TPayload, TResult>(sendingComponent, triggeredEvent, data);
|
||||
if (answer.IsAnswered)
|
||||
return answer.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// What one receiver says to a question on the message bus, cf. MessageBus.SendMessageUseFirstResult.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A bare result cannot tell an answer apart from "this is not my question". Most receivers are components
|
||||
/// which never narrow down their events, so every question reaches them, and the default they return looks
|
||||
/// like an answer: for a bool, it is false. Such a false used to drown out the chat, the only receiver which
|
||||
/// knows whether it has unsaved changes, and the warning about them appeared only by chance. That is why a
|
||||
/// receiver states that it answers, and every other one returns NONE.
|
||||
/// </remarks>
|
||||
/// <param name="IsAnswered">True, when the receiver handles the question.</param>
|
||||
/// <param name="Value">The answer. It means nothing unless IsAnswered is true.</param>
|
||||
/// <typeparam name="TResult">The type of the answer.</typeparam>
|
||||
public readonly record struct MessageBusAnswer<TResult>(bool IsAnswered, TResult? Value)
|
||||
{
|
||||
/// <summary>
|
||||
/// The receiver does not handle this question.
|
||||
/// </summary>
|
||||
public static readonly MessageBusAnswer<TResult> NONE = new(false, default);
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
||||
}
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) => Task.FromResult<TResult?>(default);
|
||||
public Task<MessageBusAnswer<TResult>> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) => Task.FromResult(MessageBusAnswer<TResult>.NONE);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -91,9 +91,9 @@ public sealed class RustAvailabilityMonitorService : BackgroundService, IMessage
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
public Task<MessageBusAnswer<TResult>> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
return Task.FromResult(MessageBusAnswer<TResult>.NONE);
|
||||
}
|
||||
|
||||
private async Task VerifyRustAvailability()
|
||||
|
||||
@@ -81,9 +81,9 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver
|
||||
}
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
public Task<MessageBusAnswer<TResult>> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
return Task.FromResult(MessageBusAnswer<TResult>.NONE);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
- Fixed a chat you started by attaching an audio or video file getting a German name, regardless of the language you use AI Studio in.
|
||||
- Fixed AI Studio sometimes warning about unsaved changes when you left a chat in which you had only selected a chat template, without writing anything yourself.
|
||||
- Fixed regenerating an answer clearing the message you had started to write.
|
||||
- Fixed the warning about unsaved changes in a chat appearing only by chance when you save your chats manually or have turned off workspaces. AI Studio now reliably asks before you open another chat, create a new one, or leave the chat page, so you no longer lose your changes by accident.
|
||||
- Updated the code contributions on the supporters page, which now thank everyone who has contributed code to AI Studio so far.
|
||||
- Upgraded several libraries to improve security.
|
||||
- Upgraded to Rust v1.99.0
|
||||
Reference in new issue
Block a user