🐛(frontend) conditionally render AI button in toolbar

Added a feature flag check to ensure the AIGroupButton is only rendered
when AI_FEATURE_ENABLED is explicitly set to "true". This prevents the
AI button from appearing when the feature is not configured or disabled.

Fixes #782

Signed-off-by: Matthias <matthias@universum.com>
This commit is contained in:
Matthias
2025-03-27 23:21:26 +01:00
committed by Anthony LC
parent fbe8a26dba
commit f2ed8e0ea1
9 changed files with 55 additions and 4 deletions

View File

@@ -25,6 +25,7 @@ and this project adheres to
## Fixed
- 🐛(frontend) conditionally render AI button only when feature is enabled #814
- 🐛(backend) compute ancestor_links in get_abilities if needed #725
- 🔒️(back) restrict access to document accesses #801

View File

@@ -50,6 +50,7 @@ OIDC_REDIRECT_ALLOWED_HOSTS=["http://localhost:8083", "http://localhost:3000"]
OIDC_AUTH_REQUEST_EXTRA_PARAMS={"acr_values": "eidas1"}
# AI
AI_FEATURE_ENABLED=true
AI_BASE_URL=https://openaiendpoint.com
AI_API_KEY=password
AI_MODEL=llama

View File

@@ -1684,6 +1684,7 @@ class ConfigView(drf.views.APIView):
Return a dictionary of public settings.
"""
array_settings = [
"AI_FEATURE_ENABLED",
"COLLABORATION_WS_URL",
"CRISP_WEBSITE_ID",
"ENVIRONMENT",

View File

@@ -49,4 +49,5 @@ def test_api_config(is_authenticated):
"MEDIA_BASE_URL": "http://testserver/",
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
"SENTRY_DSN": "https://sentry.test/123",
"AI_FEATURE_ENABLED": False,
}

View File

@@ -528,6 +528,9 @@ class Base(Configuration):
)
# AI service
AI_FEATURE_ENABLED = values.BooleanValue(
default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None
)
AI_API_KEY = values.Value(None, environ_name="AI_API_KEY", environ_prefix=None)
AI_BASE_URL = values.Value(None, environ_name="AI_BASE_URL", environ_prefix=None)
AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None)

View File

@@ -5,6 +5,7 @@ import { expect, test } from '@playwright/test';
import { createDoc, verifyDocName } from './common';
const config = {
AI_FEATURE_ENABLED: true,
CRISP_WEBSITE_ID: null,
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
ENVIRONMENT: 'development',
@@ -117,6 +118,38 @@ test.describe('Config', () => {
expect(webSocket.url()).toContain('ws://localhost:4444/collaboration/ws/');
});
test('it checks the AI feature flag from config endpoint', async ({
page,
browserName,
}) => {
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
...config,
AI_FEATURE_ENABLED: false,
},
});
} else {
await route.continue();
}
});
await page.goto('/');
await createDoc(page, 'doc-ai-feature', browserName, 1);
await page.locator('.bn-block-outer').last().fill('Anything');
await page.getByText('Anything').dblclick();
expect(
await page.locator('button[data-test="convertMarkdown"]').count(),
).toBe(1);
expect(await page.locator('button[data-test="ai-actions"]').count()).toBe(
0,
);
});
test('it checks that Crisp is trying to init from config endpoint', async ({
page,
}) => {

View File

@@ -338,6 +338,7 @@ test.describe('Doc Editor', () => {
].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 ({
page,
browserName,
}) => {
await mockedDocument(page, {
accesses: [
@@ -364,11 +365,17 @@ test.describe('Doc Editor', () => {
link_reach: 'public',
link_role: 'editor',
created_at: '2021-09-01T09:00:00Z',
title: '',
});
await goToGridDoc(page);
const [randomDoc] = await createDoc(
page,
'doc-editor-ai',
browserName,
1,
);
await verifyDocName(page, 'Mocked document');
await verifyDocName(page, randomDoc);
await page.locator('.bn-block-outer').last().fill('Hello World');

View File

@@ -14,6 +14,7 @@ interface ConfigResponse {
MEDIA_BASE_URL?: string;
POSTHOG_KEY?: PostHogConf;
SENTRY_DSN?: string;
AI_FEATURE_ENABLED?: boolean;
}
export const getConfig = async (): Promise<ConfigResponse> => {

View File

@@ -8,6 +8,8 @@ import {
import React, { JSX, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useConfig } from '@/core/config/api';
import { getQuoteFormattingToolbarItems } from '../custom-blocks';
import { AIGroupButton } from './AIButton';
@@ -20,6 +22,7 @@ export const BlockNoteToolbar = () => {
const [confirmOpen, setIsConfirmOpen] = useState(false);
const [onConfirm, setOnConfirm] = useState<() => void | Promise<void>>();
const { t } = useTranslation();
const { data: conf } = useConfig();
const toolbarItems = useMemo(() => {
const toolbarItems = getFormattingToolbarItems([
@@ -56,13 +59,13 @@ export const BlockNoteToolbar = () => {
{toolbarItems}
{/* Extra button to do some AI powered actions */}
<AIGroupButton key="AIButton" />
{conf?.AI_FEATURE_ENABLED && <AIGroupButton key="AIButton" />}
{/* Extra button to convert from markdown to json */}
<MarkdownButton key="customButton" />
</FormattingToolbar>
);
}, [toolbarItems]);
}, [toolbarItems, conf?.AI_FEATURE_ENABLED]);
return (
<>