anurag051194 commited on
Commit
1a4e48d
·
verified ·
1 Parent(s): 8bb4ca9

Stop pinning the transcript to the bottom while streaming

Browse files

Replace 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.

Files changed (1) hide show
  1. app.py +51 -2
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=True,
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 = [