lvwerra HF Staff commited on
Commit
0c519b9
·
verified ·
1 Parent(s): 4f7d887

Long-sequence rendering: coarse spans, incremental append, rAF throttle, lpRange-threshold full repaint

Browse files
Files changed (1) hide show
  1. index.html +108 -26
index.html CHANGED
@@ -169,7 +169,8 @@
169
  .seq-block.empty { color: #aaa; font-weight: 300; letter-spacing: normal; }
170
  .seq-line { white-space: pre; }
171
  .pos { color: #bbb; user-select: none; font-weight: 300; }
172
- .cursor {
 
173
  display: inline-block; width: 7px; height: 14px;
174
  background: #1a1a1a; vertical-align: text-bottom;
175
  margin-left: 2px;
@@ -470,38 +471,119 @@ function basesPerLine() {
470
  return Math.max(10, Math.min(blocks, 30) * 10);
471
  }
472
 
473
- function renderSequence() {
474
- if (colorMode === "logprob") recomputeLpRange();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
  const total = promptBases + genText;
476
- els.copy.disabled = total.length === 0;
477
  if (!total) {
478
  els.seq.classList.add("empty");
479
  els.seq.textContent = "prompt + generated bases will stream here";
480
- return;
 
 
 
 
 
 
481
  }
482
- els.seq.classList.remove("empty");
 
 
 
 
483
 
484
- const bpl = basesPerLine();
485
- const lines = [];
486
- for (let i = 0; i < total.length; i += bpl) {
487
- const lineBases = total.slice(i, i + bpl);
488
- const pos = String(i + 1).padStart(5, " ");
489
- let html = `<span class="pos">${pos}</span> `;
490
- for (let j = 0; j < lineBases.length; j++) {
491
- if (j > 0 && j % 10 === 0) html += " ";
492
- const absIdx = i + j;
493
- const base = lineBases[j];
494
- const [r, g, b] = rgbForBase(absIdx, base);
495
- const tinted = colorMode !== "none" && absIdx >= promptBases.length;
496
- const bg = tinted ? `;background:rgba(${r},${g},${b},${BG_ALPHA})` : "";
497
- html += `<span style="color:rgb(${r},${g},${b})${bg}">${base}</span>`;
498
- }
499
- if (abortCtrl && i + lineBases.length === total.length) {
500
- html += `<span class="cursor"></span>`;
501
  }
502
- lines.push(`<div class="seq-line">${html}</div>`);
503
  }
504
- els.seq.innerHTML = lines.join("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  }
506
 
507
  // --- Stats ---
@@ -619,7 +701,7 @@ async function generate() {
619
  }
620
  if (data.text) {
621
  genText += cleanPrompt(data.text);
622
- renderSequence();
623
  }
624
  }
625
  }
 
169
  .seq-block.empty { color: #aaa; font-weight: 300; letter-spacing: normal; }
170
  .seq-line { white-space: pre; }
171
  .pos { color: #bbb; user-select: none; font-weight: 300; }
172
+ .seq-line.tail::after {
173
+ content: "";
174
  display: inline-block; width: 7px; height: 14px;
175
  background: #1a1a1a; vertical-align: text-bottom;
176
  margin-left: 2px;
 
471
  return Math.max(10, Math.min(blocks, 30) * 10);
472
  }
473
 
474
+ // Cheap discriminator: bases sharing a key inside the same 10-block fold into one span.
475
+ function colorKey(absIdx, base) {
476
+ if (absIdx < promptBases.length) return "p";
477
+ if (colorMode === "none") return "g";
478
+ if (colorMode === "bases") return "b" + base;
479
+ if (colorMode === "logprob") return "t" + genTokenAtBase[absIdx - promptBases.length];
480
+ return "g";
481
+ }
482
+
483
+ function buildLineHTML(start, lineBases) {
484
+ const pos = String(start + 1).padStart(5, " ");
485
+ let html = `<span class="pos">${pos}</span> `;
486
+ let j = 0;
487
+ while (j < lineBases.length) {
488
+ if (j > 0 && j % 10 === 0) html += " ";
489
+ const startAbs = start + j;
490
+ const startKey = colorKey(startAbs, lineBases[j]);
491
+ const blockEnd = Math.min(lineBases.length, Math.floor(j / 10) * 10 + 10);
492
+ let runEnd = j + 1;
493
+ while (runEnd < blockEnd && colorKey(start + runEnd, lineBases[runEnd]) === startKey) runEnd++;
494
+ const runText = lineBases.slice(j, runEnd);
495
+ const [r, g, b] = rgbForBase(startAbs, lineBases[j]);
496
+ const tinted = colorMode !== "none" && startAbs >= promptBases.length;
497
+ const bg = tinted ? `;background:rgba(${r},${g},${b},${BG_ALPHA})` : "";
498
+ html += `<span style="color:rgb(${r},${g},${b})${bg}">${runText}</span>`;
499
+ j = runEnd;
500
+ }
501
+ return html;
502
+ }
503
+
504
+ function updateTail() {
505
+ const prev = els.seq.querySelector(".seq-line.tail");
506
+ if (prev) prev.classList.remove("tail");
507
+ const last = els.seq.lastElementChild;
508
+ if (abortCtrl && last && last.classList.contains("seq-line")) last.classList.add("tail");
509
+ }
510
+
511
+ // Re-render frequency for logprob: tolerance is max of 0.2 absolute and 5% of current range.
512
+ function lpRangeShifted(prev, curr) {
513
+ if (!prev || !curr) return prev !== curr;
514
+ const range = Math.max(0.1, prev.max - prev.min);
515
+ const tol = Math.max(0.2, range * 0.05);
516
+ return Math.abs(prev.min - curr.min) > tol
517
+ || Math.abs(prev.mid - curr.mid) > tol
518
+ || Math.abs(prev.max - curr.max) > tol;
519
+ }
520
+
521
+ let lastRenderedMode = null;
522
+ let lastRenderedBpl = null;
523
+ let lastRenderedLpRange = null;
524
+
525
+ function fullRender(bpl) {
526
  const total = promptBases + genText;
 
527
  if (!total) {
528
  els.seq.classList.add("empty");
529
  els.seq.textContent = "prompt + generated bases will stream here";
530
+ } else {
531
+ els.seq.classList.remove("empty");
532
+ const parts = [];
533
+ for (let i = 0; i < total.length; i += bpl) {
534
+ parts.push(`<div class="seq-line">${buildLineHTML(i, total.slice(i, i + bpl))}</div>`);
535
+ }
536
+ els.seq.innerHTML = parts.join("");
537
  }
538
+ lastRenderedMode = colorMode;
539
+ lastRenderedBpl = bpl;
540
+ lastRenderedLpRange = lpRange ? { ...lpRange } : null;
541
+ updateTail();
542
+ }
543
 
544
+ function incrementalRender(bpl) {
545
+ const total = promptBases + genText;
546
+ const totalLines = Math.ceil(total.length / bpl);
547
+ const lineDivs = els.seq.children;
548
+ if (lineDivs.length > 0) {
549
+ const lastIdx = lineDivs.length - 1;
550
+ const start = lastIdx * bpl;
551
+ lineDivs[lastIdx].innerHTML = buildLineHTML(start, total.slice(start, start + bpl));
552
+ }
553
+ if (totalLines > lineDivs.length) {
554
+ const parts = [];
555
+ for (let li = lineDivs.length; li < totalLines; li++) {
556
+ const start = li * bpl;
557
+ parts.push(`<div class="seq-line">${buildLineHTML(start, total.slice(start, start + bpl))}</div>`);
 
 
 
558
  }
559
+ els.seq.insertAdjacentHTML("beforeend", parts.join(""));
560
  }
561
+ lastRenderedLpRange = lpRange ? { ...lpRange } : null;
562
+ updateTail();
563
+ }
564
+
565
+ function renderSequence() {
566
+ if (colorMode === "logprob") recomputeLpRange();
567
+ const total = promptBases + genText;
568
+ els.copy.disabled = total.length === 0;
569
+ const bpl = basesPerLine();
570
+ const totalLines = total ? Math.ceil(total.length / bpl) : 0;
571
+ const renderedLines = els.seq.children.length;
572
+ const needFull =
573
+ !total ||
574
+ lastRenderedMode !== colorMode ||
575
+ lastRenderedBpl !== bpl ||
576
+ totalLines < renderedLines ||
577
+ (colorMode === "logprob" && lpRangeShifted(lastRenderedLpRange, lpRange));
578
+ if (needFull) fullRender(bpl);
579
+ else incrementalRender(bpl);
580
+ }
581
+
582
+ let renderQueued = false;
583
+ function scheduleRender() {
584
+ if (renderQueued) return;
585
+ renderQueued = true;
586
+ requestAnimationFrame(() => { renderQueued = false; renderSequence(); });
587
  }
588
 
589
  // --- Stats ---
 
701
  }
702
  if (data.text) {
703
  genText += cleanPrompt(data.text);
704
+ scheduleRender();
705
  }
706
  }
707
  }