/** * main.js — YOOtheme Alchemy Controller * Architecture: ES6 Modules | Async/Await | Event Delegation * * "The Hand that wields the logic." */ // === CONFIGURATION === const CONFIG = { API_ENDPOINT: "/gradio_api/call/predict", // Points to local Gradio instance ANIMATION_DURATION: 300, DEBOUNCE_DELAY: 150 }; // === UTILITIES === const Utils = { debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; }, sanitize(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }, copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { UI.showToast("✨ Code copied to clipboard", "success"); }).catch(err => { UI.showToast("❌ Failed to copy", "error"); console.error(err); }); } }; // === API CLIENT === class AlchemyClient { constructor(endpoint) { this.endpoint = endpoint; } async generate(prompt, componentType) { try { // Simulating connection to the Gradio Backend (app.py) // Note: Actual implementation depends on specific Gradio API exposure const response = await fetch(this.endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: [prompt, [], componentType] // Standard Gradio payload structure }) }); if (!response.ok) throw new Error(`HTTP Error: ${response.status}`); const result = await response.json(); return result.data ? result.data[0] : null; } catch (error) { console.error("Alchemy API Error:", error); throw error; } } } // === UI CONTROLLER === class UIController { constructor() { this.elements = { input: document.getElementById('user-prompt'), generateBtn: document.getElementById('generate-btn'), outputContainer: document.getElementById('code-output'), outputPre: document.getElementById('output-pre'), componentSelect: document.getElementById('component-select'), loader: document.getElementById('alchemy-loader'), toast: document.getElementById('toast-container') }; this.client = new AlchemyClient(CONFIG.API_ENDPOINT); this.initEventListeners(); } initEventListeners() { // Generate Button this.elements.generateBtn.addEventListener('click', () => this.handleGeneration()); // Ctrl+Enter Shortcut this.elements.input.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { this.handleGeneration(); } }); // Copy Button (Event Delegation) document.addEventListener('click', (e) => { if (e.target.closest('.copy-btn')) { const code = this.elements.outputPre.textContent; Utils.copyToClipboard(code); } }); } setLoading(isLoading) { if (isLoading) { this.elements.generateBtn.disabled = true; this.elements.generateBtn.innerHTML = `Transmuting...
`; this.elements.outputContainer.classList.add('opacity-50', 'blur-sm'); } else { this.elements.generateBtn.disabled = false; this.elements.generateBtn.innerHTML = `Generate Code ✨`; this.elements.outputContainer.classList.remove('opacity-50', 'blur-sm'); } } async handleGeneration() { const prompt = this.elements.input.value.trim(); const type = this.elements.componentSelect.value; if (!prompt) { this.showToast("⚠️ Please enter a prompt first.", "warning"); return; } this.setLoading(true); try { // For demo purposes, if API is not connected, we mock a response // to show the UI behavior (remove this in production) const result = await this.client.generate(prompt, type).catch(() => { // Fallback mock for UI demonstration if backend isn't reachable return new Promise(resolve => setTimeout(() => resolve(`\n