Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> | |
| <title>V.AI AVATAR + V.AI STUDIO - Chat & Voice</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| <!-- v55: hot third-party origins (esm.sh avatar stack, CDN favicon libs) — | |
| preconnect saves a TLS round trip on low-latency mobile networks. --> | |
| <link rel="preconnect" href="https://esm.sh" crossorigin /> | |
| <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin /> | |
| <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin /> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Geist+Mono:wght@500&display=swap" rel="stylesheet" /> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" /> | |
| <link rel="stylesheet" href="/src/style.css?gc=31" /> | |
| <!-- BIG SALE Floating Promotion CSS --> | |
| <link rel="stylesheet" href="/big_sale.css?v=7" /> | |
| </head> | |
| <body> | |
| <main id="app"> | |
| <div id="stage" aria-label="3D avatar"></div> | |
| <div id="loading">Loading avatar…</div> | |
| <!-- Welcome Modal --> | |
| <div id="welcome-modal" class="welcome-modal-overlay"> | |
| <div class="welcome-modal"> | |
| <div class="welcome-avatar-icon">🤖</div> | |
| <h2 class="welcome-title">Chào mừng đến với V.AI AVATAR</h2> | |
| <p class="welcome-subtitle">Bạn muốn giao tiếp thế nào?</p> | |
| <!-- ✨ Avatar chooser (Mr V / Lisamy) --> | |
| <div class="welcome-avatars"> | |
| <p class="welcome-avatars-title">Chọn đại diện của em:</p> | |
| <div id="welcome-avatar-row" class="welcome-avatar-row"> | |
| <!-- populated by src/avatar-picker.js --> | |
| </div> | |
| </div> | |
| <div class="welcome-actions"> | |
| <button id="welcome-quick-btn" class="welcome-btn welcome-btn-quick" aria-label="Tư vấn nhanh"> | |
| <span class="welcome-btn-icon">⚡</span> | |
| <span class="welcome-btn-text-content"> | |
| <span class="welcome-btn-main-text">Tư vấn nhanh</span> | |
| <span class="welcome-btn-desc">Không cần chờ avatar, chat với AI và xem sản phẩm ngay</span> | |
| </span> | |
| </button> | |
| <button id="welcome-catalogue-btn" class="welcome-btn welcome-btn-catalogue" aria-label="Catalogue AI"> | |
| <span class="welcome-btn-icon">📚</span> | |
| <span class="welcome-btn-text-content"> | |
| <span class="welcome-btn-main-text">Catalogue AI</span> | |
| <span class="welcome-btn-desc">Tìm sản phẩm Malloca 2026 từ catalogue PDF bằng ngôn ngữ tự nhiên</span> | |
| </span> | |
| </button> | |
| <button id="welcome-baogia-btn" class="welcome-btn welcome-btn-baogia" aria-label="Báo giá nội thất"> | |
| <span class="welcome-btn-icon">📐</span> | |
| <span class="welcome-btn-text-content"> | |
| <span class="welcome-btn-main-text">Báo giá nội thất</span> | |
| <span class="welcome-btn-desc">Tải bản vẽ kỹ thuật (PDF/ảnh) → OCR đọc kích thước → m² · mét dài · cái theo hạng mục</span> | |
| </span> | |
| </button> | |
| <button id="welcome-chat-btn" class="welcome-btn welcome-btn-text" aria-label="Chat tin nhắn"> | |
| <span class="welcome-btn-icon">💬</span> | |
| <span class="welcome-btn-text-content"> | |
| <span class="welcome-btn-main-text">Chat tin nhắn</span> | |
| <span class="welcome-btn-desc">Không cần mic, trò chuyện bằng chữ với AI</span> | |
| </span> | |
| </button> | |
| <button id="welcome-voice-btn" class="welcome-btn welcome-btn-voice" aria-label="Gọi voice"> | |
| <span class="welcome-btn-icon">🎙️</span> | |
| <span class="welcome-btn-text-content"> | |
| <span class="welcome-btn-main-text">Gọi voice</span> | |
| <span class="welcome-btn-desc">Cần mic & loa, nói chuyện trực tiếp</span> | |
| </span> | |
| </button> | |
| </div> | |
| <div class="welcome-features"> | |
| <div class="welcome-feature"><span class="wf-icon">🗣️</span><span class="wf-text">Nhận diện giọng nói tiếng Việt — tự động gõ vào ô chat (nhấn nút mic 🎤 để bật)</span></div> | |
| <div class="welcome-feature"><span class="wf-icon">🎙️</span><span class="wf-text">Chế độ voice call — nói chuyện trực tiếp với AI, phụ đề song ngữ</span></div> | |
| <div class="welcome-feature"><span class="wf-icon">🛒</span><span class="wf-text">8000+ sản phẩm gia dụng — Malloca, Eurogold, Grob, Canzy & khoá thông minh</span></div> | |
| <div class="welcome-feature"><span class="wf-icon">🧠</span><span class="wf-text">AI Avatar + 11 công cụ: tra cứu, web, Wikipedia, thời gian thực</span></div> | |
| <div class="welcome-feature"><span class="wf-icon">🎭</span><span class="wf-text">3D avatar lip-sync — biểu tượng, cử chỉ, giọng nói Qwen3-TTS</span></div> | |
| <div class="welcome-feature"><span class="wf-icon">⚙️</span><span class="wf-text">Tuỳ chỉnh giọng, avatar, hướng dẫn riêng, phụ đề</span></div> | |
| </div> | |
| <p class="welcome-hint">Bạn luôn có thể chuyển đổi chế độ sau</p> | |
| </div> | |
| </div> | |
| <!-- ✨ Avatar loading overlay with 1–100% progress bar --> | |
| <div id="avatar-loading-overlay" class="avatar-loading-overlay" hidden> | |
| <div class="avatar-loading-box"> | |
| <div class="avatar-loading-face">🤖</div> | |
| <div class="avatar-loading-msg">V.AI STUDIO sẽ đến với anh chị ngay bây giờ.<br>Mong anh chị chờ em xíu ạ!</div> | |
| <div class="avatar-loading-track"><div class="avatar-loading-fill" id="avatar-loading-bar"></div></div> | |
| <div class="avatar-loading-pct" id="avatar-loading-pct">0%</div> | |
| </div> | |
| </div> | |
| <header id="topbar"> | |
| <div id="identity"> | |
| <h1>V.AI AVATAR + V.AI STUDIO</h1> | |
| <p>Chat or talk to <strong>AI Avatar</strong>. Text → Qwen3-TTS → lip-sync avatar. Control V.AI STUDIO catalog.</p> | |
| </div> | |
| <div style="display:flex;align-items:center;gap:8px;pointer-events:auto"> | |
| <button id="add-product-btn" class="icon-btn" data-add-product-btn aria-label="Thêm sản phẩm" title="Thêm sản phẩm (chỉ sau khi nhập mã V.AISTUDIO)" style="display:none"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line> | |
| </svg> | |
| </button> | |
| <button id="order-btn" class="icon-btn" aria-label="Đơn hàng" title="Đơn hàng"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <path d="M9 11l3 3L22 4"></path><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path> | |
| </svg> | |
| </button> | |
| <button id="customer-btn" class="icon-btn" aria-label="Khách hàng" title="Danh sách khách hàng"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path> | |
| </svg> | |
| </button> | |
| <button id="settings-btn" class="icon-btn" aria-label="Settings" title="Settings"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <circle cx="12" cy="12" r="3"></circle> | |
| <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l-.06-.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path> | |
| </svg> | |
| </button> | |
| <button id="cart-badge-btn" class="icon-btn" aria-label="Giỏ hàng" title="Giỏ hàng" style="position:relative"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle> | |
| <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path> | |
| </svg> | |
| <span id="cartNum" style="display:none;position:absolute;top:-2px;right:-2px;background:#ef4444;color:#fff;font-size:9px;font-weight:800;width:16px;height:16px;border-radius:50%;align-items:center;justify-content:center">0</span> | |
| </button> | |
| </div> | |
| </header> | |
| <div id="subtitles" aria-live="polite"></div> | |
| <div id="text-chat" hidden> | |
| <div id="chat-header"> | |
| <span id="chat-title">Text Chat</span> | |
| <div id="chat-header-actions"> | |
| <select id="chat-avatar-select" title="Switch avatar"></select> | |
| <button id="chatbox-mute-btn" class="icon-btn small" aria-label="Bật/tắt âm thanh (im lặng)" title="Bật/tắt âm thanh (im lặng)"> | |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon> | |
| <path d="M19.07 4.93a10 10 0 0 1 0 14.14" id="chatbox-mute-sound"></path> | |
| <path d="M15.53 8.47a4 4 0 0 1 0 7.07" id="chatbox-mute-muted" style="display:none"></path> | |
| </svg> | |
| </button> | |
| <button id="chat-audio-toggle-btn" class="icon-btn small" aria-label="Toggle avatar audio" title="Toggle avatar audio" style="display:none"> | |
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon> | |
| <path d="M19.07 4.93a10 10 0 0 1 0 14.14" id="audio-icon-sound"></path> | |
| <path d="M15.53 8.47a4 4 0 0 1 0 7.07" id="audio-icon-muted" style="display:none"></path> | |
| </svg> | |
| </button> | |
| <button id="chat-close-btn" class="icon-btn small" aria-label="Close text chat" title="Close"> | |
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="chat-messages"></div> | |
| <div id="chat-input-container"> | |
| <input id="chat-input" type="text" placeholder="Type a message..." autocomplete="off" /> | |
| <button id="chat-mic-btn" class="icon-btn" aria-label="Nhập bằng giọng nói tiếng Việt" title="Nhập bằng giọng nói (Việt Nam)" hidden> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path> | |
| <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line> | |
| </svg> | |
| </button> | |
| <button id="chat-send-btn" class="icon-btn" aria-label="Send message"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> | |
| </svg> | |
| </button> | |
| </div> | |
| <!-- Suggested Questions: rendered as dark pills inside the chat (see vaix-rag.js) --> | |
| <div id="suggested-questions" style="padding:8px 12px;display:none;flex-wrap:wrap;gap:6px"></div> | |
| <div id="chat-resize-handle"></div> | |
| </div> | |
| <footer id="controls"> | |
| <div id="caption">LOADING…</div> | |
| <div id="buttons"> | |
| <button id="main-btn" disabled><span id="main-btn-label">Loading…</span></button> | |
| <button id="mute-btn" class="icon-btn" hidden aria-label="Mute avatar audio" title="Mute avatar audio"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path> | |
| <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line> | |
| </svg> | |
| </button> | |
| <button id="text-mode-btn" class="icon-btn" hidden aria-label="Text chat mode" title="Text chat mode"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| </footer> | |
| <dialog id="settings"> | |
| <form method="dialog"> | |
| <h2>Settings</h2> | |
| <label>Voice<select id="voice"></select></label> | |
| <label>Avatar model<select id="settings-avatar-select"></select></label> | |
| <label class="check-row"><input type="checkbox" id="subtitles-toggle" />Show subtitles while she speaks</label> | |
| <label>Extra instructions<textarea id="instructions" rows="4" placeholder="Anything you want her to know or do (persona stays)."></textarea></label> | |
| <label id="direct-url-row" hidden>Direct server URL<input id="direct-url" type="url" placeholder="ws://localhost:8080/v1/realtime" spellcheck="false" /></label> | |
| <div class="dialog-actions"><button value="close" class="primary">Done</button></div> | |
| </form> | |
| </dialog> | |
| </main> | |
| <!-- V.AISTUDIO floating button --> | |
| <button id="vaistudio-toggle" aria-label="Open V.AI STUDIO catalog" title="Open V.AI STUDIO catalog" style="position:fixed;bottom:24px;right:24px;z-index:180;width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;border:none;cursor:pointer;box-shadow:0 4px 20px rgba(0,63,98,0.4);display:flex;align-items:center;justify-content:center;font-size:1.4rem;transition:all 0.25s ease" onmouseover="this.style.transform='scale(1.1)';this.style.boxShadow='0 6px 28px rgba(0,63,98,0.5)'" onmouseout="this.style.transform='scale(1)';this.style.boxShadow='0 4px 20px rgba(0,63,98,0.4)'"> | |
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path><polyline points="17 8 12 13 7 8"></polyline></svg> | |
| </button> | |
| <!-- V.AISTUDIO Panel --> | |
| <div id="vaistudio-panel" role="dialog" aria-label="V.AI STUDIO Catalog" style="position:fixed;bottom:0;right:0;width:400px;max-width:100vw;height:560px;max-height:calc(100vh - 48px);background:#fff;border-radius:20px 20px 0 0;box-shadow:0 8px 40px rgba(0,0,0,0.18);z-index:160;display:none;flex-direction:column;overflow:hidden"> | |
| <div id="vaistudio-header" style="background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;padding:14px 16px;display:flex;align-items:center;gap:12px;flex-shrink:0"> | |
| <div style="width:38px;height:38px;border-radius:12px;background:rgba(255,255,255,0.15);display:flex;align-items:center;justify-content:center;font-size:1.1rem;flex-shrink:0"><i class="fas fa-store"></i></div> | |
| <div style="flex:1;min-width:0"> | |
| <h4 style="font-size:0.9rem;font-weight:700;margin:0">V.AI STUDIO</h4> | |
| <span id="vaistudio-count" style="font-size:0.68rem;opacity:0.75">Đang tải sản phẩm…</span> | |
| </div> | |
| <button id="vaistudio-add-product-btn" data-add-product-btn aria-label="Thêm sản phẩm" title="Thêm sản phẩm (chỉ sau khi nhập mã V.AISTUDIO)" style="width:40px;height:40px;min-width:40px;min-height:40px;border-radius:50%;background:rgba(255,255,255,0.25);border:none;color:#fff;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:1.05rem;transition:all 0.2s" onmouseover="this.style.background='rgba(255,255,255,0.4)'" onmouseout="this.style.background='rgba(255,255,255,0.25)'"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| <button id="vaistudio-order-btn" aria-label="Đơn hàng" title="Đơn hàng" style="width:40px;height:40px;min-width:40px;min-height:40px;border-radius:50%;background:rgba(255,255,255,0.25);border:none;color:#fff;cursor:pointer;align-items:center;justify-content:center;font-size:1.05rem;transition:all 0.2s" onmouseover="this.style.background='rgba(255,255,255,0.4)'" onmouseout="this.style.background='rgba(255,255,255,0.25)'"> | |
| <i class="fas fa-receipt"></i> | |
| </button> | |
| <button id="vaistudio-baogia-btn" aria-label="Báo giá nội thất" title="Báo giá nội thất (bản vẽ 2D)" style="width:40px;height:40px;min-width:40px;min-height:40px;border-radius:50%;background:rgba(255,255,255,0.25);border:none;color:#fff;cursor:pointer;align-items:center;justify-content:center;font-size:1.05rem;transition:all 0.2s" onmouseover="this.style.background='rgba(255,255,255,0.4)'" onmouseout="this.style.background='rgba(255,255,255,0.25)'"> | |
| <i class="fas fa-ruler-combined"></i> | |
| </button> | |
| <button id="vaistudio-close" aria-label="Close" style="width:40px;height:40px;min-width:40px;min-height:40px;border-radius:50%;background:rgba(255,255,255,0.25);border:none;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:1.1rem;transition:all 0.2s" onmouseover="this.style.background='rgba(255,255,255,0.4)'" onmouseout="this.style.background='rgba(255,255,255,0.25)'"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> | |
| </button> | |
| </div> | |
| <!-- Search + Suggestions dropdown --> | |
| <div style="padding:8px 12px 4px;flex-shrink:0;background:#f8fafc;border-bottom:1px solid #e2e8f0;position:relative"> | |
| <input id="vaix-search" type="text" placeholder="🔍 Tìm sản phẩm..." autocomplete="off" style="width:100%;padding:10px 14px;border:1px solid #e2e8f0;border-radius:10px;font-size:0.85rem;outline:none;background:#fff;box-sizing:border-box" onfocus="this.style.borderColor='#0077b6'" onblur="this.style.borderColor='#e2e8f0'" /> | |
| <div id="vaix-suggestions" style="position:absolute;top:100%;left:12px;right:12px;z-index:50;background:#fff;border:1px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,0.12);display:none;max-height:360px;overflow-y:auto"></div> | |
| </div> | |
| <div id="vaix-category-chips" style="padding:4px 12px 8px;flex-shrink:0;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:none;flex-wrap:wrap;gap:4px;overflow-x:auto"></div> | |
| <div id="vaistudio-body" style="flex:1;overflow:hidden;position:relative;background:#f8fafc"> | |
| <div id="vaistudio-loading" style="position:absolute;inset:0;background:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;z-index:10"> | |
| <div style="width:32px;height:32px;border:3px solid #e2e8f0;border-top-color:#003f62;border-radius:50%;animation:spin 1s linear infinite"></div> | |
| <div style="font-size:0.85rem;color:#64748b">Đang tải sản phẩm V.AI STUDIO…</div> | |
| </div> | |
| <div id="vaistudio-products" style="overflow-y:auto;height:100%;padding:12px;display:none"></div> | |
| <div id="vaistudio-error" style="position:absolute;inset:0;background:#fff;display:none;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:24px;text-align:center"> | |
| <div style="font-size:2rem;margin-bottom:8px">⚠️</div> | |
| <div id="vaistudio-error-text" style="font-size:0.9rem;color:#64748b">Không thể tải sản phẩm</div> | |
| <button id="vaistudio-retry-btn" style="padding:8px 20px;background:#003f62;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:0.85rem">Thử lại</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Product Detail Modal --> | |
| <div id="vaistudio-detail-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:110030;display:none;align-items:center;justify-content:center;padding:20px" onclick="if(event.target===this)this.style.display='none'"> | |
| <div id="vaistudio-detail-modal" style="background:#fff;border-radius:20px;max-width:480px;width:100%;max-height:85vh;overflow-y:auto;padding:0;box-shadow:0 20px 60px rgba(0,0,0,0.25);animation:modalIn 0.2s ease"> | |
| <div style="position:sticky;top:0;z-index:2;display:flex;justify-content:flex-end;padding:12px" onclick="document.getElementById('vaistudio-detail-overlay').style.display='none'"> | |
| <button style="width:36px;height:36px;border-radius:50%;border:none;background:rgba(0,0,0,0.06);cursor:pointer;font-size:1.1rem;display:flex;align-items:center;justify-content:center">✕</button> | |
| </div> | |
| <div id="detail-main-img-wrap" style="position:relative;padding:0 16px 6px;display:flex;justify-content:center;align-items:center;min-height:180px;cursor:zoom-in" onclick="openImageViewerFromDetail()"> | |
| <img id="detail-main-img" alt="Ảnh sản phẩm" style="max-width:100%;max-height:320px;object-fit:contain;border-radius:14px;background:#f8fafc" /> | |
| <div style="position:absolute;bottom:10px;right:24px;background:rgba(0,0,0,0.45);color:#fff;border-radius:50%;width:28px;height:28px;display:none;align-items:center;justify-content:center;font-size:0.75rem" id="detail-zoom-badge">🔍</div> | |
| </div> | |
| <div id="detail-images" style="display:flex;gap:8px;overflow-x:auto;padding:0 16px 12px;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch"></div> | |
| <div style="padding:0 16px 12px;border-bottom:1px solid #e2e8f0"> | |
| <h2 id="detail-title" style="font-size:1.05rem;font-weight:700;color:#1e293b;margin:0 0 6px;line-height:1.4"></h2> | |
| <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"> | |
| <span id="detail-brand" style="font-size:0.75rem;color:#64748b;background:#f1f5f9;padding:3px 10px;border-radius:8px"></span> | |
| <span id="detail-model" style="font-size:0.7rem;color:#94a3b8"></span> | |
| </div> | |
| <div id="detail-price" style="font-size:1.2rem;font-weight:800;color:#c2410c;margin-top:8px"></div> | |
| </div> | |
| <div id="detail-summary" style="padding:12px 16px;font-size:0.82rem;color:#475569;line-height:1.6;border-bottom:1px solid #f1f5f9;display:none"></div> | |
| <div id="detail-features" style="padding:12px 16px;border-bottom:1px solid #f1f5f9;display:none"> | |
| <h3 style="font-size:0.8rem;font-weight:700;color:#003f62;margin:0 0 8px;text-transform:uppercase;letter-spacing:0.04em">Tính năng nổi bật</h3> | |
| <ul id="detail-features-list" style="margin:0;padding:0;list-style:none"></ul> | |
| </div> | |
| <div id="detail-specs" style="padding:12px 16px 20px;display:none"> | |
| <h3 style="font-size:0.8rem;font-weight:700;color:#003f62;margin:0 0 8px;text-transform:uppercase;letter-spacing:0.04em">Thông số kỹ thuật</h3> | |
| <table id="detail-specs-table" style="width:100%;border-collapse:collapse;font-size:0.78rem"><tbody></tbody></table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- CART DRAWER --> | |
| <div class="cart-overlay" id="cartOverlay" onclick="if(window.closeCart)closeCart()"></div> | |
| <div class="cart-drawer" id="cartDrawer"> | |
| <div class="cart-header"><h3><i class="fas fa-shopping-cart"></i> Giỏ Hàng</h3><button class="cart-close" onclick="closeCart()"><i class="fas fa-times"></i></button></div> | |
| <div class="cart-items" id="cartItems"></div> | |
| <div class="cart-footer" id="cartFooter" style="display:none"> | |
| <div class="cart-total"><span>Tổng cộng:</span><span id="cartTotal">0đ</span></div> | |
| <div class="cart-footer-btns"> | |
| <button class="cart-checkout-btn" onclick="saveOrder()" style="background:#059669"><i class="fas fa-save"></i> Lưu</button> | |
| <button class="cart-checkout-btn" onclick="openQuotation()" style="background:var(--p, #003f62)"><i class="fas fa-file-invoice"></i> Báo giá</button> | |
| <button class="cart-checkout-btn" onclick="clearCart()" style="background:#64748b"><i class="fas fa-trash"></i> Xóa</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- QUOTATION MODAL --> | |
| <div class="quote-overlay" id="quoteOverlay" onclick="if(event.target===this)closeQuotation()"> | |
| <div class="quote-modal"> | |
| <button class="quote-close" onclick="closeQuotation()"><i class="fas fa-times"></i></button> | |
| <div class="quote-head"> | |
| <h2><i class="fas fa-file-invoice" style="margin-right:8px"></i>Lập Báo Giá</h2> | |
| <p>Điền thông tin khách hàng và chiết khấu để xuất báo giá</p> | |
| </div> | |
| <div class="quote-body"> | |
| <div class="quote-section"> | |
| <h3><i class="fas fa-user"></i> Thông tin khách hàng</h3> | |
| <div class="quote-grid"> | |
| <div class="quote-field"><label>Họ tên khách hàng</label><input id="qcName" placeholder="Nhập họ tên"></div> | |
| <div class="quote-field"><label>Số điện thoại</label><input id="qcPhone" placeholder="Nhập SĐT"></div> | |
| <div class="quote-field"><label>Email</label><input id="qcEmail" placeholder="Nhập email"></div> | |
| <div class="quote-field"><label>Địa chỉ</label><input id="qcAddr" placeholder="Nhập địa chỉ"></div> | |
| <div class="quote-field"><label>Mã đơn hàng</label><input id="qcCompany" placeholder="Tự động tạo" readonly style="background:#f1f5f9;font-weight:700;color:#003f62"></div> | |
| <div class="quote-field"><label>Ngày báo giá</label><input id="qcDate" type="date"></div> | |
| </div> | |
| <div style="margin-top:12px;padding:12px 14px;background:#f1f5f9;border-radius:10px"> | |
| <div style="display:flex;align-items:center;gap:10px;margin-bottom:8px"> | |
| <i class="fas fa-lock" id="qcLockIcon" style="color:#16a34a;font-size:.85rem"></i> | |
| <input id="qcAccessCode" type="password" placeholder="Nhập mã truy cập..." style="width:180px;padding:7px 10px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:.8rem;font-family:inherit;outline:none;background:#fff" oninput="checkQuoteAccess()"> | |
| <span id="qcAccessStatus" style="font-size:.72rem;font-weight:600;color:#16a34a">🔒</span> | |
| </div> | |
| <div id="aiPromptBox" style="display:none"> | |
| <div style="display:flex;gap:8px"> | |
| <textarea id="qcAiPrompt" placeholder="VD: Malloca ck 35%, giao hàng 200k, lắp đặt 500k, cọc 5tr, ghi chú: giao thứ 7" style="flex:1;padding:8px 10px;border:1.5px solid #0077b6;border-radius:8px;font-size:.82rem;font-family:inherit;outline:none;resize:none;height:48px;background:#fff"></textarea> | |
| <button onclick="applyAiDiscount()" style="padding:8px 16px;background:#0077b6;color:#fff;border:none;border-radius:8px;font-size:.8rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;display:flex;align-items:center;gap:6px" id="aiDiscBtn"><i class="fas fa-robot"></i> Áp dụng</button> | |
| </div> | |
| <div id="aiDiscStatus" style="font-size:.72rem;color:#16a34a;margin-top:4px"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="quote-section"> | |
| <h3><i class="fas fa-list-check"></i> Chi tiết sản phẩm</h3> | |
| <div style="overflow-x:auto"> | |
| <table class="quote-table" id="quoteTable"> | |
| <thead><tr><th>STT</th><th>Hình ảnh</th><th style="text-align:left">Tên sản phẩm</th><th>Mã SP</th><th>Thông tin</th><th>SL</th><th>Đơn giá</th><th>Đơn giá CK</th><th>Thành tiền</th><th>Ghi chú</th></tr></thead> | |
| <tbody id="quoteTableBody"></tbody> | |
| <tfoot><tr class="quote-total-row"><td colspan="8" style="text-align:right;padding-right:12px">TỔNG CỘNG:</td><td class="qt-price" id="quoteTotalCell">0₫</td><td></td></tr></tfoot> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="quote-actions"> | |
| <button class="quote-btn" onclick="shareQuoteImage()" style="background:#25D366"><i class="fas fa-share-alt"></i> Chia sẻ ảnh</button> | |
| <button class="quote-btn" onclick="shareQuoteLink()" style="background:#0068FF"><i class="fas fa-copy"></i> Sao chép báo giá</button> | |
| <button class="quote-btn quote-btn-excel" onclick="exportQuoteExcel()"><i class="fas fa-file-excel"></i> Xuất Excel</button> | |
| <button class="quote-btn quote-btn-pdf" onclick="exportQuotePDF()"><i class="fas fa-file-pdf"></i> Xuất PDF</button> | |
| <button class="quote-btn quote-btn-print" onclick="printQuotation()"><i class="fas fa-print"></i> In báo giá</button> | |
| <button class="quote-btn" onclick="saveOrder()" style="background:#7c3aed"><i class="fas fa-save"></i> Lưu</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ADD PRODUCT MODAL (Thêm SP — 100% port from V.AISTUDIO, gated by V.AISTUDIO code) --> | |
| <div class="add-product-overlay" id="addProductOverlay" onclick="if(event.target===this)closeAddProductModal()"> | |
| <div class="add-product-modal"> | |
| <div class="add-product-head"> | |
| <h3><i class="fas fa-link"></i> Thêm sản phẩm</h3> | |
| <button class="add-product-close" onclick="closeAddProductModal()"><i class="fas fa-times"></i></button> | |
| </div> | |
| <div class="add-product-body"> | |
| <label>Mã truy cập</label> | |
| <input class="add-product-input" id="addProductSecret" type="password" placeholder="Nhập mã V.AI STUDIO" style="display:none"> | |
| <div class="add-product-sep"><span>1 · Nhập URL (tùy chọn)</span></div> | |
| <label>URL sản phẩm</label> | |
| <input class="add-product-input" id="addProductUrl" type="url" placeholder="https://.../san-pham/..."> | |
| <div style="margin-top:8px"> | |
| <div style="display:flex;gap:8px;align-items:center"> | |
| <input class="add-product-input" id="addUrlSearchKw" type="text" placeholder="Tìm trên Google: nhập từ khóa SP (VD: máy hút mùi Eurogold)…" style="flex:1;margin:0"> | |
| <button type="button" id="addUrlSearchBtn" onclick="BaoGiaUrlSuggest.search()" style="padding:9px 14px;border-radius:8px;border:none;background:#0077b6;color:#fff;font-size:.8rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap"><i class="fas fa-magnifying-glass"></i> Tìm</button> | |
| </div> | |
| <div id="addUrlSuggestBox" style="margin-top:8px"></div> | |
| </div> | |
| <div class="add-product-sep" style="margin-top:12px"><span>2 · Hoặc dán văn bản sản phẩm (AI bóc tách)</span></div> | |
| <label>Dán nội dung sản phẩm (tên, mã, giá, mô tả, thông số…) — AI tự điền vào các trường bên dưới</label> | |
| <textarea class="add-product-input" id="addTextInput" rows="4" placeholder="Dán văn bản sản phẩm vào đây. VD: Chậu rửa 1 hố mạ nano inox 304 Mã: BN7546 Giá: 5.803.500đ Chất liệu: Inox 304..."></textarea> | |
| <button class="add-img-scan" id="addTextScan" onclick="runTextExtractAddProduct()"><i class="fas fa-wand-magic-sparkles"></i> Bóc tách văn bản → điền trường</button> | |
| <div class="add-product-sep" style="margin-top:14px"><span>3 · Hoặc dán / tải ảnh sản phẩm (OCR)</span></div> | |
| <div class="add-paste-box" id="addPasteBox"> | |
| <textarea class="add-paste-input" id="addPasteInput" rows="2" placeholder="Dán ảnh vào đây (bấm giữ → Dán), hoặc gõ ghi chú…"></textarea> | |
| <div class="add-paste-bar"> | |
| <span class="add-paste-hint"><i class="fas fa-paste"></i> Trên điện thoại: bấm giữ ô này → Dán · Trên máy tính: Ctrl+V vào ô "link ảnh ngoài" bên dưới → ảnh được thêm & có thể cắt ✂</span> | |
| </div> | |
| </div> | |
| <div class="add-img-zone" id="addImgZone"> | |
| <div class="zic"><i class="fas fa-cloud-arrow-up"></i></div> | |
| <div class="zt">Hoặc nhấn để chọn ảnh / kéo-thả ảnh vào đây</div> | |
| <div class="zs">Hỗ trợ nhiều ảnh — hệ thống tự trích xuất thông tin (OCR) & dùng làm ảnh sản phẩm</div> | |
| <input type="file" id="addImgInput" accept="image/*" multiple style="display:none"> | |
| </div> | |
| <div class="add-imagelink" style="margin-top:10px;display:flex;gap:8px;align-items:stretch"> | |
| <input class="add-product-input" id="addExternalImgUrl" type="url" placeholder="Dán link ảnh ngoài tại đây (hoặc bấm vào ô rồi Ctrl+V từ app/tab trình duyệt khác)…" style="flex:1;margin:0"> | |
| <button type="button" class="add-img-btn" id="addExternalImgBtn" onclick="addImageFromUrl()" style="padding:0 16px;border-radius:8px;border:none;background:#0077b6;color:#fff;font-size:.78rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap"><i class="fas fa-image"></i> Lấy ảnh</button> | |
| </div> | |
| <div class="add-img-preview" id="addImgPreview"></div> | |
| <div class="add-product-sep" style="margin-top:14px"><span>Ghi chú (tùy chọn)</span></div> | |
| <label>Ghi chú — nếu ảnh chứa nhiều sản phẩm, gõ mô tả để hệ thống chỉ lấy đúng sản phẩm đó (VD: "máy hút mùi Eurogold")</label> | |
| <textarea class="add-product-input" id="addOcrNote" rows="2" placeholder="Ghi chú để lọc đúng sản phẩm (VD: bếp từ Grob, máy hút mùi Eurogold…)"></textarea> | |
| <button class="add-img-scan" id="addImgScan" onclick="runOCRAddProduct()"><i class="fas fa-magnifying-glass"></i> Trích xuất thông tin từ ảnh (OCR)</button> | |
| <div id="addOcrFields" style="display:none;margin-top:6px"> | |
| <label>Tên sản phẩm</label> | |
| <input class="add-product-input" id="addOcrName" placeholder="Tên sản phẩm"> | |
| <label>Mã số / Model</label> | |
| <input class="add-product-input" id="addOcrSku" placeholder="Mã sản phẩm"> | |
| <label>Giá (VNĐ)</label> | |
| <input class="add-product-input" id="addOcrPrice" type="number" placeholder="0"> | |
| <label>Giá niêm yết (giá gốc, VNĐ) — tùy chọn</label> | |
| <input class="add-product-input" id="addOcrListPrice" type="number" placeholder="0"> | |
| <label>Giá hiển thị cho sản phẩm</label> | |
| <div class="add-price-mode"> | |
| <label class="add-price-mode-item"><input type="radio" name="addPriceMode" value="both" checked> <span>Cả 2 giá (niêm yết + KM)</span></label> | |
| <label class="add-price-mode-item"><input type="radio" name="addPriceMode" value="sale"> <span>Chỉ giá khuyến mãi</span></label> | |
| <label class="add-price-mode-item"><input type="radio" name="addPriceMode" value="list"> <span>Chỉ giá niêm yết</span></label> | |
| </div> | |
| <div class="add-price-preview" id="addPricePreview" style="font-size:.8rem;color:#334155;margin:4px 0 8px"></div> | |
| <label>Thương hiệu</label> | |
| <input class="add-product-input" id="addOcrBrand" placeholder="Thương hiệu"> | |
| <label>Danh mục</label> | |
| <input class="add-product-input" id="addOcrCat" placeholder="VD: Bếp từ, Máy hút mùi…"> | |
| <label>Mô tả</label> | |
| <textarea class="add-product-input" id="addOcrDesc" rows="3" placeholder="Mô tả sản phẩm (tùy chọn)"></textarea> | |
| <label>Tính năng (mỗi dòng một tính năng)</label> | |
| <textarea class="add-product-input" id="addOcrFeats" rows="3" placeholder="VD: Chức năng tự động tắt bếp khi không có nồi Chỉ báo nhiệt dư còn nóng"></textarea> | |
| <label>Thông số kỹ thuật (mỗi dòng: Tên: Giá trị)</label> | |
| <textarea class="add-product-input" id="addOcrSpecs" rows="4" placeholder="VD: Chất liệu: Inox 304 Xuất xứ: Việt Nam Công suất: 200W"></textarea> | |
| <div class="add-product-hint">Thông tin đã được trích xuất — bạn có thể sửa lại trước khi thêm vào cửa hàng.</div> | |
| <div style="margin-top:8px;text-align:right"> | |
| <button class="add-product-btn secondary" style="padding:7px 14px;font-size:.78rem" onclick="clearAddProductForm()"><i class="fas fa-eraser"></i> Clear — xóa toàn bộ trường</button> | |
| </div> | |
| </div> | |
| <div id="addPreviewWrap" style="display:none;margin-top:16px"> | |
| <div class="add-product-sep"><span>4 · Xem trước sản phẩm</span></div> | |
| <div style="text-align:right;margin:8px 0 4px"> | |
| <button class="add-product-btn secondary" style="padding:7px 12px;font-size:.75rem" onclick="goBackFromPreview()"><i class="fas fa-arrow-left"></i> Quay lại bước dán URL / OCR</button> | |
| </div> | |
| <div class="add-preview" id="addPreviewPanel"> | |
| <div class="add-pv-gallery" id="addPvGallery"></div> | |
| <div class="add-pv-info"> | |
| <div class="add-pv-name" id="addPvName"></div> | |
| <div class="add-pv-sku" id="addPvSku"></div> | |
| <div class="add-pv-price" id="addPvPrice"></div> | |
| <div class="add-pv-brand" id="addPvBrand"></div> | |
| <div class="add-pv-desc" id="addPvDesc"></div> | |
| <div class="add-pv-feats" id="addPvFeats"></div> | |
| <div class="add-pv-specs" id="addPvSpecs"></div> | |
| </div> | |
| </div> | |
| <div class="add-product-hint">Kiểm tra thông tin & ảnh bên trên. Bạn có thể sửa trực tiếp, xóa ảnh, hoặc thêm ảnh (tải lên / dán) trước khi thêm vào cửa hàng.</div> | |
| </div> | |
| <div class="add-product-hint" id="addProductHintText">Hệ thống sẽ tải & phân tích trang sản phẩm (hỗ trợ URL ngoài) hoặc OCR ảnh để lấy tự động: tên, ảnh (gallery), mã/model, giá, brand, mô tả và thông số kỹ thuật. <b>Tính năng chỉ dùng được sau khi nhập mã V.AISTUDIO trên trang chủ.</b></div> | |
| <div class="add-product-actions"> | |
| <button class="add-product-btn secondary" onclick="closeAddProductModal()"><i class="fas fa-times"></i> Đóng</button> | |
| <button class="add-product-btn secondary" id="addBackBtn" style="display:none" onclick="goBackFromPreview()"><i class="fas fa-arrow-left"></i> Quay lại</button> | |
| <button class="add-product-btn secondary" id="addMoreBtn" style="display:none" onclick="resetAddProductForm()"><i class="fas fa-plus"></i> Thêm SP khác</button> | |
| <button class="add-product-btn primary" id="addProductSubmit" onclick="if(this.getAttribute('data-confirm')==='1'){confirmAddProduct();}else{submitAddProductUrl();}"><i class="fas fa-wand-magic-sparkles"></i> Thêm SP</button> | |
| </div> | |
| <div class="add-product-status" id="addProductStatus"></div> | |
| <div style="display:flex;gap:8px;margin-top:12px;flex-wrap:wrap"> | |
| <button class="add-product-btn secondary" id="openBulkImportBtn" style="flex:1;min-width:140px" onclick="openBulkImportModal()"><i class="fas fa-layer-group"></i> Thêm hàng loạt (Excel/Ảnh/PDF)</button> | |
| </div> | |
| <!-- Image crop UI --> | |
| <div class="add-crop-overlay" id="addCropOverlay" style="display:none"> | |
| <div class="add-crop-modal"> | |
| <div class="add-crop-head"> | |
| <h4><i class="fas fa-crop-alt"></i> Cắt ảnh</h4> | |
| <button class="add-product-close" onclick="closeCropModal()"><i class="fas fa-times"></i></button> | |
| </div> | |
| <div class="add-crop-stage" id="addCropStage"></div> | |
| <div class="add-crop-actions"> | |
| <button class="add-product-btn secondary" onclick="closeCropModal()">Hủy</button> | |
| <button class="add-product-btn primary" onclick="applyCrop()"><i class="fas fa-check"></i> Áp dụng</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- BULK IMPORT MODAL (Thêm hàng loạt — Excel/CSV/PDF/Ảnh catalogue) --> | |
| <div class="add-product-overlay" id="bulkImportOverlay" onclick="if(event.target===this)closeBulkImportModal()"> | |
| <div class="add-product-modal" style="max-width:880px"> | |
| <div class="add-product-head"> | |
| <h3><i class="fas fa-layer-group"></i> Thêm sản phẩm hàng loạt</h3> | |
| <button class="add-product-close" onclick="closeBulkImportModal()"><i class="fas fa-times"></i></button> | |
| </div> | |
| <div class="add-product-body"> | |
| <div class="add-product-hint" style="background:#eff6ff;color:#1e40af"> | |
| Tải lên Bảng giá / Catalogue (Excel .xlsx, CSV, PDF, hoặc Ảnh chụp bảng kê nhiều sản phẩm). Hệ thống tự nhận diện <b>cột & dòng</b>, trích xuất từng sản phẩm (tên, mã, giá, thương hiệu, mô tả, tính năng, thông số, ảnh) → bạn kiểm tra / chỉnh sửa bảng preview (bấm <b>✂ Ảnh</b> để tải & cắt ảnh cho từng SP) → thêm <b>tất cả cùng lúc</b>. Ảnh/PDF được đọc chữ trực tiếp trên trình duyệt (OCR), không cần kết nối AI. | |
| </div> | |
| <div class="add-img-zone" id="bulkDropZone" style="margin-top:12px"> | |
| <div class="zic"><i class="fas fa-file-import"></i></div> | |
| <div class="zt">Nhấn chọn hoặc kéo-thả file vào đây</div> | |
| <div class="zs">Hỗ trợ .xlsx · .csv · .pdf · .jpg/.png (ảnh catalogue)</div> | |
| <input type="file" id="bulkFileInput" accept=".xlsx,.xls,.csv,.pdf,image/*" style="display:none"> | |
| </div> | |
| <div style="display:flex;gap:8px;align-items:center;margin-top:10px;flex-wrap:wrap"> | |
| <div style="flex:1;min-width:180px"> | |
| <label style="font-size:.75rem;font-weight:700;color:#0f172a">Danh mục mặc định (nếu file không có cột danh mục)</label> | |
| <input class="add-product-input" id="bulkDefaultCategory" placeholder="VD: Bếp từ, Máy hút mùi…" style="margin-top:4px"> | |
| </div> | |
| <button class="add-product-btn primary" id="bulkParseBtn" style="margin-top:26px;flex:0 0 auto" onclick="runBulkParse()"><i class="fas fa-wand-magic-sparkles"></i> Phân tích file</button> | |
| </div> | |
| <div class="add-product-status" id="bulkParseStatus"></div> | |
| <div id="bulkResultWrap" style="display:none;margin-top:8px"> | |
| <div class="add-product-sep"><span>Kết quả phát hiện — kiểm tra & chỉnh sửa trước khi thêm</span></div> | |
| <div style="font-size:.78rem;color:#475569;margin:4px 0 8px" id="bulkResultSummary"></div> | |
| <div style="overflow:auto;max-height:40vh;border:1px solid #e2e8f0;border-radius:10px"> | |
| <table id="bulkProductsTable" style="width:100%;border-collapse:collapse;font-size:.76rem;min-width:780px"> | |
| <thead id="bulkTableHead" style="position:sticky;top:0;background:#f1f5f9"></thead> | |
| <tbody id="bulkTableBody"></tbody> | |
| </table> | |
| </div> | |
| <div style="display:flex;gap:8px;margin-top:12px;flex-wrap:wrap;justify-content:flex-end"> | |
| <button class="add-product-btn secondary" onclick="bulkTglAll(true)"><i class="fas fa-check-double"></i> Chọn hết</button> | |
| <button class="add-product-btn secondary" onclick="bulkTglAll(false)"><i class="fas fa-square"></i> Bỏ chọn</button> | |
| <button class="add-product-btn primary" id="bulkCommitBtn" onclick="bulkCommit()" disabled><i class="fas fa-layer-group"></i> Thêm N sản phẩm</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bulk row image crop overlay (Thêm hàng loạt: cắt ảnh tải lên làm ảnh SP) --> | |
| <div class="add-product-overlay" id="bulkCropOverlay" style="display:none"> | |
| <div class="add-crop-modal"> | |
| <div class="add-crop-head"> | |
| <h4><i class="fas fa-crop-alt"></i> Cắt ảnh sản phẩm</h4> | |
| <button class="add-product-close" onclick="closeBulkCrop()"><i class="fas fa-times"></i></button> | |
| </div> | |
| <div class="add-crop-stage" id="bulkCropStage"></div> | |
| <div class="add-crop-actions"> | |
| <button class="add-product-btn secondary" onclick="closeBulkCrop()">Hủy</button> | |
| <button class="add-product-btn primary" onclick="applyBulkCrop()"><i class="fas fa-check"></i> Áp dụng</button> | |
| </div> | |
| </div> | |
| </div> | |
| <style> | |
| /* ===== ADD PRODUCT MODAL (port from V.AISTUDIO) ===== */ | |
| .add-product-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:110040;align-items:center;justify-content:center;padding:18px;backdrop-filter:blur(4px)} | |
| .add-product-overlay.open{display:flex} | |
| .add-product-modal{background:#fff;border-radius:20px;max-width:560px;width:100%;box-shadow:0 12px 44px rgba(0,0,0,.22);overflow:hidden;max-height:92vh;display:flex;flex-direction:column} | |
| .add-product-head{background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;padding:20px 24px;display:flex;align-items:center;justify-content:space-between;gap:12px} | |
| .add-product-head h3{font-size:1.05rem;font-weight:800;display:flex;align-items:center;gap:9px;margin:0;color:#fff} | |
| .add-product-close{width:34px;height:34px;border:none;background:rgba(255,255,255,.18);color:#fff;border-radius:10px;cursor:pointer} | |
| .add-product-body{padding:22px 24px;overflow-y:auto;flex:1 1 auto} | |
| .add-product-body label{display:block;font-size:.78rem;font-weight:700;color:#0f172a;margin:12px 0 6px} | |
| .add-product-input{width:100%;padding:11px 13px;border:2px solid #e2e8f0;border-radius:10px;font-size:.86rem;font-family:inherit;outline:none;box-sizing:border-box} | |
| .add-product-input:focus{border-color:#003f62;box-shadow:0 0 0 3px rgba(0,63,98,.08)} | |
| .add-product-hint{font-size:.75rem;color:#64748b;line-height:1.55;margin-top:10px;background:#f8fafc;border-radius:10px;padding:10px 12px} | |
| .add-product-actions{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap} | |
| .add-product-btn{border:none;border-radius:10px;padding:11px 16px;font-weight:800;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;justify-content:center;gap:8px} | |
| .add-product-btn.primary{background:#003f62;color:#fff;flex:1}.add-product-btn.secondary{background:#e2e8f0;color:#0f172a} | |
| .add-product-status{font-size:.8rem;margin-top:14px;line-height:1.6;white-space:pre-line}.add-product-status.ok{color:#047857}.add-product-status.err{color:#b91c1c} | |
| .add-product-sep{display:flex;align-items:center;gap:10px;color:#94a3b8;font-size:.72rem;font-weight:700;margin:16px 0 4px;text-transform:uppercase;letter-spacing:.4px}.add-product-sep::before,.add-product-sep::after{content:"";flex:1;height:1px;background:#e2e8f0} | |
| .add-img-zone{border:2px dashed #cbd5e1;border-radius:12px;padding:16px;text-align:center;cursor:pointer;transition:.2s;background:#f8fafc;position:relative} | |
| .add-img-zone.add-drag{border-color:#003f62;background:#eff6ff} | |
| .add-img-zone .zic{font-size:1.4rem;color:#003f62;margin-bottom:4px} | |
| .add-img-zone .zt{font-size:.8rem;font-weight:700;color:#0f172a} | |
| .add-img-zone .zs{font-size:.68rem;color:#64748b;margin-top:2px} | |
| .add-paste-box{background:#fff;border:2px solid #e2e8f0;border-radius:12px;padding:10px 12px;margin-bottom:10px} | |
| .add-paste-input{width:100%;border:none;outline:none;font-family:inherit;font-size:.82rem;color:#0f172a;resize:none;background:transparent;min-height:40px} | |
| .add-paste-bar{margin-top:6px;padding-top:8px;border-top:1px dashed #e2e8f0;display:flex;align-items:center;gap:6px} | |
| .add-paste-hint{font-size:.66rem;color:#64748b} | |
| .add-preview{background:#fff;border:2px solid #e2e8f0;border-radius:14px;padding:14px;display:flex;gap:14px;flex-wrap:wrap} | |
| .add-pv-gallery{flex:0 0 130px} | |
| .add-pv-gallery img{width:130px;height:130px;object-fit:cover;border-radius:10px;border:1px solid #e2e8f0} | |
| .add-pv-info{flex:1;min-width:200px} | |
| .add-pv-name{font-weight:800;color:#0f172a;font-size:.92rem;line-height:1.35} | |
| .add-pv-sku{font-size:.72rem;color:#64748b;margin-top:3px} | |
| .add-pv-price{font-weight:800;color:#db9815;font-size:1.05rem;margin-top:6px} | |
| .add-pv-brand{font-size:.72rem;font-weight:700;color:#2e7d32;margin-top:2px;letter-spacing:1px} | |
| .add-pv-desc{font-size:.78rem;color:#475569;line-height:1.5;margin-top:8px;max-height:96px;overflow:auto} | |
| .add-pv-specs{font-size:.76rem;color:#334155;margin-top:8px;max-height:140px;overflow:auto;border-top:1px dashed #cbd5e1;padding-top:8px} | |
| .add-pv-specs div{margin:2px 0} | |
| .add-pv-feats{font-size:.76rem;color:#1e3a5f;margin-top:8px;max-height:120px;overflow:auto;border-top:1px dashed #bae6fd;padding-top:8px} | |
| .add-pv-feats div{margin:2px 0} | |
| .add-pv-thumbs{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px} | |
| .add-pv-thumbs img{width:44px;height:44px;object-fit:cover;border-radius:7px;border:1px solid #e2e8f0;cursor:pointer} | |
| .add-price-mode{display:flex;flex-wrap:wrap;gap:6px 14px;margin:4px 0 6px;padding:8px 10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px} | |
| .add-price-mode-item{display:inline-flex;align-items:center;gap:6px;font-size:.78rem;color:#334155;cursor:pointer} | |
| .add-price-mode-item input{accent-color:#4f46e5} | |
| .add-price-preview{background:#eef2ff;border:1px dashed #c7d2fe;border-radius:8px;padding:6px 10px;font-weight:600;color:#3730a3} | |
| .add-img-preview{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px} | |
| .add-img-thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;border:2px solid #e2e8f0} | |
| .add-img-thumb img{width:100%;height:100%;object-fit:cover} | |
| .add-img-thumb .rm{position:absolute;top:-6px;right:-6px;width:18px;height:18px;border-radius:50%;background:#dc2626;color:#fff;border:none;font-size:.6rem;cursor:pointer;line-height:18px} | |
| .add-img-thumb .cropbtn{position:absolute;bottom:-6px;right:-6px;width:18px;height:18px;border-radius:50%;background:#2563eb;color:#fff;border:none;font-size:.55rem;cursor:pointer;line-height:18px} | |
| .add-img-scan{width:100%;margin-top:10px;padding:11px;border-radius:10px;border:none;background:linear-gradient(135deg,#7c3aed,#4f46e5);color:#fff;font-weight:800;cursor:pointer;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:8px;font-size:.84rem} | |
| .add-img-scan:disabled{opacity:.6;cursor:not-allowed} | |
| /* Image crop UI */ | |
| .add-crop-overlay{position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:110050;display:flex;align-items:center;justify-content:center;padding:20px} | |
| .add-crop-modal{background:#fff;border-radius:16px;max-width:560px;width:100%;padding:16px;max-height:92vh;display:flex;flex-direction:column} | |
| .add-crop-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px} | |
| .add-crop-head h4{margin:0;color:#0f172a} | |
| .add-crop-stage{flex:1;overflow:hidden;position:relative;background:#0f172a;border-radius:12px;min-height:320px;touch-action:none;display:flex;align-items:center;justify-content:center} | |
| .add-crop-wrap{position:relative;display:block;flex:0 0 auto;margin:auto;line-height:0} | |
| .add-crop-wrap img{display:block;width:100%;height:100%;user-select:none;-webkit-user-drag:none;pointer-events:none} | |
| .add-crop-actions{display:flex;gap:10px;margin-top:12px} | |
| .add-crop-actions .add-product-btn{flex:1} | |
| .add-crop-box{position:absolute;border:2px solid #4f46e5;background:rgba(79,70,229,.15);cursor:move;box-shadow:0 0 0 9999px rgba(15,23,42,.5)} | |
| .add-crop-handle{position:absolute;width:14px;height:14px;background:#fff;border:2px solid #4f46e5;border-radius:3px;z-index:3} | |
| .add-crop-handle.h-nw{top:-7px;left:-7px;cursor:nwse-resize} | |
| .add-crop-handle.h-n{top:-7px;left:50%;margin-left:-7px;cursor:ns-resize} | |
| .add-crop-handle.h-ne{top:-7px;right:-7px;cursor:nesw-resize} | |
| .add-crop-handle.h-e{top:50%;right:-7px;margin-top:-7px;cursor:ew-resize} | |
| .add-crop-handle.h-se{bottom:-7px;right:-7px;cursor:nwse-resize} | |
| .add-crop-handle.h-s{bottom:-7px;left:50%;margin-left:-7px;cursor:ns-resize} | |
| .add-crop-handle.h-sw{bottom:-7px;left:-7px;cursor:nesw-resize} | |
| .add-crop-handle.h-w{top:50%;left:-7px;margin-top:-7px;cursor:ew-resize} | |
| @media(max-width:768px){.add-product-modal{border-radius:14px}.add-product-head,.add-product-body{padding:16px}.add-product-actions{flex-direction:column}.add-product-btn{width:100%}.add-preview{flex-direction:column}.add-pv-gallery{flex:auto}.add-pv-gallery img{width:100%;height:auto}} | |
| </style> | |
| <style> | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| @keyframes modalIn { from { opacity:0;transform:translateY(20px) scale(0.96) } to { opacity:1;transform:translateY(0) scale(1) } } | |
| /* ── Welcome Modal ── */ | |
| #vaistudio-detail-overlay.show { display: flex ; } | |
| .welcome-modal-overlay { position: fixed; inset: 0; z-index: 110070; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.85); opacity: 0; visibility: hidden; transition: all 0.3s ease; } | |
| .welcome-modal-overlay.show { opacity: 1; visibility: visible; } | |
| .welcome-modal { background: #1a1a1a; border-radius: 20px; padding: 40px; text-align: center; max-width: 400px; width: 90%; border: 1px solid #333; box-shadow: 0 20px 60px rgba(0,0,0,0.5); transform: translateY(20px); transition: transform 0.3s ease; } | |
| .welcome-modal-overlay.show .welcome-modal { transform: translateY(0); } | |
| .welcome-avatar-icon { font-size: 3rem; margin-bottom: 16px; } | |
| .welcome-title { color: #fff; font-size: 1.5rem; margin-bottom: 8px; font-family: Inter, sans-serif; font-weight: 600; } | |
| .welcome-subtitle { color: #888; font-size: 0.9rem; margin-bottom: 24px; font-family: Inter, sans-serif; } | |
| .welcome-actions { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; } | |
| .welcome-btn { padding: 16px 24px; border: none; border-radius: 12px; cursor: pointer; font-family: Inter, sans-serif; font-size: 1rem; font-weight: 600; text-align: left; display: flex; align-items: center; gap: 12px; transition: transform 0.2s ease, box-shadow 0.2s ease; } | |
| .welcome-btn:hover { transform: scale(1.02); } | |
| .welcome-btn:active { transform: scale(0.98); } | |
| .welcome-btn-text { background: linear-gradient(135deg, #003f62, #0077b6); color: #fff; box-shadow: 0 4px 16px rgba(0,63,98,0.3); } | |
| .welcome-btn-text:hover { box-shadow: 0 6px 24px rgba(0,63,98,0.4); } | |
| .welcome-btn-voice { background: linear-gradient(135deg, #6B21A8, #9333EA); color: #fff; box-shadow: 0 4px 16px rgba(107,33,168,0.3); } | |
| .welcome-btn-voice:hover { box-shadow: 0 6px 24px rgba(107,33,168,0.4); } | |
| .welcome-btn-quick { background: linear-gradient(135deg, #047857, #10B981); color: #fff; box-shadow: 0 4px 16px rgba(4,120,87,0.3); } | |
| .welcome-btn-catalogue { background: linear-gradient(135deg, #7c2d12, #c2410c); color: #fff; box-shadow: 0 4px 16px rgba(124,45,18,0.3); } | |
| .welcome-btn-baogia { background: linear-gradient(135deg, #164e63, #0e7490); color: #fff; box-shadow: 0 4px 16px rgba(22,78,99,0.3); } | |
| .welcome-btn-baogia:hover { box-shadow: 0 6px 24px rgba(22,78,99,0.4); } | |
| .welcome-btn-quick:hover { box-shadow: 0 6px 24px rgba(4,120,87,0.4); } | |
| .welcome-btn-icon { font-size: 1.3rem; } | |
| .welcome-btn-text-content { display: flex; flex-direction: column; } | |
| .welcome-btn-main-text { font-size: 1rem; font-weight: 600; } | |
| .welcome-btn-desc { font-size: 0.75rem; font-weight: 400; opacity: 0.8; margin-top: 2px; } | |
| .welcome-hint { color: #555; font-size: 0.75rem; font-family: Inter, sans-serif; } | |
| .welcome-modal { max-height: 90vh; overflow-y: auto; } | |
| .welcome-modal::-webkit-scrollbar { width: 4px; } | |
| .welcome-modal::-webkit-scrollbar-thumb { background: #444; border-radius: 2px; } | |
| .welcome-features { margin: 0 0 16px; text-align: left; display: flex; flex-direction: column; gap: 10px; } | |
| .welcome-feature { display: flex; align-items: flex-start; gap: 10px; color: #ccc; font-size: 0.82rem; line-height: 1.45; font-family: Inter, sans-serif; } | |
| .welcome-feature .wf-icon { flex-shrink: 0; font-size: 1.1rem; width: 22px; text-align: center; } | |
| .welcome-feature .wf-text { color: #aaa; } | |
| /* ✨ Avatar chooser on the welcome modal */ | |
| .welcome-avatars { margin: 0 0 16px; } | |
| .welcome-avatars-title { color: #888; font-size: 0.82rem; font-family: Inter, sans-serif; margin: 0 0 12px; } | |
| .welcome-avatar-row { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; } | |
| .welcome-avatar-card { background: transparent; border: 2px solid #333; border-radius: 16px; padding: 10px 12px 8px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 6px; transition: all 0.2s; font-family: Inter, sans-serif; } | |
| .welcome-avatar-card:hover { border-color: #0077b6; transform: translateY(-2px); } | |
| .welcome-avatar-card.selected { border-color: #22d3ee; box-shadow: 0 0 0 2px rgba(34,211,238,0.35); } | |
| .welcome-avatar-img-wrap { width: 64px; height: 64px; border-radius: 50%; overflow: hidden; border: 2px solid #0077b6; display: flex; align-items: center; justify-content: center; background: #222; } | |
| .welcome-avatar-img-wrap img { width: 100%; height: 100%; object-fit: cover; } | |
| .welcome-avatar-name { color: #e0e0e0; font-size: 0.8rem; font-weight: 600; } | |
| /* ✨ Avatar loading overlay + 1–100% progress bar */ | |
| .avatar-loading-overlay { position: fixed; inset: 0; z-index: 110080; display: flex; align-items: center; justify-content: center; background: rgba(10,10,12,0.92); } | |
| /* Emulate UA [hidden] with author-level !important — the overlay is | |
| position:fixed + display:flex, which would otherwise OVERRIDE the | |
| hidden attribute and keep the loading layer blocking the whole UI | |
| (welcome modal, quick consult, catalogue...) until the avatar loads. */ | |
| #avatar-loading-overlay[hidden] { display: none ; } | |
| .avatar-loading-box { background: #1a1a1a; border: 1px solid #333; border-radius: 20px; padding: 36px 40px; text-align: center; max-width: 420px; width: 88%; box-shadow: 0 20px 60px rgba(0,0,0,0.6); } | |
| .avatar-loading-face { font-size: 2.6rem; margin-bottom: 14px; } | |
| .avatar-loading-msg { color: #ddd; font-size: 1rem; line-height: 1.5; font-family: Inter, sans-serif; margin-bottom: 20px; } | |
| .avatar-loading-track { height: 12px; background: #2a2a2a; border-radius: 999px; overflow: hidden; border: 1px solid #3a3a3a; } | |
| .avatar-loading-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #003f62, #22d3ee); border-radius: 999px; transition: width 0.3s ease; } | |
| .avatar-loading-pct { color: #22d3ee; font-size: 1rem; font-weight: 700; margin-top: 10px; font-family: Inter, sans-serif; } | |
| #vaistudio-panel.open { display: flex ; } | |
| #vaistudio-loading.hidden { display: none ; } | |
| @media (max-width:768px) { #vaistudio-toggle { bottom:16px;right:16px;width:50px;height:50px } #vaistudio-panel { width:100%;max-width:100%;height:100%;max-height:100vh;border-radius:0 } } | |
| .product-card { display:flex;gap:12px;padding:12px;background:#fff;border-radius:12px;margin-bottom:8px;border:1px solid #e2e8f0;cursor:pointer;transition:all 0.2s } | |
| .product-card:hover { border-color:#0077b6;box-shadow:0 2px 8px rgba(0,63,98,0.12) } | |
| .product-card-img { width:80px;height:80px;border-radius:8px;object-fit:cover;background:#f1f5f9;flex-shrink:0 } | |
| .product-card-info { flex:1;min-width:0 } | |
| .product-card-title { font-size:0.85rem;font-weight:600;color:#1e293b;margin:0 0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap } | |
| .product-card-brand { font-size:0.75rem;color:#64748b;margin:0 0 4px } | |
| .product-card-price { font-size:0.85rem;font-weight:700;color:#003f62;margin:0 } | |
| .product-card-cat { font-size:0.7rem;color:#94a3b8;background:#f1f5f9;padding:2px 8px;border-radius:10px;display:inline-block;margin-top:4px } | |
| .product-card .product-card-btn { align-self:center;flex-shrink:0;display:inline-flex;align-items:center;gap:5px;padding:8px 12px;border:none;border-radius:8px;background:#25d366;color:#fff;cursor:pointer;font-size:0.72rem;font-weight:600;font-family:inherit;transition:all 0.15s } | |
| .product-card .product-card-btn:hover { background:#1fb959 } | |
| .product-card .product-card-btn.added { background:#166534 } | |
| .vaix-suggestion-item { display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;border-bottom:1px solid #f1f5f9;transition:background 0.15s } | |
| .vaix-suggestion-item:last-child { border-bottom:none } | |
| .vaix-suggestion-item:hover { background:#f0f9ff } | |
| #vaix-suggestions::-webkit-scrollbar { width:5px } | |
| #vaix-suggestions::-webkit-scrollbar-thumb { background:#e2e8f0;border-radius:3px } | |
| .vaix-category-chip { display:inline-flex;align-items:center;padding:4px 10px;background:#eef2ff;color:#003f62;border:1px solid #d0dfed;border-radius:14px;font-size:0.7rem;cursor:pointer;white-space:nowrap;transition:all 0.2s;font-family:inherit } | |
| .vaix-category-chip:hover { background:#dbeafe;border-color:#0077b6;transform:translateY(-1px) } | |
| .chat-product-cards { display:flex;flex-direction:column;gap:8px;margin:6px 0;max-width:100% } | |
| .chat-product-card { background:#1e293b;border:1px solid #334155;border-radius:12px;overflow:hidden;transition:all 0.2s } | |
| .chat-product-card:hover { border-color:#0077b6;box-shadow:0 2px 8px rgba(0,119,182,0.15) } | |
| .chat-product-card-inner { display:flex;gap:10px;padding:10px;cursor:pointer } | |
| .chat-product-card-img-wrap { width:64px;height:64px;border-radius:8px;overflow:hidden;flex-shrink:0;background:#1a1f2e } | |
| .chat-product-card-img { width:100%;height:100%;object-fit:cover } | |
| .chat-product-card-img-placeholder { width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:1.3rem } | |
| .chat-product-card-body { flex:1;min-width:0;display:flex;flex-direction:column;gap:2px } | |
| .chat-product-card-name { font-size:0.8rem;font-weight:600;color:#e2e8f0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3 } | |
| .chat-product-card-meta { display:flex;align-items:center;gap:6px;flex-wrap:wrap } | |
| .chat-product-card-code { font-size:0.62rem;color:#7dd3fc;background:#0c4a6e33;border:1px solid #0c4a6e66;padding:1px 6px;border-radius:4px;display:inline-block;width:fit-content;font-family:monospace;letter-spacing:0.3px } | |
| .chat-product-card-brand { font-size:0.68rem;color:#94a3b8 } | |
| .chat-product-card-stars { font-size:0.6rem;color:#f59e0b;letter-spacing:1px } | |
| .chat-product-card-price { font-size:0.82rem;font-weight:700;color:#22d3ee;margin-top:1px } | |
| .chat-product-card-cat { font-size:0.6rem;color:#64748b;background:#2d3748;padding:1px 6px;border-radius:4px;display:inline-block;width:fit-content;margin-top:1px } | |
| .chat-product-card-actions { display:flex;gap:0;border-top:1px solid #334155 } | |
| .chat-product-card-btn { flex:1;padding:7px 8px;background:transparent;border:none;color:#94a3b8;cursor:pointer;font-size:0.7rem;font-family:inherit;transition:all 0.15s;display:flex;align-items:center;justify-content:center;gap:4px } | |
| .chat-product-card-btn:hover { background:#2d3748;color:#e2e8f0 } | |
| .chat-product-card-btn:first-child { border-right:1px solid #334155 } | |
| .chat-product-card-btn.detail-btn:hover { color:#22d3ee } | |
| .chat-product-card-btn.share-btn:hover { color:#f59e0b } | |
| .chat-product-card-btn.askai-btn { color:#a78bfa } | |
| .chat-product-card-btn.askai-btn:hover { background:#4c1d95; color:#e9d5ff } | |
| .chat-product-card-btn.cart-btn { color:#4ade80 } | |
| .chat-product-card-btn.cart-btn:hover { background:#14532d; color:#4ade80 } | |
| .chat-product-card-btn.cart-btn.added { background:#166534; color:#fff } | |
| .chat-message.assistant .chat-product-card { background:#1e293b;border:1px solid #334155;margin:4px 0 } | |
| /* Combo suggestion section */ | |
| .chat-combo-section { margin:8px 0;padding:10px;background:linear-gradient(135deg,rgba(34,211,238,0.08),rgba(147,51,234,0.08));border:1px solid #334155;border-radius:14px;max-width:100% } | |
| .chat-combo-title { font-size:0.82rem;font-weight:700;color:#e2e8f0;display:flex;align-items:center;gap:6px;margin-bottom:2px } | |
| .chat-combo-spark { color:#f59e0b } | |
| .chat-combo-sub { font-size:0.68rem;color:#94a3b8;margin-bottom:8px } | |
| .chat-combo-cards { display:flex;flex-direction:column;gap:7px;max-height:none;overflow:visible } | |
| .chat-combo-cards .chat-product-card { margin:0 } | |
| /* Make the "Thêm giỏ" button on combo cards clearly visible */ | |
| .chat-combo-cards .chat-product-card-btn.cart-btn { | |
| background: rgba(37,211,102,0.16); | |
| color:#4ade80; font-weight:700; flex:1.3; | |
| } | |
| .chat-combo-cards .chat-product-card-btn.cart-btn:hover { background:#14532d; color:#4ade80 } | |
| .chat-combo-cards .chat-product-card-btn.cart-btn.added { background:#166534; color:#fff } | |
| .chat-combo-random { margin-top:8px;padding:6px 12px;background:rgba(34,211,238,0.15);color:#22d3ee;border:1px solid rgba(34,211,238,0.35);border-radius:10px;cursor:pointer;font-size:0.72rem;font-family:inherit;font-weight:600;transition:all 0.15s } | |
| .chat-combo-random:hover { background:rgba(34,211,238,0.28) } | |
| /* Chatbox mute button — highlighted red when silent mode is ON */ | |
| #chatbox-mute-btn { color:#94a3b8 } | |
| #chatbox-mute-btn.mute-on { color:#f87171; background:rgba(248,113,113,0.15); border:1px solid rgba(248,113,113,0.4); border-radius:6px } | |
| #detail-share-btn { display:inline-flex } | |
| /* ✨ FIX: Suggested Question Pills CSS */ | |
| .suggested-pill { padding:6px 14px; border:1px solid #0077b6; border-radius:16px; background:#f0f9ff; color:#003f62; font-size:0.75rem; cursor:pointer; white-space:normal; max-width:100%; font-family:Inter,sans-serif; font-weight:500; transition:all 0.2s; } | |
| .suggested-pill:hover { background:#dbeafe; border-color:#005580; } | |
| </style> | |
| <style> | |
| /* ── Cart Drawer (V.AISTUDIO layout) ── */ | |
| .cart-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:110010;backdrop-filter:blur(3px)} | |
| .cart-drawer{position:fixed;top:0;right:0;width:400px;max-width:90vw;height:100vh;height:100dvh;background:#fff;color:#1e293b;z-index:110011;display:flex;flex-direction:column;box-shadow:-4px 0 30px rgba(0,0,0,.15);transform:translateX(100%);transition:transform .35s ease;visibility:hidden} | |
| .cart-drawer.open{transform:translateX(0);visibility:visible} | |
| .cart-header{padding:20px;border-bottom:2px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between} | |
| .cart-header h3{font-size:1rem;font-weight:700;display:flex;align-items:center;gap:8px;color:#1e293b;margin:0} | |
| .cart-close{width:34px;height:34px;border:none;background:#e2e8f0;border-radius:9px;cursor:pointer;font-size:.9rem;color:#475569} | |
| .cart-items{flex:1;overflow-y:auto;padding:16px;min-height:0} | |
| .cart-item{display:flex;gap:12px;padding:12px;background:#f8fafc;border-radius:12px;margin-bottom:10px;align-items:center} | |
| .cart-item img{width:56px;height:56px;border-radius:8px;object-fit:cover;background:#fff;flex-shrink:0;border:1px solid #e2e8f0} | |
| .cart-item-info{flex:1;min-width:0} | |
| .cart-item-name{font-size:.8rem;font-weight:600;color:#1e293b;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} | |
| .cart-item-price{font-size:.88rem;font-weight:800;color:#003f62;margin-top:4px} | |
| .cart-item-qty{display:flex;align-items:center;gap:8px;margin-top:6px} | |
| .qty-btn{width:26px;height:26px;border:1px solid #e2e8f0;border-radius:6px;background:#fff;cursor:pointer;font-size:.8rem;display:flex;align-items:center;justify-content:center;color:#334155} | |
| .qty-val{font-size:.82rem;font-weight:700;min-width:20px;text-align:center;color:#1e293b} | |
| .cart-item-del{background:none;border:none;color:#dc3545;cursor:pointer;font-size:.85rem;padding:4px} | |
| .cart-footer{padding:10px 14px;border-top:2px solid #e2e8f0;background:#fff;flex-shrink:0} | |
| .cart-total{display:flex;justify-content:space-between;font-size:.92rem;font-weight:800;color:#1e293b;margin-bottom:6px} | |
| .cart-footer-btns{display:flex;gap:6px;margin-top:6px} | |
| .cart-checkout-btn{flex:1;padding:10px 6px;font-size:.78rem;border-radius:10px;border:none;color:#fff;font-weight:700;cursor:pointer;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:4px} | |
| .cart-empty{text-align:center;padding:40px;color:#64748b} | |
| .cart-empty i{font-size:2rem;color:#cbd5e1;margin-bottom:8px} | |
| .cart-empty h3{color:#1e293b;margin:0 0 4px} | |
| #cart-badge-btn svg{display:block} | |
| /* ── Quotation Modal (V.AISTUDIO layout) ── */ | |
| .quote-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:110020;align-items:center;justify-content:center;padding:16px;backdrop-filter:blur(4px)} | |
| .quote-overlay.open{display:flex} | |
| .quote-modal{background:#fff;border-radius:20px;max-width:960px;width:100%;max-height:92vh;overflow-y:auto;padding:0;position:relative;color:#1e293b} | |
| .quote-close{position:absolute;top:16px;right:16px;width:34px;height:34px;border:none;background:rgba(255,255,255,.2);border-radius:9px;cursor:pointer;color:#fff;font-size:.9rem;display:flex;align-items:center;justify-content:center} | |
| .quote-head{background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;padding:24px 28px;border-radius:20px 20px 0 0} | |
| .quote-head h2{font-size:1.2rem;font-weight:800;margin:0 0 4px;color:#fff} | |
| .quote-head p{font-size:.8rem;opacity:.75;margin:0} | |
| .quote-body{padding:24px 28px} | |
| .quote-section{margin-bottom:20px} | |
| .quote-section h3{font-size:.88rem;font-weight:700;color:#1e293b;margin:0 0 12px;display:flex;align-items:center;gap:8px} | |
| .quote-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px} | |
| .quote-field label{font-size:.72rem;font-weight:600;color:#475569;display:block;margin-bottom:4px} | |
| .quote-field input{width:100%;padding:9px 12px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:.82rem;font-family:inherit;outline:none;color:#1e293b;background:#fff} | |
| .quote-table{width:100%;border-collapse:collapse;font-size:.78rem;margin-top:8px} | |
| .quote-table th{background:#003f62;color:#fff;padding:8px 6px;font-weight:600;text-align:center;white-space:nowrap} | |
| .quote-table td{padding:8px 6px;border-bottom:1px solid #e2e8f0;vertical-align:middle;color:#334155} | |
| .quote-table tbody tr:nth-child(odd){background:#f8fafc} | |
| .quote-total-row td{font-weight:800;color:#003f62;padding:12px 6px} | |
| .qt-price{text-align:right;white-space:nowrap} | |
| .quote-table img{width:40px;height:40px;object-fit:cover;border-radius:6px} | |
| .quote-actions{display:flex;gap:10px;margin-top:20px;flex-wrap:wrap;justify-content:center} | |
| .quote-btn{padding:12px 24px;border:none;border-radius:10px;font-weight:700;font-size:.85rem;cursor:pointer;font-family:inherit;display:flex;align-items:center;gap:8px;color:#fff} | |
| .quote-btn-excel{background:#217346} | |
| .quote-btn-pdf{background:#dc3545} | |
| .quote-btn-print{background:#003f62} | |
| .quote-btn-print:hover{background:#00293f} | |
| .quote-field input:focus{border-color:#0077b6} | |
| .quote-table .qt-img{width:72px;height:72px;object-fit:contain;border-radius:6px;background:#f1f5f9;border:1px solid #e2e8f0} | |
| .quote-table .qt-disc{width:80px;padding:5px 6px;border:1.5px solid #e2e8f0;border-radius:6px;font-size:.78rem;text-align:right;font-family:inherit;outline:none} | |
| .quote-table .qt-disc:focus{border-color:#0077b6} | |
| .quote-table .qt-note{width:100%;min-width:90px;padding:5px 6px;border:1.5px solid #e2e8f0;border-radius:6px;font-size:.78rem;font-family:inherit;outline:none} | |
| .qt-extra-row td{font-size:.82rem} | |
| @media(max-width:768px){.quote-grid{grid-template-columns:1fr}.quote-modal{border-radius:12px}.quote-table{font-size:.7rem}} | |
| /* ── Ask AI button on news cards ── */ | |
| .source-card-askai{display:flex;align-items:center;gap:6px;margin:8px 4px 2px;padding:7px 12px;background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;border:none;border-radius:10px;font-size:.72rem;font-weight:600;cursor:pointer;font-family:inherit;transition:all .2s;width:fit-content} | |
| .source-card-askai:hover{opacity:.9;transform:translateY(-1px)} | |
| /* ── V.AI STUDIO order buttons ── */ | |
| #order-btn, #vaistudio-order-btn { display: flex !important; } | |
| /* ── Thêm SP button: hidden until the homepage V.AISTUDIO code unlocks it ── */ | |
| #add-product-btn, #vaistudio-add-product-btn { display: none; } | |
| body.vas-unlocked #add-product-btn { display: flex !important; } | |
| #vaistudio-panel.open #vaistudio-add-product-btn[style*="display: none"] { display: none !important; } | |
| body.vas-unlocked #vaistudio-add-product-btn { display: flex !important; } | |
| /* ── Customer list (Danh sách khách hàng) row styling ── | |
| Rows use semantic classes injected by customers.js. Style them with | |
| full contrast so they read clearly against the white modal background. */ | |
| #cus-list .cus-row{display:flex;align-items:center;gap:12px;padding:12px 10px;border:1px solid #e2e8f0;border-radius:12px;margin-bottom:8px;background:#f8fafc;transition:background .15s} | |
| #cus-list .cus-row:hover{background:#eef6ff;border-color:#b8d4e8} | |
| #cus-list .cus-row-main{flex:1;min-width:0} | |
| #cus-list .cus-row-name{font-size:0.86rem;font-weight:700;color:#1e293b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} | |
| #cus-list .cus-row-meta{font-size:0.7rem;color:#64748b;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} | |
| #cus-list .cus-row-actions{display:flex;gap:6px;flex-shrink:0} | |
| #cus-list .cus-select-btn{padding:7px 14px;border:none;border-radius:8px;background:#0077b6;color:#fff;font-size:0.74rem;font-weight:700;cursor:pointer} | |
| #cus-list .cus-select-btn:hover{background:#005f94} | |
| #cus-list .cus-edit-btn{padding:7px 12px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;color:#334155;font-size:0.78rem;cursor:pointer} | |
| #cus-list .cus-edit-btn:hover{border-color:#0077b6;color:#0077b6} | |
| /* Multi-value rows: give each input row a clear background */ | |
| #cus-detail-modal .cus-mv-row{display:flex;gap:8px;margin-bottom:6px;align-items:center} | |
| #cus-detail-modal .cus-mv-row input{background:#fff !important} | |
| </style> | |
| <!-- v55: ExcelJS/jspdf/html2canvas are now lazy-loaded inside cart-quote.js | |
| on first export click — removing 3 CDN round trips + ~1.5MB parse from | |
| every page load (incl. catalogue deep-link). --> | |
| <!-- Modal-top manager: any opened modal sits on top. Loads first so all | |
| app modules can call window.VAI_modalTop() when they open a modal. --> | |
| <script defer src="/src/modal-top.js?gc=4"></script> | |
| <!-- App modules: defer keeps execution order (parse order) but doesn't block | |
| first paint — the welcome/avatar-picker modal renders immediately. --> | |
| <script defer src="/src/cart-quote.js?gc=50"></script> <!-- Cart + Quote module (Giỏ hàng / Báo giá) --> | |
| <script defer src="/src/panel-fx.js?gc=2"></script> <!-- Drag + resize cho panel danh sách SP (V.AI STUDIO / FLASHSALE / BIG SALE / COMBO) --> | |
| <script defer src="/src/bao-gia.js?gc=6"></script> <!-- Báo giá nội thất từ bản vẽ kỹ thuật 2D: OCR đọc bản vẽ → m² / mét dài / cái theo hạng mục --> | |
| <script defer src="/src/version.js"></script> | |
| <script defer src="/src/catalogue-ai.js?gc=37"></script> <!-- Catalogue AI module (NL search over OCR'd Malloca 2026 catalogue) --> | |
| <script defer src="/src/catai-intro.js?v=3"></script> <!-- What's-new auto-intro banner (giới thiệu tính năng mới tự động) --> | |
| <script defer src="/src/customers.js?gc=21"></script> <!-- Customer list (Danh sách khách hàng) --> | |
| <script defer src="/src/vaix-rag.js?gc=77"></script> <!-- V.AI STUDIO RAG Module - loads FASTER via light index; lazy galleries; spec-aware search --> | |
| <script defer src="/src/greeting-news.js?gc=44"></script> <!-- Greeting HOT news cards + source links --> | |
| <script defer src="/src/order-sync.js?gc=19"></script> <!-- Order sync with V.AISTUDIO backend --> | |
| <script type="module" src="/src/app.js?gc=65"></script> | |
| <script defer src="/src/greeting-source-cards.js?gc=15"></script> <!-- FIX: Nguồn tin cards under greeting --> | |
| <script defer src="/src/avatar-picker.js?gc=15"></script> <!-- ✨ Circular avatar picker (Mr V / Lisamy) + loading progress bar --> | |
| <!-- BIG SALE Floating Promotion Module --> | |
| <script defer src="/big_sale.js?v=15"></script> | |
| <!-- V.AI STUDIO Promo CMS (admin editor + BIGSALE/COMBO manager) --> | |
| <script defer src="/src/vaix-promo-cms.js?v=8"></script> | |
| <script defer src="/src/vaix-combo.js?v=7"></script> | |
| <script defer src="/src/vaix-flashsale.js?v=4"></script> | |
| <script> | |
| // Wire the "Đơn hàng" buttons to the order modal | |
| (function () { | |
| function wire() { | |
| var ob = document.getElementById("order-btn"); | |
| if (ob) { ob.onclick = function (e) { e.preventDefault(); e.stopPropagation(); if (window.openOrderModal) window.openOrderModal(); }; } | |
| var cb = document.getElementById("customer-btn"); | |
| if (cb) { cb.onclick = function (e) { e.preventDefault(); e.stopPropagation(); if (window.openCustomerModal) window.openCustomerModal(); }; } | |
| var sb = document.getElementById("settings-btn"); | |
| if (sb) { sb.onclick = function (e) { e.preventDefault(); e.stopPropagation(); if (window.openSettings) window.openSettings(); }; } | |
| var vob = document.getElementById("vaistudio-order-btn"); | |
| if (vob) { vob.onclick = function (e) { e.preventDefault(); e.stopPropagation(); if (window.openOrderModal) window.openOrderModal(); }; } | |
| } | |
| if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", wire); } else { wire(); } | |
| })(); | |
| </script> | |
| <!-- ── Thêm SP (Add Product) module: 100% port from V.AISTUDIO, gated by the | |
| homepage V.AISTUDIO access code (window.isVaAdmin()). ── --> | |
| <script defer src="/src/vaix-add-product.js?v=21"></script> | |
| <script defer src="/src/vaix-bulk-import.js?v=10"></script> | |
| </body> | |
| </html> |