Reubencf commited on
Commit
1d30c5e
·
1 Parent(s): 8c43388

made changes to the code

Browse files
Files changed (2) hide show
  1. app/api/process/route.ts +11 -14
  2. app/nodes.tsx +608 -178
app/api/process/route.ts CHANGED
@@ -315,7 +315,8 @@ The result should look like all subjects were photographed together in the same
315
  if (params.stylePreset) {
316
  const strength = params.styleStrength || 50;
317
  const styleMap: { [key: string]: string } = {
318
- "90s-anime": "Convert the image to 90's anime art style with classic anime features: large expressive eyes, detailed hair, soft shading, nostalgic colors reminiscent of Studio Ghibli and classic anime productions",
 
319
  "mha": "Transform the image into My Hero Academia anime style with modern crisp lines, vibrant colors, dynamic character design, and heroic aesthetics typical of the series",
320
  "dbz": "Apply Dragon Ball Z anime style with sharp angular features, spiky hair, intense expressions, bold outlines, high contrast shading, and dramatic action-oriented aesthetics",
321
  "ukiyo-e": "Render in traditional Japanese Ukiyo-e woodblock print style with flat colors, bold outlines, stylized waves and clouds, traditional Japanese artistic elements",
@@ -325,10 +326,9 @@ The result should look like all subjects were photographed together in the same
325
  "van-gogh": "Apply Post-Impressionist Van Gogh style with thick swirling brushstrokes, vibrant yellows and blues, expressive texture, starry night-like patterns",
326
  "simpsons": "Convert to The Simpsons cartoon style with yellow skin tones, simple rounded features, bulging eyes, overbite, Matt Groening's distinctive character design",
327
  "family-guy": "Transform into Family Guy animation style with rounded character design, simplified features, Seth MacFarlane's distinctive art style, bold outlines",
328
- "arcane": "Apply Arcane (League of Legends) style with painterly brush-stroke textures, neon rim lighting, hand-painted feel, stylized realism, vibrant color grading",
329
  "wildwest": "Render in Wild West style with dusty desert tones, sunset orange lighting, vintage film grain, cowboy aesthetic, sepia and brown color palette",
330
- "stranger-things": "Apply Stranger Things 80s aesthetic with Kodak film push-process look, neon magenta backlight, grainy vignette, retro sci-fi horror atmosphere",
331
- "breaking-bad": "Transform with Breaking Bad cinematography style featuring dusty New Mexico orange and teal color grading, 35mm film grain, desert atmosphere, dramatic lighting"
332
  };
333
 
334
  const styleDescription = styleMap[params.stylePreset];
@@ -347,7 +347,7 @@ The result should look like all subjects were photographed together in the same
347
  params.iso || params.filmStyle || params.lighting || params.bokeh || params.composition) {
348
  const cameraSettings: string[] = [];
349
  if (params.focalLength) {
350
- if (params.focalLength === "8mm fisheye") {
351
  cameraSettings.push("Apply 8mm fisheye lens effect with 180-degree circular distortion");
352
  } else {
353
  cameraSettings.push(`Focal Length: ${params.focalLength}`);
@@ -358,12 +358,10 @@ The result should look like all subjects were photographed together in the same
358
  if (params.whiteBalance) cameraSettings.push(`White Balance: ${params.whiteBalance}`);
359
  if (params.angle) cameraSettings.push(`Camera Angle: ${params.angle}`);
360
  if (params.iso) cameraSettings.push(`${params.iso}`);
361
- if (params.filmStyle) cameraSettings.push(`${params.filmStyle}`);
362
  if (params.lighting) cameraSettings.push(`Lighting: ${params.lighting}`);
363
  if (params.bokeh) cameraSettings.push(`Bokeh effect: ${params.bokeh}`);
364
- if (params.composition) cameraSettings.push(`Composition: ${params.composition}`);
365
-
366
- if (cameraSettings.length > 0) {
367
  prompts.push(`Apply professional photography settings: ${cameraSettings.join(", ")}`);
368
  }
369
  }
@@ -375,8 +373,7 @@ The result should look like all subjects were photographed together in the same
375
 
376
  // Lightning effects
377
  if (params.lightingImage && params.selectedLighting) {
378
- const lightingStrength = params.lightingStrength || 75;
379
- prompts.push(`Apply ${params.selectedLighting} lighting effect to the person in the image. Adjust the lighting, shadows, and highlights to match the reference lighting style shown in the second image. Maintain the person's appearance, pose, and background while enhancing the lighting at ${lightingStrength}% intensity.`);
380
 
381
  try {
382
  const lightingRef = await toInlineDataFromAny(params.lightingImage);
@@ -390,8 +387,7 @@ The result should look like all subjects were photographed together in the same
390
 
391
  // Pose modifications
392
  if (params.poseImage && params.selectedPose) {
393
- const poseStrength = params.poseStrength || 60;
394
- prompts.push(`Change the pose of the person in the first image to match the pose shown in the reference image. Keep the person's facial features, clothing, and overall appearance the same, only modify their body position and pose to match the reference at ${poseStrength}% strength.`);
395
 
396
  try {
397
  const poseRef = await toInlineDataFromAny(params.poseImage);
@@ -413,6 +409,7 @@ The result should look like all subjects were photographed together in the same
413
  if (face.changeHairstyle) modifications.push(`change hairstyle to ${face.changeHairstyle}`);
414
  if (face.facialExpression) modifications.push(`change facial expression to ${face.facialExpression}`);
415
  if (face.beardStyle) modifications.push(`add/change beard to ${face.beardStyle}`);
 
416
 
417
  if (modifications.length > 0) {
418
  prompts.push(`Face modifications: ${modifications.join(", ")}`);
@@ -421,7 +418,7 @@ The result should look like all subjects were photographed together in the same
421
 
422
  // Combine all prompts
423
  let prompt = prompts.length > 0
424
- ? prompts.join("\n\n") + "\n\nApply all these modifications while maintaining the person's identity and keeping unspecified aspects unchanged."
425
  : "Process this image with high quality output.";
426
 
427
  // Add the custom prompt if provided
 
315
  if (params.stylePreset) {
316
  const strength = params.styleStrength || 50;
317
  const styleMap: { [key: string]: string } = {
318
+ "90s-anime": "Convert the image to 90's anime art style with classic anime features",
319
+ "Gibhli": "Apply Studio Ghibli style with vibrant colors, detailed character design, and fantasy elements typical of Studio Ghibli movies",
320
  "mha": "Transform the image into My Hero Academia anime style with modern crisp lines, vibrant colors, dynamic character design, and heroic aesthetics typical of the series",
321
  "dbz": "Apply Dragon Ball Z anime style with sharp angular features, spiky hair, intense expressions, bold outlines, high contrast shading, and dramatic action-oriented aesthetics",
322
  "ukiyo-e": "Render in traditional Japanese Ukiyo-e woodblock print style with flat colors, bold outlines, stylized waves and clouds, traditional Japanese artistic elements",
 
326
  "van-gogh": "Apply Post-Impressionist Van Gogh style with thick swirling brushstrokes, vibrant yellows and blues, expressive texture, starry night-like patterns",
327
  "simpsons": "Convert to The Simpsons cartoon style with yellow skin tones, simple rounded features, bulging eyes, overbite, Matt Groening's distinctive character design",
328
  "family-guy": "Transform into Family Guy animation style with rounded character design, simplified features, Seth MacFarlane's distinctive art style, bold outlines",
 
329
  "wildwest": "Render in Wild West style with dusty desert tones, sunset orange lighting, vintage film grain, cowboy aesthetic, sepia and brown color palette",
330
+ "star-wars": "Apply Star Wars style with vibrant colors, detailed character design, and fantasy elements typical of Star Wars movies",
331
+ "star-trek": "Apply Star Trek style with vibrant colors, detailed character design, and fantasy elements typical of Star Trek movies",
332
  };
333
 
334
  const styleDescription = styleMap[params.stylePreset];
 
347
  params.iso || params.filmStyle || params.lighting || params.bokeh || params.composition) {
348
  const cameraSettings: string[] = [];
349
  if (params.focalLength) {
350
+ if (params.focalLength === "8mm") {
351
  cameraSettings.push("Apply 8mm fisheye lens effect with 180-degree circular distortion");
352
  } else {
353
  cameraSettings.push(`Focal Length: ${params.focalLength}`);
 
358
  if (params.whiteBalance) cameraSettings.push(`White Balance: ${params.whiteBalance}`);
359
  if (params.angle) cameraSettings.push(`Camera Angle: ${params.angle}`);
360
  if (params.iso) cameraSettings.push(`${params.iso}`);
 
361
  if (params.lighting) cameraSettings.push(`Lighting: ${params.lighting}`);
362
  if (params.bokeh) cameraSettings.push(`Bokeh effect: ${params.bokeh}`);
363
+ if (params.filmStyle == "RAW") cameraSettings.push(`Convert the image to RAW image`); else cameraSettings.push(`Film Style: ${params.filmStyle}`);
364
+ if (cameraSettings.length > 0) {
 
365
  prompts.push(`Apply professional photography settings: ${cameraSettings.join(", ")}`);
366
  }
367
  }
 
373
 
374
  // Lightning effects
375
  if (params.lightingImage && params.selectedLighting) {
376
+ prompts.push(`Apply ${params.selectedLighting} lighting effect to the person in the image. Adjust the lighting, shadows, and highlights to match the reference lighting style shown in the second image. Maintain the person's appearance, pose, and background`);
 
377
 
378
  try {
379
  const lightingRef = await toInlineDataFromAny(params.lightingImage);
 
387
 
388
  // Pose modifications
389
  if (params.poseImage && params.selectedPose) {
390
+ prompts.push(`Change the pose of the person in the first image to match the pose shown in the reference image. Keep the person's facial features, clothing, and overall appearance the same, only modify their body position and pose to match the reference`);
 
391
 
392
  try {
393
  const poseRef = await toInlineDataFromAny(params.poseImage);
 
409
  if (face.changeHairstyle) modifications.push(`change hairstyle to ${face.changeHairstyle}`);
410
  if (face.facialExpression) modifications.push(`change facial expression to ${face.facialExpression}`);
411
  if (face.beardStyle) modifications.push(`add/change beard to ${face.beardStyle}`);
412
+ if (face.selectedMakeup) modifications.push(`add a face makeup with red colors on cheeks and and some yellow blue colors around the eye area`);
413
 
414
  if (modifications.length > 0) {
415
  prompts.push(`Face modifications: ${modifications.join(", ")}`);
 
418
 
419
  // Combine all prompts
420
  let prompt = prompts.length > 0
421
+ ? prompts.join("\n\n") + "\nApply all these modifications while maintaining the person's identity and keeping unspecified aspects unchanged."
422
  : "Process this image with high quality output.";
423
 
424
  // Add the custom prompt if provided
app/nodes.tsx CHANGED
@@ -1,22 +1,52 @@
1
  /**
2
- * NODE COMPONENT VIEWS
3
  *
4
- * This file contains all the visual node components for the Nano Banana Editor.
5
- * Each node type has its own React component that handles:
6
- * - User interface and controls
7
- * - Drag and drop functionality
8
- * - Connection port rendering
9
- * - Processing status display
10
- * - Image upload/preview
11
  *
12
- * Node Types Available:
13
- * - BackgroundNodeView: Change/generate image backgrounds
14
- * - ClothesNodeView: Add/modify clothing on subjects
15
- * - StyleNodeView: Apply artistic styles and filters
16
- * - EditNodeView: General text-based image editing
17
- * - CameraNodeView: Apply camera effects and settings
18
- * - AgeNodeView: Transform subject age
19
- * - FaceNodeView: Modify facial features and accessories
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  */
21
  // Enable React Server Components client-side rendering for this file
22
  "use client";
@@ -101,7 +131,32 @@ async function copyImageToClipboard(dataUrl: string) {
101
  }
102
 
103
  /**
104
- * Reusable output section with history navigation for node components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  */
106
  function NodeOutputSection({
107
  nodeId, // Unique identifier for the node
@@ -281,13 +336,15 @@ function Port({
281
  nodeId,
282
  isOutput,
283
  onStartConnection,
284
- onEndConnection
 
285
  }: {
286
  className?: string;
287
  nodeId?: string;
288
  isOutput?: boolean;
289
  onStartConnection?: (nodeId: string) => void;
290
  onEndConnection?: (nodeId: string) => void;
 
291
  }) {
292
  /**
293
  * Handle starting a connection (pointer down on output port)
@@ -309,16 +366,61 @@ function Port({
309
  }
310
  };
311
 
 
 
 
 
 
 
 
 
 
 
 
312
  return (
313
  <div
314
- className={cx("nb-port", className)} // Apply base port styling plus custom classes
315
- onPointerDown={handlePointerDown} // Handle connection start
316
- onPointerUp={handlePointerUp} // Handle connection end
317
- onPointerEnter={handlePointerUp} // Also handle connection end on hover (for better UX)
 
 
 
 
 
 
318
  />
319
  );
320
  }
321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  export function BackgroundNodeView({
323
  node,
324
  onDelete,
@@ -327,40 +429,50 @@ export function BackgroundNodeView({
327
  onEndConnection,
328
  onProcess,
329
  onUpdatePosition,
330
- getNodeHistoryInfo,
331
- navigateNodeHistory,
332
- getCurrentNodeImage,
333
  }: any) {
 
334
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
335
 
 
 
 
 
336
  const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
337
  if (e.target.files?.length) {
338
- const reader = new FileReader();
339
  reader.onload = () => {
340
- onUpdate(node.id, { customBackgroundImage: reader.result });
341
  };
342
- reader.readAsDataURL(e.target.files[0]);
343
  }
344
  };
345
 
 
 
 
 
346
  const handleImagePaste = (e: React.ClipboardEvent) => {
347
- const items = e.clipboardData.items;
 
 
348
  for (let i = 0; i < items.length; i++) {
349
- if (items[i].type.startsWith("image/")) {
350
- const file = items[i].getAsFile();
351
  if (file) {
352
- const reader = new FileReader();
353
  reader.onload = () => {
354
- onUpdate(node.id, { customBackgroundImage: reader.result });
355
  };
356
- reader.readAsDataURL(file);
357
- return;
358
  }
359
  }
360
  }
361
- const text = e.clipboardData.getData("text");
 
 
362
  if (text && (text.startsWith("http") || text.startsWith("data:image"))) {
363
- onUpdate(node.id, { customBackgroundImage: text });
364
  }
365
  };
366
 
@@ -390,7 +502,7 @@ export function BackgroundNodeView({
390
  onPointerMove={onPointerMove}
391
  onPointerUp={onPointerUp}
392
  >
393
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
394
  <div className="font-semibold text-sm flex-1 text-center">BACKGROUND</div>
395
  <div className="flex items-center gap-1">
396
  <Button
@@ -413,6 +525,7 @@ export function BackgroundNodeView({
413
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
414
  </div>
415
  </div>
 
416
  <div className="p-3 space-y-3">
417
  {node.input && (
418
  <div className="flex justify-end mb-2">
@@ -422,7 +535,7 @@ export function BackgroundNodeView({
422
  onClick={() => onUpdate(node.id, { input: undefined })}
423
  className="text-xs"
424
  >
425
- Clear Connection
426
  </Button>
427
  </div>
428
  )}
@@ -551,9 +664,6 @@ export function BackgroundNodeView({
551
  nodeId={node.id}
552
  output={node.output}
553
  downloadFileName={`background-${Date.now()}.png`}
554
- getNodeHistoryInfo={getNodeHistoryInfo}
555
- navigateNodeHistory={navigateNodeHistory}
556
- getCurrentNodeImage={getCurrentNodeImage}
557
  />
558
  {node.error && (
559
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
@@ -563,14 +673,46 @@ export function BackgroundNodeView({
563
  );
564
  }
565
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  export function ClothesNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
567
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
568
 
 
 
 
 
569
  const presetClothes = [
570
- { name: "Sukajan", path: "/clothes/sukajan.png" },
571
- { name: "Blazer", path: "/clothes/blazzer.png" },
572
- { name: "Suit", path: "/clothes/suit.png" },
573
- { name: "Women's Outfit", path: "/clothes/womenoutfit.png" },
574
  ];
575
 
576
  const onDrop = async (e: React.DragEvent) => {
@@ -620,7 +762,7 @@ export function ClothesNodeView({ node, onDelete, onUpdate, onStartConnection, o
620
  onPointerMove={onPointerMove}
621
  onPointerUp={onPointerUp}
622
  >
623
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
624
  <div className="font-semibold text-sm flex-1 text-center">CLOTHES</div>
625
  <div className="flex items-center gap-1">
626
  <Button
@@ -643,6 +785,7 @@ export function ClothesNodeView({ node, onDelete, onUpdate, onStartConnection, o
643
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
644
  </div>
645
  </div>
 
646
  <div className="p-3 space-y-3">
647
  {node.input && (
648
  <div className="flex justify-end mb-2">
@@ -652,7 +795,7 @@ export function ClothesNodeView({ node, onDelete, onUpdate, onStartConnection, o
652
  onClick={() => onUpdate(node.id, { input: undefined })}
653
  className="text-xs"
654
  >
655
- Clear Connection
656
  </Button>
657
  </div>
658
  )}
@@ -725,9 +868,6 @@ export function ClothesNodeView({ node, onDelete, onUpdate, onStartConnection, o
725
  nodeId={node.id}
726
  output={node.output}
727
  downloadFileName={`clothes-${Date.now()}.png`}
728
- getNodeHistoryInfo={getNodeHistoryInfo}
729
- navigateNodeHistory={navigateNodeHistory}
730
- getCurrentNodeImage={getCurrentNodeImage}
731
  />
732
  {node.error && (
733
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
@@ -737,7 +877,38 @@ export function ClothesNodeView({ node, onDelete, onUpdate, onStartConnection, o
737
  );
738
  }
739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
  export function AgeNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
741
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
742
 
743
  return (
@@ -748,7 +919,7 @@ export function AgeNodeView({ node, onDelete, onUpdate, onStartConnection, onEnd
748
  onPointerMove={onPointerMove}
749
  onPointerUp={onPointerUp}
750
  >
751
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
752
  <div className="font-semibold text-sm flex-1 text-center">AGE</div>
753
  <div className="flex items-center gap-1">
754
  <Button
@@ -771,6 +942,7 @@ export function AgeNodeView({ node, onDelete, onUpdate, onStartConnection, onEnd
771
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
772
  </div>
773
  </div>
 
774
  <div className="p-3 space-y-3">
775
  {node.input && (
776
  <div className="flex justify-end mb-2">
@@ -780,7 +952,7 @@ export function AgeNodeView({ node, onDelete, onUpdate, onStartConnection, onEnd
780
  onClick={() => onUpdate(node.id, { input: undefined })}
781
  className="text-xs"
782
  >
783
- Clear Connection
784
  </Button>
785
  </div>
786
  )}
@@ -806,9 +978,6 @@ export function AgeNodeView({ node, onDelete, onUpdate, onStartConnection, onEnd
806
  nodeId={node.id}
807
  output={node.output}
808
  downloadFileName={`age-${Date.now()}.png`}
809
- getNodeHistoryInfo={getNodeHistoryInfo}
810
- navigateNodeHistory={navigateNodeHistory}
811
- getCurrentNodeImage={getCurrentNodeImage}
812
  />
813
  {node.error && (
814
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
@@ -818,19 +987,74 @@ export function AgeNodeView({ node, onDelete, onUpdate, onStartConnection, onEnd
818
  );
819
  }
820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821
  export function CameraNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
822
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
823
- const focalLengths = ["None", "8mm fisheye", "12mm", "24mm", "35mm", "50mm", "85mm", "135mm", "200mm", "300mm", "400mm"];
824
- const apertures = ["None", "f/0.95", "f/1.2", "f/1.4", "f/1.8", "f/2", "f/2.8", "f/4", "f/5.6", "f/8", "f/11", "f/16", "f/22"];
825
- const shutterSpeeds = ["None", "1/8000s", "1/4000s", "1/2000s", "1/1000s", "1/500s", "1/250s", "1/125s", "1/60s", "1/30s", "1/15s", "1/8s", "1/4s", "1/2s", "1s", "2s", "5s", "10s", "30s"];
 
 
 
 
 
 
 
 
826
  const whiteBalances = ["None", "2800K candlelight", "3200K tungsten", "4000K fluorescent", "5600K daylight", "6500K cloudy", "7000K shade", "8000K blue sky"];
 
 
827
  const angles = ["None", "eye level", "low angle", "high angle", "Dutch tilt", "bird's eye", "worm's eye", "over the shoulder", "POV"];
828
- const isoValues = ["None", "ISO 50", "ISO 100", "ISO 200", "ISO 400", "ISO 800", "ISO 1600", "ISO 3200", "ISO 6400", "ISO 12800"];
829
- const filmStyles = ["None", "Kodak Portra", "Fuji Velvia", "Ilford HP5", "Cinestill 800T", "Lomography", "Cross Process", "Black & White", "Sepia", "Vintage", "Film Noir"];
 
 
 
 
 
 
830
  const lightingTypes = ["None", "Natural Light", "Golden Hour", "Blue Hour", "Studio Lighting", "Rembrandt", "Split Lighting", "Butterfly Lighting", "Loop Lighting", "Rim Lighting", "Silhouette", "High Key", "Low Key"];
831
- const bokehStyles = ["None", "Smooth Bokeh", "Swirly Bokeh", "Hexagonal Bokeh", "Cat Eye Bokeh", "Bubble Bokeh", "Creamy Bokeh"];
832
- const compositions = ["None", "Rule of Thirds", "Golden Ratio", "Symmetrical", "Leading Lines", "Frame in Frame", "Fill the Frame", "Negative Space", "Patterns", "Diagonal"];
833
- const aspectRatios = ["None", "1:1 Square", "3:2 Standard", "4:3 Classic", "16:9 Widescreen", "21:9 Cinematic", "9:16 Portrait", "4:5 Instagram", "2:3 Portrait"];
 
 
 
834
 
835
  return (
836
  <div className="nb-node absolute text-white w-[360px]" style={{ left: localPos.x, top: localPos.y }}>
@@ -840,7 +1064,7 @@ export function CameraNodeView({ node, onDelete, onUpdate, onStartConnection, on
840
  onPointerMove={onPointerMove}
841
  onPointerUp={onPointerUp}
842
  >
843
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
844
  <div className="font-semibold text-sm flex-1 text-center">CAMERA</div>
845
  <div className="flex items-center gap-1">
846
  <Button
@@ -872,49 +1096,72 @@ export function CameraNodeView({ node, onDelete, onUpdate, onStartConnection, on
872
  onClick={() => onUpdate(node.id, { input: undefined })}
873
  className="text-xs"
874
  >
875
- Clear Connection
876
  </Button>
877
  </div>
878
  )}
879
- {/* Basic Camera Settings */}
880
  <div className="text-xs text-white/50 font-semibold mb-1">Basic Settings</div>
881
- <div className="grid grid-cols-2 gap-2">
 
 
 
 
 
 
 
 
 
 
 
 
 
882
  <div>
883
  <label className="text-xs text-white/70">Focal Length</label>
884
  <Select
885
  className="w-full"
886
- value={node.focalLength || "None"}
887
  onChange={(e) => onUpdate(node.id, { focalLength: (e.target as HTMLSelectElement).value })}
 
888
  >
889
  {focalLengths.map(f => <option key={f} value={f}>{f}</option>)}
890
  </Select>
891
  </div>
 
 
892
  <div>
893
  <label className="text-xs text-white/70">Aperture</label>
894
  <Select
895
  className="w-full"
896
- value={node.aperture || "None"}
897
  onChange={(e) => onUpdate(node.id, { aperture: (e.target as HTMLSelectElement).value })}
 
898
  >
899
  {apertures.map(a => <option key={a} value={a}>{a}</option>)}
900
  </Select>
901
  </div>
 
 
902
  <div>
903
  <label className="text-xs text-white/70">Shutter Speed</label>
904
  <Select
905
  className="w-full"
906
- value={node.shutterSpeed || "None"}
907
  onChange={(e) => onUpdate(node.id, { shutterSpeed: (e.target as HTMLSelectElement).value })}
 
908
  >
909
  {shutterSpeeds.map(s => <option key={s} value={s}>{s}</option>)}
910
  </Select>
911
  </div>
 
 
912
  <div>
913
  <label className="text-xs text-white/70">ISO</label>
914
  <Select
915
  className="w-full"
916
- value={node.iso || "None"}
917
  onChange={(e) => onUpdate(node.id, { iso: (e.target as HTMLSelectElement).value })}
 
918
  >
919
  {isoValues.map(i => <option key={i} value={i}>{i}</option>)}
920
  </Select>
@@ -979,26 +1226,6 @@ export function CameraNodeView({ node, onDelete, onUpdate, onStartConnection, on
979
  {angles.map(a => <option key={a} value={a}>{a}</option>)}
980
  </Select>
981
  </div>
982
- <div>
983
- <label className="text-xs text-white/70">Composition</label>
984
- <Select
985
- className="w-full"
986
- value={node.composition || "None"}
987
- onChange={(e) => onUpdate(node.id, { composition: (e.target as HTMLSelectElement).value })}
988
- >
989
- {compositions.map(c => <option key={c} value={c}>{c}</option>)}
990
- </Select>
991
- </div>
992
- <div>
993
- <label className="text-xs text-white/70">Aspect Ratio</label>
994
- <Select
995
- className="w-full"
996
- value={node.aspectRatio || "None"}
997
- onChange={(e) => onUpdate(node.id, { aspectRatio: (e.target as HTMLSelectElement).value })}
998
- >
999
- {aspectRatios.map(a => <option key={a} value={a}>{a}</option>)}
1000
- </Select>
1001
- </div>
1002
  </div>
1003
  <Button
1004
  className="w-full"
@@ -1013,9 +1240,6 @@ export function CameraNodeView({ node, onDelete, onUpdate, onStartConnection, on
1013
  nodeId={node.id}
1014
  output={node.output}
1015
  downloadFileName={`camera-${Date.now()}.png`}
1016
- getNodeHistoryInfo={getNodeHistoryInfo}
1017
- navigateNodeHistory={navigateNodeHistory}
1018
- getCurrentNodeImage={getCurrentNodeImage}
1019
  />
1020
  </div>
1021
  {node.error && (
@@ -1026,10 +1250,50 @@ export function CameraNodeView({ node, onDelete, onUpdate, onStartConnection, on
1026
  );
1027
  }
1028
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1029
  export function FaceNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
1030
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
 
 
1031
  const hairstyles = ["None", "short", "long", "curly", "straight", "bald", "mohawk", "ponytail"];
 
 
1032
  const expressions = ["None", "happy", "serious", "smiling", "laughing", "sad", "surprised", "angry"];
 
 
1033
  const beardStyles = ["None", "stubble", "goatee", "full beard", "mustache", "clean shaven"];
1034
 
1035
  return (
@@ -1040,7 +1304,7 @@ export function FaceNodeView({ node, onDelete, onUpdate, onStartConnection, onEn
1040
  onPointerMove={onPointerMove}
1041
  onPointerUp={onPointerUp}
1042
  >
1043
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
1044
  <div className="font-semibold text-sm flex-1 text-center">FACE</div>
1045
  <div className="flex items-center gap-1">
1046
  <Button
@@ -1072,37 +1336,52 @@ export function FaceNodeView({ node, onDelete, onUpdate, onStartConnection, onEn
1072
  onClick={() => onUpdate(node.id, { input: undefined })}
1073
  className="text-xs"
1074
  >
1075
- Clear Connection
1076
  </Button>
1077
  </div>
1078
  )}
 
1079
  <div className="space-y-2">
1080
- <label className="flex items-center gap-2 text-xs">
 
1081
  <Checkbox
1082
- checked={node.faceOptions?.removePimples || false}
1083
  onChange={(e) => onUpdate(node.id, {
1084
- faceOptions: { ...node.faceOptions, removePimples: (e.target as HTMLInputElement).checked }
 
 
 
1085
  })}
1086
  />
1087
- Remove pimples
1088
  </label>
1089
- <label className="flex items-center gap-2 text-xs">
 
 
1090
  <Checkbox
1091
- checked={node.faceOptions?.addSunglasses || false}
1092
  onChange={(e) => onUpdate(node.id, {
1093
- faceOptions: { ...node.faceOptions, addSunglasses: (e.target as HTMLInputElement).checked }
 
 
 
1094
  })}
1095
  />
1096
- Add sunglasses
1097
  </label>
1098
- <label className="flex items-center gap-2 text-xs">
 
 
1099
  <Checkbox
1100
- checked={node.faceOptions?.addHat || false}
1101
  onChange={(e) => onUpdate(node.id, {
1102
- faceOptions: { ...node.faceOptions, addHat: (e.target as HTMLInputElement).checked }
 
 
 
1103
  })}
1104
  />
1105
- Add hat
1106
  </label>
1107
  </div>
1108
 
@@ -1145,41 +1424,58 @@ export function FaceNodeView({ node, onDelete, onUpdate, onStartConnection, onEn
1145
  </Select>
1146
  </div>
1147
 
 
1148
  <div>
1149
  <label className="text-xs text-white/70">Makeup</label>
1150
- <div className="grid grid-cols-2 gap-2 mt-2">
 
 
1151
  <button
1152
- className={`p-1 rounded border ${
1153
  !node.faceOptions?.selectedMakeup || node.faceOptions?.selectedMakeup === "None"
1154
- ? "border-indigo-400 bg-indigo-500/20"
1155
- : "border-white/20 hover:border-white/40"
1156
  }`}
1157
  onClick={() => onUpdate(node.id, {
1158
- faceOptions: { ...node.faceOptions, selectedMakeup: "None", makeupImage: null }
 
 
 
 
1159
  })}
 
1160
  >
 
1161
  <div className="w-full h-24 flex items-center justify-center text-xs text-white/60 border border-dashed border-white/20 rounded mb-1">
1162
- No Makeup
1163
  </div>
1164
- <div className="text-xs">None</div>
1165
  </button>
 
 
1166
  <button
1167
- className={`p-1 rounded border ${
1168
  node.faceOptions?.selectedMakeup === "Makeup"
1169
- ? "border-indigo-400 bg-indigo-500/20"
1170
- : "border-white/20 hover:border-white/40"
1171
  }`}
1172
  onClick={() => onUpdate(node.id, {
1173
- faceOptions: { ...node.faceOptions, selectedMakeup: "Makeup", makeupImage: "/makeup/makeup1.png" }
 
 
 
 
1174
  })}
 
1175
  >
 
1176
  <img
1177
  src="/makeup/makeup1.png"
1178
- alt="Makeup"
1179
  className="w-full h-24 object-contain rounded mb-1"
1180
- title="Click to select makeup"
1181
  />
1182
- <div className="text-xs">Makeup</div>
1183
  </button>
1184
  </div>
1185
  </div>
@@ -1197,9 +1493,6 @@ export function FaceNodeView({ node, onDelete, onUpdate, onStartConnection, onEn
1197
  nodeId={node.id}
1198
  output={node.output}
1199
  downloadFileName={`face-${Date.now()}.png`}
1200
- getNodeHistoryInfo={getNodeHistoryInfo}
1201
- navigateNodeHistory={navigateNodeHistory}
1202
- getCurrentNodeImage={getCurrentNodeImage}
1203
  />
1204
  </div>
1205
  {node.error && (
@@ -1210,24 +1503,65 @@ export function FaceNodeView({ node, onDelete, onUpdate, onStartConnection, onEn
1210
  );
1211
  }
1212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1213
  export function StyleNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
1214
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1215
 
 
 
 
 
1216
  const styleOptions = [
1217
- { value: "90s-anime", label: "90's Anime Style" },
1218
- { value: "mha", label: "My Hero Academia Style" },
1219
- { value: "dbz", label: "Dragon Ball Z Style" },
1220
- { value: "ukiyo-e", label: "Ukiyo-e Style" },
1221
- { value: "cyberpunk", label: "Cyberpunk Style" },
1222
- { value: "steampunk", label: "Steampunk Style" },
1223
- { value: "cubism", label: "Cubism Style" },
1224
- { value: "van-gogh", label: "Post-Impressionist (Van Gogh) Style" },
1225
- { value: "simpsons", label: "Simpsons Style" },
1226
- { value: "family-guy", label: "Family Guy Style" },
1227
- { value: "arcane", label: "Arcane Painterly + Neon Rim Light" },
1228
- { value: "wildwest", label: "Wild West Style" },
1229
- { value: "stranger-things", label: "Stranger Things – 80s Kodak Style" },
1230
- { value: "breaking-bad", label: "Breaking Bad – Dusty Orange & Teal" },
1231
  ];
1232
 
1233
  return (
@@ -1241,7 +1575,7 @@ export function StyleNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1241
  onPointerMove={onPointerMove}
1242
  onPointerUp={onPointerUp}
1243
  >
1244
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
1245
  <div className="font-semibold text-sm flex-1 text-center">STYLE</div>
1246
  <div className="flex items-center gap-1">
1247
  <Button
@@ -1264,6 +1598,7 @@ export function StyleNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1264
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1265
  </div>
1266
  </div>
 
1267
  <div className="p-3 space-y-3">
1268
  {node.input && (
1269
  <div className="flex justify-end mb-2">
@@ -1273,7 +1608,7 @@ export function StyleNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1273
  onClick={() => onUpdate(node.id, { input: undefined })}
1274
  className="text-xs"
1275
  >
1276
- Clear Connection
1277
  </Button>
1278
  </div>
1279
  )}
@@ -1291,31 +1626,38 @@ export function StyleNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1291
  </option>
1292
  ))}
1293
  </Select>
 
1294
  <div>
1295
  <Slider
1296
- label="Style Strength"
1297
- valueLabel={`${node.styleStrength || 50}%`}
1298
- min={0}
1299
- max={100}
1300
- value={node.styleStrength || 50}
1301
- onChange={(e) => onUpdate(node.id, { styleStrength: parseInt((e.target as HTMLInputElement).value) })}
 
 
 
1302
  />
1303
  </div>
 
1304
  <Button
1305
  className="w-full"
1306
- onClick={() => onProcess(node.id)}
1307
- disabled={node.isRunning || !node.stylePreset}
1308
- title={!node.input ? "Connect an input first" : !node.stylePreset ? "Select a style first" : "Apply the style to your input image"}
 
 
 
 
1309
  >
 
1310
  {node.isRunning ? "Applying Style..." : "Apply Style Transfer"}
1311
  </Button>
1312
  <NodeOutputSection
1313
  nodeId={node.id}
1314
  output={node.output}
1315
  downloadFileName={`style-${Date.now()}.png`}
1316
- getNodeHistoryInfo={getNodeHistoryInfo}
1317
- navigateNodeHistory={navigateNodeHistory}
1318
- getCurrentNodeImage={getCurrentNodeImage}
1319
  />
1320
  {node.error && (
1321
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
@@ -1325,17 +1667,64 @@ export function StyleNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1325
  );
1326
  }
1327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1328
  export function LightningNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
1329
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1330
 
 
 
 
 
1331
  const presetLightings = [
1332
- { name: "Studio Light", path: "/lighting/light1.png" },
1333
- { name: "Natural Light", path: "/lighting/light2.png" },
1334
- { name: "Dramatic Light", path: "/lighting/light3.png" },
1335
  ];
1336
 
 
 
 
 
1337
  const selectLighting = (lightingPath: string, lightingName: string) => {
1338
- onUpdate(node.id, { lightingImage: lightingPath, selectedLighting: lightingName });
 
 
 
1339
  };
1340
 
1341
  return (
@@ -1346,7 +1735,7 @@ export function LightningNodeView({ node, onDelete, onUpdate, onStartConnection,
1346
  onPointerMove={onPointerMove}
1347
  onPointerUp={onPointerUp}
1348
  >
1349
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
1350
  <div className="font-semibold text-sm flex-1 text-center">LIGHTNING</div>
1351
  <div className="flex items-center gap-1">
1352
  <Button
@@ -1369,6 +1758,7 @@ export function LightningNodeView({ node, onDelete, onUpdate, onStartConnection,
1369
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1370
  </div>
1371
  </div>
 
1372
  <div className="p-3 space-y-3">
1373
  {node.input && (
1374
  <div className="flex justify-end mb-2">
@@ -1378,7 +1768,7 @@ export function LightningNodeView({ node, onDelete, onUpdate, onStartConnection,
1378
  onClick={() => onUpdate(node.id, { input: undefined })}
1379
  className="text-xs"
1380
  >
1381
- Clear Connection
1382
  </Button>
1383
  </div>
1384
  )}
@@ -1419,9 +1809,6 @@ export function LightningNodeView({ node, onDelete, onUpdate, onStartConnection,
1419
  nodeId={node.id}
1420
  output={node.output}
1421
  downloadFileName={`lightning-${Date.now()}.png`}
1422
- getNodeHistoryInfo={getNodeHistoryInfo}
1423
- navigateNodeHistory={navigateNodeHistory}
1424
- getCurrentNodeImage={getCurrentNodeImage}
1425
  />
1426
  {node.error && (
1427
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
@@ -1432,18 +1819,65 @@ export function LightningNodeView({ node, onDelete, onUpdate, onStartConnection,
1432
  }
1433
 
1434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1435
  export function PosesNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
1436
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1437
 
 
 
 
 
1438
  const presetPoses = [
1439
- { name: "Standing Pose 1", path: "/poses/stand1.png" },
1440
- { name: "Standing Pose 2", path: "/poses/stand2.png" },
1441
- { name: "Sitting Pose 1", path: "/poses/sit1.png" },
1442
- { name: "Sitting Pose 2", path: "/poses/sit2.png" },
1443
  ];
1444
 
 
 
 
 
1445
  const selectPose = (posePath: string, poseName: string) => {
1446
- onUpdate(node.id, { poseImage: posePath, selectedPose: poseName });
 
 
 
1447
  };
1448
 
1449
  return (
@@ -1454,7 +1888,7 @@ export function PosesNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1454
  onPointerMove={onPointerMove}
1455
  onPointerUp={onPointerUp}
1456
  >
1457
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
1458
  <div className="font-semibold text-sm flex-1 text-center">POSES</div>
1459
  <div className="flex items-center gap-1">
1460
  <Button
@@ -1477,6 +1911,7 @@ export function PosesNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1477
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1478
  </div>
1479
  </div>
 
1480
  <div className="p-3 space-y-3">
1481
  {node.input && (
1482
  <div className="flex justify-end mb-2">
@@ -1486,7 +1921,7 @@ export function PosesNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1486
  onClick={() => onUpdate(node.id, { input: undefined })}
1487
  className="text-xs"
1488
  >
1489
- Clear Connection
1490
  </Button>
1491
  </div>
1492
  )}
@@ -1527,9 +1962,6 @@ export function PosesNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1527
  nodeId={node.id}
1528
  output={node.output}
1529
  downloadFileName={`poses-${Date.now()}.png`}
1530
- getNodeHistoryInfo={getNodeHistoryInfo}
1531
- navigateNodeHistory={navigateNodeHistory}
1532
- getCurrentNodeImage={getCurrentNodeImage}
1533
  />
1534
  {node.error && (
1535
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
@@ -1649,7 +2081,7 @@ export function EditNodeView({
1649
  onPointerUp={onPointerUp} // End dragging
1650
  >
1651
  {/* Input port (left side) - where connections come in */}
1652
- <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
1653
 
1654
  {/* Node title */}
1655
  <div className="font-semibold text-sm flex-1 text-center">EDIT</div>
@@ -1674,6 +2106,7 @@ export function EditNodeView({
1674
  </div>
1675
 
1676
  {/* Node Content - Contains all the controls and outputs */}
 
1677
  <div className="p-3 space-y-3">
1678
  {/* Show clear connection button if node has input */}
1679
  {node.input && (
@@ -1685,7 +2118,7 @@ export function EditNodeView({
1685
  className="text-xs"
1686
  title="Remove input connection"
1687
  >
1688
- Clear Connection
1689
  </Button>
1690
  </div>
1691
  )}
@@ -1733,9 +2166,6 @@ export function EditNodeView({
1733
  nodeId={node.id}
1734
  output={node.output}
1735
  downloadFileName={`edit-${Date.now()}.png`}
1736
- getNodeHistoryInfo={getNodeHistoryInfo}
1737
- navigateNodeHistory={navigateNodeHistory}
1738
- getCurrentNodeImage={getCurrentNodeImage}
1739
  />
1740
 
1741
  {/* Error display */}
 
1
  /**
2
+ * NODE COMPONENT VIEWS FOR NANO BANANA EDITOR
3
  *
4
+ * This file contains all the visual node components for the Nano Banana Editor,
5
+ * a visual node-based AI image processing application. Each node represents a
6
+ * specific image transformation or effect that can be chained together to create
7
+ * complex image processing workflows.
 
 
 
8
  *
9
+ * ARCHITECTURE OVERVIEW:
10
+ * - Each node is a self-contained React component with its own state and UI
11
+ * - Nodes use a common dragging system (useNodeDrag hook) for positioning
12
+ * - All nodes follow a consistent structure: Header + Content + Output
13
+ * - Nodes communicate through a connection system using input/output ports
14
+ * - Processing is handled asynchronously with loading states and error handling
15
+ *
16
+ * NODE TYPES AVAILABLE:
17
+ * - BackgroundNodeView: Change/generate image backgrounds (color, preset, upload, AI-generated)
18
+ * - ClothesNodeView: Add/modify clothing on subjects (preset garments or custom uploads)
19
+ * - StyleNodeView: Apply artistic styles and filters (anime, fine art, cinematic styles)
20
+ * - EditNodeView: General text-based image editing (natural language instructions)
21
+ * - CameraNodeView: Apply camera effects and settings (focal length, aperture, film styles)
22
+ * - AgeNodeView: Transform subject age (AI-powered age progression/regression)
23
+ * - FaceNodeView: Modify facial features and accessories (hair, makeup, expressions)
24
+ * - LightningNodeView: Apply professional lighting effects
25
+ * - PosesNodeView: Modify body poses and positioning
26
+ *
27
+ * COMMON PATTERNS:
28
+ * - All nodes support drag-and-drop for repositioning in the editor
29
+ * - Input/output ports allow chaining nodes together in processing pipelines
30
+ * - File upload via drag-drop, file picker, or clipboard paste where applicable
31
+ * - Real-time preview of settings and processed results
32
+ * - History navigation for viewing different processing results
33
+ * - Error handling with user-friendly error messages
34
+ * - AI-powered prompt improvement using Gemini API where applicable
35
+ *
36
+ * USER WORKFLOW:
37
+ * 1. Add nodes to the editor canvas
38
+ * 2. Configure each node's settings (colors, styles, uploaded images, etc.)
39
+ * 3. Connect nodes using input/output ports to create processing chains
40
+ * 4. Process individual nodes or entire chains
41
+ * 5. Preview results, navigate history, and download final images
42
+ *
43
+ * TECHNICAL DETAILS:
44
+ * - Uses React hooks for state management (useState, useEffect, useRef)
45
+ * - Custom useNodeDrag hook handles node positioning and drag interactions
46
+ * - Port component manages connection logic between nodes
47
+ * - All image data is handled as base64 data URLs for browser compatibility
48
+ * - Processing results are cached with history navigation support
49
+ * - Responsive UI components from shadcn/ui component library
50
  */
51
  // Enable React Server Components client-side rendering for this file
52
  "use client";
 
131
  }
132
 
133
  /**
134
+ * REUSABLE OUTPUT SECTION COMPONENT
135
+ *
136
+ * This component provides a standardized output display for all node types.
137
+ * It handles the common functionality that every node needs for showing results:
138
+ *
139
+ * Key Features:
140
+ * - Displays processed output images with click-to-copy functionality
141
+ * - Provides download functionality with custom filenames
142
+ * - Visual feedback when images are copied to clipboard
143
+ * - Consistent styling across all node types
144
+ * - Hover effects and tooltips for better UX
145
+ *
146
+ * User Interactions:
147
+ * - Left-click or right-click image to copy to clipboard
148
+ * - Click download button to save image with timestamp
149
+ * - Visual feedback shows when image is successfully copied
150
+ *
151
+ * Technical Implementation:
152
+ * - Converts images to clipboard-compatible format (PNG)
153
+ * - Uses browser's native download API
154
+ * - Provides visual feedback through temporary styling changes
155
+ * - Handles both base64 data URLs and regular image URLs
156
+ *
157
+ * @param nodeId - Unique identifier for the node (for potential future features)
158
+ * @param output - Optional current output image (base64 data URL or image URL)
159
+ * @param downloadFileName - Filename to use when downloading (should include extension)
160
  */
161
  function NodeOutputSection({
162
  nodeId, // Unique identifier for the node
 
336
  nodeId,
337
  isOutput,
338
  onStartConnection,
339
+ onEndConnection,
340
+ onDisconnect
341
  }: {
342
  className?: string;
343
  nodeId?: string;
344
  isOutput?: boolean;
345
  onStartConnection?: (nodeId: string) => void;
346
  onEndConnection?: (nodeId: string) => void;
347
+ onDisconnect?: (nodeId: string) => void;
348
  }) {
349
  /**
350
  * Handle starting a connection (pointer down on output port)
 
366
  }
367
  };
368
 
369
+ /**
370
+ * Handle clicking on input port to disconnect
371
+ * Allows users to remove connections by clicking on input ports
372
+ */
373
+ const handleClick = (e: React.MouseEvent) => {
374
+ e.stopPropagation(); // Prevent event from bubbling to parent elements
375
+ if (!isOutput && nodeId && onDisconnect) {
376
+ onDisconnect(nodeId); // Disconnect from this input port
377
+ }
378
+ };
379
+
380
  return (
381
  <div
382
+ className={cx("nb-port", className)} // Combine base port classes with custom ones
383
+ onPointerDown={handlePointerDown} // Start connection drag from output ports
384
+ onPointerUp={handlePointerUp} // End connection drag at input ports
385
+ onPointerEnter={handlePointerUp} // Also accept connections on hover (better UX)
386
+ onClick={handleClick} // Allow clicking input ports to disconnect
387
+ title={
388
+ isOutput
389
+ ? "Drag from here to connect to another node's input"
390
+ : "Drop connections here or click to disconnect"
391
+ }
392
  />
393
  );
394
  }
395
 
396
+ /**
397
+ * BACKGROUND NODE VIEW COMPONENT
398
+ *
399
+ * Allows users to change or generate image backgrounds using various methods:
400
+ * - Solid colors with color picker
401
+ * - Preset background images (beach, office, studio, etc.)
402
+ * - Custom uploaded images via file upload or drag/drop
403
+ * - AI-generated backgrounds from text descriptions
404
+ *
405
+ * Key Features:
406
+ * - Multiple background source types (color/preset/upload/custom prompt)
407
+ * - Drag and drop image upload functionality
408
+ * - Paste image from clipboard support
409
+ * - AI-powered prompt improvement using Gemini
410
+ * - Real-time preview of uploaded images
411
+ * - Connection management for node-based workflow
412
+ *
413
+ * @param node - Background node data containing backgroundType, backgroundColor, etc.
414
+ * @param onDelete - Callback to delete this node from the editor
415
+ * @param onUpdate - Callback to update node properties (backgroundType, colors, images, etc.)
416
+ * @param onStartConnection - Callback when user starts dragging from output port
417
+ * @param onEndConnection - Callback when user drops connection on input port
418
+ * @param onProcess - Callback to process this node and apply background changes
419
+ * @param onUpdatePosition - Callback to update node position when dragged
420
+ * @param getNodeHistoryInfo - Function to get processing history for this node
421
+ * @param navigateNodeHistory - Function to navigate through different processing results
422
+ * @param getCurrentNodeImage - Function to get the current processed image
423
+ */
424
  export function BackgroundNodeView({
425
  node,
426
  onDelete,
 
429
  onEndConnection,
430
  onProcess,
431
  onUpdatePosition,
 
 
 
432
  }: any) {
433
+ // Use custom drag hook to handle node positioning in the editor
434
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
435
 
436
+ /**
437
+ * Handle image file upload from file input
438
+ * Converts uploaded file to base64 data URL for storage and preview
439
+ */
440
  const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
441
  if (e.target.files?.length) {
442
+ const reader = new FileReader(); // Create file reader
443
  reader.onload = () => {
444
+ onUpdate(node.id, { customBackgroundImage: reader.result }); // Store base64 data URL
445
  };
446
+ reader.readAsDataURL(e.target.files[0]); // Convert file to base64
447
  }
448
  };
449
 
450
+ /**
451
+ * Handle image paste from clipboard
452
+ * Supports both image files and image URLs pasted from clipboard
453
+ */
454
  const handleImagePaste = (e: React.ClipboardEvent) => {
455
+ const items = e.clipboardData.items; // Get clipboard items
456
+
457
+ // First, try to find image files in clipboard
458
  for (let i = 0; i < items.length; i++) {
459
+ if (items[i].type.startsWith("image/")) { // Check if item is an image
460
+ const file = items[i].getAsFile(); // Get image file
461
  if (file) {
462
+ const reader = new FileReader(); // Create file reader
463
  reader.onload = () => {
464
+ onUpdate(node.id, { customBackgroundImage: reader.result }); // Store base64 data
465
  };
466
+ reader.readAsDataURL(file); // Convert to base64
467
+ return; // Exit early if image found
468
  }
469
  }
470
  }
471
+
472
+ // If no image files, check for text that might be image URLs
473
+ const text = e.clipboardData.getData("text"); // Get text from clipboard
474
  if (text && (text.startsWith("http") || text.startsWith("data:image"))) {
475
+ onUpdate(node.id, { customBackgroundImage: text }); // Use URL directly
476
  }
477
  };
478
 
 
502
  onPointerMove={onPointerMove}
503
  onPointerUp={onPointerUp}
504
  >
505
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
506
  <div className="font-semibold text-sm flex-1 text-center">BACKGROUND</div>
507
  <div className="flex items-center gap-1">
508
  <Button
 
525
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
526
  </div>
527
  </div>
528
+ {/* Node Content Area - Contains all controls, inputs, and outputs */}
529
  <div className="p-3 space-y-3">
530
  {node.input && (
531
  <div className="flex justify-end mb-2">
 
535
  onClick={() => onUpdate(node.id, { input: undefined })}
536
  className="text-xs"
537
  >
538
+ Clear Connection {/* Remove input connection to this node */}
539
  </Button>
540
  </div>
541
  )}
 
664
  nodeId={node.id}
665
  output={node.output}
666
  downloadFileName={`background-${Date.now()}.png`}
 
 
 
667
  />
668
  {node.error && (
669
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
 
673
  );
674
  }
675
 
676
+ /**
677
+ * CLOTHES NODE VIEW COMPONENT
678
+ *
679
+ * Allows users to add or modify clothing on subjects in images.
680
+ * Supports both preset clothing options and custom uploaded garments.
681
+ *
682
+ * Key Features:
683
+ * - Preset clothing gallery (Sukajan, Blazer, Suit, Women's Outfit)
684
+ * - Custom clothing upload via drag/drop, file picker, or clipboard paste
685
+ * - Visual selection interface with thumbnails
686
+ * - Real-time preview of selected clothing
687
+ * - Integration with AI processing pipeline
688
+ *
689
+ * The node processes input images and applies the selected clothing using
690
+ * AI models that understand garment fitting and realistic clothing application.
691
+ *
692
+ * @param node - Clothes node data containing clothesImage, selectedPreset, etc.
693
+ * @param onDelete - Callback to delete this node
694
+ * @param onUpdate - Callback to update node properties
695
+ * @param onStartConnection - Callback when starting connection from output
696
+ * @param onEndConnection - Callback when ending connection at input
697
+ * @param onProcess - Callback to process this node
698
+ * @param onUpdatePosition - Callback to update node position
699
+ * @param getNodeHistoryInfo - Function to get processing history
700
+ * @param navigateNodeHistory - Function to navigate history
701
+ * @param getCurrentNodeImage - Function to get current image
702
+ */
703
  export function ClothesNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
704
+ // Handle node dragging functionality
705
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
706
 
707
+ /**
708
+ * Preset clothing options available for quick selection
709
+ * Each preset includes a display name and path to the reference image
710
+ */
711
  const presetClothes = [
712
+ { name: "Sukajan", path: "/clothes/sukajan.png" }, // Japanese-style embroidered jacket
713
+ { name: "Blazer", path: "/clothes/blazzer.png" }, // Business blazer/jacket
714
+ { name: "Suit", path: "/clothes/suit.png" }, // Formal business suit
715
+ { name: "Women's Outfit", path: "/clothes/womenoutfit.png" }, // Women's clothing ensemble
716
  ];
717
 
718
  const onDrop = async (e: React.DragEvent) => {
 
762
  onPointerMove={onPointerMove}
763
  onPointerUp={onPointerUp}
764
  >
765
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
766
  <div className="font-semibold text-sm flex-1 text-center">CLOTHES</div>
767
  <div className="flex items-center gap-1">
768
  <Button
 
785
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
786
  </div>
787
  </div>
788
+ {/* Node Content Area - Contains all controls, inputs, and outputs */}
789
  <div className="p-3 space-y-3">
790
  {node.input && (
791
  <div className="flex justify-end mb-2">
 
795
  onClick={() => onUpdate(node.id, { input: undefined })}
796
  className="text-xs"
797
  >
798
+ Clear Connection {/* Remove input connection to this node */}
799
  </Button>
800
  </div>
801
  )}
 
868
  nodeId={node.id}
869
  output={node.output}
870
  downloadFileName={`clothes-${Date.now()}.png`}
 
 
 
871
  />
872
  {node.error && (
873
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
 
877
  );
878
  }
879
 
880
+ /**
881
+ * AGE NODE VIEW COMPONENT
882
+ *
883
+ * Allows users to transform the apparent age of subjects in images.
884
+ * Uses AI age transformation models to make people appear older or younger
885
+ * while maintaining facial features and identity.
886
+ *
887
+ * Key Features:
888
+ * - Slider-based age selection (18-100 years)
889
+ * - Real-time age value display
890
+ * - Preserves facial identity during transformation
891
+ * - Smooth age progression/regression
892
+ *
893
+ * The AI models understand facial aging patterns and can:
894
+ * - Add/remove wrinkles and age lines
895
+ * - Adjust skin texture and tone
896
+ * - Modify facial structure subtly
897
+ * - Maintain eye color and basic facial features
898
+ *
899
+ * @param node - Age node data containing targetAge, input, output, etc.
900
+ * @param onDelete - Callback to delete this node
901
+ * @param onUpdate - Callback to update node properties (targetAge)
902
+ * @param onStartConnection - Callback when starting connection from output
903
+ * @param onEndConnection - Callback when ending connection at input
904
+ * @param onProcess - Callback to process age transformation
905
+ * @param onUpdatePosition - Callback to update node position
906
+ * @param getNodeHistoryInfo - Function to get processing history
907
+ * @param navigateNodeHistory - Function to navigate history
908
+ * @param getCurrentNodeImage - Function to get current image
909
+ */
910
  export function AgeNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
911
+ // Handle node dragging functionality
912
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
913
 
914
  return (
 
919
  onPointerMove={onPointerMove}
920
  onPointerUp={onPointerUp}
921
  >
922
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
923
  <div className="font-semibold text-sm flex-1 text-center">AGE</div>
924
  <div className="flex items-center gap-1">
925
  <Button
 
942
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
943
  </div>
944
  </div>
945
+ {/* Node Content Area - Contains all controls, inputs, and outputs */}
946
  <div className="p-3 space-y-3">
947
  {node.input && (
948
  <div className="flex justify-end mb-2">
 
952
  onClick={() => onUpdate(node.id, { input: undefined })}
953
  className="text-xs"
954
  >
955
+ Clear Connection {/* Remove input connection to this node */}
956
  </Button>
957
  </div>
958
  )}
 
978
  nodeId={node.id}
979
  output={node.output}
980
  downloadFileName={`age-${Date.now()}.png`}
 
 
 
981
  />
982
  {node.error && (
983
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
 
987
  );
988
  }
989
 
990
+ /**
991
+ * CAMERA NODE VIEW COMPONENT
992
+ *
993
+ * Applies professional camera settings and photographic effects to images.
994
+ * Simulates various camera equipment, settings, and photographic techniques
995
+ * to achieve specific visual styles and technical characteristics.
996
+ *
997
+ * Key Features:
998
+ * - Complete camera settings simulation (focal length, aperture, shutter speed, ISO)
999
+ * - Film stock emulation (Kodak, Fuji, Ilford, etc.)
1000
+ * - Professional lighting setups (studio, natural, dramatic)
1001
+ * - Composition guides (rule of thirds, golden ratio, etc.)
1002
+ * - Bokeh effects and depth of field control
1003
+ * - Color temperature and white balance adjustment
1004
+ * - Aspect ratio modifications
1005
+ *
1006
+ * Technical Settings Available:
1007
+ * - Focal lengths from fisheye (8mm) to telephoto (400mm)
1008
+ * - Aperture range from f/0.95 to f/22
1009
+ * - Shutter speeds from 1/8000s to 30s
1010
+ * - ISO values from 50 to 12800
1011
+ * - Professional lighting setups
1012
+ * - Film stock characteristics
1013
+ *
1014
+ * @param node - Camera node data containing all camera settings
1015
+ * @param onDelete - Callback to delete this node
1016
+ * @param onUpdate - Callback to update camera settings
1017
+ * @param onStartConnection - Callback when starting connection from output
1018
+ * @param onEndConnection - Callback when ending connection at input
1019
+ * @param onProcess - Callback to process camera effects
1020
+ * @param onUpdatePosition - Callback to update node position
1021
+ * @param getNodeHistoryInfo - Function to get processing history
1022
+ * @param navigateNodeHistory - Function to navigate history
1023
+ * @param getCurrentNodeImage - Function to get current image
1024
+ */
1025
  export function CameraNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
1026
+ // Handle node dragging functionality
1027
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1028
+
1029
+ // Camera lens focal length options (affects field of view and perspective)
1030
+ const focalLengths = ["None", "8mm", "12mm", "24mm", "35mm", "50mm", "85mm"];
1031
+
1032
+ // Aperture settings (affects depth of field and exposure)
1033
+ const apertures = ["None", "f/0.95", "f/1.2", "f/1.4", "f/1.8", "f/2", "f/2.8", "f/4", "f/5.6","f/11"];
1034
+
1035
+ // Shutter speed options (affects motion blur and exposure)
1036
+ const shutterSpeeds = ["None", "1/1000s", "1/250s","1/30s","1/15", "5s", ];
1037
+
1038
+ // White balance presets for different lighting conditions
1039
  const whiteBalances = ["None", "2800K candlelight", "3200K tungsten", "4000K fluorescent", "5600K daylight", "6500K cloudy", "7000K shade", "8000K blue sky"];
1040
+
1041
+ // Camera angle and perspective options
1042
  const angles = ["None", "eye level", "low angle", "high angle", "Dutch tilt", "bird's eye", "worm's eye", "over the shoulder", "POV"];
1043
+
1044
+ // ISO sensitivity values (affects image noise and exposure)
1045
+ const isoValues = ["None", "ISO 100", "ISO 400", "ISO 1600", "ISO 6400"];
1046
+
1047
+ // Film stock emulation for different photographic styles
1048
+ const filmStyles = ["None","RAW","Kodak Portra", "Fuji Velvia", "Kodak Gold 200","Black & White", "Sepia", "Vintage", "Film Noir"];
1049
+
1050
+ // Professional lighting setups and natural lighting conditions
1051
  const lightingTypes = ["None", "Natural Light", "Golden Hour", "Blue Hour", "Studio Lighting", "Rembrandt", "Split Lighting", "Butterfly Lighting", "Loop Lighting", "Rim Lighting", "Silhouette", "High Key", "Low Key"];
1052
+
1053
+ // Bokeh (background blur) styles for different lens characteristics
1054
+ const bokehStyles = ["None", "Smooth Bokeh", "Swirly Bokeh", "Hexagonal Bokeh", "Cat Eye Bokeh", "Bubble Bokeh"];
1055
+
1056
+ // Manual or automatic
1057
+ const manualOrAutomatic = ["None", "AF-S", "AF-C", "AF-A"];
1058
 
1059
  return (
1060
  <div className="nb-node absolute text-white w-[360px]" style={{ left: localPos.x, top: localPos.y }}>
 
1064
  onPointerMove={onPointerMove}
1065
  onPointerUp={onPointerUp}
1066
  >
1067
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
1068
  <div className="font-semibold text-sm flex-1 text-center">CAMERA</div>
1069
  <div className="flex items-center gap-1">
1070
  <Button
 
1096
  onClick={() => onUpdate(node.id, { input: undefined })}
1097
  className="text-xs"
1098
  >
1099
+ Clear Connection {/* Remove input connection to this node */}
1100
  </Button>
1101
  </div>
1102
  )}
1103
+ {/* Basic Camera Settings Section */}
1104
  <div className="text-xs text-white/50 font-semibold mb-1">Basic Settings</div>
1105
+ <div className="grid grid-cols-2 gap-2"> {/* 2-column grid for compact layout */}
1106
+ {/* Automaticormanual Control - affects field of view and perspective */}
1107
+ <div>
1108
+ <label className="text-xs text-white/70">Manual or Automatic</label>
1109
+ <Select
1110
+ className="w-full"
1111
+ value={node.manualOrAutomatic || "None"} // Default to "None" if not set
1112
+ onChange={(e) => onUpdate(node.id, { manualOrAutomatic: (e.target as HTMLSelectElement).value })}
1113
+ title="Select Focus Modes"
1114
+ >
1115
+ {manualOrAutomatic.map(f => <option key={f} value={f}>{f}</option>)}
1116
+ </Select>
1117
+ </div>
1118
+ {/* Focal Length Control - affects field of view and perspective */}
1119
  <div>
1120
  <label className="text-xs text-white/70">Focal Length</label>
1121
  <Select
1122
  className="w-full"
1123
+ value={node.focalLength || "None"} // Default to "None" if not set
1124
  onChange={(e) => onUpdate(node.id, { focalLength: (e.target as HTMLSelectElement).value })}
1125
+ title="Select lens focal length - affects field of view and perspective distortion"
1126
  >
1127
  {focalLengths.map(f => <option key={f} value={f}>{f}</option>)}
1128
  </Select>
1129
  </div>
1130
+
1131
+ {/* Aperture Control - affects depth of field and exposure */}
1132
  <div>
1133
  <label className="text-xs text-white/70">Aperture</label>
1134
  <Select
1135
  className="w-full"
1136
+ value={node.aperture || "None"} // Default to "None" if not set
1137
  onChange={(e) => onUpdate(node.id, { aperture: (e.target as HTMLSelectElement).value })}
1138
+ title="Select aperture value - lower f-numbers create shallower depth of field"
1139
  >
1140
  {apertures.map(a => <option key={a} value={a}>{a}</option>)}
1141
  </Select>
1142
  </div>
1143
+
1144
+ {/* Shutter Speed Control - affects motion blur and exposure */}
1145
  <div>
1146
  <label className="text-xs text-white/70">Shutter Speed</label>
1147
  <Select
1148
  className="w-full"
1149
+ value={node.shutterSpeed || "None"} // Default to "None" if not set
1150
  onChange={(e) => onUpdate(node.id, { shutterSpeed: (e.target as HTMLSelectElement).value })}
1151
+ title="Select shutter speed - faster speeds freeze motion, slower speeds create blur"
1152
  >
1153
  {shutterSpeeds.map(s => <option key={s} value={s}>{s}</option>)}
1154
  </Select>
1155
  </div>
1156
+
1157
+ {/* ISO Control - affects sensor sensitivity and image noise */}
1158
  <div>
1159
  <label className="text-xs text-white/70">ISO</label>
1160
  <Select
1161
  className="w-full"
1162
+ value={node.iso || "None"} // Default to "None" if not set
1163
  onChange={(e) => onUpdate(node.id, { iso: (e.target as HTMLSelectElement).value })}
1164
+ title="Select ISO value - higher values increase sensitivity but add noise"
1165
  >
1166
  {isoValues.map(i => <option key={i} value={i}>{i}</option>)}
1167
  </Select>
 
1226
  {angles.map(a => <option key={a} value={a}>{a}</option>)}
1227
  </Select>
1228
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1229
  </div>
1230
  <Button
1231
  className="w-full"
 
1240
  nodeId={node.id}
1241
  output={node.output}
1242
  downloadFileName={`camera-${Date.now()}.png`}
 
 
 
1243
  />
1244
  </div>
1245
  {node.error && (
 
1250
  );
1251
  }
1252
 
1253
+ /**
1254
+ * FACE NODE VIEW COMPONENT
1255
+ *
1256
+ * Provides comprehensive facial feature modification capabilities.
1257
+ * Allows users to change various aspects of faces in images including
1258
+ * hairstyles, expressions, facial hair, accessories, and makeup.
1259
+ *
1260
+ * Key Features:
1261
+ * - Hairstyle modifications (short, long, curly, straight, etc.)
1262
+ * - Facial expression changes (happy, sad, surprised, etc.)
1263
+ * - Beard and mustache styling options
1264
+ * - Accessory addition (sunglasses, hats)
1265
+ * - Makeup application with preset styles
1266
+ * - Skin enhancement (pimple removal)
1267
+ *
1268
+ * The AI models can:
1269
+ * - Preserve facial identity while making changes
1270
+ * - Apply realistic hair textures and colors
1271
+ * - Generate natural-looking expressions
1272
+ * - Add accessories that fit properly
1273
+ * - Apply makeup that matches lighting and skin tone
1274
+ *
1275
+ * @param node - Face node data containing all face modification settings
1276
+ * @param onDelete - Callback to delete this node
1277
+ * @param onUpdate - Callback to update face settings
1278
+ * @param onStartConnection - Callback when starting connection from output
1279
+ * @param onEndConnection - Callback when ending connection at input
1280
+ * @param onProcess - Callback to process face modifications
1281
+ * @param onUpdatePosition - Callback to update node position
1282
+ * @param getNodeHistoryInfo - Function to get processing history
1283
+ * @param navigateNodeHistory - Function to navigate history
1284
+ * @param getCurrentNodeImage - Function to get current image
1285
+ */
1286
  export function FaceNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
1287
+ // Handle node dragging functionality
1288
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1289
+
1290
+ // Available hairstyle options for hair modification
1291
  const hairstyles = ["None", "short", "long", "curly", "straight", "bald", "mohawk", "ponytail"];
1292
+
1293
+ // Facial expression options for emotion changes
1294
  const expressions = ["None", "happy", "serious", "smiling", "laughing", "sad", "surprised", "angry"];
1295
+
1296
+ // Beard and facial hair styling options
1297
  const beardStyles = ["None", "stubble", "goatee", "full beard", "mustache", "clean shaven"];
1298
 
1299
  return (
 
1304
  onPointerMove={onPointerMove}
1305
  onPointerUp={onPointerUp}
1306
  >
1307
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
1308
  <div className="font-semibold text-sm flex-1 text-center">FACE</div>
1309
  <div className="flex items-center gap-1">
1310
  <Button
 
1336
  onClick={() => onUpdate(node.id, { input: undefined })}
1337
  className="text-xs"
1338
  >
1339
+ Clear Connection {/* Remove input connection to this node */}
1340
  </Button>
1341
  </div>
1342
  )}
1343
+ {/* Face Enhancement Checkboxes - toggleable options for face improvements and accessories */}
1344
  <div className="space-y-2">
1345
+ {/* Pimple removal option for skin enhancement */}
1346
+ <label className="flex items-center gap-2 text-xs cursor-pointer">
1347
  <Checkbox
1348
+ checked={node.faceOptions?.removePimples || false} // Default to false if not set
1349
  onChange={(e) => onUpdate(node.id, {
1350
+ faceOptions: {
1351
+ ...node.faceOptions, // Preserve existing options
1352
+ removePimples: (e.target as HTMLInputElement).checked // Update pimple removal setting
1353
+ }
1354
  })}
1355
  />
1356
+ Remove pimples {/* Clean up skin imperfections */}
1357
  </label>
1358
+
1359
+ {/* Sunglasses addition option */}
1360
+ <label className="flex items-center gap-2 text-xs cursor-pointer">
1361
  <Checkbox
1362
+ checked={node.faceOptions?.addSunglasses || false} // Default to false if not set
1363
  onChange={(e) => onUpdate(node.id, {
1364
+ faceOptions: {
1365
+ ...node.faceOptions, // Preserve existing options
1366
+ addSunglasses: (e.target as HTMLInputElement).checked // Update sunglasses setting
1367
+ }
1368
  })}
1369
  />
1370
+ Add sunglasses {/* Add stylish sunglasses accessory */}
1371
  </label>
1372
+
1373
+ {/* Hat addition option */}
1374
+ <label className="flex items-center gap-2 text-xs cursor-pointer">
1375
  <Checkbox
1376
+ checked={node.faceOptions?.addHat || false} // Default to false if not set
1377
  onChange={(e) => onUpdate(node.id, {
1378
+ faceOptions: {
1379
+ ...node.faceOptions, // Preserve existing options
1380
+ addHat: (e.target as HTMLInputElement).checked // Update hat setting
1381
+ }
1382
  })}
1383
  />
1384
+ Add hat {/* Add hat accessory */}
1385
  </label>
1386
  </div>
1387
 
 
1424
  </Select>
1425
  </div>
1426
 
1427
+ {/* Makeup Selection Section - allows users to choose makeup application */}
1428
  <div>
1429
  <label className="text-xs text-white/70">Makeup</label>
1430
+ <div className="grid grid-cols-2 gap-2 mt-2"> {/* 2-column grid for makeup options */}
1431
+
1432
+ {/* No Makeup Option - removes or prevents makeup application */}
1433
  <button
1434
+ className={`p-1 rounded border transition-colors ${
1435
  !node.faceOptions?.selectedMakeup || node.faceOptions?.selectedMakeup === "None"
1436
+ ? "border-indigo-400 bg-indigo-500/20" // Highlighted when selected
1437
+ : "border-white/20 hover:border-white/40" // Default and hover states
1438
  }`}
1439
  onClick={() => onUpdate(node.id, {
1440
+ faceOptions: {
1441
+ ...node.faceOptions, // Preserve other face options
1442
+ selectedMakeup: "None", // Set makeup to none
1443
+ makeupImage: null // Clear makeup image reference
1444
+ }
1445
  })}
1446
+ title="No makeup application - natural look"
1447
  >
1448
+ {/* Visual placeholder for no makeup option */}
1449
  <div className="w-full h-24 flex items-center justify-center text-xs text-white/60 border border-dashed border-white/20 rounded mb-1">
1450
+ No Makeup {/* Text indicator for no makeup */}
1451
  </div>
1452
+ <div className="text-xs">None</div> {/* Option label */}
1453
  </button>
1454
+
1455
+ {/* Makeup Application Option - applies preset makeup style */}
1456
  <button
1457
+ className={`p-1 rounded border transition-colors ${
1458
  node.faceOptions?.selectedMakeup === "Makeup"
1459
+ ? "border-indigo-400 bg-indigo-500/20" // Highlighted when selected
1460
+ : "border-white/20 hover:border-white/40" // Default and hover states
1461
  }`}
1462
  onClick={() => onUpdate(node.id, {
1463
+ faceOptions: {
1464
+ ...node.faceOptions, // Preserve other face options
1465
+ selectedMakeup: "Makeup", // Set makeup type
1466
+ makeupImage: "/makeup/makeup1.png" // Reference image for makeup style
1467
+ }
1468
  })}
1469
+ title="Apply makeup style - enhances facial features"
1470
  >
1471
+ {/* Makeup preview image */}
1472
  <img
1473
  src="/makeup/makeup1.png"
1474
+ alt="Makeup Style Preview"
1475
  className="w-full h-24 object-contain rounded mb-1"
1476
+ title="Preview of makeup style that will be applied"
1477
  />
1478
+ <div className="text-xs">Makeup</div> {/* Option label */}
1479
  </button>
1480
  </div>
1481
  </div>
 
1493
  nodeId={node.id}
1494
  output={node.output}
1495
  downloadFileName={`face-${Date.now()}.png`}
 
 
 
1496
  />
1497
  </div>
1498
  {node.error && (
 
1503
  );
1504
  }
1505
 
1506
+ /**
1507
+ * STYLE NODE VIEW COMPONENT
1508
+ *
1509
+ * Applies artistic style transfer to images, transforming them to match
1510
+ * various artistic movements, pop culture aesthetics, and visual styles.
1511
+ *
1512
+ * Key Features:
1513
+ * - Wide variety of artistic styles (anime, fine art, pop culture)
1514
+ * - Adjustable style strength for subtle or dramatic transformations
1515
+ * - Preserves original image content while applying style characteristics
1516
+ * - Real-time style preview and processing
1517
+ *
1518
+ * Style Categories Available:
1519
+ * - Anime styles (90s anime, My Hero Academia, Dragon Ball Z)
1520
+ * - Fine art movements (Ukiyo-e, Cubism, Post-Impressionism)
1521
+ * - Modern aesthetics (Cyberpunk, Steampunk)
1522
+ * - Pop culture (Simpsons, Family Guy, Arcane)
1523
+ * - Cinematic styles (Breaking Bad, Stranger Things)
1524
+ *
1525
+ * The AI style transfer models can:
1526
+ * - Apply artistic brushstrokes and textures
1527
+ * - Adapt color palettes to match target styles
1528
+ * - Maintain subject recognition while stylizing
1529
+ * - Handle various image compositions and subjects
1530
+ *
1531
+ * @param node - Style node data containing stylePreset, styleStrength, etc.
1532
+ * @param onDelete - Callback to delete this node
1533
+ * @param onUpdate - Callback to update style settings
1534
+ * @param onStartConnection - Callback when starting connection from output
1535
+ * @param onEndConnection - Callback when ending connection at input
1536
+ * @param onProcess - Callback to process style transfer
1537
+ * @param onUpdatePosition - Callback to update node position
1538
+ * @param getNodeHistoryInfo - Function to get processing history
1539
+ * @param navigateNodeHistory - Function to navigate history
1540
+ * @param getCurrentNodeImage - Function to get current image
1541
+ */
1542
  export function StyleNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
1543
+ // Handle node dragging functionality
1544
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1545
 
1546
+ /**
1547
+ * Available artistic style options with descriptive labels
1548
+ * Each style represents a different artistic movement or pop culture aesthetic
1549
+ */
1550
  const styleOptions = [
1551
+ { value: "90s-anime", label: "90's Anime Style" }, // Classic 90s anime art style
1552
+ { value: "gibhli", label: "Gibhli Style" },
1553
+ { value: "mha", label: "My Hero Academia Style" }, // Modern anime style
1554
+ { value: "dbz", label: "Dragon Ball Z Style" }, // Iconic manga/anime style
1555
+ { value: "ukiyo-e", label: "Ukiyo-e Style" }, // Traditional Japanese woodblock prints
1556
+ { value: "cyberpunk", label: "Cyberpunk Style" }, // Futuristic neon aesthetic
1557
+ { value: "steampunk", label: "Steampunk Style" }, // Victorian-era industrial aesthetic
1558
+ { value: "cubism", label: "Cubism Style" }, // Picasso-style geometric art
1559
+ { value: "van-gogh", label: "Post-Impressionist (Van Gogh) Style" }, // Van Gogh's distinctive brushwork
1560
+ { value: "simpsons", label: "Simpsons Style" }, // Cartoon animation style
1561
+ { value: "family-guy", label: "Family Guy Style" }, // Modern cartoon animation // Netflix series visual style
1562
+ { value: "wildwest", label: "Wild West Style" },// Cinematic color grading
1563
+ { value: "star-wars", label: "Star Wars Style" },
1564
+ { value: "star-trek", label: "Star Trek Style" },
1565
  ];
1566
 
1567
  return (
 
1575
  onPointerMove={onPointerMove}
1576
  onPointerUp={onPointerUp}
1577
  >
1578
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
1579
  <div className="font-semibold text-sm flex-1 text-center">STYLE</div>
1580
  <div className="flex items-center gap-1">
1581
  <Button
 
1598
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1599
  </div>
1600
  </div>
1601
+ {/* Node Content Area - Contains all controls, inputs, and outputs */}
1602
  <div className="p-3 space-y-3">
1603
  {node.input && (
1604
  <div className="flex justify-end mb-2">
 
1608
  onClick={() => onUpdate(node.id, { input: undefined })}
1609
  className="text-xs"
1610
  >
1611
+ Clear Connection {/* Remove input connection to this node */}
1612
  </Button>
1613
  </div>
1614
  )}
 
1626
  </option>
1627
  ))}
1628
  </Select>
1629
+ {/* Style Strength Slider - controls how strongly the style is applied */}
1630
  <div>
1631
  <Slider
1632
+ label="Style Strength" // Slider label
1633
+ valueLabel={`${node.styleStrength || 50}%`} // Display current percentage value
1634
+ min={0} // Minimum strength (subtle effect)
1635
+ max={100} // Maximum strength (full style transfer)
1636
+ value={node.styleStrength || 50} // Current value (default 50%)
1637
+ onChange={(e) => onUpdate(node.id, {
1638
+ styleStrength: parseInt((e.target as HTMLInputElement).value) // Update strength value
1639
+ })}
1640
+ title="Adjust how strongly the artistic style is applied - lower values are more subtle"
1641
  />
1642
  </div>
1643
+ {/* Style Processing Button - triggers the style transfer operation */}
1644
  <Button
1645
  className="w-full"
1646
+ onClick={() => onProcess(node.id)} // Start style transfer processing
1647
+ disabled={node.isRunning || !node.stylePreset} // Disable if processing or no style selected
1648
+ title={
1649
+ !node.input ? "Connect an input first" : // No input connection
1650
+ !node.stylePreset ? "Select a style first" : // No style selected
1651
+ "Apply the selected artistic style to your input image" // Ready to process
1652
+ }
1653
  >
1654
+ {/* Dynamic button text based on processing state */}
1655
  {node.isRunning ? "Applying Style..." : "Apply Style Transfer"}
1656
  </Button>
1657
  <NodeOutputSection
1658
  nodeId={node.id}
1659
  output={node.output}
1660
  downloadFileName={`style-${Date.now()}.png`}
 
 
 
1661
  />
1662
  {node.error && (
1663
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
 
1667
  );
1668
  }
1669
 
1670
+ /**
1671
+ * LIGHTNING NODE VIEW COMPONENT
1672
+ *
1673
+ * Applies professional lighting effects to images to enhance mood,
1674
+ * atmosphere, and visual impact. Simulates various lighting setups
1675
+ * commonly used in photography and cinematography.
1676
+ *
1677
+ * Key Features:
1678
+ * - Professional lighting presets (studio, natural, dramatic)
1679
+ * - Visual preset selection with thumbnails
1680
+ * - Realistic lighting simulation
1681
+ * - Shadow and highlight adjustment
1682
+ *
1683
+ * Lighting Types Available:
1684
+ * - Studio Light: Controlled, even lighting for professional portraits
1685
+ * - Natural Light: Soft, organic lighting that mimics daylight
1686
+ * - Dramatic Light: High-contrast lighting for artistic effect
1687
+ *
1688
+ * The lighting effects can:
1689
+ * - Add realistic shadows and highlights
1690
+ * - Enhance subject dimensionality
1691
+ * - Create mood and atmosphere
1692
+ * - Simulate professional lighting equipment
1693
+ *
1694
+ * @param node - Lightning node data containing selectedLighting, lightingImage
1695
+ * @param onDelete - Callback to delete this node
1696
+ * @param onUpdate - Callback to update lighting settings
1697
+ * @param onStartConnection - Callback when starting connection from output
1698
+ * @param onEndConnection - Callback when ending connection at input
1699
+ * @param onProcess - Callback to process lighting effects
1700
+ * @param onUpdatePosition - Callback to update node position
1701
+ * @param getNodeHistoryInfo - Function to get processing history
1702
+ * @param navigateNodeHistory - Function to navigate history
1703
+ * @param getCurrentNodeImage - Function to get current image
1704
+ */
1705
  export function LightningNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
1706
+ // Handle node dragging functionality
1707
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1708
 
1709
+ /**
1710
+ * Available lighting preset options with reference images
1711
+ * Each preset demonstrates a different lighting setup and mood
1712
+ */
1713
  const presetLightings = [
1714
+ { name: "Studio Light", path: "/lighting/light1.png" }, // Professional studio lighting
1715
+ { name: "Natural Light", path: "/lighting/light2.png" }, // Soft natural daylight
1716
+ { name: "Dramatic Light", path: "/lighting/light3.png" }, // High-contrast dramatic lighting
1717
  ];
1718
 
1719
+ /**
1720
+ * Handle selection of a lighting preset
1721
+ * Updates both the lighting image path and the selected preset name
1722
+ */
1723
  const selectLighting = (lightingPath: string, lightingName: string) => {
1724
+ onUpdate(node.id, {
1725
+ lightingImage: lightingPath, // Path to lighting reference image
1726
+ selectedLighting: lightingName // Name of selected lighting preset
1727
+ });
1728
  };
1729
 
1730
  return (
 
1735
  onPointerMove={onPointerMove}
1736
  onPointerUp={onPointerUp}
1737
  >
1738
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
1739
  <div className="font-semibold text-sm flex-1 text-center">LIGHTNING</div>
1740
  <div className="flex items-center gap-1">
1741
  <Button
 
1758
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1759
  </div>
1760
  </div>
1761
+ {/* Node Content Area - Contains all controls, inputs, and outputs */}
1762
  <div className="p-3 space-y-3">
1763
  {node.input && (
1764
  <div className="flex justify-end mb-2">
 
1768
  onClick={() => onUpdate(node.id, { input: undefined })}
1769
  className="text-xs"
1770
  >
1771
+ Clear Connection {/* Remove input connection to this node */}
1772
  </Button>
1773
  </div>
1774
  )}
 
1809
  nodeId={node.id}
1810
  output={node.output}
1811
  downloadFileName={`lightning-${Date.now()}.png`}
 
 
 
1812
  />
1813
  {node.error && (
1814
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
 
1819
  }
1820
 
1821
 
1822
+ /**
1823
+ * POSES NODE VIEW COMPONENT
1824
+ *
1825
+ * Modifies the pose and body positioning of subjects in images.
1826
+ * Uses AI-powered pose estimation and transfer to change how people
1827
+ * are positioned while maintaining natural proportions and anatomy.
1828
+ *
1829
+ * Key Features:
1830
+ * - Multiple preset poses (standing, sitting variations)
1831
+ * - Visual pose selection with reference thumbnails
1832
+ * - Natural pose transfer that preserves identity
1833
+ * - Anatomically correct pose adjustments
1834
+ *
1835
+ * Pose Categories Available:
1836
+ * - Standing poses: Various upright positions and postures
1837
+ * - Sitting poses: Different seated positions and arrangements
1838
+ *
1839
+ * The AI pose models can:
1840
+ * - Detect and map human body keypoints
1841
+ * - Transfer poses while maintaining proportions
1842
+ * - Adjust clothing to fit new poses naturally
1843
+ * - Preserve facial features and identity
1844
+ * - Handle complex body positioning
1845
+ *
1846
+ * @param node - Poses node data containing selectedPose, poseImage
1847
+ * @param onDelete - Callback to delete this node
1848
+ * @param onUpdate - Callback to update pose settings
1849
+ * @param onStartConnection - Callback when starting connection from output
1850
+ * @param onEndConnection - Callback when ending connection at input
1851
+ * @param onProcess - Callback to process pose modifications
1852
+ * @param onUpdatePosition - Callback to update node position
1853
+ * @param getNodeHistoryInfo - Function to get processing history
1854
+ * @param navigateNodeHistory - Function to navigate history
1855
+ * @param getCurrentNodeImage - Function to get current image
1856
+ */
1857
  export function PosesNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
1858
+ // Handle node dragging functionality
1859
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1860
 
1861
+ /**
1862
+ * Available pose preset options with reference images
1863
+ * Each preset shows a different body position or posture
1864
+ */
1865
  const presetPoses = [
1866
+ { name: "Standing Pose 1", path: "/poses/stand1.png" }, // First standing position variant
1867
+ { name: "Standing Pose 2", path: "/poses/stand2.png" }, // Second standing position variant
1868
+ { name: "Sitting Pose 1", path: "/poses/sit1.png" }, // First sitting position variant
1869
+ { name: "Sitting Pose 2", path: "/poses/sit2.png" }, // Second sitting position variant
1870
  ];
1871
 
1872
+ /**
1873
+ * Handle selection of a pose preset
1874
+ * Updates both the pose reference image and the selected pose name
1875
+ */
1876
  const selectPose = (posePath: string, poseName: string) => {
1877
+ onUpdate(node.id, {
1878
+ poseImage: posePath, // Path to pose reference image
1879
+ selectedPose: poseName // Name of selected pose preset
1880
+ });
1881
  };
1882
 
1883
  return (
 
1888
  onPointerMove={onPointerMove}
1889
  onPointerUp={onPointerUp}
1890
  >
1891
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
1892
  <div className="font-semibold text-sm flex-1 text-center">POSES</div>
1893
  <div className="flex items-center gap-1">
1894
  <Button
 
1911
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1912
  </div>
1913
  </div>
1914
+ {/* Node Content Area - Contains all controls, inputs, and outputs */}
1915
  <div className="p-3 space-y-3">
1916
  {node.input && (
1917
  <div className="flex justify-end mb-2">
 
1921
  onClick={() => onUpdate(node.id, { input: undefined })}
1922
  className="text-xs"
1923
  >
1924
+ Clear Connection {/* Remove input connection to this node */}
1925
  </Button>
1926
  </div>
1927
  )}
 
1962
  nodeId={node.id}
1963
  output={node.output}
1964
  downloadFileName={`poses-${Date.now()}.png`}
 
 
 
1965
  />
1966
  {node.error && (
1967
  <div className="text-xs text-red-400 mt-2">{node.error}</div>
 
2081
  onPointerUp={onPointerUp} // End dragging
2082
  >
2083
  {/* Input port (left side) - where connections come in */}
2084
+ <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} onDisconnect={(nodeId) => onUpdate(nodeId, { input: undefined })} />
2085
 
2086
  {/* Node title */}
2087
  <div className="font-semibold text-sm flex-1 text-center">EDIT</div>
 
2106
  </div>
2107
 
2108
  {/* Node Content - Contains all the controls and outputs */}
2109
+ {/* Node Content Area - Contains all controls, inputs, and outputs */}
2110
  <div className="p-3 space-y-3">
2111
  {/* Show clear connection button if node has input */}
2112
  {node.input && (
 
2118
  className="text-xs"
2119
  title="Remove input connection"
2120
  >
2121
+ Clear Connection {/* Remove input connection to this node */}
2122
  </Button>
2123
  </div>
2124
  )}
 
2166
  nodeId={node.id}
2167
  output={node.output}
2168
  downloadFileName={`edit-${Date.now()}.png`}
 
 
 
2169
  />
2170
 
2171
  {/* Error display */}