// ===== SEARCH PLUS v8: Real web scraping + LLM hybrid ===== let searchPlusOpen=false; window._spLastResults=[]; const CORS_PROXIES=[ q=>'https://api.allorigins.win/get?url='+encodeURIComponent(q), q=>'https://api.codetabs.com/v1/proxy?quest='+encodeURIComponent(q) ]; function toggleSearchPlus(){ let o=document.getElementById('searchPlusOverlay'); if(!o)createSearchPlusUI(); o=document.getElementById('searchPlusOverlay'); searchPlusOpen=!searchPlusOpen; o.classList.toggle('open',searchPlusOpen); if(searchPlusOpen)document.getElementById('spQuery').focus(); } function createSearchPlusUI(){ let d=document.createElement('div'); d.innerHTML=` `; document.body.appendChild(d); let s=document.createElement('style'); s.textContent='#searchPlusOverlay.open{display:flex!important}'; document.head.appendChild(s); } async function runSearchPlus(){ let query=document.getElementById('spQuery').value.trim(); if(!query)return; let results=document.getElementById('spResults'); let btn=document.getElementById('spSearchBtn'); btn.disabled=true;btn.innerHTML=' Đang tìm...'; results.innerHTML=`

Đang scrape dữ liệu từ web...

`; let local=localSearch(query).filter(p=>!p._notfound); let external=[]; try{ updateProgress('🔍 Đang tìm trên Google...'); let googleResults=await scrapeGoogle(query); if(googleResults.length) external.push(...googleResults); }catch(e){console.warn('Google scrape failed:',e)} try{ updateProgress('🏪 Đang scrape thegioibepnhapkhau.vn...'); let tgbResults=await scrapeTGBNK(query); if(tgbResults.length) external.push(...tgbResults); }catch(e){console.warn('TGBNK scrape failed:',e)} if(external.length<3){ let vars=window.huggingface?.variables||{}; let token=vars.HF_TOKEN||vars.VAISTUDIO||vars.AI_TOKEN||vars.TOKEN||''; if(token&&token.startsWith('hf_')){ try{ updateProgress('🤖 Bổ sung thông tin từ AI...'); let aiResults=await searchFromAI(query,token); if(aiResults.length) external.push(...aiResults); }catch(e){console.warn('AI search failed:',e)} } } let all=[...local]; let seenNames=new Set(local.map(p=>(p.name||'').toLowerCase().replace(/[^a-z0-9]/g,''))); external.forEach(p=>{ let key=(p.name||'').toLowerCase().replace(/[^a-z0-9]/g,''); if(key.length>5 && !seenNames.has(key)){ all.push(p); seenNames.add(key); } }); if(!all.length)all=[{_notfound:true}]; window._spLastResults=all; results.innerHTML=renderSPResults(all); btn.disabled=false;btn.innerHTML='🔍 Tìm'; } function updateProgress(msg){ let el=document.getElementById('spProgress'); if(el)el.textContent=msg; } async function scrapeGoogle(query){ let searchUrl='https://www.google.com/search?q='+encodeURIComponent(query+' giá site:dienmayxanh.com OR site:thegioibepnhapkhau.vn OR site:hafele.com.vn OR site:hmh.com.vn')+'&hl=vi&num=8'; let proxyUrl=CORS_PROXIES[0](searchUrl); let resp=await fetch(proxyUrl,{signal:AbortSignal.timeout(12000)}); if(!resp.ok) return []; let data=await resp.json(); let html=data.contents||''; if(!html||html.length<500) return []; let products=[]; let parser=new DOMParser(); let doc=parser.parseFromString(html,'text/html'); let resultDivs=doc.querySelectorAll('.g, div[data-hveid]'); resultDivs.forEach(div=>{ let linkEl=div.querySelector('a[href^="http"]'); let titleEl=div.querySelector('h3'); let snippetEl=div.querySelector('.VwiC3b, [data-sncf], .lEBKkf'); if(!linkEl||!titleEl) return; let url=linkEl.href||''; let title=titleEl.textContent||''; let snippet=(snippetEl?snippetEl.textContent:'').trim(); if(!/(dienmayxanh|thegioibepnhapkhau|hafele|hmh)/.test(url)) return; let priceMatch=snippet.match(/(\d{1,3}(?:\.\d{3})+)\s*(?:₫|đ|VNĐ|vnđ)/i)|| snippet.match(/(\d{1,3}(?:\.\d{3})+)\s*(?:đồng)/i)|| title.match(/(\d{1,3}(?:\.\d{3})+)\s*(?:₫|đ)/i); let price=priceMatch?priceMatch[1]+'đ':''; let priceNum=priceMatch?parseInt(priceMatch[1].replace(/\./g,'')):0; let source='Web'; if(url.includes('dienmayxanh')) source='Điện Máy Xanh'; else if(url.includes('thegioibepnhapkhau')) source='Thế Giới Bếp NK'; else if(url.includes('hafele')) source='Hafele VN'; else if(url.includes('hmh')) source='HMH'; let brand=''; let sku=''; ['Malloca','Eurogold','Grob','Canzy','Demax','Hafele','Bosch','Teka','Electrolux','Faster','Kaff'].forEach(b=>{ if(title.toLowerCase().includes(b.toLowerCase())) brand=b; }); let skuMatch=title.match(/\b([A-Z]{1,5}[-\s]?[A-Z0-9]{2,}(?:[-][A-Z0-9]+)*)\b/); if(skuMatch) sku=skuMatch[1]; products.push({ name:title.replace(/\s*[-|–]\s*(Điện Máy Xanh|Thế Giới Bếp|HAFELE|hmh).*$/i,'').trim(), sku:sku,brand:brand,price:price,priceNum:priceNum, category:'Thiết bị bếp',description:snippet, specs:{},features:[],image:'', source:source,sourceUrl:url,_needImage:true }); }); await Promise.allSettled(products.slice(0,6).map(async(p,i)=>{ if(p.sourceUrl){ try{ let img=await scrapeProductImage(p.sourceUrl,p.name); if(img) products[i].image=img; }catch(e){} } if(!products[i].image){ products[i].image=getGoogleImageUrl(p.name+(p.sku?' '+p.sku:'')); } })); return products; } async function scrapeTGBNK(query){ let searchUrl='https://www.thegioibepnhapkhau.vn/?s='+encodeURIComponent(query); let proxyUrl=CORS_PROXIES[0](searchUrl); let resp=await fetch(proxyUrl,{signal:AbortSignal.timeout(12000)}); if(!resp.ok) return []; let data=await resp.json(); let html=data.contents||''; if(!html||html.length<1000) return []; let products=[]; let parser=new DOMParser(); let doc=parser.parseFromString(html,'text/html'); let linkEls=doc.querySelectorAll('a[href*="/san-pham/"], a[href*="/product/"], a[href*="/bep-"], a[href*="/may-"], a[href*="/lo-"], a[href*="/khoa-"]'); let seen=new Set(); linkEls.forEach(link=>{ let href=link.href||link.getAttribute('href')||''; if(!href||href==='#'||seen.has(href)) return; let name=link.textContent.trim()||link.title||''; if(name.length<5||name.length>200) return; let parent=link.closest('.product-item, .product-card, .item, article, .product, .col-md-3, .col-sm-6, .col-xs-12'); let img=parent?parent.querySelector('img'):null; let imgSrc=''; if(img){ imgSrc=img.getAttribute('data-src')||img.getAttribute('src')||''; } let priceEl=parent?parent.querySelector('.price, [class*="price"], .gia, [class*="gia"]'):null; let priceText=priceEl?priceEl.textContent:''; let priceMatch=priceText.match(/(\d{1,3}(?:\.\d{3})+)/); if(href.startsWith('/')) href='https://www.thegioibepnhapkhau.vn'+href; let brand=''; ['Malloca','Eurogold','Grob','Canzy','Demax','Hafele','Bosch','Teka'].forEach(b=>{ if(name.toLowerCase().includes(b.toLowerCase())) brand=b; }); seen.add(href); products.push({ name:name.replace(/<[^>]*>/g,'').trim(), sku:'',brand:brand, price:priceMatch?priceMatch[1]+'đ':'Liên hệ', priceNum:priceMatch?parseInt(priceMatch[1].replace(/\./g,'')):0, category:'Thiết bị bếp',description:'', specs:{},features:[],image:imgSrc||'', source:'Thế Giới Bếp NK',sourceUrl:href }); }); return products.slice(0,6); } async function scrapeProductImage(url,productName){ try{ let proxyUrl=CORS_PROXIES[0](url); let resp=await fetch(proxyUrl,{signal:AbortSignal.timeout(8000)}); if(!resp.ok) return ''; let data=await resp.json(); let html=data.contents||''; if(!html) return ''; let parser=new DOMParser(); let doc=parser.parseFromString(html,'text/html'); let ldScripts=doc.querySelectorAll('script[type="application/ld+json"]'); for(let script of ldScripts){ try{ let ld=JSON.parse(script.textContent); if(Array.isArray(ld)) ld=ld.find(x=>x['@type']==='Product')||ld[0]; if(ld['@type']==='Product'){ let img=ld.image; if(Array.isArray(img)) img=img[0]; if(typeof img==='object') img=img.url||img.contentUrl; if(img&&img.startsWith('http')) return img; } }catch(e){} } let ogImg=doc.querySelector('meta[property="og:image"]'); if(ogImg){ let src=ogImg.getAttribute('content')||''; if(src&&src.startsWith('http')&&!src.includes('logo')&&!src.includes('favicon')) return src; } let mainImg=doc.querySelector('.product-image img, .gallery-main img, .product-detail img, [class*="product"] img[src*="product"], .product__image img'); if(mainImg){ let src=mainImg.getAttribute('data-src')||mainImg.getAttribute('src')||''; if(src&&src.startsWith('http')&&src.match(/\.(jpg|png|webp)/i)) return src; } let allImgs=doc.querySelectorAll('img'); for(let img of allImgs){ let src=img.getAttribute('data-src')||img.getAttribute('src')||''; let w=parseInt(img.getAttribute('width')||'0'); let h=parseInt(img.getAttribute('height')||'0'); if(src&&src.startsWith('http')&&(w>200||h>200||src.includes('product'))&&!src.includes('logo')&&!src.includes('icon')&&!src.includes('banner')){ return src; } } }catch(e){} return ''; } function getGoogleImageUrl(query){ return ''; } async function searchFromAI(query,token){ let resp=await fetch('https://router.huggingface.co/v1/chat/completions',{ method:'POST', headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json'}, signal:AbortSignal.timeout(20000), body:JSON.stringify({ model:'meta-llama/Llama-3.3-70B-Instruct', messages:[{role:'system',content:`Bạn là hệ thống tra cứu sản phẩm thiết bị nhà bếp & phụ kiện tại Việt Nam. CHỈ trả về sản phẩm bạn CHẮC CHẮN tồn tại thực tế. Không bịa sản phẩm. MỖI sản phẩm cần: - sku: mã SP chính hãng (phải chính xác, VD: MH02I, EUH2288PRO...) - name: tên đầy đủ - brand: thương hiệu - price: giá niêm yết VNĐ (ước lượng dựa trên phân khúc nếu không biết chính xác) - specs: object với các thông số THỰC (kích thước, công suất, chất liệu, xuất xứ, bảo hành) - features: array 3-4 tính năng nổi bật - description: mô tả 2-3 câu - source: "AI tổng hợp" KHÔNG bao gồm trường image (sẽ tự xử lý). Trả về JSON array. KHÔNG có text khác.`}, {role:'user',content:`Tìm: "${query}" — trả về 4-6 sản phẩm thực tế.`}], max_tokens:2500,temperature:0.1 }) }); if(!resp.ok) return []; let data=await resp.json(); let text=data.choices?.[0]?.message?.content||''; let m=text.match(/\[[\s\S]*?\]/); if(!m) return []; try{ let products=JSON.parse(m[0]); return products.filter(p=>p.name).map(p=>({ sku:p.sku||'',name:p.name||'',brand:p.brand||'', price:p.price||(p.priceNum>0?p.priceNum.toLocaleString('vi-VN')+'đ':''), priceNum:p.priceNum||parseInt(String(p.price||'').replace(/[^\d]/g,''))||0, category:p.category||'Thiết bị bếp', specs:p.specs||{},features:p.features||[], image:'',description:p.description||'', source:p.source||'AI tổng hợp',sourceUrl:'',_fromAI:true })); }catch(e){return []} } function localSearch(query){ let q=query.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d'); if(typeof D!=='undefined'&&D.length){ let matched=D.filter(p=>{ let idx=(p.name+' '+p.model+' '+p.brand+' '+p.cat+' '+(p.sku||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d'); return q.split(/\s+/).filter(w=>w.length>1).every(w=>idx.includes(w)); }).slice(0,3); if(matched.length)return matched.map(p=>({ sku:p.sku||p.model,name:p.name,brand:p.brand, price:p.price||'Liên hệ',priceNum:p.priceNum||0, category:p.cat,specs:p.specs||{},features:p.feats||[], image:p.image,description:p.summary||'', source:'V.AI STUDIO',sourceUrl:'',_existing:true })); } return[{_notfound:true}]; } function renderSPResults(products){ let real=products.filter(p=>!p._notfound); if(!real.length)return`
😕

Không tìm thấy sản phẩm.

Thử từ khóa khác hoặc mã sản phẩm cụ thể hơn.

`; let scrapeCount=real.filter(p=>!p._existing&&!p._fromAI).length; let aiCount=real.filter(p=>p._fromAI).length; let localCount=real.filter(p=>p._existing).length; let summary=`
${real.length} sản phẩm ${localCount?'✅ '+localCount+' trong kho':''} ${scrapeCount?'🌐 '+scrapeCount+' từ web':''} ${aiCount?'🤖 '+aiCount+' AI tổng hợp':''}
`; return summary+real.map((p,i)=>{ let img=makeImg(p,90); let priceHtml=p.price&&p.price!=='Liên hệ'?'
'+p.price+'
':'
Liên hệ báo giá
'; let specs=''; if(p.specs&&Object.keys(p.specs).length) specs='
'+Object.entries(p.specs).slice(0,4).map(([k,v])=>''+k+': '+v+'').join('')+'
'; let src=''; if(!p._existing){ if(p.sourceUrl) src=''+p.source+' ↗'; else if(p._fromAI) src='🤖 '+p.source+''; else src='📍'+p.source+''; } let verifyLink=p.sourceUrl?'':(!p._existing?' Verify':''); let btn=p._existing?'✅ Đã có':''; return`
${img}
${p.sku?'
'+p.sku+(p.brand?' · '+p.brand:'')+'
':''}
${p.name}
${priceHtml}${p.description?'
'+p.description+'
':''}${specs}
${btn}${src}${verifyLink}
`; }).join(''); } function makeImg(p,sz){ if(p.image&&p.image.startsWith('http')) return`
`; return phDiv(p,sz); } function phDiv(p,sz){ let c=(p.brand||p.name||'?').charAt(0).toUpperCase(); let isWeb=p.sourceUrl&&!p._existing; let bg=isWeb?'linear-gradient(135deg,#059669,#10b981)':'linear-gradient(135deg,#003f62,#0077b6)'; return`
${c}
${(p.brand||'').substring(0,10)}
${isWeb?'
🌐 web
':''}
`; } function esc(s){return s.replace(/'/g,"\\'")} window.phImg=function(b,src){return`
${src?'🌐':'📦'}
${(b||'').substring(0,8)}
`}; function previewSP(idx){ let p=window._spLastResults.filter(x=>!x._notfound)[idx]; if(!p||p._existing)return; let img=p.image&&p.image.startsWith('http')?`
`:`
${(p.brand||'?').charAt(0)}
${p._fromAI?'🤖 AI — Ảnh chưa xác minh':'🌐 Ảnh đang cập nhật'}
`; let specs=p.specs&&Object.keys(p.specs).length?`
⚙️ Thông số kỹ thuật
`+Object.entries(p.specs).map(([k,v])=>``).join('')+'
${k}${v}
':''; let feats=p.features&&p.features.length?`
✅ Tính năng nổi bật
`+p.features.map(f=>`
• ${f}
`).join('')+'
':''; let srcBadge=''; if(p.sourceUrl) srcBadge=`
Nguồn: ${p.source} ↗ ✓ Link xác minh
`; else if(p._fromAI) srcBadge=`
Nguồn: AI tổng hợp — Nên xác minh thông tin trước khi sử dụng
`; let verifyBtn=` Google ảnh`; let ov=document.createElement('div');ov.id='spPrev'; ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:260;display:flex;align-items:center;justify-content:center;padding:16px;backdrop-filter:blur(4px)'; ov.onclick=function(e){if(e.target===ov)ov.remove()}; ov.innerHTML=`
📋 Chi tiết sản phẩm
${img}
${p.sku?`
Mã: ${p.sku}
`:''}
${p.name}
${p.brand?`
${p.brand}${p.origin?' · '+p.origin:''}
`:''}
${p.price||'Liên hệ báo giá'}
${p.warranty?`
🛡️ ${p.warranty}
`:''}
${p.description?`
${p.description}
`:''}${specs}${feats}${srcBadge}
${p.sourceUrl?` Xem nguồn`:''}${verifyBtn}
`; document.body.appendChild(ov); } function doAdd(idx){ let p=window._spLastResults.filter(x=>!x._notfound)[idx]; if(!p||p._existing)return; let slug=(p.sku||p.name||'x').toLowerCase().replace(/[^a-z0-9]+/g,'-').substring(0,50)+'-ext'; let n={name:((p.sku?p.sku+' | ':'')+(p.brand||'Khác')+' | '+p.name),link:p.sourceUrl||'',image:p.image||'',price:p.price||'',priceNum:p.priceNum||0,cat:p.category||'Thiết bị khác',catSlug:'thiet-bi-gia-dung-nho',catIcon:'fa-box',subCat:'',subCatSlug:'',images:p.image?[p.image]:[],summary:(p.description||'')+' ['+(p.source||'')+']',desc:p.description||'',specs:p.specs||{},feats:p.features||[],sku:p.sku||'',video:'',model:(p.sku||'').replace(/[^A-Za-z0-9]/g,''),slug:slug,brand:p.brand||'Khác',_idx:''}; if(typeof D!=='undefined'){ if(typeof buildSearchIndex==='function')n._idx=buildSearchIndex(n); D.push(n); let sv=JSON.parse(localStorage.getItem('vai_external_products')||'[]'); sv.push(n);localStorage.setItem('vai_external_products',JSON.stringify(sv)); if(typeof buildCats==='function')buildCats(); if(typeof buildBrands==='function')buildBrands(); } let btns=document.querySelectorAll('#spResults [onclick*="previewSP"]'); if(btns[idx])btns[idx].outerHTML='✅ Đã thêm'; if(typeof showToast==='function')showToast('Đã thêm sản phẩm vào trang!'); } function loadExternalProducts(){ let sv=JSON.parse(localStorage.getItem('vai_external_products')||'[]'); if(!sv.length||typeof D==='undefined')return; sv.forEach(p=>{ if(D.some(d=>d.slug===p.slug||(d.sku&&d.sku===p.sku)))return; if(typeof buildSearchIndex==='function')p._idx=buildSearchIndex(p); D.push(p); }); if(typeof buildCats==='function')buildCats(); if(typeof buildBrands==='function')buildBrands(); }