Easy Gen is a new Fooocus-style frontend at /funpack/easy — big preview, one prompt box, Upload, Generate, and an Advanced settings panel — built entirely on the Movie Editor's existing project format and API (no new backend endpoints), so projects stay interchangeable between the two UIs. The Hub at /funpack links to both UIs and surfaces version/branch/commit plus an Update/Restart action reusing the Editor's git_update.py flow. Startup now prints full host:port URLs for all three (Hub, Editor, Easy Gen) via a new config.comfy_display_url() helper. Also: settings_window.js's About section now labels itself via window.FunPackAppName instead of hardcoding "Cutting Room", and Easy Gen's boot-error banner accumulates distinct errors instead of overwriting, so a cascading failure (e.g. dom.js not loading) surfaces its root cause instead of just the last script in load order.
105 lines
4.6 KiB
JavaScript
105 lines
4.6 KiB
JavaScript
// window.FunPackMaintenance shim — ports the five actions menubar.js exposes
|
|
// for the Settings window's built-in "Refinement & Taste" / "Updates & ComfyUI"
|
|
// sections (settings_window.js), without pulling in the other ~300 lines of
|
|
// menubar.js (menu rendering, keyboard shortcuts, timeline-specific state).
|
|
// Same API calls, same confirm/alert flows as the Editor.
|
|
(function () {
|
|
const API = window.MovieEditorAPI;
|
|
|
|
function projectRefinementKey() {
|
|
return window.Store?.get().project?.refinement_key || "";
|
|
}
|
|
|
|
const refinementKeyImportInput = (function () {
|
|
const inp = document.createElement("input");
|
|
inp.type = "file"; inp.accept = ".json,application/json"; inp.style.display = "none";
|
|
inp.onchange = async () => {
|
|
const file = inp.files?.[0];
|
|
inp.value = "";
|
|
if (!file) return;
|
|
try {
|
|
const data = JSON.parse(await file.text());
|
|
let res;
|
|
try {
|
|
res = await API.importRefinementKey(data);
|
|
} catch (e) {
|
|
if (!e.exists) throw e;
|
|
if (!confirm(`A refinement key named "${e.key}" already exists. Overwrite it?`)) return;
|
|
res = await API.importRefinementKey(data, { overwrite: true });
|
|
}
|
|
alert(`Imported refinement key "${res.key}".`);
|
|
} catch (e) {
|
|
alert("Refinement key import failed: " + (e.message || e));
|
|
}
|
|
};
|
|
document.body.appendChild(inp);
|
|
return inp;
|
|
})();
|
|
|
|
async function exportRefinementKey() {
|
|
let payload;
|
|
try { payload = await API.refinementKeys(); }
|
|
catch (e) { alert("Could not list refinement keys: " + (e.message || e)); return; }
|
|
const names = (payload.keys || []).filter((k) => k && k !== "-None-");
|
|
if (!names.length) { alert("No refinement keys on disk yet."); return; }
|
|
const cur = projectRefinementKey();
|
|
if (!window.SlotPicker) return;
|
|
window.SlotPicker.open({
|
|
title: "Export refinement key",
|
|
options: names.map((k) => ({ value: k, label: k, hint: k === cur ? "project session" : "" })),
|
|
onPick: async (key) => {
|
|
try { await API.exportRefinementKeyFile(key); }
|
|
catch (e) { alert("Refinement key export failed: " + (e.message || e)); }
|
|
},
|
|
});
|
|
}
|
|
|
|
async function deleteRefinementKey() {
|
|
let payload;
|
|
try { payload = await API.refinementKeys(); }
|
|
catch (e) { alert("Could not list refinement keys: " + (e.message || e)); return; }
|
|
const names = (payload.keys || []).filter((k) => k && k !== "-None-");
|
|
if (!names.length) { alert("No refinement keys on disk yet."); return; }
|
|
const cur = projectRefinementKey();
|
|
if (!window.SlotPicker) return;
|
|
window.SlotPicker.open({
|
|
title: "Delete refinement key",
|
|
options: names.map((k) => ({ value: k, label: k, hint: k === cur ? "project session" : "" })),
|
|
onPick: async (key) => {
|
|
if (!confirm(`Delete refinement key "${key}"?\n\nThis removes its learned state AND all sidecars (value function, blessed attention/K-V banks, creativity latent, velocity memory). This cannot be undone.`)) {
|
|
return;
|
|
}
|
|
try { await API.deleteRefinementKey(key); alert(`Deleted "${key}".`); }
|
|
catch (e) { alert("Delete failed: " + (e.message || e)); }
|
|
},
|
|
});
|
|
}
|
|
|
|
async function clearGlobalTaste() {
|
|
let info;
|
|
try { info = await API.absoluteStoreInfo(); }
|
|
catch (e) { alert("Could not read the global-taste store: " + (e.message || e)); return; }
|
|
if (!info.exists) { alert("The Absolute global-taste store is already empty."); return; }
|
|
if (!confirm(`Clear the Absolute global-taste store?\n\nIt has pooled ${info.total_iterations} rated generation(s) (${info.liked_count} liked / ${info.bad_count} disliked directions) across all prompts. It learns from every rated run and is applied only in absolute/both steer mode. This cannot be undone.`)) {
|
|
return;
|
|
}
|
|
try { await API.clearAbsoluteStore(); alert("Cleared the Absolute global-taste store."); }
|
|
catch (e) { alert("Clear global taste failed: " + (e.message || e)); }
|
|
}
|
|
|
|
async function restartComfy() {
|
|
if (!confirm("Restart ComfyUI now?\n\nThe server will be down for ~10-40s and any running generation will be lost. This page reloads automatically when it's back.")) return;
|
|
const msg = window.FunPackRestart?.showOverlay?.("Restarting ComfyUI…\nThis page will reload when it's back.");
|
|
try { await API.restart(); } catch (_) { /* connection drops as it execv's - expected */ }
|
|
window.FunPackRestart?.waitForReload?.(msg, Date.now());
|
|
}
|
|
|
|
window.FunPackMaintenance = {
|
|
exportRefinementKey,
|
|
importRefinementKeyFile: () => refinementKeyImportInput.click(),
|
|
deleteRefinementKey,
|
|
clearGlobalTaste,
|
|
restartComfy,
|
|
};
|
|
})();
|