Spaces:
Sleeping
Sleeping
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>App Creator AI</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/[email protected]/lib/core.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/languages/javascript.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/languages/html.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/languages/css.min.js"></script> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/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"> | |
<!-- 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="botSelect" class="text-sm">AI Model:</label> | |
<select id="botSelect" 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="Claude-3.7-Sonnet" selected>Claude-3.7-Sonnet</option> | |
<option value="GPT-4o">GPT-4o</option> | |
<option value="GPT-4o-mini">GPT-4o-mini</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> | |
<button id="copyCodeBtn" class="mt-4 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> | |
</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 on Poe</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 generateBtn = document.getElementById('generateBtn'); | |
const generateBtnText = document.getElementById('generateBtnText'); | |
const generateBtnSpinner = document.getElementById('generateBtnSpinner'); | |
const appDescription = document.getElementById('appDescription'); | |
const botSelect = document.getElementById('botSelect'); | |
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 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 = ''; | |
// 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); | |
copyCodeBtn.textContent = 'Copied!'; | |
setTimeout(() => { | |
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="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`; | |
}, 2000); | |
} catch (err) { | |
console.error('Failed to copy: ', err); | |
} | |
}); | |
// 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; | |
} | |
// Register handler for bot responses | |
window.Poe.registerHandler("app-creator-handler", (result, context) => { | |
const response = result.responses[0]; | |
if (response.status === "error") { | |
setGenerating(false); | |
setRefining(false); | |
alert(`Error: ${response.statusText || 'An error occurred while generating the app'}`); | |
return; | |
} | |
if (response.status === "complete") { | |
// Extract code | |
const extractedCode = extractCodeFromMarkdown(response.content); | |
if (extractedCode) { | |
currentCode = extractedCode; | |
// 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 { | |
alert("No code found in the response. Please try again with a more detailed description."); | |
} | |
setGenerating(false); | |
setRefining(false); | |
} | |
}); | |
// 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'); | |
} | |
} | |
// 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 selectedBot = botSelect.value; | |
try { | |
setGenerating(true); | |
const prompt = `@${selectedBot} Create a canvas app based on this description: "${description}". | |
Please provide a fully functional HTML application that can be rendered in a Poe Canvas App. Include all necessary HTML, CSS, and JavaScript in a single code block. Ensure the app is responsive and follows best practices for web development. | |
Your response should follow this format: | |
1. A brief explanation of what you've created | |
2. A Markdown code block containing the complete HTML/CSS/JS for the app | |
Make sure the code is clean, well-documented, and ready to deploy.`; | |
await window.Poe.sendUserMessage(prompt, { | |
handler: "app-creator-handler", | |
stream: false, | |
openChat: false | |
}); | |
} catch (err) { | |
console.error('Error:', err); | |
alert(`Error: ${err.message || 'An error occurred while generating the app'}`); | |
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 selectedBot = botSelect.value; | |
try { | |
setRefining(true); | |
const prompt = `@${selectedBot} I have a Poe Canvas App 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 code that implements these changes. Keep the core functionality and only modify what's necessary. | |
Your response should follow this format: | |
1. A brief explanation of what changes you've made | |
2. A Markdown code block containing the complete updated HTML/CSS/JS for the app`; | |
await window.Poe.sendUserMessage(prompt, { | |
handler: "app-creator-handler", | |
stream: false, | |
openChat: false | |
}); | |
} catch (err) { | |
console.error('Error:', err); | |
alert(`Error: ${err.message || 'An error occurred while refining the app'}`); | |
setRefining(false); | |
} | |
}); | |
</script> | |
</body> | |
</html> | |