Files

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. */
}
}
}