Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>App Creator AI - Hugging Face Edition</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/lib/core.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/lib/languages/javascript.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/lib/languages/html.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/lib/languages/css.min.js"></script> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/styles/github.css"> | |
<style> | |
/* Custom scrollbar */ | |
::-webkit-scrollbar { | |
width: 8px; | |
height: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: transparent; | |
} | |
::-webkit-scrollbar-thumb { | |
background: #5D5CDE; | |
border-radius: 4px; | |
} | |
::-webkit-scrollbar-thumb:hover { | |
background: #4a49b0; | |
} | |
/* Dark mode adjustments */ | |
.dark .hljs { | |
background: #2d2d2d; | |
color: #ccc; | |
} | |
.dark .hljs-tag, | |
.dark .hljs-keyword { | |
color: #e78c45; | |
} | |
.dark .hljs-title, | |
.dark .hljs-class, | |
.dark .hljs-section { | |
color: #e7c547; | |
} | |
.dark .hljs-string { | |
color: #b9ca4a; | |
} | |
.dark .hljs-attr { | |
color: #70c0b1; | |
} | |
</style> | |
<script> | |
tailwind.config = { | |
darkMode: 'class', | |
theme: { | |
extend: { | |
colors: { | |
primary: '#5D5CDE', | |
'primary-dark': '#4a49b0', | |
'primary-light': '#7a79e6', | |
}, | |
} | |
} | |
} | |
</script> | |
</head> | |
<body class="min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200"> | |
<div class="container mx-auto px-4 py-6 max-w-6xl"> | |
<header class="mb-8 text-center"> | |
<h1 class="text-3xl font-bold text-primary dark:text-primary-light mb-2">App Creator AI</h1> | |
<p class="text-gray-600 dark:text-gray-400">Describe your app and I'll generate the code for you</p> | |
</header> | |
<main class="space-y-8"> | |
<!-- API Key Input --> | |
<section class="space-y-4"> | |
<h2 class="text-xl font-semibold">0. Setup Hugging Face API</h2> | |
<div class="bg-gray-100 dark:bg-gray-800 rounded-md p-4"> | |
<p class="mb-4 text-sm">You need a free Hugging Face API token to use this app. <a href="https://huggingface.co/settings/tokens" target="_blank" class="text-primary dark:text-primary-light underline">Get your token here</a>.</p> | |
<div class="flex flex-col md:flex-row gap-4"> | |
<input type="password" id="apiKeyInput" placeholder="Paste your Hugging Face API token here" class="flex-grow p-2 text-base border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary"> | |
<button id="saveApiKeyBtn" class="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-md transition-colors duration-200">Save Token</button> | |
</div> | |
<p id="apiKeyStatus" class="mt-2 text-sm text-gray-500 dark:text-gray-400">No API token saved</p> | |
</div> | |
</section> | |
<!-- App Description Input --> | |
<section class="space-y-4"> | |
<div class="flex justify-between items-center"> | |
<h2 class="text-xl font-semibold">1. Describe Your App</h2> | |
<div class="flex items-center space-x-2"> | |
<label for="modelSelect" class="text-sm">AI Model:</label> | |
<select id="modelSelect" class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md px-3 py-1 text-base focus:outline-none focus:ring-2 focus:ring-primary"> | |
<option value="mistralai/Mistral-7B-Instruct-v0.2" selected>Mistral-7B-Instruct</option> | |
<option value="google/gemma-7b-it">Gemma-7B-Instruct</option> | |
<option value="meta-llama/Llama-2-7b-chat-hf">Llama-2-7B-Chat</option> | |
<option value="bigcode/starcoder2-15b">StarCoder2-15B</option> | |
</select> | |
</div> | |
</div> | |
<textarea id="appDescription" rows="5" placeholder="Describe the app you want to create in detail. For example: 'Create a to-do list app with the ability to add, remove, and mark tasks as complete. The app should have a clean design with a dark mode option.'" class="w-full p-3 text-base border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary resize-y"></textarea> | |
<button id="generateBtn" class="w-full md:w-auto px-6 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-md transition-colors duration-200 flex items-center justify-center"> | |
<span id="generateBtnText">Generate App</span> | |
<svg id="generateBtnSpinner" class="animate-spin ml-2 h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
</svg> | |
</button> | |
</section> | |
<!-- Results Section (hidden initially) --> | |
<section id="resultsSection" class="hidden space-y-6"> | |
<div class="border-t border-gray-300 dark:border-gray-700 pt-6"> | |
<h2 class="text-xl font-semibold mb-4">2. Generated App</h2> | |
<!-- Tabs --> | |
<div class="flex border-b border-gray-300 dark:border-gray-700 mb-4"> | |
<button id="previewTab" class="px-4 py-2 font-medium border-b-2 border-primary text-primary dark:text-primary-light">Preview</button> | |
<button id="codeTab" class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400 hover:text-primary dark:hover:text-primary-light">Code</button> | |
</div> | |
<!-- Preview panel --> | |
<div id="previewPanel" class="bg-gray-100 dark:bg-gray-800 rounded-md p-4 min-h-[300px]"> | |
<div class="bg-white dark:bg-gray-900 rounded-md w-full h-full min-h-[300px] border border-gray-300 dark:border-gray-700 overflow-hidden"> | |
<iframe id="previewFrame" class="w-full h-[500px] border-none"></iframe> | |
</div> | |
</div> | |
<!-- Code panel --> | |
<div id="codePanel" class="hidden"> | |
<div class="bg-gray-100 dark:bg-gray-800 rounded-md p-4"> | |
<pre><code id="codeDisplay" class="language-html text-sm overflow-x-auto"></code></pre> | |
<div class="flex gap-4 mt-4"> | |
<button id="copyCodeBtn" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md text-sm font-medium transition-colors duration-200 flex items-center"> | |
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /> | |
</svg> | |
Copy Code | |
</button> | |
<button id="downloadCodeBtn" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md text-sm font-medium transition-colors duration-200 flex items-center"> | |
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> | |
</svg> | |
Download HTML | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</section> | |
<!-- Refinement Section (hidden initially) --> | |
<section id="refinementSection" class="hidden space-y-4 border-t border-gray-300 dark:border-gray-700 pt-6"> | |
<h2 class="text-xl font-semibold">3. Refine Your App</h2> | |
<textarea id="refinementInput" rows="3" placeholder="Describe the changes you want to make to the app. For example: 'Add a search feature to filter tasks' or 'Change the color scheme to blue'." class="w-full p-3 text-base border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary resize-y"></textarea> | |
<button id="refineBtn" class="w-full md:w-auto px-6 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-md transition-colors duration-200 flex items-center justify-center"> | |
<span id="refineBtnText">Refine App</span> | |
<svg id="refineBtnSpinner" class="animate-spin ml-2 h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
</svg> | |
</button> | |
</section> | |
</main> | |
<footer class="mt-12 text-center text-gray-500 dark:text-gray-400 text-sm"> | |
<p>Created with App Creator AI - Hugging Face Edition</p> | |
</footer> | |
</div> | |
<script> | |
// Dark mode detection and handling | |
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { | |
document.documentElement.classList.add('dark'); | |
} | |
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { | |
if (event.matches) { | |
document.documentElement.classList.add('dark'); | |
} else { | |
document.documentElement.classList.remove('dark'); | |
} | |
}); | |
// DOM Elements | |
const apiKeyInput = document.getElementById('apiKeyInput'); | |
const saveApiKeyBtn = document.getElementById('saveApiKeyBtn'); | |
const apiKeyStatus = document.getElementById('apiKeyStatus'); | |
const generateBtn = document.getElementById('generateBtn'); | |
const generateBtnText = document.getElementById('generateBtnText'); | |
const generateBtnSpinner = document.getElementById('generateBtnSpinner'); | |
const appDescription = document.getElementById('appDescription'); | |
const modelSelect = document.getElementById('modelSelect'); | |
const resultsSection = document.getElementById('resultsSection'); | |
const refinementSection = document.getElementById('refinementSection'); | |
const codeDisplay = document.getElementById('codeDisplay'); | |
const previewFrame = document.getElementById('previewFrame'); | |
const copyCodeBtn = document.getElementById('copyCodeBtn'); | |
const downloadCodeBtn = document.getElementById('downloadCodeBtn'); | |
const previewTab = document.getElementById('previewTab'); | |
const codeTab = document.getElementById('codeTab'); | |
const previewPanel = document.getElementById('previewPanel'); | |
const codePanel = document.getElementById('codePanel'); | |
const refinementInput = document.getElementById('refinementInput'); | |
const refineBtn = document.getElementById('refineBtn'); | |
const refineBtnText = document.getElementById('refineBtnText'); | |
const refineBtnSpinner = document.getElementById('refineBtnSpinner'); | |
// State variables | |
let currentCode = ''; | |
let apiKey = localStorage.getItem('hf_api_key') || ''; | |
// Initialize API key field | |
if (apiKey) { | |
apiKeyInput.value = '••••••••••••••••••••••••••'; | |
apiKeyStatus.textContent = 'API token saved'; | |
apiKeyStatus.classList.add('text-green-500'); | |
apiKeyStatus.classList.remove('text-gray-500', 'text-red-500'); | |
} | |
// Save API key | |
saveApiKeyBtn.addEventListener('click', () => { | |
const newApiKey = apiKeyInput.value.trim(); | |
if (!newApiKey) { | |
apiKeyStatus.textContent = 'Please enter a valid API token'; | |
apiKeyStatus.classList.add('text-red-500'); | |
apiKeyStatus.classList.remove('text-green-500', 'text-gray-500'); | |
return; | |
} | |
// Save to localStorage | |
localStorage.setItem('hf_api_key', newApiKey); | |
apiKey = newApiKey; | |
// Update UI | |
apiKeyInput.value = '••••••••••••••••••••••••••'; | |
apiKeyStatus.textContent = 'API token saved'; | |
apiKeyStatus.classList.add('text-green-500'); | |
apiKeyStatus.classList.remove('text-red-500', 'text-gray-500'); | |
}); | |
// Tab switching | |
previewTab.addEventListener('click', () => { | |
previewTab.classList.add('border-b-2', 'border-primary', 'text-primary', 'dark:text-primary-light'); | |
previewTab.classList.remove('text-gray-600', 'dark:text-gray-400'); | |
codeTab.classList.remove('border-b-2', 'border-primary', 'text-primary', 'dark:text-primary-light'); | |
codeTab.classList.add('text-gray-600', 'dark:text-gray-400'); | |
previewPanel.classList.remove('hidden'); | |
codePanel.classList.add('hidden'); | |
}); | |
codeTab.addEventListener('click', () => { | |
codeTab.classList.add('border-b-2', 'border-primary', 'text-primary', 'dark:text-primary-light'); | |
codeTab.classList.remove('text-gray-600', 'dark:text-gray-400'); | |
previewTab.classList.remove('border-b-2', 'border-primary', 'text-primary', 'dark:text-primary-light'); | |
previewTab.classList.add('text-gray-600', 'dark:text-gray-400'); | |
codePanel.classList.remove('hidden'); | |
previewPanel.classList.add('hidden'); | |
}); | |
// Copy code to clipboard | |
copyCodeBtn.addEventListener('click', async () => { | |
try { | |
await navigator.clipboard.writeText(currentCode); | |
const originalText = copyCodeBtn.innerHTML; | |
copyCodeBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>Copied!'; | |
setTimeout(() => { | |
copyCodeBtn.innerHTML = originalText; | |
}, 2000); | |
} catch (err) { | |
console.error('Failed to copy: ', err); | |
} | |
}); | |
// Download code as HTML file | |
downloadCodeBtn.addEventListener('click', () => { | |
const blob = new Blob([currentCode], { type: 'text/html' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = 'app.html'; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(url); | |
}); | |
// Extract code from markdown response | |
function extractCodeFromMarkdown(markdown) { | |
const codeRegex = /```(?:html|)\n([\s\S]*?)```/; | |
const match = markdown.match(codeRegex); | |
return match ? match[1] : ''; | |
} | |
// Update preview iframe | |
function updatePreview(code) { | |
const blob = new Blob([code], { type: 'text/html' }); | |
const url = URL.createObjectURL(blob); | |
previewFrame.src = url; | |
} | |
// Helper functions for button states | |
function setGenerating(isGenerating) { | |
if (isGenerating) { | |
generateBtn.disabled = true; | |
generateBtnText.textContent = 'Generating...'; | |
generateBtnSpinner.classList.remove('hidden'); | |
} else { | |
generateBtn.disabled = false; | |
generateBtnText.textContent = 'Generate App'; | |
generateBtnSpinner.classList.add('hidden'); | |
} | |
} | |
function setRefining(isRefining) { | |
if (isRefining) { | |
refineBtn.disabled = true; | |
refineBtnText.textContent = 'Refining...'; | |
refineBtnSpinner.classList.remove('hidden'); | |
} else { | |
refineBtn.disabled = false; | |
refineBtnText.textContent = 'Refine App'; | |
refineBtnSpinner.classList.add('hidden'); | |
} | |
} | |
// Call Hugging Face Inference API | |
async function callHuggingFaceAPI(model, prompt) { | |
if (!apiKey) { | |
throw new Error('Please add your Hugging Face API token first'); | |
} | |
// Prepare the prompt for different model types | |
let formattedPrompt = prompt; | |
if (model.includes('mistral')) { | |
formattedPrompt = `<s>[INST]${prompt}[/INST]`; | |
} else if (model.includes('llama')) { | |
formattedPrompt = `<s>[INST] ${prompt} [/INST]`; | |
} else if (model.includes('gemma')) { | |
formattedPrompt = `<start_of_turn>user\n${prompt}<end_of_turn>\n<start_of_turn>model`; | |
} | |
const response = await fetch(`https://api-inference.huggingface.co/models/${model}`, { | |
method: 'POST', | |
headers: { | |
'Authorization': `Bearer ${apiKey}`, | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
inputs: formattedPrompt, | |
parameters: { | |
max_new_tokens: 2048, | |
temperature: 0.7, | |
top_p: 0.95, | |
do_sample: true | |
} | |
}) | |
}); | |
if (!response.ok) { | |
const errorText = await response.text(); | |
throw new Error(`API Error (${response.status}): ${errorText}`); | |
} | |
const result = await response.json(); | |
// Handle different response formats | |
let generatedText = ''; | |
if (Array.isArray(result) && result[0] && result[0].generated_text) { | |
generatedText = result[0].generated_text; | |
} else if (result.generated_text) { | |
generatedText = result.generated_text; | |
} else { | |
generatedText = result; | |
} | |
return generatedText; | |
} | |
// Generate App button handler | |
generateBtn.addEventListener('click', async () => { | |
const description = appDescription.value.trim(); | |
if (!description) { | |
alert('Please enter a description of the app you want to create.'); | |
return; | |
} | |
const selectedModel = modelSelect.value; | |
try { | |
setGenerating(true); | |
const prompt = `Create a canvas app based on this description: "${description}". | |
Please provide a fully functional HTML application that works as a standalone single file. Include all necessary HTML, CSS, and JavaScript in a single HTML document. Ensure the app is responsive and follows best practices for web development. | |
Your response MUST include a complete HTML code with proper <html>, <head>, and <body> tags, and all JavaScript and CSS should be included within this HTML file. | |
IMPORTANT: The code should be fully functional when copied to an HTML file and opened in a browser.`; | |
const response = await callHuggingFaceAPI(selectedModel, prompt); | |
// Extract code from response | |
const extractedCode = extractCodeFromMarkdown(response) || response; | |
if (extractedCode) { | |
// Check if the extracted code contains proper HTML structure | |
let finalCode = extractedCode; | |
if (!finalCode.includes('<!DOCTYPE html>') && !finalCode.includes('<html')) { | |
finalCode = `<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Generated App</title> | |
</head> | |
<body> | |
${finalCode} | |
</body> | |
</html>`; | |
} | |
currentCode = finalCode; | |
// Display the code with syntax highlighting | |
codeDisplay.textContent = currentCode; | |
hljs.highlightElement(codeDisplay); | |
// Update the preview | |
updatePreview(currentCode); | |
// Show results and refinement sections | |
resultsSection.classList.remove('hidden'); | |
refinementSection.classList.remove('hidden'); | |
} else { | |
throw new Error("No usable code found in the response. Please try again with a more detailed description."); | |
} | |
} catch (err) { | |
console.error('Error:', err); | |
alert(`Error: ${err.message || 'An error occurred while generating the app'}`); | |
} finally { | |
setGenerating(false); | |
} | |
}); | |
// Refine App button handler | |
refineBtn.addEventListener('click', async () => { | |
const refinement = refinementInput.value.trim(); | |
if (!refinement) { | |
alert('Please enter a description of the changes you want to make.'); | |
return; | |
} | |
const selectedModel = modelSelect.value; | |
try { | |
setRefining(true); | |
const prompt = `I have an HTML application that I want to refine. Here is the current code: | |
\`\`\`html | |
${currentCode} | |
\`\`\` | |
I want to make the following changes: | |
"${refinement}" | |
Please provide an updated version of the complete HTML code that implements these changes. Keep the core functionality and only modify what's necessary. The output should be a complete HTML file that works on its own.`; | |
const response = await callHuggingFaceAPI(selectedModel, prompt); | |
// Extract code from response | |
const extractedCode = extractCodeFromMarkdown(response) || response; | |
if (extractedCode) { | |
// Check if the extracted code contains proper HTML structure | |
let finalCode = extractedCode; | |
if (!finalCode.includes('<!DOCTYPE html>') && !finalCode.includes('<html')) { | |
finalCode = `<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Generated App</title> | |
</head> | |
<body> | |
${finalCode} | |
</body> | |
</html>`; | |
} | |
currentCode = finalCode; | |
// Display the code with syntax highlighting | |
codeDisplay.textContent = currentCode; | |
hljs.highlightElement(codeDisplay); | |
// Update the preview | |
updatePreview(currentCode); | |
} else { | |
throw new Error("No usable code found in the response. Please try again with a more detailed description."); | |
} | |
} catch (err) { | |
console.error('Error:', err); | |
alert(`Error: ${err.message || 'An error occurred while refining the app'}`); | |
} finally { | |
setRefining(false); | |
} | |
}); | |
</script> | |
</body> | |
</html> |