♻️(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.
This commit is contained in:
@@ -20,6 +20,7 @@ and this project adheres to
|
|||||||
- 🐛(backend) require right to manage document accesses to see invitations #369
|
- 🐛(backend) require right to manage document accesses to see invitations #369
|
||||||
- 🐛(i18n) same frontend and backend language using shared cookies #365
|
- 🐛(i18n) same frontend and backend language using shared cookies #365
|
||||||
- 🐛(frontend) add default toolbar buttons #355
|
- 🐛(frontend) add default toolbar buttons #355
|
||||||
|
- 🐛(frontend) throttle error correctly display #378
|
||||||
|
|
||||||
|
|
||||||
## [1.6.0] - 2024-10-17
|
## [1.6.0] - 2024-10-17
|
||||||
|
|||||||
@@ -9,14 +9,7 @@ import {
|
|||||||
VariantType,
|
VariantType,
|
||||||
useToastProvider,
|
useToastProvider,
|
||||||
} from '@openfun/cunningham-react';
|
} from '@openfun/cunningham-react';
|
||||||
import {
|
import { PropsWithChildren, ReactNode, useMemo } from 'react';
|
||||||
PropsWithChildren,
|
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { isAPIError } from '@/api';
|
import { isAPIError } from '@/api';
|
||||||
@@ -70,9 +63,8 @@ export function AIGroupButton() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentDoc } = useDocStore();
|
const { currentDoc } = useDocStore();
|
||||||
const { data: docOptions } = useDocOptions();
|
const { data: docOptions } = useDocOptions();
|
||||||
const [languages, setLanguages] = useState<LanguageTranslate[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const languages = useMemo(() => {
|
||||||
const languages = docOptions?.actions.POST.language.choices;
|
const languages = docOptions?.actions.POST.language.choices;
|
||||||
|
|
||||||
if (!languages) {
|
if (!languages) {
|
||||||
@@ -90,7 +82,7 @@ export function AIGroupButton() {
|
|||||||
'pl',
|
'pl',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setLanguages(languages);
|
return languages;
|
||||||
}, [docOptions?.actions.POST.language.choices]);
|
}, [docOptions?.actions.POST.language.choices]);
|
||||||
|
|
||||||
const show = useMemo(() => {
|
const show = useMemo(() => {
|
||||||
@@ -220,45 +212,19 @@ const AIMenuItemTransform = ({
|
|||||||
children,
|
children,
|
||||||
icon,
|
icon,
|
||||||
}: PropsWithChildren<AIMenuItemTransform>) => {
|
}: PropsWithChildren<AIMenuItemTransform>) => {
|
||||||
const editor = useBlockNoteEditor();
|
|
||||||
const { mutateAsync: requestAI, isPending } = useDocAITransform();
|
const { mutateAsync: requestAI, isPending } = useDocAITransform();
|
||||||
const handleAIError = useHandleAIError();
|
|
||||||
|
|
||||||
const handleAIAction = useCallback(async () => {
|
const requestAIAction = async (markdown: string) => {
|
||||||
const selectedBlocks = editor.getSelection()?.blocks;
|
const responseAI = await requestAI({
|
||||||
|
text: markdown,
|
||||||
if (!selectedBlocks || selectedBlocks.length === 0) {
|
action,
|
||||||
return;
|
docId,
|
||||||
}
|
});
|
||||||
|
return responseAI.answer;
|
||||||
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]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AIMenuItem
|
<AIMenuItem icon={icon} requestAI={requestAIAction} isPending={isPending}>
|
||||||
icon={icon}
|
|
||||||
handleAIAction={handleAIAction}
|
|
||||||
isPending={isPending}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</AIMenuItem>
|
</AIMenuItem>
|
||||||
);
|
);
|
||||||
@@ -276,11 +242,46 @@ const AIMenuItemTranslate = ({
|
|||||||
icon,
|
icon,
|
||||||
language,
|
language,
|
||||||
}: PropsWithChildren<AIMenuItemTranslate>) => {
|
}: PropsWithChildren<AIMenuItemTranslate>) => {
|
||||||
const editor = useBlockNoteEditor();
|
|
||||||
const { mutateAsync: requestAI, isPending } = useDocAITranslate();
|
const { mutateAsync: requestAI, isPending } = useDocAITranslate();
|
||||||
|
|
||||||
|
const requestAITranslate = async (markdown: string) => {
|
||||||
|
const responseAI = await requestAI({
|
||||||
|
text: markdown,
|
||||||
|
language,
|
||||||
|
docId,
|
||||||
|
});
|
||||||
|
return responseAI.answer;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AIMenuItem
|
||||||
|
icon={icon}
|
||||||
|
requestAI={requestAITranslate}
|
||||||
|
isPending={isPending}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AIMenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AIMenuItemProps {
|
||||||
|
requestAI: (markdown: string) => Promise<string>;
|
||||||
|
isPending: boolean;
|
||||||
|
icon?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AIMenuItem = ({
|
||||||
|
requestAI,
|
||||||
|
isPending,
|
||||||
|
children,
|
||||||
|
icon,
|
||||||
|
}: PropsWithChildren<AIMenuItemProps>) => {
|
||||||
|
const Components = useComponentsContext();
|
||||||
|
|
||||||
|
const editor = useBlockNoteEditor();
|
||||||
const handleAIError = useHandleAIError();
|
const handleAIError = useHandleAIError();
|
||||||
|
|
||||||
const handleAIAction = useCallback(async () => {
|
const handleAIAction = async () => {
|
||||||
const selectedBlocks = editor.getSelection()?.blocks;
|
const selectedBlocks = editor.getSelection()?.blocks;
|
||||||
|
|
||||||
if (!selectedBlocks || selectedBlocks.length === 0) {
|
if (!selectedBlocks || selectedBlocks.length === 0) {
|
||||||
@@ -290,49 +291,18 @@ const AIMenuItemTranslate = ({
|
|||||||
const markdown = await editor.blocksToMarkdownLossy(selectedBlocks);
|
const markdown = await editor.blocksToMarkdownLossy(selectedBlocks);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responseAI = await requestAI({
|
const responseAI = await requestAI(markdown);
|
||||||
text: markdown,
|
|
||||||
language,
|
|
||||||
docId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!responseAI.answer) {
|
if (!responseAI) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockMarkdown = await editor.tryParseMarkdownToBlocks(
|
const blockMarkdown = await editor.tryParseMarkdownToBlocks(responseAI);
|
||||||
responseAI.answer,
|
|
||||||
);
|
|
||||||
editor.replaceBlocks(selectedBlocks, blockMarkdown);
|
editor.replaceBlocks(selectedBlocks, blockMarkdown);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleAIError(error);
|
handleAIError(error);
|
||||||
}
|
}
|
||||||
}, [editor, requestAI, language, docId, handleAIError]);
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<AIMenuItem
|
|
||||||
icon={icon}
|
|
||||||
handleAIAction={handleAIAction}
|
|
||||||
isPending={isPending}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</AIMenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface AIMenuItemProps {
|
|
||||||
handleAIAction: () => Promise<void>;
|
|
||||||
isPending: boolean;
|
|
||||||
icon?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AIMenuItem = ({
|
|
||||||
handleAIAction,
|
|
||||||
isPending,
|
|
||||||
children,
|
|
||||||
icon,
|
|
||||||
}: PropsWithChildren<AIMenuItemProps>) => {
|
|
||||||
const Components = useComponentsContext();
|
|
||||||
|
|
||||||
if (!Components) {
|
if (!Components) {
|
||||||
return null;
|
return null;
|
||||||
@@ -359,26 +329,12 @@ const useHandleAIError = () => {
|
|||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleAIError = useCallback(
|
return (error: unknown) => {
|
||||||
(error: unknown) => {
|
if (isAPIError(error) && error.status === 429) {
|
||||||
if (isAPIError(error)) {
|
toast(t('Too many requests. Please wait 60 seconds.'), VariantType.ERROR);
|
||||||
error.cause?.forEach((cause) => {
|
return;
|
||||||
if (
|
}
|
||||||
cause === 'Request was throttled. Expected available in 60 seconds.'
|
|
||||||
) {
|
|
||||||
toast(
|
|
||||||
t('Too many requests. Please wait 60 seconds.'),
|
|
||||||
VariantType.ERROR,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toast(t('AI seems busy! Please try again.'), VariantType.ERROR);
|
toast(t('AI seems busy! Please try again.'), VariantType.ERROR);
|
||||||
console.error(error);
|
};
|
||||||
},
|
|
||||||
[toast, t],
|
|
||||||
);
|
|
||||||
|
|
||||||
return handleAIError;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user