import React, { useState, useRef } from 'react';
export default function UGCStoryboardStudio() {
const [currentStep, setCurrentStep] = useState(1);
// Step 1: Product & Character State
const [productImage, setProductImage] = useState(null);
const [productImageBase64, setProductImageBase64] = useState('');
const [productName, setProductName] = useState('');
const [productUsp, setProductUsp] = useState('');
const [isAnalyzingProduct, setIsAnalyzingProduct] = useState(false);
const [characterImage, setCharacterImage] = useState(null);
const [characterImageBase64, setCharacterImageBase64] = useState('');
const [characterDescription, setCharacterDescription] = useState('');
const [isAnalyzingCharacter, setIsAnalyzingCharacter] = useState(false);
// Step 2: Audience, Hook & Setting State
const [targetAudience, setTargetAudience] = useState('Wanita Muda & Gen-Z Enthusiast (18-28 thn)');
const [customAudience, setCustomAudience] = useState('');
const [hooksList, setHooksList] = useState([]);
const [selectedHook, setSelectedHook] = useState('');
const [customHook, setCustomHook] = useState('');
const [isGeneratingHooks, setIsGeneratingHooks] = useState(false);
const [backgroundSetting, setBackgroundSetting] = useState('Kamar Tidur Estetik Modern / Desk Setup Minimalis');
// Step 3: Storyboard Configuration State
const [sceneCount, setSceneCount] = useState(6);
const [sceneDuration, setSceneDuration] = useState(5);
const [promptEngine, setPromptEngine] = useState('Nano Banana 2 Lite (Rekomendasi Utama)');
const [videoEngine, setVideoEngine] = useState('Veo 3.1 Quality');
// Step 4: Output & Preview State
const [isGeneratingStoryboard, setIsGeneratingStoryboard] = useState(false);
const [storyboardData, setStoryboardData] = useState(null);
const [toastMessage, setToastMessage] = useState('');
const [previewImages, setPreviewImages] = useState({});
const [generatingImageIndex, setGeneratingImageIndex] = useState(null);
const [playingAudioIndex, setPlayingAudioIndex] = useState(null);
const showToast = (msg) => {
setToastMessage(msg);
setTimeout(() => setToastMessage(''), 3000);
};
const handleProductImageUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onloadend = async () => {
const base64String = reader.result.split(',')[1];
setProductImage(reader.result);
setProductImageBase64(base64String);
setIsAnalyzingProduct(true);
try {
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent?key=${apiKey}`;
const payload = {
contents: [
{
role: "user",
parts: [
{ text: "Identifikasi produk dalam foto ini. Berikan: 1. Nama/Kategori Produk ringkas, 2. Unik Selling Point (USP) utama & manfaatnya untuk konten affiliate UGC TikTok. Balas dalam format ringkas Bahasa Indonesia." },
{ inlineData: { mimeType: file.type || "image/jpeg", data: base64String } }
]
}
]
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
if (text) {
const lines = text.split('\n').filter(l => l.trim());
setProductName(lines[0]?.replace(/^[0-9.#*-\s]+/, '') || 'Produk Estetik Premium');
setProductUsp(text);
showToast('✨ Produk berhasil terdeteksi otomatis oleh AI!');
}
} catch (err) {
console.error("Gagal mendeteksi gambar:", err);
} finally {
setIsAnalyzingProduct(false);
}
};
reader.readAsDataURL(file);
};
const handleCharacterImageUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onloadend = async () => {
const base64String = reader.result.split(',')[1];
setCharacterImage(reader.result);
setCharacterImageBase64(base64String);
setIsAnalyzingCharacter(true);
try {
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent?key=${apiKey}`;
const payload = {
contents: [
{
role: "user",
parts: [
{ text: "Analisis karakter dalam foto ini untuk konsistensi gambar AI. Deskripsikan: Umur perkiraan, etnis/gaya wajah, rambut, pakaian, ekspresi, dan style khas UGC. Buat ringkas dalam bahasa Inggris untuk keperluan prompt AI." },
{ inlineData: { mimeType: file.type || "image/jpeg", data: base64String } }
]
}
]
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
if (text) {
setCharacterDescription(text);
showToast('👤 Profil & Karakter berhasil diekstrak!');
}
} catch (err) {
console.error("Gagal mendeteksi karakter:", err);
} finally {
setIsAnalyzingCharacter(false);
}
};
reader.readAsDataURL(file);
};
const handleGenerateHooks = async () => {
if (!productName) {
showToast('⚠️ Isi nama produk terlebih dahulu!');
return;
}
setIsGeneratingHooks(true);
try {
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent?key=${apiKey}`;
const prompt = `Buatkan 4 opsi HOOK video UGC TikTok/Reels yang sangat tinggi konversinya dalam Bahasa Indonesia kasual/gaul untuk produk berikut:
Nama Produk: ${productName}
USP/Deskripsi: ${productUsp}
Target Market: ${targetAudience}
Buat 4 tipe hook berbeda:
1. Problem-Agitate Hook (Menyentuh pain point)
2. Curiosity / Stop Scrolling Hook (Bikin penasaran)
3. Direct Visual & Benefit Hook (Langsung kasih liat keunggulan)
4. Shocking Secret / Before-After Hook (Rahasia rahasia-an)
Format output JSON array string murni saja, contoh: ["Hook 1", "Hook 2", "Hook 3", "Hook 4"]`;
const payload = {
contents: [{ role: 'user', parts: [{ text: prompt }] }],
generationConfig: { responseMimeType: "application/json" }
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
const parsedHooks = JSON.parse(text);
if (Array.isArray(parsedHooks)) {
setHooksList(parsedHooks);
if (parsedHooks.length > 0) setSelectedHook(parsedHooks[0]);
showToast('🎯 4 Opsi Hook berhasil dibuat!');
}
} catch (err) {
console.error("Gagal membuat hooks:", err);
const fallbacks = [
`"Sumpah baru tau produk ${productName} ini ternyata bikin..."`,
`"Jangan beli ${productName} ini sebelum kamu tahu fakta ini!"`,
`"Awalnya ga percaya tapi pas nyobain ${productName} langsung kaget banget!"`,
`"Rahasia ${targetAudience} tetap awet & tampil estetik tiap hari!"`
];
setHooksList(fallbacks);
setSelectedHook(fallbacks[0]);
} finally {
setIsGeneratingHooks(false);
}
};
const handleGenerateStoryboard = async () => {
if (!productName) {
showToast('⚠️ Harap lengkapi nama produk terlebih dahulu.');
return;
}
setIsGeneratingStoryboard(true);
setCurrentStep(4);
try {
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent?key=${apiKey}`;
const chosenHookFinal = customHook || selectedHook || `Review jujur pemakaian ${productName}`;
const chosenAudienceFinal = customAudience || targetAudience;
const isYoutubeCreateEngine = videoEngine.toLowerCase().includes('youtube create');
const videoPromptRule = isYoutubeCreateEngine
? '⚠️ KHUSUS ENGINE "Youtube Create": Prompt video WAJIB KETAT MAKSIMAL 900 KARAKTER (strict limit under 900 chars), namun harus sangat kaya informasi gerakan, sudut kamera, dan subjek.'
: 'TIDAK ADA BATASAN KARAKTER untuk prompt video. Buat prompt video sespesifik, sedetail, dan seoptimal mungkin menggambarkan pergerakan kamera, subjek, pencahayaan, dan dinamika aksinya.';
const systemPrompt = `Kamu adalah Pakar Prompt UGC (User-Generated Content) Hyper-Realistis, Sutradara Konten TikTok/Reels Affiliate, dan Master Prompt Engineer untuk AI Photo Engine (${promptEngine}) serta AI Video Engine (${videoEngine}).
Tugas utama: Menghasilkan storyboard lengkap sebanyak ${sceneCount} scene bergaya UGC yang SANGAT NATURAL, HYPER-REALISTIS, dan HIGH CONVERTING.
ATURAN PROMPT FOTO (${promptEngine}):
1. TIDAK ADA BATASAN KARAKTER. Buat photoPrompt sangat komprehensif, ultra-detail, kaya deskripsi visual, dan dioptimalkan penuh untuk ${promptEngine}.
2. GAYA FOTO MENTAH HANDHELD: "Raw handheld smartphone photo, shot on iPhone 15 Pro Max 24mm f/1.8 optical lens feel, authentic UGC style, natural skin texture with subtle imperfections, realistic pore structure, soft ambient window lighting, authentic shadows, zero artificial 3D/CGI render look".
3. KONSISTENSI KARAKTER RINCI: Masukkan rincian fisik karakter secara presisi (${characterDescription || 'Indonesian young adult UGC creator, natural casual home outfit, minimal makeup, natural facial traits'}).
4. DETAIL INTERAKSI PRODUK: Jelaskan visual produk secara mendalam, cara tangan memegang produk, angle kemasan, dan penggunaan produk (${productName} - ${productUsp}).
5. Latar belakang & Komposisi: Masukkan detail environment (${backgroundSetting}), depth of field, framing, serta parameter aspect ratio 9:16 vertical video (--ar 9:16).
ATURAN PROMPT VIDEO (${videoEngine}):
1. Format Bahasa Inggris khusus yang disesuaikan penuh untuk AI Video Generator (${videoEngine}).
2. Jelaskan detail gerakan kamera (misal: natural micro-handheld shake, slow drift, smooth camera tracking, dynamic push-in, subtle tilt).
3. Rincikan perubahan aksi karakter, ekspresi wajah, interaksi produk, serta perubahan pencahayaan alami secara rinci.
4. ${videoPromptRule}
ATURAN VOICE OVER (NASKAH):
- Gunakan Bahasa Indonesia percakapan kasual UGC affiliate (seperti bicarakan rekomendasi ke teman sendiri, tanpa bahasa formal yang kaku).`;
const userQuery = `Buatkan ${sceneCount} scene storyboard UGC untuk:
- Produk: ${productName}
- Deskripsi/USP Lengkap: ${productUsp}
- Karakter Spesifikasi: ${characterDescription || 'Kreator UGC lokal kasual'}
- Hook Pilihan: ${chosenHookFinal}
- Target Audience: ${chosenAudienceFinal}
- Background Setting: ${backgroundSetting}
- Durasi per scene: ${sceneDuration} detik.
- Target Engine Foto: ${promptEngine} (Bebas batasan karakter, buat ultra-detail)
- Target Engine Video: ${videoEngine} ${isYoutubeCreateEngine ? '(MAKSIMAL 900 KARAKTER)' : '(Bebas batasan karakter, buat detail dan optimal)'}
Kembalikan respon DALAM FORMAT JSON murni sesuai skema ini:
{
"storyboardTitle": "Judul Konsep Konten",
"angleStrategy": "Penjelasan singkat strategi UGC",
"caption": {
"headline": "Teks Caption Hook untuk TikTok/Reels/Shopee",
"body": "Isi caption lengkap dengan panggilan aksi (CTA)",
"hashtags": ["#racuntiktok", "#reviewproduk", "#affiliate"]
},
"scenes": [
{
"sceneNumber": 1,
"shotType": "Talking Head / POV Handheld / Product Close-up",
"duration": "${sceneDuration}s",
"voiceover": "Naskah voiceover Bahasa Indonesia kasual",
"actionDescription": "Deskripsi aksi fisik dalam scene",
"photoPrompt": "Ultra-detailed hyper-realistic UGC photo prompt in English optimized for ${promptEngine} with --ar 9:16 (No character length limit)",
"videoPrompt": "Detailed UGC video prompt in English optimized for ${videoEngine} with exact camera movements ${isYoutubeCreateEngine ? '(strictly max 900 characters)' : '(no character length limit)'}"
}
]
}`;
const payload = {
contents: [{ role: 'user', parts: [{ text: userQuery }] }],
systemInstruction: { parts: [{ text: systemPrompt }] },
generationConfig: {
responseMimeType: "application/json",
responseSchema: {
type: "OBJECT",
properties: {
storyboardTitle: { type: "STRING" },
angleStrategy: { type: "STRING" },
caption: {
type: "OBJECT",
properties: {
headline: { type: "STRING" },
body: { type: "STRING" },
hashtags: { type: "ARRAY", items: { type: "STRING" } }
},
required: ["headline", "body", "hashtags"]
},
scenes: {
type: "ARRAY",
items: {
type: "OBJECT",
properties: {
sceneNumber: { type: "INTEGER" },
shotType: { type: "STRING" },
duration: { type: "STRING" },
voiceover: { type: "STRING" },
actionDescription: { type: "STRING" },
photoPrompt: { type: "STRING" },
videoPrompt: { type: "STRING" }
},
required: ["sceneNumber", "shotType", "duration", "voiceover", "actionDescription", "photoPrompt", "videoPrompt"]
}
}
},
required: ["storyboardTitle", "angleStrategy", "caption", "scenes"]
}
}
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
const parsedResult = JSON.parse(text);
setStoryboardData(parsedResult);
showToast('🎉 Storyboard Hyper-Realistis berhasil dibuat!');
} catch (err) {
console.error("Gagal membuat storyboard:", err);
showToast('❌ Terjadi kesalahan saat memproses storyboard.');
} finally {
setIsGeneratingStoryboard(false);
}
};
const handleGenerateSceneImagePreview = async (sceneIndex, promptText) => {
setGeneratingImageIndex(sceneIndex);
try {
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image:generateContent?key=${apiKey}`;
const payload = {
contents: [{ parts: [{ text: promptText }] }],
generationConfig: {
responseModalities: ["IMAGE"],
imageConfig: { aspectRatio: "9:16" }
}
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await response.json();
const part = result?.candidates?.[0]?.content?.parts?.find(p => p.inlineData);
if (part) {
const imageUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
setPreviewImages(prev => ({ ...prev, [sceneIndex]: imageUrl }));
showToast(`📷 Preview Scene ${sceneIndex + 1} selesai!`);
} else {
showToast('❌ Gagal menghasilkan gambar preview.');
}
} catch (err) {
console.error("Gagal generate image preview:", err);
showToast('❌ Error saat menghubungi server Gemini Image.');
} finally {
setGeneratingImageIndex(null);
}
};
const handlePlayVoiceover = (index, text) => {
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel();
if (playingAudioIndex === index) {
setPlayingAudioIndex(null);
return;
}
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'id-ID';
utterance.rate = 1.05;
utterance.pitch = 1.0;
utterance.onend = () => setPlayingAudioIndex(null);
utterance.onerror = () => setPlayingAudioIndex(null);
setPlayingAudioIndex(index);
window.speechSynthesis.speak(utterance);
} else {
showToast('Browser Anda tidak mendukung pratinjau suara.');
}
};
const copyToClipboard = async (text, label) => {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
showToast(`📋 ${label} disalin ke clipboard!`);
return;
}
} catch (err) {
console.warn("Clipboard API blocked by permissions policy, using fallback method:", err);
}
try {
const textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed";
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.opacity = "0";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (successful) {
showToast(`📋 ${label} disalin ke clipboard!`);
} else {
showToast(`❌ Gagal menyalin. Silakan salin secara manual.`);
}
} catch (fallbackErr) {
console.error("Fallback copy failed: ", fallbackErr);
showToast(`❌ Gagal menyalin ke clipboard.`);
}
};
return (
{/* Toast Alert */}
{toastMessage && (
)}
{/* Top Header */}
{/* Main Workflow Area */}
{/* Workflow Steps Navigator */}
{[
{ num: 1, title: 'Foto & Karakter', desc: 'Identifikasi Produk & Sheet' },
{ num: 2, title: 'Target & Hook', desc: 'Audience, Hook & Setting' },
{ num: 3, title: 'Opsi Storyboard', desc: 'Durasi, Scene & Engine' },
{ num: 4, title: 'Hasil Storyboard', desc: 'Prompt Foto & Video UGC' },
].map((step) => {
const isActive = currentStep === step.num;
const isDone = currentStep > step.num;
return (
);
})}
{/* STEP 1: Upload Foto Produk & Character Sheet */}
{currentStep === 1 && (
{/* Box 1: Product Upload & Details */}
1. Identifikasi Produk
Vision AI Enabled
{/* Upload Dropzone */}
{productImage ? (
Klik / Drop untuk Ganti Foto
) : (
Upload Foto Produk
AI akan otomatis membaca visual & keunggulan produk
)}
{isAnalyzingProduct && (
AI sedang mengidentifikasi foto produk...
)}
{/* Product Info Inputs */}
{/* Box 2: Character Sheet Upload & Consistency */}
2. Konsistensi Karakter Sheet
Consistent UGC Profile
{/* Upload Dropzone */}
{characterImage ? (
Klik / Drop untuk Ganti Karakter
) : (
Upload Character Sheet / Model Foto
AI akan mengekstrak detail fisik agar prompt antar scene konsisten
)}
{isAnalyzingCharacter && (
Mengekstrak profil dan konsistensi fisik model...
)}
{/* Character Details Box */}
)}
{/* STEP 2: Target Market, Hook & Background Setting */}
{currentStep === 2 && (
Target Market, Hook & Background Setting
Pilih angle pemasaran yang sesuai dengan produk dan target pembeli
Step 2 of 4
{/* Target Market Option */}
{/* 4 Hook Options */}
{isGeneratingHooks ? (
AI sedang memformulasikan 4 hook bernilai konversi tinggi...
) : (
{hooksList.map((hookText, idx) => (
{
setSelectedHook(hookText);
setCustomHook('');
}}
className={`p-3.5 rounded-xl border text-xs cursor-pointer transition-all flex items-start gap-3 ${
selectedHook === hookText && !customHook
? 'bg-gradient-to-r from-indigo-950 to-slate-900 border-indigo-500 text-white shadow-md'
: 'bg-slate-950 border-slate-800 text-slate-300 hover:border-slate-700'
}`}
>
{idx + 1}
{hookText}
))}
)}
setCustomHook(e.target.value)}
className="w-full bg-slate-950 border border-slate-700/80 rounded-xl p-3 text-xs focus:outline-none focus:border-indigo-500"
/>
{/* Background Setting */}
{[
'Kamar Tidur Estetik Modern / Desk Setup Minimalis',
'Dapur Bersih / Minimalist Kitchen Table',
'Kamar Mandi Estetik Skincare Area (Aesthetic Bathroom)',
'Interior Mobil Vlogger Style (Car Vlog Setting)',
'Kafe Kekinian / Modern Outdoor Workspace',
'Gym / Tempat Fitnes / Outdoor Park Activity'
].map((bg) => (
))}
)}
{/* STEP 3: Scene Options & Storyboard Configuration */}
{currentStep === 3 && (
Konfigurasi Scene & Prompt Engine
Atur jumlah scene, durasi per detik, serta AI Generator pilihan Anda
Step 3 of 4
{/* Scene Count */}
{[4, 6, 8, 10].map((count) => (
))}
Rekomendasi affiliate: 6 scene (durasi total ~30 detik)
{/* Duration per Scene */}
{[3, 5, 8, 10].map((dur) => (
))}
Cocok untuk TikTok/Reels fast-paced engagement
{/* AI Generator Preference */}
✨ Prompt foto dibuat ultra-detail tanpa batasan karakter.
{videoEngine.toLowerCase().includes('youtube create')
? '⚠️ Youtube Create aktif: Prompt video dibatasi ketat maks 900 karakter.'
: '✨ Prompt video dibuat optimal & detail tanpa batasan karakter.'}
{/* Summary Box */}
Ringkasan Konsep Konten UGC:
• Produk: {productName || 'Belum diisi'}
• Target: {customAudience || targetAudience}
• Scene: {sceneCount} Scene ({sceneCount * sceneDuration}s Total)
• Setting: {backgroundSetting}
• Photo AI: {promptEngine}
• Video AI: {videoEngine}
)}
{/* STEP 4: Storyboard Output & Prompts Display */}
{currentStep === 4 && (
{isGeneratingStoryboard ? (
AI Sedang Menyusun Storyboard UGC
Menyeimbangkan konsistensi karakter, sudut pengambilan foto mentah ({promptEngine}), prompt video ({videoEngine}), dan naskah Voiceover kasual khas TikTok affiliate...
) : storyboardData ? (
<>
{/* Header Info & Strategy */}
✓ UGC Storyboard Ready
{storyboardData.storyboardTitle || 'Konsep UGC Viral'}
{storyboardData.angleStrategy}
{/* Scene-by-Scene Storyboard Cards */}
{storyboardData.scenes?.map((scene, idx) => (
{/* Scene Header */}
#{scene.sceneNumber}
{scene.shotType}
({scene.duration})
{/* Voiceover TTS Trigger */}
{/* Generate Image Preview Trigger */}
{/* Content Grid: VO, Action & Prompts */}
{/* Script & Voiceover Info */}
🎙️ Voiceover Script (Bahasa Indonesia Kasual):
"{scene.voiceover}"
🎬 Aksi Visual Scene:
{scene.actionDescription}
{/* Generated Preview Image Display if available */}
{previewImages[idx] && (
📸 Sample Image Preview (Generated by Gemini Flash):
)}
{/* Prompts Section */}
{/* Photo Prompt Box */}
📸 PROMPT FOTO ({promptEngine}):
{scene.photoPrompt?.length || 0} Karakter
{scene.photoPrompt}
{/* Video Prompt Box */}
🎥 PROMPT VIDEO ({videoEngine}):
900
? 'bg-rose-500/10 text-rose-300 border-rose-500/30'
: 'bg-purple-500/10 text-purple-300 border-purple-500/20'
}`}>
{scene.videoPrompt?.length || 0} Karakter {videoEngine.toLowerCase().includes('youtube create') ? '/ Maks 900' : ''}
{scene.videoPrompt}
))}
{/* Caption & Hashtags Recommendations */}
{storyboardData.caption && (
📝 Rekomendasi Caption & Hashtag TikTok / Reels / Shopee
{storyboardData.caption.headline}
{storyboardData.caption.body}
{storyboardData.caption.hashtags?.map((tag, tIdx) => (
{tag}
))}
)}
>
) : null}
)}
{/* Footer */}
);
}