// Global variables let currentXMLContent = ''; let currentFilename = ''; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { // Load saved webhook URL from localStorage const savedWebhookUrl = localStorage.getItem('webhookUrl'); if (savedWebhookUrl) { document.getElementById('webhookUrl').value = savedWebhookUrl; } // Initialize event listeners initializeEventListeners(); // Character counter updateCharCount(); }); // Initialize all event listeners function initializeEventListeners() { // Generate button document.getElementById('generateBtn').addEventListener('click', generateScene); // Example chips document.querySelectorAll('.example-chip').forEach(chip => { chip.addEventListener('click', function() { const prompt = this.getAttribute('data-prompt'); document.getElementById('scenePrompt').value = prompt; updateCharCount(); showToast('Example loaded! Click Generate to create scene.'); }); }); // Character counter document.getElementById('scenePrompt').addEventListener('input', updateCharCount); // Save webhook URL document.getElementById('webhookUrl').addEventListener('change', function() { localStorage.setItem('webhookUrl', this.value); showToast('Webhook URL saved!'); }); // Download button document.getElementById('downloadBtn')?.addEventListener('click', downloadXML); // Copy button document.getElementById('copyBtn')?.addEventListener('click', copyXML); // View XML button document.getElementById('viewXmlBtn')?.addEventListener('click', toggleXmlPreview); // Enter key in prompt textarea document.getElementById('scenePrompt').addEventListener('keydown', function(e) { if (e.ctrlKey && e.key === 'Enter') { generateScene(); } }); } // Update character count function updateCharCount() { const textarea = document.getElementById('scenePrompt'); const charCount = document.getElementById('charCount'); charCount.textContent = textarea.value.length; } // Generate scene function async function generateScene() { const webhookUrl = document.getElementById('webhookUrl').value.trim(); const prompt = document.getElementById('scenePrompt').value.trim(); // Validation if (!webhookUrl) { showError('Please enter your n8n webhook URL'); return; } if (!prompt) { showError('Please describe the scene you want to create'); return; } // Hide previous results hideResults(); hideError(); // Show loading state showLoading(); const generateBtn = document.getElementById('generateBtn'); generateBtn.disabled = true; try { // Simulate loading states updateLoadingText('Connecting to n8n workflow...'); // Make API call const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: prompt }) }); updateLoadingText('AI is analyzing your prompt...'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } updateLoadingText('Selecting 3D models from library...'); const data = await response.json(); updateLoadingText('Building Unity XML scene...'); // Small delay for better UX await new Promise(resolve => setTimeout(resolve, 500)); // Hide loading and show results hideLoading(); displayResults(data, prompt); // Show success toast showToast('✅ Scene generated successfully!'); } catch (error) { console.error('Error generating scene:', error); hideLoading(); showError(`Failed to generate scene: ${error.message}`); } finally { generateBtn.disabled = false; } } // Show loading state function showLoading() { document.getElementById('loadingState').style.display = 'block'; } // Hide loading state function hideLoading() { document.getElementById('loadingState').style.display = 'none'; } // Update loading text function updateLoadingText(text) { document.getElementById('loadingSubtext').textContent = text; } // Show error function showError(message) { const errorState = document.getElementById('errorState'); const errorMessage = document.getElementById('errorMessage'); errorMessage.textContent = message; errorState.style.display = 'block'; // Scroll to error errorState.scrollIntoView({ behavior: 'smooth', block: 'center' }); } // Hide error function hideError() { document.getElementById('errorState').style.display = 'none'; } // Display results function displayResults(data, prompt) { // Store XML content globally currentXMLContent = data.xmlContent || data.xml || ''; currentFilename = data.filename || `scene_${Date.now()}.txt`; // Update result fields document.getElementById('resultEnvironment').textContent = data.environment || 'N/A'; document.getElementById('resultMood').textContent = data.mood || 'N/A'; document.getElementById('resultObjects').textContent = data.totalObjects || '0'; // Calculate and display file size const sizeKB = (currentXMLContent.length / 1024).toFixed(2); document.getElementById('resultSize').textContent = `${sizeKB} KB`; // Display prompt document.getElementById('resultPrompt').textContent = prompt; // Show results panel document.getElementById('resultsPanel').style.display = 'block'; // Scroll to results document.getElementById('resultsPanel').scrollIntoView({ behavior: 'smooth', block: 'start' }); } // Close results function closeResults() { document.getElementById('resultsPanel').style.display = 'none'; hideXmlPreview(); } // Hide results function hideResults() { document.getElementById('resultsPanel').style.display = 'none'; hideXmlPreview(); } // Download XML file function downloadXML() { if (!currentXMLContent) { showToast('⚠️ No XML content to download'); return; } // Create blob and download const blob = new Blob([currentXMLContent], { type: 'text/plain' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = currentFilename; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); showToast('📥 XML file downloaded!'); } // Copy XML to clipboard async function copyXML() { if (!currentXMLContent) { showToast('⚠️ No XML content to copy'); return; } try { await navigator.clipboard.writeText(currentXMLContent); showToast('📋 XML copied to clipboard!'); // Visual feedback on button const copyBtn = document.getElementById('copyBtn'); const originalText = copyBtn.innerHTML; copyBtn.innerHTML = 'Copied!'; copyBtn.style.background = '#10b981'; copyBtn.style.color = 'white'; setTimeout(() => { copyBtn.innerHTML = originalText; copyBtn.style.background = ''; copyBtn.style.color = ''; }, 2000); } catch (error) { console.error('Failed to copy:', error); showToast('❌ Failed to copy to clipboard'); } } // Toggle XML preview function toggleXmlPreview() { const xmlPreview = document.getElementById('xmlPreview'); const viewBtn = document.getElementById('viewXmlBtn'); if (xmlPreview.style.display === 'none' || !xmlPreview.style.display) { // Show preview document.getElementById('xmlContent').textContent = currentXMLContent; xmlPreview.style.display = 'block'; viewBtn.innerHTML = 'Hide XML'; // Scroll to preview xmlPreview.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } else { hideXmlPreview(); } } // Hide XML preview function hideXmlPreview() { const xmlPreview = document.getElementById('xmlPreview'); const viewBtn = document.getElementById('viewXmlBtn'); xmlPreview.style.display = 'none'; viewBtn.innerHTML = 'View XML'; } // Show toast notification function showToast(message, duration = 3000) { const toast = document.getElementById('toast'); const toastMessage = document.getElementById('toastMessage'); toastMessage.textContent = message; toast.classList.add('show'); setTimeout(() => { toast.classList.remove('show'); }, duration); } // Show documentation (placeholder) function showDocs() { showToast('📚 Documentation coming soon!'); } // Show about (placeholder) function showAbout() { alert('AIPlot Scene Generator\n\nVersion: 1.0\n\nPowered by:\n- n8n Workflow Automation\n- Claude AI (Anthropic)\n- Google Sheets\n\nGenerate Unity 3D scenes from natural language descriptions using AI.'); } // Keyboard shortcuts document.addEventListener('keydown', function(e) { // Ctrl/Cmd + K to focus prompt if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); document.getElementById('scenePrompt').focus(); } // Escape to close results/preview if (e.key === 'Escape') { if (document.getElementById('xmlPreview').style.display === 'block') { hideXmlPreview(); } else if (document.getElementById('resultsPanel').style.display === 'block') { closeResults(); } } }); // Handle online/offline status window.addEventListener('online', () => showToast('✅ Back online')); window.addEventListener('offline', () => showToast('⚠️ You are offline')); // Error handling for fetch window.addEventListener('unhandledrejection', function(event) { console.error('Unhandled promise rejection:', event.reason); showError(`Unexpected error: ${event.reason.message || 'Unknown error'}`); }); // Log initialization console.log('%cAIPlot Scene Generator', 'color: #6366f1; font-size: 20px; font-weight: bold;'); console.log('%cReady to generate Unity 3D scenes!', 'color: #10b981; font-size: 14px;'); console.log('%cKeyboard shortcuts:', 'color: #64748b; font-weight: bold;'); console.log(' Ctrl/Cmd + K: Focus prompt input'); console.log(' Ctrl + Enter: Generate scene'); console.log(' Escape: Close results/preview');