From b85afa7394165a3c22542fa5aa8355c40506bc94 Mon Sep 17 00:00:00 2001 From: Wu Yue Date: Tue, 15 Jul 2025 10:56:57 +0800 Subject: [PATCH] refactor(core): extract ai-chat-panel-title component (#13209) ## Summary by CodeRabbit * **New Features** * Introduced a dedicated AI chat panel title bar with dynamic embedding progress display and an optional playground button. * Added a modal playground interface accessible from the chat panel title when enabled. * **Refactor** * Moved the chat panel title and related UI logic into a new, reusable component for improved modularity. * Simplified the chat content area by removing the internal chat title rendering and related methods. --- .../src/blocksuite/ai/chat-panel/ai-title.ts | 186 ++++++++++++++++++ .../src/blocksuite/ai/chat-panel/index.ts | 104 +++------- .../ai-chat-content/ai-chat-content.ts | 44 +---- .../core/src/blocksuite/ai/effects.ts | 2 + 4 files changed, 213 insertions(+), 123 deletions(-) create mode 100644 packages/frontend/core/src/blocksuite/ai/chat-panel/ai-title.ts diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/ai-title.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/ai-title.ts new file mode 100644 index 0000000000..2bf4365269 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/ai-title.ts @@ -0,0 +1,186 @@ +import type { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import type { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import type { AppThemeService } from '@affine/core/modules/theme'; +import type { CopilotChatHistoryFragment } from '@affine/graphql'; +import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; +import { type NotificationService } from '@blocksuite/affine/shared/services'; +import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; +import type { EditorHost } from '@blocksuite/affine/std'; +import { ShadowlessElement } from '@blocksuite/affine/std'; +import type { ExtensionType, Store } from '@blocksuite/affine/store'; +import { CenterPeekIcon } from '@blocksuite/icons/lit'; +import { css, html, nothing } from 'lit'; +import { property } from 'lit/decorators.js'; + +import type { SearchMenuConfig } from '../components/ai-chat-add-context'; +import type { DocDisplayConfig } from '../components/ai-chat-chips'; +import type { + AINetworkSearchConfig, + AIPlaygroundConfig, + AIReasoningConfig, +} from '../components/ai-chat-input'; +import type { ChatStatus } from '../components/ai-chat-messages'; +import { createPlaygroundModal } from '../components/playground/modal'; +import type { AppSidebarConfig } from './chat-config'; + +export class AIChatPanelTitle extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { + static override styles = css` + .ai-chat-panel-title { + background: var(--affine-background-primary-color); + position: relative; + padding: 8px var(--h-padding); + width: 100%; + height: 36px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 1; + + svg { + width: 18px; + height: 18px; + color: var(--affine-text-secondary-color); + } + + .chat-panel-title-text { + font-size: 14px; + font-weight: 500; + color: var(--affine-text-secondary-color); + } + + .chat-panel-playground { + cursor: pointer; + padding: 2px; + margin-left: 8px; + margin-right: auto; + display: flex; + justify-content: center; + align-items: center; + } + + .chat-panel-playground:hover svg { + color: ${unsafeCSSVarV2('icon/activated')}; + } + } + `; + + @property({ attribute: false }) + accessor host!: EditorHost; + + @property({ attribute: false }) + accessor doc!: Store; + + @property({ attribute: false }) + accessor playgroundConfig!: AIPlaygroundConfig; + + @property({ attribute: false }) + accessor appSidebarConfig!: AppSidebarConfig; + + @property({ attribute: false }) + accessor networkSearchConfig!: AINetworkSearchConfig; + + @property({ attribute: false }) + accessor reasoningConfig!: AIReasoningConfig; + + @property({ attribute: false }) + accessor searchMenuConfig!: SearchMenuConfig; + + @property({ attribute: false }) + accessor docDisplayConfig!: DocDisplayConfig; + + @property({ attribute: false }) + accessor extensions!: ExtensionType[]; + + @property({ attribute: false }) + accessor affineFeatureFlagService!: FeatureFlagService; + + @property({ attribute: false }) + accessor affineWorkspaceDialogService!: WorkspaceDialogService; + + @property({ attribute: false }) + accessor affineThemeService!: AppThemeService; + + @property({ attribute: false }) + accessor notificationService!: NotificationService; + + @property({ attribute: false }) + accessor session!: CopilotChatHistoryFragment; + + @property({ attribute: false }) + accessor status!: ChatStatus; + + @property({ attribute: false }) + accessor embeddingProgress: [number, number] = [0, 0]; + + @property({ attribute: false }) + accessor newSession!: () => void; + + @property({ attribute: false }) + accessor togglePin!: () => void; + + @property({ attribute: false }) + accessor openSession!: (sessionId: string) => void; + + @property({ attribute: false }) + accessor openDoc!: (docId: string, sessionId: string) => void; + + private readonly openPlayground = () => { + const playgroundContent = html` + + `; + + createPlaygroundModal(playgroundContent, 'AI Playground'); + }; + + override render() { + const [done, total] = this.embeddingProgress; + const isEmbedding = total > 0 && done < total; + + return html` +
+
+ ${isEmbedding + ? html`Embedding ${done}/${total}` + : 'AFFiNE AI'} +
+ ${this.playgroundConfig.visible.value + ? html` +
+ ${CenterPeekIcon()} +
+ ` + : nothing} + +
+ `; + } +} 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 710aecfe92..2dbb14a989 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts @@ -9,13 +9,11 @@ import type { } from '@affine/graphql'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { type NotificationService } from '@blocksuite/affine/shared/services'; -import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import type { EditorHost } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std'; import type { ExtensionType, Store } from '@blocksuite/affine/store'; -import { CenterPeekIcon } from '@blocksuite/icons/lit'; import { type Signal, signal } from '@preact/signals-core'; -import { css, html, nothing, type PropertyValues } from 'lit'; +import { css, html, type PropertyValues } from 'lit'; import { property, state } from 'lit/decorators.js'; import { keyed } from 'lit/directives/keyed.js'; @@ -29,7 +27,6 @@ import type { AIReasoningConfig, } from '../components/ai-chat-input'; import type { ChatStatus } from '../components/ai-chat-messages'; -import { createPlaygroundModal } from '../components/playground/modal'; import { AIProvider } from '../provider'; import type { AppSidebarConfig } from './chat-config'; @@ -45,12 +42,6 @@ export class ChatPanel extends SignalWatcher( height: 100%; } - .chat-panel-title-text { - font-size: 14px; - font-weight: 500; - color: var(--affine-text-secondary-color); - } - .chat-loading-container { position: relative; padding: 44px 0 166px 0; @@ -72,20 +63,6 @@ export class ChatPanel extends SignalWatcher( font-size: var(--affine-font-sm); color: var(--affine-text-secondary-color); } - - .chat-panel-playground { - cursor: pointer; - padding: 2px; - margin-left: 8px; - margin-right: auto; - display: flex; - justify-content: center; - align-items: center; - } - - .chat-panel-playground:hover svg { - color: ${unsafeCSSVarV2('icon/activated')}; - } } `; @@ -150,40 +127,6 @@ export class ChatPanel extends SignalWatcher( return this.session !== undefined; } - private get chatTitle() { - const [done, total] = this.embeddingProgress; - const isEmbedding = total > 0 && done < total; - - return html` -
- ${isEmbedding - ? html`Embedding ${done}/${total}` - : 'AFFiNE AI'} -
- ${this.playgroundConfig.visible.value - ? html` -
- ${CenterPeekIcon()} -
- ` - : nothing} - - `; - } - private readonly getSessionIdFromUrl = () => { if (this.affineWorkbenchService) { const { workbench } = this.affineWorkbenchService; @@ -368,28 +311,6 @@ export class ChatPanel extends SignalWatcher( } }; - private readonly openPlayground = () => { - const playgroundContent = html` - - `; - - createPlaygroundModal(playgroundContent, 'AI Playground'); - }; - protected override updated(changedProperties: PropertyValues) { if (changedProperties.has('doc')) { if (this.session?.pinned) { @@ -441,10 +362,31 @@ export class ChatPanel extends SignalWatcher( } return html`
+ ${keyed( this.hasPinned ? this.session?.sessionId : this.doc.id, html` | undefined; - @property({ attribute: false }) accessor host: EditorHost | null | undefined; @@ -328,16 +301,6 @@ export class AIChatContent extends SignalWatcher( } } - public reset() { - this.updateContext(DEFAULT_CHAT_CONTEXT_VALUE); - this.closePreviewPanel(true); - } - - public reloadSession() { - this.reset(); - this.initChatContent().catch(console.error); - } - public openPreviewPanel(content?: TemplateResult<1>) { this.showPreviewPanel = true; if (content) this.previewPanelContent = content; @@ -390,10 +353,7 @@ export class AIChatContent extends SignalWatcher( } override render() { - const left = html`${this.chatTitle - ? html`
${this.chatTitle}
` - : nothing} -