Mobile_Reccomendation / index.html
smhs16's picture
Upload 8 files
f651dd8 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>PriceOye AI Phone Advisor</title>
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet"/>
<style>
:root{--red:#E8192C;--dark:#0D0D0F;--card:#161618;--border:#2a2a2e;--muted:#6b6b75;--text:#f0f0f2;--green:#22C55E;--amber:#F59E0B;--blue:#3B82F6;--r:14px;}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%;font-family:'Sora',sans-serif;background:var(--dark);color:var(--text);overflow:hidden;}
body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellipse 80% 60% at 10% 0%,rgba(232,25,44,.10) 0%,transparent 60%),radial-gradient(ellipse 60% 50% at 90% 100%,rgba(59,130,246,.07) 0%,transparent 60%);pointer-events:none;z-index:0;}
#app{display:flex;flex-direction:column;height:100vh;position:relative;z-index:1;max-width:860px;margin:0 auto;}
/* HEADER */
header{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid var(--border);background:rgba(13,13,15,.9);backdrop-filter:blur(16px);flex-shrink:0;}
.logo{display:flex;align-items:center;gap:10px;}
.logo-icon{width:36px;height:36px;background:var(--red);border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:17px;}
.logo-text{font-weight:800;font-size:17px;letter-spacing:-.5px;}.logo-text span{color:var(--red);}
.hdr-actions{margin-left:auto;display:flex;align-items:center;gap:8px;}
.hdr-btn{background:transparent;border:1px solid var(--border);color:var(--muted);padding:6px 12px;border-radius:8px;font-size:12px;font-family:'Sora',sans-serif;cursor:pointer;transition:.2s;}
.hdr-btn:hover{border-color:var(--red);color:var(--text);}
.status-dot{width:7px;height:7px;background:var(--green);border-radius:50%;animation:pulse 2s infinite;}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
/* MESSAGES */
#messages{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:14px;scroll-behavior:smooth;}
#messages::-webkit-scrollbar{width:3px;}
#messages::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
.msg{display:flex;gap:10px;animation:fadeUp .3s ease;max-width:100%;}
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.msg.user{flex-direction:row-reverse;}
.avatar{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0;margin-top:2px;}
.bot-avatar{background:linear-gradient(135deg,var(--red),#ff4f64);}
.user-avatar{background:linear-gradient(135deg,#3B82F6,#6366f1);}
.bubble{max-width:80%;padding:11px 15px;border-radius:var(--r);font-size:14px;line-height:1.65;}
.msg.bot .bubble{background:var(--card);border:1px solid var(--border);border-top-left-radius:3px;}
.msg.user .bubble{background:linear-gradient(135deg,var(--red),#c0102a);border-top-right-radius:3px;color:#fff;}
/* QUICK REPLIES */
.qrs{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px;}
.qr{background:transparent;border:1px solid var(--border);color:var(--text);padding:6px 13px;border-radius:999px;font-size:12.5px;font-family:'Sora',sans-serif;cursor:pointer;transition:.2s;white-space:nowrap;}
.qr:hover{background:var(--red);border-color:var(--red);color:#fff;transform:translateY(-1px);}
/* TYPING */
.typing{display:flex;gap:5px;align-items:center;padding:3px 0;}
.typing span{width:7px;height:7px;background:var(--muted);border-radius:50%;animation:bounce 1.2s infinite;}
.typing span:nth-child(2){animation-delay:.2s;}.typing span:nth-child(3){animation-delay:.4s;}
@keyframes bounce{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}}
/* PHONE CARD */
.phone-card{background:var(--card);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;margin-top:8px;max-width:440px;}
.pc-header{background:linear-gradient(135deg,rgba(232,25,44,.12),rgba(59,130,246,.06));padding:14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:11px;}
.pc-emoji{font-size:34px;}.pc-meta h3{font-size:15px;font-weight:700;letter-spacing:-.3px;}.pc-meta .price{font-size:13px;color:var(--red);font-weight:600;margin-top:2px;}
.ai-badge{margin-left:auto;border:2px solid;border-radius:10px;padding:5px 10px;text-align:center;min-width:58px;}
.ai-badge .score-n{font-size:19px;font-weight:800;font-family:'JetBrains Mono',monospace;}
.ai-badge .score-l{font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;}
.pc-highlights{padding:10px 14px;display:flex;flex-wrap:wrap;gap:5px;border-bottom:1px solid var(--border);}
.hi-chip{background:rgba(255,255,255,.04);border:1px solid var(--border);border-radius:7px;padding:3px 7px;font-size:10.5px;}
.hi-key{color:var(--muted);margin-right:4px;text-transform:capitalize;}.hi-val{color:var(--text);font-weight:500;}
.sec-lbl{padding:7px 14px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--muted);background:rgba(255,255,255,.02);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:6px;user-select:none;}
.sec-lbl.clickable{cursor:pointer;}.toggle-arrow{margin-left:auto;font-size:9px;}
.cat-scores{padding:10px 14px;display:flex;flex-direction:column;gap:5px;}
.cat-row{display:flex;align-items:center;gap:7px;}
.cat-icon{font-size:12px;width:18px;text-align:center;}.cat-name{font-size:11px;color:var(--muted);width:76px;flex-shrink:0;}
.cat-bar{flex:1;height:4px;background:var(--border);border-radius:4px;overflow:hidden;}
.cat-bar-f{height:100%;border-radius:4px;transition:width .8s ease;}
.cat-score{font-size:11px;font-weight:700;font-family:'JetBrains Mono',monospace;width:26px;text-align:right;}
.deep-dive{display:none;padding:10px 14px;flex-direction:column;gap:7px;border-bottom:1px solid var(--border);}
.sub-row{display:flex;flex-direction:column;gap:2px;}
.sub-label{font-size:11px;font-weight:600;}.sub-note{font-size:10.5px;color:var(--muted);line-height:1.4;}
.sub-sw{display:flex;align-items:center;gap:7px;margin-top:2px;}
.sub-bar{flex:1;height:3px;background:var(--border);border-radius:3px;overflow:hidden;}
.sub-bar-f{height:100%;border-radius:3px;transition:width .8s ease;}
.sub-num{font-size:10px;font-weight:700;font-family:'JetBrains Mono',monospace;width:16px;}
.pc-footer{padding:10px 14px;border-top:1px solid var(--border);display:flex;gap:8px;}
.btn-po{flex:1;background:var(--red);color:#fff;border:none;border-radius:9px;padding:9px;font-family:'Sora',sans-serif;font-size:12px;font-weight:600;cursor:pointer;transition:.2s;}
.btn-po:hover{background:#c0102a;transform:translateY(-1px);}
.btn-wm{background:transparent;border:1px solid var(--border);color:var(--text);border-radius:9px;padding:9px 12px;font-family:'Sora',sans-serif;font-size:12px;cursor:pointer;transition:.2s;}
.btn-wm:hover{border-color:var(--muted);}
.btn-cmp{background:rgba(59,130,246,.12);border:1px solid rgba(59,130,246,.3);color:#93c5fd;border-radius:9px;padding:9px 12px;font-family:'Sora',sans-serif;font-size:12px;cursor:pointer;transition:.2s;}
.btn-cmp:hover{background:rgba(59,130,246,.2);}
/* COMPARISON MODAL */
#cmp-modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:100;align-items:center;justify-content:center;padding:20px;}
#cmp-modal.open{display:flex;}
.cmp-box{background:var(--card);border:1px solid var(--border);border-radius:18px;width:100%;max-width:800px;max-height:90vh;overflow-y:auto;animation:fadeUp .3s ease;}
.cmp-head{padding:18px 22px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;}
.cmp-head h2{font-size:16px;font-weight:700;}
.cmp-close{background:transparent;border:1px solid var(--border);color:var(--text);width:32px;height:32px;border-radius:8px;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;}
.cmp-body{padding:18px 22px;}
.cmp-phones{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:18px;}
.cmp-phone-col{text-align:center;}
.cmp-phone-name{font-size:14px;font-weight:700;margin-bottom:4px;}
.cmp-phone-price{font-size:12px;color:var(--red);font-weight:600;}
.cmp-phone-score{font-size:28px;font-weight:800;font-family:'JetBrains Mono',monospace;margin:8px 0;}
.cmp-winner-tag{font-size:11px;padding:3px 10px;border-radius:999px;background:rgba(34,197,94,.15);color:var(--green);font-weight:600;}
.cmp-winner-tag.red{background:rgba(232,25,44,.15);color:var(--red);}
.cmp-cats{display:flex;flex-direction:column;gap:8px;}
.cmp-cat-row{display:grid;grid-template-columns:70px 1fr 60px 1fr 70px;align-items:center;gap:8px;}
.cmp-cat-name{font-size:11px;color:var(--muted);text-align:center;}
.cmp-bar-wrap{display:flex;align-items:center;gap:6px;}
.cmp-bar-wrap.right{flex-direction:row-reverse;}
.cmp-bar-bg{flex:1;height:5px;background:var(--border);border-radius:4px;overflow:hidden;}
.cmp-bar-fill{height:100%;border-radius:4px;transition:width .8s ease;}
.cmp-score-val{font-size:11px;font-weight:700;font-family:'JetBrains Mono',monospace;width:28px;}
.cmp-score-val.right{text-align:right;}
.cmp-winner-indicator{width:8px;height:8px;border-radius:50%;flex-shrink:0;}
.cmp-footer{padding:14px 22px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:10px;}
.cmp-priority-sel{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;}
.cmp-pri-btn{background:transparent;border:1px solid var(--border);color:var(--muted);padding:5px 12px;border-radius:999px;font-size:12px;font-family:'Sora',sans-serif;cursor:pointer;transition:.2s;}
.cmp-pri-btn.active{background:rgba(232,25,44,.15);border-color:var(--red);color:var(--red);}
.cmp-verdict{background:rgba(34,197,94,.08);border:1px solid rgba(34,197,94,.2);border-radius:10px;padding:12px;margin-top:14px;font-size:13px;line-height:1.6;}
/* IMAGE DETECT */
.detect-result{background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.2);border-radius:10px;padding:12px;margin-top:8px;font-size:13px;}
.detect-result .detected-name{font-size:16px;font-weight:700;margin-bottom:4px;}
.detect-links{display:flex;gap:8px;margin-top:8px;}
.detect-link{background:transparent;border:1px solid var(--border);color:var(--text);padding:6px 12px;border-radius:8px;font-size:12px;cursor:pointer;text-decoration:none;transition:.2s;font-family:'Sora',sans-serif;}
.detect-link:hover{border-color:var(--blue);color:#93c5fd;}
/* INPUT AREA */
#input-area{padding:12px 20px 16px;border-top:1px solid var(--border);background:rgba(13,13,15,.9);backdrop-filter:blur(16px);flex-shrink:0;}
.input-row{display:flex;gap:8px;align-items:flex-end;}
.input-actions{display:flex;gap:6px;flex-shrink:0;align-items:flex-end;}
.act-btn{width:42px;height:42px;border-radius:11px;border:1px solid var(--border);background:transparent;color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:17px;transition:.2s;flex-shrink:0;}
.act-btn:hover{border-color:var(--muted);color:var(--text);}
#user-input{flex:1;background:var(--card);border:1px solid var(--border);border-radius:12px;padding:11px 14px;color:var(--text);font-family:'Sora',sans-serif;font-size:13.5px;resize:none;outline:none;transition:border-color .2s;min-height:44px;max-height:110px;line-height:1.5;}
#user-input:focus{border-color:var(--red);}
#user-input::placeholder{color:var(--muted);}
#send-btn{width:44px;height:44px;background:var(--red);border:none;border-radius:12px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.2s;flex-shrink:0;}
#send-btn:hover{background:#c0102a;transform:scale(1.04);}
#send-btn svg{width:18px;height:18px;fill:#fff;}
#file-input{display:none;}
.hint{font-size:10.5px;color:var(--muted);margin-top:7px;text-align:center;}
/* MEDAL */
.medal{font-size:11px;font-weight:700;color:var(--amber);margin-bottom:6px;}
@media(max-width:500px){.bubble{max-width:92%;}.cmp-phones{grid-template-columns:1fr 1fr;}.cmp-cat-row{grid-template-columns:55px 1fr 44px 1fr 55px;}}
</style>
</head>
<body>
<div id="app">
<header>
<div class="logo">
<div class="logo-icon">πŸ“±</div>
<div class="logo-text"><span>Price</span>Oye AI</div>
</div>
<div class="hdr-actions">
<button class="hdr-btn" onclick="openCompareModal()">βš–οΈ Compare</button>
<button class="hdr-btn" onclick="resetChat()">πŸ”„ Reset</button>
<div class="status-dot" title="AI Online"></div>
</div>
</header>
<div id="messages"></div>
<div id="input-area">
<div class="input-row">
<div class="input-actions">
<button class="act-btn" title="Upload phone image to detect" onclick="document.getElementById('file-input').click()">πŸ“·</button>
<input type="file" id="file-input" accept="image/*" onchange="handleImageUpload(event)"/>
</div>
<textarea id="user-input" rows="1" placeholder="Budget, OS, use case batayein… ya phone image upload karein πŸ“·"></textarea>
<button id="send-btn" onclick="send()">
<svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
</button>
</div>
<div class="hint">Data: WhatMobile.com.pk Β· PriceOye.pk Β· Budget: 9k – 5 lakh PKR</div>
</div>
</div>
<!-- COMPARISON MODAL -->
<div id="cmp-modal">
<div class="cmp-box">
<div class="cmp-head">
<h2>βš–οΈ Phone Comparison</h2>
<button class="cmp-close" onclick="closeCompare()">βœ•</button>
</div>
<div class="cmp-body" id="cmp-body">
<div style="color:var(--muted);font-size:13px;margin-bottom:14px">Do phones ka naam likhein compare karne ke liye:</div>
<div style="display:flex;gap:10px;margin-bottom:12px">
<input id="cmp-a" placeholder="e.g. Samsung A55" style="flex:1;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 14px;color:var(--text);font-family:Sora,sans-serif;font-size:13px;outline:none"/>
<div style="display:flex;align-items:center;color:var(--muted);font-weight:700">VS</div>
<input id="cmp-b" placeholder="e.g. Nothing Phone 3a" style="flex:1;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 14px;color:var(--text);font-family:Sora,sans-serif;font-size:13px;outline:none"/>
</div>
<div id="cmp-results"></div>
</div>
<div class="cmp-footer">
<button class="btn-wm" onclick="closeCompare()">BΩ†Ψ― کریں</button>
<button class="btn-po" onclick="runCompare()">Compare Karein βš–οΈ</button>
</div>
</div>
</div>
<script>
// ── CONFIG ──────────────────────────────────────────────
const API = ''; // empty = same origin (FastAPI backend)
let SESSION_ID = null;
// ── PHONE DB cache (loaded from /phones) ──────────────────
let PHONE_DB_CACHE = [];
async function loadPhones() {
try {
const r = await fetch(`${API}/phones`);
PHONE_DB_CACHE = await r.json();
} catch(e) { console.warn('Could not load phone DB:', e); }
}
// ── SCORE COLOR ──────────────────────────────────────────
function sc(s) {
if (s >= 8.5) return '#22C55E';
if (s >= 7) return '#F59E0B';
if (s >= 5.5) return '#3B82F6';
return '#EF4444';
}
// ── UI HELPERS ────────────────────────────────────────────
function appendMsg(type, html, qrs = []) {
const msgs = document.getElementById('messages');
const wrap = document.createElement('div');
wrap.className = `msg ${type}`;
const av = document.createElement('div');
av.className = `avatar ${type}-avatar`;
av.textContent = type === 'bot' ? 'πŸ€–' : 'πŸ‘€';
const b = document.createElement('div');
b.className = 'bubble';
b.innerHTML = html;
if (qrs.length) {
const qDiv = document.createElement('div');
qDiv.className = 'qrs';
qrs.forEach(label => {
const btn = document.createElement('button');
btn.className = 'qr'; btn.textContent = label;
btn.onclick = () => send(label);
qDiv.appendChild(btn);
});
b.appendChild(qDiv);
}
wrap.appendChild(av); wrap.appendChild(b);
msgs.appendChild(wrap);
msgs.scrollTop = msgs.scrollHeight;
}
function showTyping() {
const msgs = document.getElementById('messages');
const d = document.createElement('div');
d.className = 'msg bot'; d.id = 'typing-ind';
const av = document.createElement('div');
av.className = 'avatar bot-avatar'; av.textContent = 'πŸ€–';
const b = document.createElement('div');
b.className = 'bubble';
b.innerHTML = '<div class="typing"><span></span><span></span><span></span></div>';
d.appendChild(av); d.appendChild(b);
msgs.appendChild(d);
msgs.scrollTop = msgs.scrollHeight;
}
function removeTyping() { const e = document.getElementById('typing-ind'); if(e) e.remove(); }
// ── PHONE CARD BUILDER ────────────────────────────────────
function buildCard(p, medal) {
const score = p.ai_score ?? p.priority_score ?? 0;
const color = sc(score);
const cats = p.category_scores || {};
const CAT_META = {camera:'πŸ“Έ Camera',performance:'⚑ Perf',display:'πŸ–₯️ Display',battery:'πŸ”‹ Battery',charging:'πŸ”Œ Charging',ram:'🧠 RAM',storage:'πŸ’Ύ Storage',build:'πŸ—οΈ Build',software:'πŸ“² Software',audio:'πŸ”Š Audio'};
const hiHtml = Object.entries(p.highlights||{}).map(([k,v]) =>
`<div class="hi-chip"><span class="hi-key">${k}</span><span class="hi-val">${v}</span></div>`).join('');
const catHtml = Object.entries(CAT_META).map(([k,label]) => {
const s = cats[k] ?? 5;
const c = sc(s);
return `<div class="cat-row">
<span class="cat-icon">${label.split(' ')[0]}</span>
<span class="cat-name">${label.split(' ').slice(1).join(' ')}</span>
<div class="cat-bar"><div class="cat-bar-f" style="width:${s*10}%;background:${c}"></div></div>
<span class="cat-score" style="color:${c}">${s.toFixed(1)}</span>
</div>`;
}).join('');
// Deep dive
const deepCat = p.deep_dive_category || 'camera';
const deepScores = p.deep_dive_scores || [];
const deepHtml = deepScores.map(d => {
const c = sc(d.score);
return `<div class="sub-row">
<div class="sub-label">${d.label}</div>
<div class="sub-note">${d.note}</div>
<div class="sub-sw">
<div class="sub-bar"><div class="sub-bar-f" style="width:${d.score*10}%;background:${c}"></div></div>
<span class="sub-num" style="color:${c}">${d.score}</span>
</div>
</div>`;
}).join('');
const card = document.createElement('div');
card.className = 'phone-card';
card.dataset.phoneId = p.id;
card.innerHTML = `
${medal ? `<div class="medal" style="padding:8px 14px 0">${medal}</div>` : ''}
<div class="pc-header">
<div class="pc-emoji">${p.emoji}</div>
<div class="pc-meta"><h3>${p.name}</h3><div class="price">${p.price_label}</div></div>
<div class="ai-badge" style="border-color:${color}">
<div class="score-n" style="color:${color}">${score.toFixed(1)}</div>
<div class="score-l">AI Score</div>
</div>
</div>
<div class="pc-highlights">${hiHtml}</div>
<div class="sec-lbl">πŸ“Š Category Scores</div>
<div class="cat-scores">${catHtml}</div>
${deepHtml ? `
<div class="sec-lbl clickable" onclick="toggleDeep(this)">
πŸ” ${deepCat.charAt(0).toUpperCase()+deepCat.slice(1)} Deep Dive
<span class="toggle-arrow">β–Ό</span>
</div>
<div class="deep-dive">${deepHtml}</div>` : ''}
<div class="pc-footer">
<button class="btn-po" onclick="window.open('${p.priceoye_url}','_blank')">PriceOye πŸ›’</button>
<button class="btn-wm" onclick="window.open('${p.whatmobile_url}','_blank')">WhatMobile πŸ“‹</button>
<button class="btn-cmp" onclick="compareWithThis('${p.id}','${p.name}')">βš–οΈ Compare</button>
</div>`;
return card;
}
function appendPhoneCard(p, medal) {
const msgs = document.getElementById('messages');
const wrap = document.createElement('div');
wrap.className = 'msg bot';
const av = document.createElement('div');
av.className = 'avatar bot-avatar'; av.textContent = 'πŸ€–';
const b = document.createElement('div');
b.className = 'bubble';
b.appendChild(buildCard(p, medal));
wrap.appendChild(av); wrap.appendChild(b);
msgs.appendChild(wrap);
msgs.scrollTop = msgs.scrollHeight;
}
function toggleDeep(el) {
const d = el.nextElementSibling;
const a = el.querySelector('.toggle-arrow');
const open = d.style.display === 'flex';
d.style.display = open ? 'none' : 'flex';
d.style.flexDirection = 'column';
d.style.gap = '7px';
a.textContent = open ? 'β–Ό' : 'β–²';
}
// ── CHAT ─────────────────────────────────────────────────
async function send(overrideText) {
const inp = document.getElementById('user-input');
const text = overrideText ?? inp.value.trim();
if (!text) return;
if (!overrideText) { inp.value = ''; inp.style.height = 'auto'; }
appendMsg('user', text);
showTyping();
try {
const r = await fetch(`${API}/chat`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ session_id: SESSION_ID, message: text })
});
const data = await r.json();
SESSION_ID = data.session_id;
removeTyping();
appendMsg('bot', data.text || '', data.quick_replies || []);
if (data.phones && data.phones.length) {
const medals = ['πŸ₯‡ Behtar in Sab Mein','πŸ₯ˆ Doosra Best','πŸ₯‰ Aur Ek Option','4️⃣ Yeh Bhi'];
data.phones.forEach((p, i) => {
setTimeout(() => appendPhoneCard(p, p.medal || medals[i] || ''), i * 400);
});
if (data.followup) {
setTimeout(() => appendMsg('bot', data.followup, data.quick_replies || []),
data.phones.length * 400 + 500);
}
}
} catch(e) {
removeTyping();
appendMsg('bot', 'Maafi, server se connection nahi ho raha. Please thodi der baad try karein.', ['Dobara Try Karein']);
}
}
// ── IMAGE UPLOAD & PHONE DETECTION ────────────────────────
async function handleImageUpload(event) {
const file = event.target.files[0];
if (!file) return;
event.target.value = '';
appendMsg('user', `πŸ“· Image upload ho rahi hai: <b>${file.name}</b>`);
showTyping();
const formData = new FormData();
formData.append('file', file);
try {
const r = await fetch(`${API}/detect`, { method: 'POST', body: formData });
const data = await r.json();
removeTyping();
if (!data.brand && !data.model) {
appendMsg('bot',
'Is image mein koi phone nahi dikha ya detect nahi ho saka. Please ek clear phone photo upload karein.',
['Camera phone 1 lakh mein', 'Gaming phone 2 lakh mein']
);
return;
}
const conf = data.confidence === 'high' ? 'βœ… High Confidence' :
data.confidence === 'medium' ? '⚠️ Medium Confidence' : '❓ Low Confidence';
const msgs = document.getElementById('messages');
const wrap = document.createElement('div');
wrap.className = 'msg bot';
const av = document.createElement('div');
av.className = 'avatar bot-avatar'; av.textContent = 'πŸ€–';
const b = document.createElement('div');
b.className = 'bubble';
let html = `Image mein yeh phone detect hua:<br>`;
html += `<div class="detect-result">`;
html += `<div class="detected-name">${data.brand || ''} ${data.model || ''}</div>`;
html += `<div style="font-size:11px;color:var(--muted)">${conf}</div>`;
if (data.matched_in_db) {
const m = data.matched_in_db;
html += `<div style="margin-top:8px;font-size:12px">πŸ“Š Hamare database mein hai: <b>${m.name}</b> β€” ${m.price_label}</div>`;
} else {
html += `<div style="margin-top:8px;font-size:12px;color:var(--muted)">Yeh phone hamare database mein nahi hai β€” links check karein:</div>`;
}
html += `<div class="detect-links">
<a class="detect-link" href="${data.search_url || '#'}" target="_blank">PriceOye pe Dekho πŸ›’</a>
<a class="detect-link" href="${data.whatmobile_url || '#'}" target="_blank">WhatMobile πŸ“‹</a>
</div></div>`;
html += `<br><small style="color:var(--muted)">Kya aap is phone aur ek recommended phone ka comparison dekhna chahte hain?</small>`;
b.innerHTML = html;
// Add quick replies
const qDiv = document.createElement('div');
qDiv.className = 'qrs';
const qrs = [];
if (data.matched_in_db) {
qrs.push(`${data.matched_in_db.name} ko Compare Karein`);
}
qrs.push('Is se Behtar Phone Batao', 'Naya Search');
qrs.forEach(label => {
const btn = document.createElement('button');
btn.className = 'qr'; btn.textContent = label;
btn.onclick = () => {
if (label.includes('Compare')) {
compareWithThis(data.matched_in_db?.id, data.matched_in_db?.name);
} else {
send(label);
}
};
qDiv.appendChild(btn);
});
b.appendChild(qDiv);
wrap.appendChild(av); wrap.appendChild(b);
msgs.appendChild(wrap);
msgs.scrollTop = msgs.scrollHeight;
} catch(e) {
removeTyping();
appendMsg('bot', 'Image detect karne mein masla aaya. ANTHROPIC_API_KEY set karein HF Secrets mein.', []);
}
}
// ── COMPARISON ────────────────────────────────────────────
let comparePreFill = null;
function openCompareModal() {
document.getElementById('cmp-modal').classList.add('open');
document.getElementById('cmp-results').innerHTML = '';
if (comparePreFill) {
document.getElementById('cmp-a').value = comparePreFill;
comparePreFill = null;
}
}
function closeCompare() {
document.getElementById('cmp-modal').classList.remove('open');
}
function compareWithThis(phoneId, phoneName) {
comparePreFill = phoneName || phoneId;
openCompareModal();
document.getElementById('cmp-b').focus();
}
async function runCompare() {
const nameA = document.getElementById('cmp-a').value.trim();
const nameB = document.getElementById('cmp-b').value.trim();
if (!nameA || !nameB) {
document.getElementById('cmp-results').innerHTML =
'<div style="color:var(--red);font-size:13px">Dono phone ka naam zaroor likhein.</div>';
return;
}
// Find IDs from cache by fuzzy name match
function findId(name) {
const n = name.toLowerCase();
const exact = PHONE_DB_CACHE.find(p => p.name.toLowerCase() === n);
if (exact) return exact.id;
const partial = PHONE_DB_CACHE.find(p =>
p.name.toLowerCase().includes(n) || n.includes(p.name.toLowerCase().split(' ')[1] || '')
);
return partial?.id;
}
const idA = findId(nameA);
const idB = findId(nameB);
if (!idA) {
document.getElementById('cmp-results').innerHTML =
`<div style="color:var(--red);font-size:13px">"${nameA}" hamare database mein nahi mila.</div>`;
return;
}
if (!idB) {
document.getElementById('cmp-results').innerHTML =
`<div style="color:var(--red);font-size:13px">"${nameB}" hamare database mein nahi mila.</div>`;
return;
}
document.getElementById('cmp-results').innerHTML =
'<div class="typing" style="justify-content:center"><span></span><span></span><span></span></div>';
try {
const r = await fetch(`${API}/compare`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ phone_id_a: idA, phone_id_b: idB, priority: 'balanced' })
});
const data = await r.json();
renderComparison(data);
} catch(e) {
document.getElementById('cmp-results').innerHTML =
'<div style="color:var(--red);font-size:13px">Comparison mein error. Dobara try karein.</div>';
}
}
function renderComparison(data) {
const { phone_a: a, phone_b: b, winners, overall_winner, priority_winner } = data;
const aWin = overall_winner === 'a';
const cats = Object.keys(a.category_scores);
const catLabels = {camera:'πŸ“Έ Camera',performance:'⚑ Performance',display:'πŸ–₯️ Display',
battery:'πŸ”‹ Battery',charging:'πŸ”Œ Charging',ram:'🧠 RAM',
storage:'πŸ’Ύ Storage',build:'πŸ—οΈ Build',software:'πŸ“² Software',audio:'πŸ”Š Audio'};
const catRows = cats.map(cat => {
const sa = a.category_scores[cat];
const sb = b.category_scores[cat];
const w = winners[cat];
const ca = sc(sa), cb = sc(sb);
return `<div class="cmp-cat-row">
<div class="cmp-bar-wrap right">
<div class="cmp-bar-bg"><div class="cmp-bar-fill" style="width:${sa*10}%;background:${ca}"></div></div>
<span class="cmp-score-val right" style="color:${ca}">${sa.toFixed(1)}</span>
<div class="cmp-winner-indicator" style="background:${w==='a'?'#22C55E':w==='tie'?'#F59E0B':'transparent'}"></div>
</div>
<div class="cmp-cat-name">${catLabels[cat]||cat}</div>
<div class="cmp-bar-wrap">
<div class="cmp-winner-indicator" style="background:${w==='b'?'#22C55E':w==='tie'?'#F59E0B':'transparent'}"></div>
<span class="cmp-score-val" style="color:${cb}">${sb.toFixed(1)}</span>
<div class="cmp-bar-bg"><div class="cmp-bar-fill" style="width:${sb*10}%;background:${cb}"></div></div>
</div>
</div>`;
}).join('');
// Count wins
const aWins = Object.values(winners).filter(v=>v==='a').length;
const bWins = Object.values(winners).filter(v=>v==='b').length;
const ties = Object.values(winners).filter(v=>v==='tie').length;
const verdict = aWin
? `<b>${a.name}</b> overall better hai (${a.overall.toFixed(1)} vs ${b.overall.toFixed(1)}) β€” ${aWins} categories mein jeet gaya, ${ties} ties.`
: overall_winner === 'b'
? `<b>${b.name}</b> overall better hai (${b.overall.toFixed(1)} vs ${a.overall.toFixed(1)}) β€” ${bWins} categories mein jeet gaya, ${ties} ties.`
: `Dono phones kaafi close hain! (${a.overall.toFixed(1)} vs ${b.overall.toFixed(1)}) β€” Use case ke mutabiq choose karein.`;
document.getElementById('cmp-results').innerHTML = `
<div class="cmp-phones">
<div class="cmp-phone-col">
<div style="font-size:28px">${a.emoji||'πŸ“±'}</div>
<div class="cmp-phone-name">${a.name}</div>
<div class="cmp-phone-price">${a.price_label}</div>
<div class="cmp-phone-score" style="color:${sc(a.overall)}">${a.overall.toFixed(1)}</div>
${aWin ? '<div class="cmp-winner-tag">πŸ† Winner</div>' : ''}
</div>
<div class="cmp-phone-col">
<div style="font-size:28px">${b.emoji||'πŸ“±'}</div>
<div class="cmp-phone-name">${b.name}</div>
<div class="cmp-phone-price">${b.price_label}</div>
<div class="cmp-phone-score" style="color:${sc(b.overall)}">${b.overall.toFixed(1)}</div>
${!aWin && overall_winner!=='tie' ? '<div class="cmp-winner-tag">πŸ† Winner</div>' : ''}
</div>
</div>
<div class="cmp-cats">${catRows}</div>
<div class="cmp-verdict">πŸ“Š ${verdict}<br><br>
<a href="${a.priceoye_url}" target="_blank" style="color:var(--red);font-size:12px">${a.name} PriceOye pe dekho</a> &nbsp;Β·&nbsp;
<a href="${b.priceoye_url}" target="_blank" style="color:var(--red);font-size:12px">${b.name} PriceOye pe dekho</a>
</div>`;
}
// ── INIT ──────────────────────────────────────────────────
function resetChat() {
SESSION_ID = null;
document.getElementById('messages').innerHTML = '';
init();
}
async function init() {
await loadPhones();
showTyping();
try {
const r = await fetch(`${API}/chat`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ session_id: null, message: '' })
});
const data = await r.json();
SESSION_ID = data.session_id;
removeTyping();
appendMsg('bot', data.text, data.quick_replies || []);
} catch(e) {
removeTyping();
appendMsg('bot', '⚠️ Backend se connect nahi ho raha. Check karein ke server chal raha hai.', []);
}
}
// Input handlers
document.getElementById('user-input').addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
});
document.getElementById('user-input').addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 110) + 'px';
});
document.getElementById('cmp-modal').addEventListener('click', e => {
if (e.target === e.currentTarget) closeCompare();
});
init();
</script>
</body>
</html>