From 89917d43283100e08f98d6662dd76ebfffe3ddc8 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 1 Sep 2026 18:02:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(AgentIO):=20=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E5=8C=BA=E4=BF=9D=E6=8C=81=E6=9C=80=E5=90=8E=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=E2=80=94=E2=80=94=E8=B6=85=E5=87=BA=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E8=8C=83=E5=9B=B4=E8=87=AA=E5=8A=A8=E6=BB=9A=E5=88=B0?= =?UTF-8?q?=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - chunk_response流式更新后scroll_to_bottom(rAF延迟到布局完成) - show_input用户发消息强制滚底 - 用户上翻看历史暂停跟滚(_track_follow),滚回底部恢复——不强行打断阅读 --- bricks/agent.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/bricks/agent.js b/bricks/agent.js index 225d469..1a3b1a7 100644 --- a/bricks/agent.js +++ b/bricks/agent.js @@ -327,6 +327,31 @@ bricks.AgentIO = class extends bricks.VBox { this.inputw.bind('inputed', this.user_inputed.bind(this)); this.add_widget(this.msg_box); this.add_widget(this.inputw); + // 用户上翻看历史时暂停跟滚,滚回底部恢复(scroll_to_bottom 依据 _follow) + this.msg_box.bind('scroll', this._track_follow.bind(this)); + } + // 会话区保持最后输出可见(2026-09-01):内容超出显示范围时自动滚到底。 + // 仅当用户本就停在底部附近才跟滚——用户主动上翻看历史时不强行拉回; + // 滚回底部或发送新消息后恢复跟滚。rAF 延迟到本帧布局完成后滚, + // 保证 scrollHeight 已是新内容的尺寸(同步滚会拿到旧高度)。 + scroll_to_bottom(){ + if (this._scroll_pending) return; + this._scroll_pending = true; + requestAnimationFrame(function(){ + this._scroll_pending = false; + var e = this.msg_box && this.msg_box.dom_element; + if (!e) return; + if (this._follow === false) { + var near_bottom = (e.scrollHeight - e.scrollTop - e.clientHeight) < 80; + if (!near_bottom) return; + } + e.scrollTop = e.scrollHeight; + }.bind(this)); + } + _track_follow(){ + var e = this.msg_box && this.msg_box.dom_element; + if (!e) return; + this._follow = (e.scrollHeight - e.scrollTop - e.clientHeight) < 80; } chunk_response(mout, l){ l = l.trim(); @@ -338,6 +363,7 @@ bricks.AgentIO = class extends bricks.VBox { } console.log('l=', l, 'd=', d); mout.update_data(d); + this.scroll_to_bottom(); } chunk_ended(){ console.log('chunk end'); @@ -395,6 +421,9 @@ bricks.AgentIO = class extends bricks.VBox { box.add_widget(w); box.add_widget(img); this.msg_box.add_widget(box); + // 用户刚发的消息必须可见:强制跟滚到底 + this._follow = true; + this.scroll_to_bottom(); } } From 26aad5712a9c87f7e6f1d92c94a2a027dcc93b96 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 1 Sep 2026 18:11:22 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(AgentIO):=20=E6=BB=9A=E5=BA=95=E6=94=B9?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E2=80=94=E2=80=94=E5=90=8E=E5=8F=B0tab?= =?UTF-8?q?=E4=B8=8D=E8=A7=A6=E5=8F=91rAF=E5=AF=BC=E8=87=B4=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E6=9B=B4=E6=96=B0=E6=97=B6=E4=B8=8D=E6=BB=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bricks/agent.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/bricks/agent.js b/bricks/agent.js index 1a3b1a7..55ea074 100644 --- a/bricks/agent.js +++ b/bricks/agent.js @@ -332,21 +332,17 @@ bricks.AgentIO = class extends bricks.VBox { } // 会话区保持最后输出可见(2026-09-01):内容超出显示范围时自动滚到底。 // 仅当用户本就停在底部附近才跟滚——用户主动上翻看历史时不强行拉回; - // 滚回底部或发送新消息后恢复跟滚。rAF 延迟到本帧布局完成后滚, - // 保证 scrollHeight 已是新内容的尺寸(同步滚会拿到旧高度)。 + // 滚回底部或发送新消息后恢复跟滚。 + // 同步滚:读 scrollHeight 本身强制布局,拿到的是新内容尺寸; + // 不用 rAF(后台 tab 不触发 rAF,流式更新时不滚)。 scroll_to_bottom(){ - if (this._scroll_pending) return; - this._scroll_pending = true; - requestAnimationFrame(function(){ - this._scroll_pending = false; - var e = this.msg_box && this.msg_box.dom_element; - if (!e) return; - if (this._follow === false) { - var near_bottom = (e.scrollHeight - e.scrollTop - e.clientHeight) < 80; - if (!near_bottom) return; - } - e.scrollTop = e.scrollHeight; - }.bind(this)); + var e = this.msg_box && this.msg_box.dom_element; + if (!e) return; + if (this._follow === false) { + var near_bottom = (e.scrollHeight - e.scrollTop - e.clientHeight) < 80; + if (!near_bottom) return; + } + e.scrollTop = e.scrollHeight; } _track_follow(){ var e = this.msg_box && this.msg_box.dom_element; From 4815e1e54d388376bb90b7964390de2c6245aae1 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 2 Sep 2026 10:57:17 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(AgentIO):=20=E6=BB=9A=E5=BA=95=E6=A0=B9?= =?UTF-8?q?=E6=B2=BB=E2=80=94=E2=80=94MutationObserver=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=A2=9E=E9=95=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit widget描述符走widgetBuild().then()异步挂载,同步滚总在挂载前执行滚不到新内容; MutationObserver在挂载落DOM后触发,同步/异步渲染全覆盖,后台tab也生效 --- bricks/agent.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bricks/agent.js b/bricks/agent.js index 55ea074..cdaaef6 100644 --- a/bricks/agent.js +++ b/bricks/agent.js @@ -329,6 +329,16 @@ bricks.AgentIO = class extends bricks.VBox { this.add_widget(this.inputw); // 用户上翻看历史时暂停跟滚,滚回底部恢复(scroll_to_bottom 依据 _follow) this.msg_box.bind('scroll', this._track_follow.bind(this)); + // 内容增长即跟滚到底(2026-09-01 根治):agent 输出多为 widget 描述符, + // AgentOut.update 走 widgetBuild().then() 异步挂载——同步滚总在挂载前执行、 + // 滚不到新内容。MutationObserver 在挂载落 DOM 后触发,同步/异步渲染全覆盖, + // 后台 tab 也生效(rAF 在后台 tab 不触发,已弃用)。 + var mo = window.MutationObserver; + if (mo) { + this._mo = new mo(this.scroll_to_bottom.bind(this)); + this._mo.observe(this.msg_box.dom_element, + {childList: true, subtree: true, characterData: true}); + } } // 会话区保持最后输出可见(2026-09-01):内容超出显示范围时自动滚到底。 // 仅当用户本就停在底部附近才跟滚——用户主动上翻看历史时不强行拉回; From d3cff3e9238228c98b6de09c7f6c08ffc5b1e343 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 2 Sep 2026 11:02:51 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(AgentIO):=20=E8=B7=9F=E6=BB=9A=E8=AF=AF?= =?UTF-8?q?=E5=85=B3=E6=A0=B9=E6=B2=BB=E2=80=94=E2=80=94=5Ftrack=5Ffollow?= =?UTF-8?q?=E6=8C=89scrollTop=E5=87=8F=E5=B0=8F=E5=88=A4=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=B8=8A=E7=BF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧实现按gap判follow:异步widget挂载使scroll事件滞后,程序滚底后gap瞬时>80 被误判为用户上翻→follow关→第二条消息后输出不再跟滚 --- bricks/agent.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/bricks/agent.js b/bricks/agent.js index cdaaef6..cbe4b39 100644 --- a/bricks/agent.js +++ b/bricks/agent.js @@ -357,7 +357,20 @@ bricks.AgentIO = class extends bricks.VBox { _track_follow(){ var e = this.msg_box && this.msg_box.dom_element; if (!e) return; - this._follow = (e.scrollHeight - e.scrollTop - e.clientHeight) < 80; + var st = e.scrollTop; + var near = (e.scrollHeight - st - e.clientHeight) < 80; + if (near) { + // 滚回底部附近 → 恢复跟滚 + this._follow = true; + } else if (this._last_st !== undefined && st < this._last_st - 5) { + // scrollTop 减小 = 用户真上翻 → 暂停跟滚。 + // 程序滚底只会增大 scrollTop;异步 widget 挂载使 scroll 事件滞后时 + // gap 会瞬时 >80,但 st 不减小——不能误判为用户上翻(2026-09-01 根因: + // 旧实现按 gap 判 follow,第二条消息后程序滚底被自己的 scroll 事件 + // 误关 follow,后续输出不再跟滚)。 + this._follow = false; + } + this._last_st = st; } chunk_response(mout, l){ l = l.trim();