diff --git a/docs/env.md b/docs/env.md index 186cfb30..214fb54e 100644 --- a/docs/env.md +++ b/docs/env.md @@ -11,6 +11,7 @@ These are the environment variables you can set for the `impress-backend` contai | AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated | | AI_API_KEY | AI key to be used for 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_FEATURE_ENABLED | Enable AI options | false | | AI_MODEL | AI Model to use | | | ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true | diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 63ffc96a..85875ceb 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -2349,7 +2349,9 @@ class ConfigView(drf.views.APIView): Return a dictionary of public settings. """ array_settings = [ + "AI_BOT", "AI_FEATURE_ENABLED", + "AI_MODEL", "COLLABORATION_WS_URL", "COLLABORATION_WS_NOT_CONNECTED_READY_ONLY", "CONVERSION_FILE_EXTENSIONS_ALLOWED", diff --git a/src/backend/core/tests/test_api_config.py b/src/backend/core/tests/test_api_config.py index ac3a9b30..587a4ad4 100644 --- a/src/backend/core/tests/test_api_config.py +++ b/src/backend/core/tests/test_api_config.py @@ -19,7 +19,9 @@ pytestmark = pytest.mark.django_db @override_settings( + AI_BOT={"name": "Test Bot", "color": "#000000"}, AI_FEATURE_ENABLED=False, + AI_MODEL="test-model", COLLABORATION_WS_URL="http://testcollab/", COLLABORATION_WS_NOT_CONNECTED_READY_ONLY=True, CRISP_WEBSITE_ID="123", @@ -43,6 +45,9 @@ def test_api_config(is_authenticated): response = client.get("/api/v1.0/config/") assert response.status_code == HTTP_200_OK assert response.json() == { + "AI_BOT": {"name": "Test Bot", "color": "#000000"}, + "AI_FEATURE_ENABLED": False, + "AI_MODEL": "test-model", "AI_FEATURE_ENABLED": False, "COLLABORATION_WS_URL": "http://testcollab/", "COLLABORATION_WS_NOT_CONNECTED_READY_ONLY": True, diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 75101603..d93ccf25 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -670,24 +670,32 @@ class Base(Configuration): default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None ) - # AI service - AI_FEATURE_ENABLED = values.BooleanValue( - default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None - ) - AI_API_KEY = SecretFileValue(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) + # AI settings AI_ALLOW_REACH_FROM = values.Value( choices=("public", "authenticated", "restricted"), default="authenticated", environ_name="AI_ALLOW_REACH_FROM", environ_prefix=None, ) + AI_API_KEY = SecretFileValue(None, environ_name="AI_API_KEY", environ_prefix=None) + AI_BASE_URL = values.Value(None, environ_name="AI_BASE_URL", environ_prefix=None) + AI_BOT = values.DictValue( + default={ + "name": _("Docs AI"), + "color": "#8bc6ff", + }, + environ_name="AI_BOT", + environ_prefix=None, + ) AI_DOCUMENT_RATE_THROTTLE_RATES = { "minute": 5, "hour": 100, "day": 500, } + AI_FEATURE_ENABLED = values.BooleanValue( + default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None + ) + AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None) AI_USER_RATE_THROTTLE_RATES = { "minute": 3, "hour": 50, 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 e4f78671..911e67db 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 @@ -390,6 +390,15 @@ test.describe('Doc Editor', () => { }); test('it checks the AI feature', async ({ page, browserName }) => { + await overrideConfig(page, { + AI_BOT: { + name: 'Albert AI', + color: '#8bc6ff', + }, + }); + + await page.goto('/'); + await page.route(/.*\/ai-proxy\//, async (route) => { const request = route.request(); if (request.method().includes('POST')) { @@ -474,7 +483,7 @@ test.describe('Doc Editor', () => { await page.getByRole('option', { name: 'Translate' }).click(); await page.getByPlaceholder('Ask AI anything…').fill('French'); await page.getByPlaceholder('Ask AI anything…').press('Enter'); - await expect(editor.getByText('Docs AI')).toBeVisible(); + await expect(editor.getByText('Albert AI')).toBeVisible(); await page .locator('p.bn-mt-suggestion-menu-item-title') .getByText('Accept') diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts index 6854e0b4..6c76ae70 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts @@ -4,7 +4,12 @@ export type BrowserName = 'chromium' | 'firefox' | 'webkit'; export const BROWSERS: BrowserName[] = ['chromium', 'webkit', 'firefox']; export const CONFIG = { + AI_BOT: { + name: 'Docs AI', + color: '#8bc6ff', + }, AI_FEATURE_ENABLED: true, + AI_MODEL: 'llama', CRISP_WEBSITE_ID: null, COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/', COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true, diff --git a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx index 6181ecab..9148f550 100644 --- a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx +++ b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx @@ -15,7 +15,9 @@ interface ThemeCustomization { } export interface ConfigResponse { + AI_BOT: { name: string; color: string }; AI_FEATURE_ENABLED?: boolean; + AI_MODEL?: string; COLLABORATION_WS_URL?: string; COLLABORATION_WS_NOT_CONNECTED_READY_ONLY?: boolean; CONVERSION_FILE_EXTENSIONS_ALLOWED: string[]; 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 b0f791e2..691c9039 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 @@ -3,6 +3,7 @@ import { createAIExtension, createBlockNoteAIClient } from '@blocknote/xl-ai'; import { useMemo } from 'react'; import { fetchAPI } from '@/api'; +import { useConfig } from '@/core'; import { Doc } from '@/docs/doc-management'; const client = createBlockNoteAIClient({ @@ -17,7 +18,13 @@ const client = createBlockNoteAIClient({ * Custom prompts can be invoked using the pattern !promptName in the AI input field. */ export const useAI = (docId: Doc['id']) => { + const conf = useConfig().data; + return useMemo(() => { + if (!conf?.AI_MODEL) { + return null; + } + const openai = createOpenAI({ ...client.getProviderSettings('openai'), fetch: (input, init) => { @@ -31,17 +38,14 @@ export const useAI = (docId: Doc['id']) => { }); }, }); - const model = openai.chat('neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8'); + const model = openai.chat(conf.AI_MODEL); const extension = createAIExtension({ stream: false, model, - agentCursor: { - name: 'Albert', - color: '#8bc6ff', - }, + agentCursor: conf?.AI_BOT, }); return extension; - }, [docId]); + }, [docId, conf?.AI_BOT, conf?.AI_MODEL]); }; 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 ba866b07..08154b37 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 @@ -252,7 +252,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { comments={showComments} aria-label={t('Document editor')} > - + {aiExtension && }