Pepguy commited on
Commit
f05d55a
Β·
verified Β·
1 Parent(s): fff1d2d

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +56 -25
public/index.html CHANGED
@@ -31,6 +31,19 @@
31
  .copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: #4b5563; color: #e5e7eb; border: none; padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: background 0.2s; }
32
  .copy-btn:hover { background: #6b7280; }
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  .reasoning-block { border-left: 3px solid #6366f1; padding-left: 1rem; margin-bottom: 1rem; color: #9ca3af; font-size: 0.9em; background: rgba(99, 102, 241, 0.05); padding: 0.75rem; border-radius: 0 0.5rem 0.5rem 0; }
35
  </style>
36
  </head>
@@ -133,8 +146,8 @@
133
 
134
  // ─── State ────────────────────────────────────────────────────────────────
135
  let currentChatId = null;
136
- let attachedImages = [];
137
- let pollingInterval = null; // FIX: always explicitly null when cleared
138
  let currentTokens = { total: 0, in: 0, out: 0 };
139
 
140
  // ─── UI Helpers ───────────────────────────────────────────────────────────
@@ -172,20 +185,53 @@
172
  document.getElementById('token-out').innerText = (output || 0).toLocaleString();
173
  }
174
 
175
- // ─── Copy buttons ─────────────────────────────────────────────────────────
176
  function attachCopyButtons(scope) {
177
  (scope || document).querySelectorAll('.markdown-body pre').forEach(pre => {
178
  if (pre.querySelector('.copy-btn')) return;
 
 
 
 
 
 
179
  const btn = document.createElement('button');
180
  btn.className = 'copy-btn';
181
  btn.innerText = 'Copy';
182
  btn.onclick = () => {
183
- const text = pre.querySelector('code')?.innerText || pre.innerText.replace('Copy', '');
 
 
 
 
 
 
 
 
 
184
  navigator.clipboard.writeText(text.trim());
185
  btn.innerText = 'Copied!';
186
  setTimeout(() => btn.innerText = 'Copy', 2000);
187
  };
 
 
188
  pre.appendChild(btn);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  });
190
  }
191
 
@@ -324,7 +370,7 @@
324
  function stopPolling() {
325
  if (pollingInterval !== null) {
326
  clearInterval(pollingInterval);
327
- pollingInterval = null; // FIX: always null after clear
328
  }
329
  }
330
 
@@ -345,7 +391,7 @@
345
  }
346
 
347
  if (!chat.isGenerating) {
348
- stopPolling(); // FIX: use helper so pollingInterval becomes null
349
  if (currentChatId === id) toggleInputState(false);
350
  }
351
  }, 1500);
@@ -399,7 +445,7 @@
399
  // Reset input
400
  input.value = '';
401
  input.style.height = 'auto';
402
- attachedImages = [];
403
  document.getElementById('image-preview-container').innerHTML = '';
404
  document.getElementById('image-preview-container').classList.add('hidden');
405
 
@@ -420,12 +466,10 @@
420
  let aiContent = '';
421
  let aiReasoning = '';
422
 
423
- // ── FIX: persistent buffer so markers are never split across chunks ──
424
  let buffer = '';
425
- const MARKER_LEN = 9; // '__THINK__' and '__USAGE__' are both 9 chars
426
 
427
  function flushBuffer(final = false) {
428
- // Keep processing until we can't make progress
429
  let progress = true;
430
  while (progress) {
431
  progress = false;
@@ -434,7 +478,6 @@
434
  const usageIdx = buffer.indexOf('__USAGE__');
435
 
436
  if (usageIdx !== -1) {
437
- // Everything before __USAGE__ is plain content
438
  if (usageIdx > 0) aiContent += buffer.slice(0, usageIdx);
439
  const jsonStr = buffer.slice(usageIdx + MARKER_LEN);
440
  try {
@@ -447,17 +490,14 @@
447
  buffer = '';
448
  progress = true;
449
  } catch (_) {
450
- // Incomplete JSON β€” keep buffer from __USAGE__ onwards and wait
451
  buffer = buffer.slice(usageIdx);
452
  }
453
 
454
  } else if (thinkIdx !== -1) {
455
- // Flush content before the marker
456
  if (thinkIdx > 0) {
457
  aiContent += buffer.slice(0, thinkIdx);
458
  buffer = buffer.slice(thinkIdx);
459
  }
460
- // buffer now starts with __THINK__; find where reasoning ends
461
  const afterMarker = buffer.slice(MARKER_LEN);
462
  const nextThink = afterMarker.indexOf('__THINK__');
463
  const nextUsage = afterMarker.indexOf('__USAGE__');
@@ -465,23 +505,18 @@
465
  const nextMarker = candidates.length ? Math.min(...candidates) : -1;
466
 
467
  if (nextMarker !== -1) {
468
- // We have a full reasoning segment
469
  aiReasoning += afterMarker.slice(0, nextMarker);
470
  buffer = afterMarker.slice(nextMarker);
471
  progress = true;
472
  } else {
473
- // Reasoning continues β€” consume safely up to last MARKER_LEN chars
474
  const safeLen = afterMarker.length - MARKER_LEN;
475
  if (safeLen > 0) {
476
  aiReasoning += afterMarker.slice(0, safeLen);
477
  buffer = '__THINK__' + afterMarker.slice(safeLen);
478
  }
479
- // else not enough data yet β€” wait for next chunk
480
  }
481
 
482
  } else {
483
- // No markers visible β€” safely flush all but the last MARKER_LEN chars
484
- // (a marker could be arriving split across the boundary)
485
  if (final) {
486
  aiContent += buffer;
487
  buffer = '';
@@ -493,7 +528,6 @@
493
  buffer = buffer.slice(safeLen);
494
  progress = true;
495
  }
496
- // else buffer too small to safely consume β€” wait
497
  }
498
  }
499
  }
@@ -503,7 +537,7 @@
503
  const { value, done } = await reader.read();
504
 
505
  if (done) {
506
- flushBuffer(true); // flush remainder on stream close
507
  break;
508
  }
509
 
@@ -525,7 +559,6 @@
525
  }
526
  }
527
 
528
- // ── FIX: replace temp bubble in-place β€” no selectChat(), no DOM wipe ──
529
  const tempWrapper = document.getElementById('temp-ai-wrapper');
530
  if (tempWrapper) {
531
  let finalHtml = '';
@@ -541,7 +574,6 @@
541
  attachCopyButtons();
542
  }
543
 
544
- // Refresh sidebar token counts only β€” no full re-render
545
  loadSidebar();
546
 
547
  } catch (error) {
@@ -551,7 +583,6 @@
551
  tempMsg.innerHTML = "<span class='text-red-400'>Connection lost or chat busy. Reload to sync.</span>";
552
  }
553
  } finally {
554
- // FIX: pollingInterval is null when not polling, so this check is reliable
555
  if (pollingInterval === null) toggleInputState(false);
556
  }
557
  }
@@ -570,4 +601,4 @@
570
  loadSidebar();
571
  </script>
572
  </body>
573
- </html>
 
31
  .copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: #4b5563; color: #e5e7eb; border: none; padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: background 0.2s; }
32
  .copy-btn:hover { background: #6b7280; }
33
 
34
+ .expand-btn { position: absolute; top: 0.5rem; right: 4.5rem; background: #4b5563; color: #e5e7eb; border: none; padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: background 0.2s; }
35
+ .expand-btn:hover { background: #6b7280; }
36
+
37
+ .markdown-body pre.snippet-collapsed { max-height: 8rem; overflow: hidden !important; }
38
+ .markdown-body pre.snippet-collapsed::after {
39
+ content: "";
40
+ position: absolute;
41
+ bottom: 0; left: 0; right: 0;
42
+ height: 3rem;
43
+ background: linear-gradient(transparent, #1e1e1e);
44
+ pointer-events: none;
45
+ }
46
+
47
  .reasoning-block { border-left: 3px solid #6366f1; padding-left: 1rem; margin-bottom: 1rem; color: #9ca3af; font-size: 0.9em; background: rgba(99, 102, 241, 0.05); padding: 0.75rem; border-radius: 0 0.5rem 0.5rem 0; }
48
  </style>
49
  </head>
 
146
 
147
  // ─── State ────────────────────────────────────────────────────────────────
148
  let currentChatId = null;
149
+ let attachedImages =[];
150
+ let pollingInterval = null;
151
  let currentTokens = { total: 0, in: 0, out: 0 };
152
 
153
  // ─── UI Helpers ───────────────────────────────────────────────────────────
 
185
  document.getElementById('token-out').innerText = (output || 0).toLocaleString();
186
  }
187
 
188
+ // ─── Copy buttons & snippet collapse ──────────────────────────────────────
189
  function attachCopyButtons(scope) {
190
  (scope || document).querySelectorAll('.markdown-body pre').forEach(pre => {
191
  if (pre.querySelector('.copy-btn')) return;
192
+
193
+ const expandBtn = document.createElement('button');
194
+ expandBtn.className = 'expand-btn';
195
+ expandBtn.innerText = 'Expand';
196
+ expandBtn.style.display = 'none';
197
+
198
  const btn = document.createElement('button');
199
  btn.className = 'copy-btn';
200
  btn.innerText = 'Copy';
201
  btn.onclick = () => {
202
+ const codeBlock = pre.querySelector('code');
203
+ let text = '';
204
+ // textContent is used to ensure visibility (like overflow: hidden) doesn't break copied output
205
+ if (codeBlock) {
206
+ text = codeBlock.textContent;
207
+ } else {
208
+ const clone = pre.cloneNode(true);
209
+ clone.querySelectorAll('.copy-btn, .expand-btn').forEach(b => b.remove());
210
+ text = clone.textContent;
211
+ }
212
  navigator.clipboard.writeText(text.trim());
213
  btn.innerText = 'Copied!';
214
  setTimeout(() => btn.innerText = 'Copy', 2000);
215
  };
216
+
217
+ pre.appendChild(expandBtn);
218
  pre.appendChild(btn);
219
+
220
+ // Dynamically apply collapsed state only to snippets that are tall enough
221
+ if (pre.scrollHeight > 160) {
222
+ pre.classList.add('snippet-collapsed');
223
+ expandBtn.style.display = 'block';
224
+
225
+ expandBtn.onclick = () => {
226
+ if (pre.classList.contains('snippet-collapsed')) {
227
+ pre.classList.remove('snippet-collapsed');
228
+ expandBtn.innerText = 'Collapse';
229
+ } else {
230
+ pre.classList.add('snippet-collapsed');
231
+ expandBtn.innerText = 'Expand';
232
+ }
233
+ };
234
+ }
235
  });
236
  }
237
 
 
370
  function stopPolling() {
371
  if (pollingInterval !== null) {
372
  clearInterval(pollingInterval);
373
+ pollingInterval = null;
374
  }
375
  }
376
 
 
391
  }
392
 
393
  if (!chat.isGenerating) {
394
+ stopPolling();
395
  if (currentChatId === id) toggleInputState(false);
396
  }
397
  }, 1500);
 
445
  // Reset input
446
  input.value = '';
447
  input.style.height = 'auto';
448
+ attachedImages =[];
449
  document.getElementById('image-preview-container').innerHTML = '';
450
  document.getElementById('image-preview-container').classList.add('hidden');
451
 
 
466
  let aiContent = '';
467
  let aiReasoning = '';
468
 
 
469
  let buffer = '';
470
+ const MARKER_LEN = 9;
471
 
472
  function flushBuffer(final = false) {
 
473
  let progress = true;
474
  while (progress) {
475
  progress = false;
 
478
  const usageIdx = buffer.indexOf('__USAGE__');
479
 
480
  if (usageIdx !== -1) {
 
481
  if (usageIdx > 0) aiContent += buffer.slice(0, usageIdx);
482
  const jsonStr = buffer.slice(usageIdx + MARKER_LEN);
483
  try {
 
490
  buffer = '';
491
  progress = true;
492
  } catch (_) {
 
493
  buffer = buffer.slice(usageIdx);
494
  }
495
 
496
  } else if (thinkIdx !== -1) {
 
497
  if (thinkIdx > 0) {
498
  aiContent += buffer.slice(0, thinkIdx);
499
  buffer = buffer.slice(thinkIdx);
500
  }
 
501
  const afterMarker = buffer.slice(MARKER_LEN);
502
  const nextThink = afterMarker.indexOf('__THINK__');
503
  const nextUsage = afterMarker.indexOf('__USAGE__');
 
505
  const nextMarker = candidates.length ? Math.min(...candidates) : -1;
506
 
507
  if (nextMarker !== -1) {
 
508
  aiReasoning += afterMarker.slice(0, nextMarker);
509
  buffer = afterMarker.slice(nextMarker);
510
  progress = true;
511
  } else {
 
512
  const safeLen = afterMarker.length - MARKER_LEN;
513
  if (safeLen > 0) {
514
  aiReasoning += afterMarker.slice(0, safeLen);
515
  buffer = '__THINK__' + afterMarker.slice(safeLen);
516
  }
 
517
  }
518
 
519
  } else {
 
 
520
  if (final) {
521
  aiContent += buffer;
522
  buffer = '';
 
528
  buffer = buffer.slice(safeLen);
529
  progress = true;
530
  }
 
531
  }
532
  }
533
  }
 
537
  const { value, done } = await reader.read();
538
 
539
  if (done) {
540
+ flushBuffer(true);
541
  break;
542
  }
543
 
 
559
  }
560
  }
561
 
 
562
  const tempWrapper = document.getElementById('temp-ai-wrapper');
563
  if (tempWrapper) {
564
  let finalHtml = '';
 
574
  attachCopyButtons();
575
  }
576
 
 
577
  loadSidebar();
578
 
579
  } catch (error) {
 
583
  tempMsg.innerHTML = "<span class='text-red-400'>Connection lost or chat busy. Reload to sync.</span>";
584
  }
585
  } finally {
 
586
  if (pollingInterval === null) toggleInputState(false);
587
  }
588
  }
 
601
  loadSidebar();
602
  </script>
603
  </body>
604
+ </html>