mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
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:
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user