mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-08 20:57:08 +08:00
feat(core): impl AI switch (#8018)
close PD-1658 https://github.com/user-attachments/assets/2f3d1d26-1879-4d95-b80c-7c0965cefbd0
This commit is contained in:
@@ -26,15 +26,15 @@ import { assertInstanceOf } from '@blocksuite/global/utils';
|
|||||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||||
|
|
||||||
import { buildAIPanelConfig } from './ai-panel';
|
import { buildAIPanelConfig } from './ai-panel';
|
||||||
import { setupCodeToolbarEntry } from './entries/code-toolbar/setup-code-toolbar';
|
import { setupCodeToolbarAIEntry } from './entries/code-toolbar/setup-code-toolbar';
|
||||||
import {
|
import {
|
||||||
setupEdgelessCopilot,
|
setupEdgelessCopilot,
|
||||||
setupEdgelessElementToolbarEntry,
|
setupEdgelessElementToolbarAIEntry,
|
||||||
} from './entries/edgeless/index';
|
} from './entries/edgeless/index';
|
||||||
import { setupFormatBarEntry } from './entries/format-bar/setup-format-bar';
|
import { setupFormatBarAIEntry } from './entries/format-bar/setup-format-bar';
|
||||||
import { setupImageToolbarEntry } from './entries/image-toolbar/setup-image-toolbar';
|
import { setupImageToolbarAIEntry } from './entries/image-toolbar/setup-image-toolbar';
|
||||||
import { setupSlashMenuEntry } from './entries/slash-menu/setup-slash-menu';
|
import { setupSlashMenuAIEntry } from './entries/slash-menu/setup-slash-menu';
|
||||||
import { setupSpaceEntry } from './entries/space/setup-space';
|
import { setupSpaceAIEntry } from './entries/space/setup-space';
|
||||||
|
|
||||||
class AIPageRootWatcher extends BlockServiceWatcher {
|
class AIPageRootWatcher extends BlockServiceWatcher {
|
||||||
static override readonly flavour = 'affine:page';
|
static override readonly flavour = 'affine:page';
|
||||||
@@ -45,15 +45,15 @@ class AIPageRootWatcher extends BlockServiceWatcher {
|
|||||||
if (view.component instanceof AffineAIPanelWidget) {
|
if (view.component instanceof AffineAIPanelWidget) {
|
||||||
view.component.style.width = '630px';
|
view.component.style.width = '630px';
|
||||||
view.component.config = buildAIPanelConfig(view.component);
|
view.component.config = buildAIPanelConfig(view.component);
|
||||||
setupSpaceEntry(view.component);
|
setupSpaceAIEntry(view.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.component instanceof AffineFormatBarWidget) {
|
if (view.component instanceof AffineFormatBarWidget) {
|
||||||
setupFormatBarEntry(view.component);
|
setupFormatBarAIEntry(view.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.component instanceof AffineSlashMenuWidget) {
|
if (view.component instanceof AffineSlashMenuWidget) {
|
||||||
setupSlashMenuEntry(view.component);
|
setupSlashMenuAIEntry(view.component);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ class AIEdgelessRootWatcher extends BlockServiceWatcher {
|
|||||||
if (view.component instanceof AffineAIPanelWidget) {
|
if (view.component instanceof AffineAIPanelWidget) {
|
||||||
view.component.style.width = '430px';
|
view.component.style.width = '430px';
|
||||||
view.component.config = buildAIPanelConfig(view.component);
|
view.component.config = buildAIPanelConfig(view.component);
|
||||||
setupSpaceEntry(view.component);
|
setupSpaceAIEntry(view.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.component instanceof EdgelessCopilotWidget) {
|
if (view.component instanceof EdgelessCopilotWidget) {
|
||||||
@@ -93,15 +93,15 @@ class AIEdgelessRootWatcher extends BlockServiceWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (view.component instanceof EdgelessElementToolbarWidget) {
|
if (view.component instanceof EdgelessElementToolbarWidget) {
|
||||||
setupEdgelessElementToolbarEntry(view.component);
|
setupEdgelessElementToolbarAIEntry(view.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.component instanceof AffineFormatBarWidget) {
|
if (view.component instanceof AffineFormatBarWidget) {
|
||||||
setupFormatBarEntry(view.component);
|
setupFormatBarAIEntry(view.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.component instanceof AffineSlashMenuWidget) {
|
if (view.component instanceof AffineSlashMenuWidget) {
|
||||||
setupSlashMenuEntry(view.component);
|
setupSlashMenuAIEntry(view.component);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ class AICodeBlockWatcher extends BlockServiceWatcher {
|
|||||||
const service = this.blockService;
|
const service = this.blockService;
|
||||||
service.specSlots.widgetConnected.on(view => {
|
service.specSlots.widgetConnected.on(view => {
|
||||||
if (view.component instanceof AffineCodeToolbarWidget) {
|
if (view.component instanceof AffineCodeToolbarWidget) {
|
||||||
setupCodeToolbarEntry(view.component);
|
setupCodeToolbarAIEntry(view.component);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,7 @@ class AIImageBlockWatcher extends BlockServiceWatcher {
|
|||||||
super.mounted();
|
super.mounted();
|
||||||
this.blockService.specSlots.widgetConnected.on(view => {
|
this.blockService.specSlots.widgetConnected.on(view => {
|
||||||
if (view.component instanceof AffineImageToolbarWidget) {
|
if (view.component instanceof AffineImageToolbarWidget) {
|
||||||
setupImageToolbarEntry(view.component);
|
setupImageToolbarAIEntry(view.component);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ const buttonOptions: AskAIButtonOptions = {
|
|||||||
import type { AskAIButtonOptions } from '../../_common/components/ask-ai-button';
|
import type { AskAIButtonOptions } from '../../_common/components/ask-ai-button';
|
||||||
import { buildAICodeItemGroups } from '../../_common/config';
|
import { buildAICodeItemGroups } from '../../_common/config';
|
||||||
|
|
||||||
export function setupCodeToolbarEntry(codeToolbar: AffineCodeToolbarWidget) {
|
export function setupCodeToolbarAIEntry(codeToolbar: AffineCodeToolbarWidget) {
|
||||||
codeToolbar.addPrimaryItems([
|
codeToolbar.addPrimaryItems([
|
||||||
{
|
{
|
||||||
type: 'ask-ai',
|
type: 'ask-ai',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function setupEdgelessCopilot(widget: EdgelessCopilotWidget) {
|
|||||||
widget.groups = edgelessActionGroups;
|
widget.groups = edgelessActionGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupEdgelessElementToolbarEntry(
|
export function setupEdgelessElementToolbarAIEntry(
|
||||||
widget: EdgelessElementToolbarWidget
|
widget: EdgelessElementToolbarWidget
|
||||||
) {
|
) {
|
||||||
widget.registerEntry({
|
widget.registerEntry({
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ import { html, type TemplateResult } from 'lit';
|
|||||||
|
|
||||||
import { AIItemGroups } from '../../_common/config';
|
import { AIItemGroups } from '../../_common/config';
|
||||||
|
|
||||||
export function setupFormatBarEntry(formatBar: AffineFormatBarWidget) {
|
export function setupFormatBarAIEntry(formatBar: AffineFormatBarWidget) {
|
||||||
toolbarDefaultConfig(formatBar);
|
toolbarDefaultConfig(formatBar);
|
||||||
formatBar.addRawConfigItems(
|
formatBar.addRawConfigItems(
|
||||||
[
|
[
|
||||||
|
|||||||
+3
-1
@@ -13,7 +13,9 @@ const buttonOptions: AskAIButtonOptions = {
|
|||||||
panelWidth: 300,
|
panelWidth: 300,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setupImageToolbarEntry(imageToolbar: AffineImageToolbarWidget) {
|
export function setupImageToolbarAIEntry(
|
||||||
|
imageToolbar: AffineImageToolbarWidget
|
||||||
|
) {
|
||||||
imageToolbar.addPrimaryItems(
|
imageToolbar.addPrimaryItems(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import { AIItemGroups } from '../../_common/config';
|
|||||||
import { handleInlineAskAIAction } from '../../actions/doc-handler';
|
import { handleInlineAskAIAction } from '../../actions/doc-handler';
|
||||||
import { AIProvider } from '../../provider';
|
import { AIProvider } from '../../provider';
|
||||||
|
|
||||||
export function setupSlashMenuEntry(slashMenu: AffineSlashMenuWidget) {
|
export function setupSlashMenuAIEntry(slashMenu: AffineSlashMenuWidget) {
|
||||||
const AIItems = AIItemGroups.map(group => group.items).flat();
|
const AIItems = AIItemGroups.map(group => group.items).flat();
|
||||||
|
|
||||||
const iconWrapper = (icon: AIItemConfig['icon']) => {
|
const iconWrapper = (icon: AIItemConfig['icon']) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { AffineAIPanelWidget } from '@blocksuite/blocks';
|
|||||||
import { handleInlineAskAIAction } from '../../actions/doc-handler';
|
import { handleInlineAskAIAction } from '../../actions/doc-handler';
|
||||||
import { AIProvider } from '../../provider';
|
import { AIProvider } from '../../provider';
|
||||||
|
|
||||||
export function setupSpaceEntry(panel: AffineAIPanelWidget) {
|
export function setupSpaceAIEntry(panel: AffineAIPanelWidget) {
|
||||||
panel.handleEvent('keyDown', ctx => {
|
panel.handleEvent('keyDown', ctx => {
|
||||||
const host = panel.host;
|
const host = panel.host;
|
||||||
const keyboardState = ctx.get('keyboardState');
|
const keyboardState = ctx.get('keyboardState');
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { AIOnboardingEdgeless } from './edgeless.dialog';
|
import { AIOnboardingEdgeless } from './edgeless.dialog';
|
||||||
@@ -28,19 +30,29 @@ const useDismiss = (key: AIOnboardingType) => {
|
|||||||
export const WorkspaceAIOnboarding = () => {
|
export const WorkspaceAIOnboarding = () => {
|
||||||
const [dismissGeneral] = useDismiss(AIOnboardingType.GENERAL);
|
const [dismissGeneral] = useDismiss(AIOnboardingType.GENERAL);
|
||||||
const [dismissLocal] = useDismiss(AIOnboardingType.LOCAL);
|
const [dismissLocal] = useDismiss(AIOnboardingType.LOCAL);
|
||||||
|
const editorSettingService = useService(EditorSettingService);
|
||||||
|
const enableAI = useLiveData(
|
||||||
|
editorSettingService.editorSetting.settings$.map(s => s.enableAI)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
{dismissGeneral ? null : <AIOnboardingGeneral />}
|
{!enableAI || dismissGeneral ? null : <AIOnboardingGeneral />}
|
||||||
{dismissLocal ? null : <AIOnboardingLocal />}
|
{!enableAI || dismissLocal ? null : <AIOnboardingLocal />}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PageAIOnboarding = () => {
|
export const PageAIOnboarding = () => {
|
||||||
const [dismissEdgeless] = useDismiss(AIOnboardingType.EDGELESS);
|
const [dismissEdgeless] = useDismiss(AIOnboardingType.EDGELESS);
|
||||||
|
const editorSettingService = useService(EditorSettingService);
|
||||||
|
const enableAI = useLiveData(
|
||||||
|
editorSettingService.editorSetting.settings$.map(s => s.enableAI)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>{dismissEdgeless ? null : <AIOnboardingEdgeless />}</Suspense>
|
<Suspense>
|
||||||
|
{!enableAI || dismissEdgeless ? null : <AIOnboardingEdgeless />}
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+42
-6
@@ -8,6 +8,7 @@ import {
|
|||||||
type RadioItem,
|
type RadioItem,
|
||||||
Scrollable,
|
Scrollable,
|
||||||
Switch,
|
Switch,
|
||||||
|
useConfirmModal,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import {
|
import {
|
||||||
SettingRow,
|
SettingRow,
|
||||||
@@ -393,16 +394,51 @@ export const SpellCheckSettings = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AISettings = () => {
|
||||||
|
const t = useI18n();
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
const { editorSettingService } = useServices({ EditorSettingService });
|
||||||
|
|
||||||
|
const settings = useLiveData(editorSettingService.editorSetting.settings$);
|
||||||
|
|
||||||
|
const onAIChange = useCallback(
|
||||||
|
(checked: boolean) => {
|
||||||
|
editorSettingService.editorSetting.set('enableAI', checked);
|
||||||
|
},
|
||||||
|
[editorSettingService]
|
||||||
|
);
|
||||||
|
const onToggleAI = useCallback(
|
||||||
|
(checked: boolean) => {
|
||||||
|
openConfirmModal({
|
||||||
|
title: checked ? 'Enable AI?' : 'Disable AI?',
|
||||||
|
description: `Are you sure you want to ${checked ? 'enable' : 'disable'} AI?`,
|
||||||
|
confirmText: checked ? 'Enable' : 'Disable',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
onConfirm: () => onAIChange(checked),
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: checked ? 'primary' : 'error',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[openConfirmModal, onAIChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingRow
|
||||||
|
name={t['com.affine.settings.editorSettings.general.ai.title']()}
|
||||||
|
desc={t['com.affine.settings.editorSettings.general.ai.description']()}
|
||||||
|
>
|
||||||
|
<Switch checked={settings.enableAI} onChange={onToggleAI} />
|
||||||
|
</SettingRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const General = () => {
|
export const General = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingWrapper title={t['com.affine.settings.editorSettings.general']()}>
|
<SettingWrapper title={t['com.affine.settings.editorSettings.general']()}>
|
||||||
<SettingRow
|
<AISettings />
|
||||||
name={t['com.affine.settings.editorSettings.general.ai.title']()}
|
|
||||||
desc={t['com.affine.settings.editorSettings.general.ai.description']()}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</SettingRow>
|
|
||||||
<FontFamilySettings />
|
<FontFamilySettings />
|
||||||
<CustomFontFamilySettings />
|
<CustomFontFamilySettings />
|
||||||
<NewDocDefaultModeSettings />
|
<NewDocDefaultModeSettings />
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
useFramework,
|
useFramework,
|
||||||
useLiveData,
|
useLiveData,
|
||||||
useService,
|
useService,
|
||||||
|
useServices,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
@@ -65,9 +66,13 @@ interface BlocksuiteEditorProps {
|
|||||||
|
|
||||||
const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => {
|
const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => {
|
||||||
const [reactToLit, portals] = useLitPortalFactory();
|
const [reactToLit, portals] = useLitPortalFactory();
|
||||||
const peekViewService = useService(PeekViewService);
|
const { peekViewService, docService, docsService, editorSettingService } =
|
||||||
const docService = useService(DocService);
|
useServices({
|
||||||
const docsService = useService(DocsService);
|
PeekViewService,
|
||||||
|
DocService,
|
||||||
|
DocsService,
|
||||||
|
EditorSettingService,
|
||||||
|
});
|
||||||
const framework = useFramework();
|
const framework = useFramework();
|
||||||
const referenceRenderer: ReferenceReactRenderer = useMemo(() => {
|
const referenceRenderer: ReferenceReactRenderer = useMemo(() => {
|
||||||
return function customReference(reference) {
|
return function customReference(reference) {
|
||||||
@@ -89,10 +94,12 @@ const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => {
|
|||||||
}, [mode, page.collection]);
|
}, [mode, page.collection]);
|
||||||
|
|
||||||
const specs = useMemo(() => {
|
const specs = useMemo(() => {
|
||||||
|
const enableAI =
|
||||||
|
editorSettingService.editorSetting.settings$.value.enableAI;
|
||||||
return mode === 'edgeless'
|
return mode === 'edgeless'
|
||||||
? createEdgelessModeSpecs(framework)
|
? createEdgelessModeSpecs(framework, enableAI)
|
||||||
: createPageModeSpecs(framework);
|
: createPageModeSpecs(framework, enableAI);
|
||||||
}, [mode, framework]);
|
}, [editorSettingService, mode, framework]);
|
||||||
|
|
||||||
const confirmModal = useConfirmModal();
|
const confirmModal = useConfirmModal();
|
||||||
const patchedSpecs = useMemo(() => {
|
const patchedSpecs = useMemo(() => {
|
||||||
|
|||||||
+17
-1
@@ -6,6 +6,7 @@ import {
|
|||||||
import type { ExtensionType } from '@blocksuite/block-std';
|
import type { ExtensionType } from '@blocksuite/block-std';
|
||||||
import {
|
import {
|
||||||
BookmarkBlockSpec,
|
BookmarkBlockSpec,
|
||||||
|
CodeBlockSpec,
|
||||||
DatabaseBlockSpec,
|
DatabaseBlockSpec,
|
||||||
DataViewBlockSpec,
|
DataViewBlockSpec,
|
||||||
DividerBlockSpec,
|
DividerBlockSpec,
|
||||||
@@ -16,12 +17,15 @@ import {
|
|||||||
EmbedLoomBlockSpec,
|
EmbedLoomBlockSpec,
|
||||||
EmbedSyncedDocBlockSpec,
|
EmbedSyncedDocBlockSpec,
|
||||||
EmbedYoutubeBlockSpec,
|
EmbedYoutubeBlockSpec,
|
||||||
|
ImageBlockSpec,
|
||||||
ListBlockSpec,
|
ListBlockSpec,
|
||||||
|
ParagraphBlockSpec,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
|
import { AIChatBlockSpec } from '@blocksuite/presets';
|
||||||
|
|
||||||
import { CustomAttachmentBlockSpec } from './custom/attachment-block';
|
import { CustomAttachmentBlockSpec } from './custom/attachment-block';
|
||||||
|
|
||||||
export const CommonBlockSpecs: ExtensionType[] = [
|
const CommonBlockSpecs: ExtensionType[] = [
|
||||||
ListBlockSpec,
|
ListBlockSpec,
|
||||||
DatabaseBlockSpec,
|
DatabaseBlockSpec,
|
||||||
DataViewBlockSpec,
|
DataViewBlockSpec,
|
||||||
@@ -36,7 +40,19 @@ export const CommonBlockSpecs: ExtensionType[] = [
|
|||||||
EmbedLinkedDocBlockSpec,
|
EmbedLinkedDocBlockSpec,
|
||||||
// special
|
// special
|
||||||
CustomAttachmentBlockSpec,
|
CustomAttachmentBlockSpec,
|
||||||
|
].flat();
|
||||||
|
|
||||||
|
export const DefaultBlockSpecs: ExtensionType[] = [
|
||||||
|
CodeBlockSpec,
|
||||||
|
ImageBlockSpec,
|
||||||
|
ParagraphBlockSpec,
|
||||||
|
...CommonBlockSpecs,
|
||||||
|
].flat();
|
||||||
|
|
||||||
|
export const AIBlockSpecs: ExtensionType[] = [
|
||||||
AICodeBlockSpec,
|
AICodeBlockSpec,
|
||||||
AIImageBlockSpec,
|
AIImageBlockSpec,
|
||||||
AIParagraphBlockSpec,
|
AIParagraphBlockSpec,
|
||||||
|
AIChatBlockSpec,
|
||||||
|
...CommonBlockSpecs,
|
||||||
].flat();
|
].flat();
|
||||||
|
|||||||
+8
-4
@@ -14,7 +14,9 @@ import {
|
|||||||
import type { RootService, TelemetryEventMap } from '@blocksuite/blocks';
|
import type { RootService, TelemetryEventMap } from '@blocksuite/blocks';
|
||||||
import {
|
import {
|
||||||
AffineCanvasTextFonts,
|
AffineCanvasTextFonts,
|
||||||
|
EdgelessRootBlockSpec,
|
||||||
EdgelessRootService,
|
EdgelessRootService,
|
||||||
|
PageRootBlockSpec,
|
||||||
PageRootService,
|
PageRootService,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
import { type FrameworkProvider } from '@toeverything/infra';
|
import { type FrameworkProvider } from '@toeverything/infra';
|
||||||
@@ -55,11 +57,12 @@ function withAffineRootService(Service: typeof PageRootService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createPageRootBlockSpec(
|
export function createPageRootBlockSpec(
|
||||||
framework: FrameworkProvider
|
framework: FrameworkProvider,
|
||||||
|
enableAI: boolean
|
||||||
): ExtensionType[] {
|
): ExtensionType[] {
|
||||||
const editorSettingService = framework.get(EditorSettingService);
|
const editorSettingService = framework.get(EditorSettingService);
|
||||||
return [
|
return [
|
||||||
...AIPageRootBlockSpec,
|
...(enableAI ? AIPageRootBlockSpec : PageRootBlockSpec),
|
||||||
{
|
{
|
||||||
setup: di => {
|
setup: di => {
|
||||||
di.override(
|
di.override(
|
||||||
@@ -79,11 +82,12 @@ export function createPageRootBlockSpec(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createEdgelessRootBlockSpec(
|
export function createEdgelessRootBlockSpec(
|
||||||
framework: FrameworkProvider
|
framework: FrameworkProvider,
|
||||||
|
enableAI: boolean
|
||||||
): ExtensionType[] {
|
): ExtensionType[] {
|
||||||
const editorSettingService = framework.get(EditorSettingService);
|
const editorSettingService = framework.get(EditorSettingService);
|
||||||
return [
|
return [
|
||||||
...AIEdgelessRootBlockSpec,
|
...(enableAI ? AIEdgelessRootBlockSpec : EdgelessRootBlockSpec),
|
||||||
{
|
{
|
||||||
setup: di => {
|
setup: di => {
|
||||||
di.override(
|
di.override(
|
||||||
|
|||||||
+5
-6
@@ -6,24 +6,23 @@ import {
|
|||||||
EdgelessTextBlockSpec,
|
EdgelessTextBlockSpec,
|
||||||
FrameBlockSpec,
|
FrameBlockSpec,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
import { AIChatBlockSpec } from '@blocksuite/presets';
|
|
||||||
import type { FrameworkProvider } from '@toeverything/infra';
|
import type { FrameworkProvider } from '@toeverything/infra';
|
||||||
|
|
||||||
import { CommonBlockSpecs } from './common';
|
import { AIBlockSpecs, DefaultBlockSpecs } from './common';
|
||||||
import { createEdgelessRootBlockSpec } from './custom/root-block';
|
import { createEdgelessRootBlockSpec } from './custom/root-block';
|
||||||
|
|
||||||
export function createEdgelessModeSpecs(
|
export function createEdgelessModeSpecs(
|
||||||
framework: FrameworkProvider
|
framework: FrameworkProvider,
|
||||||
|
enableAI: boolean
|
||||||
): ExtensionType[] {
|
): ExtensionType[] {
|
||||||
return [
|
return [
|
||||||
...CommonBlockSpecs,
|
...(enableAI ? AIBlockSpecs : DefaultBlockSpecs),
|
||||||
EdgelessSurfaceBlockSpec,
|
EdgelessSurfaceBlockSpec,
|
||||||
EdgelessSurfaceRefBlockSpec,
|
EdgelessSurfaceRefBlockSpec,
|
||||||
FrameBlockSpec,
|
FrameBlockSpec,
|
||||||
EdgelessTextBlockSpec,
|
EdgelessTextBlockSpec,
|
||||||
EdgelessNoteBlockSpec,
|
EdgelessNoteBlockSpec,
|
||||||
AIChatBlockSpec,
|
|
||||||
// special
|
// special
|
||||||
createEdgelessRootBlockSpec(framework),
|
createEdgelessRootBlockSpec(framework, enableAI),
|
||||||
].flat();
|
].flat();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,21 @@ import {
|
|||||||
PageSurfaceBlockSpec,
|
PageSurfaceBlockSpec,
|
||||||
PageSurfaceRefBlockSpec,
|
PageSurfaceRefBlockSpec,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
import { AIChatBlockSpec } from '@blocksuite/presets';
|
|
||||||
import { type FrameworkProvider } from '@toeverything/infra';
|
import { type FrameworkProvider } from '@toeverything/infra';
|
||||||
|
|
||||||
import { CommonBlockSpecs } from './common';
|
import { AIBlockSpecs, DefaultBlockSpecs } from './common';
|
||||||
import { createPageRootBlockSpec } from './custom/root-block';
|
import { createPageRootBlockSpec } from './custom/root-block';
|
||||||
|
|
||||||
export function createPageModeSpecs(
|
export function createPageModeSpecs(
|
||||||
framework: FrameworkProvider
|
framework: FrameworkProvider,
|
||||||
|
enableAI: boolean
|
||||||
): ExtensionType[] {
|
): ExtensionType[] {
|
||||||
return [
|
return [
|
||||||
...CommonBlockSpecs,
|
...(enableAI ? AIBlockSpecs : DefaultBlockSpecs),
|
||||||
PageSurfaceBlockSpec,
|
PageSurfaceBlockSpec,
|
||||||
PageSurfaceRefBlockSpec,
|
PageSurfaceRefBlockSpec,
|
||||||
NoteBlockSpec,
|
NoteBlockSpec,
|
||||||
AIChatBlockSpec,
|
|
||||||
// special
|
// special
|
||||||
createPageRootBlockSpec(framework),
|
createPageRootBlockSpec(framework, enableAI),
|
||||||
].flat();
|
].flat();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const fontStyleOptions = [
|
|||||||
}[];
|
}[];
|
||||||
|
|
||||||
const AffineEditorSettingSchema = z.object({
|
const AffineEditorSettingSchema = z.object({
|
||||||
|
enableAI: z.boolean().default(true),
|
||||||
fontFamily: z.enum(['Sans', 'Serif', 'Mono', 'Custom']).default('Sans'),
|
fontFamily: z.enum(['Sans', 'Serif', 'Mono', 'Custom']).default('Sans'),
|
||||||
customFontFamily: z.string().default(''),
|
customFontFamily: z.string().default(''),
|
||||||
newDocDefaultMode: z.enum(['edgeless', 'page']).default('page'),
|
newDocDefaultMode: z.enum(['edgeless', 'page']).default('page'),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-
|
|||||||
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
||||||
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
|
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
|
import { EditorSettingService } from '@affine/core/modules/editor-settting';
|
||||||
import { RecentDocsService } from '@affine/core/modules/quicksearch';
|
import { RecentDocsService } from '@affine/core/modules/quicksearch';
|
||||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||||
import type { PageRootService } from '@blocksuite/blocks';
|
import type { PageRootService } from '@blocksuite/blocks';
|
||||||
@@ -71,6 +72,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
docService,
|
docService,
|
||||||
workspaceService,
|
workspaceService,
|
||||||
globalContextService,
|
globalContextService,
|
||||||
|
editorSettingService,
|
||||||
} = useServices({
|
} = useServices({
|
||||||
WorkbenchService,
|
WorkbenchService,
|
||||||
ViewService,
|
ViewService,
|
||||||
@@ -78,6 +80,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
DocService,
|
DocService,
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
GlobalContextService,
|
GlobalContextService,
|
||||||
|
EditorSettingService,
|
||||||
});
|
});
|
||||||
const workbench = workbenchService.workbench;
|
const workbench = workbenchService.workbench;
|
||||||
const editor = editorService.editor;
|
const editor = editorService.editor;
|
||||||
@@ -307,9 +310,15 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
</div>
|
</div>
|
||||||
</ViewBody>
|
</ViewBody>
|
||||||
|
|
||||||
<ViewSidebarTab tabId="chat" icon={<AiIcon />} unmountOnInactive={false}>
|
{editorSettingService.editorSetting.settings$.value.enableAI && (
|
||||||
<EditorChatPanel editor={editorContainer} ref={chatPanelRef} />
|
<ViewSidebarTab
|
||||||
</ViewSidebarTab>
|
tabId="chat"
|
||||||
|
icon={<AiIcon />}
|
||||||
|
unmountOnInactive={false}
|
||||||
|
>
|
||||||
|
<EditorChatPanel editor={editorContainer} ref={chatPanelRef} />
|
||||||
|
</ViewSidebarTab>
|
||||||
|
)}
|
||||||
|
|
||||||
<ViewSidebarTab tabId="journal" icon={<TodayIcon />}>
|
<ViewSidebarTab tabId="journal" icon={<TodayIcon />}>
|
||||||
<EditorJournalPanel />
|
<EditorJournalPanel />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NotificationCenter, notify } from '@affine/component';
|
import { ConfirmModal, NotificationCenter, notify } from '@affine/component';
|
||||||
import { events } from '@affine/electron-api';
|
import { events } from '@affine/electron-api';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import type { SettingAtom } from '../atoms';
|
import type { SettingAtom } from '../atoms';
|
||||||
import { openSettingModalAtom, openSignOutModalAtom } from '../atoms';
|
import { openSettingModalAtom, openSignOutModalAtom } from '../atoms';
|
||||||
@@ -31,6 +31,7 @@ import { useAsyncCallback } from '../hooks/affine-async-hooks';
|
|||||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||||
import { AuthService } from '../modules/cloud/services/auth';
|
import { AuthService } from '../modules/cloud/services/auth';
|
||||||
import { CreateWorkspaceDialogProvider } from '../modules/create-workspace';
|
import { CreateWorkspaceDialogProvider } from '../modules/create-workspace';
|
||||||
|
import { EditorSettingService } from '../modules/editor-settting';
|
||||||
import { FindInPageModal } from '../modules/find-in-page/view/find-in-page-modal';
|
import { FindInPageModal } from '../modules/find-in-page/view/find-in-page-modal';
|
||||||
import { ImportTemplateDialogProvider } from '../modules/import-template';
|
import { ImportTemplateDialogProvider } from '../modules/import-template';
|
||||||
import { PeekViewManagerModal } from '../modules/peek-view';
|
import { PeekViewManagerModal } from '../modules/peek-view';
|
||||||
@@ -183,6 +184,40 @@ export const SignOutConfirmModal = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AIReloadConfirmModal = () => {
|
||||||
|
const editorSettingService = useService(EditorSettingService);
|
||||||
|
const enableAI = useLiveData(
|
||||||
|
editorSettingService.editorSetting.settings$
|
||||||
|
).enableAI;
|
||||||
|
const [aiState] = useState(enableAI);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(enableAI !== aiState);
|
||||||
|
}, [aiState, enableAI]);
|
||||||
|
|
||||||
|
const onConfirm = useCallback(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmModal
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
onConfirm={onConfirm}
|
||||||
|
confirmButtonOptions={{
|
||||||
|
variant: 'primary',
|
||||||
|
}}
|
||||||
|
title={'You need to reload the page'}
|
||||||
|
description={
|
||||||
|
'AI settings have been updated. Please reload the page to apply the changes.'
|
||||||
|
}
|
||||||
|
cancelText={'Cancel'}
|
||||||
|
confirmText={'Reload'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const AllWorkspaceModals = (): ReactElement => {
|
export const AllWorkspaceModals = (): ReactElement => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -191,6 +226,7 @@ export const AllWorkspaceModals = (): ReactElement => {
|
|||||||
<CreateWorkspaceDialogProvider />
|
<CreateWorkspaceDialogProvider />
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
<SignOutConfirmModal />
|
<SignOutConfirmModal />
|
||||||
|
<AIReloadConfirmModal />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1225,7 +1225,7 @@
|
|||||||
"com.affine.settings.auto-check-description": "If enabled, it will automatically check for new versions at regular intervals.",
|
"com.affine.settings.auto-check-description": "If enabled, it will automatically check for new versions at regular intervals.",
|
||||||
"com.affine.settings.auto-download-description": "If enabled, new versions will be automatically downloaded to the current device.",
|
"com.affine.settings.auto-download-description": "If enabled, new versions will be automatically downloaded to the current device.",
|
||||||
"com.affine.settings.editorSettings": "Editor",
|
"com.affine.settings.editorSettings": "Editor",
|
||||||
"com.affine.settings.editorSettings.title": "Editor Settings",
|
"com.affine.settings.editorSettings.title": "Editor settings",
|
||||||
"com.affine.settings.editorSettings.subtitle": "Configure your own editor.",
|
"com.affine.settings.editorSettings.subtitle": "Configure your own editor.",
|
||||||
"com.affine.settings.editorSettings.general": "General",
|
"com.affine.settings.editorSettings.general": "General",
|
||||||
"com.affine.settings.editorSettings.general.ai.title": "AFFiNE AI",
|
"com.affine.settings.editorSettings.general.ai.title": "AFFiNE AI",
|
||||||
|
|||||||
Reference in New Issue
Block a user