From 5c8fff01a58263d57c3241ec801d4f86c822a6c8 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 10 Jun 2025 12:36:06 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=84(frontend)=20remove=20AI=20feature?= =?UTF-8?q?=20when=20MIT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI feature is under AGPL license, so it is removed when the project is under MIT license. NEXT_PUBLIC_PUBLISH_AS_MIT manage this. --- .../__tests__/app-impress/doc-editor.spec.ts | 27 ++++++++++++++-- .../AIButton.tsx => AI/AIButtonMIT.tsx} | 0 .../docs/doc-editor/components/AI/index.ts | 31 +++++++++++++++++-- .../doc-editor/components/BlockNoteEditor.tsx | 31 +++++++++++++------ .../components/BlockNoteSuggestionMenu.tsx | 8 +++-- .../BlockNoteToolBar/BlockNoteToolbar.tsx | 14 ++++++--- 6 files changed, 88 insertions(+), 23 deletions(-) rename src/frontend/apps/impress/src/features/docs/doc-editor/components/{BlockNoteToolBar/AIButton.tsx => AI/AIButtonMIT.tsx} (100%) diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 454f446d..2f572944 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -487,7 +487,16 @@ test.describe('Doc Editor', () => { await expect(editor.getByText('Hello World')).toBeVisible(); }); - test('it checks the AI buttons feature', async ({ page, browserName }) => { + /** + * We have to skip this test for the CI for now, we cannot assert + * it because of `process.env.NEXT_PUBLIC_PUBLISH_AS_MIT` that is set + * at build time. + * It can be interesting to keep it for local tests. + */ + test.skip('it checks the AI buttons feature', async ({ + page, + browserName, + }) => { await page.route(/.*\/ai-translate\//, async (route) => { const request = route.request(); if (request.method().includes('POST')) { @@ -540,7 +549,13 @@ test.describe('Doc Editor', () => { await expect(editor.getByText('Bonjour le monde')).toBeVisible(); }); - test('it checks the AI buttons', async ({ page, browserName }) => { + /** + * We have to skip this test for the CI for now, we cannot assert + * it because of `process.env.NEXT_PUBLIC_PUBLISH_AS_MIT` that is set + * at build time. + * It can be interesting to keep it for local tests. + */ + test.skip('it checks the AI buttons', async ({ page, browserName }) => { await page.route(/.*\/ai-translate\//, async (route) => { const request = route.request(); if (request.method().includes('POST')) { @@ -593,12 +608,18 @@ test.describe('Doc Editor', () => { await expect(editor.getByText('Bonjour le monde')).toBeVisible(); }); + /** + * We have to skip this test for the CI for now, we cannot assert + * it because of `process.env.NEXT_PUBLIC_PUBLISH_AS_MIT` that is set + * at build time. + * It can be interesting to keep it for local tests. + */ [ { ai_transform: false, ai_translate: false }, { ai_transform: true, ai_translate: false }, { ai_transform: false, ai_translate: true }, ].forEach(({ ai_transform, ai_translate }) => { - test(`it checks AI buttons when can transform is at "${ai_transform}" and can translate is at "${ai_translate}"`, async ({ + test.skip(`it checks AI buttons when can transform is at "${ai_transform}" and can translate is at "${ai_translate}"`, async ({ page, browserName, }) => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/AIButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/AIButtonMIT.tsx similarity index 100% rename from src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/AIButton.tsx rename to src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/AIButtonMIT.tsx diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/index.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/index.ts index 3d701497..fb278b17 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/index.ts @@ -1,3 +1,28 @@ -export * from './AIMenu'; -export * from './AIToolbarButton'; -export * from './useAI'; +/** + * To import AI modules you must import from the index file. + * This is to ensure that the AI modules are only loaded when + * the application is not published as MIT. + */ +import * as XLAI from '@blocknote/xl-ai'; +import * as localesAI from '@blocknote/xl-ai/locales'; + +import * as AIMenu from './AIMenu'; +import * as AIToolbarButton from './AIToolbarButton'; +import * as useAI from './useAI'; + +let modulesAI = undefined; +if (process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false') { + modulesAI = { + ...XLAI, + ...AIToolbarButton, + ...AIMenu, + localesAI: localesAI, + ...useAI, + }; +} + +type ModulesAI = typeof XLAI & + typeof AIToolbarButton & + typeof AIMenu & { localesAI: typeof localesAI } & typeof useAI; + +export default modulesAI as ModulesAI; diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index 8c864130..d072e3ea 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -12,9 +12,6 @@ import * as locales from '@blocknote/core/locales'; import { BlockNoteView } from '@blocknote/mantine'; import '@blocknote/mantine/style.css'; import { useCreateBlockNote } from '@blocknote/react'; -import { AIMenuController } from '@blocknote/xl-ai'; -import { en as aiEn } from '@blocknote/xl-ai/locales'; -import '@blocknote/xl-ai/style.css'; import { HocuspocusProvider } from '@hocuspocus/provider'; import { useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; @@ -39,7 +36,7 @@ import { cssEditor } from '../styles'; import { DocsBlockNoteEditor } from '../types'; import { randomColor } from '../utils'; -import { AIMenu, useAI } from './AI'; +import BlockNoteAI from './AI'; import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu'; import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar'; import { cssComments, useComments } from './comments/'; @@ -49,6 +46,11 @@ import { PdfBlock, UploadLoaderBlock, } from './custom-blocks'; + +const AIMenu = BlockNoteAI?.AIMenu; +const AIMenuController = BlockNoteAI?.AIMenuController; +const useAI = BlockNoteAI?.useAI; +const localesAI = BlockNoteAI?.localesAI; import { InterlinkingLinkInlineContent, InterlinkingSearchInlineContent, @@ -87,7 +89,6 @@ interface BlockNoteEditorProps { export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { const { user } = useAuth(); const { setEditor } = useEditorStore(); - const { t } = useTranslation(); const { themeTokens } = useCunninghamTheme(); const { isSynced: isConnectedToCollabServer } = useProviderStore(); const refEditorContainer = useRef(null); @@ -96,14 +97,14 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { const showComments = canSeeComment; useSaveDoc(doc.id, provider.document, isConnectedToCollabServer); - const { i18n } = useTranslation(); + const { i18n, t } = useTranslation(); let lang = i18n.resolvedLanguage; if (!lang || !(lang in locales)) { lang = 'en'; } const { uploadFile, errorAttachment } = useUploadFile(doc.id); - const aiExtension = useAI(doc.id); + const aiExtension = useAI?.(doc.id); const collabName = user?.full_name || user?.email; const cursorName = collabName || t('Anonymous'); @@ -173,7 +174,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { ...(multiColumnLocales && { multi_column: multiColumnLocales[lang as keyof typeof multiColumnLocales], - ai: aiEn, + ai: localesAI?.[lang as keyof typeof localesAI], }), }, pasteHandler: ({ event, defaultPasteHandler }) => { @@ -214,7 +215,15 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { uploadFile, schema: blockNoteSchema, }, - [cursorName, lang, provider, uploadFile, threadStore, resolveUsers], + [ + aiExtension, + cursorName, + lang, + provider, + uploadFile, + threadStore, + resolveUsers, + ], ); useHeadings(editor); @@ -257,7 +266,9 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { comments={showComments} aria-label={t('Document editor')} > - {aiExtension && } + {aiExtension && AIMenuController && AIMenu && ( + + )} diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx index 35d60549..58f414cc 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx @@ -8,7 +8,6 @@ import { useBlockNoteEditor, useDictionary, } from '@blocknote/react'; -import { getAISlashMenuItems } from '@blocknote/xl-ai'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,6 +19,7 @@ import { DocsStyleSchema, } from '../types'; +import BlockNoteAI from './AI'; import { getCalloutReactSlashMenuItems, getPdfReactSlashMenuItems, @@ -30,6 +30,8 @@ import XLMultiColumn from './xl-multi-column'; const getMultiColumnSlashMenuItems = XLMultiColumn?.getMultiColumnSlashMenuItems; +const getAISlashMenuItems = BlockNoteAI?.getAISlashMenuItems; + export const BlockNoteSuggestionMenu = () => { const editor = useBlockNoteEditor< DocsBlockSchema, @@ -54,7 +56,9 @@ export const BlockNoteSuggestionMenu = () => { getMultiColumnSlashMenuItems?.(editor) || [], getPdfReactSlashMenuItems(editor, t, fileBlocksName), getCalloutReactSlashMenuItems(editor, t, basicBlocksName), - conf?.AI_FEATURE_ENABLED ? getAISlashMenuItems(editor) : [], + conf?.AI_FEATURE_ENABLED && getAISlashMenuItems + ? getAISlashMenuItems(editor) + : [], ); const index = combinedMenu.findIndex( diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx index a8f1916b..a3f29bdf 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx @@ -10,15 +10,17 @@ import { useTranslation } from 'react-i18next'; import { useConfig } from '@/core/config/api'; -import { AIToolbarButton } from '../AI/'; +import BlockNoteAI from '../AI/'; +import { AIGroupButton } from '../AI/AIButtonMIT'; import { CommentToolbarButton } from '../comments/CommentToolbarButton'; import { getCalloutFormattingToolbarItems } from '../custom-blocks'; -import { AIGroupButton } from './AIButton'; import { FileDownloadButton } from './FileDownloadButton'; import { MarkdownButton } from './MarkdownButton'; import { ModalConfirmDownloadUnsafe } from './ModalConfirmDownloadUnsafe'; +const AIToolbarButton = BlockNoteAI?.AIToolbarButton; + export const BlockNoteToolbar = () => { const dict = useDictionary(); const [confirmOpen, setIsConfirmOpen] = useState(false); @@ -70,14 +72,16 @@ export const BlockNoteToolbar = () => { const formattingToolbar = useCallback(() => { return ( - {conf?.AI_FEATURE_ENABLED && } + {conf?.AI_FEATURE_ENABLED && AIToolbarButton && } {toolbarItems} - {/* Extra button to do some AI powered actions */} - {conf?.AI_FEATURE_ENABLED && } + {/* Extra button to do some AI powered actions - only if AIToolbarButton is not available because of MIT license */} + {conf?.AI_FEATURE_ENABLED && !AIToolbarButton && ( + + )} {/* Extra button to convert from markdown to json */}