Spaces:
Running
Running
added error modal
Browse files- components/ErrorModal.js +74 -0
- pages/index.js +36 -16
components/ErrorModal.js
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react';
|
2 |
+
import { X } from 'lucide-react';
|
3 |
+
import { Github } from 'lucide-react';
|
4 |
+
|
5 |
+
const ErrorModal = ({ isOpen, onClose }) => {
|
6 |
+
if (!isOpen) return null;
|
7 |
+
|
8 |
+
return (
|
9 |
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
10 |
+
{/* Semi-opaque blur background */}
|
11 |
+
<div
|
12 |
+
className="absolute inset-0 bg-gray-800/30 backdrop-blur-sm"
|
13 |
+
onClick={onClose}
|
14 |
+
onKeyDown={(e) => e.key === 'Escape' && onClose()}
|
15 |
+
role="button"
|
16 |
+
tabIndex={0}
|
17 |
+
/>
|
18 |
+
|
19 |
+
{/* Modal card */}
|
20 |
+
<div className="relative bg-white rounded-2xl p-8 shadow-lg border border-gray-200 max-w-lg mx-4 z-10">
|
21 |
+
<button
|
22 |
+
type="button"
|
23 |
+
onClick={onClose}
|
24 |
+
className="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors"
|
25 |
+
>
|
26 |
+
<X size={24} />
|
27 |
+
</button>
|
28 |
+
|
29 |
+
<h2 className="text-xl font-bold text-gray-900 mb-4">
|
30 |
+
This space is very popular right now...
|
31 |
+
</h2>
|
32 |
+
|
33 |
+
<p className="text-gray-600 mb-4">
|
34 |
+
Due to high demand, we're not able to process requests right now. Want to run this locally? Get the code and host it yourself!
|
35 |
+
</p>
|
36 |
+
|
37 |
+
<p className="text-gray-600 mb-8">
|
38 |
+
You can get your own API key from{' '}
|
39 |
+
<a
|
40 |
+
href="https://ai.google.dev"
|
41 |
+
target="_blank"
|
42 |
+
rel="noopener noreferrer"
|
43 |
+
className="text-blue-600 hover:text-blue-800 underline"
|
44 |
+
>
|
45 |
+
ai.google.dev
|
46 |
+
</a>
|
47 |
+
{' '}to start using this right away.
|
48 |
+
</p>
|
49 |
+
|
50 |
+
<div className="flex gap-4 justify-center">
|
51 |
+
<a
|
52 |
+
href="https://github.com/googlecreativelab/gemini-demos/tree/main/image-to-code"
|
53 |
+
target="_blank"
|
54 |
+
rel="noopener noreferrer"
|
55 |
+
className="flex items-center gap-2 px-4 py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 transition-colors"
|
56 |
+
>
|
57 |
+
<Github size={20} />
|
58 |
+
<span>Get the code on GitHub</span>
|
59 |
+
</a>
|
60 |
+
|
61 |
+
<button
|
62 |
+
type="button"
|
63 |
+
onClick={onClose}
|
64 |
+
className="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors"
|
65 |
+
>
|
66 |
+
Close
|
67 |
+
</button>
|
68 |
+
</div>
|
69 |
+
</div>
|
70 |
+
</div>
|
71 |
+
);
|
72 |
+
};
|
73 |
+
|
74 |
+
export default ErrorModal;
|
pages/index.js
CHANGED
@@ -20,6 +20,7 @@ import { ChevronDown, Image, Upload, Settings, Send, History, ArrowRight, Pen, L
|
|
20 |
import Head from "next/head";
|
21 |
import CodePreview from "../components/CodePreview";
|
22 |
import Header from '../components/Header';
|
|
|
23 |
|
24 |
const SAMPLE_IMAGES = [
|
25 |
'beeripple.jpeg',
|
@@ -36,23 +37,28 @@ const SAMPLE_IMAGES = [
|
|
36 |
|
37 |
// Helper function to generate code from image
|
38 |
async function generateCodeFromImage(imageBase64, prompt, userInput) {
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
-
|
52 |
-
|
|
|
|
|
53 |
}
|
54 |
-
|
55 |
-
return response.json();
|
56 |
}
|
57 |
|
58 |
export default function Home() {
|
@@ -64,6 +70,7 @@ export default function Home() {
|
|
64 |
const [concurrentRequests, setConcurrentRequests] = useState(5);
|
65 |
const [showPrompt, setShowPrompt] = useState(false);
|
66 |
const [prompt, setPrompt] = useState("");
|
|
|
67 |
|
68 |
// Load prompt from localStorage on initial render
|
69 |
useEffect(() => {
|
@@ -181,12 +188,20 @@ export default function Home() {
|
|
181 |
setLoading(true);
|
182 |
setHasStartedGenerating(true);
|
183 |
setOutputs([]);
|
|
|
184 |
try {
|
185 |
const requests = Array(concurrentRequests)
|
186 |
.fill()
|
187 |
.map(() => generateCodeFromImage(imageBase64, prompt, userInput));
|
188 |
|
189 |
const results = await Promise.all(requests);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
setOutputs(results.map((result, index) => ({
|
191 |
id: index + 1,
|
192 |
code: result.code,
|
@@ -194,6 +209,7 @@ export default function Home() {
|
|
194 |
})));
|
195 |
} catch (error) {
|
196 |
console.error("Error generating code:", error);
|
|
|
197 |
} finally {
|
198 |
setLoading(false);
|
199 |
}
|
@@ -277,6 +293,10 @@ export default function Home() {
|
|
277 |
</Head>
|
278 |
<div className="h-screen max-h-screen bg-white flex items-center justify-center overflow-y-hidden tracking-[-0.005em]">
|
279 |
<Header />
|
|
|
|
|
|
|
|
|
280 |
|
281 |
<div className="w-full h-full max-h-full overflow-hidden bg-white">
|
282 |
<div className={`flex flex-col md:flex-row gap-0 max-w-6xl mx-auto py-0 h-full transition-all duration-500 ${!hasStartedGenerating ? 'justify-center' : ''}`}>
|
@@ -410,7 +430,7 @@ export default function Home() {
|
|
410 |
{loading ? (
|
411 |
// Loading skeletons for code previews
|
412 |
Array(concurrentRequests).fill().map((_, index) => (
|
413 |
-
<div key={`skeleton-${index}`} className="mb-4 p-6 rounded-3xl bg-gray-100 animate-pulse">
|
414 |
<div className="w-full h-[500px] bg-gray-200 rounded-lg mb-4" />
|
415 |
<div className="flex justify-between items-center">
|
416 |
<div className="h-10 w-32 bg-gray-200 rounded-full" />
|
|
|
20 |
import Head from "next/head";
|
21 |
import CodePreview from "../components/CodePreview";
|
22 |
import Header from '../components/Header';
|
23 |
+
import ErrorModal from '../components/ErrorModal';
|
24 |
|
25 |
const SAMPLE_IMAGES = [
|
26 |
'beeripple.jpeg',
|
|
|
37 |
|
38 |
// Helper function to generate code from image
|
39 |
async function generateCodeFromImage(imageBase64, prompt, userInput) {
|
40 |
+
try {
|
41 |
+
const response = await fetch('/api/hello', {
|
42 |
+
method: 'POST',
|
43 |
+
headers: {
|
44 |
+
'Content-Type': 'application/json',
|
45 |
+
},
|
46 |
+
body: JSON.stringify({
|
47 |
+
imageBase64,
|
48 |
+
prompt,
|
49 |
+
userInput
|
50 |
+
})
|
51 |
+
});
|
52 |
+
|
53 |
+
if (!response.ok) {
|
54 |
+
return { error: true, message: 'Failed to generate code' };
|
55 |
+
}
|
56 |
|
57 |
+
const data = await response.json();
|
58 |
+
return { error: false, ...data };
|
59 |
+
} catch (error) {
|
60 |
+
return { error: true, message: error.message || 'Failed to generate code' };
|
61 |
}
|
|
|
|
|
62 |
}
|
63 |
|
64 |
export default function Home() {
|
|
|
70 |
const [concurrentRequests, setConcurrentRequests] = useState(5);
|
71 |
const [showPrompt, setShowPrompt] = useState(false);
|
72 |
const [prompt, setPrompt] = useState("");
|
73 |
+
const [showErrorModal, setShowErrorModal] = useState(false);
|
74 |
|
75 |
// Load prompt from localStorage on initial render
|
76 |
useEffect(() => {
|
|
|
188 |
setLoading(true);
|
189 |
setHasStartedGenerating(true);
|
190 |
setOutputs([]);
|
191 |
+
|
192 |
try {
|
193 |
const requests = Array(concurrentRequests)
|
194 |
.fill()
|
195 |
.map(() => generateCodeFromImage(imageBase64, prompt, userInput));
|
196 |
|
197 |
const results = await Promise.all(requests);
|
198 |
+
|
199 |
+
// Check if any requests resulted in an error
|
200 |
+
if (results.some(result => result.error)) {
|
201 |
+
setShowErrorModal(true);
|
202 |
+
return;
|
203 |
+
}
|
204 |
+
|
205 |
setOutputs(results.map((result, index) => ({
|
206 |
id: index + 1,
|
207 |
code: result.code,
|
|
|
209 |
})));
|
210 |
} catch (error) {
|
211 |
console.error("Error generating code:", error);
|
212 |
+
setShowErrorModal(true);
|
213 |
} finally {
|
214 |
setLoading(false);
|
215 |
}
|
|
|
293 |
</Head>
|
294 |
<div className="h-screen max-h-screen bg-white flex items-center justify-center overflow-y-hidden tracking-[-0.005em]">
|
295 |
<Header />
|
296 |
+
<ErrorModal
|
297 |
+
isOpen={showErrorModal}
|
298 |
+
onClose={() => setShowErrorModal(false)}
|
299 |
+
/>
|
300 |
|
301 |
<div className="w-full h-full max-h-full overflow-hidden bg-white">
|
302 |
<div className={`flex flex-col md:flex-row gap-0 max-w-6xl mx-auto py-0 h-full transition-all duration-500 ${!hasStartedGenerating ? 'justify-center' : ''}`}>
|
|
|
430 |
{loading ? (
|
431 |
// Loading skeletons for code previews
|
432 |
Array(concurrentRequests).fill().map((_, index) => (
|
433 |
+
<div key={`skeleton-preview-${Date.now()}-${index}`} className="mb-4 p-6 rounded-3xl bg-gray-100 animate-pulse">
|
434 |
<div className="w-full h-[500px] bg-gray-200 rounded-lg mb-4" />
|
435 |
<div className="flex justify-between items-center">
|
436 |
<div className="h-10 w-32 bg-gray-200 rounded-full" />
|