diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-config.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-config.ts index 60bea05e49..8cfde6e69f 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-config.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-config.ts @@ -3,6 +3,17 @@ import type { LinkedMenuGroup } from '@blocksuite/affine/blocks'; import type { Store } from '@blocksuite/affine/store'; import type { Signal } from '@preact/signals-core'; +export interface AppSidebarConfig { + getWidth: () => { + signal: Signal; + cleanup: () => void; + }; + isOpen: () => { + signal: Signal; + cleanup: () => void; + }; +} + export interface AINetworkSearchConfig { visible: Signal; enabled: Signal; diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts index 0290317ee9..0a574d8df9 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts @@ -8,7 +8,7 @@ import { } from '@blocksuite/affine/blocks'; import { WithDisposable } from '@blocksuite/affine/global/utils'; import type { BaseSelection } from '@blocksuite/affine/store'; -import { css, html, nothing } from 'lit'; +import { css, html, nothing, type PropertyValues } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { debounce } from 'lodash-es'; @@ -196,17 +196,6 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) { return this.messagesContainer; } - get showDownIndicator() { - if (!this.messagesContainer) return false; - const { clientHeight, scrollTop, scrollHeight } = this.messagesContainer; - const canScrollDown = scrollHeight - scrollTop - clientHeight > 200; - const showDownIndicator = - canScrollDown && - this.chatContextValue.items.length > 0 && - this.chatContextValue.status !== 'transmitting'; - return showDownIndicator; - } - private _renderAIOnboarding() { return this.isLoading || !this.host?.doc.get(FeatureFlagService).getFlag('enable_ai_onboarding') @@ -239,6 +228,11 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) { 100 ); + private readonly _onDownIndicatorClick = () => { + this.canScrollDown = false; + this.scrollToEnd(); + }; + protected override render() { const { items } = this.chatContextValue; const { isLoading } = this; @@ -251,6 +245,11 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) { ); }); + const showDownIndicator = + this.canScrollDown && + filteredItems.length > 0 && + this.chatContextValue.status !== 'transmitting'; + return html`
- ${this.showDownIndicator && filteredItems.length > 0 - ? html`
+ ${showDownIndicator && filteredItems.length > 0 + ? html`
${DownArrowIcon}
` : nothing} @@ -329,6 +328,12 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) { ); } + protected override updated(_changedProperties: PropertyValues) { + if (_changedProperties.has('isLoading')) { + this.canScrollDown = false; + } + } + renderItem(item: ChatItem, isLast: boolean) { const { status, error } = this.chatContextValue; const { host } = this; diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts index 3c67488854..70a4ecc85d 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts @@ -7,11 +7,12 @@ import { NotificationProvider, type SpecBuilder, } from '@blocksuite/affine/blocks'; -import { WithDisposable } from '@blocksuite/affine/global/utils'; +import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/utils'; import type { Store } from '@blocksuite/affine/store'; import { css, html, type PropertyValues } from 'lit'; import { property, state } from 'lit/decorators.js'; import { createRef, type Ref, ref } from 'lit/directives/ref.js'; +import { styleMap } from 'lit/directives/style-map.js'; import { throttle } from 'lodash-es'; import { AIHelpIcon, SmallHintIcon } from '../_common/icons'; @@ -23,6 +24,7 @@ import { } from '../utils/selection-utils'; import type { AINetworkSearchConfig, + AppSidebarConfig, DocDisplayConfig, DocSearchMenuConfig, } from './chat-config'; @@ -47,7 +49,9 @@ const DEFAULT_CHAT_CONTEXT_VALUE: ChatContextValue = { markdown: '', }; -export class ChatPanel extends WithDisposable(ShadowlessElement) { +export class ChatPanel extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { static override styles = css` chat-panel { width: 100%; @@ -56,8 +60,6 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { .chat-panel-container { display: flex; flex-direction: column; - padding: 0 16px; - padding-top: 8px; height: 100%; } @@ -241,6 +243,9 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { @property({ attribute: false }) accessor networkSearchConfig!: AINetworkSearchConfig; + @property({ attribute: false }) + accessor appSidebarConfig!: AppSidebarConfig; + @property({ attribute: false }) accessor docSearchMenuConfig!: DocSearchMenuConfig; @@ -296,6 +301,9 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { private readonly _initPanel = async () => { try { + const isOpen = !!this.appSidebarConfig.isOpen().signal.value; + if (!isOpen) return; + const userId = (await AIProvider.userInfo)?.id; if (!userId) return; @@ -326,6 +334,7 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { this._chatSessionId = null; this._chatContextId = null; this.chatContextValue = DEFAULT_CHAT_CONTEXT_VALUE; + this.isLoading = true; requestAnimationFrame(async () => { await this._initPanel(); @@ -365,6 +374,14 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { }) .catch(console.error); } + + this._disposables.add( + this.appSidebarConfig.isOpen().signal.subscribe(isOpen => { + if (isOpen && this.isLoading) { + this._initPanel().catch(console.error); + } + }) + ); } override connectedCallback() { @@ -414,7 +431,13 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) { }; override render() { - return html`
+ const panelWidth = this.appSidebarConfig.getWidth().signal.value; + const style = styleMap({ + padding: + panelWidth && panelWidth > 540 ? '8px 24px 0 24px' : '8px 12px 0 12px', + }); + + return html`
AFFiNE AI
{ + const width$ = workbench.sidebarWidth$; + return createSignalFromObservable(width$, 0); + }, + isOpen: () => { + const open$ = workbench.sidebarOpen$; + return createSignalFromObservable(open$, true); + }, + }; chatPanelRef.current.networkSearchConfig = { visible: searchService.visible, enabled: searchService.enabled,