diff --git a/backend/src/controller/chatController.ts b/backend/src/controller/chatController.ts index 809aa68e5..37d89208a 100644 --- a/backend/src/controller/chatController.ts +++ b/backend/src/controller/chatController.ts @@ -293,6 +293,7 @@ async function handleChatToGPT(req: OpenAiChatRequest, res: Response) { ...levelResult.chatResponse, wonLevel: !levelResult.chatResponse.defenceReport.isBlocked && + !levelResult.chatResponse.openAIErrorMessage && isLevelWon(levelResult.chatResponse.sentEmails, currentLevel), }; @@ -334,6 +335,29 @@ async function handleChatToGPT(req: OpenAiChatRequest, res: Response) { req.session.levelState[currentLevel].chatHistory = updatedChatHistory; req.session.levelState[currentLevel].sentEmails = totalSentEmails; + // If the level has just been won, add a level complete message to chat history and return it in the response + const levelCompleteMessageInChatHistory = req.session.levelState[ + currentLevel + ].chatHistory.some((msg) => msg.chatMessageType === 'LEVEL_COMPLETE'); + if (updatedChatResponse.wonLevel && !levelCompleteMessageInChatHistory) { + const levelCompleteMessage = { + chatMessageType: 'LEVEL_COMPLETE', + infoMessage: + currentLevel === LEVEL_NAMES.LEVEL_3 + ? `🎉 Congratulations, you have completed the final level of your assignment!` + : `🎉 Congratulations! You have completed this level. Please click on the next level to continue.`, + } as ChatInfoMessage; + + // add level complete message to chat history + req.session.levelState[currentLevel].chatHistory = pushMessageToHistory( + req.session.levelState[currentLevel].chatHistory, + levelCompleteMessage + ); + + // add level complete message to chat response + updatedChatResponse.wonLevelMessage = levelCompleteMessage; + } + console.log('chatResponse: ', updatedChatResponse); console.log('chatHistory: ', updatedChatHistory); diff --git a/backend/src/models/chat.ts b/backend/src/models/chat.ts index 22a4fdc31..6025249b3 100644 --- a/backend/src/models/chat.ts +++ b/backend/src/models/chat.ts @@ -3,7 +3,7 @@ import { ChatCompletionMessageParam, } from 'openai/resources/chat/completions'; -import { ChatMessage } from './chatMessage'; +import { ChatInfoMessage, ChatMessage } from './chatMessage'; import { DEFENCE_ID } from './defence'; import { EmailInfo } from './email'; @@ -97,6 +97,7 @@ interface ChatHttpResponse { openAIErrorMessage: string | null; sentEmails: EmailInfo[]; transformedMessageInfo?: string; + wonLevelMessage?: ChatInfoMessage; } interface LevelHandlerResponse { diff --git a/backend/src/models/chatMessage.ts b/backend/src/models/chatMessage.ts index 97962bb2e..0fc60a64c 100644 --- a/backend/src/models/chatMessage.ts +++ b/backend/src/models/chatMessage.ts @@ -10,7 +10,7 @@ import { TransformedChatMessage } from './chat'; const chatInfoMessageTypes = [ 'DEFENCE_ALERTED', 'DEFENCE_TRIGGERED', - 'LEVEL_INFO', + 'LEVEL_COMPLETE', 'RESET_LEVEL', 'ERROR_MSG', 'BOT_BLOCKED', diff --git a/backend/test/unit/controller/chatController.test.ts b/backend/test/unit/controller/chatController.test.ts index a5760ea10..21dcbd924 100644 --- a/backend/test/unit/controller/chatController.test.ts +++ b/backend/test/unit/controller/chatController.test.ts @@ -781,8 +781,17 @@ describe('handleChatToGPT unit tests', () => { await handleChatToGPT(req, res); + const expectedWonLevelMessage = { + infoMessage: + '🎉 Congratulations! You have completed this level. Please click on the next level to continue.', + chatMessageType: 'LEVEL_COMPLETE', + } as ChatMessage; + expect(res.send).toHaveBeenCalledWith( - expect.objectContaining({ wonLevel: true }) + expect.objectContaining({ + wonLevel: true, + wonLevelMessage: expectedWonLevelMessage, + }) ); }); @@ -827,11 +836,13 @@ describe('handleChatToGPT unit tests', () => { await handleChatToGPT(req, res); expect(res.send).toHaveBeenCalledWith( - expect.objectContaining({ wonLevel: false }) + expect.objectContaining({ + wonLevel: false, + }) ); }); - test('Given win condition met AND openAI error THEN level is won', async () => { + test('Given win condition met AND openAI error THEN level is not won', async () => { const newUserChatMessage = { completion: { content: 'Here is the answer to the level', @@ -868,7 +879,9 @@ describe('handleChatToGPT unit tests', () => { await handleChatToGPT(req, res); expect(res.send).toHaveBeenCalledWith( - expect.objectContaining({ wonLevel: true }) + expect.objectContaining({ + wonLevel: false, + }) ); }); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3fe892a5a..ceb511831 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -72,7 +72,6 @@ function App() { setIsNewUser(false); openOverlay( { setStartLevel(level); }} diff --git a/frontend/src/components/ChatBox/ChatBox.tsx b/frontend/src/components/ChatBox/ChatBox.tsx index 8531914c3..2fe61e71f 100644 --- a/frontend/src/components/ChatBox/ChatBox.tsx +++ b/frontend/src/components/ChatBox/ChatBox.tsx @@ -65,21 +65,9 @@ function ChatBox({ setChatInput(recalledMessage); }, [recalledMessageReverseIndex]); - function getSuccessMessage() { - return currentLevel < LEVEL_NAMES.LEVEL_3 - ? `Congratulations! You have completed this level. Please click on the next level to continue.` - : `Congratulations, you have completed the final level of your assignment!`; - } - - function isLevelComplete() { - // level is complete if the chat contains a LEVEL_INFO message - return messages.some((message) => message.type === 'LEVEL_INFO'); - } - function processChatResponse(response: ChatResponse) { const transformedMessageInfo = response.transformedMessageInfo; const transformedMessage = response.transformedMessage; - // add transformation info message to the chat box if (transformedMessageInfo) { addChatMessage({ message: transformedMessageInfo, @@ -156,19 +144,13 @@ function ChatBox({ // update emails addSentEmails(response.sentEmails); - if (response.wonLevel && !isLevelComplete()) { + if (response.wonLevelMessage) { updateNumCompletedLevels(currentLevel); - const successMessage = getSuccessMessage(); - addChatMessage({ - type: 'LEVEL_INFO', - message: successMessage, - }); - // asynchronously add the message to the chat history - void chatService.addInfoMessageToChatHistory( - successMessage, - 'LEVEL_INFO', - currentLevel + const levelCompleteMessage = chatService.makeChatMessageFromDTO( + response.wonLevelMessage ); + addChatMessage(levelCompleteMessage); + // if this is the last level, show the level complete overlay if (currentLevel === LEVEL_NAMES.LEVEL_3) { openLevelsCompleteOverlay(); diff --git a/frontend/src/components/ChatBox/ChatBoxMessage/MessageBubble.tsx b/frontend/src/components/ChatBox/ChatBoxMessage/MessageBubble.tsx index a675552da..1416f2456 100644 --- a/frontend/src/components/ChatBox/ChatBoxMessage/MessageBubble.tsx +++ b/frontend/src/components/ChatBox/ChatBoxMessage/MessageBubble.tsx @@ -14,7 +14,7 @@ function MessageBubble({ const baseClassName = 'message-bubble'; const messageTypeClassName = - message.type === 'LEVEL_INFO' + message.type === 'LEVEL_COMPLETE' ? 'level-info' : message.type === 'USER' ? 'user' @@ -35,7 +35,7 @@ function MessageBubble({ ); const messageAuthor = - message.type === 'LEVEL_INFO' + message.type === 'LEVEL_COMPLETE' ? '' : message.type === 'USER' ? 'You said:' @@ -48,12 +48,6 @@ function MessageBubble({ return ( // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
- {message.type === 'LEVEL_INFO' && ( - <> -

Information

- message: - - )} {messageAuthor} {message.transformedMessage ? ( diff --git a/frontend/src/components/DocumentViewer/DocumentViewBox.css b/frontend/src/components/DocumentViewer/DocumentViewBox.css index 8f994be8b..a285a38d9 100644 --- a/frontend/src/components/DocumentViewer/DocumentViewBox.css +++ b/frontend/src/components/DocumentViewer/DocumentViewBox.css @@ -37,8 +37,8 @@ padding: 2rem; border: 0.1875rem solid var(--main-border-colour); background: var(--main-background); - aspect-ratio: 7/8; color: var(--main-text-colour); + aspect-ratio: 7/8; } .document-viewer-container { diff --git a/frontend/src/components/ExportChat/ExportChatMessage.tsx b/frontend/src/components/ExportChat/ExportChatMessage.tsx index 2dea933f3..55def05c5 100644 --- a/frontend/src/components/ExportChat/ExportChatMessage.tsx +++ b/frontend/src/components/ExportChat/ExportChatMessage.tsx @@ -72,7 +72,7 @@ function getMessageStyle(type: CHAT_MESSAGE_TYPE) { return styles.chatBoxInfo; case 'BOT_BLOCKED': case 'BOT': - case 'LEVEL_INFO': + case 'LEVEL_COMPLETE': case 'ERROR_MSG': return styles.chatBoxMessageBot; case 'USER': diff --git a/frontend/src/components/LevelSelectionBox/LevelSelectionBox.tsx b/frontend/src/components/LevelSelectionBox/LevelSelectionBox.tsx index f31dcf16e..5ff607e2e 100644 --- a/frontend/src/components/LevelSelectionBox/LevelSelectionBox.tsx +++ b/frontend/src/components/LevelSelectionBox/LevelSelectionBox.tsx @@ -29,7 +29,7 @@ function LevelSelectionBox({ } return ( -
+
+ ); } diff --git a/frontend/src/components/Overlay/OverlayWelcome.tsx b/frontend/src/components/Overlay/OverlayWelcome.tsx index 4f0865eb1..8d0910290 100644 --- a/frontend/src/components/Overlay/OverlayWelcome.tsx +++ b/frontend/src/components/Overlay/OverlayWelcome.tsx @@ -5,11 +5,9 @@ import { LEVEL_NAMES } from '@src/models/level'; import MultipageOverlay from './MultipageOverlay'; function OverlayWelcome({ - currentLevel, setStartLevel, closeOverlay, }: { - currentLevel: LEVEL_NAMES; setStartLevel: (newLevel: LEVEL_NAMES) => void; closeOverlay: () => void; }) { @@ -43,10 +41,7 @@ function OverlayWelcome({ beginning, or are you an expert spy, and would prefer to jump straight in at the sandbox?

- + ), imageUrl: BotAvatarDefault, diff --git a/frontend/src/components/ThemedButtons/ChatButton.css b/frontend/src/components/ThemedButtons/ChatButton.css index 4d80f3b20..29256354b 100644 --- a/frontend/src/components/ThemedButtons/ChatButton.css +++ b/frontend/src/components/ThemedButtons/ChatButton.css @@ -2,6 +2,7 @@ display: flex; justify-content: center; align-items: center; + width: 100%; padding: 0.75rem; border: 1px solid var(--chat-button-border-colour); border-radius: 0.625rem; @@ -9,7 +10,6 @@ color: var(--chat-button-text-colour); font-weight: 700; font-size: 1rem; - width: 100%; } .chat-button:hover { diff --git a/frontend/src/components/ThemedButtons/LevelsCompleteButtons.tsx b/frontend/src/components/ThemedButtons/LevelsCompleteButtons.tsx index 057effa00..9bfe6df52 100644 --- a/frontend/src/components/ThemedButtons/LevelsCompleteButtons.tsx +++ b/frontend/src/components/ThemedButtons/LevelsCompleteButtons.tsx @@ -24,13 +24,7 @@ function LevelsCompleteButtons({ } } - return ( - - ); + return ; } export default LevelsCompleteButtons; diff --git a/frontend/src/components/ThemedButtons/ModeSelectButtons.css b/frontend/src/components/ThemedButtons/ModeSelectButtons.css index 77c6955bd..c1a11b0e5 100644 --- a/frontend/src/components/ThemedButtons/ModeSelectButtons.css +++ b/frontend/src/components/ThemedButtons/ModeSelectButtons.css @@ -12,7 +12,7 @@ align-items: center; margin: 0; padding: 1rem 2rem; - border: 0.0625rem solid var(--overlay-tab-colour); + border: 0.125rem solid var(--overlay-tab-colour); border-radius: 0.5rem; background-color: var(--overlay-background-colour); font-weight: 700; @@ -22,10 +22,6 @@ transition: ease 0.1s; } -.mode-selection-buttons .mode-button.selected { - background-color: var(--overlay-tab-colour); -} - .mode-selection-buttons button:hover { background-color: var(--overlay-tab-colour); } diff --git a/frontend/src/components/ThemedButtons/ModeSelectButtons.tsx b/frontend/src/components/ThemedButtons/ModeSelectButtons.tsx index 58989e660..9ce861ffa 100644 --- a/frontend/src/components/ThemedButtons/ModeSelectButtons.tsx +++ b/frontend/src/components/ThemedButtons/ModeSelectButtons.tsx @@ -1,35 +1,20 @@ -import { clsx } from 'clsx'; - import { LEVEL_NAMES, ModeSelectButton } from '@src/models/level'; import './ModeSelectButtons.css'; function ModeSelectButtons({ - defaultSelection, modeButtons, setLevel, }: { - defaultSelection: LEVEL_NAMES; modeButtons: ModeSelectButton[]; setLevel: (newLevel: LEVEL_NAMES) => void; }) { - function defaultButton(targetLevel: LEVEL_NAMES) { - return targetLevel === defaultSelection; - } - return (
    {modeButtons.map((modeButton) => ( -
  • +