111 lines
4.2 KiB
JavaScript
111 lines
4.2 KiB
JavaScript
// Probe only our own temporary key. Existing ComfyUI drafts are never edited or deleted.
|
|
export const inlineWidgetNames = [
|
|
"image", "mask_data", "source_data", "edit_mode", "crop_state", "blend_data", "layer_image",
|
|
];
|
|
|
|
// Some extensions also serialize a named snapshot. Keep it in step with the
|
|
// actual widgets, otherwise a removed embedding can survive in that snapshot.
|
|
export function syncInlineWidgets(data, widgets) {
|
|
for (const name of inlineWidgetNames) {
|
|
const index = widgets.findIndex((widget) => widget.name === name);
|
|
if (index < 0) continue;
|
|
if (Array.isArray(data.widgets_values)) data.widgets_values[index] = widgets[index].value;
|
|
if (data.widgets_values_named && typeof data.widgets_values_named === "object")
|
|
data.widgets_values_named[name] = widgets[index].value;
|
|
}
|
|
}
|
|
|
|
function inlineNodes(graph, seen = new Set()) {
|
|
if (!graph || seen.has(graph)) return [];
|
|
seen.add(graph);
|
|
return [
|
|
...(graph.nodes || []).filter((node) => node.type === "AdachiInlineMask"),
|
|
...(graph.definitions?.subgraphs || []).flatMap((subgraph) => inlineNodes(subgraph, seen)),
|
|
];
|
|
}
|
|
|
|
// Called before graph configuration: native autosave must never see the large
|
|
// source first, since a quota failure disables its storage for the entire tab.
|
|
export async function prepareInlineDraft(graph, { fits, upload }) {
|
|
const nodes = inlineNodes(graph);
|
|
for (const node of nodes) {
|
|
if (!Array.isArray(node.widgets_values)) continue;
|
|
for (const [index, name] of inlineWidgetNames.entries()) {
|
|
if (index < node.widgets_values.length && node.widgets_values_named)
|
|
node.widgets_values_named[name] = node.widgets_values[index];
|
|
}
|
|
}
|
|
const candidates = nodes.filter((node) => {
|
|
const embedded = node.widgets_values?.[2] ?? node.widgets_values_named?.source_data;
|
|
return typeof embedded === "string" && embedded.startsWith("data:") && embedded.length >= 256 * 1024;
|
|
}).sort((a, b) => (b.widgets_values?.[2] ?? b.widgets_values_named.source_data).length - (a.widgets_values?.[2] ?? a.widgets_values_named.source_data).length);
|
|
let converted = 0;
|
|
for (const node of candidates) {
|
|
if (fits(graph) !== false) break;
|
|
const embedded = node.widgets_values?.[2] ?? node.widgets_values_named.source_data;
|
|
const filename = await upload(embedded);
|
|
if (typeof filename !== "string" || !filename) throw new Error("Source upload returned no filename");
|
|
// Commit only after a successful exact upload. All masks and layers stay intact.
|
|
if (Array.isArray(node.widgets_values)) {
|
|
node.widgets_values[0] = filename;
|
|
node.widgets_values[2] = "";
|
|
}
|
|
if (node.widgets_values_named) {
|
|
node.widgets_values_named.image = filename;
|
|
node.widgets_values_named.source_data = "";
|
|
}
|
|
converted++;
|
|
}
|
|
return converted;
|
|
}
|
|
|
|
export function canStoreDraft(
|
|
graph,
|
|
{ path = "", workspace = "personal", storage } = {},
|
|
) {
|
|
try {
|
|
storage ??= globalThis.localStorage;
|
|
} catch {
|
|
return null;
|
|
}
|
|
const serialized = JSON.stringify({
|
|
data: JSON.stringify(graph),
|
|
updatedAt: Date.now(),
|
|
});
|
|
let credit = 0;
|
|
try {
|
|
if (path) {
|
|
const index = JSON.parse(
|
|
storage.getItem(`Comfy.Workflow.DraftIndex.v2:${workspace}`) || "null",
|
|
);
|
|
if (
|
|
Object.values(index?.entries || {}).some((entry) => entry.path === path)
|
|
) {
|
|
// Standard FNV-1a path hash used by the current ComfyUI draft format.
|
|
let hash = 2166136261;
|
|
for (let i = 0; i < path.length; i++)
|
|
hash = Math.imul(hash ^ path.charCodeAt(i), 16777619);
|
|
const key = `Comfy.Workflow.Draft.v2:${workspace}:${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
credit = storage.getItem(key)?.length || 0;
|
|
}
|
|
}
|
|
} catch {
|
|
/* Unknown metadata: a conservative probe still preserves all existing drafts. */
|
|
}
|
|
const needed = Math.max(0, serialized.length - credit);
|
|
if (!needed) return true;
|
|
const key = `Adachi.DraftProbe:${crypto.randomUUID()}`;
|
|
try {
|
|
storage.setItem(key, " ".repeat(needed));
|
|
return true;
|
|
} catch (error) {
|
|
return error?.name === "QuotaExceededError" ? false : null;
|
|
} finally {
|
|
try {
|
|
storage.removeItem(key);
|
|
} catch {
|
|
/* Storage may be unavailable. */
|
|
}
|
|
}
|
|
}
|