Spaces:
Running
Running
restore to c1013cc7ebfdc93cbb55645441528a84d871a052: 100% restore of target commit tree
9b44afa verified | // ── RAG: query_catalog — in-memory search, shows results in panel + returns data to AI ── | |
| // Hàm find product theo tên/sku — dùng cho show_product tool | |
| function findProductByName(query) { | |
| if (!vaistudioAllProducts.length) return null; | |
| const qs = _sd(query), terms = qs.split(/\s+/).filter(t=>t.length>1); | |
| let best = null, bestScore = 0; | |
| for (const p of vaistudioAllProducts) { | |
| let sc = 0; | |
| const nm = _sd(p.title_clean||p.name), sk = _sd(p.sku||p.model), br = _sd(p.brand); | |
| if (nm===qs||sk===qs) return p; | |
| if (nm.includes(qs)||sk.includes(qs)||br.includes(qs)) sc+=20; | |
| for (const t of terms) { if (nm.includes(t)) sc+=5; if (sk.includes(t)) sc+=4; if (br.includes(t)) sc+=3; } | |
| if (sc>bestScore) { bestScore=sc; best=p; } | |
| } | |
| return best; | |
| } | |
| // Update UI panel with search results | |
| function updatePanelResults(products) { | |
| const panel = document.getElementById("vaistudio-panel"); | |
| const toggle = document.getElementById("vaistudio-toggle"); | |
| const loadingEl = document.getElementById("vaistudio-loading"); | |
| const prodsEl = document.getElementById("vaistudio-products"); | |
| const countEl = document.getElementById("vaistudio-count"); | |
| if (!prodsEl) return; | |
| if (panel && !panel.classList.contains("open")) { panel.classList.add("open"); panel.style.display="flex"; } | |
| if (toggle) toggle.classList.add("active"); | |
| if (loadingEl) loadingEl.hidden = true; | |
| prodsEl.innerHTML = ""; | |
| prodsEl.style.display = "block"; | |
| for (let i=0; i<products.length; i++) { | |
| const p = products[i]; | |
| const card = document.createElement("div"); card.className = "product-card"; | |
| if (p.image) { | |
| const ie = document.createElement("img"); ie.className="product-card-img"; ie.src=p.image; ie.alt=""; ie.loading="lazy"; | |
| ie.onerror = function(){this.style.display="none"}; card.appendChild(ie); | |
| } else { | |
| const pl = document.createElement("div"); pl.className="product-card-img"; | |
| pl.style.cssText = "flex-shrink:0;background:#e2e8f0;display:flex;align-items:center;justify-content:center;font-size:1.5rem"; | |
| pl.textContent="📦"; card.appendChild(pl); | |
| } | |
| const info = document.createElement("div"); info.className="product-card-info"; | |
| const t = document.createElement("p"); t.className="product-card-title"; t.textContent=p.title_clean||p.name; | |
| const b = document.createElement("p"); b.className="product-card-brand"; b.textContent=p.brand||""; | |
| const pr = document.createElement("p"); pr.className="product-card-price"; pr.textContent=_fmt(p.priceNum); | |
| info.append(t,b,pr); | |
| if (p.category) { const ct = document.createElement("span"); ct.className="product-card-cat"; ct.textContent=p.category; info.appendChild(ct); } | |
| card.appendChild(info); | |
| card.addEventListener("click", function(e){ e.stopPropagation(); showProductDetail(p); }); | |
| prodsEl.appendChild(card); | |
| } | |
| if (countEl) countEl.textContent = products.length+" kết quả"; | |
| } | |
| /** Search products in memory, update UI panel, and return structured text. */ | |
| function queryProducts(query) { | |
| if (!vaistudioAllProducts.length) return "Catalog still loading, please wait."; | |
| const q = query.trim(); | |
| if (!q) return "What product are you looking for?"; | |
| const qNorm = _sd(q), terms = qNorm.split(/\s+/).filter(t=>t.length>1); | |
| const scored = []; | |
| for (let i=0; i<vaistudioAllProducts.length; i++) { | |
| const p = vaistudioAllProducts[i]; | |
| let score = 0; | |
| const nm = _sd(p.title_clean||p.name), sk = _sd(p.sku||p.model), br = _sd(p.brand); | |
| const all = [nm, br, _sd(p.category), _sd(p.description||""), _sd((p.features||[]).join(" ")), _sd(p.summary||"")].join(" "); | |
| if (nm.includes(qNorm)) score += 50; | |
| else if (all.includes(qNorm)) score += 30; | |
| for (const t of terms) { | |
| if (nm.includes(t)) score += 8; | |
| else if (sk.includes(t)) score += 6; | |
| else if (br.includes(t)) score += 5; | |
| else if (all.includes(t)) score += 1; | |
| } | |
| if (score > 0) scored.push({ product:p, score }); | |
| } | |
| scored.sort((a,b)=>b.score-a.score); | |
| const top = scored.slice(0, 5).map(s=>s.product); | |
| // Update UI panel with results | |
| updatePanelResults(top.length ? top : vaistudioAllProducts.slice(0,20)); | |
| if (!top.length) return `No products matching "${q}". Try a different term.`; | |
| let result = `Found ${top.length} product(s) matching "${q}":\n\n`; | |
| for (let i=0; i<top.length; i++) { | |
| const p = top[i]; | |
| result += `【${i+1}】${p.title_clean||p.name}\n Brand: ${p.brand||"N/A"} | Price: ${_fmt(p.priceNum)}\n`; | |
| if (p.model||p.sku) result += ` Model: ${p.model||p.sku}\n`; | |
| if (p.summary) result += ` ${p.summary.slice(0,200)}\n`; | |
| if (p.features?.length) result += ` Features: ${p.features.slice(0,5).join(", ")}${p.features.length>5?"...":""}\n`; | |
| if (p.specs&&Object.keys(p.specs).length) { | |
| const entries = Object.entries(p.specs).filter(([_,v])=>v&&v!=="None").slice(0,4); | |
| for (const [k,v] of entries) result += ` ${k}: ${String(v).slice(0,80)}\n`; | |
| } | |
| result += `\n`; | |
| } | |
| result += `Best: ${top[0].title_clean||top[0].name} (${_fmt(top[0].priceNum)}). Panel updated!`; | |
| return result; | |
| } |