Spaces:
Sleeping
Sleeping
| /** | |
| * 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 = `<span>Transmuting...</span> <div class="spinner"></div>`; | |
| this.elements.outputContainer.classList.add('opacity-50', 'blur-sm'); | |
| } else { | |
| this.elements.generateBtn.disabled = false; | |
| this.elements.generateBtn.innerHTML = `<span>Generate Code</span> <span class="icon">β¨</span>`; | |
| 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<div class="uk-section uk-section-default">\n <div class="uk-container">\n <h1>${Utils.sanitize(prompt)}</h1>\n \n </div>\n</div>`), 1500)); | |
| }); | |
| this.renderOutput(result); | |
| this.showToast("β Generation Complete", "success"); | |
| } catch (error) { | |
| this.showToast("β System Error: Could not reach Alchemy Core.", "error"); | |
| } finally { | |
| this.setLoading(false); | |
| } | |
| } | |
| renderOutput(code) { | |
| // Simple syntax highlighting simulation | |
| const highlighted = Utils.sanitize(code) | |
| .replace(/</g, '<').replace(/>/g, '>') // Revert for display | |
| .replace(/(".+?")/g, '<span class="token-string">$1</span>') | |
| .replace(/(<\/?.+?>)/g, '<span class="token-tag">$1</span>'); | |
| this.elements.outputPre.innerHTML = highlighted; | |
| // Animate in | |
| this.elements.outputContainer.classList.remove('hidden'); | |
| this.elements.outputContainer.animate([ | |
| { opacity: 0, transform: 'translateY(20px)' }, | |
| { opacity: 1, transform: 'translateY(0)' } | |
| ], { duration: 400, easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)' }); | |
| } | |
| showToast(message, type = 'info') { | |
| const toast = document.createElement('div'); | |
| toast.className = `toast toast-${type}`; | |
| toast.textContent = message; | |
| this.elements.toast.appendChild(toast); | |
| // Remove after 3s | |
| setTimeout(() => { | |
| toast.style.opacity = '0'; | |
| setTimeout(() => toast.remove(), 300); | |
| }, 3000); | |
| } | |
| } | |
| // === BOOTSTRAP === | |
| document.addEventListener('DOMContentLoaded', () => { | |
| window.UI = new UIController(); | |
| console.log("β‘ Alchemy Client Initialized"); | |
| }); |