From 95118f2d035ee9d19552526b7db0a067d0c8be6c Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Mon, 30 Sep 2024 12:16:12 +0300 Subject: [PATCH] Refactoring --- .../src/components/chatbot/ChatbotDialog.tsx | 11 ++++- .../components/chatbot/ChatbotDialogBody.tsx | 46 +++++++++++++++---- .../src/components/chatbot/MessageBubble.tsx | 23 ++++------ .../course-material/src/services/backend.ts | 31 +++++++++---- .../src/locales/en/course-material.json | 1 + 5 files changed, 80 insertions(+), 32 deletions(-) diff --git a/services/course-material/src/components/chatbot/ChatbotDialog.tsx b/services/course-material/src/components/chatbot/ChatbotDialog.tsx index 8c338cc6407..6ed50977b00 100644 --- a/services/course-material/src/components/chatbot/ChatbotDialog.tsx +++ b/services/course-material/src/components/chatbot/ChatbotDialog.tsx @@ -1,6 +1,6 @@ import { css, keyframes } from "@emotion/css" import { useQuery } from "@tanstack/react-query" -import { useEffect, useState } from "react" +import React, { useEffect, useState } from "react" import ChatbotDialogBody from "./ChatbotDialogBody" import ChatbotDialogHeader from "./ChatbotDialogHeader" @@ -39,6 +39,8 @@ const ChatbotDialog: React.FC = (props) => { const { dialogOpen, chatbotConfigurationId } = props const [shouldRender, setShouldRender] = useState(dialogOpen) + const [error, setError] = useState(null) + const currentConversationInfoQuery = useQuery({ queryKey: ["currentConversationInfo", chatbotConfigurationId], queryFn: () => getChatbotCurrentConversationInfo(chatbotConfigurationId), @@ -83,7 +85,12 @@ const ChatbotDialog: React.FC = (props) => { onAnimationEnd={handleAnimationEnd} > - + ) } diff --git a/services/course-material/src/components/chatbot/ChatbotDialogBody.tsx b/services/course-material/src/components/chatbot/ChatbotDialogBody.tsx index 8169a7640ce..72c1076d071 100644 --- a/services/course-material/src/components/chatbot/ChatbotDialogBody.tsx +++ b/services/course-material/src/components/chatbot/ChatbotDialogBody.tsx @@ -16,6 +16,12 @@ import Spinner from "@/shared-module/common/components/Spinner" import useToastMutation from "@/shared-module/common/hooks/useToastMutation" import { baseTheme } from "@/shared-module/common/styles" +interface ChatbotDialogBodyProps extends ChatbotDialogProps { + currentConversationInfo: UseQueryResult + error: Error | null + setError: (error: Error | null) => void +} + interface MessageState { optimisticMessage: string | null streamingMessage: string | null @@ -39,9 +45,12 @@ const messageReducer = (state: MessageState, action: MessageAction): MessageStat } } -const ChatbotDialogBody: React.FC< - ChatbotDialogProps & { currentConversationInfo: UseQueryResult } -> = ({ currentConversationInfo, chatbotConfigurationId }) => { +const ChatbotDialogBody: React.FC = ({ + currentConversationInfo, + chatbotConfigurationId, + error, + setError, +}) => { const scrollContainerRef = useRef(null) const { t } = useTranslation() @@ -58,6 +67,7 @@ const ChatbotDialogBody: React.FC< onSuccess: () => { currentConversationInfo.refetch() dispatch({ type: "RESET_MESSAGES" }) + setError(null) // Clear any existing errors when starting a new conversation }, }, ) @@ -106,6 +116,17 @@ const ChatbotDialogBody: React.FC< onSuccess: async () => { await currentConversationInfo.refetch() dispatch({ type: "RESET_MESSAGES" }) + setError(null) + }, + onError: async (error) => { + if (error instanceof Error) { + setError(error) + dispatch({ type: "SET_OPTIMISTIC_MESSAGE", payload: null }) + } else { + console.error(`Failed to send chat message: ${error}`) + setError(new Error("Unknown error occurred")) + } + await currentConversationInfo.refetch() }, }, ) @@ -179,11 +200,7 @@ const ChatbotDialogBody: React.FC< `} > - @@ -273,6 +290,19 @@ const ChatbotDialogBody: React.FC< /> ))} + {error && ( +
+ {t("failed-to-send-message")}: {error.message} +
+ )}
css` margin-left: 2rem; align-self: flex-end; border: 2px solid ${baseTheme.colors.gray[200]}; + background-color: #ffffff; `} ` -const MessageBubble: React.FC = React.memo( - ({ message, isFromChatbot, isPending }) => { - return ( -
- {message} - {isPending && } -
- ) - }, -) - -MessageBubble.displayName = "MessageBubble" +const MessageBubble: React.FC = ({ message, isFromChatbot, isPending }) => { + return ( +
+ {message} + {isPending && } +
+ ) +} -export default MessageBubble +export default React.memo(MessageBubble) diff --git a/services/course-material/src/services/backend.ts b/services/course-material/src/services/backend.ts index 30b4b53b611..0ef3cdafa74 100644 --- a/services/course-material/src/services/backend.ts +++ b/services/course-material/src/services/backend.ts @@ -698,16 +698,29 @@ export const sendChatbotMessage = async ( conversationId: string, message: string, ): Promise> => { - const res = await fetch( - `/api/v0/course-material/chatbot/${chatBotConfigurationId}/conversations/${conversationId}/send-message`, - { - method: "POST", - body: JSON.stringify(message), - headers: { - "Content-Type": "application/json", - }, + const url = `/api/v0/course-material/chatbot/${chatBotConfigurationId}/conversations/${conversationId}/send-message` + + const requestOptions: RequestInit = { + method: "POST", + body: JSON.stringify({ message }), // It's better to send an object for clarity + headers: { + "Content-Type": "application/json", }, - ) + } + + const res = await fetch(url, requestOptions) + + if (res.status !== 200) { + let errorDetails: string + try { + const errorData = await res.json() + errorDetails = JSON.stringify(errorData) + } catch { + errorDetails = await res.text() + } + + throw new Error(`Request failed with status ${res.status}: ${errorDetails}`) + } const stream = res.body diff --git a/shared-module/packages/common/src/locales/en/course-material.json b/shared-module/packages/common/src/locales/en/course-material.json index 3c011f21dec..a50a5d56fe4 100644 --- a/shared-module/packages/common/src/locales/en/course-material.json +++ b/shared-module/packages/common/src/locales/en/course-material.json @@ -81,6 +81,7 @@ "exercises-attempted": "Exercises attempted", "exercises-in-this-chapter": "Exercises in this chapter", "exit": "Exit", + "failed-to-send-message": "Failed to send message", "failed-to-submit": "Failed to submit {{ error }}", "feedback-submitted-succesfully": "Feedback submitted successfully", "finnish": "Finnish",