diff --git a/app/MindWork AI Studio/App.razor b/app/MindWork AI Studio/App.razor
index 7df24793..e05a7749 100644
--- a/app/MindWork AI Studio/App.razor
+++ b/app/MindWork AI Studio/App.razor
@@ -21,6 +21,9 @@
+
+ Reconnecting to AI Studio...
+
diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs
index b3d58859..b37b729b 100644
--- a/app/MindWork AI Studio/Program.cs
+++ b/app/MindWork AI Studio/Program.cs
@@ -154,12 +154,17 @@ internal sealed class Program
// ReSharper restore AccessToDisposedClosure
builder.Services.AddRazorComponents()
- .AddInteractiveServerComponents()
+ .AddInteractiveServerComponents(options =>
+ {
+ options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromDays(30);
+ options.DisconnectedCircuitMaxRetained = 2;
+ })
.AddHubOptions(options =>
{
options.MaximumReceiveMessageSize = null;
- options.ClientTimeoutInterval = TimeSpan.FromDays(14);
+ options.ClientTimeoutInterval = TimeSpan.FromSeconds(120);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
+ options.KeepAliveInterval = TimeSpan.FromSeconds(30);
});
builder.Services.AddSingleton(new HttpClient
diff --git a/app/MindWork AI Studio/wwwroot/boot.js b/app/MindWork AI Studio/wwwroot/boot.js
index d18dd7e9..4d605ee0 100644
--- a/app/MindWork AI Studio/wwwroot/boot.js
+++ b/app/MindWork AI Studio/wwwroot/boot.js
@@ -1,33 +1,73 @@
(() => {
- const maximumRetryCount = 3;
- const retryIntervalMilliseconds = 500;
+ const maximumRetryCount = 12;
const reconnectModal = document.getElementById('reconnect-modal');
+ const retryDelaysMilliseconds = [
+ 0,
+ 1_000,
+ 2_000,
+ 5_000,
+ 10_000,
+ 15_000,
+ 30_000,
+ ];
+
+ let currentReconnectionProcess = null;
+ let isConnectionDown = false;
+
+ const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
+
+ const getRetryDelayMilliseconds = attempt => retryDelaysMilliseconds[Math.min(attempt, retryDelaysMilliseconds.length - 1)];
+
+ const showReconnectModal = () => {
+ if (reconnectModal)
+ reconnectModal.style.display = 'flex';
+ };
+
+ const hideReconnectModal = () => {
+ if (reconnectModal)
+ reconnectModal.style.display = 'none';
+ };
+
+ const setReconnectModalText = text => {
+ if (reconnectModal)
+ reconnectModal.textContent = text;
+ };
const startReconnectionProcess = () => {
- reconnectModal.style.display = 'block';
+ showReconnectModal();
let isCanceled = false;
+ let forceAttempt = false;
- (async () => {
- for (let i = 0; i < maximumRetryCount; i++) {
- reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;
+ const waitForNextAttempt = async milliseconds => {
+ const startedAt = Date.now();
+ while (!isCanceled && !forceAttempt && Date.now() - startedAt < milliseconds)
+ await delay(250);
- await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));
+ forceAttempt = false;
+ };
- if (isCanceled) {
+ void (async () => {
+ for (let attempt = 0; attempt < maximumRetryCount && !isCanceled; attempt++) {
+ setReconnectModalText(`Reconnecting to AI Studio (${attempt + 1}/${maximumRetryCount})...`);
+
+ const retryDelayMilliseconds = getRetryDelayMilliseconds(attempt);
+ if (retryDelayMilliseconds > 0)
+ await waitForNextAttempt(retryDelayMilliseconds);
+
+ if (isCanceled)
return;
- }
try {
const result = await Blazor.reconnect();
- if (!result) {
+ if (result === false) {
// The server was reached, but the connection was rejected; reload the page.
location.reload();
return;
}
- // Successfully reconnected to the server.
- return;
+ if (result === true)
+ return;
} catch {
// Didn't reach the server; try again.
}
@@ -40,25 +80,42 @@
return {
cancel: () => {
isCanceled = true;
- reconnectModal.style.display = 'none';
+ hideReconnectModal();
+ },
+ triggerImmediateAttempt: () => {
+ forceAttempt = true;
},
};
};
- let currentReconnectionProcess = null;
+ const triggerReconnectAfterWake = () => {
+ if (isConnectionDown)
+ currentReconnectionProcess?.triggerImmediateAttempt();
+ };
+
+ document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'visible')
+ triggerReconnectAfterWake();
+ });
+
+ globalThis.addEventListener('pageshow', triggerReconnectAfterWake);
Blazor.start({
circuit: {
reconnectionHandler: {
- onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
+ onConnectionDown: () => {
+ isConnectionDown = true;
+ currentReconnectionProcess ??= startReconnectionProcess();
+ },
onConnectionUp: () => {
+ isConnectionDown = false;
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
},
configureSignalR: function (builder) {
- builder.withServerTimeout(1_200_000);
+ builder.withServerTimeout(120_000);
builder.withKeepAliveInterval(30_000);
},
}
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md b/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md
index 39b67be4..24e7b9e3 100644
--- a/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md
+++ b/app/MindWork AI Studio/wwwroot/changelog/v26.7.3.md
@@ -1,5 +1,6 @@
# v26.7.3, build 245 (2026-07-xx xx:xx UTC)
- Improved the "My Tasks Assistant": you can now provide one or more documents in addition to text or use documents alone when asking to identify tasks.
+- Fixed an issue that could leave AI Studio unresponsive after waking the computer from sleep.
- Fixed enterprise configuration plugins from Windows-created ZIP files not loading correctly on Linux when the ZIP contained plugin files inside a folder.
- Upgraded Rust to v1.97.0.
- Upgraded Tauri to v2.11.5.