diff --git a/.eslintrc.json b/.eslintrc.json
index 62feb13a5..2dddb41a1 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -42,9 +42,13 @@
"globals": {
// asssets
"panzoom": "readonly",
- // script.js
+ // logger.js
"log": "readonly",
"debug": "readonly",
+ "error": "readonly",
+ "xhrGet": "readonly",
+ "xhrPost": "readonly",
+ // script.js
"gradioApp": "readonly",
"executeCallbacks": "readonly",
"onAfterUiUpdate": "readonly",
@@ -87,7 +91,6 @@
// settings.js
"registerDragDrop": "readonly",
// extraNetworks.js
- "requestGet": "readonly",
"getENActiveTab": "readonly",
"quickApplyStyle": "readonly",
"quickSaveStyle": "readonly",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0dabf0d7c..4a4a0a416 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@
- Flux: do not recast quants
- Sampler improvements
- update DPM FlowMatch samplers
+- UI:
+ - browser->server logging
- Fixes:
- update `diffusers`
- fix README links
diff --git a/installer.py b/installer.py
index 0b64c3616..8f552526a 100644
--- a/installer.py
+++ b/installer.py
@@ -459,7 +459,7 @@ def check_python(supported_minors=[9, 10, 11, 12], reason=None):
def check_diffusers():
if args.skip_all or args.skip_requirements:
return
- sha = 'b5fd6f13f5434d69d919cc8cedf0b11db664cf06'
+ sha = '7ac6e286ee994270e737b70c904ea50049d53567'
pkg = pkg_resources.working_set.by_key.get('diffusers', None)
minor = int(pkg.version.split('.')[1] if pkg is not None else 0)
cur = opts.get('diffusers_version', '') if minor > 0 else ''
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index 77fe125f3..622e40faf 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -3,19 +3,6 @@ let sortVal = -1;
// helpers
-const requestGet = (url, data, handler) => {
- const xhr = new XMLHttpRequest();
- const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
- xhr.open('GET', `${url}?${args}`, true);
- xhr.onreadystatechange = () => {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) handler(JSON.parse(xhr.responseText));
- else console.error(`Request: url=${url} status=${xhr.status} err`);
- }
- };
- xhr.send(JSON.stringify(data));
-};
-
const getENActiveTab = () => {
let tabName = '';
if (gradioApp().getElementById('tab_txt2img').style.display === 'block') tabName = 'txt2img';
@@ -98,7 +85,7 @@ function readCardTags(el, tags) {
}
function readCardDescription(page, item) {
- requestGet('/sd_extra_networks/description', { page, item }, (data) => {
+ xhrGet('/sd_extra_networks/description', { page, item }, (data) => {
const tabname = getENActiveTab();
const description = gradioApp().querySelector(`#${tabname}_description > label > textarea`);
description.value = data?.description?.trim() || '';
diff --git a/javascript/gallery.js b/javascript/gallery.js
index 1f3afd148..05e594e4c 100644
--- a/javascript/gallery.js
+++ b/javascript/gallery.js
@@ -94,14 +94,14 @@ async function delayFetchThumb(fn) {
outstanding++;
const res = await fetch(`/sdapi/v1/browser/thumb?file=${encodeURI(fn)}`, { priority: 'low' });
if (!res.ok) {
- console.error(res.statusText);
+ error(`fetchThumb: ${res.statusText}`);
outstanding--;
return undefined;
}
const json = await res.json();
outstanding--;
if (!res || !json || json.error || Object.keys(json).length === 0) {
- if (json.error) console.error(json.error);
+ if (json.error) error(`fetchThumb: ${json.error}`);
return undefined;
}
return json;
diff --git a/javascript/loader.js b/javascript/loader.js
index f3c7fe60f..8cd4811bf 100644
--- a/javascript/loader.js
+++ b/javascript/loader.js
@@ -20,7 +20,7 @@ async function preloadImages() {
try {
await Promise.all(imagePromises);
} catch (error) {
- console.error('Error preloading images:', error);
+ error(`preloadImages: ${error}`);
}
}
@@ -43,14 +43,16 @@ async function createSplash() {
const motdEl = document.getElementById('motd');
if (motdEl) motdEl.innerHTML = text.replace(/["]+/g, '');
})
- .catch((err) => console.error('getMOTD:', err));
+ .catch((err) => error(`getMOTD: ${err}`));
}
async function removeSplash() {
const splash = document.getElementById('splash');
if (splash) splash.remove();
log('removeSplash');
- log('startupTime', Math.round(performance.now() - appStartTime) / 1000);
+ const t = Math.round(performance.now() - appStartTime) / 1000;
+ log('startupTime', t);
+ xhrPost('/sdapi/v1/log', { message: `ready time=${t}` });
}
window.onload = createSplash;
diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js
index e4fe99a7f..9b915e6da 100644
--- a/javascript/logMonitor.js
+++ b/javascript/logMonitor.js
@@ -2,6 +2,7 @@ let logMonitorEl = null;
let logMonitorStatus = true;
let logWarnings = 0;
let logErrors = 0;
+let logConnected = false;
function dateToStr(ts) {
const dt = new Date(1000 * ts);
@@ -29,8 +30,7 @@ async function logMonitor() {
row.innerHTML = `
${dateToStr(l.created)} | ${level}${l.facility} | ${module}${l.msg} | `;
logMonitorEl.appendChild(row);
} catch (e) {
- // console.log('logMonitor', e);
- console.error('logMonitor line', line);
+ error(`logMonitor: ${line}`);
}
};
@@ -46,6 +46,7 @@ async function logMonitor() {
if (logMonitorStatus) setTimeout(logMonitor, opts.logmonitor_refresh_period);
else setTimeout(logMonitor, 10 * 1000); // on failure try to reconnect every 10sec
+
if (!opts.logmonitor_show) return;
logMonitorStatus = false;
if (!logMonitorEl) {
@@ -64,14 +65,20 @@ async function logMonitor() {
const lines = await res.json();
if (logMonitorEl && lines?.length > 0) logMonitorEl.parentElement.parentElement.style.display = opts.logmonitor_show ? 'block' : 'none';
for (const line of lines) addLogLine(line);
+ if (!logConnected) {
+ logConnected = true;
+ xhrPost('/sdapi/v1/log', { debug: 'connected' });
+ }
} else {
- addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: ${res?.status} ${res?.statusText}" }`);
+ logConnected = false;
logErrors++;
+ addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: ${res?.status} ${res?.statusText}" }`);
}
cleanupLog(atBottom);
} catch (err) {
- addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: server unreachable" }`);
+ logConnected = false;
logErrors++;
+ addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: server unreachable" }`);
cleanupLog(atBottom);
}
}
diff --git a/javascript/logger.js b/javascript/logger.js
new file mode 100644
index 000000000..4ff1fb822
--- /dev/null
+++ b/javascript/logger.js
@@ -0,0 +1,67 @@
+const serverTimeout = 5000;
+
+const log = async (...msg) => {
+ const dt = new Date();
+ const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
+ if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
+ console.log(ts, ...msg); // eslint-disable-line no-console
+};
+
+const debug = async (...msg) => {
+ const dt = new Date();
+ const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
+ if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
+ console.debug(ts, ...msg); // eslint-disable-line no-console
+};
+
+const error = async (...msg) => {
+ const dt = new Date();
+ const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
+ if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
+ console.error(ts, ...msg); // eslint-disable-line no-console
+ xhrPost('/sdapi/v1/log', { error: msg.join(' ') }); // eslint-disable-line no-use-before-define
+};
+
+const xhrInternal = (xhrObj, data, handler = undefined, errorHandler = undefined, ignore = false) => {
+ const err = (msg) => {
+ if (!ignore) {
+ error(`${msg}: state=${xhrObj.readyState} status=${xhrObj.status} response=${xhrObj.responseText}`);
+ if (errorHandler) errorHandler();
+ }
+ };
+
+ xhrObj.setRequestHeader('Content-Type', 'application/json');
+ xhrObj.timeout = serverTimeout;
+ xhrObj.ontimeout = () => err('xhr.ontimeout');
+ xhrObj.onerror = () => err('xhr.onerror');
+ xhrObj.onabort = () => err('xhr.onabort');
+ xhrObj.onreadystatechange = () => {
+ if (xhrObj.readyState === 4) {
+ if (xhrObj.status === 200) {
+ try {
+ const json = JSON.parse(xhrObj.responseText);
+ if (handler) handler(json);
+ } catch (e) {
+ error(`xhr.onreadystatechange: ${e}`);
+ }
+ } else {
+ err(`xhr.onreadystatechange: state=${xhrObj.readyState} status=${xhrObj.status} response=${xhrObj.responseText}`);
+ }
+ }
+ };
+ const req = JSON.stringify(data);
+ xhrObj.send(req);
+};
+
+const xhrGet = (url, data, handler = undefined, errorHandler = undefined, ignore = false) => {
+ const xhr = new XMLHttpRequest();
+ const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
+ xhr.open('GET', `${url}?${args}`, true);
+ xhrInternal(xhr, data, handler, errorHandler, ignore);
+};
+
+function xhrPost(url, data, handler = undefined, errorHandler = undefined, ignore = false) {
+ const xhr = new XMLHttpRequest();
+ xhr.open('POST', url, true);
+ xhrInternal(xhr, data, handler, errorHandler, ignore);
+}
diff --git a/javascript/progressBar.js b/javascript/progressBar.js
index 3f954e13b..9d897bc87 100644
--- a/javascript/progressBar.js
+++ b/javascript/progressBar.js
@@ -1,42 +1,5 @@
let lastState = {};
-function request(url, data, handler, errorHandler) {
- const xhr = new XMLHttpRequest();
- xhr.open('POST', url, true);
- xhr.setRequestHeader('Content-Type', 'application/json');
- xhr.timeout = 5000;
- xhr.ontimeout = () => {
- console.error('xhr.ontimeout', xhr);
- errorHandler();
- };
- xhr.onerror = () => {
- console.error('xhr.onerror', xhr);
- errorHandler();
- };
- xhr.onabort = () => {
- console.error('xhr.onabort', xhr);
- errorHandler();
- };
- xhr.onreadystatechange = () => {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- try {
- const json = JSON.parse(xhr.responseText);
- handler(json);
- } catch (err) {
- console.error('xhr.onreadystatechange', xhr, err);
- errorHandler();
- }
- } else {
- console.error('xhr.onreadystatechange', xhr);
- errorHandler();
- }
- }
- };
- const req = JSON.stringify(data);
- xhr.send(req);
-}
-
function pad2(x) {
return x < 10 ? `0${x}` : x;
}
@@ -151,11 +114,11 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres
};
const onProgressErrorHandler = (err) => {
- console.error('onProgressError', err);
+ error(`onProgressError: ${err}`);
done();
};
- request('./internal/progress', { id_task, id_live_preview }, onProgressHandler, onProgressErrorHandler);
+ xhrPost('./internal/progress', { id_task, id_live_preview }, onProgressHandler, onProgressErrorHandler);
};
start(id_task, 0);
}
diff --git a/javascript/script.js b/javascript/script.js
index 104567dd7..250e90ba2 100644
--- a/javascript/script.js
+++ b/javascript/script.js
@@ -1,17 +1,3 @@
-const log = (...msg) => {
- const dt = new Date();
- const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
- if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
- console.log(ts, ...msg); // eslint-disable-line no-console
-};
-
-const debug = (...msg) => {
- const dt = new Date();
- const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
- if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
- console.debug(ts, ...msg); // eslint-disable-line no-console
-};
-
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); // eslint-disable-line no-promise-executor-return
}
@@ -82,7 +68,7 @@ function executeCallbacks(queue, arg) {
try {
callback(arg);
} catch (e) {
- console.error('error running callback', callback, ':', e);
+ error(`executeCallbacks: ${callback} ${e}`);
}
}
}
diff --git a/javascript/ui.js b/javascript/ui.js
index 8808f1c8b..81d1c67e4 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -28,7 +28,7 @@ function clip_gallery_urls(gallery) {
const files = gallery.map((v) => v.data);
navigator.clipboard.writeText(JSON.stringify(files)).then(
() => log('clipboard:', files),
- (err) => console.error('clipboard:', files, err),
+ (err) => error(`clipboard: ${files} ${err}`),
);
}
@@ -493,9 +493,9 @@ function previewTheme() {
el.src = `/file=html/${name}.jpg`;
}
})
- .catch((e) => console.error('previewTheme:', e));
+ .catch((e) => error(`previewTheme: ${e}`));
})
- .catch((e) => console.error('previewTheme:', e));
+ .catch((e) => error(`previewTheme: ${e}`));
}
async function browseFolder() {
diff --git a/modules/api/api.py b/modules/api/api.py
index f8346995d..d48cbf521 100644
--- a/modules/api/api.py
+++ b/modules/api/api.py
@@ -35,7 +35,8 @@ class Api:
# server api
self.add_api_route("/sdapi/v1/motd", server.get_motd, methods=["GET"], response_model=str)
- self.add_api_route("/sdapi/v1/log", server.get_log_buffer, methods=["GET"], response_model=List[str])
+ self.add_api_route("/sdapi/v1/log", server.get_log, methods=["GET"], response_model=List[str])
+ self.add_api_route("/sdapi/v1/log", server.post_log, methods=["POST"])
self.add_api_route("/sdapi/v1/start", self.get_session_start, methods=["GET"])
self.add_api_route("/sdapi/v1/version", server.get_version, methods=["GET"])
self.add_api_route("/sdapi/v1/status", server.get_status, methods=["GET"], response_model=models.ResStatus)
diff --git a/modules/api/models.py b/modules/api/models.py
index e68ebf081..39bcbe383 100644
--- a/modules/api/models.py
+++ b/modules/api/models.py
@@ -286,10 +286,16 @@ class ResImageInfo(BaseModel):
items: dict = Field(title="Items", description="A dictionary containing all the other fields the image had")
parameters: dict = Field(title="Parameters", description="A dictionary with parsed generation info fields")
-class ReqLog(BaseModel):
+class ReqGetLog(BaseModel):
lines: int = Field(default=100, title="Lines", description="How many lines to return")
clear: bool = Field(default=False, title="Clear", description="Should the log be cleared after returning the lines?")
+
+class ReqPostLog(BaseModel):
+ message: Optional[str] = Field(title="Message", description="The info message to log")
+ debug: Optional[str] = Field(title="Debug message", description="The debug message to log")
+ error: Optional[str] = Field(title="Error message", description="The error message to log")
+
class ReqProgress(BaseModel):
skip_current_image: bool = Field(default=False, title="Skip current image", description="Skip current image serialization")
diff --git a/modules/api/server.py b/modules/api/server.py
index 939e19c86..dabbe634c 100644
--- a/modules/api/server.py
+++ b/modules/api/server.py
@@ -37,12 +37,22 @@ def get_platform():
from modules.loader import get_packages as loader_get_packages
return { **installer_get_platform(), **loader_get_packages() }
-def get_log_buffer(req: models.ReqLog = Depends()):
+def get_log(req: models.ReqGetLog = Depends()):
lines = shared.log.buffer[:req.lines] if req.lines > 0 else shared.log.buffer.copy()
if req.clear:
shared.log.buffer.clear()
return lines
+def post_log(req: models.ReqPostLog):
+ if req.message is not None:
+ shared.log.info(f'UI: {req.message}')
+ if req.debug is not None:
+ shared.log.debug(f'UI: {req.debug}')
+ if req.error is not None:
+ shared.log.error(f'UI: {req.error}')
+ return {}
+
+
def get_config():
options = {}
for k in shared.opts.data.keys():