Updated the FE to be able to display warnings and images
This commit is contained in:
@@ -1,144 +1,195 @@
|
||||
import { Box, Button, InputAdornment, TextField, Typography } from '@mui/material';
|
||||
import React, {useRef, useContext, useEffect, useState } from 'react';
|
||||
import { Conversation, ConversationPrompt, ConversationPromptType, 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';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React, { useRef, useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
Conversation,
|
||||
ConversationPrompt,
|
||||
ConversationPromptType,
|
||||
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= {
|
||||
response: string
|
||||
index: number
|
||||
type RenderMessageProps = {
|
||||
response: string;
|
||||
index: number;
|
||||
};
|
||||
|
||||
type AsyncChatProps = {
|
||||
selectedConversation: number | undefined
|
||||
conversationTitle: string
|
||||
conversations: Conversation[]
|
||||
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>
|
||||
setSelectedConversation: React.Dispatch<React.SetStateAction<number | undefined>>
|
||||
selectedConversation: number | undefined;
|
||||
conversationTitle: string;
|
||||
conversations: Conversation[];
|
||||
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>;
|
||||
setSelectedConversation: React.Dispatch<
|
||||
React.SetStateAction<number | undefined>
|
||||
>;
|
||||
drawerWidth: number;
|
||||
}
|
||||
};
|
||||
|
||||
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')({
|
||||
clip: 'rect(0 0 0 0)',
|
||||
clipPath: 'inset(50%)',
|
||||
const VisuallyHiddenInput = styled("input")({
|
||||
clip: "rect(0 0 0 0)",
|
||||
clipPath: "inset(50%)",
|
||||
height: 1,
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
whiteSpace: "nowrap",
|
||||
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<ConversationPrompt[]>([])
|
||||
const [conversationDetails, setConversationDetails] = useState<
|
||||
ConversationPrompt[]
|
||||
>([]);
|
||||
const [disableInput, setDisableInput] = useState<boolean>(false);
|
||||
|
||||
const [subscribe, unsubscribe, socket, sendMessage] = useContext(WebSocketContext)
|
||||
const { account, setAccount } = useContext(AccountContext)
|
||||
const messageRef = useRef('')
|
||||
const [subscribe, unsubscribe, socket, sendMessage] =
|
||||
useContext(WebSocketContext);
|
||||
const { account, setAccount } = useContext(AccountContext);
|
||||
const messageRef = useRef("");
|
||||
const messageResponsePart = useRef(0);
|
||||
const conversationRef = useRef(conversationDetails)
|
||||
const [stateMessage, setStateMessage] = useState<string>('')
|
||||
const selectedConversationRef = useRef<undefined | number>(undefined)
|
||||
const conversationRef = useRef(conversationDetails);
|
||||
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}`
|
||||
const channelName = `ACCOUNT_ID_${account?.email}`;
|
||||
|
||||
/* subscribe to channel and register callback */
|
||||
subscribe(channelName, (message: string) => {
|
||||
/* when a message is received just add it to the UI */
|
||||
|
||||
if (message === 'END_OF_THE_STREAM_ENDER_GAME_42'){
|
||||
messageResponsePart.current = 0
|
||||
if (message === "END_OF_THE_STREAM_ENDER_GAME_42") {
|
||||
messageResponsePart.current = 0;
|
||||
|
||||
conversationRef.current.pop()
|
||||
conversationRef.current.pop();
|
||||
|
||||
//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){
|
||||
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){
|
||||
if (!selectedConversation) {
|
||||
//console.log("we have a new conversation")
|
||||
setSelectedConversation(Number(message))
|
||||
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 (messageResponsePart.current === 2){
|
||||
messageRef.current += message
|
||||
setStateMessage(messageRef.current)
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
// Not a JSON object, treat as raw string.
|
||||
// contentToAdd is already `message`.
|
||||
}
|
||||
|
||||
})
|
||||
messageRef.current += contentToAdd;
|
||||
setStateMessage(messageRef.current);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
/* unsubscribe from channel during cleanup */
|
||||
unsubscribe(channelName)
|
||||
}
|
||||
}, [account, subscribe, unsubscribe])
|
||||
unsubscribe(channelName);
|
||||
};
|
||||
}, [account, subscribe, unsubscribe]);
|
||||
|
||||
|
||||
async function GetConversationDetails(){
|
||||
if(selectedConversation){
|
||||
|
||||
try{
|
||||
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 { data }: AxiosResponse<ConversationPromptType[]> =
|
||||
await axiosInstance.get(
|
||||
`conversation_details?conversation_id=${selectedConversation}`,
|
||||
);
|
||||
|
||||
const tempConversations: ConversationPrompt[] = data.map((item) => new ConversationPrompt({
|
||||
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{
|
||||
created_timestamp: item.created_timestamp,
|
||||
}),
|
||||
);
|
||||
conversationRef.current = tempConversations;
|
||||
setConversationDetails(tempConversations);
|
||||
} finally {
|
||||
//setPromptProcessing(false)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
GetConversationDetails();
|
||||
}, [selectedConversation])
|
||||
|
||||
}, [selectedConversation]);
|
||||
|
||||
// Function to render each message
|
||||
|
||||
const renderMessage = ({response, index}: RenderMessageProps) => (
|
||||
const renderMessage = ({ response, index }: RenderMessageProps) => (
|
||||
<div key={index}>
|
||||
<p>{response}</p>
|
||||
</div>
|
||||
@@ -148,47 +199,60 @@ const AsyncChat = ({selectedConversation, conversationTitle, conversations, setC
|
||||
prompt: string;
|
||||
file: Blob | null;
|
||||
fileType: string | null;
|
||||
|
||||
};
|
||||
|
||||
const handlePromptSubmit = async ({prompt, file, fileType}: PromptValues, {resetForm}: any): Promise<void> => {
|
||||
|
||||
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)
|
||||
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)
|
||||
console.log(
|
||||
`Sending message. ${prompt} ${selectedConversation} ${fileType}`,
|
||||
);
|
||||
sendMessage(prompt, selectedConversation, file, fileType);
|
||||
resetForm();
|
||||
|
||||
|
||||
}catch(e){
|
||||
console.log(`error ${e}`)
|
||||
} catch (e) {
|
||||
console.log(`error ${e}`);
|
||||
// TODO: make this user friendly
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return(
|
||||
return (
|
||||
<Box
|
||||
sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { sm: `${drawerWidth}px` }, mt: 5 }}
|
||||
position='fixed'
|
||||
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"
|
||||
>
|
||||
<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={{
|
||||
<Box
|
||||
sx={{
|
||||
mb: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@@ -196,94 +260,99 @@ const AsyncChat = ({selectedConversation, conversationTitle, conversations, setC
|
||||
overflow: "hidden",
|
||||
overflowY: "scroll",
|
||||
//justifyContent="flex-end" //# DO NOT USE THIS WITH 'scroll'
|
||||
}}>
|
||||
{selectedConversation ?
|
||||
}}
|
||||
>
|
||||
{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}/>
|
||||
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>
|
||||
}
|
||||
) : (
|
||||
<Markdown className="text-light text-center">
|
||||
Either select a previous conversation on start a new one.
|
||||
</Markdown>
|
||||
)}
|
||||
<div ref={messageEndRef} />
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='card-footer'>
|
||||
<div className="card-footer">
|
||||
<Formik
|
||||
initialValues={{
|
||||
prompt: '',
|
||||
prompt: "",
|
||||
file: null,
|
||||
fileType: null,
|
||||
}}
|
||||
onSubmit={handlePromptSubmit}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(formik) =>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<div className='row' style={{
|
||||
position: 'sticky',
|
||||
bottom: 0
|
||||
}}>
|
||||
<div className='col-12'>
|
||||
<div
|
||||
className="row"
|
||||
style={{
|
||||
position: "sticky",
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<div className="col-12">
|
||||
<Field
|
||||
name={"prompt"}
|
||||
fullWidth
|
||||
as={TextField}
|
||||
label={'Prompt'}
|
||||
errorstring={<ErrorMessage name={"prompt"}/>}
|
||||
label={"Prompt"}
|
||||
errorstring={<ErrorMessage name={"prompt"} />}
|
||||
size={"small"}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
|
||||
|
||||
margin={"dense"}
|
||||
variant={"outlined"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end' >
|
||||
|
||||
<InputAdornment position="end">
|
||||
<Button
|
||||
component="label"
|
||||
startIcon={<AttachFile/>}
|
||||
startIcon={<AttachFile />}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
|
||||
>
|
||||
<VisuallyHiddenInput
|
||||
type="file"
|
||||
accept='.csv,.xlsx,.txt'
|
||||
|
||||
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)
|
||||
formik.setFieldValue("file", file);
|
||||
formik.setFieldValue("fileType", file.type);
|
||||
} else {
|
||||
formik.setFieldValue('file',null)
|
||||
formik.setFieldValue('fileType',null)
|
||||
formik.setFieldValue("file", null);
|
||||
formik.setFieldValue("fileType", null);
|
||||
}
|
||||
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<Send />}
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid}>
|
||||
|
||||
</Button>
|
||||
type={"submit"}
|
||||
disabled={!formik.isValid}
|
||||
></Button>
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
>
|
||||
</Field>
|
||||
></Field>
|
||||
</div>
|
||||
{/* <div className='col-1'>
|
||||
<Button
|
||||
@@ -295,17 +364,11 @@ const AsyncChat = ({selectedConversation, conversationTitle, conversations, setC
|
||||
</div> */}
|
||||
</div>
|
||||
</Form>
|
||||
}
|
||||
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default AsyncChat
|
||||
export default AsyncChat;
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { useContext, useEffect, useRef, useState } from "react"
|
||||
import { WebSocketContext } from "../../contexts/WebSocketContext"
|
||||
import { AccountContext } from "../../contexts/AccountContext"
|
||||
import Markdown from "markdown-to-jsx"
|
||||
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"
|
||||
import React from "react";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import { Card, CardContent, CircularProgress } from "@mui/material";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyleDiv = styled.div`
|
||||
background-color: #f0f0f0;
|
||||
@@ -26,44 +20,112 @@ const StyleDiv = styled.div`
|
||||
`;
|
||||
|
||||
type ConversationDetailCardProps = {
|
||||
message: string
|
||||
user_created: boolean
|
||||
message: string;
|
||||
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 => {
|
||||
const type = user_created ? 'info' : 'dark'
|
||||
if(user_created){
|
||||
// Custom component for rendering errors
|
||||
const MyError = ({ content }: { content: string }) => {
|
||||
return (
|
||||
<span style={{ color: "red", fontWeight: "bold" }}>Error: {content}</span>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
const text_align = user_created ? 'right' : 'left'
|
||||
if (message.length === 0){
|
||||
return(
|
||||
<Card sx={{margin: '0.25rem'}}>
|
||||
<CardContent className='card-body-small text-dark ' style={{textAlign: `right`, marginRight: '1rem', marginLeft: '1rem', marginTop: '1rem', marginBottom: '1rem'}}>
|
||||
<CircularProgress color="inherit"/>
|
||||
const ConversationDetailCard = ({
|
||||
message,
|
||||
user_created,
|
||||
}: ConversationDetailCardProps): JSX.Element => {
|
||||
const text_align = user_created ? "right" : "left";
|
||||
if (message.length === 0) {
|
||||
return (
|
||||
<Card sx={{ margin: "0.25rem" }}>
|
||||
<CardContent
|
||||
className="card-body-small text-dark "
|
||||
style={{
|
||||
textAlign: `right`,
|
||||
marginRight: "1rem",
|
||||
marginLeft: "1rem",
|
||||
marginTop: "1rem",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="inherit" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}else{
|
||||
const newMessage: string = message.replace("```", "\n```\n");
|
||||
);
|
||||
} 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
console.log(contentToAdd);
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<Card sx={{margin: '0.25rem'}}>
|
||||
<CardContent sx={{textAlign: `${text_align}`, marginRight: '1rem', marginLeft: '1rem', marginTop: '1rem', marginBottom: '1rem'}}>
|
||||
<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"}}
|
||||
color='#F000000'
|
||||
>{newMessage}</Markdown>
|
||||
className="display-linebreak"
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
options={{
|
||||
overrides: {
|
||||
plot: {
|
||||
component: MyPlot,
|
||||
},
|
||||
error: {
|
||||
component: MyError,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{contentToAdd}
|
||||
</Markdown>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default ConversationDetailCard;
|
||||
@@ -1,39 +1,41 @@
|
||||
import { AccountContext } from "./AccountContext"
|
||||
import { AccountContext } from "./AccountContext";
|
||||
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 }) {
|
||||
const { authenticated, loading } = useContext(AuthContext);
|
||||
const ws = useRef(null)
|
||||
const [socket, setSocket] = useState(null)
|
||||
const channels = useRef({}) // maps each channel to the callback
|
||||
const { account, setAccount } = useContext(AccountContext)
|
||||
const [currentChannel, setCurrentChannel] = useState('')
|
||||
const ws = useRef(null);
|
||||
const [socket, setSocket] = useState(null);
|
||||
const channels = useRef({}); // maps each channel to the callback
|
||||
const { account, setAccount } = useContext(AccountContext);
|
||||
const [currentChannel, setCurrentChannel] = useState("");
|
||||
/* called from a component that registers a callback for a channel */
|
||||
const subscribe = (channel, callback) => {
|
||||
//console.log(`Subbing to ${channel}`)
|
||||
setCurrentChannel(channel)
|
||||
channels.current[channel] = callback
|
||||
}
|
||||
setCurrentChannel(channel);
|
||||
channels.current[channel] = callback;
|
||||
};
|
||||
/* remove callback */
|
||||
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){
|
||||
|
||||
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64File = reader.result?.toString().split(',')[1];
|
||||
if (base64File){
|
||||
const base64File = reader.result?.toString().split(",")[1];
|
||||
if (base64File) {
|
||||
const data = {
|
||||
message: message,
|
||||
conversation_id: conversation_id,
|
||||
@@ -41,13 +43,12 @@ function WebSocketProvider({ children }) {
|
||||
file: base64File,
|
||||
fileType: fileType,
|
||||
modelName: modelName,
|
||||
};
|
||||
socket.send(JSON.stringify(data));
|
||||
}
|
||||
socket.send(JSON.stringify(data))
|
||||
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}else{
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
const data = {
|
||||
message: message,
|
||||
conversation_id: conversation_id,
|
||||
@@ -55,59 +56,56 @@ function WebSocketProvider({ children }) {
|
||||
file: null,
|
||||
fileType: null,
|
||||
modelName: modelName,
|
||||
}
|
||||
};
|
||||
|
||||
socket.send(JSON.stringify(data))
|
||||
socket.send(JSON.stringify(data));
|
||||
}
|
||||
//socket.send(`${conversation_id} | ${message}`)
|
||||
|
||||
}else{
|
||||
console.log('Error sending message. WebSocket is not open')
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log("Error sending message. WebSocket is not open");
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
/* 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/')
|
||||
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;
|
||||
|
||||
ws.current.onopen = () => { setSocket(ws.current); }
|
||||
ws.current.onclose = () => { }
|
||||
ws.current.onopen = () => {
|
||||
setSocket(ws.current);
|
||||
};
|
||||
ws.current.onclose = () => {};
|
||||
ws.current.onmessage = (message) => {
|
||||
const data = message.data
|
||||
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]
|
||||
const chatChannel = Object.entries(channels.current)[0][0];
|
||||
|
||||
if (channels.current[chatChannel]) {
|
||||
/* in chat component the subscribed channel is `MESSAGE_CREATE_${id}` */
|
||||
channels.current[chatChannel](data)
|
||||
channels.current[chatChannel](data);
|
||||
} else {
|
||||
/* in notifications wrapper the subscribed channel is `MESSAGE_CREATE` */
|
||||
console.log('Error')
|
||||
console.log("Error");
|
||||
// channels.current[type]?.(data)
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
ws.current.close();
|
||||
};
|
||||
}
|
||||
return () => { ws.current.close() }
|
||||
|
||||
}
|
||||
|
||||
|
||||
}, [account])
|
||||
|
||||
|
||||
}, [account]);
|
||||
|
||||
/* WS provider dom */
|
||||
/* subscribe and unsubscribe are the only required prop for the context */
|
||||
return (
|
||||
<WebSocketContext.Provider value={[subscribe, unsubscribe, socket, sendMessage]}>
|
||||
<WebSocketContext.Provider
|
||||
value={[subscribe, unsubscribe, socket, sendMessage]}
|
||||
>
|
||||
{children}
|
||||
</WebSocketContext.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { WebSocketContext, WebSocketProvider }
|
||||
export { WebSocketContext, WebSocketProvider };
|
||||
|
||||
Reference in New Issue
Block a user