feat(core): auto expand workbench sidebar when opening ai preview panel (#13058)

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

* **New Features**
* Added the ability to programmatically open and close the AI chat
preview panel, with improved control over its visibility and content.
* Introduced event notifications for preview panel open/close actions,
enabling responsive UI updates.

* **Enhancements**
* Automatically adjusts the sidebar width to a minimum value when the
preview panel is opened for optimal viewing.
* Improved synchronization between sidebar width and external changes,
ensuring a consistent user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-07-07 14:35:54 +08:00
committed by GitHub
parent 6175bde86e
commit 563a14d0b3
5 changed files with 52 additions and 11 deletions

View File

@@ -174,10 +174,10 @@ export class AIChatContent extends SignalWatcher(
accessor isHistoryLoading = false;
@state()
accessor showPreviewPanel = false;
private accessor showPreviewPanel = false;
@state()
accessor previewPanelContent: TemplateResult<1> | null = null;
private accessor previewPanelContent: TemplateResult<1> | null = null;
private readonly chatMessagesRef: Ref<AIChatMessages> =
createRef<AIChatMessages>();
@@ -338,8 +338,23 @@ export class AIChatContent extends SignalWatcher(
public reset() {
this.updateContext(DEFAULT_CHAT_CONTEXT_VALUE);
this.closePreviewPanel(true);
}
public openPreviewPanel(content?: TemplateResult<1>) {
this.showPreviewPanel = true;
if (content) this.previewPanelContent = content;
AIProvider.slots.previewPanelOpenChange.next(true);
}
public closePreviewPanel(destroyContent: boolean = false) {
this.showPreviewPanel = false;
this.previewPanelContent = null;
if (destroyContent) this.previewPanelContent = null;
AIProvider.slots.previewPanelOpenChange.next(false);
}
public get isPreviewPanelOpen() {
return this.showPreviewPanel;
}
override connectedCallback() {

View File

@@ -13,7 +13,7 @@ function getChatPanel(target: HTMLElement) {
export const isPreviewPanelOpen = (target: HTMLElement) => {
const chatPanel = getChatPanel(target);
return chatPanel?.showPreviewPanel ?? false;
return chatPanel?.isPreviewPanelOpen ?? false;
};
export const renderPreviewPanel = (
@@ -28,13 +28,11 @@ export const renderPreviewPanel = (
return;
}
chatPanel.showPreviewPanel = true;
const preview = html`<artifact-preview-panel
.content=${content}
.controls=${controls ?? nothing}
></artifact-preview-panel>`;
chatPanel.previewPanelContent = preview;
chatPanel.openPreviewPanel(preview);
};
export const closePreviewPanel = (target: HTMLElement) => {
@@ -45,7 +43,7 @@ export const closePreviewPanel = (target: HTMLElement) => {
return;
}
chatPanel.showPreviewPanel = false;
chatPanel.closePreviewPanel();
};
export class ArtifactPreviewPanel extends WithDisposable(ShadowlessElement) {

View File

@@ -149,6 +149,7 @@ export class AIProvider {
}>(),
// downstream can emit this slot to notify ai presets that user info has been updated
userInfo: new Subject<AIUserInfo | null>(),
previewPanelOpenChange: new Subject<boolean>(),
/* eslint-enable rxjs/finnish */
};

View File

@@ -1,4 +1,4 @@
import { ChatPanel } from '@affine/core/blocksuite/ai';
import { AIProvider, ChatPanel } from '@affine/core/blocksuite/ai';
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
@@ -8,8 +8,8 @@ import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
import { DocModeProvider } from '@blocksuite/affine/shared/services';
import { createSignalFromObservable } from '@blocksuite/affine/shared/utils';
import { useFramework } from '@toeverything/infra';
import { forwardRef, useEffect, useRef } from 'react';
import { useFramework, useService } from '@toeverything/infra';
import { forwardRef, useEffect, useRef, useState } from 'react';
import * as styles from './chat.css';
@@ -25,6 +25,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
) {
const chatPanelRef = useRef<ChatPanel | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const workbench = useService(WorkbenchService).workbench;
const framework = useFramework();
useEffect(() => {
@@ -118,5 +119,25 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
playgroundConfig,
]);
const [autoResized, setAutoResized] = useState(false);
useEffect(() => {
// after auto expanded first time, do not auto expand again(even if user manually resized)
if (autoResized) return;
const subscription = AIProvider.slots.previewPanelOpenChange.subscribe(
open => {
if (!open) return;
const sidebarWidth = workbench.sidebarWidth$.value;
const MIN_SIDEBAR_WIDTH = 1080;
if (!sidebarWidth || sidebarWidth < MIN_SIDEBAR_WIDTH) {
workbench.setSidebarWidth(MIN_SIDEBAR_WIDTH);
setAutoResized(true);
}
}
);
return () => {
subscription.unsubscribe();
};
}, [autoResized, workbench]);
return <div className={styles.root} ref={containerRef} />;
});

View File

@@ -117,6 +117,7 @@ const WorkbenchSidebar = () => {
const [resizing, setResizing] = useState(false);
const workbench = useService(WorkbenchService).workbench;
const sidebarWidth = useLiveData(workbench.sidebarWidth$);
const [width, setWidth] = useState(workbench.sidebarWidth$.value ?? 0);
const views = useLiveData(workbench.views$);
@@ -152,6 +153,11 @@ const WorkbenchSidebar = () => {
};
}, []);
useEffect(() => {
if (resizing) return;
setWidth(sidebarWidth ?? 0);
}, [resizing, sidebarWidth]);
return (
<ResizePanel
floating={floating}