Pepguy commited on
Commit
9db3fad
·
verified ·
1 Parent(s): b66135b

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +198 -1
public/index.html CHANGED
@@ -234,4 +234,201 @@
234
  document.getElementById('current-chat-title').innerText = 'Select or create a chat';
235
  document.getElementById('edit-title-btn').classList.add('hidden');
236
  updateTokenDisplay(0, 0, 0);
237
- loadSid
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  document.getElementById('current-chat-title').innerText = 'Select or create a chat';
235
  document.getElementById('edit-title-btn').classList.add('hidden');
236
  updateTokenDisplay(0, 0, 0);
237
+ loadSidebar();
238
+ }
239
+ }
240
+
241
+ async function selectChat(id) {
242
+ currentChatId = id;
243
+ clearInterval(pollingInterval);
244
+ const res = await fetch(`/api/chats/${id}`);
245
+ const chat = await res.json();
246
+
247
+ document.getElementById('current-chat-title').innerText = chat.title;
248
+ document.getElementById('edit-title-btn').classList.remove('hidden');
249
+
250
+ updateTokenDisplay(chat.totalTokens, chat.inputTokens, chat.outputTokens);
251
+
252
+ cancelTitleEdit();
253
+ renderMessages(chat.messages);
254
+ loadSidebar();
255
+
256
+ if(window.innerWidth < 1024 && !document.getElementById('sidebar').classList.contains('-translate-x-full')) {
257
+ toggleSidebar();
258
+ }
259
+ if (chat.isGenerating) pollGeneratingChat(id);
260
+ }
261
+
262
+ function renderMessages(messages) {
263
+ const container = document.getElementById('chat-window');
264
+ container.innerHTML = messages.map(m => {
265
+ let html = '';
266
+ if (m.reasoning) {
267
+ html += `<div class="reasoning-block"><i>Thinking Process</i><br/>${marked.parse(m.reasoning)}</div>`;
268
+ }
269
+ if (m.content) {
270
+ html += DOMPurify.sanitize(marked.parse(m.content));
271
+ }
272
+
273
+ return `
274
+ <div class="flex ${m.role === 'user' ? 'justify-end' : 'justify-start'} w-full">
275
+ <div class="max-w-[95%] lg:max-w-[85%] rounded-2xl p-4 shadow-sm ${m.role === 'user' ? 'bg-blue-600 text-white rounded-br-sm' : 'bg-gray-850 border border-gray-800 text-gray-200 markdown-body rounded-bl-sm'}">
276
+ ${m.role === 'user' ? `<div class="whitespace-pre-wrap">${m.content}</div>` : html}
277
+ </div>
278
+ </div>
279
+ `;
280
+ }).join('');
281
+
282
+ document.querySelectorAll('.markdown-body pre').forEach(pre => {
283
+ if (!pre.querySelector('.copy-btn')) {
284
+ const btn = document.createElement('button');
285
+ btn.className = 'copy-btn';
286
+ btn.innerText = 'Copy';
287
+ btn.onclick = () => {
288
+ const codeText = pre.querySelector('code')?.innerText || pre.innerText.replace('Copy', '');
289
+ navigator.clipboard.writeText(codeText.trim());
290
+ btn.innerText = 'Copied!';
291
+ setTimeout(() => btn.innerText = 'Copy', 2000);
292
+ };
293
+ pre.appendChild(btn);
294
+ }
295
+ });
296
+ container.scrollTop = container.scrollHeight;
297
+ }
298
+
299
+ function pollGeneratingChat(id) {
300
+ pollingInterval = setInterval(async () => {
301
+ const res = await fetch(`/api/chats/${id}`);
302
+ const chat = await res.json();
303
+ renderMessages(chat.messages);
304
+ updateTokenDisplay(chat.totalTokens, chat.inputTokens, chat.outputTokens);
305
+
306
+ if (chat.title !== document.getElementById('current-chat-title').innerText && document.getElementById('title-display').classList.contains('hidden') === false) {
307
+ document.getElementById('current-chat-title').innerText = chat.title;
308
+ loadSidebar();
309
+ }
310
+ if (!chat.isGenerating) clearInterval(pollingInterval);
311
+ }, 1500);
312
+ }
313
+
314
+ function handleImageSelect(event) {
315
+ const files = event.target.files;
316
+ const container = document.getElementById('image-preview-container');
317
+ container.classList.remove('hidden');
318
+
319
+ Array.from(files).forEach(file => {
320
+ const reader = new FileReader();
321
+ reader.onload = (e) => {
322
+ attachedImages.push(e.target.result);
323
+ container.innerHTML += `
324
+ <div class="relative group">
325
+ <img src="${e.target.result}" class="h-14 w-14 object-cover rounded-lg border border-gray-600 shadow-sm">
326
+ </div>`;
327
+ };
328
+ reader.readAsDataURL(file);
329
+ });
330
+ }
331
+
332
+ async function sendMessage() {
333
+ const input = document.getElementById('message-input');
334
+ const text = input.value.trim();
335
+ if (!text || !currentChatId) return;
336
+
337
+ const payload = {
338
+ model: document.getElementById('model-select').value,
339
+ prompt: text,
340
+ images: attachedImages
341
+ };
342
+
343
+ const container = document.getElementById('chat-window');
344
+ container.innerHTML += `
345
+ <div class="flex justify-end w-full">
346
+ <div class="max-w-[95%] lg:max-w-[85%] rounded-2xl rounded-br-sm p-4 bg-blue-600 text-white shadow-sm whitespace-pre-wrap">${text}</div>
347
+ </div>
348
+ <div class="flex justify-start w-full" id="temp-ai-wrapper">
349
+ <div class="max-w-[95%] lg:max-w-[85%] rounded-2xl rounded-bl-sm p-4 bg-gray-850 border border-gray-800 text-gray-200 markdown-body shadow-sm" id="temp-ai-msg">
350
+ <span class="flex items-center gap-2 text-gray-400">
351
+ <svg class="animate-spin h-4 w-4" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
352
+ Thinking...
353
+ </span>
354
+ </div>
355
+ </div>
356
+ `;
357
+ container.scrollTop = container.scrollHeight;
358
+
359
+ input.value = '';
360
+ input.style.height = 'auto';
361
+ attachedImages =[];
362
+ document.getElementById('image-preview-container').innerHTML = '';
363
+ document.getElementById('image-preview-container').classList.add('hidden');
364
+ document.getElementById('send-btn').disabled = true;
365
+
366
+ try {
367
+ const response = await fetch(`/api/chats/${currentChatId}/stream`, {
368
+ method: 'POST',
369
+ headers: { 'Content-Type': 'application/json' },
370
+ body: JSON.stringify(payload)
371
+ });
372
+
373
+ const reader = response.body.getReader();
374
+ const decoder = new TextDecoder("utf-8");
375
+ let aiContent = "";
376
+ let aiReasoning = "";
377
+
378
+ while (true) {
379
+ const { value, done } = await reader.read();
380
+ if (done) break;
381
+
382
+ const chunkText = decoder.decode(value, { stream: true });
383
+ const chunks = chunkText.split(/(__THINK__|__USAGE__)/);
384
+ let i = 0;
385
+
386
+ while (i < chunks.length) {
387
+ if (chunks[i] === '__THINK__') {
388
+ aiReasoning += chunks[i+1];
389
+ i += 2;
390
+ } else if (chunks[i] === '__USAGE__') {
391
+ const usageData = JSON.parse(chunks[i+1]);
392
+ // Update the live token state globally
393
+ updateTokenDisplay(
394
+ currentTokens.total + usageData.totalTokens,
395
+ currentTokens.in + usageData.inputTokens,
396
+ currentTokens.out + usageData.outputTokens
397
+ );
398
+ i += 2;
399
+ } else {
400
+ aiContent += chunks[i];
401
+ i++;
402
+ }
403
+ }
404
+
405
+ let tempHtml = "";
406
+ if (aiReasoning) tempHtml += `<div class="reasoning-block"><i>Thinking Process</i><br/>${marked.parse(aiReasoning)}</div>`;
407
+ if (aiContent) tempHtml += DOMPurify.sanitize(marked.parse(aiContent));
408
+
409
+ document.getElementById('temp-ai-msg').innerHTML = tempHtml || "<span class='animate-pulse text-gray-400'>Thinking...</span>";
410
+ container.scrollTop = container.scrollHeight;
411
+ }
412
+
413
+ // Force final refresh of states from db
414
+ selectChat(currentChatId);
415
+
416
+ } catch (error) {
417
+ console.error(error);
418
+ document.getElementById('temp-ai-msg').innerHTML = "<span class='text-red-400'>Error connecting to API.</span>";
419
+ } finally {
420
+ document.getElementById('send-btn').disabled = false;
421
+ }
422
+ }
423
+
424
+ document.getElementById('message-input').addEventListener('keydown', (e) => {
425
+ if (e.key === 'Enter' && !e.shiftKey) {
426
+ e.preventDefault();
427
+ sendMessage();
428
+ }
429
+ });
430
+
431
+ loadSidebar();
432
+ </script>
433
+ </body>
434
+ </html>