From a815fd6b9a2ea7b1699f88285b4164e080a14ff1 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Tue, 19 Dec 2023 05:13:28 +0000 Subject: [PATCH] feat(core): ai poc (#5317) --- packages/common/env/src/global.ts | 1 + .../frontend/core/.webpack/runtime-config.ts | 8 ++++ .../register-blocksuite-components.ts | 6 ++- .../workspace/detail-page/detail-page.tsx | 23 ++++------ .../detail-page/editor-sidebar/atoms.ts | 11 +++-- .../editor-sidebar/editor-sidebar.tsx | 6 +-- .../editor-sidebar/extensions/copilot.css.ts | 7 +++ .../editor-sidebar/extensions/copilot.tsx | 46 +++++++++++++++++++ .../detail-page/editor-sidebar/types.ts | 2 +- 9 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.css.ts create mode 100644 packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx diff --git a/packages/common/env/src/global.ts b/packages/common/env/src/global.ts index 87dc1b3f63..8709d98008 100644 --- a/packages/common/env/src/global.ts +++ b/packages/common/env/src/global.ts @@ -31,6 +31,7 @@ export const runtimeFlagsSchema = z.object({ enableEnhanceShareMode: z.boolean(), enablePayment: z.boolean(), enablePageHistory: z.boolean(), + enableCopilot: z.boolean(), // this is for the electron app serverUrlPrefix: z.string(), enableMoveDatabase: z.boolean(), diff --git a/packages/frontend/core/.webpack/runtime-config.ts b/packages/frontend/core/.webpack/runtime-config.ts index da056b8cff..c899a40a90 100644 --- a/packages/frontend/core/.webpack/runtime-config.ts +++ b/packages/frontend/core/.webpack/runtime-config.ts @@ -32,6 +32,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { enableEnhanceShareMode: false, enablePayment: true, enablePageHistory: false, + enableCopilot: false, serverUrlPrefix: 'https://insider.affine.pro', // Let insider be stable environment temporarily. editorFlags, appVersion: packageJson.version, @@ -42,6 +43,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { return { ...this.stable, enablePageHistory: false, + enableCopilot: false, serverUrlPrefix: 'https://insider.affine.pro', appBuildType: 'beta' as const, }; @@ -80,6 +82,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { enableEnhanceShareMode: false, enablePayment: true, enablePageHistory: true, + enableCopilot: true, serverUrlPrefix: 'https://affine.fail', editorFlags, appVersion: packageJson.version, @@ -153,6 +156,11 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig { : buildFlags.mode === 'development' ? true : currentBuildPreset.enablePageHistory, + enableCopilot: process.env.ENABLE_COPILOT + ? process.env.ENABLE_COPILOT === 'true' + : buildFlags.mode === 'development' + ? true + : currentBuildPreset.enableCopilot, }; if (buildFlags.mode === 'development') { diff --git a/packages/frontend/core/src/bootstrap/register-blocksuite-components.ts b/packages/frontend/core/src/bootstrap/register-blocksuite-components.ts index ce451b8d26..dbe4739f60 100644 --- a/packages/frontend/core/src/bootstrap/register-blocksuite-components.ts +++ b/packages/frontend/core/src/bootstrap/register-blocksuite-components.ts @@ -1,5 +1,7 @@ -import { registerTOCPanelComponents } from '@blocksuite/presets'; -import { registerFramePanelComponents } from '@blocksuite/presets'; +import { + registerFramePanelComponents, + registerTOCPanelComponents, +} from '@blocksuite/presets'; registerTOCPanelComponents(components => { for (const compName in components) { diff --git a/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx index b955b829f5..99216d5ad5 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx @@ -12,7 +12,7 @@ import type { Page, Workspace } from '@blocksuite/store'; import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta'; import { useWorkspaceStatus } from '@toeverything/hooks/use-workspace-status'; import { appSettingAtom, currentPageIdAtom } from '@toeverything/infra/atom'; -import { useAtomValue, useSetAtom } from 'jotai'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { memo, type ReactElement, @@ -43,7 +43,6 @@ import { EditorSidebar, editorSidebarOpenAtom, editorSidebarResizingAtom, - editorSidebarStateAtom, editorSidebarWidthAtom, } from './editor-sidebar'; @@ -64,17 +63,13 @@ const DetailPageLayout = ({ footer, sidebar, }: DetailPageLayoutProps): ReactElement => { - const sidebarState = useAtomValue(editorSidebarStateAtom); - const setSidebarWidth = useSetAtom(editorSidebarWidthAtom); + const [width, setWidth] = useAtom(editorSidebarWidthAtom); const { clientBorder } = useAtomValue(appSettingAtom); - const setResizing = useSetAtom(editorSidebarResizingAtom); - const setOpen = useSetAtom(editorSidebarOpenAtom); + const [resizing, setResizing] = useAtom(editorSidebarResizingAtom); + const [open, setOpen] = useAtom(editorSidebarOpenAtom); return ( -
+
{header} {main} @@ -85,13 +80,13 @@ const DetailPageLayout = ({ enableAnimation={false} resizeHandlePos="left" resizeHandleOffset={clientBorder ? 4 : 0} - width={sidebarState.width} + width={width} className={styles.sidebarContainer} onResizing={setResizing} - resizing={sidebarState.resizing} - open={sidebarState.isOpen} + resizing={resizing} + open={open} onOpen={setOpen} - onWidthChange={setSidebarWidth} + onWidthChange={setWidth} minWidth={MIN_SIDEBAR_WIDTH} maxWidth={MAX_SIDEBAR_WIDTH} > diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/atoms.ts b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/atoms.ts index 1685e5c088..f26511d258 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/atoms.ts +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/atoms.ts @@ -3,6 +3,7 @@ import { assertExists } from '@blocksuite/global/utils'; import { atom } from 'jotai'; import { selectAtom } from 'jotai/utils'; +import { copilotExtension } from './extensions/copilot'; import { framePanelExtension } from './extensions/frame'; import { outlineExtension } from './extensions/outline'; import type { EditorExtension, EditorExtensionName } from './types'; @@ -12,6 +13,7 @@ import type { EditorExtension, EditorExtensionName } from './types'; export const extensions: EditorExtension[] = [ outlineExtension, framePanelExtension, + copilotExtension, ]; export interface EditorSidebarState { @@ -30,8 +32,6 @@ const baseStateAtom = atom({ extensions: extensions, // todo: maybe should be dynamic (by feature flag?) }); -export const editorSidebarStateAtom = atom(get => get(baseStateAtom)); - const isOpenAtom = selectAtom(baseStateAtom, state => state.isOpen); const resizingAtom = selectAtom(baseStateAtom, state => state.resizing); const activeExtensionAtom = selectAtom( @@ -40,9 +40,10 @@ const activeExtensionAtom = selectAtom( ); const widthAtom = selectAtom(baseStateAtom, state => state.width); -export const editorExtensionsAtom = selectAtom( - baseStateAtom, - state => state.extensions +export const editorExtensionsAtom = selectAtom(baseStateAtom, state => + state.extensions.filter(e => { + return e.name !== 'copilot' || runtimeConfig.enableCopilot; + }) ); // get/set sidebar open state diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/editor-sidebar.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/editor-sidebar.tsx index 31279f810e..b611beddd9 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/editor-sidebar.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/editor-sidebar.tsx @@ -1,11 +1,11 @@ import { useAtomValue } from 'jotai'; -import { editorSidebarStateAtom } from './atoms'; +import { editorSidebarActiveExtensionAtom } from './atoms'; import * as styles from './editor-sidebar.css'; export const EditorSidebar = () => { - const sidebarState = useAtomValue(editorSidebarStateAtom); - const Component = sidebarState.activeExtension?.Component; + const activeExtension = useAtomValue(editorSidebarActiveExtensionAtom); + const Component = activeExtension?.Component; return
{Component ? : null}
; }; diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.css.ts b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.css.ts new file mode 100644 index 0000000000..0a2dbb4421 --- /dev/null +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.css.ts @@ -0,0 +1,7 @@ +import { style } from '@vanilla-extract/css'; + +export const root = style({ + display: 'flex', + height: '100%', + width: '100%', +}); diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx new file mode 100644 index 0000000000..db04dbce69 --- /dev/null +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/copilot.tsx @@ -0,0 +1,46 @@ +import { editorContainerAtom } from '@affine/component/block-suite-editor'; +import { assertExists } from '@blocksuite/global/utils'; +import { AiIcon } from '@blocksuite/icons'; +import { CopilotPanel } from '@blocksuite/presets'; +import { useAtom } from 'jotai'; +import { useCallback, useRef } from 'react'; + +import type { EditorExtension } from '../types'; +import * as styles from './outline.css'; + +// A wrapper for CopilotPanel +const EditorCopilotPanel = () => { + const copilotPanelRef = useRef(null); + const [editorContainer] = useAtom(editorContainerAtom); + + const onRefChange = useCallback((container: HTMLDivElement | null) => { + if (container) { + assertExists( + copilotPanelRef.current, + 'copilot panel should be initialized' + ); + container.append(copilotPanelRef.current); + } + }, []); + + if (!editorContainer) { + return; + } + + if (!copilotPanelRef.current) { + copilotPanelRef.current = new CopilotPanel(); + } + + if (editorContainer !== copilotPanelRef.current?.editor) { + (copilotPanelRef.current as CopilotPanel).editor = editorContainer; + // (copilotPanelRef.current as CopilotPanel).fitPadding = [20, 20, 20, 20]; + } + + return
; +}; + +export const copilotExtension: EditorExtension = { + name: 'copilot', + icon: , + Component: EditorCopilotPanel, +}; diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/types.ts b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/types.ts index ab7ab4d043..b992ec9f9f 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/types.ts +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/types.ts @@ -1,4 +1,4 @@ -export type EditorExtensionName = 'outline' | 'frame'; +export type EditorExtensionName = 'outline' | 'frame' | 'copilot'; export interface EditorExtension { name: EditorExtensionName;