bep40 commited on
Commit
9052ad3
·
verified ·
1 Parent(s): b83e6ef

AI Quote v5: LLM (Qwen-72B) selects combo based on prompt + fallback random

Browse files
Files changed (1) hide show
  1. index.html +88 -37
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>
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>
@@ -2802,53 +2802,104 @@ function _doSearch(q,res){
2802
  function doAIQuote(){
2803
  const q=document.getElementById('aiSearch').value.trim()||'bếp từ + máy hút mùi + chậu rửa khoảng 50 triệu';
2804
  const res=document.getElementById('aiResults');
2805
- res.style.display='block';res.innerHTML='<i class="fas fa-spinner fa-spin"></i> AI đang lên báo giá...';
2806
- if(typeof D==='undefined'||!D.length){res.innerHTML='⏳ Đang tải...';return}
2807
- setTimeout(()=>{_doQuote(q,res)},100);
2808
  }
2809
- function _doQuote(q,res){
2810
- const qn=q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2811
- const bm=q.match(/(\d+)\s*(tr|triệu|m)/i);
2812
- const budget=bm?parseInt(bm[1])*1000000:50000000;
2813
- const typeMap=[{k:['bep tu','bep dien','bep gas','bep'],n:'Bếp'},{k:['may hut','hut mui','hut khoi'],n:'Máy hút mùi'},{k:['chau rua','bon rua','chau'],n:'Chậu rửa'},{k:['voi rua','voi'],n:'Vòi rửa'},{k:['lo nuong'],n:'Lò nướng'},{k:['may rua bat','may rua chen'],n:'Máy rửa bát'}];
2814
- let wantTypes=typeMap.filter(t=>t.k.some(k=>qn.includes(k)));
2815
- if(!wantTypes.length)wantTypes=typeMap.slice(0,3);
2816
- const perBudget=Math.floor(budget/wantTypes.length);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2817
  let picks=[];let total=0;
 
2818
  wantTypes.forEach(typ=>{
2819
- // Get ALL matching products with price
2820
  let all=D.filter(p=>{
2821
  const pn=((p.name||'')+(p.cat||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2822
  return typ.k.some(k=>pn.includes(k))&&p.priceNum>0&&p.priceNum<=perBudget*1.5;
2823
  });
2824
- // Separate brands
2825
- let malloca=all.filter(p=>/malloca/i.test(p.brand));
2826
- let grob=all.filter(p=>/grob/i.test(p.brand));
2827
- let others=all.filter(p=>!/malloca|grob/i.test(p.brand));
2828
- // Shuffle EACH brand pool independently
2829
- function shuffle(a){for(let i=a.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a}
2830
- shuffle(malloca);shuffle(grob);shuffle(others);
2831
- // Build pool: random Malloca (60%) + Grob (30%) + others (10%)
2832
- let pool=[];
2833
- pool.push(...malloca.slice(0,Math.max(3,Math.ceil(malloca.length*0.6))));
2834
- pool.push(...grob.slice(0,Math.max(2,Math.ceil(grob.length*0.3))));
2835
- pool.push(...others.slice(0,2));
2836
- shuffle(pool);// Final shuffle
2837
- // Filter by budget
2838
- let affordable=pool.filter(p=>p.priceNum<=perBudget*1.3);
2839
- if(!affordable.length)affordable=pool.slice(0,5);
2840
- // Pick ONE random
2841
- if(affordable.length){
2842
- let pick=affordable[Math.floor(Math.random()*affordable.length)];
2843
- picks.push({...pick,typeName:typ.n});total+=pick.priceNum;
2844
- }
2845
  });
2846
- if(!picks.length){res.innerHTML='😅 Không đủ SP. Thử tăng ngân sách!';return}
2847
- let h='<div style="font-weight:700;margin-bottom:10px;font-size:.95rem">📋 AI BÁO GIÁ <span style="color:#64748b;font-weight:400">(~'+budget.toLocaleString('vi')+'đ)</span></div>';
 
 
 
2848
  h+='<table style="width:100%;border-collapse:collapse;font-size:.8rem">';
2849
  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>';
2850
  picks.forEach((p,i)=>{
2851
- 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)+'<br><span style="font-size:.65rem;color:#888">'+p.sku+'</span></td><td style="text-align:center">'+p.brand+'</td><td style="text-align:right;font-weight:700;color:#003f62;padding-right:8px">'+(p.price||'LH')+'</td></tr>';
2852
  });
2853
  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>';
2854
  const diff=budget-total;
 
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>
 
2802
  function doAIQuote(){
2803
  const q=document.getElementById('aiSearch').value.trim()||'bếp từ + máy hút mùi + chậu rửa khoảng 50 triệu';
2804
  const res=document.getElementById('aiResults');
2805
+ res.style.display='block';
2806
+ res.innerHTML='<div style="text-align:center;padding:20px"><i class="fas fa-robot fa-spin" style="font-size:1.5rem;color:#003f62"></i><br><span style="font-size:.85rem;margin-top:8px;display:block">AI đang phân tích yêu cầu & chọn combo tốt nhất...</span></div>';
2807
+ _aiSelectProducts(q,res);
2808
  }
2809
+ async function _aiSelectProducts(q,res){
2810
+ try{
2811
+ if(typeof D==='undefined'||!D.length){res.innerHTML='⏳ Đang tải SP...';return}
2812
+ // Parse budget
2813
+ const bm=q.match(/(\d+)\s*(tr|triệu|m)/i);
2814
+ const budget=bm?parseInt(bm[1])*1000000:50000000;
2815
+ // Get product summary for AI (top products per category, Malloca/Grob priority)
2816
+ const typeMap=[{k:['bep tu','bep dien','bep'],n:'Bếp từ'},{k:['may hut','hut mui'],n:'Máy hút mùi'},{k:['chau rua','bon rua'],n:'Chậu rửa'},{k:['voi rua'],n:'Vòi rửa'},{k:['lo nuong'],n:'Lò nướng'}];
2817
+ const qn=q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2818
+ let wantTypes=typeMap.filter(t=>t.k.some(k=>qn.includes(k)));
2819
+ if(!wantTypes.length)wantTypes=typeMap.slice(0,3);
2820
+ const perBudget=Math.floor(budget/wantTypes.length);
2821
+ // Build catalog summary for AI
2822
+ let catalog=[];
2823
+ wantTypes.forEach(typ=>{
2824
+ let items=D.filter(p=>{
2825
+ const pn=((p.name||'')+(p.cat||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2826
+ return typ.k.some(k=>pn.includes(k))&&p.priceNum>0&&p.priceNum<=perBudget*1.5;
2827
+ });
2828
+ // Shuffle and take samples for AI to choose from
2829
+ for(let i=items.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[items[i],items[j]]=[items[j],items[i]]}
2830
+ // Priority: Malloca/Grob first in the list AI sees
2831
+ let m=items.filter(p=>/malloca/i.test(p.brand));
2832
+ let g=items.filter(p=>/grob/i.test(p.brand));
2833
+ let o=items.filter(p=>!/malloca|grob/i.test(p.brand));
2834
+ let pool=[...m.slice(0,5),...g.slice(0,4),...o.slice(0,3)];
2835
+ pool.forEach(p=>catalog.push({sku:p.sku,name:p.name.substring(0,50),brand:p.brand,price:p.priceNum,type:typ.n}));
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){
2845
+ // Fallback to random selection if AI fails
2846
+ _doQuoteFallback(wantTypes,perBudget,budget,res);return;
2847
+ }
2848
+ const aiData=await aiRes.json();
2849
+ const content=aiData.choices[0].message.content;
2850
+ const jsonMatch=content.match(/\{[\s\S]*\}/);
2851
+ if(!jsonMatch){_doQuoteFallback(wantTypes,perBudget,budget,res);return}
2852
+ const result=JSON.parse(jsonMatch[0]);
2853
+ const selectedSkus=result.picks.map(p=>p.sku);
2854
+ const reasons=Object.fromEntries(result.picks.map(p=>[p.sku,p.reason||'']));
2855
+ // Map SKUs to actual products
2856
+ let picks=[];let total=0;
2857
+ selectedSkus.forEach(sku=>{
2858
+ const found=D.find(p=>p.sku===sku);
2859
+ if(found){
2860
+ const typ=catalog.find(c=>c.sku===sku);
2861
+ picks.push({...found,typeName:typ?typ.type:'SP',reason:reasons[sku]||''});
2862
+ total+=found.priceNum;
2863
+ }
2864
+ });
2865
+ if(!picks.length){_doQuoteFallback(wantTypes,perBudget,budget,res);return}
2866
+ _renderQuote(picks,total,budget,res,true);
2867
+ }catch(e){
2868
+ console.error(e);
2869
+ // Fallback
2870
+ const bm=q.match(/(\d+)\s*(tr|triệu|m)/i);
2871
+ const budget=bm?parseInt(bm[1])*1000000:50000000;
2872
+ const typeMap=[{k:['bep tu','bep dien','bep'],n:'Bếp từ'},{k:['may hut','hut mui'],n:'Máy hút mùi'},{k:['chau rua','bon rua'],n:'Chậu rửa'}];
2873
+ const qn=q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2874
+ let wantTypes=typeMap.filter(t=>t.k.some(k=>qn.includes(k)));
2875
+ if(!wantTypes.length)wantTypes=typeMap.slice(0,3);
2876
+ _doQuoteFallback(wantTypes,Math.floor(budget/wantTypes.length),budget,res);
2877
+ }
2878
+ }
2879
+ function _doQuoteFallback(wantTypes,perBudget,budget,res){
2880
  let picks=[];let total=0;
2881
+ function shuffle(a){for(let i=a.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a}
2882
  wantTypes.forEach(typ=>{
 
2883
  let all=D.filter(p=>{
2884
  const pn=((p.name||'')+(p.cat||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
2885
  return typ.k.some(k=>pn.includes(k))&&p.priceNum>0&&p.priceNum<=perBudget*1.5;
2886
  });
2887
+ let m=shuffle(all.filter(p=>/malloca/i.test(p.brand)));
2888
+ let g=shuffle(all.filter(p=>/grob/i.test(p.brand)));
2889
+ let pool=[...m.slice(0,4),...g.slice(0,3),...shuffle(all).slice(0,3)];
2890
+ shuffle(pool);
2891
+ let pick=pool.find(p=>p.priceNum<=perBudget*1.3)||pool[0];
2892
+ if(pick){picks.push({...pick,typeName:typ.n,reason:''});total+=pick.priceNum}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2893
  });
2894
+ if(!picks.length){res.innerHTML='😅 Không đủ SP phù hợp';return}
2895
+ _renderQuote(picks,total,budget,res,false);
2896
+ }
2897
+ function _renderQuote(picks,total,budget,res,isAI){
2898
+ let h='<div style="font-weight:700;margin-bottom:10px;font-size:.95rem">'+(isAI?'🤖':'📋')+' AI BÁO GIÁ <span style="color:#64748b;font-weight:400">(~'+budget.toLocaleString('vi')+'đ)</span></div>';
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;