Spaces:
Running
Running
| <html class="dark" lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta content="width=device-width, initial-scale=1.0" name="viewport" /> | |
| <title>MerchFlow AI Dashboard</title> | |
| <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script> | |
| <link href="https://fonts.googleapis.com" rel="preconnect" /> | |
| <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect" /> | |
| <link href="https://fonts.googleapis.com/css2?family=Spline+Sans:wght@300;400;500;600;700&display=swap" | |
| rel="stylesheet" /> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" | |
| rel="stylesheet" /> | |
| <script id="tailwind-config"> | |
| tailwind.config = { | |
| darkMode: "class", | |
| theme: { | |
| extend: { | |
| colors: { | |
| "primary": "#3b82f6", // Electric Blue | |
| "primary-hover": "#2563eb", | |
| "secondary": "#6366f1", // Indigo accent | |
| "background-light": "#f8fafc", | |
| "background-dark": "#0f172a", // Deep Slate | |
| "surface-dark": "#1e293b", // Slate 800 | |
| "surface-darker": "#020617", // Slate 950 | |
| "border-dark": "#334155", // Slate 700 | |
| }, | |
| fontFamily: { | |
| "display": ["Spline Sans", "sans-serif"], | |
| "mono": ["monospace"] | |
| }, | |
| borderRadius: { "DEFAULT": "1rem", "lg": "2rem", "xl": "3rem", "full": "9999px" }, | |
| }, | |
| }, | |
| } | |
| </script> | |
| <style> | |
| .custom-scrollbar::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-track { | |
| background: #020617; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb { | |
| background: #334155; | |
| border-radius: 4px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb:hover { | |
| background: #3b82f6; | |
| } | |
| </style> | |
| </head> | |
| <body | |
| class="font-display bg-background-light dark:bg-background-dark text-slate-900 dark:text-white antialiased overflow-hidden h-screen flex flex-col"> | |
| <header | |
| class="flex-none flex items-center justify-between whitespace-nowrap border-b border-solid border-border-dark px-6 py-4 bg-background-dark z-10"> | |
| <div class="flex items-center gap-3 text-white"> | |
| <div class="flex items-center justify-center size-10 rounded-full bg-primary/10 text-primary"> | |
| <span class="material-symbols-outlined text-2xl">all_inclusive</span> | |
| </div> | |
| <div> | |
| <h2 class="text-white text-xl font-bold leading-tight tracking-tight">MerchFlow AI</h2> | |
| <span class="text-xs text-primary/60 font-medium uppercase tracking-wider">Enterprise Edition</span> | |
| </div> | |
| </div> | |
| <button id="deployBtn" | |
| class="flex min-w-[84px] cursor-pointer items-center justify-center overflow-hidden rounded-full h-10 px-6 bg-primary hover:bg-primary-hover transition-colors text-white text-sm font-bold leading-normal tracking-[0.015em]"> | |
| <span class="material-symbols-outlined mr-2 text-lg">rocket_launch</span> | |
| <span class="truncate">Deploy</span> | |
| </button> | |
| </header> | |
| <main class="flex-1 flex overflow-hidden"> | |
| <div | |
| class="flex-1 flex flex-col border-r border-border-dark min-w-[400px] overflow-y-auto custom-scrollbar p-8"> | |
| <div class="max-w-2xl w-full mx-auto flex flex-col gap-6 h-full"> | |
| <div class="flex items-center justify-between"> | |
| <h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Input Data</h2> | |
| <span | |
| class="bg-surface-dark text-white px-3 py-1 rounded-full text-xs font-medium border border-border-dark">Step | |
| 1 of 2</span> | |
| </div> | |
| <div class="flex flex-col flex-1 max-h-[300px] min-h-[200px]"> | |
| <input type="file" id="fileInput" class="hidden" accept=".jpg,.jpeg,.png,.webp" /> | |
| <div id="dropZone" | |
| class="group relative flex flex-col items-center justify-center gap-4 rounded-xl border-2 border-dashed border-slate-600 hover:border-primary/50 hover:bg-surface-dark transition-all cursor-pointer h-full w-full px-6 py-8"> | |
| <!-- Initial content populated by JS --> | |
| <div | |
| class="size-16 rounded-full bg-surface-dark group-hover:bg-primary/20 flex items-center justify-center transition-colors border border-border-dark group-hover:border-primary/30"> | |
| <span class="material-symbols-outlined text-3xl text-primary">cloud_upload</span> | |
| </div> | |
| <div class="flex flex-col items-center gap-1"> | |
| <p class="text-white text-lg font-bold leading-tight tracking-tight text-center">Drop | |
| Product Image Here</p> | |
| <p class="text-slate-400 text-sm font-normal text-center">Supports JPG, PNG, WEBP</p> | |
| </div> | |
| <button id="browseBtn" | |
| class="mt-2 flex items-center justify-center rounded-full h-9 px-4 bg-slate-700 hover:bg-slate-600 text-white text-xs font-bold transition-colors"> | |
| Browse Files | |
| </button> | |
| <div | |
| class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex flex-col gap-3 flex-1"> | |
| <label class="flex items-center justify-between"> | |
| <span class="text-white text-base font-medium">Raw Product Specs</span> | |
| <span class="text-xs text-slate-400">JSON or Plain Text</span> | |
| </label> | |
| <div class="relative flex-1"> | |
| <textarea | |
| class="form-input w-full h-full resize-none rounded-xl text-white placeholder:text-slate-500 focus:outline-0 focus:ring-2 focus:ring-primary/50 border border-border-dark bg-surface-dark p-4 text-base font-normal leading-relaxed font-mono" | |
| placeholder="Enter fabric details, dimensions, and SKU... | |
| Example: | |
| Material: 100% Recycled Polyester | |
| Fit: Regular | |
| SKU: JK-2024-WTR"></textarea> | |
| </div> | |
| </div> | |
| <div class="pt-2"> | |
| <button id="startBtn" | |
| class="group relative w-full cursor-pointer overflow-hidden rounded-xl h-16 bg-gradient-to-r from-primary to-blue-700 hover:from-blue-500 hover:to-primary transition-all shadow-[0_0_20px_rgba(59,130,246,0.3)]"> | |
| <div class="absolute inset-0 flex items-center justify-center gap-3"> | |
| <span | |
| class="text-white text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start | |
| Agent Workflow</span> | |
| <span | |
| class="material-symbols-outlined text-white group-hover:translate-x-1 transition-transform">arrow_forward</span> | |
| </div> | |
| <div | |
| class="absolute top-0 -inset-full h-full w-1/2 z-5 block transform -skew-x-12 bg-gradient-to-r from-transparent to-white opacity-20 group-hover:animate-shine"> | |
| </div> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex-1 flex flex-col bg-surface-darker p-8 overflow-hidden relative"> | |
| <div class="absolute inset-0 opacity-5 pointer-events-none" | |
| style="background-image: radial-gradient(#64748b 1px, transparent 1px); background-size: 24px 24px;"> | |
| </div> | |
| <div class="max-w-3xl w-full mx-auto flex flex-col gap-6 h-full relative z-10"> | |
| <div class="flex items-center justify-between"> | |
| <h2 class="text-white tracking-light text-[28px] font-bold leading-tight">Generated Output</h2> | |
| <div class="flex items-center gap-2"> | |
| <span | |
| class="bg-surface-dark text-white px-3 py-1 rounded-full text-xs font-medium border border-border-dark">Step | |
| 2 of 2</span> | |
| <button id="copyBtn" | |
| class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors"> | |
| <span class="material-symbols-outlined text-xl">content_copy</span> | |
| </button> | |
| <button id="downloadBtn" | |
| class="p-2 hover:bg-surface-dark rounded-lg text-slate-400 hover:text-white transition-colors"> | |
| <span class="material-symbols-outlined text-xl">download</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-4"> | |
| <div | |
| class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm"> | |
| <div class="relative size-3"> | |
| <span | |
| class="animate-ping absolute inline-flex h-full w-full rounded-full bg-purple-500 opacity-75"></span> | |
| <span class="relative inline-flex rounded-full size-3 bg-purple-500"></span> | |
| </div> | |
| <div class="flex flex-col overflow-hidden"> | |
| <span class="text-xs text-slate-400 truncate">Vision Agent</span> | |
| <span class="text-sm font-bold text-white truncate">Gemini Pro 1.5</span> | |
| </div> | |
| <span class="material-symbols-outlined text-purple-500 ml-auto text-lg">visibility</span> | |
| </div> | |
| <div | |
| class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm"> | |
| <div class="relative size-3"> | |
| <span class="relative inline-flex rounded-full size-3 bg-primary"></span> | |
| </div> | |
| <div class="flex flex-col overflow-hidden"> | |
| <span class="text-xs text-slate-400 truncate">Reasoning Agent</span> | |
| <span class="text-sm font-bold text-white truncate">Llama 3 70B</span> | |
| </div> | |
| <span class="material-symbols-outlined text-primary ml-auto text-lg">psychology</span> | |
| </div> | |
| <div | |
| class="flex items-center gap-3 p-3 rounded-lg bg-surface-dark border border-border-dark shadow-sm"> | |
| <div class="relative size-3"> | |
| <span class="relative inline-flex rounded-full size-3 bg-cyan-500"></span> | |
| </div> | |
| <div class="flex flex-col overflow-hidden"> | |
| <span class="text-xs text-slate-400 truncate">SEO Context</span> | |
| <span class="text-sm font-bold text-white truncate">Pinecone DB</span> | |
| </div> | |
| <span class="material-symbols-outlined text-cyan-500 ml-auto text-lg">database</span> | |
| </div> | |
| </div> | |
| <div | |
| class="flex-1 rounded-xl bg-[#0d1117] border border-border-dark flex flex-col overflow-hidden shadow-2xl"> | |
| <div | |
| class="flex items-center justify-between px-4 py-2 bg-surface-dark border-b border-border-dark"> | |
| <span class="text-xs font-mono text-slate-400">output.json</span> | |
| <div class="flex gap-1.5"> | |
| <div class="size-2.5 rounded-full bg-red-500/20"></div> | |
| <div class="size-2.5 rounded-full bg-yellow-500/20"></div> | |
| <div class="size-2.5 rounded-full bg-green-500/20"></div> | |
| </div> | |
| </div> | |
| <div class="flex-1 p-4 overflow-auto custom-scrollbar font-mono text-sm leading-6"> | |
| <pre><code id="jsonOutput" class="language-json"><span class="text-slate-500">1</span> <span class="text-yellow-500">{</span> | |
| <span class="text-slate-500">2</span> <span class="text-primary">"product_analysis"</span><span class="text-white">:</span> <span class="text-yellow-500">{</span> | |
| <span class="text-slate-500">3</span> <span class="text-primary">"title"</span><span class="text-white">:</span> <span class="text-sky-300">"Apex Terrain All-Weather Performance Jacket"</span><span class="text-white">,</span> | |
| <span class="text-slate-500">4</span> <span class="text-primary">"category"</span><span class="text-white">:</span> <span class="text-sky-300">"Outerwear / Men's / Technical Shells"</span><span class="text-white">,</span> | |
| <span class="text-slate-500">5</span> <span class="text-primary">"features"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span> | |
| <span class="text-slate-500">6</span> <span class="text-sky-300">"Gore-Tex Pro Membrane"</span><span class="text-white">,</span> | |
| <span class="text-slate-500">7</span> <span class="text-sky-300">"Articulated Sleeves"</span><span class="text-white">,</span> | |
| <span class="text-slate-500">8</span> <span class="text-sky-300">"Helmet-Compatible Hood"</span> | |
| <span class="text-slate-500">9</span> <span class="text-yellow-500">]</span><span class="text-white">,</span> | |
| <span class="text-slate-500">10</span> <span class="text-primary">"seo_tags"</span><span class="text-white">:</span> <span class="text-yellow-500">[</span> | |
| <span class="text-slate-500">11</span> <span class="text-sky-300">"#hikinggear"</span><span class="text-white">,</span> <span class="text-sky-300">"#waterproof"</span><span class="text-white">,</span> <span class="text-sky-300">"#adventure"</span> | |
| <span class="text-slate-500">12</span> <span class="text-yellow-500">]</span><span class="text-white">,</span> | |
| <span class="text-slate-500">13</span> <span class="text-primary">"sentiment_score"</span><span class="text-white">:</span> <span class="text-purple-400">0.98</span><span class="text-white">,</span> | |
| <span class="text-slate-500">14</span> <span class="text-primary">"market_fit"</span><span class="text-white">:</span> <span class="text-sky-300">"High Demand"</span> | |
| <span class="text-slate-500">15</span> <span class="text-yellow-500">}</span><span class="text-white">,</span> | |
| <span class="text-slate-500">16</span> <span class="text-primary">"deployment_status"</span><span class="text-white">:</span> <span class="text-sky-300">"Ready"</span> | |
| <span class="text-slate-500">17</span> <span class="text-yellow-500">}</span></code></pre> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| tailwind.config.theme.extend.animation = { | |
| shine: 'shine 1s', | |
| } | |
| tailwind.config.theme.extend.keyframes = { | |
| shine: { | |
| '100%': { left: '125%' }, | |
| } | |
| } | |
| const dropZone = document.getElementById('dropZone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const jsonOutput = document.getElementById('jsonOutput'); | |
| const deployBtn = document.getElementById('deployBtn'); | |
| const copyBtn = document.getElementById('copyBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| let selectedFile = null; | |
| let isCatalogGenerated = false; | |
| // Default DropZone Content Template | |
| const defaultDropZoneContent = ` | |
| <div class="size-16 rounded-full bg-surface-dark group-hover:bg-primary/20 flex items-center justify-center transition-colors border border-border-dark group-hover:border-primary/30"> | |
| <span class="material-symbols-outlined text-3xl text-primary">cloud_upload</span> | |
| </div> | |
| <div class="flex flex-col items-center gap-1"> | |
| <p class="text-white text-lg font-bold leading-tight tracking-tight text-center">Drop Product Image Here</p> | |
| <p class="text-slate-400 text-sm font-normal text-center">Supports JPG, PNG, WEBP</p> | |
| </div> | |
| <button id="browseBtn" class="mt-2 flex items-center justify-center rounded-full h-9 px-4 bg-slate-700 hover:bg-slate-600 text-white text-xs font-bold transition-colors"> | |
| Browse Files | |
| </button> | |
| <div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl"></div> | |
| `; | |
| // Initialize DropZone with listeners | |
| function initDropZone() { | |
| const browseBtn = document.getElementById('browseBtn'); | |
| if (browseBtn) { | |
| browseBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| fileInput.click(); | |
| }); | |
| } | |
| } | |
| // Initial setup | |
| initDropZone(); | |
| // File Input Change | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length > 0) { | |
| handleFile(e.target.files[0]); | |
| } | |
| }); | |
| // Drag & Drop | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.add('border-primary'); | |
| }); | |
| dropZone.addEventListener('dragleave', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('border-primary'); | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('border-primary'); | |
| if (e.dataTransfer.files.length > 0) { | |
| handleFile(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| function handleFile(file) { | |
| selectedFile = file; | |
| // Update UI to Selected State | |
| dropZone.innerHTML = ` | |
| <div class="flex flex-col items-center justify-center gap-4 z-10"> | |
| <div class="size-16 rounded-full bg-surface-dark flex items-center justify-center border border-border-dark"> | |
| <span class="material-symbols-outlined text-3xl text-primary">description</span> | |
| </div> | |
| <div class="flex flex-col items-center gap-1"> | |
| <p class="text-white text-lg font-bold text-center">${file.name}</p> | |
| <p class="text-slate-400 text-sm text-center">${(file.size / 1024).toFixed(1)} KB</p> | |
| </div> | |
| <button id="removeFileBtn" class="mt-2 flex items-center justify-center gap-2 rounded-full h-9 px-4 bg-slate-700 hover:bg-red-500/20 hover:text-red-500 hover:border-red-500/50 border border-transparent transition-all text-white text-xs font-bold"> | |
| <span class="material-symbols-outlined text-base">close</span> | |
| <span>Remove File</span> | |
| </button> | |
| </div> | |
| <div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/10 pointer-events-none rounded-xl"></div> | |
| `; | |
| // Add listener to the new remove button | |
| document.getElementById('removeFileBtn').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| resetUploadUI(); | |
| }); | |
| } | |
| function resetUploadUI() { | |
| selectedFile = null; | |
| fileInput.value = ""; // Clear input | |
| dropZone.innerHTML = defaultDropZoneContent; | |
| initDropZone(); // Re-attach browse listener | |
| } | |
| // Deploy Button | |
| deployBtn.addEventListener('click', () => { | |
| if (!isCatalogGenerated) { | |
| alert("Please generate a catalog first before deploying."); | |
| return; | |
| } | |
| const originalContent = ` | |
| <span class="material-symbols-outlined mr-2 text-lg">rocket_launch</span> | |
| <span class="truncate">Deploy</span> | |
| `; | |
| deployBtn.disabled = true; | |
| // Spinner | |
| deployBtn.innerHTML = ` | |
| <svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" 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> | |
| Processing... | |
| `; | |
| setTimeout(() => { | |
| alert("Success: Catalog pushed to Shopify!"); | |
| deployBtn.innerHTML = originalContent; | |
| deployBtn.disabled = false; | |
| }, 1500); | |
| }); | |
| // Copy Button | |
| copyBtn.addEventListener('click', () => { | |
| const textToCopy = jsonOutput.innerText; | |
| navigator.clipboard.writeText(textToCopy).then(() => { | |
| const originalIcon = copyBtn.innerHTML; | |
| copyBtn.innerHTML = '<span class="material-symbols-outlined text-xl text-green-500">check</span>'; | |
| setTimeout(() => { | |
| copyBtn.innerHTML = originalIcon; | |
| }, 2000); | |
| }); | |
| }); | |
| // Download Button | |
| downloadBtn.addEventListener('click', () => { | |
| const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(jsonOutput.innerText); | |
| const downloadAnchorNode = document.createElement('a'); | |
| downloadAnchorNode.setAttribute("href", dataStr); | |
| downloadAnchorNode.setAttribute("download", "merchflow_catalog.json"); | |
| document.body.appendChild(downloadAnchorNode); | |
| downloadAnchorNode.click(); | |
| downloadAnchorNode.remove(); | |
| }); | |
| // Start Workflow | |
| startBtn.addEventListener('click', async () => { | |
| if (!selectedFile) { | |
| alert("Please select a file first."); | |
| return; | |
| } | |
| // Show loading state | |
| startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-3"><span class="text-white text-lg font-bold tracking-wide">Processing...</span></div>'; | |
| startBtn.disabled = true; | |
| const formData = new FormData(); | |
| formData.append('file', selectedFile); | |
| try { | |
| // Determine API URL - expecting localhost for this demo | |
| const response = await fetch('/generate-catalog', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| // Format JSON for display | |
| jsonOutput.textContent = JSON.stringify(data, null, 2); | |
| jsonOutput.className = "language-json"; | |
| // Allow deployment | |
| isCatalogGenerated = true; | |
| } catch (error) { | |
| console.error("Error:", error); | |
| // Fallback simulation for demo purposes if backend isn't running | |
| console.log("Backend failed, simulating success for demo."); | |
| const demoData = { | |
| "product_analysis": { | |
| "title": "Simulated Product Title", | |
| "category": "Men's Apparel", | |
| "features": ["Feature 1", "Feature 2"], | |
| "seo_tags": ["#demo", "#test"], | |
| "sentiment_score": 0.95 | |
| }, | |
| "status": "Generated (Simulation)" | |
| }; | |
| jsonOutput.textContent = JSON.stringify(demoData, null, 2); | |
| isCatalogGenerated = true; | |
| // Use this to show error if strictly required: | |
| // jsonOutput.textContent = JSON.stringify({ error: error.message }, null, 2); | |
| } finally { | |
| // Reset button | |
| startBtn.innerHTML = '<div class="absolute inset-0 flex items-center justify-center gap-3"><span class="text-white text-lg font-bold tracking-wide group-hover:scale-105 transition-transform">Start Agent Workflow</span><span class="material-symbols-outlined text-white group-hover:translate-x-1 transition-transform">arrow_forward</span></div><div class="absolute top-0 -inset-full h-full w-1/2 z-5 block transform -skew-x-12 bg-gradient-to-r from-transparent to-white opacity-20 group-hover:animate-shine"></div>'; | |
| startBtn.disabled = false; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |