const messagesEl = document.getElementById('messages'); const inputArea = document.getElementById('input-area'); const balanceBar = document.getElementById('balance-bar'); const balanceValue = document.getElementById('balance-value'); const balanceProgress = document.getElementById('balance-progress'); const sendBtn = document.getElementById('send'); let state = { expenses: { fixed: {}, optional: {}, custom: {} } }; let queue = [ { key:'name', text:'Hello there! What’s your name?', type:'text' }, { key:'intro', text:"I’m your helpful advisor. I’ll help you make your money boom 💰—but remember, there’s always risk. Think twice!", type:'info' }, { key:'salary', text:'Enter your total monthly in-hand salary: ₹', type:'number' }, ...[ 'Rent/Mortgage','Groceries','Utilities','EMI/Loan' ].map(k=>({ key:k, text:`Enter your monthly ${k}: ₹`, type:'number', cat:'fixed' })), ...[ 'Insurance','Transport','Gym','Subs' ].flatMap(k=>[ { key:`has_${k}`, text:`Do you have ${k}? (y/n)`, type:'yesno' }, { key:k, text:`Enter your monthly ${k}: ₹`, type:'number', depends:`has_${k}`, cat:'optional' } ]), { key:'has_custom', text:'Any other recurring expenses to add? (y/n)', type:'yesno' }, { key:'custom_count', text:'How many?', type:'number', depends:'has_custom' }, // custom entries dynamic ]; let idx = 0, customCount = 0, customIdx = 0, awaitingCustom = false; // Helper: Render input control based on question type function renderInput(q) { inputArea.innerHTML = ''; let el; if(q.type==='yesno') { el = document.createElement('div'); ['Yes','No'].forEach(opt => { const btn = document.createElement('button'); btn.textContent = opt; btn.className = 'px-4 py-2 rounded-lg border bg-gray-50 hover:bg-blue-100 text-blue-700 font-semibold mx-1'; btn.onclick = () => { inputArea.querySelectorAll('button').forEach(b=>b.disabled=true); handleYesNo(opt); }; el.appendChild(btn); }); } else if(q.type==='number') { el = document.createElement('input'); el.type = 'number'; el.className = 'flex-1 p-3 rounded-lg border bg-gray-100 focus:outline-none'; el.placeholder = 'Enter amount'; el.id = 'input'; el.oninput = () => sendBtn.disabled = !el.value; } else { el = document.createElement('input'); el.type = 'text'; el.className = 'flex-1 p-3 rounded-lg border bg-gray-100 focus:outline-none'; el.placeholder = 'Type your answer...'; el.id = 'input'; el.oninput = () => sendBtn.disabled = !el.value; } inputArea.appendChild(el); if(q.type!=='yesno') { sendBtn.disabled = true; el.addEventListener('keydown', e => { if(e.key==='Enter' && el.value) sendBtn.click(); }); el.focus(); } else { sendBtn.disabled = true; } } // Helper: Update balance bar function updateBalanceBar(balance, salary) { if(salary && salary > 0) { balanceBar.classList.remove('hidden'); balanceValue.textContent = `₹${balance.toLocaleString()}`; let percent = Math.max(0, Math.min(100, Math.round((balance/salary)*100))); balanceProgress.style.width = percent + '%'; balanceProgress.className = 'h-2 rounded-full ' + (percent > 60 ? 'bg-green-400' : percent > 30 ? 'bg-yellow-400' : 'bg-red-400'); } else { balanceBar.classList.add('hidden'); } } // Helper: Add message to chat (supports markdown for bot) function addMessage(txt, who){ const div = document.createElement('div'); div.className = 'message flex items-end gap-2 ' + (who==='bot' ? 'justify-start' : 'justify-end flex-row-reverse'); if(who==='bot') { // Render markdown for bot replies const avatar = ``; const msg = document.createElement('div'); msg.className = 'bg-gray-100 text-gray-800 p-3 rounded-2xl max-w-[80%] whitespace-pre-line shadow-sm'; msg.innerHTML = marked.parse(txt) + `
${new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})}
`; div.innerHTML = avatar; div.appendChild(msg); } else { const avatar = ``; const msg = document.createElement('div'); msg.className = 'bg-blue-600 text-white p-3 rounded-2xl max-w-[80%] whitespace-pre-line shadow-sm'; msg.innerHTML = txt + `
${new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})}
`; div.innerHTML = avatar; div.appendChild(msg); } messagesEl.appendChild(div); messagesEl.scrollTop = messagesEl.scrollHeight; } // Helper: Add typing indicator function addTyping(){ const div = document.createElement('div'); div.className = 'typing self-start flex space-x-1'; div.innerHTML = ''; messagesEl.appendChild(div); messagesEl.scrollTop = messagesEl.scrollHeight; return div; } // Helper: Handle yes/no button clicks function handleYesNo(val) { addMessage(val,'user'); const q = queue[idx]; if(q && q.type==='yesno'){ state[q.key] = val.toLowerCase().startsWith('y'); } idx++; ask(); } // Main logic async function ask(){ if(awaitingCustom){ const q = queue[idx]; if(customIdx < customCount){ if(state.customStep==='name'){ addMessage(`Name of expense #${customIdx+1}?`,'bot'); renderInput({type:'text'}); return; } else { addMessage(`Amount for '${state.current}' ₹`,'bot'); renderInput({type:'number'}); return; } } else { awaitingCustom = false; idx++; } } if(idx < queue.length){ const q = queue[idx]; if(q.depends && !state[q.depends]){ idx++; return ask(); } addMessage(q.text,'bot'); renderInput(q); } else { // submit addMessage('Fetching suggestions…','bot'); const spinner = addTyping(); const payload = { salary: state.salary, risk_tolerance: 'moderate', expenses: state.expenses }; const res = await fetch('/analyze', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) }); spinner.remove(); const json = await res.json(); addMessage(json.groq_advice_markdown,'bot'); inputArea.innerHTML = ''; sendBtn.disabled = true; } } // Send button handler sendBtn.onclick = async ()=>{ let el = inputArea.querySelector('input'); let val = el ? el.value.trim() : ''; if(!val) return; addMessage(val,'user'); const q = queue[idx]; // handle custom if(q && q.key==='custom_count'){ customCount = parseInt(val); state.customStep = 'name'; awaitingCustom = true; if(el) el.value=''; return ask(); } if(awaitingCustom){ if(state.customStep==='name'){ state.current = val; state.customStep = 'amount'; } else { if(!state.expenses.custom) state.expenses.custom = {}; state.expenses.custom[state.current] = parseFloat(val); customIdx++; state.customStep = 'name'; } if(el) el.value=''; return ask(); } // store normal if(q && q.type==='number'){ const num = parseFloat(val); if(q.key==='salary') state.salary = num; else { const cat = q.cat || 'custom'; state.expenses[cat][q.key] = num; // Show balance after each expense let spent = 0; Object.values(state.expenses).forEach(catObj => Object.values(catObj).forEach(amt => spent+=amt)); let balance = (state.salary||0) - spent; updateBalanceBar(balance, state.salary); addMessage(`Paid ${q.key.replace(/_/g,' ')} → ₹${num.toLocaleString()}, Balance: ₹${balance.toLocaleString()}`,'bot'); } } else if(q && q.key==='name'){ state.name = val; } idx++; if(el) el.value=''; ask(); }; // Initialize ask(); // Add the Marked.js CDN for markdown support if (!window.marked) { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js'; document.head.appendChild(script); }