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

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

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>devto</title>
<g id="devto" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(15.000000, 17.000000)" fill="#000000" fill-rule="nonzero" id="Shape">
<path d="M28.689375,42.1049777 C27.7626786,41.4123437 26.8335937,41.0660268 25.9068973,41.0660268 L21.7415402,41.0660268 L21.7415402,66.017567 L25.9092857,66.017567 C26.8359821,66.017567 27.765067,65.67125 28.6917634,64.9786161 C29.6184598,64.2859821 30.081808,63.2470312 30.081808,61.859375 L30.081808,45.2242187 C30.0794196,43.8389509 29.613683,42.7976116 28.689375,42.1049777 L28.689375,42.1049777 Z M96.5149554,0 L10.4850446,0 C4.70513393,0 0.0143303571,4.67886161 0,10.4611607 L0,96.5388393 C0.0143303571,102.321138 4.70513393,107 10.4850446,107 L96.5149554,107 C102.297254,107 106.98567,102.321138 107,96.5388393 L107,10.4611607 C106.98567,4.67886161 102.294866,0 96.5149554,0 L96.5149554,0 Z M36.8290179,61.9047545 C36.8290179,66.3973214 34.0560937,73.2042411 25.27875,73.1899333 L14.1966071,73.1899333 L14.1966071,33.6715625 L25.5128125,33.6715625 C33.9772768,33.6715625 36.8242411,40.4689286 36.8266295,44.9638839 L36.8290179,61.9047545 Z M60.8753571,40.7292634 L48.15,40.7292634 L48.15,49.9054687 L55.9289955,49.9054687 L55.9289955,56.9679464 L48.15,56.9679464 L48.15,66.1417634 L60.8777455,66.1417634 L60.8777455,73.2042411 L46.0267187,73.2042411 C43.3612723,73.2735045 41.1448437,71.166942 41.0779687,68.5014955 L41.0779687,38.6203125 C41.0134821,35.9572545 43.122433,33.7456027 45.7854911,33.6787277 L60.8777455,33.6787277 L60.8753571,40.7292634 Z M85.6286607,68.2650446 C82.4759821,75.6093527 76.827433,74.1476562 74.298125,68.2650446 L65.0956473,33.6811161 L72.8746429,33.6811161 L79.970558,60.8419196 L87.0330357,33.6811161 L94.8144196,33.6811161 L85.6286607,68.2650446 Z"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<?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>github</title>
<g id="github" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="github-icon-1" transform="translate(15.000000, 19.000000)" fill="#161614" fill-rule="nonzero">
<path d="M53.0002078,0 C23.7327702,0 0,23.6406273 0,52.8036819 C0,76.1341256 15.1861464,95.9270685 36.2449012,102.909305 C38.8935606,103.397979 39.8662322,101.763821 39.8662322,100.36903 C39.8662322,99.1100738 39.8167673,94.9501351 39.794321,90.5379911 C25.0492022,93.732178 21.9379002,84.3078151 21.9379002,84.3078151 C19.5270049,78.2043629 16.0532373,76.5818005 16.0532373,76.5818005 C11.2447482,73.3043734 16.415703,73.3714625 16.415703,73.3714625 C21.7379622,73.7441798 24.5404201,78.8131345 24.5404201,78.8131345 C29.2674376,86.8857761 36.9390727,84.5517378 39.9634993,83.2029155 C40.4390276,79.7896537 41.8128223,77.4605849 43.328361,76.1419941 C31.5565411,74.806424 19.1811662,70.2787376 19.1811662,50.0459884 C19.1811662,44.2812948 21.2516264,39.5705628 24.6422597,35.8727935 C24.0919105,34.542607 22.2779196,29.1721655 25.1556141,21.8987953 C25.1556141,21.8987953 29.60621,20.4795708 39.7344643,27.3114781 C43.9618445,26.141146 48.4959903,25.5547375 53.0002078,25.5348592 C57.5044253,25.5547375 62.0418966,26.141146 66.2775902,27.3114781 C76.39379,20.4795708 80.8381508,21.8987953 80.8381508,21.8987953 C83.7229117,29.1721655 81.9080895,34.542607 81.3577403,35.8727935 C84.7558557,39.5705628 86.8121831,44.2808807 86.8121831,50.0459884 C86.8121831,70.3267767 74.4135305,74.7927577 62.6117823,76.0997528 C64.5126486,77.7384664 66.2065104,80.9521174 66.2065104,85.8781971 C66.2065104,92.9432598 66.144991,98.6296827 66.144991,100.36903 C66.144991,101.774174 67.0989573,103.420756 69.7858585,102.902265 C90.8329745,95.9121598 106,76.1262572 106,52.8036819 C106,23.6406273 82.2705552,0 53.0002078,0" id="Path"></path>
<path d="M19.3815873,74.9620202 C19.268311,75.243666 18.8645619,75.3282042 18.4976277,75.1351011 C18.1234113,74.9500069 17.913041,74.5655804 18.0344085,74.2825998 C18.145662,73.9925001 18.5494111,73.9115214 18.9228183,74.1064043 C19.2978438,74.2910536 19.5114505,74.6790396 19.3815873,74.9620202 M21.9189764,77.4518937 C21.673005,77.7028388 21.1919853,77.586265 20.8655068,77.1898252 C20.5281053,76.7942752 20.4649942,76.2656888 20.7146066,76.0107393 C20.9682646,75.7602391 21.4347202,75.8772578 21.7729309,76.2732527 C22.1103324,76.6732521 22.1758708,77.1987239 21.9185718,77.4523386 M23.6597903,80.6376503 C23.3434258,80.8792517 22.8264004,80.6527782 22.507204,80.1482184 C22.1912441,79.6441036 22.1912441,79.0389878 22.5140815,78.7964966 C22.8344916,78.5540053 23.3434258,78.772025 23.6670724,79.2725803 C23.9826278,79.7855939 23.9826278,80.3907097 23.6593858,80.6380953 M26.6033558,84.3275218 C26.3205697,84.6701241 25.718587,84.5784669 25.2776186,84.110392 C24.8269408,83.6529957 24.7011232,83.003831 24.9847185,82.6607837 C25.2707411,82.3172916 25.8763647,82.4138431 26.3205697,82.8779135 C26.7684156,83.33442 26.9051563,83.988479 26.6037604,84.3275218 M30.4078212,85.5733484 C30.2836218,86.0173966 29.7034854,86.2193985 29.1193033,86.0307447 C28.5359304,85.8363068 28.1540274,85.3157292 28.2717539,84.8667867 C28.3931213,84.419624 28.9756851,84.2091683 29.5643173,84.4111701 C30.1468811,84.6047182 30.5295932,85.1212913 30.4082257,85.5733484 M34.7382122,86.1014899 C34.7527763,86.5695647 34.2571925,86.9575508 33.6438823,86.9660046 C33.026931,86.9806875 32.5281107,86.6020452 32.5216378,86.1419793 C32.5216378,85.669455 33.005894,85.2850285 33.6224407,85.2739051 C34.2357509,85.2605569 34.7382122,85.6365296 34.7382122,86.1014899 M38.9917372,85.9221798 C39.0653668,86.3786863 38.6389624,86.847651 38.0301023,86.9722337 C37.4313562,87.092367 36.8771114,86.8107212 36.8006499,86.3582192 C36.7262112,85.8901443 37.1607067,85.4216245 37.7582392,85.3001564 C38.368313,85.1835826 38.914062,85.4581094 38.9917372,85.9221798" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,11 @@
<?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>google-webdev</title>
<g id="google-webdev" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="google-webdev-logo" transform="translate(17.000000, 25.000000)" fill-rule="nonzero">
<path d="M93.8463619,66.1884002 L58.0362274,66.1884002 C57.4587141,66.1884002 56.89844,66.3823412 56.445911,66.7443645 L29.7596247,87.9615136 C29.0700566,88.5088583 29.4579387,89.6121672 30.3414478,89.6121672 L94.255793,89.6121672 C100.573961,89.6121672 106.254278,83.9404695 105.810369,77.1180549 C105.405247,70.8947026 100.104193,66.1884002 93.8463619,66.1884002" id="Path" fill="#00C9DB"></path>
<path d="M64.8758812,44.0403341 C64.8758812,44.0101655 64.8715714,43.9799969 64.8672616,43.9455185 C64.6690108,40.7476463 63.1648903,37.64028 60.4324767,35.5069286 L18.8859974,2.47661565 C13.8133619,-1.48840104 6.47377125,-0.596272281 2.49151535,4.4634338 C-1.49074054,9.52744968 -0.602921586,16.8411816 4.4654041,20.8061983 L34.5866015,44.7514512 L4.4654041,68.6967042 C-0.602921586,72.6574111 -1.49074054,79.9754528 2.49151535,85.0351588 C6.47377125,90.0948649 13.8133619,90.9869937 18.8859974,87.021977 L60.4367865,53.9873542 C63.1692001,51.8540029 64.6733206,48.7466365 64.8715714,45.5487644 C64.8758812,45.5185958 64.8758812,45.4884272 64.880191,45.4539487 C64.8931204,45.2169097 64.8974302,44.9841805 64.8974302,44.7471414 C64.8974302,44.5144122 64.8888106,44.2773731 64.8758812,44.0403341" id="Path" fill="#0D55FF"></path>
<path d="M106,77.8981288 C106,84.3671397 100.746353,89.6078574 94.255793,89.6078574 C87.7738526,89.6078574 82.5202055,84.3628299 82.5202055,77.8981288 C82.5202055,71.4334277 87.7738526,66.1884002 94.255793,66.1884002 C100.746353,66.1884002 106,71.4334277 106,77.8981288" id="Path" fill="#7000F2"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="44px" height="46px" viewBox="0 0 44 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>icon-bulb</title>
<defs>
<linearGradient x1="0%" y1="50%" x2="100%" y2="50%" id="linearGradient-1">
<stop stop-color="#3A416F" offset="0%"></stop>
<stop stop-color="#141727" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0%" y1="50%" x2="100%" y2="50%" id="linearGradient-2">
<stop stop-color="#3A416F" offset="0%"></stop>
<stop stop-color="#141727" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0%" y1="50%" x2="100%" y2="50%" id="linearGradient-3">
<stop stop-color="#3A416F" offset="0%"></stop>
<stop stop-color="#141727" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0%" y1="50%" x2="100%" y2="50%" id="linearGradient-4">
<stop stop-color="#3A416F" offset="0%"></stop>
<stop stop-color="#141727" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0%" y1="50%" x2="100%" y2="50%" id="linearGradient-5">
<stop stop-color="#3A416F" offset="0%"></stop>
<stop stop-color="#141727" offset="100%"></stop>
</linearGradient>
</defs>
<g id="icon-bulb" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="bulb-63" transform="translate(0.000000, 2.000000)" fill-rule="nonzero">
<path d="M4.7826087,22.9565217 L0.956521739,22.9565217 C0.428521739,22.9565217 0,22.5289565 0,22 C0,21.4710435 0.428521739,21.0434783 0.956521739,21.0434783 L4.7826087,21.0434783 C5.3106087,21.0434783 5.73913043,21.4710435 5.73913043,22 C5.73913043,22.5289565 5.3106087,22.9565217 4.7826087,22.9565217 Z" id="Path" fill="url(#linearGradient-1)" opacity="0.498432958"></path>
<path d="M9.8253913,10.781913 C9.58052174,10.781913 9.33565217,10.6881739 9.14913043,10.5016522 L6.44408696,7.79565217 C6.07008696,7.42165217 6.07008696,6.81713043 6.44408696,6.44313043 C6.81808696,6.06913043 7.4226087,6.06913043 7.7966087,6.44313043 L10.5016522,9.14913043 C10.8756522,9.52313043 10.8756522,10.1276522 10.5016522,10.5016522 C10.3151304,10.6891304 10.0702609,10.781913 9.8253913,10.781913 Z" id="Path" fill="url(#linearGradient-2)" opacity="0.498432958"></path>
<path d="M22,5.73913043 C21.472,5.73913043 21.0434783,5.31156522 21.0434783,4.7826087 L21.0434783,0.956521739 C21.0434783,0.427565217 21.472,0 22,0 C22.528,0 22.9565217,0.427565217 22.9565217,0.956521739 L22.9565217,4.7826087 C22.9565217,5.31156522 22.528,5.73913043 22,5.73913043 Z" id="Path" fill="url(#linearGradient-3)" opacity="0.498432958"></path>
<path d="M34.1746087,10.781913 C33.9297391,10.781913 33.6848696,10.6881739 33.4983478,10.5016522 C33.1243478,10.1276522 33.1243478,9.52313043 33.4983478,9.14913043 L36.2033913,6.44313043 C36.5764348,6.06913043 37.181913,6.06913043 37.555913,6.44313043 C37.929913,6.81713043 37.929913,7.42165217 37.555913,7.79565217 L34.8508696,10.5016522 C34.6643478,10.6891304 34.4194783,10.781913 34.1746087,10.781913 Z" id="Path" fill="url(#linearGradient-2)" opacity="0.498432958"></path>
<path d="M43.0434783,22.9565217 L39.2173913,22.9565217 C38.6893913,22.9565217 38.2608696,22.5289565 38.2608696,22 C38.2608696,21.4710435 38.6893913,21.0434783 39.2173913,21.0434783 L43.0434783,21.0434783 C43.5714783,21.0434783 44,21.4710435 44,22 C44,22.5289565 43.5714783,22.9565217 43.0434783,22.9565217 Z" id="Path" fill="url(#linearGradient-1)" opacity="0.500931141"></path>
<path d="M22,9.56521739 C15.1436522,9.56521739 9.56521739,15.1436522 9.56521739,22 C9.56521739,27.0217391 12.6136522,31.546087 17.2173913,33.4667826 L17.2173913,39.2173913 L26.7826087,39.2173913 L26.7826087,33.4667826 C31.3863478,31.546087 34.4347826,27.0217391 34.4347826,22 C34.4347826,15.1436522 28.8563478,9.56521739 22,9.56521739 Z" id="Path" fill="url(#linearGradient-4)"></path>
<path d="M20.0869565,44 L23.9130435,44 C25.498,44 26.7826087,42.7153913 26.7826087,41.1304348 L17.2173913,41.1304348 C17.2173913,42.7153913 18.502,44 20.0869565,44 Z" id="Path" fill="url(#linearGradient-5)"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="177px" height="150px" viewBox="0 0 177 150" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logos</title>
<defs>
<radialGradient cx="50.0135415%" cy="54.714503%" fx="50.0135415%" fy="54.714503%" r="71.9292757%" gradientTransform="translate(0.500135,0.547145),scale(0.921739,1.000000),translate(-0.500135,-0.547145)" id="radialGradient-1">
<stop stop-color="#FFB900" offset="0%"></stop>
<stop stop-color="#F95D8F" offset="60%"></stop>
<stop stop-color="#F95353" offset="99.9%"></stop>
</radialGradient>
</defs>
<g id="Logos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="asana-logo" transform="translate(31.000000, 22.000000)" fill="url(#radialGradient-1)" fill-rule="nonzero">
<path d="M89.9906921,56.1170331 C76.175985,56.1170331 64.97895,67.282887 64.97895,81.056696 C64.97895,94.8318401 76.175985,106 89.9906921,106 C103.803087,106 115,94.8318401 115,81.056696 C115,67.282887 103.803087,56.1170331 89.9906921,56.1170331 Z M25.010525,56.1182468 C11.1981304,56.1194604 0,67.282887 0,81.0579097 C0,94.8318401 11.1981304,105.998786 25.010525,105.998786 C38.824015,105.998786 50.0222671,94.8318401 50.0222671,81.0579097 C50.0222671,67.2827656 38.824015,56.1182468 25.0093079,56.1182468 L25.010525,56.1182468 L25.010525,56.1182468 Z M82.5105859,24.9408766 C82.5105859,38.714807 71.3136726,49.8841806 57.5012779,49.8841806 C43.6865709,49.8841806 32.4895359,38.714807 32.4895359,24.9408766 C32.4895359,11.1669462 43.6865709,0 57.5011562,0 C71.3135508,0 82.5093688,11.1669462 82.5093688,24.9408766 L82.5105859,24.9408766 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="177px" height="150px" viewBox="0 0 177 150" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logos</title>
<defs>
<linearGradient x1="80.5250892%" y1="15.6897142%" x2="43.803046%" y2="97.3483651%" id="linearGradient-1">
<stop stop-color="#0052CC" offset="0%"></stop>
<stop stop-color="#2684FF" offset="92%"></stop>
</linearGradient>
</defs>
<g id="Logos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="atlassian-1" transform="translate(35.000000, 21.000000)" fill-rule="nonzero">
<path d="M32.2177437,50.2228454 C31.1791646,48.8591721 29.2398559,48.5964284 27.8794818,49.635084 C27.4950585,49.9399792 27.1849667,50.3291598 26.9728674,50.7729298 L0.336388365,104.364716 C-0.449192603,105.946205 0.18801967,107.868741 1.75999765,108.659896 C2.20126182,108.88437 2.68910685,109 3.18360694,108.998989 L40.2948533,108.998989 C41.5095132,109.030231 42.627261,108.335051 43.1420719,107.228169 C51.1517473,90.5899995 46.2964903,65.2936515 32.2177437,50.2228454 Z" id="Path" fill="url(#linearGradient-1)"></path>
<path d="M51.4733618,1.73446471 C37.8211056,22.5862851 36.24436,49.0339597 47.3237511,71.3383072 L65.4063614,107.233778 C65.9512026,108.315801 67.0651049,109 68.2838287,109 L105.781771,109 C106.635913,109.001993 107.455652,108.666103 108.059623,108.066637 C108.663594,107.467171 109.002008,106.653546 109,105.805775 C109,105.310759 108.886342,104.822253 108.666811,104.377773 L56.948122,1.71943311 C56.4470574,0.669442923 55.3812876,0 54.2107419,0 C53.0401961,0 51.9744264,0.669442923 51.4733618,1.71943311 L51.4733618,1.73446471 Z" id="Path" fill="#2684FF"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="177px" height="150px" viewBox="0 0 177 150" 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="invision" transform="translate(36.000000, 22.000000)" fill-rule="nonzero">
<path d="M96.3908516,0 L9.60914844,0 C4.30210938,0 0,4.30210938 0,9.60914844 L0,96.3908516 C0,101.697891 4.30210938,106 9.60914844,106 L96.3908516,106 C101.697891,106 106,101.697891 106,96.3908516 L106,9.60914844 C106,4.30210938 101.697891,0 96.3908516,0" id="Path" fill="#DC395F"></path>
<path d="M35.8726639,33.7452625 C39.3481912,33.7452625 42.2597111,30.9866909 42.2597111,27.3711837 C42.2597111,23.758158 39.3481912,21 35.8726639,21 C32.3971366,21 29.4864335,23.758158 29.4864335,27.3711837 C29.4864335,30.9862773 32.3971366,33.7452625 35.8726639,33.7452625 M22.6297204,67.8737974 C22.2543961,69.4908911 22.0661214,71.2370214 22.0661214,72.6613873 C22.0661214,78.2740672 25.0715744,82 31.4586217,82 C36.7556357,82 41.0500152,78.8146149 44.1420502,73.6709335 L42.2539935,81.3440638 L52.7724666,81.3440638 L58.7841895,56.9267762 C60.2871203,50.744598 63.1986402,47.535639 67.6134992,47.535639 C71.0886182,47.535639 73.2486727,49.7242988 73.2486727,53.3373245 C73.2486727,54.3840928 73.1547395,55.52433 72.7790068,56.7613446 L69.6792122,67.9846366 C69.2095463,69.6017303 69.0224968,71.2196511 69.0224968,72.7403809 C69.0224968,78.0685185 72.1210662,81.9656729 78.6020466,81.9656729 C84.1436953,81.9656729 88.5577376,78.3530608 91,69.697267 L86.8677573,68.0818276 C84.8012275,73.8806181 83.0164973,74.9290407 81.6074997,74.9290407 C80.1985021,74.9290407 79.4470368,73.9786363 79.4470368,72.0774138 C79.4470368,71.2213054 79.6357199,70.2713146 79.9167026,69.1273552 L82.9229725,58.1931549 C83.6740295,55.6256568 83.9562374,53.3489047 83.9562374,51.2570224 C83.9562374,43.0772579 79.0717125,38.808296 73.1547395,38.808296 C67.6134992,38.808296 61.977509,43.8700887 59.1603306,49.1973992 L61.2260436,39.6354539 L45.1659218,39.6354539 L42.9115257,48.0575756 L50.4257709,48.0575756 L45.7985412,66.8175168 C42.1649611,74.9972812 35.4903967,75.1300401 34.6527579,74.9402073 C33.2772495,74.6258873 32.397545,74.0969199 32.397545,72.2870984 C32.397545,71.2428115 32.5854113,69.7427607 33.0550772,67.9341799 L40.100065,39.6354539 L22.2543961,39.6354539 L20,48.0575756 L27.4194953,48.0575756 L22.6301288,67.8737974" id="Shape" fill="#FFFFFF"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="177px" height="150px" viewBox="0 0 177 150" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logos</title>
<defs>
<linearGradient x1="67.9273487%" y1="40.3110476%" x2="40.6754218%" y2="81.641044%" id="linearGradient-1">
<stop stop-color="#0052CC" offset="18%"></stop>
<stop stop-color="#2684FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="32.3045012%" y1="-23.3247078%" x2="59.5183684%" y2="17.8549121%" id="linearGradient-2">
<stop stop-color="#0052CC" offset="18%"></stop>
<stop stop-color="#2684FF" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Logos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="jira-3" transform="translate(35.000000, 21.000000)" fill-rule="nonzero">
<path d="M105.705696,51.9209821 L58.1065074,4.58660714 L53.4942403,0 L17.6627523,35.6321429 L1.27784635,51.9209821 C-0.425948784,53.6240313 -0.425948784,56.3759687 1.27784635,58.0790179 L34.0130909,90.6321429 L53.4942403,110 L89.3207902,74.3678571 L89.8788054,73.8178571 L105.705696,58.1035714 C106.534097,57.2858004 107,56.173045 107,55.0122768 C107,53.8515086 106.534097,52.7387531 105.705696,51.9209821 Z M53.4942403,71.2642857 L37.1389635,55 L53.4942403,38.7357143 L69.8445789,55 L53.4942403,71.2642857 Z" id="Shape" fill="#2684FF"></path>
<path d="M53,38.6445299 C42.2639805,27.9872611 42.2108919,10.7221586 52.8811718,0 L17,35.6113693 L36.5274378,55 L53,38.6445299 Z" id="Path" fill="url(#linearGradient-1)"></path>
<path d="M69.446952,55 L53,71.2982957 C63.7858387,81.9863246 63.7858387,99.3119711 53,110 L89,74.3557598 L69.446952,55 Z" id="Path" fill="url(#linearGradient-2)"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="177px" height="150px" viewBox="0 0 177 150" 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="slack-new-logo" transform="translate(35.000000, 22.000000)" fill-rule="nonzero">
<path d="M39.2803996,-1.77635684e-15 C33.3581411,0.00436871997 28.5651823,4.80122324 28.5695565,10.7121014 C28.5651823,16.6229795 33.3625182,21.419834 39.2847767,21.4242027 L49.999997,21.4242027 L49.999997,10.7164701 C50.0043711,4.80559196 45.2070353,0.00873743993 39.2803996,-1.77635684e-15 C39.2847767,-1.77635684e-15 39.2847767,-1.77635684e-15 39.2803996,-1.77635684e-15 M39.2803996,28.5714286 L10.7152412,28.5714286 C4.7929826,28.5757973 -0.00435323957,33.3726518 1.19295144e-05,39.2835299 C-0.0087303708,45.194408 4.78860547,49.9912626 10.710864,50 L39.2803996,50 C45.2026582,49.9956313 49.999994,45.1987768 49.9956199,39.2878986 C49.999994,33.3726518 45.2026582,28.5757973 39.2803996,28.5714286 Z" id="Shape" fill="#36C5F0"></path>
<path d="M106.999997,39.2835299 C107.004372,33.3726518 102.206616,28.5757973 96.2838386,28.5714286 C90.3610616,28.5757973 85.5633057,33.3726518 85.5676802,39.2835299 L85.5676802,50 L96.2838386,50 C102.206616,49.9956313 107.004372,45.1987768 106.999997,39.2835299 Z M78.4279602,39.2835299 L78.4279602,10.7121014 C78.4323347,4.80559196 73.6389563,0.00873743993 67.7161793,7.10542736e-15 C61.7934022,0.00436871997 56.9956464,4.80122324 57.0000119,10.7121014 L57.0000119,39.2835299 C56.9912689,45.194408 61.7890247,49.9912626 67.7118018,50 C73.6345788,49.9956313 78.4323347,45.1987768 78.4279602,39.2835299 Z" id="Shape" fill="#2EB67D"></path>
<path d="M67.7152233,107 C73.6374818,106.995632 78.4348177,102.199196 78.4304435,96.2888345 C78.4348177,90.3784728 73.6374818,85.5820374 67.7152233,85.5776691 L57.000003,85.5776691 L57.000003,96.2888345 C56.9956289,102.194828 61.7929647,106.991263 67.7152233,107 Z M67.7152233,78.4266993 L96.2847588,78.4266993 C102.207017,78.4223309 107.004353,73.6258955 106.999988,67.7155338 C107.00873,61.8051721 102.211395,57.0087367 96.289136,57 L67.7196004,57 C61.7973418,57.0043683 57.000006,61.8008038 57.0043801,67.7111655 C57.000006,73.6258955 61.7929647,78.4223309 67.7152233,78.4266993 L67.7152233,78.4266993 Z" id="Shape" fill="#ECB22E"></path>
<path d="M2.98908926e-06,67.7121014 C-0.00437153219,73.6229795 4.79338431,78.419834 10.7161614,78.4242027 C16.6389384,78.419834 21.4366943,73.6229795 21.4323198,67.7121014 L21.4323198,57 L10.7161614,57 C4.79338431,57.0043687 -0.00437153219,61.8012232 2.98908926e-06,67.7121014 Z M28.5720309,67.7121014 L28.5720309,96.2835299 C28.5632878,102.194408 33.3610437,106.991263 39.2838207,107 C45.2065978,106.995631 50.0043536,102.198777 49.9999881,96.2878986 L49.9999881,67.7208388 C50.0087311,61.8099607 45.2109753,57.0131062 39.2881982,57.0043687 C33.3610437,57.0043687 28.5676653,61.8012232 28.5720309,67.7121014 C28.5720309,67.7121014 28.5720309,67.7164701 28.5720309,67.7121014" id="Shape" fill="#E01E5A"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="177px" height="150px" viewBox="0 0 177 150" 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="spotify-2" transform="translate(36.000000, 22.000000)" fill="#2EBD59" fill-rule="nonzero">
<path d="M53.5,0 C23.9517912,0 0,23.9517912 0,53.5 C0,83.0482088 23.9517912,107 53.5,107 C83.0482088,107 107,83.0482088 107,53.5 C107,23.9554418 83.0482088,0.00365063118 53.5,0 Z M78.0358922,77.1597407 C77.0757762,78.7368134 75.0204708,79.2296486 73.4506994,78.2695326 C60.8888775,70.5922552 45.0743432,68.8582054 26.4524736,73.1111907 C24.656363,73.523712 22.8675537,72.3993176 22.458683,70.6032071 C22.0461617,68.8070966 23.1669055,67.0182873 24.9666667,66.6094166 C45.3444899,61.9548618 62.8273627,63.9590583 76.9297509,72.5745479 C78.4995223,73.5419652 78.9996588,75.5899693 78.0358922,77.1597407 L78.0358922,77.1597407 Z M84.5814739,62.5973729 C83.373115,64.5614125 80.8030706,65.1747185 78.8426817,63.9700102 C64.4664961,55.1318321 42.5408052,52.5727397 25.5325145,57.7347322 C23.3275333,58.4027977 20.9984306,57.1579324 20.3267144,54.9566018 C19.6622996,52.7516206 20.9071648,50.4261685 23.1084954,49.7544524 C42.5371546,43.858683 66.6933811,46.7134766 83.2051859,56.8622313 C85.1692255,58.0705902 85.7898328,60.636984 84.5814739,62.5973729 Z M85.1436711,47.4253497 C67.8980894,37.1853292 39.4523712,36.2434664 22.9880246,41.2375299 C20.3449676,42.0406687 17.5485841,40.5475606 16.7490959,37.9045036 C15.9496076,35.2614466 17.4390652,32.4650631 20.0857728,31.6619243 C38.9850904,25.9267827 70.3987718,27.0329239 90.2509041,38.8171614 C92.627465,40.2299556 93.4087001,43.3001365 91.9995565,45.6730467 C90.5940635,48.0532583 87.5165814,48.838144 85.1436711,47.4253497 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="177px" height="150px" viewBox="0 0 177 150" 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="adobe-xd-1" transform="translate(35.000000, 23.000000)" fill-rule="nonzero">
<path d="M88.87536,105 L19.12464,105 C8.54928,105 0,96.4750769 0,85.9298462 L0,19.0701538 C0,8.52492308 8.54928,0 19.12464,0 L88.87536,0 C99.45072,0 108,8.52492308 108,19.0701538 L108,85.9298462 C108,96.4750769 99.45072,105 88.87536,105 Z" id="Path" fill="#FF26BE"></path>
<path d="M85.8661662,101 L20.1764293,101 C11.2646329,101 4,93.7634471 4,84.8860975 L4,22.1139025 C4,13.2365529 11.2646329,6 20.1764293,6 L85.8233827,6 C94.7822409,6 102,13.2365529 102,22.1139025 L102,84.8434794 C102.042595,93.7634471 94.7779626,101 85.8661662,101 L85.8661662,101 Z" id="Path" fill="#2E001F"></path>
<path d="M42.8937592,49.0345674 L55.6917204,73.6219562 C55.9198637,73.9861163 55.7838552,74.3546639 55.4153161,74.3546639 L47.4610124,74.3546639 C46.9564647,74.3546639 46.7283214,74.2186523 46.5001781,73.7623553 C43.5738017,67.7295823 40.6035515,61.6968094 37.5411665,55.2077394 L37.4490317,55.2077394 C34.7069249,61.3326493 31.6884136,67.7778445 28.7664245,73.8106174 C28.5382812,74.1747775 28.3101379,74.3151766 27.9415987,74.3151766 L20.4084827,74.3151766 C19.9521961,74.3151766 19.903935,73.9510165 20.1364657,73.6746058 L32.6580226,49.8155373 L20.5488786,25.6844455 C20.2724742,25.3202854 20.5488786,25.0438747 20.8208956,25.0438747 L28.6830644,25.0438747 C29.139351,25.0438747 29.3236206,25.1360116 29.5035028,25.5484339 C32.3816182,31.5812068 35.3079947,37.7982536 38.0501016,43.8749012 L38.1422364,43.8749012 C40.7922085,37.8421283 43.7185849,31.5812068 46.5528266,25.5923086 C46.7809699,25.2281485 46.9169784,25 47.3776523,25 L54.7352735,25 C55.0994253,25 55.2398212,25.2764107 55.0116779,25.6405708 L42.8937592,49.0345674 Z M57.933665,56.4844935 C57.933665,45.8361009 65.0192712,37.5174554 76.2597158,37.5174554 C77.22055,37.5174554 77.7207103,37.5174554 78.6376708,37.6095923 L78.6376708,25.5001717 C78.6376708,25.223761 78.8658141,25.0438747 79.0939574,25.0438747 L86.3155701,25.0438747 C86.6797219,25.0438747 86.7718567,25.1798863 86.7718567,25.4080348 L86.7718567,67.3654222 C86.7718567,68.5983016 86.7718567,70.1514664 87,71.8450303 C87,72.121441 86.9078652,72.2091904 86.6358482,72.3495895 C82.7968986,74.179165 78.7736793,74.999812 74.9347297,74.999812 C65.0148838,75.0391094 57.9292796,68.918587 57.933665,56.4844935 L57.933665,56.4844935 Z M78.6332835,44.7874952 C77.9927273,44.5110846 77.0801541,44.3311982 75.9833114,44.3311982 C70.2226932,44.3311982 66.2038614,48.7625442 66.2038614,56.1247209 C66.2038614,64.5355033 70.3192154,67.9182436 75.4831511,67.9182436 C76.5799938,67.9182436 77.7689714,67.782232 78.6376708,67.4136844 L78.6376708,44.7874952 L78.6332835,44.7874952 Z" id="Shape" fill="#FFD9F2"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

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