(function() { // ---------------------------------------------------- // 1. CONFIGURATION: Security & Metadata // ---------------------------------------------------- const scriptTag = document.currentScript; // Auth & Config const API_KEY = scriptTag.getAttribute("data-api-key"); const API_URL = scriptTag.getAttribute("data-api-url"); const THEME_COLOR = scriptTag.getAttribute("data-theme-color") || "#0084FF"; if (!API_KEY || !API_URL) { console.error("OmniAgent Security Error: data-api-key or data-api-url is missing!"); return; } const CHAT_SESSION_ID = "omni_session_" + Math.random().toString(36).slice(2, 11); // ---------------------------------------------------- // 2. STYLES: UI & Responsive Design (Tabs + Camera) // ---------------------------------------------------- const style = document.createElement('style'); style.innerHTML = ` #omni-widget-container { position: fixed; bottom: 20px; right: 20px; z-index: 999999; font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } #omni-chat-btn { background: ${THEME_COLOR}; color: white; border: none; padding: 15px; border-radius: 50%; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.3); width: 60px; height: 60px; font-size: 28px; display: flex; align-items: center; justify-content: center; transition: all 0.3s; } #omni-chat-btn:hover { transform: scale(1.1); } #omni-window { display: none; width: 380px; height: 600px; background: white; border-radius: 16px; box-shadow: 0 12px 40px rgba(0,0,0,0.25); flex-direction: column; overflow: hidden; margin-bottom: 20px; animation: omniSlideUp 0.3s ease; border: 1px solid #e0e0e0; } @keyframes omniSlideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } /* --- Header & Tabs --- */ #omni-header { background: ${THEME_COLOR}; padding: 15px; color: white; } .omni-title { font-weight: bold; font-size: 16px; display: flex; align-items: center; gap: 8px; } .omni-close { cursor: pointer; float: right; font-size: 24px; line-height: 16px; } .omni-tabs { display: flex; background: #f1f1f1; border-bottom: 1px solid #ddd; } .omni-tab { flex: 1; padding: 12px; text-align: center; cursor: pointer; font-size: 14px; font-weight: 600; color: #555; transition: 0.2s; } .omni-tab.active { background: white; color: ${THEME_COLOR}; border-bottom: 3px solid ${THEME_COLOR}; } /* --- Content Areas --- */ .omni-view { display: none; flex: 1; flex-direction: column; overflow: hidden; } .omni-view.active { display: flex; } /* --- Chat View --- */ #omni-messages { flex: 1; padding: 15px; overflow-y: auto; background: #f9f9f9; display: flex; flex-direction: column; gap: 10px; } .omni-msg { padding: 10px 14px; border-radius: 12px; max-width: 80%; font-size: 14px; line-height: 1.4; } .omni-msg.user { background: ${THEME_COLOR}; color: white; align-self: flex-end; border-bottom-right-radius: 2px; } .omni-msg.bot { background: #e9eff5; color: #333; align-self: flex-start; border-bottom-left-radius: 2px; } #omni-input-area { display: flex; border-top: 1px solid #eee; padding: 10px; background: white; } #omni-input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 20px; outline: none; } #omni-send { background: none; border: none; color: ${THEME_COLOR}; font-size: 20px; cursor: pointer; padding-left: 10px; } /* --- Visual Search View --- */ #omni-visual-area { flex: 1; padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; overflow-y: auto; text-align: center; } #omni-upload-box { border: 2px dashed #ccc; border-radius: 12px; padding: 30px; margin-bottom: 20px; width: 80%; cursor: pointer; transition: 0.2s; background: #fafafa; } #omni-upload-box:hover { border-color: ${THEME_COLOR}; background: #f0f7ff; } #omni-file-input { display: none; } .omni-result-card { display: flex; align-items: center; gap: 10px; background: white; border: 1px solid #eee; padding: 10px; border-radius: 8px; width: 100%; margin-bottom: 10px; text-align: left; transition: 0.2s; text-decoration: none; color: inherit; } .omni-result-card:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0,0,0,0.1); } .omni-result-img { width: 60px; height: 60px; object-fit: cover; border-radius: 6px; } .omni-loader { border: 3px solid #f3f3f3; border-top: 3px solid ${THEME_COLOR}; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; margin: 20px auto; display: none; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); // ---------------------------------------------------- // 3. HTML STRUCTURE // ---------------------------------------------------- const container = document.createElement('div'); container.id = 'omni-widget-container'; container.innerHTML = `
×
🤖 AI Assistant
💬 Chat
📷 Visual Search
📤

Click to Upload Image

Find similar products

`; document.body.appendChild(container); // ---------------------------------------------------- // 4. UI LOGIC (Toggle & Tabs) // ---------------------------------------------------- window.toggleOmni = function() { const win = document.getElementById('omni-window'); const isVisible = win.style.display === 'flex'; win.style.display = isVisible ? 'none' : 'flex'; }; window.switchTab = function(tab) { document.querySelectorAll('.omni-tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.omni-view').forEach(v => v.classList.remove('active')); if (tab === 'chat') { document.querySelector('.omni-tabs .omni-tab:nth-child(1)').classList.add('active'); document.getElementById('omni-chat-view').classList.add('active'); } else { document.querySelector('.omni-tabs .omni-tab:nth-child(2)').classList.add('active'); document.getElementById('omni-visual-view').classList.add('active'); } }; // ---------------------------------------------------- // 5. CHAT ENGINE // ---------------------------------------------------- const chatInput = document.getElementById('omni-input'); const sendBtn = document.getElementById('omni-send'); const msgContainer = document.getElementById('omni-messages'); async function sendMessage() { const text = chatInput.value.trim(); if (!text) return; addMsg(text, 'user'); chatInput.value = ''; const loadingDiv = document.createElement('div'); loadingDiv.className = 'omni-msg bot'; loadingDiv.innerHTML = '...'; msgContainer.appendChild(loadingDiv); msgContainer.scrollTop = msgContainer.scrollHeight; try { const response = await fetch(`${API_URL}/api/v1/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text, session_id: CHAT_SESSION_ID, api_key: API_KEY // Auth }) }); msgContainer.removeChild(loadingDiv); const data = await response.json(); if (response.ok) { addMsg(data.response, 'bot'); } else { addMsg("⚠️ Error: " + (data.detail || "Server error"), 'bot'); } } catch (e) { if(loadingDiv.parentNode) msgContainer.removeChild(loadingDiv); addMsg("📡 Connection Error", 'bot'); } } function addMsg(text, sender) { const div = document.createElement('div'); div.className = `omni-msg ${sender}`; div.innerText = text; // Secure text insertion msgContainer.appendChild(div); msgContainer.scrollTop = msgContainer.scrollHeight; } sendBtn.onclick = sendMessage; chatInput.onkeypress = (e) => { if(e.key === 'Enter') sendMessage(); }; // ---------------------------------------------------- // 6. VISUAL SEARCH ENGINE // ---------------------------------------------------- const fileInput = document.getElementById('omni-file-input'); const visualLoader = document.getElementById('omni-visual-loader'); const visualResults = document.getElementById('omni-visual-results'); fileInput.onchange = async function() { const file = fileInput.files[0]; if (!file) return; visualLoader.style.display = 'block'; visualResults.innerHTML = ''; const formData = new FormData(); formData.append('file', file); try { const response = await fetch(`${API_URL}/api/v1/visual/search`, { method: 'POST', headers: { 'x-api-key': API_KEY // Header Auth ✅ }, body: formData }); const data = await response.json(); visualLoader.style.display = 'none'; if (!response.ok) { visualResults.innerHTML = `

Error: ${data.detail}

`; return; } if (!data.results || data.results.length === 0) { visualResults.innerHTML = '

No similar products found.

'; return; } // Render Results data.results.forEach(item => { const score = (item.similarity * 100).toFixed(0); const el = document.createElement('a'); el.className = 'omni-result-card'; el.href = `/product/${item.slug || '#'}`; // Dynamic link el.target = '_blank'; el.innerHTML = `
Product Match
${score}% Similarity
`; visualResults.appendChild(el); }); } catch (e) { visualLoader.style.display = 'none'; visualResults.innerHTML = '

Connection Failed

'; } }; // Initial Hello setTimeout(() => addMsg("Hi! I can help you find products by chatting or uploading an image.", "bot"), 1000); })();