mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-24 08:53:37 +00:00
Fix: Mindwork Studio can miss the change from light to dark mode (#991)
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
parent
0bd0d6bcbd
commit
c4ba83f0be
@ -102,4 +102,4 @@
|
|||||||
</MudLayout>
|
</MudLayout>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<MudThemeProvider @ref="@this.themeProvider" Theme="@this.ColorTheme" IsDarkMode="@this.useDarkMode" />
|
<MudThemeProvider @ref="@this.themeProvider" Theme="@this.ColorTheme" IsDarkMode="@this.useDarkMode" ObserveSystemThemeChange="@this.FollowSystemTheme" />
|
||||||
@ -144,7 +144,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
// Send a message to start the plugin system:
|
// Send a message to start the plugin system:
|
||||||
await this.MessageBus.SendMessage<bool>(this, Event.STARTUP_PLUGIN_SYSTEM);
|
await this.MessageBus.SendMessage<bool>(this, Event.STARTUP_PLUGIN_SYSTEM);
|
||||||
|
|
||||||
await this.themeProvider.WatchSystemDarkModeAsync(this.SystemeThemeChanged);
|
await this.themeProvider.WatchSystemDarkModeAsync(this.SystemThemeChanged);
|
||||||
|
this.CircuitState.ConnectionRestored += this.OnConnectionRestored;
|
||||||
await this.UpdateThemeConfiguration();
|
await this.UpdateThemeConfiguration();
|
||||||
this.LoadNavItems();
|
this.LoadNavItems();
|
||||||
this.LoadEmbeddingItem();
|
this.LoadEmbeddingItem();
|
||||||
@ -564,15 +565,45 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SystemeThemeChanged(bool isDark)
|
/// <summary>
|
||||||
|
/// True, when the user wants AI Studio to follow the light or dark mode of the operating system.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This also decides whether the MudThemeProvider watches the operating system at all. On a system
|
||||||
|
/// change, the provider takes the new mode into its own state first and calls our handler only
|
||||||
|
/// afterward, so the handler cannot prevent it. Nor can a new render of this layout undo it: the
|
||||||
|
/// provider takes over its IsDarkMode parameter only when that value changes, and with a fixed theme,
|
||||||
|
/// it never does. Were the provider watching while the user chose a fixed theme, MudBlazor would show
|
||||||
|
/// the colors of the system from the next render on, while the rest of the app kept the chosen ones.
|
||||||
|
/// </remarks>
|
||||||
|
private bool FollowSystemTheme => this.SettingsManager.ConfigurationData.App.PreferredTheme is Themes.SYSTEM;
|
||||||
|
|
||||||
|
private async Task SystemThemeChanged(bool isDark)
|
||||||
{
|
{
|
||||||
this.Logger.LogInformation($"The system theme changed to {(isDark ? "dark" : "light")}.");
|
this.Logger.LogInformation($"The system theme changed to {(isDark ? "dark" : "light")}.");
|
||||||
await this.UpdateThemeConfiguration();
|
await this.UpdateThemeConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the color theme anew once the browser connection of this circuit returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The browser reports a change of the system theme exactly once. Blazor drops that report while the
|
||||||
|
/// connection is down, which happens when the machine switches its theme during sleep and wakes up
|
||||||
|
/// again. Since the circuit survives the sleep (cf. the retention settings in Program.cs), no reload
|
||||||
|
/// reads the theme anew either, so AI Studio would keep the theme it had before the sleep.
|
||||||
|
/// <br/><br/>
|
||||||
|
/// The update is deliberately not awaited: this handler runs while Blazor is still completing the
|
||||||
|
/// reconnection, and the answer to the JavaScript call inside can only arrive afterward.
|
||||||
|
/// </remarks>
|
||||||
|
private void OnConnectionRestored()
|
||||||
|
{
|
||||||
|
this.InvokeAsync(this.UpdateThemeConfiguration).Observe($"{nameof(MainLayout)}: reading the color theme after the connection returned");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UpdateThemeConfiguration()
|
private async Task UpdateThemeConfiguration()
|
||||||
{
|
{
|
||||||
if (this.SettingsManager.ConfigurationData.App.PreferredTheme is Themes.SYSTEM)
|
if (this.FollowSystemTheme)
|
||||||
this.useDarkMode = await this.themeProvider.GetSystemDarkModeAsync();
|
this.useDarkMode = await this.themeProvider.GetSystemDarkModeAsync();
|
||||||
else
|
else
|
||||||
this.useDarkMode = this.SettingsManager.ConfigurationData.App.PreferredTheme == Themes.DARK;
|
this.useDarkMode = this.SettingsManager.ConfigurationData.App.PreferredTheme == Themes.DARK;
|
||||||
@ -664,6 +695,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.MediaTranscriptionService.StateChanged -= this.OnMediaImportStateChanged;
|
this.MediaTranscriptionService.StateChanged -= this.OnMediaImportStateChanged;
|
||||||
|
this.CircuitState.ConnectionRestored -= this.OnConnectionRestored;
|
||||||
this.MessageBus.Unregister(this);
|
this.MessageBus.Unregister(this);
|
||||||
this.mandatoryInfoDialogSemaphore.Dispose();
|
this.mandatoryInfoDialogSemaphore.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,18 @@ public sealed class CircuitStateService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string CircuitId { get; private set; } = "n/a";
|
public string CircuitId { get; private set; } = "n/a";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the browser connection returned after it was lost.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It does not occur for the first connection of a circuit, only for the ones which follow a loss. Use it
|
||||||
|
/// to fetch again what the browser reports on its own: Blazor drops such reports while the connection is
|
||||||
|
/// down, and nothing sends them a second time. The event is raised while Blazor is still completing the
|
||||||
|
/// reconnection, though. A handler must not wait for JavaScript interop, because the browser's answer can
|
||||||
|
/// only be processed once the reconnection has finished. Start such work without awaiting it instead.
|
||||||
|
/// </remarks>
|
||||||
|
public event Action? ConnectionRestored;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called by the circuit handler when the circuit was opened.
|
/// Called by the circuit handler when the circuit was opened.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -37,7 +49,17 @@ public sealed class CircuitStateService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called by the circuit handler when the browser connection was established or restored.
|
/// Called by the circuit handler when the browser connection was established or restored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MarkAsConnected() => this.isConnected = true;
|
/// <remarks>
|
||||||
|
/// A restored connection raises ConnectionRestored. Blazor never runs the handler's events of one circuit
|
||||||
|
/// concurrently, so reading and writing the state in two steps is safe here.
|
||||||
|
/// </remarks>
|
||||||
|
public void MarkAsConnected()
|
||||||
|
{
|
||||||
|
var wasConnected = this.isConnected;
|
||||||
|
this.isConnected = true;
|
||||||
|
if (!wasConnected)
|
||||||
|
this.ConnectionRestored?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called by the circuit handler when the browser connection was lost or the circuit ended.
|
/// Called by the circuit handler when the browser connection was lost or the circuit ended.
|
||||||
|
|||||||
@ -87,5 +87,7 @@
|
|||||||
- Fixed AI Studio trying for minutes when a provider turns a request down for good. Such an answer does not change by asking a second time, so AI Studio now stops at the first one and tells you what the provider said about it.
|
- Fixed AI Studio trying for minutes when a provider turns a request down for good. Such an answer does not change by asking a second time, so AI Studio now stops at the first one and tells you what the provider said about it.
|
||||||
- Fixed errors about a provider arriving as two messages at once, the second of which spoke of several attempts that were never made. You now get the single message which names the cause.
|
- Fixed errors about a provider arriving as two messages at once, the second of which spoke of several attempts that were never made. You now get the single message which names the cause.
|
||||||
- Fixed the button in the chat toolbar that deletes the current chat and starts a new one doing so without asking. It now asks for your confirmation first, just like the chat list does, because a deleted chat cannot be brought back. The button shows a delete icon in red now, instead of one that looked like a reload.
|
- Fixed the button in the chat toolbar that deletes the current chat and starts a new one doing so without asking. It now asks for your confirmation first, just like the chat list does, because a deleted chat cannot be brought back. The button shows a delete icon in red now, instead of one that looked like a reload.
|
||||||
|
- Fixed AI Studio following your system into light or dark mode even though you had chosen a fixed color theme in the app settings.
|
||||||
|
- Fixed AI Studio keeping its previous color theme after your computer woke up from sleep, when your system had switched between light and dark mode during that time.
|
||||||
- Upgraded the Visual Briefing assistant (in preview) from the prototype to the beta state. The assistant is now completely implemented and is undergoing a deeper testing phase in preparation for release. To try it, open the app settings, allow preview features down to beta, and then enable the Visual Briefing assistant there.
|
- Upgraded the Visual Briefing assistant (in preview) from the prototype to the beta state. The assistant is now completely implemented and is undergoing a deeper testing phase in preparation for release. To try it, open the app settings, allow preview features down to beta, and then enable the Visual Briefing assistant there.
|
||||||
- Upgraded the vector database behind local RAG (Qdrant Edge) to version 0.8.0.
|
- Upgraded the vector database behind local RAG (Qdrant Edge) to version 0.8.0.
|
||||||
|
|||||||
71
app/Tests/Tools/CircuitStateServiceTests.cs
Normal file
71
app/Tests/Tools/CircuitStateServiceTests.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using AIStudio.Tools.Services;
|
||||||
|
|
||||||
|
namespace AIStudio.Tests.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks when the circuit state reports that the browser connection came back.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The report is there to fetch again what the browser sent while the connection was down, because Blazor
|
||||||
|
/// drops it. The layout reads the color theme anew on it, for instance, since the machine may have switched
|
||||||
|
/// its theme during sleep. So it has to come after every loss, and only then: missing one leaves the app
|
||||||
|
/// with stale state, while a connection which was never lost has nothing to fetch again.
|
||||||
|
/// </remarks>
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class CircuitStateServiceTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void AConnectionWhichReturnsAfterALossIsReportedOnce()
|
||||||
|
{
|
||||||
|
var circuitState = new CircuitStateService();
|
||||||
|
var numReports = 0;
|
||||||
|
circuitState.ConnectionRestored += () => numReports++;
|
||||||
|
|
||||||
|
circuitState.MarkAsDisconnected();
|
||||||
|
circuitState.MarkAsConnected();
|
||||||
|
|
||||||
|
Assert.That(numReports, Is.EqualTo(1), "The connection was lost and came back, so whatever the browser sent in between is gone.");
|
||||||
|
Assert.That(circuitState.IsConnected, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TheFirstConnectionIsNotReported()
|
||||||
|
{
|
||||||
|
var circuitState = new CircuitStateService();
|
||||||
|
var numReports = 0;
|
||||||
|
circuitState.ConnectionRestored += () => numReports++;
|
||||||
|
|
||||||
|
circuitState.MarkAsConnected();
|
||||||
|
|
||||||
|
Assert.That(numReports, Is.Zero, "A circuit starts out connected, so its first connection has not lost anything.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AConnectionWhichWasNotLostIsNotReportedAgain()
|
||||||
|
{
|
||||||
|
var circuitState = new CircuitStateService();
|
||||||
|
var numReports = 0;
|
||||||
|
circuitState.ConnectionRestored += () => numReports++;
|
||||||
|
|
||||||
|
circuitState.MarkAsDisconnected();
|
||||||
|
circuitState.MarkAsConnected();
|
||||||
|
circuitState.MarkAsConnected();
|
||||||
|
|
||||||
|
Assert.That(numReports, Is.EqualTo(1), "The second call follows a connection which was up all along.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void EveryLossIsReportedOnItsOwn()
|
||||||
|
{
|
||||||
|
var circuitState = new CircuitStateService();
|
||||||
|
var numReports = 0;
|
||||||
|
circuitState.ConnectionRestored += () => numReports++;
|
||||||
|
|
||||||
|
circuitState.MarkAsDisconnected();
|
||||||
|
circuitState.MarkAsConnected();
|
||||||
|
circuitState.MarkAsDisconnected();
|
||||||
|
circuitState.MarkAsConnected();
|
||||||
|
|
||||||
|
Assert.That(numReports, Is.EqualTo(2), "The machine went to sleep twice, and each time something may have been lost.");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user