Reubencf commited on
Commit
4bcdacd
Β·
1 Parent(s): 4934e5b

lets see whats fixed

Browse files
.claude/settings.local.json CHANGED
@@ -11,7 +11,10 @@
11
  "Bash(git add:*)",
12
  "Bash(git commit:*)",
13
  "Bash(git push:*)",
14
- "WebSearch"
 
 
 
15
  ],
16
  "deny": [],
17
  "ask": []
 
11
  "Bash(git add:*)",
12
  "Bash(git commit:*)",
13
  "Bash(git push:*)",
14
+ "WebSearch",
15
+ "Read(//Users/reubenfernandes/Desktop/**)",
16
+ "mcp__puppeteer__puppeteer_click",
17
+ "mcp__browser-tools__getConsoleErrors"
18
  ],
19
  "deny": [],
20
  "ask": []
app/nodes.tsx CHANGED
@@ -26,7 +26,6 @@ import React, { useState, useRef, useEffect } from "react";
26
  import { Button } from "../components/ui/button";
27
  import { Select } from "../components/ui/select";
28
  import { Textarea } from "../components/ui/textarea";
29
- import { Label } from "../components/ui/label";
30
  import { Slider } from "../components/ui/slider";
31
  import { ColorPicker } from "../components/ui/color-picker";
32
  import { Checkbox } from "../components/ui/checkbox";
@@ -57,9 +56,35 @@ async function copyImageToClipboard(dataUrl: string) {
57
  try {
58
  const response = await fetch(dataUrl);
59
  const blob = await response.blob();
60
- await navigator.clipboard.write([
61
- new ClipboardItem({ [blob.type]: blob })
62
- ]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  } catch (error) {
64
  console.error('Failed to copy image to clipboard:', error);
65
  }
@@ -1537,105 +1562,196 @@ export function PosesNodeView({ node, onDelete, onUpdate, onStartConnection, onE
1537
  );
1538
  }
1539
 
1540
- export function EditNodeView({ node, onDelete, onUpdate, onStartConnection, onEndConnection, onProcess, onUpdatePosition, getNodeHistoryInfo, navigateNodeHistory, getCurrentNodeImage }: any) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1541
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1542
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1543
  return (
1544
  <div className="nb-node absolute text-white w-[320px]" style={{ left: localPos.x, top: localPos.y }}>
 
1545
  <div
1546
  className="nb-header px-3 py-2 flex items-center justify-between rounded-t-[14px] cursor-grab active:cursor-grabbing"
1547
- onPointerDown={onPointerDown}
1548
- onPointerMove={onPointerMove}
1549
- onPointerUp={onPointerUp}
1550
  >
 
1551
  <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
 
 
1552
  <div className="font-semibold text-sm flex-1 text-center">EDIT</div>
 
1553
  <div className="flex items-center gap-1">
 
1554
  <Button
1555
  variant="ghost"
1556
  size="icon"
1557
  className="text-destructive hover:bg-destructive/20 h-6 w-6"
1558
- onClick={(e) => {
1559
- e.stopPropagation();
1560
- e.preventDefault();
1561
- if (confirm('Delete this node?')) {
1562
- onDelete(node.id);
1563
- }
1564
- }}
1565
- onPointerDown={(e) => e.stopPropagation()}
1566
  title="Delete node"
1567
  aria-label="Delete node"
1568
  >
1569
  Γ—
1570
  </Button>
 
 
1571
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1572
  </div>
1573
  </div>
 
 
1574
  <div className="p-3 space-y-3">
 
1575
  {node.input && (
1576
  <div className="flex justify-end mb-2">
1577
  <Button
1578
  variant="ghost"
1579
  size="sm"
1580
- onClick={() => onUpdate(node.id, { input: undefined })}
1581
  className="text-xs"
 
1582
  >
1583
  Clear Connection
1584
  </Button>
1585
  </div>
1586
  )}
 
 
1587
  <div className="space-y-2">
 
1588
  <Textarea
1589
  className="w-full"
1590
  placeholder="Describe what to edit (e.g., 'make it brighter', 'add more contrast', 'make it look vintage')"
1591
  value={node.editPrompt || ""}
1592
- onChange={(e) => onUpdate(node.id, { editPrompt: (e.target as HTMLTextAreaElement).value })}
1593
  rows={3}
1594
  />
 
 
1595
  <Button
1596
  variant="outline"
1597
  size="sm"
1598
  className="w-full text-xs"
1599
- onClick={async () => {
1600
- if (!node.editPrompt) {
1601
- alert('Please enter an edit description first');
1602
- return;
1603
- }
1604
-
1605
- try {
1606
- const response = await fetch('/api/improve-prompt', {
1607
- method: 'POST',
1608
- headers: { 'Content-Type': 'application/json' },
1609
- body: JSON.stringify({
1610
- prompt: node.editPrompt,
1611
- type: 'edit'
1612
- })
1613
- });
1614
-
1615
- if (response.ok) {
1616
- const { improvedPrompt } = await response.json();
1617
- onUpdate(node.id, { editPrompt: improvedPrompt });
1618
- } else {
1619
- alert('Failed to improve prompt. Please try again.');
1620
- }
1621
- } catch (error) {
1622
- console.error('Error improving prompt:', error);
1623
- alert('Failed to improve prompt. Please try again.');
1624
- }
1625
- }}
1626
  title="Use Gemini 2.5 Flash to improve your edit prompt"
 
1627
  >
1628
  ✨ Improve with Gemini
1629
  </Button>
1630
  </div>
 
 
1631
  <Button
1632
  className="w-full"
1633
  onClick={() => onProcess(node.id)}
1634
- disabled={node.isRunning}
1635
- title={!node.input ? "Connect an input first" : "Process all unprocessed nodes in chain"}
 
 
 
 
1636
  >
1637
  {node.isRunning ? "Processing..." : "Apply Edit"}
1638
  </Button>
 
 
1639
  <NodeOutputSection
1640
  nodeId={node.id}
1641
  output={node.output}
@@ -1644,8 +1760,12 @@ export function EditNodeView({ node, onDelete, onUpdate, onStartConnection, onEn
1644
  navigateNodeHistory={navigateNodeHistory}
1645
  getCurrentNodeImage={getCurrentNodeImage}
1646
  />
 
 
1647
  {node.error && (
1648
- <div className="text-xs text-red-400 mt-2">{node.error}</div>
 
 
1649
  )}
1650
  </div>
1651
  </div>
 
26
  import { Button } from "../components/ui/button";
27
  import { Select } from "../components/ui/select";
28
  import { Textarea } from "../components/ui/textarea";
 
29
  import { Slider } from "../components/ui/slider";
30
  import { ColorPicker } from "../components/ui/color-picker";
31
  import { Checkbox } from "../components/ui/checkbox";
 
56
  try {
57
  const response = await fetch(dataUrl);
58
  const blob = await response.blob();
59
+
60
+ // Convert to PNG if not already PNG (clipboard API only supports PNG for images)
61
+ if (blob.type !== 'image/png') {
62
+ const canvas = document.createElement('canvas');
63
+ const ctx = canvas.getContext('2d');
64
+ const img = new Image();
65
+
66
+ await new Promise((resolve) => {
67
+ img.onload = () => {
68
+ canvas.width = img.width;
69
+ canvas.height = img.height;
70
+ ctx?.drawImage(img, 0, 0);
71
+ resolve(void 0);
72
+ };
73
+ img.src = dataUrl;
74
+ });
75
+
76
+ const pngBlob = await new Promise<Blob>((resolve) => {
77
+ canvas.toBlob((blob) => resolve(blob!), 'image/png');
78
+ });
79
+
80
+ await navigator.clipboard.write([
81
+ new ClipboardItem({ 'image/png': pngBlob })
82
+ ]);
83
+ } else {
84
+ await navigator.clipboard.write([
85
+ new ClipboardItem({ 'image/png': blob })
86
+ ]);
87
+ }
88
  } catch (error) {
89
  console.error('Failed to copy image to clipboard:', error);
90
  }
 
1562
  );
1563
  }
1564
 
1565
+ /**
1566
+ * EDIT NODE VIEW COMPONENT
1567
+ *
1568
+ * This node allows users to perform general text-based image editing operations.
1569
+ * Users can describe what they want to change about an image using natural language,
1570
+ * and the AI will attempt to apply those changes.
1571
+ *
1572
+ * Features:
1573
+ * - Natural language editing prompts (e.g., "make it brighter", "add vintage effect")
1574
+ * - AI-powered prompt improvement using Gemini
1575
+ * - Real-time editing processing
1576
+ * - Output history with navigation
1577
+ * - Connection management for input/output workflow
1578
+ *
1579
+ * @param node - The edit node data containing editPrompt, input, output, etc.
1580
+ * @param onDelete - Callback to delete this node
1581
+ * @param onUpdate - Callback to update node properties
1582
+ * @param onStartConnection - Callback when starting a connection from output port
1583
+ * @param onEndConnection - Callback when ending a connection at input port
1584
+ * @param onProcess - Callback to process this node
1585
+ * @param onUpdatePosition - Callback to update node position when dragged
1586
+ * @param getNodeHistoryInfo - Function to get history information for this node
1587
+ * @param navigateNodeHistory - Function to navigate through node history
1588
+ * @param getCurrentNodeImage - Function to get the current image for this node
1589
+ */
1590
+ export function EditNodeView({
1591
+ node,
1592
+ onDelete,
1593
+ onUpdate,
1594
+ onStartConnection,
1595
+ onEndConnection,
1596
+ onProcess,
1597
+ onUpdatePosition,
1598
+ getNodeHistoryInfo,
1599
+ navigateNodeHistory,
1600
+ getCurrentNodeImage
1601
+ }: any) {
1602
+ // Use custom hook for drag functionality - handles position updates during dragging
1603
  const { localPos, onPointerDown, onPointerMove, onPointerUp } = useNodeDrag(node, onUpdatePosition);
1604
 
1605
+ /**
1606
+ * Handle prompt improvement using Gemini API
1607
+ * Takes the user's basic edit description and enhances it for better AI processing
1608
+ */
1609
+ const handlePromptImprovement = async () => {
1610
+ // Validate that user has entered a prompt
1611
+ if (!node.editPrompt?.trim()) {
1612
+ alert('Please enter an edit description first');
1613
+ return;
1614
+ }
1615
+
1616
+ try {
1617
+ // Call the API to improve the prompt
1618
+ const response = await fetch('/api/improve-prompt', {
1619
+ method: 'POST',
1620
+ headers: { 'Content-Type': 'application/json' },
1621
+ body: JSON.stringify({
1622
+ prompt: node.editPrompt.trim(),
1623
+ type: 'edit'
1624
+ })
1625
+ });
1626
+
1627
+ if (response.ok) {
1628
+ const { improvedPrompt } = await response.json();
1629
+ onUpdate(node.id, { editPrompt: improvedPrompt });
1630
+ } else {
1631
+ alert('Failed to improve prompt. Please try again.');
1632
+ }
1633
+ } catch (error) {
1634
+ console.error('Error improving prompt:', error);
1635
+ alert('Failed to improve prompt. Please try again.');
1636
+ }
1637
+ };
1638
+
1639
+ /**
1640
+ * Handle delete node action with confirmation
1641
+ */
1642
+ const handleDeleteNode = (e: React.MouseEvent) => {
1643
+ e.stopPropagation(); // Prevent triggering drag
1644
+ e.preventDefault();
1645
+
1646
+ if (confirm('Delete this node?')) {
1647
+ onDelete(node.id);
1648
+ }
1649
+ };
1650
+
1651
+ /**
1652
+ * Handle clearing the input connection
1653
+ */
1654
+ const handleClearConnection = () => {
1655
+ onUpdate(node.id, { input: undefined });
1656
+ };
1657
+
1658
+ /**
1659
+ * Handle edit prompt changes
1660
+ */
1661
+ const handlePromptChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
1662
+ onUpdate(node.id, { editPrompt: e.target.value });
1663
+ };
1664
+
1665
  return (
1666
  <div className="nb-node absolute text-white w-[320px]" style={{ left: localPos.x, top: localPos.y }}>
1667
+ {/* Node Header - Contains title, delete button, and connection ports */}
1668
  <div
1669
  className="nb-header px-3 py-2 flex items-center justify-between rounded-t-[14px] cursor-grab active:cursor-grabbing"
1670
+ onPointerDown={onPointerDown} // Start dragging
1671
+ onPointerMove={onPointerMove} // Handle drag movement
1672
+ onPointerUp={onPointerUp} // End dragging
1673
  >
1674
+ {/* Input port (left side) - where connections come in */}
1675
  <Port className="in" nodeId={node.id} isOutput={false} onEndConnection={onEndConnection} />
1676
+
1677
+ {/* Node title */}
1678
  <div className="font-semibold text-sm flex-1 text-center">EDIT</div>
1679
+
1680
  <div className="flex items-center gap-1">
1681
+ {/* Delete button */}
1682
  <Button
1683
  variant="ghost"
1684
  size="icon"
1685
  className="text-destructive hover:bg-destructive/20 h-6 w-6"
1686
+ onClick={handleDeleteNode}
1687
+ onPointerDown={(e) => e.stopPropagation()} // Prevent drag when clicking delete
 
 
 
 
 
 
1688
  title="Delete node"
1689
  aria-label="Delete node"
1690
  >
1691
  Γ—
1692
  </Button>
1693
+
1694
+ {/* Output port (right side) - where connections go out */}
1695
  <Port className="out" nodeId={node.id} isOutput={true} onStartConnection={onStartConnection} />
1696
  </div>
1697
  </div>
1698
+
1699
+ {/* Node Content - Contains all the controls and outputs */}
1700
  <div className="p-3 space-y-3">
1701
+ {/* Show clear connection button if node has input */}
1702
  {node.input && (
1703
  <div className="flex justify-end mb-2">
1704
  <Button
1705
  variant="ghost"
1706
  size="sm"
1707
+ onClick={handleClearConnection}
1708
  className="text-xs"
1709
+ title="Remove input connection"
1710
  >
1711
  Clear Connection
1712
  </Button>
1713
  </div>
1714
  )}
1715
+
1716
+ {/* Edit prompt input and improvement section */}
1717
  <div className="space-y-2">
1718
+ <div className="text-xs text-white/70 mb-1">Edit Instructions</div>
1719
  <Textarea
1720
  className="w-full"
1721
  placeholder="Describe what to edit (e.g., 'make it brighter', 'add more contrast', 'make it look vintage')"
1722
  value={node.editPrompt || ""}
1723
+ onChange={handlePromptChange}
1724
  rows={3}
1725
  />
1726
+
1727
+ {/* AI-powered prompt improvement button */}
1728
  <Button
1729
  variant="outline"
1730
  size="sm"
1731
  className="w-full text-xs"
1732
+ onClick={handlePromptImprovement}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1733
  title="Use Gemini 2.5 Flash to improve your edit prompt"
1734
+ disabled={!node.editPrompt?.trim()}
1735
  >
1736
  ✨ Improve with Gemini
1737
  </Button>
1738
  </div>
1739
+
1740
+ {/* Process button - starts the editing operation */}
1741
  <Button
1742
  className="w-full"
1743
  onClick={() => onProcess(node.id)}
1744
+ disabled={node.isRunning || !node.editPrompt?.trim()}
1745
+ title={
1746
+ !node.input ? "Connect an input first" :
1747
+ !node.editPrompt?.trim() ? "Enter edit instructions first" :
1748
+ "Apply the edit to the input image"
1749
+ }
1750
  >
1751
  {node.isRunning ? "Processing..." : "Apply Edit"}
1752
  </Button>
1753
+
1754
+ {/* Output section with history navigation and download */}
1755
  <NodeOutputSection
1756
  nodeId={node.id}
1757
  output={node.output}
 
1760
  navigateNodeHistory={navigateNodeHistory}
1761
  getCurrentNodeImage={getCurrentNodeImage}
1762
  />
1763
+
1764
+ {/* Error display */}
1765
  {node.error && (
1766
+ <div className="text-xs text-red-400 mt-2 p-2 bg-red-900/20 rounded">
1767
+ {node.error}
1768
+ </div>
1769
  )}
1770
  </div>
1771
  </div>
app/page.tsx CHANGED
@@ -101,6 +101,48 @@ CRITICAL REQUIREMENTS:
101
  The result should look like all subjects were photographed together in the same place at the same time, NOT like separate images placed side by side.`;
102
  }
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  /* ========================================
105
  TYPE DEFINITIONS
106
  ======================================== */
@@ -2113,6 +2155,9 @@ export default function EditorPage() {
2113
  case "FACE":
2114
  setNodes(prev => [...prev, { ...commonProps, type: "FACE", faceOptions: {} } as FaceNode]);
2115
  break;
 
 
 
2116
  case "LIGHTNING":
2117
  setNodes(prev => [...prev, { ...commonProps, type: "LIGHTNING", lightingStrength: 75 } as LightningNode]);
2118
  break;
 
101
  The result should look like all subjects were photographed together in the same place at the same time, NOT like separate images placed side by side.`;
102
  }
103
 
104
+ /**
105
+ * Copy image to clipboard with PNG conversion
106
+ * The clipboard API only supports PNG format for images, so we convert other formats
107
+ */
108
+ async function copyImageToClipboard(dataUrl: string) {
109
+ try {
110
+ const response = await fetch(dataUrl);
111
+ const blob = await response.blob();
112
+
113
+ // Convert to PNG if not already PNG
114
+ if (blob.type !== 'image/png') {
115
+ const canvas = document.createElement('canvas');
116
+ const ctx = canvas.getContext('2d');
117
+ const img = new Image();
118
+
119
+ await new Promise((resolve) => {
120
+ img.onload = () => {
121
+ canvas.width = img.width;
122
+ canvas.height = img.height;
123
+ ctx?.drawImage(img, 0, 0);
124
+ resolve(void 0);
125
+ };
126
+ img.src = dataUrl;
127
+ });
128
+
129
+ const pngBlob = await new Promise<Blob>((resolve) => {
130
+ canvas.toBlob((blob) => resolve(blob!), 'image/png');
131
+ });
132
+
133
+ await navigator.clipboard.write([
134
+ new ClipboardItem({ 'image/png': pngBlob })
135
+ ]);
136
+ } else {
137
+ await navigator.clipboard.write([
138
+ new ClipboardItem({ 'image/png': blob })
139
+ ]);
140
+ }
141
+ } catch (error) {
142
+ console.error('Failed to copy image to clipboard:', error);
143
+ }
144
+ }
145
+
146
  /* ========================================
147
  TYPE DEFINITIONS
148
  ======================================== */
 
2155
  case "FACE":
2156
  setNodes(prev => [...prev, { ...commonProps, type: "FACE", faceOptions: {} } as FaceNode]);
2157
  break;
2158
+ case "EDIT":
2159
+ setNodes(prev => [...prev, { ...commonProps, type: "EDIT" } as EditNode]);
2160
+ break;
2161
  case "LIGHTNING":
2162
  setNodes(prev => [...prev, { ...commonProps, type: "LIGHTNING", lightingStrength: 75 } as LightningNode]);
2163
  break;
public/clothes/{suit.jpg β†’ suit.png} RENAMED
File without changes
public/lighting/light1.png ADDED

Git LFS Details

  • SHA256: 93fc82cb9d60413d2cb20f18ed26e2205acc4e5bd522166214ce490e1c7dd4da
  • Pointer size: 132 Bytes
  • Size of remote file: 1.14 MB
public/lighting/{light1.jpg β†’ light2.png} RENAMED
File without changes
public/lighting/{light2.jpg β†’ light3.png} RENAMED
File without changes
public/makeup/makeup1.jpg DELETED

Git LFS Details

  • SHA256: bac109fa54cdeb264e3b037b4b6c71e7a929b5d70cad4cf424c12809bc2ad144
  • Pointer size: 130 Bytes
  • Size of remote file: 44.8 kB
public/{lighting/light3.jpg β†’ makeup/makeup1.png} RENAMED
File without changes
public/poses/sit1.jpg DELETED

Git LFS Details

  • SHA256: f3ee133b2b3a12469fa5d00223354a32473b09c30b61bb8d8557ba4ccc1847a1
  • Pointer size: 130 Bytes
  • Size of remote file: 89.2 kB
public/poses/sit1.png ADDED

Git LFS Details

  • SHA256: 56f037e9af7299f0a1f816f3d3d38f1789589f623e73ef5223f7f07d2711df81
  • Pointer size: 131 Bytes
  • Size of remote file: 288 kB
public/poses/sit2.jpg DELETED

Git LFS Details

  • SHA256: 9fd274b65cc253344207bacf48b100aaf6e75fb0360733a67432266c53f3c8e6
  • Pointer size: 130 Bytes
  • Size of remote file: 49.4 kB
public/poses/sit2.png ADDED

Git LFS Details

  • SHA256: 0485c4e595132ba1cbd14e745379f89c6d6f2aa0d784bc9eeeaf2eb73cbf53d9
  • Pointer size: 131 Bytes
  • Size of remote file: 170 kB
public/poses/stand1.jpg DELETED

Git LFS Details

  • SHA256: 0e95dcb09a881958cf7f24a547d067357bd9fc46e61e0feba2c788f6e9741a6a
  • Pointer size: 130 Bytes
  • Size of remote file: 78.4 kB
public/poses/stand1.png ADDED

Git LFS Details

  • SHA256: 23b020da3b48d56f3dff304a20cc9586c9976080bc5c845b096547c847085a56
  • Pointer size: 131 Bytes
  • Size of remote file: 280 kB
public/poses/stand2.jpg DELETED

Git LFS Details

  • SHA256: 420840c4c54c24c6b1d6c2ad1821313d95f708235250da1b1c724d0e3c051321
  • Pointer size: 131 Bytes
  • Size of remote file: 101 kB
public/poses/stand2.png ADDED

Git LFS Details

  • SHA256: 090dcd2e031401378d3d4ce16ed812de4ab3ba53a96641793f9c57eea5703bb9
  • Pointer size: 131 Bytes
  • Size of remote file: 321 kB