File size: 8,276 Bytes
9ea2a49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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 = `<img src="https://img.icons8.com/color/48/000000/bot.png" class="w-8 h-8 rounded-full border shadow bg-white">`;
    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) + `<div class='text-xs text-gray-400 mt-1 text-right'>${new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})}</div>`;
    div.innerHTML = avatar;
    div.appendChild(msg);
  } else {
    const avatar = `<img src="https://img.icons8.com/color/48/000000/user-male-circle.png" class="w-8 h-8 rounded-full border shadow bg-blue-100">`;
    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 + `<div class='text-xs text-gray-200 mt-1 text-right'>${new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})}</div>`;
    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 = '<span></span><span></span><span></span>';
  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);
}