Spaces:
Running
Running
| // DOM Elements | |
| const apiKeyInput = document.getElementById('apiKey'); | |
| const toggleApiKeyBtn = document.getElementById('toggleApiKey'); | |
| const scriptInput = document.getElementById('scriptInput'); | |
| const themeInput = document.getElementById('themeInput'); | |
| const toolButtons = document.querySelectorAll('.tool-btn'); | |
| const outputSection = document.getElementById('outputSection'); | |
| const outputTitle = document.getElementById('outputTitle'); | |
| const outputContent = document.getElementById('outputContent'); | |
| const copyBtn = document.getElementById('copyBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const loadingOverlay = document.getElementById('loadingOverlay'); | |
| // State | |
| let currentGenerationType = ''; | |
| let currentOutput = ''; | |
| // API Key Toggle | |
| toggleApiKeyBtn.addEventListener('click', () => { | |
| const type = apiKeyInput.type === 'password' ? 'text' : 'password'; | |
| apiKeyInput.type = type; | |
| toggleApiKeyBtn.textContent = type === 'password' ? '👁️' : '🙈'; | |
| }); | |
| // Tool Button Handlers | |
| toolButtons.forEach(button => { | |
| button.addEventListener('click', async () => { | |
| const tool = button.dataset.tool; | |
| const apiKey = apiKeyInput.value.trim(); | |
| const script = scriptInput.value.trim(); | |
| // Validation | |
| if (!apiKey) { | |
| showNotification('Please enter your Google Gemini API key', 'error'); | |
| apiKeyInput.focus(); | |
| return; | |
| } | |
| if (!script) { | |
| showNotification('Please enter a script or scene', 'error'); | |
| scriptInput.focus(); | |
| return; | |
| } | |
| if (tool === 'suno' && !themeInput.value.trim()) { | |
| showNotification('Please enter a theme for audio generation', 'error'); | |
| themeInput.focus(); | |
| return; | |
| } | |
| currentGenerationType = tool; | |
| await generateContent(tool, apiKey, script); | |
| }); | |
| }); | |
| // Generate Content | |
| async function generateContent(tool, apiKey, script) { | |
| showLoading(true); | |
| const prompts = { | |
| blueprint: `You are an expert cinematographer and director. Analyze the following script and create a detailed cinematic blueprint. | |
| Include: | |
| 1. Shot-by-shot breakdown with timestamps | |
| 2. Camera angles, movements, and framing (wide, medium, close-up, etc.) | |
| 3. Lighting and color palette | |
| 4. Sound design and audio cues | |
| 5. Pacing and rhythm notes | |
| 6. Strategic context and emotional beats | |
| Format the output as a structured, professional production document. | |
| Script: | |
| ${script}`, | |
| suno: `You are a music supervisor creating audio prompts for the Suno AI music generator. Based on the script and theme provided, generate a detailed JSON prompt structure. | |
| Include: | |
| 1. Style tags (genre, mood, instrumentation) | |
| 2. Lyrics or vocal direction | |
| 3. Tempo and energy level | |
| 4. Musical progression notes | |
| Theme: ${themeInput.value.trim()} | |
| Script: | |
| ${script} | |
| Output as valid JSON with fields: style_tags (array), lyrics (string), tempo (string), mood (string), description (string)`, | |
| storyboard: `You are a professional storyboard artist. Create detailed image generation prompts for each key shot in this script. | |
| For each shot, provide: | |
| 1. Shot number and description | |
| 2. Detailed visual prompt optimized for Midjourney/DALL-E | |
| 3. Camera angle and composition notes | |
| 4. Lighting and atmosphere | |
| 5. Key visual elements and subjects | |
| Format as a numbered list of prompts, each ready to copy into an image generator. | |
| Script: | |
| ${script}`, | |
| feedback: `You are an experienced script doctor and creative consultant. Provide concise, actionable feedback on this script excerpt. | |
| Focus on: | |
| 1. Strongest elements | |
| 2. One key improvement opportunity | |
| 3. Cinematic potential | |
| 4. Emotional impact | |
| Keep feedback brief (3-4 sentences) and constructive. | |
| Script: | |
| ${script}` | |
| }; | |
| const prompt = prompts[tool]; | |
| try { | |
| const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${apiKey}`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| contents: [{ | |
| parts: [{ | |
| text: prompt | |
| }] | |
| }], | |
| generationConfig: { | |
| temperature: 0.7, | |
| topK: 40, | |
| topP: 0.95, | |
| maxOutputTokens: 8192, | |
| } | |
| }) | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.error?.message || 'API request failed'); | |
| } | |
| const data = await response.json(); | |
| const generatedText = data.candidates[0]?.content?.parts[0]?.text; | |
| if (!generatedText) { | |
| throw new Error('No content generated'); | |
| } | |
| currentOutput = generatedText; | |
| displayOutput(tool, generatedText); | |
| showNotification('Content generated successfully!', 'success'); | |
| } catch (error) { | |
| console.error('Generation error:', error); | |
| showNotification(`Error: ${error.message}`, 'error'); | |
| } finally { | |
| showLoading(false); | |
| } | |
| } | |
| // Display Output | |
| function displayOutput(tool, content) { | |
| const titles = { | |
| blueprint: 'Cinematic Blueprint', | |
| suno: 'Suno Audio Prompts', | |
| storyboard: 'Storyboard Image Prompts', | |
| feedback: 'Script Feedback' | |
| }; | |
| outputTitle.textContent = titles[tool] || 'Generated Content'; | |
| outputContent.textContent = content; | |
| outputSection.style.display = 'block'; | |
| outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
| } | |
| // Copy to Clipboard | |
| copyBtn.addEventListener('click', async () => { | |
| try { | |
| await navigator.clipboard.writeText(currentOutput); | |
| showNotification('Copied to clipboard!', 'success'); | |
| // Visual feedback | |
| const originalText = copyBtn.innerHTML; | |
| copyBtn.innerHTML = '<span class="btn-icon">✓</span>Copied!'; | |
| setTimeout(() => { | |
| copyBtn.innerHTML = originalText; | |
| }, 2000); | |
| } catch (error) { | |
| showNotification('Failed to copy', 'error'); | |
| } | |
| }); | |
| // Download as File | |
| downloadBtn.addEventListener('click', () => { | |
| const fileNames = { | |
| blueprint: 'cinematic-blueprint.txt', | |
| suno: 'suno-prompts.json', | |
| storyboard: 'storyboard-prompts.txt', | |
| feedback: 'script-feedback.txt' | |
| }; | |
| const fileName = fileNames[currentGenerationType] || 'generated-content.txt'; | |
| const blob = new Blob([currentOutput], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = fileName; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| showNotification('File downloaded!', 'success'); | |
| }); | |
| // Show/Hide Loading | |
| function showLoading(show) { | |
| loadingOverlay.style.display = show ? 'flex' : 'none'; | |
| // Disable all buttons while loading | |
| toolButtons.forEach(btn => { | |
| btn.disabled = show; | |
| }); | |
| } | |
| // Notification System | |
| function showNotification(message, type = 'info') { | |
| // Remove existing notifications | |
| const existing = document.querySelector('.notification'); | |
| if (existing) { | |
| existing.remove(); | |
| } | |
| const notification = document.createElement('div'); | |
| notification.className = `notification notification-${type}`; | |
| notification.textContent = message; | |
| const styles = { | |
| position: 'fixed', | |
| top: '20px', | |
| right: '20px', | |
| padding: '1rem 1.5rem', | |
| borderRadius: '0.75rem', | |
| fontSize: '0.875rem', | |
| fontWeight: '500', | |
| zIndex: '10000', | |
| maxWidth: '400px', | |
| boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.5)', | |
| animation: 'slideIn 0.3s ease-out', | |
| transition: 'all 0.3s ease' | |
| }; | |
| const colors = { | |
| success: { | |
| background: '#10b981', | |
| color: 'white' | |
| }, | |
| error: { | |
| background: '#ef4444', | |
| color: 'white' | |
| }, | |
| info: { | |
| background: '#6366f1', | |
| color: 'white' | |
| } | |
| }; | |
| Object.assign(notification.style, styles, colors[type] || colors.info); | |
| document.body.appendChild(notification); | |
| // Add slide-in animation | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| @keyframes slideIn { | |
| from { | |
| transform: translateX(400px); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateX(0); | |
| opacity: 1; | |
| } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| setTimeout(() => { | |
| notification.style.opacity = '0'; | |
| notification.style.transform = 'translateX(400px)'; | |
| setTimeout(() => notification.remove(), 300); | |
| }, 4000); | |
| } | |
| // Load API key from localStorage if exists | |
| window.addEventListener('DOMContentLoaded', () => { | |
| const savedApiKey = localStorage.getItem('gemini_api_key'); | |
| if (savedApiKey) { | |
| apiKeyInput.value = savedApiKey; | |
| } | |
| }); | |
| // Save API key to localStorage on change | |
| apiKeyInput.addEventListener('change', () => { | |
| const apiKey = apiKeyInput.value.trim(); | |
| if (apiKey) { | |
| localStorage.setItem('gemini_api_key', apiKey); | |
| } else { | |
| localStorage.removeItem('gemini_api_key'); | |
| } | |
| }); | |
| // Example script for demo | |
| const exampleScript = `INT. ABANDONED WAREHOUSE - NIGHT | |
| Moonlight streams through broken windows, casting geometric shadows across concrete floors. | |
| DETECTIVE SARAH CHEN (35) steps through the doorway, her flashlight cutting through darkness. She moves with practiced caution, every footstep echoing. | |
| A SOUND - metal scraping metal - from the shadows ahead. | |
| Sarah freezes. Her hand moves to her holster. | |
| SARAH | |
| (whispered) | |
| I know you're here. | |
| Silence. Then a FIGURE emerges from behind a steel pillar - MARCUS REID (40s), disheveled but defiant. | |
| MARCUS | |
| (bitter laugh) | |
| Took you long enough. | |
| Their eyes lock. Years of history compressed into a single moment.`; | |
| // Add example button functionality | |
| const addExampleBtn = document.createElement('button'); | |
| addExampleBtn.textContent = '📄 Load Example Script'; | |
| addExampleBtn.className = 'tool-btn'; | |
| addExampleBtn.style.marginTop = '1rem'; | |
| addExampleBtn.style.background = 'var(--color-surface-light)'; | |
| addExampleBtn.addEventListener('click', () => { | |
| scriptInput.value = exampleScript; | |
| showNotification('Example script loaded!', 'info'); | |
| }); | |
| document.querySelector('.script-card').appendChild(addExampleBtn); |