Spaces:
Running
Running
Long-sequence rendering: coarse spans, incremental append, rAF throttle, lpRange-threshold full repaint
Browse files- 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 |
-
.
|
|
|
|
| 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 |
-
|
| 474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
}
|
| 482 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
|
| 484 |
-
|
| 485 |
-
const
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
const
|
| 497 |
-
|
| 498 |
-
}
|
| 499 |
-
if (abortCtrl && i + lineBases.length === total.length) {
|
| 500 |
-
html += `<span class="cursor"></span>`;
|
| 501 |
}
|
| 502 |
-
|
| 503 |
}
|
| 504 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
}
|
| 506 |
|
| 507 |
// --- Stats ---
|
|
@@ -619,7 +701,7 @@ async function generate() {
|
|
| 619 |
}
|
| 620 |
if (data.text) {
|
| 621 |
genText += cleanPrompt(data.text);
|
| 622 |
-
|
| 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 |
}
|