// rebuild-trigger: 1778762495 /** * V.AI STUDIO — ALL-IN-ONE Boot v18 * - Purge Malloca from BOTH: DMX + Hafele VN (hafele-vn.com) * - showDetail(index) navigation * - AI hook + context search */ (function(){ var _origFetch = window.fetch; var _D = function(){ return (typeof D !== 'undefined' && D && D.length) ? D : []; window._vaiGetD=_D; }; function norm(s){return(s||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'d').toLowerCase();} function fmt(n){if(!n||isNaN(n))return'LH';return Number(n).toLocaleString('vi-VN')+'đ';} var PRIORITY_BRANDS=['malloca','grob','canzy','eurogold','garis','demax']; var STOPWORDS='tìm tim giup giúp cho toi tôi anh chi chị em xin muon muốn cần can co có nao nào loai loại mua kiểu kieu nên nen duoc được mấy may cai cái xem hay hoac hoặc va và với voi the thế nay này kia đó do ấy ay bao lam làm sao gi gì vay vậy hỏi hoi ơi la là một mot nha nhé nhe ạ a ơ ừ uk ok vâng dạ da rồi roi duoc không khong ko sp san pham sản phẩm cua của'.split(' '); var CATEGORY_MAP={ 'bep tu':['bep tu','bep dien tu','bep cam ung','induction','bep tu doi','bep tu 2','bep tu 3','bep tu don','bep tu ba'], 'bep gas':['bep gas','bep ga','gas am','gas doi'], 'bep hong ngoai':['bep hong ngoai','hong ngoai','infrared'], 'bep ket hop':['bep gas ket hop','gas ket hop dien','bep ket hop','ket hop tu gas'], 'may hut mui':['may hut mui','hut mui','hood','hut khoi','khu mui','may hut khoi'], 'may rua bat':['may rua bat','rua bat','rua chen','may rua chen','dishwasher'], 'lo nuong':['lo nuong','oven','lo am tu','lo nuong am','lo nuong dien'], 'lo vi song':['lo vi song','vi song','microwave'], 'chau rua':['chau rua','bon rua','sink','chau rua chen','chau da','chau inox'], 'voi rua':['voi rua','faucet','voi rua chen','voi nong lanh','voi chen'], 'tu lanh':['tu lanh','tu ruou','wine','wine cooler'], 'may say chen':['may say chen','say chen','say bat','may say bat'], 'phu kien tu bep':['phu kien tu bep','phu kien bep','ke bat','gia bat','gia dia','tu do kho','ke gia vi','gia gia vi','thung rac','ke xoong','mam xoay','phu kien'], 'gia bat':['gia bat','gia dia','ke bat','gia nang ha'], 'tu do kho':['tu do kho','tu kho','he kho'], 'ray':['ray am','ray bi','ray giam chan','ray hop'], 'thung rac':['thung rac','thung gao'], 'khoa cua':['khoa cua','khoa van tay','khoa thong minh','khoa dien tu','smart lock'], 'tay nam':['tay nam','num tu','tay cam'], 'may giat':['may giat','may say','washing'], 'may loc nuoc':['may loc nuoc','loc nuoc','water filter'], 'tivi':['tivi','tv','television'], 'dieu hoa':['dieu hoa','may lanh','air conditioner'], 'noi chien':['noi chien khong dau','air fryer','chien khong dau'], 'may hut am':['may hut am','hut am','dehumidifier'] }; function detectCategory(text){ var n=norm(text);var bestCat='',bestLen=0; for(var cat in CATEGORY_MAP){var kws=CATEGORY_MAP[cat];for(var i=0;ibestLen){bestCat=cat;bestLen=kws[i].length;}}} return bestCat; } function detectBrand(text){ var n=norm(text); var allBrands=['malloca','grob','canzy','eurogold','garis','demax','boss','hafele','teka','bosch','panasonic','electrolux','samsung','lg','toshiba','sharp']; for(var i=0;i|tu|toi thieu|lon hon)\s*([\d.,]+)\s*(trieu|tr|t|m)?/);if(m2){min=parsePrice(m2[2],m2[3]);} var m3=n.match(/(tu|from)\s*([\d.,]+)\s*(trieu|tr|t|m)?\s*(den|toi|to|-)\s*([\d.,]+)\s*(trieu|tr|t|m)?/);if(m3){min=parsePrice(m3[2],m3[3]||m3[6]);max=parsePrice(m3[5],m3[6]);} var m4=n.match(/(tam|khoang|around|gang)\s*([\d.,]+)\s*(trieu|tr|t|m)?/);if(m4&&max===Infinity){var mid=parsePrice(m4[2],m4[3]);min=mid*0.7;max=mid*1.3;} if(max===Infinity&&min===0){var m5=n.match(/([\d.,]+)\s*(trieu|tr)\b/);if(m5){if(n.indexOf('duoi')!==-1||n.indexOf('thap')!==-1||n.indexOf('re')!==-1)max=parsePrice(m5[1],m5[2]);}} return{min:min,max:max}; } function parsePrice(numStr,unit){ var v=parseFloat((numStr||'').replace(/\./g,'').replace(',','.'));if(isNaN(v))return 0; unit=(unit||'').toLowerCase(); if(unit==='trieu'||unit==='tr'||unit==='t'||unit==='m')return v*1000000; if(v>1000)return v;if(v>0&&v<200)return v*1000000;return v; } function _vaiIsCabinetAccessory(p){ var brand=norm(p.brand||''); var cat=norm(p.cat||p.c||''); var name=norm(p.name||p.n||''); return (brand.indexOf('grob')!==-1||brand.indexOf('eurogold')!==-1||brand.indexOf('garis')!==-1) && (cat.indexOf('phu kien tu bep')!==-1||name.indexOf('gia ')!==-1||name.indexOf('ro ')!==-1||name.indexOf('dao')!==-1||name.indexOf('gia vi')!==-1||name.indexOf('thung')!==-1||name.indexOf('ke ')!==-1||name.indexOf('ray')!==-1); } function _vaiAccessoryDimQuery(text){ var n=norm(text||''); var hasDim=/(kich thuoc|phu bi|phù bì|lot long|lọt lòng|khoang tu|chieu rong tu|kt mat canh|mat canh|tu |tu bep|phu kien|gia vi|dao thot|thung rac|ke goc|ro)/i.test(n); var m=n.match(/(?:phu bi|lot long|khoang tu|chieu rong tu|tu|rong|r)?\s*(\d{3,4})\s*(?:mm)?/i); if(!hasDim||!m)return null; var target=parseInt(m[1],10);if(!target||target<100||target>1200)return null; var isOuter=/(phu bi|khoang tu|chieu rong tu|kt mat canh|mat canh|tu |tu bep|rong tu)/i.test(n) || !/(lot long|lọt lòng)/i.test(n); return {target:target,isOuter:isOuter,min:isOuter?target-50:target-8,max:isOuter?target:target+8}; } function _vaiAccessoryClearDims(p){ var vals=[];var specs=p.specs||{}; function addFrom(v){ String(v||'').replace(/(?:R|W|W=|R=)?\s*(\d{2,4})\s*(?:mm)?/gi,function(_,num){var x=parseInt(num,10);if(x>=100&&x<=1200&&[201,304].indexOf(x)===-1)vals.push(x);}); } if(specs&&typeof specs==='object'){ for(var k in specs){var kn=norm(k);if(kn.indexOf('kich thuoc san pham')!==-1||kn.indexOf('kich thuoc lot long')!==-1||kn.indexOf('lot long')!==-1){addFrom(specs[k]);}} } // Fallback only if no usable spec dimension exists. if(!vals.length){addFrom((p.name||p.n||'')+' '+(p.summary||p.sum||''));} var uniq=[];vals.forEach(function(v){if(uniq.indexOf(v)===-1)uniq.push(v);});return uniq; } function _vaiAccessoryTypeOk(p,text){ var n=norm(text||'');var pn=norm((p.name||p.n||'')+' '+(p.cat||p.c||'')); var groups=[ [['gia vi','chai lo','chai lọ'],['gia vi','chai lo','chai lọ']], [['dao thot','dao thớt'],['dao thot','dao thớt']], [['thung rac','thùng rác'],['thung rac','thùng rác']], [['thung gao','thùng gạo'],['thung gao','thùng gạo','gao']], [['gia bat','bát','bat dia','bát đĩa'],['gia bat','bát','bat dia','bát đĩa']], [['xoong noi','xoong nồi'],['xoong noi','xoong nồi']], [['khay chia','chia thia','thìa dĩa'],['khay chia','chia thia','thìa dĩa']], [['ke goc','góc','lien hoan','liên hoàn'],['ke goc','góc','lien hoan','liên hoàn']], [['tu kho','tủ kho','do kho','đồ khô'],['tu kho','tủ kho','do kho','đồ khô']] ]; for(var i=0;i=]*\s*(\d{3,4})\s*(?:mm)?/gi,function(_,num){var x=parseInt(num,10);if(x>=100&&x<=1200&&[201,304].indexOf(x)===-1)vals.push(x);});} if(specs&&typeof specs==='object'){ for(var k in specs){var kn=norm(k);if(kn.indexOf('chieu rong tu')!==-1||kn.indexOf('khoang tu')!==-1||kn.indexOf('lot long tu')!==-1||kn.indexOf('mat canh')!==-1){addFrom(specs[k]);}} } addFrom((p.name||p.n||'')+' '+(p.summary||p.sum||'')); var uniq=[];vals.forEach(function(v){if(uniq.indexOf(v)===-1)uniq.push(v);});return uniq; } function _vaiAccessoryDimMatch(p,dq){ if(!dq||!_vaiIsCabinetAccessory(p))return null; var dims=_vaiAccessoryClearDims(p);var noms=_vaiAccessoryNominalDims(p); // Garis-style mapping: nominal/phủ bì/khoang tủ/mặt cánh can equal target; R/W/quy cách is clear/real accessory size. var nominalHit=noms.filter(function(v){return Math.abs(v-dq.target)<=3;}); var clearHits=dims.filter(function(v){return v>dq.min&&vdq.target-80;});} if(!hits.length)return null; hits.sort(function(a,b){return Math.abs(a-dq.target)-Math.abs(b-dq.target);}); return {dims:dims,nominal:noms,hits:hits,nominalHit:nominalHit}; } function extractKeywords(query){ var n=norm(query);var words=n.split(/[\s,;.!?]+/).filter(function(w){return w.length>=2;}); var filtered=words.filter(function(w){return STOPWORDS.indexOf(w)===-1;}); return filtered.length>0?filtered:words.filter(function(w){return w.length>=2;}); } function compactNorm(s){return norm(s||'').replace(/[.\-_\s\/]+/g,'');} function productSearchText(p){return norm((p.name||p.n||'')+' '+(p.sku||'')+' '+(p.model||p.mod||'')+' '+(p.brand||'')+' '+(p.cat||p.c||''));} function productCodeText(p){return compactNorm((p.sku||'')+' '+(p.model||p.mod||''));} function isSpecificProductQuery(query,keywords){ var raw=(query||'').trim(); if(!raw)return false; var cat=detectCategory(raw), brand=detectBrand(raw), pr=detectPriceRange(raw), dim=_vaiAccessoryDimQuery(raw); // Broad category/price queries should still use contextual ranking. if(dim||pr.min>0||pr.max=3)return true; if(/[.\-_\/]/.test(t)&&compactNorm(t).length>=4)return true; } // If user types a concrete name fragment with 2+ meaningful non-category words, treat as product search. var k=(keywords||[]).filter(function(w){return w.length>=3&&w!==brand;}); if(cat){ var catW=[];(CATEGORY_MAP[cat]||[]).forEach(function(kw){kw.split(' ').forEach(function(w){if(w.length>=2)catW.push(w);});}); k=k.filter(function(w){return catW.indexOf(w)===-1;}); } return k.length>=2; } function directProductScore(p,query,keywords){ var qn=norm(query||'').trim();var qc=compactNorm(query||''); if(!qn||!qc)return 0; var nameN=norm(p.name||p.n||'');var nameC=compactNorm(p.name||p.n||''); var skuC=compactNorm(p.sku||'');var modelC=compactNorm(p.model||p.mod||''); var codeC=(skuC+' '+modelC).trim(); var score=0; if(skuC&&qc===skuC)score+=100000; if(modelC&&qc===modelC)score+=100000; if(skuC&&skuC.length>=3&&(qc.indexOf(skuC)!==-1||skuC.indexOf(qc)!==-1))score+=80000; if(modelC&&modelC.length>=3&&(qc.indexOf(modelC)!==-1||modelC.indexOf(qc)!==-1))score+=80000; if(nameC&&qc.length>=5&&nameC.indexOf(qc)!==-1)score+=60000; var useful=(keywords||[]).filter(function(w){return w.length>=2;}); if(useful.length){ var inName=0,inCode=0; for(var i=0;i0)score+=12000+inName*1500; } return score; } function passesHardFilters(p,text,cat,brand,priceRange,dimQuery){ var nameN=norm(p.name||p.n||'');var brandN=norm(p.brand||'');var catN=norm(p.cat||p.c||''); var price=p.priceNum||p.pn||0;if(!price)price=parseInt((p.price||p.p||'').toString().replace(/[^\d]/g,''))||0; if(dimQuery){var dm=_vaiAccessoryDimMatch(p,dimQuery);if(['grob','eurogold','garis'].every(function(b){return brandN.indexOf(b)===-1;}))return false;if(!_vaiAccessoryTypeOk(p,text)||!dm)return false;} if(cat){var catKws=CATEGORY_MAP[cat]||[];var inCat=false;for(var ci=0;ci0){if(pricepriceRange.max)return false;}else if(priceRange.max0){return false;} return true; } function searchProductsByContext(text,limit){ limit=limit||24;var data=_D();if(!data.length)return[]; var cat=detectCategory(text);var brand=detectBrand(text);var priceRange=detectPriceRange(text);var dimQuery=_vaiAccessoryDimQuery(text);var keywords=extractKeywords(text); // v17: If the user types a concrete product code/name, return exact/name matches first. // This fixes dropdown/AI search showing Grob products just because Grob had a large brand boost. if(isSpecificProductQuery(text,keywords)){ var direct=[]; for(var di=0;di0){var dprice=dp.priceNum||dp.pn||0;direct.push({p:dp,score:ds,price:dprice,idx:di});} } if(direct.length){direct.sort(function(a,b){return b.score-a.score||(a.price-b.price);});return direct.slice(0,limit);} } if(cat&&CATEGORY_MAP[cat]){var catW=[];CATEGORY_MAP[cat].forEach(function(kw){kw.split(' ').forEach(function(w){if(w.length>=2)catW.push(w);});});keywords=keywords.filter(function(w){return catW.indexOf(w)===-1;});} if(brand)keywords=keywords.filter(function(w){return w!==brand;}); keywords=keywords.filter(function(w){return!w.match(/^\d+$/);}); keywords=keywords.filter(function(w){return['trieu','tr','duoi','tren','den','tam','khoang','gia','tot','nhat','re','dep','chat','luong'].indexOf(w)===-1;}); var results=[]; for(var i=0;i0){if(pricepriceRange.max)continue;score+=10;}else if(priceRange.max=2)score+=kwMatched*8; if(kwNameMatched===keywords.length&&keywords.length>0)score+=70; if(kwCodeMatched>0)score+=90; // Only apply Grob/Eurogold/Garis boost for broad accessory/category queries, not for concrete name/code searches. if(!keywords.length||dimQuery||cat){if(_vaiIsCabinetAccessory(p)){if(brandN.indexOf('grob')!==-1)score+=60;else if(brandN.indexOf('eurogold')!==-1)score+=35;else if(brandN.indexOf('garis')!==-1)score+=15;}} for(var bi=0;bi0&&!cat&&!brand&&priceRange.max===Infinity&&priceRange.min===0&&!dimQuery&&kwMatched===0)continue; if(score>0){results.push({p:p,score:score,price:price,idx:i});} } results.sort(function(a,b){return b.score-a.score||(a.price-b.price);}); return results.slice(0,limit); } function detectCategories(text){ var n=norm(text||'');var cats=[]; for(var cat in CATEGORY_MAP){ var kws=CATEGORY_MAP[cat]; for(var i=0;i1&&cats.indexOf('phu kien tu bep')!==-1){ var explicit=/phu kien tu bep|phu kien bep/i.test(n); if(!explicit)cats=cats.filter(function(c){return c!=='phu kien tu bep';}); } return cats; } function productFullText(p){ var specs=''; if(p.specs&&typeof p.specs==='object'){ try{for(var k in p.specs){specs+=' '+k+' '+p.specs[k];}}catch(e){} } return norm((p.name||p.n||'')+' '+(p.cat||p.c||'')+' '+(p.brand||'')+' '+(p.summary||p.sum||'')+' '+(p.desc||'')+' '+(p.feats||[]).join(' ')+' '+specs); } function specPairsText(p){ var arr=[]; if(p.specs&&typeof p.specs==='object'){ try{for(var k in p.specs){arr.push({k:norm(k),v:norm(String(p.specs[k]||'')),raw:k+': '+p.specs[k]});}}catch(e){} } return arr; } function detectSpecConstraints(text){ var n=norm(text||'');var cs=[];var m; // Ống thoát / phi / Ø diameter, e.g. "ống thoát phi 150mm", "đường ống Ø150". m=n.match(/(?:ong\s*thoat|duong\s*ong|ong\s*khoi|phi|ø|d\s*=?)\D{0,25}(\d{2,3})\s*(?:mm)?/i) || n.match(/(\d{2,3})\s*(?:mm)?\D{0,20}(?:ong\s*thoat|duong\s*ong|phi|ø)/i); if(m)cs.push({type:'diameter',value:parseInt(m[1],10),label:'ống thoát Ø'+parseInt(m[1],10)}); // Công suất hút, supports >=/<= hints. m=n.match(/(?:cong\s*suat\s*hut|suc\s*hut|hut\s*manh|m3\/?h)\D{0,20}(\d{3,4})/i); if(m)cs.push({type:'suction',value:parseInt(m[1],10),op:(/duoi|nho hon|<=|toi da/i.test(n)?'<=':'>='),label:'công suất hút '+parseInt(m[1],10)+'m³/h'}); // Độ ồn. m=n.match(/(?:do\s*on|ồn|db)\D{0,16}(\d{2})/i); if(m)cs.push({type:'noise',value:parseInt(m[1],10),op:(/tren|lon hon|>=/i.test(n)?'>=':'<='),label:'độ ồn '+parseInt(m[1],10)+'dB'}); // Width in cm/mm: "rộng 70cm", "ngang 90cm". Do NOT add this approximate constraint for "dưới/trên 800mm" queries; maxdim handles those strictly. if(!/(duoi|nho hon|<|toi da|khong qua|tren|lon hon|>|toi thieu)\D{0,20}\d{2,4}\s*(cm|mm)?/i.test(n)){ m=n.match(/(?:rong|ngang|chieu\s*rong|kich\s*thuoc)\D{0,16}(\d{2,4})\s*(cm|mm)?/i); if(m){var val=parseInt(m[1],10);var unit=m[2]||'';if(unit==='cm'||val<200)val*=10; if(val>=300&&val<=1400)cs.push({type:'width',value:val,label:'rộng '+val+'mm'});} } // Generic product dimension: e.g. "chậu rửa dưới 800mm" means largest listed dimension must be < 800mm (790/780...), not equal 800. var dm=n.match(/(?:duoi|nho hon|<|toi da|khong qua)\s*(\d{2,4})\s*(cm|mm)?/i) || n.match(/(?:kich\s*thuoc|rong|ngang|dai|chieu\s*rong)\D{0,18}(?:duoi|nho hon|<|toi da|khong qua)\D{0,8}(\d{2,4})\s*(cm|mm)?/i); if(dm){var dv=parseInt(dm[1],10);var du=dm[2]||'';if(du==='cm'||dv<200)dv*=10;if(dv>=300&&dv<=2000)cs.push({type:'maxdim',op:'<',value:dv,role:(/dai|dài|rong|rộng|ngang|width/.test(n)?'width':(/sau|sâu|depth/.test(n)?'depth':(/cao|height/.test(n)?'height':(isAccessoryDimensionQuery(text)?'width':'max')))),label:((/dai|dài|rong|rộng|ngang|width/.test(n)?'chiều dài/ngang':(/sau|sâu|depth/.test(n)?'chiều sâu':(/cao|height/.test(n)?'chiều cao':'kích thước')))+' dưới '+dv+'mm')});} var dm2=n.match(/(?:tren|lon hon|>|toi thieu)\s*(\d{2,4})\s*(cm|mm)?/i); if(dm2){var dv2=parseInt(dm2[1],10);var du2=dm2[2]||'';if(du2==='cm'||dv2<200)dv2*=10;if(dv2>=300&&dv2<=2000)cs.push({type:'maxdim',op:'>',value:dv2,label:'kích thước trên '+dv2+'mm'});} var wm=n.match(/(?:duoi|nho hon|<|toi da|khong qua)\s*(\d{2,5})\s*w\b/i)||n.match(/(?:cong\s*suat|dien|dong\s*co|motor)\D{0,22}(?:duoi|nho hon|<|toi da|khong qua)\D{0,8}(\d{2,5})\s*w\b/i); if(wm){var wv=parseInt(wm[1],10);if(wv>0&&wv<=10000)cs.push({type:'powerw',op:'<',value:wv,label:'công suất dưới '+wv+'W'});} // Feature keywords as soft technical constraints. var featureMap=[ {re:/bldc|dc inverter|inverter/i,label:'động cơ BLDC/Inverter',terms:['bldc','inverter']}, {re:/cam ung|cảm ứng/i,label:'điều khiển cảm ứng',terms:['cam ung','cảm ứng']}, {re:/cu chi|vay tay|khong cham|không chạm/i,label:'điều khiển cử chỉ/không chạm',terms:['cu chi','khong cham','cử chỉ','không chạm']}, {re:/than hoat tinh|than hoạt tính/i,label:'than hoạt tính',terms:['than hoat tinh','than hoạt tính']}, {re:/say khi nong|sấy khí nóng|hot air/i,label:'sấy khí nóng',terms:['say khi nong','sấy khí nóng','hot air']}, {re:/uv|khang khuan|kháng khuẩn/i,label:'UV/kháng khuẩn',terms:['uv','khang khuan','kháng khuẩn']} ]; featureMap.forEach(function(f){if(f.re.test(n))cs.push({type:'feature',label:f.label,terms:f.terms});}); return cs; } function numberNearText(txt,patterns){ var hits=[]; patterns.forEach(function(re){var m;while((m=re.exec(txt))!==null){var v=parseFloat((m[1]||m[2]||'').replace(',','.'));if(!isNaN(v))hits.push(v);}}); return hits; } function productMatchesSpecConstraints(p,constraints){ if(!constraints||!constraints.length)return {ok:true,score:0,labels:[]}; var full=productFullText(p);var pairs=specPairsText(p);var score=0;var labels=[]; for(var i=0;i=c.value;}); if(ok){score+=45;labels.push(c.label);}else return {ok:false,score:0,labels:labels}; }else if(c.type==='noise'){ var nms=numberNearText(full,[/(?:do\s*on|ồn)\D{0,14}(\d{2})/ig,/(\d{2})\s*db/ig]); ok=nms.some(function(v){return c.op==='>='?v>=c.value:v<=c.value;}); if(ok){score+=35;labels.push(c.label);}else return {ok:false,score:0,labels:labels}; }else if(c.type==='width'){ var widths=numberNearText(full,[/(?:chieu\s*rong\s*san\s*pham|rong|ngang)\D{0,18}(\d{2,4})/ig]); ok=widths.some(function(v){if(v<200)v*=10;return Math.abs(v-c.value)<=30;}); if(ok){score+=30;labels.push(c.label);}else return {ok:false,score:0,labels:labels}; }else if(c.type==='maxdim'){ var dimObjs=(typeof extractProductDimensions==='function')?extractProductDimensions(p):[];var role=c.role||'max';var vals=dimObjs.filter(function(d){return role==='max'||d.role===role||d.role==='dim';}).map(function(d){return d.v;}); if(vals.length){var chosen=(role==='max')?Math.max.apply(null,vals):Math.min.apply(null,vals);ok=c.op==='<'?chosenc.value;} if(ok){score+=55;labels.push(c.label);}else return {ok:false,score:0,labels:labels}; }else if(c.type==='powerw'){ var wvals=[];full.replace(/(?:cong\s*suat|dien\s*nang|motor|dong\s*co)?\D{0,16}(\d{2,5})\s*w\b/ig,function(_,a){var v=parseInt(a,10);if(v>0&&v<=10000)wvals.push(v);}); ok=wvals.length?wvals.some(function(v){return c.op==='<'?vc.value;}):true; if(ok){score+=25;labels.push(c.label);}else return {ok:false,score:0,labels:labels}; }else if(c.type==='feature'){ ok=c.terms.some(function(t){return full.indexOf(norm(t))!==-1;}); if(ok){score+=22;labels.push(c.label);}else return {ok:false,score:0,labels:labels}; } } return {ok:true,score:score,labels:labels}; } function isValidCategoryProduct(p,cat){ var name=norm(p.name||p.n||'');var catN=norm(p.cat||p.c||'');var full=name+' '+catN; // Exclude accessories/cleaning consumables that mention appliance names but are not the appliance. var rejectCommon=/tay\s*rua|tẩy\s*rửa|dung\s*dich|dung\s*dịch|ve\s*sinh|vệ\s*sinh|nuoc\s*rua|nước\s*rửa|chai|lo\s*nuoc|lọ\s*nước|bo\s*ve\s*sinh|bộ\s*vệ\s*sinh/.test(full); if(cat==='bep tu'){ var nameOnly=norm(p.name||p.n||''); if(rejectCommon||/chao|chảo|noi\s|nồi\s|vi\s|vỉ\s|khay|mat\s*kinh|mặt\s*kính|dao\s*cao|khăn|khan/.test(full))return false; // Query bếp từ must not pull bếp gas just because a wrong category contains generic "bếp từ". if(/bep\s*gas|bếp\s*gas|bep\s*ga|bếp\s*ga|gas\s*2|gas\s*am|gas\s*âm/.test(nameOnly))return false; // Require product NAME itself to indicate induction/electric hob, not only category text. return /bep\s*(tu|dien\s*tu|cam\s*ung)|bếp\s*(từ|điện\s*từ|cảm\s*ứng)|induction/.test(nameOnly); } if(cat==='may hut mui'){ if(rejectCommon||/than\s*hoat\s*tinh\s*(thay|bo|bộ)|ong\s*thoat\s*(roi|rời)|phu\s*kien|phụ\s*kiện/.test(full))return false; return /may\s*hut|máy\s*hút|hut\s*mui|hút\s*mùi|hut\s*khoi|hút\s*khói|khu\s*mui|khử\s*mùi/.test(full); } if(cat==='chau rua'){ if(rejectCommon||/bo\s*xa|bộ\s*xả|ro\s*loc|rổ\s*lọc/.test(full))return false; return /chau\s*rua|chậu\s*rửa|bon\s*rua|bồn\s*rửa|sink/.test(full); } if(cat==='voi rua'){ if(rejectCommon||/day\s*cap|dây\s*cấp|loi\s*tron|lõi\s*trộn/.test(full))return false; return /voi\s*rua|vòi\s*rửa|faucet/.test(full); } return true; } function productDedupeKey(p){ var m=compactNorm(p.model||p.mod||''); if(m)return m; return compactNorm((p.name||p.n||'').replace(/\|.*$/,'')); } function searchProductsForCategory(text,cat,limit,extraConstraints,priceMax){ var data=_D();var out=[];var catKws=CATEGORY_MAP[cat]||[];var brand=detectBrand(text);var keywords=extractKeywords(text); // Remove category words so query terms like "bếp từ" don't penalize hoods in combo mode. var catW=[];catKws.forEach(function(kw){kw.split(' ').forEach(function(w){if(w.length>=2)catW.push(w);});}); keywords=keywords.filter(function(w){return catW.indexOf(w)===-1&&['duoi','tren','trieu','tr','tong','cong','combo','va','và'].indexOf(w)===-1&&!/^\d+$/.test(w);}); for(var i=0;ipriceMax)continue; var sm=productMatchesSpecConstraints(p,extraConstraints||[]);if(!sm.ok)continue; var score=30+sm.score; if(price>0)score+=Math.max(0,18-Math.floor(price/2000000)); if(cat==='bep tu'){ var explicitPortable=/de\s*ban|để\s*bàn|mini|don|đơn|portable/i.test(norm(text)); if(/de\s*ban|để\s*bàn|bep\s*tu\s*don|bếp\s*từ\s*đơn|mini/.test(nameN)&&!explicitPortable)score-=45; if(/am|âm|2\s*vung|2\s*vùng|doi|đôi|hai\s*vung|hai\s*vùng/.test(nameN))score+=28; if(/3\s*vung|3\s*vùng|4\s*vung|4\s*vùng/.test(nameN))score+=12; } if(cat==='may hut mui'){ if(/am\s*tu|âm\s*tủ|gan\s*tu|gắn\s*tủ|ap\s*tuong|áp\s*tường/.test(nameN))score+=15; if(/ecokitchen/.test(nameN))score+=4; } if(brandN.indexOf('malloca')!==-1)score+=12;else if(brandN.indexOf('grob')!==-1)score+=10;else if(brandN.indexOf('eurogold')!==-1)score+=7; keywords.forEach(function(kw){var ft=productFullText(p);if(nameN.indexOf(kw)!==-1)score+=12;else if(ft.indexOf(kw)!==-1)score+=5;}); out.push({p:p,score:score,price:price,labels:sm.labels,idx:i,cat:cat}); } out.sort(function(a,b){return b.score-a.score||(a.price-b.price);}); var dedup=[];var seen={}; for(var oi=0;oibudget)continue; items.push(r);rec(pos+1,items,total+pr,score+r.score);items.pop(); } } rec(0,[],0,0); combos.forEach(function(c){ var util=budget?Math.min(1,c.total/budget):0; // Prefer useful combos that utilize the budget reasonably, not just the cheapest possible pair. c.rank=c.score+util*120-(util<0.45?35:0); }); combos.sort(function(a,b){return b.rank-a.rank||b.total-a.total;});return combos.slice(0,limit||6); } function renderNaturalResults(nr,res){ var fmt=function(n){return (!n||isNaN(n))?'LH':Number(n).toLocaleString('vi-VN')+'đ';}; var h=''; h+='
'; h+='🧠 Tìm kiếm tự nhiên'; if(nr.categories&&nr.categories.length)h+='📂 '+nr.categories.join(' + ')+''; if(nr.budget&&nr.budget💰 Tổng ≤ '+fmt(nr.budget)+''; if(nr.constraints&&nr.constraints.length)h+=nr.constraints.map(function(c){return '⚙️ '+c.label+'';}).join(''); h+='
'; if(nr.combos&&nr.combos.length){ h+='
✅ Combo phù hợp ngân sách tổng
'; nr.combos.forEach(function(c,ci){ h+='
Combo '+(ci+1)+'Tổng: '+fmt(c.total)+'
'; h+='
'; c.items.forEach(function(r){var p=r.p;var idx=_D().indexOf(p);var img=p.image||p.img||p.i||'';h+='
';if(img)h+='
';h+='
'+r.cat+'
'+(p.name||p.n||'').substring(0,56)+'
'+(p.price||p.p||'LH')+'
';}); h+='
'; }); } if(nr.groups&&nr.groups.length){ nr.groups.forEach(function(g){ h+='
📌 '+g.cat+' ('+g.items.length+' SP)
'; h+='
'; g.items.slice(0,12).forEach(function(r){var p=r.p;var idx=_D().indexOf(p);var img=p.image||p.img||p.i||'';var specs=p.specs||{};var sk=Object.entries(specs).filter(function(kv){return /ống|ong|hút|hut|ồn|on|rộng|rong|đường|duong/i.test(kv[0]);}).slice(0,3);h+='
';if(img)h+='
';h+='
'+(p.name||p.n||'').substring(0,58)+'
'+(p.brand||'')+' | '+(p.sku||p.mod||'')+'
';if(r.labels&&r.labels.length)h+='
✓ '+r.labels.join(', ')+'
';if(sk.length){h+='
';sk.forEach(function(kv){h+=kv[0]+': '+kv[1]+'
';});h+='
';}h+='
'+(p.price||p.p||'LH')+'
';}); h+='
'; }); } if(!h||(!(nr.combos&&nr.combos.length)&&!(nr.groups&&nr.groups.some(function(g){return g.items.length;}))))h='❌ Chưa tìm thấy sản phẩm khớp đúng thông số/ngân sách trong dữ liệu hiện có. Anh/chị thử nới điều kiện hoặc nhắn Zalo 0981873395.'; res.innerHTML=h; } function extractProductDimensions(p){ var dims=[];var specs=p.specs||{}; function add(v,role,source){ v=parseInt(v,10);if(!v)return;if(v<200)v*=10; if(v>=250&&v<=2500&&[201,202,304,316,430,220,240,50,60].indexOf(v)===-1)dims.push({v:v,role:role||'dim',source:source||''}); } function parseDimString(s,role,source){ s=String(s||'').toLowerCase(); // Prefer full dimension groups: 790 x 500 x 220 mm s.replace(/(\d{2,4})\s*(?:x|×|\*)\s*(\d{2,4})(?:\s*(?:x|×|\*)\s*(\d{2,4}))?/ig,function(_,a,b,c){add(a,'width',source);add(b,'depth',source);if(c)add(c,'height',source);}); // Contextual single dimensions only; avoid random material/thickness/power numbers. s.replace(/(?:rộng|rong|ngang|width|w)\D{0,12}(\d{2,4})\s*(?:mm|cm)?/ig,function(_,a){add(a,'width',source);}); s.replace(/(?:dài|dai|length|l)\D{0,12}(\d{2,4})\s*(?:mm|cm)?/ig,function(_,a){add(a,'length',source);}); s.replace(/(?:sâu|sau|depth|d)\D{0,12}(\d{2,4})\s*(?:mm|cm)?/ig,function(_,a){add(a,'depth',source);}); s.replace(/(?:cao|height|h)\D{0,12}(\d{2,4})\s*(?:mm|cm)?/ig,function(_,a){add(a,'height',source);}); } if(specs&&typeof specs==='object'){ for(var k in specs){var kn=norm(k);if(/kich thuoc|kích thước|rong|rộng|ngang|dai|dài|sau|sâu|cao|size|dimension/.test(kn)){parseDimString(String(specs[k]||''),/rong|rộng|ngang/.test(kn)?'width':'dim',k);}} } parseDimString((p.name||p.n||'')+' '+(p.summary||p.sum||''),'dim','name'); var seen={},out=[];dims.forEach(function(d){var key=d.v+'-'+d.role;if(!seen[key]){seen[key]=1;out.push(d);}});return out; } function genericNumbersFromProduct(p){return extractProductDimensions(p).map(function(d){return d.v;});} function isAccessoryDimensionQuery(text){var n=norm(text||'');return /(gia vi|gia gia vi|ke gia vi|chai lo|dao thot|thung rac|gia bat|bat dia|xoong noi|tu kho|phu kien|ray)/.test(n);} function detectGenericMaxDimQuery(text){ var n=norm(text||'');var m=n.match(/(?:duoi|nho hon|<|toi da|khong qua)\s*(\d{2,4})\s*(cm|mm)?/i)||n.match(/(?:kich\s*thuoc|rong|ngang|dai|chieu\s*rong)\D{0,22}(?:duoi|nho hon|<|toi da|khong qua)\D{0,8}(\d{2,4})\s*(cm|mm)?/i); if(!m)return null;var v=parseInt(m[1],10);var u=m[2]||'';if(u==='cm'||v<200)v*=10;if(v>=300&&v<=2000)return {op:'<',value:v,role:(isAccessoryDimensionQuery(text)?'width':'max'),label:(isAccessoryDimensionQuery(text)?'kích thước tủ/rộng dưới ':'kích thước dưới ')+v+'mm'};return null; } function detectDimensionRoleQuery(text){ var n=norm(text||''); var role='max'; if(/\b(dai|dài|length|ngang|rong|rộng|width)\b/.test(n))role='width'; else if(/\b(sau|sâu|depth)\b/.test(n))role='depth'; else if(/\b(cao|height)\b/.test(n))role='height'; var m=n.match(/(?:dai|dài|length|ngang|rong|rộng|width|sau|sâu|depth|cao|height)\D{0,20}(?:duoi|nho hon|<|toi da|khong qua)\D{0,8}(\d{2,4})\s*(cm|mm)?/i)||n.match(/(?:duoi|nho hon|<|toi da|khong qua)\D{0,8}(\d{2,4})\s*(cm|mm)?\D{0,20}(?:dai|dài|length|ngang|rong|rộng|width|sau|sâu|depth|cao|height)/i); if(!m)return null;var v=parseInt(m[1],10);var u=m[2]||'';if(u==='cm'||v<200)v*=10;if(v>=250&&v<=2500)return {op:'<',value:v,role:role,label:(role==='width'?'chiều dài/ngang':role==='depth'?'chiều sâu':role==='height'?'chiều cao':'kích thước')+' dưới '+v+'mm'};return null; } function detectPowerWQuery(text){ var n=norm(text||'');var m=n.match(/(?:duoi|nho hon|<|toi da|khong qua)\s*(\d{2,5})\s*w\b/i)||n.match(/(?:cong\s*suat|dien|dong\s*co|motor)\D{0,22}(?:duoi|nho hon|<|toi da|khong qua)\D{0,8}(\d{2,5})\s*w\b/i); if(!m)return null;var v=parseInt(m[1],10);if(v>0&&v<=10000)return {op:'<',value:v,label:'công suất dưới '+v+'W'};return null; } function productPowerWatts(p){ var full=productFullText(p);var vals=[];full.replace(/(?:cong\s*suat|dien\s*nang|motor|dong\s*co|động\s*cơ)?\D{0,16}(\d{2,5})\s*w\b/ig,function(_,a){var v=parseInt(a,10);if(v>0&&v<=10000)vals.push(v);});return vals; } function fallbackTechnicalSearch(text,limit){ limit=limit||24;var cats=detectCategories(text);if(!cats.length){var dc=detectCategory(text);if(dc)cats=[dc];} var roleDim=detectDimensionRoleQuery(text);var md=roleDim||detectGenericMaxDimQuery(text);var pw=detectPowerWQuery(text);if(!md&&!pw)return null; var out=[];var data=_D(); for(var i=0;i=2&&(budgetbestScore){best=p;bestScore=sc;} } return bestScore>=25000?best:null; } function technicalIntent(text){ var n=norm(text||'');var intents=[]; var map=[['kich_thuoc',/(kich thuoc|rộng|rong|ngang|dai|dài|sau|sâu|cao|lo da|lỗ đá|cat da|cắt đá|lap dat|lắp đặt)/],['cong_suat',/(cong suat|w\b|kw|dien nang|điện năng|motor|dong co|động cơ|hut m3|m3\/h|suc hut)/],['tinh_nang',/(tinh nang|tính năng|co gi|có gì|uu diem|ưu điểm|inverter|bldc|cam ung|cảm ứng|khu mui|khử mùi|say|sấy|uv)/],['bao_hanh',/(bao hanh|bảo hành)/],['gia',/(gia|giá|bao nhieu|bao nhiêu)/]]; map.forEach(function(x){if(x[1].test(n))intents.push(x[0]);}); return intents; } function specEntries(p){var arr=[];var specs=p.specs||{};if(specs&&typeof specs==='object'){for(var k in specs){if(k&&specs[k])arr.push({k:k,v:String(specs[k])});}}return arr;} function pickSpecs(p,intents){ var entries=specEntries(p);var out=[];var nints=intents.join(' '); function hit(k,v){var s=norm(k+' '+v);if(nints.indexOf('kich_thuoc')!==-1&&/(kich thuoc|rong|ngang|dai|sau|cao|lo da|cat da|size|dimension)/.test(s))return true;if(nints.indexOf('cong_suat')!==-1&&/(cong suat|w\b|kw|dien|motor|dong co|hut|m3|db|on|ồn)/.test(s))return true;if(nints.indexOf('bao_hanh')!==-1&&/bao hanh/.test(s))return true;if(nints.indexOf('tinh_nang')!==-1&&/(tinh nang|inverter|bldc|cam ung|uv|say|khu|filter|loc|điều khiển|dieu khien)/.test(s))return true;return false;} entries.forEach(function(e){if(hit(e.k,e.v))out.push(e);}); if(!out.length&&entries.length)out=entries.slice(0,8);return out.slice(0,12); } function vaiTechnicalAnswer(query){ var p=findProductForQuestion(query);if(!p)return null;var intents=technicalIntent(query);if(!intents.length&&detectCategory(query))return null; var specs=pickSpecs(p,intents);var dims=(typeof extractProductDimensions==='function')?extractProductDimensions(p):[];var feats=(p.feats||[]).slice(0,8);var html=''; html+='
'; html+='
🧑‍🔧 Tư vấn kỹ thuật theo dữ liệu sản phẩm
'; html+='
'+(p.name||p.n||'')+'
🏷 '+(p.brand||'')+' | 🔖 '+(p.sku||p.mod||p.model||'')+' | 💰 '+(p.price||p.p||'LH')+'
'; if(specs.length){html+='
Thông số liên quan câu hỏi:
    ';specs.forEach(function(e){html+='
  • '+e.k+': '+e.v+'
  • ';});html+='
';} if(dims.length){html+='
📐 Kích thước đã nhận diện: '+dims.slice(0,8).map(function(d){return d.role+' '+d.v+'mm';}).join(', ')+'
';} if(feats.length){html+='
Tính năng: '+feats.join(' • ')+'
';} html+='
Em chỉ trả lời theo dữ liệu đang có; nếu thiếu kích thước lỗ đá/công suất cụ thể, nên xác nhận lại catalogue/hãng trước khi thi công.
'; html+='
'; var res={html:html,product:p};return res; } function vaiTechnicalTextAnswer(query){ var ans=vaiTechnicalAnswer(query);if(!ans||!ans.product)return ''; var p=ans.product;var specs=pickSpecs(p,technicalIntent(query));var lines=[]; lines.push('🧑‍🔧 Em kiểm tra theo dữ liệu sản phẩm: '+(p.name||p.n||'')); lines.push('🏷 '+(p.brand||'')+' | 🔖 '+(p.sku||p.mod||p.model||'')+' | 💰 '+(p.price||p.p||'LH')); if(specs.length){lines.push('📋 Thông số liên quan:');specs.slice(0,8).forEach(function(e){lines.push('• '+e.k+': '+e.v);});} var feats=(p.feats||[]).slice(0,6);if(feats.length){lines.push('✨ Tính năng: '+feats.join(' • '));} lines.push('Lưu ý: em chỉ dùng dữ liệu đang có, nếu thiếu thông số thi công cần xác nhận lại catalogue/hãng.'); return lines.join(' '); } window._vaiTechnicalTextAnswer=vaiTechnicalTextAnswer; window._vaiTechnicalAnswer=vaiTechnicalAnswer; window._vaiNaturalSearch=naturalSearch; window._vaiRenderNaturalResults=renderNaturalResults; window._vaiSearchContext=searchProductsByContext; window._vaiDetectCategory=detectCategory; window._vaiDetectBrand=detectBrand; window._vaiDetectPrice=detectPriceRange; // === NAVIGATE TO PRODUCT (showDetail takes INDEX) === window._vaiGoProduct=function(slug){ if(!slug)return; var data=_D();if(!data.length)return; var idx=-1; for(var i=0;i=0&&typeof showDetail==='function'){ try{showDetail(idx);window.scrollTo({top:0,behavior:'smooth'});return;}catch(e){} } window.location.href='/san-pham/'+slug+'/index.html'; }; // === HOOK FETCH === var _lastProduct=null; var _conversationHistory=[]; var VISION_MODEL='meta-llama/Llama-4-Scout-17B-16E-Instruct'; window.fetch=function(url,options){ if(url&&typeof url==='string'&&url.indexOf('router.huggingface.co')!==-1&&options&&options.body){ try{ var body=JSON.parse(options.body); if(body.messages&&Array.isArray(body.messages)){ var userMsg=body.messages.filter(function(m){return m.role==='user';}).pop(); if(!userMsg)return _origFetch.apply(this,arguments); var userText=typeof userMsg.content==='string'?userMsg.content:(userMsg.content&&userMsg.content[0]?userMsg.content[0].text:''); var data=_D();var product=null; if(data.length){ var q=norm(userText).replace(/[.\-_ ]/g,''); for(var i=0;i3&&q.indexOf(sku)!==-1){product=p;break;}} if(!product){var qn=norm(userText);var qkw=extractKeywords(userText);var best=null,bestScore=0;for(var j=0;jbestScore){best=p2;bestScore=sc;}}if(best&&bestScore>=40000){product=best;}} } if(product){_lastProduct=product;}else if(_lastProduct){product=_lastProduct;} var contextProducts=[]; var cat=detectCategory(userText);var brand=detectBrand(userText);var priceRange=detectPriceRange(userText);var dimQuery=_vaiAccessoryDimQuery(userText); var isContextQuery=dimQuery||cat||(brand&&!product)||(priceRange.max0); if(isContextQuery||!product){contextProducts=searchProductsByContext(userText,8);} if(product&&/thiết kế|kiểu dáng|màu sắc|design|phong cách|chất liệu/i.test(userText)){ var imgV=product.image||product.img||product.i||''; if(imgV){body.model=VISION_MODEL;var lu=body.messages[body.messages.length-1];if(lu&&lu.role==='user'&&typeof lu.content==='string')lu.content=[{type:'text',text:lu.content},{type:'image_url',image_url:{url:imgV}}];} } var strict='\n\n[QUY TẮC TUYỆT ĐỐI]:\n1. CHỈ trả lời dựa trên [DỮ LIỆU] bên dưới. KHÔNG bịa thông số/giá.\n2. Nếu không có data → "Em chưa có thông tin SP này, anh/chị liên hệ Zalo 0981873395".\n3. LUÔN đề cập TÊN SẢN PHẨM + GIÁ cụ thể.\n4. Gạch đầu dòng, ngắn gọn.\n5. Khi có nhiều SP: so sánh ưu/nhược.\n6. Cuối câu: gợi ý SP phù hợp nhất.\n7. Với phụ kiện tủ bếp Garis/Grob/Eurogold: học theo cấu trúc Garis. Các mã/khoang tủ/chiều rộng tủ/phủ bì/KT mặt cánh là kích thước tủ danh nghĩa; quy cách R/W hoặc kích thước sản phẩm là kích thước lọt lòng/thực của phụ kiện. Nếu khách hỏi phủ bì/tủ 300mm thì chọn mã có tủ danh nghĩa 300mm hoặc lọt lòng nhỏ hơn 300mm, thường lớn hơn 250mm. Ưu tiên Grob trước, sau đó Eurogold; Garis chỉ dùng làm mẫu tham chiếu cấu trúc. Khi khách hỏi kích thước phụ kiện tủ bếp thì KHÔNG lấy Hafele nếu có Grob/Eurogold/Garis phù hợp.'; if(product&&!isContextQuery){ var specs=product.specs||{};var ss='';if(typeof specs==='object')for(var sk2 in specs)ss+=sk2+':'+specs[sk2]+'; '; var feats=(product.feats||[]).slice(0,10).join('; '); strict+='\n\n[DỮ LIỆU SP]:\nTên: '+(product.name||product.n)+'\nGiá: '+(product.price||product.p||'LH')+'\nBrand: '+(product.brand||'')+'\nMã: '+(product.sku||product.mod||''); if(ss)strict+='\nThông số: '+ss;if(feats)strict+='\nTính năng: '+feats; if(product.summary||product.sum)strict+='\nMô tả: '+(product.summary||product.sum||'').substring(0,500); } if(contextProducts.length>0){ strict+='\n\n[DANH SÁCH SP PHÙ HỢP'; if(cat)strict+=' | Loại:'+cat;if(brand)strict+=' | Brand:'+brand; if(priceRange.max=2)return contextResults.map(function(r){var p=r.p;return{name:p.name||p.n||'',slug:p.slug||'',price:p.price||p.p||'',image:p.image||p.img||p.i||'',model:p.model||p.mod||p.sku||'',brand:p.brand||''};}); var data=_D();if(data.length){ var n=norm(botText);var found=[];var seen={}; for(var i=0;i3&&n.indexOf(sku)!==-1){seen[p.slug]=true;found.push({name:p.name||p.n||'',slug:p.slug,price:p.price||p.p||'',image:p.image||p.img||p.i||'',model:p.model||p.mod||'',brand:p.brand||''});}} if(found.length)return found; var botResults=searchProductsByContext(botText,limit); if(botResults.length>=1)return botResults.map(function(r){var p=r.p;return{name:p.name||p.n||'',slug:p.slug||'',price:p.price||p.p||'',image:p.image||p.img||p.i||'',model:p.model||p.mod||p.sku||'',brand:p.brand||''};}); if(_conversationHistory.length){var histResults=searchProductsByContext(_conversationHistory.slice(-3).join(' '),limit);if(histResults.length>=1)return histResults.map(function(r){var p=r.p;return{name:p.name||p.n||'',slug:p.slug||'',price:p.price||p.p||'',image:p.image||p.img||p.i||'',model:p.model||p.mod||p.sku||'',brand:p.brand||''};});} } return[]; } function createCardsHTML(products){ if(!products||!products.length)return''; var h='
'; products.forEach(function(p){ h+='
'; h+='
'; if(p.image)h+=''; h+='
'+(p.name||'').substring(0,35)+'
'; if(p.model)h+='
'+(p.model||'').substring(0,20)+'
'; h+='
'+(p.price||'LH')+'
'; h+='
'; }); h+='
';return h; } function injectLoop(){ var chatBody=document.querySelector('.chat-body');if(!chatBody)return; var lastUserMsg='';var userMsgs=chatBody.querySelectorAll('.chat-msg.user'); if(userMsgs.length)lastUserMsg=userMsgs[userMsgs.length-1].textContent||''; var botMsgs=chatBody.querySelectorAll('.chat-msg.bot'); for(var i=0;i10)_conversationHistory.shift();} var cards=getCards(text,lastUserMsg,6); if(cards.length){var div=document.createElement('div');div.innerHTML=createCardsHTML(cards);msg.appendChild(div.firstChild);} } chatBody.querySelectorAll('.vai-card-add:not([data-b])').forEach(function(btn){ btn.setAttribute('data-b','1'); btn.addEventListener('click',function(e){e.preventDefault();e.stopPropagation();var slug=this.getAttribute('data-slug');if(!slug)return;var self=this;var data=_D();for(var i=0;i=2)showDD(this.value.trim());}); document.addEventListener('click',function(e){if(!dropdown.contains(e.target)&&e.target!==searchInput)dropdown.style.display='none';}); function showDD(query){ var data=_D();if(!data.length){dropdown.style.display='none';return;} var results=searchProductsByContext(query,15); if(results.length<3){var kw=extractKeywords(query);var seen={};results.forEach(function(r){seen[r.p.slug]=true;});for(var i=0;i0){seen[p.slug]=true;results.push({p:p,score:score});}}} if(!results.length){dropdown.style.display='none';return;} var html='';results.forEach(function(r){var p=r.p;var name=p.name||p.n||'';var price=p.price||p.p||'LH';var img=p.image||p.img||p.i||'';var slug=p.slug||'';var brand2=p.brand||''; html+='
'; if(img)html+=''; html+='
'+name.substring(0,55)+'
'+brand2+'
'; html+='
'+price+'
'; }); dropdown.innerHTML=html;dropdown.style.display='block'; dropdown.querySelectorAll('.vai-dd-item').forEach(function(item){item.addEventListener('click',function(){var sl=this.getAttribute('data-slug');if(sl)window._vaiGoProduct(sl);dropdown.style.display='none';});}); } } // ============================================================ // PHẦN 4: PURGE MALLOCA FROM ALL EXTERNAL SOURCES (DMX + Hafele VN) // ============================================================ function cleanUrl(u){try{return u.split('?')[0].split('#')[0]}catch(e){return u}} function fixImg(src){if(!src)return '';src=src.trim();if(src.indexOf('//')===0)src='https:'+src;return src.split('?')[0];} function mapProduct(p,defaultBrand){ var link=cleanUrl(p.l||'');var image=fixImg(p.i||''); var images=(p.imgs||[]).map(fixImg).filter(Boolean); if(image&&images.indexOf(image)===-1)images.unshift(image); if(!image&&images.length)image=images[0]; return{name:p.n,link:link,image:image,price:p.p,priceNum:p.pn,cat:p.c,catSlug:p.cs,catIcon:p.ci,subCat:p.sub_c||'',subCatSlug:p.sub_cs||'',images:images,summary:p.sum||'',desc:p.desc||'',specs:p.specs||{},feats:p.feats||[],sku:p.sku||'',video:p.vid||'',model:p.mod||'',slug:p.slug||'',brand:p.brand||defaultBrand,_src:defaultBrand,_idx:(p.sku||'')+' '+(p.brand||'').toLowerCase()+' '+(p.n||'').toLowerCase()}; } function isMallocaProduct(p){ var check=((p.n||p.name||'')+' '+(p.brand||'')+' '+(p.sku||'')+' '+(p.mod||'')+' '+(p.model||'')).toLowerCase(); return check.indexOf('malloca')!==-1; } // PURGE Malloca from DMX + Hafele VN sources function purgeMallocaExternal(){ if(typeof D==='undefined'||!D||!D.length)return; var before=D.length;var newD=[]; for(var i=0;i0)console.log('[Boot v18] PURGED '+removed+' Malloca products from external sources (DMX+HafeleVN). Remaining:',D.length); } function updateStats(){ var st=document.getElementById('statTotal');if(st)st.textContent=D.length; var sc=document.getElementById('statCats');if(sc){var cats={};D.forEach(function(p){cats[p.catSlug]=true});sc.textContent=Object.keys(cats).length;} } function loadSource(jsonFile,brand,label,filterFn){ return _origFetch(jsonFile).then(function(r){return r.json()}).then(function(data){ var existingSlugs={};D.forEach(function(p){if(p.slug)existingSlugs[p.slug]=true;}); var added=0,skipped=0; data.forEach(function(p){ if(filterFn&&filterFn(p)){skipped++;return;} var mapped=mapProduct(p,brand); if(!existingSlugs[mapped.slug]){ if(typeof buildSearchIndex==='function')mapped._idx=buildSearchIndex(mapped); D.push(mapped);existingSlugs[mapped.slug]=true;added++; } }); console.log('['+label+'] Added '+added+' (skipped '+skipped+'). Total:'+D.length); return added; }); } // ============================================================ // PHẦN 5: BOOT // ============================================================ var _ts='?_='+Date.now(); var c=document.createElement('script');c.src='crawled-data.js'+_ts; c.onload=function(){console.log('[Boot v18] crawled-data loaded');}; document.head.appendChild(c); function loadHelper(src){ var s=document.createElement('script');s.src=src+_ts;s.defer=true; if(document.body)document.body.appendChild(s); else document.addEventListener('DOMContentLoaded',function(){document.body.appendChild(s);}); } loadHelper('qr-payment.js'); loadHelper('quote-ui.js'); loadHelper('order-store.js'); loadHelper('epo-image-fix.js'); loadHelper('order-button.js'); loadHelper('zalo-feed.js'); loadHelper('shorts-subtitles.js'); loadHelper('ancuong-shorts.js'); loadHelper('custom-shorts.js'); loadHelper('smart-kitchen-shorts.js'); var _checkInterval=setInterval(function(){ if(typeof D!=='undefined'&&D.length>0&&typeof init==='function'){ clearInterval(_checkInterval); console.log('[Boot v18] Main data:',D.length); // PURGE Malloca from crawled-data (Hafele VN source) FIRST purgeMallocaExternal(); // Then load DMX + Garis + Konox (also filtering Malloca during load) loadSource('products_dmx_slugs.json'+_ts,'Điện Máy Xanh','DMX',isMallocaProduct) .then(function(){return loadSource('products_garis_slugs.json'+_ts,'Garis','Garis');}) .then(function(){return loadSource('products_konox_slugs.json'+_ts,'Konox','Konox');}) .then(function(){ // Second purge pass — catch any Malloca that slipped through purgeMallocaExternal(); updateStats(); if(typeof init==='function')init(); }) .catch(function(e){console.warn('[Boot v18] Error:',e);purgeMallocaExternal();updateStats();if(typeof init==='function')init();}); } },300); setTimeout(function(){clearInterval(_checkInterval);},30000); function initChatAndSearch(){ setInterval(injectLoop,800); var chatBody=document.querySelector('.chat-body'); if(chatBody){var mo=new MutationObserver(function(){setTimeout(injectLoop,300);});mo.observe(chatBody,{childList:true,subtree:true});} else{var retryObs=setInterval(function(){var cb=document.querySelector('.chat-body');if(cb){clearInterval(retryObs);var mo2=new MutationObserver(function(){setTimeout(injectLoop,300);});mo2.observe(cb,{childList:true,subtree:true});}},2000);setTimeout(function(){clearInterval(retryObs);},60000);} var ddTimer=setInterval(function(){if(_D().length>100){clearInterval(ddTimer);initSearchDropdown();}},2000); setTimeout(function(){clearInterval(ddTimer);initSearchDropdown();},15000); } if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',initChatAndSearch);else initChatAndSearch(); console.log('✅ Boot v18: purge Malloca from DMX+HafeleVN + AI hook + navigation'); })(); // ===== AUTO-NOTIFY ZALO BOT ON NEW DEPLOY ===== // Auto-detects the current Space commit SHA from HF API. Any web commit becomes a new update_id. // deploy-notify.json can override message/enabled, but no longer needs manual version bumps. (function(){ var BOT='https://bep40-vaistudio-zalo-bot.hf.space'; var KEY='vai_notify_ver'; var CFG='deploy-notify.json?_=' + Date.now(); var API='https://huggingface.co/api/spaces/bep40/V.AISTUDIO?_=' + Date.now(); function keepAlive(){try{fetch(BOT+'/webhook').catch(function(){});}catch(e){}} function notify(ver,msg){ if(!ver){keepAlive();return;} ver=String(ver); var last=localStorage.getItem(KEY)||''; if(last===ver){keepAlive();return;} localStorage.setItem(KEY,ver); fetch(BOT+'/hf-webhook',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({update_id:ver,event:{action:'update',update_id:ver,version:ver,scope:msg},repo:{type:'space',name:'bep40/V.AISTUDIO'}})}).catch(function(){}); console.log('[Notify] Space update '+ver+' → notify requested'); } try{ Promise.all([ fetch(CFG,{cache:'no-store'}).then(function(r){return r.ok?r.json():{};}).catch(function(){return{};}), fetch(API,{cache:'no-store'}).then(function(r){return r.ok?r.json():{};}).catch(function(){return{};}) ]).then(function(arr){ var cfg=arr[0]||{}, api=arr[1]||{}; if(cfg.enabled===false){keepAlive();return;} var sha=api.sha||api.lastModified||cfg.version||('web-'+Date.now()); var ver='space-'+String(sha).slice(0,12); var msg=cfg.message||('🌐 V.AI STUDIO vừa được cập nhật trên web.\n✅ Nội dung, sản phẩm hoặc tính năng mới đã sẵn sàng.\n👉 Mở V.AI STUDIO để xem ngay!'); notify(ver,msg); }).catch(function(){keepAlive();}); }catch(e){keepAlive();} })();