Skip to content

Commit 5dc43cb

Browse files
AntoLCsampaccoud
authored andcommitted
✨(frontend) add ai blocknote feature
Add AI button to the editor toolbar. We can use AI to generate content with our editor. A list of predefined actions are available to use.
1 parent 9abf688 commit 5dc43cb

File tree

11 files changed

+584
-4
lines changed

11 files changed

+584
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to
1111

1212
## Added
1313

14+
- ✨AI to doc editor #250
15+
- ✨(backend) allow uploading more types of attachments #309
1416
- ✨(frontend) add buttons to copy document to clipboard as HTML/Markdown #300
1517

1618
## Changed
@@ -22,9 +24,6 @@ and this project adheres to
2224
## Fixed
2325

2426
- 🐛(frontend) invalidate queries after removing user #336
25-
26-
## Fixed
27-
2827
- 🐛(backend) Fix dysfunctional permissions on document create #329
2928

3029
## [1.5.1] - 2024-10-10
@@ -37,7 +36,6 @@ and this project adheres to
3736

3837
## Added
3938

40-
- ✨(backend) allow uploading more types of attachments #309
4139
- ✨(backend) add name fields to the user synchronized with OIDC #301
4240
- ✨(ci) add security scan #291
4341
- ♻️(frontend) Add versions #277

src/frontend/apps/e2e/__tests__/app-impress/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export const createDoc = async (
5151

5252
await page.locator('.c__modal__backdrop').click({
5353
position: { x: 0, y: 0 },
54+
force: true,
5455
});
5556

5657
await expect(

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,57 @@ test.describe('Doc Editor', () => {
181181
/http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/,
182182
);
183183
});
184+
185+
test('it checks the AI buttons', async ({ page, browserName }) => {
186+
await page.route(/.*\/ai-translate\//, async (route) => {
187+
const request = route.request();
188+
if (request.method().includes('POST')) {
189+
await route.fulfill({
190+
json: {
191+
answer: 'Bonjour le monde',
192+
},
193+
});
194+
} else {
195+
await route.continue();
196+
}
197+
});
198+
199+
await createDoc(page, 'doc-ai', browserName, 1);
200+
201+
await page.locator('.bn-block-outer').last().fill('Hello World');
202+
203+
const editor = page.locator('.ProseMirror');
204+
await editor.getByText('Hello').dblclick();
205+
206+
await page.getByRole('button', { name: 'AI' }).click();
207+
208+
await expect(
209+
page.getByRole('menuitem', { name: 'Use as prompt' }),
210+
).toBeVisible();
211+
await expect(
212+
page.getByRole('menuitem', { name: 'Rephrase' }),
213+
).toBeVisible();
214+
await expect(
215+
page.getByRole('menuitem', { name: 'Summarize' }),
216+
).toBeVisible();
217+
await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible();
218+
await expect(
219+
page.getByRole('menuitem', { name: 'Language' }),
220+
).toBeVisible();
221+
222+
await page.getByRole('menuitem', { name: 'Language' }).hover();
223+
await expect(
224+
page.getByRole('menuitem', { name: 'English', exact: true }),
225+
).toBeVisible();
226+
await expect(
227+
page.getByRole('menuitem', { name: 'French', exact: true }),
228+
).toBeVisible();
229+
await expect(
230+
page.getByRole('menuitem', { name: 'German', exact: true }),
231+
).toBeVisible();
232+
233+
await page.getByRole('menuitem', { name: 'English', exact: true }).click();
234+
235+
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
236+
});
184237
});

src/frontend/apps/impress/src/api/APIError.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ export class APIError<T = unknown> extends Error implements IAPIError<T> {
1818
this.data = data;
1919
}
2020
}
21+
22+
export const isAPIError = (error: unknown): error is APIError => {
23+
return error instanceof APIError;
24+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './useCreateDocUpload';
2+
export * from './useDocAITransform';
3+
export * from './useDocAITranslate';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useMutation } from '@tanstack/react-query';
2+
3+
import { APIError, errorCauses, fetchAPI } from '@/api';
4+
5+
export type AITransformActions =
6+
| 'correct'
7+
| 'prompt'
8+
| 'rephrase'
9+
| 'summarize';
10+
11+
export type DocAITransform = {
12+
docId: string;
13+
text: string;
14+
action: AITransformActions;
15+
};
16+
17+
export type DocAITransformResponse = {
18+
answer: string;
19+
};
20+
21+
export const docAITransform = async ({
22+
docId,
23+
...params
24+
}: DocAITransform): Promise<DocAITransformResponse> => {
25+
const response = await fetchAPI(`documents/${docId}/ai-transform/`, {
26+
method: 'POST',
27+
body: JSON.stringify({
28+
...params,
29+
}),
30+
});
31+
32+
if (!response.ok) {
33+
throw new APIError(
34+
'Failed to request ai transform',
35+
await errorCauses(response),
36+
);
37+
}
38+
39+
return response.json() as Promise<DocAITransformResponse>;
40+
};
41+
42+
export function useDocAITransform() {
43+
return useMutation<DocAITransformResponse, APIError, DocAITransform>({
44+
mutationFn: docAITransform,
45+
});
46+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useMutation } from '@tanstack/react-query';
2+
3+
import { APIError, errorCauses, fetchAPI } from '@/api';
4+
5+
export type DocAITranslate = {
6+
docId: string;
7+
text: string;
8+
language: string;
9+
};
10+
11+
export type DocAITranslateResponse = {
12+
answer: string;
13+
};
14+
15+
export const docAITranslate = async ({
16+
docId,
17+
...params
18+
}: DocAITranslate): Promise<DocAITranslateResponse> => {
19+
const response = await fetchAPI(`documents/${docId}/ai-translate/`, {
20+
method: 'POST',
21+
body: JSON.stringify({
22+
...params,
23+
}),
24+
});
25+
26+
if (!response.ok) {
27+
throw new APIError(
28+
'Failed to request ai translate',
29+
await errorCauses(response),
30+
);
31+
}
32+
33+
return response.json() as Promise<DocAITranslateResponse>;
34+
};
35+
36+
export function useDocAITranslate() {
37+
return useMutation<DocAITranslateResponse, APIError, DocAITranslate>({
38+
mutationFn: docAITranslate,
39+
});
40+
}

0 commit comments

Comments
 (0)