TDN-M commited on
Commit
bf849d3
·
verified ·
1 Parent(s): 330b45b

Update pages/index.js

Browse files
Files changed (1) hide show
  1. pages/index.js +129 -389
pages/index.js CHANGED
@@ -1,418 +1,158 @@
1
- import { useState, useRef, useEffect } from "react";
2
- import { SendHorizontal, LoaderCircle, Trash2, X } from "lucide-react";
3
- import Head from "next/head";
4
-
5
- export default function Home() {
6
- const canvasRef = useRef(null);
7
- const backgroundImageRef = useRef(null);
8
- const [isDrawing, setIsDrawing] = useState(false);
9
- const [penColor, setPenColor] = useState("#000000");
10
- const colorInputRef = useRef(null);
11
- const [prompt, setPrompt] = useState("");
12
  const [generatedImage, setGeneratedImage] = useState(null);
13
  const [isLoading, setIsLoading] = useState(false);
14
- const [showErrorModal, setShowErrorModal] = useState(false);
15
- const [errorMessage, setErrorMessage] = useState("");
16
- const [customApiKey, setCustomApiKey] = useState("");
17
-
18
- // Load background image when generatedImage changes
19
- useEffect(() => {
20
- if (generatedImage && canvasRef.current) {
21
- // Use the window.Image constructor to avoid conflict with Next.js Image component
22
- const img = new window.Image();
23
- img.onload = () => {
24
- backgroundImageRef.current = img;
25
- drawImageToCanvas();
26
- };
27
- img.src = generatedImage;
28
- }
29
- }, [generatedImage]);
30
-
31
- // Initialize canvas with white background when component mounts
32
- useEffect(() => {
33
- if (canvasRef.current) {
34
- initializeCanvas();
35
- }
36
- }, []);
37
-
38
- // Initialize canvas with white background
39
- const initializeCanvas = () => {
40
- const canvas = canvasRef.current;
41
- const ctx = canvas.getContext("2d");
42
-
43
- // Fill canvas with white background
44
- ctx.fillStyle = "#FFFFFF";
45
- ctx.fillRect(0, 0, canvas.width, canvas.height);
46
- };
47
-
48
- // Draw the background image to the canvas
49
- const drawImageToCanvas = () => {
50
- if (!canvasRef.current || !backgroundImageRef.current) return;
51
-
52
- const canvas = canvasRef.current;
53
- const ctx = canvas.getContext("2d");
54
-
55
- // Fill with white background first
56
- ctx.fillStyle = "#FFFFFF";
57
- ctx.fillRect(0, 0, canvas.width, canvas.height);
58
-
59
- // Draw the background image
60
- ctx.drawImage(
61
- backgroundImageRef.current,
62
- 0, 0,
63
- canvas.width, canvas.height
64
- );
65
- };
66
-
67
- // Get the correct coordinates based on canvas scaling
68
- const getCoordinates = (e) => {
69
- const canvas = canvasRef.current;
70
- const rect = canvas.getBoundingClientRect();
71
-
72
- // Calculate the scaling factor between the internal canvas size and displayed size
73
- const scaleX = canvas.width / rect.width;
74
- const scaleY = canvas.height / rect.height;
75
-
76
- // Apply the scaling to get accurate coordinates
77
- return {
78
- x: (e.nativeEvent.offsetX || (e.nativeEvent.touches?.[0]?.clientX - rect.left)) * scaleX,
79
- y: (e.nativeEvent.offsetY || (e.nativeEvent.touches?.[0]?.clientY - rect.top)) * scaleY
80
- };
81
- };
82
-
83
- const startDrawing = (e) => {
84
- const canvas = canvasRef.current;
85
- const ctx = canvas.getContext("2d");
86
- const { x, y } = getCoordinates(e);
87
-
88
- // Prevent default behavior to avoid scrolling on touch devices
89
- if (e.type === 'touchstart') {
90
- e.preventDefault();
91
  }
92
-
93
- // Start a new path without clearing the canvas
94
- ctx.beginPath();
95
- ctx.moveTo(x, y);
96
- setIsDrawing(true);
97
  };
98
 
99
- const draw = (e) => {
100
- if (!isDrawing) return;
101
-
102
- // Prevent default behavior to avoid scrolling on touch devices
103
- if (e.type === 'touchmove') {
104
- e.preventDefault();
105
- }
106
-
107
- const canvas = canvasRef.current;
108
- const ctx = canvas.getContext("2d");
109
- const { x, y } = getCoordinates(e);
110
-
111
- ctx.lineWidth = 5;
112
- ctx.lineCap = "round";
113
- ctx.strokeStyle = penColor;
114
- ctx.lineTo(x, y);
115
- ctx.stroke();
116
- };
117
-
118
- const stopDrawing = () => {
119
- setIsDrawing(false);
120
- };
121
-
122
- const clearCanvas = () => {
123
- const canvas = canvasRef.current;
124
- const ctx = canvas.getContext("2d");
125
-
126
- // Fill with white instead of just clearing
127
- ctx.fillStyle = "#FFFFFF";
128
- ctx.fillRect(0, 0, canvas.width, canvas.height);
129
-
130
- setGeneratedImage(null);
131
- backgroundImageRef.current = null;
132
- };
133
-
134
- const handleColorChange = (e) => {
135
- setPenColor(e.target.value);
136
- };
137
-
138
- const openColorPicker = () => {
139
- if (colorInputRef.current) {
140
- colorInputRef.current.click();
141
- }
142
  };
143
 
144
- const handleKeyDown = (e) => {
145
- if (e.key === 'Enter' || e.key === ' ') {
146
- openColorPicker();
 
 
147
  }
148
- };
149
 
150
- const handleSubmit = async (e) => {
151
- e.preventDefault();
152
-
153
- if (!canvasRef.current) return;
154
-
155
  setIsLoading(true);
156
-
 
157
  try {
158
- // Get the drawing as base64 data
159
- const canvas = canvasRef.current;
160
-
161
- // Create a temporary canvas to add white background
162
- const tempCanvas = document.createElement('canvas');
163
- tempCanvas.width = canvas.width;
164
- tempCanvas.height = canvas.height;
165
- const tempCtx = tempCanvas.getContext('2d');
166
-
167
- // Fill with white background
168
- tempCtx.fillStyle = '#FFFFFF';
169
- tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
170
-
171
- // Draw the original canvas content on top of the white background
172
- tempCtx.drawImage(canvas, 0, 0);
173
-
174
- const drawingData = tempCanvas.toDataURL("image/png").split(",")[1];
175
-
176
- // Create request payload
177
- const requestPayload = {
178
- prompt,
179
- drawingData,
180
- customApiKey // Add the custom API key to the payload if it exists
181
- };
182
-
183
- // Log the request payload (without the full image data for brevity)
184
- console.log("Request payload:", {
185
- ...requestPayload,
186
- drawingData: drawingData ? `${drawingData.substring(0, 50)}... (truncated)` : null,
187
- customApiKey: customApiKey ? "**********" : null
188
  });
189
-
190
- // Send the drawing and prompt to the API
191
- const response = await fetch("/api/generate", {
192
- method: "POST",
193
- headers: {
194
- "Content-Type": "application/json",
195
- },
196
- body: JSON.stringify(requestPayload),
197
- });
198
-
199
- const data = await response.json();
200
-
201
- // Log the response (without the full image data for brevity)
202
- console.log("Response:", {
203
- ...data,
204
- imageData: data.imageData ? `${data.imageData.substring(0, 50)}... (truncated)` : null
205
- });
206
-
207
- if (data.success && data.imageData) {
208
- const imageUrl = `data:image/png;base64,${data.imageData}`;
209
- setGeneratedImage(imageUrl);
210
- } else {
211
- console.error("Failed to generate image:", data.error);
212
-
213
- // Check if the error is related to quota exhaustion or other API errors
214
- if (data.error && (
215
- data.error.includes("Resource has been exhausted") ||
216
- data.error.includes("quota") ||
217
- response.status === 429 ||
218
- response.status === 500
219
- )) {
220
- setErrorMessage(data.error);
221
- setShowErrorModal(true);
222
- } else {
223
- alert("Failed to generate image. Please try again.");
224
- }
225
- }
226
- } catch (error) {
227
- console.error("Error submitting drawing:", error);
228
- setErrorMessage(error.message || "An unexpected error occurred.");
229
- setShowErrorModal(true);
230
  } finally {
231
  setIsLoading(false);
232
  }
233
  };
234
 
235
- // Close the error modal
236
- const closeErrorModal = () => {
237
- setShowErrorModal(false);
238
- };
239
-
240
- // Handle the custom API key submission
241
- const handleApiKeySubmit = (e) => {
242
- e.preventDefault();
243
- setShowErrorModal(false);
244
- // Will use the customApiKey state in the next API call
245
- };
246
-
247
- // Add touch event prevention function
248
- useEffect(() => {
249
- // Function to prevent default touch behavior on canvas
250
- const preventTouchDefault = (e) => {
251
- if (isDrawing) {
252
- e.preventDefault();
253
- }
254
- };
255
-
256
- // Add event listener when component mounts
257
- const canvas = canvasRef.current;
258
- if (canvas) {
259
- canvas.addEventListener('touchstart', preventTouchDefault, { passive: false });
260
- canvas.addEventListener('touchmove', preventTouchDefault, { passive: false });
261
- }
262
-
263
- // Remove event listener when component unmounts
264
- return () => {
265
- if (canvas) {
266
- canvas.removeEventListener('touchstart', preventTouchDefault);
267
- canvas.removeEventListener('touchmove', preventTouchDefault);
268
- }
269
- };
270
- }, [isDrawing]);
271
-
272
  return (
273
- <>
274
- <Head>
275
- <title>TDNM Space For Testing New Idea</title>
276
- <meta name="description" content="Gemini Co-DrawingTDNM Space For Testing New Idea" />
277
- </Head>
278
- <div className="min-h-screen notebook-paper-bg text-gray-900 flex flex-col justify-start items-center">
279
-
280
- <main className="container mx-auto px-3 sm:px-6 py-5 sm:py-10 pb-32 max-w-5xl w-full">
281
- {/* Header section with title and tools */}
282
- <div className="flex flex-col sm:flex-row sm:justify-between sm:items-end mb-2 sm:mb-6 gap-2">
283
- <div>
284
- <h1 className="text-2xl sm:text-3xl font-bold mb-0 leading-tight font-mega">Draw Your Layout</h1>
285
- </div>
286
-
287
- <menu className="flex items-center bg-gray-300 rounded-full p-2 shadow-sm self-start sm:self-auto">
288
- <button
289
- type="button"
290
- className="w-10 h-10 rounded-full overflow-hidden mr-2 flex items-center justify-center border-2 border-white shadow-sm transition-transform hover:scale-110"
291
- onClick={openColorPicker}
292
- onKeyDown={handleKeyDown}
293
- aria-label="Open color picker"
294
- style={{ backgroundColor: penColor }}
295
- >
296
- <input
297
- ref={colorInputRef}
298
- type="color"
299
- value={penColor}
300
- onChange={handleColorChange}
301
- className="opacity-0 absolute w-px h-px"
302
- aria-label="Select pen color"
303
- />
304
- </button>
305
- <button
306
- type="button"
307
- onClick={clearCanvas}
308
- className="w-10 h-10 rounded-full flex items-center justify-center bg-white shadow-sm transition-all hover:bg-gray-50 hover:scale-110"
309
- >
310
- <Trash2 className="w-5 h-5 text-gray-700" aria-label="Clear Canvas" />
311
- </button>
312
- </menu>
313
- </div>
314
-
315
- {/* Canvas section with notebook paper background */}
316
- <div className="w-full mb-6">
317
-
318
- <canvas
319
- ref={canvasRef}
320
- width={960}
321
- height={540}
322
- onMouseDown={startDrawing}
323
- onMouseMove={draw}
324
- onMouseUp={stopDrawing}
325
- onMouseLeave={stopDrawing}
326
- onTouchStart={startDrawing}
327
- onTouchMove={draw}
328
- onTouchEnd={stopDrawing}
329
- className="border-2 border-black w-full hover:cursor-crosshair sm:h-[60vh]
330
- h-[30vh] min-h-[320px] bg-white/90 touch-none"
331
- />
332
- </div>
333
-
334
- {/* Input form that matches canvas width */}
335
- <form onSubmit={handleSubmit} className="w-full">
336
- <div className="relative">
337
  <input
338
  type="text"
339
- value={prompt}
340
- onChange={(e) => setPrompt(e.target.value)}
341
- placeholder="Add your change..."
342
- className="w-full p-3 sm:p-4 pr-12 sm:pr-14 text-sm sm:text-base border-2 border-black bg-white text-gray-800 shadow-sm focus:ring-2 focus:ring-gray-200 focus:outline-none transition-all font-mono"
343
- required
344
  />
345
- <button
346
- type="submit"
 
 
347
  disabled={isLoading}
348
- className="absolute right-3 sm:right-4 top-1/2 -translate-y-1/2 p-1.5 sm:p-2 rounded-none bg-black text-white hover:cursor-pointer hover:bg-gray-800 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
349
  >
350
  {isLoading ? (
351
- <LoaderCircle className="w-5 sm:w-6 h-5 sm:h-6 animate-spin" aria-label="Loading" />
 
 
 
352
  ) : (
353
- <SendHorizontal className="w-5 sm:w-6 h-5 sm:h-6" aria-label="Submit" />
354
  )}
355
- </button>
356
- </div>
357
- </form>
358
- </main>
359
-
360
- {/* Error Modal */}
361
- {showErrorModal && (
362
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
363
- <div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
364
- <div className="flex justify-between items-start mb-4">
365
- <h3 className="text-xl font-bold text-gray-700">Failed to generate</h3>
366
- <button
367
- onClick={closeErrorModal}
368
- className="text-gray-400 hover:text-gray-500"
369
- >
370
- <X className="w-5 h-5" />
371
- </button>
372
- </div>
373
-
374
-
375
- <form onSubmit={handleApiKeySubmit} className="mb-4">
376
- <label className="block text-sm font-medium text-gray-600 mb-2">
377
- This space is pretty popular... add your own Gemini API key from <a
378
- href="https://ai.google.dev/"
379
- target="_blank"
380
- rel="noopener noreferrer"
381
- className="underline"
382
- >
383
- Google AI Studio
384
- </a>:
385
-
386
-
387
- </label>
388
- <input
389
- type="text"
390
- value={customApiKey}
391
- onChange={(e) => setCustomApiKey(e.target.value)}
392
- placeholder="API Key..."
393
- className="w-full p-3 border border-gray-300 rounded mb-4 font-mono text-sm"
394
- required
395
- />
396
- <div className="flex justify-end gap-2">
397
- <button
398
- type="button"
399
- onClick={closeErrorModal}
400
- className="px-4 py-2 text-sm border border-gray-300 rounded hover:bg-gray-50"
401
- >
402
- Cancel
403
- </button>
404
- <button
405
- type="submit"
406
- className="px-4 py-2 text-sm bg-black text-white rounded hover:bg-gray-800"
407
- >
408
- Use My API Key
409
- </button>
410
  </div>
411
- </form>
412
- </div>
413
  </div>
414
- )}
415
- </div>
416
- </>
417
  );
418
- }
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Upload, Image as ImageIcon, Loader } from 'lucide-react';
4
+ import axios from 'axios';
5
+
6
+ const InteriorStyleTransformer = () => {
7
+ const [selectedImage, setSelectedImage] = useState(null);
8
+ const [styleInput, setStyleInput] = useState('');
 
 
 
9
  const [generatedImage, setGeneratedImage] = useState(null);
10
  const [isLoading, setIsLoading] = useState(false);
11
+ const [error, setError] = useState(null);
12
+
13
+ // Handle image upload
14
+ const handleImageUpload = (event) => {
15
+ const file = event.target.files[0];
16
+ if (file) {
17
+ setSelectedImage(URL.createObjectURL(file));
18
+ setGeneratedImage(null);
19
+ setError(null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
 
 
 
 
 
21
  };
22
 
23
+ // Handle style input change
24
+ const handleStyleInput = (event) => {
25
+ setStyleInput(event.target.value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  };
27
 
28
+ // Handle form submission to process the image
29
+ const handleSubmit = async () => {
30
+ if (!selectedImage || !styleInput) {
31
+ setError('Please upload an image and specify a design style.');
32
+ return;
33
  }
 
34
 
 
 
 
 
 
35
  setIsLoading(true);
36
+ setError(null);
37
+
38
  try {
39
+ // Prepare form data
40
+ const formData = new FormData();
41
+ const fileInput = document.querySelector('input[type="file"]');
42
+ formData.append('image', fileInput.files[0]);
43
+ formData.append('style', styleInput);
44
+
45
+ // Hypothetical API call to an AI image processing service
46
+ const response = await axios.post('https://api.example.com/transform-interior', formData, {
47
+ headers: { 'Content-Type': 'multipart/form-data' },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  });
49
+
50
+ // Assuming the API returns a URL to the generated image
51
+ setGeneratedImage(response.data.imageUrl);
52
+ } catch (err) {
53
+ setError('Failed to process the image. Please try again.');
54
+ console.error(err);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  } finally {
56
  setIsLoading(false);
57
  }
58
  };
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  return (
61
+ <section className="py-20 bg-[#0B1118]">
62
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
63
+ <motion.div
64
+ initial={{ opacity: 0, y: 20 }}
65
+ whileInView={{ opacity: 1, y: 0 }}
66
+ transition={{ duration: 0.8 }}
67
+ className="text-center mb-12"
68
+ >
69
+ <h2 className="text-3xl md:text-4xl font-bold mb-4 text-white">
70
+ Transform Your Interior Design
71
+ </h2>
72
+ <p className="text-xl text-gray-400">
73
+ Upload a photo of your space and specify your desired interior design style.
74
+ </p>
75
+ </motion.div>
76
+
77
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
78
+ {/* Image Upload Section */}
79
+ <motion.div
80
+ initial={{ opacity: 0, scale: 0.9 }}
81
+ whileInView={{ opacity: 1, scale: 1 }}
82
+ transition={{ duration: 0.5 }}
83
+ className="bg-gray-800/30 rounded-lg p-6 border border-gray-700 hover:border-blue-500/50"
84
+ >
85
+ <h3 className="text-xl font-semibold mb-4 text-white">Upload Your Room Photo</h3>
86
+ <div className="flex flex-col items-center">
87
+ <label className="w-full flex flex-col items-center px-4 py-6 bg-gray-900/50 rounded-lg border-2 border-dashed border-gray-600 cursor-pointer hover:border-blue-500">
88
+ <Upload className="h-12 w-12 text-blue-500 mb-4" />
89
+ <span className="text-gray-400">
90
+ {selectedImage ? 'Change Image' : 'Upload an Image'}
91
+ </span>
92
+ <input
93
+ type="file"
94
+ accept="image/*"
95
+ className="hidden"
96
+ onChange={handleImageUpload}
97
+ />
98
+ </label>
99
+ {selectedImage && (
100
+ <img
101
+ src={selectedImage}
102
+ alt="Uploaded room"
103
+ className="mt-4 max-h-64 w-full object-contain rounded-lg"
104
+ />
105
+ )}
106
+ </div>
107
+ </motion.div>
108
+
109
+ {/* Style Input and Generated Image Section */}
110
+ <motion.div
111
+ initial={{ opacity: 0, scale: 0.9 }}
112
+ whileInView={{ opacity: 1, scale: 1 }}
113
+ transition={{ duration: 0.5 }}
114
+ className="bg-gray-800/30 rounded-lg p-6 border border-gray-700 hover:border-blue-500/50"
115
+ >
116
+ <h3 className="text-xl font-semibold mb-4 text-white">Specify Design Style</h3>
 
 
 
 
 
 
 
 
117
  <input
118
  type="text"
119
+ placeholder="e.g., Modern, Scandinavian, Minimalist"
120
+ value={styleInput}
121
+ onChange={handleStyleInput}
122
+ className="w-full px-4 py-2 bg-gray-900 text-white rounded-lg border border-gray-600 focus:outline-none focus:border-blue-500 mb-4"
 
123
  />
124
+ <motion.button
125
+ whileHover={{ scale: 1.05 }}
126
+ whileTap={{ scale: 0.95 }}
127
+ onClick={handleSubmit}
128
  disabled={isLoading}
129
+ className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 disabled:bg-gray-600"
130
  >
131
  {isLoading ? (
132
+ <div className="flex items-center justify-center">
133
+ <Loader className="h-6 w-6 animate-spin mr-2" />
134
+ Processing...
135
+ </div>
136
  ) : (
137
+ 'Generate Design'
138
  )}
139
+ </motion.button>
140
+ {error && <p className="text-red-500 mt-4">{error}</p>}
141
+ {generatedImage && (
142
+ <div className="mt-4">
143
+ <h3 className="text-lg font-semibold text-white mb-2">Generated Design</h3>
144
+ <img
145
+ src={generatedImage}
146
+ alt="Generated interior design"
147
+ className="max-h-64 w-full object-contain rounded-lg"
148
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  </div>
150
+ )}
151
+ </motion.div>
152
  </div>
153
+ </div>
154
+ </section>
 
155
  );
156
+ };
157
+
158
+ export default InteriorStyleTransformer;