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..51d1ebb 100644
--- a/src/components/Catalogue/itemChat.jsx
+++ b/src/components/Catalogue/itemChat.jsx
@@ -1,5 +1,132 @@
-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 userMessage = { role: "user", content: input };
+ setMessages(prev => [...prev, userMessage]);
+ setInput("");
+ setLoading(true);
+
+ try {
+ const response = await server.post('/chatbot/query', {
+ artefactID,
+ newPrompt: input,
+ history: messages
+ })
+
+ if (response.data instanceof JSONResponse) {
+ if (response.data.isErrorStatus()) {
+ const errObject = {
+ response: {
+ data: response.data
+ }
+ };
+ throw new Error(errObject);
+ }
+
+ 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");
+ }
+ } 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 ? (
+
+ Let's discover this artefact together !
+
+ ) : (
+ 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}