import React, { useState, useEffect } from 'react';
import { Aperture, Settings, Moon, Sun, User, Image as ImageIcon, Video, Lock, Clipboard, Loader2, UploadCloud, X, Link, RotateCcw, WandSparkles, Crown } from 'lucide-react';
// -- Data dan Konfigurasi -- //
const translations = {
id: {
title: "Prompt Generator",
by: "by Nawam",
login: "Login",
settings: "Pengaturan",
premiumAccessTitle: "Buka Fitur Premium",
premiumAccessPrompt: "Masukkan kata sandi untuk membuka fitur premium:",
premiumAccessSubmit: "Buka Akses",
premiumUnlocked: "Fitur premium telah dibuka!",
premiumWrongPass: "Kata sandi salah.",
imagePromptTab: "Prompt Gambar",
videoPromptTab: "Prompt Video",
providerLabel: "Pilih Penyedia",
keywordsLabel: "Masukkan Kata Kunci/Deskripsi",
generateButton: "Buat Prompt",
generatingButton: "Membuat...",
resetButton: "Atur Ulang",
advancedOptionsTitle: "Opsi Tingkat Lanjut",
variationsLabel: "Jumlah Variasi",
visualStyleLabel: "Gaya Visual",
lightingLabel: "Pencahayaan",
compositionLabel: "Komposisi",
shotTypeLabel: "Jenis Tembakan",
moodLabel: "Suasana/Atmosfer",
durationLabel: "Durasi (detik)",
frameRateLabel: "Frame Rate (fps)",
aspectRatioLabel: "Rasio Aspek",
negativePromptLabel: "Prompt Negatif (apa yang harus dihindari)",
chaosLabel: "Tingkat Kekacauan (Chaos)",
freeStylesLabel: "Opsi Gratis",
premiumStylesLabel: "Opsi Premium",
promptResultLabel: "Hasil Prompt",
copyButton: "Salin",
copiedButton: "Tersalin!",
uploadFeature: {
title: "Deskripsikan dari Gambar",
},
adSlot: "Slot Iklan (Versi Gratis)",
footerVersion: "Versi",
footerLanguage: "Bahasa",
errorOccurred: "Terjadi kesalahan. Silakan coba lagi.",
errorKeywords: "Kata kunci tidak boleh kosong.",
},
en: {
title: "Prompt Generator",
by: "by Nawam",
login: "Login",
settings: "Settings",
premiumAccessTitle: "Unlock Premium Features",
premiumAccessPrompt: "Enter password to unlock premium features:",
premiumAccessSubmit: "Unlock",
premiumUnlocked: "Premium features unlocked!",
premiumWrongPass: "Incorrect password.",
imagePromptTab: "Image Prompt",
videoPromptTab: "Video Prompt",
providerLabel: "Select Provider",
keywordsLabel: "Enter Keywords/Description",
generateButton: "Generate Prompt",
generatingButton: "Generating...",
resetButton: "Reset",
advancedOptionsTitle: "Advanced Options",
variationsLabel: "Number of Variations",
visualStyleLabel: "Visual Style",
lightingLabel: "Lighting",
compositionLabel: "Composition",
shotTypeLabel: "Shot Type",
moodLabel: "Mood/Atmosphere",
durationLabel: "Duration (seconds)",
frameRateLabel: "Frame Rate (fps)",
aspectRatioLabel: "Aspect Ratio",
negativePromptLabel: "Negative Prompt (what to avoid)",
chaosLabel: "Chaos Level",
freeStylesLabel: "Free Options",
premiumStylesLabel: "Premium Options",
promptResultLabel: "Prompt Result",
copyButton: "Copy",
copiedButton: "Copied!",
uploadFeature: {
title: "Describe from Image",
cta: "Upload Image",
or: "or",
urlPlaceholder: "Paste image link here...",
},
adSlot: "Ad Slot (Free Version)",
footerVersion: "Version",
footerLanguage: "Language",
errorOccurred: "An error occurred. Please try again.",
errorKeywords: "Keywords cannot be empty.",
}
};
const imageProviders = ["Midjourney", "Imagen 3", "DALL-E 3", "Stable Diffusion"];
const videoProviders = ["Sora", "Veo", "Kling", "Stable Video"];
const aspectRatios = ["16:9 (Landscape)", "1:1 (Square)", "9:16 (Portrait)", "4:3 (Classic)", "3:2 (Photo)"];
const optionsData = {
visualStyleImage: { free: ["Default", "Photorealistic", "Digital Art", "Illustration"], premium: ["Anime (80s style)", "Epic Fantasy Painting", "Ghibli Style", "Pixel Art", "3D Model Render", "Vaporwave", "Cyberpunk"] },
visualStyleVideo: { free: ["Default", "Cinematic", "Documentary", "Home Video"], premium: ["Found Footage", "Music Video", "Noir Film", "Time-lapse", "Slow Motion", "Wes Anderson Style", "Animated (2D)"] },
lighting: { free: ["Default", "Natural Light", "Studio Light", "Dramatic Lighting"], premium: ["Cinematic Lighting", "Golden Hour", "Blue Hour", "Rim Lighting", "Neon Lights", "Volumetric Lighting"] },
composition: { free: ["Default", "Centered", "Asymmetrical", "Close-up"], premium: ["Rule of Thirds", "Leading Lines", "Dutch Angle", "Bird's-eye View", "Worm's-eye View", "Symmetrical"] },
shotType: { free: ["Default", "Medium Shot", "Wide Shot", "Close-up Shot"], premium: ["Extreme Wide Shot", "Cowboy Shot", "Point of View (POV)", "Crane Shot", "Drone Shot", "Tracking Shot"] },
mood: { free: ["Default", "Happy", "Sad", "Mysterious"], premium: ["Serene", "Suspenseful", "Nostalgic", "Energetic", "Eerie", "Romantic", "Tense"] },
duration: { free: ["Default", "5s", "10s", "15s"], premium: ["3s", "20s", "30s", "60s"] },
frameRate: { free: ["Default", "24fps (Cinematic)", "30fps (Standard)"], premium: ["12fps (Animation)", "60fps (Smooth)", "120fps (Slow-mo)"] }
};
// -- Komponen UI -- //
const Header = ({ isDarkMode, toggleTheme, onUnlockPremium, t, isPremium }) => (
);
const Tab = ({ label, icon, isActive, onClick }) => (
);
const PromptOutput = ({ prompts, isLoading, t, variationCount }) => {
const [copied, setCopied] = useState(null);
const handleCopy = (text, index) => { if (!text) return; const textArea = document.createElement("textarea"); textArea.value = text; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); setCopied(index); setTimeout(() => setCopied(null), 2000); } catch (err) { console.error('Gagal menyalin teks: ', err); } document.body.removeChild(textArea); };
if (isLoading) { return (
{Array.from({ length: variationCount }).map((_, i) => (
))}
) }
if (!prompts || prompts.length === 0) return null;
return (
{t.promptResultLabel} ({prompts.length})
{prompts.map((prompt, index) => (
{prompt}
))}
);
}
const PremiumModal = ({ t, onClose, onUnlock }) => {
const [password, setPassword] = useState(''); const [error, setError] = useState('');
const handleSubmit = (e) => { e.preventDefault(); if (password === 'premium') { onUnlock(); onClose(); } else { setError(t.premiumWrongPass); } };
return (
e.stopPropagation()}>
{t.premiumAccessTitle}
);
};
const OptionDropdown = ({ label, value, onChange, options, isPremium, t }) => (
);
// Komponen utama untuk Generator
const PromptGenerator = ({ language, t, isPremium, onUnlockPremium }) => {
// State
const [activeTab, setActiveTab] = useState('image');
const [provider, setProvider] = useState('Midjourney');
const [keywords, setKeywords] = useState('');
const [generatedPrompts, setGeneratedPrompts] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [uploadedImage, setUploadedImage] = useState(null);
const [imageUrl, setImageUrl] = useState('');
const [isDescribing, setIsDescribing] = useState(false);
const [visualStyle, setVisualStyle] = useState(optionsData.visualStyleImage.free[0]);
const [lighting, setLighting] = useState(optionsData.lighting.free[0]);
const [composition, setComposition] = useState(optionsData.composition.free[0]);
const [shotType, setShotType] = useState(optionsData.shotType.free[0]);
const [mood, setMood] = useState(optionsData.mood.free[0]);
const [duration, setDuration] = useState(optionsData.duration.free[0]);
const [frameRate, setFrameRate] = useState(optionsData.frameRate.free[0]);
const [aspectRatio, setAspectRatio] = useState(aspectRatios[0]);
const [negativePrompt, setNegativePrompt] = useState('');
const [chaos, setChaos] = useState(0);
const [variationCount, setVariationCount] = useState(1);
const currentProviders = activeTab === 'image' ? imageProviders : videoProviders;
const resetStatesToDefault = () => {
setProvider(currentProviders[0]);
setVisualStyle(activeTab === 'image' ? optionsData.visualStyleImage.free[0] : optionsData.visualStyleVideo.free[0]);
setLighting(optionsData.lighting.free[0]); setComposition(optionsData.composition.free[0]);
setShotType(optionsData.shotType.free[0]); setMood(optionsData.mood.free[0]); setDuration(optionsData.duration.free[0]); setFrameRate(optionsData.frameRate.free[0]);
};
useEffect(() => { resetStatesToDefault(); }, [activeTab, language]);
useEffect(() => { if (!isPremium && variationCount > 2) { setVariationCount(2); } }, [isPremium, variationCount]);
const handleReset = () => { setKeywords(''); setGeneratedPrompts([]); setError(''); setUploadedImage(null); setImageUrl(''); resetStatesToDefault(); setAspectRatio(aspectRatios[0]); setNegativePrompt(''); setChaos(0); setVariationCount(1); };
const cleanPrompt = (text) => text.replace(/^(prompt:|here is your prompt:|sure, here's a prompt:|```json|```\s*)/gim, '').replace(/(\r\n|\n|\r)/gm," ").replace(/(--[a-zA-Z]+)\s+:/g, '$1 ').trim();
const handleImageUpload = (e) => { if(!isPremium) return; const file = e.target.files[0]; if (file?.type.startsWith('image/')) { const reader = new FileReader(); reader.onloadend = () => { setUploadedImage(reader.result); setImageUrl(''); generateDescriptionFromImage(reader.result.split(',')[1]); }; reader.readAsDataURL(file); } };
const generateDescriptionFromImage = async (base64ImageData) => { if (!isPremium) return; setIsDescribing(true); setError(''); setGeneratedPrompts([]); const promptText = language === 'id' ? "Deskripsikan gambar ini secara ringkas tapi detil (subjek utama, aksi, lingkungan, dan gaya visual) untuk membuat prompt AI." : "Briefly but descriptively describe this image (main subject, action, environment, and visual style) for an AI prompt."; const payload = { contents: [{ role: "user", parts: [{ text: promptText }, { inlineData: { mimeType: "image/jpeg", data: base64ImageData } }] }] }; const apiKey = ""; const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) throw new Error(`API Error: ${response.statusText}`); const result = await response.json(); if (result.candidates?.length > 0) { setKeywords(result.candidates[0].content.parts[0].text.trim()); } else { throw new Error(t.errorOccurred); } } catch (err) { console.error(err); setError(err.message); } finally { setIsDescribing(false); } };
const handleDescribeFromUrl = async () => { if (!imageUrl.trim() || !isPremium) return; setIsDescribing(true); setError(''); try { const response = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(imageUrl)}`); if (!response.ok) throw new Error('Network response was not ok.'); const blob = await response.blob(); const reader = new FileReader(); reader.onloadend = () => { setUploadedImage(reader.result); generateDescriptionFromImage(reader.result.split(',')[1]); }; reader.readAsDataURL(blob); } catch (err) { console.error("CORS or fetch error:", err); setError(t.errorOccurred); setIsDescribing(false); } };
const handleGeneratePrompt = async (e) => {
e.preventDefault(); if (!keywords.trim()) { setError(t.errorKeywords); return; } setIsLoading(true); setError(''); setGeneratedPrompts([]);
const finalVariationCount = isPremium ? variationCount : Math.min(variationCount, 2);
const detailsArray = [];
if (visualStyle !== 'Default') detailsArray.push(`visual style: ${visualStyle}`);
if (activeTab === 'image') {
if (lighting !== 'Default') detailsArray.push(`lighting: ${lighting}`);
if (composition !== 'Default') detailsArray.push(`composition: ${composition}`);
} else { // video
if (shotType !== 'Default') detailsArray.push(`shot type: ${shotType}`);
if (mood !== 'Default') detailsArray.push(`mood: ${mood}`);
if (duration !== 'Default') detailsArray.push(`duration: ${duration}`);
if (frameRate !== 'Default') detailsArray.push(`frame rate: ${frameRate}`);
}
const promptDetails = detailsArray.join(', ');
let premiumDetails = isPremium ? ` --ar ${aspectRatio.split(' ')[0]}` : '';
if (isPremium && negativePrompt) { premiumDetails += ` --no ${negativePrompt}`; }
if (isPremium && chaos > 0) { premiumDetails += ` --chaos ${chaos}`; }
const metaPrompt = `You are a professional prompt engineer. Your task is to generate ${finalVariationCount} distinct, highly detailed, and effective prompts for the AI generator "${provider}". Base the prompts on the description: "${keywords}". ${promptDetails ? `Incorporate these details: ${promptDetails}.` : ''} ${isPremium && premiumDetails ? `Also add these advanced parameters: "${premiumDetails}".` : ''} Your response MUST ONLY contain the prompt text. Do not add any extra words, titles, or explanations. Each prompt should be on a new line.`;
const payload = { contents: [{ role: "user", parts: [{ text: metaPrompt }] }]};
const apiKey = ""; const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) throw new Error(`API Error: ${response.statusText}`); const result = await response.json(); if (result.candidates?.length > 0) { const text = result.candidates[0].content.parts[0].text; const prompts = text.split('\n').filter(p => p.trim() !== '').map(p => cleanPrompt(p)); setGeneratedPrompts(prompts); } else { throw new Error(t.errorOccurred); } } catch (err) { console.error(err); setError(err.message); } finally { setIsLoading(false); }
};
return (
} isActive={activeTab === 'image'} onClick={() => setActiveTab('image')} /> } isActive={activeTab === 'video'} onClick={() => setActiveTab('video')} />
The form has been sent - thank you.
Please fill in all required fields!