acecalisto3's picture
Update main.js
cf291a2 verified
raw
history blame
6.4 kB
/**
* 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(/&lt;/g, '<').replace(/&gt;/g, '>') // Revert for display
.replace(/(".+?")/g, '<span class="token-string">$1</span>')
.replace(/(&lt;\/?.+?&gt;)/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");
});