Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BzJLJWVu7RZnbRqm3nYAnX
99 lines
4.1 KiB
JavaScript
99 lines
4.1 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({ dom, poll = EVERY, setFooter = () => {}, close = () => {} }) {
|
|
let timer = null;
|
|
let follow = true; // stick to the end until the user scrolls off it
|
|
let last = "";
|
|
let previous = false; // the run before this one, from disk: what a restart took off the screen
|
|
let copyText = ""; // what Copy gives: the lines, never the colour bookkeeping
|
|
|
|
const view = composer.code.block({ text: "Reading…" });
|
|
const body = composer.region.stack({ gap: "sm", fill: true, children: [view] });
|
|
|
|
const selecting = () => dom.selectingIn(view.node);
|
|
|
|
// 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(`/fun/api/log?limit=600${previous ? "&previous=1" : ""}`, { cache: "no-store" });
|
|
const payload = await res.json().catch(() => ({}));
|
|
const lines = payload.lines || [];
|
|
const text = lines.join("\n") || payload.detail || "Nothing in the log yet.";
|
|
const key = text + "\u0000" + (payload.tones || []).join(",");
|
|
if (key !== last) {
|
|
last = key;
|
|
copyText = text;
|
|
if (lines.length) view.setLines(lines, payload.tones || []);
|
|
else 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: previous ? "This run" : "Previous run", tone: "ghost", title: "The server's console from before its last start, kept on disk",
|
|
onClick: () => { previous = !previous; last = ""; follow = true; refresh(); },
|
|
}),
|
|
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(copyText).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({ dom, poll = EVERY }) {
|
|
let window_ = null;
|
|
const content = mount({
|
|
dom, 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_;
|
|
}
|