Spaces:
Sleeping
Sleeping
File size: 25,979 Bytes
69c20e8 d288e3a 69c20e8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
<!DOCTYPE html>
<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> |