feat: cleanup chat panel (#14259)

#### PR Dependency Tree


* **PR #14258**
  * **PR #14259** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

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

* **Refactor**
* Split AI initialization into separate editor, app, and shared
registries; removed legacy chat-panel and replaced it with a
component-based editor chat, updating wiring and public exports.
* Propagated server/subscription/model services into chat/playground
components and improved session lifecycle and UI composition.

* **Tests**
* Added tests for AI effect registration and chat session resolution;
extended DOM/test utilities and assertions.

* **Chores**
  * Added happy-dom for runtime and test environments.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-01-26 04:02:07 +08:00
committed by GitHub
parent 02449026b9
commit 09cc2dceda
35 changed files with 1408 additions and 1059 deletions
@@ -26,7 +26,7 @@ import { property, state } from 'lit/decorators.js';
import { type ChatAction } from '../../components/ai-chat-messages';
import { createTextRenderer } from '../../components/text-renderer';
import { HISTORY_IMAGE_ACTIONS } from '../const';
import { HISTORY_IMAGE_ACTIONS } from '../../utils/history-image-actions';
const icons: Record<string, TemplateResult<1>> = {
'Fix spelling for it': DoneIcon(),
@@ -1,195 +0,0 @@
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
import type { ServerService } from '@affine/core/modules/cloud';
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 {
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, 16px);
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 reasoningConfig!: AIReasoningConfig;
@property({ attribute: false })
accessor searchMenuConfig!: SearchMenuConfig;
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@property({ attribute: false })
accessor extensions!: ExtensionType[];
@property({ attribute: false })
accessor serverService!: ServerService;
@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 aiToolsConfigService!: AIToolsConfigService;
@property({ attribute: false })
accessor session!: CopilotChatHistoryFragment | null | undefined;
@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;
@property({ attribute: false })
accessor deleteSession!: (session: BlockSuitePresets.AIRecentSession) => void;
private readonly openPlayground = () => {
const playgroundContent = html`
<playground-content
.host=${this.host}
.doc=${this.doc}
.reasoningConfig=${this.reasoningConfig}
.playgroundConfig=${this.playgroundConfig}
.appSidebarConfig=${this.appSidebarConfig}
.searchMenuConfig=${this.searchMenuConfig}
.docDisplayConfig=${this.docDisplayConfig}
.extensions=${this.extensions}
.serverService=${this.serverService}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
.aiToolsConfigService=${this.aiToolsConfigService}
></playground-content>
`;
createPlaygroundModal(playgroundContent, 'AI Playground');
};
override render() {
const [done, total] = this.embeddingProgress;
const isEmbedding = total > 0 && done < total;
return html`
<div class="ai-chat-panel-title">
<div class="chat-panel-title-text">
${isEmbedding
? html`<span data-testid="chat-panel-embedding-progress"
>Embedding ${done}/${total}</span
>`
: 'AFFiNE AI'}
</div>
${this.playgroundConfig.visible.value
? html`
<div class="chat-panel-playground" @click=${this.openPlayground}>
${CenterPeekIcon()}
</div>
`
: nothing}
<ai-chat-toolbar
.session=${this.session}
.workspaceId=${this.doc.workspace.id}
.docId=${this.doc.id}
.status=${this.status}
.onNewSession=${this.newSession}
.onTogglePin=${this.togglePin}
.onOpenSession=${this.openSession}
.onOpenDoc=${this.openDoc}
.onSessionDelete=${this.deleteSession}
.docDisplayConfig=${this.docDisplayConfig}
.notificationService=${this.notificationService}
></ai-chat-toolbar>
</div>
`;
}
}
@@ -1,485 +0,0 @@
import type {
AIDraftService,
AIToolsConfigService,
} from '@affine/core/modules/ai-button';
import type { AIModelService } from '@affine/core/modules/ai-button/services/models';
import type {
ServerService,
SubscriptionService,
} from '@affine/core/modules/cloud';
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import type { PeekViewService } from '@affine/core/modules/peek-view';
import type { AppThemeService } from '@affine/core/modules/theme';
import type { WorkbenchService } from '@affine/core/modules/workbench';
import type {
ContextEmbedStatus,
CopilotChatHistoryFragment,
UpdateChatSessionInput,
} from '@affine/graphql';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { type NotificationService } from '@blocksuite/affine/shared/services';
import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType, Store } from '@blocksuite/affine/store';
import { type Signal, signal } from '@preact/signals-core';
import { css, html, type PropertyValues } from 'lit';
import { property, state } from 'lit/decorators.js';
import { keyed } from 'lit/directives/keyed.js';
import { AffineIcon } from '../_common/icons';
import type { SearchMenuConfig } from '../components/ai-chat-add-context';
import type { DocDisplayConfig } from '../components/ai-chat-chips';
import type { ChatContextValue } from '../components/ai-chat-content';
import type {
AIPlaygroundConfig,
AIReasoningConfig,
} from '../components/ai-chat-input';
import type { ChatStatus } from '../components/ai-chat-messages';
import { AIProvider } from '../provider';
import type { AppSidebarConfig } from './chat-config';
export class ChatPanel extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
chat-panel {
width: 100%;
user-select: text;
.chat-panel-container {
height: 100%;
display: flex;
flex-direction: column;
}
ai-chat-content {
height: 0;
flex-grow: 1;
}
.chat-loading-container {
position: relative;
padding: 44px 0 166px 0;
height: 100%;
display: flex;
align-items: center;
}
.chat-loading {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.chat-loading-title {
font-weight: 600;
font-size: var(--affine-font-sm);
color: var(--affine-text-secondary-color);
}
}
`;
@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 reasoningConfig!: AIReasoningConfig;
@property({ attribute: false })
accessor searchMenuConfig!: SearchMenuConfig;
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@property({ attribute: false })
accessor extensions!: ExtensionType[];
@property({ attribute: false })
accessor serverService!: ServerService;
@property({ attribute: false })
accessor affineFeatureFlagService!: FeatureFlagService;
@property({ attribute: false })
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
@property({ attribute: false })
accessor affineWorkbenchService!: WorkbenchService;
@property({ attribute: false })
accessor affineThemeService!: AppThemeService;
@property({ attribute: false })
accessor notificationService!: NotificationService;
@property({ attribute: false })
accessor aiDraftService!: AIDraftService;
@property({ attribute: false })
accessor aiToolsConfigService!: AIToolsConfigService;
@property({ attribute: false })
accessor peekViewService!: PeekViewService;
@property({ attribute: false })
accessor subscriptionService!: SubscriptionService;
@property({ attribute: false })
accessor aiModelService!: AIModelService;
@property({ attribute: false })
accessor onAISubscribe!: () => Promise<void>;
@state()
accessor session: CopilotChatHistoryFragment | null | undefined;
@state()
accessor embeddingProgress: [number, number] = [0, 0];
@state()
accessor status: ChatStatus = 'idle';
private isSidebarOpen: Signal<boolean | undefined> = signal(false);
private sidebarWidth: Signal<number | undefined> = signal(undefined);
private hasPinned = false;
private get isInitialized() {
return this.session !== undefined;
}
private readonly getSessionIdFromUrl = () => {
if (this.affineWorkbenchService) {
const { workbench } = this.affineWorkbenchService;
const location = workbench.location$.value;
const searchParams = new URLSearchParams(location.search);
const sessionId = searchParams.get('sessionId');
if (sessionId) {
workbench.activeView$.value.updateQueryString(
{ sessionId: undefined },
{ replace: true }
);
}
return sessionId;
}
return undefined;
};
private readonly setSession = (
session: CopilotChatHistoryFragment | null | undefined
) => {
this.session = session ?? null;
};
private readonly initSession = async () => {
if (!AIProvider.session) {
return;
}
const sessionId = this.getSessionIdFromUrl();
const pinSessions = await AIProvider.session.getSessions(
this.doc.workspace.id,
undefined,
{ pinned: true, limit: 1 }
);
if (Array.isArray(pinSessions) && pinSessions[0]) {
// pinned session
this.session = pinSessions[0];
} else if (sessionId) {
// sessionId from url
const session = await AIProvider.session.getSession(
this.doc.workspace.id,
sessionId
);
this.setSession(session);
} else {
// latest doc session
const docSessions = await AIProvider.session.getSessions(
this.doc.workspace.id,
this.doc.id,
{ action: false, fork: false, limit: 1 }
);
// sessions is descending ordered by updatedAt
// the first item is the latest session
const session = docSessions?.[0];
this.setSession(session);
}
};
private readonly createSession = async (
options: Partial<BlockSuitePresets.AICreateSessionOptions> = {}
) => {
if (this.session) {
return this.session;
}
const sessionId = await AIProvider.session?.createSession({
docId: this.doc.id,
workspaceId: this.doc.workspace.id,
promptName: 'Chat With AFFiNE AI',
reuseLatestChat: false,
...options,
});
if (sessionId) {
const session = await AIProvider.session?.getSession(
this.doc.workspace.id,
sessionId
);
this.setSession(session);
}
return this.session;
};
private readonly deleteSession = async (
session: BlockSuitePresets.AIRecentSession
) => {
if (!AIProvider.histories) {
return;
}
const confirm = await this.notificationService.confirm({
title: 'Delete this history?',
message:
'Do you want to delete this AI conversation history? Once deleted, it cannot be recovered.',
confirmText: 'Delete',
cancelText: 'Cancel',
});
if (confirm) {
await AIProvider.histories.cleanup(
session.workspaceId,
session.docId || undefined,
[session.sessionId]
);
if (session.sessionId === this.session?.sessionId) {
this.newSession();
}
}
};
private readonly updateSession = async (options: UpdateChatSessionInput) => {
await AIProvider.session?.updateSession(options);
const session = await AIProvider.session?.getSession(
this.doc.workspace.id,
options.sessionId
);
this.setSession(session);
};
private readonly newSession = () => {
this.resetPanel();
requestAnimationFrame(() => {
this.session = null;
});
};
private readonly openSession = async (sessionId: string) => {
if (this.session?.sessionId === sessionId) {
return;
}
this.resetPanel();
const session = await AIProvider.session?.getSession(
this.doc.workspace.id,
sessionId
);
this.setSession(session);
};
private readonly openDoc = async (docId: string, sessionId: string) => {
if (this.doc.id === docId) {
if (this.session?.sessionId === sessionId || this.session?.pinned) {
return;
}
await this.openSession(sessionId);
} else if (this.affineWorkbenchService) {
const { workbench } = this.affineWorkbenchService;
if (this.session?.pinned) {
workbench.open(`/${docId}`, { at: 'active' });
} else {
workbench.open(`/${docId}?sessionId=${sessionId}`, { at: 'active' });
}
}
};
private readonly togglePin = async () => {
const pinned = !this.session?.pinned;
this.hasPinned = true;
if (!this.session) {
await this.createSession({ pinned });
} else {
await this.updateSession({
sessionId: this.session.sessionId,
pinned,
});
}
};
private readonly rebindSession = async () => {
if (!this.session) {
return;
}
if (this.session.docId !== this.doc.id) {
await this.updateSession({
sessionId: this.session.sessionId,
docId: this.doc.id,
});
}
};
private readonly initPanel = async () => {
try {
if (!this.isSidebarOpen.value) {
return;
}
await this.initSession();
this.hasPinned = !!this.session?.pinned;
} catch (error) {
console.error(error);
}
};
private readonly resetPanel = () => {
this.session = undefined;
this.embeddingProgress = [0, 0];
this.hasPinned = false;
};
private readonly onEmbeddingProgressChange = (
count: Record<ContextEmbedStatus, number>
) => {
const total = count.finished + count.processing + count.failed;
this.embeddingProgress = [count.finished, total];
};
private readonly onContextChange = async (
context: Partial<ChatContextValue>
) => {
this.status = context.status ?? 'idle';
if (context.status === 'success') {
await this.rebindSession();
}
};
protected override updated(changedProperties: PropertyValues) {
if (changedProperties.has('doc')) {
if (this.session?.pinned) {
return;
}
this.resetPanel();
this.initPanel().catch(console.error);
}
}
override connectedCallback() {
super.connectedCallback();
if (!this.doc) throw new Error('doc is required');
this._disposables.add(
AIProvider.slots.userInfo.subscribe(() => {
this.resetPanel();
this.initPanel().catch(console.error);
})
);
const isOpen = this.appSidebarConfig.isOpen();
this.isSidebarOpen = isOpen.signal;
this._disposables.add(isOpen.cleanup);
const width = this.appSidebarConfig.getWidth();
this.sidebarWidth = width.signal;
this._disposables.add(width.cleanup);
this._disposables.add(
this.isSidebarOpen.subscribe(isOpen => {
if (isOpen && !this.isInitialized) {
this.initPanel().catch(console.error);
}
})
);
}
override render() {
if (!this.isInitialized) {
return html`<div class="chat-loading-container">
<div class="chat-loading">
${AffineIcon('var(--affine-icon-secondary)')}
<div class="chat-loading-title">
<span> AFFiNE AI is loading history... </span>
</div>
</div>
</div>`;
}
return html`<div class="chat-panel-container">
<ai-chat-panel-title
.host=${this.host}
.doc=${this.doc}
.playgroundConfig=${this.playgroundConfig}
.appSidebarConfig=${this.appSidebarConfig}
.reasoningConfig=${this.reasoningConfig}
.searchMenuConfig=${this.searchMenuConfig}
.docDisplayConfig=${this.docDisplayConfig}
.extensions=${this.extensions}
.serverService=${this.serverService}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
.aiToolsConfigService=${this.aiToolsConfigService}
.session=${this.session}
.status=${this.status}
.embeddingProgress=${this.embeddingProgress}
.newSession=${this.newSession}
.togglePin=${this.togglePin}
.openSession=${this.openSession}
.openDoc=${this.openDoc}
.deleteSession=${this.deleteSession}
></ai-chat-panel-title>
${keyed(
this.hasPinned ? this.session?.sessionId : this.doc.id,
html`<ai-chat-content
.host=${this.host}
.session=${this.session}
.createSession=${this.createSession}
.workspaceId=${this.doc.workspace.id}
.docId=${this.doc.id}
.reasoningConfig=${this.reasoningConfig}
.searchMenuConfig=${this.searchMenuConfig}
.docDisplayConfig=${this.docDisplayConfig}
.extensions=${this.extensions}
.serverService=${this.serverService}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
.aiDraftService=${this.aiDraftService}
.aiToolsConfigService=${this.aiToolsConfigService}
.peekViewService=${this.peekViewService}
.subscriptionService=${this.subscriptionService}
.aiModelService=${this.aiModelService}
.onAISubscribe=${this.onAISubscribe}
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
.onContextChange=${this.onContextChange}
.width=${this.sidebarWidth}
.onOpenDoc=${this.openDoc}
></ai-chat-content>`
)}
</div>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'chat-panel': ChatPanel;
}
}
@@ -5,7 +5,7 @@ import { html } from 'lit';
import { property } from 'lit/decorators.js';
import { type ChatAction } from '../../components/ai-chat-messages';
import { HISTORY_IMAGE_ACTIONS } from '../const';
import { HISTORY_IMAGE_ACTIONS } from '../../utils/history-image-actions';
export class ChatMessageAction extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
@@ -28,9 +28,9 @@ import { createRef, type Ref, ref } from 'lit/directives/ref.js';
import { styleMap } from 'lit/directives/style-map.js';
import { pick } from 'lodash-es';
import { HISTORY_IMAGE_ACTIONS } from '../../chat-panel/const';
import { type AIChatParams, AIProvider } from '../../provider/ai-provider';
import { extractSelectedContent } from '../../utils/extract';
import { HISTORY_IMAGE_ACTIONS } from '../../utils/history-image-actions';
import type { SearchMenuConfig } from '../ai-chat-add-context';
import type { DocDisplayConfig } from '../ai-chat-chips';
import type { AIReasoningConfig } from '../ai-chat-input';
@@ -19,12 +19,12 @@ import { repeat } from 'lit/directives/repeat.js';
import { debounce } from 'lodash-es';
import { AffineIcon } from '../../_common/icons';
import { AIPreloadConfig } from '../../chat-panel/preload-config';
import { type AIError, AIProvider, UnauthorizedError } from '../../provider';
import { mergeStreamObjects } from '../../utils/stream-objects';
import type { DocDisplayConfig } from '../ai-chat-chips';
import { type ChatContextValue } from '../ai-chat-content/type';
import type { AIReasoningConfig } from '../ai-chat-input';
import { AIPreloadConfig } from './preload-config';
import {
type HistoryMessage,
isChatAction,
@@ -6,7 +6,7 @@ import {
SendIcon,
} from '@blocksuite/icons/lit';
import { AIProvider } from '../provider/ai-provider.js';
import { AIProvider } from '../../provider/ai-provider.js';
import completeWritingWithAI from './templates/completeWritingWithAI.zip';
import freelyCommunicateWithAI from './templates/freelyCommunicateWithAI.zip';
import readAforeign from './templates/readAforeign.zip';
@@ -1,5 +1,9 @@
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
import type { ServerService } from '@affine/core/modules/cloud';
import type { AIModelService } from '@affine/core/modules/ai-button/services/models';
import type {
ServerService,
SubscriptionService,
} from '@affine/core/modules/cloud';
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';
@@ -20,8 +24,8 @@ import { createRef, type Ref, ref } from 'lit/directives/ref.js';
import { throttle } from 'lodash-es';
import type { AppSidebarConfig } from '../../chat-panel/chat-config';
import { HISTORY_IMAGE_ACTIONS } from '../../chat-panel/const';
import { AIProvider } from '../../provider';
import { HISTORY_IMAGE_ACTIONS } from '../../utils/history-image-actions';
import type { SearchMenuConfig } from '../ai-chat-add-context';
import type { DocDisplayConfig } from '../ai-chat-chips';
import type { ChatContextValue } from '../ai-chat-content';
@@ -179,6 +183,12 @@ export class PlaygroundChat extends SignalWatcher(
@property({ attribute: false })
accessor aiToolsConfigService!: AIToolsConfigService;
@property({ attribute: false })
accessor subscriptionService!: SubscriptionService;
@property({ attribute: false })
accessor aiModelService!: AIModelService;
@property({ attribute: false })
accessor onAISubscribe: (() => Promise<void>) | undefined;
@@ -373,6 +383,8 @@ export class PlaygroundChat extends SignalWatcher(
.aiToolsConfigService=${this.aiToolsConfigService}
.affineWorkspaceDialogService=${this.affineWorkspaceDialogService}
.affineFeatureFlagService=${this.affineFeatureFlagService}
.subscriptionService=${this.subscriptionService}
.aiModelService=${this.aiModelService}
.onAISubscribe=${this.onAISubscribe}
></ai-chat-composer>
</div>`;
@@ -1,5 +1,10 @@
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
import type { ServerService } from '@affine/core/modules/cloud';
import type { AIModelService } from '@affine/core/modules/ai-button/services/models';
import type {
ServerService,
SubscriptionService,
} from '@affine/core/modules/cloud';
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';
@@ -93,6 +98,15 @@ export class PlaygroundContent extends SignalWatcher(
@property({ attribute: false })
accessor aiToolsConfigService!: AIToolsConfigService;
@property({ attribute: false })
accessor affineWorkspaceDialogService!: WorkspaceDialogService;
@property({ attribute: false })
accessor subscriptionService!: SubscriptionService;
@property({ attribute: false })
accessor aiModelService!: AIModelService;
@state()
accessor sessions: CopilotChatHistoryFragment[] = [];
@@ -351,6 +365,10 @@ export class PlaygroundContent extends SignalWatcher(
.affineThemeService=${this.affineThemeService}
.notificationService=${this.notificationService}
.aiToolsConfigService=${this.aiToolsConfigService}
.affineWorkspaceDialogService=${this
.affineWorkspaceDialogService}
.subscriptionService=${this.subscriptionService}
.aiModelService=${this.aiModelService}
.addChat=${this.addChat}
></playground-chat>
</div>
@@ -1,256 +0,0 @@
import { effects as tooltipEffects } from '@blocksuite/affine-components/tooltip';
import { AIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-block';
import { EdgelessAIChatBlockComponent } from './blocks/ai-chat-block/ai-chat-edgeless-block';
import { LitTranscriptionBlock } from './blocks/ai-chat-block/ai-transcription-block';
import {
AIChatBlockMessage,
AIChatBlockMessages,
} from './blocks/ai-chat-block/components/ai-chat-messages';
import {
ChatImage,
ChatImages,
} from './blocks/ai-chat-block/components/chat-images';
import { ImagePlaceholder } from './blocks/ai-chat-block/components/image-placeholder';
import { UserInfo } from './blocks/ai-chat-block/components/user-info';
import { ChatPanel } from './chat-panel';
import { ActionWrapper } from './chat-panel/actions/action-wrapper';
import { ActionImage } from './chat-panel/actions/image';
import { ActionImageToText } from './chat-panel/actions/image-to-text';
import { ActionMakeReal } from './chat-panel/actions/make-real';
import { ActionMindmap } from './chat-panel/actions/mindmap';
import { ActionSlides } from './chat-panel/actions/slides';
import { ActionText } from './chat-panel/actions/text';
import { AILoading } from './chat-panel/ai-loading';
import { AIChatPanelTitle } from './chat-panel/ai-title';
import { ChatMessageAction } from './chat-panel/message/action';
import { ChatMessageAssistant } from './chat-panel/message/assistant';
import { ChatMessageUser } from './chat-panel/message/user';
import { ChatPanelSplitView } from './chat-panel/split-view';
import { ArtifactSkeleton } from './components/ai-artifact-skeleton';
import { AIChatAddContext } from './components/ai-chat-add-context';
import { ChatPanelAddPopover } from './components/ai-chat-chips/add-popover';
import { ChatPanelAttachmentChip } from './components/ai-chat-chips/attachment-chip';
import { ChatPanelCandidatesPopover } from './components/ai-chat-chips/candidates-popover';
import { ChatPanelChips } from './components/ai-chat-chips/chat-panel-chips';
import { ChatPanelChip } from './components/ai-chat-chips/chip';
import { ChatPanelCollectionChip } from './components/ai-chat-chips/collection-chip';
import { ChatPanelDocChip } from './components/ai-chat-chips/doc-chip';
import { ChatPanelFileChip } from './components/ai-chat-chips/file-chip';
import { ChatPanelSelectedChip } from './components/ai-chat-chips/selected-chip';
import { ChatPanelTagChip } from './components/ai-chat-chips/tag-chip';
import { AIChatComposer } from './components/ai-chat-composer';
import { AIChatContent } from './components/ai-chat-content';
import { AIChatInput } from './components/ai-chat-input';
import { AIChatEmbeddingStatusTooltip } from './components/ai-chat-input/embedding-status-tooltip';
import { ChatInputPreference } from './components/ai-chat-input/preference-popup';
import { AIChatMessages } from './components/ai-chat-messages/ai-chat-messages';
import { AIChatToolbar, AISessionHistory } from './components/ai-chat-toolbar';
import { AIHistoryClear } from './components/ai-history-clear';
import { effects as componentAiItemEffects } from './components/ai-item';
import { AssistantAvatar } from './components/ai-message-content/assistant-avatar';
import { ChatContentImages } from './components/ai-message-content/images';
import { ChatContentPureText } from './components/ai-message-content/pure-text';
import { ChatContentRichText } from './components/ai-message-content/rich-text';
import { ChatContentStreamObjects } from './components/ai-message-content/stream-objects';
import { AIScrollableTextRenderer } from './components/ai-scrollable-text-renderer';
import { ArtifactPreviewPanel } from './components/ai-tools/artifacts-preview-panel';
import {
CodeArtifactTool,
CodeHighlighter,
} from './components/ai-tools/code-artifact';
import { DocComposeTool } from './components/ai-tools/doc-compose';
import { DocEditTool } from './components/ai-tools/doc-edit';
import { DocKeywordSearchResult } from './components/ai-tools/doc-keyword-search-result';
import { DocReadResult } from './components/ai-tools/doc-read-result';
import { DocSemanticSearchResult } from './components/ai-tools/doc-semantic-search-result';
import { DocWriteTool } from './components/ai-tools/doc-write';
import { SectionEditTool } from './components/ai-tools/section-edit';
import { ToolCallCard } from './components/ai-tools/tool-call-card';
import { ToolFailedCard } from './components/ai-tools/tool-failed-card';
import { ToolResultCard } from './components/ai-tools/tool-result-card';
import { WebCrawlTool } from './components/ai-tools/web-crawl';
import { WebSearchTool } from './components/ai-tools/web-search';
import { AskAIButton } from './components/ask-ai-button';
import { AskAIIcon } from './components/ask-ai-icon';
import { AskAIPanel } from './components/ask-ai-panel';
import { AskAIToolbarButton } from './components/ask-ai-toolbar';
import { ChatActionList } from './components/chat-action-list';
import { ChatCopyMore } from './components/copy-more';
import { ImagePreviewGrid } from './components/image-preview-grid';
import { effects as componentPlaygroundEffects } from './components/playground';
import { TextRenderer } from './components/text-renderer';
import { AIErrorWrapper } from './messages/error';
import { AISlidesRenderer } from './messages/slides-renderer';
import { AIAnswerWrapper } from './messages/wrapper';
import { registerMiniMindmapBlocks } from './mini-mindmap';
import { AIChatBlockPeekView } from './peek-view/chat-block-peek-view';
import { DateTime } from './peek-view/date-time';
import {
AFFINE_AI_PANEL_WIDGET,
AffineAIPanelWidget,
} from './widgets/ai-panel/ai-panel';
import {
AIPanelAnswer,
AIPanelDivider,
AIPanelError,
AIPanelGenerating,
AIPanelInput,
} from './widgets/ai-panel/components';
import { AIFinishTip } from './widgets/ai-panel/components/finish-tip';
import { GeneratingPlaceholder } from './widgets/ai-panel/components/generating-placeholder';
import {
AFFINE_BLOCK_DIFF_WIDGET_FOR_BLOCK,
AffineBlockDiffWidgetForBlock,
} from './widgets/block-diff/block';
import { BlockDiffOptions } from './widgets/block-diff/options';
import {
AFFINE_BLOCK_DIFF_WIDGET_FOR_PAGE,
AffineBlockDiffWidgetForPage,
} from './widgets/block-diff/page';
import {
AFFINE_BLOCK_DIFF_PLAYGROUND,
AFFINE_BLOCK_DIFF_PLAYGROUND_MODAL,
BlockDiffPlayground,
BlockDiffPlaygroundModal,
} from './widgets/block-diff/playground';
import {
AFFINE_EDGELESS_COPILOT_WIDGET,
EdgelessCopilotWidget,
} from './widgets/edgeless-copilot';
import { EdgelessCopilotPanel } from './widgets/edgeless-copilot-panel';
import { EdgelessCopilotToolbarEntry } from './widgets/edgeless-copilot-panel/toolbar-entry';
export function registerAIEffects() {
registerMiniMindmapBlocks();
componentAiItemEffects();
componentPlaygroundEffects();
tooltipEffects();
customElements.define('ask-ai-icon', AskAIIcon);
customElements.define('ask-ai-button', AskAIButton);
customElements.define('ask-ai-toolbar-button', AskAIToolbarButton);
customElements.define('ask-ai-panel', AskAIPanel);
customElements.define('chat-action-list', ChatActionList);
customElements.define('chat-copy-more', ChatCopyMore);
customElements.define('image-preview-grid', ImagePreviewGrid);
customElements.define('action-wrapper', ActionWrapper);
customElements.define('action-image-to-text', ActionImageToText);
customElements.define('action-image', ActionImage);
customElements.define('action-make-real', ActionMakeReal);
customElements.define('action-mindmap', ActionMindmap);
customElements.define('action-slides', ActionSlides);
customElements.define('action-text', ActionText);
customElements.define('ai-loading', AILoading);
customElements.define('ai-chat-content', AIChatContent);
customElements.define('ai-chat-toolbar', AIChatToolbar);
customElements.define('ai-session-history', AISessionHistory);
customElements.define('ai-chat-messages', AIChatMessages);
customElements.define('chat-panel', ChatPanel);
customElements.define('ai-chat-panel-title', AIChatPanelTitle);
customElements.define('ai-chat-input', AIChatInput);
customElements.define('ai-chat-add-context', AIChatAddContext);
customElements.define(
'ai-chat-embedding-status-tooltip',
AIChatEmbeddingStatusTooltip
);
customElements.define('ai-chat-composer', AIChatComposer);
customElements.define('chat-panel-chips', ChatPanelChips);
customElements.define('ai-history-clear', AIHistoryClear);
customElements.define('chat-panel-add-popover', ChatPanelAddPopover);
customElements.define('chat-input-preference', ChatInputPreference);
customElements.define(
'chat-panel-candidates-popover',
ChatPanelCandidatesPopover
);
customElements.define('chat-panel-doc-chip', ChatPanelDocChip);
customElements.define('chat-panel-file-chip', ChatPanelFileChip);
customElements.define('chat-panel-tag-chip', ChatPanelTagChip);
customElements.define('chat-panel-collection-chip', ChatPanelCollectionChip);
customElements.define('chat-panel-selected-chip', ChatPanelSelectedChip);
customElements.define('chat-panel-attachment-chip', ChatPanelAttachmentChip);
customElements.define('chat-panel-chip', ChatPanelChip);
customElements.define('ai-error-wrapper', AIErrorWrapper);
customElements.define('ai-slides-renderer', AISlidesRenderer);
customElements.define('ai-answer-wrapper', AIAnswerWrapper);
customElements.define('ai-chat-block-peek-view', AIChatBlockPeekView);
customElements.define('date-time', DateTime);
customElements.define(
'affine-edgeless-ai-chat',
EdgelessAIChatBlockComponent
);
customElements.define('affine-ai-chat', AIChatBlockComponent);
customElements.define('ai-chat-block-message', AIChatBlockMessage);
customElements.define('ai-chat-block-messages', AIChatBlockMessages);
customElements.define(
'ai-scrollable-text-renderer',
AIScrollableTextRenderer
);
customElements.define('image-placeholder', ImagePlaceholder);
customElements.define('chat-image', ChatImage);
customElements.define('chat-images', ChatImages);
customElements.define('user-info', UserInfo);
customElements.define('text-renderer', TextRenderer);
customElements.define('generating-placeholder', GeneratingPlaceholder);
customElements.define('ai-finish-tip', AIFinishTip);
customElements.define('ai-panel-divider', AIPanelDivider);
customElements.define('ai-panel-answer', AIPanelAnswer);
customElements.define('ai-panel-input', AIPanelInput);
customElements.define('ai-panel-generating', AIPanelGenerating);
customElements.define('ai-panel-error', AIPanelError);
customElements.define('chat-assistant-avatar', AssistantAvatar);
customElements.define('chat-content-images', ChatContentImages);
customElements.define('chat-content-pure-text', ChatContentPureText);
customElements.define('chat-content-rich-text', ChatContentRichText);
customElements.define(
'chat-content-stream-objects',
ChatContentStreamObjects
);
customElements.define('chat-message-action', ChatMessageAction);
customElements.define('chat-message-assistant', ChatMessageAssistant);
customElements.define('chat-message-user', ChatMessageUser);
customElements.define('ai-block-diff-options', BlockDiffOptions);
customElements.define(AFFINE_BLOCK_DIFF_PLAYGROUND, BlockDiffPlayground);
customElements.define(
AFFINE_BLOCK_DIFF_PLAYGROUND_MODAL,
BlockDiffPlaygroundModal
);
customElements.define('tool-call-card', ToolCallCard);
customElements.define('tool-result-card', ToolResultCard);
customElements.define('tool-call-failed', ToolFailedCard);
customElements.define('doc-semantic-search-result', DocSemanticSearchResult);
customElements.define('doc-keyword-search-result', DocKeywordSearchResult);
customElements.define('doc-read-result', DocReadResult);
customElements.define('doc-write-tool', DocWriteTool);
customElements.define('web-crawl-tool', WebCrawlTool);
customElements.define('web-search-tool', WebSearchTool);
customElements.define('section-edit-tool', SectionEditTool);
customElements.define('doc-compose-tool', DocComposeTool);
customElements.define('code-artifact-tool', CodeArtifactTool);
customElements.define('code-highlighter', CodeHighlighter);
customElements.define('artifact-preview-panel', ArtifactPreviewPanel);
customElements.define('doc-edit-tool', DocEditTool);
customElements.define(AFFINE_AI_PANEL_WIDGET, AffineAIPanelWidget);
customElements.define(AFFINE_EDGELESS_COPILOT_WIDGET, EdgelessCopilotWidget);
customElements.define(
AFFINE_BLOCK_DIFF_WIDGET_FOR_BLOCK,
AffineBlockDiffWidgetForBlock
);
customElements.define(
AFFINE_BLOCK_DIFF_WIDGET_FOR_PAGE,
AffineBlockDiffWidgetForPage
);
customElements.define('edgeless-copilot-panel', EdgelessCopilotPanel);
customElements.define(
'edgeless-copilot-toolbar-entry',
EdgelessCopilotToolbarEntry
);
customElements.define('transcription-block', LitTranscriptionBlock);
customElements.define('chat-panel-split-view', ChatPanelSplitView);
customElements.define('artifact-skeleton', ArtifactSkeleton);
}
@@ -0,0 +1,95 @@
import { ActionWrapper } from '../chat-panel/actions/action-wrapper';
import { ActionImage } from '../chat-panel/actions/image';
import { ActionImageToText } from '../chat-panel/actions/image-to-text';
import { ActionMakeReal } from '../chat-panel/actions/make-real';
import { ActionMindmap } from '../chat-panel/actions/mindmap';
import { ActionSlides } from '../chat-panel/actions/slides';
import { ActionText } from '../chat-panel/actions/text';
import { AILoading } from '../chat-panel/ai-loading';
import { ChatMessageAction } from '../chat-panel/message/action';
import { ChatMessageAssistant } from '../chat-panel/message/assistant';
import { ChatMessageUser } from '../chat-panel/message/user';
import { AIChatAddContext } from '../components/ai-chat-add-context';
import { ChatPanelAddPopover } from '../components/ai-chat-chips/add-popover';
import { ChatPanelAttachmentChip } from '../components/ai-chat-chips/attachment-chip';
import { ChatPanelCandidatesPopover } from '../components/ai-chat-chips/candidates-popover';
import { ChatPanelChips } from '../components/ai-chat-chips/chat-panel-chips';
import { ChatPanelChip } from '../components/ai-chat-chips/chip';
import { ChatPanelCollectionChip } from '../components/ai-chat-chips/collection-chip';
import { ChatPanelDocChip } from '../components/ai-chat-chips/doc-chip';
import { ChatPanelFileChip } from '../components/ai-chat-chips/file-chip';
import { ChatPanelSelectedChip } from '../components/ai-chat-chips/selected-chip';
import { ChatPanelTagChip } from '../components/ai-chat-chips/tag-chip';
import { AIChatComposer } from '../components/ai-chat-composer';
import { AIChatContent } from '../components/ai-chat-content';
import { ChatPanelSplitView } from '../components/ai-chat-content/split-view';
import { AIChatInput } from '../components/ai-chat-input';
import { AIChatEmbeddingStatusTooltip } from '../components/ai-chat-input/embedding-status-tooltip';
import { ChatInputPreference } from '../components/ai-chat-input/preference-popup';
import { AIChatMessages } from '../components/ai-chat-messages/ai-chat-messages';
import { AIChatToolbar, AISessionHistory } from '../components/ai-chat-toolbar';
import { AIHistoryClear } from '../components/ai-history-clear';
import { AssistantAvatar } from '../components/ai-message-content/assistant-avatar';
import { ChatActionList } from '../components/chat-action-list';
import { ChatCopyMore } from '../components/copy-more';
import { ImagePreviewGrid } from '../components/image-preview-grid';
import { effects as componentPlaygroundEffects } from '../components/playground';
import { AIChatBlockPeekView } from '../peek-view/chat-block-peek-view';
import { DateTime } from '../peek-view/date-time';
import { type AppEffectElementTag, appEffectElementTags } from './registry';
import { registerAISharedEffects } from './shared';
const appRegistries = new WeakSet<CustomElementRegistry>();
const appElements = {
'chat-action-list': ChatActionList,
'chat-copy-more': ChatCopyMore,
'image-preview-grid': ImagePreviewGrid,
'action-wrapper': ActionWrapper,
'action-image-to-text': ActionImageToText,
'action-image': ActionImage,
'action-make-real': ActionMakeReal,
'action-mindmap': ActionMindmap,
'action-slides': ActionSlides,
'action-text': ActionText,
'ai-loading': AILoading,
'ai-chat-content': AIChatContent,
'ai-chat-toolbar': AIChatToolbar,
'ai-session-history': AISessionHistory,
'ai-chat-messages': AIChatMessages,
'ai-chat-input': AIChatInput,
'ai-chat-add-context': AIChatAddContext,
'ai-chat-embedding-status-tooltip': AIChatEmbeddingStatusTooltip,
'ai-chat-composer': AIChatComposer,
'chat-panel-chips': ChatPanelChips,
'ai-history-clear': AIHistoryClear,
'chat-panel-add-popover': ChatPanelAddPopover,
'chat-input-preference': ChatInputPreference,
'chat-panel-candidates-popover': ChatPanelCandidatesPopover,
'chat-panel-doc-chip': ChatPanelDocChip,
'chat-panel-file-chip': ChatPanelFileChip,
'chat-panel-tag-chip': ChatPanelTagChip,
'chat-panel-collection-chip': ChatPanelCollectionChip,
'chat-panel-selected-chip': ChatPanelSelectedChip,
'chat-panel-attachment-chip': ChatPanelAttachmentChip,
'chat-panel-chip': ChatPanelChip,
'chat-assistant-avatar': AssistantAvatar,
'chat-message-action': ChatMessageAction,
'chat-message-assistant': ChatMessageAssistant,
'chat-message-user': ChatMessageUser,
'ai-chat-block-peek-view': AIChatBlockPeekView,
'date-time': DateTime,
'chat-panel-split-view': ChatPanelSplitView,
} satisfies Record<AppEffectElementTag, CustomElementConstructor>;
export function registerAIAppEffects() {
const registry = customElements;
if (appRegistries.has(registry)) return;
appRegistries.add(registry);
registerAISharedEffects();
componentPlaygroundEffects();
for (const tag of appEffectElementTags) {
customElements.define(tag, appElements[tag]);
}
}
@@ -0,0 +1,105 @@
import { AIChatBlockComponent } from '../blocks/ai-chat-block/ai-chat-block';
import { EdgelessAIChatBlockComponent } from '../blocks/ai-chat-block/ai-chat-edgeless-block';
import { LitTranscriptionBlock } from '../blocks/ai-chat-block/ai-transcription-block';
import {
AIChatBlockMessage,
AIChatBlockMessages,
} from '../blocks/ai-chat-block/components/ai-chat-messages';
import {
ChatImage,
ChatImages,
} from '../blocks/ai-chat-block/components/chat-images';
import { ImagePlaceholder } from '../blocks/ai-chat-block/components/image-placeholder';
import { UserInfo } from '../blocks/ai-chat-block/components/user-info';
import { effects as componentAiItemEffects } from '../components/ai-item';
import { AIScrollableTextRenderer } from '../components/ai-scrollable-text-renderer';
import { AskAIButton } from '../components/ask-ai-button';
import { AskAIIcon } from '../components/ask-ai-icon';
import { AskAIPanel } from '../components/ask-ai-panel';
import { AskAIToolbarButton } from '../components/ask-ai-toolbar';
import {
AFFINE_AI_PANEL_WIDGET,
AffineAIPanelWidget,
} from '../widgets/ai-panel/ai-panel';
import {
AIPanelAnswer,
AIPanelDivider,
AIPanelError,
AIPanelGenerating,
AIPanelInput,
} from '../widgets/ai-panel/components';
import { AIFinishTip } from '../widgets/ai-panel/components/finish-tip';
import { GeneratingPlaceholder } from '../widgets/ai-panel/components/generating-placeholder';
import {
AFFINE_BLOCK_DIFF_WIDGET_FOR_BLOCK,
AffineBlockDiffWidgetForBlock,
} from '../widgets/block-diff/block';
import { BlockDiffOptions } from '../widgets/block-diff/options';
import {
AFFINE_BLOCK_DIFF_WIDGET_FOR_PAGE,
AffineBlockDiffWidgetForPage,
} from '../widgets/block-diff/page';
import {
AFFINE_BLOCK_DIFF_PLAYGROUND,
AFFINE_BLOCK_DIFF_PLAYGROUND_MODAL,
BlockDiffPlayground,
BlockDiffPlaygroundModal,
} from '../widgets/block-diff/playground';
import {
AFFINE_EDGELESS_COPILOT_WIDGET,
EdgelessCopilotWidget,
} from '../widgets/edgeless-copilot';
import { EdgelessCopilotPanel } from '../widgets/edgeless-copilot-panel';
import { EdgelessCopilotToolbarEntry } from '../widgets/edgeless-copilot-panel/toolbar-entry';
import {
type EditorEffectElementTag,
editorEffectElementTags,
} from './registry';
import { registerAISharedEffects } from './shared';
const editorRegistries = new WeakSet<CustomElementRegistry>();
const editorElements = {
'ask-ai-icon': AskAIIcon,
'ask-ai-button': AskAIButton,
'ask-ai-toolbar-button': AskAIToolbarButton,
'ask-ai-panel': AskAIPanel,
'affine-edgeless-ai-chat': EdgelessAIChatBlockComponent,
'affine-ai-chat': AIChatBlockComponent,
'ai-chat-block-message': AIChatBlockMessage,
'ai-chat-block-messages': AIChatBlockMessages,
'ai-scrollable-text-renderer': AIScrollableTextRenderer,
'image-placeholder': ImagePlaceholder,
'chat-image': ChatImage,
'chat-images': ChatImages,
'user-info': UserInfo,
'generating-placeholder': GeneratingPlaceholder,
'ai-finish-tip': AIFinishTip,
'ai-panel-divider': AIPanelDivider,
'ai-panel-answer': AIPanelAnswer,
'ai-panel-input': AIPanelInput,
'ai-panel-generating': AIPanelGenerating,
'ai-panel-error': AIPanelError,
'ai-block-diff-options': BlockDiffOptions,
[AFFINE_BLOCK_DIFF_PLAYGROUND]: BlockDiffPlayground,
[AFFINE_BLOCK_DIFF_PLAYGROUND_MODAL]: BlockDiffPlaygroundModal,
[AFFINE_AI_PANEL_WIDGET]: AffineAIPanelWidget,
[AFFINE_EDGELESS_COPILOT_WIDGET]: EdgelessCopilotWidget,
[AFFINE_BLOCK_DIFF_WIDGET_FOR_BLOCK]: AffineBlockDiffWidgetForBlock,
[AFFINE_BLOCK_DIFF_WIDGET_FOR_PAGE]: AffineBlockDiffWidgetForPage,
'edgeless-copilot-panel': EdgelessCopilotPanel,
'edgeless-copilot-toolbar-entry': EdgelessCopilotToolbarEntry,
'transcription-block': LitTranscriptionBlock,
} satisfies Record<EditorEffectElementTag, CustomElementConstructor>;
export function registerAIEditorEffects() {
const registry = customElements;
if (editorRegistries.has(registry)) return;
editorRegistries.add(registry);
registerAISharedEffects();
componentAiItemEffects();
for (const tag of editorEffectElementTags) {
customElements.define(tag, editorElements[tag]);
}
}
@@ -0,0 +1,106 @@
export const sharedEffectElementTags = [
'ai-error-wrapper',
'ai-slides-renderer',
'ai-answer-wrapper',
'chat-content-images',
'chat-content-pure-text',
'chat-content-rich-text',
'chat-content-stream-objects',
'text-renderer',
'tool-call-card',
'tool-result-card',
'tool-call-failed',
'doc-semantic-search-result',
'doc-keyword-search-result',
'doc-read-result',
'doc-write-tool',
'web-crawl-tool',
'web-search-tool',
'section-edit-tool',
'doc-compose-tool',
'code-artifact-tool',
'code-highlighter',
'artifact-preview-panel',
'doc-edit-tool',
'artifact-skeleton',
] as const;
export type SharedEffectElementTag = (typeof sharedEffectElementTags)[number];
export const editorEffectElementTags = [
'ask-ai-icon',
'ask-ai-button',
'ask-ai-toolbar-button',
'ask-ai-panel',
'affine-edgeless-ai-chat',
'affine-ai-chat',
'ai-chat-block-message',
'ai-chat-block-messages',
'ai-scrollable-text-renderer',
'image-placeholder',
'chat-image',
'chat-images',
'user-info',
'generating-placeholder',
'ai-finish-tip',
'ai-panel-divider',
'ai-panel-answer',
'ai-panel-input',
'ai-panel-generating',
'ai-panel-error',
'ai-block-diff-options',
'affine-block-diff-playground',
'affine-block-diff-playground-modal',
'affine-ai-panel-widget',
'affine-edgeless-copilot-widget',
'affine-block-diff-widget-for-block',
'affine-block-diff-widget-for-page',
'edgeless-copilot-panel',
'edgeless-copilot-toolbar-entry',
'transcription-block',
] as const;
export type EditorEffectElementTag = (typeof editorEffectElementTags)[number];
export const appEffectElementTags = [
'chat-action-list',
'chat-copy-more',
'image-preview-grid',
'action-wrapper',
'action-image-to-text',
'action-image',
'action-make-real',
'action-mindmap',
'action-slides',
'action-text',
'ai-loading',
'ai-chat-content',
'ai-chat-toolbar',
'ai-session-history',
'ai-chat-messages',
'ai-chat-input',
'ai-chat-add-context',
'ai-chat-embedding-status-tooltip',
'ai-chat-composer',
'chat-panel-chips',
'ai-history-clear',
'chat-panel-add-popover',
'chat-input-preference',
'chat-panel-candidates-popover',
'chat-panel-doc-chip',
'chat-panel-file-chip',
'chat-panel-tag-chip',
'chat-panel-collection-chip',
'chat-panel-selected-chip',
'chat-panel-attachment-chip',
'chat-panel-chip',
'chat-assistant-avatar',
'chat-message-action',
'chat-message-assistant',
'chat-message-user',
'ai-chat-block-peek-view',
'date-time',
'chat-panel-split-view',
] as const;
export type AppEffectElementTag = (typeof appEffectElementTags)[number];
@@ -0,0 +1,74 @@
import { effects as tooltipEffects } from '@blocksuite/affine-components/tooltip';
import { ArtifactSkeleton } from '../components/ai-artifact-skeleton';
import { ChatContentImages } from '../components/ai-message-content/images';
import { ChatContentPureText } from '../components/ai-message-content/pure-text';
import { ChatContentRichText } from '../components/ai-message-content/rich-text';
import { ChatContentStreamObjects } from '../components/ai-message-content/stream-objects';
import { ArtifactPreviewPanel } from '../components/ai-tools/artifacts-preview-panel';
import {
CodeArtifactTool,
CodeHighlighter,
} from '../components/ai-tools/code-artifact';
import { DocComposeTool } from '../components/ai-tools/doc-compose';
import { DocEditTool } from '../components/ai-tools/doc-edit';
import { DocKeywordSearchResult } from '../components/ai-tools/doc-keyword-search-result';
import { DocReadResult } from '../components/ai-tools/doc-read-result';
import { DocSemanticSearchResult } from '../components/ai-tools/doc-semantic-search-result';
import { DocWriteTool } from '../components/ai-tools/doc-write';
import { SectionEditTool } from '../components/ai-tools/section-edit';
import { ToolCallCard } from '../components/ai-tools/tool-call-card';
import { ToolFailedCard } from '../components/ai-tools/tool-failed-card';
import { ToolResultCard } from '../components/ai-tools/tool-result-card';
import { WebCrawlTool } from '../components/ai-tools/web-crawl';
import { WebSearchTool } from '../components/ai-tools/web-search';
import { TextRenderer } from '../components/text-renderer';
import { AIErrorWrapper } from '../messages/error';
import { AISlidesRenderer } from '../messages/slides-renderer';
import { AIAnswerWrapper } from '../messages/wrapper';
import { registerMiniMindmapBlocks } from '../mini-mindmap';
import {
type SharedEffectElementTag,
sharedEffectElementTags,
} from './registry';
const sharedRegistries = new WeakSet<CustomElementRegistry>();
const sharedElements = {
'ai-error-wrapper': AIErrorWrapper,
'ai-slides-renderer': AISlidesRenderer,
'ai-answer-wrapper': AIAnswerWrapper,
'chat-content-images': ChatContentImages,
'chat-content-pure-text': ChatContentPureText,
'chat-content-rich-text': ChatContentRichText,
'chat-content-stream-objects': ChatContentStreamObjects,
'text-renderer': TextRenderer,
'tool-call-card': ToolCallCard,
'tool-result-card': ToolResultCard,
'tool-call-failed': ToolFailedCard,
'doc-semantic-search-result': DocSemanticSearchResult,
'doc-keyword-search-result': DocKeywordSearchResult,
'doc-read-result': DocReadResult,
'doc-write-tool': DocWriteTool,
'web-crawl-tool': WebCrawlTool,
'web-search-tool': WebSearchTool,
'section-edit-tool': SectionEditTool,
'doc-compose-tool': DocComposeTool,
'code-artifact-tool': CodeArtifactTool,
'code-highlighter': CodeHighlighter,
'artifact-preview-panel': ArtifactPreviewPanel,
'doc-edit-tool': DocEditTool,
'artifact-skeleton': ArtifactSkeleton,
} satisfies Record<SharedEffectElementTag, CustomElementConstructor>;
export function registerAISharedEffects() {
const registry = customElements;
if (sharedRegistries.has(registry)) return;
sharedRegistries.add(registry);
registerMiniMindmapBlocks();
tooltipEffects();
for (const tag of sharedEffectElementTags) {
customElements.define(tag, sharedElements[tag]);
}
}
@@ -1,6 +1,5 @@
export * from './_common/config';
export * from './actions';
export { ChatPanel } from './chat-panel';
export * from './entries';
export * from './entries/edgeless/actions-config';
export * from './messages';
@@ -1,10 +1,10 @@
import { registerAIEffects } from '@affine/core/blocksuite/ai/effects';
import { registerAIEditorEffects } from '@affine/core/blocksuite/ai/effects/editor';
import { editorEffects } from '@affine/core/blocksuite/editors';
import { registerTemplates } from './register-templates';
editorEffects();
registerAIEffects();
registerAIEditorEffects();
registerTemplates();
export * from './blocksuite-editor';