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 2f572944..fa67c658 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 @@ -696,6 +696,53 @@ test.describe('Doc Editor', () => { }); }); + test(`it checks ai_proxy ability`, async ({ page, browserName }) => { + await mockedDocument(page, { + accesses: [ + { + id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg', + role: 'owner', + user: { + email: 'super@owner.com', + full_name: 'Super Owner', + }, + }, + ], + abilities: { + destroy: true, // Means owner + link_configuration: true, + ai_proxy: false, + accesses_manage: true, + accesses_view: true, + update: true, + partial_update: true, + retrieve: true, + }, + link_reach: 'restricted', + link_role: 'editor', + created_at: '2021-09-01T09:00:00Z', + title: '', + }); + + const [randomDoc] = await createDoc( + page, + 'doc-editor-ai-proxy', + browserName, + 1, + ); + + await verifyDocName(page, randomDoc); + + await page.locator('.bn-block-outer').last().fill('Hello World'); + + const editor = page.locator('.ProseMirror'); + await editor.getByText('Hello').selectText(); + + await expect(page.getByRole('button', { name: 'Ask AI' })).toBeHidden(); + await page.locator('.bn-block-outer').last().fill('/'); + await expect(page.getByText('Write with AI')).toBeHidden(); + }); + test('it downloads unsafe files', async ({ page, browserName }) => { const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/useAI.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/useAI.tsx index a43f4f6a..607b3d65 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/useAI.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AI/useAI.tsx @@ -6,10 +6,14 @@ import { fetchAPI } from '@/api'; import { useConfig } from '@/core'; import { Doc } from '@/docs/doc-management'; -export const useAI = (docId: Doc['id']) => { +export const useAI = (docId: Doc['id'], aiAllowed: boolean) => { const conf = useConfig().data; return useMemo(() => { + if (!aiAllowed) { + return null; + } + const extension = AIExtension({ transport: new DefaultChatTransport({ fetch: (input, init) => { @@ -27,5 +31,5 @@ export const useAI = (docId: Doc['id']) => { }); return extension; - }, [conf?.AI_BOT, docId]); + }, [conf?.AI_BOT, docId, aiAllowed]); }; 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 d072e3ea..28731c2d 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 @@ -20,6 +20,7 @@ import type { Awareness } from 'y-protocols/awareness'; import * as Y from 'yjs'; import { Box, TextErrors } from '@/components'; +import { useConfig } from '@/core'; import { useCunninghamTheme } from '@/cunningham'; import { Doc, useProviderStore } from '@/docs/doc-management'; import { avatarUrlFromName, useAuth } from '@/features/auth'; @@ -46,7 +47,6 @@ import { PdfBlock, UploadLoaderBlock, } from './custom-blocks'; - const AIMenu = BlockNoteAI?.AIMenu; const AIMenuController = BlockNoteAI?.AIMenuController; const useAI = BlockNoteAI?.useAI; @@ -104,7 +104,9 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { } const { uploadFile, errorAttachment } = useUploadFile(doc.id); - const aiExtension = useAI?.(doc.id); + const conf = useConfig().data; + const aiAllowed = !!(conf?.AI_FEATURE_ENABLED && doc.abilities?.ai_proxy); + const aiExtension = useAI?.(doc.id, aiAllowed); const collabName = user?.full_name || user?.email; const cursorName = collabName || t('Anonymous'); @@ -266,11 +268,11 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { comments={showComments} aria-label={t('Document editor')} > - {aiExtension && AIMenuController && AIMenu && ( + {aiAllowed && AIMenuController && AIMenu && ( )} - - + + ); @@ -337,7 +339,7 @@ export const BlockNoteReader = ({ slashMenu={false} comments={false} > - + ); 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 58f414cc..805377f6 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 @@ -11,8 +11,6 @@ import { import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useConfig } from '@/core'; - import { DocsBlockSchema, DocsInlineContentSchema, @@ -32,7 +30,11 @@ const getMultiColumnSlashMenuItems = const getAISlashMenuItems = BlockNoteAI?.getAISlashMenuItems; -export const BlockNoteSuggestionMenu = () => { +export const BlockNoteSuggestionMenu = ({ + aiAllowed, +}: { + aiAllowed: boolean; +}) => { const editor = useBlockNoteEditor< DocsBlockSchema, DocsInlineContentSchema, @@ -44,7 +46,6 @@ export const BlockNoteSuggestionMenu = () => { const fileBlocksName = dictionaryDate.slash_menu.file.group; const getInterlinkingMenuItems = useGetInterlinkingMenuItems(); - const { data: conf } = useConfig(); const getSlashMenuItems = useMemo(() => { // We insert it after the "Code Block" item to have the interlinking block displayed after the basic blocks @@ -56,9 +57,7 @@ export const BlockNoteSuggestionMenu = () => { getMultiColumnSlashMenuItems?.(editor) || [], getPdfReactSlashMenuItems(editor, t, fileBlocksName), getCalloutReactSlashMenuItems(editor, t, basicBlocksName), - conf?.AI_FEATURE_ENABLED && getAISlashMenuItems - ? getAISlashMenuItems(editor) - : [], + aiAllowed && getAISlashMenuItems ? getAISlashMenuItems(editor) : [], ); const index = combinedMenu.findIndex( @@ -80,7 +79,7 @@ export const BlockNoteSuggestionMenu = () => { t, fileBlocksName, basicBlocksName, - conf?.AI_FEATURE_ENABLED, + aiAllowed, getInterlinkingMenuItems, ]); 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 a3f29bdf..eec8b33a 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 @@ -21,7 +21,7 @@ import { ModalConfirmDownloadUnsafe } from './ModalConfirmDownloadUnsafe'; const AIToolbarButton = BlockNoteAI?.AIToolbarButton; -export const BlockNoteToolbar = () => { +export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => { const dict = useDictionary(); const [confirmOpen, setIsConfirmOpen] = useState(false); const [onConfirm, setOnConfirm] = useState<() => void | Promise>(); @@ -72,7 +72,7 @@ export const BlockNoteToolbar = () => { const formattingToolbar = useCallback(() => { return ( - {conf?.AI_FEATURE_ENABLED && AIToolbarButton && } + {aiAllowed && AIToolbarButton && } @@ -87,7 +87,7 @@ export const BlockNoteToolbar = () => { ); - }, [toolbarItems, conf?.AI_FEATURE_ENABLED]); + }, [toolbarItems, aiAllowed, conf?.AI_FEATURE_ENABLED]); return ( <>