Spaces:
Running on Zero
Running on Zero
Stop pinning the transcript to the bottom while streaming
Browse filesReplace Gradio's per-token delayed autoscroll with a follow-the-bottom script that releases as soon as the reader scrolls up and resumes when they return to the bottom or send a message.
app.py
CHANGED
|
@@ -181,6 +181,56 @@ document.addEventListener("keydown", (e) => {
|
|
| 181 |
e.stopPropagation();
|
| 182 |
document.querySelector("#send-btn")?.click();
|
| 183 |
}, true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
</script>
|
| 185 |
"""
|
| 186 |
|
|
@@ -415,7 +465,6 @@ footer, .footer, .built-with, .settings, .settings-bar, .show-api,
|
|
| 415 |
overflow-y: auto !important;
|
| 416 |
overscroll-behavior: contain;
|
| 417 |
scrollbar-gutter: stable;
|
| 418 |
-
scroll-behavior: smooth;
|
| 419 |
padding-right: 8px !important;
|
| 420 |
}
|
| 421 |
#twil-chat .bubble-wrap::-webkit-scrollbar,
|
|
@@ -1098,7 +1147,7 @@ with gr.Blocks(fill_height=True, fill_width=True, elem_id="app-root") as demo:
|
|
| 1098 |
elem_id="twil-chat",
|
| 1099 |
scale=1,
|
| 1100 |
height="100%",
|
| 1101 |
-
autoscroll=
|
| 1102 |
)
|
| 1103 |
with gr.Row(elem_id="chips-row") as chips_row:
|
| 1104 |
chip_btns = [
|
|
|
|
| 181 |
e.stopPropagation();
|
| 182 |
document.querySelector("#send-btn")?.click();
|
| 183 |
}, true);
|
| 184 |
+
|
| 185 |
+
// Follow streamed output only while the reader is at the bottom. Gradio's
|
| 186 |
+
// built-in autoscroll queues a delayed jump per token, so it keeps dragging
|
| 187 |
+
// the reader back down after they start scrolling up.
|
| 188 |
+
(() => {
|
| 189 |
+
const LOG = '#twil-chat [role="log"]';
|
| 190 |
+
const NEAR_BOTTOM_PX = 24;
|
| 191 |
+
let log = null;
|
| 192 |
+
let pinned = true;
|
| 193 |
+
let dragging = false;
|
| 194 |
+
|
| 195 |
+
const atBottom = () =>
|
| 196 |
+
log.scrollHeight - log.scrollTop - log.clientHeight <= NEAR_BOTTOM_PX;
|
| 197 |
+
const follow = () => {
|
| 198 |
+
if (log && pinned) log.scrollTop = log.scrollHeight;
|
| 199 |
+
};
|
| 200 |
+
|
| 201 |
+
const attach = (el) => {
|
| 202 |
+
log = el;
|
| 203 |
+
pinned = true;
|
| 204 |
+
el.addEventListener("wheel", (e) => { if (e.deltaY < 0) pinned = false; }, { passive: true });
|
| 205 |
+
el.addEventListener("touchmove", () => { pinned = false; }, { passive: true });
|
| 206 |
+
el.addEventListener("mousedown", (e) => {
|
| 207 |
+
if (e.offsetX >= el.clientWidth) dragging = true;
|
| 208 |
+
});
|
| 209 |
+
el.addEventListener("scroll", () => {
|
| 210 |
+
if (atBottom()) pinned = true;
|
| 211 |
+
else if (dragging) pinned = false;
|
| 212 |
+
}, { passive: true });
|
| 213 |
+
};
|
| 214 |
+
|
| 215 |
+
document.addEventListener("mouseup", () => { dragging = false; });
|
| 216 |
+
document.addEventListener("keydown", (e) => {
|
| 217 |
+
if (log && ["PageUp", "ArrowUp", "Home"].includes(e.key) && log.contains(document.activeElement)) {
|
| 218 |
+
pinned = false;
|
| 219 |
+
}
|
| 220 |
+
});
|
| 221 |
+
document.addEventListener("click", (e) => {
|
| 222 |
+
if (e.target.closest("#send-btn, .twil-card-btn")) pinned = true;
|
| 223 |
+
}, true);
|
| 224 |
+
document.addEventListener("keydown", (e) => {
|
| 225 |
+
if (e.key === "Enter" && !e.shiftKey && e.target.closest?.("#twil-input")) pinned = true;
|
| 226 |
+
}, true);
|
| 227 |
+
|
| 228 |
+
new MutationObserver(() => {
|
| 229 |
+
const el = document.querySelector(LOG);
|
| 230 |
+
if (el && el !== log) attach(el);
|
| 231 |
+
if (pinned) requestAnimationFrame(follow);
|
| 232 |
+
}).observe(document.documentElement, { childList: true, subtree: true, characterData: true });
|
| 233 |
+
})();
|
| 234 |
</script>
|
| 235 |
"""
|
| 236 |
|
|
|
|
| 465 |
overflow-y: auto !important;
|
| 466 |
overscroll-behavior: contain;
|
| 467 |
scrollbar-gutter: stable;
|
|
|
|
| 468 |
padding-right: 8px !important;
|
| 469 |
}
|
| 470 |
#twil-chat .bubble-wrap::-webkit-scrollbar,
|
|
|
|
| 1147 |
elem_id="twil-chat",
|
| 1148 |
scale=1,
|
| 1149 |
height="100%",
|
| 1150 |
+
autoscroll=False,
|
| 1151 |
)
|
| 1152 |
with gr.Row(elem_id="chips-row") as chips_row:
|
| 1153 |
chip_btns = [
|