// 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');