Updated the FE to be able to display warnings and images

This commit is contained in:
2025-09-24 11:49:57 -05:00
parent 1306ba9ed1
commit 0acfcc0d08
3 changed files with 549 additions and 426 deletions

View File

@@ -1,291 +1,360 @@
import { Box, Button, InputAdornment, TextField, Typography } from '@mui/material'; import {
import React, {useRef, useContext, useEffect, useState } from 'react'; Box,
import { Conversation, ConversationPrompt, ConversationPromptType, ConversationType } from '../../data'; Button,
import { axiosInstance } from '../../../axiosApi'; InputAdornment,
import { AxiosResponse } from 'axios'; TextField,
import ConversationDetailCard from '../ConversationDetailCard/ConversationDetailCard'; Typography,
import { ErrorMessage, Field, Form, Formik } from 'formik'; } from "@mui/material";
import { WebSocketContext } from '../../contexts/WebSocketContext'; import React, { useRef, useContext, useEffect, useState } from "react";
import { AccountContext } from '../../contexts/AccountContext'; import {
import * as Yup from 'yup'; Conversation,
import { styled } from '@mui/material/styles'; ConversationPrompt,
import { AttachFile, Send } from '@mui/icons-material'; ConversationPromptType,
import Markdown from 'markdown-to-jsx'; ConversationType,
} from "../../data";
import { axiosInstance } from "../../../axiosApi";
import { AxiosResponse } from "axios";
import ConversationDetailCard from "../ConversationDetailCard/ConversationDetailCard";
import { ErrorMessage, Field, Form, Formik } from "formik";
import { WebSocketContext } from "../../contexts/WebSocketContext";
import { AccountContext } from "../../contexts/AccountContext";
import * as Yup from "yup";
import { styled } from "@mui/material/styles";
import { AttachFile, Send } from "@mui/icons-material";
import Markdown from "markdown-to-jsx";
type RenderMessageProps= { type RenderMessageProps = {
response: string response: string;
index: number index: number;
}; };
type AsyncChatProps = { type AsyncChatProps = {
selectedConversation: number | undefined selectedConversation: number | undefined;
conversationTitle: string conversationTitle: string;
conversations: Conversation[] conversations: Conversation[];
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>> setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>;
setSelectedConversation: React.Dispatch<React.SetStateAction<number | undefined>> setSelectedConversation: React.Dispatch<
drawerWidth: number; React.SetStateAction<number | undefined>
} >;
drawerWidth: number;
};
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
prompt: Yup.string().min(1, "Need to have at least one character").required("This is requried") prompt: Yup.string()
} .min(1, "Need to have at least one character")
) .required("This is requried"),
});
const VisuallyHiddenInput = styled('input')({ const VisuallyHiddenInput = styled("input")({
clip: 'rect(0 0 0 0)', clip: "rect(0 0 0 0)",
clipPath: 'inset(50%)', clipPath: "inset(50%)",
height: 1, height: 1,
overflow: 'hidden', overflow: "hidden",
position: 'absolute', position: "absolute",
bottom: 0, bottom: 0,
left: 0, left: 0,
whiteSpace: 'nowrap', whiteSpace: "nowrap",
width: 1, width: 1,
}); });
const AsyncChat = ({selectedConversation, conversationTitle, conversations, setConversations, setSelectedConversation, drawerWidth}: AsyncChatProps): JSX.Element => { const AsyncChat = ({
selectedConversation,
conversationTitle,
conversations,
setConversations,
setSelectedConversation,
drawerWidth,
}: AsyncChatProps): JSX.Element => {
const messageEndRef = useRef(null);
const [conversationDetails, setConversationDetails] = useState<
const messageEndRef = useRef(null); ConversationPrompt[]
>([]);
const [conversationDetails, setConversationDetails] = useState<ConversationPrompt[]>([]) const [disableInput, setDisableInput] = useState<boolean>(false);
const [disableInput, setDisableInput] = useState<boolean>(false);
const [subscribe, unsubscribe, socket, sendMessage] = useContext(WebSocketContext) const [subscribe, unsubscribe, socket, sendMessage] =
const { account, setAccount } = useContext(AccountContext) useContext(WebSocketContext);
const messageRef = useRef('') const { account, setAccount } = useContext(AccountContext);
const messageResponsePart = useRef(0); const messageRef = useRef("");
const conversationRef = useRef(conversationDetails) const messageResponsePart = useRef(0);
const [stateMessage, setStateMessage] = useState<string>('') const conversationRef = useRef(conversationDetails);
const selectedConversationRef = useRef<undefined | number>(undefined) const [stateMessage, setStateMessage] = useState<string>("");
const selectedConversationRef = useRef<undefined | number>(undefined);
useEffect(() => {
/* register a consistent channel name for identifing this chat messages */
const channelName = `ACCOUNT_ID_${account?.email}`
/* subscribe to channel and register callback */ useEffect(() => {
subscribe(channelName, (message: string) => { /* register a consistent channel name for identifing this chat messages */
/* when a message is received just add it to the UI */ const channelName = `ACCOUNT_ID_${account?.email}`;
if (message === 'END_OF_THE_STREAM_ENDER_GAME_42'){ /* subscribe to channel and register callback */
messageResponsePart.current = 0 subscribe(channelName, (message: string) => {
/* when a message is received just add it to the UI */
conversationRef.current.pop()
if (message === "END_OF_THE_STREAM_ENDER_GAME_42") {
//handleAssistantPrompt({prompt: messageRef.current}) messageResponsePart.current = 0;
setConversationDetails([...conversationRef.current, new ConversationPrompt({message: `${messageRef.current}`, user_created:false})])
messageRef.current = '' conversationRef.current.pop();
setStateMessage('')
//handleAssistantPrompt({prompt: messageRef.current})
setConversationDetails([
...conversationRef.current,
new ConversationPrompt({
message: `${messageRef.current}`,
user_created: false,
}),
]);
messageRef.current = "";
setStateMessage("");
} else if (message === "START_OF_THE_STREAM_ENDER_GAME_42") {
messageResponsePart.current = 2;
} else if (message === "CONVERSATION_ID") {
messageResponsePart.current = 1;
} else {
if (messageResponsePart.current === 1) {
// this has to do with the conversation id
if (!selectedConversation) {
//console.log("we have a new conversation")
setSelectedConversation(Number(message));
}
} else if (messageResponsePart.current === 2) {
let contentToAdd = message;
try {
const parsedMessage = JSON.parse(message);
if (
parsedMessage &&
typeof parsedMessage === "object" &&
parsedMessage.type
) {
switch (parsedMessage.type) {
case "text":
contentToAdd = parsedMessage.content;
break;
case "plot":
contentToAdd = `<plot format="${parsedMessage.format}" image="${parsedMessage.image}"></plot>`;
break;
case "error":
contentToAdd = `<error content="${parsedMessage.content}"></error>`;
break;
default:
// contentToAdd is already `message`
break;
}
} }
else if (message === 'START_OF_THE_STREAM_ENDER_GAME_42'){ } catch (e) {
messageResponsePart.current = 2 // Not a JSON object, treat as raw string.
// contentToAdd is already `message`.
}
}else if (message === 'CONVERSATION_ID'){ messageRef.current += contentToAdd;
messageResponsePart.current = 1 setStateMessage(messageRef.current);
}else{
if (messageResponsePart.current === 1){
// this has to do with the conversation id
if(!selectedConversation){
//console.log("we have a new conversation")
setSelectedConversation(Number(message))
}
}
else if (messageResponsePart.current === 2){
messageRef.current += message
setStateMessage(messageRef.current)
}
}
})
return () => {
/* unsubscribe from channel during cleanup */
unsubscribe(channelName)
} }
}, [account, subscribe, unsubscribe]) }
});
async function GetConversationDetails(){
if(selectedConversation){
try{
//console.log('GetConversationDetails')
//setPromptProcessing(true)
selectedConversationRef.current = selectedConversation;
const {data, }: AxiosResponse<ConversationPromptType[]> = await axiosInstance.get(`conversation_details?conversation_id=${selectedConversation}`)
const tempConversations: ConversationPrompt[] = data.map((item) => new ConversationPrompt({
message: item.message,
user_created: item.user_created,
created_timestamp: item.created_timestamp
}))
conversationRef.current = tempConversations
setConversationDetails(tempConversations)
}finally{
//setPromptProcessing(false)
}
}
}
useEffect(() => {
GetConversationDetails();
}, [selectedConversation])
// Function to render each message
const renderMessage = ({response, index}: RenderMessageProps) => (
<div key={index}>
<p>{response}</p>
</div>
);
type PromptValues = {
prompt: string;
file: Blob | null;
fileType: string | null;
return () => {
/* unsubscribe from channel during cleanup */
unsubscribe(channelName);
}; };
}, [account, subscribe, unsubscribe]);
const handlePromptSubmit = async ({prompt, file, fileType}: PromptValues, {resetForm}: any): Promise<void> => { async function GetConversationDetails() {
if (selectedConversation) {
// send the prompt to be saved try {
console.log(fileType) //console.log('GetConversationDetails')
try{ //setPromptProcessing(true)
const tempConversations: ConversationPrompt[] = [...conversationDetails, new ConversationPrompt({message: prompt, user_created:true}), new ConversationPrompt({message: '', user_created:false})] selectedConversationRef.current = selectedConversation;
conversationRef.current = tempConversations const { data }: AxiosResponse<ConversationPromptType[]> =
setConversationDetails(tempConversations) await axiosInstance.get(
// TODO: add the file here `conversation_details?conversation_id=${selectedConversation}`,
console.log(`Sending message. ${prompt} ${selectedConversation} ${fileType}`) );
sendMessage(prompt, selectedConversation, file, fileType)
resetForm(); const tempConversations: ConversationPrompt[] = data.map(
(item) =>
new ConversationPrompt({
}catch(e){ message: item.message,
console.log(`error ${e}`) user_created: item.user_created,
// TODO: make this user friendly created_timestamp: item.created_timestamp,
} }),
);
conversationRef.current = tempConversations;
setConversationDetails(tempConversations);
} finally {
//setPromptProcessing(false)
}
} }
}
useEffect(() => {
GetConversationDetails();
}, [selectedConversation]);
return( // Function to render each message
<Box
sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` }, const renderMessage = ({ response, index }: RenderMessageProps) => (
ml: { sm: `${drawerWidth}px` }, mt: 5 }} <div key={index}>
position='fixed' <p>{response}</p>
</div>
);
type PromptValues = {
prompt: string;
file: Blob | null;
fileType: string | null;
};
const handlePromptSubmit = async (
{ prompt, file, fileType }: PromptValues,
{ resetForm }: any,
): Promise<void> => {
// send the prompt to be saved
console.log(fileType);
try {
const tempConversations: ConversationPrompt[] = [
...conversationDetails,
new ConversationPrompt({ message: prompt, user_created: true }),
new ConversationPrompt({ message: "", user_created: false }),
];
conversationRef.current = tempConversations;
setConversationDetails(tempConversations);
// TODO: add the file here
console.log(
`Sending message. ${prompt} ${selectedConversation} ${fileType}`,
);
sendMessage(prompt, selectedConversation, file, fileType);
resetForm();
} catch (e) {
console.log(`error ${e}`);
// TODO: make this user friendly
}
};
return (
<Box
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
mt: 5,
}}
position="fixed"
>
<div className="card" style={{ height: "auto" }}>
<div className="card-header">
<div className="bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1">
<Typography
variant="h6"
style={{ flexGrow: 1, marginLeft: "1rem" }}
className="text-white font-weight-bold"
>
{conversationTitle}
</Typography>
</div>
</div>
<div className="card-body bg-gradient-dark border-radius-lg py-3 pe-1">
<Box
sx={{
mb: 2,
display: "flex",
flexDirection: "column",
height: 700,
overflow: "hidden",
overflowY: "scroll",
//justifyContent="flex-end" //# DO NOT USE THIS WITH 'scroll'
}}
>
{selectedConversation ? (
conversationDetails.map((convo_detail) =>
convo_detail.message.length > 0 ? (
<ConversationDetailCard
message={convo_detail.message}
user_created={convo_detail.user_created}
key={convo_detail.id}
/>
) : (
<ConversationDetailCard
message={stateMessage}
user_created={convo_detail.user_created}
key={convo_detail.id}
/>
),
)
) : (
<Markdown className="text-light text-center">
Either select a previous conversation on start a new one.
</Markdown>
)}
<div ref={messageEndRef} />
</Box>
</div>
</div>
<div className="card-footer">
<Formik
initialValues={{
prompt: "",
file: null,
fileType: null,
}}
onSubmit={handlePromptSubmit}
validationSchema={validationSchema}
> >
<div className="card" style= {{height: 'auto'}}> {(formik) => (
<div className='card-header'> <Form>
<div
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'> className="row"
<Typography variant="h6" style={{ flexGrow: 1, marginLeft: '1rem' }} className='text-white font-weight-bold'> style={{
{conversationTitle} position: "sticky",
</Typography> bottom: 0,
</div> }}
</div> >
<div className="card-body bg-gradient-dark border-radius-lg py-3 pe-1"> <div className="col-12">
<Box sx={{ <Field
mb: 2, name={"prompt"}
display: "flex", fullWidth
flexDirection: "column", as={TextField}
height: 700, label={"Prompt"}
overflow: "hidden", errorstring={<ErrorMessage name={"prompt"} />}
overflowY: "scroll", size={"small"}
//justifyContent="flex-end" //# DO NOT USE THIS WITH 'scroll' role={undefined}
}}> tabIndex={-1}
{selectedConversation ? margin={"dense"}
conversationDetails.map((convo_detail) => variant={"outlined"}
convo_detail.message.length >0 ? InputProps={{
<ConversationDetailCard message={convo_detail.message} user_created={convo_detail.user_created} key={convo_detail.id}/> : <ConversationDetailCard message={stateMessage} user_created={convo_detail.user_created} key={convo_detail.id}/> endAdornment: (
) <InputAdornment position="end">
: <Button
<Markdown className='text-light text-center'>Either select a previous conversation on start a new one.</Markdown> component="label"
} startIcon={<AttachFile />}
<div ref={messageEndRef} /> role={undefined}
tabIndex={-1}
</Box> >
<VisuallyHiddenInput
type="file"
</div> accept=".csv,.xlsx,.txt"
onChange={(event) => {
</div> const file = event.target.files?.[0];
//console.log(file)
<div className='card-footer'> if (file) {
<Formik formik.setFieldValue("file", file);
initialValues={{ formik.setFieldValue("fileType", file.type);
prompt: '', } else {
file: null, formik.setFieldValue("file", null);
fileType: null, formik.setFieldValue("fileType", null);
}
}}
/>
</Button>
<Button
startIcon={<Send />}
type={"submit"}
disabled={!formik.isValid}
></Button>
</InputAdornment>
),
}} }}
onSubmit={handlePromptSubmit} ></Field>
validationSchema={validationSchema} </div>
> {/* <div className='col-1'>
{(formik) =>
<Form>
<div className='row' style={{
position: 'sticky',
bottom: 0
}}>
<div className='col-12'>
<Field
name={"prompt"}
fullWidth
as={TextField}
label={'Prompt'}
errorstring={<ErrorMessage name={"prompt"}/>}
size={"small"}
role={undefined}
tabIndex={-1}
margin={"dense"}
variant={"outlined"}
InputProps={{
endAdornment: (
<InputAdornment position='end' >
<Button
component="label"
startIcon={<AttachFile/>}
role={undefined}
tabIndex={-1}
>
<VisuallyHiddenInput
type="file"
accept='.csv,.xlsx,.txt'
onChange={(event) => {
const file = event.target.files?.[0];
//console.log(file)
if (file) {
formik.setFieldValue('file',file)
formik.setFieldValue('fileType',file.type)
} else {
formik.setFieldValue('file',null)
formik.setFieldValue('fileType',null)
}
}}
/>
</Button>
<Button
startIcon={<Send />}
type={'submit'}
disabled={!formik.isValid}>
</Button>
</InputAdornment>
)
}}
>
</Field>
</div>
{/* <div className='col-1'>
<Button <Button
type={'submit'} type={'submit'}
// disabled={selectedConversation === undefined ? true : false || !formik.isValid} // disabled={selectedConversation === undefined ? true : false || !formik.isValid}
@@ -293,19 +362,13 @@ const AsyncChat = ({selectedConversation, conversationTitle, conversations, setC
</Button> </Button>
</div> */} </div> */}
</div> </div>
</Form> </Form>
} )}
</Formik>
</div>
</Box>
);
};
</Formik> export default AsyncChat;
</div>
</Box>
)
}
export default AsyncChat

View File

@@ -1,13 +1,7 @@
import { useContext, useEffect, useRef, useState } from "react" import React from "react";
import { WebSocketContext } from "../../contexts/WebSocketContext" import Markdown from "markdown-to-jsx";
import { AccountContext } from "../../contexts/AccountContext" import { Card, CardContent, CircularProgress } from "@mui/material";
import Markdown from "markdown-to-jsx" import styled from "styled-components";
import { Card, CardContent, CircularProgress } from "@mui/material"
import MDTypography from "../../ui-kit/components/MDTypography"
import styled from 'styled-components';
// import { CodeBlock } from "react-code-blocks"
// import CustomCodeBlock from "../CustomPreBlock/CustomPreBlock"
// import CustomPreBlock from "../CustomPreBlock/CustomPreBlock"
const StyleDiv = styled.div` const StyleDiv = styled.div`
background-color: #f0f0f0; background-color: #f0f0f0;
@@ -26,44 +20,112 @@ const StyleDiv = styled.div`
`; `;
type ConversationDetailCardProps = { type ConversationDetailCardProps = {
message: string message: string;
user_created: boolean user_created: boolean;
};
} // Custom component for rendering plots
const MyPlot = ({ format, image }: { format: string; image: string }) => {
const imageSrc = `data:image/${format};base64,${image}`;
return (
<img
src={imageSrc}
style={{ maxWidth: "100%", height: "auto" }}
alt="plot"
/>
);
};
const ConversationDetailCard = ({message, user_created}: ConversationDetailCardProps): JSX.Element => { // Custom component for rendering errors
const type = user_created ? 'info' : 'dark' const MyError = ({ content }: { content: string }) => {
if(user_created){ return (
<span style={{ color: "red", fontWeight: "bold" }}>Error: {content}</span>
);
};
} const ConversationDetailCard = ({
const text_align = user_created ? 'right' : 'left' message,
if (message.length === 0){ user_created,
return( }: ConversationDetailCardProps): JSX.Element => {
<Card sx={{margin: '0.25rem'}}> const text_align = user_created ? "right" : "left";
<CardContent className='card-body-small text-dark ' style={{textAlign: `right`, marginRight: '1rem', marginLeft: '1rem', marginTop: '1rem', marginBottom: '1rem'}}> if (message.length === 0) {
<CircularProgress color="inherit"/> return (
</CardContent> <Card sx={{ margin: "0.25rem" }}>
</Card> <CardContent
) className="card-body-small text-dark "
}else{ style={{
const newMessage: string = message.replace("```", "\n```\n"); textAlign: `right`,
marginRight: "1rem",
return ( marginLeft: "1rem",
marginTop: "1rem",
marginBottom: "1rem",
}}
>
<CircularProgress color="inherit" />
</CardContent>
</Card>
);
} else {
let contentToAdd = message;
console.log(contentToAdd);
try {
const parsedMessage = JSON.parse(message);
if (
parsedMessage &&
typeof parsedMessage === "object" &&
parsedMessage.type
) {
switch (parsedMessage.type) {
case "text":
contentToAdd = parsedMessage.content;
break;
<Card sx={{margin: '0.25rem'}}> case "plot":
<CardContent sx={{textAlign: `${text_align}`, marginRight: '1rem', marginLeft: '1rem', marginTop: '1rem', marginBottom: '1rem'}}> contentToAdd = `<plot format="${parsedMessage.format}" image="${parsedMessage.image}"></plot>`;
<Markdown break;
className='display-linebreak' case "error":
style={{whiteSpace: "pre-line"}} contentToAdd = `<error content="${parsedMessage.content}"></error>`;
color='#F000000' break;
>{newMessage}</Markdown> default:
</CardContent> // contentToAdd is already `message`
</Card>
)
} break;
}
} }
} catch {}
console.log(contentToAdd);
export default ConversationDetailCard; return (
<Card sx={{ margin: "0.25rem" }}>
<CardContent
sx={{
textAlign: `${text_align}`,
marginRight: "1rem",
marginLeft: "1rem",
marginTop: "1rem",
marginBottom: "1rem",
}}
>
<Markdown
className="display-linebreak"
style={{ whiteSpace: "pre-line" }}
options={{
overrides: {
plot: {
component: MyPlot,
},
error: {
component: MyError,
},
},
}}
>
{contentToAdd}
</Markdown>
</CardContent>
</Card>
);
}
};
export default ConversationDetailCard;

View File

@@ -1,113 +1,111 @@
import { AccountContext } from "./AccountContext" import { AccountContext } from "./AccountContext";
import { AuthContext } from "./AuthContext"; import { AuthContext } from "./AuthContext";
const { useEffect, createContext, useRef, useState, useContext } = require("react") const {
useEffect,
createContext,
useRef,
useState,
useContext,
} = require("react");
const WebSocketContext = createContext();
const WebSocketContext = createContext()
function WebSocketProvider({ children }) { function WebSocketProvider({ children }) {
const { authenticated, loading } = useContext(AuthContext); const { authenticated, loading } = useContext(AuthContext);
const ws = useRef(null) const ws = useRef(null);
const [socket, setSocket] = useState(null) const [socket, setSocket] = useState(null);
const channels = useRef({}) // maps each channel to the callback const channels = useRef({}); // maps each channel to the callback
const { account, setAccount } = useContext(AccountContext) const { account, setAccount } = useContext(AccountContext);
const [currentChannel, setCurrentChannel] = useState('') const [currentChannel, setCurrentChannel] = useState("");
/* called from a component that registers a callback for a channel */ /* called from a component that registers a callback for a channel */
const subscribe = (channel, callback) => { const subscribe = (channel, callback) => {
//console.log(`Subbing to ${channel}`) //console.log(`Subbing to ${channel}`)
setCurrentChannel(channel) setCurrentChannel(channel);
channels.current[channel] = callback channels.current[channel] = callback;
} };
/* remove callback */ /* remove callback */
const unsubscribe = (channel) => { const unsubscribe = (channel) => {
delete channels.current[channel] delete channels.current[channel];
};
const sendMessage = (message, conversation_id, file, fileType, modelName) => {
if (socket && socket.readyState === WebSocket.OPEN) {
if (file) {
const reader = new FileReader();
reader.onload = () => {
const base64File = reader.result?.toString().split(",")[1];
if (base64File) {
const data = {
message: message,
conversation_id: conversation_id,
email: account?.email,
file: base64File,
fileType: fileType,
modelName: modelName,
};
socket.send(JSON.stringify(data));
}
};
reader.readAsDataURL(file);
} else {
const data = {
message: message,
conversation_id: conversation_id,
email: account?.email,
file: null,
fileType: null,
modelName: modelName,
};
socket.send(JSON.stringify(data));
}
//socket.send(`${conversation_id} | ${message}`)
} else {
console.log("Error sending message. WebSocket is not open");
} }
};
useEffect(() => {
/* WS initialization and cleanup */
if (account) {
ws.current = new WebSocket(`ws://localhost:8011/ws/chat_again/`);
//ws.current = new WebSocket('wss://chatbackend.aimloperations.com/ws/chat_again/')
//ws.current = process.env.REACT_APP_BACKEND_WS_API_BASE_URL;
const sendMessage = (message, conversation_id, file, fileType, modelName) => { ws.current.onopen = () => {
if (socket && socket.readyState === WebSocket.OPEN){ setSocket(ws.current);
};
if (file){ ws.current.onclose = () => {};
ws.current.onmessage = (message) => {
const data = message.data;
// lookup for an existing chat in which this message belongs
// if no chat is subscribed send message to generic channel
const chatChannel = Object.entries(channels.current)[0][0];
if (channels.current[chatChannel]) {
const reader = new FileReader(); /* in chat component the subscribed channel is `MESSAGE_CREATE_${id}` */
reader.onload = () => { channels.current[chatChannel](data);
const base64File = reader.result?.toString().split(',')[1]; } else {
if (base64File){ /* in notifications wrapper the subscribed channel is `MESSAGE_CREATE` */
const data = { console.log("Error");
message: message, // channels.current[type]?.(data)
conversation_id: conversation_id,
email: account?.email,
file: base64File,
fileType: fileType,
modelName: modelName,
}
socket.send(JSON.stringify(data))
}
}
reader.readAsDataURL(file)
}else{
const data = {
message: message,
conversation_id: conversation_id,
email: account?.email,
file: null,
fileType: null,
modelName: modelName,
}
socket.send(JSON.stringify(data))
}
//socket.send(`${conversation_id} | ${message}`)
}else{
console.log('Error sending message. WebSocket is not open')
} }
};
return () => {
ws.current.close();
};
} }
useEffect(() => { }, [account]);
/* WS initialization and cleanup */
if (account){
//ws.current = new WebSocket(`ws://127.0.0.1:8011/ws/chat_again/`);
ws.current = new WebSocket('wss://chatbackend.aimloperations.com/ws/chat_again/')
//ws.current = process.env.REACT_APP_BACKEND_WS_API_BASE_URL;
ws.current.onopen = () => { setSocket(ws.current); } /* WS provider dom */
ws.current.onclose = () => { } /* subscribe and unsubscribe are the only required prop for the context */
ws.current.onmessage = (message) => { return (
const data = message.data <WebSocketContext.Provider
// lookup for an existing chat in which this message belongs value={[subscribe, unsubscribe, socket, sendMessage]}
// if no chat is subscribed send message to generic channel >
const chatChannel = Object.entries(channels.current)[0][0] {children}
</WebSocketContext.Provider>
if (channels.current[chatChannel]) { );
/* in chat component the subscribed channel is `MESSAGE_CREATE_${id}` */
channels.current[chatChannel](data)
} else {
/* in notifications wrapper the subscribed channel is `MESSAGE_CREATE` */
console.log('Error')
// channels.current[type]?.(data)
}
}
return () => { ws.current.close() }
}
}, [account])
/* WS provider dom */
/* subscribe and unsubscribe are the only required prop for the context */
return (
<WebSocketContext.Provider value={[subscribe, unsubscribe, socket, sendMessage]}>
{children}
</WebSocketContext.Provider>
)
} }
export { WebSocketContext, WebSocketProvider } export { WebSocketContext, WebSocketProvider };