Skip to content

Commit 3434029

Browse files
committed
♻️(frontend) improve handleAIError
To display the throttle error messages, we are doing a condition on the error message that we get from the backend. It is error prone because the backend error message are internationalized. This commit fixes this issue. It DRY the component as well.
1 parent 6baa06b commit 3434029

File tree

2 files changed

+57
-100
lines changed

2 files changed

+57
-100
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to
2020
- 🐛(backend) require right to manage document accesses to see invitations #369
2121
- 🐛(i18n) same frontend and backend language using shared cookies #365
2222
- 🐛(frontend) add default toolbar buttons #355
23+
- 🐛(frontend) throttle error correctly display #378
2324

2425

2526
## [1.6.0] - 2024-10-17

src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx

Lines changed: 56 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,7 @@ import {
99
VariantType,
1010
useToastProvider,
1111
} from '@openfun/cunningham-react';
12-
import {
13-
PropsWithChildren,
14-
ReactNode,
15-
useCallback,
16-
useEffect,
17-
useMemo,
18-
useState,
19-
} from 'react';
12+
import { PropsWithChildren, ReactNode, useMemo } from 'react';
2013
import { useTranslation } from 'react-i18next';
2114

2215
import { isAPIError } from '@/api';
@@ -70,9 +63,8 @@ export function AIGroupButton() {
7063
const { t } = useTranslation();
7164
const { currentDoc } = useDocStore();
7265
const { data: docOptions } = useDocOptions();
73-
const [languages, setLanguages] = useState<LanguageTranslate[]>([]);
7466

75-
useEffect(() => {
67+
const languages = useMemo(() => {
7668
const languages = docOptions?.actions.POST.language.choices;
7769

7870
if (!languages) {
@@ -90,7 +82,7 @@ export function AIGroupButton() {
9082
'pl',
9183
]);
9284

93-
setLanguages(languages);
85+
return languages;
9486
}, [docOptions?.actions.POST.language.choices]);
9587

9688
const show = useMemo(() => {
@@ -220,45 +212,19 @@ const AIMenuItemTransform = ({
220212
children,
221213
icon,
222214
}: PropsWithChildren<AIMenuItemTransform>) => {
223-
const editor = useBlockNoteEditor();
224215
const { mutateAsync: requestAI, isPending } = useDocAITransform();
225-
const handleAIError = useHandleAIError();
226-
227-
const handleAIAction = useCallback(async () => {
228-
const selectedBlocks = editor.getSelection()?.blocks;
229-
230-
if (!selectedBlocks || selectedBlocks.length === 0) {
231-
return;
232-
}
233216

234-
const markdown = await editor.blocksToMarkdownLossy(selectedBlocks);
235-
236-
try {
237-
const responseAI = await requestAI({
238-
text: markdown,
239-
action,
240-
docId,
241-
});
242-
243-
if (!responseAI.answer) {
244-
return;
245-
}
246-
247-
const blockMarkdown = await editor.tryParseMarkdownToBlocks(
248-
responseAI.answer,
249-
);
250-
editor.replaceBlocks(selectedBlocks, blockMarkdown);
251-
} catch (error) {
252-
handleAIError(error);
253-
}
254-
}, [editor, requestAI, action, docId, handleAIError]);
217+
const requestAIAction = async (markdown: string) => {
218+
const responseAI = await requestAI({
219+
text: markdown,
220+
action,
221+
docId,
222+
});
223+
return responseAI.answer;
224+
};
255225

256226
return (
257-
<AIMenuItem
258-
icon={icon}
259-
handleAIAction={handleAIAction}
260-
isPending={isPending}
261-
>
227+
<AIMenuItem icon={icon} requestAI={requestAIAction} isPending={isPending}>
262228
{children}
263229
</AIMenuItem>
264230
);
@@ -276,43 +242,21 @@ const AIMenuItemTranslate = ({
276242
icon,
277243
language,
278244
}: PropsWithChildren<AIMenuItemTranslate>) => {
279-
const editor = useBlockNoteEditor();
280245
const { mutateAsync: requestAI, isPending } = useDocAITranslate();
281-
const handleAIError = useHandleAIError();
282-
283-
const handleAIAction = useCallback(async () => {
284-
const selectedBlocks = editor.getSelection()?.blocks;
285-
286-
if (!selectedBlocks || selectedBlocks.length === 0) {
287-
return;
288-
}
289-
290-
const markdown = await editor.blocksToMarkdownLossy(selectedBlocks);
291-
292-
try {
293-
const responseAI = await requestAI({
294-
text: markdown,
295-
language,
296-
docId,
297-
});
298246

299-
if (!responseAI.answer) {
300-
return;
301-
}
302-
303-
const blockMarkdown = await editor.tryParseMarkdownToBlocks(
304-
responseAI.answer,
305-
);
306-
editor.replaceBlocks(selectedBlocks, blockMarkdown);
307-
} catch (error) {
308-
handleAIError(error);
309-
}
310-
}, [editor, requestAI, language, docId, handleAIError]);
247+
const requestAITranslate = async (markdown: string) => {
248+
const responseAI = await requestAI({
249+
text: markdown,
250+
language,
251+
docId,
252+
});
253+
return responseAI.answer;
254+
};
311255

312256
return (
313257
<AIMenuItem
314258
icon={icon}
315-
handleAIAction={handleAIAction}
259+
requestAI={requestAITranslate}
316260
isPending={isPending}
317261
>
318262
{children}
@@ -321,19 +265,45 @@ const AIMenuItemTranslate = ({
321265
};
322266

323267
interface AIMenuItemProps {
324-
handleAIAction: () => Promise<void>;
268+
requestAI: (markdown: string) => Promise<string>;
325269
isPending: boolean;
326270
icon?: ReactNode;
327271
}
328272

329273
const AIMenuItem = ({
330-
handleAIAction,
274+
requestAI,
331275
isPending,
332276
children,
333277
icon,
334278
}: PropsWithChildren<AIMenuItemProps>) => {
335279
const Components = useComponentsContext();
336280

281+
const editor = useBlockNoteEditor();
282+
const handleAIError = useHandleAIError();
283+
284+
const handleAIAction = async () => {
285+
const selectedBlocks = editor.getSelection()?.blocks;
286+
287+
if (!selectedBlocks || selectedBlocks.length === 0) {
288+
return;
289+
}
290+
291+
const markdown = await editor.blocksToMarkdownLossy(selectedBlocks);
292+
293+
try {
294+
const responseAI = await requestAI(markdown);
295+
296+
if (!responseAI) {
297+
return;
298+
}
299+
300+
const blockMarkdown = await editor.tryParseMarkdownToBlocks(responseAI);
301+
editor.replaceBlocks(selectedBlocks, blockMarkdown);
302+
} catch (error) {
303+
handleAIError(error);
304+
}
305+
};
306+
337307
if (!Components) {
338308
return null;
339309
}
@@ -359,26 +329,12 @@ const useHandleAIError = () => {
359329
const { toast } = useToastProvider();
360330
const { t } = useTranslation();
361331

362-
const handleAIError = useCallback(
363-
(error: unknown) => {
364-
if (isAPIError(error)) {
365-
error.cause?.forEach((cause) => {
366-
if (
367-
cause === 'Request was throttled. Expected available in 60 seconds.'
368-
) {
369-
toast(
370-
t('Too many requests. Please wait 60 seconds.'),
371-
VariantType.ERROR,
372-
);
373-
}
374-
});
375-
}
376-
377-
toast(t('AI seems busy! Please try again.'), VariantType.ERROR);
378-
console.error(error);
379-
},
380-
[toast, t],
381-
);
332+
return (error: unknown) => {
333+
if (isAPIError(error) && error.status === 429) {
334+
toast(t('Too many requests. Please wait 60 seconds.'), VariantType.ERROR);
335+
return;
336+
}
382337

383-
return handleAIError;
338+
toast(t('AI seems busy! Please try again.'), VariantType.ERROR);
339+
};
384340
};

0 commit comments

Comments
 (0)