From 5c02b3e097f6d70a45b1fdb04bebc97feaaccf56 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:18:19 +0000 Subject: [PATCH] feat: Create AI Website Builder This commit introduces a new web application that allows users to generate websites from text prompts using multiple LLMs. The application features: - A user interface for entering a prompt and an API key. - Dynamic creation of result cards for each LLM. - Streaming API calls to the specified endpoints. - A "live coding" effect as the HTML is generated. - A live preview of the generated website in an iframe. - Progress indicators. - Fullscreen previews. - Options to copy or download the generated code. The application is built with HTML, CSS, and JavaScript. The API key is not hardcoded in the source code for security reasons. Instead, the user is prompted to enter their own API key. --- index.html | 78 ++++++++ script.js | 574 +++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 314 +++++++++++++++++++++++++++++ 3 files changed, 966 insertions(+) create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..de09098 --- /dev/null +++ b/index.html @@ -0,0 +1,78 @@ + + + + + + AI Website Builder + + + +
+

AI Website Builder

+

Enter a description of the website you want to create, and watch AI models build it for you in real-time.

+ +
+ + + +
+ + + +
+ +
+

Your generated websites will appear here

+

Get started by entering a prompt above.

+
+
+ +
+
+

Live Preview

+ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..4c80a99 --- /dev/null +++ b/script.js @@ -0,0 +1,574 @@ +// Configuration +const API_CONFIG = { + baseUrl: 'https://apis.iflow.cn/v1', + models: [ + { + name: 'GLM-4.6', + model: 'glm-4.6', + baseUrl: 'https://apis.iflow.cn/v1', + }, + { + name: 'qwen3-max', + model: 'qwen3-max', + baseUrl: 'https://apis.iflow.cn/v1', + }, + { + name: 'qwen3-coder', + model: 'qwen3-coder-plus', + baseUrl: 'https://apis.iflow.cn/v1', + }, + { + name: 'tbao', + model: 'tstars2.0', + baseUrl: 'https://apis.iflow.cn/v1', + }, + { + name: 'kimi-k2', + model: 'kimi-k2-0905', + baseUrl: 'https://apis.iflow.cn/v1', + }, + ] +}; + +// State management +let currentResults = []; +let isGenerating = false; +let currentCodeForModal = ''; +let activeTypingAnimations = {}; + +// Initialize +document.addEventListener('DOMContentLoaded', () => { + initializeEventListeners(); + checkEmptyState(); + console.log('AI Website Builder initialized'); +}); + +function initializeEventListeners() { + // Enter key shortcut + document.getElementById('promptInput').addEventListener('keydown', (e) => { + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + generateWebsites(); + } + }); + + document.getElementById('generateBtn').addEventListener('click', generateWebsites); + + // Close modal on outside click + document.getElementById('codeModal').addEventListener('click', (e) => { + if (e.target.id === 'codeModal') { + closeModal(); + } + }); + + // Close fullscreen on ESC key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + closeFullscreen(); + } + }); +} + +async function generateWebsites() { + const prompt = document.getElementById('promptInput').value.trim(); + + if (!prompt) { + showToast('Please enter a website description', 'error'); + return; + } + + if (isGenerating) { + showToast('Generation already in progress', 'warning'); + return; + } + + console.log('Starting generation with prompt:', prompt); + + isGenerating = true; + const generateBtn = document.getElementById('generateBtn'); + const resultsContainer = document.getElementById('resultsContainer'); + const emptyState = document.getElementById('emptyState'); + const progressSection = document.getElementById('progressSection'); + + // Update UI states + generateBtn.classList.add('loading'); + generateBtn.disabled = true; + generateBtn.querySelector('.btn-text').textContent = 'Generating...'; + emptyState.style.display = 'none'; + progressSection.style.display = 'block'; + + // Clear previous results + resultsContainer.innerHTML = ''; + currentResults = []; + + // Clear any active typing animations + Object.values(activeTypingAnimations).forEach(animation => clearInterval(animation)); + activeTypingAnimations = {}; + + // Animate LLM badges + const badges = document.querySelectorAll('.llm-badge'); + badges.forEach(badge => badge.classList.remove('active')); + + try { + const models = API_CONFIG.models; + const totalModels = models.length; + + for (let i = 0; i < models.length; i++) { + const model = models[i]; + + console.log(`Processing model ${i + 1}/${totalModels}: ${model.name}`); + + // Update progress + updateProgress((i / totalModels) * 100, `Generating with ${model.name}...`); + + // Activate current badge + if (badges[i]) badges[i].classList.add('active'); + + // Create result card with loading state + const cardId = `result-${i}`; + const card = createResultCard({ + id: cardId, + llm: model.name, + status: 'loading', + html: '' + }); + resultsContainer.appendChild(card); + + try { + // Generate HTML with the model and show live coding + await generateWithModelLive(model, prompt, cardId); + + } catch (error) { + console.error(`Error with ${model.name}:`, error); + updateResultCard(cardId, { + llm: model.name, + status: 'error', + error: error.message || 'Unknown error occurred' + }); + } + + // Deactivate badge + if (badges[i]) badges[i].classList.remove('active'); + } + + updateProgress(100, 'Generation complete!'); + showToast('All websites generated successfully!', 'success'); + + } catch (error) { + console.error('Generation error:', error); + showToast('Failed to generate websites: ' + error.message, 'error'); + } finally { + // Reset UI states + isGenerating = false; + generateBtn.classList.remove('loading'); + generateBtn.disabled = false; + generateBtn.querySelector('.btn-text').textContent = 'Generate Websites'; + + setTimeout(() => { + progressSection.style.display = 'none'; + }, 2000); + } +} + +async function generateWithModelLive(model, prompt, cardId) { + const apiKey = document.getElementById('apiKeyInput').value.trim(); + if (!apiKey) { + showToast('Please enter your API key', 'error'); + throw new Error('API Key is required.'); + } + const systemPrompt = `You are an expert web developer. Generate complete, valid HTML code for the requested website. +Include inline CSS styles within a + + +

Generated Website

+

${prompt}

+
+

Feature 1

+

Modern and responsive design

+
+
+

Feature 2

+

Clean and professional layout

+
+
+

Feature 3

+

Customizable content

+
+

Note: API connection failed. This is a fallback template.

+

Model: ${model.name}

+ +`; + await typingEffect(cardId, fallbackHtml, model.name, true); + } +} + +function createResultCard(result) { + const card = document.createElement('div'); + card.className = 'result-card'; + card.id = result.id; + + if (result.status === 'loading') { + card.innerHTML = ` +
+ ${result.llm} + Generating... +
+
+
+
+ `; + } + + return card; +} + +function showLiveCoding(cardId, llmName) { + const card = document.getElementById(cardId); + if (!card) return; + + const uniqueId = cardId.replace('result-', ''); + + card.innerHTML = ` +
+ ${llmName} +
+ + +
+
+
+
+ +
+
+
+
+
+ + `; +} + +async function typingEffect(cardId, html, llmName, isComplete) { + const uniqueId = cardId.replace('result-', ''); + const codeElement = document.getElementById(`code-text-${uniqueId}`); + const codeBlock = document.getElementById(`code-block-${uniqueId}`); + + if (!codeElement) { + console.warn(`Code element not found for ${cardId}`); + return; + } + + // Use a simple text update for performance + codeElement.textContent = html; + + if (codeBlock) { + codeBlock.scrollTop = codeBlock.scrollHeight; + } + + updatePreviewDuringTyping(uniqueId, html); + + if (isComplete) { + updateResultCard(cardId, { + llm: llmName, + status: 'success', + html: html + }); + + currentResults.push({ + llm: llmName, + html: html + }); + + console.log(`Generation complete for ${llmName}`); + } +} + +function updatePreviewDuringTyping(uniqueId, partialHtml) { + const iframe = document.getElementById(`iframe-${uniqueId}`); + if (iframe) { + try { + const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; + iframeDoc.open(); + iframeDoc.write(partialHtml); + iframeDoc.close(); + } catch (e) { + console.debug('Preview update error (expected):', e.message); + } + } +} + +function updateResultCard(cardId, result) { + const card = document.getElementById(cardId); + if (!card) return; + + const uniqueId = cardId.replace('result-', ''); + + if (result.status === 'success') { + const codeElement = document.getElementById(`code-text-${uniqueId}`); + if (codeElement) { + codeElement.textContent = result.html; + } + + // Final load into iframe to ensure all scripts run if any + setTimeout(() => { + updatePreviewDuringTyping(uniqueId, result.html); + }, 100); + + if (!window.generatedHTML) window.generatedHTML = {}; + window.generatedHTML[uniqueId] = result.html; + + } else { + card.innerHTML = ` +
+ ${result.llm} + ✗ Error +
+
+

Failed to generate website

+

${result.error || 'Unknown error'}

+

Check console for details (F12)

+
+ `; + } +} + +function openFullscreen(uniqueId, llmName) { + const html = window.generatedHTML[uniqueId]; + if (html) { + const fullscreenPreview = document.getElementById('fullscreenPreview'); + const fullscreenIframe = document.getElementById('fullscreenIframe'); + const llmNameElement = document.getElementById('fullscreenLLMName'); + + llmNameElement.textContent = llmName; + + const iframeDoc = fullscreenIframe.contentDocument || fullscreenIframe.contentWindow.document; + iframeDoc.open(); + iframeDoc.write(html); + iframeDoc.close(); + + fullscreenPreview.classList.add('show'); + document.getElementById('fullscreenBody').className = 'fullscreen-body desktop'; + } +} + +function closeFullscreen() { + document.getElementById('fullscreenPreview').classList.remove('show'); +} + +function toggleDeviceView(device) { + document.getElementById('fullscreenBody').className = `fullscreen-body ${device}`; +} + +function switchTab(cardId, tabName) { + const card = document.getElementById(`result-${cardId}`); + if (!card) return; + + const contents = card.querySelectorAll('.tab-content'); + const tabs = card.querySelectorAll('.tab'); + + contents.forEach(content => content.classList.remove('active')); + tabs.forEach(tab => tab.classList.remove('active')); + + card.querySelector(`#${tabName}-${cardId}`).classList.add('active'); + + const clickedTab = tabName === 'preview' ? tabs[0] : tabs[1]; + if (clickedTab) { + clickedTab.classList.add('active'); + } +} + +function copyCode(id) { + const html = window.generatedHTML[id]; + if (html) { + navigator.clipboard.writeText(html).then(() => { + showToast('Code copied to clipboard!', 'success'); + }).catch(() => { + showToast('Failed to copy code', 'error'); + }); + } +} + +function downloadHTML(id, llmName) { + const html = window.generatedHTML[id]; + if (html) { + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${llmName.toLowerCase().replace(/\s+/g, '-')}-website.html`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + showToast('HTML file downloaded!', 'success'); + } +} + +function viewFullCode(id) { + const html = window.generatedHTML[id]; + if (html) { + currentCodeForModal = html; + document.getElementById('modalCode').textContent = html; + document.getElementById('codeModal').classList.add('show'); + } +} + +function closeModal() { + document.getElementById('codeModal').classList.remove('show'); +} + +function copyModalCode() { + if (currentCodeForModal) { + navigator.clipboard.writeText(currentCodeForModal).then(() => { + showToast('Code copied to clipboard!', 'success'); + }).catch(() => { + showToast('Failed to copy code', 'error'); + }); + } +} + +function downloadCode() { + if (currentCodeForModal) { + const blob = new Blob([currentCodeForModal], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'generated-website.html'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + showToast('HTML file downloaded!', 'success'); + } +} + +function updateProgress(percentage, text) { + const progressFill = document.getElementById('progressFill'); + const progressText = document.getElementById('progressText'); + + if (progressFill) progressFill.style.width = `${percentage}%`; + if (progressText) progressText.textContent = text; +} + +function showToast(message, type = 'info') { + const toast = document.getElementById('toast'); + toast.textContent = message; + toast.className = `toast ${type}`; + toast.classList.add('show'); + + setTimeout(() => { + toast.classList.remove('show'); + }, 3000); +} + +function checkEmptyState() { + const resultsContainer = document.getElementById('resultsContainer'); + const emptyState = document.getElementById('emptyState'); + + if (resultsContainer.children.length === 0) { + emptyState.style.display = 'block'; + } else { + emptyState.style.display = 'none'; + } +} + +// Example prompts for quick testing +const examplePrompts = [ + "Create a modern portfolio website for a photographer with dark theme", + "Build a colorful landing page for a children's toy store", + "Design a minimalist blog website with a clean white design", + "Create a tech startup landing page with animations", + "Build a restaurant website with menu and reservation form" +]; + +// Add example prompt on double-click of textarea placeholder +document.getElementById('promptInput').addEventListener('dblclick', function () { + if (this.value === '') { + this.value = examplePrompts[Math.floor(Math.random() * examplePrompts.length)]; + showToast('Example prompt added!', 'success'); + } +}); diff --git a/style.css b/style.css new file mode 100644 index 0000000..bd37420 --- /dev/null +++ b/style.css @@ -0,0 +1,314 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'); + +:root { + --primary-color: #6a11cb; + --secondary-color: #2575fc; + --background-color: #f0f2f5; + --card-background: #ffffff; + --text-color: #333; + --light-text-color: #f8f9fa; + --border-color: #dee2e6; + --shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + --border-radius: 12px; +} + +body { + font-family: 'Roboto', sans-serif; + background-color: var(--background-color); + color: var(--text-color); + margin: 0; + padding: 20px; + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +h1, h2 { + text-align: center; + color: var(--primary-color); +} + +p { + text-align: center; + margin-bottom: 25px; + color: #555; +} + +.input-container { + display: flex; + gap: 15px; + margin-bottom: 30px; +} + +#promptInput, #apiKeyInput { + flex-grow: 1; + padding: 15px; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + font-size: 16px; + resize: vertical; + min-height: 50px; +} + +#generateBtn { + padding: 15px 30px; + background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); + color: var(--light-text-color); + border: none; + border-radius: var(--border-radius); + cursor: pointer; + font-size: 16px; + font-weight: 500; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 8px; +} + +#generateBtn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); +} + +#generateBtn.loading { + cursor: not-allowed; + background: #ccc; +} + +#progressSection { + margin-bottom: 30px; +} + +.llm-badges { + display: flex; + justify-content: center; + gap: 10px; + margin-bottom: 15px; +} + +.llm-badge { + padding: 8px 15px; + border-radius: 20px; + background-color: #e9ecef; + color: #495057; + font-size: 14px; + transition: all 0.3s ease; +} + +.llm-badge.active { + background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); + color: var(--light-text-color); + transform: scale(1.1); +} + +.progress-bar { + width: 100%; + height: 10px; + background-color: #e9ecef; + border-radius: 5px; + overflow: hidden; + margin-bottom: 10px; +} + +#progressFill { + width: 0%; + height: 100%; + background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); + transition: width 0.5s ease; +} + +#progressText { + text-align: center; + font-size: 14px; + color: #666; +} + +#resultsContainer { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 25px; +} + +.result-card { + background-color: var(--card-background); + border-radius: var(--border-radius); + box-shadow: var(--shadow); + overflow: hidden; + display: flex; + flex-direction: column; +} + +.result-header { + padding: 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--border-color); +} + +.result-title { + font-size: 18px; + font-weight: 500; +} + +.status-success { color: #28a745; } +.status-error { color: #dc3545; } + +.result-body { + padding: 15px; + flex-grow: 1; +} + +.tab-content { + display: none; +} +.tab-content.active { + display: block; +} + +iframe { + width: 100%; + height: 300px; + border: 1px solid var(--border-color); + border-radius: 8px; +} + +.code-block { + background-color: #2d2d2d; + color: #f8f8f2; + padding: 15px; + border-radius: 8px; + height: 300px; + overflow: auto; + font-family: 'Courier New', Courier, monospace; +} + +.result-footer { + padding: 15px; + border-top: 1px solid var(--border-color); + display: flex; + justify-content: space-around; +} + +.result-footer button { + background: none; + border: 1px solid var(--border-color); + padding: 8px 15px; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; +} +.result-footer button:hover { + background-color: #f1f1f1; +} + +/* Fullscreen Preview */ +#fullscreenPreview { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + display: none; + flex-direction: column; + z-index: 1000; +} +#fullscreenPreview.show { + display: flex; +} +.fullscreen-header { + background-color: #2d2d2d; + color: white; + padding: 15px; + display: flex; + align-items: center; + justify-content: space-between; +} +.fullscreen-body { + flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} +#fullscreenIframe { + background-color: white; + border: none; + box-shadow: 0 0 20px rgba(0,0,0,0.5); + transition: all 0.3s ease; +} +.desktop #fullscreenIframe { width: 100%; height: 100%; } +.tablet #fullscreenIframe { width: 768px; height: 1024px; max-width: 100%; max-height: 100%;} +.mobile #fullscreenIframe { width: 375px; height: 667px; max-width: 100%; max-height: 100%;} + +/* Code Modal */ +#codeModal { + position: fixed; + top: 0; left: 0; + width: 100%; height: 100%; + background: rgba(0,0,0,0.7); + display: none; + justify-content: center; + align-items: center; + z-index: 1001; +} +#codeModal.show { display: flex; } +.modal-content { + background: white; + width: 80%; + max-width: 900px; + border-radius: var(--border-radius); + display: flex; + flex-direction: column; + max-height: 80vh; +} +.modal-header, .modal-footer { + padding: 15px; + display: flex; + justify-content: space-between; + align-items: center; +} +.modal-header { border-bottom: 1px solid var(--border-color); } +.modal-footer { border-top: 1px solid var(--border-color); } +#modalCode { + flex-grow: 1; + overflow: auto; + padding: 15px; + background-color: #2d2d2d; + color: #f8f8f2; + margin: 0; +} + +/* Toast Notification */ +#toast { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + padding: 12px 25px; + border-radius: 8px; + color: white; + font-size: 16px; + z-index: 2000; + opacity: 0; + transition: opacity 0.3s, bottom 0.3s; +} +#toast.show { + opacity: 1; + bottom: 30px; +} +#toast.success { background-color: #28a745; } +#toast.error { background-color: #dc3545; } +#toast.warning { background-color: #ffc107; color: #333; } + +#emptyState { + text-align: center; + padding: 50px; + border: 2px dashed var(--border-color); + border-radius: var(--border-radius); +}