bep40 commited on
Commit
5fddfbd
·
verified ·
1 Parent(s): b7ffac1

Search Plus v4: web search thật lấy ảnh+giá từ nguồn, không dùng AI bịa

Browse files
Files changed (1) hide show
  1. search-plus.js +141 -120
search-plus.js CHANGED
@@ -1,4 +1,4 @@
1
- // ===== SEARCH PLUS v3: Fix ảnh + hiển thị nguồn sản phẩm =====
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">AI tìm sản phẩm từ internet → xem chi tiết → thêm vào trang</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, khóa vân tay Samsung, máy hút mùi Malloca..." 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-robot"></i> Tìm AI</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 tên sản phẩm và bấm <strong>Tìm AI</strong></p>
 
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">AI đang tìm kiếm sản phẩm...</p></div>';
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
- if(!token){
58
- window._spLastResults=localSearch(query);
59
- results.innerHTML=renderSPResults(window._spLastResults);
60
- btn.disabled=false;btn.innerHTML='<i class="fas fa-robot"></i> Tìm AI';return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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à chuyên gia tìm kiếm sản phẩm. Trả về CHÍNH XÁC JSON array.
70
- QUAN TRỌNG về hình ảnh:
71
- - KHÔNG bịa URL ảnh. Nếu không chắc chắn URL ảnh, để trống "image":""
72
- - Chỉ dùng URL ảnh từ website chính hãng mà bạn BIẾT CHẮC tồn tại
73
- QUAN TRỌNG về nguồn:
74
- - Mỗi sản phẩm PHẢI có "source": tên website/nguồn lấy thông tin (VD: "malloca.com", "bosch-home.com.vn", "dienmayxanh.com")
75
- - "sourceUrl": link trang sản phẩm gốc nếu biết`},
76
- {role:'user',content:`Tìm 3-5 sản phẩm: "${query}". JSON:
77
- [{"sku":"mã SP chính xác","name":"tên đầy đủ tiếng Việt","brand":"thương hiệu","price":"giá VNĐ hoặc Liên hệ","priceNum":0,"category":"danh mục","specs":{"key":"value"},"features":["tính năng"],"image":"","description":"mô tả 2-3 câu","origin":"xuất xứ","warranty":"bảo hành","source":"tên nguồn","sourceUrl":"URL nguồn nếu biết"}]
78
- CHỈ JSON.`}
79
- ],max_tokens:2000,temperature:0.3
 
 
 
 
 
 
 
 
 
 
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
- if(m){try{products=JSON.parse(m[0])}catch(e){}}
87
 
88
- // Validate và fix ảnh: loại bỏ URL ảnh không hợp lệ
 
89
  products.forEach(p=>{
 
90
  if(p.image){
91
- // Loại URL ảnh rõ ràng là bịa (không có domain thật)
92
- if(!p.image.startsWith('http')||p.image.includes('example.com')||p.image.includes('placeholder')){
93
- p.image='';
 
94
  }
 
 
 
 
95
  }
96
- // Đảm bảo có source
97
- if(!p.source)p.source='AI tổng hợ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
- btn.disabled=false;btn.innerHTML='<i class="fas fa-robot"></i> Tìm AI';
 
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 (dữ liệu sẵn có)',sourceUrl:'',
123
- _existing:true
124
  }));
125
  }
126
- return [{_notfound:true,description:'Không tìm thấy. Thử từ khóa khác.'}];
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.map((p,i)=>{
132
- if(p._notfound)return '<div style="text-align:center;padding:30px;color:#64748b"><p>'+p.description+'</p></div>';
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;padding:8px><div style=font-size:1.5rem>📦</div><div style=font-size:.55rem;color:#94a3b8;margin-top:2px>${(p.brand||'SP').substring(0,6)}</div></div>'">
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.6rem;font-weight:900">${initial}</div>
144
- <div style="font-size:.55rem;opacity:.7;margin-top:2px">${(p.brand||'').substring(0,10)}</div>
145
  </div>`;
146
  }
147
 
148
- let specs='';
149
- if(p.specs&&Object.keys(p.specs).length){
150
- specs='<div style="margin-top:8px;display:flex;flex-wrap:wrap;gap:3px">'+Object.entries(p.specs).slice(0,6).map(([k,v])=>'<span style="padding:2px 7px;background:#f1f5f9;border-radius:5px;font-size:.65rem;color:#334155"><b>'+k+':</b> '+v+'</span>').join('')+'</div>';
151
- }
152
 
153
- let feats='';
154
- if(p.features&&p.features.length){
155
- feats='<div style="margin-top:5px;font-size:.68rem;color:#64748b">'+p.features.slice(0,3).map(f=>'✓ '+f).join(' · ')+'</div>';
156
- }
157
 
158
- // Nguồn sản phẩm - luôn hiển thị
159
- let sourceHtml='';
160
- if(p.source){
161
- let srcLink=p.sourceUrl?`<a href="${p.sourceUrl}" target="_blank" style="color:#0077b6;text-decoration:underline">${p.source}</a>`:p.source;
162
- sourceHtml=`<div style="margin-top:6px;font-size:.65rem;color:#94a3b8;display:flex;align-items:center;gap:4px"><i class="fas fa-link" style="font-size:.55rem"></i> Nguồn: ${srcLink}</div>`;
 
163
  }
164
 
165
- let statusBtn=p._existing
166
- ?'<span style="padding:6px 12px;background:#e2e8f0;color:#64748b;border-radius:8px;font-size:.73rem;font-weight:600">✅ Đã có trên trang</span>'
167
- :'<button onclick="previewBeforeAdd('+i+')" style="padding:6px 12px;background:#003f62;color:#fff;border:none;border-radius:8px;font-size:.73rem;font-weight:700;cursor:pointer;font-family:inherit;display:flex;align-items:center;gap:5px"><i class="fas fa-eye"></i> Xem & Thêm</button>';
168
 
169
- return `<div style="display:flex;gap:12px;padding:14px;border:1.5px solid #e2e8f0;border-radius:12px;margin-bottom:8px;transition:all .2s" onmouseover="this.style.borderColor='#003f62'" onmouseout="this.style.borderColor='#e2e8f0'">
170
  ${imgHtml}
171
  <div style="flex:1;min-width:0">
172
- ${p.sku?'<div style="font-size:.65rem;color:#94a3b8">SKU: <b>'+p.sku+'</b>'+(p.brand?' · '+p.brand:'')+'</div>':''}
173
  <div style="font-size:.85rem;font-weight:700;color:#0f172a;line-height:1.3">${p.name}</div>
174
- ${p.price?'<div style="font-size:1rem;font-weight:900;color:#003f62;margin-top:3px">'+p.price+'</div>':''}
175
- ${p.description?'<div style="font-size:.73rem;color:#475569;margin-top:4px;line-height:1.4">'+p.description+'</div>':''}
176
- ${specs}${feats}
177
- ${p.warranty?'<div style="margin-top:3px;font-size:.65rem;color:#059669">🛡️ '+p.warranty+'</div>':''}
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||p._notfound)return;
188
 
189
- let imgPreview='';
190
  if(p.image&&p.image.startsWith('http')){
191
- imgPreview=`<div style="text-align:center;margin-bottom:16px;padding:16px;background:#f8fafc;border-radius:12px">
192
- <img src="${p.image}" style="max-width:280px;max-height:280px;object-fit:contain;margin:0 auto;display:block;border-radius:8px" onerror="this.parentElement.innerHTML='<div style=font-size:3rem;color:#cbd5e1>📦</div><div style=font-size:.78rem;color:#94a3b8;margin-top:6px>Hình ảnh không khả dụng</div>'">
193
  </div>`;
194
  }else{
195
- imgPreview=`<div style="text-align:center;margin-bottom:16px;padding:24px;background:linear-gradient(135deg,#003f62,#0077b6);border-radius:12px;color:#fff">
196
  <div style="font-size:3rem">${(p.brand||p.name||'?').charAt(0).toUpperCase()}</div>
197
- <div style="font-size:.78rem;opacity:.7;margin-top:4px">${p.brand||'Sản phẩm'}</div>
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;color:#334155;margin-bottom:8px">⚙️ Thông số kỹ thuật</div>'+
204
- '<table style="width:100%;border-collapse:collapse">'+
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:14px"><div style="font-size:.78rem;font-weight:700;color:#334155;margin-bottom:8px">✅ Tính năng</div>'+
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
- // Nguồn
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 overlay=document.createElement('div');
223
- overlay.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)';
224
- overlay.onclick=function(e){if(e.target===overlay)overlay.remove()};
225
- overlay.innerHTML=`
226
- <div style="background:#fff;border-radius:20px;max-width:600px;width:100%;max-height:90vh;overflow-y:auto">
227
- <div style="background:linear-gradient(135deg,#003f62,#0077b6);color:#fff;padding:16px 20px;border-radius:20px 20px 0 0;display:flex;justify-content:space-between;align-items:center">
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
- ${imgPreview}
233
- <div style="text-align:center;margin-bottom:16px">
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.15rem;font-weight:800;color:#0f172a">${p.name}</div>
236
- ${p.brand?'<div style="font-size:.8rem;color:#64748b;margin-top:4px">'+p.brand+(p.origin?' · '+p.origin:'')+'</div>':''}
237
- ${p.price?'<div style="font-size:1.4rem;font-weight:900;color:#003f62;margin-top:10px">'+p.price+'</div>':''}
238
- ${p.warranty?'<div style="font-size:.75rem;color:#059669;margin-top:4px">🛡️ '+p.warranty+'</div>':''}
239
  </div>
240
- ${p.description?'<div style="padding:12px;background:#f8fafc;border-radius:10px;font-size:.8rem;color:#475569;line-height:1.6">'+p.description+'</div>':''}
241
  ${specs}${feats}${srcHtml}
242
- <div style="display:flex;gap:10px;margin-top:20px;justify-content:center;flex-wrap:wrap">
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;display:flex;align-items:center;gap:8px"><i class="fas fa-plus-circle"></i> ADD vào trang</button>
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(overlay);
249
  }
250
 
251
  function doAddExternal(idx){
252
- let p=window._spLastResults[idx];
253
- if(!p||p._existing||p._notfound)return;
254
- let slug=(p.sku||p.name||'').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/-+$/,'').substring(0,50)+'-ext';
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?' (Nguồn: '+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:6px 12px;background:#10b981;color:#fff;border-radius:8px;font-size:.73rem;font-weight:600">✅ Đã thêm</span>';
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