Spaces:
Running
Running
RESTORE to 9052ad3e (AI Quote v5 - stable version before CK/Excel fixes)
Browse files- 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>
|
| 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"
|
| 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}"
|
| 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
|
| 2764 |
const res=document.getElementById('aiResults');
|
| 2765 |
-
res.style.display='block';res.innerHTML='
|
| 2766 |
if(typeof D==='undefined'||!D.length){res.innerHTML='⏳ Đang tải SP...';return}
|
| 2767 |
-
|
| 2768 |
-
|
| 2769 |
-
|
| 2770 |
-
|
| 2771 |
-
|
| 2772 |
-
|
| 2773 |
-
|
| 2774 |
-
|
| 2775 |
-
|
| 2776 |
-
|
| 2777 |
-
|
| 2778 |
-
|
| 2779 |
-
|
| 2780 |
-
|
| 2781 |
-
|
| 2782 |
-
|
| 2783 |
-
|
| 2784 |
-
|
| 2785 |
-
|
| 2786 |
-
|
| 2787 |
-
|
| 2788 |
-
|
| 2789 |
-
const
|
| 2790 |
-
let
|
| 2791 |
-
|
| 2792 |
-
|
| 2793 |
-
|
| 2794 |
-
|
| 2795 |
-
|
| 2796 |
-
|
| 2797 |
-
|
| 2798 |
-
|
| 2799 |
-
|
| 2800 |
-
|
| 2801 |
-
|
| 2802 |
-
|
| 2803 |
-
|
| 2804 |
-
|
| 2805 |
-
|
| 2806 |
-
|
| 2807 |
-
|
| 2808 |
-
|
| 2809 |
-
|
| 2810 |
-
|
| 2811 |
-
|
| 2812 |
-
|
| 2813 |
-
|
| 2814 |
-
if(
|
| 2815 |
-
|
| 2816 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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:.
|
| 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>
|