fix(core): use patched preview spec builder in ai chat (#10090)
[BS-2526](https://linear.app/affine-design/issue/BS-2526/chat-panel-里的-footnote-popup-需要支持交互)
This commit is contained in:
@@ -15,11 +15,17 @@ type FootNotePopupRenderer = (
|
|||||||
abortController: AbortController
|
abortController: AbortController
|
||||||
) => TemplateResult<1>;
|
) => TemplateResult<1>;
|
||||||
|
|
||||||
|
export type FootNotePopupClickHandler = (
|
||||||
|
footnote: FootNote,
|
||||||
|
abortController: AbortController
|
||||||
|
) => void;
|
||||||
|
|
||||||
export interface FootNoteNodeConfig {
|
export interface FootNoteNodeConfig {
|
||||||
customNodeRenderer?: FootNoteNodeRenderer;
|
customNodeRenderer?: FootNoteNodeRenderer;
|
||||||
customPopupRenderer?: FootNotePopupRenderer;
|
customPopupRenderer?: FootNotePopupRenderer;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
hidePopup?: boolean;
|
hidePopup?: boolean;
|
||||||
|
onPopupClick?: FootNotePopupClickHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FootNoteNodeConfigProvider {
|
export class FootNoteNodeConfigProvider {
|
||||||
@@ -27,7 +33,7 @@ export class FootNoteNodeConfigProvider {
|
|||||||
private _customPopupRenderer?: FootNotePopupRenderer;
|
private _customPopupRenderer?: FootNotePopupRenderer;
|
||||||
private _hidePopup: boolean;
|
private _hidePopup: boolean;
|
||||||
private _interactive: boolean;
|
private _interactive: boolean;
|
||||||
|
private _onPopupClick?: FootNotePopupClickHandler;
|
||||||
get customNodeRenderer() {
|
get customNodeRenderer() {
|
||||||
return this._customNodeRenderer;
|
return this._customNodeRenderer;
|
||||||
}
|
}
|
||||||
@@ -36,6 +42,10 @@ export class FootNoteNodeConfigProvider {
|
|||||||
return this._customPopupRenderer;
|
return this._customPopupRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get onPopupClick() {
|
||||||
|
return this._onPopupClick;
|
||||||
|
}
|
||||||
|
|
||||||
get doc() {
|
get doc() {
|
||||||
return this.std.store;
|
return this.std.store;
|
||||||
}
|
}
|
||||||
@@ -56,6 +66,7 @@ export class FootNoteNodeConfigProvider {
|
|||||||
this._customPopupRenderer = config.customPopupRenderer;
|
this._customPopupRenderer = config.customPopupRenderer;
|
||||||
this._hidePopup = config.hidePopup ?? false;
|
this._hidePopup = config.hidePopup ?? false;
|
||||||
this._interactive = config.interactive ?? true;
|
this._interactive = config.interactive ?? true;
|
||||||
|
this._onPopupClick = config.onPopupClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCustomNodeRenderer(renderer: FootNoteNodeRenderer) {
|
setCustomNodeRenderer(renderer: FootNoteNodeRenderer) {
|
||||||
@@ -73,6 +84,10 @@ export class FootNoteNodeConfigProvider {
|
|||||||
setInteractive(interactive: boolean) {
|
setInteractive(interactive: boolean) {
|
||||||
this._interactive = interactive;
|
this._interactive = interactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPopupClick(onPopupClick: FootNotePopupClickHandler) {
|
||||||
|
this._onPopupClick = onPopupClick;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FootNoteNodeConfigIdentifier =
|
export const FootNoteNodeConfigIdentifier =
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import { ref } from 'lit-html/directives/ref.js';
|
|||||||
import { HoverController } from '../../../../../hover/controller';
|
import { HoverController } from '../../../../../hover/controller';
|
||||||
import type { FootNoteNodeConfigProvider } from './footnote-config';
|
import type { FootNoteNodeConfigProvider } from './footnote-config';
|
||||||
|
|
||||||
|
// Virtual padding for the footnote popup overflow detection offsets.
|
||||||
|
const POPUP_SHIFT_PADDING = 8;
|
||||||
|
|
||||||
export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
.footnote-node {
|
.footnote-node {
|
||||||
@@ -64,6 +67,10 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
|||||||
return this.config?.hidePopup;
|
return this.config?.hidePopup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get onPopupClick() {
|
||||||
|
return this.config?.onPopupClick;
|
||||||
|
}
|
||||||
|
|
||||||
get inlineEditor() {
|
get inlineEditor() {
|
||||||
const inlineRoot = this.closest<InlineRootElement<AffineTextAttributes>>(
|
const inlineRoot = this.closest<InlineRootElement<AffineTextAttributes>>(
|
||||||
`[${INLINE_ROOT_ATTR}]`
|
`[${INLINE_ROOT_ATTR}]`
|
||||||
@@ -92,6 +99,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
|||||||
.footnote=${footnote}
|
.footnote=${footnote}
|
||||||
.std=${this.std}
|
.std=${this.std}
|
||||||
.abortController=${abortController}
|
.abortController=${abortController}
|
||||||
|
.onPopupClick=${this.onPopupClick}
|
||||||
></footnote-popup>`;
|
></footnote-popup>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,7 +138,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
|||||||
referenceElement: this,
|
referenceElement: this,
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
middleware: [shift()],
|
middleware: [shift({ padding: POPUP_SHIFT_PADDING })],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import {
|
|||||||
LightLoadingIcon,
|
LightLoadingIcon,
|
||||||
WebIcon16,
|
WebIcon16,
|
||||||
} from '../../../../../icons';
|
} from '../../../../../icons';
|
||||||
import { RefNodeSlotsProvider } from '../../../../extension/ref-node-slots';
|
import { PeekViewProvider } from '../../../../../peek/service';
|
||||||
|
import type { FootNotePopupClickHandler } from './footnote-config';
|
||||||
|
|
||||||
export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
@@ -130,29 +131,49 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
/**
|
/**
|
||||||
* When clicking the chip, we will navigate to the reference doc or open the url
|
* When clicking the chip, we will navigate to the reference doc or open the url
|
||||||
*/
|
*/
|
||||||
private readonly _onChipClick = () => {
|
private readonly _handleDocReference = (docId: string) => {
|
||||||
const referenceType = this.footnote.reference.type;
|
this.std
|
||||||
const { docId, url } = this.footnote.reference;
|
.getOptional(PeekViewProvider)
|
||||||
switch (referenceType) {
|
?.peek({
|
||||||
case 'doc': {
|
docId,
|
||||||
if (!docId) {
|
})
|
||||||
break;
|
.catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly _handleUrlReference = (url: string) => {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly _handleReference = () => {
|
||||||
|
const { type, docId, url } = this.footnote.reference;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'doc':
|
||||||
|
if (docId) {
|
||||||
|
this._handleDocReference(docId);
|
||||||
}
|
}
|
||||||
this.std
|
|
||||||
.getOptional(RefNodeSlotsProvider)
|
|
||||||
?.docLinkClicked.emit({ pageId: docId, host: this.std.host });
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case 'url':
|
case 'url':
|
||||||
if (!url) {
|
if (url) {
|
||||||
break;
|
this._handleUrlReference(url);
|
||||||
}
|
}
|
||||||
window.open(url, '_blank');
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.abortController.abort();
|
this.abortController.abort();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly _onChipClick = () => {
|
||||||
|
// If the onPopupClick is defined, use it
|
||||||
|
if (this.onPopupClick) {
|
||||||
|
this.onPopupClick(this.footnote, this.abortController);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, handle the reference by default
|
||||||
|
this._handleReference();
|
||||||
|
};
|
||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (this.footnote.reference.type === 'url' && this.footnote.reference.url) {
|
if (this.footnote.reference.type === 'url' && this.footnote.reference.url) {
|
||||||
@@ -195,4 +216,7 @@ export class FootNotePopup extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor abortController!: AbortController;
|
accessor abortController!: AbortController;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor onPopupClick: FootNotePopupClickHandler | undefined = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import './action-wrapper';
|
|||||||
|
|
||||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||||
|
import type { SpecBuilder } from '@blocksuite/affine/blocks';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
||||||
import { html, nothing } from 'lit';
|
import { html, nothing } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
import { createTextRenderer } from '../../../_common';
|
import { createTextRenderer } from '../../../_common';
|
||||||
import { renderImages } from '../components/images';
|
import { renderImages } from '../components/images';
|
||||||
|
|
||||||
export class ChatText extends WithDisposable(ShadowlessElement) {
|
export class ChatText extends WithDisposable(ShadowlessElement) {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor host!: EditorHost;
|
accessor host!: EditorHost;
|
||||||
@@ -21,14 +23,17 @@ export class ChatText extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor state: 'finished' | 'generating' = 'finished';
|
accessor state: 'finished' | 'generating' = 'finished';
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor previewSpecBuilder!: SpecBuilder;
|
||||||
|
|
||||||
protected override render() {
|
protected override render() {
|
||||||
const { attachments, text, host } = this;
|
const { attachments, text, host } = this;
|
||||||
return html`${attachments && attachments.length > 0
|
return html`${attachments && attachments.length > 0
|
||||||
? renderImages(attachments)
|
? renderImages(attachments)
|
||||||
: nothing}${createTextRenderer(host, { customHeading: true })(
|
: nothing}${createTextRenderer(host, {
|
||||||
text,
|
customHeading: true,
|
||||||
this.state
|
extensions: this.previewSpecBuilder.value,
|
||||||
)} `;
|
})(text, this.state)} `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
isInsidePageEditor,
|
isInsidePageEditor,
|
||||||
PaymentRequiredError,
|
PaymentRequiredError,
|
||||||
|
type SpecBuilder,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
||||||
@@ -129,6 +130,9 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor updateContext!: (context: Partial<ChatContextValue>) => void;
|
accessor updateContext!: (context: Partial<ChatContextValue>) => void;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor previewSpecBuilder!: SpecBuilder;
|
||||||
|
|
||||||
@query('.chat-panel-messages')
|
@query('.chat-panel-messages')
|
||||||
accessor messagesContainer: HTMLDivElement | null = null;
|
accessor messagesContainer: HTMLDivElement | null = null;
|
||||||
|
|
||||||
@@ -316,6 +320,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
.attachments=${item.attachments}
|
.attachments=${item.attachments}
|
||||||
.text=${item.content}
|
.text=${item.content}
|
||||||
.state=${state}
|
.state=${state}
|
||||||
|
.previewSpecBuilder=${this.previewSpecBuilder}
|
||||||
></chat-text>
|
></chat-text>
|
||||||
${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
|
${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
|
||||||
${this.renderEditorActions(item, isLast)}`;
|
${this.renderEditorActions(item, isLast)}`;
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import './chat-panel-messages';
|
|||||||
|
|
||||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||||
import { NotificationProvider } from '@blocksuite/affine/blocks';
|
import {
|
||||||
|
NotificationProvider,
|
||||||
|
type SpecBuilder,
|
||||||
|
} from '@blocksuite/affine/blocks';
|
||||||
import { debounce, WithDisposable } from '@blocksuite/affine/global/utils';
|
import { debounce, WithDisposable } from '@blocksuite/affine/global/utils';
|
||||||
import type { Store } from '@blocksuite/affine/store';
|
import type { Store } from '@blocksuite/affine/store';
|
||||||
import { css, html, type PropertyValues } from 'lit';
|
import { css, html, type PropertyValues } from 'lit';
|
||||||
@@ -172,6 +175,9 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor docDisplayConfig!: DocDisplayConfig;
|
accessor docDisplayConfig!: DocDisplayConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor previewSpecBuilder!: SpecBuilder;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
accessor isLoading = false;
|
accessor isLoading = false;
|
||||||
|
|
||||||
@@ -315,6 +321,7 @@ export class ChatPanel extends WithDisposable(ShadowlessElement) {
|
|||||||
.updateContext=${this.updateContext}
|
.updateContext=${this.updateContext}
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
.isLoading=${this.isLoading}
|
.isLoading=${this.isLoading}
|
||||||
|
.previewSpecBuilder=${this.previewSpecBuilder}
|
||||||
></chat-panel-messages>
|
></chat-panel-messages>
|
||||||
<chat-panel-chips
|
<chat-panel-chips
|
||||||
.host=${this.host}
|
.host=${this.host}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
EdgelessCRUDIdentifier,
|
EdgelessCRUDIdentifier,
|
||||||
getSurfaceBlock,
|
getSurfaceBlock,
|
||||||
NotificationProvider,
|
NotificationProvider,
|
||||||
|
type SpecBuilder,
|
||||||
TelemetryProvider,
|
TelemetryProvider,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import { html, LitElement, nothing } from 'lit';
|
import { html, LitElement, nothing } from 'lit';
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
type ChatMessage,
|
type ChatMessage,
|
||||||
ChatMessagesSchema,
|
ChatMessagesSchema,
|
||||||
} from '../../../blocks';
|
} from '../../../blocks';
|
||||||
|
import type { TextRendererOptions } from '../../_common/components/text-renderer';
|
||||||
import {
|
import {
|
||||||
ChatBlockPeekViewActions,
|
ChatBlockPeekViewActions,
|
||||||
constructUserInfoWithMessages,
|
constructUserInfoWithMessages,
|
||||||
@@ -384,6 +386,9 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
userName: message.userName,
|
userName: message.userName,
|
||||||
avatarUrl: message.avatarUrl,
|
avatarUrl: message.avatarUrl,
|
||||||
};
|
};
|
||||||
|
const textRendererOptions: TextRendererOptions = {
|
||||||
|
extensions: this.previewSpecBuilder.value,
|
||||||
|
};
|
||||||
|
|
||||||
return html`<div class=${messageClasses}>
|
return html`<div class=${messageClasses}>
|
||||||
<ai-chat-message
|
<ai-chat-message
|
||||||
@@ -393,6 +398,7 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
.attachments=${attachments}
|
.attachments=${attachments}
|
||||||
.messageRole=${role}
|
.messageRole=${role}
|
||||||
.userInfo=${userInfo}
|
.userInfo=${userInfo}
|
||||||
|
.textRendererOptions=${textRendererOptions}
|
||||||
></ai-chat-message>
|
></ai-chat-message>
|
||||||
${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
|
${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
|
||||||
${shouldRenderCopyMore
|
${shouldRenderCopyMore
|
||||||
@@ -473,12 +479,16 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
const { messages: currentChatMessages } = chatContext;
|
const { messages: currentChatMessages } = chatContext;
|
||||||
|
const textRendererOptions: TextRendererOptions = {
|
||||||
|
extensions: this.previewSpecBuilder.value,
|
||||||
|
};
|
||||||
|
|
||||||
return html`<div class="ai-chat-block-peek-view-container">
|
return html`<div class="ai-chat-block-peek-view-container">
|
||||||
<div class="ai-chat-messages-container">
|
<div class="ai-chat-messages-container">
|
||||||
<ai-chat-messages
|
<ai-chat-messages
|
||||||
.host=${host}
|
.host=${host}
|
||||||
.messages=${_historyMessages}
|
.messages=${_historyMessages}
|
||||||
|
.textRendererOptions=${textRendererOptions}
|
||||||
></ai-chat-messages>
|
></ai-chat-messages>
|
||||||
<date-time .date=${latestMessageCreatedAt}></date-time>
|
<date-time .date=${latestMessageCreatedAt}></date-time>
|
||||||
<div class="new-chat-messages-container">
|
<div class="new-chat-messages-container">
|
||||||
@@ -511,6 +521,9 @@ export class AIChatBlockPeekView extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor host!: EditorHost;
|
accessor host!: EditorHost;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor previewSpecBuilder!: SpecBuilder;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
accessor _historyMessages: ChatMessage[] = [];
|
accessor _historyMessages: ChatMessage[] = [];
|
||||||
|
|
||||||
@@ -534,10 +547,12 @@ declare global {
|
|||||||
|
|
||||||
export const AIChatBlockPeekViewTemplate = (
|
export const AIChatBlockPeekViewTemplate = (
|
||||||
parentModel: AIChatBlockModel,
|
parentModel: AIChatBlockModel,
|
||||||
host: EditorHost
|
host: EditorHost,
|
||||||
|
previewSpecBuilder: SpecBuilder
|
||||||
) => {
|
) => {
|
||||||
return html`<ai-chat-block-peek-view
|
return html`<ai-chat-block-peek-view
|
||||||
.parentModel=${parentModel}
|
.parentModel=${parentModel}
|
||||||
.host=${host}
|
.host=${host}
|
||||||
|
.previewSpecBuilder=${previewSpecBuilder}
|
||||||
></ai-chat-block-peek-view>`;
|
></ai-chat-block-peek-view>`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,27 @@
|
|||||||
import { AIChatBlockSpec } from '@affine/core/blocksuite/presets/blocks/ai-chat-block';
|
import { AIChatBlockSpec } from '@affine/core/blocksuite/presets/blocks/ai-chat-block';
|
||||||
import { SpecProvider } from '@blocksuite/affine/blocks';
|
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||||
|
import { AppThemeService } from '@affine/core/modules/theme';
|
||||||
|
import {
|
||||||
|
type BlockStdScope,
|
||||||
|
LifeCycleWatcher,
|
||||||
|
StdIdentifier,
|
||||||
|
} from '@blocksuite/affine/block-std';
|
||||||
|
import {
|
||||||
|
ColorScheme,
|
||||||
|
createSignalFromObservable,
|
||||||
|
type Signal,
|
||||||
|
type SpecBuilder,
|
||||||
|
SpecProvider,
|
||||||
|
type ThemeExtension,
|
||||||
|
ThemeExtensionIdentifier,
|
||||||
|
} from '@blocksuite/affine/blocks';
|
||||||
|
import type { Container } from '@blocksuite/affine/global/di';
|
||||||
import type { ExtensionType } from '@blocksuite/affine/store';
|
import type { ExtensionType } from '@blocksuite/affine/store';
|
||||||
|
import type { FrameworkProvider } from '@toeverything/infra';
|
||||||
|
import type { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { buildDocDisplayMetaExtension } from './custom/root-block';
|
||||||
|
import { patchPeekViewService } from './custom/spec-patchers';
|
||||||
import { getFontConfigExtension } from './font-extension';
|
import { getFontConfigExtension } from './font-extension';
|
||||||
|
|
||||||
const CustomSpecs: ExtensionType[] = [
|
const CustomSpecs: ExtensionType[] = [
|
||||||
@@ -18,3 +38,73 @@ export function effects() {
|
|||||||
// Patch edgeless preview spec for blocksuite surface-ref and embed-synced-doc
|
// Patch edgeless preview spec for blocksuite surface-ref and embed-synced-doc
|
||||||
patchPreviewSpec('edgeless:preview', CustomSpecs);
|
patchPreviewSpec('edgeless:preview', CustomSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPagePreviewThemeExtension(framework: FrameworkProvider) {
|
||||||
|
class AffinePagePreviewThemeExtension
|
||||||
|
extends LifeCycleWatcher
|
||||||
|
implements ThemeExtension
|
||||||
|
{
|
||||||
|
static override readonly key = 'affine-page-preview-theme';
|
||||||
|
|
||||||
|
readonly theme: Signal<ColorScheme>;
|
||||||
|
|
||||||
|
readonly disposables: (() => void)[] = [];
|
||||||
|
|
||||||
|
static override setup(di: Container) {
|
||||||
|
super.setup(di);
|
||||||
|
di.override(ThemeExtensionIdentifier, AffinePagePreviewThemeExtension, [
|
||||||
|
StdIdentifier,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(std: BlockStdScope) {
|
||||||
|
super(std);
|
||||||
|
const theme$: Observable<ColorScheme> = framework
|
||||||
|
.get(AppThemeService)
|
||||||
|
.appTheme.theme$.map(theme => {
|
||||||
|
return theme === ColorScheme.Dark
|
||||||
|
? ColorScheme.Dark
|
||||||
|
: ColorScheme.Light;
|
||||||
|
});
|
||||||
|
const { signal, cleanup } = createSignalFromObservable<ColorScheme>(
|
||||||
|
theme$,
|
||||||
|
ColorScheme.Light
|
||||||
|
);
|
||||||
|
this.theme = signal;
|
||||||
|
this.disposables.push(cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppTheme() {
|
||||||
|
return this.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEdgelessTheme() {
|
||||||
|
return this.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
override unmounted() {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.disposables.forEach(dispose => dispose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AffinePagePreviewThemeExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPageModePreviewSpecs(
|
||||||
|
framework: FrameworkProvider
|
||||||
|
): SpecBuilder {
|
||||||
|
const specProvider = SpecProvider.getInstance();
|
||||||
|
const pagePreviewSpec = specProvider.getSpec('page:preview');
|
||||||
|
// Enable theme extension, doc display meta extension and peek view service
|
||||||
|
const peekViewService = framework.get(PeekViewService);
|
||||||
|
pagePreviewSpec.extend([
|
||||||
|
getPagePreviewThemeExtension(framework),
|
||||||
|
buildDocDisplayMetaExtension(framework),
|
||||||
|
patchPeekViewService(peekViewService),
|
||||||
|
]);
|
||||||
|
return pagePreviewSpec;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ChatPanel } from '@affine/core/blocksuite/presets/ai';
|
import { ChatPanel } from '@affine/core/blocksuite/presets/ai';
|
||||||
|
import { createPageModePreviewSpecs } from '@affine/core/components/blocksuite/block-suite-editor/specs/preview';
|
||||||
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
|
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
|
||||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||||
import { DocSearchMenuService } from '@affine/core/modules/doc-search-menu/services';
|
import { DocSearchMenuService } from '@affine/core/modules/doc-search-menu/services';
|
||||||
@@ -83,6 +84,8 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
chatPanelRef.current.previewSpecBuilder =
|
||||||
|
createPageModePreviewSpecs(framework);
|
||||||
} else {
|
} else {
|
||||||
chatPanelRef.current.host = editor.host;
|
chatPanelRef.current.host = editor.host;
|
||||||
chatPanelRef.current.doc = editor.doc;
|
chatPanelRef.current.doc = editor.doc;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { toReactNode } from '@affine/component';
|
||||||
|
import { AIChatBlockPeekViewTemplate } from '@affine/core/blocksuite/presets/ai';
|
||||||
|
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||||
|
import { useFramework } from '@toeverything/infra';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import type { AIChatBlockModel } from '../../../../blocksuite/blocks/ai-chat-block/ai-chat-model';
|
||||||
|
import { createPageModePreviewSpecs } from '../../../../components/blocksuite/block-suite-editor/specs/preview';
|
||||||
|
|
||||||
|
export type AIChatBlockPeekViewProps = {
|
||||||
|
model: AIChatBlockModel;
|
||||||
|
host: EditorHost;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AIChatBlockPeekView = ({
|
||||||
|
model,
|
||||||
|
host,
|
||||||
|
}: AIChatBlockPeekViewProps) => {
|
||||||
|
const framework = useFramework();
|
||||||
|
return useMemo(() => {
|
||||||
|
const previewSpecBuilder = createPageModePreviewSpecs(framework);
|
||||||
|
const template = AIChatBlockPeekViewTemplate(
|
||||||
|
model,
|
||||||
|
host,
|
||||||
|
previewSpecBuilder
|
||||||
|
);
|
||||||
|
return toReactNode(template);
|
||||||
|
}, [framework, model, host]);
|
||||||
|
};
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { toReactNode } from '@affine/component';
|
import { toReactNode } from '@affine/component';
|
||||||
import { AIChatBlockPeekViewTemplate } from '@affine/core/blocksuite/presets/ai';
|
|
||||||
import { BlockComponent } from '@blocksuite/affine/block-std';
|
import { BlockComponent } from '@blocksuite/affine/block-std';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import type { ActivePeekView } from '../entities/peek-view';
|
import type { ActivePeekView } from '../entities/peek-view';
|
||||||
import { PeekViewService } from '../services/peek-view';
|
import { PeekViewService } from '../services/peek-view';
|
||||||
|
import { AIChatBlockPeekView } from './ai-chat-block-peek-view';
|
||||||
import { AttachmentPreviewPeekView } from './attachment-preview';
|
import { AttachmentPreviewPeekView } from './attachment-preview';
|
||||||
import { DocPeekPreview } from './doc-preview';
|
import { DocPeekPreview } from './doc-preview';
|
||||||
import { ImagePreviewPeekView } from './image-preview';
|
import { ImagePreviewPeekView } from './image-preview';
|
||||||
@@ -46,8 +46,7 @@ function renderPeekView({ info }: ActivePeekView, animating?: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (info.type === 'ai-chat-block') {
|
if (info.type === 'ai-chat-block') {
|
||||||
const template = AIChatBlockPeekViewTemplate(info.model, info.host);
|
return <AIChatBlockPeekView model={info.model} host={info.host} />;
|
||||||
return toReactNode(template);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // unreachable
|
return null; // unreachable
|
||||||
|
|||||||
Reference in New Issue
Block a user