Files
olivv-cs-ComfyUI-FunPack/app/shell/logwindow.js
T
digital-garbage e6e4dfa882 app: mount every Settings section in place instead of deep-linking out
Picking Node packs, Updates, the log or Temp files used to close the
Settings window and open that feature's own separate modal, with no way
back except reopening Settings from the menu bar -- not what v4 does,
and not what "unified" is supposed to mean. Each of those five files
(plus pipeline_window.js) now exports mount({ setFooter, close }), the
same content its standalone open() already built, taking the modal
chrome's footer and close from whoever hosts it instead of owning a
modal directly. open() is unchanged for anyone opening one alone --
pipeline_window.test.js and every Playwright spec for these five prove
it, unedited, still passing.

Also fixes two findings from the adversarial pass on the earlier
deep-link version: filterList's search only matched a section's visible
label/hint, so a section findable only by a keyword (e.g. "loaders" for
"Models and pipeline") silently vanished from the filtered list; and
createSettingsWindow's "one at a time" state lived at module scope, so
a second independent instance would have redrawn the first one's modal
instead of opening its own. Both now have regression tests.
2026-09-07 01:06:02 +03:00

94 lines
3.5 KiB
JavaScript

// ComfyUI's log, in the app.
//
// Users do not open devtools. "It did not work" with nothing to read is where a
// bug report dies, and the one place the reason is written down is a terminal
// they may not even be able to see -- a rented box behind a tunnel has no
// terminal on this desk at all.
//
// It polls only while open, and it stops polling while you are selecting text:
// a panel that redraws under a drag makes copying a stack trace impossible,
// which is the single thing anybody opens a log to do.
import { composer } from "../composer/composer.js";
const EVERY = 2000;
/** mount({ poll, setFooter, close }) -> { node, destroy }. See tempfiles.js's
* mount() for why setFooter/close are the only things asked for -- `destroy`
* additionally stops the poll, since this one runs a timer nothing else does. */
export function mount({ poll = EVERY, setFooter = () => {}, close = () => {} } = {}) {
let timer = null;
let follow = true; // stick to the end until the user scrolls off it
let last = "";
const view = composer.code.block({ text: "Reading…" });
const body = composer.region.stack({ gap: "sm", fill: true, children: [view] });
const selecting = () => {
const sel = document.getSelection();
return sel && !sel.isCollapsed && view.node.contains(sel.anchorNode);
};
// Following the end is the default and stops being it the moment someone
// scrolls up -- otherwise reading anything older than two seconds is a fight.
view.node.addEventListener("scroll", () => {
const room = view.node.scrollHeight - view.node.scrollTop - view.node.clientHeight;
follow = room < 40;
});
async function refresh() {
if (selecting()) return; // never redraw under a drag
try {
const res = await fetch("/funpack/api/log?limit=600", { cache: "no-store" });
const payload = await res.json().catch(() => ({}));
const text = (payload.lines || []).join("\n")
|| payload.detail
|| "Nothing in the log yet.";
if (text !== last) {
last = text;
view.setText(text);
if (follow) view.node.scrollTop = view.node.scrollHeight;
}
setFooter({ note: payload.path || "", actions: footer() });
} catch (err) {
// The server being gone is the most interesting thing a log can say, so it
// is said -- and the lines already on screen are kept, because they are
// the ones from just before it went.
setFooter({ note: `Cannot reach ComfyUI: ${err.message}`, actions: footer() });
}
}
const footer = () => [
composer.button.md({
label: "Copy", onClick: () => {
// Selecting a thousand lines by hand is the other reason this is hard to
// get out of a log panel.
if (navigator.clipboard) navigator.clipboard.writeText(last).catch(() => {});
},
}),
composer.button.md({ label: "Close", tone: "primary", onClick: () => close() }),
];
setFooter({ actions: footer() });
refresh();
timer = setInterval(refresh, poll);
return { node: body.node, destroy: () => { clearInterval(timer); timer = null; body.destroy(); } };
}
export function open({ poll = EVERY } = {}) {
let window_ = null;
const content = mount({
poll,
setFooter: (f) => window_ && window_.setFooter(f),
close: () => window_ && window_.close("done"),
});
window_ = composer.modal.generic({
title: "ComfyUI log",
subtitle: "What the server printed. Newest at the bottom.",
size: "xl",
body: content,
onClose: () => { content.destroy(); window_ = null; },
});
return window_;
}