Spaces:
Running
Running
تمّ التوسّع في المتطلبات ليشمل: 1. دفع الإشعارات (Push Notifications) لتنبيهات المخزون و وصول ردود المساعد. 2. دعم الملفات (صور / PDF ≤ 5 MB) ضمن الدردشة. 3. رسائل صوتيّة (تسجيل ميكروفون، رفع الـ Blob، اختياريًا Whisper لتحويل الكلام إلى نص). أدناه برومت واحد بالإنجليزي يضيف كل هذه الميزات إلى الصفحة السابقة — انسخه إلى DeepSite ثم Generate. ⸻ # === ADD-ON FEATURES === ## Push Notifications • Ask permission on first load or via Settings toggle “Enable Push Notifications”. • Register `sw.js` service-worker (inline snippet) that listens for “push” and shows system notification with icon **fa-bell**. • Front-end helper `notify(title, body)` called when: 1) assistant sends a chat reply **while chat panel is closed**, 2) any product stock drops below 10 (badge red → notification “Low stock on SKU …”). • Fallback: if permission !== ‘granted’ just show in-app toast. ## Chat – File & Voice ### UI additions • Inside chat composer add two buttons before Send: 1. **fa-paperclip** opens hidden `<input type=file multiple accept="image/*,.pdf">`. 2. **fa-microphone-lines** toggles `recording` state → show red circle blink; on second click stops and sends audio. • Show each attachment as a bubble: - Images: thumbnail preview; click = full-screen lightbox. - PDF / other: **fa-file** icon + filename (download link). - Voice: inline `<audio controls>` player. ### JS logic ```javascript attachments: [], recording:false, mediaRec:null, chunks:[], handleFileUpload(e){[...e.target.files].forEach(f=>this.attachments.push(f));}, toggleRecord(){ if(!this.recording){ navigator.mediaDevices.getUserMedia({audio:true}).then(stream=>{ this.mediaRec = new MediaRecorder(stream); this.mediaRec.ondataavailable = ev => this.chunks.push(ev.data); this.mediaRec.onstop = ()=>{ const blob = new Blob(this.chunks,{type:'audio/webm'}); this.attachments.push(blob); this.chunks=[]; }; this.mediaRec.start(); this.recording=true; }); }else{ this.mediaRec.stop(); this.recording=false; } }, • On sendMessage() embed attachments array; upload each file to /api/upload (return URL) then include markdown links in message. Reset attachments=[]. Bulk Import Endpoint /api/products/bulk receives CSV, returns JSON array → merge into products[] and recalc stock alerts. Service Worker (include at bottom) <script> if('serviceWorker' in navigator){navigator.serviceWorker.register('/sw.js');} </script> sw.js (auto-generated by DeepSite) listens for “push” & shows notifications. === UPDATE PROMPT SECTIONS FROM PREVIOUS SPEC === ➤ Settings modal: toggle fa-bell “Enable Push Notifications”. ➤ Products section: call notify() when stock drops < 10. ➤ Chat sendMessage(): when panel hidden, call notify('Store AI', 'Assistant replied…'). ➤ Command Palette: new commands “enable notifications”, “disable notifications”, “send test push”. === EVERYTHING ELSE remains exactly as in the last spec (dark neon, glass, FABs, localStorage, speech-to-text, etc.) === --- > بعد التوليد: > • ارفع ملفّ `sw.js` البسيط (يدرّج كود `self.addEventListener('push', … )`). > • اربط `/api/upload`, `/api/products/bulk`، وWeb-Push back-end (أو Firebase Cloud Messaging) حسب بنيتك. > > لأي استفسار إضافي أنا حاضر. - Follow Up Deployment
f6f3698 verified | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Store AI – Control Center</title> | |
| <!-- Tailwind CSS --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script> | |
| tailwind.config = { | |
| darkMode: 'class', | |
| theme: { | |
| extend: { | |
| colors: { | |
| accent: '#38bdf8', | |
| primary: { | |
| 50: '#f0f9ff', | |
| 100: '#e0f2fe', | |
| 200: '#bae6fd', | |
| 300: '#7dd3fc', | |
| 400: '#38bdf8', | |
| 500: '#0ea5e9', | |
| 600: '#0284c7', | |
| 700: '#0369a1', | |
| 800: '#075985', | |
| 900: '#0c4a6e', | |
| } | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <!-- Font Awesome --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" /> | |
| <!-- Google Fonts --> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" /> | |
| <style> | |
| :root { --accent: #38bdf8; } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: #111827; | |
| color: #e5e7eb; | |
| } | |
| .blink { animation: blink 1s infinite; } | |
| @keyframes blink { 50% { opacity: 0; } } | |
| .glass { | |
| backdrop-filter: blur(25px) saturate(200%); | |
| border: 1px solid rgba(255, 255, 255, 0.18); | |
| background: rgba(30, 41, 59, 0.5); | |
| } | |
| .glass-light { | |
| background: rgba(248, 250, 252, 0.6); | |
| color: #0f172a; | |
| } | |
| .kpi-card { | |
| transition: transform 0.3s, box-shadow 0.3s; | |
| } | |
| .kpi-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3); | |
| } | |
| .tooltip:hover .tooltip-text { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| .tooltip-text { | |
| visibility: hidden; | |
| opacity: 0; | |
| position: absolute; | |
| z-index: 1; | |
| bottom: 100%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(30, 41, 59, 0.9); | |
| color: white; | |
| padding: 0.5rem; | |
| border-radius: 0.5rem; | |
| font-size: 0.75rem; | |
| white-space: nowrap; | |
| transition: opacity 0.3s; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .animate-pulse { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| .fab-shadow { | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); | |
| } | |
| .progress-ring { | |
| transform: rotate(-90deg); | |
| } | |
| .progress-ring-circle { | |
| stroke-dasharray: 283; | |
| stroke-dashoffset: 283; | |
| transition: stroke-dashoffset 0.5s; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-slate-900 text-slate-200 antialiased overflow-x-hidden"> | |
| <!-- App Bar --> | |
| <header class="glass fixed top-0 left-0 right-0 z-50"> | |
| <div class="container mx-auto px-4 h-16 flex items-center justify-between"> | |
| <!-- Logo --> | |
| <div class="flex items-center space-x-3"> | |
| <div class="w-9 h-9 rounded-full bg-gradient-to-br from-indigo-500 to-cyan-400 flex items-center justify-center"> | |
| <i class="fas fa-brain text-white text-sm"></i> | |
| </div> | |
| <span class="font-extrabold text-lg hidden sm:inline">Store AI</span> | |
| </div> | |
| <!-- Desktop Command Palette --> | |
| <div class="hidden md:flex flex-1 max-w-xs mx-4"> | |
| <button id="openCommand" class="w-full glass text-sm px-3 py-2 rounded-md flex items-center space-x-2 hover:bg-slate-700 transition"> | |
| <i class="fas fa-search text-xs"></i> | |
| <span>Search commands...</span> | |
| <kbd class="ml-auto text-xs px-2 py-1 bg-slate-700 rounded">⌘K</kbd> | |
| </button> | |
| </div> | |
| <!-- Nav Icons --> | |
| <div class="flex items-center space-x-3"> | |
| <button class="text-sm font-semibold hover:opacity-80 transition" title="Switch to Arabic">EN</button> | |
| <button id="themeToggle" class="p-1.5 rounded-full hover:bg-slate-700 transition tooltip"> | |
| <i class="fas fa-sun text-yellow-300"></i> | |
| <span class="tooltip-text">Light Mode</span> | |
| </button> | |
| <button class="p-1.5 rounded-full hover:bg-slate-700 transition tooltip"> | |
| <i class="fas fa-microphone text-red-400"></i> | |
| <span class="tooltip-text">Voice Command</span> | |
| </button> | |
| <div class="relative"> | |
| <button class="p-1.5 rounded-full hover:bg-slate-700 relative"> | |
| <i class="fas fa-bell"></i> | |
| <span class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">2</span> | |
| </button> | |
| </div> | |
| <div class="relative"> | |
| <button class="flex items-center space-x-2 hover:bg-slate-700 transition rounded-full p-1"> | |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center"> | |
| <span class="text-xs font-bold">AI</span> | |
| </div> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Side Rail --> | |
| <aside id="sidebar" class="glass fixed inset-y-0 left-0 z-40 w-64 p-4 space-y-5 overflow-y-auto lg:relative lg:translate-x-0 transform -translate-x-full lg:translate-x-0 transition-transform duration-300"> | |
| <div class="flex items-center justify-between mb-6"> | |
| <div class="flex items-center space-x-3"> | |
| <div class="w-9 h-9 rounded-full bg-gradient-to-br from-indigo-500 to-cyan-400 flex items-center justify-center"> | |
| <i class="fas fa-brain text-white text-sm"></i> | |
| </div> | |
| <span class="font-extrabold text-lg">Store AI</span> | |
| </div> | |
| <button id="closeSidebar" class="lg:hidden p-1 rounded-full hover:bg-slate-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mb-6"> | |
| <div class="glass rounded-lg p-3"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <span class="text-xs font-medium">Plan</span> | |
| <span class="text-xs px-2 py-1 bg-emerald-600/20 text-emerald-400 rounded-full">Pro</span> | |
| </div> | |
| <div class="w-full bg-slate-700 rounded-full h-1.5 mb-1"> | |
| <div class="bg-accent h-1.5 rounded-full" style="width: 65%"></div> | |
| </div> | |
| <div class="flex justify-between text-xs text-slate-400"> | |
| <span>65% used</span> | |
| <span>remaining 35%</span> | |
| </div> | |
| </div> | |
| </div> | |
| <nav class="space-y-1"> | |
| <a href="#kpi" class="flex items-center space-x-3 px-3 py-2 rounded-md hover:bg-slate-700 transition bg-slate-700/50"> | |
| <i class="fas fa-tachometer-alt text-sm w-5 text-center"></i> | |
| <span>Dashboard</span> | |
| <span class="ml-auto text-xs px-2 py-1 rounded-full bg-accent/20 text-accent">3</span> | |
| </a> | |
| <a href="#sim" class="flex items-center space-x-3 px-3 py-2 rounded-md hover:bg-slate-700 transition"> | |
| <i class="fas fa-sliders-h text-sm w-5 text-center"></i> | |
| <span>What-If</span> | |
| </a> | |
| <a href="#models" class="flex items-center space-x-3 px-3 py-2 rounded-md hover:bg-slate-700 transition"> | |
| <i class="fas fa-brain text-sm w-5 text-center"></i> | |
| <span>Models</span> | |
| </a> | |
| <a href="#stores" class="flex items-center space-x-3 px-3 py-2 rounded-md hover:bg-slate-700 transition"> | |
| <i class="fas fa-store text-sm w-5 text-center"></i> | |
| <span>Stores</span> | |
| </a> | |
| <a href="#products" class="flex items-center space-x-3 px-3 py-2 rounded-md hover:bg-slate-700 transition"> | |
| <i class="fas fa-box text-sm w-5 text-center"></i> | |
| <span>Products</span> | |
| </a> | |
| <a href="#ai" class="flex items-center space-x-3 px-3 py-2 rounded-md hover:bg-slate-700 transition"> | |
| <i class="fas fa-chart-bar text-sm w-5 text-center"></i> | |
| <span>AI</span> | |
| </a> | |
| <a href="#ads" class="flex items-center space-x-3 px-3 py-2 rounded-md hover:bg-slate-700 transition"> | |
| <i class="fas fa-bullhorn text-sm w-5 text-center"></i> | |
| <span>Integrations</span> | |
| <span class="ml-auto text-xs px-2 py-1 rounded-full bg-accent/20 text-accent">2</span> | |
| </a> | |
| </nav> | |
| <div class="absolute bottom-4 left-4 right-4"> | |
| <div class="glass rounded-lg p-3 text-center"> | |
| <p class="text-xs mb-1">Need help with Store AI?</p> | |
| <button class="text-xs text-accent font-medium hover:underline">Contact Support</button> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- Mobile Hamburger --> | |
| <button id="openSidebar" class="glass fixed top-20 left-4 z-30 p-2 rounded-full lg:hidden fab-shadow"> | |
| <i class="fas fa-bars"></i> | |
| </button> | |
| <!-- Main Content --> | |
| <main class="pt-20 lg:pl-72 px-4 pb-16 space-y-16"> | |
| <!-- KPI Section --> | |
| <section id="kpi" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-6"> | |
| <h2 class="text-2xl font-extrabold">Dashboard</h2> | |
| <div class="flex items-center space-x-2"> | |
| <div class="relative"> | |
| <select class="glass text-sm px-3 py-1.5 rounded appearance-none pr-7"> | |
| <option value="today">Today</option> | |
| <option value="week">This Week</option> | |
| <option value="month">This Month</option> | |
| </select> | |
| <i class="fas fa-chevron-down absolute right-2 top-1/2 transform -translate-y-1/2 text-xs pointer-events-none"></i> | |
| </div> | |
| <button class="glass p-1.5 rounded hover:bg-slate-700 transition"> | |
| <i class="fas fa-sync-alt text-sm"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| <div class="kpi-card glass p-4 rounded-xl"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="flex items-center"> | |
| <div class="w-8 h-8 rounded-full bg-accent/10 flex items-center justify-center mr-2"> | |
| <i class="fas fa-dollar-sign text-accent text-sm"></i> | |
| </div> | |
| <span class="text-sm">Revenue</span> | |
| </div> | |
| <button class="text-xs text-slate-400 hover:text-accent"> | |
| <i class="fas fa-info-circle"></i> | |
| </button> | |
| </div> | |
| <p class="text-2xl font-bold mb-1">$1,247</p> | |
| <div class="flex items-center"> | |
| <span class="text-xs text-emerald-400"> | |
| <i class="fas fa-caret-up"></i> | |
| <span>5.2%</span> | |
| </span> | |
| <span class="text-xs text-slate-400 ml-2">vs yesterday</span> | |
| </div> | |
| </div> | |
| <div class="kpi-card glass p-4 rounded-xl"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="flex items-center"> | |
| <div class="w-8 h-8 rounded-full bg-accent/10 flex items-center justify-center mr-2"> | |
| <i class="fas fa-shopping-cart text-accent text-sm"></i> | |
| </div> | |
| <span class="text-sm">Orders</span> | |
| </div> | |
| <button class="text-xs text-slate-400 hover:text-accent"> | |
| <i class="fas fa-info-circle"></i> | |
| </button> | |
| </div> | |
| <p class="text-2xl font-bold mb-1">28</p> | |
| <div class="flex items-center"> | |
| <span class="text-xs text-red-400"> | |
| <i class="fas fa-caret-down"></i> | |
| <span>2.1%</span> | |
| </span> | |
| <span class="text-xs text-slate-400 ml-2">vs yesterday</span> | |
| </div> | |
| </div> | |
| <div class="kpi-card glass p-4 rounded-xl"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="flex items-center"> | |
| <div class="w-8 h-8 rounded-full bg-accent/10 flex items-center justify-center mr-2"> | |
| <i class="fas fa-chart-line text-accent text-sm"></i> | |
| </div> | |
| <span class="text-sm">Conversion</span> | |
| </div> | |
| <button class="text-xs text-slate-400 hover:text-accent"> | |
| <i class="fas fa-info-circle"></i> | |
| </button> | |
| </div> | |
| <p class="text-2xl font-bold mb-1">12.2%</p> | |
| <div class="flex items-center"> | |
| <span class="text-xs text-emerald-400"> | |
| <i class="fas fa-caret-up"></i> | |
| <span>1.8%</span> | |
| </span> | |
| <span class="text-xs text-slate-400 ml-2">vs yesterday</span> | |
| </div> | |
| </div> | |
| <div class="kpi-card glass p-4 rounded-xl"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="flex items-center"> | |
| <div class="w-8 h-8 rounded-full bg-accent/10 flex items-center justify-center mr-2"> | |
| <i class="fas fa-heartbeat text-accent text-sm"></i> | |
| </div> | |
| <span class="text-sm">Health</span> | |
| </div> | |
| <button class="text-xs text-slate-400 hover:text-accent"> | |
| <i class="fas fa-info-circle"></i> | |
| </button> | |
| </div> | |
| <p class="text-2xl font-bold mb-1">97%</p> | |
| <div class="flex items-center"> | |
| <span class="text-xs text-emerald-400"> | |
| <i class="fas fa-caret-up"></i> | |
| <span>0.5%</span> | |
| </span> | |
| <span class="text-xs text-slate-400 ml-2">vs yesterday</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mini charts row --> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4"> | |
| <div class="glass rounded-xl p-4"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <span class="text-sm">Conversion Rate</span> | |
| <span class="text-xs px-2 py-1 bg-emerald-600/20 text-emerald-400 rounded-full">+2.4%</span> | |
| </div> | |
| <div class="h-24"> | |
| <div class="w-full h-full flex items-center justify-center text-slate-400"> | |
| <i class="fas fa-chart-line text-3xl"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl p-4"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <span class="text-sm">Avg Order Value</span> | |
| <span class="text-xs px-2 py-1 bg-red-600/20 text-red-400 rounded-full">-1.2%</span> | |
| </div> | |
| <div class="h-24"> | |
| <div class="w-full h-full flex items-center justify-center text-slate-400"> | |
| <i class="fas fa-chart-bar text-3xl"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl p-4"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <span class="text-sm">New Customers</span> | |
| <span class="text-xs px-2 py-1 bg-emerald-600/20 text-emerald-400 rounded-full">+8.7%</span> | |
| </div> | |
| <div class="h-24"> | |
| <div class="w-full h-full flex items-center justify-center text-slate-400"> | |
| <i class="fas fa-chart-pie text-3xl"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- What-If Sim --> | |
| <section id="sim" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">What-If Simulator</h2> | |
| <button class="text-sm glass px-3 py-1 rounded hover:bg-slate-700">Save Scenario</button> | |
| </div> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <div class="glass rounded-xl p-5"> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="text-sm flex items-center justify-between"> | |
| <span>Discount</span> | |
| <span class="text-xs text-slate-400">Current: 15%</span> | |
| </label> | |
| <input type="range" min="0" max="50" value="10" class="w-full accent-accent mb-1"> | |
| <div class="flex justify-between text-xs text-slate-400"> | |
| <span>0%</span> | |
| <span>10%</span> | |
| <span>50%</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="text-sm flex items-center justify-between"> | |
| <span>Budget</span> | |
| <span class="text-xs text-slate-400">Current: $750</span> | |
| </label> | |
| <input type="range" min="0" max="2000" step="50" value="500" class="w-full accent-accent mb-1"> | |
| <div class="flex justify-between text-xs text-slate-400"> | |
| <span>$0</span> | |
| <span>$500</span> | |
| <span>$2000</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl p-5"> | |
| <h3 class="text-sm font-medium mb-3">Projected Results</h3> | |
| <div class="space-y-3"> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-sm">Revenue</span> | |
| <span class="font-medium">$1,075</span> | |
| </div> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-sm">Profit</span> | |
| <span class="font-medium">$725</span> | |
| </div> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-sm">New Customers</span> | |
| <span class="font-medium">20</span> | |
| </div> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-sm">ROI</span> | |
| <span class="font-medium">145%</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl p-5"> | |
| <h3 class="text-sm font-medium mb-3">Saved Scenarios</h3> | |
| <div class="space-y-2"> | |
| <div class="flex items-center justify-between p-2 hover:bg-slate-700 rounded cursor-pointer"> | |
| <div> | |
| <p class="text-sm font-medium">Summer Sale</p> | |
| <p class="text-xs text-slate-400">Discount: 20% | Budget: $1000</p> | |
| </div> | |
| <button class="text-red-400 hover:text-red-300 p-1"> | |
| <i class="fas fa-trash text-xs"></i> | |
| </button> | |
| </div> | |
| <div class="flex items-center justify-between p-2 hover:bg-slate-700 rounded cursor-pointer"> | |
| <div> | |
| <p class="text-sm font-medium">Black Friday</p> | |
| <p class="text-xs text-slate-400">Discount: 30% | Budget: $1500</p> | |
| </div> | |
| <button class="text-red-400 hover:text-red-300 p-1"> | |
| <i class="fas fa-trash text-xs"></i> | |
| </button> | |
| </div> | |
| <div class="text-center py-4 text-sm text-slate-400"> | |
| <p>No saved scenarios</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Models Table --> | |
| <section id="models" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">AI Models</h2> | |
| <div class="flex items-center space-x-2"> | |
| <button class="text-sm glass px-3 py-1 rounded hover:bg-slate-700 flex items-center space-x-1"> | |
| <i class="fas fa-plus text-xs"></i> | |
| <span>Add Model</span> | |
| </button> | |
| <div class="relative"> | |
| <select class="glass text-sm px-3 py-1 rounded appearance-none pr-7"> | |
| <option value="all">All Models</option> | |
| <option value="active">Active</option> | |
| <option value="inactive">Inactive</option> | |
| </select> | |
| <i class="fas fa-chevron-down absolute right-2 top-1/2 transform -translate-y-1/2 text-xs pointer-events-none"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl overflow-x-auto"> | |
| <table class="w-full text-sm"> | |
| <thead class="bg-slate-800"> | |
| <tr> | |
| <th class="p-3 text-left">Model</th> | |
| <th class="p-3 text-left">Provider</th> | |
| <th class="p-3 text-left">Size</th> | |
| <th class="p-3 text-left">Status</th> | |
| <th class="p-3 text-left">Usage</th> | |
| <th class="p-3">Action</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="border-t border-slate-700 hover:bg-slate-800/50"> | |
| <td class="p-3"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-robot text-accent"></i> | |
| <span>GPT-3.5</span> | |
| </div> | |
| </td> | |
| <td class="p-3">OpenAI</td> | |
| <td class="p-3">800 MB</td> | |
| <td class="p-3"> | |
| <span class="px-2 py-1 rounded-full text-xs bg-emerald-600/20 text-emerald-400">Active</span> | |
| </td> | |
| <td class="p-3"> | |
| <div class="w-20 h-1.5 bg-slate-700 rounded-full overflow-hidden"> | |
| <div class="h-full bg-accent rounded-full" style="width: 75%"></div> | |
| </div> | |
| </td> | |
| <td class="p-3"> | |
| <div class="flex items-center space-x-2"> | |
| <button class="text-xs px-2 py-1 rounded hover:bg-slate-700 text-red-400">Deactivate</button> | |
| <button class="text-xs px-2 py-1 rounded hover:bg-slate-700 text-slate-400">Remove</button> | |
| </div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <!-- Stores Table --> | |
| <section id="stores" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">Stores</h2> | |
| <div class="flex items-center space-x-2"> | |
| <button class="text-sm glass px-3 py-1 rounded hover:bg-slate-700 flex items-center space-x-1"> | |
| <i class="fas fa-plus text-xs"></i> | |
| <span>Add Store</span> | |
| </button> | |
| <div class="relative"> | |
| <select class="glass text-sm px-3 py-1 rounded appearance-none pr-7"> | |
| <option value="all">All Stores</option> | |
| <option value="healthy">Healthy</option> | |
| <option value="warning">Warning</option> | |
| <option value="critical">Critical</option> | |
| </select> | |
| <i class="fas fa-chevron-down absolute right-2 top-1/2 transform -translate-y-1/2 text-xs pointer-events-none"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl overflow-x-auto"> | |
| <table class="w-full text-sm"> | |
| <thead class="bg-slate-800"> | |
| <tr> | |
| <th class="p-3 text-left">Store</th> | |
| <th class="p-3 text-left">Platform</th> | |
| <th class="p-3 text-left">Health</th> | |
| <th class="p-3 text-left">Last Sync</th> | |
| <th class="p-3">Action</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="border-t border-slate-700 hover:bg-slate-800/50"> | |
| <td class="p-3"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-tshirt text-accent"></i> | |
| <span>FashionHub</span> | |
| </div> | |
| </td> | |
| <td class="p-3">Shopify</td> | |
| <td class="p-3"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="relative w-8 h-8"> | |
| <svg class="w-8 h-8 progress-ring" viewBox="0 0 100 100"> | |
| <circle class="text-slate-700" stroke-width="8" stroke="currentColor" fill="transparent" r="45" cx="50" cy="50" /> | |
| <circle class="progress-ring-circle" stroke-width="8" stroke-linecap="round" stroke="#10b981" fill="transparent" r="45" cx="50" cy="50" stroke-dashoffset="283 - (283 * 94 / 100)" /> | |
| </svg> | |
| <span class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs font-bold text-emerald-400">94%</span> | |
| </div> | |
| <span class="text-xs text-emerald-400"> | |
| <i class="fas fa-caret-up"></i> | |
| <span>2%</span> | |
| </span> | |
| </div> | |
| </td> | |
| <td class="p-3 text-xs text-slate-400">1 hour ago</td> | |
| <td class="p-3"> | |
| <div class="flex items-center space-x-2"> | |
| <button class="text-xs px-2 py-1 rounded hover:bg-slate-700 text-accent">Sync</button> | |
| <button class="text-xs px-2 py-1 rounded hover:bg-slate-700">Inspect</button> | |
| </div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <!-- Products Table --> | |
| <section id="products" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">Products</h2> | |
| <div class="flex items-center space-x-2"> | |
| <button class="text-sm glass px-3 py-1 rounded hover:bg-slate-700 flex items-center space-x-1"> | |
| <i class="fas fa-plus text-xs"></i> | |
| <span>Add Product</span> | |
| </button> | |
| <label class="glass px-3 py-1 rounded cursor-pointer text-sm"> | |
| <i class="fas fa-file-upload mr-1"></i> Import CSV | |
| <input type="file" accept=".csv" class="hidden" /> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl overflow-x-auto"> | |
| <table class="w-full text-sm"> | |
| <thead class="bg-slate-800"> | |
| <tr> | |
| <th class="p-3 text-left">#</th> | |
| <th class="p-3 text-left">Name</th> | |
| <th class="p-3 text-left">SKU</th> | |
| <th class="p-3 text-left">Price</th> | |
| <th class="p-3 text-left">Stock</th> | |
| <th class="p-3 text-left">Action</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr class="border-t border-slate-700"> | |
| <td class="p-3">1</td> | |
| <td class="p-3">Premium T-Shirt</td> | |
| <td class="p-3">TSH-001</td> | |
| <td class="p-3">$29.99</td> | |
| <td class="p-3"> | |
| <span class="px-2 py-1 rounded-full text-xs bg-emerald-600">42</span> | |
| </td> | |
| <td class="p-3"> | |
| <button class="text-accent hover:underline">Edit</button> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <!-- AI Usage Section --> | |
| <section id="ai" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">AI Usage</h2> | |
| <div class="relative"> | |
| <select class="glass text-sm px-3 py-1 rounded appearance-none pr-7"> | |
| <option value="7d">Last 7 Days</option> | |
| <option value="30d">Last 30 Days</option> | |
| <option value="90d">Last 90 Days</option> | |
| </select> | |
| <i class="fas fa-chevron-down absolute right-2 top-1/2 transform -translate-y-1/2 text-xs pointer-events-none"></i> | |
| </div> | |
| </div> | |
| <div class="grid md:grid-cols-2 gap-6"> | |
| <div class="glass rounded-xl p-4"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h3 class="font-medium">API Usage</h3> | |
| <div class="flex items-center space-x-2"> | |
| <div class="flex items-center"> | |
| <div class="w-2 h-2 rounded-full bg-accent mr-1"></div> | |
| <span class="text-xs">This Month</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-2 h-2 rounded-full bg-slate-500 mr-1"></div> | |
| <span class="text-xs">Last Month</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="h-48"> | |
| <div class="w-full h-full flex items-center justify-center text-slate-400"> | |
| <i class="fas fa-chart-line text-4xl"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl p-4 mt-6"> | |
| <h3 class="font-medium mb-4">Cost Summary</h3> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-4"> | |
| <div class="bg-slate-800/50 rounded-lg p-3"> | |
| <p class="text-sm text-slate-400 mb-1">This Month</p> | |
| <p class="text-xl font-bold">$1,245</p> | |
| </div> | |
| <div class="bg-slate-800/50 rounded-lg p-3"> | |
| <p class="text-sm text-slate-400 mb-1">Last Month</p> | |
| <p class="text-xl font-bold">$1,080</p> | |
| </div> | |
| <div class="bg-slate-800/50 rounded-lg p-3"> | |
| <p class="text-sm text-slate-400 mb-1">Change</p> | |
| <p class="text-xl font-bold text-emerald-400">+15.3%</p> | |
| </div> | |
| <div class="bg-slate-800/50 rounded-lg p-3"> | |
| <p class="text-sm text-slate-400 mb-1">Forecast</p> | |
| <p class="text-xl font-bold">$1,450</p> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Products Section --> | |
| <section id="products" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">Products</h2> | |
| <div class="flex items-center space-x-2"> | |
| <button id="bulkImportBtn" class="text-sm glass px-3 py-1 rounded hover:bg-slate-700 flex items-center space-x-1"> | |
| <i class="fas fa-file-import text-xs"></i> | |
| <span>Bulk Import</span> | |
| </button> | |
| <button class="text-sm glass px-3 py-1 rounded hover:bg-slate-700 flex items-center space-x-1"> | |
| <i class="fas fa-plus text-xs"></i> | |
| <span>Add Product</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl overflow-x-auto"> | |
| <table id="productsTable" class="w-full text-sm"> | |
| <thead class="bg-slate-800"> | |
| <tr> | |
| <th class="p-3 text-left">Name</th> | |
| <th class="p-3 text-left">SKU</th> | |
| <th class="p-3 text-left">Price</th> | |
| <th class="p-3 text-left">Stock</th> | |
| <th class="p-3 text-left">Status</th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <!-- Integrations --> | |
| <section id="ads" class="scroll-mt-20"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">Integrations</h2> | |
| <button class="text-sm glass px-3 py-1 rounded hover:bg-slate-700 flex items-center space-x-1"> | |
| <i class="fas fa-plus text-xs"></i> | |
| <span>Add Integration</span> | |
| </button> | |
| </div> | |
| <div class="flex flex-wrap gap-2 mb-4"> | |
| <button class="px-3 py-1 rounded-md text-sm transition bg-accent text-white">All</button> | |
| <button class="px-3 py-1 rounded-md text-sm transition glass hover:bg-slate-700">Ads</button> | |
| <button class="px-3 py-1 rounded-md text-sm transition glass hover:bg-slate-700">Stores</button> | |
| <button class="px-3 py-1 rounded-md text-sm transition glass hover:bg-slate-700">Email</button> | |
| <button class="px-3 py-1 rounded-md text-sm transition glass hover:bg-slate-700">Automation</button> | |
| </div> | |
| <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> | |
| <div class="glass rounded-xl p-4 hover:bg-slate-700/50 transition cursor-pointer"> | |
| <div class="flex items-start justify-between mb-3"> | |
| <div class="w-10 h-10 rounded-lg bg-accent/10 flex items-center justify-center"> | |
| <i class="fab fa-wordpress text-accent text-lg"></i> | |
| </div> | |
| <span class="text-xs px-2 py-1 rounded-full bg-slate-700/50 text-slate-400">Disconnected</span> | |
| </div> | |
| <p class="font-semibold mb-1">WooCommerce</p> | |
| <p class="text-xs text-slate-400 mb-3">Connect your WooCommerce store</p> | |
| <div class="w-full bg-slate-700 rounded-full h-1.5 mb-1"> | |
| <div class="bg-accent h-1.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <div class="flex justify-between text-xs text-slate-400"> | |
| <span>Not connected</span> | |
| <span>E-commerce</span> | |
| </div> | |
| </div> | |
| <div class="glass rounded-xl p-4 hover:bg-slate-700/50 transition cursor-pointer"> | |
| <div class="flex items-start justify-between mb-3"> | |
| <div class="w-10 h-10 rounded-lg bg-accent/10 flex items-center justify-center"> | |
| <i class="fab fa-shopify text-accent text-lg"></i> | |
| </div> | |
| <span class="text-xs px-2 py-1 rounded-full bg-emerald-600/20 text-emerald-400">Connected</span> | |
| </div> | |
| <p class="font-semibold mb-1">Shopify</p> | |
| <p class="text-xs text-slate-400 mb-3">Connect your Shopify store</p> | |
| <div class="w-full bg-slate-700 rounded-full h-1.5 mb-1"> | |
| <div class="bg-accent h-1.5 rounded-full" style="width: 100%"></div> | |
| </div> | |
| <div class="flex justify-between text-xs text-slate-400"> | |
| <span>Connected</span> | |
| <span>E-commerce</span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- n8n Hidden Section --> | |
| <section id="n8n" class="scroll-mt-20 hidden"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-xl font-bold">n8n Automation</h2> | |
| <button id="refreshWorkflowsBtn" class="text-sm glass px-3 py-1 rounded hover:bg-slate-700"> | |
| <i class="fas fa-sync-alt mr-1"></i>Refresh | |
| </button> | |
| </div> | |
| <div id="workflowsList" class="glass rounded-xl p-4"> | |
| <p class="text-slate-400">Loading workflows…</p> | |
| </div> | |
| </section> | |
| <!-- Command Palette Modal --> | |
| <div id="commandModal" class="fixed inset-0 bg-black/50 z-50 hidden items-start justify-center pt-20"> | |
| <div class="glass w-full max-w-lg rounded-xl overflow-hidden"> | |
| <div class="p-4 border-b border-slate-700 flex items-center"> | |
| <i class="fas fa-search text-sm mr-2 text-slate-400"></i> | |
| <input type="search" placeholder="Type to search..." class="w-full bg-transparent outline-none placeholder-slate-400" /> | |
| </div> | |
| <div class="max-h-96 overflow-y-auto"> | |
| <button class="w-full text-left p-3 hover:bg-slate-700 flex items-center space-x-3"> | |
| <i class="fas fa-sync-alt text-sm w-5 text-center text-slate-400"></i> | |
| <div> | |
| <p>Refresh Data</p> | |
| <p class="text-xs text-slate-400">Reload all dashboard data</p> | |
| </div> | |
| <kbd class="ml-auto text-xs px-2 py-1 bg-slate-700 rounded">F5</kbd> | |
| </button> | |
| <button class="w-full text-left p-3 hover:bg-slate-700 flex items-center space-x-3"> | |
| <i class="fas fa-cog text-sm w-5 text-center text-slate-400"></i> | |
| <div> | |
| <p>Open Settings</p> | |
| <p class="text-xs text-slate-400">Open application settings</p> | |
| </div> | |
| <kbd class="ml-auto text-xs px-2 py-1 bg-slate-700 rounded">⌘S</kbd> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Floating Action Cluster --> | |
| <div class="fixed bottom-6 right-6 flex flex-col items-end space-y-3"> | |
| <button id="settingsFab" class="glass p-3 rounded-full shadow-lg fab-shadow transition hover:scale-110" aria-label="Settings"> | |
| <i class="fas fa-cog"></i> | |
| </button> | |
| <button id="addProductFab" class="glass p-3 rounded-full shadow-lg fab-shadow transition hover:scale-110 neon" aria-label="Add Product"> | |
| <i class="fas fa-box-open"></i> | |
| </button> | |
| <button id="chatToggleFab" class="glass p-4 rounded-full shadow-lg fab-shadow transition hover:scale-110" aria-label="Toggle Chat"> | |
| <i class="fas fa-robot"></i> | |
| </button> | |
| </div> | |
| <!-- Live Chat Panel --> | |
| <div id="chatPanel" class="fixed inset-x-0 bottom-0 z-50 glass rounded-t-xl shadow-2xl transform translate-y-full transition-transform duration-300 md:max-w-md md:mx-auto"> | |
| <header class="flex items-center justify-between p-4 border-b border-slate-700"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-robot text-accent"></i> | |
| <span class="font-bold">Live Chat</span> | |
| </div> | |
| <button id="closeChat" class="p-1 rounded-full hover:bg-slate-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </header> | |
| <div id="chatMessages" class="h-[60vh] md:h-[70vh] overflow-y-auto p-4 space-y-3"></div> | |
| <footer class="p-4 border-t border-slate-700 flex items-center space-x-2"> | |
| <button id="emojiToggle" class="p-2 rounded-full hover:bg-slate-700"> | |
| <i class="far fa-face-smile"></i> | |
| </button> | |
| <label class="p-2 rounded-full hover:bg-slate-700 cursor-pointer"> | |
| <i class="fas fa-paperclip"></i> | |
| <input type="file" multiple accept="image/*,.pdf" class="hidden" @change="handleFileUpload($event)"> | |
| </label> | |
| <button id="recordBtn" :class="recording ? 'text-red-400 blink' : ''" class="p-2 rounded-full hover:bg-slate-700" @click="toggleRecord"> | |
| <i :class="recording ? 'fas fa-stop' : 'fas fa-microphone-lines'"></i> | |
| </button> | |
| <input id="chatInput" type="text" placeholder="Type a message…" class="flex-1 bg-transparent outline-none" /> | |
| <button id="sendMessage" class="p-2 rounded-full hover:bg-slate-700"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </footer> | |
| </div> | |
| <!-- Settings Modal --> | |
| <div id="settingsModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center"> | |
| <div class="glass w-full max-w-md rounded-xl p-6 space-y-4"> | |
| <div class="flex items-center justify-between"> | |
| <h3 class="text-lg font-bold">Settings</h3> | |
| <button id="closeSettings" class="p-1 rounded-full hover:bg-slate-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div> | |
| <label class="block text-sm mb-1">Accent Color</label> | |
| <input id="accentColorPicker" type="color" value="#38bdf8" class="w-full h-10 rounded cursor-pointer" /> | |
| </div> | |
| <div> | |
| <label class="block text-sm mb-1">Theme</label> | |
| <button id="themeToggleBtn" class="glass px-3 py-1 rounded">Toggle Dark / Light</button> | |
| </div> | |
| <div> | |
| <label class="flex items-center space-x-2"> | |
| <input id="pushToggle" type="checkbox" class="accent-accent"> | |
| <span class="text-sm">Enable Push Notifications</span> | |
| </label> | |
| </div> | |
| <div class="text-right"> | |
| <button id="saveSettings" class="px-4 py-2 bg-accent rounded hover:bg-opacity-80">Save</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script> | |
| <script> | |
| // Service-Worker registration | |
| if ('serviceWorker' in navigator) { | |
| const swCode = ` | |
| self.addEventListener('push', e => { | |
| const data = e.data?.json() ?? { title: 'Store AI', body: 'New message' }; | |
| e.waitUntil( | |
| self.registration.showNotification(data.title, { | |
| body: data.body, | |
| icon: '/favicon.ico', | |
| badge: '/favicon.ico' | |
| }) | |
| ); | |
| }); | |
| `; | |
| const blob = new Blob([swCode], { type: 'text/javascript' }); | |
| navigator.serviceWorker.register(URL.createObjectURL(blob)) | |
| .then(() => Notification.requestPermission()); | |
| } | |
| // Push helper | |
| function notify(title, body) { | |
| if (Notification.permission === 'granted') { | |
| navigator.serviceWorker.getRegistration().then(reg => { | |
| reg?.showNotification(title, { body, icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJ3LTQgaC00IiBmaWxsPSIjZmZmIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZD0iTTIyNCAxMzZjMCA4LjgtNy4yIDE2LTE2IDE2cy0xNi03LjItMTYtMTZWNTEyYzAgOC44IDcuMiAxNiAxNiAxNnMxNi03LjIgMTYtMTZWMTM2em0tOTYgMGMwIDguOC03LjIgMTYtMTYgMTZzLTE2LTcuMi0xNi0xNlY1MTJjMCA4LjggNy4yIDE2IDE2IDE2czE2LTcuMiAxNi0xNlYxMzZ6bTMyMCAwYzAgOC44LTcuMiAxNi0xNiAxNnMtMTYtNy4yLTE2LTE2VjUxMmMwIDguOCA3LjIgMTYgMTYgMTZzMTYtNy4yIDE2LTE2VjEzNnoiLz48L3N2Zz4=' }); | |
| } else { | |
| // fallback toast | |
| const toast = document.createElement('div'); | |
| toast.className = 'fixed top-20 right-4 glass px-4 py-2 rounded shadow'; | |
| toast.textContent = `${title}: ${body}`; | |
| document.body.appendChild(toast); | |
| setTimeout(() => toast.remove(), 3000); | |
| } | |
| } | |
| // Persisted state keys | |
| const STORAGE_KEYS = ['lang', 'theme', 'accent', 'plugins', 'products', 'chat']; | |
| // Default state | |
| const storeAi = () => ({ | |
| theme: 'dark', | |
| accent: '#38bdf8', | |
| lang: 'en', | |
| products: [], | |
| openCmdK: false, | |
| chatOpen: false, | |
| emojisEnabled: false, | |
| messages: [], | |
| attachments: [], | |
| recording: false, | |
| mediaRec: null, | |
| chunks: [], | |
| init() { | |
| // Load persisted state | |
| STORAGE_KEYS.forEach(k => { | |
| const val = localStorage.getItem(k); | |
| if (val) { this[k] = k === 'products' ? JSON.parse(val) : val; } | |
| }); | |
| this.applyTheme(); | |
| this.applyAccent(); | |
| this.loadProductsTable(); | |
| this.loadChat(); | |
| // Sidebar toggle | |
| document.getElementById('openSidebar').addEventListener('click', () => { | |
| document.getElementById('sidebar').classList.remove('-translate-x-full'); | |
| }); | |
| document.getElementById('closeSidebar').addEventListener('click', () => { | |
| document.getElementById('sidebar').classList.add('-translate-x-full'); | |
| }); | |
| // Command Palette | |
| document.addEventListener('keydown', e => { | |
| if ((e.metaKey || e.ctrlKey) && e.key === 'k') { | |
| e.preventDefault(); | |
| this.openCmdK = true; | |
| this.$nextTick(() => this.$refs.cmdInput?.focus()); | |
| } | |
| if (e.key === 'Escape') { | |
| this.openCmdK = false; | |
| this.chatOpen = false; | |
| document.getElementById('settingsModal').classList.add('hidden'); | |
| } | |
| }); | |
| // Floating buttons | |
| document.getElementById('settingsFab').addEventListener('click', () => { | |
| document.getElementById('settingsModal').classList.remove('hidden'); | |
| }); | |
| document.getElementById('closeSettings').addEventListener('click', () => { | |
| document.getElementById('settingsModal').classList.add('hidden'); | |
| }); | |
| document.getElementById('addProductFab').addEventListener('click', () => this.openAddProductModal()); | |
| document.getElementById('chatToggleFab').addEventListener('click', () => this.toggleChat()); | |
| document.getElementById('closeChat').addEventListener('click', () => this.toggleChat()); | |
| document.getElementById('sendMessage').addEventListener('click', () => this.sendChatMessage()); | |
| document.getElementById('chatInput').addEventListener('keydown', e => { | |
| if (e.key === 'Enter') this.sendChatMessage(); | |
| }); | |
| document.getElementById('emojiToggle').addEventListener('click', () => this.emojisEnabled = !this.emojisEnabled); | |
| // Settings modal | |
| document.getElementById('saveSettings').addEventListener('click', () => this.saveSettings()); | |
| document.getElementById('themeToggleBtn').addEventListener('click', () => { | |
| this.theme = this.theme === 'dark' ? 'light' : 'dark'; | |
| this.applyTheme(); | |
| }); | |
| // Bulk import | |
| document.getElementById('bulkImportBtn').addEventListener('click', () => { | |
| const inp = document.createElement('input'); | |
| inp.type = 'file'; | |
| inp.accept = '.csv'; | |
| inp.onchange = e => { | |
| const file = e.target.files[0]; | |
| const reader = new FileReader(); | |
| reader.onload = () => { | |
| const lines = reader.result.split('\n').slice(1); | |
| const prods = lines.map(l => { | |
| const [name, sku, price, stock] = l.split(','); | |
| return { name, sku, price: parseFloat(price), stock: parseInt(stock) }; | |
| }).filter(p => p.name); | |
| this.products = [...this.products, ...prods]; | |
| this.saveProducts(); | |
| }; | |
| reader.readAsText(file); | |
| }; | |
| inp.click(); | |
| }); | |
| // Observer for nav highlighting | |
| const sections = document.querySelectorAll('section[id]'); | |
| const navLinks = document.querySelectorAll('nav a'); | |
| const observer = new IntersectionObserver(entries => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| navLinks.forEach(link => link.classList.toggle('bg-slate-700/50', link.getAttribute('href') === `#${entry.target.id}`)); | |
| } | |
| }); | |
| }, { threshold: 0.4 }); | |
| sections.forEach(s => observer.observe(s)); | |
| }, | |
| applyTheme() { | |
| document.documentElement.classList.toggle('dark', this.theme === 'dark'); | |
| localStorage.setItem('theme', this.theme); | |
| }, | |
| applyAccent() { | |
| document.documentElement.style.setProperty('--accent', this.accent); | |
| localStorage.setItem('accent', this.accent); | |
| }, | |
| saveSettings() { | |
| this.accent = document.getElementById('accentColorPicker').value; | |
| this.applyAccent(); | |
| // Push toggle | |
| const pushOn = document.getElementById('pushToggle').checked; | |
| if (pushOn && Notification.permission !== 'granted') { | |
| Notification.requestPermission().then(p => { | |
| if (p === 'granted') notify('Push enabled', 'You will receive system notifications.'); | |
| }); | |
| } | |
| document.getElementById('settingsModal').classList.add('hidden'); | |
| }, | |
| openAddProductModal() { | |
| const name = prompt('Product Name'); | |
| const sku = prompt('SKU'); | |
| const price = parseFloat(prompt('Price')); | |
| const stock = parseInt(prompt('Stock')); | |
| if (name && sku && !isNaN(price) && !isNaN(stock)) { | |
| this.products.push({ name, sku, price, stock }); | |
| this.saveProducts(); | |
| } | |
| }, | |
| saveProducts() { | |
| localStorage.setItem('products', JSON.stringify(this.products)); | |
| this.products.forEach(p => { | |
| if (p.stock < 10) notify('Low stock', `SKU ${p.sku} has ${p.stock} left`); | |
| }); | |
| this.loadProductsTable(); | |
| }, | |
| loadProductsTable() { | |
| const tbody = document.querySelector('#productsTable tbody'); | |
| tbody.innerHTML = ''; | |
| this.products.forEach(p => { | |
| const tr = document.createElement('tr'); | |
| tr.className = 'border-t border-slate-700 hover:bg-slate-800/50'; | |
| tr.innerHTML = ` | |
| <td class="p-3">${p.name}</td> | |
| <td class="p-3">${p.sku}</td> | |
| <td class="p-3">${p.price.toFixed(2)}</td> | |
| <td class="p-3"> | |
| <span class="px-2 py-1 rounded-full text-xs ${p.stock < 10 ? 'bg-red-600' : 'bg-emerald-600'}">${p.stock}</span> | |
| </td> | |
| <td class="p-3"> | |
| <span class="text-xs px-2 py-1 rounded-full bg-slate-700">Active</span> | |
| </td> | |
| `; | |
| tbody.appendChild(tr); | |
| }); | |
| }, | |
| toggleChat() { | |
| this.chatOpen = !this.chatOpen; | |
| const panel = document.getElementById('chatPanel'); | |
| panel.classList.toggle('translate-y-full', !this.chatOpen); | |
| panel.classList.toggle('translate-y-0', this.chatOpen); | |
| if (this.chatOpen) this.scrollToBottom(); | |
| }, | |
| sendChatMessage() { | |
| const input = document.getElementById('chatInput'); | |
| const text = input.value.trim(); | |
| if (!text && this.attachments.length === 0) return; | |
| const msg = { | |
| role: 'user', | |
| content: text, | |
| attachments: [...this.attachments] | |
| }; | |
| this.messages.push(msg); | |
| this.messages.push({ role: 'assistant', content: '...', emojis: this.emojisEnabled ? ['🤖'] : [] }); | |
| input.value = ''; | |
| this.attachments = []; | |
| this.saveChat(); | |
| this.scrollToBottom(); | |
| // Simulate response | |
| setTimeout(() => { | |
| const last = this.messages[this.messages.length - 1]; | |
| last.content = 'Got your message! Anything else?'; | |
| this.saveChat(); | |
| if (!this.chatOpen) notify('Store AI', last.content); | |
| }, 500); | |
| }, | |
| scrollToBottom() { | |
| this.$nextTick(() => { | |
| const box = document.getElementById('chatMessages'); | |
| box.scrollTop = box.scrollHeight; | |
| }); | |
| }, | |
| saveChat() { | |
| localStorage.setItem('chat', JSON.stringify(this.messages)); | |
| }, | |
| loadChat() { | |
| this.messages = JSON.parse(localStorage.getItem('chat') || '[]'); | |
| this.$nextTick(() => { | |
| const box = document.getElementById('chatMessages'); | |
| box.innerHTML = ''; | |
| this.messages.forEach(m => { | |
| const div = document.createElement('div'); | |
| div.className = `flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`; | |
| let attachHTML = ''; | |
| (m.attachments || []).forEach(f => { | |
| const url = URL.createObjectURL(f); | |
| if (f.type.startsWith('image/')) { | |
| attachHTML += `<img src="${url}" class="rounded mb-1 max-h-32 cursor-pointer" onclick="window.open('${url}')"/>`; | |
| } else if (f.type === 'application/pdf') { | |
| attachHTML += `<a href="${url}" download="${f.name}" class="text-sm underline flex items-center"><i class="fas fa-file mr-1"></i>${f.name}</a>`; | |
| } else if (f.type.startsWith('audio/')) { | |
| attachHTML += `<audio controls src="${url}" class="w-full"></audio>`; | |
| } | |
| }); | |
| div.innerHTML = ` | |
| <div class="max-w-[70%] px-3 py-2 rounded-xl ${ | |
| m.role === 'user' ? 'bg-accent text-white' : 'glass' | |
| }"> | |
| <span>${m.content}</span> | |
| ${m.emojis?.length ? `<div class="text-xs mt-1">${m.emojis.join('')}</div>` : ''} | |
| <div class="mt-1">${attachHTML}</div> | |
| </div> | |
| `; | |
| box.appendChild(div); | |
| }); | |
| }); | |
| }, | |
| handleFileUpload(e) { | |
| [...e.target.files].forEach(f => this.attachments.push(f)); | |
| }, | |
| toggleRecord() { | |
| if (!this.recording) { | |
| navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { | |
| this.mediaRec = new MediaRecorder(stream); | |
| this.mediaRec.ondataavailable = ev => this.chunks.push(ev.data); | |
| this.mediaRec.onstop = () => { | |
| const blob = new Blob(this.chunks, { type: 'audio/webm' }); | |
| this.attachments.push(blob); | |
| this.chunks = []; | |
| }; | |
| this.mediaRec.start(); | |
| this.recording = true; | |
| }); | |
| } else { | |
| this.mediaRec.stop(); | |
| this.recording = false; | |
| } | |
| }, | |
| // Command palette handler | |
| runCommand(cmd) { | |
| cmd = cmd.toLowerCase(); | |
| if (cmd.includes('add product')) this.openAddProductModal(); | |
| if (cmd.includes('bulk import')) document.getElementById('bulkImportBtn').click(); | |
| if (cmd.includes('open n8n')) document.getElementById('n8n').classList.remove('hidden'); | |
| if (cmd.startsWith('find sku')) { | |
| const sku = cmd.split('sku')[1]?.trim(); | |
| if (sku) { | |
| const row = [...document.querySelectorAll('#productsTable tbody tr')].find(tr => tr.children[1].textContent === sku); | |
| row?.scrollIntoView({ behavior: 'smooth' }); | |
| row?.classList.add('ring', 'ring-accent'); | |
| setTimeout(() => row?.classList.remove('ring', 'ring-accent'), 2000); | |
| } | |
| } | |
| } | |
| }); | |
| Alpine.data('storeAi', storeAi); | |
| // --- Push Notification helpers --- | |
| function notify(title, body) { | |
| if (Notification.permission === 'granted') { | |
| navigator.serviceWorker.getRegistration().then(reg => { | |
| if (reg) reg.showNotification(title, { body, icon: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/svgs/solid/bell.svg' }); | |
| }); | |
| } else { | |
| // fallback toast | |
| const toast = document.createElement('div'); | |
| toast.className = 'fixed top-20 right-4 glass px-4 py-2 rounded shadow text-sm'; | |
| toast.textContent = `${title}: ${body}`; | |
| document.body.appendChild(toast); | |
| setTimeout(() => toast.remove(), 3000); | |
| } | |
| } | |
| // --- File & voice handlers inside Alpine --- | |
| document.getElementById('filePicker').addEventListener('change', e => { | |
| [...e.target.files].forEach(f => Alpine.store('storeAi').attachments.push(f)); | |
| }); | |
| document.getElementById('recordBtn').addEventListener('click', () => Alpine.store('storeAi').toggleRecord()); | |
| // --- Inline Service-Worker registration --- | |
| if ('serviceWorker' in navigator) { | |
| const swCode = ` | |
| self.addEventListener('push', e => { | |
| const data = e.data?.json() || { title: 'Store AI', body: 'New message' }; | |
| e.waitUntil( | |
| self.registration.showNotification(data.title, { | |
| body: data.body, | |
| icon: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/svgs/solid/bell.svg' | |
| }) | |
| ); | |
| }); | |
| `; | |
| const blob = new Blob([swCode], { type: 'text/javascript' }); | |
| navigator.serviceWorker.register(URL.createObjectURL(blob)); | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=fsalmansour/store1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |