Skip to content

Commit 36a47a3

Browse files
Jaewon Hwangclaude
andcommitted
fix: enable auto-scroll during assistant response streaming
Fix auto-scroll behavior when AI assistant streams responses in the web UI. Previously, the viewport would remain at the sent message position and users had to manually click a badge to see streaming responses. Fixes #14959 Changes: - Reset chat scroll state before sending message to ensure viewport readiness - Force scroll to bottom after message send to position viewport correctly - Detect streaming start (chatStream: null -> string) and trigger auto-scroll - Ensure smooth scroll-following during entire streaming response Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 4199f98 commit 36a47a3

File tree

2 files changed

+12
-3
lines changed

2 files changed

+12
-3
lines changed

ui/src/ui/app-chat.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { OpenClawApp } from "./app.ts";
22
import type { GatewayHelloOk } from "./gateway.ts";
33
import type { ChatAttachment, ChatQueueItem } from "./ui-types.ts";
44
import { parseAgentSessionKey } from "../../../src/sessions/session-key-utils.js";
5-
import { scheduleChatScroll } from "./app-scroll.ts";
5+
import { scheduleChatScroll, resetChatScroll } from "./app-scroll.ts";
66
import { setLastActiveSessionKey } from "./app-settings.ts";
77
import { resetToolStream } from "./app-tool-stream.ts";
88
import { abortChatRun, loadChatHistory, sendChatMessage } from "./controllers/chat.ts";
@@ -104,6 +104,8 @@ async function sendChatMessageNow(
104104
},
105105
) {
106106
resetToolStream(host as unknown as Parameters<typeof resetToolStream>[0]);
107+
// Reset scroll state before sending to ensure auto-scroll works for the response
108+
resetChatScroll(host as unknown as Parameters<typeof resetChatScroll>[0]);
107109
const runId = await sendChatMessage(host as unknown as OpenClawApp, message, opts?.attachments);
108110
const ok = Boolean(runId);
109111
if (!ok && opts?.previousDraft != null) {
@@ -124,7 +126,8 @@ async function sendChatMessageNow(
124126
if (ok && opts?.restoreAttachments && opts.previousAttachments?.length) {
125127
host.chatAttachments = opts.previousAttachments;
126128
}
127-
scheduleChatScroll(host as unknown as Parameters<typeof scheduleChatScroll>[0]);
129+
// Force scroll after sending to ensure viewport is at bottom for incoming stream
130+
scheduleChatScroll(host as unknown as Parameters<typeof scheduleChatScroll>[0], true);
128131
if (ok && !host.chatRunId) {
129132
void flushChatQueue(host);
130133
}

ui/src/ui/app-lifecycle.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,15 @@ export function handleUpdated(host: LifecycleHost, changed: Map<PropertyKey, unk
8080
const forcedByTab = changed.has("tab");
8181
const forcedByLoad =
8282
changed.has("chatLoading") && changed.get("chatLoading") === true && !host.chatLoading;
83+
// Detect streaming start: chatStream changed from null/undefined to a string value
84+
const previousStream = changed.get("chatStream") as string | null | undefined;
85+
const streamJustStarted =
86+
changed.has("chatStream") &&
87+
(previousStream === null || previousStream === undefined) &&
88+
typeof host.chatStream === "string";
8389
scheduleChatScroll(
8490
host as unknown as Parameters<typeof scheduleChatScroll>[0],
85-
forcedByTab || forcedByLoad || !host.chatHasAutoScrolled,
91+
forcedByTab || forcedByLoad || streamJustStarted || !host.chatHasAutoScrolled,
8692
);
8793
}
8894
if (

0 commit comments

Comments
 (0)