From fd35ce2074f2d1860b397478fd7d44f10e7c5297 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 31 Jan 2025 14:42:06 +0000 Subject: [PATCH 01/28] Can send calls to the chatbot locally --- .../AssistantApiHandlers/useAssistantApi.jsx | 23 +++ .../ChatbotInterface.jsx | 137 ++++++++++++++++++ .../syntheticImageDetectionResults.jsx | 29 ++++ src/redux/actions/tools/assistantActions.jsx | 19 +++ src/redux/sagas/assistantSaga.jsx | 81 ++++++++--- 5 files changed, 265 insertions(+), 24 deletions(-) create mode 100644 src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx diff --git a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx index ce9e353f5..9bcf237bd 100644 --- a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx +++ b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx @@ -2,6 +2,7 @@ import axios from "axios"; export default function assistantApiCalls() { const assistantEndpoint = process.env.REACT_APP_ASSISTANT_URL; + const chatbotEndpoint = process.env.REACT_APP_CHATBOT_URL; function handleAssistantError(errorResponse) { if (errorResponse.response) { @@ -22,6 +23,27 @@ export default function assistantApiCalls() { } } + const callChatbot = async (userInput) => { + let chatbotResponse; + try { + // chatbotResponse = await axios.post( + // chatbotEndpoint + "chat/", + // {user_input: userInput} + // ); + chatbotResponse = await axios.get( + chatbotEndpoint + "chat/" + encodeURIComponent(userInput), + ); + } catch (error) { + handleAssistantError(error); + } + + if (chatbotResponse.data.status === "success") { + return chatbotResponse.data; + } else { + throw new Error("assistant_error_server_error"); + } + }; + const callAssistantScraper = async (urlType, userInput) => { let scrapeResult; try { @@ -248,6 +270,7 @@ export default function assistantApiCalls() { }; return { + callChatbot, callAssistantScraper, callSourceCredibilityService, callNamedEntityService, diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx new file mode 100644 index 000000000..6082536cf --- /dev/null +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -0,0 +1,137 @@ +import React, { useEffect, useRef, useState } from "react"; +import GaugeChart from "react-gauge-chart"; +import { useDispatch, useSelector } from "react-redux"; + +import Accordion from "@mui/material/Accordion"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import Alert from "@mui/material/Alert"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CardHeader from "@mui/material/CardHeader"; +import Chip from "@mui/material/Chip"; +import Divider from "@mui/material/Divider"; +import Grid2 from "@mui/material/Grid2"; +import IconButton from "@mui/material/IconButton"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemText from "@mui/material/ListItemText"; +import Stack from "@mui/material/Stack"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import Typography from "@mui/material/Typography"; + +import { Close, Download, ExpandMore } from "@mui/icons-material"; + +import { styled } from "@mui/system"; +import { useTrackEvent } from "Hooks/useAnalytics"; +import { getclientId } from "components/Shared/GoogleAnalytics/MatomoAnalytics"; +import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace"; + +import { submitUserChatbotMessage } from "../../../../redux/actions/tools/assistantActions"; + +const MessageBubble = styled(Box)(({ sent }) => ({ + maxWidth: "70%", + padding: "10px 15px", + borderRadius: sent ? "15px 15px 0 15px" : "15px 15px 15px 0", + backgroundColor: sent ? "#00926c" : "#fff", + color: sent ? "#fff" : "#000", + marginBottom: "10px", + alignSelf: sent ? "flex-end" : "flex-start", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + transition: "transform 0.2s ease", + "&:hover": { + transform: "scale(1.02)", + }, +})); + +const ChatbotInterface = (props) => { + const dispatch = useDispatch(); + const keyword = i18nLoadNamespace( + "components/NavItems/tools/SyntheticImageDetection", + ); + const [formInput, setFormInput] = useState(""); + + const [chatbotMessages, setChatbotMessages] = useState([]); + + const sendMessage = () => { + chatbotMessages.push({ + id: chatbotMessages.length + 1, + sent: 1, + text: formInput, + class: "greeting", + }); + dispatch(submitUserChatbotMessage(formInput)); + chatbotMessages.push({ + id: chatbotMessages.length + 1, + sent: 0, + text: "Hi! I am a chatbot assistant. How can I help you today?", + }); + setFormInput(""); + setChatbotMessages(chatbotMessages); + }; + + return ( +
+ + {/* Conversation */} + + {chatbotMessages.map((msg) => ( + + + {msg.text} + + + + ))} + + + {/* text box */} + + setFormInput(e.target.value)} + data-testid="assistant-chatbot-input" + /> + + {/* submit button */} + + + +
+ ); +}; + +export default ChatbotInterface; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index 1bd130752..f0b4cbba5 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -30,6 +30,7 @@ import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace import CustomAlertScore from "../../../Shared/CustomAlertScore"; import GaugeChartModalExplanation from "../../../Shared/GaugeChartResults/GaugeChartModalExplanation"; import { exportReactElementAsJpg } from "../../../Shared/Utils/htmlUtils"; +import ChatbotInterface from "./ChatbotInterface"; import NddDatagrid from "./NddDatagrid"; import { DETECTION_THRESHOLDS, @@ -295,6 +296,16 @@ const SyntheticImageDetectionResults = ({ ); }; + const [chatbotPanelMessage, setChatbotPanelMessage] = useState( + "synthetic_image_detection_chatbot_hide", + ); + + const handleChatbotChange = () => { + chatbotPanelMessage === "synthetic_image_detection_chatbot_hide" + ? setChatbotPanelMessage("synthetic_image_detection_chatbot") + : setChatbotPanelMessage("synthetic_image_detection_chatbot_hide"); + }; + const keywords = [ "gauge_scale_modal_explanation_rating_1", "gauge_scale_modal_explanation_rating_2", @@ -602,6 +613,24 @@ const SyntheticImageDetectionResults = ({ )} + + + + }> + {keyword(chatbotPanelMessage)} + + + + + + + + + {filteredNddRows && filteredNddRows.length > 0 && ( { }; }; +export const setChatbotResponse = (message, userMessageClass) => { + return { + type: "SET_CHATBOT_RESPONSE", + payload: { + message: message, + userMessageClass: userMessageClass, + }, + }; +}; + export const setScrapedData = ( text, lang, @@ -319,6 +329,15 @@ export const submitInputUrl = (inputUrl) => { }; }; +export const submitUserChatbotMessage = (message) => { + return { + type: "SUBMIT_USER_CHATBOT_MESSAGE", + payload: { + message: message, + }, + }; +}; + export const submitUpload = (contentType) => { return { type: "SUBMIT_UPLOAD", diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index fed1cb051..f5527a228 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -1,6 +1,26 @@ -import uniqWith from "lodash/uniqWith"; import isEqual from "lodash/isEqual"; +import uniqWith from "lodash/uniqWith"; +import { + all, + call, + fork, + put, + select, + take, + takeLatest, +} from "redux-saga/effects"; +import assistantApiCalls from "../../components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi"; +import DBKFApi from "../../components/NavItems/Assistant/AssistantApiHandlers/useDBKFApi"; +import { + CONTENT_TYPE, + KNOWN_LINKS, + KNOWN_LINK_PATTERNS, + NE_SUPPORTED_LANGS, + TYPE_PATTERNS, + matchPattern, + selectCorrectActions, +} from "../../components/NavItems/Assistant/AssistantRuleBook"; import { cleanAssistantState, setAssistantLoading, @@ -11,41 +31,20 @@ import { setImageVideoSelected, setInputSourceCredDetails, setInputUrl, + setMachineGeneratedTextDetails, setNeDetails, setNewsGenreDetails, setNewsTopicDetails, setPersuasionDetails, - setSubjectivityDetails, setPrevFactChecksDetails, - setMachineGeneratedTextDetails, setProcessUrl, setProcessUrlActions, setScrapedData, setSingleMediaPresent, + setSubjectivityDetails, setUrlMode, } from "../actions/tools/assistantActions"; -import { - all, - call, - fork, - put, - select, - take, - takeLatest, -} from "redux-saga/effects"; -import assistantApiCalls from "../../components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi"; -import DBKFApi from "../../components/NavItems/Assistant/AssistantApiHandlers/useDBKFApi"; -import { - CONTENT_TYPE, - KNOWN_LINK_PATTERNS, - KNOWN_LINKS, - matchPattern, - NE_SUPPORTED_LANGS, - selectCorrectActions, - TYPE_PATTERNS, -} from "../../components/NavItems/Assistant/AssistantRuleBook"; - /** * APIs **/ @@ -70,6 +69,10 @@ function* getMediaActionSaga() { ); } +function* getAssistantChatbotSaga() { + yield takeLatest(["SUBMIT_USER_CHATBOT_MESSAGE"], handleAssistantChatbotCall); +} + function* getAssistantScrapeSaga() { yield takeLatest("SUBMIT_INPUT_URL", handleAssistantScrapeCall); } @@ -571,6 +574,35 @@ function* handleNamedEntityCall(action) { } } +function* handleAssistantChatbotCall(action) { + const message = action.payload.message; + + yield put(cleanAssistantState()); + // yield put(setAssistantLoading(true)); + + try { + const chatbotResponse = yield call(assistantApi.callChatbot, message); + console.log(chatbotResponse); + + // yield put(setInputUrl(inputUrl, urlType)); + // yield put( + // setScrapedData( + // filteredSR.urlText, + // filteredSR.textLang, + // filteredSR.linkList, + // filteredSR.imageList, + // filteredSR.videoList, + // filteredSR.urlTextHtmlMap, + // ), + // ); + // yield put(setAssistantLoading(false)); + } catch (error) { + // yield put(setAssistantLoading(false)); + console.log(error); + // yield put(setErrorKey(error.message)); + } +} + function* handleAssistantScrapeCall(action) { let inputUrl = action.payload.inputUrl; @@ -1033,6 +1065,7 @@ export default function* assistantSaga() { fork(getMediaSimilaritySaga), fork(getMediaListSaga), fork(getNamedEntitySaga), + fork(getAssistantChatbotSaga), fork(getAssistantScrapeSaga), fork(getUploadSaga), fork(getNewsTopicSaga), From 6e466c0db00ea2e570e405cb707b98942bbff5bc Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 31 Jan 2025 16:07:12 +0000 Subject: [PATCH 02/28] User messages and chatbot responses --- .../AssistantApiHandlers/useAssistantApi.jsx | 1 + .../ChatbotInterface.jsx | 21 +++++---------- src/redux/actions/tools/assistantActions.jsx | 5 ++-- src/redux/reducers/assistantReducer.jsx | 9 +++++++ src/redux/sagas/assistantSaga.jsx | 27 +++++++------------ 5 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx index 9bcf237bd..b23cad261 100644 --- a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx +++ b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx @@ -40,6 +40,7 @@ export default function assistantApiCalls() { if (chatbotResponse.data.status === "success") { return chatbotResponse.data; } else { + console.log("Chatbot error:", scrapeResult); throw new Error("assistant_error_server_error"); } }; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 6082536cf..7e106521a 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -54,23 +54,16 @@ const ChatbotInterface = (props) => { ); const [formInput, setFormInput] = useState(""); - const [chatbotMessages, setChatbotMessages] = useState([]); + const chatbotMessages = useSelector( + (state) => state.assistant.chatbotMessages, + ); // Access the entire state + + const entireState = useSelector((state) => state); // Access the entire state + console.log(entireState); const sendMessage = () => { - chatbotMessages.push({ - id: chatbotMessages.length + 1, - sent: 1, - text: formInput, - class: "greeting", - }); dispatch(submitUserChatbotMessage(formInput)); - chatbotMessages.push({ - id: chatbotMessages.length + 1, - sent: 0, - text: "Hi! I am a chatbot assistant. How can I help you today?", - }); setFormInput(""); - setChatbotMessages(chatbotMessages); }; return ( @@ -86,7 +79,7 @@ const ChatbotInterface = (props) => { spacing={1} > - {msg.text} + {msg.message} { }; }; -export const setChatbotResponse = (message, userMessageClass) => { +export const addChatbotMessage = (message, userMessageClasses) => { return { - type: "SET_CHATBOT_RESPONSE", + type: "ADD_CHATBOT_MESSAGE", payload: { message: message, - userMessageClass: userMessageClass, }, }; }; diff --git a/src/redux/reducers/assistantReducer.jsx b/src/redux/reducers/assistantReducer.jsx index 5c8a3c09b..431a74151 100644 --- a/src/redux/reducers/assistantReducer.jsx +++ b/src/redux/reducers/assistantReducer.jsx @@ -16,6 +16,8 @@ const defaultState = { processUrlType: null, inputUrlType: null, + chatbotMessages: [], + positiveSourceCred: null, cautionSourceCred: null, mixedSourceCred: null, @@ -85,6 +87,11 @@ const assistantReducer = (state = defaultState, action) => { case "SET_ERROR_KEY": case "SET_PROCESS_URL": case "SET_SCRAPED_DATA": + case "ADD_CHATBOT_MESSAGE": + return { + ...state, + chatbotMessages: [...state.chatbotMessages, action.payload], + }; case "SET_PROCESS_URL_ACTIONS": case "SET_MODE": case "SET_IMAGE_VIDEO_SELECTED": @@ -113,6 +120,8 @@ const assistantReducer = (state = defaultState, action) => { imageVideoSelected: false, singleMediaPresent: null, + chatbotMessages: [], + inputUrl: null, errorKey: null, processUrl: null, diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index f5527a228..22de07da5 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -22,6 +22,7 @@ import { selectCorrectActions, } from "../../components/NavItems/Assistant/AssistantRuleBook"; import { + addChatbotMessage, cleanAssistantState, setAssistantLoading, setDbkfImageMatchDetails, @@ -70,7 +71,7 @@ function* getMediaActionSaga() { } function* getAssistantChatbotSaga() { - yield takeLatest(["SUBMIT_USER_CHATBOT_MESSAGE"], handleAssistantChatbotCall); + yield takeLatest("SUBMIT_USER_CHATBOT_MESSAGE", handleAssistantChatbotCall); } function* getAssistantScrapeSaga() { @@ -578,28 +579,18 @@ function* handleAssistantChatbotCall(action) { const message = action.payload.message; yield put(cleanAssistantState()); - // yield put(setAssistantLoading(true)); + yield put(setAssistantLoading(true)); + yield put(addChatbotMessage(message)); try { const chatbotResponse = yield call(assistantApi.callChatbot, message); - console.log(chatbotResponse); - - // yield put(setInputUrl(inputUrl, urlType)); - // yield put( - // setScrapedData( - // filteredSR.urlText, - // filteredSR.textLang, - // filteredSR.linkList, - // filteredSR.imageList, - // filteredSR.videoList, - // filteredSR.urlTextHtmlMap, - // ), - // ); - // yield put(setAssistantLoading(false)); + + yield put(addChatbotMessage(chatbotResponse.message)); + yield put(setAssistantLoading(false)); } catch (error) { - // yield put(setAssistantLoading(false)); + yield put(setAssistantLoading(false)); console.log(error); - // yield put(setErrorKey(error.message)); + yield put(setErrorKey(error.message)); } } From d37052c026d17247c65db6b2a8096858bb684b1a Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 31 Jan 2025 16:19:31 +0000 Subject: [PATCH 03/28] Now tagged with unique sequential IDs and who sent --- src/redux/actions/tools/assistantActions.jsx | 3 ++- src/redux/reducers/assistantReducer.jsx | 4 +++- src/redux/sagas/assistantSaga.jsx | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/redux/actions/tools/assistantActions.jsx b/src/redux/actions/tools/assistantActions.jsx index a527b0e10..8bb7e7775 100644 --- a/src/redux/actions/tools/assistantActions.jsx +++ b/src/redux/actions/tools/assistantActions.jsx @@ -55,11 +55,12 @@ export const setProcessUrl = (processUrl, processUrlType) => { }; }; -export const addChatbotMessage = (message, userMessageClasses) => { +export const addChatbotMessage = (message, sent) => { return { type: "ADD_CHATBOT_MESSAGE", payload: { message: message, + sent: sent, }, }; }; diff --git a/src/redux/reducers/assistantReducer.jsx b/src/redux/reducers/assistantReducer.jsx index 431a74151..4f9a24f94 100644 --- a/src/redux/reducers/assistantReducer.jsx +++ b/src/redux/reducers/assistantReducer.jsx @@ -88,9 +88,11 @@ const assistantReducer = (state = defaultState, action) => { case "SET_PROCESS_URL": case "SET_SCRAPED_DATA": case "ADD_CHATBOT_MESSAGE": + const message = action.payload; + message.id = state.chatbotMessages.length + 1; return { ...state, - chatbotMessages: [...state.chatbotMessages, action.payload], + chatbotMessages: [...state.chatbotMessages, message], }; case "SET_PROCESS_URL_ACTIONS": case "SET_MODE": diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index 22de07da5..536ccbc8b 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -580,12 +580,12 @@ function* handleAssistantChatbotCall(action) { yield put(cleanAssistantState()); yield put(setAssistantLoading(true)); - yield put(addChatbotMessage(message)); + yield put(addChatbotMessage(message, 1)); try { const chatbotResponse = yield call(assistantApi.callChatbot, message); - yield put(addChatbotMessage(chatbotResponse.message)); + yield put(addChatbotMessage(chatbotResponse.message, 0)); yield put(setAssistantLoading(false)); } catch (error) { yield put(setAssistantLoading(false)); From 2d5339fee4901511f129437cf83eda2af77ef00f Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Mon, 3 Feb 2025 13:54:57 +0000 Subject: [PATCH 04/28] Easier chatbot testing --- .../SyntheticImageDetection/ChatbotInterface.jsx | 6 +----- .../tools/SyntheticImageDetection/index.jsx | 14 ++++++++++++++ src/redux/sagas/assistantSaga.jsx | 14 +++++++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 7e106521a..06d74f666 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -42,9 +42,6 @@ const MessageBubble = styled(Box)(({ sent }) => ({ alignSelf: sent ? "flex-end" : "flex-start", boxShadow: "0 2px 4px rgba(0,0,0,0.1)", transition: "transform 0.2s ease", - "&:hover": { - transform: "scale(1.02)", - }, })); const ChatbotInterface = (props) => { @@ -56,10 +53,9 @@ const ChatbotInterface = (props) => { const chatbotMessages = useSelector( (state) => state.assistant.chatbotMessages, - ); // Access the entire state + ).filter((msg) => msg.message); // Access the entire state const entireState = useSelector((state) => state); // Access the entire state - console.log(entireState); const sendMessage = () => { dispatch(submitUserChatbotMessage(formInput)); diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index ea59fd94c..7f5bdb7b5 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -31,9 +31,12 @@ import useMyStyles from "../../../Shared/MaterialUiStyles/useMyStyles"; import StringFileUploadField from "../../../Shared/StringFileUploadField"; import { isValidUrl } from "../../../Shared/Utils/URLUtils"; import { preprocessFileUpload } from "../../../Shared/Utils/fileUtils"; +import ChatbotInterface from "./ChatbotInterface"; import { syntheticImageDetectionAlgorithms } from "./SyntheticImageDetectionAlgorithms"; import SyntheticImageDetectionResults from "./syntheticImageDetectionResults"; +// Delete me! + const SyntheticImageDetection = () => { const location = useLocation(); const urlParams = new URLSearchParams(location.search); @@ -383,6 +386,17 @@ const SyntheticImageDetection = () => { + + + + + + + + + chatbotResponse.userMessageClasses.includes(keyword), + ) + ) { + console.log("TODO: Hook up to slack"); + } + if (chatbotResponse.userMessageClasses.includes("EXPLAIN")) { + console.log("TODO: Hook up to slack"); + } + yield put(addChatbotMessage(chatbotResponse.message, 0)); yield put(setAssistantLoading(false)); } catch (error) { From 734bb78f3dc4801cd974efcbf19dafe6c555fa38 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 7 Feb 2025 16:26:19 +0000 Subject: [PATCH 05/28] Feedback exports extra functions for the slack integration. --- src/components/Feedback/Feedback.jsx | 102 +++++++++--------- .../ChatbotInterface.jsx | 3 +- src/redux/actions/tools/assistantActions.jsx | 3 +- src/redux/sagas/assistantSaga.jsx | 24 +++-- 4 files changed, 72 insertions(+), 60 deletions(-) diff --git a/src/components/Feedback/Feedback.jsx b/src/components/Feedback/Feedback.jsx index cf42aca11..88e8cb037 100644 --- a/src/components/Feedback/Feedback.jsx +++ b/src/components/Feedback/Feedback.jsx @@ -19,9 +19,58 @@ import QuestionAnswerOutlinedIcon from "@mui/icons-material/QuestionAnswerOutlin import LoadingButton from "@mui/lab/LoadingButton"; import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace"; +const API_URL = process.env.REACT_APP_MY_WEB_HOOK_URL; + +const getFeedbackMessage = (email, message, messageType) => { + if (typeof message !== "string" || typeof messageType !== "string") + throw new Error("Invalid message type"); + + return { + blocks: [ + { + type: "header", + text: { + type: "plain_text", + text: "" + messageType + "", + }, + }, + ...(email + ? [ + { + type: "section", + text: { + type: "mrkdwn", + text: "", + }, + }, + ] + : []), + { + type: "section", + text: { + type: "mrkdwn", + text: "" + message + "", + }, + }, + ], + }; +}; + +const sendToSlack = async (message, messageType) => { + const feedbackMessage = getFeedbackMessage(email, message, messageType); + const response = await fetch(API_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(feedbackMessage), + }); + + if (!response.ok) throw response; + + return response; +}; + const Feedback = () => { const keyword = i18nLoadNamespace("components/FeedBack"); - const API_URL = process.env.REACT_APP_MY_WEB_HOOK_URL; const [isButtonHovered, setIsButtonHovered] = useState(false); const [displayCard, setDisplayCard] = useState(false); @@ -42,56 +91,6 @@ const Feedback = () => { const containerRef = React.useRef(null); - const getFeedbackMessage = (message, messageType) => { - if (typeof message !== "string" || typeof messageType !== "string") - throw new Error("Invalid message type"); - - return { - blocks: [ - { - type: "header", - text: { - type: "plain_text", - text: "" + messageType + "", - }, - }, - ...(email - ? [ - { - type: "section", - text: { - type: "mrkdwn", - text: "", - }, - }, - ] - : []), - { - type: "section", - text: { - type: "mrkdwn", - text: "" + message + "", - }, - }, - ], - }; - }; - - const sendToSlack = async (message, messageType) => { - const feedbackMessage = getFeedbackMessage(message, messageType); - //console.log(feedbackMessage); - //console.log(JSON.stringify(feedbackMessage)); - const response = await fetch(API_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(feedbackMessage), - }); - - if (!response.ok) throw response; - - return response; - }; - const validateEmail = (email) => { if (!email) return true; //allow to proceed if the email is empty const re = /\S+@\S+\.\S+/; //match string@string.string @@ -294,3 +293,4 @@ const Feedback = () => { }; export default Feedback; +export { getFeedbackMessage, sendToSlack }; // Named exports diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 06d74f666..5d16ed1a3 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -54,11 +54,12 @@ const ChatbotInterface = (props) => { const chatbotMessages = useSelector( (state) => state.assistant.chatbotMessages, ).filter((msg) => msg.message); // Access the entire state + const userEmail = useSelector((state) => state.userSession.user.email); const entireState = useSelector((state) => state); // Access the entire state const sendMessage = () => { - dispatch(submitUserChatbotMessage(formInput)); + dispatch(submitUserChatbotMessage(formInput, userEmail)); setFormInput(""); }; diff --git a/src/redux/actions/tools/assistantActions.jsx b/src/redux/actions/tools/assistantActions.jsx index 8bb7e7775..579b65446 100644 --- a/src/redux/actions/tools/assistantActions.jsx +++ b/src/redux/actions/tools/assistantActions.jsx @@ -329,10 +329,11 @@ export const submitInputUrl = (inputUrl) => { }; }; -export const submitUserChatbotMessage = (message) => { +export const submitUserChatbotMessage = (message, email) => { return { type: "SUBMIT_USER_CHATBOT_MESSAGE", payload: { + email: email, message: message, }, }; diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index 04915f2a4..b40d3eabd 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -10,6 +10,10 @@ import { takeLatest, } from "redux-saga/effects"; +import { + getFeedbackMessage, + sendToSlack, +} from "../../components/Feedback/Feedback"; import assistantApiCalls from "../../components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi"; import DBKFApi from "../../components/NavItems/Assistant/AssistantApiHandlers/useDBKFApi"; import { @@ -577,6 +581,7 @@ function* handleNamedEntityCall(action) { function* handleAssistantChatbotCall(action) { const message = action.payload.message; + const email = action.payload.email; yield put(setAssistantLoading(true)); yield put(addChatbotMessage(message, 1)); @@ -586,15 +591,20 @@ function* handleAssistantChatbotCall(action) { console.log(chatbotResponse); - if ( - ["BUG", "FEATURE", "OUTPUT"].some((keyword) => - chatbotResponse.userMessageClasses.includes(keyword), - ) - ) { - console.log("TODO: Hook up to slack"); + if (chatbotResponse.userMessageClasses.includes("BUG")) { + const feedbackMessage = getFeedbackMessage(email, message, "BUG"); + console.log("TODO: Hook BUG up to slack", feedbackMessage); + } + if (chatbotResponse.userMessageClasses.includes("FEATURE")) { + const feedbackMessage = getFeedbackMessage(email, message, "FEATURE"); + console.log("TODO: Hook FEATURE up to slack", feedbackMessage); + } + if (chatbotResponse.userMessageClasses.includes("OUTPUT")) { + const feedbackMessage = getFeedbackMessage(email, message, "IMPROVEMENT"); + console.log("TODO: Hook up OUTPUT to slack", feedbackMessage); } if (chatbotResponse.userMessageClasses.includes("EXPLAIN")) { - console.log("TODO: Hook up to slack"); + console.log("TODO: Hook up to LLM"); } yield put(addChatbotMessage(chatbotResponse.message, 0)); From 3d3b6bae2be5f539e094af433a9b83353d461dd2 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 14 Feb 2025 11:31:53 +0000 Subject: [PATCH 06/28] Chatbot integration to slack now sends a link to the image --- .../AssistantApiHandlers/useAssistantApi.jsx | 14 ++++++-------- .../SyntheticImageDetection/ChatbotInterface.jsx | 7 ++++--- src/redux/actions/tools/assistantActions.jsx | 3 ++- src/redux/sagas/assistantSaga.jsx | 8 +++++++- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx index b23cad261..52416e522 100644 --- a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx +++ b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx @@ -23,16 +23,14 @@ export default function assistantApiCalls() { } } - const callChatbot = async (userInput) => { + const callChatbot = async (userInput, email, archiveURL) => { let chatbotResponse; try { - // chatbotResponse = await axios.post( - // chatbotEndpoint + "chat/", - // {user_input: userInput} - // ); - chatbotResponse = await axios.get( - chatbotEndpoint + "chat/" + encodeURIComponent(userInput), - ); + chatbotResponse = await axios.post(chatbotEndpoint + "chat/", { + message: userInput, + email: email, + archiveURL: archiveURL, + }); } catch (error) { handleAssistantError(error); } diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 5d16ed1a3..0d480b3b4 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -50,16 +50,17 @@ const ChatbotInterface = (props) => { "components/NavItems/tools/SyntheticImageDetection", ); const [formInput, setFormInput] = useState(""); + const archiveURL = useSelector( + (state) => state?.syntheticImageDetection?.duplicates?.archive_url ?? null, + ); const chatbotMessages = useSelector( (state) => state.assistant.chatbotMessages, ).filter((msg) => msg.message); // Access the entire state const userEmail = useSelector((state) => state.userSession.user.email); - const entireState = useSelector((state) => state); // Access the entire state - const sendMessage = () => { - dispatch(submitUserChatbotMessage(formInput, userEmail)); + dispatch(submitUserChatbotMessage(formInput, userEmail, archiveURL)); setFormInput(""); }; diff --git a/src/redux/actions/tools/assistantActions.jsx b/src/redux/actions/tools/assistantActions.jsx index 579b65446..a869183c2 100644 --- a/src/redux/actions/tools/assistantActions.jsx +++ b/src/redux/actions/tools/assistantActions.jsx @@ -329,12 +329,13 @@ export const submitInputUrl = (inputUrl) => { }; }; -export const submitUserChatbotMessage = (message, email) => { +export const submitUserChatbotMessage = (message, email, archiveURL) => { return { type: "SUBMIT_USER_CHATBOT_MESSAGE", payload: { email: email, message: message, + archiveURL: archiveURL, }, }; }; diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index b40d3eabd..85b5c35c6 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -582,12 +582,18 @@ function* handleNamedEntityCall(action) { function* handleAssistantChatbotCall(action) { const message = action.payload.message; const email = action.payload.email; + const archiveURL = action.payload.archiveURL; yield put(setAssistantLoading(true)); yield put(addChatbotMessage(message, 1)); try { - const chatbotResponse = yield call(assistantApi.callChatbot, message); + const chatbotResponse = yield call( + assistantApi.callChatbot, + message, + email, + archiveURL, + ); console.log(chatbotResponse); From 19bed691b36d7e4046866c42973635d7187cbe5f Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Mon, 17 Feb 2025 15:19:27 +0000 Subject: [PATCH 07/28] Integrated slack feedback into frontend --- src/components/Feedback/Feedback.jsx | 15 ++++++++++++--- src/redux/sagas/assistantSaga.jsx | 13 ++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/components/Feedback/Feedback.jsx b/src/components/Feedback/Feedback.jsx index 88e8cb037..d06ac35da 100644 --- a/src/components/Feedback/Feedback.jsx +++ b/src/components/Feedback/Feedback.jsx @@ -21,7 +21,7 @@ import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace const API_URL = process.env.REACT_APP_MY_WEB_HOOK_URL; -const getFeedbackMessage = (email, message, messageType) => { +const getFeedbackMessage = (email, message, messageType, archiveURL = null) => { if (typeof message !== "string" || typeof messageType !== "string") throw new Error("Invalid message type"); @@ -51,12 +51,21 @@ const getFeedbackMessage = (email, message, messageType) => { type: "mrkdwn", text: "" + message + "", }, + ...(archiveURL // Conditionally add the accessory + ? { + accessory: { + type: "image", + image_url: archiveURL, + alt_text: "Problematic image", + }, + } + : {}), // Important: Return an empty object if archiveURL is not defined }, ], }; }; -const sendToSlack = async (message, messageType) => { +const sendToSlack = async (email, message, messageType, archiveURL = null) => { const feedbackMessage = getFeedbackMessage(email, message, messageType); const response = await fetch(API_URL, { method: "POST", @@ -112,7 +121,7 @@ const Feedback = () => { setIsFeedbackSending(true); - await sendToSlack(message, messageType); + await sendToSlack(email, message, messageType); //console.log("submitted"); diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index 85b5c35c6..ce90cf83b 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -595,19 +595,14 @@ function* handleAssistantChatbotCall(action) { archiveURL, ); - console.log(chatbotResponse); - if (chatbotResponse.userMessageClasses.includes("BUG")) { - const feedbackMessage = getFeedbackMessage(email, message, "BUG"); - console.log("TODO: Hook BUG up to slack", feedbackMessage); + sendToSlack(email, message, "BUG", archiveURL); } if (chatbotResponse.userMessageClasses.includes("FEATURE")) { - const feedbackMessage = getFeedbackMessage(email, message, "FEATURE"); - console.log("TODO: Hook FEATURE up to slack", feedbackMessage); + sendToSlack(email, message, "FEATURE", archiveURL); } - if (chatbotResponse.userMessageClasses.includes("OUTPUT")) { - const feedbackMessage = getFeedbackMessage(email, message, "IMPROVEMENT"); - console.log("TODO: Hook up OUTPUT to slack", feedbackMessage); + if (chatbotResponse.userMessageClasses.includes("IMPROVEMENT")) { + sendToSlack(email, message, "IMPROVEMENT", archiveURL); } if (chatbotResponse.userMessageClasses.includes("EXPLAIN")) { console.log("TODO: Hook up to LLM"); From d0051c45295fcd941728be11feba19d3fcd1ad7e Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 19 Feb 2025 10:14:26 +0000 Subject: [PATCH 08/28] Fixed slack integration problem --- src/components/Feedback/Feedback.jsx | 9 +++++++-- src/redux/sagas/assistantSaga.jsx | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/Feedback/Feedback.jsx b/src/components/Feedback/Feedback.jsx index d06ac35da..8f318c128 100644 --- a/src/components/Feedback/Feedback.jsx +++ b/src/components/Feedback/Feedback.jsx @@ -55,7 +55,7 @@ const getFeedbackMessage = (email, message, messageType, archiveURL = null) => { ? { accessory: { type: "image", - image_url: archiveURL, + image_url: archiveURL.trim(), alt_text: "Problematic image", }, } @@ -66,7 +66,12 @@ const getFeedbackMessage = (email, message, messageType, archiveURL = null) => { }; const sendToSlack = async (email, message, messageType, archiveURL = null) => { - const feedbackMessage = getFeedbackMessage(email, message, messageType); + const feedbackMessage = getFeedbackMessage( + email, + message, + messageType, + archiveURL, + ); const response = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index ce90cf83b..c7631bd98 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -594,15 +594,16 @@ function* handleAssistantChatbotCall(action) { email, archiveURL, ); + const suffix = "\n[Sent via the chatbot assistant]"; if (chatbotResponse.userMessageClasses.includes("BUG")) { - sendToSlack(email, message, "BUG", archiveURL); + sendToSlack(email, message + suffix, "BUG", archiveURL); } if (chatbotResponse.userMessageClasses.includes("FEATURE")) { - sendToSlack(email, message, "FEATURE", archiveURL); + sendToSlack(email, message + suffix, "FEATURE", archiveURL); } if (chatbotResponse.userMessageClasses.includes("IMPROVEMENT")) { - sendToSlack(email, message, "IMPROVEMENT", archiveURL); + sendToSlack(email, message + suffix, "IMPROVEMENT", archiveURL); } if (chatbotResponse.userMessageClasses.includes("EXPLAIN")) { console.log("TODO: Hook up to LLM"); From f8bc1cd51ba7dd87b0c50588e3d32e299cbe8b5a Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Thu, 27 Feb 2025 14:38:36 +0000 Subject: [PATCH 09/28] Commented out slack integration so as not to spam when debugging --- src/redux/sagas/assistantSaga.jsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index c7631bd98..55d793f70 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -596,15 +596,16 @@ function* handleAssistantChatbotCall(action) { ); const suffix = "\n[Sent via the chatbot assistant]"; - if (chatbotResponse.userMessageClasses.includes("BUG")) { - sendToSlack(email, message + suffix, "BUG", archiveURL); - } - if (chatbotResponse.userMessageClasses.includes("FEATURE")) { - sendToSlack(email, message + suffix, "FEATURE", archiveURL); - } - if (chatbotResponse.userMessageClasses.includes("IMPROVEMENT")) { - sendToSlack(email, message + suffix, "IMPROVEMENT", archiveURL); - } + // UNCOMMENT BEFORE MERGING + // if (chatbotResponse.userMessageClasses.includes("BUG")) { + // sendToSlack(email, message + suffix, "BUG", archiveURL); + // } + // if (chatbotResponse.userMessageClasses.includes("FEATURE")) { + // sendToSlack(email, message + suffix, "FEATURE", archiveURL); + // } + // if (chatbotResponse.userMessageClasses.includes("IMPROVEMENT")) { + // sendToSlack(email, message + suffix, "IMPROVEMENT", archiveURL); + // } if (chatbotResponse.userMessageClasses.includes("EXPLAIN")) { console.log("TODO: Hook up to LLM"); } @@ -612,8 +613,8 @@ function* handleAssistantChatbotCall(action) { yield put(addChatbotMessage(chatbotResponse.message, 0)); yield put(setAssistantLoading(false)); } catch (error) { - yield put(setAssistantLoading(false)); console.log(error); + yield put(setAssistantLoading(false)); yield put(setErrorKey(error.message)); } } From e66e44df203c561a67186fc05851956b8a745bb1 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 7 Mar 2025 11:48:22 +0000 Subject: [PATCH 10/28] Removed direct call in favour of going through backend like the other GATE services --- .../Assistant/AssistantApiHandlers/useAssistantApi.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx index 52416e522..4c4787ca8 100644 --- a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx +++ b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx @@ -26,7 +26,7 @@ export default function assistantApiCalls() { const callChatbot = async (userInput, email, archiveURL) => { let chatbotResponse; try { - chatbotResponse = await axios.post(chatbotEndpoint + "chat/", { + chatbotResponse = await axios.post(assistantEndpoint + "gcloud/chatbot", { message: userInput, email: email, archiveURL: archiveURL, @@ -38,7 +38,7 @@ export default function assistantApiCalls() { if (chatbotResponse.data.status === "success") { return chatbotResponse.data; } else { - console.log("Chatbot error:", scrapeResult); + console.log("Chatbot error:", chatbotResponse); throw new Error("assistant_error_server_error"); } }; From 4836d578e43f229d0b86ad42a5d2e309813b26de Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 11 Mar 2025 11:01:47 +0000 Subject: [PATCH 11/28] Removed TODO comment --- src/redux/sagas/assistantSaga.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index 55d793f70..7b5ef20b6 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -606,9 +606,6 @@ function* handleAssistantChatbotCall(action) { // if (chatbotResponse.userMessageClasses.includes("IMPROVEMENT")) { // sendToSlack(email, message + suffix, "IMPROVEMENT", archiveURL); // } - if (chatbotResponse.userMessageClasses.includes("EXPLAIN")) { - console.log("TODO: Hook up to LLM"); - } yield put(addChatbotMessage(chatbotResponse.message, 0)); yield put(setAssistantLoading(false)); From ea0df08360c3e16c5a81198b1261b6fbd342b754 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 12 Mar 2025 08:46:35 +0000 Subject: [PATCH 12/28] Added brackets to reducer --- src/redux/reducers/assistantReducer.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/redux/reducers/assistantReducer.jsx b/src/redux/reducers/assistantReducer.jsx index 4f9a24f94..6d731654b 100644 --- a/src/redux/reducers/assistantReducer.jsx +++ b/src/redux/reducers/assistantReducer.jsx @@ -87,13 +87,14 @@ const assistantReducer = (state = defaultState, action) => { case "SET_ERROR_KEY": case "SET_PROCESS_URL": case "SET_SCRAPED_DATA": - case "ADD_CHATBOT_MESSAGE": + case "ADD_CHATBOT_MESSAGE": { const message = action.payload; message.id = state.chatbotMessages.length + 1; return { ...state, chatbotMessages: [...state.chatbotMessages, message], }; + } case "SET_PROCESS_URL_ACTIONS": case "SET_MODE": case "SET_IMAGE_VIDEO_SELECTED": From 04095205086e829c7ef3b972a4ae400ef97a643f Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 12 Mar 2025 09:06:53 +0000 Subject: [PATCH 13/28] Tidied up imports in chatbot interface --- .../ChatbotInterface.jsx | 20 ------------------- .../tools/SyntheticImageDetection/index.jsx | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 0d480b3b4..7aedc57c0 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -1,33 +1,13 @@ import React, { useEffect, useRef, useState } from "react"; -import GaugeChart from "react-gauge-chart"; import { useDispatch, useSelector } from "react-redux"; -import Accordion from "@mui/material/Accordion"; -import AccordionDetails from "@mui/material/AccordionDetails"; -import AccordionSummary from "@mui/material/AccordionSummary"; -import Alert from "@mui/material/Alert"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; -import Card from "@mui/material/Card"; -import CardContent from "@mui/material/CardContent"; -import CardHeader from "@mui/material/CardHeader"; -import Chip from "@mui/material/Chip"; -import Divider from "@mui/material/Divider"; -import Grid2 from "@mui/material/Grid2"; -import IconButton from "@mui/material/IconButton"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemText from "@mui/material/ListItemText"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; -import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; -import { Close, Download, ExpandMore } from "@mui/icons-material"; - import { styled } from "@mui/system"; -import { useTrackEvent } from "Hooks/useAnalytics"; -import { getclientId } from "components/Shared/GoogleAnalytics/MatomoAnalytics"; import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace"; import { submitUserChatbotMessage } from "../../../../redux/actions/tools/assistantActions"; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index 7b6a7b4b1..9809d0433 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -396,7 +396,7 @@ const SyntheticImageDetection = () => { - + Date: Wed, 12 Mar 2025 15:50:58 +0000 Subject: [PATCH 14/28] Changed translation keywords --- .../SyntheticImageDetection/ChatbotInterface.jsx | 2 +- .../syntheticImageDetectionResults.jsx | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 7aedc57c0..210ad9140 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -97,7 +97,7 @@ const ChatbotInterface = (props) => { sendMessage(); }} > - {keyword("button_submit")} + {keyword("submit_button")} diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index d1bb3ebca..1faa9de51 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -296,14 +296,13 @@ const SyntheticImageDetectionResults = ({ ); }; - const [chatbotPanelMessage, setChatbotPanelMessage] = useState( - "synthetic_image_detection_chatbot_hide", - ); + const [chatbotPanelMessage, setChatbotPanelMessage] = + useState("hide_chatbot"); const handleChatbotChange = () => { - chatbotPanelMessage === "synthetic_image_detection_chatbot_hide" - ? setChatbotPanelMessage("synthetic_image_detection_chatbot") - : setChatbotPanelMessage("synthetic_image_detection_chatbot_hide"); + chatbotPanelMessage === "hide_chatbot" + ? setChatbotPanelMessage("show_chatbot") + : setChatbotPanelMessage("hide_chatbot"); }; const keywords = [ From d0a435b8be456822dff9c541faf42e5ba46baf78 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 21 Mar 2025 09:51:08 +0000 Subject: [PATCH 15/28] Chatbot session IDs --- .../AssistantApiHandlers/useAssistantApi.jsx | 3 ++- .../ChatbotInterface.jsx | 18 ++++++++++-------- .../tools/SyntheticImageDetection/index.jsx | 4 +--- .../syntheticImageDetectionResults.jsx | 11 ++++++----- src/redux/actions/tools/assistantActions.jsx | 8 +++++++- src/redux/sagas/assistantSaga.jsx | 3 ++- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx index 4c4787ca8..ebb3f567c 100644 --- a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx +++ b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx @@ -23,13 +23,14 @@ export default function assistantApiCalls() { } } - const callChatbot = async (userInput, email, archiveURL) => { + const callChatbot = async (sessionID, userInput, email, archiveURL) => { let chatbotResponse; try { chatbotResponse = await axios.post(assistantEndpoint + "gcloud/chatbot", { message: userInput, email: email, archiveURL: archiveURL, + sessionID: sessionID, }); } catch (error) { handleAssistantError(error); diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 210ad9140..e2075f58e 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -9,6 +9,7 @@ import Typography from "@mui/material/Typography"; import { styled } from "@mui/system"; import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace"; +import { v4 as uuidv4 } from "uuid"; import { submitUserChatbotMessage } from "../../../../redux/actions/tools/assistantActions"; @@ -26,9 +27,7 @@ const MessageBubble = styled(Box)(({ sent }) => ({ const ChatbotInterface = (props) => { const dispatch = useDispatch(); - const keyword = i18nLoadNamespace( - "components/NavItems/tools/SyntheticImageDetection", - ); + const keyword = i18nLoadNamespace("components/Shared/chatbot"); const [formInput, setFormInput] = useState(""); const archiveURL = useSelector( (state) => state?.syntheticImageDetection?.duplicates?.archive_url ?? null, @@ -38,9 +37,12 @@ const ChatbotInterface = (props) => { (state) => state.assistant.chatbotMessages, ).filter((msg) => msg.message); // Access the entire state const userEmail = useSelector((state) => state.userSession.user.email); + const [sessionID, setSessionID] = useState(uuidv4()); const sendMessage = () => { - dispatch(submitUserChatbotMessage(formInput, userEmail, archiveURL)); + dispatch( + submitUserChatbotMessage(sessionID, formInput, userEmail, archiveURL), + ); setFormInput(""); }; @@ -77,13 +79,13 @@ const ChatbotInterface = (props) => { setFormInput(e.target.value)} - data-testid="assistant-chatbot-input" + data-testid="chatbot-input" /> {/* submit button */} @@ -91,13 +93,13 @@ const ChatbotInterface = (props) => { type="submit" variant="contained" color="primary" - data-testid="assistant-url-selected-analyse-btn" + data-testid="chatbot-submit-btn" onClick={(e) => { e.preventDefault(); sendMessage(); }} > - {keyword("submit_button")} + {keyword("chatbot_submit_button")} diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index 9809d0433..a7c244eac 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -35,8 +35,6 @@ import ChatbotInterface from "./ChatbotInterface"; import { syntheticImageDetectionAlgorithms } from "./SyntheticImageDetectionAlgorithms"; import SyntheticImageDetectionResults from "./syntheticImageDetectionResults"; -// Delete me! - const SyntheticImageDetection = () => { const location = useLocation(); const urlParams = new URLSearchParams(location.search); @@ -387,7 +385,7 @@ const SyntheticImageDetection = () => { diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index 1faa9de51..3044eb3bf 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -296,13 +296,14 @@ const SyntheticImageDetectionResults = ({ ); }; - const [chatbotPanelMessage, setChatbotPanelMessage] = - useState("hide_chatbot"); + const [chatbotPanelMessage, setChatbotPanelMessage] = useState( + "synthetic_image_detection_hide_chatbot", + ); const handleChatbotChange = () => { - chatbotPanelMessage === "hide_chatbot" - ? setChatbotPanelMessage("show_chatbot") - : setChatbotPanelMessage("hide_chatbot"); + chatbotPanelMessage === "synthetic_image_detection_hide_chatbot" + ? setChatbotPanelMessage("synthetic_image_detection_show_chatbot") + : setChatbotPanelMessage("synthetic_image_detection_hide_chatbot"); }; const keywords = [ diff --git a/src/redux/actions/tools/assistantActions.jsx b/src/redux/actions/tools/assistantActions.jsx index a869183c2..c83c825a2 100644 --- a/src/redux/actions/tools/assistantActions.jsx +++ b/src/redux/actions/tools/assistantActions.jsx @@ -329,13 +329,19 @@ export const submitInputUrl = (inputUrl) => { }; }; -export const submitUserChatbotMessage = (message, email, archiveURL) => { +export const submitUserChatbotMessage = ( + sessionID, + message, + email, + archiveURL, +) => { return { type: "SUBMIT_USER_CHATBOT_MESSAGE", payload: { email: email, message: message, archiveURL: archiveURL, + sessionID: sessionID, }, }; }; diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index 6fa62eca5..2ff9cc5c0 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -665,13 +665,14 @@ function* handleAssistantChatbotCall(action) { const message = action.payload.message; const email = action.payload.email; const archiveURL = action.payload.archiveURL; + const sessionID = action.payload.sessionID; - yield put(setAssistantLoading(true)); yield put(addChatbotMessage(message, 1)); try { const chatbotResponse = yield call( assistantApi.callChatbot, + sessionID, message, email, archiveURL, From 42e9773117390528d8c52f1b32120632b013397e Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 21 Mar 2025 11:51:54 +0000 Subject: [PATCH 16/28] Session removal logic --- .../tools/SyntheticImageDetection/ChatbotInterface.jsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index e2075f58e..72daf1309 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -46,6 +46,15 @@ const ChatbotInterface = (props) => { setFormInput(""); }; + // Detect when the tab is closed and send a special request to the chatbot to clear the session history + useEffect(() => { + return () => { + window.addEventListener("beforeunload", function (e) { + dispatch(submitUserChatbotMessage(sessionID, null, null, null)); + }); + }; + }); + return (
From e76179d350499675c36cafadcdaa897f5388fb7e Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 25 Mar 2025 12:20:30 +0000 Subject: [PATCH 17/28] Clear chatbot messages --- .../ChatbotInterface.jsx | 31 ++++++++++++++++++- src/redux/actions/tools/assistantActions.jsx | 6 ++++ src/redux/reducers/assistantReducer.jsx | 6 ++++ src/redux/sagas/assistantSaga.jsx | 1 + 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 72daf1309..0998bcc45 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -5,13 +5,19 @@ import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; +import RestartAltIcon from "@mui/icons-material/RestartAlt"; + import { styled } from "@mui/system"; import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace"; import { v4 as uuidv4 } from "uuid"; -import { submitUserChatbotMessage } from "../../../../redux/actions/tools/assistantActions"; +import { + clearChatbotMessages, + submitUserChatbotMessage, +} from "../../../../redux/actions/tools/assistantActions"; const MessageBubble = styled(Box)(({ sent }) => ({ maxWidth: "70%", @@ -46,6 +52,13 @@ const ChatbotInterface = (props) => { setFormInput(""); }; + const resetChatbot = () => { + dispatch(clearChatbotMessages()); + setSessionID(uuidv4()); + setFormInput(""); + console.log(chatbotMessages); + }; + // Detect when the tab is closed and send a special request to the chatbot to clear the session history useEffect(() => { return () => { @@ -85,6 +98,22 @@ const ChatbotInterface = (props) => { justifyContent="flex-start" alignItems="center" > + { }; }; +export const clearChatbotMessages = (message, sent) => { + return { + type: "CLEAR_CHATBOT_MESSAGES", + }; +}; + export const setScrapedData = ( text, lang, diff --git a/src/redux/reducers/assistantReducer.jsx b/src/redux/reducers/assistantReducer.jsx index 6d731654b..42482aab4 100644 --- a/src/redux/reducers/assistantReducer.jsx +++ b/src/redux/reducers/assistantReducer.jsx @@ -95,6 +95,12 @@ const assistantReducer = (state = defaultState, action) => { chatbotMessages: [...state.chatbotMessages, message], }; } + case "CLEAR_CHATBOT_MESSAGES": { + return { + ...state, + chatbotMessages: [], + }; + } case "SET_PROCESS_URL_ACTIONS": case "SET_MODE": case "SET_IMAGE_VIDEO_SELECTED": diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index 2ff9cc5c0..49e77d024 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -28,6 +28,7 @@ import { import { addChatbotMessage, cleanAssistantState, + clearChatbotMessages, setAssistantLoading, setDbkfImageMatchDetails, setDbkfTextMatchDetails, From 1dd7095d40b1c1abe0a3f75871406eb4380a48aa Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 25 Mar 2025 15:24:09 +0000 Subject: [PATCH 18/28] Removed unnecessary payload from user chatbot messages --- .../NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx | 2 -- src/redux/actions/tools/assistantActions.jsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx index 0b40c1a54..b7a6a7480 100644 --- a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx +++ b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx @@ -28,8 +28,6 @@ export default function assistantApiCalls() { try { chatbotResponse = await axios.post(assistantEndpoint + "gcloud/chatbot", { message: userInput, - email: email, - archiveURL: archiveURL, sessionID: sessionID, }); } catch (error) { diff --git a/src/redux/actions/tools/assistantActions.jsx b/src/redux/actions/tools/assistantActions.jsx index 981d2e3d2..f4ee3df79 100644 --- a/src/redux/actions/tools/assistantActions.jsx +++ b/src/redux/actions/tools/assistantActions.jsx @@ -372,9 +372,7 @@ export const submitUserChatbotMessage = ( return { type: "SUBMIT_USER_CHATBOT_MESSAGE", payload: { - email: email, message: message, - archiveURL: archiveURL, sessionID: sessionID, }, }; From d142bc2aa91d5a9ed2758b639171f0d6b9c133ad Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 25 Mar 2025 16:00:18 +0000 Subject: [PATCH 19/28] Clear the session history in the chatbot backend --- .../tools/SyntheticImageDetection/ChatbotInterface.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 0998bcc45..60287bd1c 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -54,9 +54,10 @@ const ChatbotInterface = (props) => { const resetChatbot = () => { dispatch(clearChatbotMessages()); + // Clear the session history in the chatbot backend + dispatch(submitUserChatbotMessage(sessionID, null, null, null)); setSessionID(uuidv4()); setFormInput(""); - console.log(chatbotMessages); }; // Detect when the tab is closed and send a special request to the chatbot to clear the session history From 230f1c4ab1b7f9b0dd72b1fd112cac90191dfa55 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 15 Apr 2025 10:59:25 +0100 Subject: [PATCH 20/28] Now passing through the tool and result --- .../AssistantApiHandlers/useAssistantApi.jsx | 11 ++++++++++- .../SyntheticImageDetection/ChatbotInterface.jsx | 15 +++++++++++---- .../tools/SyntheticImageDetection/index.jsx | 2 +- .../syntheticImageDetectionResults.jsx | 5 ++++- src/redux/actions/tools/assistantActions.jsx | 12 +++++++++--- src/redux/sagas/assistantSaga.jsx | 4 ++++ 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx index b7a6a7480..34328fef3 100644 --- a/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx +++ b/src/components/NavItems/Assistant/AssistantApiHandlers/useAssistantApi.jsx @@ -23,12 +23,21 @@ export default function assistantApiCalls() { } } - const callChatbot = async (sessionID, userInput, email, archiveURL) => { + const callChatbot = async ( + sessionID, + userInput = null, + email = null, + archiveURL = null, + tool = null, + result = null, + ) => { let chatbotResponse; try { chatbotResponse = await axios.post(assistantEndpoint + "gcloud/chatbot", { message: userInput, sessionID: sessionID, + tool: tool, + result: result, }); } catch (error) { handleAssistantError(error); diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx index 60287bd1c..e450c1865 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx @@ -31,7 +31,7 @@ const MessageBubble = styled(Box)(({ sent }) => ({ transition: "transform 0.2s ease", })); -const ChatbotInterface = (props) => { +const ChatbotInterface = ({ tool, result }) => { const dispatch = useDispatch(); const keyword = i18nLoadNamespace("components/Shared/chatbot"); const [formInput, setFormInput] = useState(""); @@ -47,7 +47,14 @@ const ChatbotInterface = (props) => { const sendMessage = () => { dispatch( - submitUserChatbotMessage(sessionID, formInput, userEmail, archiveURL), + submitUserChatbotMessage( + sessionID, + formInput, + userEmail, + archiveURL, + tool, + result, + ), ); setFormInput(""); }; @@ -55,7 +62,7 @@ const ChatbotInterface = (props) => { const resetChatbot = () => { dispatch(clearChatbotMessages()); // Clear the session history in the chatbot backend - dispatch(submitUserChatbotMessage(sessionID, null, null, null)); + dispatch(submitUserChatbotMessage(sessionID)); setSessionID(uuidv4()); setFormInput(""); }; @@ -64,7 +71,7 @@ const ChatbotInterface = (props) => { useEffect(() => { return () => { window.addEventListener("beforeunload", function (e) { - dispatch(submitUserChatbotMessage(sessionID, null, null, null)); + dispatch(submitUserChatbotMessage(sessionID)); }); }; }); diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index a7c244eac..63b02138b 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -390,7 +390,7 @@ const SyntheticImageDetection = () => { /> - + diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index 4a9abf0d6..c0c7e93e3 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -631,7 +631,10 @@ const SyntheticImageDetectionResults = ({ - + diff --git a/src/redux/actions/tools/assistantActions.jsx b/src/redux/actions/tools/assistantActions.jsx index f4ee3df79..8f5217833 100644 --- a/src/redux/actions/tools/assistantActions.jsx +++ b/src/redux/actions/tools/assistantActions.jsx @@ -365,15 +365,21 @@ export const submitInputUrl = (inputUrl) => { export const submitUserChatbotMessage = ( sessionID, - message, - email, - archiveURL, + message = null, + email = null, + archiveURL = null, + tool = null, + result = null, ) => { return { type: "SUBMIT_USER_CHATBOT_MESSAGE", payload: { message: message, sessionID: sessionID, + email: email, + archiveURL: archiveURL, + tool: tool, + result: result, }, }; }; diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index 1d0f82ff1..b0979d670 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -676,6 +676,8 @@ function* handleAssistantChatbotCall(action) { const email = action.payload.email; const archiveURL = action.payload.archiveURL; const sessionID = action.payload.sessionID; + const tool = action.payload.tool; + const result = action.payload.result; yield put(addChatbotMessage(message, 1)); @@ -686,6 +688,8 @@ function* handleAssistantChatbotCall(action) { message, email, archiveURL, + tool, + result, ); const suffix = "\n[Sent via the chatbot assistant]"; From 9a6b6f5a118c9a9285f4e47b27cc1c8df947ddbd Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 15 Apr 2025 11:30:16 +0100 Subject: [PATCH 21/28] Moved ChatbotInterface into Assistant --- .../SyntheticImageDetection => Assistant}/ChatbotInterface.jsx | 2 +- src/components/NavItems/tools/SyntheticImageDetection/index.jsx | 2 +- .../SyntheticImageDetection/syntheticImageDetectionResults.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/components/NavItems/{tools/SyntheticImageDetection => Assistant}/ChatbotInterface.jsx (98%) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx b/src/components/NavItems/Assistant/ChatbotInterface.jsx similarity index 98% rename from src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx rename to src/components/NavItems/Assistant/ChatbotInterface.jsx index e450c1865..ee3cf0cab 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/ChatbotInterface.jsx +++ b/src/components/NavItems/Assistant/ChatbotInterface.jsx @@ -17,7 +17,7 @@ import { v4 as uuidv4 } from "uuid"; import { clearChatbotMessages, submitUserChatbotMessage, -} from "../../../../redux/actions/tools/assistantActions"; +} from "../../../redux/actions/tools/assistantActions"; const MessageBubble = styled(Box)(({ sent }) => ({ maxWidth: "70%", diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index 63b02138b..f9577cb5a 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -31,7 +31,7 @@ import useMyStyles from "../../../Shared/MaterialUiStyles/useMyStyles"; import StringFileUploadField from "../../../Shared/StringFileUploadField"; import { isValidUrl } from "../../../Shared/Utils/URLUtils"; import { preprocessFileUpload } from "../../../Shared/Utils/fileUtils"; -import ChatbotInterface from "./ChatbotInterface"; +import ChatbotInterface from "../../Assistant/ChatbotInterface"; import { syntheticImageDetectionAlgorithms } from "./SyntheticImageDetectionAlgorithms"; import SyntheticImageDetectionResults from "./syntheticImageDetectionResults"; diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index c0c7e93e3..3a4bcddd8 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -31,7 +31,7 @@ import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace import CustomAlertScore from "../../../Shared/CustomAlertScore"; import GaugeChartModalExplanation from "../../../Shared/GaugeChartResults/GaugeChartModalExplanation"; import { exportReactElementAsJpg } from "../../../Shared/Utils/htmlUtils"; -import ChatbotInterface from "./ChatbotInterface"; +import ChatbotInterface from "../../Assistant/ChatbotInterface"; import NddDatagrid from "./NddDatagrid"; import { DETECTION_THRESHOLDS, From e2e14a8f38f1fedf5af04de1aecf96d992355a12 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 15 Apr 2025 15:05:56 +0100 Subject: [PATCH 22/28] Removed console.log --- .../SyntheticImageDetection/syntheticImageDetectionResults.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx index 3a4bcddd8..7db63ab7a 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/syntheticImageDetectionResults.jsx @@ -216,8 +216,6 @@ const SyntheticImageDetectionResults = ({ return scoreB - scoreA; }); - console.log(res); - const hasResultError = () => { for (const algorithm of res) { if (algorithm.isError) return true; From 7ebc7603e2745cbcdac589611bc09792d58557b1 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 6 Jun 2025 09:45:02 +0100 Subject: [PATCH 23/28] Only sending tool and result if changed --- .../NavItems/Assistant/ChatbotInterface.jsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/NavItems/Assistant/ChatbotInterface.jsx b/src/components/NavItems/Assistant/ChatbotInterface.jsx index ee3cf0cab..e4622efa8 100644 --- a/src/components/NavItems/Assistant/ChatbotInterface.jsx +++ b/src/components/NavItems/Assistant/ChatbotInterface.jsx @@ -44,6 +44,13 @@ const ChatbotInterface = ({ tool, result }) => { ).filter((msg) => msg.message); // Access the entire state const userEmail = useSelector((state) => state.userSession.user.email); const [sessionID, setSessionID] = useState(uuidv4()); + const [previousResult, setPreviousResult] = useState(null); + const [sendResult, setSendResult] = useState(true); + + if (result != previousResult) { + setPreviousResult(result); + setSendResult(true); + } const sendMessage = () => { dispatch( @@ -52,11 +59,12 @@ const ChatbotInterface = ({ tool, result }) => { formInput, userEmail, archiveURL, - tool, - result, + sendResult ? tool : null, + sendResult ? result : null, ), ); setFormInput(""); + setSendResult(false); }; const resetChatbot = () => { @@ -65,6 +73,8 @@ const ChatbotInterface = ({ tool, result }) => { dispatch(submitUserChatbotMessage(sessionID)); setSessionID(uuidv4()); setFormInput(""); + setSendResult(true); + setPreviousResult(result); }; // Detect when the tab is closed and send a special request to the chatbot to clear the session history @@ -72,6 +82,10 @@ const ChatbotInterface = ({ tool, result }) => { return () => { window.addEventListener("beforeunload", function (e) { dispatch(submitUserChatbotMessage(sessionID)); + if (sessionID) { + dispatch(submitUserChatbotMessage(sessionID)); + setSessionID(null); + } }); }; }); From 7c7c939536b1c14844b865380ce71c7d7753b613 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 6 Jun 2025 13:34:02 +0100 Subject: [PATCH 24/28] Chatbot reducer no longer breaks the assistant page --- src/redux/reducers/assistantReducer.jsx | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/redux/reducers/assistantReducer.jsx b/src/redux/reducers/assistantReducer.jsx index d7657c02e..479fc4e10 100644 --- a/src/redux/reducers/assistantReducer.jsx +++ b/src/redux/reducers/assistantReducer.jsx @@ -98,20 +98,6 @@ const assistantReducer = (state = defaultState, action) => { case "SET_ERROR_KEY": case "SET_PROCESS_URL": case "SET_SCRAPED_DATA": - case "ADD_CHATBOT_MESSAGE": { - const message = action.payload; - message.id = state.chatbotMessages.length + 1; - return { - ...state, - chatbotMessages: [...state.chatbotMessages, message], - }; - } - case "CLEAR_CHATBOT_MESSAGES": { - return { - ...state, - chatbotMessages: [], - }; - } case "SET_PROCESS_URL_ACTIONS": case "SET_MODE": case "SET_IMAGE_VIDEO_SELECTED": @@ -135,7 +121,20 @@ const assistantReducer = (state = defaultState, action) => { case "SET_ASSURANCE_EXPANDED": case "SET_STATE_EXPANDED": return Object.assign({}, state, action.payload); - + case "ADD_CHATBOT_MESSAGE": { + const message = action.payload; + message.id = state.chatbotMessages.length + 1; + return { + ...state, + chatbotMessages: [...state.chatbotMessages, message], + }; + } + case "CLEAR_CHATBOT_MESSAGES": { + return { + ...state, + chatbotMessages: [], + }; + } case "CLEAN_STATE": return { ...state, From 45f5965e726705968b95a630f50bc683c6e37401 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 10 Jun 2025 11:34:33 +0100 Subject: [PATCH 25/28] Persistant chatbot session ID accross images --- src/components/NavItems/Assistant/ChatbotInterface.jsx | 4 +--- src/redux/reducers/assistantReducer.jsx | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/NavItems/Assistant/ChatbotInterface.jsx b/src/components/NavItems/Assistant/ChatbotInterface.jsx index e4622efa8..00dfcc8d6 100644 --- a/src/components/NavItems/Assistant/ChatbotInterface.jsx +++ b/src/components/NavItems/Assistant/ChatbotInterface.jsx @@ -12,7 +12,6 @@ import RestartAltIcon from "@mui/icons-material/RestartAlt"; import { styled } from "@mui/system"; import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace"; -import { v4 as uuidv4 } from "uuid"; import { clearChatbotMessages, @@ -42,8 +41,8 @@ const ChatbotInterface = ({ tool, result }) => { const chatbotMessages = useSelector( (state) => state.assistant.chatbotMessages, ).filter((msg) => msg.message); // Access the entire state + const sessionID = useSelector((state) => state.assistant.chatbotSessionID); const userEmail = useSelector((state) => state.userSession.user.email); - const [sessionID, setSessionID] = useState(uuidv4()); const [previousResult, setPreviousResult] = useState(null); const [sendResult, setSendResult] = useState(true); @@ -71,7 +70,6 @@ const ChatbotInterface = ({ tool, result }) => { dispatch(clearChatbotMessages()); // Clear the session history in the chatbot backend dispatch(submitUserChatbotMessage(sessionID)); - setSessionID(uuidv4()); setFormInput(""); setSendResult(true); setPreviousResult(result); diff --git a/src/redux/reducers/assistantReducer.jsx b/src/redux/reducers/assistantReducer.jsx index 479fc4e10..b1f12205b 100644 --- a/src/redux/reducers/assistantReducer.jsx +++ b/src/redux/reducers/assistantReducer.jsx @@ -1,3 +1,5 @@ +import { v4 as uuidv4 } from "uuid"; + const defaultState = { urlMode: false, imageVideoSelected: false, @@ -18,6 +20,7 @@ const defaultState = { inputUrlType: null, chatbotMessages: [], + chatbotSessionID: uuidv4(), positiveSourceCred: null, cautionSourceCred: null, @@ -133,6 +136,7 @@ const assistantReducer = (state = defaultState, action) => { return { ...state, chatbotMessages: [], + chatbotSessionID: uuidv4(), }; } case "CLEAN_STATE": @@ -143,6 +147,7 @@ const assistantReducer = (state = defaultState, action) => { singleMediaPresent: null, chatbotMessages: [], + chatbotSessionID: uuidv4(), inputUrl: null, errorKey: null, From c3a04ef63acad449daf81987da0c275877d0f9b3 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 8 Jul 2025 10:44:44 +0100 Subject: [PATCH 26/28] Added test input for test box --- .../tools/SyntheticImageDetection/index.jsx | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx index 601aa0074..87f61717c 100644 --- a/src/components/NavItems/tools/SyntheticImageDetection/index.jsx +++ b/src/components/NavItems/tools/SyntheticImageDetection/index.jsx @@ -398,7 +398,37 @@ const SyntheticImageDetection = () => { /> - + From 8fb1fb461c6eedd7e2ba9257a696f756a8d19501 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 11 Jul 2025 15:29:16 +0100 Subject: [PATCH 27/28] Added scroll bar on chatbot messages --- src/components/NavItems/Assistant/ChatbotInterface.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/NavItems/Assistant/ChatbotInterface.jsx b/src/components/NavItems/Assistant/ChatbotInterface.jsx index 00dfcc8d6..e478a96d0 100644 --- a/src/components/NavItems/Assistant/ChatbotInterface.jsx +++ b/src/components/NavItems/Assistant/ChatbotInterface.jsx @@ -90,9 +90,9 @@ const ChatbotInterface = ({ tool, result }) => { return ( - + {/* Conversation */} - + {chatbotMessages.map((msg) => ( Date: Fri, 11 Jul 2025 16:10:04 +0100 Subject: [PATCH 28/28] Loading spinner and better error handling. --- .../NavItems/Assistant/ChatbotInterface.jsx | 25 ++++++++++++++++++- src/redux/reducers/assistantReducer.jsx | 9 +++++++ src/redux/sagas/assistantSaga.jsx | 6 +++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/components/NavItems/Assistant/ChatbotInterface.jsx b/src/components/NavItems/Assistant/ChatbotInterface.jsx index e478a96d0..fc07b5572 100644 --- a/src/components/NavItems/Assistant/ChatbotInterface.jsx +++ b/src/components/NavItems/Assistant/ChatbotInterface.jsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from "react-redux"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; +import CircularProgress from "@mui/material/CircularProgress"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; import Tooltip from "@mui/material/Tooltip"; @@ -45,6 +46,8 @@ const ChatbotInterface = ({ tool, result }) => { const userEmail = useSelector((state) => state.userSession.user.email); const [previousResult, setPreviousResult] = useState(null); const [sendResult, setSendResult] = useState(true); + const chatbotLoading = useSelector((state) => state.assistant.chatbotLoading); + const messageWindow = useRef(null); if (result != previousResult) { setPreviousResult(result); @@ -88,11 +91,24 @@ const ChatbotInterface = ({ tool, result }) => { }; }); + useEffect(() => { + if (messageWindow.current) { + const lastChild = messageWindow.current.lastElementChild; + if (lastChild) { + lastChild.scrollIntoView({ behavior: "smooth", block: "end" }); + } + } + }, [chatbotLoading]); // The empty dependency array means this effect runs once after the initial render + return ( {/* Conversation */} - + {chatbotMessages.map((msg) => ( { ))} + {chatbotLoading ? ( + + + + ) : ( +
+ )}
{/* text box */} diff --git a/src/redux/reducers/assistantReducer.jsx b/src/redux/reducers/assistantReducer.jsx index b1f12205b..8e61dbf43 100644 --- a/src/redux/reducers/assistantReducer.jsx +++ b/src/redux/reducers/assistantReducer.jsx @@ -21,6 +21,7 @@ const defaultState = { chatbotMessages: [], chatbotSessionID: uuidv4(), + chatbotLoading: false, positiveSourceCred: null, cautionSourceCred: null, @@ -130,6 +131,13 @@ const assistantReducer = (state = defaultState, action) => { return { ...state, chatbotMessages: [...state.chatbotMessages, message], + chatbotLoading: message.sent == 1, + }; + } + case "SUBMIT_USER_CHATBOT_MESSAGE": { + return { + ...state, + chatbotLoading: true, }; } case "CLEAR_CHATBOT_MESSAGES": { @@ -148,6 +156,7 @@ const assistantReducer = (state = defaultState, action) => { chatbotMessages: [], chatbotSessionID: uuidv4(), + chatbotLoading: false, inputUrl: null, errorKey: null, diff --git a/src/redux/sagas/assistantSaga.jsx b/src/redux/sagas/assistantSaga.jsx index dac81e229..a153c89ab 100644 --- a/src/redux/sagas/assistantSaga.jsx +++ b/src/redux/sagas/assistantSaga.jsx @@ -754,6 +754,12 @@ function* handleAssistantChatbotCall(action) { yield put(setAssistantLoading(false)); } catch (error) { console.log(error); + yield put( + addChatbotMessage( + "I'm sorry, something went wrong and I could not provide an answer. Please try again.", + 0, + ), + ); yield put(setAssistantLoading(false)); yield put(setErrorKey(error.message)); }