From b3363beb63f8761b5a2db455db88f5879710db7d Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Thu, 14 Aug 2025 19:11:34 +0800 Subject: [PATCH 1/3] Updated itemChat Passed down artefactID form section to catalogueItemView to itemChat --- .../Catalogue/catalogueItemView.jsx | 56 ++++---- src/components/Catalogue/itemChat.jsx | 129 +++++++++++++++++- src/components/Catalogue/section.jsx | 11 +- 3 files changed, 161 insertions(+), 35 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 2afa607..7a6073e 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -12,7 +12,7 @@ import { IoOpen } from "react-icons/io5"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); -const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialogTitle, imageSrc }) => { +const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialogTitle, imageSrc, artefactID }) => { const [isNavigating, setIsNavigating] = useState(false); const [direction, setDirection] = useState(0); const [isInitialRender, setIsInitialRender] = useState(true); @@ -92,11 +92,11 @@ const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialog debounceTimeout = setTimeout(() => { const scrollLeft = scrollRef.current.scrollLeft; const newIndex = Math.round(scrollLeft / window.innerWidth); - + if (newIndex !== currentIndex && items[newIndex]) { setDialogTitle(items[newIndex].title); } - + // Reset scrolling flag after debounce isScrolling = false; }, 100); // Keep the debounce time reasonable (100ms) @@ -104,7 +104,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialog const scrollEl = scrollRef.current; scrollEl.addEventListener("scroll", handleScroll); - + return () => { scrollEl.removeEventListener("scroll", handleScroll); if (debounceTimeout) clearTimeout(debounceTimeout); @@ -151,13 +151,13 @@ const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialog left: isMobile ? "10px" : isTablet - ? "calc(5vw - 50px)" - : "calc(10vw - 60px)", + ? "calc(5vw - 50px)" + : "calc(10vw - 60px)", right: isMobile ? "10px" : isTablet - ? "calc(5vw - 50px)" - : "calc(10vw - 60px)", + ? "calc(5vw - 50px)" + : "calc(10vw - 60px)", top: "50%", }; @@ -362,15 +362,15 @@ const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialog {item.title} - - navigate(`/studio/${sectionId}/${currentId}`)} cursor={"pointer"}/> + navigate(`/studio/${sectionId}/${currentId}`)} cursor={"pointer"} /> @@ -394,14 +394,14 @@ const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialog {selectedSegment === - "metadata" ? ( + "metadata" ? ( ) : selectedSegment === - "chat" ? ( - + "chat" ? ( + ) : null} @@ -500,15 +500,15 @@ const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialog {title} - - navigate(`/studio/${sectionId}/${currentId}`)} cursor={"pointer"}/> + navigate(`/studio/${sectionId}/${currentId}`)} cursor={"pointer"} /> @@ -532,14 +532,14 @@ const CatalogueItemView = ({ isOpen, onClose, title, sectionId, items, setDialog {selectedSegment === - "metadata" ? ( + "metadata" ? ( ) : selectedSegment === - "chat" ? ( - + "chat" ? ( + ) : null} diff --git a/src/components/Catalogue/itemChat.jsx b/src/components/Catalogue/itemChat.jsx index 415be99..50209ac 100644 --- a/src/components/Catalogue/itemChat.jsx +++ b/src/components/Catalogue/itemChat.jsx @@ -1,5 +1,128 @@ -function ItemChat() { - return
ItemChat
; +import { useState, useEffect, useRef } from "react"; +import { Input, Box, Button, VStack, HStack, Text } from "@chakra-ui/react"; +import { IoIosSend } from "react-icons/io"; +import server, { JSONResponse } from "../../networking"; +import ToastWizard from "../toastWizard"; + +function ItemChat({ artefactID }) { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + const [loading, setLoading] = useState(false); + const messagesEndRef = useRef(null); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + const sendMessage = async () => { + if (!input.trim() || loading) return; + + const userMessageContent = input; + setInput(""); + setLoading(true); + + try { + const response = await server.post('/chatbot/query', { + artefactID: artefactID, + newPrompt: userMessageContent, + history: messages + }) + + if (response.data instanceof JSONResponse) { + if (response.data.isErrorStatus()) { + const errObject = { + response: { + data: response.data + } + }; + throw new Error(errObject); + } + + // Success case + if (response.data?.raw?.history) { + setMessages(response.data.raw.history); + } + } else { + throw new Error("Unexpected response format"); + } + } catch (err) { + if (err.response && err.response.data instanceof JSONResponse) { + console.log("Error response in chatbot query:", err.response.data.fullMessage()); + if (err.response.data.userErrorType()) { + ToastWizard.standard("error", "Chatbot query failed.", err.response.data.message); + } else { + ToastWizard.standard("error", "Chatbot query failed.", "An unexpected error occurred."); + } + } else { + console.log("Unexpected error in chatbot query:", err); + ToastWizard.standard("error", "Chatbot query failed.", "An unexpected error occurred."); + } + } finally { + setLoading(false); + } + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + }; + + return ( + + + {messages.length === 0 ? ( + + No messages yet. Start chatting! + + ) : ( + messages.map((msg, idx) => ( + + + {msg.role === "user" ? "You: " : "Archivus: "} + + {msg.content} + + )) + )} +
+ + + + setInput(e.target.value)} + onKeyDown={handleKeyDown} + disabled={loading} + /> + + + + ); } -export default ItemChat; +export default ItemChat; \ No newline at end of file diff --git a/src/components/Catalogue/section.jsx b/src/components/Catalogue/section.jsx index 0d9bb91..8206c51 100644 --- a/src/components/Catalogue/section.jsx +++ b/src/components/Catalogue/section.jsx @@ -25,6 +25,7 @@ const Section = ({ sectionTitle, sectionId, onItemClick, artefacts = [] }) => { const [atEnd, setAtEnd] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [dialogTitle, setDialogTitle] = useState(""); + const [dialogArtefactID, setDialogArtefactID] = useState(""); const checkScrollEdges = () => { const node = scrollRef.current; @@ -45,10 +46,11 @@ const Section = ({ sectionTitle, sectionId, onItemClick, artefacts = [] }) => { }); }; - const handleCardClick = (title) => { - setDialogTitle(title); + const handleCardClick = (item) => { + setDialogTitle(item.title); + setDialogArtefactID(item.id); setDialogOpen(true); - if (onItemClick) onItemClick(title); + if (onItemClick) onItemClick(item.id); }; useEffect(() => { @@ -129,7 +131,7 @@ const Section = ({ sectionTitle, sectionId, onItemClick, artefacts = [] }) => { flex="0 0 auto" cursor="pointer" borderRadius="md" - onClick={() => handleCardClick(item.title)} + onClick={() => handleCardClick(item)} > { setDialogOpen(false)} + artefactID={dialogArtefactID} title={dialogTitle} sectionId={sectionId} items={items} From 2055d737e150a4a43da559fa3c9161e6814582f6 Mon Sep 17 00:00:00 2001 From: ZacTohZY <234453p@mymail.nyp.edu.sg> Date: Thu, 14 Aug 2025 23:37:00 +0800 Subject: [PATCH 2/3] Modified to create msg object immediately added --- src/components/Catalogue/itemChat.jsx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/Catalogue/itemChat.jsx b/src/components/Catalogue/itemChat.jsx index 50209ac..e21ea9a 100644 --- a/src/components/Catalogue/itemChat.jsx +++ b/src/components/Catalogue/itemChat.jsx @@ -17,14 +17,15 @@ function ItemChat({ artefactID }) { const sendMessage = async () => { if (!input.trim() || loading) return; - const userMessageContent = input; + const userMessage = { role: "user", content: input }; + setMessages(prev => [...prev, userMessage]); setInput(""); setLoading(true); try { const response = await server.post('/chatbot/query', { - artefactID: artefactID, - newPrompt: userMessageContent, + artefactID, + newPrompt: input, history: messages }) @@ -38,10 +39,14 @@ function ItemChat({ artefactID }) { throw new Error(errObject); } - // Success case - if (response.data?.raw?.history) { - setMessages(response.data.raw.history); + if (response.data?.raw?.data?.history) { + setMessages(response.data.raw.data.history); + } else if (response.data?.raw?.data?.response) { + setMessages(prev => [...prev, + { role: "assistant", content: response.data.raw.data.response } + ]); } + } else { throw new Error("Unexpected response format"); } @@ -82,7 +87,7 @@ function ItemChat({ artefactID }) { > {messages.length === 0 ? ( - No messages yet. Start chatting! + Let's discover this artefact together ! ) : ( messages.map((msg, idx) => ( @@ -105,7 +110,7 @@ function ItemChat({ artefactID }) { setInput(e.target.value)} onKeyDown={handleKeyDown} @@ -114,7 +119,7 @@ function ItemChat({ artefactID }) {