import React, { useState, useEffect, useRef } from 'react'; import { Upload, Image as ImageIcon, Wand2, Loader2, AlertCircle, X, Download, Lock, User, LogOut, Settings, KeyRound } from 'lucide-react'; import { GoogleGenAI } from '@google/genai'; declare global { interface Window { aistudio?: { hasSelectedApiKey: () => Promise; openSelectKey: () => Promise; }; } } function useApiKey() { const [hasKey, setHasKey] = useState(false); const [isLoading, setIsLoading] = useState(true); const [apiKey, setApiKey] = useState(''); useEffect(() => { checkKey(); }, []); const checkKey = async () => { try { const localApiKey = localStorage.getItem('geminiApiKey') || ''; const envApiKey = process.env.API_KEY || process.env.GEMINI_API_KEY || ''; const nextApiKey = localApiKey || envApiKey; setApiKey(nextApiKey); if (window.aistudio?.hasSelectedApiKey) { const result = await window.aistudio.hasSelectedApiKey(); setHasKey(result || Boolean(nextApiKey)); } else { setHasKey(Boolean(nextApiKey)); } } catch (e) { console.error(e); setHasKey(Boolean(localStorage.getItem('geminiApiKey') || process.env.API_KEY || process.env.GEMINI_API_KEY)); } finally { setIsLoading(false); } }; const selectKey = async () => { try { if (window.aistudio?.openSelectKey) { await window.aistudio.openSelectKey(); setHasKey(true); } else { checkKey(); } } catch (e) { console.error(e); if (e instanceof Error && e.message.includes("Requested entity was not found.")) { setHasKey(false); } } }; const saveKey = (nextApiKey: string) => { const trimmed = nextApiKey.trim(); if (trimmed) { localStorage.setItem('geminiApiKey', trimmed); } else { localStorage.removeItem('geminiApiKey'); } setApiKey(trimmed); setHasKey(Boolean(trimmed || process.env.API_KEY || process.env.GEMINI_API_KEY)); }; return { hasKey, isLoading, apiKey, selectKey, saveKey }; } function useAuth() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const loggedIn = localStorage.getItem('isLoggedIn') === 'true'; setIsLoggedIn(loggedIn); setIsLoading(false); }, []); const login = (username: string, password: string) => { const envPassword = process.env.APP_PASSWORD || '123456'; const storedPassword = localStorage.getItem('appPassword') || envPassword; if (username === 'admin' && password === storedPassword) { localStorage.setItem('isLoggedIn', 'true'); setIsLoggedIn(true); return true; } return false; }; const logout = () => { localStorage.removeItem('isLoggedIn'); setIsLoggedIn(false); }; const changePassword = (newPassword: string) => { localStorage.setItem('appPassword', newPassword); }; return { isLoggedIn, isLoading, login, logout, changePassword }; } function Login({ onLogin }: { onLogin: (u: string, p: string) => boolean }) { const [username, setUsername] = useState('admin'); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (onLogin(username, password)) { setError(''); } else { setError('Invalid username or password'); } }; return (

Private Access

Please sign in to continue

setUsername(e.target.value)} className="w-full bg-zinc-950 border border-zinc-800 rounded-xl py-3 pl-10 pr-4 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all" placeholder="admin" />
setPassword(e.target.value)} className="w-full bg-zinc-950 border border-zinc-800 rounded-xl py-3 pl-10 pr-4 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all" placeholder="••••••" />
{error && (
{error}
)}
); } function ChangePasswordModal({ onClose, onChange }: { onClose: () => void, onChange: (p: string) => void }) { const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(''); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (newPassword.length < 6) { setError('Password must be at least 6 characters'); return; } if (newPassword !== confirmPassword) { setError('Passwords do not match'); return; } onChange(newPassword); onClose(); }; return (

Change Password

setNewPassword(e.target.value)} className="w-full bg-zinc-950 border border-zinc-800 rounded-xl py-3 px-4 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all" placeholder="At least 6 characters" />
setConfirmPassword(e.target.value)} className="w-full bg-zinc-950 border border-zinc-800 rounded-xl py-3 px-4 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all" placeholder="Confirm new password" />
{error && (
{error}
)}
); } function ApiKeyModal({ currentKey, onClose, onSave }: { currentKey: string, onClose: () => void, onSave: (apiKey: string) => void }) { const [apiKey, setApiKey] = useState(currentKey); const [saved, setSaved] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onSave(apiKey); setSaved(true); setTimeout(onClose, 500); }; return (

Gemini API Key

setApiKey(e.target.value)} className="w-full bg-zinc-950 border border-zinc-800 rounded-xl py-3 px-4 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all" placeholder="AIza..." />
{saved && (
Saved
)}
); } export default function App() { const { isLoggedIn, isLoading: isAuthLoading, login, logout, changePassword } = useAuth(); const { hasKey, isLoading: isKeyLoading, apiKey, saveKey } = useApiKey(); const [showChangePassword, setShowChangePassword] = useState(false); const [showApiKey, setShowApiKey] = useState(false); if (isAuthLoading || isKeyLoading) { return
; } if (!isLoggedIn) { return ; } if (!hasKey) { return ( <>

API Key Required

To use the Gemini 3.1 Flash Image model, you need to set a Google AI Studio API key.

Billing documentation

{showApiKey && ( setShowApiKey(false)} onSave={saveKey} /> )} ); } return ( <> setShowApiKey(true)} onLogout={logout} onChangePassword={() => setShowChangePassword(true)} /> {showChangePassword && ( setShowChangePassword(false)} onChange={changePassword} /> )} {showApiKey && ( setShowApiKey(false)} onSave={saveKey} /> )} ); } function ImageEditor({ apiKey, onOpenApiKey, onLogout, onChangePassword }: { apiKey: string, onOpenApiKey: () => void, onLogout: () => void, onChangePassword: () => void }) { const [image, setImage] = useState(null); const [mimeType, setMimeType] = useState(''); const [prompt, setPrompt] = useState(''); const [isGenerating, setIsGenerating] = useState(false); const [resultImage, setResultImage] = useState(null); const [error, setError] = useState(null); const [imageSize, setImageSize] = useState<'1K' | '2K' | '4K'>('1K'); const [aspectRatio, setAspectRatio] = useState<'1:1' | '4:3' | '3:4' | '16:9' | '9:16'>('1:1'); const fileInputRef = useRef(null); const handleImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setMimeType(file.type); const reader = new FileReader(); reader.onload = (event) => { setImage(event.target?.result as string); }; reader.readAsDataURL(file); }; const handleClearImage = (e: React.MouseEvent) => { e.stopPropagation(); setImage(null); setMimeType(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleDownload = () => { if (!resultImage) return; const a = document.createElement('a'); a.href = resultImage; a.download = `generated-image-${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; const handleUseAsSource = () => { if (!resultImage) return; setImage(resultImage); // Try to extract mime type from data URL const match = resultImage.match(/^data:(image\/[a-z]+);base64,/); if (match) { setMimeType(match[1]); } setResultImage(null); }; const handleGenerate = async () => { if (!prompt) return; setIsGenerating(true); setError(null); try { if (!apiKey) { setError("Gemini API Key is missing. Please set it first."); return; } const ai = new GoogleGenAI({ apiKey }); const parts: any[] = []; if (image) { const base64Data = image.split(',')[1]; parts.push({ inlineData: { data: base64Data, mimeType: mimeType, }, }); } parts.push({ text: prompt }); const response = await ai.models.generateContent({ model: 'gemini-3.1-flash-image-preview', contents: { parts }, config: { imageConfig: { imageSize, aspectRatio, }, }, }); let foundImage = false; for (const part of response.candidates?.[0]?.content?.parts || []) { if (part.inlineData) { const base64EncodeString = part.inlineData.data; setResultImage(`data:${part.inlineData.mimeType || 'image/png'};base64,${base64EncodeString}`); foundImage = true; break; } } if (!foundImage) { setError("No image was returned by the model. It might have returned text instead."); } } catch (err: any) { console.error(err); setError(err.message || "An error occurred during generation."); if (err.message?.includes("Requested entity was not found.")) { setError("API Key error. Please refresh the page and select your API key again."); } } finally { setIsGenerating(false); } }; return (

AI Image Studio

Powered by Gemini 3.1 Flash Image

Source Image (Optional)

!image && fileInputRef.current?.click()} > {image ? ( <> Source ) : (

Click to upload an image to edit

Leave empty to generate from scratch

)}

Settings

Instructions