fix(core): re-layout ai-chat-content to display preview panel (#13030)

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

* **New Features**
* Introduced a split-view layout in the AI chat, allowing chat content
and a preview panel to be displayed side-by-side.
* Added responsive padding and layout adjustments for improved chat
panel appearance.

* **Refactor**
* Simplified the chat panel by removing the previous preview panel
feature and related state from the main chat component.
  * Updated internal logic to support the new split-view structure.

* **Style**
* Adjusted chat panel and workspace chat page styles for better layout
consistency and responsiveness.

* **Chores**
* Improved code organization and import statements for maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-07-04 16:00:24 +08:00
committed by GitHub
parent b9c4d7230e
commit 882d06b359
6 changed files with 56 additions and 44 deletions

View File

@@ -13,16 +13,9 @@ 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,
type TemplateResult,
} from 'lit';
import { css, html, nothing, type PropertyValues } from 'lit';
import { property, state } from 'lit/decorators.js';
import { keyed } from 'lit/directives/keyed.js';
import { styleMap } from 'lit/directives/style-map.js';
import type {
DocDisplayConfig,
@@ -111,12 +104,6 @@ export class ChatPanel extends SignalWatcher(
@state()
accessor embeddingProgress: [number, number] = [0, 0];
@state()
accessor showPreviewPanel = false;
@state()
accessor previewPanelContent: TemplateResult<1> | null = null;
private isSidebarOpen: Signal<boolean | undefined> = signal(false);
private sidebarWidth: Signal<number | undefined> = signal(undefined);
@@ -269,7 +256,6 @@ export class ChatPanel extends SignalWatcher(
private readonly resetPanel = () => {
this.session = undefined;
this.embeddingProgress = [0, 0];
this.showPreviewPanel = false;
this.hasPinned = false;
};
@@ -350,12 +336,7 @@ export class ChatPanel extends SignalWatcher(
return nothing;
}
const width = this.sidebarWidth.value || 0;
const style = styleMap({
padding: width > 540 ? '8px 24px 0 24px' : '8px 12px 0 12px',
});
const left = html`<div class="chat-panel-container" style=${style}>
return html`<div class="chat-panel-container">
${keyed(
this.hasPinned ? this.session?.id : this.doc.id,
html`<ai-chat-content
@@ -378,15 +359,6 @@ export class ChatPanel extends SignalWatcher(
></ai-chat-content>`
)}
</div>`;
const right = this.previewPanelContent;
return html`<chat-panel-split-view
.left=${left}
.right=${right}
.open=${this.showPreviewPanel}
>
</chat-panel-split-view>`;
}
}

View File

@@ -46,7 +46,8 @@ export class ChatPanelSplitView extends SignalWatcher(
width: var(--gap);
position: relative;
}
.ai-chat-panel-split-view-divider[data-open='false'] {
.ai-chat-panel-split-view[data-open='false']
.ai-chat-panel-split-view-divider {
width: 0;
visibility: hidden;
pointer-events: none;

View File

@@ -88,6 +88,27 @@ export class AIChatContent extends SignalWatcher(
overflow-y: visible;
}
}
chat-panel-split-view {
height: 100%;
width: 100%;
container-type: inline-size;
container-name: chat-panel-split-view;
}
.chat-panel-main {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
width: 100%;
padding: 8px 24px 0 24px;
max-width: 800px;
margin: 0 auto;
}
@container chat-panel-split-view (width < 540px) {
.chat-panel-main {
padding: 8px 12px 0 12px;
}
}
`;
@property({ attribute: false })
@@ -152,6 +173,12 @@ export class AIChatContent extends SignalWatcher(
@state()
accessor isHistoryLoading = false;
@state()
accessor showPreviewPanel = false;
@state()
accessor previewPanelContent: TemplateResult<1> | null = null;
private readonly chatMessagesRef: Ref<AIChatMessages> =
createRef<AIChatMessages>();
@@ -311,6 +338,8 @@ export class AIChatContent extends SignalWatcher(
public reset() {
this.updateContext(DEFAULT_CHAT_CONTEXT_VALUE);
this.showPreviewPanel = false;
this.previewPanelContent = null;
}
override connectedCallback() {
@@ -349,7 +378,7 @@ export class AIChatContent extends SignalWatcher(
}
override render() {
return html`${this.chatTitle
const left = html`${this.chatTitle
? html`<div class="ai-chat-title">${this.chatTitle}</div>`
: nothing}
<ai-chat-messages
@@ -399,5 +428,14 @@ export class AIChatContent extends SignalWatcher(
control: 'chat-send',
}}
></ai-chat-composer>`;
const right = this.previewPanelContent;
return html`<chat-panel-split-view
.left=${html`<div class="chat-panel-main">${left}</div>`}
.right=${right}
.open=${this.showPreviewPanel}
>
</chat-panel-split-view>`;
}
}

View File

@@ -5,8 +5,10 @@ import { EmptyIcon } from '@blocksuite/icons/lit';
import { css, html, nothing, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import type { AIChatContent } from '../ai-chat-content';
function getChatPanel(target: HTMLElement) {
return target.closest('chat-panel');
return target.closest('ai-chat-content') as AIChatContent;
}
export const isPreviewPanelOpen = (target: HTMLElement) => {

View File

@@ -3,9 +3,6 @@ import { style } from '@vanilla-extract/css';
export const chatRoot = style({
width: '100%',
height: '100%',
maxWidth: 800,
padding: '0px 16px',
margin: '0 auto',
});
export const chatHeader = style({

View File

@@ -188,16 +188,18 @@ export const Component = () => {
tool.session = currentSession;
tool.onNewSession = () => {
if (!currentSession) return;
setCurrentSession(null);
chatContent?.reset();
};
tool.onTogglePin = async () => {
await togglePin();
};
// initial props
if (!chatTool) {
tool.onNewSession = () => {
if (!currentSession) return;
setCurrentSession(null);
chatContent?.reset();
};
tool.onTogglePin = async () => {
await togglePin();
};
// mount
chatToolContainerRef.current.append(tool);
setChatTool(tool);