diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3cd53b1ab..e0de868b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,12 @@
# Change Log for SD.Next
-## Update for 2026-02-21
+## Update for 2026-02-22
-### Highlights for 2026-02-21
+### Highlights for 2026-02-22
TBD
-### Details for 2026-02-21
+### Details for 2026-02-22
- **Models**
- [FireRed Image Edit](https://huggingface.co/FireRedTeam/FireRed-Image-Edit-1.0)
@@ -88,6 +88,7 @@ TBD
- gallery over remote/unsecure connections
- ltx2-i2v
- handle missing preview image
+ - ui connection monitor
## Update for 2026-02-04
diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js
index ee3bf9369..70472ced0 100644
--- a/javascript/logMonitor.js
+++ b/javascript/logMonitor.js
@@ -82,7 +82,6 @@ async function logMonitor() {
for (const line of lines) addLogLine(line);
if (!logConnected) {
logConnected = true;
- monitorConnection();
xhrPost(`${window.api}/log`, { debug: 'connected' });
}
} else {
diff --git a/javascript/monitor.js b/javascript/monitor.js
index abc3c38ab..c2905377b 100644
--- a/javascript/monitor.js
+++ b/javascript/monitor.js
@@ -3,6 +3,8 @@ class ConnectionMonitorState {
static version = '';
static commit = '';
static branch = '';
+ static model = '';
+ static startup = '';
static online = false;
static getModel() {
@@ -11,66 +13,85 @@ class ConnectionMonitorState {
}
static trimModelName(name) {
- // remove trailing [hash], split on / or \, return last segment, trim
return name.replace(/\s*\[.*\]\s*$/, '').split(/[\\/]/).pop().trim() || 'unknown model';
}
- static setData({ online, updated, commit, branch }) {
+ static setData({ online, data }) {
this.online = online;
- this.version = updated;
- this.commit = commit;
- this.branch = branch;
+ if (data?.version) this.version = data.version;
+ if (data?.commit) this.commit = data.commit;
+ if (data?.branch) this.branch = data.branch;
+ if (data?.model) this.model = this.trimModelName(data.model);
}
- static setElement(el) {
- this.element = el;
- }
-
- static toHTML(modelOverride) {
+ static toHTML() {
+ if (!this.model) this.model = this.getModel();
return `
Version: ${this.version}
Commit: ${this.commit}
Branch: ${this.branch}
Status: ${this.online ? 'online' : 'offline'}
- Model: ${modelOverride ? this.trimModelName(modelOverride) : this.getModel()}
- Since: ${new Date().toLocaleString()}
+ Model: ${this.model}
+ Since: ${this.startup.toLocaleString()}
`;
}
- static updateState(incomingModel) {
- this.element.dataset.hint = this.toHTML(incomingModel);
+ static updateState() {
+ if (!this.element) {
+ const el = document.getElementById('logo_nav');
+ if (el) this.element = el;
+ else return;
+ }
+ this.element.dataset.hint = this.toHTML();
this.element.style.backgroundColor = this.online ? 'var(--sd-main-accent-color)' : 'var(--color-error)';
}
}
-let monitorAutoUpdating = false;
-
-async function updateIndicator(online, data, msg) {
- const el = document.getElementById('logo_nav');
- if (!el || !data) return;
- ConnectionMonitorState.setElement(el);
- if (!monitorAutoUpdating) {
- monitorOption('sd_model_checkpoint', (newVal) => { ConnectionMonitorState.updateState(newVal); }); // Runs before opt actually changes
- monitorAutoUpdating = true;
- }
- ConnectionMonitorState.setData({ online, ...data });
+async function updateIndicator(online, data = {}, msg = undefined) {
+ ConnectionMonitorState.setData({ online, data });
ConnectionMonitorState.updateState();
- if (online) {
- log('monitorConnection: online', data);
- } else {
- log('monitorConnection: offline', msg);
+ if (msg) log('monitorConnection:', { online, data, msg });
+}
+
+async function wsMonitorLoop(url) {
+ try {
+ const ws = new WebSocket(`${url}/queue/join`);
+ ws.onopen = () => {};
+ ws.onmessage = (evt) => updateIndicator(true);
+ ws.onclose = () => setTimeout(() => wsMonitorLoop(url), 10000);
+ ws.onerror = (e) => {
+ updateIndicator(false, {}, e.message);
+ setTimeout(() => monitorConnection(url), 10000); // eslint-disable-line no-use-before-define
+ };
+ } catch (e) {
+ updateIndicator(false, {}, e.message);
+ setTimeout(() => monitorConnection(url), 10000); // eslint-disable-line no-use-before-define
}
}
+let monitorActive = false;
+
async function monitorConnection() {
+ if (!monitorActive) { // start monitor loop only once on startup
+ monitorActive = true;
+ monitorOption('sd_model_checkpoint', (newVal) => { // runs before opt actually changes
+ ConnectionMonitorState.model = newVal;
+ ConnectionMonitorState.updateState();
+ });
+ }
+ ConnectionMonitorState.startup = new Date();
+
+ let data = {};
try {
const res = await authFetch(`${window.api}/version`);
- const data = await res.json();
- const url = res.url.split('/sdapi')[0].replace('http', 'ws'); // update global url as ws need fqdn
- const ws = new WebSocket(`${url}/queue/join`);
- ws.onopen = () => updateIndicator(true, data, '');
- ws.onclose = () => updateIndicator(false, data, '');
- ws.onerror = (e) => updateIndicator(false, data, e.message);
- ws.onmessage = (evt) => log('monitorConnection: message', evt.data);
- } catch { /**/ }
+ data = await res.json();
+ log('monitorConnection:', { data });
+ ConnectionMonitorState.startup = new Date();
+ updateIndicator(true, data);
+ const url = res.url.split('/sdapi')[0].replace('https:', 'wss:').replace('http:', 'ws:'); // update global url as ws need fqdn
+ wsMonitorLoop(url);
+ } catch {
+ monitorConnection(false, data);
+ setTimeout(monitorConnection, 10000);
+ }
}
diff --git a/javascript/startup.js b/javascript/startup.js
index 8f512572b..5f6b3f926 100644
--- a/javascript/startup.js
+++ b/javascript/startup.js
@@ -30,22 +30,21 @@ async function initStartup() {
if (window.setupLogger) await setupLogger();
// all items here are non-blocking async calls
- await initModels();
- await getUIDefaults();
- await initPromptChecker();
- await initContextMenu();
- await initDragDrop();
- await initAccordions();
- await initSettings();
- await initImageViewer();
- await initiGenerationParams();
- await initChangelog();
- await setupControlUI();
+ initModels();
+ getUIDefaults();
+ initPromptChecker();
+ initContextMenu();
+ initDragDrop();
+ initAccordions();
+ initSettings();
+ initImageViewer();
+ initiGenerationParams();
+ initChangelog();
+ setupControlUI();
// reconnect server session
await reconnectUI();
await waitForOpts();
-
await initGallery();
log('mountURL', window.opts.subpath);
@@ -60,6 +59,7 @@ async function initStartup() {
// optinally wait for modern ui
if (window.waitForUiReady) await waitForUiReady();
+ monitorConnection();
removeSplash();
// post startup tasks that may take longer but are not critical
diff --git a/javascript/ui.js b/javascript/ui.js
index 27f159385..e76d55611 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -630,5 +630,4 @@ async function reconnectUI() {
const sd_model_observer = new MutationObserver(sd_model_callback);
sd_model_observer.observe(sd_model, { attributes: true, childList: true, subtree: true });
log('reconnectUI');
- monitorConnection();
}