Browse Source

fix: handle outputGallery nodes in image externalization

OutputGallery nodes were not being handled in externalizeWorkflowImages(),
causing their images arrays (which can contain dozens of high-res base64
images) to remain embedded in workflow JSON even when "Embed images as
base64" was disabled.

This caused workflow files to balloon to 500MB+ and hit JavaScript's
"Invalid string length" limit when saving.

Changes:
- Add outputGallery case to externalizeNodeImages() to clear images array
- Add outputGallery case to hydrateNodeImages() for consistency
- Add cleanup-workflow.js script for fixing existing affected workflows

OutputGallery content is display-only and regenerated on workflow execution,
so it doesn't need to be persisted.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
handoff-20260429-1057
Shrimbly 4 months ago
committed by shrimbly
parent
commit
7360d9f8aa
  1. 73
      cleanup-workflow.js
  2. 14
      src/utils/imageStorage.ts

73
cleanup-workflow.js

@ -0,0 +1,73 @@
/**
* One-time script to clean up outputGallery nodes with embedded images
* This fixes workflows saved before the outputGallery externalization bug was fixed
*/
const fs = require('fs');
const path = require('path');
const workflowPath = process.argv[2];
if (!workflowPath) {
console.error('Usage: node cleanup-workflow.js <path-to-workflow.json>');
process.exit(1);
}
console.log(`Reading workflow: ${workflowPath}`);
const workflow = JSON.parse(fs.readFileSync(workflowPath, 'utf8'));
let cleaned = false;
let totalSizeBefore = 0;
let totalSizeAfter = 0;
workflow.nodes.forEach((node, index) => {
if (node.type === 'outputGallery' && node.data.images && node.data.images.length > 0) {
const sizeBefore = JSON.stringify(node.data.images).length;
const imageCount = node.data.images.filter(img => img && img.startsWith('data:')).length;
if (imageCount > 0) {
console.log(`\nNode ${index} (${node.id}):`);
console.log(` Found ${imageCount} embedded images`);
console.log(` Size: ${(sizeBefore / 1024 / 1024).toFixed(2)} MB`);
node.data.images = [];
totalSizeBefore += sizeBefore;
cleaned = true;
}
}
if (node.type === 'output' && node.data.image && node.data.image.startsWith('data:')) {
const sizeBefore = node.data.image.length;
console.log(`\nNode ${index} (${node.id}):`);
console.log(` Found embedded output image`);
console.log(` Size: ${(sizeBefore / 1024 / 1024).toFixed(2)} MB`);
node.data.image = null;
node.data.imageRef = undefined;
if (node.data.video) {
node.data.video = null;
}
totalSizeBefore += sizeBefore;
cleaned = true;
}
});
if (cleaned) {
const backupPath = workflowPath.replace('.json', '.backup.json');
console.log(`\nCreating backup: ${backupPath}`);
fs.copyFileSync(workflowPath, backupPath);
console.log(`Writing cleaned workflow...`);
fs.writeFileSync(workflowPath, JSON.stringify(workflow, null, 2));
const finalSize = fs.statSync(workflowPath).size;
const originalSize = fs.statSync(backupPath).size;
console.log(`\n✅ Done!`);
console.log(` Original size: ${(originalSize / 1024 / 1024).toFixed(2)} MB`);
console.log(` New size: ${(finalSize / 1024 / 1024).toFixed(2)} MB`);
console.log(` Saved: ${((originalSize - finalSize) / 1024 / 1024).toFixed(2)} MB`);
console.log(`\nBackup saved to: ${backupPath}`);
} else {
console.log('\n✓ No embedded images found - workflow is already clean!');
}

14
src/utils/imageStorage.ts

@ -270,6 +270,14 @@ async function externalizeNodeImages(
break;
}
case "outputGallery": {
const d = data as import("@/types").OutputGalleryNodeData;
// OutputGallery content is regenerated on each workflow run
// Clear images array to keep workflow file small
newData = { ...d, images: [] };
break;
}
case "splitGrid": {
const d = data as import("@/types").SplitGridNodeData;
// SplitGrid source is input content, save to inputs
@ -510,6 +518,12 @@ async function hydrateNodeImages(
break;
}
case "outputGallery": {
// OutputGallery content is not persisted - it's regenerated on each workflow run
newData = data;
break;
}
case "splitGrid": {
const d = data as import("@/types").SplitGridNodeData;
if (d.sourceImageRef && !d.sourceImage) {

Loading…
Cancel
Save