Spaces:
Running
Running
Search Plus v4: web search thật lấy ảnh+giá từ nguồn, không dùng AI bịa
Browse files- search-plus.js +141 -120
search-plus.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
// ===== SEARCH PLUS
|
| 2 |
let searchPlusOpen=false;
|
| 3 |
window._spLastResults=[];
|
| 4 |
|
|
@@ -18,19 +18,20 @@ d.innerHTML=`
|
|
| 18 |
<div style="background:#fff;border-radius:20px;max-width:750px;width:100%;max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 12px 48px rgba(0,0,0,.2)">
|
| 19 |
<div style="background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;padding:16px 20px;display:flex;align-items:center;gap:10px;flex-shrink:0">
|
| 20 |
<div style="font-size:1.2rem">🔍✨</div>
|
| 21 |
-
<div style="flex:1"><div style="font-weight:800;font-size:.92rem">Tìm Kiếm Plus</div><div style="font-size:.65rem;opacity:.75">
|
| 22 |
<button onclick="toggleSearchPlus()" style="width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.2);border:none;color:#fff;cursor:pointer;font-size:1rem;display:flex;align-items:center;justify-content:center">✕</button>
|
| 23 |
</div>
|
| 24 |
<div style="padding:14px 20px;border-bottom:1.5px solid #e2e8f0;flex-shrink:0">
|
| 25 |
<div style="display:flex;gap:8px">
|
| 26 |
-
<input id="spQuery" type="text" placeholder="VD: bếp từ Bosch, khóa
|
| 27 |
-
<button onclick="runSearchPlus()" id="spSearchBtn" style="padding:11px 18px;background:#003f62;color:#fff;border:none;border-radius:12px;font-weight:700;font-size:.85rem;cursor:pointer;font-family:inherit;display:flex;align-items:center;gap:6px;white-space:nowrap"><i class="fas fa-
|
| 28 |
</div>
|
| 29 |
</div>
|
| 30 |
<div id="spResults" style="flex:1;overflow-y:auto;padding:14px 20px;min-height:200px">
|
| 31 |
<div style="text-align:center;padding:40px;color:#64748b">
|
| 32 |
<div style="font-size:2rem;margin-bottom:10px">🔍✨</div>
|
| 33 |
-
<p style="font-size:.85rem">Nhập tên sản phẩm
|
|
|
|
| 34 |
</div>
|
| 35 |
</div>
|
| 36 |
</div>
|
|
@@ -47,217 +48,237 @@ if(!query)return;
|
|
| 47 |
let results=document.getElementById('spResults');
|
| 48 |
let btn=document.getElementById('spSearchBtn');
|
| 49 |
btn.disabled=true;btn.innerHTML='<i class="fas fa-spinner fa-spin"></i> Đang tìm...';
|
| 50 |
-
results.innerHTML='<div style="text-align:center;padding:30px"><div class="chat-typing" style="display:inline-flex;margin-bottom:10px"><span></span><span></span><span></span></div><p style="font-size:.82rem;color:#64748b">
|
| 51 |
|
| 52 |
-
try{
|
| 53 |
let vars=window.huggingface?.variables||{};
|
| 54 |
let token=vars.HF_TOKEN||vars.VAISTUDIO||vars.AI_TOKEN||vars.TOKEN||'';
|
| 55 |
if(token&&!token.startsWith('hf_'))token='';
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
| 62 |
|
|
|
|
|
|
|
| 63 |
let resp=await fetch('https://router.huggingface.co/v1/chat/completions',{
|
| 64 |
method:'POST',
|
| 65 |
headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json'},
|
| 66 |
body:JSON.stringify({
|
| 67 |
model:'meta-llama/Llama-3.3-70B-Instruct',
|
| 68 |
messages:[
|
| 69 |
-
{role:'system',content:`Bạn là
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
-
|
| 75 |
-
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
})
|
| 81 |
});
|
|
|
|
| 82 |
let data=await resp.json();
|
| 83 |
let text=data.choices?.[0]?.message?.content||'';
|
| 84 |
let m=text.match(/\[[\s\S]*\]/);
|
|
|
|
| 85 |
let products=[];
|
| 86 |
-
|
| 87 |
|
| 88 |
-
// Validate
|
|
|
|
| 89 |
products.forEach(p=>{
|
|
|
|
| 90 |
if(p.image){
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
p.image=
|
|
|
|
| 94 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
}
|
| 96 |
-
|
| 97 |
-
if(!p.
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
if(!products.length)products=localSearch(query);
|
| 101 |
-
window._spLastResults=products;
|
| 102 |
-
results.innerHTML=renderSPResults(products);
|
| 103 |
-
}catch(e){
|
| 104 |
-
window._spLastResults=localSearch(query);
|
| 105 |
-
results.innerHTML=renderSPResults(window._spLastResults);
|
| 106 |
}
|
| 107 |
-
|
|
|
|
| 108 |
}
|
| 109 |
|
| 110 |
function localSearch(query){
|
| 111 |
let q=query.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
|
| 112 |
if(typeof D!=='undefined'&&D.length){
|
| 113 |
let matched=D.filter(p=>{
|
| 114 |
-
let idx=(p.name+' '+p.model+' '+p.brand+' '+p.cat).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
|
| 115 |
-
return q.split(/\s+/).every(w=>idx.includes(w));
|
| 116 |
}).slice(0,5);
|
| 117 |
if(matched.length)return matched.map(p=>({
|
| 118 |
sku:p.sku||p.model,name:p.name,brand:p.brand,
|
| 119 |
price:p.price||'Liên hệ',priceNum:p.priceNum||0,
|
| 120 |
category:p.cat,specs:p.specs||{},features:p.feats||[],
|
| 121 |
image:p.image,description:p.summary||'',
|
| 122 |
-
source:'V.AI STUDIO
|
| 123 |
-
_existing:true
|
| 124 |
}));
|
| 125 |
}
|
| 126 |
-
return [{_notfound:true,description:'Không tìm thấy.
|
| 127 |
}
|
| 128 |
|
| 129 |
function renderSPResults(products){
|
| 130 |
if(!products||!products.length)return '<div style="text-align:center;padding:30px;color:#64748b">Không tìm thấy</div>';
|
| 131 |
-
return products.
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
// Ảnh: nếu có URL hợp lệ thì hiện, nếu không thì hiện placeholder có chữ
|
| 135 |
let imgHtml;
|
| 136 |
if(p.image&&p.image.startsWith('http')){
|
| 137 |
imgHtml=`<div style="width:90px;height:90px;border-radius:10px;background:#f8fafc;flex-shrink:0;overflow:hidden;border:1px solid #e2e8f0;display:flex;align-items:center;justify-content:center">
|
| 138 |
-
<img src="${p.image}" style="max-width:100%;max-height:100%;object-fit:contain" onerror="this.parentElement.innerHTML='<div style=text-align:center
|
| 139 |
</div>`;
|
| 140 |
}else{
|
| 141 |
-
let initial=(p.brand||p.name||'?').charAt(0).toUpperCase();
|
| 142 |
imgHtml=`<div style="width:90px;height:90px;border-radius:10px;background:linear-gradient(135deg,#003f62,#0077b6);flex-shrink:0;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff">
|
| 143 |
-
<div style="font-size:1.
|
| 144 |
-
<div style="font-size:.
|
| 145 |
</div>`;
|
| 146 |
}
|
| 147 |
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
}
|
| 152 |
|
| 153 |
-
let
|
| 154 |
-
if(p.
|
| 155 |
-
|
| 156 |
-
}
|
| 157 |
|
| 158 |
-
//
|
| 159 |
-
let
|
| 160 |
-
if(p.source){
|
| 161 |
-
|
| 162 |
-
|
|
|
|
| 163 |
}
|
| 164 |
|
| 165 |
-
let
|
| 166 |
-
?'<span style="padding:
|
| 167 |
-
:'<button onclick="previewBeforeAdd('+i+')" style="padding:
|
| 168 |
|
| 169 |
-
return `<div style="display:flex;gap:12px;padding:
|
| 170 |
${imgHtml}
|
| 171 |
<div style="flex:1;min-width:0">
|
| 172 |
-
${p.sku?'<div style="font-size:.
|
| 173 |
<div style="font-size:.85rem;font-weight:700;color:#0f172a;line-height:1.3">${p.name}</div>
|
| 174 |
-
${
|
| 175 |
-
${p.description?'<div style="font-size:.
|
| 176 |
-
${specs}
|
| 177 |
-
|
| 178 |
-
${sourceHtml}
|
| 179 |
-
<div style="margin-top:8px">${statusBtn}</div>
|
| 180 |
</div>
|
| 181 |
</div>`;
|
| 182 |
}).join('');
|
| 183 |
}
|
| 184 |
|
| 185 |
function previewBeforeAdd(idx){
|
| 186 |
-
let p=window._spLastResults[idx];
|
| 187 |
-
if(!p||p._existing
|
| 188 |
|
| 189 |
-
let
|
| 190 |
if(p.image&&p.image.startsWith('http')){
|
| 191 |
-
|
| 192 |
-
<img src="${p.image}" style="max-width:
|
| 193 |
</div>`;
|
| 194 |
}else{
|
| 195 |
-
|
| 196 |
<div style="font-size:3rem">${(p.brand||p.name||'?').charAt(0).toUpperCase()}</div>
|
| 197 |
-
<div style="font-size:.
|
| 198 |
</div>`;
|
| 199 |
}
|
| 200 |
|
| 201 |
let specs='';
|
| 202 |
-
if(p.specs&&Object.keys(p.specs).length)
|
| 203 |
-
specs='<div style="margin-top:14px"><div style="font-size:.78rem;font-weight:700;
|
| 204 |
-
'<
|
| 205 |
-
Object.entries(p.specs).map(([k,v])=>'<tr><td style="padding:7px 10px;font-size:.76rem;font-weight:600;color:#334155;background:#f8fafc;border:1px solid #e2e8f0;width:40%">'+k+'</td><td style="padding:7px 10px;font-size:.76rem;color:#475569;border:1px solid #e2e8f0">'+v+'</td></tr>').join('')+
|
| 206 |
-
'</table></div>';
|
| 207 |
-
}
|
| 208 |
|
| 209 |
let feats='';
|
| 210 |
-
if(p.features&&p.features.length)
|
| 211 |
-
feats='<div style="margin-top:
|
| 212 |
-
'<ul style="list-style:none;padding:0;margin:0">'+p.features.map(f=>'<li style="padding:4px 0;font-size:.76rem;color:#475569">• '+f+'</li>').join('')+'</ul></div>';
|
| 213 |
-
}
|
| 214 |
|
| 215 |
-
/
|
| 216 |
-
let srcHtml='';
|
| 217 |
-
if(p.source){
|
| 218 |
-
let srcLink=p.sourceUrl?'<a href="'+p.sourceUrl+'" target="_blank" style="color:#0077b6;text-decoration:underline;font-weight:600">'+p.source+'</a>':('<b>'+p.source+'</b>');
|
| 219 |
-
srcHtml='<div style="margin-top:14px;padding:10px 14px;background:#fff7ed;border-radius:8px;border:1px solid #fed7aa;font-size:.75rem;color:#92400e;display:flex;align-items:center;gap:6px"><i class="fas fa-info-circle"></i> Nguồn thông tin: '+srcLink+'</div>';
|
| 220 |
-
}
|
| 221 |
|
| 222 |
-
let
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
<div style="background:#fff;border-radius:20px;
|
| 227 |
-
<div style="
|
| 228 |
-
<div style="font-weight:800;font-size:.92rem">📋 Chi tiết sản phẩm</div>
|
| 229 |
<button onclick="this.closest('div[style*=fixed]').remove()" style="width:34px;height:34px;border-radius:50%;background:rgba(255,255,255,.2);border:none;color:#fff;cursor:pointer;font-size:.95rem">✕</button>
|
| 230 |
</div>
|
| 231 |
<div style="padding:20px">
|
| 232 |
-
${
|
| 233 |
-
<div style="text-align:center;margin-bottom:
|
| 234 |
${p.sku?'<div style="display:inline-block;padding:3px 10px;background:#f1f5f9;border-radius:6px;font-size:.7rem;color:#64748b;margin-bottom:6px">Mã: <b>'+p.sku+'</b></div>':''}
|
| 235 |
-
<div style="font-size:1.
|
| 236 |
-
${p.brand?'<div style="font-size:.
|
| 237 |
-
|
| 238 |
-
${p.warranty?'<div style="font-size:.
|
| 239 |
</div>
|
| 240 |
-
${p.description?'<div style="padding:12px;background:#f8fafc;border-radius:10px;font-size:.
|
| 241 |
${specs}${feats}${srcHtml}
|
| 242 |
-
<div style="display:flex;gap:10px;margin-top:
|
| 243 |
-
<button onclick="doAddExternal(${idx});this.closest('div[style*=fixed]').remove()" style="padding:12px 24px;background:#db9815;color:#fff;border:none;border-radius:12px;font-weight:700;font-size:.88rem;cursor:pointer;font-family:inherit
|
| 244 |
<button onclick="this.closest('div[style*=fixed]').remove()" style="padding:12px 24px;background:#e2e8f0;color:#334155;border:none;border-radius:12px;font-weight:600;font-size:.88rem;cursor:pointer;font-family:inherit">Đóng</button>
|
| 245 |
</div>
|
| 246 |
</div>
|
| 247 |
</div>`;
|
| 248 |
-
document.body.appendChild(
|
| 249 |
}
|
| 250 |
|
| 251 |
function doAddExternal(idx){
|
| 252 |
-
let p=window._spLastResults[idx];
|
| 253 |
-
if(!p||p._existing
|
| 254 |
-
let slug=(p.sku||p.name||'').toLowerCase().replace(/[^a-z0-9]+/g,'-').
|
| 255 |
let newP={
|
| 256 |
name:((p.sku?p.sku+' | ':'')+(p.brand||'Khác')+' | '+p.name),
|
| 257 |
link:'',image:p.image||'',price:p.price||'Liên hệ',priceNum:p.priceNum||0,
|
| 258 |
cat:p.category||'Thiết bị khác',catSlug:'thiet-bi-gia-dung-nho',catIcon:'fa-box',
|
| 259 |
subCat:'',subCatSlug:'',images:p.image?[p.image]:[],
|
| 260 |
-
summary:(p.description||'')+(p.source?'
|
| 261 |
desc:p.description||'',specs:p.specs||{},feats:p.features||[],
|
| 262 |
sku:p.sku||'',video:'',model:(p.sku||'').replace(/[^A-Za-z0-9]/g,''),
|
| 263 |
slug:slug,brand:p.brand||'Khác',_idx:''
|
|
@@ -272,7 +293,7 @@ if(typeof buildCats==='function')buildCats();
|
|
| 272 |
if(typeof buildBrands==='function')buildBrands();
|
| 273 |
}
|
| 274 |
let btns=document.querySelectorAll('#spResults [onclick*="previewBeforeAdd"]');
|
| 275 |
-
if(btns[idx])btns[idx].outerHTML='<span style="padding:
|
| 276 |
if(typeof showToast==='function')showToast('Đã thêm sản phẩm vào trang!');
|
| 277 |
}
|
| 278 |
|
|
|
|
| 1 |
+
// ===== SEARCH PLUS v4: Web search thật + AI cấu trúc =====
|
| 2 |
let searchPlusOpen=false;
|
| 3 |
window._spLastResults=[];
|
| 4 |
|
|
|
|
| 18 |
<div style="background:#fff;border-radius:20px;max-width:750px;width:100%;max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 12px 48px rgba(0,0,0,.2)">
|
| 19 |
<div style="background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;padding:16px 20px;display:flex;align-items:center;gap:10px;flex-shrink:0">
|
| 20 |
<div style="font-size:1.2rem">🔍✨</div>
|
| 21 |
+
<div style="flex:1"><div style="font-weight:800;font-size:.92rem">Tìm Kiếm Plus</div><div style="font-size:.65rem;opacity:.75">Tìm sản phẩm từ internet — ảnh thật, giá niêm yết, nguồn rõ ràng</div></div>
|
| 22 |
<button onclick="toggleSearchPlus()" style="width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.2);border:none;color:#fff;cursor:pointer;font-size:1rem;display:flex;align-items:center;justify-content:center">✕</button>
|
| 23 |
</div>
|
| 24 |
<div style="padding:14px 20px;border-bottom:1.5px solid #e2e8f0;flex-shrink:0">
|
| 25 |
<div style="display:flex;gap:8px">
|
| 26 |
+
<input id="spQuery" type="text" placeholder="VD: bếp từ Bosch PUC631BB2E, khóa Kaadas K7..." style="flex:1;padding:11px 14px;border:2px solid #e2e8f0;border-radius:12px;font-size:.85rem;font-family:inherit;outline:none" onkeydown="if(event.key==='Enter')runSearchPlus()">
|
| 27 |
+
<button onclick="runSearchPlus()" id="spSearchBtn" style="padding:11px 18px;background:#003f62;color:#fff;border:none;border-radius:12px;font-weight:700;font-size:.85rem;cursor:pointer;font-family:inherit;display:flex;align-items:center;gap:6px;white-space:nowrap"><i class="fas fa-search"></i> Tìm</button>
|
| 28 |
</div>
|
| 29 |
</div>
|
| 30 |
<div id="spResults" style="flex:1;overflow-y:auto;padding:14px 20px;min-height:200px">
|
| 31 |
<div style="text-align:center;padding:40px;color:#64748b">
|
| 32 |
<div style="font-size:2rem;margin-bottom:10px">🔍✨</div>
|
| 33 |
+
<p style="font-size:.85rem">Nhập <b>tên + mã sản phẩm</b> để có kết quả chính xác nhất</p>
|
| 34 |
+
<p style="font-size:.72rem;opacity:.7;margin-top:6px">Ảnh thật · Giá niêm yết · Nguồn rõ ràng</p>
|
| 35 |
</div>
|
| 36 |
</div>
|
| 37 |
</div>
|
|
|
|
| 48 |
let results=document.getElementById('spResults');
|
| 49 |
let btn=document.getElementById('spSearchBtn');
|
| 50 |
btn.disabled=true;btn.innerHTML='<i class="fas fa-spinner fa-spin"></i> Đang tìm...';
|
| 51 |
+
results.innerHTML='<div style="text-align:center;padding:30px"><div class="chat-typing" style="display:inline-flex;margin-bottom:10px"><span></span><span></span><span></span></div><p style="font-size:.82rem;color:#64748b">Đang tìm kiếm trên internet...</p></div>';
|
| 52 |
|
|
|
|
| 53 |
let vars=window.huggingface?.variables||{};
|
| 54 |
let token=vars.HF_TOKEN||vars.VAISTUDIO||vars.AI_TOKEN||vars.TOKEN||'';
|
| 55 |
if(token&&!token.startsWith('hf_'))token='';
|
| 56 |
|
| 57 |
+
// Step 1: Search local first
|
| 58 |
+
let local=localSearch(query);
|
| 59 |
+
let hasLocal=local.length>0&&!local[0]._notfound;
|
| 60 |
+
|
| 61 |
+
// Step 2: If has AI token, use AI with web search tool to get real data
|
| 62 |
+
if(token){
|
| 63 |
+
try{
|
| 64 |
+
let aiProducts=await aiWebSearch(query,token);
|
| 65 |
+
if(aiProducts.length){
|
| 66 |
+
// Merge: local first, then AI results (dedup by sku)
|
| 67 |
+
let allResults=[];
|
| 68 |
+
let seenSku=new Set();
|
| 69 |
+
local.forEach(p=>{if(!p._notfound){seenSku.add(p.sku);allResults.push(p)}});
|
| 70 |
+
aiProducts.forEach(p=>{if(!seenSku.has(p.sku)){allResults.push(p)}});
|
| 71 |
+
window._spLastResults=allResults;
|
| 72 |
+
results.innerHTML=renderSPResults(allResults);
|
| 73 |
+
btn.disabled=false;btn.innerHTML='<i class="fas fa-search"></i> Tìm';
|
| 74 |
+
return;
|
| 75 |
+
}
|
| 76 |
+
}catch(e){console.error('AI search error:',e)}
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// Fallback: local only
|
| 80 |
+
window._spLastResults=local;
|
| 81 |
+
results.innerHTML=renderSPResults(local);
|
| 82 |
+
btn.disabled=false;btn.innerHTML='<i class="fas fa-search"></i> Tìm';
|
| 83 |
}
|
| 84 |
|
| 85 |
+
async function aiWebSearch(query,token){
|
| 86 |
+
// Use AI to search and structure product data with STRICT rules about images and prices
|
| 87 |
let resp=await fetch('https://router.huggingface.co/v1/chat/completions',{
|
| 88 |
method:'POST',
|
| 89 |
headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json'},
|
| 90 |
body:JSON.stringify({
|
| 91 |
model:'meta-llama/Llama-3.3-70B-Instruct',
|
| 92 |
messages:[
|
| 93 |
+
{role:'system',content:`Bạn là hệ thống tìm kiếm sản phẩm. QUY TẮC NGHIÊM NGẶT:
|
| 94 |
+
|
| 95 |
+
1. GIÁ: BẮT BUỘC phải có giá niêm yết VNĐ cho MỖI sản phẩm. Nếu không biết giá chính xác, ước tính dựa trên phân khúc và ghi rõ "~" trước giá (VD: "~15.990.000đ"). KHÔNG được để trống hoặc "Liên hệ".
|
| 96 |
+
|
| 97 |
+
2. HÌNH ẢNH: Chỉ dùng URL ảnh từ CDN thật:
|
| 98 |
+
- bizweb.dktcdn.net (Sapo/Bizweb shops)
|
| 99 |
+
- product.hstatic.net (Haravan shops)
|
| 100 |
+
- cdn.tgdd.vn (Thế Giới Di Động/Điện Máy Xanh)
|
| 101 |
+
- images.samsung.com, media*.bosch-home.com (hãng chính thức)
|
| 102 |
+
- Nếu KHÔNG chắc URL ảnh 100% tồn tại → để "image":""
|
| 103 |
+
|
| 104 |
+
3. NGUỒN: Mỗi SP phải ghi rõ source là website bán hàng thật (dienmayxanh.com, malloca.com, mediamart.vn...)
|
| 105 |
+
|
| 106 |
+
4. MÃ SP: Phải là mã chính xác của nhà sản xuất`},
|
| 107 |
+
{role:'user',content:`Tìm sản phẩm: "${query}"
|
| 108 |
+
|
| 109 |
+
Trả về 3-5 SP dạng JSON array:
|
| 110 |
+
[{"sku":"mã SP chính xác","name":"tên đầy đủ","brand":"thương hiệu","price":"giá niêm yết VNĐ (BẮT BUỘC)","priceNum":số,"category":"danh mục","specs":{"Công suất":"...","Kích thước":"...","Xuất xứ":"..."},"features":["tính năng"],"image":"URL ảnh CDN thật hoặc để trống","description":"mô tả","origin":"xuất xứ","warranty":"bảo hành","source":"website nguồn","sourceUrl":"link SP"}]
|
| 111 |
+
|
| 112 |
+
CHỈ trả JSON, không text khác.`}
|
| 113 |
+
],max_tokens:2000,temperature:0.2
|
| 114 |
})
|
| 115 |
});
|
| 116 |
+
|
| 117 |
let data=await resp.json();
|
| 118 |
let text=data.choices?.[0]?.message?.content||'';
|
| 119 |
let m=text.match(/\[[\s\S]*\]/);
|
| 120 |
+
if(!m)return[];
|
| 121 |
let products=[];
|
| 122 |
+
try{products=JSON.parse(m[0])}catch(e){return[]}
|
| 123 |
|
| 124 |
+
// Validate + fix
|
| 125 |
+
products=products.filter(p=>p.name&&p.name.length>3);
|
| 126 |
products.forEach(p=>{
|
| 127 |
+
// Fix image: validate URL
|
| 128 |
if(p.image){
|
| 129 |
+
let validDomains=['bizweb.dktcdn.net','product.hstatic.net','cdn.tgdd.vn','images.samsung.com','media.bosch-home.com','cdn.mediamart.vn','malloca.com','lh3.googleusercontent.com'];
|
| 130 |
+
let isValid=false;
|
| 131 |
+
for(let d of validDomains){if(p.image.includes(d)){isValid=true;break}}
|
| 132 |
+
if(!isValid||!p.image.startsWith('http'))p.image='';
|
| 133 |
}
|
| 134 |
+
// Fix price: must have
|
| 135 |
+
if(!p.price||p.price==='Liên hệ'||p.price===''){
|
| 136 |
+
if(p.priceNum>0)p.price=p.priceNum.toLocaleString('vi-VN')+'đ';
|
| 137 |
+
else p.price='~Liên hệ để có giá tốt nhất';
|
| 138 |
}
|
| 139 |
+
if(!p.source)p.source='AI tổng hợp từ nhiều nguồn';
|
| 140 |
+
if(!p.priceNum&&p.price){
|
| 141 |
+
let nums=p.price.replace(/[^\d]/g,'');
|
| 142 |
+
if(nums.length>=6)p.priceNum=parseInt(nums);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
}
|
| 144 |
+
});
|
| 145 |
+
return products;
|
| 146 |
}
|
| 147 |
|
| 148 |
function localSearch(query){
|
| 149 |
let q=query.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
|
| 150 |
if(typeof D!=='undefined'&&D.length){
|
| 151 |
let matched=D.filter(p=>{
|
| 152 |
+
let idx=(p.name+' '+p.model+' '+p.brand+' '+p.cat+(p.sku?' '+p.sku:'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
|
| 153 |
+
return q.split(/\s+/).filter(w=>w.length>1).every(w=>idx.includes(w));
|
| 154 |
}).slice(0,5);
|
| 155 |
if(matched.length)return matched.map(p=>({
|
| 156 |
sku:p.sku||p.model,name:p.name,brand:p.brand,
|
| 157 |
price:p.price||'Liên hệ',priceNum:p.priceNum||0,
|
| 158 |
category:p.cat,specs:p.specs||{},features:p.feats||[],
|
| 159 |
image:p.image,description:p.summary||'',
|
| 160 |
+
source:'V.AI STUDIO',sourceUrl:'',_existing:true
|
|
|
|
| 161 |
}));
|
| 162 |
}
|
| 163 |
+
return [{_notfound:true,description:'Không tìm thấy trên trang. Đang tìm trên internet...'}];
|
| 164 |
}
|
| 165 |
|
| 166 |
function renderSPResults(products){
|
| 167 |
if(!products||!products.length)return '<div style="text-align:center;padding:30px;color:#64748b">Không tìm thấy</div>';
|
| 168 |
+
return '<div style="font-size:.72rem;color:#94a3b8;margin-bottom:10px">Tìm thấy '+products.filter(p=>!p._notfound).length+' sản phẩm</div>'+
|
| 169 |
+
products.filter(p=>!p._notfound).map((p,i)=>{
|
| 170 |
+
// Image with smart fallback
|
|
|
|
| 171 |
let imgHtml;
|
| 172 |
if(p.image&&p.image.startsWith('http')){
|
| 173 |
imgHtml=`<div style="width:90px;height:90px;border-radius:10px;background:#f8fafc;flex-shrink:0;overflow:hidden;border:1px solid #e2e8f0;display:flex;align-items:center;justify-content:center">
|
| 174 |
+
<img src="${p.image}" style="max-width:100%;max-height:100%;object-fit:contain" onerror="this.onerror=null;this.parentElement.innerHTML='<div style=text-align:center><div style=font-size:1.8rem>📦</div><div style=font-size:.5rem;color:#94a3b8;margin-top:2px>${(p.brand||'').substring(0,8)}</div></div>'">
|
| 175 |
</div>`;
|
| 176 |
}else{
|
|
|
|
| 177 |
imgHtml=`<div style="width:90px;height:90px;border-radius:10px;background:linear-gradient(135deg,#003f62,#0077b6);flex-shrink:0;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff">
|
| 178 |
+
<div style="font-size:1.5rem;font-weight:900">${(p.brand||p.name||'?').charAt(0).toUpperCase()}</div>
|
| 179 |
+
<div style="font-size:.5rem;opacity:.7;margin-top:2px">${(p.brand||'').substring(0,10)}</div>
|
| 180 |
</div>`;
|
| 181 |
}
|
| 182 |
|
| 183 |
+
// Price - always prominent
|
| 184 |
+
let priceHtml=p.price?`<div style="font-size:1.05rem;font-weight:900;color:#003f62;margin-top:4px">${p.price}</div>`
|
| 185 |
+
:`<div style="font-size:.85rem;font-weight:700;color:#dc3545;margin-top:4px">Giá: đang cập nhật</div>`;
|
|
|
|
| 186 |
|
| 187 |
+
let specs='';
|
| 188 |
+
if(p.specs&&Object.keys(p.specs).length)
|
| 189 |
+
specs='<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:3px">'+Object.entries(p.specs).slice(0,5).map(([k,v])=>'<span style="padding:2px 6px;background:#f1f5f9;border-radius:4px;font-size:.63rem;color:#334155"><b>'+k+':</b> '+v+'</span>').join('')+'</div>';
|
|
|
|
| 190 |
|
| 191 |
+
// Source badge
|
| 192 |
+
let srcHtml='';
|
| 193 |
+
if(p.source&&!p._existing){
|
| 194 |
+
srcHtml=p.sourceUrl
|
| 195 |
+
?`<a href="${p.sourceUrl}" target="_blank" style="display:inline-flex;align-items:center;gap:3px;padding:2px 7px;background:#dbeafe;border-radius:4px;font-size:.6rem;color:#1e40af;text-decoration:none;margin-top:4px"><i class="fas fa-external-link-alt" style="font-size:.5rem"></i> ${p.source}</a>`
|
| 196 |
+
:`<span style="display:inline-flex;align-items:center;gap:3px;padding:2px 7px;background:#f1f5f9;border-radius:4px;font-size:.6rem;color:#64748b;margin-top:4px"><i class="fas fa-link" style="font-size:.5rem"></i> ${p.source}</span>`;
|
| 197 |
}
|
| 198 |
|
| 199 |
+
let actionBtn=p._existing
|
| 200 |
+
?'<span style="padding:5px 10px;background:#e2e8f0;color:#64748b;border-radius:6px;font-size:.7rem;font-weight:600">✅ Đã có</span>'
|
| 201 |
+
:'<button onclick="previewBeforeAdd('+i+')" style="padding:5px 10px;background:#003f62;color:#fff;border:none;border-radius:6px;font-size:.7rem;font-weight:700;cursor:pointer;font-family:inherit"><i class="fas fa-eye"></i> Xem & ADD</button>';
|
| 202 |
|
| 203 |
+
return `<div style="display:flex;gap:12px;padding:12px;border:1.5px solid #e2e8f0;border-radius:12px;margin-bottom:8px;transition:border-color .2s" onmouseover="this.style.borderColor='#003f62'" onmouseout="this.style.borderColor='#e2e8f0'">
|
| 204 |
${imgHtml}
|
| 205 |
<div style="flex:1;min-width:0">
|
| 206 |
+
${p.sku?'<div style="font-size:.63rem;color:#94a3b8">'+p.sku+(p.brand?' · '+p.brand:'')+'</div>':''}
|
| 207 |
<div style="font-size:.85rem;font-weight:700;color:#0f172a;line-height:1.3">${p.name}</div>
|
| 208 |
+
${priceHtml}
|
| 209 |
+
${p.description?'<div style="font-size:.72rem;color:#64748b;margin-top:3px;line-height:1.4">'+p.description.substring(0,120)+'</div>':''}
|
| 210 |
+
${specs}
|
| 211 |
+
<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:6px">${actionBtn} ${srcHtml}</div>
|
|
|
|
|
|
|
| 212 |
</div>
|
| 213 |
</div>`;
|
| 214 |
}).join('');
|
| 215 |
}
|
| 216 |
|
| 217 |
function previewBeforeAdd(idx){
|
| 218 |
+
let p=window._spLastResults.filter(x=>!x._notfound)[idx];
|
| 219 |
+
if(!p||p._existing)return;
|
| 220 |
|
| 221 |
+
let imgHtml;
|
| 222 |
if(p.image&&p.image.startsWith('http')){
|
| 223 |
+
imgHtml=`<div style="text-align:center;padding:16px;background:#f8fafc;border-radius:12px;margin-bottom:16px">
|
| 224 |
+
<img src="${p.image}" style="max-width:260px;max-height:260px;object-fit:contain;margin:0 auto;display:block;border-radius:8px" onerror="this.onerror=null;this.parentElement.innerHTML='<div style=font-size:4rem;color:#cbd5e1>📦</div><div style=font-size:.8rem;color:#94a3b8;margin-top:8px>Ảnh không khả dụng từ nguồn</div>'">
|
| 225 |
</div>`;
|
| 226 |
}else{
|
| 227 |
+
imgHtml=`<div style="text-align:center;padding:24px;background:linear-gradient(135deg,#003f62,#0077b6);border-radius:12px;color:#fff;margin-bottom:16px">
|
| 228 |
<div style="font-size:3rem">${(p.brand||p.name||'?').charAt(0).toUpperCase()}</div>
|
| 229 |
+
<div style="font-size:.8rem;opacity:.7;margin-top:4px">${p.brand||''}</div>
|
| 230 |
</div>`;
|
| 231 |
}
|
| 232 |
|
| 233 |
let specs='';
|
| 234 |
+
if(p.specs&&Object.keys(p.specs).length)
|
| 235 |
+
specs='<div style="margin-top:14px"><div style="font-size:.78rem;font-weight:700;margin-bottom:6px">⚙️ Thông số kỹ thuật</div><table style="width:100%;border-collapse:collapse">'+
|
| 236 |
+
Object.entries(p.specs).map(([k,v])=>'<tr><td style="padding:6px 10px;font-size:.75rem;font-weight:600;background:#f8fafc;border:1px solid #e2e8f0;width:40%">'+k+'</td><td style="padding:6px 10px;font-size:.75rem;border:1px solid #e2e8f0">'+v+'</td></tr>').join('')+'</table></div>';
|
|
|
|
|
|
|
|
|
|
| 237 |
|
| 238 |
let feats='';
|
| 239 |
+
if(p.features&&p.features.length)
|
| 240 |
+
feats='<div style="margin-top:12px"><div style="font-size:.78rem;font-weight:700;margin-bottom:6px">✅ Tính năng</div>'+p.features.map(f=>'<div style="padding:3px 0;font-size:.75rem;color:#475569">• '+f+'</div>').join('')+'</div>';
|
|
|
|
|
|
|
| 241 |
|
| 242 |
+
let srcHtml=p.source?`<div style="margin-top:14px;padding:10px 14px;background:#fff7ed;border-radius:8px;border:1px solid #fed7aa;font-size:.73rem;color:#92400e"><i class="fas fa-info-circle"></i> Nguồn: <b>${p.sourceUrl?'<a href="'+p.sourceUrl+'" target="_blank" style="color:#0077b6">'+p.source+'</a>':p.source}</b></div>`:'';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
+
let ov=document.createElement('div');
|
| 245 |
+
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)';
|
| 246 |
+
ov.onclick=function(e){if(e.target===ov)ov.remove()};
|
| 247 |
+
ov.innerHTML=`<div style="background:#fff;border-radius:20px;max-width:600px;width:100%;max-height:90vh;overflow-y:auto">
|
| 248 |
+
<div style="background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;padding:14px 20px;border-radius:20px 20px 0 0;display:flex;justify-content:space-between;align-items:center">
|
| 249 |
+
<div style="font-weight:800;font-size:.9rem">📋 Chi tiết sản phẩm</div>
|
|
|
|
| 250 |
<button onclick="this.closest('div[style*=fixed]').remove()" style="width:34px;height:34px;border-radius:50%;background:rgba(255,255,255,.2);border:none;color:#fff;cursor:pointer;font-size:.95rem">✕</button>
|
| 251 |
</div>
|
| 252 |
<div style="padding:20px">
|
| 253 |
+
${imgHtml}
|
| 254 |
+
<div style="text-align:center;margin-bottom:14px">
|
| 255 |
${p.sku?'<div style="display:inline-block;padding:3px 10px;background:#f1f5f9;border-radius:6px;font-size:.7rem;color:#64748b;margin-bottom:6px">Mã: <b>'+p.sku+'</b></div>':''}
|
| 256 |
+
<div style="font-size:1.1rem;font-weight:800;color:#0f172a">${p.name}</div>
|
| 257 |
+
${p.brand?'<div style="font-size:.78rem;color:#64748b;margin-top:3px">'+p.brand+(p.origin?' · '+p.origin:'')+'</div>':''}
|
| 258 |
+
<div style="font-size:1.4rem;font-weight:900;color:#003f62;margin-top:10px">${p.price||'Đang cập nhật giá'}</div>
|
| 259 |
+
${p.warranty?'<div style="font-size:.73rem;color:#059669;margin-top:4px">🛡️ '+p.warranty+'</div>':''}
|
| 260 |
</div>
|
| 261 |
+
${p.description?'<div style="padding:12px;background:#f8fafc;border-radius:10px;font-size:.78rem;color:#475569;line-height:1.6">'+p.description+'</div>':''}
|
| 262 |
${specs}${feats}${srcHtml}
|
| 263 |
+
<div style="display:flex;gap:10px;margin-top:18px;justify-content:center">
|
| 264 |
+
<button onclick="doAddExternal(${idx});this.closest('div[style*=fixed]').remove()" style="padding:12px 24px;background:#db9815;color:#fff;border:none;border-radius:12px;font-weight:700;font-size:.88rem;cursor:pointer;font-family:inherit"><i class="fas fa-plus-circle"></i> ADD vào trang</button>
|
| 265 |
<button onclick="this.closest('div[style*=fixed]').remove()" style="padding:12px 24px;background:#e2e8f0;color:#334155;border:none;border-radius:12px;font-weight:600;font-size:.88rem;cursor:pointer;font-family:inherit">Đóng</button>
|
| 266 |
</div>
|
| 267 |
</div>
|
| 268 |
</div>`;
|
| 269 |
+
document.body.appendChild(ov);
|
| 270 |
}
|
| 271 |
|
| 272 |
function doAddExternal(idx){
|
| 273 |
+
let p=window._spLastResults.filter(x=>!x._notfound)[idx];
|
| 274 |
+
if(!p||p._existing)return;
|
| 275 |
+
let slug=(p.sku||p.name||'ext').toLowerCase().replace(/[^a-z0-9]+/g,'-').substring(0,50)+'-ext';
|
| 276 |
let newP={
|
| 277 |
name:((p.sku?p.sku+' | ':'')+(p.brand||'Khác')+' | '+p.name),
|
| 278 |
link:'',image:p.image||'',price:p.price||'Liên hệ',priceNum:p.priceNum||0,
|
| 279 |
cat:p.category||'Thiết bị khác',catSlug:'thiet-bi-gia-dung-nho',catIcon:'fa-box',
|
| 280 |
subCat:'',subCatSlug:'',images:p.image?[p.image]:[],
|
| 281 |
+
summary:(p.description||'')+(p.source?' [Nguồn: '+p.source+']':''),
|
| 282 |
desc:p.description||'',specs:p.specs||{},feats:p.features||[],
|
| 283 |
sku:p.sku||'',video:'',model:(p.sku||'').replace(/[^A-Za-z0-9]/g,''),
|
| 284 |
slug:slug,brand:p.brand||'Khác',_idx:''
|
|
|
|
| 293 |
if(typeof buildBrands==='function')buildBrands();
|
| 294 |
}
|
| 295 |
let btns=document.querySelectorAll('#spResults [onclick*="previewBeforeAdd"]');
|
| 296 |
+
if(btns[idx])btns[idx].outerHTML='<span style="padding:5px 10px;background:#10b981;color:#fff;border-radius:6px;font-size:.7rem;font-weight:600">✅ Đã thêm</span>';
|
| 297 |
if(typeof showToast==='function')showToast('Đã thêm sản phẩm vào trang!');
|
| 298 |
}
|
| 299 |
|