Routed all notifications through the message bus instead of the snackbar

This commit is contained in:
Thorsten Sommer 2026-08-02 20:11:27 +02:00
parent 2a29aa3f33
commit 18dc8bf9b8
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
13 changed files with 62 additions and 55 deletions

View File

@ -478,13 +478,13 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
{
if (this.selectedLanguagePlugin is null)
{
this.Snackbar.Add(T("No language plugin selected."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.Translate, T("No language plugin selected.")));
return;
}
if (this.finalLuaCode.Length == 0)
{
this.Snackbar.Add(T("No Lua code generated yet."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.Code, T("No Lua code generated yet.")));
return;
}
@ -500,7 +500,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
if (!File.Exists(pluginFilePath))
{
this.Logger.LogError("Plugin file not found: {PluginFilePath}.", pluginFilePath);
this.Snackbar.Add(T("Plugin file not found."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.FindInPage, T("Plugin file not found.")));
return;
}
@ -514,7 +514,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
if (markerIndex == -1)
{
this.Logger.LogError("Could not find 'UI_TEXT_CONTENT = {{}}' marker in plugin file: {PluginFilePath}", pluginFilePath);
this.Snackbar.Add(T("Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.FindInPage, T("Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file.")));
return;
}
@ -524,12 +524,12 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
// Write the updated content back to the file:
await File.WriteAllTextAsync(pluginFilePath, newContent);
this.Snackbar.Add(T("Successfully updated plugin file."), Severity.Success);
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.Translate, T("Successfully updated plugin file.")));
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Error writing to plugin file.");
this.Snackbar.Add(T("Error writing to plugin file."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.Translate, T("Error writing to plugin file.")));
}
}
#endif

View File

@ -35,9 +35,6 @@ public partial class AssistantLogViewer : MSGComponentBase
[Inject]
private RustService RustService { get; init; } = null!;
[Inject]
private ISnackbar Snackbar { get; init; } = null!;
[Inject]
private NavigationManager NavigationManager { get; init; } = null!;
@ -215,11 +212,7 @@ public partial class AssistantLogViewer : MSGComponentBase
var path = this.CurrentLogPath;
if (string.IsNullOrWhiteSpace(path))
{
this.Snackbar.Add(T("The log file path is not available yet."), Severity.Warning, config =>
{
config.Icon = Icons.Material.Filled.Folder;
config.IconSize = Size.Large;
});
await this.MessageBus.SendWarning(new(Icons.Material.Filled.Folder, T("The log file path is not available yet.")));
return;
}
@ -231,30 +224,18 @@ public partial class AssistantLogViewer : MSGComponentBase
catch (Exception e)
{
this.Logger.LogWarning(e, "Could not open the log file location in the file manager.");
this.Snackbar.Add(T("Could not open the log file location."), Severity.Error, config =>
{
config.Icon = Icons.Material.Filled.Folder;
config.IconSize = Size.Large;
});
await this.MessageBus.SendError(new(Icons.Material.Filled.Folder, T("Could not open the log file location.")));
return;
}
if (response.Success)
{
this.Snackbar.Add(T("Opened the log file location."), Severity.Success, config =>
{
config.Icon = Icons.Material.Filled.FolderOpen;
config.IconSize = Size.Large;
});
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.FolderOpen, T("Opened the log file location.")));
return;
}
var issue = string.IsNullOrWhiteSpace(response.Issue) ? T("Unknown error") : response.Issue;
this.Snackbar.Add(string.Format(T("Could not open the log file location: {0}"), issue), Severity.Error, config =>
{
config.Icon = Icons.Material.Filled.Folder;
config.IconSize = Size.Large;
});
await this.MessageBus.SendError(new(Icons.Material.Filled.Folder, string.Format(T("Could not open the log file location: {0}"), issue)));
}
private void ClearFilters()

View File

@ -562,7 +562,7 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
this.currentCustomPromptGuidePath = selected.FilePath;
if (files.Count > 1 || replacedPrevious)
this.Snackbar.Add(T("Replaced the previously selected custom prompt guide file."), Severity.Info);
await this.MessageBus.SendInfo(new(Icons.Material.Filled.SwapHoriz, T("Replaced the previously selected custom prompt guide file.")));
await this.LoadCustomPromptGuidelineContentAsync(selected);
}
@ -572,7 +572,7 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
if (!fileAttachment.Exists)
{
this.customPromptingGuidelineContent = string.Empty;
this.Snackbar.Add(T("The selected custom prompt guide file could not be found."), Severity.Warning);
await this.MessageBus.SendWarning(new(Icons.Material.Filled.FindInPage, T("The selected custom prompt guide file could not be found.")));
return;
}
@ -581,12 +581,12 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
this.isLoadingCustomPromptGuide = true;
this.customPromptingGuidelineContent = await UserFile.LoadFileData(fileAttachment.FilePath, this.RustService, this.DialogService);
if (string.IsNullOrWhiteSpace(this.customPromptingGuidelineContent))
this.Snackbar.Add(T("The custom prompt guide file is empty or could not be read."), Severity.Warning);
await this.MessageBus.SendWarning(new(Icons.Material.Filled.Description, T("The custom prompt guide file is empty or could not be read.")));
}
catch
{
this.customPromptingGuidelineContent = string.Empty;
this.Snackbar.Add(T("Failed to load custom prompt guide content."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.Description, T("Failed to load custom prompt guide content.")));
}
finally
{
@ -600,7 +600,7 @@ public partial class AssistantPromptOptimizer : AssistantBaseCore<SettingsDialog
var promptingGuideline = await ReadPromptingGuidelineAsync();
if (string.IsNullOrWhiteSpace(promptingGuideline))
{
this.Snackbar.Add(T("The prompting guideline file could not be loaded."), Severity.Warning);
await this.MessageBus.SendWarning(new(Icons.Material.Filled.MenuBook, T("The prompting guideline file could not be loaded.")));
return;
}

View File

@ -89,7 +89,7 @@ public partial class VisualBriefingAssistant
terminalIssue = result.Issue;
if (terminalStatus is not AssistantSessionStatus.CANCELED)
this.Snackbar.Add(result.Issue, Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.AutoAwesome, result.Issue));
return;
}
@ -107,7 +107,7 @@ public partial class VisualBriefingAssistant
this.UpdateProject(latest);
}
this.Snackbar.Add(successMessage, Severity.Success);
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.AutoAwesome, successMessage));
terminalStatus = AssistantSessionStatus.COMPLETED;
}
catch (OperationCanceledException)
@ -119,7 +119,7 @@ public partial class VisualBriefingAssistant
{
terminalIssue = unexpectedFailureMessage;
this.Logger.LogError("Unexpected visual briefing UI failure. BriefingId={BriefingId} Mode={Mode} ExceptionType={ExceptionType}", briefingId, mode, exception.GetType().Name);
this.Snackbar.Add(terminalIssue, Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.AutoAwesome, terminalIssue));
}
finally
{

View File

@ -154,7 +154,7 @@ public partial class VisualBriefingAssistant
var path = await this.Store.GetProjectDirectoryPathAsync(this.selectedProject.BriefingId);
if (string.IsNullOrWhiteSpace(path))
{
this.Snackbar.Add(T("The visual briefing project folder is not available."), Severity.Warning);
await this.MessageBus.SendWarning(new(Icons.Material.Filled.Folder, T("The visual briefing project folder is not available.")));
return;
}
@ -166,18 +166,18 @@ public partial class VisualBriefingAssistant
catch (Exception exception)
{
this.Logger.LogWarning(exception, "Could not open the visual briefing project folder. BriefingId={BriefingId}", this.selectedProject.BriefingId);
this.Snackbar.Add(T("Could not open the visual briefing project folder."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.Folder, T("Could not open the visual briefing project folder.")));
return;
}
if (response.Success)
{
this.Snackbar.Add(T("Opened the visual briefing project folder."), Severity.Success);
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.Folder, T("Opened the visual briefing project folder.")));
return;
}
var issue = string.IsNullOrWhiteSpace(response.Issue) ? T("Unknown error") : response.Issue;
this.Snackbar.Add(string.Format(T("Could not open the visual briefing project folder: {0}"), issue), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.Folder, string.Format(T("Could not open the visual briefing project folder: {0}"), issue)));
}
/// <summary>

View File

@ -118,14 +118,14 @@ public partial class VisualBriefingAssistant
if (PathComparer().Equals(Path.GetFullPath(sourcePath), Path.GetFullPath(response.SaveFilePath)))
{
this.Snackbar.Add(T("Choose a different export location so the immutable briefing version is not overwritten."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.SaveAs, T("Choose a different export location so the immutable briefing version is not overwritten.")));
return;
}
var verified = await this.Store.OpenIntegrityCheckedVersionAsync(this.selectedBriefing.BriefingId, this.selectedRevisionId);
if (verified is null)
{
this.Snackbar.Add(T("The selected briefing version failed its integrity check and cannot be exported."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.GppBad, T("The selected briefing version failed its integrity check and cannot be exported.")));
return;
}
@ -146,7 +146,7 @@ public partial class VisualBriefingAssistant
exportedVersion.DocumentHash,
source.Length);
this.Snackbar.Add(T("The visual briefing was exported."), Severity.Success);
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.FileDownload, T("The visual briefing was exported.")));
}
/// <summary>
@ -176,7 +176,7 @@ public partial class VisualBriefingAssistant
if (!imported.Success)
{
this.Snackbar.Add(imported.Issue, Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.FileUpload, imported.Issue));
return;
}
@ -190,7 +190,7 @@ public partial class VisualBriefingAssistant
imported.RevisionId,
imported.WasDeduplicated);
this.Snackbar.Add(imported.WasDeduplicated ? T("This briefing revision was already imported.") : T("The visual briefing was imported."), Severity.Success);
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.FileUpload, imported.WasDeduplicated ? T("This briefing revision was already imported.") : T("The visual briefing was imported.")));
}
/// <summary>

View File

@ -217,7 +217,7 @@ public partial class VisualBriefingAssistant : MSGComponentBase
"Could not auto-save visual briefing. BriefingId={BriefingId} ExceptionType={ExceptionType}",
this.selectedBriefing.BriefingId,
exception.GetType().Name);
this.Snackbar.Add(T("The visual briefing settings could not be saved."), Severity.Error);
await this.MessageBus.SendError(new(Icons.Material.Filled.SaveAs, T("The visual briefing settings could not be saved.")));
}
}

View File

@ -77,12 +77,9 @@ public partial class MudCopyClipboardButton : ComponentBase
break;
default:
this.Snackbar.Add(TB("Cannot copy this content type to clipboard."), Severity.Error, config =>
{
config.Icon = Icons.Material.Filled.ContentCopy;
config.IconSize = Size.Large;
config.IconColor = Color.Error;
});
// This component is no MSGComponentBase, so it uses the shared bus instance the same
// way FileExtensionValidation does:
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ContentCopy, TB("Cannot copy this content type to clipboard.")));
break;
}
}

View File

@ -47,7 +47,7 @@ public abstract class SettingsPanelProviderBase : SettingsPanelBase
else
{
// No encryption secret available - inform the user:
this.Snackbar.Add(TB("Cannot export the encrypted API key: No enterprise encryption secret is configured."), Severity.Warning);
await this.MessageBus.SendWarning(new(Icons.Material.Filled.Key, TB("Cannot export the encrypted API key: No enterprise encryption secret is configured.")));
}
}
}

View File

@ -112,7 +112,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
this.MessageBus.ApplyFilters(this, [],
[
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.SHOW_WARNING, Event.SHOW_SUCCESS, Event.SHOW_INFO, 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.ASSISTANT_SESSION_CHANGED, Event.ASSISTANT_SESSION_FINISHED,
]);
@ -266,6 +266,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
break;
case Event.SHOW_INFO:
if (data is DataInfoMessage info)
info.Show(this.Snackbar);
break;
case Event.STARTUP_PLUGIN_SYSTEM:
_ = Task.Run(async () =>
{

View File

@ -0,0 +1,16 @@
namespace AIStudio.Tools;
public readonly record struct DataInfoMessage(string Icon, string Message)
{
public void Show(ISnackbar snackbar)
{
var icon = this.Icon;
snackbar.Add(this.Message, Severity.Info, config =>
{
config.Icon = icon;
config.IconSize = Size.Large;
config.HideTransitionDuration = 600;
config.VisibleStateDuration = 10_000;
});
}
}

View File

@ -73,6 +73,11 @@ public enum Event
/// </summary>
SHOW_SUCCESS,
/// <summary>
/// Requests display of an informational notification.
/// </summary>
SHOW_INFO,
/// <summary>
/// Carries an event received from the Tauri runtime.
/// </summary>

View File

@ -91,6 +91,8 @@ public sealed class MessageBus
public Task SendSuccess(DataSuccessMessage dataSuccessMessage) => this.SendMessage(null, Event.SHOW_SUCCESS, dataSuccessMessage);
public Task SendInfo(DataInfoMessage dataInfoMessage) => this.SendMessage(null, Event.SHOW_INFO, dataInfoMessage);
public void DeferMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data = default)
{
if (this.deferredMessages.TryGetValue(triggeredEvent, out var queue))