-
Notifications
You must be signed in to change notification settings - Fork 3.7k
improvement(chat): smooth streaming — eased stick-to-bottom glide and time-based reveal pacing #5417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+199
−60
Merged
improvement(chat): smooth streaming — eased stick-to-bottom glide and time-based reveal pacing #5417
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
546addd
improvement(chat): eased stick-to-bottom glide for streaming chat
TheodoreSpeaks 276fee1
improvement(chat): time-based word-at-a-time reveal pacing
TheodoreSpeaks d43a39c
improvement(chat): harden smooth-chase against reentrant kick/cancel …
TheodoreSpeaks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| /** | ||
| * Fraction of the remaining gap to close per frame while chasing the bottom — | ||
| * an exponential glide (originating in the subagent viewport's stick-to-bottom, | ||
| * see BoundedViewport in agent-group.tsx) instead of snapping `scrollTop` to | ||
| * `scrollHeight` on every content append. Closes ~90% of any gap within ~18 | ||
| * frames (~300ms) — deliberately lazier than the subagent viewport's 0.18 so a | ||
| * large content burst reads as a calm upward drift of the transcript rather | ||
| * than a lurch. | ||
| */ | ||
| export const SMOOTH_CHASE_RATE = 0.12 | ||
|
|
||
| /** Gap (px) below which the chase parks until new growth reopens it. */ | ||
| export const CHASE_REST_GAP = 0.5 | ||
|
|
||
| export interface SmoothBottomChaseTarget { | ||
| /** Current scroll offset. */ | ||
| getTop: () => number | ||
| /** Scroll offset at which the viewport bottom meets the content bottom. */ | ||
| getBottomTop: () => number | ||
| /** Apply a new scroll offset. */ | ||
| setTop: (top: number) => void | ||
| } | ||
|
|
||
| export interface SmoothBottomChaseHandle { | ||
| /** True while a chase frame is scheduled (gap still closing). */ | ||
| isActive: () => boolean | ||
| /** Start the loop if parked. Call after content growth. */ | ||
| kick: () => void | ||
| cancel: () => void | ||
| } | ||
|
|
||
| /** | ||
| * Eased stick-to-bottom chase over any scrollable target (a DOM element or an | ||
| * editor API like Monaco's). Each frame closes {@link SMOOTH_CHASE_RATE} of the | ||
| * remaining gap and self-parks at {@link CHASE_REST_GAP}; content growth | ||
| * restarts it via `kick()`. | ||
| * | ||
| * Self-interrupting: chase writes only ever move the offset down, and content | ||
| * growth leaves it where the last write put it — so an offset that moved UP | ||
| * since the last write can only be a user scrolling away, and the loop parks | ||
| * instead of fighting them. `shouldContinue` layers any caller-owned stickiness | ||
| * on top (checked every frame). | ||
| */ | ||
| export function createSmoothBottomChase( | ||
| target: SmoothBottomChaseTarget, | ||
| shouldContinue: () => boolean = () => true | ||
| ): SmoothBottomChaseHandle { | ||
| let raf: number | null = null | ||
| let lastTop: number | null = null | ||
|
|
||
| const park = () => { | ||
| if (raf !== null) cancelAnimationFrame(raf) | ||
| raf = null | ||
| lastTop = null | ||
| } | ||
|
|
||
| const step = () => { | ||
| // `raf` deliberately keeps this (already-fired) frame's id while the step | ||
| // body runs: canceling a fired handle is a no-op, and a non-null `raf` | ||
| // means `isActive()` stays true and a reentrant `kick()` — e.g. from a | ||
| // target whose `setTop` fires synchronous scroll listeners, like Monaco's | ||
| // onDidScrollChange — cannot start a second parallel chain. | ||
| if (!shouldContinue()) { | ||
| park() | ||
| return | ||
| } | ||
| const top = target.getTop() | ||
| if (lastTop !== null && top < lastTop - 1) { | ||
| park() | ||
| return | ||
| } | ||
| const gap = target.getBottomTop() - top | ||
| if (gap <= CHASE_REST_GAP) { | ||
| park() | ||
| return | ||
| } | ||
| target.setTop(top + Math.max(1, gap * SMOOTH_CHASE_RATE)) | ||
| // A synchronous side-effect of `setTop` may have called `cancel()`; honor | ||
| // it instead of re-queuing over it. | ||
| if (raf === null) return | ||
| lastTop = target.getTop() | ||
| raf = requestAnimationFrame(step) | ||
| } | ||
|
|
||
| return { | ||
| isActive: () => raf !== null, | ||
| kick: () => { | ||
| if (raf === null) raf = requestAnimationFrame(step) | ||
| }, | ||
| cancel: park, | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.