Delete src/vaix.css, src/vaix.js

#4
by bep40 - opened
Files changed (2) hide show
  1. src/vaix.css +0 -457
  2. src/vaix.js +0 -324
src/vaix.css DELETED
@@ -1,457 +0,0 @@
1
- /* V.AIX Panel styles — compact, mobile-friendly product catalog */
2
-
3
- .vaix-panel {
4
- position: fixed;
5
- bottom: 0;
6
- right: 0;
7
- width: 100%;
8
- max-width: 100vw;
9
- height: 100%;
10
- max-height: 100vh;
11
- background: #fff;
12
- border-radius: 20px 20px 0 0;
13
- box-shadow: 0 8px 40px rgba(0, 0, 0, 0.18);
14
- z-index: 160;
15
- display: flex;
16
- flex-direction: column;
17
- overflow: hidden;
18
- visibility: hidden;
19
- transition: visibility 0.3s;
20
- }
21
-
22
- .vaix-panel.open {
23
- visibility: visible;
24
- }
25
-
26
- .vaix-header {
27
- background: linear-gradient(135deg, #003f62, #0077b6);
28
- color: #fff;
29
- padding: 14px 16px;
30
- display: flex;
31
- align-items: center;
32
- gap: 12px;
33
- flex-shrink: 0;
34
- }
35
-
36
- .vaix-header-avatar {
37
- width: 38px;
38
- height: 38px;
39
- border-radius: 12px;
40
- background: rgba(255, 255, 255, 0.15);
41
- display: flex;
42
- align-items: center;
43
- justify-content: center;
44
- font-size: 1.1rem;
45
- flex-shrink: 0;
46
- }
47
-
48
- .vaix-header-info {
49
- flex: 1;
50
- }
51
-
52
- .vaix-header-info h4 {
53
- font-size: 0.9rem;
54
- font-weight: 700;
55
- margin: 0;
56
- }
57
-
58
- .vaix-header-info span {
59
- font-size: 0.68rem;
60
- opacity: 0.75;
61
- }
62
-
63
- .vaix-close-btn {
64
- width: 40px;
65
- height: 40px;
66
- min-width: 40px;
67
- min-height: 40px;
68
- border-radius: 50%;
69
- background: rgba(255, 255, 255, 0.25);
70
- border: none;
71
- color: #fff;
72
- cursor: pointer;
73
- display: flex;
74
- align-items: center;
75
- justify-content: center;
76
- font-size: 1.1rem;
77
- transition: all 0.2s ease;
78
- }
79
-
80
- .vaix-close-btn:hover {
81
- background: rgba(255, 255, 255, 0.4);
82
- }
83
-
84
- .vaix-body {
85
- flex: 1;
86
- overflow-y: auto;
87
- padding: 16px;
88
- background: #f8fafc;
89
- }
90
-
91
- .vaix-search-box {
92
- position: relative;
93
- margin-bottom: 16px;
94
- }
95
-
96
- .vaix-search-box input {
97
- width: 100%;
98
- padding: 10px 40px 10px 14px;
99
- border: 2px solid #e2e8f0;
100
- border-radius: 12px;
101
- font-size: 0.88rem;
102
- font-family: inherit;
103
- outline: none;
104
- transition: border-color 0.2s;
105
- }
106
-
107
- .vaix-search-box input:focus {
108
- border-color: #003f62;
109
- box-shadow: 0 0 0 3px rgba(0, 63, 98, 0.1);
110
- }
111
-
112
- .vaix-search-btn {
113
- position: absolute;
114
- right: 4px;
115
- top: 4px;
116
- bottom: 4px;
117
- width: 34px;
118
- border-radius: 8px;
119
- background: #003f62;
120
- color: #fff;
121
- border: none;
122
- cursor: pointer;
123
- display: flex;
124
- align-items: center;
125
- justify-content: center;
126
- font-size: 0.9rem;
127
- }
128
-
129
- .vaix-search-btn:hover {
130
- background: #005a8c;
131
- }
132
-
133
- .vaix-loading {
134
- display: none;
135
- flex-direction: column;
136
- align-items: center;
137
- justify-content: center;
138
- gap: 12px;
139
- padding: 60px 20px;
140
- color: #64748b;
141
- }
142
-
143
- .vaix-loading-spinner {
144
- width: 32px;
145
- height: 32px;
146
- border: 3px solid #e2e8f0;
147
- border-top-color: #003f62;
148
- border-radius: 50%;
149
- animation: vaix-spin 1s linear infinite;
150
- }
151
-
152
- @keyframes vaix-spin {
153
- to { transform: rotate(360deg); }
154
- }
155
-
156
- .vaix-empty {
157
- text-align: center;
158
- padding: 50px 20px;
159
- color: #64748b;
160
- }
161
-
162
- .vaix-empty i {
163
- font-size: 2.5rem;
164
- opacity: 0.3;
165
- margin-bottom: 14px;
166
- display: block;
167
- }
168
-
169
- .vaix-empty h3 {
170
- font-size: 1rem;
171
- font-weight: 700;
172
- color: #0f172a;
173
- margin-bottom: 6px;
174
- }
175
-
176
- .vaix-clear-btn {
177
- padding: 8px 16px;
178
- background: #f1f5f9;
179
- border: 1px solid #e2e8f0;
180
- border-radius: 8px;
181
- font-size: 0.82rem;
182
- cursor: pointer;
183
- font-family: inherit;
184
- color: #64748b;
185
- }
186
-
187
- .vaix-clear-btn:hover {
188
- background: #e2e8f0;
189
- }
190
-
191
- .vaix-grid {
192
- display: flex;
193
- flex-direction: column;
194
- gap: 16px;
195
- }
196
-
197
- .vaix-category {
198
- background: #fff;
199
- border-radius: 12px;
200
- overflow: hidden;
201
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
202
- }
203
-
204
- .vaix-category h3 {
205
- font-size: 0.85rem;
206
- font-weight: 700;
207
- color: #003f62;
208
- padding: 12px 14px 10px;
209
- background: #f8fafc;
210
- border-bottom: 1px solid #e2e8f0;
211
- }
212
-
213
- .vaix-category h3 .vaix-cat-name::after {
214
- content: "";
215
- display: inline-block;
216
- width: 4px;
217
- height: 14px;
218
- background: #db9815;
219
- border-radius: 2px;
220
- margin-left: 8px;
221
- vertical-align: middle;
222
- }
223
-
224
- .vaix-products {
225
- padding: 8px;
226
- display: grid;
227
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
228
- gap: 8px;
229
- }
230
-
231
- .vaix-product-card {
232
- background: #fff;
233
- border: 1px solid #e2e8f0;
234
- border-radius: 10px;
235
- overflow: hidden;
236
- cursor: pointer;
237
- transition: all 0.2s ease;
238
- display: flex;
239
- flex-direction: column;
240
- }
241
-
242
- .vaix-product-card:hover {
243
- border-color: #003f62;
244
- box-shadow: 0 4px 12px rgba(0, 63, 98, 0.15);
245
- }
246
-
247
- .vaix-product-card img {
248
- width: 100%;
249
- height: 100px;
250
- object-fit: contain;
251
- background: #f8fafc;
252
- padding: 6px;
253
- }
254
-
255
- .vaix-product-info {
256
- padding: 8px;
257
- flex: 1;
258
- min-width: 0;
259
- }
260
-
261
- .vaix-product-title {
262
- font-size: 0.74rem;
263
- font-weight: 600;
264
- color: #0f172a;
265
- line-height: 1.3;
266
- margin-bottom: 4px;
267
- display: -webkit-box;
268
- -webkit-line-clamp: 2;
269
- -webkit-box-orient: vertical;
270
- overflow: hidden;
271
- }
272
-
273
- .vaix-product-brand {
274
- font-size: 0.64rem;
275
- font-weight: 500;
276
- color: #64748b;
277
- margin-bottom: 2px;
278
- }
279
-
280
- .vaix-product-price {
281
- font-size: 0.78rem;
282
- font-weight: 800;
283
- color: #003f62;
284
- }
285
-
286
- .vaix-product-sku {
287
- font-size: 0.62rem;
288
- color: #94a3b8;
289
- margin-top: 2px;
290
- }
291
-
292
- /* Product detail modal */
293
- .vaix-product-detail {
294
- background: #f8fafc;
295
- border-radius: 16px;
296
- padding: 16px;
297
- min-height: 200px;
298
- }
299
-
300
- .vaix-detail-grid {
301
- display: flex;
302
- flex-direction: column;
303
- gap: 12px;
304
- }
305
-
306
- .vaix-detail-grid img {
307
- width: 100%;
308
- height: auto;
309
- max-height: 200px;
310
- object-fit: contain;
311
- border-radius: 12px;
312
- background: #fff;
313
- padding: 12px;
314
- }
315
-
316
- .vaix-detail-info {
317
- padding: 8px 0;
318
- }
319
-
320
- .vaix-detail-info h2 {
321
- font-size: 1.1rem;
322
- font-weight: 700;
323
- color: #0f172a;
324
- margin: 0 0 8px;
325
- }
326
-
327
- .vaix-detail-sku {
328
- font-size: 0.74rem;
329
- color: #64748b;
330
- margin-bottom: 4px;
331
- }
332
-
333
- .vaix-detail-brand {
334
- font-size: 0.82rem;
335
- font-weight: 600;
336
- color: #003f62;
337
- margin-bottom: 8px;
338
- }
339
-
340
- .vaix-detail-price {
341
- font-size: 1.2rem;
342
- font-weight: 800;
343
- color: #003f62;
344
- margin-bottom: 12px;
345
- }
346
-
347
- .vaix-detail-category {
348
- display: inline-block;
349
- background: #e0f2fe;
350
- color: #003f62;
351
- padding: 4px 12px;
352
- border-radius: 20px;
353
- font-size: 0.74rem;
354
- font-weight: 600;
355
- margin-bottom: 12px;
356
- }
357
-
358
- .vaix-detail-features {
359
- display: flex;
360
- align-items: center;
361
- gap: 8px;
362
- font-size: 0.82rem;
363
- color: #0f172a;
364
- margin-bottom: 12px;
365
- }
366
-
367
- .vaix-detail-features i {
368
- color: #db9815;
369
- }
370
-
371
- .vaix-detail-description {
372
- font-size: 0.82rem;
373
- color: #64748b;
374
- line-height: 1.6;
375
- margin-bottom: 16px;
376
- }
377
-
378
- .vaix-inquire-btn {
379
- width: 100%;
380
- padding: 12px 16px;
381
- background: linear-gradient(135deg, #003f62, #0077b6);
382
- color: #fff;
383
- border: none;
384
- border-radius: 10px;
385
- font-size: 0.88rem;
386
- font-weight: 700;
387
- cursor: pointer;
388
- font-family: inherit;
389
- display: flex;
390
- align-items: center;
391
- justify-content: center;
392
- gap: 8px;
393
- }
394
-
395
- .vaix-inquire-btn:hover {
396
- background: linear-gradient(135deg, #005a8c, #0086c9);
397
- }
398
-
399
- .vaix-back-btn {
400
- display: inline-flex;
401
- align-items: center;
402
- gap: 6px;
403
- padding: 8px 12px;
404
- background: #fff;
405
- border: 1px solid #e2e8f0;
406
- border-radius: 8px;
407
- font-size: 0.82rem;
408
- cursor: pointer;
409
- font-family: inherit;
410
- color: #64748b;
411
- margin-bottom: 12px;
412
- }
413
-
414
- .vaix-back-btn:hover {
415
- background: #f1f5f9;
416
- }
417
-
418
- /* Inquiry suggestion */
419
- .vaix-inquiry-suggest {
420
- display: none;
421
- padding: 10px 14px;
422
- background: #fffbeb;
423
- border: 1px solid #fde68a;
424
- border-radius: 10px;
425
- font-size: 0.82rem;
426
- color: #92400e;
427
- margin-top: 12px;
428
- }
429
-
430
- /* Small screen adjustments */
431
- @media (max-width: 768px) {
432
- .vaix-panel {
433
- width: 100%;
434
- max-width: 100%;
435
- height: 100%;
436
- max-height: 100vh;
437
- border-radius: 0;
438
- }
439
-
440
- .vaix-products {
441
- grid-template-columns: repeat(2, 1fr);
442
- }
443
-
444
- .vaix-product-card img {
445
- height: 80px;
446
- }
447
- }
448
-
449
- @media (max-width: 480px) {
450
- .vaix-products {
451
- grid-template-columns: 1fr;
452
- }
453
-
454
- .vaix-product-card img {
455
- height: 120px;
456
- }
457
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/vaix.js DELETED
@@ -1,324 +0,0 @@
1
- /**
2
- * V.AIX Panel — lightweight product search & display for gemma-avatar.
3
- *
4
- * Data source: /api/vaix/* endpoints (serving from HF Dataset via index.ts).
5
- *
6
- * Caching strategy:
7
- * 1. Load products from /api/vaix/products on first open
8
- * 2. Subsequent opens load from localStorage (instant)
9
- * 3. Cache refreshes every 24h via background fetch
10
- */
11
-
12
- // Use local server API endpoints (index.ts serves these)
13
- const API_PRODUCTS = "/api/vaix/products";
14
- const API_SEARCH = "/api/vaix/search";
15
-
16
- // Cache keys
17
- const CACHE_KEY = "vaix_products_cache";
18
- const CACHE_TIMESTAMP_KEY = "vaix_cache_timestamp";
19
- const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
20
-
21
- // State
22
- let allProducts = [];
23
- let filteredProducts = [];
24
- let isLoading = false;
25
- let searchDebounceTimer = null;
26
-
27
- // Search index (pre-built for fast client-side search)
28
- let searchIndex = new Map();
29
-
30
- function initSearchIndex(products) {
31
- searchIndex.clear();
32
- products.forEach((p, i) => {
33
- const searchable = [p.title_clean, p.sku, p.brand, p.category, p.features]
34
- .filter(Boolean)
35
- .join(" ")
36
- .toLowerCase()
37
- .normalize('NFD').replace(/[\u0300-\u036f]/g, '');
38
- searchable.split(/\s+/).forEach(word => {
39
- if (word.length > 1) {
40
- if (!searchIndex.has(word)) searchIndex.set(word, []);
41
- searchIndex.get(word).push(i);
42
- }
43
- });
44
- });
45
- console.log(`[V.AIX] Search index built: ${searchIndex.size} terms, ${products.length} products`);
46
- }
47
-
48
- function searchLocal(query) {
49
- if (!query || !searchIndex.size) return allProducts.slice(0, 50);
50
-
51
- const norm = query.toLowerCase().normalize('NFD').replace(/[\u0300-\u06f]/g, '');
52
- const terms = norm.split(/\s+/).filter(w => w.length > 0);
53
-
54
- const scores = new Map();
55
- allProducts.forEach((p, i) => {
56
- const searchable = [p.title_clean, p.sku, p.brand, p.category, p.features, p.description]
57
- .filter(Boolean)
58
- .join(" ")
59
- .toLowerCase()
60
- .normalize('NFD')
61
- .replace(/[\u0300-\u036f]/g, '');
62
-
63
- let score = 0;
64
- terms.forEach(term => {
65
- const termLen = term.length;
66
- if (p.title_clean && p.title_clean.toLowerCase().includes(term)) score += 10 * termLen;
67
- if (p.sku && p.sku.toLowerCase().includes(term)) score += 8 * termLen;
68
- if (p.brand && p.brand.toLowerCase().includes(term)) score += 6 * termLen;
69
- if (p.category && p.category.toLowerCase().includes(term)) score += 4 * termLen;
70
- if (p.features && p.features.toLowerCase().includes(term)) score += 2 * termLen;
71
- });
72
-
73
- if (score > 0) scores.set(i, score);
74
- });
75
-
76
- const sorted = [...scores.entries()].sort((a, b) => b[1] - a[1]);
77
- return sorted.slice(0, 100).map(([idx]) => allProducts[idx]);
78
- }
79
-
80
- function saveToCache() {
81
- try {
82
- localStorage.setItem(CACHE_KEY, JSON.stringify({
83
- products: allProducts.map(p => ({
84
- title_clean: p.title_clean,
85
- title: p.title,
86
- sku: p.sku,
87
- brand: p.brand,
88
- category: p.category,
89
- price: p.price,
90
- image: p.image || "",
91
- features: p.features,
92
- description: p.description,
93
- })),
94
- timestamp: Date.now(),
95
- }));
96
- localStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
97
- console.log(`[V.AIX] Cached ${allProducts.length} products`);
98
- } catch (e) { console.warn("[V.AIX] Cache save failed:", e); }
99
- }
100
-
101
- function loadFromCache() {
102
- try {
103
- const data = localStorage.getItem(CACHE_KEY);
104
- const ts = localStorage.getItem(CACHE_TIMESTAMP_KEY);
105
- if (!data || !ts) return null;
106
-
107
- const cache = JSON.parse(data);
108
- const age = Date.now() - parseInt(ts);
109
-
110
- if (age > CACHE_EXPIRY_MS) {
111
- localStorage.removeItem(CACHE_KEY);
112
- localStorage.removeItem(CACHE_TIMESTAMP_KEY);
113
- return null;
114
- }
115
-
116
- console.log(`[V.AIX] Loaded ${cache.products.length} products from cache (${Math.round(age / 60000)}m old)`);
117
- return cache.products;
118
- } catch (e) {
119
- console.warn("[V.AIX] Cache load failed:", e);
120
- return null;
121
- }
122
- }
123
-
124
- async function fetchProducts() {
125
- if (isLoading) return;
126
- isLoading = true;
127
-
128
- // Try cache first
129
- const cached = loadFromCache();
130
- if (cached && cached.length > 0) {
131
- allProducts = cached;
132
- initSearchIndex(allProducts);
133
- updateProductCount(cached.length);
134
- isLoading = false;
135
- return;
136
- }
137
-
138
- showLoadingState(true);
139
-
140
- try {
141
- const resp = await fetch(API_PRODUCTS);
142
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
143
-
144
- const data = await resp.json();
145
- allProducts = Array.isArray(data) ? data : (data.products || []);
146
-
147
- console.log(`[V.AIX V2] Fetched ${allProducts.length} products from ${API_PRODUCTS}`);
148
-
149
- if (allProducts.length > 0) {
150
- initSearchIndex(allProducts);
151
- saveToCache();
152
- updateProductCount(allProducts.length);
153
- } else {
154
- console.warn("[V.AIX V2] No products returned from API");
155
- }
156
- } catch (e) {
157
- console.error("[V.AIX V2] Fetch failed:", e);
158
- }
159
-
160
- showLoadingState(false);
161
- isLoading = false;
162
- }
163
-
164
- function updateProductCount(count) {
165
- const el = document.getElementById("vaix-count");
166
- if (el) el.textContent = `${count.toLocaleString("vi-VN")} sản phẩm`;
167
- }
168
-
169
- function showLoadingState(show) {
170
- const el = document.getElementById("vaix-loading");
171
- if (el) el.style.display = show ? "flex" : "none";
172
- }
173
-
174
- function showProducts(productList) {
175
- const container = document.getElementById("vaix-product-list");
176
- if (!container) return;
177
-
178
- if (!productList || productList.length === 0) {
179
- container.innerHTML = `
180
- <div class="vaix-empty">
181
- <i class="fas fa-search"></i>
182
- <h3>Không tìm thấy sản phẩm</h3>
183
- <p>Thử từ khóa khác hoặc duyệt theo danh mục</p>
184
- </div>
185
- `;
186
- return;
187
- }
188
-
189
- // Group by category for compact display
190
- const categories = {};
191
- productList.forEach(p => {
192
- const cat = p.category || "Khác";
193
- if (!categories[cat]) categories[cat] = [];
194
- categories[cat].push(p);
195
- });
196
-
197
- let html = `<div class="vaix-grid">`;
198
-
199
- Object.entries(categories).forEach(([cat, products]) => {
200
- html += `<div class="vaix-category">
201
- <h3><span class="vaix-cat-name">${escHtml(cat)}</span> (${products.length})</h3>
202
- <div class="vaix-products">`;
203
-
204
- products.forEach(p => {
205
- const priceStr = formatPrice(p.price);
206
- const imgSrc = p.image || 'https://via.placeholder.com/120x120?text=V.AIX';
207
- html += `
208
- <div class="vaix-product-card" onclick="openProduct(${allProducts.indexOf(p)})">
209
- <img src="${imgSrc}" alt="${escHtml(p.title)}" loading="lazy" />
210
- <div class="vaix-product-info">
211
- <div class="vaix-product-title">${escHtml(p.title_clean || p.title)}</div>
212
- <div class="vaix-product-brand">${escHtml(p.brand || "")}</div>
213
- <div class="vaix-product-price">${priceStr}</div>
214
- ${p.sku ? `<div class="vaix-product-sku">${escHtml(p.sku)}</div>` : ""}
215
- </div>
216
- </div>
217
- `;
218
- });
219
-
220
- html += `</div></div>`;
221
- });
222
-
223
- html += `</div>`;
224
- container.innerHTML = html;
225
- }
226
-
227
- function formatPrice(price) {
228
- if (!price || price <= 0) return "Liên hệ";
229
- return price.toLocaleString("vi-VN") + "đ";
230
- }
231
-
232
- function escHtml(str) {
233
- if (!str) return "";
234
- const div = document.createElement("div");
235
- div.textContent = str;
236
- return div.innerHTML;
237
- }
238
-
239
- function openProduct(index) {
240
- if (index < 0 || index >= allProducts.length) return;
241
- showProductDetail(allProducts[index]);
242
- }
243
-
244
- function showProductDetail(product) {
245
- const container = document.getElementById("vaix-product-detail");
246
- if (!container) return;
247
-
248
- container.innerHTML = `
249
- <div class="vaix-product-detail">
250
- <button class="vaix-back-btn" onclick="closeDetail()"><i class="fas fa-arrow-left"></i> Quay lại</button>
251
- <div class="vaix-detail-grid">
252
- <img src="${product.image || 'https://via.placeholder.com/300x300?text=V.AIX'}" alt="${product.title}" />
253
- <div class="vaix-detail-info">
254
- <h2>${escHtml(product.title_clean || product.title)}</h2>
255
- <div class="vaix-detail-sku">${escHtml(product.sku || "")}</div>
256
- <div class="vaix-detail-brand">${escHtml(product.brand || "")}</div>
257
- <div class="vaix-detail-price">${formatPrice(product.price)}</div>
258
- ${product.category ? `<div class="vaix-detail-category">${escHtml(product.category)}</div>` : ""}
259
- ${product.features ? `<div class="vaix-detail-features">${escHtml(product.features)}</div>` : ""}
260
- ${product.description ? `<div class="vaix-detail-description">${escHtml(product.description)}</div>` : ""}
261
- <button class="vaix-inquire-btn" onclick="inquireProduct('${escHtml(product.title_clean || product.title)}')">
262
- <i class="fas fa-comments"></i> Tư vấn sản phẩm
263
- </button>
264
- </div>
265
- </div>
266
- </div>
267
- `;
268
- }
269
-
270
- function closeDetail() {
271
- document.getElementById("vaix-product-detail").innerHTML = "";
272
- }
273
-
274
- function inquireProduct(title) {
275
- console.log("[V.AIX] Inquiring about:", title);
276
- if (window.parent && window.parent.postMessage) {
277
- window.parent.postMessage({
278
- type: "VAIX_INQUIRY",
279
- product: title,
280
- }, "*");
281
- }
282
-
283
- const suggestEl = document.getElementById("vaix-inquiry-suggest");
284
- if (suggestEl) {
285
- suggestEl.textContent = `Bạn có muốn hỏi Gemma về "${title}"?`;
286
- suggestEl.style.display = "block";
287
- }
288
- }
289
-
290
- function clearSearch() {
291
- const input = document.getElementById("vaix-search-input");
292
- if (input) input.value = "";
293
- const container = document.getElementById("vaix-product-list");
294
- if (container) container.innerHTML = "";
295
- const detailViewBtn = document.getElementById("vaix-detail-view-btn");
296
- if (detailViewBtn) detailViewBtn.style.display = "flex";
297
- document.getElementById("vaix-product-detail").innerHTML = "";
298
- }
299
-
300
- const VAX_SEARCH = (e) => {
301
- const q = e.target.value.trim();
302
- clearTimeout(searchDebounceTimer);
303
-
304
- searchDebounceTimer = setTimeout(() => {
305
- const results = q ? searchLocal(q) : allProducts.slice(0, 20);
306
- filteredProducts = results;
307
- showProducts(results);
308
- }, 200);
309
- };
310
-
311
- window.vaix = {
312
- fetchProducts,
313
- searchLocal,
314
- openProduct,
315
- showProducts,
316
- getProducts: () => allProducts,
317
- search: (q) => searchLocal(q),
318
- };
319
-
320
- document.addEventListener("DOMContentLoaded", () => {
321
- const searchEl = document.getElementById("vaix-search-input");
322
- if (searchEl) searchEl.addEventListener("input", VAX_SEARCH);
323
- fetchProducts();
324
- });