From b90e6271d96b12f3efd53fda5e8117feadde9077 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 10 Jun 2025 16:16:01 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=82(frontend)=20bind=20ai=5Fproxy=20ab?= =?UTF-8?q?ilities=20with=20AI=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bind ai_proxy abilities to the AI feature. If ai_proxy is false, the AI feature will not be available. --- .../__tests__/app-impress/doc-editor.spec.ts | 47 +++++++++++++++++++ .../docs/doc-editor/components/AI/useAI.tsx | 6 +-- .../doc-editor/components/BlockNoteEditor.tsx | 11 +++-- .../components/BlockNoteSuggestionMenu.tsx | 15 +++--- .../BlockNoteToolBar/BlockNoteToolbar.tsx | 9 ++-- 5 files changed, 67 insertions(+), 21 deletions(-) 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 a67e3143..2f1b1e8c 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 @@ -495,6 +495,53 @@ test.describe('Doc Editor', () => { await expect(page.getByText('Write with AI')).toBeVisible(); }); + 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 bac94447..d9200f4b 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 @@ -8,12 +8,12 @@ import { Doc } from '@/docs/doc-management'; import { usePromptAI } from './usePromptAI'; -export const useAI = (docId: Doc['id']) => { +export const useAI = (docId: Doc['id'], aiAllowed: boolean) => { const conf = useConfig().data; const promptBuilder = usePromptAI(); return useMemo(() => { - if (!conf?.AI_MODEL) { + if (!aiAllowed || !conf?.AI_MODEL) { return; } @@ -40,5 +40,5 @@ export const useAI = (docId: Doc['id']) => { }); return extension; - }, [conf, docId, promptBuilder]); + }, [aiAllowed, conf, docId, promptBuilder]); }; 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 5baa69a7..66fa8380 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'; @@ -104,7 +105,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'); @@ -261,11 +264,11 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { comments={showComments} aria-label={t('Document editor')} > - {aiExtension && AIMenuController && AIMenu && ( + {aiAllowed && 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 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 0b4d9c63..3a38a4b9 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 @@ -8,8 +8,6 @@ import { import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useConfig } from '@/core/config/api'; - import BlockNoteAI from '../AI'; import { CommentToolbarButton } from '../comments/CommentToolbarButton'; import { getCalloutFormattingToolbarItems } from '../custom-blocks'; @@ -20,12 +18,11 @@ 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>(); const { t } = useTranslation(); - const { data: conf } = useConfig(); const toolbarItems = useMemo(() => { let toolbarItems = getFormattingToolbarItems([ @@ -71,7 +68,7 @@ export const BlockNoteToolbar = () => { const formattingToolbar = useCallback(() => { return ( - {conf?.AI_FEATURE_ENABLED && AIToolbarButton && } + {aiAllowed && AIToolbarButton && } @@ -81,7 +78,7 @@ export const BlockNoteToolbar = () => { ); - }, [toolbarItems, conf?.AI_FEATURE_ENABLED]); + }, [toolbarItems, aiAllowed]); return ( <>