Upload ai-search-fix.js
Browse files- ai-search-fix.js +216 -0
ai-search-fix.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* FIX: AI Search Button - doAISearch()
|
| 3 |
+
* =====================================
|
| 4 |
+
* Root cause: doAISearch() in index.html depended on window._vaiSearchContext
|
| 5 |
+
* from search-plus-boot.js?v=27 which DOES NOT EXIST in the repo (404).
|
| 6 |
+
*
|
| 7 |
+
* Fix: Rewrite doAISearch() to use window.aiSearch() from ai-search.js which is
|
| 8 |
+
* properly loaded and contains full NL search logic.
|
| 9 |
+
*
|
| 10 |
+
* Additional fix: Handle multi-product search (comma/semicolon separated codes).
|
| 11 |
+
*/
|
| 12 |
+
|
| 13 |
+
window.doAISearch = function() {
|
| 14 |
+
var input = document.getElementById('aiSearch');
|
| 15 |
+
var resultsDiv = document.getElementById('aiResults');
|
| 16 |
+
if (!input || !resultsDiv) return;
|
| 17 |
+
|
| 18 |
+
var query = input.value.trim();
|
| 19 |
+
if (!query || query.length < 2) {
|
| 20 |
+
resultsDiv.style.display = 'block';
|
| 21 |
+
resultsDiv.innerHTML = '💡 Nhập mã SP hoặc mô tả: "bếp từ Grob dưới 10tr", "máy hút mùi Malloca"';
|
| 22 |
+
return;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
resultsDiv.style.display = 'block';
|
| 26 |
+
resultsDiv.innerHTML = '⏳ Đang tìm...';
|
| 27 |
+
|
| 28 |
+
// Multi-product search: if query contains comma/semicolon, search by product codes
|
| 29 |
+
var queries = query.split(/[,;]+/).map(function(c) { return c.trim(); }).filter(function(c) { return c.length >= 2; });
|
| 30 |
+
|
| 31 |
+
if (queries.length > 1) {
|
| 32 |
+
// Multi-code search mode
|
| 33 |
+
var allResults = [];
|
| 34 |
+
var normFn = function(s) { return String(s || '').toLowerCase().replace(/[.\- _\/]/g, '').replace(/đ/g, 'd'); };
|
| 35 |
+
|
| 36 |
+
queries.forEach(function(q) {
|
| 37 |
+
var qNorm = normFn(q);
|
| 38 |
+
for (var i = 0; i < D.length && allResults.length < 30; i++) {
|
| 39 |
+
var p = D[i];
|
| 40 |
+
if (!p) continue;
|
| 41 |
+
var fields = [(p.sku || ''), (p.mod || ''), (p.model || ''), (p.slug || '')].map(normFn);
|
| 42 |
+
if (fields.some(function(f) {
|
| 43 |
+
return f === qNorm || (f && qNorm.length >= 3 && (f.indexOf(qNorm) !== -1 || qNorm.indexOf(f) !== -1));
|
| 44 |
+
})) {
|
| 45 |
+
allResults.push(p);
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
if (allResults.length > 0) {
|
| 51 |
+
_renderMultiCodeResults(allResults, resultsDiv, query);
|
| 52 |
+
return;
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
if (typeof window.aiSearch === 'function' && typeof window.D !== 'undefined' && D.length > 0) {
|
| 57 |
+
var aiToken = (window.huggingface && window.huggingface.variables && window.huggingface.variables.VAISTUDIO) || '';
|
| 58 |
+
window.aiSearch(query, D, aiToken).then(function(aiResult) {
|
| 59 |
+
if (!aiResult || (!aiResult.results.length && !aiResult.aiAnswer)) {
|
| 60 |
+
_fallbackAISearch(query, resultsDiv);
|
| 61 |
+
return;
|
| 62 |
+
}
|
| 63 |
+
_renderAIResultsToDiv(aiResult, resultsDiv, query);
|
| 64 |
+
}).catch(function(err) {
|
| 65 |
+
_fallbackAISearch(query, resultsDiv);
|
| 66 |
+
console.error('AI Search error:', err);
|
| 67 |
+
});
|
| 68 |
+
} else {
|
| 69 |
+
_fallbackAISearch(query, resultsDiv);
|
| 70 |
+
}
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
// Render results for multi-product code search
|
| 74 |
+
function _renderMultiCodeResults(products, container, query) {
|
| 75 |
+
var html = '<div style="font-size:.78rem;color:#64748b;margin-bottom:10px"><i class="fas fa-search" style="margin-right:4px"></i><strong>' + products.length + '</strong> sản phẩm tìm được cho "<strong>' + query + '</strong>"</div>';
|
| 76 |
+
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px">';
|
| 77 |
+
|
| 78 |
+
products.forEach(function(p, i) {
|
| 79 |
+
var idx = window.D ? window.D.indexOf(p) : -1;
|
| 80 |
+
var brandColor = p.brand === 'Eurogold' ? '#c8102e' : (p.brand === 'Grob' ? '#2e7d32' : '#003f62');
|
| 81 |
+
var imgSrc = p.image || p.i || '';
|
| 82 |
+
var priceStr = p.price || (p.pn ? p.pn : 'Liên hệ');
|
| 83 |
+
var catStr = p.cat || '';
|
| 84 |
+
var catIcon = p.catIcon || 'fa-tag';
|
| 85 |
+
|
| 86 |
+
html += '<div class="pc fade" onclick="showDetail(' + idx + ')" style="cursor:pointer;transition-delay:' + Math.min(i * 20, 300) + 'ms">' +
|
| 87 |
+
'<div class="pi" style="height:150px"><img src="' + imgSrc + '" alt="' + p.name + '" loading="lazy" onerror="this.style.display=\'none\'">' +
|
| 88 |
+
'<span class="pi-badge"><i class="fas ' + catIcon + '"></i> ' + catStr + '</span>' +
|
| 89 |
+
'<span style="position:absolute;top:8px;right:8px;background:' + brandColor + ';color:#fff;padding:3px 7px;border-radius:5px;font-size:.55rem;font-weight:700">' + (p.brand || '') + '</span></div>' +
|
| 90 |
+
'<div class="pb" style="padding:10px"><div style="font-size:.62rem;font-weight:600;color:' + brandColor + ';letter-spacing:.5px;margin-bottom:3px">' + (p.sku || p.mod || p.model || '') + '</div>' +
|
| 91 |
+
'<div class="pn" style="font-size:.76rem">' + p.name + '</div>' +
|
| 92 |
+
'<div class="pf" style="margin-top:6px"><span class="pp" style="font-size:.92rem">' + priceStr + '</span><i class="fas fa-chevron-right"></i></div></div></div>';
|
| 93 |
+
});
|
| 94 |
+
|
| 95 |
+
html += '</div>';
|
| 96 |
+
container.innerHTML = html;
|
| 97 |
+
container.style.display = 'block';
|
| 98 |
+
|
| 99 |
+
if (window.requestAnimationFrame) {
|
| 100 |
+
requestAnimationFrame(function() {
|
| 101 |
+
var fadeEls = container.querySelectorAll('.fade');
|
| 102 |
+
for (var j = 0; j < fadeEls.length; j++) {
|
| 103 |
+
fadeEls[j].classList.add('vis');
|
| 104 |
+
}
|
| 105 |
+
});
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
function _renderAIResultsToDiv(aiResult, container, query) {
|
| 110 |
+
var badges = '';
|
| 111 |
+
if (aiResult.categories && aiResult.categories.length > 0) {
|
| 112 |
+
badges += '<span style="display:inline-block;background:rgba(0,63,98,.08);color:#003f62;padding:4px 10px;border-radius:20px;font-size:.7rem;font-weight:600;margin-right:6px;margin-bottom:6px"><i class="fas fa-layer-group" style="margin-right:4px"></i>' + aiResult.categories.map(function(c) { return c.replace(/_/g, ' '); }).join(', ') + '</span>';
|
| 113 |
+
}
|
| 114 |
+
if (aiResult.constraints && aiResult.constraints.length > 0) {
|
| 115 |
+
aiResult.constraints.forEach(function(c) {
|
| 116 |
+
badges += '<span style="display:inline-block;background:rgba(219,152,21,.15);color:#8a6d0e;padding:4px 10px;border-radius:20px;font-size:.7rem;font-weight:600;margin-right:6px;margin-bottom:6px"><i class="fas fa-sliders" style="margin-right:4px"></i>' + c.label + '</span>';
|
| 117 |
+
});
|
| 118 |
+
}
|
| 119 |
+
if (aiResult.budget && aiResult.budget > 0) {
|
| 120 |
+
badges += '<span style="display:inline-block;background:rgba(46,125,50,.1);color:#2e7d32;padding:4px 10px;border-radius:20px;font-size:.7rem;font-weight:600;margin-right:6px;margin-bottom:6px"><i class="fas fa-wallet" style="margin-right:4px"></i>Tối đa ' + (aiResult.budget / 1000000).toFixed(0) + ' triệu</span>';
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
var aiHtml = '';
|
| 124 |
+
if (aiResult.aiAnswer) {
|
| 125 |
+
aiHtml = '<div style="background:linear-gradient(135deg,rgba(0,63,98,.04),rgba(219,152,21,.06));border-radius:16px;padding:16px;margin-bottom:16px;border:2px solid #e2e8f0"><div style="display:flex;align-items:center;gap:8px;margin-bottom:10px"><div style="width:32px;height:32px;border-radius:50%;background:#003f62;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.8rem"><i class="fas fa-robot"></i></div><span style="font-weight:700;color:#003f62;font-size:.85rem">V.AI STUDIO trả lời</span></div><div style="font-size:.85rem;color:#0f172a;line-height:1.7">' + aiResult.aiAnswer + '</div></div>';
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
var comboHtml = '';
|
| 129 |
+
if (aiResult.combos && aiResult.combos.length > 0) {
|
| 130 |
+
comboHtml = '<div style="background:linear-gradient(135deg,rgba(219,152,21,.06),rgba(46,125,50,.04));border-radius:16px;padding:16px;margin-bottom:16px;border:2px solid rgba(219,152,21,.2)"><div style="display:flex;align-items:center;gap:8px;margin-bottom:12px"><div style="width:32px;height:32px;border-radius:50%;background:#db9815;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.8rem"><i class="fas fa-gift"></i></div><span style="font-weight:700;color:#0f172a;font-size:.85rem">Gợi ý combo trong ngân sách</span></div>';
|
| 131 |
+
aiResult.combos.forEach(function(combo, i) {
|
| 132 |
+
var totalStr = combo.total > 0 ? (Number(combo.total).toLocaleString('vi-VN').replace(/,/g, '.') + 'đ') : 'Liên hệ';
|
| 133 |
+
comboHtml += '<div style="background:#fff;border-radius:12px;padding:12px;margin-bottom:8px;border:1px solid #e2e8f0"><div style="font-weight:600;font-size:.8rem;color:#003f62;margin-bottom:6px">Combo ' + (i + 1) + ' — Tổng: ' + totalStr + '</div>';
|
| 134 |
+
combo.items.forEach(function(item) {
|
| 135 |
+
var p = item.p;
|
| 136 |
+
comboHtml += '<div style="display:flex;align-items:center;gap:8px;padding:5px 0;font-size:.76rem;color:#64748b"><i class="fas fa-check-circle" style="color:#2e7d32;font-size:.6rem"></i>' + p.name + ' <span style="color:#003f62;font-weight:600;margin-left:auto">' + (p.price || 'LH') + '</span></div>';
|
| 137 |
+
});
|
| 138 |
+
comboHtml += '</div>';
|
| 139 |
+
});
|
| 140 |
+
comboHtml += '</div>';
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
var items = aiResult.results || [];
|
| 144 |
+
var html = '<div style="margin-bottom:12px">' + badges + '</div>' + aiHtml + comboHtml;
|
| 145 |
+
|
| 146 |
+
if (items.length > 0) {
|
| 147 |
+
html += '<div style="font-size:.78rem;color:#64748b;margin-bottom:10px"><i class="fas fa-search" style="margin-right:4px"></i><strong>' + items.length + '</strong> kết quả cho "<strong>' + query + '</strong>"</div>';
|
| 148 |
+
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px">';
|
| 149 |
+
html += items.map(function(r, i) {
|
| 150 |
+
var p = r.p;
|
| 151 |
+
var idx = (r.idx !== undefined && r.idx >= 0) ? r.idx : (window.D ? window.D.indexOf(p) : -1);
|
| 152 |
+
var brandColor = p.brand === 'Eurogold' ? '#c8102e' : (p.brand === 'Grob' ? '#2e7d32' : '#003f62');
|
| 153 |
+
var labels = (r.labels || []).map(function(l) {
|
| 154 |
+
return '<span style="display:inline-block;background:rgba(0,63,98,.06);padding:2px 6px;border-radius:4px;font-size:.58rem;color:#003f62;margin-right:3px;margin-bottom:3px">' + l + '</span>';
|
| 155 |
+
}).join('');
|
| 156 |
+
var imgSrc = p.image || p.i || '';
|
| 157 |
+
var priceStr = p.price || (window.formatPrice ? window.formatPrice(p.priceNum || p.pn) : (p.pn ? p.pn : 'Liên hệ'));
|
| 158 |
+
var catStr = p.cat || '';
|
| 159 |
+
var catIcon = p.catIcon || 'fa-tag';
|
| 160 |
+
|
| 161 |
+
return '<div class="pc fade" onclick="showDetail(' + idx + ')" style="cursor:pointer;transition-delay:' + Math.min(i * 20, 300) + 'ms">' +
|
| 162 |
+
'<div class="pi" style="height:150px"><img src="' + imgSrc + '" alt="' + p.name + '" loading="lazy" onerror="this.style.display=\'none\'">' +
|
| 163 |
+
'<span class="pi-badge"><i class="fas ' + catIcon + '"></i> ' + catStr + '</span>' +
|
| 164 |
+
'<span style="position:absolute;top:8px;right:8px;background:' + brandColor + ';color:#fff;padding:3px 7px;border-radius:5px;font-size:.55rem;font-weight:700">' + (p.brand || '') + '</span></div>' +
|
| 165 |
+
'<div class="pb" style="padding:10px"><div style="font-size:.62rem;font-weight:600;color:' + brandColor + ';letter-spacing:.5px;margin-bottom:3px">' + (p.model || p.brand || '') + '</div>' +
|
| 166 |
+
'<div class="pn" style="font-size:.76rem">' + p.name + '</div>' +
|
| 167 |
+
(labels ? '<div style="margin-top:4px">' + labels + '</div>' : '') +
|
| 168 |
+
'<div class="pf" style="margin-top:6px"><span class="pp" style="font-size:.92rem">' + priceStr + '</span><i class="fas fa-chevron-right"></i></div></div></div>';
|
| 169 |
+
}).join('');
|
| 170 |
+
html += '</div>';
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
container.innerHTML = html;
|
| 174 |
+
container.style.display = 'block';
|
| 175 |
+
|
| 176 |
+
if (window.requestAnimationFrame) {
|
| 177 |
+
requestAnimationFrame(function() {
|
| 178 |
+
var fadeEls = container.querySelectorAll('.fade');
|
| 179 |
+
for (var j = 0; j < fadeEls.length; j++) {
|
| 180 |
+
fadeEls[j].classList.add('vis');
|
| 181 |
+
}
|
| 182 |
+
});
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
function _fallbackAISearch(query, container) {
|
| 187 |
+
if (typeof D === 'undefined' || !D.length) {
|
| 188 |
+
container.innerHTML = '⏳ Đang tải dữ liệu...';
|
| 189 |
+
return;
|
| 190 |
+
}
|
| 191 |
+
var qNorm = query.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/đ/g, 'd');
|
| 192 |
+
var results = [];
|
| 193 |
+
for (var i = 0; i < D.length && results.length < 20; i++) {
|
| 194 |
+
var p = D[i];
|
| 195 |
+
if (!p) continue;
|
| 196 |
+
var nameNorm = (p.name || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/đ/g, 'd');
|
| 197 |
+
var skuNorm = (p.sku || p.mod || '').toLowerCase();
|
| 198 |
+
if (nameNorm.indexOf(qNorm) !== -1 || (skuNorm && skuNorm.indexOf(qNorm) !== -1)) {
|
| 199 |
+
results.push(p);
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
if (!results.length) {
|
| 203 |
+
container.innerHTML = '❌ Không tìm thấy "' + query + '". Thử: "bếp từ", "máy hút mùi"';
|
| 204 |
+
return;
|
| 205 |
+
}
|
| 206 |
+
container.innerHTML = '<div style="font-size:.78rem;color:#64748b;margin-bottom:10px"><strong>' + results.length + '</strong> kết quả</div>' +
|
| 207 |
+
'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px">' +
|
| 208 |
+
results.map(function(p, idx) {
|
| 209 |
+
var imgSrc = p.image || p.i || '';
|
| 210 |
+
var priceStr = p.price || (p.pn ? p.pn : 'Liên hệ');
|
| 211 |
+
return '<div class="pc fade" onclick="showDetail(' + idx + ')" style="cursor:pointer">' +
|
| 212 |
+
'<div class="pi" style="height:150px"><img src="' + imgSrc + '" alt="' + p.name + '" loading="lazy"></div>' +
|
| 213 |
+
'<div class="pb" style="padding:10px"><div class="pn" style="font-size:.76rem">' + p.name + '</div>' +
|
| 214 |
+
'<div class="pf" style="margin-top:6px"><span class="pp" style="font-size:.92rem">' + priceStr + '</span></div></div></div>';
|
| 215 |
+
}).join('') + '</div>';
|
| 216 |
+
}
|