From 34340296543abd021fcf29f6a6094cfc95ee0792 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 23 Oct 2024 14:28:44 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20improve=20handle?= =?UTF-8?q?AIError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- CHANGELOG.md | 1 + .../docs/doc-editor/components/AIButton.tsx | 164 +++++++----------- 2 files changed, 61 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd88e3d5..e2867cd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to - 🐛(backend) require right to manage document accesses to see invitations #369 - 🐛(i18n) same frontend and backend language using shared cookies #365 - 🐛(frontend) add default toolbar buttons #355 +- 🐛(frontend) throttle error correctly display #378 ## [1.6.0] - 2024-10-17 diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx index bf5d524f..5746c44a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/AIButton.tsx @@ -9,14 +9,7 @@ import { VariantType, useToastProvider, } from '@openfun/cunningham-react'; -import { - PropsWithChildren, - ReactNode, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; +import { PropsWithChildren, ReactNode, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { isAPIError } from '@/api'; @@ -70,9 +63,8 @@ export function AIGroupButton() { const { t } = useTranslation(); const { currentDoc } = useDocStore(); const { data: docOptions } = useDocOptions(); - const [languages, setLanguages] = useState([]); - useEffect(() => { + const languages = useMemo(() => { const languages = docOptions?.actions.POST.language.choices; if (!languages) { @@ -90,7 +82,7 @@ export function AIGroupButton() { 'pl', ]); - setLanguages(languages); + return languages; }, [docOptions?.actions.POST.language.choices]); const show = useMemo(() => { @@ -220,45 +212,19 @@ const AIMenuItemTransform = ({ children, icon, }: PropsWithChildren) => { - const editor = useBlockNoteEditor(); const { mutateAsync: requestAI, isPending } = useDocAITransform(); - const handleAIError = useHandleAIError(); - const handleAIAction = useCallback(async () => { - const selectedBlocks = editor.getSelection()?.blocks; - - if (!selectedBlocks || selectedBlocks.length === 0) { - return; - } - - const markdown = await editor.blocksToMarkdownLossy(selectedBlocks); - - try { - const responseAI = await requestAI({ - text: markdown, - action, - docId, - }); - - if (!responseAI.answer) { - return; - } - - const blockMarkdown = await editor.tryParseMarkdownToBlocks( - responseAI.answer, - ); - editor.replaceBlocks(selectedBlocks, blockMarkdown); - } catch (error) { - handleAIError(error); - } - }, [editor, requestAI, action, docId, handleAIError]); + const requestAIAction = async (markdown: string) => { + const responseAI = await requestAI({ + text: markdown, + action, + docId, + }); + return responseAI.answer; + }; return ( - + {children} ); @@ -276,11 +242,46 @@ const AIMenuItemTranslate = ({ icon, language, }: PropsWithChildren) => { - const editor = useBlockNoteEditor(); const { mutateAsync: requestAI, isPending } = useDocAITranslate(); + + const requestAITranslate = async (markdown: string) => { + const responseAI = await requestAI({ + text: markdown, + language, + docId, + }); + return responseAI.answer; + }; + + return ( + + {children} + + ); +}; + +interface AIMenuItemProps { + requestAI: (markdown: string) => Promise; + isPending: boolean; + icon?: ReactNode; +} + +const AIMenuItem = ({ + requestAI, + isPending, + children, + icon, +}: PropsWithChildren) => { + const Components = useComponentsContext(); + + const editor = useBlockNoteEditor(); const handleAIError = useHandleAIError(); - const handleAIAction = useCallback(async () => { + const handleAIAction = async () => { const selectedBlocks = editor.getSelection()?.blocks; if (!selectedBlocks || selectedBlocks.length === 0) { @@ -290,49 +291,18 @@ const AIMenuItemTranslate = ({ const markdown = await editor.blocksToMarkdownLossy(selectedBlocks); try { - const responseAI = await requestAI({ - text: markdown, - language, - docId, - }); + const responseAI = await requestAI(markdown); - if (!responseAI.answer) { + if (!responseAI) { return; } - const blockMarkdown = await editor.tryParseMarkdownToBlocks( - responseAI.answer, - ); + const blockMarkdown = await editor.tryParseMarkdownToBlocks(responseAI); editor.replaceBlocks(selectedBlocks, blockMarkdown); } catch (error) { handleAIError(error); } - }, [editor, requestAI, language, docId, handleAIError]); - - return ( - - {children} - - ); -}; - -interface AIMenuItemProps { - handleAIAction: () => Promise; - isPending: boolean; - icon?: ReactNode; -} - -const AIMenuItem = ({ - handleAIAction, - isPending, - children, - icon, -}: PropsWithChildren) => { - const Components = useComponentsContext(); + }; if (!Components) { return null; @@ -359,26 +329,12 @@ const useHandleAIError = () => { const { toast } = useToastProvider(); const { t } = useTranslation(); - const handleAIError = useCallback( - (error: unknown) => { - if (isAPIError(error)) { - error.cause?.forEach((cause) => { - if ( - cause === 'Request was throttled. Expected available in 60 seconds.' - ) { - toast( - t('Too many requests. Please wait 60 seconds.'), - VariantType.ERROR, - ); - } - }); - } + return (error: unknown) => { + if (isAPIError(error) && error.status === 429) { + toast(t('Too many requests. Please wait 60 seconds.'), VariantType.ERROR); + return; + } - toast(t('AI seems busy! Please try again.'), VariantType.ERROR); - console.error(error); - }, - [toast, t], - ); - - return handleAIError; + toast(t('AI seems busy! Please try again.'), VariantType.ERROR); + }; };