diff --git a/apps/web/components/Contexts/AI/AIChatBotContext.tsx b/apps/web/components/Contexts/AI/AIChatBotContext.tsx index b912bd1a..dbe2e510 100644 --- a/apps/web/components/Contexts/AI/AIChatBotContext.tsx +++ b/apps/web/components/Contexts/AI/AIChatBotContext.tsx @@ -5,12 +5,18 @@ export const AIChatBotContext = createContext(null) as any; export const AIChatBotDispatchContext = createContext(null) as any; export type AIChatBotStateTypes = { - messages: AIMessage[], isModalOpen: boolean, aichat_uuid: string, isWaitingForResponse: boolean, chatInputValue: string + error: AIError +} + +type AIError = { + isError: boolean + status: number + error_message: string } function AIChatBotProvider({ children }: { children: React.ReactNode }) { @@ -20,7 +26,8 @@ function AIChatBotProvider({ children }: { children: React.ReactNode }) { isModalOpen: false, aichat_uuid: null, isWaitingForResponse: false, - chatInputValue: '' + chatInputValue: '', + error: { isError: false, status: 0, error_message: ' ' } as AIError } ); return ( @@ -60,6 +67,8 @@ function aiChatBotReducer(state: any, action: any) { return { ...state, isWaitingForResponse: false }; case 'setChatInputValue': return { ...state, chatInputValue: action.payload }; + case 'setError': + return { ...state, error: action.payload }; default: throw new Error(`Unhandled action type: ${action.type}`) diff --git a/apps/web/components/Contexts/AI/AIEditorContext.tsx b/apps/web/components/Contexts/AI/AIEditorContext.tsx index 49be94b3..efd9be04 100644 --- a/apps/web/components/Contexts/AI/AIEditorContext.tsx +++ b/apps/web/components/Contexts/AI/AIEditorContext.tsx @@ -14,6 +14,13 @@ export type AIEditorStateTypes = { chatInputValue: string, selectedTool: 'Writer' | 'ContinueWriting' | 'MakeLonger' | 'GenerateQuiz' | 'Translate' isUserInputEnabled: boolean + error: AIError +} + +type AIError = { + isError: boolean + status: number + error_message: string } function AIEditorProvider({ children }: { children: React.ReactNode }) { @@ -26,7 +33,8 @@ function AIEditorProvider({ children }: { children: React.ReactNode }) { isWaitingForResponse: false, chatInputValue: '', selectedTool: 'Writer', - isUserInputEnabled: true + isUserInputEnabled: true, + error: { isError: false, status: 0, error_message: ' ' } as AIError } ); return ( @@ -74,7 +82,9 @@ function aIEditorReducer(state: any, action: any) { return { ...state, isFeedbackModalOpen: false }; case 'setIsUserInputEnabled': return { ...state, isUserInputEnabled: action.payload }; - + case 'setError': + return { ...state, error: action.payload }; + default: throw new Error(`Unhandled action type: ${action.type}`) diff --git a/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx b/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx index 884e0435..a1fcff3d 100644 --- a/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx +++ b/apps/web/components/Objects/Activities/AI/AIActivityAsk.tsx @@ -1,6 +1,6 @@ import { useSession } from '@components/Contexts/SessionContext' import { sendActivityAIChatMessage, startActivityAIChatSession } from '@services/ai/ai'; -import { BadgeInfo, NotebookTabs } from 'lucide-react'; +import { AlertTriangle, BadgeInfo, NotebookTabs } from 'lucide-react'; import Avvvatars from 'avvvatars-react'; import { motion, AnimatePresence } from 'framer-motion'; import { FlaskConical, Keyboard, MessageCircle, MessageSquareIcon, Sparkle, Sparkles, X } from 'lucide-react' @@ -34,7 +34,7 @@ function AIActivityAsk(props: AIActivityAskProps) { return ( <> - {isButtonAvailable && ( + {isButtonAvailable && (
- {aiChatBotState.messages.length > 0 ? ( + {aiChatBotState.messages.length > 0 && !aiChatBotState.error.isError ? (
{aiChatBotState.messages.map((message: AIMessage, index: number) => { return ( @@ -177,6 +189,19 @@ function ActivityChatMessageBox(props: ActivityChatMessageBoxProps) { ) : ( )} + {aiChatBotState.error.isError && ( +
+
+ +
+

Something wrong happened

+ {aiChatBotState.error.error_message} +
+
+
+ + ) + }
@@ -235,10 +260,11 @@ function AIMessage(props: AIMessageProps) { const AIMessagePlaceHolder = (props: { activity_uuid: string, sendMessage: any }) => { const session = useSession() as any; - const [feedbackModal, setFeedbackModal] = React.useState(false); - return ( -
+ const aiChatBotState = useAIChatBot() as AIChatBotStateTypes; + + if (!aiChatBotState.error.isError) { + return
- ) + } } const AIChatPredefinedQuestion = (props: { sendMessage: any, label: string }) => { diff --git a/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx b/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx index efdd29af..86ea38e2 100644 --- a/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx +++ b/apps/web/components/Objects/Editor/AI/AIEditorToolkit.tsx @@ -2,7 +2,7 @@ import React from 'react' import learnhouseAI_icon from "public/learnhouse_ai_simple.png"; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; -import { BetweenHorizontalStart, FastForward, Feather, FileStack, HelpCircle, Languages, MessageCircle, MoreVertical, Pen, X } from 'lucide-react'; +import { AlertTriangle, BetweenHorizontalStart, FastForward, Feather, FileStack, HelpCircle, Languages, MessageCircle, MoreVertical, Pen, X } from 'lucide-react'; import { Editor } from '@tiptap/react'; import { AIChatBotStateTypes, useAIChatBot, useAIChatBotDispatch } from '@components/Contexts/AI/AIChatBotContext'; import { AIEditorStateTypes, useAIEditor, useAIEditorDispatch } from '@components/Contexts/AI/AIEditorContext'; @@ -96,20 +96,38 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => { await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'user', message: message, type: 'user' } }); await dispatchAIEditor({ type: 'setIsWaitingForResponse' }); const response = await sendActivityAIChatMessage(message, aiEditorState.aichat_uuid, props.activity.activity_uuid) + if (response.success === false) { + await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }); + await dispatchAIEditor({ type: 'setIsModalClose' }); + // wait for 200ms before opening the modal again + await new Promise(resolve => setTimeout(resolve, 200)); + await dispatchAIEditor({ type: 'setError', payload: { isError: true, status: response.status, error_message: response.data.detail } }); + await dispatchAIEditor({ type: 'setIsModalOpen' }); + return ''; + } await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }); await dispatchAIEditor({ type: 'setChatInputValue', payload: '' }); - await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'ai', message: response.message, type: 'ai' } }); - return response.message; + await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'ai', message: response.data.message, type: 'ai' } }); + return response.data.message; } else { await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'user', message: message, type: 'user' } }); await dispatchAIEditor({ type: 'setIsWaitingForResponse' }); const response = await startActivityAIChatSession(message, props.activity.activity_uuid) - await dispatchAIEditor({ type: 'setAichat_uuid', payload: response.aichat_uuid }); + if (response.success === false) { + await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }); + await dispatchAIEditor({ type: 'setIsModalClose' }); + // wait for 200ms before opening the modal again + await new Promise(resolve => setTimeout(resolve, 200)); + await dispatchAIEditor({ type: 'setError', payload: { isError: true, status: response.status, error_message: response.data.detail } }); + await dispatchAIEditor({ type: 'setIsModalOpen' }); + return ''; + } + await dispatchAIEditor({ type: 'setAichat_uuid', payload: response.data.aichat_uuid }); await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }); await dispatchAIEditor({ type: 'setChatInputValue', payload: '' }); - await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'ai', message: response.message, type: 'ai' } }); - return response.message; + await dispatchAIEditor({ type: 'addMessage', payload: { sender: 'ai', message: response.data.message, type: 'ai' } }); + return response.data.message; } } @@ -158,10 +176,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => { await dispatchAIEditor({ type: 'setIsNoLongerWaitingForResponse' }); } } else if (label === 'GenerateQuiz') { - // Send message to AI - // Wait for response - // Add response to editor - // Close modal + // will be implemented in future stages } else if (label === 'Translate') { let ai_message = ''; let text_selection = getTipTapEditorSelectedText(); @@ -211,6 +226,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => { // Wait for 0.3 seconds before adding the next word await new Promise(resolve => setTimeout(resolve, 120)); + } } @@ -313,7 +329,7 @@ const UserFeedbackModal = (props: AIEditorToolkitProps) => {
- {aiEditorState.isUserInputEnabled &&
+ {aiEditorState.isUserInputEnabled && !aiEditorState.error.isError &&
@@ -379,11 +395,11 @@ const AiEditorActionScreen = ({ handleOperation }: { handleOperation: any }) => return (
- {aiEditorState.selectedTool === 'Writer' && !aiEditorState.isWaitingForResponse && + {aiEditorState.selectedTool === 'Writer' && !aiEditorState.isWaitingForResponse && !aiEditorState.error.isError &&
Write about...
} - {aiEditorState.selectedTool === 'ContinueWriting' && !aiEditorState.isWaitingForResponse && + {aiEditorState.selectedTool === 'ContinueWriting' && !aiEditorState.isWaitingForResponse && !aiEditorState.error.isError &&

Place your cursor at the end of a sentence to continue writing

{ @@ -393,7 +409,7 @@ const AiEditorActionScreen = ({ handleOperation }: { handleOperation: any }) =>
} - {aiEditorState.selectedTool === 'MakeLonger' && !aiEditorState.isWaitingForResponse && + {aiEditorState.selectedTool === 'MakeLonger' && !aiEditorState.isWaitingForResponse && !aiEditorState.error.isError &&

Select text to make longer

{ @@ -403,7 +419,7 @@ const AiEditorActionScreen = ({ handleOperation }: { handleOperation: any }) =>
} - {aiEditorState.selectedTool === 'Translate' && !aiEditorState.isWaitingForResponse && + {aiEditorState.selectedTool === 'Translate' && !aiEditorState.isWaitingForResponse && !aiEditorState.error.isError &&

Translate selected text to

@@ -416,8 +432,7 @@ const AiEditorActionScreen = ({ handleOperation }: { handleOperation: any }) =>
} - {aiEditorState.isWaitingForResponse &&
- + {aiEditorState.isWaitingForResponse && !aiEditorState.error.isError &&
@@ -425,6 +440,20 @@ const AiEditorActionScreen = ({ handleOperation }: { handleOperation: any }) =>

Thinking...

} + {aiEditorState.error.isError && ( +
+
+ +
+

Something wrong happened

+ {aiEditorState.error.error_message} +
+
+
+ + ) + } +
) } diff --git a/apps/web/services/ai/ai.ts b/apps/web/services/ai/ai.ts index 07f1e522..a3b8e871 100644 --- a/apps/web/services/ai/ai.ts +++ b/apps/web/services/ai/ai.ts @@ -7,8 +7,12 @@ export async function startActivityAIChatSession(message: string, activity_uuid: activity_uuid, }; const result = await fetch(`${getAPIUrl()}ai/start/activity_chat_session`, RequestBody("POST", data, null)); - const res = await result.json(); - return res; + const json = await result.json(); + if (result.status === 200) { + return { success: true, data: json, status: result.status, HTTPmessage: result.statusText }; + } else { + return { success: false, data: json, status: result.status, HTTPmessage: result.statusText }; + } } export async function sendActivityAIChatMessage(message: string, aichat_uuid: string, activity_uuid: string) { @@ -18,6 +22,11 @@ export async function sendActivityAIChatMessage(message: string, aichat_uuid: st activity_uuid, }; const result = await fetch(`${getAPIUrl()}ai/send/activity_chat_message`, RequestBody("POST", data, null)); - const res = await result.json(); - return res; + + const json = await result.json(); + if (result.status === 200) { + return { success: true, data: json, status: result.status, HTTPmessage: result.statusText }; + } else { + return { success: false, data: json, status: result.status, HTTPmessage: result.statusText }; + } }