feat: responsive chat-panel padding and request (#10620)

Close [BS-2751](https://linear.app/affine-design/issue/BS-2751).

### What Changed?
- Do not send AI gql request when chat-panel is not open.
- When the chat-panel width is greater than 540px, adjust the padding to 24px.
- Optimize the display and hide logic of scroll to end button.
This commit is contained in:
akumatus
2025-03-05 09:12:03 +00:00
parent 61162c59fc
commit 1c2a6eac85
4 changed files with 70 additions and 19 deletions

View File

@@ -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<number | undefined>;
cleanup: () => void;
};
isOpen: () => {
signal: Signal<boolean | undefined>;
cleanup: () => void;
};
}
export interface AINetworkSearchConfig {
visible: Signal<boolean | undefined>;
enabled: Signal<boolean | undefined>;

View File

@@ -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`
<div
class="chat-panel-messages"
@@ -284,8 +283,8 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
}
)}
</div>
${this.showDownIndicator && filteredItems.length > 0
? html`<div class="down-indicator" @click=${this.scrollToEnd}>
${showDownIndicator && filteredItems.length > 0
? html`<div class="down-indicator" @click=${this._onDownIndicatorClick}>
${DownArrowIcon}
</div>`
: 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;

View File

@@ -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` <div class="chat-panel-container">
const panelWidth = this.appSidebarConfig.getWidth().signal.value;
const style = styleMap({
padding:
panelWidth && panelWidth > 540 ? '8px 24px 0 24px' : '8px 12px 0 12px',
});
return html`<div class="chat-panel-container" style=${style}>
<div class="chat-panel-title">
<div>AFFiNE AI</div>
<div

View File

@@ -4,6 +4,7 @@ import { enableFootnoteConfigExtension } from '@affine/core/blocksuite/extension
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { DocSearchMenuService } from '@affine/core/modules/doc-search-menu/services';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace';
import {
createSignalFromObservable,
@@ -58,6 +59,17 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
const docDisplayMetaService = framework.get(DocDisplayMetaService);
const workspaceService = framework.get(WorkspaceService);
const docSearchMenuService = framework.get(DocSearchMenuService);
const workbench = framework.get(WorkbenchService).workbench;
chatPanelRef.current.appSidebarConfig = {
getWidth: () => {
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,