update app
Browse files
Home.tsx
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
| 21 |
} from 'lucide-react';
|
| 22 |
import {useState, useRef, useEffect} from 'react';
|
| 23 |
|
| 24 |
-
const ai = new GoogleGenAI({apiKey: process.env.API_KEY});
|
| 25 |
|
| 26 |
const aspectRatios = ['1:1', '16:9', '9:16', '4:3', '3:4'];
|
| 27 |
|
|
@@ -59,6 +59,13 @@ export default function Home() {
|
|
| 59 |
const dropdownRef = useRef<HTMLDivElement>(null);
|
| 60 |
const fileInputRef = useRef<HTMLInputElement>(null);
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
// Canvas state
|
| 63 |
const canvasRef = useRef<HTMLCanvasElement>(null);
|
| 64 |
const [isDrawing, setIsDrawing] = useState(false);
|
|
@@ -341,6 +348,43 @@ export default function Home() {
|
|
| 341 |
|
| 342 |
const handleSubmit = async (e: React.FormEvent) => {
|
| 343 |
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
if (!prompt) {
|
| 345 |
setErrorMessage('Please enter a prompt to continue.');
|
| 346 |
return;
|
|
@@ -356,6 +400,7 @@ export default function Home() {
|
|
| 356 |
setErrorMessage('');
|
| 357 |
|
| 358 |
try {
|
|
|
|
| 359 |
if ((mode === 'image-to-image' && sourceImages.length > 0) || mode === 'draw-to-image') {
|
| 360 |
|
| 361 |
const parts = [];
|
|
@@ -378,16 +423,18 @@ export default function Home() {
|
|
| 378 |
parts.push(textPart);
|
| 379 |
|
| 380 |
const response = await ai.models.generateContent({
|
| 381 |
-
model: 'gemini-
|
| 382 |
contents: {parts},
|
| 383 |
config: {
|
| 384 |
-
|
| 385 |
},
|
| 386 |
});
|
| 387 |
-
|
| 388 |
let foundImage = false;
|
| 389 |
-
|
| 390 |
-
|
|
|
|
|
|
|
| 391 |
if (part.inlineData) {
|
| 392 |
const imageUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
|
| 393 |
setResultImages([imageUrl]);
|
|
@@ -398,7 +445,7 @@ export default function Home() {
|
|
| 398 |
}
|
| 399 |
|
| 400 |
if (!foundImage) {
|
| 401 |
-
const textMessage =
|
| 402 |
setErrorMessage(
|
| 403 |
textMessage ||
|
| 404 |
'The model did not return an image. Please try a different prompt.',
|
|
@@ -406,7 +453,7 @@ export default function Home() {
|
|
| 406 |
}
|
| 407 |
} else { // Text-to-image
|
| 408 |
const response = await ai.models.generateImages({
|
| 409 |
-
model: 'imagen-
|
| 410 |
prompt: prompt,
|
| 411 |
config: {
|
| 412 |
numberOfImages: numberOfImages,
|
|
@@ -644,7 +691,7 @@ export default function Home() {
|
|
| 644 |
<div className="info-tooltip-container">
|
| 645 |
<Info className="w-4 h-4 info-icon" />
|
| 646 |
<span className="info-tooltip">
|
| 647 |
-
The model used for this mode is <code>imagen-
|
| 648 |
</span>
|
| 649 |
</div>
|
| 650 |
)}
|
|
@@ -652,7 +699,7 @@ export default function Home() {
|
|
| 652 |
<div className="info-tooltip-container">
|
| 653 |
<Info className="w-4 h-4 info-icon" />
|
| 654 |
<span className="info-tooltip">
|
| 655 |
-
The model used for image editing is <code>gemini-
|
| 656 |
</span>
|
| 657 |
</div>
|
| 658 |
)}
|
|
@@ -858,6 +905,43 @@ export default function Home() {
|
|
| 858 |
</div>
|
| 859 |
</div>
|
| 860 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
</div>
|
| 862 |
);
|
| 863 |
}
|
|
|
|
| 21 |
} from 'lucide-react';
|
| 22 |
import {useState, useRef, useEffect} from 'react';
|
| 23 |
|
| 24 |
+
// REMOVED: const ai = new GoogleGenAI({apiKey: process.env.API_KEY});
|
| 25 |
|
| 26 |
const aspectRatios = ['1:1', '16:9', '9:16', '4:3', '3:4'];
|
| 27 |
|
|
|
|
| 59 |
const dropdownRef = useRef<HTMLDivElement>(null);
|
| 60 |
const fileInputRef = useRef<HTMLInputElement>(null);
|
| 61 |
|
| 62 |
+
// New state for API Key management
|
| 63 |
+
const [apiKey, setApiKey] = useState('');
|
| 64 |
+
const [showApiKeyModal, setShowApiKeyModal] = useState(false);
|
| 65 |
+
const [tempApiKey, setTempApiKey] = useState('');
|
| 66 |
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
| 67 |
+
|
| 68 |
+
|
| 69 |
// Canvas state
|
| 70 |
const canvasRef = useRef<HTMLCanvasElement>(null);
|
| 71 |
const [isDrawing, setIsDrawing] = useState(false);
|
|
|
|
| 348 |
|
| 349 |
const handleSubmit = async (e: React.FormEvent) => {
|
| 350 |
e.preventDefault();
|
| 351 |
+
if (!apiKey) {
|
| 352 |
+
setShowApiKeyModal(true);
|
| 353 |
+
return;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
if (isSubmitting) return;
|
| 357 |
+
setIsSubmitting(true);
|
| 358 |
+
await generateOrEditImage();
|
| 359 |
+
setIsSubmitting(false);
|
| 360 |
+
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
const handleApiKeySubmit = async (e: React.FormEvent) => {
|
| 364 |
+
e.preventDefault();
|
| 365 |
+
if (!tempApiKey) {
|
| 366 |
+
setErrorMessage("Please enter an API key.");
|
| 367 |
+
return;
|
| 368 |
+
}
|
| 369 |
+
setApiKey(tempApiKey);
|
| 370 |
+
setShowApiKeyModal(false);
|
| 371 |
+
|
| 372 |
+
// Use a timeout to allow state to update before triggering generation
|
| 373 |
+
setTimeout(() => {
|
| 374 |
+
if (isSubmitting) return;
|
| 375 |
+
setIsSubmitting(true);
|
| 376 |
+
generateOrEditImage(tempApiKey).finally(() => setIsSubmitting(false));
|
| 377 |
+
}, 0);
|
| 378 |
+
};
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
const generateOrEditImage = async (currentApiKey?: string) => {
|
| 382 |
+
const keyToUse = currentApiKey || apiKey;
|
| 383 |
+
if (!keyToUse) {
|
| 384 |
+
setShowApiKeyModal(true);
|
| 385 |
+
return;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
if (!prompt) {
|
| 389 |
setErrorMessage('Please enter a prompt to continue.');
|
| 390 |
return;
|
|
|
|
| 400 |
setErrorMessage('');
|
| 401 |
|
| 402 |
try {
|
| 403 |
+
const ai = new GoogleGenAI({apiKey: keyToUse});
|
| 404 |
if ((mode === 'image-to-image' && sourceImages.length > 0) || mode === 'draw-to-image') {
|
| 405 |
|
| 406 |
const parts = [];
|
|
|
|
| 423 |
parts.push(textPart);
|
| 424 |
|
| 425 |
const response = await ai.models.generateContent({
|
| 426 |
+
model: 'gemini-1.5-flash-latest',
|
| 427 |
contents: {parts},
|
| 428 |
config: {
|
| 429 |
+
responseMimeType: 'application/json',
|
| 430 |
},
|
| 431 |
});
|
| 432 |
+
|
| 433 |
let foundImage = false;
|
| 434 |
+
const parsedResponse = JSON.parse(response.text);
|
| 435 |
+
|
| 436 |
+
if(parsedResponse.candidates && parsedResponse.candidates[0].content.parts) {
|
| 437 |
+
for (const part of parsedResponse.candidates[0].content.parts) {
|
| 438 |
if (part.inlineData) {
|
| 439 |
const imageUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
|
| 440 |
setResultImages([imageUrl]);
|
|
|
|
| 445 |
}
|
| 446 |
|
| 447 |
if (!foundImage) {
|
| 448 |
+
const textMessage = parsedResponse.text;
|
| 449 |
setErrorMessage(
|
| 450 |
textMessage ||
|
| 451 |
'The model did not return an image. Please try a different prompt.',
|
|
|
|
| 453 |
}
|
| 454 |
} else { // Text-to-image
|
| 455 |
const response = await ai.models.generateImages({
|
| 456 |
+
model: 'imagen-3.0-generate-001',
|
| 457 |
prompt: prompt,
|
| 458 |
config: {
|
| 459 |
numberOfImages: numberOfImages,
|
|
|
|
| 691 |
<div className="info-tooltip-container">
|
| 692 |
<Info className="w-4 h-4 info-icon" />
|
| 693 |
<span className="info-tooltip">
|
| 694 |
+
The model used for this mode is <code>imagen-3.0-generate-001</code>.
|
| 695 |
</span>
|
| 696 |
</div>
|
| 697 |
)}
|
|
|
|
| 699 |
<div className="info-tooltip-container">
|
| 700 |
<Info className="w-4 h-4 info-icon" />
|
| 701 |
<span className="info-tooltip">
|
| 702 |
+
The model used for image editing is <code>gemini-1.5-flash-latest</code>.
|
| 703 |
</span>
|
| 704 |
</div>
|
| 705 |
)}
|
|
|
|
| 905 |
</div>
|
| 906 |
</div>
|
| 907 |
)}
|
| 908 |
+
|
| 909 |
+
{showApiKeyModal && (
|
| 910 |
+
<div className="modal-backdrop">
|
| 911 |
+
<div className="card modal-card api-key-modal">
|
| 912 |
+
<div className="head">
|
| 913 |
+
<span>Add Your Gemini API Key</span>
|
| 914 |
+
<button
|
| 915 |
+
onClick={() => setShowApiKeyModal(false)}
|
| 916 |
+
className="modal-close-button">
|
| 917 |
+
<X className="w-5 h-5" />
|
| 918 |
+
</button>
|
| 919 |
+
</div>
|
| 920 |
+
<div className="content">
|
| 921 |
+
<p className="api-key-info">
|
| 922 |
+
Your API key is only stored for this session and will be lost when you reload or exit the page. It is not shared or exposed anywhere.
|
| 923 |
+
</p>
|
| 924 |
+
<form onSubmit={handleApiKeySubmit} className="api-key-form">
|
| 925 |
+
<input
|
| 926 |
+
type="password"
|
| 927 |
+
value={tempApiKey}
|
| 928 |
+
onChange={(e) => setTempApiKey(e.target.value)}
|
| 929 |
+
className="input"
|
| 930 |
+
placeholder="Enter your Gemini API Key"
|
| 931 |
+
required
|
| 932 |
+
/>
|
| 933 |
+
<button type="submit" className="button primary" disabled={isLoading}>
|
| 934 |
+
{isLoading ? (
|
| 935 |
+
<LoaderCircle className="w-5 h-5 animate-spin" />
|
| 936 |
+
) : (
|
| 937 |
+
"Submit & Run"
|
| 938 |
+
)}
|
| 939 |
+
</button>
|
| 940 |
+
</form>
|
| 941 |
+
</div>
|
| 942 |
+
</div>
|
| 943 |
+
</div>
|
| 944 |
+
)}
|
| 945 |
</div>
|
| 946 |
);
|
| 947 |
}
|