Browse Source

Update welcome modal tests and input image storage hashing approach

handoff-20260429-1057
shrimbly 6 months ago
parent
commit
7efa8fe8d3
  1. 8
      src/components/__tests__/QuickstartInitialView.test.tsx
  2. 73
      src/utils/imageStorage.ts

8
src/components/__tests__/QuickstartInitialView.test.tsx

@ -209,7 +209,7 @@ describe("QuickstartInitialView", () => {
expect(twitterLink).toHaveAttribute("rel", "noopener noreferrer"); expect(twitterLink).toHaveAttribute("rel", "noopener noreferrer");
}); });
it("should render docs coming soon text (not as a link)", () => { it("should render docs link", () => {
render( render(
<QuickstartInitialView <QuickstartInitialView
onSelectBlankCanvas={mockOnSelectBlankCanvas} onSelectBlankCanvas={mockOnSelectBlankCanvas}
@ -219,8 +219,10 @@ describe("QuickstartInitialView", () => {
/> />
); );
expect(screen.getByText("Docs (coming soon)")).toBeInTheDocument(); const docsLink = screen.getByText("Docs").closest("a");
expect(screen.getByText("Docs (coming soon)").closest("a")).toBeNull(); expect(docsLink).toHaveAttribute("href", "https://node-banana-docs.vercel.app/");
expect(docsLink).toHaveAttribute("target", "_blank");
expect(docsLink).toHaveAttribute("rel", "noopener noreferrer");
}); });
}); });

73
src/utils/imageStorage.ts

@ -62,13 +62,12 @@ async function externalizeNodeImages(
switch (node.type) { switch (node.type) {
case "imageInput": { case "imageInput": {
const d = data as import("@/types").ImageInputNodeData; const d = data as import("@/types").ImageInputNodeData;
if (isBase64DataUrl(d.image)) { // Skip if already has a valid imageRef (prevents duplicates on re-save after hydration)
if (d.imageRef && isBase64DataUrl(d.image)) {
newData = { ...d, image: null };
} else if (isBase64DataUrl(d.image)) {
const imageId = await saveImageAndGetId(d.image, workflowPath, savedImageIds, "inputs"); const imageId = await saveImageAndGetId(d.image, workflowPath, savedImageIds, "inputs");
newData = { newData = { ...d, image: null, imageRef: imageId };
...d,
image: null,
imageRef: imageId,
};
} else { } else {
newData = d; newData = d;
} }
@ -83,11 +82,16 @@ async function externalizeNodeImages(
let outputImage = d.outputImage; let outputImage = d.outputImage;
// Annotation images are user-created, save to inputs // Annotation images are user-created, save to inputs
if (isBase64DataUrl(d.sourceImage)) { // Skip if already has ref (prevents duplicates on re-save after hydration)
if (d.sourceImageRef && isBase64DataUrl(d.sourceImage)) {
sourceImage = null;
} else if (isBase64DataUrl(d.sourceImage)) {
sourceImageRef = await saveImageAndGetId(d.sourceImage, workflowPath, savedImageIds, "inputs"); sourceImageRef = await saveImageAndGetId(d.sourceImage, workflowPath, savedImageIds, "inputs");
sourceImage = null; sourceImage = null;
} }
if (isBase64DataUrl(d.outputImage)) { if (d.outputImageRef && isBase64DataUrl(d.outputImage)) {
outputImage = null;
} else if (isBase64DataUrl(d.outputImage)) {
outputImageRef = await saveImageAndGetId(d.outputImage, workflowPath, savedImageIds, "inputs"); outputImageRef = await saveImageAndGetId(d.outputImage, workflowPath, savedImageIds, "inputs");
outputImage = null; outputImage = null;
} }
@ -106,20 +110,28 @@ async function externalizeNodeImages(
const d = data as import("@/types").NanoBananaNodeData; const d = data as import("@/types").NanoBananaNodeData;
let outputImageRef = d.outputImageRef; let outputImageRef = d.outputImageRef;
let outputImage = d.outputImage; let outputImage = d.outputImage;
const inputImageRefs: string[] = []; let inputImageRefs = d.inputImageRefs ? [...d.inputImageRefs] : [];
const inputImages: string[] = []; const inputImages: string[] = [];
// Handle output image - AI generated, save to generations // Handle output image - AI generated, save to generations
if (isBase64DataUrl(d.outputImage)) { // Skip if already has ref (prevents duplicates on re-save after hydration)
if (d.outputImageRef && isBase64DataUrl(d.outputImage)) {
outputImage = null;
} else if (isBase64DataUrl(d.outputImage)) {
outputImageRef = await saveImageAndGetId(d.outputImage, workflowPath, savedImageIds, "generations"); outputImageRef = await saveImageAndGetId(d.outputImage, workflowPath, savedImageIds, "generations");
outputImage = null; outputImage = null;
} }
// Handle input images array (these come from connected nodes, save to inputs if present) // Handle input images array (these come from connected nodes, save to inputs if present)
for (const img of d.inputImages) { // Skip if corresponding inputImageRef already exists
if (isBase64DataUrl(img)) { for (let i = 0; i < d.inputImages.length; i++) {
const img = d.inputImages[i];
const existingRef = d.inputImageRefs?.[i];
if (existingRef && isBase64DataUrl(img)) {
inputImages.push(""); // Already has ref, just clear the base64
} else if (isBase64DataUrl(img)) {
const ref = await saveImageAndGetId(img, workflowPath, savedImageIds, "inputs"); const ref = await saveImageAndGetId(img, workflowPath, savedImageIds, "inputs");
inputImageRefs.push(ref); inputImageRefs[i] = ref;
inputImages.push(""); // Empty placeholder inputImages.push(""); // Empty placeholder
} else { } else {
inputImages.push(img); inputImages.push(img);
@ -138,14 +150,19 @@ async function externalizeNodeImages(
case "llmGenerate": { case "llmGenerate": {
const d = data as import("@/types").LLMGenerateNodeData; const d = data as import("@/types").LLMGenerateNodeData;
const inputImageRefs: string[] = []; let inputImageRefs = d.inputImageRefs ? [...d.inputImageRefs] : [];
const inputImages: string[] = []; const inputImages: string[] = [];
// Handle input images array (save to inputs) // Handle input images array (save to inputs)
for (const img of d.inputImages) { // Skip if corresponding inputImageRef already exists
if (isBase64DataUrl(img)) { for (let i = 0; i < d.inputImages.length; i++) {
const img = d.inputImages[i];
const existingRef = d.inputImageRefs?.[i];
if (existingRef && isBase64DataUrl(img)) {
inputImages.push(""); // Already has ref, just clear the base64
} else if (isBase64DataUrl(img)) {
const ref = await saveImageAndGetId(img, workflowPath, savedImageIds, "inputs"); const ref = await saveImageAndGetId(img, workflowPath, savedImageIds, "inputs");
inputImageRefs.push(ref); inputImageRefs[i] = ref;
inputImages.push(""); // Empty placeholder inputImages.push(""); // Empty placeholder
} else { } else {
inputImages.push(img); inputImages.push(img);
@ -163,13 +180,12 @@ async function externalizeNodeImages(
case "output": { case "output": {
const d = data as import("@/types").OutputNodeData; const d = data as import("@/types").OutputNodeData;
// Output displays generated content, save to generations // Output displays generated content, save to generations
if (isBase64DataUrl(d.image)) { // Skip if already has ref (prevents duplicates on re-save after hydration)
if (d.imageRef && isBase64DataUrl(d.image)) {
newData = { ...d, image: null };
} else if (isBase64DataUrl(d.image)) {
const imageId = await saveImageAndGetId(d.image, workflowPath, savedImageIds, "generations"); const imageId = await saveImageAndGetId(d.image, workflowPath, savedImageIds, "generations");
newData = { newData = { ...d, image: null, imageRef: imageId };
...d,
image: null,
imageRef: imageId,
};
} else { } else {
newData = d; newData = d;
} }
@ -179,13 +195,12 @@ async function externalizeNodeImages(
case "splitGrid": { case "splitGrid": {
const d = data as import("@/types").SplitGridNodeData; const d = data as import("@/types").SplitGridNodeData;
// SplitGrid source is input content, save to inputs // SplitGrid source is input content, save to inputs
if (isBase64DataUrl(d.sourceImage)) { // Skip if already has ref (prevents duplicates on re-save after hydration)
if (d.sourceImageRef && isBase64DataUrl(d.sourceImage)) {
newData = { ...d, sourceImage: null };
} else if (isBase64DataUrl(d.sourceImage)) {
const imageId = await saveImageAndGetId(d.sourceImage, workflowPath, savedImageIds, "inputs"); const imageId = await saveImageAndGetId(d.sourceImage, workflowPath, savedImageIds, "inputs");
newData = { newData = { ...d, sourceImage: null, sourceImageRef: imageId };
...d,
sourceImage: null,
sourceImageRef: imageId,
};
} else { } else {
newData = d; newData = d;
} }

Loading…
Cancel
Save