Revert "Fix stale generate input and live preview visibility."

This reverts commit 1a259bd56a941aedf722fa03dfaaa833f92adb08.
This commit is contained in:
digital-garbage
2026-06-13 02:40:31 +03:00
parent 4647cd87b2
commit dc21ea4235
8 changed files with 87 additions and 182 deletions
+9 -4
View File
@@ -23,7 +23,6 @@ import json
from typing import Any, Optional
from . import config
from . import live_preview
from . import pipeline_wiring
from .nodes import WIDGET_PRIMITIVES, connection_inputs, node_outputs, _combo_default
@@ -203,20 +202,26 @@ def _wire_live_preview_vae(
slot_def: dict,
report: dict,
) -> None:
"""Wire preview VAE slot -> Chain Sampler preview_vae when configured."""
"""Wire models.live_preview slot VAE output -> Chain Sampler preview_vae."""
lp = (models_config or {}).get("live_preview") or {}
if not lp.get("enabled"):
return
if "sampler" not in graph:
return
existing = graph["sampler"].get("inputs", {}).get("preview_vae")
if isinstance(existing, list):
return
sid, out_name = live_preview.preview_vae_wire(models_config)
sid = lp.get("slot_id")
if not sid:
report["unsatisfied"].append(
"Live preview is enabled but no preview VAE is selected (View → Choose preview VAE…)."
)
return
nid = slot_node_id.get(sid)
if not nid or nid not in graph:
report["unsatisfied"].append(f"Live preview VAE slot '{sid}' is not in the graph.")
return
out_name = str(out_name or "VAE")
out_name = str(lp.get("vae_output") or "VAE")
nd = slot_def.get(sid) or {}
outs = node_outputs(nd)
oidx = _output_index(nd, out_name)
+6 -29
View File
@@ -95,12 +95,6 @@ def preview_vae_wire(models: Any) -> tuple[Optional[str], Optional[str]]:
for out_name, raw in (slot.get("wires") or {}).items():
if PREVIEW_VAE_WIRE in _wire_targets(raw):
return str(sid), str(out_name or "VAE")
co = (models.get("core_overrides") or {}).get("sampler") or {}
src = str(co.get("preview_vae") or "")
if src.startswith("out:"):
parts = src.split(":", 2)
if len(parts) >= 3 and parts[1]:
return str(parts[1]), str(parts[2] or "VAE")
lp = models.get("live_preview") or {}
sid = lp.get("slot_id")
if sid:
@@ -109,27 +103,10 @@ def preview_vae_wire(models: Any) -> tuple[Optional[str], Optional[str]]:
def models_enabled(models: Any) -> bool:
if not isinstance(models, dict):
return False
sid, _ = preview_vae_wire(models)
return bool(sid)
def api_payload(ui_prefix: str = "/funpack/movie") -> dict[str, Any]:
"""Status dict for /api/live-preview and /api/progress."""
import os
snap = snapshot()
if not snap.get("active"):
return {"active": False}
frame_path = snap.get("frame_path") or ""
ready = bool(frame_path and os.path.isfile(frame_path))
payload: dict[str, Any] = {
"active": True,
"ready": ready,
"scene_index": snap.get("scene_index", -1),
"scene_count": snap.get("scene_count", 0),
"updated_at": snap.get("updated_at", 0),
}
if ready:
ts = int(float(snap.get("updated_at") or 0) * 1000)
payload["url"] = f"{ui_prefix}/api/live-preview/frame?t={ts}"
return payload
if sid:
return True
lp = models.get("live_preview") or {}
return bool(lp.get("enabled") and lp.get("slot_id"))
+2 -1
View File
@@ -920,7 +920,8 @@
if (_editing) return;
const k = t.dataset?.k;
if (k === "global-prompt") {
S.flushPendingEdits().then(() => render(S.get()));
S.flushSave();
render(S.get());
return;
}
if (!S.flushSave()) render(S.get());
+16 -38
View File
@@ -877,36 +877,7 @@
let _tcEl = null; // timecode span (updated live, not rebuilt)
let _playBtnEl = null; // play/pause button (glyph updated live, not rebuilt)
let _genMsgEl = null, _genBarEl = null, _genFillEl = null; // gen readout, updated live
let _livePreviewEl = null, _livePreviewBadgeEl = null, _livePreviewLayerEl = null;
function _livePreviewConfigured(st) {
const m = st?.models || {};
const lp = m.live_preview || {};
if (lp.enabled || lp.slot_id) return true;
if ((m.core_overrides?.sampler?.preview_vae || "").startsWith("out:")) return true;
const port = "port:FunPackLTXAVSceneChainSampler.preview_vae";
return (m.slots || []).some((s) =>
Object.values(s.wires || {}).some((raw) => {
const arr = Array.isArray(raw) ? raw : raw ? [raw] : [];
return arr.includes(port);
}));
}
function _ensureLivePreviewDom() {
if (_livePreviewLayerEl && _livePreviewEl) return;
const canvas = document.querySelector(".pm-canvas");
if (!canvas) return;
const lpLayer = el("div", "pm-live-preview");
_livePreviewEl = el("img", "pm-live-preview-img");
_livePreviewEl.alt = "Live preview";
_livePreviewEl.draggable = false;
_livePreviewEl.style.display = "none";
_livePreviewBadgeEl = el("div", "pm-live-preview-badge");
_livePreviewBadgeEl.style.display = "none";
lpLayer.append(_livePreviewEl, _livePreviewBadgeEl);
canvas.append(lpLayer);
_livePreviewLayerEl = lpLayer;
}
let _livePreviewEl = null, _livePreviewBadgeEl = null;
// Update the generation readout in place on progress ticks (no store re-render, so the
// editor stays interactive — frames can be saved, effects added — while a job runs).
@@ -919,11 +890,9 @@
_genFillEl.style.width = Math.min(100, Math.round((g.step / g.maxStep) * 100)) + "%";
}
}
if (g.livePreviewUrl) {
_ensureLivePreviewDom();
if (_livePreviewLayerEl) _livePreviewLayerEl.style.display = "";
if (_livePreviewEl && _livePreviewEl.src !== g.livePreviewUrl) _livePreviewEl.src = g.livePreviewUrl;
if (_livePreviewEl) _livePreviewEl.style.display = "";
if (_livePreviewEl && g.livePreviewUrl) {
if (_livePreviewEl.src !== g.livePreviewUrl) _livePreviewEl.src = g.livePreviewUrl;
_livePreviewEl.style.display = "";
}
if (_livePreviewBadgeEl && g.liveScene >= 0 && g.liveSceneCount > 0) {
_livePreviewBadgeEl.textContent = `Scene ${g.liveScene + 1}/${g.liveSceneCount}`;
@@ -1143,7 +1112,7 @@
// store render; live progress ticks update it in place (see the gen-progress listener),
// so the rest of the editor isn't rebuilt while a generation runs.
_genMsgEl = _genBarEl = _genFillEl = null;
_livePreviewEl = _livePreviewBadgeEl = _livePreviewLayerEl = null;
_livePreviewEl = _livePreviewBadgeEl = null;
if (["queuing", "running", "pending", "error"].includes(gen.state)) {
const busy = gen.state !== "error";
const ro = el("div", "gen-readout" + (gen.state === "error" ? " error" : ""));
@@ -1162,9 +1131,17 @@
}
canvas.append(ro);
}
if (["queuing", "running", "pending"].includes(gen.state) && _livePreviewConfigured(st)) {
if (["queuing", "running", "pending"].includes(gen.state)) {
const lp = S.get().models?.live_preview || {};
const lpPort = "port:FunPackLTXAVSceneChainSampler.preview_vae";
const lpWired = (S.get().models?.slots || []).some((s) =>
Object.values(s.wires || {}).some((raw) => {
const arr = Array.isArray(raw) ? raw : raw ? [raw] : [];
return arr.includes(lpPort);
}));
const showLp = lp.enabled || lp.slot_id || lpWired;
if (showLp) {
const lpLayer = el("div", "pm-live-preview");
_livePreviewLayerEl = lpLayer;
_livePreviewEl = el("img", "pm-live-preview-img");
_livePreviewEl.alt = "Live preview";
_livePreviewEl.draggable = false;
@@ -1179,6 +1156,7 @@
lpLayer.append(_livePreviewEl, _livePreviewBadgeEl);
canvas.append(lpLayer);
}
}
if (previewAsset?.kind === "image") {
const layer = el("div", "pm-media-preview");
const label = el("div", "pm-media-preview-label", previewAsset.name || "Media preview");
+34 -83
View File
@@ -69,8 +69,7 @@
function _livePreviewConfigured() {
const lp = state.models?.live_preview || {};
if (lp.slot_id || lp.enabled) return true;
if ((state.models?.core_overrides?.sampler?.preview_vae || "").startsWith("out:")) return true;
if (lp.slot_id) return true;
return (state.models?.slots || []).some((s) =>
Object.values(s.wires || {}).some((raw) => _modelWireTargets(raw).includes(LIVE_PREVIEW_PORT)));
}
@@ -253,36 +252,13 @@
}
let _globalApplyTimer = null;
let _globalApplyPromise = null;
let _pendingGlobalPromptText = null;
function scheduleGlobalPromptApply(text) {
_pendingGlobalPromptText = text;
const trimmed = String(text || "").trim();
if (trimmed && trimmed !== buildGlobalPromptFromTimeline(state.project)) {
_localDirty = true;
_notifySaveChip();
}
clearTimeout(_globalApplyTimer);
_globalApplyTimer = setTimeout(() => {
_globalApplyTimer = null;
applyGlobalPromptQuiet(text).catch((e) => console.warn("Global prompt apply failed:", e));
}, 500);
}
async function _flushPendingGlobalPrompt() {
clearTimeout(_globalApplyTimer);
_globalApplyTimer = null;
if (_globalApplyPromise) await _globalApplyPromise;
const pending = (_pendingGlobalPromptText || "").trim();
if (!pending) return;
if (pending === buildGlobalPromptFromTimeline(state.project) && !_pinnedGlobalPrompt) {
_pendingGlobalPromptText = null;
return;
}
await applyGlobalPromptQuiet(pending, { force: true });
}
function _normalizeSceneText(t) {
return String(t || "").trim().replace(/\s+/g, " ");
}
@@ -530,53 +506,30 @@
return true;
}
async function applyGlobalPromptQuiet(text, opts = {}) {
async function applyGlobalPromptQuiet(text) {
if (!state.project) return false;
const trimmed = String(text || "").trim();
const force = !!opts.force;
if (!trimmed) {
_pendingGlobalPromptText = null;
if (!trimmed) return false;
if (trimmed === buildGlobalPromptFromTimeline(state.project) && !_pinnedGlobalPrompt) return true;
_historyRecord();
let res;
try { res = await API.parsePrompt(state.project.id, trimmed); }
catch (e) {
console.warn("Global prompt parse failed:", e);
return false;
}
if (trimmed === buildGlobalPromptFromTimeline(state.project) && !_pinnedGlobalPrompt) {
_pendingGlobalPromptText = null;
return true;
}
if (_globalApplyPromise) return _globalApplyPromise;
_globalApplyPromise = (async () => {
try {
if (!force && _pendingGlobalPromptText != null && String(_pendingGlobalPromptText).trim() !== trimmed) {
return false;
}
_historyRecord();
let res;
try { res = await API.parsePrompt(state.project.id, trimmed); }
catch (e) {
console.warn("Global prompt parse failed:", e);
return false;
}
if (!force && _pendingGlobalPromptText != null && String(_pendingGlobalPromptText).trim() !== trimmed) {
return false;
}
if (!await _distributeGlobalPrompt(trimmed, res)) return false;
clearTimeout(saveTimer);
saveTimer = null;
_localDirty = true;
scheduleSaveSilent();
refreshPreview(true);
return true;
} finally {
_globalApplyPromise = null;
if (_pendingGlobalPromptText === trimmed) _pendingGlobalPromptText = null;
}
})();
return _globalApplyPromise;
if (!await _distributeGlobalPrompt(trimmed, res)) return false;
clearTimeout(saveTimer);
saveTimer = null;
_localDirty = true;
scheduleSaveSilent();
refreshPreview(true);
return true;
}
function _syncPromptPreview() {
return (async () => {
try {
await _flushPendingGlobalPrompt();
await flushSave();
await refreshPreview(true);
} catch (e) { console.error("prompt preview sync failed", e); }
@@ -2428,13 +2381,8 @@
throw last;
}
async function flushPendingEdits() {
await _flushPendingGlobalPrompt();
return flushSave();
}
async function _flushSaveForGenerate() {
await flushPendingEdits();
await flushSave();
if (_localDirty) {
throw new Error(
"Could not save your latest edits before generate (tunnel may have dropped). "
@@ -2776,19 +2724,23 @@
msg: `${head} · sampling ${pr.value}/${pr.max}`,
});
}
const lp = pr?.live_preview;
if (lp?.ready && lp.url) {
const sceneNote = (lp.scene_index >= 0 && lp.scene_count > 0)
? ` · scene ${lp.scene_index + 1}/${lp.scene_count}`
: "";
updateGenProgress({
livePreviewUrl: lp.url,
liveScene: lp.scene_index,
liveSceneCount: lp.scene_count,
msg: `${(state.gen.msg || prefix).replace(/\s*·\s*scene \d+\/\d+$/, "")}${sceneNote}`,
});
}
} catch (_) {}
if (_livePreviewConfigured()) {
try {
const lp = await API.livePreview();
if (lp?.ready && lp.url) {
const sceneNote = (lp.scene_index >= 0 && lp.scene_count > 0)
? ` · scene ${lp.scene_index + 1}/${lp.scene_count}`
: "";
updateGenProgress({
livePreviewUrl: lp.url,
liveScene: lp.scene_index,
liveSceneCount: lp.scene_count,
msg: `${(state.gen.msg || prefix).replace(/\s*·\s*scene \d+\/\d+$/, "")}${sceneNote}`,
});
}
} catch (_) {}
}
}, 700);
pollTimer = setInterval(async () => {
if (_interrupted) {
@@ -2905,7 +2857,6 @@
10,
);
if (!r.prompt_id) { set({ gen: { ...state.gen, state: "error", msg: "No prompt id returned." } }); return false; }
if (r.live_preview_note) console.warn("[live preview]", r.live_preview_note);
_queueRenderPrompts(sceneIds);
if (r.validation && state.project) {
state.project.generation_meta = {
@@ -3727,7 +3678,7 @@
isOverlayAudioTrack, isSeparatedAudioTrack,
resizeScene, setSceneGapAfter, splitScene, snapFrames, snapFramesFloor, snapFramesCeil, sceneEffFrames, sceneEffFps, setSourceTrim, trimSceneLeft, slipScene,
applyEnginePreset, ENGINE_PRESETS, undo, redo,
refreshPreview, syncFromPreview, applyGlobalPromptQuiet, scheduleGlobalPromptApply, buildGlobalPromptFromTimeline, syncGlobalPromptFromTimeline, flushPendingEdits, generate, generateMontage, generateSelected, selectedSceneCount, renderFinal, exportSelected, saveSelectedToMediaBin, clipSaveableToMediaBin, interrupt, loadModels, loadImageTargets, setModelInput, setModelLink, setLivePreviewEnabled, setLivePreviewSlot, clearNotice,
refreshPreview, syncFromPreview, applyGlobalPromptQuiet, scheduleGlobalPromptApply, buildGlobalPromptFromTimeline, syncGlobalPromptFromTimeline, generate, generateMontage, generateSelected, selectedSceneCount, renderFinal, exportSelected, saveSelectedToMediaBin, clipSaveableToMediaBin, interrupt, loadModels, loadImageTargets, setModelInput, setModelLink, setLivePreviewEnabled, setLivePreviewSlot, clearNotice,
setConditioningSlot, setSamplerSlot, setSamplerInput, setSamplerInputNow, unsetSamplerInput, setStudioInput, setStudioInputNow,
loadMedia, uploadMedia, deleteMedia, deleteMediaMany, renameMedia, previewMedia, clearMediaPreview, assignMediaToScene, exportMediaAsset,
loadShortcuts, saveShortcut, deleteShortcut, importShortcuts,
+1 -1
View File
@@ -448,7 +448,7 @@ input[type=number] { font-family: var(--font-mono); }
.gen-readout .pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--amber); animation: pulse 1s infinite; flex: 0 0 auto; }
.gen-readout.error { color: var(--danger); }
.gen-readout.error .pulse { background: var(--danger); animation: none; }
.pm-live-preview { position: absolute; inset: 12px 12px 52px 12px; display: flex; align-items: center; justify-content: center; pointer-events: none; z-index: 3; }
.pm-live-preview { position: absolute; inset: 12px 12px 52px 12px; display: flex; align-items: center; justify-content: center; pointer-events: none; z-index: 1; }
.pm-live-preview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 6px; border: 1px solid var(--line); box-shadow: 0 8px 32px #000a; background: #0006; }
.pm-live-preview-badge { position: absolute; top: 8px; left: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--amber-2); background: #000b; border: 1px solid var(--line); padding: 3px 8px; border-radius: 12px; }
.gen-bar { width: 90px; height: 5px; background: #ffffff22; border-radius: 3px; overflow: hidden; flex: 0 0 auto; }
+18 -18
View File
@@ -1466,16 +1466,6 @@ if web is not None and PromptServer is not None:
if report["blocking"]:
detail = "Generation blocked — " + "; ".join(report["blocking"])
return web.json_response({"detail": detail, "report": report}, status=400)
live_preview_note = None
if live_preview.models_enabled(models_cfg):
samp_in = (graph.get("sampler") or {}).get("inputs") or {}
if not isinstance(samp_in.get("preview_vae"), list):
live_preview_note = (
"Live preview VAE is configured but preview_vae is not wired — "
"wire a VAE loader to Chain Sampler · live preview in Models, then restart ComfyUI if you recently updated FunPack."
)
elif not samp_in.get("funpack_live_preview"):
live_preview_note = "Live preview directory was not passed to the Chain Sampler."
try:
result = await bridge.queue_prompt(graph)
except Exception as e: # noqa: BLE001
@@ -1493,8 +1483,6 @@ if web is not None and PromptServer is not None:
"validation": validation,
"prompt_repairs_cleared": prompt_changed,
"reset_session": reset_session,
"live_preview": live_preview.models_enabled(models_cfg),
"live_preview_note": live_preview_note,
})
@routes.get(UI_PREFIX + "/api/projects/{pid}/status/{prompt_id}")
@@ -1544,15 +1532,27 @@ if web is not None and PromptServer is not None:
@routes.get(UI_PREFIX + "/api/progress")
async def _progress(_req):
payload = bridge.current_progress()
lp = live_preview.api_payload(UI_PREFIX)
if lp.get("active"):
payload["live_preview"] = lp
return web.json_response(payload)
return web.json_response(bridge.current_progress())
@routes.get(UI_PREFIX + "/api/live-preview")
async def _live_preview_status(_req):
return web.json_response(live_preview.api_payload(UI_PREFIX))
import os
snap = live_preview.snapshot()
if not snap.get("active"):
return web.json_response({"active": False})
frame_path = snap.get("frame_path") or ""
ready = bool(frame_path and os.path.isfile(frame_path))
payload = {
"active": True,
"ready": ready,
"scene_index": snap.get("scene_index", -1),
"scene_count": snap.get("scene_count", 0),
"updated_at": snap.get("updated_at", 0),
}
if ready:
ts = int(float(snap.get("updated_at") or 0) * 1000)
payload["url"] = f"{UI_PREFIX}/api/live-preview/frame?t={ts}"
return web.json_response(payload)
@routes.get(UI_PREFIX + "/api/live-preview/frame")
async def _live_preview_frame(_req):
+1 -8
View File
@@ -3705,11 +3705,7 @@ class FunPackLTXAVSceneChainSampler:
return
os.makedirs(preview_dir, exist_ok=True)
tile = int(cfg.get("decode_tile_size") or 256)
try:
decoded = self._decode_for_preview(preview_vae, scene_latent, decode_tile_size=tile)
except Exception as e:
print(f"[FunPackSceneChain] live preview decode failed (scene {scene_index + 1}): {e}")
return
decoded = self._decode_for_preview(preview_vae, scene_latent, decode_tile_size=tile)
path = os.path.join(preview_dir, f"scene_{int(scene_index):03d}.webp")
current = os.path.join(preview_dir, "current.webp")
if self._save_batch_preview(
@@ -3723,9 +3719,6 @@ class FunPackLTXAVSceneChainSampler:
except Exception:
current = path
_lp.publish(int(scene_index), current, scene_count)
print(f"[FunPackSceneChain] live preview scene {scene_index + 1}/{scene_count} -> {current}")
else:
print(f"[FunPackSceneChain] live preview save failed (scene {scene_index + 1})")
def _save_batch_preview(self, decoded, path, max_frames=16, width=256):
"""Save a decoded video tensor [T,H,W,C] in 0..1 as a downscaled animated webp."""