bep40 commited on
Commit
b137a08
·
verified ·
1 Parent(s): 5966890

Revert to 0f8aedf — restore src/vaix-rag.js

Browse files
Files changed (1) hide show
  1. src/vaix-rag.js +44 -65
src/vaix-rag.js CHANGED
@@ -2,7 +2,6 @@
2
  // Products loaded ONCE from dataset JSON, cached in memory.
3
  // Supports ?product=xxx URL param to auto-open product detail
4
  // Full-screen image viewer on image click
5
- // SSR: window.__VAIX_PRODUCT_DATA__ from server renders instantly before full JSON load
6
 
7
  (function() {
8
  'use strict';
@@ -10,7 +9,6 @@
10
  const JSON_URL = "https://huggingface.co/datasets/bep40/grob-products-updated/resolve/main/products_with_slugs.json";
11
  let allProducts = [];
12
  let loaded = false;
13
- let fullCatalogLoading = false;
14
  let searchDebounceTimer = null;
15
  let lastSearchResults = [];
16
  let lastShownProduct = null;
@@ -54,52 +52,6 @@ function scoreProduct(p, queryStr, terms) {
54
 
55
  async function load() {
56
  if (loaded) return;
57
-
58
- // ── Check for server-injected product data (?product=xxx) ──
59
- const injectedData = window.__VAIX_PRODUCT_DATA__;
60
- if (injectedData && injectedData.title_clean) {
61
- console.log("[VAIX] Using injected product data:", injectedData.title_clean);
62
- // Quick-set as 1-item catalog, will be replaced by full JSON in background
63
- allProducts = [injectedData];
64
- loaded = true;
65
- fullCatalogLoading = true;
66
-
67
- // Immediately render from injected data — no waiting
68
- setTimeout(function() {
69
- // Open panel and show the product right away
70
- handleProductUrlParam();
71
- }, 10);
72
-
73
- // Also trigger full JSON fetch in background for search/related
74
- fullCatalogLoading = true;
75
- fetch(JSON_URL).then(r => {
76
- if (!r.ok) throw new Error("HTTP "+r.status);
77
- return r.json();
78
- }).then(raw => {
79
- // Only replace if we don't already have the full catalog
80
- if (allProducts.length <= 1 || !fullCatalogLoading) return;
81
- allProducts = (Array.isArray(raw) ? raw : []).map((p,i)=>({
82
- name: p.n||p.name||"", title_clean: p.n||p.name||"",
83
- brand: p.brand||"", price: p.p||p.price||"", priceNum: Number(p.pn??0),
84
- category: p.c||p.cat||"", category_slug: p.cs||"", category_icon: p.ci||"fa-box",
85
- sku: p.sku||"", model: p.mod||p.model||"", slug: p.slug||"",
86
- description: p.desc||"", summary: p.sum||p.summary||"",
87
- features: Array.isArray(p.feats)?p.feats:[],
88
- specs: (typeof p.specs==="object"&&p.specs!==null)?p.specs:{},
89
- video: p.vid||"", image: p.i||(Array.isArray(p.imgs)?p.imgs[0]:"")||"",
90
- images: p.imgs||p.images||[], link: p.l||"", _idx: i
91
- }));
92
- loaded = true;
93
- fullCatalogLoading = false;
94
- console.log("[VAIX] Full catalog loaded in background:", allProducts.length);
95
- }).catch(e => {
96
- console.error("[VAIX] Background catalog load failed:", e);
97
- fullCatalogLoading = false;
98
- });
99
- return;
100
- }
101
-
102
- // ── Normal: full JSON load ──
103
  try {
104
  const resp = await fetch(JSON_URL);
105
  if (!resp.ok) throw new Error("HTTP "+resp.status);
@@ -118,10 +70,8 @@ async function load() {
118
  loaded = true;
119
  console.log("[VAIX] Loaded "+allProducts.length+" products");
120
 
121
- // Also check URL param after full load (if not already shown via injected data)
122
- if (!window.__VAIX_PRODUCT_DATA__) {
123
- handleProductUrlParam();
124
- }
125
  } catch(e) {
126
  console.error("[VAIX] Load error:", e);
127
  const errEl = document.getElementById("vaistudio-error");
@@ -166,11 +116,13 @@ function handleProductUrlParam() {
166
  }
167
 
168
  if (found) {
169
- renderDetail(found);
170
- renderSimilarInPanel(found);
171
- renderPanelResults([found]);
172
- lastShownProduct = found;
173
- document.body.style.overflow = "hidden";
 
 
174
  }
175
  } catch(e) {
176
  console.error("[VAIX] URL param error:", e);
@@ -229,7 +181,7 @@ function getSimilarProducts(product, limit) {
229
  const nameWords = sd(p.title_clean).split(/\s+/).filter(w => w.length > 2);
230
  const scored = [];
231
  for (const other of allProducts) {
232
- if (other._idx === p._idx || !other.title_clean) continue;
233
  let sc = 0;
234
  const otherCat = sd(other.category), otherBrand = sd(other.brand);
235
  if (otherCat && cat && (otherCat === cat || otherCat.includes(cat) || cat.includes(otherCat))) sc += 40;
@@ -306,6 +258,7 @@ function openImageViewer(imageUrl, productName) {
306
  viewer.id = "vaix-image-viewer";
307
  viewer.style.cssText = "position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.92);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;cursor:zoom-out;animation:modalIn 0.2s ease";
308
 
 
309
  const closeBtn = document.createElement("button");
310
  closeBtn.innerHTML = "✕";
311
  closeBtn.style.cssText = "position:absolute;top:16px;right:16px;width:40px;height:40px;border-radius:50%;border:none;background:rgba(255,255,255,0.15);color:#fff;font-size:1.3rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.2s;z-index:2";
@@ -314,6 +267,7 @@ function openImageViewer(imageUrl, productName) {
314
  closeBtn.onclick = function(e){ e.stopPropagation(); viewer.remove(); document.body.style.overflow = ""; };
315
  viewer.appendChild(closeBtn);
316
 
 
317
  if (productName) {
318
  const caption = document.createElement("div");
319
  caption.textContent = productName;
@@ -335,13 +289,20 @@ function openImageViewer(imageUrl, productName) {
335
  viewer.appendChild(img);
336
 
337
  viewer.addEventListener("click", function(e) {
338
- if (e.target === viewer) { viewer.remove(); document.body.style.overflow = ""; }
 
 
 
339
  });
340
 
341
  viewer.setAttribute("tabindex", "0");
342
  viewer.focus();
343
  function keyHandler(e) {
344
- if (e.key === "Escape") { viewer.remove(); document.body.style.overflow = ""; document.removeEventListener("keydown", keyHandler); }
 
 
 
 
345
  }
346
  document.addEventListener("keydown", keyHandler);
347
 
@@ -400,7 +361,10 @@ function attachChatCardHandlers(container) {
400
  });
401
  });
402
  container.querySelectorAll(".share-btn").forEach(function(btn) {
403
- btn.addEventListener("click", function(e) { e.stopPropagation(); shareProduct(this.getAttribute("data-product")); });
 
 
 
404
  });
405
  container.querySelectorAll(".chat-product-card-inner").forEach(function(inner) {
406
  inner.addEventListener("click", function(e) {
@@ -541,7 +505,11 @@ function renderPanelResults(products) {
541
  info.appendChild(ct);
542
  }
543
  card.appendChild(info);
544
- card.addEventListener("click", function(e){ e.stopPropagation(); renderDetail(p); renderSimilarInPanel(p); });
 
 
 
 
545
  productsEl.appendChild(card);
546
  }
547
  if (countEl) countEl.textContent = products.length + " kết quả";
@@ -609,6 +577,7 @@ function renderDetail(p) {
609
  if (p.priceNum >= 1000000) { trieuEl.textContent = "(" + (p.priceNum/1000000).toFixed(1) + " triệu)"; trieuEl.style.display="inline"; }
610
  else trieuEl.style.display = "none";
611
 
 
612
  const ic = document.getElementById("detail-images");
613
  ic.innerHTML = "";
614
  const imgs = [];
@@ -620,7 +589,7 @@ function renderDetail(p) {
620
  d.style.cssText = "width:120px;height:120px;background:#f1f5f9;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:2rem;flex-shrink:0";
621
  d.textContent = "📦"; ic.appendChild(d);
622
  } else {
623
- imgs.forEach(function(url){
624
  const wrapper = document.createElement("div");
625
  wrapper.style.cssText = "position:relative;flex-shrink:0;scroll-snap-align:start;cursor:zoom-in";
626
  wrapper.title = "Nhấn để xem ảnh toàn màn hình";
@@ -632,13 +601,19 @@ function renderDetail(p) {
632
  img.onmouseover = function(){ this.style.transform = "scale(1.05)"; this.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)"; };
633
  img.onmouseout = function(){ this.style.transform = ""; this.style.boxShadow = ""; };
634
 
635
- img.addEventListener("click", function(e) { e.stopPropagation(); openImageViewer(url, p.title_clean); });
 
 
 
 
636
 
637
  wrapper.appendChild(img);
638
 
 
639
  const zoomIcon = document.createElement("div");
640
  zoomIcon.textContent = "🔍";
641
  zoomIcon.style.cssText = "position:absolute;bottom:4px;right:4px;font-size:0.7rem;background:rgba(0,0,0,0.5);border-radius:50%;width:22px;height:22px;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0.7";
 
642
  wrapper.appendChild(zoomIcon);
643
  ic.appendChild(wrapper);
644
  });
@@ -663,6 +638,7 @@ function renderDetail(p) {
663
  sc.style.display = sb.children.length?"block":"none";
664
  } else sc.style.display="none";
665
 
 
666
  let shareSection = document.getElementById("detail-share-section");
667
  if (!shareSection) {
668
  shareSection = document.createElement("div");
@@ -674,8 +650,11 @@ function renderDetail(p) {
674
  else document.getElementById("vaistudio-detail-modal").appendChild(shareSection);
675
  }
676
  const shareBtn = document.getElementById("detail-share-btn");
677
- if (shareBtn) shareBtn.onclick = function(e) { e.stopPropagation(); shareProduct(p.title_clean); };
 
 
678
 
 
679
  let similarContainer = document.getElementById("detail-similar");
680
  if (!similarContainer) {
681
  const modal = document.getElementById("vaistudio-detail-modal");
 
2
  // Products loaded ONCE from dataset JSON, cached in memory.
3
  // Supports ?product=xxx URL param to auto-open product detail
4
  // Full-screen image viewer on image click
 
5
 
6
  (function() {
7
  'use strict';
 
9
  const JSON_URL = "https://huggingface.co/datasets/bep40/grob-products-updated/resolve/main/products_with_slugs.json";
10
  let allProducts = [];
11
  let loaded = false;
 
12
  let searchDebounceTimer = null;
13
  let lastSearchResults = [];
14
  let lastShownProduct = null;
 
52
 
53
  async function load() {
54
  if (loaded) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  try {
56
  const resp = await fetch(JSON_URL);
57
  if (!resp.ok) throw new Error("HTTP "+resp.status);
 
70
  loaded = true;
71
  console.log("[VAIX] Loaded "+allProducts.length+" products");
72
 
73
+ // After loading, check URL param for ?product=xxx
74
+ handleProductUrlParam();
 
 
75
  } catch(e) {
76
  console.error("[VAIX] Load error:", e);
77
  const errEl = document.getElementById("vaistudio-error");
 
116
  }
117
 
118
  if (found) {
119
+ setTimeout(function() {
120
+ renderDetail(found);
121
+ renderSimilarInPanel(found);
122
+ renderPanelResults([found]);
123
+ lastShownProduct = found;
124
+ document.body.style.overflow = "hidden";
125
+ }, 300);
126
  }
127
  } catch(e) {
128
  console.error("[VAIX] URL param error:", e);
 
181
  const nameWords = sd(p.title_clean).split(/\s+/).filter(w => w.length > 2);
182
  const scored = [];
183
  for (const other of allProducts) {
184
+ if (other._idx === p._idx) continue;
185
  let sc = 0;
186
  const otherCat = sd(other.category), otherBrand = sd(other.brand);
187
  if (otherCat && cat && (otherCat === cat || otherCat.includes(cat) || cat.includes(otherCat))) sc += 40;
 
258
  viewer.id = "vaix-image-viewer";
259
  viewer.style.cssText = "position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.92);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;cursor:zoom-out;animation:modalIn 0.2s ease";
260
 
261
+ // Close button
262
  const closeBtn = document.createElement("button");
263
  closeBtn.innerHTML = "✕";
264
  closeBtn.style.cssText = "position:absolute;top:16px;right:16px;width:40px;height:40px;border-radius:50%;border:none;background:rgba(255,255,255,0.15);color:#fff;font-size:1.3rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background 0.2s;z-index:2";
 
267
  closeBtn.onclick = function(e){ e.stopPropagation(); viewer.remove(); document.body.style.overflow = ""; };
268
  viewer.appendChild(closeBtn);
269
 
270
+ // Product name caption
271
  if (productName) {
272
  const caption = document.createElement("div");
273
  caption.textContent = productName;
 
289
  viewer.appendChild(img);
290
 
291
  viewer.addEventListener("click", function(e) {
292
+ if (e.target === viewer) {
293
+ viewer.remove();
294
+ document.body.style.overflow = "";
295
+ }
296
  });
297
 
298
  viewer.setAttribute("tabindex", "0");
299
  viewer.focus();
300
  function keyHandler(e) {
301
+ if (e.key === "Escape") {
302
+ viewer.remove();
303
+ document.body.style.overflow = "";
304
+ document.removeEventListener("keydown", keyHandler);
305
+ }
306
  }
307
  document.addEventListener("keydown", keyHandler);
308
 
 
361
  });
362
  });
363
  container.querySelectorAll(".share-btn").forEach(function(btn) {
364
+ btn.addEventListener("click", function(e) {
365
+ e.stopPropagation();
366
+ shareProduct(this.getAttribute("data-product"));
367
+ });
368
  });
369
  container.querySelectorAll(".chat-product-card-inner").forEach(function(inner) {
370
  inner.addEventListener("click", function(e) {
 
505
  info.appendChild(ct);
506
  }
507
  card.appendChild(info);
508
+ card.addEventListener("click", function(e){
509
+ e.stopPropagation();
510
+ renderDetail(p);
511
+ renderSimilarInPanel(p);
512
+ });
513
  productsEl.appendChild(card);
514
  }
515
  if (countEl) countEl.textContent = products.length + " kết quả";
 
577
  if (p.priceNum >= 1000000) { trieuEl.textContent = "(" + (p.priceNum/1000000).toFixed(1) + " triệu)"; trieuEl.style.display="inline"; }
578
  else trieuEl.style.display = "none";
579
 
580
+ // Images — with click-to-fullscreen handler
581
  const ic = document.getElementById("detail-images");
582
  ic.innerHTML = "";
583
  const imgs = [];
 
589
  d.style.cssText = "width:120px;height:120px;background:#f1f5f9;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:2rem;flex-shrink:0";
590
  d.textContent = "📦"; ic.appendChild(d);
591
  } else {
592
+ imgs.forEach(function(url, idx){
593
  const wrapper = document.createElement("div");
594
  wrapper.style.cssText = "position:relative;flex-shrink:0;scroll-snap-align:start;cursor:zoom-in";
595
  wrapper.title = "Nhấn để xem ảnh toàn màn hình";
 
601
  img.onmouseover = function(){ this.style.transform = "scale(1.05)"; this.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)"; };
602
  img.onmouseout = function(){ this.style.transform = ""; this.style.boxShadow = ""; };
603
 
604
+ // Click image → open fullscreen viewer
605
+ img.addEventListener("click", function(e) {
606
+ e.stopPropagation();
607
+ openImageViewer(url, p.title_clean);
608
+ });
609
 
610
  wrapper.appendChild(img);
611
 
612
+ // Small zoom icon overlay
613
  const zoomIcon = document.createElement("div");
614
  zoomIcon.textContent = "🔍";
615
  zoomIcon.style.cssText = "position:absolute;bottom:4px;right:4px;font-size:0.7rem;background:rgba(0,0,0,0.5);border-radius:50%;width:22px;height:22px;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0.7";
616
+
617
  wrapper.appendChild(zoomIcon);
618
  ic.appendChild(wrapper);
619
  });
 
638
  sc.style.display = sb.children.length?"block":"none";
639
  } else sc.style.display="none";
640
 
641
+ // Share button section
642
  let shareSection = document.getElementById("detail-share-section");
643
  if (!shareSection) {
644
  shareSection = document.createElement("div");
 
650
  else document.getElementById("vaistudio-detail-modal").appendChild(shareSection);
651
  }
652
  const shareBtn = document.getElementById("detail-share-btn");
653
+ if (shareBtn) {
654
+ shareBtn.onclick = function(e) { e.stopPropagation(); shareProduct(p.title_clean); };
655
+ }
656
 
657
+ // Similar products in modal
658
  let similarContainer = document.getElementById("detail-similar");
659
  if (!similarContainer) {
660
  const modal = document.getElementById("vaistudio-detail-modal");