inital check in

This commit is contained in:
2025-03-07 12:21:37 -06:00
parent a1d67c7c15
commit 626d471a15
382 changed files with 48131 additions and 0 deletions

2
llm-fe/.env Normal file
View File

@@ -0,0 +1,2 @@
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/

2
llm-fe/.env.development Normal file
View File

@@ -0,0 +1,2 @@
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/

2
llm-fe/.env.production Normal file
View File

@@ -0,0 +1,2 @@
REACT_APP_BACKEND_REST_API_BASE_URL=https://chatbackend.aimloperations.com/api/
REACT_APP_BACKEND_WS_API_BASE_URL=wss://chatbackend.aimloperations.com/ws/chat_again/

23
llm-fe/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

46
llm-fe/README.md Normal file
View File

@@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

BIN
llm-fe/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

21303
llm-fe/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

73
llm-fe/package.json Normal file
View File

@@ -0,0 +1,73 @@
{
"name": "llm-fe",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^5.16.11",
"@mui/material": "^5.16.11",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.2",
"axios": "^1.7.9",
"babel-loader": "^9.2.1",
"bootstrap": "^5.3.3",
"chroma-js": "^3.1.2",
"formik": "^2.4.6",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"markdown-to-jsx": "^7.7.2",
"mini.css": "^3.0.1",
"react-bootstrap": "^2.10.6",
"react-code-blocks": "^0.1.6",
"react-github-btn": "^1.4.0",
"react-router-dom": "^6.28.0",
"react-scripts": "^5.0.1",
"react-syntax-highlighter": "^15.6.1",
"recharts": "^2.15.1",
"styled-components": "^6.1.14",
"stylis-plugin-rtl": "^2.1.1",
"web-vitals": "^4.2.4",
"webpack": "^5.97.1",
"webpack-cli": "^5.1.4",
"yup": "^1.5.0"
},
"scripts": {
"start": "NODE_ENV=development react-scripts start",
"build": "NODE_ENV=production react-scripts build",
"test": "NODE_ENV=development react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/bootstrap": "~5.2.10",
"@types/lodash": "~4.17.13",
"@types/react": "^18.3.16",
"@types/react-dom": "^18.3.5",
"@types/react-syntax-highlighter": "^15.5.13",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^4.9.5"
}
}

BIN
llm-fe/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

43
llm-fe/public/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Chat by AI ML Operations. LCC</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
llm-fe/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
llm-fe/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,15 @@
{
"short_name": "Chat by AI ML Operations. LLC",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
llm-fe/public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

1
llm-fe/scp_command.txt Normal file
View File

@@ -0,0 +1 @@
scp -r ./* westfarn@10.0.0.103:/var/www/chat.aimloperations/html

71
llm-fe/src/App.css Normal file
View File

@@ -0,0 +1,71 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.main-content {
flex: 1;
padding: 20px;
}
/* .app-container {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.sidebar {
background-color: #f4f4f4;
padding: 20px;
border-right: 1px solid #ddd;
}
.message-box {
height: 100%;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
}
.footer {
background-color: #f4f4f4;
padding: 10px;
text-align: center;
border-top: 1px solid #ddd;
} */

9
llm-fe/src/App.test.tsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

76
llm-fe/src/App.tsx Normal file
View File

@@ -0,0 +1,76 @@
import React, { Component, useContext } from 'react';
import './App.css';
import Dashboard from './llm-fe/pages/Dashboard/Dashboard';
import { Routes, Route, createRoutesFromElements, Outlet, Navigate, useLocation } from 'react-router-dom';
import { createRoot } from 'react-dom/client';
import SignIn from './llm-fe/pages/SignIn/SignIn';
import PasswordReset from './llm-fe/pages/PasswordReset/PasswordReset';
import NotFound from './llm-fe/pages/NotFound/NotFound';
import { AuthContext, AuthProvider } from './llm-fe/contexts/AuthContext';
import AccountPage from './llm-fe/pages/Account/Account';
import AsyncDashboard from './llm-fe/pages/AsyncDashboard/AsyncDashboard';
import TermsOfService from './llm-fe/pages/TermsOfService/TermsOfService';
import SetPassword from './llm-fe/components/SetPassword/SetPassword';
import FeedbackPage from './llm-fe/pages/FeedbackPage/FeedbackPage';
import { Typography } from '@mui/material';
import AsyncDashboard2 from './llm-fe/pages/AsyncDashboard2/AsyncDashboard2';
import FeedbackPage2 from './llm-fe/pages/FeedbackPage2/FeedbackPage2';
import Account2 from './llm-fe/pages/Account2/Account2';
import AnalyticsPage from './llm-fe/pages/Analytics/Analytics';
const ProtectedRoutes = () => {
const { authenticated, needsNewPassword, loading } = useContext(AuthContext);
if(loading){
return(
<div>Loading</div>
)
}
if(!authenticated) return <Navigate to='/signin/' replace />
//if(!needsNewPassword) return <Navigate to='/password_reset/' replace/>
return <Outlet key={Date.now()}/>
}
class App extends Component {
render() {
return (
<div className='site'>
<main>
<div className="main-container">
<Routes>
<Route path='*' element={<NotFound />} />
<Route path={"/signin/"} Component={SignIn}/>
<Route path={"/password_reset/"} Component={PasswordReset}/>
<Route path={'/set_password/'} Component={SetPassword}/>
<Route element={<ProtectedRoutes />}>
<Route path={"/"} index={true} Component={AsyncDashboard2}/>
<Route path={"/account/"} Component={Account2}/>
<Route path={"/terms_of_service/"} Component={TermsOfService}/>
<Route path={"/feedback/"} Component={FeedbackPage2}/>
<Route path={"/analytics/"} Component={AnalyticsPage} />
</Route>
</Routes>
</div>
</main>
</div>
);
}
}
export default App;

75
llm-fe/src/axiosApi.js Normal file
View File

@@ -0,0 +1,75 @@
import axios from "axios";
//const baseURL = 'http://127.0.0.1:8001/api/';
const baseURL = 'https://chatbackend.aimloperations.com/api/';
//const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL;
export const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
headers: {
"Authorization": 'JWT ' + localStorage.getItem('access_token'),
'Content-Type': 'application/json',
'Accept': 'application/json',
}
});
axiosInstance.interceptors.request.use(config => {
config.timeout = 100000;
return config;
})
axiosInstance.interceptors.response.use(
response => response,
error => {
const originalRequest = error.config;
// Prevent infinite loop
if (error.response.status === 401 && originalRequest.url === baseURL+'/token/refresh/') {
window.location.href = '/signin/';
//console.log('remove the local storage here')
return Promise.reject(error);
}
if(error.response.data.code === "token_not_valid" &&
error.response.status == 401 &&
error.response.statusText == 'Unauthorized')
{
const refresh_token = localStorage.getItem('refresh_token');
if (refresh_token){
const tokenParts = JSON.parse(atob(refresh_token.split('.')[1]));
const now = Math.ceil(Date.now() / 1000);
//console.log(tokenParts.exp)
if(tokenParts.exp > now){
return axiosInstance.post('/token/refresh/', {refresh: refresh_token}).then((response) => {
localStorage.setItem('access_token', response.data.access);
localStorage.setItem('refresh_token', response.data.refresh);
axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access;
originalRequest.headers['Authorization'] = 'JWT ' + response.data.access;
return axiosInstance(originalRequest);
}).catch(err => {
console.log(err)
});
}else{
console.log('Refresh token is expired');
window.location.href = '/signin/';
}
}else {
console.log('Refresh token not available');
window.location.href = '/signin/';
}
}
return Promise.reject(error);
}
);

BIN
llm-fe/src/bg.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

42
llm-fe/src/index.css Normal file

File diff suppressed because one or more lines are too long

37
llm-fe/src/index.tsx Normal file
View File

@@ -0,0 +1,37 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from './llm-fe/contexts/AuthContext';
import { AccountProvider } from './llm-fe/contexts/AccountContext';
import { WebSocketProvider } from './llm-fe/contexts/WebSocketContext';
import { MaterialUIControllerProvider } from './llm-fe/ui-kit/context';
import { ConversationProvider } from './llm-fe/contexts/ConversationContext';
import { MessageProvider } from './llm-fe/contexts/MessageContext';
import { PreferenceProvider } from './llm-fe/contexts/PreferencesContext';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<AccountProvider>
<WebSocketProvider>
<PreferenceProvider>
<ConversationProvider>
<MessageProvider>
<MaterialUIControllerProvider>
<App />
</MaterialUIControllerProvider>
</MessageProvider>
</ConversationProvider>
</PreferenceProvider>
</WebSocketProvider>
</AccountProvider>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
);

View File

@@ -0,0 +1,311 @@
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 AsyncChatProps = {
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")
}
)
const VisuallyHiddenInput = styled('input')({
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
height: 1,
overflow: 'hidden',
position: 'absolute',
bottom: 0,
left: 0,
whiteSpace: 'nowrap',
width: 1,
});
const AsyncChat = ({selectedConversation, conversationTitle, conversations, setConversations, setSelectedConversation, drawerWidth}: AsyncChatProps): JSX.Element => {
const messageEndRef = useRef(null);
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 messageResponsePart = useRef(0);
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}`
/* 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
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){
// 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;
};
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}
>
{(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
type={'submit'}
// disabled={selectedConversation === undefined ? true : false || !formik.isValid}
>Send
</Button>
</div> */}
</div>
</Form>
}
</Formik>
</div>
</Box>
)
}
export default AsyncChat

View File

@@ -0,0 +1,16 @@
import React, { JSX, ReactNode } from 'react';
export type CardProps = {
children?: ReactNode
};
const Card = ({children}: CardProps): JSX.Element => {
return(
<div className='card card-body'>
{children}
</div>
)
}
export default Card;

View File

@@ -0,0 +1,93 @@
import { Dispatch, SetStateAction } from "react";
import { Conversation, ConversationType } from "../../data";
import { Box, Button, ButtonBase, Card, CardContent, IconButton, Typography } from "@mui/material";
import { Delete } from "@mui/icons-material";
import { ButtonGroup, Col, Row } from "react-bootstrap";
import MDTypography from "../../ui-kit/components/MDTypography";
type ConversationCardProps = {
title: string,
conversation_id: number | undefined,
setSelectConversation: Dispatch<SetStateAction<number | undefined>>; // state function to set the selected conversation
deleteConversation: (conversation_id: number | undefined)=>void;
selectedConversation: number | undefined;
};
const ConversationCard = ({title, conversation_id, setSelectConversation, deleteConversation, selectedConversation}: ConversationCardProps): JSX.Element => {
const backgroundColor = selectedConversation===conversation_id ? '#ffffff' : 'lightgray';
return (
// <Row>
// <ButtonGroup>
// <ButtonBase onClick={() => {setSelectConversation(conversation_id)}} style={{width: "80%", margin:'1px'}}>
// <CardContent >{title}</CardContent>
// </ButtonBase>
// <IconButton aria-label="delete ${conversation_id}" onClick={() => deleteConversation(conversation_id)}>
// <Delete />
// </IconButton>
// </ButtonGroup>
// </Row>
<Row color="red">
<Col className="col-8">
<MDTypography
onClick={() => {setSelectConversation(conversation_id)}}
key={conversation_id}
color={'white'}
fontWeight={selectedConversation===conversation_id ? 'bold' : 'regular'}
display="block"
variant="caption"
textTransform="uppercase"
mt={2}
mb={1}
ml={1}
noWrap
>
{title}
</MDTypography>
</Col>
<Col className="col-4">
<IconButton
aria-label="delete ${conversation_id}"
onClick={() => deleteConversation(conversation_id)}
>
<Delete sx={{color:backgroundColor}}/>
</IconButton>
</Col>
</Row>
// <Card style={{width: "100%", margin:'0px'}}
// sx={{
// backgroundColor: selectedConversation === conversation_id ? 'lightblue' : 'inherit',
// }}
// >
// <div className="row">
// <div className='col-10'>
// <ButtonBase onClick={() => {console.log(conversation_id); setSelectConversation(conversation_id)}} style={{width: "100%", margin:'1px'}}>
// <CardContent style={{width: "100%", margin:'0px'}}>{title}</CardContent>
// </ButtonBase>
// </div>
// <div className='col-1'>
// <IconButton aria-label="delete ${conversation_id}" onClick={() => deleteConversation(conversation_id)}>
// <Delete />
// </IconButton>
// </div>
// </div>
// </Card>
);
}
export default ConversationCard;

View File

@@ -0,0 +1,69 @@
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"
const StyleDiv = styled.div`
background-color: #f0f0f0;
padding: 20px;
border-radius: 8px;
h1 {
color: #333;
font-size: 24px;
}
span {
color: #666;
font-size: 16px;
}
`;
type ConversationDetailCardProps = {
message: string
user_created: boolean
}
const ConversationDetailCard = ({message, user_created}: ConversationDetailCardProps): JSX.Element => {
const type = user_created ? 'info' : 'dark'
if(user_created){
}
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");
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"}}
color='#F000000'
>{newMessage}</Markdown>
</CardContent>
</Card>
)
}
}
export default ConversationDetailCard;

View File

@@ -0,0 +1,54 @@
// src/components/ChatDrawer.tsx
import React, { Dispatch, SetStateAction } from 'react';
import { Conversation, ConversationType } from "../../data";
import { Button, Drawer, List, ListItem, ListItemText, Typography } from '@mui/material';
import ConversationCard from '../ConversationCard/ConversationCard';
interface ChatDrawerProps {
conversations: Conversation[];
setSelectConversation: Dispatch<SetStateAction<number | undefined>>; // state function to set the selected conversation
deleteConversation: (conversation_id: number | undefined)=>void;
selectedConversation: number | undefined;
}
const ConversationDrawer = ({ conversations,
setSelectConversation,
deleteConversation,
selectedConversation
}: ChatDrawerProps): JSX.Element => {
return (
//<Drawer variant="temporary" anchor="left" hideBackdrop sx={{width: 320}}>
<>
<Button color={'inherit'} onClick={() => setSelectConversation(undefined)}>
New Conversation
</Button>
<List>
{/* {conversations.map((conversation) => (
<ListItem
button
key={conversation.id}
onClick={() => setSelectConversation(conversation.id)}
sx={{
backgroundColor: selectedConversation === conversation.id ? 'lightblue' : 'inherit',
}}
>
<ListItemText primary={conversation.title} />
</ListItem>
))} */}
{ conversations ? (
conversations.map((conversation) =>
<ConversationCard key={conversation.id} title={conversation.title} conversation_id={conversation.id} setSelectConversation={setSelectConversation} deleteConversation={deleteConversation} selectedConversation={selectedConversation}/>
)
): (
<p> Either select a previous conversation, or enter a prompt to start a new one.</p>
)}
</List>
</>
//</Drawer>
);
};
export default ConversationDrawer;

View File

@@ -0,0 +1,113 @@
import { Dispatch, SetStateAction } from "react";
import { Conversation, ConversationType } from "../../data";
import { Box, Button, ButtonBase, Card, CardContent, IconButton, Typography } from "@mui/material";
import { Delete } from "@mui/icons-material";
import { Form, Formik } from "formik";
import CustomTextField from "../CustomTextField/CustomTextField";
import { axiosInstance } from "../../../axiosApi";
import { AxiosResponse } from "axios";
import * as Yup from 'yup';
type ConversationCardProps = {
title: string,
conversation_id: number | undefined,
setSelectConversation: Dispatch<SetStateAction<number | undefined>>; // state function to set the selected conversation
deleteConversation: (conversation_id: number | undefined)=>void;
};
const ConversationCard = ({title, conversation_id, setSelectConversation, deleteConversation}: ConversationCardProps): JSX.Element => {
return (
<Card style={{width: "100%", margin:'0px'}}>
<div className="row">
<div className='col-10'>
<ButtonBase onClick={() => {console.log(conversation_id); setSelectConversation(conversation_id)}} style={{width: "100%", margin:'1px'}}>
<CardContent style={{width: "100%", margin:'0px'}}>{title}</CardContent>
</ButtonBase>
</div>
<div className='col-1'>
<IconButton aria-label="settings" onClick={() => deleteConversation(conversation_id)}>
<Delete />
</IconButton>
</div>
</div>
</Card>
);
}
type ConversationLogProps = {
conversations: Conversation[];
setSelectConversation: Dispatch<SetStateAction<number | undefined>>; // state function to set the selected conversation
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>;
deleteConversation: (conversation_id: number | undefined)=>void;
};
type ConversationValues = {
name: string;
};
const validataionSchema = Yup.object().shape({
name: Yup.string().min(1, "Need to have at least one character").required("This is requried")
}
)
const initialValues = {name: ''}
const ConversationLog = ({conversations, setConversations, setSelectConversation, deleteConversation}: ConversationLogProps): JSX.Element => {
const handleConversationSubmit = async ({name}: ConversationValues): Promise<void> => {
try{
const {data, }: AxiosResponse<ConversationType> = await axiosInstance.post('/conversations', {
name: name
})
setConversations([...conversations, new Conversation({title: data.title, id: data.id})])
}catch{
}
}
return (
<div className="card" style= {{height: 'auto'}}>
<Box sx={{
mb: 2,
display: "flex",
flexDirection: "column",
height: 950,
overflow: "hidden",
overflowY: "scroll",
// justifyContent="flex-end" # DO NOT USE THIS WITH 'scroll'
}}>
{
conversations ? (
conversations.map((conversation ) =>
<ConversationCard key={conversation.id} title={conversation.title} conversation_id={conversation.id} setSelectConversation={setSelectConversation} deleteConversation={deleteConversation}/>
// <ButtonBase onClick={() => setSelectConversation(conversation.id)} style={{width: "100%", margin:'1px'}}>
// </ButtonBase>
)
) : (
<p>Conversations will show up here</p>
)
}
</Box>
</div>
);
}
export default ConversationLog;

View File

@@ -0,0 +1,72 @@
import { FileUpload } from "@mui/icons-material";
import { Button, IconButton, OutlinedInput, TextField } from "@mui/material";
import InputAdornment from '@mui/material/InputAdornment';
import { useState } from "react";
export type CustomAdornmentTextFieldProps = {
label: string,
name: string,
changeHandler: (event: React.ChangeEvent<HTMLInputElement>) => void,
isMultline: boolean,
}
const CustomAdornmentTextField = (props: CustomAdornmentTextFieldProps) => {
const [fileName, setFileName] = useState<string>("");
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setFileName(file.name);
} else {
setFileName("");
}
};
//<InputLabel htmlFor="outlined-adornment-password">Password</InputLabel>
return (
<div>
<TextField
fullWidth
variant="outlined"
label="Choose File"
value={fileName}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Button variant="contained" component="label">
Browse
<input
type="file"
hidden
onChange={handleFileChange}
/>
</Button>
</InputAdornment>
),
}}
/>
</div>
// <OutlinedInput
// label={props.label}
// name={props.name}
// onChange={props.changeHandler}
// fullWidth={true}
// endAdornment={
// <InputAdornment position="end">
// <Button variant="contained" component="label">
// <IconButton>
// <FileUpload/>
// </IconButton>
// <input
// type="file"
// hidden
// onChange={handleFileChange}
// />
// </Button>
// </InputAdornment>
// }
// />
);
}
export default CustomAdornmentTextField;

View File

@@ -0,0 +1,25 @@
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import tsx from "react-syntax-highlighter/dist/cjs/languages/prism/tsx";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
SyntaxHighlighter.registerLanguage("tsx", tsx);
type CustomCodeBlock = {
children: string,
className: string
}
const CustomCodeBlock = ({children, className}: CustomCodeBlock): JSX.Element => {
const language = className?.replace("lang-","");
return (
<SyntaxHighlighter language={language} style={oneDark}>
{children}
</SyntaxHighlighter>
)
}
export default CustomCodeBlock;

View File

@@ -0,0 +1,27 @@
import { TextField } from "@mui/material";
export type CustomPasswordFieldProps = {
label: string,
name: string,
changeHandler: (event: React.ChangeEvent<HTMLInputElement>) => void,
}
const CustomPasswordField = (props: CustomPasswordFieldProps) => {
return (
<TextField
label={props.label}
name={props.name}
onChange={props.changeHandler}
fullWidth={true}
type='password'
variant={"outlined"} //enables special material-ui styling
size={"small"}
margin={"dense"}
/>
);
}
export default CustomPasswordField;

View File

@@ -0,0 +1,14 @@
import CustomCodeBlock from "../CustomCodeBlock/CustomCodeBlock"
type CustomPreBlockProps = {
children: JSX.Element | JSX.Element[]
}
const CustomPreBlock = ({children, ...rest}: CustomPreBlockProps): JSX.Element => {
if ("type" in children && children["type"] === "code") {
return CustomCodeBlock({children: children["props"]["children"], className: children["props"]["className"] });
}
return <pre {...rest}>{children}</pre>
}
export default CustomPreBlock;

View File

@@ -0,0 +1,75 @@
import React, { ReactNode } from "react";
import { Field, ErrorMessage, FieldInputProps } from "formik";
import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
export interface CustomSelectItem {
label: string;
value: string;
}
interface FormikSelectProps {
name: string;
items: CustomSelectItem[];
label: string;
required?: boolean;
}
interface MaterialUISelectFieldProps extends FieldInputProps<string> {
errorString?: string;
children: ReactNode;
label: string;
required: boolean;
}
const MaterialUISelectField = ({
errorString,
label,
children,
value,
name,
onChange,
onBlur,
required
}: MaterialUISelectFieldProps): JSX.Element => {
return (
<FormControl fullWidth
variant={"outlined"}>
<InputLabel required={required}
variant={"outlined"}>{label}</InputLabel>
<Select name={name} onChange={onChange} onBlur={onBlur} value={value} size={"small"}
variant={"outlined"}>
{children}
</Select>
<FormHelperText>{errorString}</FormHelperText>
</FormControl>
);
};
const CustomSelect = ({ name, items, label, required = false }: FormikSelectProps):JSX.Element => {
return (
<div className="FormikSelect">
<Field
name={name}
as={MaterialUISelectField}
label={label}
errorString={<ErrorMessage name={name} />}
required={required}
variant={"outlined"}
>
{items.map(item => (
<MenuItem key={item.value} value={item.value}>
{item.label}
</MenuItem>
))}
</Field>
</div>
);
};
export default CustomSelect;

View File

@@ -0,0 +1,26 @@
import { TextField } from "@mui/material";
export type CustomTextFieldProps = {
label: string,
name: string,
changeHandler: (event: React.ChangeEvent<HTMLInputElement>) => void,
isMultline: boolean,
}
const CustomTextField = (props: CustomTextFieldProps) => {
return (
<TextField
label={props.label}
name={props.name}
onChange={props.changeHandler}
fullWidth={true}
variant={"outlined"} //enables special material-ui styling
size={"small"}
multiline={props.isMultline}
margin={"dense"}
/>
);
}
export default CustomTextField;

View File

@@ -0,0 +1,22 @@
export type CustomToastMessageProps = {
message: string
}
const CustomToastMessage= ({message}: CustomToastMessageProps): JSX.Element => {
return (
<div className="position-fixed bottom-0 end-0 p-3" style={{zIndex: 11}}>
<div id="liveToast" className="toast hide" role="alert" aria-live="assertive" aria-atomic="true">
<div className="toast-header">
<strong className="me-auto">Bootstrap</strong>
<small>11 mins ago</small>
<button type="button" className="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div className="toast-body">
{message}
</div>
</div>
</div>
);
}
export default CustomToastMessage;

View File

@@ -0,0 +1,152 @@
import createCache, { EmotionCache } from "@emotion/cache";
import { setMiniSidenav, setOpenConfigurator, useMaterialUIController } from "../../ui-kit/context";
import { useEffect, useMemo, useState } from "react";
import { useLocation } from "react-router-dom";
import stylisRTLPlugin from "stylis-plugin-rtl";
import MDBox from "../../ui-kit/components/MDBox";
import { CacheProvider } from "@emotion/react";
import { CssBaseline, Icon, ThemeProvider } from "@mui/material";
import brandWhite from "../../ui-kit/assets/images/logo-ct.png";
import brandDark from "../../ui-kit/assets/images/logo-ct-dark.png";
import themeDark from "../../ui-kit/assets/theme-dark";
import themeDarkRTL from "../../ui-kit/assets/theme-dark/theme-rtl";
import theme from "../../ui-kit/assets/theme";
import themeRTL from "../../ui-kit/assets/theme/theme-rtl";
import Sidenav from "../../ui-kit/examples/Sidenav";
import Configurator from "../../ui-kit/examples/Configurator";
type DashboardWrapperLayoutProps = {
children: React.ReactNode;
}
const DashboardWrapperLayout = ({children}: DashboardWrapperLayoutProps): JSX.Element => {
const [controller, dispatch] = useMaterialUIController();
const {
miniSidenav,
direction,
layout,
openConfigurator,
sidenavColor,
transparentSidenav,
whiteSidenav,
darkMode,
} = controller;
const [onMouseEnter, setOnMouseEnter] = useState(false);
const [rtlCache, setRtlCache] = useState<EmotionCache | null>(null);
const { pathname } = useLocation();
// Cache for the rtl
useMemo(() => {
const cacheRtl = createCache({
key: "rtl",
stylisPlugins: [stylisRTLPlugin],
});
setRtlCache(cacheRtl);
}, []);
// Open sidenav when mouse enter on mini sidenav
const handleOnMouseEnter = () => {
if (miniSidenav && !onMouseEnter) {
setMiniSidenav(dispatch, false);
setOnMouseEnter(true);
}
};
// Close sidenav when mouse leave mini sidenav
const handleOnMouseLeave = () => {
if (onMouseEnter) {
setMiniSidenav(dispatch, true);
setOnMouseEnter(false);
}
};
// Change the openConfigurator state
const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator);
// Setting the dir attribute for the body element
useEffect(() => {
document.body.setAttribute("dir", direction);
}, [direction]);
// Setting page scroll to 0 when changing the route
useEffect(() => {
document.documentElement.scrollTop = 0;
if(document.scrollingElement){
document.scrollingElement.scrollTop = 0;
}
}, [pathname]);
const configsButton = (
<MDBox
display="flex"
justifyContent="center"
alignItems="center"
width="3.25rem"
height="3.25rem"
bgColor="white"
shadow="sm"
borderRadius="50%"
position="fixed"
right="2rem"
bottom="2rem"
zIndex={99}
color="dark"
sx={{ cursor: "pointer" }}
onClick={handleConfiguratorOpen}
>
<Icon fontSize="small" color="inherit">
settings
</Icon>
</MDBox>
);
return direction === "rtl" ? (
<CacheProvider value={rtlCache}>
<ThemeProvider theme={darkMode ? themeDarkRTL : themeRTL}>
<CssBaseline />
{layout === "dashboard" && (
<>
<Sidenav
color={sidenavColor}
brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
brandName="Chat by AI ML, Operations, LLC"
routes={[]} // {routes}
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
/>
{/* <Configurator />
{configsButton} */}
</>
)}
{layout === "vr" && <Configurator />}
{children}
</ThemeProvider>
</CacheProvider>
) : (
<ThemeProvider theme={darkMode ? themeDark : theme}>
<CssBaseline />
{layout === "dashboard" && (
<>
<Sidenav
color={sidenavColor}
brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
brandName="AI ML, Operations, LLC"
routes={[]} // {routes}
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
/>
{/* <Configurator />
{configsButton} */}
</>
)}
{layout === "vr" && <Configurator />}
{children}
</ThemeProvider>
);
}
export default DashboardWrapperLayout;

View File

@@ -0,0 +1,167 @@
import { Card, CardContent, Divider, Switch, TextField } from "@mui/material";
import { Feedback } from "../../data";
import { axiosInstance } from "../../../axiosApi";
import MDTypography from "../../ui-kit/components/MDTypography";
import { Form, Formik } from "formik";
import * as Yup from 'yup';
import CustomSelect, { CustomSelectItem } from "../../components/CustomSelect/CustomSelect";
import MDButton from "../../ui-kit/components/MDButton";
import { useMaterialUIController } from "../../ui-kit/context";
type FeedbackSubmitValues = {
text: string
title: string
category: string
}
type FeedbackSubmitCardProps = {
feedbacks: Feedback[],
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
}
const validataionSchema = Yup.object().shape({
text: Yup.string().min(1, "Need to have at least one character").required("This is requried"),
title: Yup.string().min(1, "Need to have at least one character").required("This is requried"),
category: Yup.string().required("Required")
}
)
const categories: CustomSelectItem[] = [
{
label: "Bug",
value: "BUG"
},
{
label: "Enhancement",
value: "ENCHANCEMENT"
},
{
label: "Other",
value: "OTHER"
}
];
const FeedbackSubmitCard = ({feedbacks, setFeedbacks}: FeedbackSubmitCardProps) => {
const [controller, dispatch] = useMaterialUIController();
const {
darkMode,
} = controller;
const buttonColor = darkMode? 'dark' : 'light';
const handleFeedbackSubmit = async ({text, title, category}: FeedbackSubmitValues, {resetForm}: any): Promise<void> => {
//console.log(text, title, category)
try{
await axiosInstance.post('/feedbacks/', {
text: text,
title: title,
category: category
})
resetForm();
// put message here
setFeedbacks([...feedbacks, new Feedback({
title: title,
text: text,
status: 'SUBMITTED',
category: category,
})])
} catch{
// put a message here
}
}
return(
<>
<Card>
<CardContent>
<MDTypography variant="h3">
Submit Feedback
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<Formik
initialValues={{
text: '',
title: '',
category: '',
}}
onSubmit={handleFeedbackSubmit}
validationSchema={validataionSchema}
validateOnMount>
{(formik) =>
<Form>
<div className="row">
<div className='col-6'>
<TextField
label='Title'
name='title'
onChange={formik.handleChange}
value={formik.values.title}
fullWidth={true}
variant={"outlined"} //enables special material-ui styling
multiline={false}
/>
</div>
<div className='col-6'>
<CustomSelect
name="category"
items={categories}
label="Category"
required
/>
</div>
<div className="row">
<div className='col'>
<TextField
label='Text'
name='text'
onChange={formik.handleChange}
value={formik.values.text}
fullWidth={true}
variant={"outlined"} //enables special material-ui styling
size={"small"}
multiline={false}
margin={"dense"}
rows={3}
minRows={3}
maxRows={10}
/>
</div>
</div>
<div className='row'>
<div className='col'>
<Divider />
<MDButton
type={'submit'}
disabled={!formik.isValid}
color={buttonColor}
>
<MDTypography variant="h6"> Submit</MDTypography>
</MDButton>
</div>
</div>
</div>
</Form>
}
</Formik>
</CardContent>
</Card>
</>
)
}
export default FeedbackSubmitCard;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import MDTypography from '../../ui-kit/components/MDTypography';
const Footer = ({}): JSX.Element => {
return (
<div className='continer-fluid'>
<MDTypography>&copy; 2025 Chat | Developed by <a href='www.aimloperations.com'>AI ML Operations, LLC</a></MDTypography>
</div>
);
};
export default Footer;

View File

@@ -0,0 +1,110 @@
import { AppBar, Button, IconButton, Toolbar, Typography, useMediaQuery, Theme, useTheme } from '@mui/material';
import React, { useContext } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { AuthContext } from '../../contexts/AuthContext';
import { AccountContext } from '../../contexts/AccountContext';
import { axiosInstance } from '../../../axiosApi';
import MenuIcon from '@mui/icons-material/Menu';
import { AccountBox, Dashboard, Feedback, Logout } from '@mui/icons-material';
type HeaderProps = {
drawerWidth?: number;
handleDrawerToggle?: () => void
}
const Header = ({drawerWidth=0, handleDrawerToggle}: HeaderProps): JSX.Element => {
const {authenticated, setAuthentication} = useContext(AuthContext);
const { account, setAccount } = useContext(AccountContext);
const navigate = useNavigate();
const handleSignOut = async () => {
try{
const response = await axiosInstance.post('blacklist/',{
'refresh_token': localStorage.getItem("refresh_token")
})
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
axiosInstance.defaults.headers['Authorization'] = null;
setAuthentication(false)
setAccount(undefined);
navigate('/signin/')
}finally{
}
}
const handleDashboardClick = async () => {
navigate('/')
}
const handleAccountClick = async () => {
navigate('/account/')
}
const handleFeedbackClick = async () => {
navigate('/feedback/')
}
const theme = useTheme()
const isLargeScreen = useMediaQuery(theme.breakpoints.up('md'))
return (
<AppBar position='fixed'
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
}}
>
<Toolbar className='bg-gradient-dark shadow-dark'>
{ handleDrawerToggle &&
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}>
<MenuIcon />
</IconButton>
}
{isLargeScreen ? (
<>
<Typography variant="h6" style={{ flexGrow: 1 }} className='text-white font-weight-bold'>
Chat | AI Ml Operations
</Typography >
<Button color="inherit" onClick={handleDashboardClick}>
Dashboard</Button>
<Button color="inherit" onClick={handleAccountClick}>Account</Button>
<Button color="inherit" onClick={handleFeedbackClick}>Feedback</Button>
<Button color="inherit" onClick={handleSignOut}>Sign Out</Button>
</>
) : (
<>
<Typography variant="h6" style={{ flexGrow: 1 }} className='text-white font-weight-bold'>
Chat
</Typography >
<IconButton color="inherit" onClick={handleDashboardClick}>
<Dashboard />
</IconButton>
<IconButton color="inherit" onClick={handleAccountClick}>
<AccountBox />
</IconButton>
<IconButton color="inherit" onClick={handleFeedbackClick}>
<Feedback />
</IconButton>
<IconButton color="inherit" onClick={handleSignOut}>
<Logout />
</IconButton>
</>
)}
</Toolbar>
</AppBar>
);
};
export default Header;

View File

@@ -0,0 +1,98 @@
import { AppBar, Button, Toolbar } from '@mui/material';
import React, { useContext, useState } from 'react';
import { useMaterialUIController } from '../../ui-kit/context';
import { boolean } from 'yup';
import MDBox from '../../ui-kit/components/MDBox';
import {
navbar,
navbarContainer,
navbarRow,
navbarIconButton,
navbarMobileMenu,
} from "../../ui-kit/examples/Navbars/DashboardNavbar/styles";
import Breadcrumbs from '../../ui-kit/examples/Breadcrumbs';
import { AccountContext } from '../../contexts/AccountContext';
import { AuthContext } from '../../contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
import { axiosInstance } from '../../../axiosApi';
type Header2Props = {
absolute?: Boolean;
light?: Boolean;
isMini?: Boolean;
}
const Header2 = ({ absolute=false, light=false, isMini=false }: Header2Props): JSX.Element => {
const {authenticated, setAuthentication} = useContext(AuthContext);
const { account, setAccount } = useContext(AccountContext);
const navigate = useNavigate();
const handleSignOut = async () => {
try{
const response = await axiosInstance.post('blacklist/',{
'refresh_token': localStorage.getItem("refresh_token")
})
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
axiosInstance.defaults.headers['Authorization'] = null;
setAuthentication(false)
setAccount(undefined);
navigate('/signin/')
}finally{
}
}
const handleDashboardClick = async () => {
navigate('/')
}
const handleAccountClick = async () => {
navigate('/account/')
}
const handleFeedbackClick = async () => {
navigate('/feedback/')
}
const handleAnalyticsClick = async () => {
navigate('/analytics/')
}
const [navbarType, setNavbarType] = useState();
const [controller, dispatch] = useMaterialUIController();
const { miniSidenav, transparentNavbar, fixedNavbar, openConfigurator, darkMode } = controller;
return (
<AppBar
position={absolute ? 'absolute' : navbarType}
color='info'
sx={(theme) => navbar(theme, { transparentNavbar, absolute, light, darkMode })}
>
{/* <Toolbar> sx={(theme) => navbarContainer(theme)}> */}
<Toolbar sx={{justifyContent: 'start'}}>
<MDBox>
<Button color="inherit" onClick={handleDashboardClick}>Dashboard</Button>
</MDBox>
<MDBox sx={{marginLeft: "auto"}}>
<Button color="inherit" onClick={handleAccountClick}>Account</Button>
<Button color="inherit" onClick={handleAnalyticsClick}>Analytics</Button>
<Button color="inherit" onClick={handleFeedbackClick} >Feedback</Button>
<Button color="inherit" onClick={handleSignOut} >Sign Out</Button>
</MDBox>
</Toolbar>
</AppBar>
)
}
export default Header2;

View File

@@ -0,0 +1,99 @@
import createCache, { EmotionCache } from "@emotion/cache";
import { setOpenConfigurator, useMaterialUIController } from "../../ui-kit/context";
import { useEffect, useMemo, useState } from "react";
import { useLocation } from "react-router-dom";
import stylisRTLPlugin from "stylis-plugin-rtl";
import MDBox from "../../ui-kit/components/MDBox";
import { CacheProvider } from "@emotion/react";
import { CssBaseline, Icon, ThemeProvider } from "@mui/material";
import themeDark from "../../ui-kit/assets/theme-dark";
import theme from "../../ui-kit/assets/theme";
import themeRtl from "../../ui-kit/assets/theme/theme-rtl";
import themeDarkRTL from "../../ui-kit/assets/theme-dark/theme-rtl";
type PageWrapperLayoutProps = {
children: React.ReactNode;
}
const PageWrapperLayout = ({children}: PageWrapperLayoutProps): JSX.Element => {
const [controller, dispatch] = useMaterialUIController();
const {
direction,
openConfigurator,
darkMode,
} = controller;
const [rtlCache, setRtlCache] = useState<EmotionCache | null>(null);
const { pathname } = useLocation();
// Cache for the rtl
useMemo(() => {
const cacheRtl = createCache({
key: "rtl",
stylisPlugins: [stylisRTLPlugin],
});
setRtlCache(cacheRtl);
}, []);
// Change the openConfigurator state
const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator);
// Setting the dir attribute for the body element
useEffect(() => {
document.body.setAttribute("dir", direction);
}, [direction]);
// Setting page scroll to 0 when changing the route
useEffect(() => {
document.documentElement.scrollTop = 0;
if(document.scrollingElement){
document.scrollingElement.scrollTop = 0;
}
}, [pathname]);
const configsButton = (
<MDBox
display="flex"
justifyContent="center"
alignItems="center"
width="3.25rem"
height="3.25rem"
bgColor="white"
shadow="sm"
borderRadius="50%"
position="fixed"
right="2rem"
bottom="2rem"
zIndex={99}
color="dark"
sx={{ cursor: "pointer" }}
onClick={handleConfiguratorOpen}
>
<Icon fontSize="small" color="inherit">
settings
</Icon>
</MDBox>
);
return direction === "rtl" ? (
<CacheProvider value={rtlCache}>
<ThemeProvider theme={darkMode ? themeDarkRTL : themeRtl}>
<CssBaseline />
{children}
</ThemeProvider>
</CacheProvider>
) : (
<ThemeProvider theme={darkMode ? themeDark : theme}>
<CssBaseline />
{children}
</ThemeProvider>
);
}
export default PageWrapperLayout;

View File

@@ -0,0 +1,173 @@
import { Form, Formik } from 'formik';
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
import { Alert, Button, Stack, Typography } from '@mui/material';
import { axiosInstance } from '../../../axiosApi';
import { useNavigate, useSearchParams } from 'react-router-dom';
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
import background from '../../../bg.jpeg'
import * as Yup from 'yup';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import ErrorIcon from '@mui/icons-material/Error';
export type PasswordResetValues = {
password1: string;
password2: string;
};
const initialValues = {password1: '', password2: ''}
const validationSchema = Yup.object().shape({
password1: Yup.string().min(6, "Passwords have to be at least 6 digits").required(),
password2: Yup.string().min(6, "Passwords have to be at least 6 digits").required().oneOf([Yup.ref('password1')], "Passwords must match"),
})
const contains_number = (item: string): boolean => {
const numbers = ['1','2','3','4','5','6','7','8','9','0'];
const hasNumber = numbers.some((character) => item.includes(character));
if(hasNumber){
return true;
}
return false;
}
const contains_special_character = (item: string): boolean => {
const specialCharacters = ['!','@','#','$',',%','^','&','*','(',')','-','_','=','+','/','*','\\','|','`','~','<','>','.','?'];
const hasSpecialChacater = specialCharacters.some((character) => item.includes(character));
if(hasSpecialChacater){
return true;
}
return false;
}
const SetPassword = ({}): JSX.Element => {
const navigate = useNavigate();
// see if the user is allowed to come here first
const [queryParameters] = useSearchParams()
console.log(queryParameters.get("slug"))
const slug = queryParameters.get("slug");
try{
// make sure it comes back as 200 for a good request. Else go to the homepage
axiosInstance.get(`user/set_password/${slug}`)
}catch{
navigate('/')
}
const handleSetPassword = ({password1, password2}: PasswordResetValues): void => {
try{
// verify
if(password1 === password2){
axiosInstance.post(`user/set_password/${slug}/`, {
'password': password1,
});
navigate('/')
}
}catch(error){
console.log('catching the error');
<CustomToastMessage message={error as string} />
}
}
return (
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
backgroundRepeat: "no-repeat"}}>
<div className='container my-auto'>
<div className='row'>
<div className='col -lg-4 col-md-8 col-12 mx-auto'>
<div className='card z-index-0 fadeIn3 fadeInBottom'>
<div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
<h4 className='text-white font-weight-bold text-center'>Set your password</h4>
</div>
</div>
<div className='card-body text-center'>
<Formik
initialValues={initialValues}
onSubmit={handleSetPassword}
validateOnMount
validationSchema={validationSchema}>
{(formik) => (
<Form>
<div className='row'>
<div className='col'>
<CustomPasswordField
label='Password'
name="password1"
changeHandler={(e) => formik.setFieldValue('password1', e.target.value)} />
</div>
</div>
<div className='row'>
<div className='col'>
<CustomPasswordField
label='Confirm Password'
name="password2"
changeHandler={(e) => formik.setFieldValue('password2', e.target.value)} />
</div>
</div>
<div>
<Stack alignItems="center" direction="row" gap={2}>
{formik.values.password1 === formik.values.password2 ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
<Typography variant="body1">Passwords Match</Typography>
</Stack>
</div>
<div>
<Stack alignItems="center" direction="row" gap={2}>
{formik.values.password1.length > 5 ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
<Typography variant="body1">At least 6 characters</Typography>
</Stack>
</div>
<div>
<Stack alignItems="center" direction="row" gap={2}>
{contains_special_character(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
<Typography variant="body1">At least one special character</Typography>
</Stack>
</div>
<div>
<Stack alignItems="center" direction="row" gap={2}>
{contains_number(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
<Typography variant="body1">At least one number</Typography>
</Stack>
</div>
<div className='row'>
<div className='col'>
<Button
type={'submit'}
disabled={!formik.isValid || formik.isSubmitting ||
!contains_special_character(formik.values.password1)
|| !contains_number(formik.values.password1)
}
// type={'submit'}
// loading={formik.isSubmitting}
// disabled={
// !formik.isValid || !formik.dirty || formik.isSubmitting
// }
>
Set password
</Button>
</div>
</div>
</Form>
)}
</Formik>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default SetPassword;

View File

@@ -0,0 +1,87 @@
import { Card, CardContent, Divider, Switch } from "@mui/material";
import MDTypography from "../../ui-kit/components/MDTypography";
import MDBox from "../../ui-kit/components/MDBox";
import {
useMaterialUIController,
setDarkMode,
} from "../../ui-kit/context";
import { useContext, useEffect, useState } from "react";
import { AxiosResponse } from "axios";
import { axiosInstance } from "../../../axiosApi";
import { PreferencesType } from "../../data";
import { PreferenceContext } from "../../contexts/PreferencesContext";
type PreferencesValues = {
order: boolean;
}
const ThemeCard = ({}): JSX.Element => {
const [controller, dispatch] = useMaterialUIController();
const {
darkMode,
} = controller;
const [order, setOrder] = useState<boolean>(true);
const {updatePreferences} = useContext(PreferenceContext);
const handleConversationOrder = async ({order}: PreferencesValues): Promise<void> => {
try{
const {data, }: AxiosResponse<PreferencesType> = await axiosInstance.post('/conversation_preferences', {
order: order
})
updatePreferences();
}catch{
}
}
async function getConversationOrder(){
try{
const {data, }: AxiosResponse<PreferencesType> = await axiosInstance.get(`/conversation_preferences`);
setOrder(data.order);
}catch(error){
console.log(error)
}
}
useEffect(()=>{
getConversationOrder();
}, [])
const handleDarkMode = () => setDarkMode(dispatch, !darkMode);
const isThemeReady=false;
return(
<Card sx={{ mb: 1}}>
<CardContent>
<MDTypography variant="h3">
Account Preferences
</MDTypography>
</CardContent>
<Divider />
<CardContent>
{isThemeReady ? (
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
<MDTypography variant="h6">Light / Dark</MDTypography>
<Switch checked={darkMode} onChange={handleDarkMode} disabled={false} />
</MDBox>
) : (
<></>
)}
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
<MDTypography variant="h6">Converastion Order</MDTypography>
<Switch checked={order} onChange={() => {
setOrder(!order);
handleConversationOrder({order: !order})
}} />
</MDBox>
</CardContent>
</Card>
)
}
export default ThemeCard;

View File

@@ -0,0 +1,64 @@
import { ReactNode, useState, createContext, useEffect, useContext } from "react"
import { Account, AccountType } from "../data";
import { AuthContext } from "./AuthContext";
import { AxiosResponse } from "axios";
import { axiosInstance } from "../../axiosApi";
type AccountProviderProps ={
children? : ReactNode;
}
type IAccountContext = {
account: Account | undefined;
setAccount: (account: Account | undefined) => void;
}
const initialValues = {
account: undefined,
setAccount: () => {}
}
const AccountContext = createContext<IAccountContext>(initialValues);
const AccountProvider = ({children}: AccountProviderProps) => {
const [account, setAccount] = useState<Account | undefined>(initialValues.account);
const { authenticated, loading } = useContext(AuthContext);
async function getAccount (){
const get_user_response: AxiosResponse<AccountType> = await axiosInstance.get('/user/get/')
const account: Account = new Account({
email: get_user_response.data.email,
first_name: get_user_response.data.first_name,
last_name: get_user_response.data.last_name,
is_company_manager: get_user_response.data.is_company_manager,
has_signed_tos: get_user_response.data.has_signed_tos,
company: {
id: get_user_response.data.company?.id,
name: get_user_response.data.company?.name,
state: get_user_response.data.company?.state,
zipcode: get_user_response.data.company?.zipcode,
address: get_user_response.data.company?.address,
}
});
setAccount(account);
}
useEffect(() => {
if(!loading && authenticated){
getAccount();
}
}, [authenticated])
return (
<AccountContext.Provider value={{account, setAccount}}>
{children}
</AccountContext.Provider>
)
}
export { AccountContext, AccountProvider }

View File

@@ -0,0 +1,67 @@
import { Token } from "@mui/icons-material";
import { jwtDecode } from "jwt-decode";
import { createContext, ReactNode, useState, useEffect } from "react"
import { useNavigate } from "react-router-dom";
type AuthProviderProps = {
children?: ReactNode;
}
type IAuthContext = {
authenticated: boolean;
setAuthentication: (newState: boolean) => void;
needsNewPassword: boolean;
setNeedsNewPassword: (newState: boolean) => void;
loading: boolean;
}
const initialValues = {
authenticated: false,
setAuthentication: () => {},
needsNewPassword: false,
setNeedsNewPassword: () => {},
loading: true,
}
const AuthContext = createContext<IAuthContext>(initialValues);
const AuthProvider = ({children}: AuthProviderProps) => {
const [ authenticated, setAuthentication ] = useState(initialValues.authenticated);
const [loading, setLoading] = useState(true); // Add a loading state
useEffect(() => {
//console.log('we are in the auth provider')
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
const decodedToken = jwtDecode(accessToken)
//console.log(decodedToken)
if(decodedToken.exp){
//console.log(decodedToken.exp * 1000)
//console.log(Date.now())
if (decodedToken.exp * 1000> Date.now()) {
//console.log('We are setting that we are authenticated')
setAuthentication(true);
}
}
}
setLoading(false);
}, [])
//console.log(authenticated)
const [ needsNewPassword, setNeedsNewPassword] = useState(initialValues.needsNewPassword)
const navigation = useNavigate();
return (
<AuthContext.Provider value={{ authenticated, setAuthentication, needsNewPassword, setNeedsNewPassword, loading}}>
{children}
</AuthContext.Provider>
)
}
export { AuthContext, AuthProvider }

View File

@@ -0,0 +1,80 @@
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
import { Conversation, ConversationType } from "../data";
import { AxiosResponse } from "axios";
import { axiosInstance } from "../../axiosApi";
import { AuthContext } from "./AuthContext";
import { PreferenceContext } from "./PreferencesContext";
type ConversationProviderProps ={
children? : ReactNode;
}
type IConversationContext = {
conversations: Conversation[];
setConversations: (conversations: Conversation[]) => void;
selectedConversation: number | undefined;
setSelectedConversation: (conversation_id: number | undefined) => void;
deleteConversation: (conversation_id: number | undefined) => void;
}
const initialValues = {
conversations: [],
setConversations: () => {},
selectedConversation: undefined,
setSelectedConversation: () => {},
deleteConversation: () => {},
}
const ConversationContext = createContext<IConversationContext>(initialValues);
const ConversationProvider = ({children}: ConversationProviderProps) => {
const [conversations, setConversations] = useState<Conversation[]>([]);
const [selectedConversation, setSelectedConversation] = useState<number | undefined>(undefined);
const { authenticated, loading } = useContext(AuthContext);
const {preferencesUpdated} = useContext(PreferenceContext);
function deleteConversation(conversation_id: number | undefined){
//console.log(`detele ${conversation_id}`)
try{
axiosInstance.delete(`conversation_details`, {
data: {'conversation_id':conversation_id}
})
// remove it from the list now
setConversations(conversations.filter((conversation) => conversation.id !== conversation_id));
// if it the current selected one, update the selected conversation
if (selectedConversation === conversation_id){
setSelectedConversation(undefined)
}
}catch{
}
}
async function GetConversations(){
const {data, }: AxiosResponse<ConversationType[]> = await axiosInstance.get(`conversations`)
setConversations(data.map((item) => new Conversation({
id: item.id,
title: item.title
})))
}
useEffect(() => {
if(!loading && authenticated){
GetConversations();
}
}, [selectedConversation, authenticated, preferencesUpdated])
return(
<ConversationContext.Provider value={{conversations, setConversations, selectedConversation, setSelectedConversation, deleteConversation}}>
{children}
</ConversationContext.Provider>
)
}
export { ConversationContext, ConversationProvider }

View File

@@ -0,0 +1,143 @@
import { createContext, ReactNode, useContext, useEffect, useRef, useState } from "react";
import { AuthContext } from "./AuthContext";
import { WebSocketContext } from "./WebSocketContext";
import { AccountContext } from "./AccountContext";
import { ConversationContext } from "./ConversationContext";
import { ConversationPrompt, ConversationPromptType } from "../data";
import { axiosInstance } from "../../axiosApi";
import { AxiosResponse } from "axios";
type MessageProviderProps ={
children? : ReactNode;
}
type IMessageContext = {
stateMessage: string;
setStateMessage: (message: string) => void;
conversationDetails: ConversationPrompt[];
setConversationDetails: (conversationPrompts: ConversationPrompt[]) => void;
isGeneratingMessage: boolean;
}
const initialValues = {
stateMessage: '',
setStateMessage: () => {},
conversationDetails: [],
setConversationDetails: () => {},
isGeneratingMessage: false
}
const MessageContext = createContext<IMessageContext>(initialValues);
const MessageProvider = ( {children}: MessageProviderProps) => {
const { authenticated, loading } = useContext(AuthContext);
const [subscribe, unsubscribe, socket, sendMessage]= useContext(WebSocketContext)
const { account } = useContext(AccountContext)
const {conversations, selectedConversation, setSelectedConversation} = useContext(ConversationContext);
const [stateMessage, setStateMessage] = useState<string>('')
const [conversationDetails, setConversationDetails] = useState<ConversationPrompt[]>([])
const [isGeneratingMessage, setIsGeneratingMessage] = useState<boolean>(false)
const messageRef = useRef('')
const messageResponsePart = useRef(0);
const conversationRef = useRef(conversationDetails)
const selectedConversationRef = useRef<undefined | number>(undefined)
async function GetConversationDetails(){
if(selectedConversation){
try{
//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
}))
if(tempConversations.length === 1){
// we need to add another card because this is the first message
tempConversations.push(new ConversationPrompt({message: '', user_created:false}))
}
conversationRef.current = tempConversations
setConversationDetails(tempConversations)
}finally{
//setPromptProcessing(false)
}
}else{
setConversationDetails([])
}
}
useEffect(() => {
GetConversationDetails();
}, [selectedConversation])
useEffect(() => {
/* register a consistent channel name for identifing this chat messages */
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
conversationRef.current.pop()
//handleAssistantPrompt({prompt: messageRef.current})
setConversationDetails([...conversationRef.current, new ConversationPrompt({message: `${messageRef.current}`, user_created:false})])
console.log([...conversationRef.current, new ConversationPrompt({message: `${messageRef.current}`, user_created:false})])
messageRef.current = ''
setStateMessage('')
setIsGeneratingMessage(false)
}
else if (message === 'START_OF_THE_STREAM_ENDER_GAME_42'){
conversationRef.current = conversationDetails
setIsGeneratingMessage(true)
messageResponsePart.current = 2
}else if (message === 'CONVERSATION_ID'){
setIsGeneratingMessage(true)
messageResponsePart.current = 1
}else{
setIsGeneratingMessage(true)
if (messageResponsePart.current === 1){
// this has to do with the conversation id
if(!selectedConversation){
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, conversationDetails])
return(
<MessageContext.Provider value={{stateMessage, setStateMessage, conversationDetails, setConversationDetails, isGeneratingMessage}}>
{children}
</MessageContext.Provider>
)
}
export { MessageContext, MessageProvider}

View File

@@ -0,0 +1,33 @@
import { createContext, ReactNode, useState } from "react";
type PrefernceProviderProps ={
children? : ReactNode;
}
type IPreferenceContext = {
updatePreferences: () => void;
preferencesUpdated: boolean;
}
const initialValues = {
updatePreferences: () => {},
preferencesUpdated: true,
}
const PreferenceContext = createContext<IPreferenceContext>(initialValues);
const PreferenceProvider = ({children}: PrefernceProviderProps) => {
const [preferencesUpdated, setPreferencesUpdated] = useState<boolean>(true);
function updatePreferences(){
setPreferencesUpdated(!preferencesUpdated);
}
return (
<PreferenceContext.Provider value={{updatePreferences, preferencesUpdated}}>
{children}
</PreferenceContext.Provider>
)
}
export { PreferenceContext, PreferenceProvider }

View File

@@ -0,0 +1,111 @@
import { AccountContext } from "./AccountContext"
import { AuthContext } from "./AuthContext";
const { useEffect, createContext, useRef, useState, useContext } = require("react")
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('')
/* 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
}
/* remove callback */
const unsubscribe = (channel) => {
delete channels.current[channel]
}
const sendMessage = (message, conversation_id, file, fileType) => {
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,
}
socket.send(JSON.stringify(data))
}
}
reader.readAsDataURL(file)
}else{
const data = {
message: message,
conversation_id: conversation_id,
email: account?.email,
file: null,
fileType: null
}
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://127.0.0.1:8001/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.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]) {
/* 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 }

File diff suppressed because one or more lines are too long

241
llm-fe/src/llm-fe/data.ts Normal file
View File

@@ -0,0 +1,241 @@
/* Classes for the project */
export interface ConversationPromptType {
id: number,
message: string,
user_created: boolean,
created_timestamp: Date,
}
export class ConversationPrompt{
id: number | undefined;
message: string = '';
user_created: boolean = false;
created_timestamp: Date = new Date();
// TODO: add a date time stamp
constructor(initializer?: any){
if(!initializer) return;
if (initializer.id) this.id = initializer.id;
if (initializer.message) this.message = initializer.message;
if (initializer.user_created) this.user_created = initializer.user_created;
if (initializer.created_timestamp) this.created_timestamp = initializer.created_timestamp;
}
}
export interface CompanyType {
id: number;
name: string;
state: string;
zipcode: string;
address: string;
}
export class Company {
id: number | undefined;
name: string = '';
state: string = '';
zipcode: number | undefined;
address: string = '';
constructor(initializer?: any){
if (!initializer) return;
if (initializer.id) this.id = initializer.id;
if (initializer.name) this.name = initializer.name;
if (initializer.state) this.state = initializer.state;
if (initializer.zipcode) this.zipcode = initializer.zipcode;
if (initializer.address) this.address = initializer.address;
}
}
export interface ConversationType {
id: number;
title: string;
conversationDetail: ConversationPrompt[];
}
export class Conversation {
id: number | undefined;
title: string ='';
conversationDetail: ConversationPrompt[] = [];
account: Account | undefined;
constructor(initializer?: any){
if(!initializer) return;
if (initializer.id) this.id = initializer.id;
if (initializer.title) this.title = initializer.title;
if (initializer.conversationDetail) this.conversationDetail = initializer.conversationDetail;
if (initializer.account) this.account = initializer.account;
}
}
export interface AnnouncementType {
status: string;
message: string;
}
export class Announcement {
status: string = 'default';
message: string ='';
constructor(initializer?: any){
if (!initializer) return;
if (initializer.status) this.status = initializer.status;
if (initializer.message) this.message = initializer.message;
}
}
export interface FeedbackType {
id: number;
title: string;
status: string;
text: string;
category: string;
}
export class Feedback {
id: number = 0;
title: string = '';
status: string = '';
text: string = '';
category: string = '';
constructor(initializer?: any){
if(!initializer) return;
if (initializer.id) this.id = initializer.id;
if (initializer.status) this.status = initializer.status;
if (initializer.title) this.title = initializer.title;
if (initializer.text) this.text = initializer.text;
if (initializer.category) this.category = initializer.category;
}
}
export interface UserPromptAnalyticsType {
month: string,
you: number,
others: number,
all: number,
}
export class UserPromptAnalytics {
month: string = "";
you: number = 0;
others: number = 0;
all: number = 0;
constructor(initializer?: any){
if(!initializer) return;
if (initializer.id) this.month = initializer.month;
if (initializer.id) this.you = initializer.you;
if (initializer.id) this.others = initializer.others;
if (initializer.id) this.all = initializer.all;
}
}
export interface UserConvesationAnalyticsType {
month: string,
you: number,
others: number,
all: number,
}
export class UserConversationAnalytics {
month: string = "";
you: number = 0;
others: number = 0;
all: number = 0;
constructor(initializer?: any){
if(!initializer) return;
if (initializer.id) this.month = initializer.month;
if (initializer.id) this.you = initializer.you;
if (initializer.id) this.others = initializer.others;
if (initializer.id) this.all = initializer.all;
}
}
export interface CompanyUsageAnalyticsType {
month: string,
used: number,
not_used: number,
}
export class CompanyUsageAnalytics {
month: string = "";
used: number = 0;
not_used: number = 0;
constructor(initializer?: any){
if(!initializer) return;
if (initializer.id) this.month = initializer.month;
if (initializer.id) this.used = initializer.used;
if (initializer.id) this.not_used = initializer.not_used;
}
}
export interface AdminAnalyticsType {
month: string,
range: [number, number],
avg: number,
median: number,
}
export class AdminAnalytics {
month: string = "";
range: [number, number] = [0, 0];
avg: number = 0;
median: number = 0;
constructor(initializer?: any){
if(!initializer) return;
if (initializer.id) this.month = initializer.month;
if (initializer.id) this.range = initializer.range;
if (initializer.id) this.avg = initializer.avg;
if (initializer.id) this.median = initializer.median;
}
}
export interface AccountType {
email: string;
first_name: string;
last_name: string;
role: string | undefined;
company: Company | undefined;
has_usable_password: boolean;
is_company_manager: boolean;
is_active: boolean;
has_signed_tos: boolean;
}
export interface PreferencesType {
order: boolean;
}
export class Account {
email: string = '';
first_name: string ='';
last_name: string = '';
role: string | undefined;
company: Company | undefined;
is_company_manager: boolean = false;
has_password: boolean = false;
is_active: boolean = false;
has_signed_tos: boolean = false;
constructor(initializer?: any){
if (!initializer) return;
if (initializer.email) this.email = initializer.email;
if (initializer.first_name) this.first_name = initializer.first_name;
if (initializer.is_company_manager) this.is_company_manager = initializer.is_company_manager;
if (initializer.has_password) this.has_password = initializer.has_password;
if (initializer.is_active) this.is_active = initializer.is_active;
if (initializer.has_signed_tos) this.has_signed_tos = initializer.has_signed_tos;
if (initializer.last_name) this.last_name = initializer.last_name;
if (initializer.role) this.role = initializer.role;
if (initializer.company) this.company = initializer.company;
}
}

View File

@@ -0,0 +1,183 @@
import { Account, Announcement, Company, Conversation, ConversationPrompt } from "./data";
export const MOCK_ANNOUNCEMENTS: Announcement[] = [
new Announcement({
status: 'primary',
message: 'Primary',
}),
new Announcement({
status: 'warning',
message: 'Warning',
}),
new Announcement({
status: 'info',
message: 'Info',
})
,
new Announcement({
status: 'light',
message: 'Light',
})
,
new Announcement({
status: 'dark',
message: 'Dark',
})
,
new Announcement({
status: 'success',
message: 'success',
})
,
new Announcement({
status: 'danger',
message: 'Danger',
})
,
new Announcement({
status: 'secondary',
message: 'Secondary',
})
]
export const MOCK_COMPANY: Company[] = [
new Company({
id: 0,
name: "Company A",
state: 'IL',
zipcode: 60189,
address: '123 Something'
}),
new Company({
id: 1,
name: "Company B",
state: 'OH',
zipcode: 44512,
address: '91235 Boardwalk'
})
]
export const MOCK_ACCOUNTS: Account[] = [
new Account({
email: 'rufus.t.firefly@newtonia.com',
first_name: 'Rufus',
last_name: 'Firefly',
role: 'company manager',
company: MOCK_COMPANY[0]
}),
new Account({
email: 'something@something.com',
first_name: 'Billy Bob',
last_name: 'Thorton',
role: 'user',
company: MOCK_COMPANY[0]
}),
new Account({
email: 'mom@mi6.com',
first_name: 'Mallory',
last_name: 'Something',
role: 'company manager',
company: MOCK_COMPANY[1]
}),
new Account({
email: '007@mi6.com',
first_name: 'James',
last_name: 'Bond',
role: 'user',
company: MOCK_COMPANY[1]
}),
]
export const MOCK_CONVESATION_DETAIL: ConversationPrompt[] = [
new ConversationPrompt({
id: 0,
message: "Hello, World!",
}),
new ConversationPrompt({
id:1,
message: "Hello Dave",
}),
new ConversationPrompt({
id: 2,
message: "Can you help me with a math problem",
}),
new ConversationPrompt({
id:3,
message: "I cannot do that Dave",
}),
new ConversationPrompt({
id: 4,
message: "Let's start over",
}),
new ConversationPrompt({
id:5,
message: "Hello Dave",
}),
new ConversationPrompt({
id: 6,
message: "Can you help me with a math problem, pretty please",
}),
new ConversationPrompt({
id:7,
message: "Absolutely!",
}),
new ConversationPrompt({
id: 8,
message: "What is 2 + 2?",
}),
new ConversationPrompt({
id: 9,
message: "Dave, you didn't say please.",
}),
new ConversationPrompt({
id: 10,
message: "Fine. Please tell me what is 2 + 2",
}),
new ConversationPrompt({
id:11,
message: "4. 2 + 2 = 4.",
}),
];
export const MOCK_CONVERSATION: Conversation[] = [
new Conversation({
id: 0,
title: "First One",
account: MOCK_ACCOUNTS[0]
}),
new Conversation({
id: 1,
title: "Math problem",
account: MOCK_ACCOUNTS[0],
conversationDetail: [
...MOCK_CONVESATION_DETAIL.slice(0,4) // update this to be a slice
],
}),
new Conversation({
id: 2,
title: "Math problem but nice",
account: MOCK_ACCOUNTS[0],
conversationDetail: [
...MOCK_CONVESATION_DETAIL.slice(4,12) // update this to be a slice
],
}),
new Conversation({
id: 3,
title: "Create Graph",
account: MOCK_ACCOUNTS[0]
}),
new Conversation({
id: 4,
title: "Company Report",
account: MOCK_ACCOUNTS[1]
}),
]
/* add more mockk data*/

View File

@@ -0,0 +1,373 @@
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
import Footer from '../../components/Footer/Footer';
import Header from '../../components/Header/Header';
import { AccountContext } from '../../contexts/AccountContext';
import Card from '../../components/Card/Card';
import { Box, Button, CardContent, CardHeader, IconButton, InputAdornment, Modal, Paper, Switch, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, ToggleButton, Typography } from '@mui/material';
import CustomTextField from '../../components/CustomTextField/CustomTextField';
import { ErrorMessage, Field, Form, Formik, FormikContext } from 'formik';
import { Account, AccountType } from '../../data';
import { axiosInstance } from '../../../axiosApi';
import { AxiosResponse } from 'axios';
import { Add, BusinessTwoTone, Delete, DeleteForever, Send } from '@mui/icons-material';
import * as Yup from 'yup';
import { initial } from 'lodash';
type CompanyAccountLineProps = {
user: Account
handleUserUpdate: (email: string, field: string, value: string) => void
}
const CompanyAccountLine = ({user, handleUserUpdate}: CompanyAccountLineProps): JSX.Element => {
const {account} = useContext(AccountContext);
return(
<TableRow key={user.email}>
<TableCell>{user.email}</TableCell>
<TableCell>{user.first_name}</TableCell>
<TableCell>{user.last_name}</TableCell>
<TableCell >
<Switch checked={user.is_company_manager} onChange={(event) => handleUserUpdate(user.email, 'company_manager', event.target.value)} disabled={account?.email === user.email} />
</TableCell>
<TableCell >
<Switch checked={user.is_active} onChange={(event) => handleUserUpdate(user.email, 'is_active', event.target.value)} />
</TableCell>
<TableCell >
<Switch checked={user.has_password && user.has_signed_tos} onChange={(event) => handleUserUpdate(user.email, 'has_password', event.target.value)} disabled={!user.has_password} />
</TableCell>
<TableCell>
<IconButton>
<DeleteForever color="warning" onClick={() => handleUserUpdate(user.email, 'delete', '')} />
</IconButton>
</TableCell>
</TableRow>
)
}
type InviteValues = {
email: string
}
const validationSchema = Yup.object().shape({
email: Yup.string().email().required("This is requried")
}
)
type AddUserCardProps = {
getCompanyUsers: ()=> void;
}
const AddUserCard = ({getCompanyUsers}: AddUserCardProps): JSX.Element => {
const initialValues = {'email': '',}
const handleInvite = async ({email}: InviteValues, {resetForm}: any): Promise<void> => {
try{
await axiosInstance.post('/user/invite/', {
'email': email
})
getCompanyUsers()
} catch{
// put a message here
}
resetForm();
}
return(
<Card>
<div className='card-header'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
<h4 className='text-white font-weight-bold '>
Add a user
</h4>
</div>
</div>
<CardContent>
<Formik
initialValues={initialValues}
onSubmit={handleInvite}
validateOnMount
validationSchema={validationSchema}>
{(formik) =>
<Form>
<div className="row">
<div className='col-12'>
<Field
name={"email"}
fullWidth
as={TextField}
label={"Email"}
errorstring={<ErrorMessage name={"prompt"}/>}
size={'small'}
role={undefined}
tabIndex={-1}
margin={"dense"}
variant={"outlined"}
InputProps={{
endAdornment: (
<InputAdornment position='end'>
<Button
type={'submit'}
startIcon={<Send/>}
disabled={!formik.isValid || formik.isSubmitting}>
</Button>
</InputAdornment>
)
}}
>
</Field>
</div>
{/* <div className='col-11'>
<TextField
label='Email'
name='email'
onChange={formik.handleChange}
value={formik.values.email}
fullWidth={true}
variant={"outlined"} //enables special material-ui styling
size={"small"}
multiline={true}
margin={"dense"}
/>
</div>
<div className='col-1'>
<Button
type={'submit'}
disabled={!formik.isValid}>Invite
</Button>
</div> */}
</div>
</Form>
}
</Formik>
</CardContent>
</Card>
)
}
const CompanyManagerCard = ({}): JSX.Element => {
const [users, setUsers] = useState<Account[]>([]);
const {account}= useContext(AccountContext);
const [showModal, setShowModal] = useState<boolean>(false);
const handleUserUpdate = async(email: string, field: string, value: string): Promise<void> => {
//console.log(email, field, value)
if(field === 'delete'){
await axiosInstance.delete(`/company_users`, {
data: {'email':email}
})
}else {
await axiosInstance.post(`/company_users`, {
'email':email,
'field': field,
'value': value
});
}
// get all of th edata again
try{
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
setUsers(data.map((item) => new Account({
email: item.email,
first_name: item.first_name,
last_name: item.last_name,
is_company_manager: item.is_company_manager,
has_password: item.has_usable_password,
is_active: item.is_active,
company: undefined
})))
}catch(error){
console.log(error)
}
}
async function getCompanyUsers(){
try{
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
console.log(data)
setUsers(data.map((item) => new Account({
email: item.email,
first_name: item.first_name,
last_name: item.last_name,
is_company_manager: item.is_company_manager,
has_password: item.has_usable_password,
is_active: item.is_active,
has_signed_tos: item.has_signed_tos,
company: undefined
})))
}catch(error){
console.log(error)
}
}
useEffect(()=>{
getCompanyUsers();
}, [])
console.log(users)
return(
<>
<Card>
<div className='card-header'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
<h4 className='text-white font-weight-bold '>
Company Accounts
</h4>
</div>
</div>
<CardContent>
<TableContainer component={Paper}>
<Table >
<TableHead>
<TableRow>
<TableCell>Email</TableCell>
<TableCell>First Name</TableCell>
<TableCell>Last Name</TableCell>
<TableCell>Is Manager</TableCell>
<TableCell>Is Active</TableCell>
<TableCell>Has Password</TableCell>
<TableCell>Delete</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => <CompanyAccountLine user={user} handleUserUpdate={handleUserUpdate} key={user.email}/>)}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
<AddUserCard getCompanyUsers={getCompanyUsers} />
</>
)
}
type AccountInformationProps = {
account: Account | undefined
}
const AccountInformation = ({account}: AccountInformationProps): JSX.Element => {
const updateAccount = async({}: AccountUpdateValues): Promise<void> => {
//console.log('Need to update the account')
}
return (
<Card>
<div className='card-header'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
<h4 className='text-white font-weight-bold '>
Account Information
</h4>
</div>
</div>
<CardContent>
<Formik initialValues={{
email: account ? account?.email : '',
first_name: account ? account.first_name : '',
last_name: account ? account.last_name : '',
}}
onSubmit={updateAccount}
>
{(formik) => (
<Form>
<div className='row'>
{/* <p>{ account?.first_name} {account?.last_name}</p>
<p>{account?.email}</p> */}
<div className='col-12'>
<CustomTextField label='Email' name='email' changeHandler={(e) => {formik.setFieldValue('email', e.target.value)}} isMultline={false}/>
</div>
<div className='col-12'>
<CustomTextField label='First Name' name='first_name' changeHandler={(e) => {formik.setFieldValue('first_name', e.target.value)}} isMultline={false}/>
</div>
<div className='col-12'>
<CustomTextField label='Last Name' name='last_name' changeHandler={(e) => {formik.setFieldValue('last_name', e.target.value)}} isMultline={false}/>
</div>
</div>
<Button type={'submit'}>Update Account</Button>
</Form>
)}
</Formik>
</CardContent>
<p>Something</p>
</Card>
)
}
type AccountUpdateValues = {
email: string,
}
const AccountPage = ({}): JSX.Element => {
const { account } = useContext(AccountContext)
console.log(account)
return (
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
<Box
sx={{ flexGrow: 1, p: 3, mt: 5, width: { sm: `calc(100% - ${0}px)`} }}
position='fixed'
>
<div className='container-fluid'>
<div className='row'>
<div className='col-12' style={{
position: 'sticky',
top: 0,
}}>
<Header />
</div>
</div>
{/* <AccountInformation account={account}/> */}
{ account?.is_company_manager ?
<CompanyManagerCard />
:
<Typography>Account and prompt information will be available soon</Typography>
}
</div>
<Footer />
</Box>
</div>
);
};
export default AccountPage;

View File

@@ -0,0 +1,292 @@
import { useContext, useEffect, useState } from "react";
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout";
import { AccountContext } from "../../contexts/AccountContext";
import MDTypography from "../../ui-kit/components/MDTypography";
import { Account, AccountType } from "../../data";
import * as Yup from 'yup';
import { Card, CardContent, Divider, IconButton, InputAdornment, Paper, Switch, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from "@mui/material";
import { DeleteForever, Send } from "@mui/icons-material";
import { axiosInstance } from "../../../axiosApi";
import { AxiosResponse } from "axios";
import { ErrorMessage, Field, Form, Formik } from "formik";
import MDButton from "../../ui-kit/components/MDButton";
import MDBox from "../../ui-kit/components/MDBox";
import Header2 from "../../components/Header2/Header2";
import Footer from "../../components/Footer/Footer";
import ThemeCard from "../../components/ThemeCard/ThemeCard";
type AccountUpdateValues = {
email: string,
}
type AccountInformationProps = {
account: Account | undefined
}
type InviteValues = {
email: string
}
const validationSchema = Yup.object().shape({
email: Yup.string().email().required("This is requried")
}
)
type AddUserCardProps = {
getCompanyUsers: ()=> void;
}
type CompanyAccountLineProps = {
user: Account
handleUserUpdate: (email: string, field: string, value: string) => void
}
const CompanyAccountLine = ({user, handleUserUpdate}: CompanyAccountLineProps): JSX.Element => {
const {account} = useContext(AccountContext);
return(
<TableRow key={user.email}>
<TableCell><MDTypography> {user.email}</MDTypography></TableCell>
<TableCell><MDTypography> {user.first_name}</MDTypography></TableCell>
<TableCell><MDTypography>{user.last_name}</MDTypography></TableCell>
<TableCell >
<Switch checked={user.is_company_manager} onChange={(event) => handleUserUpdate(user.email, 'company_manager', event.target.value)} disabled={account?.email === user.email} />
</TableCell>
<TableCell >
<Switch checked={user.is_active} onChange={(event) => handleUserUpdate(user.email, 'is_active', event.target.value)} />
</TableCell>
<TableCell >
<Switch checked={user.has_password && user.has_signed_tos} onChange={(event) => handleUserUpdate(user.email, 'has_password', event.target.value)} disabled={!user.has_password} />
</TableCell>
<TableCell>
<IconButton onClick={() => handleUserUpdate(user.email, 'delete', '')}>
<DeleteForever color="warning" />
</IconButton>
</TableCell>
</TableRow>
)
}
const AddUserCard = ({getCompanyUsers}: AddUserCardProps): JSX.Element => {
const initialValues = {'email': '',}
const handleInvite = async ({email}: InviteValues, {resetForm}: any): Promise<void> => {
try{
await axiosInstance.post('/user/invite/', {
'email': email
})
getCompanyUsers()
} catch{
// put a message here
}
resetForm();
}
return(
<Card sx={{mt:1}}>
<CardContent>
<MDTypography variant="h3">
Invite A User
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<Formik
initialValues={initialValues}
onSubmit={handleInvite}
validateOnMount
validationSchema={validationSchema}>
{(formik) =>
<Form>
<div className="row">
<div className='col-12'>
<Field
name={"email"}
fullWidth
as={TextField}
label={"Email"}
errorstring={<ErrorMessage name={"prompt"}/>}
size={'small'}
role={undefined}
tabIndex={-1}
margin={"dense"}
variant={"outlined"}
InputProps={{
endAdornment: (
<InputAdornment position='end'>
<MDButton
type={'submit'}
startIcon={<Send/>}
disabled={!formik.isValid || formik.isSubmitting}>
<></>
</MDButton>
</InputAdornment>
)
}}
>
</Field>
</div>
</div>
</Form>
}
</Formik>
</CardContent>
</Card>
)
}
const CompanyManagerCard = ({}): JSX.Element => {
const [users, setUsers] = useState<Account[]>([]);
const {account}= useContext(AccountContext);
const [showModal, setShowModal] = useState<boolean>(false);
const handleUserUpdate = async(email: string, field: string, value: string): Promise<void> => {
//console.log(email, field, value)
if(field === 'delete'){
await axiosInstance.delete(`/company_users`, {
data: {'email':email}
})
}else {
await axiosInstance.post(`/company_users`, {
'email':email,
'field': field,
'value': value
});
}
// get all of th edata again
try{
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
setUsers(data.map((item) => new Account({
email: item.email,
first_name: item.first_name,
last_name: item.last_name,
is_company_manager: item.is_company_manager,
has_password: item.has_usable_password,
is_active: item.is_active,
company: undefined
})))
}catch(error){
console.log(error)
}
}
async function getCompanyUsers(){
try{
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
setUsers(data.map((item) => new Account({
email: item.email,
first_name: item.first_name,
last_name: item.last_name,
is_company_manager: item.is_company_manager,
has_password: item.has_usable_password,
is_active: item.is_active,
has_signed_tos: item.has_signed_tos,
company: undefined
})))
}catch(error){
console.log(error)
}
}
useEffect(()=>{
getCompanyUsers();
}, [])
return(
<>
<Card>
<CardContent>
<MDTypography variant="h3">
Company Accounts
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<TableContainer component={Paper}>
<Table >
<TableHead sx={{ display: "table-header-group" }}>
<TableRow>
<TableCell><MDTypography>Email</MDTypography></TableCell>
<TableCell><MDTypography>First Name</MDTypography></TableCell>
<TableCell><MDTypography>Last Name</MDTypography></TableCell>
<TableCell><MDTypography>Is Manager</MDTypography></TableCell>
<TableCell><MDTypography>Is Active</MDTypography></TableCell>
<TableCell><MDTypography>Has Password</MDTypography></TableCell>
<TableCell><MDTypography>Delete</MDTypography></TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => <CompanyAccountLine user={user} handleUserUpdate={handleUserUpdate} key={user.email}/>)}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
<AddUserCard getCompanyUsers={getCompanyUsers} />
</>
)
}
const AccountPage =({}): JSX.Element => {
const { account } = useContext(AccountContext)
if(account?.is_company_manager){
return(
<>
<ThemeCard />
<CompanyManagerCard />
</>
)
}else{
return(
<>
<ThemeCard />
<MDTypography>Account and prompt information will be available soon</MDTypography>
</>
)
}
}
const Account2 = ({}): JSX.Element => {
return (
<PageWrapperLayout>
<Header2 />
<MDBox sx={{mt:12}}>
</MDBox>
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
<AccountPage />
<Footer />
</MDBox>
</PageWrapperLayout>
)
}
export default Account2

View File

@@ -0,0 +1,257 @@
import { useContext, useEffect, useState } from "react"
import Footer from "../../components/Footer/Footer"
import Header2 from "../../components/Header2/Header2"
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout"
import MDBox from "../../ui-kit/components/MDBox"
import { AccountContext } from "../../contexts/AccountContext"
import { Card, CardContent, Divider } from "@mui/material"
import MDTypography from "../../ui-kit/components/MDTypography"
import { Area, Bar, BarChart, ComposedChart, Legend, Line, LineChart, Rectangle, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"
import { axiosInstance } from "../../../axiosApi"
import { AxiosResponse } from "axios"
import { AdminAnalytics, AdminAnalyticsType, CompanyUsageAnalytics, CompanyUsageAnalyticsType, UserConversationAnalytics, UserConvesationAnalyticsType, UserPromptAnalytics, UserPromptAnalyticsType } from "../../data"
const UserPromptAnalyticsCard = ({}): JSX.Element => {
const [data, setData] = useState<UserPromptAnalytics[]>([])
async function getUserPromptAnalytics(){
try{
const {data, }: AxiosResponse<UserPromptAnalyticsType[]> = await axiosInstance.get(`/analytics/user_prompts/`);
setData(data)
}catch(error){
}
}
useEffect(()=>{
getUserPromptAnalytics();
}, [])
return (
<Card sx={{ mb: 1}}>
<CardContent>
<MDTypography variant="h3">
Prompt Usage
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<div style={{ width: "100%", height: 600}} >
<ResponsiveContainer>
<BarChart data={data}>
<XAxis />
<YAxis />
<Legend />
<Bar dataKey="you" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
<Bar dataKey="all" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
)
}
const UserConversationAnalyticsCard = ({}): JSX.Element => {
const [data, setData] = useState<UserConversationAnalytics[]>([])
async function getUserConversationAnalytics(){
try{
const {data, }: AxiosResponse<UserConvesationAnalyticsType[]> = await axiosInstance.get(`/analytics/user_conversations/`);
setData(data)
}catch(error){
}
}
useEffect(()=>{
getUserConversationAnalytics();
}, [])
return (
<Card sx={{ mb: 1}}>
<CardContent>
<MDTypography variant="h3">
Conversation Usage
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<div style={{ width: "100%", height: 600}} >
<ResponsiveContainer>
<BarChart data={data}>
<XAxis />
<YAxis />
<Legend />
<Bar dataKey="you" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
<Bar dataKey="all" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
)
}
const CompanyUsageAnalyticsCard = ({}): JSX.Element => {
const [data, setData] = useState<CompanyUsageAnalytics[]>([])
async function getCompanyUsageAnalytics(){
try{
const {data, }: AxiosResponse<CompanyUsageAnalyticsType[]> = await axiosInstance.get(`/analytics/company_usage/`);
setData(data)
}catch(error){
}
}
useEffect(()=>{
getCompanyUsageAnalytics();
}, [])
// const exampleData = [
// {
// month: 'Feb',
// used: 10,
// not_used: 5
// },
// {
// month: 'Mar',
// used: 7,
// not_used: 8
// },
// {
// month: 'Apr',
// used: 15,
// not_used: 0
// },
// ]
return (
<Card sx={{ mb: 1}}>
<CardContent>
<MDTypography variant="h3">
Account Usage
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<div style={{ width: "100%", height: 600}} >
<ResponsiveContainer>
<BarChart data={data}>
<XAxis dataKey="month"/>
<YAxis />
<Legend />
<Tooltip />
<Bar dataKey="used" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
<Bar dataKey="not_used" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
)
}
const AdminAnalyticsCard = ({}): JSX.Element => {
const [data, setData] = useState<AdminAnalytics[]>([])
async function getAdminAnalytics(){
try{
const {data, }: AxiosResponse<AdminAnalyticsType[]> = await axiosInstance.get(`/analytics/admin/`);
setData(data)
}catch(error){
}
}
useEffect(()=>{
getAdminAnalytics();
}, [])
return (
<Card sx={{ mb: 1}}>
<CardContent>
<MDTypography variant="h3">
Response Times
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<div style={{ width: "100%", height: 600}} >
<ResponsiveContainer>
<ComposedChart data={data}>
<XAxis dataKey="month"/>
<YAxis />
<Legend />
<Tooltip />
<Area
dataKey="range"
dot={false}
connectNulls
activeDot={false}
/>
<Line type="natural" dataKey="avg" connectNulls />
<Line type="monotone" dataKey="median" connectNulls />
</ComposedChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
)
}
const AnalyticsInner =({}): JSX.Element => {
const { account } = useContext(AccountContext)
return (
<>
<div className="row">
<div className='col-6 col-xs-12'>
<UserConversationAnalyticsCard />
</div>
<div className='col-6 col-xs-12'>
<UserPromptAnalyticsCard />
</div>
</div>
{account?.is_company_manager ? <CompanyUsageAnalyticsCard /> : <></>}
{account?.email === "ryan+admin@aimloperations.com" ? <AdminAnalyticsCard /> : <></>}
</>
)
}
const AnalyticsPage = ({}): JSX.Element => {
return (
<PageWrapperLayout>
<Header2 />
<MDBox sx={{mt:12}}>
</MDBox>
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
<AnalyticsInner />
<Footer />
</MDBox>
</PageWrapperLayout>
)
}
export default AnalyticsPage;

View File

@@ -0,0 +1,184 @@
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useRef, useState } from 'react';
import Footer from '../../components/Footer/Footer';
import Header from '../../components/Header/Header';
import { Announcement, AnnouncementType, Conversation, ConversationType } from '../../data';
import { AxiosResponse } from 'axios';
import { axiosInstance } from '../../../axiosApi';
import { JsxEmit } from 'typescript';
import AsyncChat from '../../components/AsyncChat/AsyncChat';
import ConversationLog from '../../components/ConversationLog/ConversationLog';
import { Col, Container, Row } from 'react-bootstrap';
import { Box, Drawer, Typography } from '@mui/material';
import ConversationDrawer from '../../components/ConversationDrawer/ConversationDrawer';
interface ConfirmEntryProps {
status: string;
message: string;
}
const drawerWidth = 320;
const AnnouncementBadge = ({status, message}: PropsWithChildren<ConfirmEntryProps>): JSX.Element => {
return(
<div className={`alert alert-${status}`}>
{ message }
</div>
)
}
const AsyncDashboard = ({}): JSX.Element => {
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
const [conversations, setConversations] = useState<Conversation[]>([]);
const [selectedConversation, setSelectedConversation] = useState<number | undefined>(undefined);
const [reconnectAttempts, setReconnectAttempts] = useState<number>(0);
const maxReconnectAttempts = 5;
const [mobileOpen, setMobileOpen] = useState(false);
const [isClosing, setIsClosing] = useState(false);
const messageResponse = useRef('');
async function GetAnnouncements(){
const response: AxiosResponse<AnnouncementType[]> = await axiosInstance.get('announcment/get/')
setAnnouncement(response.data.map((status, message) => new Announcement({
status: status,
message: message
})))
}
async function GetConversations(){
const {data, }: AxiosResponse<ConversationType[]> = await axiosInstance.get(`conversations`)
//console.log(data)
setConversations(data.map((item) => new Conversation({
id: item.id,
title: item.title
})))
}
useEffect(() => {
GetAnnouncements();
GetConversations();
}, [selectedConversation])
const handleDrawerClose = () => {
setIsClosing(true);
setMobileOpen(false);
};
const handleDrawerTransitionEnd = () => {
setIsClosing(false);
};
const handleDrawerToggle = () => {
if (!isClosing) {
setMobileOpen(!mobileOpen);
}
};
function deleteConversation(conversation_id: number | undefined){
//console.log(`detele ${conversation_id}`)
try{
axiosInstance.delete(`conversation_details`, {
data: {'conversation_id':conversation_id}
})
// remove it from the list now
setConversations(conversations.filter((conversation) => conversation.id !== conversation_id));
// if it the current selected one, update the selected conversation
if (selectedConversation === conversation_id){
setSelectedConversation(undefined)
}
}catch{
}
}
const conversationTitle = conversations.find(item => item.id === selectedConversation)?.title ?? 'New Conversation';
return(<>
<Box>
<Box component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}>
<Drawer
variant='temporary'
open={mobileOpen}
onTransitionEnd={handleDrawerTransitionEnd}
onClose={handleDrawerClose}
ModalProps={{
keepMounted: true
}}
sx = {{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
>
<ConversationDrawer conversations={conversations}
setSelectConversation={setSelectedConversation}
deleteConversation={deleteConversation}
selectedConversation={selectedConversation}
// setConversations={setConversations}
/>
</Drawer>
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', sm: 'block' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
open
>
<ConversationDrawer conversations={conversations}
setSelectConversation={setSelectedConversation}
deleteConversation={deleteConversation}
selectedConversation={selectedConversation}
// setConversations={setConversations}
/>
</Drawer>
</Box>
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
<Header drawerWidth={drawerWidth} handleDrawerToggle={handleDrawerToggle}/>
<AsyncChat drawerWidth={drawerWidth} selectedConversation={selectedConversation} conversationTitle={conversationTitle} setConversations={setConversations} conversations={conversations} setSelectedConversation={setSelectedConversation}/>
</Box>
</Box>
</>)
// return(
// <>
// <Header />
// <Container fluid>
// <Row>
// <Col xs={3} className="sidebar">
// <Typography variant="h6">Conversations</Typography>
// <ConversationLog conversations={conversations} setSelectConversation={setSelectedConversation} deleteConversation={deleteConversation} setConversations={setConversations}/>
// </Col>
// <Col xs={9} className="main-content">
// <Box className="message-box">
// {/* {messages.map((msg, index) => (
// <MessageCard key={index} message={msg} />
// ))} */}
// <AsyncChat selectedConversation={selectedConversation} conversationTitle={conversationTitle} setConversations={setConversations} conversations={conversations} setSelectedConversation={setSelectedConversation}/>
// </Box>
// </Col>
// </Row>
// </Container>
// <Footer />
// </>
// )
}
export default AsyncDashboard;

View File

@@ -0,0 +1,287 @@
import { Card, CardContent, Divider, InputAdornment, } from "@mui/material"
import DashboardLayout from "../../ui-kit/examples/LayoutContainers/DashboardLayout"
import MDBox from "../../ui-kit/components/MDBox"
import { useMaterialUIController } from "../../ui-kit/context"
import { useContext, useEffect, useRef, useState } from "react"
// Images
import MDTypography from "../../ui-kit/components/MDTypography"
import { CardFooter } from "react-bootstrap"
import MDInput from "../../ui-kit/components/MDInput"
import { ErrorMessage, Field, Form, Formik } from "formik"
import MDButton from "../../ui-kit/components/MDButton"
import { AttachFile, Send } from "@mui/icons-material"
import Header2 from "../../components/Header2/Header2"
import DashboardWrapperLayout from "../../components/DashboardWrapperLayout/DashboardWrapperLayout"
import * as Yup from 'yup';
import { Announcement, AnnouncementType, Conversation, ConversationPrompt, ConversationPromptType, ConversationType } from "../../data"
import { axiosInstance } from "../../../axiosApi"
import { AxiosResponse } from "axios"
import { ConversationContext } from "../../contexts/ConversationContext"
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"
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>>
drawerWidth: number;
}
const validationSchema = Yup.object().shape({
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%)',
height: 1,
overflow: 'hidden',
position: 'absolute',
bottom: 0,
left: 0,
whiteSpace: 'nowrap',
width: 1,
});
type PromptValues = {
prompt: string;
file: Blob | null;
fileType: string | null;
modelName: string;
};
const models: CustomSelectItem[] = [
{
label: "General",
value: "GENERAL"
},
{
label: "Code",
value: "CODE"
},
{
label: "Reasoning",
value: "REASONING"
}
];
const AlwaysScrollToBottom = (): JSX.Element => {
const elementRef = useRef<any>();
useEffect(() => elementRef.current.scrollIntoView());
return <div ref={elementRef} />;
};
const AsyncDashboardInner =({}): JSX.Element => {
const [controller, dispatch] = useMaterialUIController();
const {
darkMode,
} = controller;
const buttonColor = darkMode? 'dark' : 'light';
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
const [subscribe, unsubscribe, socket, sendMessage] = useContext(WebSocketContext)
const { account } = useContext(AccountContext)
const [isClosing, setIsClosing] = useState(false);
const {conversations, selectedConversation, setSelectedConversation} = useContext(ConversationContext);
const {conversationDetails, setConversationDetails, stateMessage, isGeneratingMessage} = useContext(MessageContext);
const conversationRef = useRef(conversationDetails)
async function GetAnnouncements(){
const response: AxiosResponse<AnnouncementType[]> = await axiosInstance.get('announcment/get/')
setAnnouncement(response.data.map((status, message) => new Announcement({
status: status,
message: message
})))
}
const conversationTitle = 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> => {
// send the prompt to be saved
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
sendMessage(prompt, selectedConversation, file, fileType)
resetForm();
}catch(e){
console.log(`error ${e}`)
// TODO: make this user friendly
}
}
return(
<DashboardLayout>
<Header2 />
<MDBox sx={{mt:5}}>
</MDBox>1
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
<Card>
<CardContent>
<MDTypography variant="h3" >
{conversationTitle}
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<>
{conversationDetails.length > 0 ?
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-center' color='inherit'>Either select a previous conversation on start a new one.</Markdown>
}
<AlwaysScrollToBottom/>
</>
</CardContent>
<Divider />
<CardFooter>
<Formik
initialValues={{
prompt: '',
file: null,
fileType: null,
modelName: '',
}}
validationSchema={validationSchema}
onSubmit={handlePromptSubmit}
>
{(formik)=>
<Form>
<Field
name={"prompt"}
fullWidth
as={MDInput}
label={'Prompt'}
errorstring={<ErrorMessage name={'prompt'}/>}
size={'large'}
role={undefined}
tabIndex={-1}
variant={"outlined"}
InputProps={{
endAdornment: (
<InputAdornment position='end'>
{/* <CustomSelect
name="category"
items={models}
label="Category"
required
/> */}
<MDButton
component="label"
startIcon={<AttachFile/>}
role={undefined}
tabIndex={-1}
color={buttonColor}
>
<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)
}
}}
/>
</MDButton>
<MDButton
type={'submit'}
startIcon={<Send />}
disabled={!formik.isValid}
color={buttonColor}
>
<></>
</MDButton>
</InputAdornment>
)
}}
>
</Field>
</Form>
}
</Formik>
{/* <MDInput
label="Prompt"
/> */}
</CardFooter>
</Card>
<Footer />
</MDBox>
</DashboardLayout>
)
}
const AsyncDashboard2 = ({}): JSX.Element => {
return(
<DashboardWrapperLayout>
<AsyncDashboardInner />
</DashboardWrapperLayout>
)
}
export default AsyncDashboard2

View File

@@ -0,0 +1,296 @@
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
import { MOCK_ACCOUNTS, MOCK_ANNOUNCEMENTS, MOCK_CONVERSATION } from '../../mockData';
import Footer from '../../components/Footer/Footer';
import Header from '../../components/Header/Header';
import { Account, Announcement, AnnouncementType, Conversation, ConversationPrompt, ConversationPromptType, ConversationType } from '../../data';
import { AccountContext } from '../../contexts/AccountContext';
import { axiosInstance } from '../../../axiosApi';
import { AxiosResponse } from 'axios';
import { Form, Formik } from 'formik';
import { object, ref, string } from 'yup';
import { Button, ButtonBase, Card, CardContent, CardHeader } from '@mui/material';
import CustomTextField from '../../components/CustomTextField/CustomTextField';
import ConversationLog from '../../components/ConversationLog/ConversationLog';
import ConversationDetailCard from '../../components/ConversationDetailCard/ConversationDetailCard';
export type PromptValues = {
prompt: string;
};
const validationSchema = object().shape({
})
interface ConfirmEntryProps {
status: string;
message: string;
}
const AnnouncementBadge = ({status, message}: PropsWithChildren<ConfirmEntryProps>): JSX.Element => {
console.log(message)
return(
<div className={`alert alert-${status}`}>
{ message }
</div>
)
}
type ConversationDetailProps = {
selectedConversation: number | undefined
conversationTitle: string | undefined
conversations: Conversation[]
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>
setSelectedConversation: React.Dispatch<React.SetStateAction<number | undefined>>
};
interface IResponseObject {
role: string;
content: string;
}
const ConversationDetail = ({selectedConversation, conversationTitle,conversations, setConversations, setSelectedConversation}: ConversationDetailProps): JSX.Element => {
const [conversationDetails, setConversationDetails] = useState<ConversationPrompt[]>([])
const [promptProcessing, setPromptProcessing] = useState<boolean>(false);
useEffect(() => {
// now we want to stream the new response
const eventSource = new EventSource("http://127.0.0.1:8000/api/streamed_response")
try{
eventSource.onmessage = (event) => {
const responseObject = JSON.parse(event.data)
console.log(responseObject)
setResponse((prev: IResponseObject)=>{
const responseRole = responseObject["role"] || "";
const responseContent = responseObject["content"] || "";
const combinedObject = {
role: prev.role + responseRole,
content: prev.content + responseContent
}
return combinedObject
})
}
eventSource.onerror = (error) => {
console.log(error)
}
}finally{
eventSource.close()
}
}, [])
async function GetConversationDetails(){
if(selectedConversation){
try{
setPromptProcessing(true)
const {data, }: AxiosResponse<ConversationPromptType[]> = await axiosInstance.get(`conversation_details?conversation_id=${selectedConversation}`)
console.log(data)
setConversationDetails(data.map((item) => new ConversationPrompt({
message: item.message,
user_created: item.user_created,
created_timestamp: item.created_timestamp
})))
}finally{
setPromptProcessing(false)
}
}
}
useEffect(() => {
GetConversationDetails();
}, [selectedConversation])
const [response, setResponse] = useState<IResponseObject>({role: '', content: ''})
const handlePromptSubmit = async ({prompt}: PromptValues): Promise<void> => {
setConversationDetails([...conversationDetails, new ConversationPrompt({message: prompt, user_created:true})])
try{
setPromptProcessing(true)
if(!selectedConversation){
const {data, }: AxiosResponse<ConversationType> = await axiosInstance.post('/conversations', {
prompt: prompt
})
// add the conversation to the list
setConversations([...conversations, new Conversation({title: data.title, id: data.id})])
// set the conversation as the selected one
// TODO
setSelectedConversation(data.id)
}
console.log(selectedConversation)
const {data }: AxiosResponse<ConversationPromptType> = await axiosInstance.post('/conversation_details', {
prompt: prompt,
conversation_id: selectedConversation
})
console.log(data)
setConversationDetails([...conversationDetails, new ConversationPrompt({
message: data.message,
user_created: data.user_created,
created_timestamp: data.created_timestamp
})] )
}finally{
setPromptProcessing(false)
}
// try{
// setConversationDetails([...conversationDetails, new ConversationPrompt({
// message: prompt,
// user_created: true,
// })])
// const {data, }: AxiosResponse<ConversationPromptType[]> = await axiosInstance.post('/conversation_details', {
// prompt: prompt,
// conversation_id: selectedConversation ? selectedConversation : -1
// })
// setConversationDetails(data.map((item) => new ConversationPrompt({
// message: item.message,
// user_created: item.user_created,
// created_timestamp: item.created_timestamp
// })))
// }catch(error){
// console.log(error)
// }
}
// we have conversation details here
return (
<div className='col-lg-8 col-md-6 col-sm-12 right-column'>
<div className="card" style= {{height: 'auto'}}>
<div className='card-header'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
<h5 className='text-white'>
{selectedConversation ? conversationTitle : 'Conversation Detail'}
</h5>
</div>
</div>
<div className="card-body">
{selectedConversation ?
conversationDetails.map((convo_detail) => <ConversationDetailCard message={convo_detail.message} user_created={convo_detail.user_created}/>)
:
<p>Either select a previous conversation on start a new one.</p>
}
</div>
<div className='card-footer'>
<Formik
initialValues={{
prompt: '',
}}
onSubmit={handlePromptSubmit}
validationSchema={validationSchema}
>
{(formik) =>
<Form>
<div className='row' style={{
position: 'sticky',
bottom: 0
}}>
<div className='col-10'>
<CustomTextField label='Prompt' name='prompt' changeHandler={(e) => formik.setFieldValue('prompt',e.target.value)} isMultline={true}/>
</div>
<div className='col-2'>
<Button
type={'submit'}
disabled={promptProcessing}
>Send
</Button>
</div>
</div>
</Form>
}
</Formik>
</div>
</div>
</div>
);
}
// assume that we are using the first account at this point
const Dashboard = ({}): JSX.Element => {
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
const { account, setAccount} = useContext(AccountContext);
const [conversations, setConversations] = useState<Conversation[]>([]);
const [selectedConversation, setSelectedConversation] = useState<number | undefined>(undefined);
async function GetAnnouncements(){
const response: AxiosResponse<AnnouncementType[]> = await axiosInstance.get('announcment/get/')
setAnnouncement(response.data.map((status, message) => new Announcement({
status: status,
message: message
})))
}
async function GetConversations(){
const {data, }: AxiosResponse<ConversationType[]> = await axiosInstance.get(`conversations`)
setConversations(data.map((item) => new Conversation({
id: item.id,
title: item.title
})))
}
useEffect(() => {
GetAnnouncements();
GetConversations();
}, [selectedConversation])
const conversationTitle = selectedConversation ? conversations.find(item => item.id === selectedConversation)?.title : 'New Conversation';
return (
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
<div className='container-fluid'>
<div className='row'>
<div className='col-12' style={{
position: 'sticky',
top: 0,
}}>
<Header />
</div>
</div>
</div>
{ announcements.map((x) => <AnnouncementBadge status={x.status} message={x.message} />)}
<div className='row flex-fill'>
{/* <ConversationLog conversations={conversations} setSelectConversation={setSelectedConversation}/> */}
<ConversationDetail selectedConversation={selectedConversation} conversationTitle={conversationTitle} setConversations={setConversations} conversations={conversations} setSelectedConversation={setSelectedConversation}/>
</div>
<Footer />
</div>
);
};
export default Dashboard;

View File

@@ -0,0 +1,296 @@
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
import Footer from '../../components/Footer/Footer';
import Header from '../../components/Header/Header';
import { AccountContext } from '../../contexts/AccountContext';
import Card from '../../components/Card/Card';
import { Box, Button, CardContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from '@mui/material';
import CustomTextField from '../../components/CustomTextField/CustomTextField';
import { Form, Formik } from 'formik';
import { Account, AccountType, Feedback, FeedbackType } from '../../data';
import { axiosInstance } from '../../../axiosApi';
import { AxiosResponse } from 'axios';
import { Add, BusinessTwoTone, Delete, DeleteForever } from '@mui/icons-material';
import * as Yup from 'yup';
import CustomSelect, { CustomSelectItem } from '../../components/CustomSelect/CustomSelect';
type FeedbackSubmitValues = {
text: string
title: string
category: string
}
type FeedbackSubmitCardProps = {
feedbacks: Feedback[],
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
}
const validataionSchema = Yup.object().shape({
text: Yup.string().min(1, "Need to have at least one character").required("This is requried"),
title: Yup.string().min(1, "Need to have at least one character").required("This is requried"),
category: Yup.string().required("Required")
}
)
const categories: CustomSelectItem[] = [
{
label: "Bug",
value: "BUG"
},
{
label: "Enhancement",
value: "ENCHANCEMENT"
},
{
label: "Other",
value: "OTHER"
}
];
const FeedbackSubmitCard = ({feedbacks, setFeedbacks}: FeedbackSubmitCardProps) => {
const handleFeedbackSubmit = async ({text, title, category}: FeedbackSubmitValues, {resetForm}: any): Promise<void> => {
//console.log(text, title, category)
try{
await axiosInstance.post('/feedbacks/', {
text: text,
title: title,
category: category
})
resetForm();
// put message here
setFeedbacks([...feedbacks, new Feedback({
title: title,
text: text,
status: 'SUBMITTED',
category: category,
})])
} catch{
// put a message here
}
}
return(
<Card>
<div className='card-header'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
<h4 className='text-white font-weight-bold '>
Submit Feedback
</h4>
</div>
</div>
<CardContent>
<Formik
initialValues={{
text: '',
title: '',
category: '',
}}
onSubmit={handleFeedbackSubmit}
validationSchema={validataionSchema}
validateOnMount>
{(formik) =>
<Form>
<div className="row">
<div className='col-6'>
<TextField
label='Title'
name='title'
onChange={formik.handleChange}
value={formik.values.title}
fullWidth={true}
variant={"outlined"} //enables special material-ui styling
size={"small"}
multiline={false}
margin={"dense"}
/>
</div>
{/* <div className='col'>
<CustomTextField label='Category' name='category' changeHandler={(e) => formik.setFieldValue('category',e.target.value)} isMultline={false}/>
</div> */}
<div className='col-6'>
<CustomSelect
name="category"
items={categories}
label="Category"
required
/>
</div>
<div className="row">
<div className='col'>
<TextField
label='Text'
name='text'
onChange={formik.handleChange}
value={formik.values.text}
fullWidth={true}
variant={"outlined"} //enables special material-ui styling
size={"small"}
multiline={false}
margin={"dense"}
rows={3}
minRows={3}
maxRows={10}
/>
</div>
</div>
<div className='row'>
<div className='col'>
<Button
type={'submit'}
disabled={!formik.isValid}
>Submit
</Button>
</div>
</div>
</div>
</Form>
}
</Formik>
</CardContent>
</Card>
)
}
type FeedbackLineProps = {
feedback: Feedback
}
const FeedbackLine = ({feedback}: FeedbackLineProps): JSX.Element => {
return(
<TableRow key={feedback.id}>
<TableCell>{feedback.title}</TableCell>
<TableCell>{feedback.category}</TableCell>
<TableCell>{feedback.text}</TableCell>
<TableCell>{feedback.status}</TableCell>
</TableRow>
)
}
type FeedbackTableCardProps = {
feedbacks: Feedback[],
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
}
const FeedbackTableCard = ({feedbacks, setFeedbacks}: FeedbackTableCardProps) => {
async function getCompanyUsers(){
try{
const {data, }: AxiosResponse<FeedbackType[]> = await axiosInstance.get(`/feedbacks/`);
setFeedbacks(data.map((item) => new Feedback({
id: item.id,
title: item.title,
text: item.text,
status: item.status,
category: item.category,
})))
}catch(error){
console.log(error)
}
}
useEffect(()=>{
getCompanyUsers();
}, [])
return(
<Card>
<div className='card-header'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
<h4 className='text-white font-weight-bold '>
Feedback
</h4>
</div>
</div>
<CardContent>
<TableContainer component={Paper}>
<Table >
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Category</TableCell>
<TableCell>Text</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{feedbacks.map((feedback) => <FeedbackLine key={feedback.id} feedback={feedback}/>)}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
)
}
const FeedbackPage = ({}): JSX.Element => {
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
return (
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
<Box
sx={{ flexGrow: 1, p: 3, mt: 5, width: { sm: `calc(100% - ${0}px)` } }}
position='fixed'
>
<div className='container-fluid'>
<div className='row'>
<div className='col-12' style={{
position: 'sticky',
top: 0,
}}>
<Header />
</div>
</div>
<Box sx={{
mb: 2,
display: "flex",
flexDirection: "column",
height: 900,
overflow: "hidden",
overflowY: "scroll",
// justifyContent="flex-end" # DO NOT USE THIS WITH 'scroll'
}}>
<FeedbackSubmitCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
<FeedbackTableCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
</Box>
</div>
<Footer />
</Box>
</div>
);
};
export default FeedbackPage;

View File

@@ -0,0 +1,105 @@
import { useEffect, useState } from "react";
import MDBox from "../../ui-kit/components/MDBox";
import { CardContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
import { Feedback, FeedbackType } from "../../data";
import { AxiosResponse } from "axios";
import { axiosInstance } from "../../../axiosApi";
import Header2 from "../../components/Header2/Header2";
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout";
import Footer from "../../components/Footer/Footer";
import MDTypography from "../../ui-kit/components/MDTypography";
import FeedbackSubmitCard from "../../components/FeedbackSubmitCard/FeedbackSubmitCard";
type FeedbackLineProps = {
feedback: Feedback
}
const FeedbackLine = ({feedback}: FeedbackLineProps): JSX.Element => {
return(
<TableRow key={feedback.id}>
<TableCell><MDTypography>{feedback.title}</MDTypography></TableCell>
<TableCell><MDTypography>{feedback.category}</MDTypography></TableCell>
<TableCell><MDTypography>{feedback.text}</MDTypography></TableCell>
<TableCell><MDTypography>{feedback.status}</MDTypography></TableCell>
</TableRow>
)
}
type FeedbackTableCardProps = {
feedbacks: Feedback[],
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
}
const FeedbackTableCard =({feedbacks, setFeedbacks}: FeedbackTableCardProps): JSX.Element => {
async function getCompanyUsers(){
try{
const {data, }: AxiosResponse<FeedbackType[]> = await axiosInstance.get(`/feedbacks/`);
setFeedbacks(data.map((item) => new Feedback({
id: item.id,
title: item.title,
text: item.text,
status: item.status,
category: item.category,
})))
}catch(error){
console.log(error)
}
}
useEffect(()=>{
getCompanyUsers();
}, [])
return (
<CardContent>
<TableContainer component={Paper}>
<Table >
<TableHead sx={{ display: "table-header-group" }}>
<TableRow>
<TableCell><MDTypography>Title</MDTypography></TableCell>
<TableCell><MDTypography>Category</MDTypography></TableCell>
<TableCell><MDTypography>Text</MDTypography></TableCell>
<TableCell><MDTypography>Status</MDTypography></TableCell>
</TableRow>
</TableHead>
<TableBody>
{feedbacks.map((feedback) => <FeedbackLine key={feedback.id} feedback={feedback}/>)}
</TableBody>
</Table>
</TableContainer>
</CardContent>
)
}
const FeedbackPageInner =({}): JSX.Element => {
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
return(
<>
<FeedbackSubmitCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
<FeedbackTableCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
</>
)
}
const FeedbackPage2 = ({}): JSX.Element => {
return (
<PageWrapperLayout>
<Header2 />
<MDBox sx={{mt:12}}>
</MDBox>
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
<FeedbackPageInner />
<Footer />
</MDBox>
</PageWrapperLayout>
)
}
export default FeedbackPage2

View File

@@ -0,0 +1,40 @@
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
import MDBox from '../../ui-kit/components/MDBox';
import { Card, CardContent, Divider } from '@mui/material';
import MDTypography from '../../ui-kit/components/MDTypography';
import Footer from '../../components/Footer/Footer';
import Header2 from '../../components/Header2/Header2';
const NotFound = ({}): JSX.Element => {
return (
<PageWrapperLayout>
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
<Header2 />
<MDBox sx={{mt:12}}>
</MDBox>
<Card>
<CardContent>
<MDTypography variant="h3">
Not Found
</MDTypography>
</CardContent>
<Divider />
<CardContent>
<MDTypography >
The page is not found
</MDTypography>
</CardContent>
</Card>
<Footer/>
</MDBox>
</PageWrapperLayout>
);
};
export default NotFound;

View File

@@ -0,0 +1,100 @@
import { Form, Formik } from 'formik';
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
import { Button } from '@mui/material';
import { axiosInstance } from '../../../axiosApi';
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
export type PasswordResetValues = {
password1: string;
password2: string;
};
const PasswordReset = ({}): JSX.Element => {
const handlePasswordReset = ({password1, password2}: PasswordResetValues): void => {
try{
// verify
if(password1 === password2){
const response = axiosInstance.post('token/obtain', {
'password': password1,
});
//console.log(response)
}
}catch(error){
console.log('catching the error');
<CustomToastMessage message={error as string} />
}
}
return (
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
<div className='container my-auto'>
<div className='row'>
<div className='col -lg-4 col-md-8 col-12 mx-auto'>
<div className='card z-index-0 fadeIn3 fadeInBottom'>
<div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
<h4 className='text-white font-weight-bold text-center'>Password Reset</h4>
</div>
</div>
<div className='card-body text-center'>
<Formik
initialValues={{
password1: '',
password2: '',
}}
onSubmit={handlePasswordReset}>
{(formik) => (
<Form>
<div className='row'>
<div className='col'>
<CustomPasswordField
label='Password'
name="password1"
changeHandler={(e) => formik.setFieldValue('password1', e.target.value)} />
</div>
</div>
<div className='row'>
<div className='col'>
<CustomPasswordField
label='Confirm Password'
name="password2"
changeHandler={(e) => formik.setFieldValue('password2', e.target.value)} />
</div>
</div>
<div className='row'>
<div className='col'>
<Button
type={'submit'}
disabled={ formik.isSubmitting}
// type={'submit'}
// loading={formik.isSubmitting}
// disabled={
// !formik.isValid || !formik.dirty || formik.isSubmitting
// }
>
Reset Password
</Button>
</div>
</div>
</Form>
)}
</Formik>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default PasswordReset;

View File

@@ -0,0 +1,68 @@
import React from 'react';
import { Container, Row, Col } from 'react-bootstrap';
import { AppBar, Toolbar, Typography, Button, Box } from '@mui/material';
import ConversationCard from './components/ConversationCard';
import MessageCard from './components/MessageCard';
const SamplePage: React.FC = () => {
const conversations = [
{ id: 1, title: 'Conversation 1' },
{ id: 2, title: 'Conversation 2' },
{ id: 3, title: 'Conversation 3' },
];
const messages = [
'Message 1',
'Message 2',
'Message 3',
'Message 4',
'Message 5',
'Message 6',
'Message 7',
'Message 8',
'Message 9',
'Message 10',
];
return (
<div className="app-container">
<AppBar position="static">
<Toolbar>
<Typography variant="h6" style={{ flexGrow: 1 }}>
Dashboard
</Typography>
<Button color="inherit">Dashboard</Button>
<Button color="inherit">Account</Button>
<Button color="inherit">Sign Out</Button>
</Toolbar>
</AppBar>
<Container fluid>
<Row>
<Col xs={3} className="sidebar">
<Typography variant="h6">Conversations</Typography>
{conversations.map((conv) => (
<ConversationCard key={conv.id} title={conv.title} />
))}
</Col>
<Col xs={9} className="main-content">
<Box className="message-box">
{messages.map((msg, index) => (
<MessageCard key={index} message={msg} />
))}
</Box>
</Col>
</Row>
</Container>
<footer className="footer">
<a href="https://aimloperations.com" target="_blank" rel="noopener noreferrer">
Visit AIMLOperations.com
</a>
</footer>
</div>
);
};
export default SamplePage;

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { Card, CardContent, Typography } from '@mui/material';
interface ConversationCardProps {
title: string;
}
const ConversationCard: React.FC<ConversationCardProps> = ({ title }) => {
return (
<Card style={{ marginBottom: '10px' }}>
<CardContent>
<Typography variant="h6">{title}</Typography>
</CardContent>
</Card>
);
};
export default ConversationCard;

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { Card, CardContent, Typography } from '@mui/material';
interface MessageCardProps {
message: string;
}
const MessageCard: React.FC<MessageCardProps> = ({ message }) => {
return (
<Card style={{ marginBottom: '10px' }}>
<CardContent>
<Typography variant="body1">{message}</Typography>
</CardContent>
</Card>
);
};
export default MessageCard;

View File

@@ -0,0 +1,240 @@
import { Form, Formik } from 'formik';
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
import { Alert, Button, Card, CardContent, Divider, TextField } from '@mui/material';
import { object, ref, string } from 'yup';
import { axiosInstance } from '../../../axiosApi';
import CustomTextField, { CustomTextFieldProps } from '../../components/CustomTextField/CustomTextField';
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
import { AuthContext } from '../../contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
import { AccountContext } from '../../contexts/AccountContext';
import { MOCK_ACCOUNTS } from '../../mockData';
import { AxiosResponse } from 'axios';
import { Account, AccountType } from '../../data';
import background from '../../../bg.jpeg'
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
import MDBox from '../../ui-kit/components/MDBox';
import MDTypography from '../../ui-kit/components/MDTypography';
import { Col, Row } from 'react-bootstrap';
import MDButton from '../../ui-kit/components/MDButton';
import MDAlert from '../../ui-kit/components/MDAlert';
export type SignInValues = {
email: string;
password: string;
};
const validationSchema = object().shape({
})
interface GetUserResponse {
email: string
}
const SignIn = ({}): JSX.Element => {
const {authenticated, setAuthentication, setNeedsNewPassword} = useContext(AuthContext);
const { account, setAccount} = useContext(AccountContext);
const navigate = useNavigate();
const [errorMessage, setErrorMessage] = useState<string>('');
const handleSignIn = async ({email, password}: SignInValues): Promise<void> => {
//const curr_account = MOCK_ACCOUNTS.find((account) => account.email === email)
//if (curr_account){
try{
const response = await axiosInstance.post('/token/obtain/',{
username: email,
password: password,
})
axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access;
localStorage.setItem('access_token', response.data.access);
localStorage.setItem('refresh_token', response.data.refresh);
const get_user_response: AxiosResponse<AccountType> = await axiosInstance.get('/user/get/')
const account = new Account({
email: get_user_response.data.email,
first_name: get_user_response.data.first_name,
last_name: get_user_response.data.last_name,
is_company_manager: get_user_response.data.is_company_manager,
has_signed_tos: get_user_response.data.has_signed_tos,
company: {
id: get_user_response.data.company?.id,
name: get_user_response.data.company?.name,
state: get_user_response.data.company?.state,
zipcode: get_user_response.data.company?.zipcode,
address: get_user_response.data.company?.address,
}
});
setAccount(account);
setAuthentication(true)
setNeedsNewPassword(get_user_response.data.has_usable_password)
// TODO: terms of service
if (account.has_signed_tos){
navigate('/');
} else {
navigate('/terms_of_service/')
}
}catch(error){
console.log(error)
setErrorMessage('Error retrieving account. Try again');
}
}
return (
<PageWrapperLayout>
<MDBox sx={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
backgroundRepeat: "no-repeat",}}>
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
<Row>
<Col className='col -lg-4 col-md-8 col-12 mx-auto'>
<Card sx={{mt:30}} >
<CardContent>
<MDTypography variant="h3">
Sign In
</MDTypography>
</CardContent>
<Divider />
<Formik
initialValues={{
email: '',
password: '',
}}
onSubmit={handleSignIn}
validationSchema={validationSchema}
>
{(formik) => (
<Form>
{errorMessage &&
<MDAlert color="error" dismissible={true}>{errorMessage}</MDAlert>
}
<div className='row'>
<div className='col'>
<CustomTextField
label='Email'
name="email"
changeHandler={(e) => formik.setFieldValue('email', e.target.value)} isMultline={false}/>
</div>
</div>
<div className='row'>
<div className='col'>
<CustomPasswordField
label='Password'
name="password"
changeHandler={(e) => formik.setFieldValue('password', e.target.value)} />
</div>
</div>
<div className='row'>
<div className='col'>
<MDButton
type={'submit'}
>
Sign In
</MDButton>
</div>
</div>
</Form>
)}
</Formik>
</Card>
</Col>
</Row>
</MDBox>
</MDBox>
</PageWrapperLayout>
// <div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
// backgroundRepeat: "no-repeat",}}>
// <div className='container my-auto'>
// <div className='row'>
// <div className='col -lg-4 col-md-8 col-12 mx-auto'>
// <div className='card z-index-0 fadeIn3 fadeInBottom'>
// <div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
// <div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
// <h4 className='text-white font-weight-bold text-center'>Sign In</h4>
// </div>
// </div>
// <div className='card-body text-center'>
// <Formik
// initialValues={{
// email: '',
// password: '',
// }}
// onSubmit={handleSignIn}
// validationSchema={validationSchema}
// >
// {(formik) => (
// <Form>
// {errorMessage &&
// <Alert severity="error">{errorMessage}</Alert>
// }
// <div className='row'>
// <div className='col'>
// <CustomTextField
// label='Email'
// name="email"
// changeHandler={(e) => formik.setFieldValue('email', e.target.value)} isMultline={false}/>
// </div>
// </div>
// <div className='row'>
// <div className='col'>
// <CustomPasswordField
// label='Password'
// name="password"
// changeHandler={(e) => formik.setFieldValue('password', e.target.value)} />
// </div>
// </div>
// <div className='row'>
// <div className='col'>
// <Button
// type={'submit'}
// >
// Sign In
// </Button>
// </div>
// </div>
// </Form>
// )}
// </Formik>
// </div>
// </div>
// </div>
// </div>
// </div>
// </div>
);
};
export default SignIn;

View File

@@ -0,0 +1,105 @@
import { Form, Formik } from 'formik';
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
import { Alert, Box, Button, Card, CardContent, CardHeader, Divider, TextField } from '@mui/material';
import { object, ref, string } from 'yup';
import { axiosInstance } from '../../../axiosApi';
import CustomTextField, { CustomTextFieldProps } from '../../components/CustomTextField/CustomTextField';
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
import { AuthContext } from '../../contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
import { AccountContext } from '../../contexts/AccountContext';
import { MOCK_ACCOUNTS } from '../../mockData';
import { AxiosResponse } from 'axios';
import { Account, AccountType } from '../../data';
import { Label } from '@mui/icons-material';
import background from '../../../bg.jpeg'
import MDTypography from '../../ui-kit/components/MDTypography';
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
import MDBox from '../../ui-kit/components/MDBox';
import MDButton from '../../ui-kit/components/MDButton';
const TermsOfService = ({}): JSX.Element => {
const {authenticated, setAuthentication, setNeedsNewPassword} = useContext(AuthContext);
const { account, setAccount} = useContext(AccountContext);
const navigate = useNavigate();
const handleAcknowledge = async (): Promise<void> => {
try{
const response = await axiosInstance.post('/user/acknowledge_tos/')
navigate('/')
}catch(error){
console.log(error)
}
}
return (
<PageWrapperLayout>
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
<Card>
<CardContent>
<MDTypography variant="h3">
Terms of service
</MDTypography>
</CardContent>
<Divider />
<MDTypography>
Welcome to chat.aimloperations.com! We're excited to have you on board. Before you start using our large language model, supplied by AI ML Operations, LLC, we need you to agree to our Terms of Service.
</MDTypography>
<MDTypography variant="h4">
1. Acceptance of Terms
</MDTypography>
<MDTypography>By logging in, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service. If you don't agree, please don't use our services.</MDTypography>
<MDTypography variant="h4">2. Data Confidentiality</MDTypography>
<MDTypography>We respect your data. All information entered by you will be contained within your organization and will not be sold, shared, or disclosed to any third parties without your explicit consent.</MDTypography>
<MDTypography variant="h4">3. Use of Services</MDTypography>
<MDTypography>You agree to use our large language model for legitimate purposes only. You're responsible for ensuring that your use of our services complies with all applicable laws and regulations.</MDTypography>
<MDTypography variant="h4">4. Intellectual Property</MDTypography>
<MDTypography>All intellectual property rights in our services, including the large language model, remain the property of AI ML Operations, LLC. Any information generated by our service is for your use and AI ML Operations, LLC makes no claim to it.</MDTypography>
<MDTypography variant="h4">5. Limitation of Liability</MDTypography>
<MDTypography>In no event will we be liable for any damages, including consequential, incidental, or punitive damages, arising from your use of our services.</MDTypography>
<MDTypography variant="h4">6. Changes to Terms</MDTypography>
<MDTypography>We may update these Terms of Service from time to time. We'll notify you of significant changes, and your continued use of our services will constitute acceptance of the updated terms.</MDTypography>
<MDTypography variant="h4">7. Governing Law</MDTypography>
<MDTypography>These Terms of Service will be governed by and construed in accordance with the laws of the United States and the state of Illinois.</MDTypography>
<MDTypography>By logging in, you acknowledge that you have read and agree to these Terms of Service. If you have any questions, please don't hesitate to contact us.</MDTypography>
Happy using our services!
<Divider />
<Formik
initialValues={{
email: '',
password: '',
}}
onSubmit={handleAcknowledge}
>
{(formik) => (
<Form>
<MDButton
type={'submit'}
disabled={ formik.isSubmitting}
loading={formik.isSubmitting}
// disabled={
// !formik.isValid || !formik.dirty || formik.isSubmitting
// }
>
Acknowledge
</MDButton>
</Form>
)}
</Formik>
</Card>
</MDBox>
</PageWrapperLayout>
);
};
export default TermsOfService;

View File

@@ -0,0 +1,12 @@
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
const NotFound = ({}): JSX.Element => {
return (
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
<p>401</p>
</div>
);
};
export default NotFound;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="352px" height="92px" viewBox="0 0 352 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logos</title>
<g id="Logos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo-coinbase" transform="translate(70.000000, 26.000000)" fill="#707780" fill-rule="nonzero">
<path d="M13.7666278,46.0501403 C6.84087996,46.0501403 0.0617087798,41.0665334 0.0617087798,29.8167736 C0.0617087798,18.5303697 6.84087996,13.6200511 13.7666278,13.6200511 C17.1745355,13.6200511 19.8495598,14.4995112 21.7550565,15.7454129 L19.6663389,20.325934 C18.383793,19.4098298 16.4782963,18.8235231 14.5727995,18.8235231 C10.3953643,18.8235231 6.58437078,22.1214982 6.58437078,29.7434853 C6.58437078,37.3654723 10.5052968,40.7367358 14.5727995,40.7367358 C16.4782963,40.7367358 18.383793,40.1504292 19.6663389,39.2343249 L21.7550565,43.9247785 C19.7762714,45.2439686 17.1745355,46.0501403 13.7666278,46.0501403" id="Fill-1"></path>
<path d="M37.6219815,46.0501403 C28.7907369,46.0501403 23.9170625,39.0511041 23.9170625,29.8167736 C23.9170625,20.5824432 28.7540927,13.6200511 37.6219815,13.6200511 C46.4532261,13.6200511 51.3269006,20.545799 51.3269006,29.8167736 C51.3269006,39.0511041 46.4532261,46.0501403 37.6219815,46.0501403 Z M37.6219815,18.6403022 C32.7116629,18.6403022 30.2931478,23.0376024 30.2931478,29.7434853 C30.2931478,36.4493681 32.7116629,40.8833125 37.6219815,40.8833125 C42.5323001,40.8833125 44.9508152,36.4493681 44.9508152,29.7434853 C44.9508152,23.0376024 42.5323001,18.6403022 37.6219815,18.6403022 Z" id="Fill-2"></path>
<path d="M59.8283477,8.59980005 C57.73963,8.59980005 56.0539983,6.98745663 56.0539983,5.00867153 C56.0539983,3.02988643 57.73963,1.41754302 59.8283477,1.41754302 C61.9170653,1.41754302 63.602697,3.02988643 63.602697,5.00867153 C63.602697,6.98745663 61.8804211,8.59980005 59.8283477,8.59980005 Z M56.640305,14.243002 L63.0163903,14.243002 L63.0163903,45.3905453 L56.640305,45.3905453 L56.640305,14.243002 Z" id="Fill-3"></path>
<path d="M88.0077133,45.3905453 L88.0077133,24.6133017 C88.0077133,20.985529 85.8090631,18.7135906 81.4850513,18.7135906 C79.1764686,18.7135906 77.0511069,19.1166764 75.768561,19.6296948 L75.768561,45.4271894 L69.465764,45.4271894 L69.465764,15.8187012 C72.5805183,14.5361553 76.5747327,13.6200511 81.4484071,13.6200511 C90.1697192,13.6200511 94.3837986,17.4310447 94.3837986,24.026995 L94.3837986,45.4271894 L88.0077133,45.4271894" id="Fill-4"></path>
<path d="M111.130184,46.0501403 C107.099325,46.0501403 103.105111,45.0607477 100.649951,43.8514902 L100.649951,0.0250646113 L106.952748,0.0250646113 L106.952748,15.0491737 C108.455159,14.3529345 110.873674,13.7666278 113.03568,13.7666278 C121.060753,13.7666278 126.520734,19.5564064 126.520734,29.0838903 C126.520734,40.8466684 120.437802,46.0501403 111.130184,46.0501403 Z M111.936355,18.7135906 C110.214079,18.7135906 108.162006,19.1166764 106.952748,19.7396273 L106.952748,40.1870733 C107.868853,40.5901592 109.664417,40.993245 111.459981,40.993245 C116.480232,40.993245 120.181293,37.512049 120.181293,29.5602644 C120.217937,22.7444491 116.993251,18.7135906 111.936355,18.7135906 Z" id="Fill-5"></path>
<path d="M143.670205,46.0501403 C134.729028,46.0501403 130.185151,42.4223676 130.185151,36.2661473 C130.185151,27.5814793 139.419482,26.0424243 148.837033,25.5294059 L148.837033,23.5506208 C148.837033,19.6296948 146.235297,18.2372164 142.241083,18.2372164 C139.309549,18.2372164 135.718421,19.1533206 133.629703,20.1427131 L132.01736,15.8187012 C134.509163,14.7193762 138.723242,13.6200511 142.900678,13.6200511 C150.339444,13.6200511 154.883321,16.5149404 154.883321,24.2102158 L154.883321,43.8514902 C152.648027,45.0607477 148.067505,46.0501403 143.670205,46.0501403 L143.670205,46.0501403 Z M148.873677,29.7434853 C142.497592,30.0732828 136.158151,30.6229453 136.158151,36.1562148 C136.158151,39.4541899 138.686598,41.4696192 143.486984,41.4696192 C145.502414,41.4696192 147.884285,41.1398217 148.873677,40.6634475 L148.873677,29.7434853 Z" id="Fill-6"></path>
<path d="M169.101258,46.0501403 C165.473486,46.0501403 161.662492,45.0607477 159.390554,43.8514902 L161.515915,39.0144599 C163.128259,40.0038525 166.536166,41.0298892 168.918037,41.0298892 C172.325945,41.0298892 174.597883,39.3442574 174.597883,36.7425215 C174.597883,33.9209205 172.216013,32.8215954 169.064614,31.6489821 C164.887179,30.0732828 160.233369,28.167786 160.233369,22.3413632 C160.233369,17.2111797 164.227584,13.6200511 171.153332,13.6200511 C174.927681,13.6200511 178.042435,14.5361553 180.241085,15.8187012 L178.2623,20.2160015 C176.869822,19.3365414 174.084865,18.383793 171.849571,18.383793 C168.551596,18.383793 166.719387,20.106069 166.719387,22.3780074 C166.719387,25.1996084 169.02797,26.1890009 172.10608,27.3616143 C176.430092,28.9739577 181.230478,30.769522 181.230478,36.852454 C181.193834,42.3857234 176.906466,46.0501403 169.101258,46.0501403" id="Fill-7"></path>
<path d="M211.938291,29.670197 L191.234336,32.5650863 C191.857287,38.1716441 195.521704,40.993245 200.76182,40.993245 C203.876574,40.993245 207.247838,40.2237175 209.373199,39.0877483 L211.205408,43.814846 C208.786893,45.0973919 204.609458,46.0134961 200.32209,46.0134961 C190.501453,46.0134961 185.004827,39.7106991 185.004827,29.7801295 C185.004827,20.2526456 190.318232,13.583407 199.039544,13.583407 C207.137905,13.583407 211.938291,18.8968114 211.938291,27.288326 C212.01158,28.0578535 212.01158,28.8640252 211.938291,29.670197 Z M199.0029,18.2372164 C194.165869,18.2372164 190.977827,21.9382774 190.867894,28.4242952 L205.965292,26.3355776 C205.892003,20.9122407 203.143691,18.2372164 199.0029,18.2372164 L199.0029,18.2372164 Z" id="Fill-8"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="352px" height="92px" viewBox="0 0 352 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logos</title>
<g id="Logos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo-nasa" transform="translate(90.000000, 22.000000)" fill="#707780" fill-rule="nonzero">
<path d="M91.3021755,46.5392872 L114.652771,46.5392872 C118.141899,46.5392872 121.005239,45.107617 123.331021,42.243367 C125.567665,39.4700745 126.731011,36.160133 126.731011,32.4035904 C126.731011,24.7986383 116.979463,19.4303298 113.220191,19.4303298 L100.427027,19.4303298 C99.442867,19.4303298 98.6360745,18.9828191 98.0111968,18.2678936 C97.3845,17.4620106 97.0270372,16.5678989 97.0270372,15.4936915 C97.0270372,14.2412074 97.3845,13.1679096 98.0111968,12.2728883 C98.7270319,11.3787766 99.6211436,10.8421277 100.695351,10.8421277 L124.672644,10.8421277 L124.672644,1.26794681 L100.246931,1.26794681 C95.8627819,1.26794681 92.461883,2.61047872 90.0460532,5.20458511 C87.900367,7.62041489 86.82525,10.8412181 86.82525,14.6887181 C86.82525,18.4461702 87.900367,21.7570213 90.0460532,24.4393564 C92.461883,27.3918351 95.326133,28.9144628 98.8152606,28.9144628 L113.578564,28.9144628 C114.471766,28.9144628 115.188511,29.1827872 115.72425,29.8968032 C116.350947,30.6126383 116.619271,31.5076596 116.619271,32.4927287 C116.619271,33.6560745 116.171761,34.7293723 115.455926,35.6243936 C114.650952,36.429367 113.755931,36.8768777 112.594404,36.8768777 L88.9745745,36.8768777 L88.6171117,36.2501809 L79.7596755,7.79960106 L79.3121649,6.90457979 C77.1646596,2.52043085 73.5863936,0.373835106 68.6655957,0.373835106 C65.9814415,0.373835106 63.5656117,0.99962234 61.4181064,2.34215426 C59.1814628,3.6837766 57.7497926,5.65209574 56.9448191,8.1579734 L45.0448564,46.4492394 L55.8706117,46.5401968 L66.7855053,12.452984 C67.0538298,11.3787766 67.6805266,10.8421277 68.4855,10.8421277 C68.9330106,10.8421277 69.2904734,11.0204043 69.5587979,11.2896383 C69.8271223,11.6471011 70.0063085,12.0054734 70.0954468,12.452984 L81.0994787,46.5401968 L91.3021755,46.5401968 L91.3021755,46.5392872 Z M10.779367,15.225367 L10.779367,46.4492394 L0.758585106,46.4492394 L0.758585106,11.8253777 C0.758585106,7.35209043 1.83279255,4.22042553 4.15857447,2.34215426 C5.94770745,0.99962234 8.6318617,0.283787234 12.3893138,0.283787234 C15.4309309,0.283787234 18.0259468,1.17880851 20.3517287,2.87880319 C22.6775106,4.57879787 24.1983191,6.90457979 25.0042021,9.76792021 L31.5349468,32.7610532 L32.6091543,35.8927181 C32.6982926,36.5185053 33.0566649,36.966016 33.4141277,37.3225691 C33.8616383,37.6818511 34.3082394,37.8610372 34.8457979,37.6818511 C35.7408191,37.5927128 36.1883298,37.0551543 36.1883298,36.2501809 L36.1883298,1.35799468 L46.4774362,1.35799468 L46.4774362,35.8026702 C46.4774362,39.6501702 45.5833245,42.5144202 43.7041436,44.5718777 C41.9150106,46.6293351 39.2308564,47.7035426 35.6516809,47.7035426 C32.520016,47.7035426 29.925,47.0768457 27.8675426,46.0035479 C25.3625745,44.7510638 23.7517181,42.7827447 23.035883,40.1868191 L15.5209787,13.972883 L14.9843298,12.2728883 C14.8051436,11.6471011 14.4476809,11.1995904 14.0001702,10.7520798 C13.5526596,10.3937074 13.0160106,10.3045691 12.4793617,10.3937074 C11.9427128,10.4837553 11.58525,10.7520798 11.2268777,11.2887287 C10.9327173,11.7741096 10.7778881,12.331117 10.779367,12.8986755 L10.779367,15.225367 L10.779367,15.225367 Z M170.929053,46.5392872 L159.298324,7.79960106 L158.850814,6.90457979 C156.703309,2.52043085 153.125043,0.373835106 148.204245,0.373835106 C145.52009,0.373835106 143.104261,0.99962234 140.957665,2.34215426 C138.719202,3.6837766 137.289351,5.65209574 136.482559,8.1579734 L124.583505,46.4492394 L135.409261,46.5401968 L146.324154,12.452984 C146.592479,11.3787766 147.219176,10.8421277 148.024149,10.8421277 C148.47166,10.8421277 148.829122,11.0204043 149.097447,11.2896383 C149.36759,11.6471011 149.544957,12.0054734 149.635915,12.452984 L160.639947,46.5401968 L170.929053,46.5401968 L170.929053,46.5392872 L170.929053,46.5392872 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="352px" height="92px" viewBox="0 0 352 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logos</title>
<g id="Logos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo-netflix" transform="translate(86.000000, 23.376000)" fill="#707780" fill-rule="nonzero">
<path d="M24.8880303,46.4655998 C22.1652529,46.9445477 19.3945742,47.0881435 16.5284463,47.4705936 L7.7863584,21.822358 L7.7863584,48.571377 C5.06358105,48.8583916 2.57960352,49.2411958 0,49.624 L0,0.624 L7.26121094,0.624 L17.1971211,28.4261731 L17.1971211,0.624 L24.8880303,0.624 L24.8880303,46.4655998 Z M39.9355996,18.5683453 C42.8975303,18.5683453 47.4356104,18.4247494 50.1583877,18.4247494 L50.1583877,26.0808334 C46.766582,26.0808334 42.8019043,26.0808334 39.9355996,26.2244293 L39.9355996,37.6132969 C44.4261318,37.3262823 48.9164873,36.9431239 53.4543906,36.7995281 L53.4543906,44.1682434 L32.2922383,45.8434101 L32.2922383,0.624 L53.4543906,0.624 L53.4543906,8.28026107 L39.9355996,8.28026107 L39.9355996,18.5683453 Z M81.8774004,8.28043813 L73.9476914,8.28043813 L73.9476914,43.4991328 C71.3680879,43.4991328 68.7884844,43.4991328 66.3048604,43.5945683 L66.3048604,8.28043813 L58.3751514,8.28043813 L58.3751514,0.624 L81.8777539,0.624 L81.8774004,8.28043813 L81.8774004,8.28043813 Z M94.2974648,18.0421223 L104.759229,18.0421223 L104.759229,25.6982063 L94.2974648,25.6982063 L94.2974648,43.0685223 L86.7971006,43.0685223 L86.7971006,0.624 L108.150858,0.624 L108.150858,8.28026107 L94.2974648,8.28026107 L94.2974648,18.0421223 Z M120.570923,36.3690947 C124.918104,36.4645302 129.312657,36.8000593 133.56439,37.0389135 L133.56439,44.5995621 C126.733407,44.1685975 119.902248,43.7385182 112.927915,43.5945683 L112.927915,0.624 L120.570923,0.624 L120.570923,36.3690947 Z M140.013222,45.1259621 C142.449475,45.269735 145.029078,45.4133309 147.513056,45.6999914 L147.513056,0.624 L140.013222,0.624 L140.013222,45.1259621 Z M181,0.624 L171.302536,23.9277811 L181,49.624 C178.133519,49.2411958 175.267391,48.7147958 172.401086,48.236202 L166.907807,34.0724466 L161.319255,47.0881435 C158.548046,46.6091956 155.872993,46.4655998 153.102845,46.0827956 L162.943306,23.6404124 L154.057867,0.624 L162.273924,0.624 L167.28978,13.4961011 L172.640239,0.624 L181,0.624 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="140px" height="140px" viewBox="0 0 140 140" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>bootstrap</title>
<g id="bootstrap" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="bootstrap-5-1" transform="translate(6.000000, 21.000000)" fill="#7952B3">
<path d="M25.7551875,0 C18.6799219,0 13.4445547,6.19273437 13.6789922,12.9085937 C13.9039844,19.3607109 13.6116641,27.7171484 11.5080234,34.5318203 C9.396875,41.365625 5.82896875,45.6961797 0,46.252 L0,52.5275625 C5.82896875,53.0845937 9.396875,57.4129687 11.5077812,64.2477422 C13.6116641,71.0624141 13.9037422,79.4188516 13.67875,85.8709687 C13.4443125,92.5858594 18.6796797,98.7795625 25.7561562,98.7795625 L98.2542578,98.7795625 C105.329523,98.7795625 110.563922,92.5868281 110.329484,85.8709687 C110.104492,79.4188516 110.396812,71.0624141 112.500453,64.2477422 C114.611602,57.4129687 118.170789,53.0828984 124,52.5275625 L124,46.252 C118.171031,45.6949687 114.611844,41.3665937 112.500453,34.5318203 C110.39657,27.7181172 110.104492,19.3607109 110.329484,12.9085937 C110.563922,6.19370312 105.329523,0 98.2542578,0 L25.7542187,0 L25.7551875,0 Z M84.0678828,60.8052891 C84.0678828,70.0527344 77.1701406,75.6610703 65.7231484,75.6610703 L46.2372266,75.6610703 C45.076488,75.6610703 44.1355234,74.7201058 44.1355234,73.5593672 L44.1355234,25.2204375 C44.1355234,24.0596989 45.076488,23.1187344 46.2372266,23.1187344 L65.6122266,23.1187344 C75.1570781,23.1187344 81.4212578,28.2889531 81.4212578,36.2268906 C81.4212578,41.7984141 77.2071953,46.7862656 71.838625,47.6600781 L71.838625,47.9507031 C79.146875,48.7523437 84.0678828,53.8140625 84.0678828,60.8052891 L84.0678828,60.8052891 Z M63.5984375,29.7810703 L52.4878437,29.7810703 L52.4878437,45.4748203 L61.8457266,45.4748203 C69.0798672,45.4748203 73.0686953,42.5617891 73.0686953,37.355 C73.0679687,32.4754062 69.6385937,29.7810703 63.5984375,29.7810703 Z M52.4878437,51.7017031 L52.4878437,68.9965547 L64.00725,68.9965547 C71.5392812,68.9965547 75.5288359,65.9742969 75.5288359,60.2945156 C75.5288359,54.6147344 71.4273906,51.7007344 63.522875,51.7007344 L52.4878437,51.7007344 L52.4878437,51.7017031 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 KiB

Some files were not shown because too many files have changed in this diff Show More