feat(core): completely remove the dependence on EditorHost (#13110)

Close [AI-260](https://linear.app/affine-design/issue/AI-260)

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

* **New Features**
* Added theme support to AI chat and message components, enabling
dynamic theming based on the current app theme.
* Introduced a reactive theme signal to the theme service for improved
theme handling.
* Integrated notification and theme services across various AI chat,
playground, and message components for consistent user experience.

* **Refactor**
* Simplified component APIs by removing dependencies on editor host and
related properties across AI chat, message, and tool components.
* Centralized and streamlined clipboard and markdown conversion
utilities, reducing external dependencies.
* Standardized the interface for context file addition and improved type
usage for better consistency.
* Reworked notification service to a class-based implementation for
improved encapsulation.
* Updated AI chat components to use injected notification and theme
services instead of host-based retrieval.

* **Bug Fixes**
* Improved reliability of copy and notification actions by decoupling
them from editor host dependencies.

* **Chores**
* Updated and cleaned up internal imports and removed unused properties
to enhance maintainability.
  * Added test IDs for sidebar close button to improve test reliability.
  * Updated test prompts in end-to-end tests for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wu Yue
2025-07-09 18:16:55 +08:00
committed by GitHub
parent dace1d1738
commit d10e5ee92f
48 changed files with 523 additions and 435 deletions
@@ -1,11 +1,10 @@
import { observeResize } from '@affine/component';
import { observeResize, useConfirmModal } from '@affine/component';
import { CopilotClient } from '@affine/core/blocksuite/ai';
import { AIChatContent } from '@affine/core/blocksuite/ai/components/ai-chat-content';
import { AIChatToolbar } from '@affine/core/blocksuite/ai/components/ai-chat-toolbar';
import { getCustomPageEditorBlockSpecs } from '@affine/core/blocksuite/ai/components/text-renderer';
import type { PromptKey } from '@affine/core/blocksuite/ai/provider/prompt';
import { NotificationServiceImpl } from '@affine/core/blocksuite/view-extensions/editor-view/notification-service';
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
import { getCollection } from '@affine/core/desktop/dialogs/setting/general-setting/editor/edgeless/docs';
import {
EventSourceService,
FetchService,
@@ -13,6 +12,7 @@ import {
} from '@affine/core/modules/cloud';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { AppThemeService } from '@affine/core/modules/theme';
import {
ViewBody,
ViewHeader,
@@ -21,8 +21,6 @@ import {
} from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import type { Doc, Store } from '@blocksuite/affine/store';
import { BlockStdScope, type EditorHost } from '@blocksuite/std';
import { type Signal, signal } from '@preact/signals-core';
import { useFramework, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -52,7 +50,6 @@ export const Component = () => {
const framework = useFramework();
const [isBodyProvided, setIsBodyProvided] = useState(false);
const [isHeaderProvided, setIsHeaderProvided] = useState(false);
const [host, setHost] = useState<EditorHost | null>(null);
const [chatContent, setChatContent] = useState<AIChatContent | null>(null);
const [chatTool, setChatTool] = useState<AIChatToolbar | null>(null);
const [currentSession, setCurrentSession] = useState<CopilotSession | null>(
@@ -132,28 +129,11 @@ export const Component = () => {
[chatContent, chatTool, client, isOpeningSession, workspaceId]
);
// create a temp doc/host for ai-chat-content
useEffect(() => {
let tempDoc: Doc | null = null;
const collection = getCollection();
const doc = collection.createDoc();
tempDoc = doc;
doc.load(() => {
const host = new BlockStdScope({
store: tempDoc?.getStore() as Store,
extensions: getCustomPageEditorBlockSpecs(),
}).render();
setHost(host);
});
return () => {
tempDoc?.dispose();
};
}, []);
const confirmModal = useConfirmModal();
// init or update ai-chat-content
useEffect(() => {
if (!isBodyProvided || !host) {
if (!isBodyProvided) {
return;
}
@@ -164,7 +144,6 @@ export const Component = () => {
}
content.session = currentSession;
content.host = host;
content.workspaceId = workspaceId;
content.docDisplayConfig = docDisplayConfig;
content.searchMenuConfig = searchMenuConfig;
@@ -174,6 +153,11 @@ export const Component = () => {
content.affineWorkspaceDialogService = framework.get(
WorkspaceDialogService
);
content.affineThemeService = framework.get(AppThemeService);
content.notificationService = new NotificationServiceImpl(
confirmModal.closeConfirmModal,
confirmModal.openConfirmModal
);
if (!chatContent) {
// initial values that won't change
@@ -190,12 +174,12 @@ export const Component = () => {
currentSession,
docDisplayConfig,
framework,
host,
isBodyProvided,
networkSearchConfig,
reasoningConfig,
searchMenuConfig,
workspaceId,
confirmModal,
]);
// init or update header ai-chat-toolbar
@@ -213,6 +197,10 @@ export const Component = () => {
tool.workspaceId = workspaceId;
tool.docDisplayConfig = docDisplayConfig;
tool.onOpenSession = onOpenSession;
tool.notificationService = new NotificationServiceImpl(
confirmModal.closeConfirmModal,
confirmModal.openConfirmModal
);
tool.onNewSession = () => {
if (!currentSession) return;
@@ -239,6 +227,7 @@ export const Component = () => {
onOpenSession,
togglePin,
workspaceId,
confirmModal,
]);
const onChatContainerRef = useCallback((node: HTMLDivElement) => {
@@ -1,8 +1,11 @@
import { useConfirmModal } from '@affine/component';
import { AIProvider, ChatPanel } from '@affine/core/blocksuite/ai';
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
import { NotificationServiceImpl } from '@affine/core/blocksuite/view-extensions/editor-view/notification-service';
import { useAIChatConfig } from '@affine/core/components/hooks/affine/use-ai-chat-config';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { AppThemeService } from '@affine/core/modules/theme';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewExtensionManagerIdentifier } from '@blocksuite/affine/ext-loader';
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
@@ -51,6 +54,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
reasoningConfig,
playgroundConfig,
} = useAIChatConfig();
const confirmModal = useConfirmModal();
useEffect(() => {
if (!editor || !editor.host) return;
@@ -87,6 +91,11 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
);
chatPanelRef.current.affineWorkbenchService =
framework.get(WorkbenchService);
chatPanelRef.current.affineThemeService = framework.get(AppThemeService);
chatPanelRef.current.notificationService = new NotificationServiceImpl(
confirmModal.closeConfirmModal,
confirmModal.openConfirmModal
);
containerRef.current?.append(chatPanelRef.current);
} else {
@@ -117,6 +126,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
searchMenuConfig,
reasoningConfig,
playgroundConfig,
confirmModal,
]);
const [autoResized, setAutoResized] = useState(false);