وضع الصيانة

الموقع حالياً تحت وضع الصيانة. الدخول مسموح للمسؤولين فقط.

HomyFlow / Project Workspace
HomyFlow AI
Welcome! I am here to help you build your premium application. Describe your idea or use a template to start generating code.
Quick Starts

Preview Panel

Live code rendering will appear here.

Publish Your Portfolio

Choose the domain you want your site to appear on

Free Subdomain

.homyflow.com
Pro

Professional Custom Domain

Coming Soon
Subscribe to the Pro Yearly plan ($40) and get a free .com domain for the first year.
Fully secure transaction encrypted via Stripe
\n${bodyMatch[0]}\n`; // 5. Fallback: check if the string itself starts with HTML-like structure if (cleanText.startsWith('<') && (cleanText.includes('') || cleanText.includes('') || cleanText.includes('
'))) { return cleanText; } return null; // Return null if no valid HTML is found }; window.updatePreviewIframe = function(rawContent) { // MAGIC TRICK: Extract HTML from markdown if present const htmlContent = window.extractHTMLFromResponse(rawContent); if (!htmlContent) { // If we can't find HTML, don't break the iframe console.warn("Could not extract valid HTML from AI response."); return; } const iframe = document.getElementById('preview-iframe') || document.querySelector('iframe'); const placeholder = document.getElementById('preview-placeholder'); if (!iframe) { console.warn("Update Iframe Failed: Could not find the iframe element."); return; } if (htmlContent && htmlContent.trim() !== "") { // Hide placeholder if (placeholder) placeholder.classList.add('hidden'); const loadingState = document.getElementById('preview-loading-state'); if (loadingState) loadingState.classList.add('hidden'); iframe.classList.remove('hidden'); // Use srcdoc for direct HTML injection as requested, avoiding Blob URL security policies iframe.srcdoc = htmlContent; // Keep global memory in sync window.currentGeneratedHTML = htmlContent; } else { // Empty state iframe.src = 'about:blank'; iframe.removeAttribute('srcdoc'); if (typeof window.currentGeneratedHTML !== 'undefined') window.currentGeneratedHTML = ''; if (placeholder) placeholder.classList.remove('hidden'); iframe.classList.add('hidden'); } }; const iframe = document.getElementById('preview-iframe'); if (iframe) { window.updatePreviewIframe(latestHTML); } // Update sidebar active states if (typeof renderSidebarHistory === 'function') renderSidebarHistory(); } function renderSidebarHistory() { const historyContainer = document.getElementById('sidebar-history-list'); if (!historyContainer) return; historyContainer.innerHTML = ''; let allProjects = JSON.parse(localStorage.getItem('projectsChatHistory')) || {}; // Sort projects roughly by making sure latest are handled (or just iterate if order isn't strictly maintained, Object.keys is typically insertion order, we could reverse it for newest first) const projectIds = Object.keys(allProjects).reverse(); if (projectIds.length === 0) { historyContainer.innerHTML = '
No previous conversations.
'; return; } projectIds.forEach(id => { const history = allProjects[id]; let title = "New Conversation"; // Find the first user message for the title if (history && history.length > 0) { const firstUserMsg = history.find(msg => msg.role === 'user'); if (firstUserMsg && firstUserMsg.content) { title = firstUserMsg.content.substring(0, 25) + (firstUserMsg.content.length > 25 ? '...' : ''); } } const isActive = (window.currentProjectId === id); const wrapper = document.createElement('div'); wrapper.className = `group flex items-center justify-between w-full rounded-lg transition-colors ${isActive ? 'bg-gray-100 dark:bg-neutral-800' : 'hover:bg-gray-50 dark:hover:bg-neutral-800'}`; const btn = document.createElement('button'); btn.className = `flex-1 text-left px-3 py-2 text-sm truncate ${isActive ? 'text-gray-900 dark:text-neutral-100 font-medium' : 'text-gray-500 dark:text-neutral-400 group-hover:text-gray-900 dark:group-hover:text-neutral-200'}`; btn.textContent = title; btn.onclick = () => { switchTab('studio'); loadProjectChat(id); }; const delBtn = document.createElement('button'); delBtn.className = "text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-1 rounded-md mr-1"; delBtn.innerHTML = ''; delBtn.onclick = (e) => deleteConversation(id, e); wrapper.appendChild(btn); wrapper.appendChild(delBtn); historyContainer.appendChild(wrapper); }); } window.deleteConversation = async function(id, e) { if (e) e.stopPropagation(); const confirmed = await AppModal.confirm("Are you sure you want to delete this conversation? This action cannot be undone.", true); if (confirmed) { let allProjects = JSON.parse(localStorage.getItem('projectsChatHistory')) || {}; delete allProjects[id]; localStorage.setItem('projectsChatHistory', JSON.stringify(allProjects)); let savedProjects = JSON.parse(localStorage.getItem('savedProjectsData')) || {}; delete savedProjects[id]; localStorage.setItem('savedProjectsData', JSON.stringify(savedProjects)); if (window.currentProjectId === id) { if (typeof window.clearStudioForNewProject === 'function') { window.clearStudioForNewProject(); } else { window.currentProjectId = crypto.randomUUID(); window.projectHistory = []; const chatContainer = document.getElementById('chat-container'); if (chatContainer) chatContainer.innerHTML = ''; const iframe = document.getElementById('preview-iframe'); if (iframe) { iframe.src = 'about:blank'; iframe.removeAttribute('srcdoc'); } const placeholder = document.getElementById('preview-placeholder'); if (placeholder) placeholder.classList.remove('hidden'); } } if (typeof renderSidebarHistory === 'function') renderSidebarHistory(); } }; async function saveProject() { if (!currentSiteId) { await AppModal.alert("There is no generated project to save. Please generate a website first."); return; } const projectName = await AppModal.prompt("Enter a name for this project:", "My Awesome Portfolio"); if (!projectName) { return; } try { const res = await fetch('/api/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: projectName, siteId: currentSiteId }) }); const data = await res.json(); if (res.ok) { await AppModal.alert("Project saved successfully to the Cloud Database!"); } else { await AppModal.alert("Error saving project: " + data.error); } } catch (e) { await AppModal.alert("Network error: " + e.message); } if (wasEditModeActive) toggleEditMode(); } async function renderProjectsGrid() { const grid = document.getElementById('projects-grid'); const emptyState = document.getElementById('projects-empty-state'); try { const res = await fetch('/api/projects'); const data = await res.json(); loadedProjects = data.projects || []; } catch (e) { console.error("Failed to load projects", e); loadedProjects = []; } if (loadedProjects.length === 0) { grid.innerHTML = ''; grid.classList.add('hidden'); emptyState.classList.remove('hidden'); return; } grid.classList.remove('hidden'); emptyState.classList.add('hidden'); grid.innerHTML = ''; loadedProjects.forEach((proj, index) => { const card = document.createElement('div'); card.className = "project-card bg-neutral-900 border border-neutral-800 rounded-xl overflow-hidden hover:border-emerald-500/50 transition-all cursor-pointer group flex flex-col"; card.setAttribute('data-project-id', proj.id); // Safe thumbnail generation - we'll just show a generic placeholder or the first few letters card.innerHTML = `

${proj.name}

${proj.date}

`; grid.appendChild(card); }); } const projectsContainer = document.querySelector('#projects-grid'); if (projectsContainer) { projectsContainer.addEventListener('click', async function(e) { // Find the closest delete button (the red trash bin) const deleteBtn = e.target.closest('.delete-project-btn'); if (deleteBtn) { // Find the parent project card to get the ID and remove it later const projectCard = deleteBtn.closest('.project-card'); const projectId = projectCard.getAttribute('data-project-id'); if (!projectId) return; // 1. Show confirmation dialog const isConfirmed = await AppModal.confirm("هل أنت متأكد من حذف هذا المشروع نهائياً؟ لا يمكن التراجع عن هذا الإجراء."); if (isConfirmed) { // 2. Execute Hard Delete deleteProjectPermanently(projectId); // 3. Remove the card from the UI with a smooth visual transition projectCard.style.transition = "opacity 0.3s ease"; projectCard.style.opacity = "0"; setTimeout(() => { projectCard.remove(); // 4. Check if the grid is now empty to show an "Empty State" message if (projectsContainer.children.length === 0) { const emptyState = document.getElementById('projects-empty-state'); projectsContainer.classList.add('hidden'); if(emptyState) emptyState.classList.remove('hidden'); } }, 300); // match transition duration } } }); } async function deleteProjectPermanently(id) { try { // Delete from backend KV database await fetch('/api/projects?id=' + id, { method: 'DELETE' }); // Remove from loadedProjects array loadedProjects = loadedProjects.filter(p => p.id !== id); // Wipe associated chat history from localStorage let allProjects = JSON.parse(localStorage.getItem('projectsChatHistory')) || {}; if (allProjects[id]) { delete allProjects[id]; localStorage.setItem('projectsChatHistory', JSON.stringify(allProjects)); } // If we deleted the active project, start a new one automatically if (window.currentProjectId === id) { startNewProjectChat(); } // Refresh history if (typeof renderSidebarHistory === 'function') renderSidebarHistory(); } catch (e) { console.error("Error permanently deleting project: ", e.message); } } function canCreateNewProject() { const userPlan = localStorage.getItem('userPlan') || 'free'; // Define limits let maxProjects = 2; // Default for free if (userPlan === 'starter') maxProjects = 50; if (userPlan === 'unlimited') return true; // Infinity const savedProjects = JSON.parse(localStorage.getItem('savedProjectsData')) || {}; const chatHistory = JSON.parse(localStorage.getItem('projectsChatHistory')) || {}; const totalProjects = new Set([...Object.keys(savedProjects), ...Object.keys(chatHistory)]).size; return totalProjects < maxProjects; } async function showUpgradePrompt() { const userPlan = localStorage.getItem('userPlan') || 'free'; if (userPlan === 'free') { if(await AppModal.confirm("You have reached the Free plan limit (2 sites).\n\nUpgrade to Starter to unlock 50 sites!")) switchTab('billing'); } else if (userPlan === 'starter') { if(await AppModal.confirm("You have reached the Starter plan limit (50 sites).\n\nUpgrade to Unlimited for infinite sites and Code Export!")) switchTab('billing'); } } async function startNewProjectChat() { if(window.toggleEmptyState) window.toggleEmptyState(true); if (!canCreateNewProject()) { showUpgradePrompt(); return; } window.currentProjectId = generateProjectId(); localStorage.setItem('activeProjectId', window.currentProjectId); const chatContainer = document.getElementById('chat-messages-container'); if (chatContainer) chatContainer.innerHTML = ''; appendMessage("Welcome! I am here to help you build your premium application. Describe your idea or use a template to start generating code.", 'ai', true); } function loadProject(id) { const proj = loadedProjects.find(p => p.id === id); if (!proj) return; switchTab('studio'); const emptyState = document.getElementById('preview-placeholder'); const loadingState = document.getElementById('preview-loading-state'); const iframe = document.getElementById('preview-iframe'); emptyState.classList.add('hidden'); loadingState.classList.add('hidden'); iframe.classList.remove('hidden'); // Inject the saved HTML into the iframe currentSiteId = proj.siteId; iframe.src = '/api/preview/' + currentSiteId; // Load isolated chat history loadProjectChat(id); // Give feedback in chat without saving it to history appendMessage(`Project "${proj.name}" loaded successfully. You can now edit it or chat with the AI to make comprehensive changes.`, 'ai', true); } function clearStudioForNewProject() { if(window.toggleEmptyState) window.toggleEmptyState(true); const emptyState = document.getElementById('preview-placeholder'); const loadingState = document.getElementById('preview-loading-state'); const iframe = document.getElementById('preview-iframe'); iframe.src = ""; currentSiteId = ""; iframe.classList.add('hidden'); loadingState.classList.add('hidden'); emptyState.classList.remove('hidden'); startNewProjectChat(); } function applyUserData(user) { globalUser = user; // SYNC PLAN TO LOCAL STORAGE FOR UI GUARDS if (user.plan) { localStorage.setItem('userPlan', user.plan); } document.getElementById('user-name').textContent = user.name.split(' ')[0]; // First name if (user.picture) { const img = document.getElementById('user-avatar'); img.src = user.picture; img.classList.remove('hidden'); document.getElementById('user-avatar-placeholder').classList.add('hidden'); } } function checkAuth() { const session = localStorage.getItem('HomyFlow_session'); const userStr = localStorage.getItem('HomyFlow_user'); if (session && userStr) { try { const user = JSON.parse(userStr); applyUserData(user); // Hide overlay immediately without fade to avoid flash const overlay = document.getElementById('auth-overlay'); if(overlay) overlay.remove(); } catch (e) { // Bad JSON } // SYNC FRESH PLAN FROM SERVER (so admin upgrades take effect immediately) fetch('/api/me', { headers: { 'X-Session-Token': session } }).then(r => r.json()).then(data => { if (data.success && data.user) { // Update localStorage with latest user data from server localStorage.setItem('HomyFlow_user', JSON.stringify(data.user)); localStorage.setItem('userPlan', data.user.plan || 'free'); } }).catch(() => {/* silent fail - offline or server error */}); } } async function logout() { if(await AppModal.confirm("Are you sure you want to log out?")) { localStorage.removeItem('HomyFlow_session'); localStorage.removeItem('HomyFlow_user'); location.reload(); } } // Initialize logic document.addEventListener('DOMContentLoaded', () => { checkAuth(); enforcePremiumState(); // 1. Restore the last active session const savedProjectId = localStorage.getItem('activeProjectId'); if (savedProjectId) { window.currentProjectId = savedProjectId; loadProjectChat(savedProjectId); } else { // If completely new user, start a blank project if (typeof startNewProjectChat === 'function') startNewProjectChat(); } // 2. Render the sidebar history if (typeof renderSidebarHistory === 'function') renderSidebarHistory(); }); function enforcePremiumState() { if (localStorage.getItem('userPlan') === 'premium') { // 1. Hide Sidebar Upgrade Card const sidebarUpgrade = document.getElementById('sidebar-upgrade-card'); if (sidebarUpgrade) sidebarUpgrade.classList.add('hidden', 'md:hidden'); // 2. Change Badge to Premium Member const tierBadge = document.getElementById('user-tier-badge'); if (tierBadge) { tierBadge.textContent = 'Premium Member'; tierBadge.classList.remove('text-gray-500', 'dark:text-neutral-400'); tierBadge.classList.add('text-amber-500', 'font-bold'); } // 3. Hide Upgrade domain button or banners if they exist (Domain tab upsell) const domainUpsell = document.querySelector('.glow-emerald-sm'); // The "Option 2: Custom Domain" banner if (domainUpsell && domainUpsell.style.display !== 'none') { // If we had logic showing it, maybe we don't hide it entirely but we change it to 'Available' // Actually user said "hide the 'Upgrade to Premium' banners". } // 4. Update Billing Page State const billingPlanName = document.getElementById('subscription-plan-name'); const billingRenewalText = document.getElementById('subscription-renewal-text'); const billingUpgradeBtn = document.getElementById('upgrade-crypto-btn'); if (billingPlanName) billingPlanName.textContent = 'Premium Plan'; if (billingRenewalText) billingRenewalText.textContent = 'You have unlocked premium features and custom domains.'; if (billingUpgradeBtn) billingUpgradeBtn.style.display = 'none'; } }