fix(core): ai chat scrolldown indicator (#13382)

Close [AI-401](https://linear.app/affine-design/issue/AI-401)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Improved scrolling behavior in the AI chat messages panel by making
the entire panel the scroll container, resulting in more consistent
scroll handling.
* Adjusted the position of the down-indicator for better visibility
during scrolling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wu Yue
2025-08-01 11:56:12 +08:00
committed by GitHub
parent a088874c41
commit 3c9fe48c6c
2 changed files with 15 additions and 21 deletions

View File

@@ -314,9 +314,8 @@ export class AIChatContent extends SignalWatcher(
if (chatMessages) {
chatMessages.updateComplete
.then(() => {
const scrollContainer = chatMessages.getScrollContainer();
scrollContainer?.addEventListener('scrollend', () => {
this.lastScrollTop = scrollContainer.scrollTop;
chatMessages.addEventListener('scrollend', () => {
this.lastScrollTop = chatMessages.scrollTop;
});
this._scrollListenersInitialized = true;
})

View File

@@ -13,7 +13,7 @@ import type { BaseSelection, ExtensionType } from '@blocksuite/affine/store';
import { ArrowDownBigIcon as ArrowDownIcon } from '@blocksuite/icons/lit';
import type { Signal } from '@preact/signals-core';
import { css, html, nothing, type PropertyValues } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { repeat } from 'lit/directives/repeat.js';
import { debounce } from 'lodash-es';
@@ -125,10 +125,10 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
}
.down-indicator {
position: absolute;
position: fixed;
left: 50%;
transform: translate(-50%, 0);
bottom: 24px;
bottom: 166px;
z-index: 1;
border-radius: 50%;
width: 32px;
@@ -217,9 +217,6 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
@query('.chat-panel-messages-container')
accessor messagesContainer: HTMLDivElement | null = null;
@property({
type: String,
attribute: 'data-testid',
@@ -227,10 +224,6 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
})
accessor testId = 'chat-panel-messages';
getScrollContainer(): HTMLDivElement | null {
return this.messagesContainer;
}
private get _isNetworkActive() {
return (
!!this.networkSearchConfig.visible.value &&
@@ -264,8 +257,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
}
private readonly _onScroll = () => {
if (!this.messagesContainer) return;
const { clientHeight, scrollTop, scrollHeight } = this.messagesContainer;
const { clientHeight, scrollTop, scrollHeight } = this;
this.canScrollDown = scrollHeight - scrollTop - clientHeight > 200;
};
@@ -293,7 +285,6 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
'independent-mode': !!this.independentMode,
})}
data-testid="chat-panel-messages-container"
@scroll=${() => this._debouncedOnScroll()}
>
${filteredItems.length === 0
? html`<div
@@ -411,6 +402,12 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
)
);
}
// Add scroll event listener to the host element
this.addEventListener('scroll', this._debouncedOnScroll);
disposables.add(() => {
this.removeEventListener('scroll', this._debouncedOnScroll);
});
}
protected override updated(_changedProperties: PropertyValues) {
@@ -428,9 +425,8 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
scrollToEnd() {
requestAnimationFrame(() => {
if (!this.messagesContainer) return;
this.messagesContainer.scrollTo({
top: this.messagesContainer.scrollHeight,
this.scrollTo({
top: this.scrollHeight,
behavior: 'smooth',
});
});
@@ -438,8 +434,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
scrollToPos(top: number) {
requestAnimationFrame(() => {
if (!this.messagesContainer) return;
this.messagesContainer.scrollTo({ top });
this.scrollTo({ top });
});
}