Compare commits

..

3 Commits

Author SHA1 Message Date
2657334e0c fixing the rebase 2025-09-24 12:02:59 -05:00
b120f85ccd update button based on state 2025-09-24 11:58:38 -05:00
0acfcc0d08 Updated the FE to be able to display warnings and images 2025-09-24 11:57:40 -05:00
4 changed files with 801 additions and 650 deletions

View File

@@ -1,140 +1,191 @@
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<
React.SetStateAction<number | undefined>
>;
drawerWidth: number; 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 messageEndRef = useRef(null);
const [conversationDetails, setConversationDetails] = useState<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 messageRef = useRef("");
const messageResponsePart = useRef(0); const messageResponsePart = useRef(0);
const conversationRef = useRef(conversationDetails) const conversationRef = useRef(conversationDetails);
const [stateMessage, setStateMessage] = useState<string>('') const [stateMessage, setStateMessage] = useState<string>("");
const selectedConversationRef = useRef<undefined | number>(undefined) const selectedConversationRef = useRef<undefined | number>(undefined);
useEffect(() => { useEffect(() => {
/* register a consistent channel name for identifing this chat messages */ /* 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 to channel and register callback */
subscribe(channelName, (message: string) => { subscribe(channelName, (message: string) => {
/* when a message is received just add it to the UI */ /* when a message is received just add it to the UI */
if (message === 'END_OF_THE_STREAM_ENDER_GAME_42'){ if (message === "END_OF_THE_STREAM_ENDER_GAME_42") {
messageResponsePart.current = 0 messageResponsePart.current = 0;
conversationRef.current.pop() conversationRef.current.pop();
//handleAssistantPrompt({prompt: messageRef.current}) //handleAssistantPrompt({prompt: messageRef.current})
setConversationDetails([...conversationRef.current, new ConversationPrompt({message: `${messageRef.current}`, user_created:false})]) setConversationDetails([
messageRef.current = '' ...conversationRef.current,
setStateMessage('') new ConversationPrompt({
} message: `${messageRef.current}`,
else if (message === 'START_OF_THE_STREAM_ENDER_GAME_42'){ user_created: false,
messageResponsePart.current = 2 }),
]);
}else if (message === 'CONVERSATION_ID'){ messageRef.current = "";
messageResponsePart.current = 1 setStateMessage("");
} else if (message === "START_OF_THE_STREAM_ENDER_GAME_42") {
messageResponsePart.current = 2;
} else if (message === "CONVERSATION_ID") {
messageResponsePart.current = 1;
} else { } else {
if (messageResponsePart.current === 1) { if (messageResponsePart.current === 1) {
// this has to do with the conversation id // this has to do with the conversation id
if (!selectedConversation) { if (!selectedConversation) {
//console.log("we have a new conversation") //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){ } catch (e) {
messageRef.current += message // Not a JSON object, treat as raw string.
setStateMessage(messageRef.current) // contentToAdd is already `message`.
}
} }
}) messageRef.current += contentToAdd;
setStateMessage(messageRef.current);
}
}
});
return () => { return () => {
/* unsubscribe from channel during cleanup */ /* unsubscribe from channel during cleanup */
unsubscribe(channelName) unsubscribe(channelName);
} };
}, [account, subscribe, unsubscribe]) }, [account, subscribe, unsubscribe]);
async function GetConversationDetails() { async function GetConversationDetails() {
if (selectedConversation) { if (selectedConversation) {
try { try {
//console.log('GetConversationDetails') //console.log('GetConversationDetails')
//setPromptProcessing(true) //setPromptProcessing(true)
selectedConversationRef.current = selectedConversation; 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, message: item.message,
user_created: item.user_created, user_created: item.user_created,
created_timestamp: item.created_timestamp created_timestamp: item.created_timestamp,
})) }),
conversationRef.current = tempConversations );
setConversationDetails(tempConversations) conversationRef.current = tempConversations;
setConversationDetails(tempConversations);
} finally { } finally {
//setPromptProcessing(false) //setPromptProcessing(false)
} }
} }
} }
useEffect(() => { useEffect(() => {
GetConversationDetails(); GetConversationDetails();
}, [selectedConversation]) }, [selectedConversation]);
// Function to render each message // Function to render each message
@@ -148,47 +199,60 @@ const AsyncChat = ({selectedConversation, conversationTitle, conversations, setC
prompt: string; prompt: string;
file: Blob | null; file: Blob | null;
fileType: string | 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 // send the prompt to be saved
console.log(fileType) console.log(fileType);
try { try {
const tempConversations: ConversationPrompt[] = [...conversationDetails, new ConversationPrompt({message: prompt, user_created:true}), new ConversationPrompt({message: '', user_created:false})] const tempConversations: ConversationPrompt[] = [
conversationRef.current = tempConversations ...conversationDetails,
setConversationDetails(tempConversations) new ConversationPrompt({ message: prompt, user_created: true }),
new ConversationPrompt({ message: "", user_created: false }),
];
conversationRef.current = tempConversations;
setConversationDetails(tempConversations);
// TODO: add the file here // TODO: add the file here
console.log(`Sending message. ${prompt} ${selectedConversation} ${fileType}`) console.log(
sendMessage(prompt, selectedConversation, file, fileType) `Sending message. ${prompt} ${selectedConversation} ${fileType}`,
);
sendMessage(prompt, selectedConversation, file, fileType);
resetForm(); resetForm();
} catch (e) { } catch (e) {
console.log(`error ${e}`) console.log(`error ${e}`);
// TODO: make this user friendly // TODO: make this user friendly
} }
} };
return ( return (
<Box <Box
sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` }, sx={{
ml: { sm: `${drawerWidth}px` }, mt: 5 }} flexGrow: 1,
position='fixed' 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} {conversationTitle}
</Typography> </Typography>
</div> </div>
</div> </div>
<div className="card-body bg-gradient-dark border-radius-lg py-3 pe-1"> <div className="card-body bg-gradient-dark border-radius-lg py-3 pe-1">
<Box sx={{ <Box
sx={{
mb: 2, mb: 2,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
@@ -196,94 +260,100 @@ const AsyncChat = ({selectedConversation, conversationTitle, conversations, setC
overflow: "hidden", overflow: "hidden",
overflowY: "scroll", overflowY: "scroll",
//justifyContent="flex-end" //# DO NOT USE THIS WITH 'scroll' //justifyContent="flex-end" //# DO NOT USE THIS WITH 'scroll'
}}> }}
{selectedConversation ? >
{selectedConversation ? (
conversationDetails.map((convo_detail) => conversationDetails.map((convo_detail) =>
convo_detail.message.length >0 ? 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}/> <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} /> <div ref={messageEndRef} />
</Box> </Box>
</div>
</div> </div>
</div> <div className="card-footer">
<div className='card-footer'>
<Formik <Formik
initialValues={{ initialValues={{
prompt: '', prompt: "",
file: null, file: null,
fileType: null, fileType: null,
}} }}
onSubmit={handlePromptSubmit} onSubmit={handlePromptSubmit}
validationSchema={validationSchema} validationSchema={validationSchema}
> >
{(formik) => {(formik) => (
<Form> <Form>
<div className='row' style={{ <div
position: 'sticky', className="row"
bottom: 0 style={{
}}> position: "sticky",
<div className='col-12'> bottom: 0,
}}
>
<div className="col-12">
<Field <Field
name={"prompt"} name={"prompt"}
fullWidth fullWidth
as={TextField} as={TextField}
label={'Prompt'} label={"Prompt"}
errorstring={<ErrorMessage name={"prompt"} />} errorstring={<ErrorMessage name={"prompt"} />}
size={"small"} size={"small"}
role={undefined} role={undefined}
tabIndex={-1} tabIndex={-1}
margin={"dense"} margin={"dense"}
variant={"outlined"} variant={"outlined"}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position='end' > <InputAdornment position="end">
<Button <Button
component="label" component="label"
color={formik.values.file ? "success" : "inherit"}
startIcon={<AttachFile />} startIcon={<AttachFile />}
role={undefined} role={undefined}
tabIndex={-1} tabIndex={-1}
> >
<VisuallyHiddenInput <VisuallyHiddenInput
type="file" type="file"
accept='.csv,.xlsx,.txt' accept=".csv,.xlsx,.txt"
onChange={(event) => { onChange={(event) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
//console.log(file) //console.log(file)
if (file) { if (file) {
formik.setFieldValue('file',file) formik.setFieldValue("file", file);
formik.setFieldValue('fileType',file.type) formik.setFieldValue("fileType", file.type);
} else { } else {
formik.setFieldValue('file',null) formik.setFieldValue("file", null);
formik.setFieldValue('fileType',null) formik.setFieldValue("fileType", null);
} }
}} }}
/> />
</Button> </Button>
<Button <Button
startIcon={<Send />} startIcon={<Send />}
type={'submit'} type={"submit"}
disabled={!formik.isValid}> disabled={!formik.isValid}
></Button>
</Button>
</InputAdornment> </InputAdornment>
) ),
}} }}
> ></Field>
</Field>
</div> </div>
{/* <div className='col-1'> {/* <div className='col-1'>
<Button <Button
@@ -295,17 +365,11 @@ const AsyncChat = ({selectedConversation, conversationTitle, conversations, setC
</div> */} </div> */}
</div> </div>
</Form> </Form>
} )}
</Formik> </Formik>
</div> </div>
</Box> </Box>
);
};
export default AsyncChat;
)
}
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,
user_created,
}: ConversationDetailCardProps): JSX.Element => {
const text_align = user_created ? "right" : "left";
if (message.length === 0) { if (message.length === 0) {
return ( return (
<Card sx={{margin: '0.25rem'}}> <Card sx={{ margin: "0.25rem" }}>
<CardContent className='card-body-small text-dark ' style={{textAlign: `right`, marginRight: '1rem', marginLeft: '1rem', marginTop: '1rem', marginBottom: '1rem'}}> <CardContent
className="card-body-small text-dark "
style={{
textAlign: `right`,
marginRight: "1rem",
marginLeft: "1rem",
marginTop: "1rem",
marginBottom: "1rem",
}}
>
<CircularProgress color="inherit" /> <CircularProgress color="inherit" />
</CardContent> </CardContent>
</Card> </Card>
) );
} else { } else {
const newMessage: string = message.replace("```", "\n```\n"); 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 ( return (
<Card sx={{ margin: "0.25rem" }}>
<CardContent
<Card sx={{margin: '0.25rem'}}> sx={{
<CardContent sx={{textAlign: `${text_align}`, marginRight: '1rem', marginLeft: '1rem', marginTop: '1rem', marginBottom: '1rem'}}> textAlign: `${text_align}`,
marginRight: "1rem",
marginLeft: "1rem",
marginTop: "1rem",
marginBottom: "1rem",
}}
>
<Markdown <Markdown
className='display-linebreak' className="display-linebreak"
style={{ whiteSpace: "pre-line" }} style={{ whiteSpace: "pre-line" }}
color='#F000000' options={{
>{newMessage}</Markdown> overrides: {
plot: {
component: MyPlot,
},
error: {
component: MyError,
},
},
}}
>
{contentToAdd}
</Markdown>
</CardContent> </CardContent>
</Card> </Card>
) );
}
} }
};
export default ConversationDetailCard; export default ConversationDetailCard;

View File

@@ -1,38 +1,40 @@
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) => { const sendMessage = (message, conversation_id, file, fileType, modelName) => {
if (socket && socket.readyState === WebSocket.OPEN) { if (socket && socket.readyState === WebSocket.OPEN) {
if (file) { if (file) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = () => {
const base64File = reader.result?.toString().split(',')[1]; const base64File = reader.result?.toString().split(",")[1];
if (base64File) { if (base64File) {
const data = { const data = {
message: message, message: message,
@@ -41,12 +43,11 @@ function WebSocketProvider({ children }) {
file: base64File, file: base64File,
fileType: fileType, fileType: fileType,
modelName: modelName, modelName: modelName,
};
socket.send(JSON.stringify(data));
} }
socket.send(JSON.stringify(data)) };
reader.readAsDataURL(file);
}
}
reader.readAsDataURL(file)
} else { } else {
const data = { const data = {
message: message, message: message,
@@ -55,59 +56,56 @@ function WebSocketProvider({ children }) {
file: null, file: null,
fileType: null, fileType: null,
modelName: modelName, modelName: modelName,
} };
socket.send(JSON.stringify(data)) socket.send(JSON.stringify(data));
} }
//socket.send(`${conversation_id} | ${message}`) //socket.send(`${conversation_id} | ${message}`)
} else { } else {
console.log('Error sending message. WebSocket is not open') console.log("Error sending message. WebSocket is not open");
}
} }
};
useEffect(() => { useEffect(() => {
/* WS initialization and cleanup */ /* WS initialization and cleanup */
if (account) { 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 = 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 = process.env.REACT_APP_BACKEND_WS_API_BASE_URL;
ws.current.onopen = () => { setSocket(ws.current); } ws.current.onopen = () => {
ws.current.onclose = () => { } setSocket(ws.current);
};
ws.current.onclose = () => {};
ws.current.onmessage = (message) => { ws.current.onmessage = (message) => {
const data = message.data const data = message.data;
// lookup for an existing chat in which this message belongs // lookup for an existing chat in which this message belongs
// if no chat is subscribed send message to generic channel // 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]) { if (channels.current[chatChannel]) {
/* in chat component the subscribed channel is `MESSAGE_CREATE_${id}` */ /* in chat component the subscribed channel is `MESSAGE_CREATE_${id}` */
channels.current[chatChannel](data) channels.current[chatChannel](data);
} else { } else {
/* in notifications wrapper the subscribed channel is `MESSAGE_CREATE` */ /* in notifications wrapper the subscribed channel is `MESSAGE_CREATE` */
console.log('Error') console.log("Error");
// channels.current[type]?.(data) // channels.current[type]?.(data)
} }
};
return () => {
ws.current.close();
};
} }
return () => { ws.current.close() } }, [account]);
}
}, [account])
/* WS provider dom */ /* WS provider dom */
/* subscribe and unsubscribe are the only required prop for the context */ /* subscribe and unsubscribe are the only required prop for the context */
return ( return (
<WebSocketContext.Provider value={[subscribe, unsubscribe, socket, sendMessage]}> <WebSocketContext.Provider
value={[subscribe, unsubscribe, socket, sendMessage]}
>
{children} {children}
</WebSocketContext.Provider> </WebSocketContext.Provider>
) );
} }
export { WebSocketContext, WebSocketProvider } export { WebSocketContext, WebSocketProvider };

View File

@@ -1,64 +1,83 @@
import { Card, CardContent, Divider, InputAdornment, MenuItem, Select, } from "@mui/material" import {
Card,
CardContent,
Divider,
InputAdornment,
MenuItem,
Select,
} from "@mui/material";
import DashboardLayout from "../../ui-kit/examples/LayoutContainers/DashboardLayout" import DashboardLayout from "../../ui-kit/examples/LayoutContainers/DashboardLayout";
import MDBox from "../../ui-kit/components/MDBox" import MDBox from "../../ui-kit/components/MDBox";
import { useMaterialUIController } from "../../ui-kit/context" import { useMaterialUIController } from "../../ui-kit/context";
import { useContext, useEffect, useRef, useState } from "react" import { useContext, useEffect, useRef, useState } from "react";
// Images // Images
import MDTypography from "../../ui-kit/components/MDTypography" import MDTypography from "../../ui-kit/components/MDTypography";
import { CardFooter } from "react-bootstrap" import { CardFooter } from "react-bootstrap";
import MDInput from "../../ui-kit/components/MDInput" import MDInput from "../../ui-kit/components/MDInput";
import { ErrorMessage, Field, Form, Formik } from "formik" import { ErrorMessage, Field, Form, Formik } from "formik";
import MDButton from "../../ui-kit/components/MDButton" import MDButton from "../../ui-kit/components/MDButton";
import { AttachFile, Send } from "@mui/icons-material" import { AttachFile, Send } from "@mui/icons-material";
import Header2 from "../../components/Header2/Header2" import Header2 from "../../components/Header2/Header2";
import DashboardWrapperLayout from "../../components/DashboardWrapperLayout/DashboardWrapperLayout" import DashboardWrapperLayout from "../../components/DashboardWrapperLayout/DashboardWrapperLayout";
import * as Yup from 'yup'; import * as Yup from "yup";
import { Announcement, AnnouncementType, Conversation, ConversationPrompt, ConversationPromptType, ConversationType } from "../../data" import {
import { axiosInstance } from "../../../axiosApi" Announcement,
import { AxiosResponse } from "axios" AnnouncementType,
import { ConversationContext } from "../../contexts/ConversationContext" Conversation,
import Markdown from "markdown-to-jsx" ConversationPrompt,
import ConversationDetailCard from "../../components/ConversationDetailCard/ConversationDetailCard" ConversationPromptType,
import { WebSocketContext } from "../../contexts/WebSocketContext" ConversationType,
import { AccountContext } from "../../contexts/AccountContext" } from "../../data";
import { styled } from '@mui/material/styles'; import { axiosInstance } from "../../../axiosApi";
import Footer from "../../components/Footer/Footer" import { AxiosResponse } from "axios";
import { MessageContext } from "../../contexts/MessageContext" import { ConversationContext } from "../../contexts/ConversationContext";
import CustomSelect, { CustomSelectItem } from "../../components/CustomSelect/CustomSelect" import Markdown from "markdown-to-jsx";
import ConversationDetailCard from "../../components/ConversationDetailCard/ConversationDetailCard";
import { WebSocketContext } from "../../contexts/WebSocketContext";
import { AccountContext } from "../../contexts/AccountContext";
import { styled } from "@mui/material/styles";
import Footer from "../../components/Footer/Footer";
import { MessageContext } from "../../contexts/MessageContext";
import CustomSelect, {
CustomSelectItem,
} from "../../components/CustomSelect/CustomSelect";
const MODELS = ["Turbo","RAG"] const MODELS = ["Turbo", "RAG"];
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<
React.SetStateAction<number | undefined>
>;
drawerWidth: number; 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,
}); });
@@ -67,22 +86,21 @@ type PromptValues = {
file: Blob | null; file: Blob | null;
fileType: string | null; fileType: string | null;
modelName: string; modelName: string;
}; };
const models: CustomSelectItem[] = [ const models: CustomSelectItem[] = [
{ {
label: "General", label: "General",
value: "GENERAL" value: "GENERAL",
}, },
{ {
label: "Code", label: "Code",
value: "CODE" value: "CODE",
}, },
{ {
label: "Reasoning", label: "Reasoning",
value: "REASONING" value: "REASONING",
} },
]; ];
const AlwaysScrollToBottom = (): JSX.Element => { const AlwaysScrollToBottom = (): JSX.Element => {
@@ -93,87 +111,112 @@ const AlwaysScrollToBottom = (): JSX.Element => {
const AsyncDashboardInner = ({}): JSX.Element => { const AsyncDashboardInner = ({}): JSX.Element => {
const [controller, dispatch] = useMaterialUIController(); const [controller, dispatch] = useMaterialUIController();
const { const { darkMode } = controller;
darkMode, const buttonColor = darkMode ? "dark" : "light";
} = controller;
const buttonColor = darkMode? 'dark' : 'light';
const [announcements, setAnnouncement] = useState<Announcement[]>([]); const [announcements, setAnnouncement] = useState<Announcement[]>([]);
const [subscribe, unsubscribe, socket, sendMessage] = useContext(WebSocketContext) const [subscribe, unsubscribe, socket, sendMessage] =
const { account } = useContext(AccountContext) useContext(WebSocketContext);
const { account } = useContext(AccountContext);
const [isClosing, setIsClosing] = useState(false); const [isClosing, setIsClosing] = useState(false);
const {conversations, selectedConversation, setSelectedConversation} = useContext(ConversationContext); const { conversations, selectedConversation, setSelectedConversation } =
useContext(ConversationContext);
const {conversationDetails, setConversationDetails, stateMessage, isGeneratingMessage} = useContext(MessageContext);
const conversationRef = useRef(conversationDetails)
const {
conversationDetails,
setConversationDetails,
stateMessage,
isGeneratingMessage,
} = useContext(MessageContext);
const conversationRef = useRef(conversationDetails);
async function GetAnnouncements() { async function GetAnnouncements() {
const response: AxiosResponse<AnnouncementType[]> = await axiosInstance.get('announcment/get/') const response: AxiosResponse<AnnouncementType[]> =
setAnnouncement(response.data.map((status, message) => new Announcement({ await axiosInstance.get("announcment/get/");
setAnnouncement(
response.data.map(
(status, message) =>
new Announcement({
status: status, status: status,
message: message message: message,
}))) }),
),
);
} }
const conversationTitle = conversations.find(item => item.id === selectedConversation)?.title ?? 'New Conversation'; const conversationTitle =
const colorName = darkMode ? 'light' : 'dark'; conversations.find((item) => item.id === selectedConversation)?.title ??
"New Conversation";
const colorName = darkMode ? "light" : "dark";
const handlePromptSubmit = async ({prompt, file, fileType, modelName}: PromptValues, {resetForm}: any): Promise<void> => {
const handlePromptSubmit = async (
{ prompt, file, fileType, modelName }: PromptValues,
{ resetForm }: any,
): Promise<void> => {
// send the prompt to be saved // send the prompt to be saved
try { try {
const tempConversations: ConversationPrompt[] = [...conversationDetails, new ConversationPrompt({message: prompt, user_created:true}), new ConversationPrompt({message: '', user_created:false})] const tempConversations: ConversationPrompt[] = [
...conversationDetails,
new ConversationPrompt({ message: prompt, user_created: true }),
new ConversationPrompt({ message: "", user_created: false }),
];
conversationRef.current = tempConversations conversationRef.current = tempConversations;
setConversationDetails(tempConversations) setConversationDetails(tempConversations);
// TODO: add the file here // TODO: add the file here
sendMessage(prompt, selectedConversation, file, fileType, modelName) sendMessage(prompt, selectedConversation, file, fileType, modelName);
resetForm(); resetForm();
} catch (e) { } catch (e) {
console.log(`error ${e}`) console.log(`error ${e}`);
// TODO: make this user friendly // TODO: make this user friendly
} }
} };
return ( return (
<DashboardLayout> <DashboardLayout>
<Header2 /> <Header2 />
<MDBox sx={{mt:5}}> <MDBox sx={{ mt: 5 }}></MDBox>1
<MDBox
</MDBox>1 sx={{
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}> margin: "0 auto",
width: "80%",
height: "80%",
minHeight: "80%",
maxHeight: "80%",
align: "center",
}}
>
<Card> <Card>
<CardContent> <CardContent>
<MDTypography variant="h3" > <MDTypography variant="h3">{conversationTitle}</MDTypography>
{conversationTitle}
</MDTypography>
</CardContent> </CardContent>
<Divider /> <Divider />
<CardContent> <CardContent>
<> <>
{conversationDetails.length > 0 ? {conversationDetails.length > 0 ? (
conversationDetails.map((convo_detail) => conversationDetails.map((convo_detail) =>
convo_detail.message.length >0 ? convo_detail.message.length > 0 ? (
<ConversationDetailCard message={convo_detail.message} user_created={convo_detail.user_created} key={convo_detail.id}/> : <ConversationDetailCard
<ConversationDetailCard message={stateMessage} user_created={convo_detail.user_created} key={convo_detail.id}/> 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-center' color='inherit'>Either select a previous conversation on start a new one.</Markdown> <Markdown className="text-center" color="inherit">
} Either select a previous conversation on start a new one.
</Markdown>
)}
<AlwaysScrollToBottom /> <AlwaysScrollToBottom />
</> </>
</CardContent> </CardContent>
@@ -181,58 +224,54 @@ return(
<CardFooter> <CardFooter>
<Formik <Formik
initialValues={{ initialValues={{
prompt: '', prompt: "",
file: null, file: null,
fileType: null, fileType: null,
modelName: '', modelName: "",
}} }}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={handlePromptSubmit} onSubmit={handlePromptSubmit}
> >
{(formik)=> {(formik) => (
<Form> <Form>
<Field <Field
name={"prompt"} name={"prompt"}
fullWidth fullWidth
as={MDInput} as={MDInput}
label={'Prompt'} label={"Prompt"}
errorstring={<ErrorMessage name={'prompt'}/>} errorstring={<ErrorMessage name={"prompt"} />}
size={'large'} size={"large"}
role={undefined} role={undefined}
tabIndex={-1} tabIndex={-1}
variant={"outlined"} variant={"outlined"}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position='end'> <InputAdornment position="end">
<MDButton <MDButton
component="label" component="label"
startIcon={<AttachFile />} startIcon={<AttachFile />}
role={undefined} role={undefined}
tabIndex={-1} tabIndex={-1}
color={buttonColor} color={formik.values.file ? "dark" : "light"}
> >
<VisuallyHiddenInput <VisuallyHiddenInput
type="file" type="file"
accept='.csv,.xlsx,.txt' accept=".csv,.xlsx,.txt"
onChange={(event) => { onChange={(event) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
//console.log(file) //console.log(file)
if (file) { if (file) {
formik.setFieldValue('file',file) formik.setFieldValue("file", file);
formik.setFieldValue('fileType',file.type) formik.setFieldValue("fileType", file.type);
} else { } else {
formik.setFieldValue('file',null) formik.setFieldValue("file", null);
formik.setFieldValue('fileType',null) formik.setFieldValue("fileType", null);
} }
}} }}
/> />
</MDButton> </MDButton>
<MDButton <MDButton
type={'submit'} type={"submit"}
startIcon={<Send />} startIcon={<Send />}
disabled={!formik.isValid} disabled={!formik.isValid}
color={buttonColor} color={buttonColor}
@@ -240,17 +279,13 @@ return(
<></> <></>
</MDButton> </MDButton>
</InputAdornment> </InputAdornment>
) ),
}} }}
></Field>
>
</Field>
</Form> </Form>
} )}
</Formik> </Formik>
{/* <MDInput {/* <MDInput
label="Prompt" label="Prompt"
/> */} /> */}
@@ -258,24 +293,16 @@ return(
</Card> </Card>
<Footer /> <Footer />
</MDBox> </MDBox>
</DashboardLayout> </DashboardLayout>
) );
} };
const AsyncDashboard2 = ({}): JSX.Element => { const AsyncDashboard2 = ({}): JSX.Element => {
return ( return (
<DashboardWrapperLayout> <DashboardWrapperLayout>
<AsyncDashboardInner /> <AsyncDashboardInner />
</DashboardWrapperLayout> </DashboardWrapperLayout>
);
};
export default AsyncDashboard2;
)
}
export default AsyncDashboard2