🛂(frontend) bind ai_proxy abilities with AI feature

Bind ai_proxy abilities to the AI feature.
If ai_proxy is false, the AI feature will
not be available.
This commit is contained in:
Anthony LC
2026-01-27 17:53:19 +01:00
parent 980f882f2f
commit b90e6271d9
5 changed files with 67 additions and 21 deletions
@@ -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: '[email protected]',
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);
@@ -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]);
};
@@ -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 && (
<AIMenuController aiMenu={AIMenu} />
)}
<BlockNoteSuggestionMenu />
<BlockNoteToolbar />
<BlockNoteSuggestionMenu aiAllowed={aiAllowed} />
<BlockNoteToolbar aiAllowed={aiAllowed} />
</BlockNoteView>
</Box>
);
@@ -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,
]);
@@ -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<void>>();
const { t } = useTranslation();
const { data: conf } = useConfig();
const toolbarItems = useMemo(() => {
let toolbarItems = getFormattingToolbarItems([
@@ -71,7 +68,7 @@ export const BlockNoteToolbar = () => {
const formattingToolbar = useCallback(() => {
return (
<FormattingToolbar>
{conf?.AI_FEATURE_ENABLED && AIToolbarButton && <AIToolbarButton />}
{aiAllowed && AIToolbarButton && <AIToolbarButton />}
<CommentToolbarButton />
@@ -81,7 +78,7 @@ export const BlockNoteToolbar = () => {
<MarkdownButton key="customButton" />
</FormattingToolbar>
);
}, [toolbarItems, conf?.AI_FEATURE_ENABLED]);
}, [toolbarItems, aiAllowed]);
return (
<>