🚩(project) add more backend AI feature flags
The Blocknote AI feature is a bit flaky, we want to be able to disable it if to much issues arise, without having to do a new release. We add a bunch of feature flags to be able to disable the AI features if needed: - add AI_FEATURE_BLOCKNOTE_ENABLED, to display or not the feature powered by blocknote - add AI_FEATURE_LEGACY_ENABLED, to display or not the legacy AI features
This commit is contained in:
@@ -13,6 +13,8 @@ These are the environment variables you can set for the `impress-backend` contai
|
|||||||
| AI_BASE_URL | OpenAI compatible AI base url | |
|
| AI_BASE_URL | OpenAI compatible AI base url | |
|
||||||
| AI_BOT | Information to give to the frontend about the AI bot | { "name": "Docs AI", "color": "#8bc6ff" }
|
| AI_BOT | Information to give to the frontend about the AI bot | { "name": "Docs AI", "color": "#8bc6ff" }
|
||||||
| AI_FEATURE_ENABLED | Enable AI options | false |
|
| AI_FEATURE_ENABLED | Enable AI options | false |
|
||||||
|
| AI_FEATURE_BLOCKNOTE_ENABLED | Enable Blocknote AI options | false |
|
||||||
|
| AI_FEATURE_LEGACY_ENABLED | Enable legacyAI options | true |
|
||||||
| AI_MODEL | AI Model to use | |
|
| AI_MODEL | AI Model to use | |
|
||||||
| AI_VERCEL_SDK_VERSION | The vercel AI SDK version used | 6 |
|
| AI_VERCEL_SDK_VERSION | The vercel AI SDK version used | 6 |
|
||||||
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
|
| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true |
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ AI is disabled by default. To enable it, the following environment variables mus
|
|||||||
|
|
||||||
```env
|
```env
|
||||||
AI_FEATURE_ENABLED=true # is false by default
|
AI_FEATURE_ENABLED=true # is false by default
|
||||||
|
AI_FEATURE_BLOCKNOTE_ENABLED=true # is false by default
|
||||||
|
AI_FEATURE_LEGACY_ENABLED=true # is true by default, AI_FEATURE_ENABLED must be set to true to enable it
|
||||||
AI_BASE_URL=https://openaiendpoint.com
|
AI_BASE_URL=https://openaiendpoint.com
|
||||||
AI_API_KEY=<API key>
|
AI_API_KEY=<API key>
|
||||||
AI_MODEL=<model used> e.g. llama
|
AI_MODEL=<model used> e.g. llama
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ USER_RECONCILIATION_FORM_URL=http://localhost:3000
|
|||||||
|
|
||||||
# AI
|
# AI
|
||||||
AI_FEATURE_ENABLED=true
|
AI_FEATURE_ENABLED=true
|
||||||
|
AI_FEATURE_BLOCKNOTE_ENABLED=true
|
||||||
|
AI_FEATURE_LEGACY_ENABLED=true
|
||||||
AI_BASE_URL=https://openaiendpoint.com
|
AI_BASE_URL=https://openaiendpoint.com
|
||||||
AI_API_KEY=password
|
AI_API_KEY=password
|
||||||
AI_MODEL=llama
|
AI_MODEL=llama
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ OIDC_REDIRECT_ALLOWED_HOSTS=["https://${DOCS_HOST}"]
|
|||||||
|
|
||||||
# AI
|
# AI
|
||||||
#AI_FEATURE_ENABLED=true # is false by default
|
#AI_FEATURE_ENABLED=true # is false by default
|
||||||
|
#AI_FEATURE_BLOCKNOTE_ENABLED=true # is false by default
|
||||||
|
#AI_FEATURE_LEGACY_ENABLED=true # is true by default, AI_FEATURE_ENABLED must be set to true to enable it
|
||||||
#AI_BASE_URL=https://openaiendpoint.com
|
#AI_BASE_URL=https://openaiendpoint.com
|
||||||
#AI_API_KEY=<API key>
|
#AI_API_KEY=<API key>
|
||||||
#AI_MODEL=<model used> e.g. llama
|
#AI_MODEL=<model used> e.g. llama
|
||||||
|
|||||||
@@ -1852,7 +1852,7 @@ class DocumentViewSet(
|
|||||||
# Check permissions first
|
# Check permissions first
|
||||||
self.get_object()
|
self.get_object()
|
||||||
|
|
||||||
if not settings.AI_FEATURE_ENABLED:
|
if not settings.AI_FEATURE_ENABLED or not settings.AI_FEATURE_BLOCKNOTE_ENABLED:
|
||||||
raise ValidationError("AI feature is not enabled.")
|
raise ValidationError("AI feature is not enabled.")
|
||||||
|
|
||||||
ai_service = AIService()
|
ai_service = AIService()
|
||||||
@@ -2572,6 +2572,8 @@ class ConfigView(drf.views.APIView):
|
|||||||
array_settings = [
|
array_settings = [
|
||||||
"AI_BOT",
|
"AI_BOT",
|
||||||
"AI_FEATURE_ENABLED",
|
"AI_FEATURE_ENABLED",
|
||||||
|
"AI_FEATURE_BLOCKNOTE_ENABLED",
|
||||||
|
"AI_FEATURE_LEGACY_ENABLED",
|
||||||
"API_USERS_SEARCH_QUERY_MIN_LENGTH",
|
"API_USERS_SEARCH_QUERY_MIN_LENGTH",
|
||||||
"COLLABORATION_WS_URL",
|
"COLLABORATION_WS_URL",
|
||||||
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY",
|
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY",
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ def ai_settings(settings):
|
|||||||
settings.AI_BASE_URL = "http://localhost-ai:12345/"
|
settings.AI_BASE_URL = "http://localhost-ai:12345/"
|
||||||
settings.AI_API_KEY = "test-key"
|
settings.AI_API_KEY = "test-key"
|
||||||
settings.AI_FEATURE_ENABLED = True
|
settings.AI_FEATURE_ENABLED = True
|
||||||
|
settings.AI_FEATURE_BLOCKNOTE_ENABLED = True
|
||||||
|
settings.AI_FEATURE_LEGACY_ENABLED = True
|
||||||
settings.LANGFUSE_PUBLIC_KEY = None
|
settings.LANGFUSE_PUBLIC_KEY = None
|
||||||
settings.AI_VERCEL_SDK_VERSION = 6
|
settings.AI_VERCEL_SDK_VERSION = 6
|
||||||
|
|
||||||
@@ -239,9 +241,12 @@ def test_api_documents_ai_proxy_success(mock_stream, via, role, mock_user_teams)
|
|||||||
mock_stream.assert_called_once()
|
mock_stream.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_api_documents_ai_proxy_ai_feature_disabled(settings):
|
@pytest.mark.parametrize(
|
||||||
|
"setting_to_disable", ["AI_FEATURE_ENABLED", "AI_FEATURE_BLOCKNOTE_ENABLED"]
|
||||||
|
)
|
||||||
|
def test_api_documents_ai_proxy_ai_feature_disabled(settings, setting_to_disable):
|
||||||
"""When AI_FEATURE_ENABLED is False, the endpoint returns 400."""
|
"""When AI_FEATURE_ENABLED is False, the endpoint returns 400."""
|
||||||
settings.AI_FEATURE_ENABLED = False
|
setattr(settings, setting_to_disable, False)
|
||||||
|
|
||||||
user = factories.UserFactory()
|
user = factories.UserFactory()
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ pytestmark = pytest.mark.django_db
|
|||||||
@override_settings(
|
@override_settings(
|
||||||
AI_BOT={"name": "Test Bot", "color": "#000000"},
|
AI_BOT={"name": "Test Bot", "color": "#000000"},
|
||||||
AI_FEATURE_ENABLED=False,
|
AI_FEATURE_ENABLED=False,
|
||||||
|
AI_FEATURE_BLOCKNOTE_ENABLED=False,
|
||||||
|
AI_FEATURE_LEGACY_ENABLED=False,
|
||||||
API_USERS_SEARCH_QUERY_MIN_LENGTH=6,
|
API_USERS_SEARCH_QUERY_MIN_LENGTH=6,
|
||||||
COLLABORATION_WS_URL="http://testcollab/",
|
COLLABORATION_WS_URL="http://testcollab/",
|
||||||
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY=True,
|
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY=True,
|
||||||
@@ -47,6 +49,8 @@ def test_api_config(is_authenticated):
|
|||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"AI_BOT": {"name": "Test Bot", "color": "#000000"},
|
"AI_BOT": {"name": "Test Bot", "color": "#000000"},
|
||||||
"AI_FEATURE_ENABLED": False,
|
"AI_FEATURE_ENABLED": False,
|
||||||
|
"AI_FEATURE_BLOCKNOTE_ENABLED": False,
|
||||||
|
"AI_FEATURE_LEGACY_ENABLED": False,
|
||||||
"API_USERS_SEARCH_QUERY_MIN_LENGTH": 6,
|
"API_USERS_SEARCH_QUERY_MIN_LENGTH": 6,
|
||||||
"COLLABORATION_WS_URL": "http://testcollab/",
|
"COLLABORATION_WS_URL": "http://testcollab/",
|
||||||
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY": True,
|
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY": True,
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ def ai_settings(settings):
|
|||||||
settings.AI_BASE_URL = "http://example.com"
|
settings.AI_BASE_URL = "http://example.com"
|
||||||
settings.AI_API_KEY = "test-key"
|
settings.AI_API_KEY = "test-key"
|
||||||
settings.AI_FEATURE_ENABLED = True
|
settings.AI_FEATURE_ENABLED = True
|
||||||
|
settings.AI_FEATURE_BLOCKNOTE_ENABLED = True
|
||||||
|
settings.AI_FEATURE_LEGACY_ENABLED = True
|
||||||
settings.LANGFUSE_PUBLIC_KEY = None
|
settings.LANGFUSE_PUBLIC_KEY = None
|
||||||
settings.AI_VERCEL_SDK_VERSION = 6
|
settings.AI_VERCEL_SDK_VERSION = 6
|
||||||
|
|
||||||
|
|||||||
@@ -710,9 +710,22 @@ class Base(Configuration):
|
|||||||
"hour": 100,
|
"hour": 100,
|
||||||
"day": 500,
|
"day": 500,
|
||||||
}
|
}
|
||||||
|
# Master settings to enable AI features, if you set it to False,
|
||||||
|
# all AI features will be disabled even if the other settings are enabled.
|
||||||
AI_FEATURE_ENABLED = values.BooleanValue(
|
AI_FEATURE_ENABLED = values.BooleanValue(
|
||||||
default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None
|
default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None
|
||||||
)
|
)
|
||||||
|
# Far better UI but more flaky for the moment
|
||||||
|
# ⚠️ AGPL license, be sure to comply with the Blocknote license
|
||||||
|
# if you enable it (https://www.blocknotejs.org/)
|
||||||
|
AI_FEATURE_BLOCKNOTE_ENABLED = values.BooleanValue(
|
||||||
|
default=False, environ_name="AI_FEATURE_BLOCKNOTE_ENABLED", environ_prefix=None
|
||||||
|
)
|
||||||
|
# UI with less features but more stable
|
||||||
|
# MIT friendly license, you can enable it without worrying about the license
|
||||||
|
AI_FEATURE_LEGACY_ENABLED = values.BooleanValue(
|
||||||
|
default=True, environ_name="AI_FEATURE_LEGACY_ENABLED", environ_prefix=None
|
||||||
|
)
|
||||||
AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None)
|
AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None)
|
||||||
AI_VERCEL_SDK_VERSION = values.IntegerValue(
|
AI_VERCEL_SDK_VERSION = values.IntegerValue(
|
||||||
6, environ_name="AI_VERCEL_SDK_VERSION", environ_prefix=None
|
6, environ_name="AI_VERCEL_SDK_VERSION", environ_prefix=None
|
||||||
|
|||||||
@@ -76,26 +76,6 @@ test.describe('Config', () => {
|
|||||||
expect(webSocket.url()).toContain('ws://localhost:4444/collaboration/ws/');
|
expect(webSocket.url()).toContain('ws://localhost:4444/collaboration/ws/');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it checks the AI feature flag from config endpoint', async ({
|
|
||||||
page,
|
|
||||||
browserName,
|
|
||||||
}) => {
|
|
||||||
await overrideConfig(page, {
|
|
||||||
AI_FEATURE_ENABLED: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.goto('/');
|
|
||||||
|
|
||||||
await createDoc(page, 'doc-ai-feature', browserName, 1);
|
|
||||||
|
|
||||||
await page.locator('.bn-block-outer').last().fill('Anything');
|
|
||||||
await page.getByText('Anything').selectText();
|
|
||||||
expect(
|
|
||||||
await page.locator('button[data-test="convertMarkdown"]').count(),
|
|
||||||
).toBe(1);
|
|
||||||
await expect(page.getByRole('button', { name: 'Ask AI' })).toBeHidden();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it checks that Crisp is trying to init from config endpoint', async ({
|
test('it checks that Crisp is trying to init from config endpoint', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
339
src/frontend/apps/e2e/__tests__/app-impress/doc-ai.spec.ts
Normal file
339
src/frontend/apps/e2e/__tests__/app-impress/doc-ai.spec.ts
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
/* eslint-disable playwright/no-conditional-expect */
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createDoc,
|
||||||
|
mockedDocument,
|
||||||
|
overrideConfig,
|
||||||
|
verifyDocName,
|
||||||
|
} from './utils-common';
|
||||||
|
import {
|
||||||
|
mockAIResponse,
|
||||||
|
openSuggestionMenu,
|
||||||
|
writeInEditor,
|
||||||
|
} from './utils-editor';
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Doc AI feature', () => {
|
||||||
|
[
|
||||||
|
{
|
||||||
|
AI_FEATURE_ENABLED: false,
|
||||||
|
selector: 'Ask AI',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AI_FEATURE_ENABLED: true,
|
||||||
|
AI_FEATURE_BLOCKNOTE_ENABLED: false,
|
||||||
|
selector: 'Ask AI',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AI_FEATURE_ENABLED: true,
|
||||||
|
AI_FEATURE_LEGACY_ENABLED: false,
|
||||||
|
selector: 'AI',
|
||||||
|
},
|
||||||
|
].forEach((config) => {
|
||||||
|
test(`it checks the AI feature flag from config endpoint: ${JSON.stringify(config)}`, async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
await overrideConfig(page, config);
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
await createDoc(page, 'doc-ai-feature', browserName, 1);
|
||||||
|
|
||||||
|
await page.locator('.bn-block-outer').last().fill('Anything');
|
||||||
|
await page.getByText('Anything').selectText();
|
||||||
|
expect(
|
||||||
|
await page.locator('button[data-test="convertMarkdown"]').count(),
|
||||||
|
).toBe(1);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: config.selector, exact: true }),
|
||||||
|
).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it checks the AI feature and accepts changes', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
await overrideConfig(page, {
|
||||||
|
AI_BOT: {
|
||||||
|
name: 'Albert AI',
|
||||||
|
color: '#8bc6ff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await mockAIResponse(page);
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
await createDoc(page, 'doc-ai', browserName, 1);
|
||||||
|
|
||||||
|
await openSuggestionMenu({ page });
|
||||||
|
await page.getByText('Ask AI').click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('option', { name: 'Continue Writing' }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole('option', { name: 'Summarize' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
|
const editor = await writeInEditor({ page, text: 'Hello World' });
|
||||||
|
await editor.getByText('Hello World').selectText();
|
||||||
|
|
||||||
|
// Check from toolbar
|
||||||
|
await page.getByRole('button', { name: 'Ask AI' }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('option', { name: 'Improve Writing' }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('option', { name: 'Fix Spelling' }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole('option', { name: 'Translate' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('option', { name: 'Translate' }).click();
|
||||||
|
await page
|
||||||
|
.getByRole('textbox', { name: 'Ask anything...' })
|
||||||
|
.fill('Translate into french');
|
||||||
|
await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter');
|
||||||
|
await expect(editor.getByText('Albert AI')).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator('p.bn-mt-suggestion-menu-item-title')
|
||||||
|
.getByText('Accept')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||||
|
|
||||||
|
// Check Suggestion menu
|
||||||
|
await page.locator('.bn-block-outer').last().fill('/');
|
||||||
|
await expect(page.getByText('Write with AI')).toBeVisible();
|
||||||
|
|
||||||
|
// Reload the page to check that the AI change is still there
|
||||||
|
await page.goto(page.url());
|
||||||
|
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it reverts with the AI feature', async ({ page, browserName }) => {
|
||||||
|
await overrideConfig(page, {
|
||||||
|
AI_BOT: {
|
||||||
|
name: 'Albert AI',
|
||||||
|
color: '#8bc6ff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await mockAIResponse(page);
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
await createDoc(page, 'doc-ai', browserName, 1);
|
||||||
|
|
||||||
|
const editor = await writeInEditor({ page, text: 'Hello World' });
|
||||||
|
await editor.getByText('Hello World').selectText();
|
||||||
|
|
||||||
|
// Check from toolbar
|
||||||
|
await page.getByRole('button', { name: 'Ask AI' }).click();
|
||||||
|
|
||||||
|
await page.getByRole('option', { name: 'Translate' }).click();
|
||||||
|
await page
|
||||||
|
.getByRole('textbox', { name: 'Ask anything...' })
|
||||||
|
.fill('Translate into french');
|
||||||
|
await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter');
|
||||||
|
await expect(editor.getByText('Albert AI')).toBeVisible();
|
||||||
|
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator('p.bn-mt-suggestion-menu-item-title')
|
||||||
|
.getByText('Revert')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(editor.getByText('Hello World')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it checks the AI buttons feature legacy', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
await page.route(/.*\/ai-translate\//, async (route) => {
|
||||||
|
const request = route.request();
|
||||||
|
if (request.method().includes('POST')) {
|
||||||
|
await route.fulfill({
|
||||||
|
json: {
|
||||||
|
answer: 'Hallo Welt',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await route.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await createDoc(page, 'doc-ai', browserName, 1);
|
||||||
|
|
||||||
|
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||||
|
|
||||||
|
const editor = page.locator('.ProseMirror');
|
||||||
|
await editor.getByText('Hello').selectText();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Rephrase' }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Summarize' }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Language' }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('menuitem', { name: 'Language' }).hover();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'English', exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'French', exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'German', exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('menuitem', { name: 'German', exact: true }).click();
|
||||||
|
|
||||||
|
await expect(editor.getByText('Hallo Welt')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
{ 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 ({
|
||||||
|
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_transform,
|
||||||
|
ai_translate,
|
||||||
|
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',
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (!ai_transform && !ai_translate) {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'AI', exact: true }),
|
||||||
|
).toBeHidden();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
||||||
|
|
||||||
|
if (ai_transform) {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||||
|
).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||||
|
).toBeHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ai_translate) {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Language' }),
|
||||||
|
).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('menuitem', { name: 'Language' }),
|
||||||
|
).toBeHidden();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable playwright/no-conditional-expect */
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
@@ -7,16 +6,10 @@ import cs from 'convert-stream';
|
|||||||
import {
|
import {
|
||||||
createDoc,
|
createDoc,
|
||||||
goToGridDoc,
|
goToGridDoc,
|
||||||
mockedDocument,
|
|
||||||
overrideConfig,
|
overrideConfig,
|
||||||
verifyDocName,
|
verifyDocName,
|
||||||
} from './utils-common';
|
} from './utils-common';
|
||||||
import {
|
import { getEditor, openSuggestionMenu, writeInEditor } from './utils-editor';
|
||||||
getEditor,
|
|
||||||
mockAIResponse,
|
|
||||||
openSuggestionMenu,
|
|
||||||
writeInEditor,
|
|
||||||
} from './utils-editor';
|
|
||||||
import { connectOtherUserToDoc, updateShareLink } from './utils-share';
|
import { connectOtherUserToDoc, updateShareLink } from './utils-share';
|
||||||
import {
|
import {
|
||||||
createRootSubPage,
|
createRootSubPage,
|
||||||
@@ -390,359 +383,6 @@ test.describe('Doc Editor', () => {
|
|||||||
await expect(image).toHaveAttribute('aria-hidden', 'true');
|
await expect(image).toHaveAttribute('aria-hidden', 'true');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it checks the AI feature and accepts changes', async ({
|
|
||||||
page,
|
|
||||||
browserName,
|
|
||||||
}) => {
|
|
||||||
await overrideConfig(page, {
|
|
||||||
AI_BOT: {
|
|
||||||
name: 'Albert AI',
|
|
||||||
color: '#8bc6ff',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await mockAIResponse(page);
|
|
||||||
|
|
||||||
await page.goto('/');
|
|
||||||
|
|
||||||
await createDoc(page, 'doc-ai', browserName, 1);
|
|
||||||
|
|
||||||
await openSuggestionMenu({ page });
|
|
||||||
await page.getByText('Ask AI').click();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('option', { name: 'Continue Writing' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(page.getByRole('option', { name: 'Summarize' })).toBeVisible();
|
|
||||||
|
|
||||||
await page.keyboard.press('Escape');
|
|
||||||
|
|
||||||
const editor = await writeInEditor({ page, text: 'Hello World' });
|
|
||||||
await editor.getByText('Hello World').selectText();
|
|
||||||
|
|
||||||
// Check from toolbar
|
|
||||||
await page.getByRole('button', { name: 'Ask AI' }).click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('option', { name: 'Improve Writing' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('option', { name: 'Fix Spelling' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(page.getByRole('option', { name: 'Translate' })).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole('option', { name: 'Translate' }).click();
|
|
||||||
await page
|
|
||||||
.getByRole('textbox', { name: 'Ask anything...' })
|
|
||||||
.fill('Translate into french');
|
|
||||||
await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter');
|
|
||||||
await expect(editor.getByText('Albert AI')).toBeVisible();
|
|
||||||
await page
|
|
||||||
.locator('p.bn-mt-suggestion-menu-item-title')
|
|
||||||
.getByText('Accept')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
|
||||||
|
|
||||||
// Check Suggestion menu
|
|
||||||
await page.locator('.bn-block-outer').last().fill('/');
|
|
||||||
await expect(page.getByText('Write with AI')).toBeVisible();
|
|
||||||
|
|
||||||
// Reload the page to check that the AI change is still there
|
|
||||||
await page.goto(page.url());
|
|
||||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it reverts with the AI feature', async ({ page, browserName }) => {
|
|
||||||
await overrideConfig(page, {
|
|
||||||
AI_BOT: {
|
|
||||||
name: 'Albert AI',
|
|
||||||
color: '#8bc6ff',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await mockAIResponse(page);
|
|
||||||
|
|
||||||
await page.goto('/');
|
|
||||||
|
|
||||||
await createDoc(page, 'doc-ai', browserName, 1);
|
|
||||||
|
|
||||||
const editor = await writeInEditor({ page, text: 'Hello World' });
|
|
||||||
await editor.getByText('Hello World').selectText();
|
|
||||||
|
|
||||||
// Check from toolbar
|
|
||||||
await page.getByRole('button', { name: 'Ask AI' }).click();
|
|
||||||
|
|
||||||
await page.getByRole('option', { name: 'Translate' }).click();
|
|
||||||
await page
|
|
||||||
.getByRole('textbox', { name: 'Ask anything...' })
|
|
||||||
.fill('Translate into french');
|
|
||||||
await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter');
|
|
||||||
await expect(editor.getByText('Albert AI')).toBeVisible();
|
|
||||||
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
|
|
||||||
await page
|
|
||||||
.locator('p.bn-mt-suggestion-menu-item-title')
|
|
||||||
.getByText('Revert')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
await expect(editor.getByText('Hello World')).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.
|
|
||||||
*/
|
|
||||||
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')) {
|
|
||||||
await route.fulfill({
|
|
||||||
json: {
|
|
||||||
answer: 'Bonjour le monde',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await route.continue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await createDoc(page, 'doc-ai', browserName, 1);
|
|
||||||
|
|
||||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
|
||||||
|
|
||||||
const editor = page.locator('.ProseMirror');
|
|
||||||
await editor.getByText('Hello').selectText();
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'AI' }).click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Rephrase' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Summarize' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Language' }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Language' }).hover();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'English', exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'French', exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'German', exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'English', exact: true }).click();
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
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')) {
|
|
||||||
await route.fulfill({
|
|
||||||
json: {
|
|
||||||
answer: 'Bonjour le monde',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await route.continue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await createDoc(page, 'doc-ai', browserName, 1);
|
|
||||||
|
|
||||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
|
||||||
|
|
||||||
const editor = page.locator('.ProseMirror');
|
|
||||||
await editor.getByText('Hello').selectText();
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Rephrase' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Summarize' }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Language' }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Language' }).hover();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'English', exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'French', exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'German', exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'English', exact: true }).click();
|
|
||||||
|
|
||||||
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.skip(`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: [
|
|
||||||
{
|
|
||||||
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_transform,
|
|
||||||
ai_translate,
|
|
||||||
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',
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (!ai_transform && !ai_translate) {
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'AI', exact: true }),
|
|
||||||
).toBeHidden();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
|
||||||
|
|
||||||
if (ai_transform) {
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
|
||||||
).toBeVisible();
|
|
||||||
} else {
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
|
||||||
).toBeHidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ai_translate) {
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Language' }),
|
|
||||||
).toBeVisible();
|
|
||||||
} else {
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', { name: 'Language' }),
|
|
||||||
).toBeHidden();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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 }) => {
|
test('it downloads unsafe files', async ({ page, browserName }) => {
|
||||||
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export const CONFIG = {
|
|||||||
color: '#8bc6ff',
|
color: '#8bc6ff',
|
||||||
},
|
},
|
||||||
AI_FEATURE_ENABLED: true,
|
AI_FEATURE_ENABLED: true,
|
||||||
|
AI_FEATURE_BLOCKNOTE_ENABLED: true,
|
||||||
|
AI_FEATURE_LEGACY_ENABLED: true,
|
||||||
API_USERS_SEARCH_QUERY_MIN_LENGTH: 3,
|
API_USERS_SEARCH_QUERY_MIN_LENGTH: 3,
|
||||||
CRISP_WEBSITE_ID: null,
|
CRISP_WEBSITE_ID: null,
|
||||||
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
|
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ interface ThemeCustomization {
|
|||||||
export interface ConfigResponse {
|
export interface ConfigResponse {
|
||||||
AI_BOT: { name: string; color: string };
|
AI_BOT: { name: string; color: string };
|
||||||
AI_FEATURE_ENABLED?: boolean;
|
AI_FEATURE_ENABLED?: boolean;
|
||||||
|
AI_FEATURE_BLOCKNOTE_ENABLED?: boolean;
|
||||||
|
AI_FEATURE_LEGACY_ENABLED?: boolean;
|
||||||
API_USERS_SEARCH_QUERY_MIN_LENGTH?: number;
|
API_USERS_SEARCH_QUERY_MIN_LENGTH?: number;
|
||||||
COLLABORATION_WS_URL?: string;
|
COLLABORATION_WS_URL?: string;
|
||||||
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY?: boolean;
|
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY?: boolean;
|
||||||
|
|||||||
@@ -105,8 +105,12 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
|||||||
|
|
||||||
const { uploadFile, errorAttachment } = useUploadFile(doc.id);
|
const { uploadFile, errorAttachment } = useUploadFile(doc.id);
|
||||||
const conf = useConfig().data;
|
const conf = useConfig().data;
|
||||||
const aiAllowed = !!(conf?.AI_FEATURE_ENABLED && doc.abilities?.ai_proxy);
|
const aiBlockNoteAllowed = !!(
|
||||||
const aiExtension = useAI?.(doc.id, aiAllowed);
|
conf?.AI_FEATURE_ENABLED &&
|
||||||
|
conf?.AI_FEATURE_BLOCKNOTE_ENABLED &&
|
||||||
|
doc.abilities?.ai_proxy
|
||||||
|
);
|
||||||
|
const aiExtension = useAI?.(doc.id, aiBlockNoteAllowed);
|
||||||
|
|
||||||
const collabName = user?.full_name || user?.email;
|
const collabName = user?.full_name || user?.email;
|
||||||
const cursorName = collabName || t('Anonymous');
|
const cursorName = collabName || t('Anonymous');
|
||||||
@@ -268,11 +272,11 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
|||||||
comments={showComments}
|
comments={showComments}
|
||||||
aria-label={t('Document editor')}
|
aria-label={t('Document editor')}
|
||||||
>
|
>
|
||||||
{aiAllowed && AIMenuController && AIMenu && (
|
{aiBlockNoteAllowed && AIMenuController && AIMenu && (
|
||||||
<AIMenuController aiMenu={AIMenu} />
|
<AIMenuController aiMenu={AIMenu} />
|
||||||
)}
|
)}
|
||||||
<BlockNoteSuggestionMenu aiAllowed={aiAllowed} />
|
<BlockNoteSuggestionMenu aiAllowed={aiBlockNoteAllowed} />
|
||||||
<BlockNoteToolbar aiAllowed={aiAllowed} />
|
<BlockNoteToolbar aiAllowed={aiBlockNoteAllowed} />
|
||||||
</BlockNoteView>
|
</BlockNoteView>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => {
|
|||||||
{toolbarItems}
|
{toolbarItems}
|
||||||
|
|
||||||
{/* Extra button to do some AI powered actions - only if AIToolbarButton is not available because of MIT license */}
|
{/* Extra button to do some AI powered actions - only if AIToolbarButton is not available because of MIT license */}
|
||||||
{conf?.AI_FEATURE_ENABLED && !AIToolbarButton && (
|
{conf?.AI_FEATURE_ENABLED && conf?.AI_FEATURE_LEGACY_ENABLED && (
|
||||||
<AIGroupButton key="AIButton" />
|
<AIGroupButton key="AIButton" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -87,7 +87,12 @@ export const BlockNoteToolbar = ({ aiAllowed }: { aiAllowed: boolean }) => {
|
|||||||
<MarkdownButton key="customButton" />
|
<MarkdownButton key="customButton" />
|
||||||
</FormattingToolbar>
|
</FormattingToolbar>
|
||||||
);
|
);
|
||||||
}, [toolbarItems, aiAllowed, conf?.AI_FEATURE_ENABLED]);
|
}, [
|
||||||
|
toolbarItems,
|
||||||
|
aiAllowed,
|
||||||
|
conf?.AI_FEATURE_ENABLED,
|
||||||
|
conf?.AI_FEATURE_LEGACY_ENABLED,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user