bep40 commited on
Commit
404abc9
·
verified ·
1 Parent(s): cc0d46c

RESTORE to 9052ad3e (AI Quote v5 - stable version before CK/Excel fixes)

Browse files
Files changed (1) hide show
  1. index.html +72 -348
index.html CHANGED
@@ -1,6 +1,6 @@
1
  <!DOCTYPE html>
2
  <html lang="vi">
3
- <head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
  <title>V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi</title>
@@ -498,7 +498,7 @@ textarea.form-input{height:120px;resize:vertical}
498
  <input type="text" id="aiSearch" placeholder="🔍 AI tìm kiếm: bếp từ đôi dưới 15 triệu, máy hút mùi tốt nhất..." style="width:100%;padding:11px 14px 11px 36px;border:2px solid #003f62;border-radius:10px;font-size:.85rem;font-family:inherit;outline:none" onkeypress="if(event.key==='Enter')doAISearch()">
499
  </div>
500
  <button onclick="doAISearch()" style="padding:11px 20px;background:#003f62;color:#fff;border:none;border-radius:10px;font-weight:700;font-size:.85rem;cursor:pointer;white-space:nowrap"><i class="fas fa-search"></i> Tìm AI</button>
501
-
502
  </div>
503
  <div id="aiResults" style="max-width:900px;margin:12px auto 0;display:none;background:#f8fafc;border-radius:10px;padding:14px;font-size:.82rem"></div>
504
  </div>
@@ -670,7 +670,7 @@ textarea.form-input{height:120px;resize:vertical}
670
  <div id="aiPromptBox" style="display:none">
671
  <div style="display:flex;gap:8px">
672
  <textarea id="qcAiPrompt" placeholder="Nhập yêu cầu chiết khấu... Ví dụ: Giảm 15% tất cả sản phẩm, hoặc: SP1 giảm 2 triệu, SP2 giảm 10%..." style="flex:1;padding:8px 10px;border:1.5px solid var(--p);border-radius:8px;font-size:.82rem;font-family:inherit;outline:none;resize:none;height:48px;background:#fff"></textarea>
673
- <button onclick="applyAiDiscount()" style="padding:8px 16px;background:var(--p);color:#fff;border:none;border-radius:8px;font-size:.8rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;display:flex;align-items:center;gap:6px" id="aiDiscBtn" onclick="applyLocalCK()"><i class="fas fa-robot"></i> Áp dụng</button>
674
  </div>
675
  <div id="aiDiscStatus" style="font-size:.72rem;color:var(--g);margin-top:4px"></div>
676
  </div>
@@ -2105,7 +2105,7 @@ let discStyle='width:80px;padding:5px 6px;border:1.5px solid var(--gl);border-ra
2105
  discStyle+=locked?'background:var(--l);cursor:not-allowed':'background:#fff';
2106
  return `<tr>
2107
  <td style="text-align:center">${i+1}</td>
2108
- <td><img class="qt-img" src="${c.image}" crossorigin="anonymous" alt="${c.name}" onerror="this.style.display='none'" style="width:72px;height:72px;object-fit:contain"></td>
2109
  <td class="qt-name">${c.name}</td>
2110
  <td style="text-align:center">${model}</td>
2111
  <td style="font-size:.72rem;color:var(--g);max-width:160px">${specs}</td>
@@ -2132,36 +2132,6 @@ total+=lineTotal;
2132
  document.getElementById('quoteTotalCell').textContent=total.toLocaleString('vi-VN')+'đ';
2133
  }
2134
 
2135
- function applyLocalCK(){
2136
- // Read CK% from input (no API needed)
2137
- let ckInput=document.getElementById('ckInput')||document.querySelector('[placeholder*="chiết khấu"]')||document.querySelector('[placeholder*="CK"]');
2138
- if(!ckInput){
2139
- // Create CK input if not exists
2140
- let qtBody=document.querySelector('.quote-body');
2141
- if(qtBody){
2142
- let div=document.createElement('div');
2143
- div.style.cssText='padding:8px;background:#fff3e0;border-radius:6px;margin:8px 0;display:flex;align-items:center;gap:8px';
2144
- div.innerHTML='<label style="font-size:.78rem;font-weight:600">Chiết khấu %:</label><input id="ckInput" type="number" min="0" max="90" value="0" style="width:60px;padding:4px 8px;border:1.5px solid #ddd;border-radius:4px;font-size:.82rem" oninput="applyLocalCK()"><span id="ckStatus" style="font-size:.7rem;color:#28a745"></span>';
2145
- qtBody.insertBefore(div,qtBody.firstChild);
2146
- ckInput=document.getElementById('ckInput');
2147
- }
2148
- if(!ckInput)return;
2149
- }
2150
- let ckPercent=parseFloat(ckInput.value)||0;
2151
- if(ckPercent<0||ckPercent>90)return;
2152
- let status=document.getElementById('ckStatus');
2153
- // Apply CK to all qt-disc inputs
2154
- cart.forEach((c,i)=>{
2155
- let discInput=document.querySelector('.qt-disc[data-idx="'+i+'"]');
2156
- if(discInput&&c.priceNum>0){
2157
- let discPrice=Math.round(c.priceNum*(1-ckPercent/100));
2158
- discInput.value=discPrice.toLocaleString('vi-VN');
2159
- }
2160
- });
2161
- if(status)status.textContent=ckPercent>0?'Áp dụng CK '+ckPercent+'%':'';
2162
- updateQuoteTotal();
2163
- }
2164
-
2165
  function getQuoteData(){
2166
  let customer={
2167
  name:document.getElementById('qcName').value||'',
@@ -2409,7 +2379,7 @@ div.id='pdfRender';
2409
  div.style.cssText='position:fixed;top:-9999px;left:0;width:1100px;background:#fff;padding:40px;font-family:Inter,Arial,sans-serif';
2410
  let itemsHtml=qd.items.map(it=>`<tr style="border-bottom:1px solid #e2e8f0">
2411
  <td style="padding:10px 6px;text-align:center;font-size:13px">${it.stt}</td>
2412
- <td style="padding:10px 6px"><img src="${it.image}" style="width:80px;height:80px;object-fit:contain;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc" onerror="this.style.display='none'"></td>
2413
  <td style="padding:10px 6px;font-size:12px;font-weight:600">${it.name}</td>
2414
  <td style="padding:10px 6px;text-align:center;font-size:12px">${it.model}</td>
2415
  <td style="padding:10px 6px;font-size:11px;color:#64748b;max-width:180px">${it.specs}</td>
@@ -2646,7 +2616,7 @@ async function shareQuoteImage(){
2646
  div.style.cssText='position:fixed;top:-9999px;left:0;width:1100px;background:#fff;padding:40px;font-family:Inter,Arial,sans-serif';
2647
  let itemsHtml=qd.items.map(it=>`<tr style="border-bottom:1px solid #e2e8f0">
2648
  <td style="padding:10px 6px;text-align:center;font-size:13px">${it.stt}</td>
2649
- <td style="padding:10px 6px"><img src="${it.image}" style="width:80px;height:80px;object-fit:contain;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc" onerror="this.style.display='none'"></td>
2650
  <td style="padding:10px 6px;font-size:12px;font-weight:600">${it.name}</td>
2651
  <td style="padding:10px 6px;text-align:center;font-size:12px">${it.model}</td>
2652
  <td style="padding:10px 6px;font-size:11px;color:#64748b;max-width:180px">${it.specs}</td>
@@ -2760,60 +2730,71 @@ Miễn phí giao hàng trong TPHCM. | Giá đã bao gồm VAT.
2760
  <script>
2761
  function doAISearch(){
2762
  const q=document.getElementById('aiSearch').value.trim();
2763
- if(!q){document.getElementById('aiResults').style.display='block';document.getElementById('aiResults').innerHTML='💡 Nhập SP (VD: MDI702) hoặc nhiều mã: MDI702, MC9086HS';return}
2764
  const res=document.getElementById('aiResults');
2765
- res.style.display='block';res.innerHTML=' Đang tìm...';
2766
  if(typeof D==='undefined'||!D.length){res.innerHTML='⏳ Đang tải SP...';return}
2767
- // Split by comma/semicolon for multi-code
2768
- let queries=q.split(/[,;]+/).map(c=>c.trim()).filter(c=>c.length>=2);
2769
- if(queries.length===0)queries=[q];
2770
- let allResults=[];
2771
- queries.forEach(query=>{
2772
- const qLow=query.toLowerCase().replace(/[.\-_ \/]/g,'');
2773
- // Priority 1: EXACT match on any identifier field
2774
- let exact=D.filter(p=>{
2775
- const fields=[(p.sku||''),(p.mod||''),(p.slug||'')].map(f=>f.toLowerCase().replace(/[.\-_ \/]/g,''));
2776
- return fields.some(f=>f===qLow);
2777
- });
2778
- if(exact.length){allResults.push(...exact);return}
2779
- // Priority 2: Identifier CONTAINS query or query CONTAINS identifier
2780
- let partial=D.filter(p=>{
2781
- const fields=[(p.sku||''),(p.mod||''),(p.name||'').split('|')[0]].map(f=>f.toLowerCase().replace(/[.\-_ \/]/g,''));
2782
- return fields.some(f=>f&&qLow.length>=3&&(f.includes(qLow)||qLow.includes(f)));
2783
- });
2784
- if(partial.length){allResults.push(...partial.slice(0,5));return}
2785
- // Priority 3: Keyword search in name+category
2786
- const qNorm=query.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2787
- const words=qNorm.split(/\s+/).filter(w=>w.length>=2&&!['cua','cai','cho','cac','nhung','voi','duoi','tren','khoang'].includes(w));
2788
- const bm=query.match(/(\d+)\s*(tr|triệu|m)/i);
2789
- const budget=bm?parseInt(bm[1])*1e6:0;
2790
- let scored=D.map(p=>{
2791
- const pText=((p.name||'')+(p.cat||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2792
- let sc=0;
2793
- words.forEach(w=>{if(pText.includes(w))sc+=10});
2794
- if(/malloca/i.test(p.brand))sc+=3;
2795
- if(/grob/i.test(p.brand))sc+=2;
2796
- if(budget&&p.priceNum>0){if(p.priceNum<=budget*1.2)sc+=5;else if(p.priceNum>budget*1.5)sc-=10}
2797
- return{p,sc};
2798
- }).filter(x=>x.sc>=10).sort((a,b)=>b.sc-a.sc);
2799
- allResults.push(...scored.slice(0,6).map(x=>x.p));
2800
- });
2801
- // Deduplicate by sku
2802
- let seen=new Set();let unique=[];
2803
- allResults.forEach(p=>{const id=p.sku||p.name;if(!seen.has(id)){seen.add(id);unique.push(p)}});
2804
- if(!unique.length){res.innerHTML='❌ Không tìm thấy "'+q+'". Thử nhập chính xác mã SP.';return}
2805
- // Render results with specs
2806
- let h='<div style="font-weight:700;margin-bottom:8px;font-size:.9rem">🔍 '+unique.length+' sản phẩm:</div>';
2807
- h+='<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px">';
2808
- unique.slice(0,12).forEach(p=>{
2809
- h+='<div style="background:#fff;border-radius:10px;padding:10px;box-shadow:0 2px 6px rgba(0,0,0,.08);cursor:pointer;transition:.2s" onclick="if(typeof showDetail===\'function\')showDetail('+D.indexOf(p)+')" onmouseover="this.style.transform=\'translateY(-2px)\'" onmouseout="this.style.transform=\'\'"><div style="display:flex;align-items:center;gap:4px;margin-bottom:4px"><span style="font-size:.72rem;font-weight:700;color:#003f62">'+(p.sku||p.mod||'')+'</span><span style="font-size:.58rem;color:#888;background:#f0f0f0;padding:1px 4px;border-radius:3px">'+p.brand+'</span></div>';
2810
- if(p.image)h+='<img src="'+p.image+'" style="width:100%;height:90px;object-fit:contain;border-radius:6px;margin-bottom:6px;background:#f8f8f8" loading="lazy">';
2811
- h+='<div style="font-size:.7rem;line-height:1.3;height:2.5em;overflow:hidden;color:#333">'+((p.name||'').split('|').pop()||'').trim().substring(0,50)+'</div>';
2812
- h+='<div style="font-size:.88rem;font-weight:800;color:#003f62;margin-top:4px">'+(p.price||'Liên hệ')+'</div>';
2813
- const specs=p.specs||{};const sk=Object.entries(specs).slice(0,3);
2814
- if(sk.length){h+='<div style="margin-top:4px;padding-top:4px;border-top:1px solid #eee;font-size:.58rem;color:#555">';sk.forEach(([k,v])=>{h+='<div>'+k+': <b>'+v+'</b></div>'});h+='</div>'}
2815
- h+='<button onclick="event.stopPropagation();addToCart('+D.indexOf(p)+')" style="margin-top:5px;width:100%;padding:5px 0;background:#003f62;color:#fff;border:none;border-radius:5px;font-size:.68rem;cursor:pointer;font-weight:600">\u{1F6D2} Th\u00eam gi\u1ECF</button>';
2816
- h+='</div>';
 
 
 
 
 
 
 
 
 
 
 
2817
  });
2818
  h+='</div>';
2819
  res.innerHTML=h;
@@ -2855,11 +2836,9 @@ async function _aiSelectProducts(q,res){
2855
  });
2856
  // Call AI to select best combo
2857
  const aiPrompt=`Khách yêu cầu: "${q}"\nNgân sách: ${budget.toLocaleString('vi')}đ\nDanh sách SP có sẵn:\n${catalog.map((p,i)=>i+1+'. ['+p.sku+'] '+p.name+' | '+p.brand+' | '+p.price.toLocaleString('vi')+'đ | Loại: '+p.type).join('\n')}\n\nChọn combo TỐT NHẤT (1 SP mỗi loại, ưu tiên Malloca/Grob, tổng ≤ ngân sách). Trả lời CHỈ JSON: {"picks":[{"sku":"...","reason":"lý do chọn ngắn"}]}`;
2858
- // AI disabled (no token) - use random fallback
2859
- _doQuoteFallback(wantTypes,perBudget,budget,res);return;
2860
- const aiRes=null&&await fetch('https://router.huggingface.co/v1/chat/completions',{
2861
  method:'POST',
2862
- headers:{'Content-Type':'application/json','Authorization':'Bearer '+''},
2863
  body:JSON.stringify({model:'Qwen/Qwen2.5-72B-Instruct',messages:[{role:'user',content:aiPrompt}],max_tokens:300,temperature:0.7})
2864
  });
2865
  if(!aiRes.ok){
@@ -2920,7 +2899,7 @@ function _renderQuote(picks,total,budget,res,isAI){
2920
  h+='<table style="width:100%;border-collapse:collapse;font-size:.8rem">';
2921
  h+='<tr style="background:#003f62;color:#fff"><th style="padding:8px;text-align:left">Loại</th><th style="text-align:left">Sản phẩm</th><th>Brand</th><th style="text-align:right;padding-right:8px">Giá</th></tr>';
2922
  picks.forEach((p,i)=>{
2923
- h+='<tr style="border-bottom:1px solid #e2e8f0;'+(i%2?'background:#f8fafc':'')+'"><td style="padding:8px;font-weight:600">'+p.typeName+'</td><td>'+p.name.substring(0,38)+(p.reason?'<br><span style="font-size:.65rem;color:#28a745">💡 '+p.reason+'</span>':'')+'<br><span style="font-size:.62rem;color:#888">'+p.sku+(p.specs&&Object.keys(p.specs).length?' | '+Object.entries(p.specs).slice(0,2).map(([k,v])=>k+':'+v).join(', '):'')+'</span></td><td style="text-align:center;font-size:.75rem">'+p.brand+'</td><td style="text-align:right;font-weight:700;color:#003f62;padding-right:8px">'+(p.price||'LH')+'</td></tr>';
2924
  });
2925
  h+='<tr style="background:#003f62;color:#fff;font-weight:800"><td colspan="3" style="padding:10px;text-align:right">TỔNG:</td><td style="text-align:right;padding-right:8px;font-size:.95rem">'+total.toLocaleString('vi')+'đ</td></tr></table>';
2926
  const diff=budget-total;
@@ -2932,260 +2911,5 @@ function _renderQuote(picks,total,budget,res,isAI){
2932
  }
2933
  function _addQuotePicks(){if(!window._quotePicks)return;window._quotePicks.forEach(p=>{let idx=D.findIndex(d=>d.sku===p.sku);if(idx>=0&&typeof addToCart==='function')addToCart(idx)});document.getElementById('aiResults').innerHTML='✅ Đã thêm '+window._quotePicks.length+' SP vào giỏ hàng!';}
2934
  </script>
2935
-
2936
- <script>
2937
- // Chat AI: intercept messages and show search/quote results directly
2938
- (function(){
2939
- // Hook into chat send if exists
2940
- const origChatSend=window.sendChatMessage;
2941
- if(typeof origChatSend==='function'){
2942
- window.sendChatMessage=function(msg){
2943
- if(_handleChatAI(msg))return;
2944
- origChatSend(msg);
2945
- }
2946
- }
2947
- // Also expose for manual use
2948
- window.chatAI=function(msg){_handleChatAI(msg)};
2949
- })();
2950
- function _handleChatAI(msg){
2951
- if(!msg||typeof D==='undefined')return false;
2952
- const lo=msg.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2953
- // Detect intent
2954
- const isQuote=lo.includes('bao gia')||lo.includes('combo')||lo.includes('du toan');
2955
- const isSearch=lo.includes('tim')||lo.includes('kiem')||/[A-Z]{2,}\d{2,}/i.test(msg)||msg.includes(',');
2956
- if(isQuote||isSearch){
2957
- document.getElementById('aiSearch').value=msg;
2958
- if(isQuote)doAIQuote();else doAISearch();
2959
- // Scroll to results
2960
- document.getElementById('aiResults').scrollIntoView({behavior:'smooth',block:'start'});
2961
- return true;
2962
- }
2963
- return false;
2964
- }
2965
- </script>
2966
- <script>
2967
- // Intercept chat AI messages to use AI Search/Quote
2968
- document.addEventListener('DOMContentLoaded',function(){
2969
- // Find chat send button and input
2970
- const chatInput=document.querySelector('.chat-input');
2971
- const chatSend=document.querySelector('.chat-send');
2972
- if(chatInput&&chatSend){
2973
- const origClick=chatSend.onclick;
2974
- chatSend.addEventListener('click',function(e){
2975
- const msg=chatInput.value.trim();
2976
- if(msg&&_tryChatAI(msg)){chatInput.value='';e.stopPropagation();e.preventDefault();return false}
2977
- },true);
2978
- chatInput.addEventListener('keypress',function(e){
2979
- if(e.key==='Enter'){
2980
- const msg=chatInput.value.trim();
2981
- if(msg&&_tryChatAI(msg)){chatInput.value='';e.stopPropagation();e.preventDefault();return false}
2982
- }
2983
- },true);
2984
- }
2985
- });
2986
- function _tryChatAI(msg){
2987
- const lo=msg.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2988
- // Detect: search intent, quote intent, or product code
2989
- const isCode=/^[A-Z]{2,}[\-]?[A-Z0-9]{2,}/i.test(msg.trim());
2990
- const hasComma=/[,;]/.test(msg);
2991
- const isQuote=lo.includes('bao gia')||lo.includes('combo')||lo.includes('du toan');
2992
- const isSearch=lo.includes('tim')||lo.includes('kiem')||lo.includes('goi y')||isCode||hasComma;
2993
- if(isQuote||isSearch||isCode){
2994
- // Put query in AI search box
2995
- document.getElementById('aiSearch').value=msg;
2996
- // Trigger appropriate function
2997
- if(isQuote){doAIQuote()}else{doAISearch()}
2998
- // Show chat response
2999
- const chatBody=document.querySelector('.chat-body');
3000
- if(chatBody){
3001
- const bubble=document.createElement('div');
3002
- bubble.className='chat-msg bot';
3003
- bubble.innerHTML=isQuote?'📋 Em đã lên báo giá bên dưới ↓':'🔍 Em đã tìm SP bên dưới ↓';
3004
- chatBody.appendChild(bubble);
3005
- chatBody.scrollTop=chatBody.scrollHeight;
3006
- }
3007
- // Scroll to results
3008
- setTimeout(()=>{document.getElementById('aiResults').scrollIntoView({behavior:'smooth',block:'center'})},300);
3009
- return true;
3010
- }
3011
- return false;
3012
- }
3013
- </script>
3014
- <script>
3015
- // === FIX 1: Ensure quote table shows product images ===
3016
- // Override/patch the quote rendering to include images
3017
- (function(){
3018
- var origOpen = window.openQuoteModal || window.showQuoteModal;
3019
- // Patch: after quote modal opens, fix images
3020
- var fixQuoteImages = function(){
3021
- setTimeout(function(){
3022
- var rows = document.querySelectorAll('.quote-table tr');
3023
- var cart = window.CART || [];
3024
- rows.forEach(function(row, i){
3025
- if(i === 0) return; // skip header
3026
- var imgCell = row.querySelector('.qt-img, img');
3027
- var cartItem = cart[i-1];
3028
- if(cartItem && !imgCell){
3029
- // Add image if missing
3030
- var firstTd = row.querySelector('td');
3031
- if(firstTd && cartItem.image){
3032
- firstTd.innerHTML = '<img class="qt-img" src="'+cartItem.image+'" style="width:60px;height:60px;object-fit:contain;border-radius:4px">';
3033
- }
3034
- } else if(imgCell && cartItem && cartItem.image && imgCell.tagName === 'IMG' && !imgCell.src){
3035
- imgCell.src = cartItem.image;
3036
- }
3037
- });
3038
- }, 200);
3039
- };
3040
- // Hook into quote modal open
3041
- var origOpenCart = window.openCheckout || window.openQuote;
3042
- if(origOpenCart){
3043
- var wrapped = function(){
3044
- origOpenCart.apply(this, arguments);
3045
- fixQuoteImages();
3046
- };
3047
- if(window.openCheckout) window.openCheckout = wrapped;
3048
- if(window.openQuote) window.openQuote = wrapped;
3049
- }
3050
- document.addEventListener('click', function(e){
3051
- if(e.target && (e.target.textContent||'').includes('Báo giá')){
3052
- fixQuoteImages();
3053
- }
3054
- });
3055
- })();
3056
-
3057
- // === FIX 2: CK discount calculation - client side, no API ===
3058
- (function(){
3059
- document.addEventListener('input', function(e){
3060
- if(e.target && e.target.classList.contains('qt-disc')){
3061
- var row = e.target.closest('tr');
3062
- if(!row) return;
3063
- var ckVal = parseFloat(e.target.value) || 0;
3064
- // Find price cell (don gia)
3065
- var cells = row.querySelectorAll('td');
3066
- var priceCell = null;
3067
- var ckPriceCell = null;
3068
- var ttCell = null;
3069
- var qtyCell = null;
3070
- cells.forEach(function(td, idx){
3071
- var text = td.textContent.replace(/[^\d]/g,'');
3072
- if(td.querySelector('.qt-disc')) return;
3073
- if(td.classList.contains('qt-price') || (text.length > 4 && !priceCell && idx > 2)){
3074
- if(!priceCell) priceCell = td;
3075
- else if(!ckPriceCell) ckPriceCell = td;
3076
- else if(!ttCell) ttCell = td;
3077
- }
3078
- });
3079
- // Get original price from data attribute or cell content
3080
- var origPrice = parseInt((row.dataset.price || (priceCell?priceCell.textContent:'')).replace(/[^\d]/g,'')) || 0;
3081
- var qty = parseInt(row.dataset.qty || '1') || 1;
3082
- if(origPrice > 0 && ckVal >= 0){
3083
- var discPrice = Math.round(origPrice * (1 - ckVal/100));
3084
- var thanhTien = discPrice * qty;
3085
- // Update cells
3086
- if(ckPriceCell) ckPriceCell.textContent = discPrice.toLocaleString('vi-VN');
3087
- if(ttCell) ttCell.textContent = thanhTien.toLocaleString('vi-VN');
3088
- // Update total
3089
- var totalEl = document.querySelector('.quote-total-row td:last-child') || document.querySelector('[class*="quote-total"]');
3090
- if(totalEl){
3091
- var allTT = 0;
3092
- document.querySelectorAll('.quote-table tr').forEach(function(r,i){
3093
- if(i===0)return;
3094
- var tds = r.querySelectorAll('td');
3095
- var lastNum = tds[tds.length-1];
3096
- if(lastNum) allTT += parseInt(lastNum.textContent.replace(/[^\d]/g,''))||0;
3097
- });
3098
- totalEl.textContent = allTT.toLocaleString('vi-VN') + ' VNĐ';
3099
- }
3100
- }
3101
- }
3102
- });
3103
- })();
3104
-
3105
- // === FIX 3: Patch Excel export to use proper number format ===
3106
- (function(){
3107
- var origExport = window.exportQuoteExcel || window.downloadExcel;
3108
- window.exportQuoteExcel = window.downloadExcel = function(){
3109
- // If ExcelJS is available, create proper workbook
3110
- if(typeof ExcelJS === 'undefined'){
3111
- if(origExport) return origExport.apply(this, arguments);
3112
- alert('Excel library not loaded');
3113
- return;
3114
- }
3115
- var wb = new ExcelJS.Workbook();
3116
- var ws = wb.addWorksheet('Bao Gia');
3117
- // Header
3118
- ws.columns = [
3119
- {header:'STT', key:'stt', width:5},
3120
- {header:'Ma SP', key:'sku', width:15},
3121
- {header:'Ten SP', key:'name', width:40},
3122
- {header:'DVT', key:'dvt', width:6},
3123
- {header:'SL', key:'qty', width:5},
3124
- {header:'Don gia', key:'price', width:15},
3125
- {header:'CK %', key:'ck', width:8},
3126
- {header:'Don gia CK', key:'ckprice', width:15},
3127
- {header:'Thanh tien', key:'total', width:15},
3128
- ];
3129
- // Style header
3130
- ws.getRow(1).font = {bold: true};
3131
- ws.getRow(1).fill = {type:'pattern', pattern:'solid', fgColor:{argb:'FF1F4E79'}};
3132
- ws.getRow(1).font = {bold:true, color:{argb:'FFFFFFFF'}};
3133
- // Get data from quote table
3134
- var rows = document.querySelectorAll('.quote-table tr');
3135
- var grandTotal = 0;
3136
- rows.forEach(function(row, i){
3137
- if(i===0) return; // skip header
3138
- if(row.classList.contains('quote-total-row')) return;
3139
- var cells = row.querySelectorAll('td');
3140
- var ckInput = row.querySelector('.qt-disc');
3141
- var ckVal = ckInput ? (parseFloat(ckInput.value)||0) : 0;
3142
- // Extract values
3143
- var texts = Array.from(cells).map(function(c){return c.textContent.trim()});
3144
- var price = parseInt((row.dataset.price || texts[5] || '0').replace(/[^\d]/g,'')) || 0;
3145
- var qty = parseInt(row.dataset.qty || texts[4] || '1') || 1;
3146
- var ckPrice = Math.round(price * (1 - ckVal/100));
3147
- var tt = ckPrice * qty;
3148
- grandTotal += tt;
3149
- var dataRow = ws.addRow({
3150
- stt: i,
3151
- sku: texts[1] || '',
3152
- name: texts[2] || '',
3153
- dvt: 'Bộ',
3154
- qty: qty,
3155
- price: price,
3156
- ck: ckVal,
3157
- ckprice: ckPrice,
3158
- total: tt
3159
- });
3160
- // Number format for money columns
3161
- dataRow.getCell('price').numFmt = '#,##0';
3162
- dataRow.getCell('ckprice').numFmt = '#,##0';
3163
- dataRow.getCell('total').numFmt = '#,##0';
3164
- });
3165
- // Total row
3166
- var totalRow = ws.addRow({name:'TONG CONG', total: grandTotal});
3167
- totalRow.font = {bold:true};
3168
- totalRow.getCell('total').numFmt = '#,##0';
3169
- // Add formulas
3170
- var dataStart = 2;
3171
- var dataEnd = ws.rowCount - 1;
3172
- for(var r=dataStart; r<=dataEnd; r++){
3173
- ws.getCell('H'+r).value = {formula: 'F'+r+'*(1-G'+r+'/100)'};
3174
- ws.getCell('I'+r).value = {formula: 'H'+r+'*E'+r};
3175
- }
3176
- ws.getCell('I'+(dataEnd+1)).value = {formula: 'SUM(I'+dataStart+':I'+dataEnd+')'};
3177
- // Download
3178
- wb.xlsx.writeBuffer().then(function(buf){
3179
- var blob = new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
3180
- var url = URL.createObjectURL(blob);
3181
- var a = document.createElement('a');
3182
- a.href = url;
3183
- a.download = 'BaoGia_VAISTUDIO_' + new Date().toISOString().slice(0,10) + '.xlsx';
3184
- a.click();
3185
- URL.revokeObjectURL(url);
3186
- });
3187
- };
3188
- })();
3189
- </script>
3190
  </body>
3191
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="vi">
3
+ <head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":""}};</script>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
  <title>V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi</title>
 
498
  <input type="text" id="aiSearch" placeholder="🔍 AI tìm kiếm: bếp từ đôi dưới 15 triệu, máy hút mùi tốt nhất..." style="width:100%;padding:11px 14px 11px 36px;border:2px solid #003f62;border-radius:10px;font-size:.85rem;font-family:inherit;outline:none" onkeypress="if(event.key==='Enter')doAISearch()">
499
  </div>
500
  <button onclick="doAISearch()" style="padding:11px 20px;background:#003f62;color:#fff;border:none;border-radius:10px;font-weight:700;font-size:.85rem;cursor:pointer;white-space:nowrap"><i class="fas fa-search"></i> Tìm AI</button>
501
+ <button onclick="doAIQuote()" style="padding:11px 20px;background:#db9815;color:#fff;border:none;border-radius:10px;font-weight:700;font-size:.85rem;cursor:pointer;white-space:nowrap"><i class="fas fa-file-invoice-dollar"></i> AI Báo giá</button>
502
  </div>
503
  <div id="aiResults" style="max-width:900px;margin:12px auto 0;display:none;background:#f8fafc;border-radius:10px;padding:14px;font-size:.82rem"></div>
504
  </div>
 
670
  <div id="aiPromptBox" style="display:none">
671
  <div style="display:flex;gap:8px">
672
  <textarea id="qcAiPrompt" placeholder="Nhập yêu cầu chiết khấu... Ví dụ: Giảm 15% tất cả sản phẩm, hoặc: SP1 giảm 2 triệu, SP2 giảm 10%..." style="flex:1;padding:8px 10px;border:1.5px solid var(--p);border-radius:8px;font-size:.82rem;font-family:inherit;outline:none;resize:none;height:48px;background:#fff"></textarea>
673
+ <button onclick="applyAiDiscount()" style="padding:8px 16px;background:var(--p);color:#fff;border:none;border-radius:8px;font-size:.8rem;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;display:flex;align-items:center;gap:6px" id="aiDiscBtn"><i class="fas fa-robot"></i> Áp dụng</button>
674
  </div>
675
  <div id="aiDiscStatus" style="font-size:.72rem;color:var(--g);margin-top:4px"></div>
676
  </div>
 
2105
  discStyle+=locked?'background:var(--l);cursor:not-allowed':'background:#fff';
2106
  return `<tr>
2107
  <td style="text-align:center">${i+1}</td>
2108
+ <td><img class="qt-img" src="${c.image}" alt="${c.name}" crossorigin="anonymous" onerror="this.style.display='none'" style="width:72px;height:72px;object-fit:contain"></td>
2109
  <td class="qt-name">${c.name}</td>
2110
  <td style="text-align:center">${model}</td>
2111
  <td style="font-size:.72rem;color:var(--g);max-width:160px">${specs}</td>
 
2132
  document.getElementById('quoteTotalCell').textContent=total.toLocaleString('vi-VN')+'đ';
2133
  }
2134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2135
  function getQuoteData(){
2136
  let customer={
2137
  name:document.getElementById('qcName').value||'',
 
2379
  div.style.cssText='position:fixed;top:-9999px;left:0;width:1100px;background:#fff;padding:40px;font-family:Inter,Arial,sans-serif';
2380
  let itemsHtml=qd.items.map(it=>`<tr style="border-bottom:1px solid #e2e8f0">
2381
  <td style="padding:10px 6px;text-align:center;font-size:13px">${it.stt}</td>
2382
+ <td style="padding:10px 6px"><img src="${it.image}" style="width:80px;height:80px;object-fit:contain;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc" crossorigin="anonymous" onerror="this.style.display='none'"></td>
2383
  <td style="padding:10px 6px;font-size:12px;font-weight:600">${it.name}</td>
2384
  <td style="padding:10px 6px;text-align:center;font-size:12px">${it.model}</td>
2385
  <td style="padding:10px 6px;font-size:11px;color:#64748b;max-width:180px">${it.specs}</td>
 
2616
  div.style.cssText='position:fixed;top:-9999px;left:0;width:1100px;background:#fff;padding:40px;font-family:Inter,Arial,sans-serif';
2617
  let itemsHtml=qd.items.map(it=>`<tr style="border-bottom:1px solid #e2e8f0">
2618
  <td style="padding:10px 6px;text-align:center;font-size:13px">${it.stt}</td>
2619
+ <td style="padding:10px 6px"><img src="${it.image}" style="width:80px;height:80px;object-fit:contain;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc" crossorigin="anonymous" onerror="this.style.display='none'"></td>
2620
  <td style="padding:10px 6px;font-size:12px;font-weight:600">${it.name}</td>
2621
  <td style="padding:10px 6px;text-align:center;font-size:12px">${it.model}</td>
2622
  <td style="padding:10px 6px;font-size:11px;color:#64748b;max-width:180px">${it.specs}</td>
 
2730
  <script>
2731
  function doAISearch(){
2732
  const q=document.getElementById('aiSearch').value.trim();
2733
+ if(!q){document.getElementById('aiResults').style.display='block';document.getElementById('aiResults').innerHTML='💡 Nhập yêu cầu: "bếp từ đôi dưới 15 triệu", "máy hút mùi tốt"...';return}
2734
  const res=document.getElementById('aiResults');
2735
+ res.style.display='block';res.innerHTML='<i class="fas fa-spinner fa-spin"></i> AI đang tìm...';
2736
  if(typeof D==='undefined'||!D.length){res.innerHTML='⏳ Đang tải SP...';return}
2737
+ setTimeout(()=>{_doSearch(q,res)},100);
2738
+ }
2739
+ function _doSearch(q,res){
2740
+ const qn=q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2741
+ // Parse budget
2742
+ const bm=q.match(/(\d+)\s*(tr|triệu|m)/i);
2743
+ const budget=bm?parseInt(bm[1])*1000000:0;
2744
+ // Parse color preference
2745
+ const colors={'den':'đen','trang':'trắng','bac':'bạc','ghi':'ghi','inox':'inox','chrome':'chrome','vang':'vàng'};
2746
+ let wantColor='';
2747
+ for(const[k,v] of Object.entries(colors)){if(qn.includes(k)){wantColor=k;break}}
2748
+ // Parse size preference
2749
+ let wantSize='';
2750
+ const sizeM=qn.match(/(\d+)\s*(cm|mm)/);
2751
+ if(sizeM)wantSize=sizeM[1];
2752
+ const slotM=qn.match(/(doi|đôi|2 vung|2 lo|3 vung|don|đơn|1 vung)/);
2753
+ let wantSlot=slotM?slotM[1]:'';
2754
+ // Extract main keywords (remove noise)
2755
+ const noise=['tu van','tư vấn','khoang','khoảng','duoi','dưới','tren','trên','tot','tốt','nhat','nhất','ban','bán','chay','re','rẻ','chat','chất','luong','lượng','gia','giá','nen','nên','mua','cho','toi','tôi','em','anh','chị','cai','cái','loai','loại','nao','nào','gi','gì'];
2756
+ const words=qn.split(/\s+/).filter(w=>w.length>=2&&!noise.includes(w)&&!w.match(/^\d+$/));
2757
+ // Score each product
2758
+ let scored=D.map((p,idx)=>{
2759
+ const pn=((p.name||'')+(p.cat||'')+(p.brand||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2760
+ let score=0;
2761
+ // Keyword match
2762
+ words.forEach(w=>{if(pn.includes(w))score+=20});
2763
+ // Color match
2764
+ if(wantColor&&pn.includes(wantColor))score+=15;
2765
+ // Size/slot match
2766
+ if(wantSlot&&pn.includes(wantSlot))score+=15;
2767
+ if(wantSize&&pn.includes(wantSize))score+=10;
2768
+ // Brand bonus
2769
+ if(/malloca/i.test(p.brand))score+=5;
2770
+ else if(/grob/i.test(p.brand))score+=4;
2771
+ // Budget filter
2772
+ if(budget>0&&p.priceNum>0){
2773
+ if(p.priceNum<=budget)score+=10;
2774
+ else if(p.priceNum<=budget*1.3)score+=3;
2775
+ else score-=20;
2776
+ }
2777
+ return{p,score,idx};
2778
+ }).filter(x=>x.score>15);
2779
+ // Sort by score desc, then add randomness in top tier
2780
+ scored.sort((a,b)=>b.score-a.score);
2781
+ // Shuffle within same-score groups for variety
2782
+ let top=scored.slice(0,12);
2783
+ for(let i=top.length-1;i>0;i--){
2784
+ if(top[i].score===top[i-1].score){
2785
+ if(Math.random()>0.5)[top[i],top[i-1]]=[top[i-1],top[i]];
2786
+ }
2787
+ }
2788
+ let results=top.slice(0,8).map(x=>x.p);
2789
+ if(!results.length){res.innerHTML='😅 Không tìm thấy. Thử: "bếp từ đôi", "máy hút mùi 90cm"...';return}
2790
+ let h='<div style="font-weight:700;margin-bottom:8px">🎯 '+results.length+' SP phù hợp'+(budget?' (≤'+budget.toLocaleString('vi')+'đ)':'')+(wantColor?' | màu '+colors[wantColor]:'')+':</div>';
2791
+ h+='<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px">';
2792
+ results.forEach((p,i)=>{
2793
+ h+='<div style="background:#fff;border-radius:8px;padding:10px;box-shadow:0 1px 3px rgba(0,0,0,.08);cursor:pointer;transition:.2s" onclick="if(typeof showDetail===\'function\')showDetail('+D.indexOf(p)+')" onmouseover="this.style.transform=\'translateY(-2px)\'" onmouseout="this.style.transform=\'none\'">';
2794
+ if(p.image)h+='<img src="'+p.image+'" style="width:100%;height:90px;object-fit:contain;border-radius:4px;margin-bottom:6px" loading="lazy">';
2795
+ h+='<div style="font-size:.72rem;font-weight:600;line-height:1.3;height:2.6em;overflow:hidden">'+p.name.substring(0,45)+'</div>';
2796
+ h+='<div style="font-size:.82rem;font-weight:800;color:#003f62;margin-top:4px">'+(p.price||'LH')+'</div>';
2797
+ h+='<div style="font-size:.62rem;color:#64748b">'+p.brand+' | '+p.sku+'</div></div>';
2798
  });
2799
  h+='</div>';
2800
  res.innerHTML=h;
 
2836
  });
2837
  // Call AI to select best combo
2838
  const aiPrompt=`Khách yêu cầu: "${q}"\nNgân sách: ${budget.toLocaleString('vi')}đ\nDanh sách SP có sẵn:\n${catalog.map((p,i)=>i+1+'. ['+p.sku+'] '+p.name+' | '+p.brand+' | '+p.price.toLocaleString('vi')+'đ | Loại: '+p.type).join('\n')}\n\nChọn combo TỐT NHẤT (1 SP mỗi loại, ưu tiên Malloca/Grob, tổng ≤ ngân sách). Trả lời CHỈ JSON: {"picks":[{"sku":"...","reason":"lý do chọn ngắn"}]}`;
2839
+ const aiRes=await fetch('https://router.huggingface.co/v1/chat/completions',{
 
 
2840
  method:'POST',
2841
+ headers:{'Content-Type':'application/json','Authorization':'Bearer '+document.querySelector('meta[name=hf-token]')?.content||''},
2842
  body:JSON.stringify({model:'Qwen/Qwen2.5-72B-Instruct',messages:[{role:'user',content:aiPrompt}],max_tokens:300,temperature:0.7})
2843
  });
2844
  if(!aiRes.ok){
 
2899
  h+='<table style="width:100%;border-collapse:collapse;font-size:.8rem">';
2900
  h+='<tr style="background:#003f62;color:#fff"><th style="padding:8px;text-align:left">Loại</th><th style="text-align:left">Sản phẩm</th><th>Brand</th><th style="text-align:right;padding-right:8px">Giá</th></tr>';
2901
  picks.forEach((p,i)=>{
2902
+ h+='<tr style="border-bottom:1px solid #e2e8f0;'+(i%2?'background:#f8fafc':'')+'"><td style="padding:8px;font-weight:600">'+p.typeName+'</td><td>'+p.name.substring(0,38)+(p.reason?'<br><span style="font-size:.65rem;color:#28a745">💡 '+p.reason+'</span>':'')+'<br><span style="font-size:.65rem;color:#888">'+p.sku+'</span></td><td style="text-align:center;font-size:.75rem">'+p.brand+'</td><td style="text-align:right;font-weight:700;color:#003f62;padding-right:8px">'+(p.price||'LH')+'</td></tr>';
2903
  });
2904
  h+='<tr style="background:#003f62;color:#fff;font-weight:800"><td colspan="3" style="padding:10px;text-align:right">TỔNG:</td><td style="text-align:right;padding-right:8px;font-size:.95rem">'+total.toLocaleString('vi')+'đ</td></tr></table>';
2905
  const diff=budget-total;
 
2911
  }
2912
  function _addQuotePicks(){if(!window._quotePicks)return;window._quotePicks.forEach(p=>{let idx=D.findIndex(d=>d.sku===p.sku);if(idx>=0&&typeof addToCart==='function')addToCart(idx)});document.getElementById('aiResults').innerHTML='✅ Đã thêm '+window._quotePicks.length+' SP vào giỏ hàng!';}
2913
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2914
  </body>
2915
  </html>