inital check in
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
|
||||||
|
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
|
||||||
|
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
REACT_APP_BACKEND_REST_API_BASE_URL=https://chatbackend.aimloperations.com/api/
|
||||||
|
REACT_APP_BACKEND_WS_API_BASE_URL=wss://chatbackend.aimloperations.com/ws/chat_again/
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Getting Started with Create React App
|
||||||
|
|
||||||
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.\
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.\
|
||||||
|
You will also see any lint errors in the console.
|
||||||
|
|
||||||
|
### `npm test`
|
||||||
|
|
||||||
|
Launches the test runner in the interactive watch mode.\
|
||||||
|
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `build` folder.\
|
||||||
|
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.\
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||||
|
|
||||||
|
### `npm run eject`
|
||||||
|
|
||||||
|
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||||
|
|
||||||
|
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||||
|
|
||||||
|
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||||
|
|
||||||
|
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||||
|
|
||||||
|
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"name": "llm-fe",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/cache": "^11.14.0",
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@mui/icons-material": "^5.16.11",
|
||||||
|
"@mui/material": "^5.16.11",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@testing-library/react": "^16.1.0",
|
||||||
|
"@testing-library/user-event": "^14.5.2",
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"babel-loader": "^9.2.1",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"chroma-js": "^3.1.2",
|
||||||
|
"formik": "^2.4.6",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"markdown-to-jsx": "^7.7.2",
|
||||||
|
"mini.css": "^3.0.1",
|
||||||
|
"react-bootstrap": "^2.10.6",
|
||||||
|
"react-code-blocks": "^0.1.6",
|
||||||
|
"react-github-btn": "^1.4.0",
|
||||||
|
"react-router-dom": "^6.28.0",
|
||||||
|
"react-scripts": "^5.0.1",
|
||||||
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
|
"recharts": "^2.15.1",
|
||||||
|
"styled-components": "^6.1.14",
|
||||||
|
"stylis-plugin-rtl": "^2.1.1",
|
||||||
|
"web-vitals": "^4.2.4",
|
||||||
|
"webpack": "^5.97.1",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"yup": "^1.5.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "NODE_ENV=development react-scripts start",
|
||||||
|
"build": "NODE_ENV=production react-scripts build",
|
||||||
|
"test": "NODE_ENV=development react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bootstrap": "~5.2.10",
|
||||||
|
"@types/lodash": "~4.17.13",
|
||||||
|
"@types/react": "^18.3.16",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>Chat by AI ML Operations. LCC</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"short_name": "Chat by AI ML Operations. LLC",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
scp -r ./* westfarn@10.0.0.103:/var/www/chat.aimloperations/html
|
||||||
@@ -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;
|
||||||
|
} */
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
After Width: | Height: | Size: 324 KiB |
@@ -0,0 +1,42 @@
|
|||||||
|
/*!
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
* Material Dashboard 2 - v3.1.0
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* Product Page: https://www.creative-tim.com/product/material-dashboard
|
||||||
|
* Copyright 2023 Creative Tim (https://www.creative-tim.com)
|
||||||
|
* Licensed under MIT (https://github.com/creativetimofficial/material-dashboard/blob/master/LICENSE.md)
|
||||||
|
|
||||||
|
* Coded by www.creative-tim.com
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
* Bootstrap v5.2.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2022 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2022 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/:root{--bs-blue:#63b3ed;--bs-indigo:#596cff;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#f56565;--bs-orange:#fd7e14;--bs-yellow:#fbd38d;--bs-green:#81e6d9;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#f0f2f5;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#e91e63;--bs-secondary:#7b809a;--bs-success:#4caf50;--bs-info:#1a73e8;--bs-warning:#fb8c00;--bs-danger:#f44335;--bs-light:#f0f2f5;--bs-dark:#344767;--bs-white:#fff;--bs-dark-blue:#1a237e;--bs-primary-rgb:233,30,99;--bs-secondary-rgb:,128,154;--bs-success-rgb:76,175,80;--bs-info-rgb:26,115,232;--bs-warning-rgb:251,140,0;--bs-danger-rgb:244,67,53;--bs-light-rgb:240,242,245;--bs-dark-rgb:52,71,103;--bs-dark-blue-rgb:26,35,126;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:,128,154;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:"Roboto",Helvetica,Arial,sans-serif;--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg,hsla(0,0%,100%,.15),hsla(0,0%,100%,0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#7b809a;--bs-body-bg:#fff;--bs-border-color:#dee2e6;border:1px solid rgba(0,0,0,.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.125rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:0.75rem;--bs-border-radius-2xl:1rem;--bs-border-radius-pill:50rem;--bs-link-color:#e91e63;--bs-link-hover-color:#e91e63;--bs-code-color:#d63384;--bs-highlight-bg:#fcf8e3}*,:after,:before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:#344767}.h1,h1{font-size:calc(1.425rem + 2.1vw)}@media (min-width:1200px){.h1,h1{font-size:3rem}}.h2,h2{font-size:calc(1.35rem + 1.2vw)}@media (min-width:1200px){.h2,h2{font-size:2.25rem}}.h3,h3{font-size:calc(1.3125rem + .75vw)}@media (min-width:1200px){.h3,h3{font-size:1.875rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}p{margin-top:0;margin-bottom:1rem}abbr[title]{text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:600}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:700}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:var(--bs-link-color)}a,a:hover{text-decoration:none}a:hover{color:var(--bs-link-hover-color)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.125rem}kbd kbd{padding:0;font-size:1em;font-weight:600}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;borLine truncated
|
||||||
|
|
||||||
|
/*!
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
* Material Dashboard 2 - v3.1.0
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* Product Page: https://www.creative-tim.com/product/material-dashboard
|
||||||
|
* Copyright 2023 Creative Tim (https://www.creative-tim.com)
|
||||||
|
* Licensed under MIT (site.license)
|
||||||
|
|
||||||
|
* Coded by www.creative-tim.com
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
*/.alert-primary{background-image:linear-gradient(195deg,#ec407a,#d81b60)}.alert-secondary{background-image:linear-gradient(195deg,#747b8a,#495361)}.alert-success{background-image:linear-gradient(195deg,#66bb6a,#43a047)}.alert-info{background-image:linear-gradient(195deg,#49a3f1,#1a73e8)}.alert-warning{background-image:linear-gradient(195deg,#ffa726,#fb8c00)}.alert-danger{background-image:linear-gradient(195deg,#ef5350,#e53935)}.alert-light{background-image:linear-gradient(195deg,#ebeff4,#ced4da)}.alert-dark{background-image:linear-gradient(195deg,#42424a,#191919)}.btn-close:focus{box-shadow:none}.alert-dismissible .btn-close{background-image:none}.avatar{color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:1rem;border-radius:50rem;height:48px;width:48px;transition:all .2s ease-in-out}.avatar img{width:100%}.avatar+.avatar-content{display:inline-block;margin-left:.75rem}.avatar.avatar-raised{margin-top:-24px}.active .avatar.avatar-scale-up,.avatar.avatar-scale-up:hover{transform:scale(1.2)}.avatar-xxl{width:110px!important;height:110px!important}.avatar-xxl.avatar-raised{margin-top:-55px}.avatar-xl{width:74px!important;height:74px!important}.avatar-xl.avatar-raised{margin-top:-37px}.avatar-lg{width:58px!important;height:58px!important;font-size:.875rem}.avatar-lg.avatar-raised{margin-top:-29px}.avatar-sm{width:36px!important;height:36px!important;font-size:.875rem}.avatar-sm.avatar-raised{margin-top:-18px}.avatar-xs{width:24px!important;height:24px!important;font-size:.75rem}.avatar-xs.avatar-raised{margin-top:-12px}.avatar-group .avatar{position:relative;z-index:2;border:2px solid #fff}.avatar-group .avatar:hover{z-index:3}.avatar-group .avatar+.avatar{margin-left:-1rem}.badge.bg-primary{background:#e91e63}.badge.bg-secondary{background:#7b809a}.badge.bg-success{background:#4caf50}.badge.bg-info{background:#1a73e8}.badge.bg-warning{background:#fb8c00}.badge.bg-danger{background:#f44335}.badge.bg-light{background:#f0f2f5}.badge.bg-dark{background:#344767}.badge.bg-white{background:#fff}.badge.bg-dark-blue{background:#1a237e}.badge,.btn{text-transform:uppercase}.btn{margin-bottom:1rem;letter-spacing:0;background-size:150%;background-position-x:25%;position:relative;overflow:hidden;-webkit-appearance:none}.btn:not([class*=btn-outline-]){border:0}.btn:active,.btn:active:focus,.btn:active:hover{box-shadow:0 3px 5px -1px rgba(0,0,0,.09),0 2px 3px -1px rgba(0,0,0,.07);transform:none;opacity:.85}.btn.bg-white:hover{color:#7b809a}.btn.btn-link{font-weight:700}.btn.btn-link,.btn.btn-link:focus,.btn.btn-link:hover{box-shadow:none}.btn.btn-round{border-radius:1.875rem}.btn.btn-icon-only{width:2.375rem;height:2.375rem;padding:.7rem}.btn-group-sm>.btn.btn-icon-only,.btn.btn-sm.btn-icon-only{width:1.5rem;height:1.5rem;padding:.3rem}.btn-group-sm>.btn i,.btn.btn-sm i{font-size:.5rem}.btn-group-lg>.btn.btn-icon-only,.btn.btn-lg.btn-icon-only{width:3.25rem;height:3.25rem;padding:1rem}.btn-group-lg>.btn i,.btn.btn-lg i{font-size:1.2rem;position:relative;top:0}.btn.btn-rounded{border-radius:1.875rem}.btn .material-icons{vertical-align:middle;margin-top:-1px;margin-bottom:-1px;font-size:1.1rem;display:inline-block;top:0}.btn-check:checked+.btn svg .color-background{fill:#fff}.btn-check:checked+.btn:hover svg .color-background{fill:#344767}.icon-move-right i{transition:all .2s cubic-bezier(.34,1.61,.7,1.3)}.icon-move-right:focus i,.icon-move-right:hover i{transform:translateX(5px)}.icon-move-left i{transition:all .2s cubic-bezier(.34,1.61,.7,1.3)}.icon-move-left:focus i,.icon-move-left:hover i{transform:translateX(-5px)}.btn-primary,.btn.bg-gradient-primary{box-shadow:0 3px 3px 0 rgba(233,30,99,.15),0 3px 1px -2px rgba(233,30,99,.2),0 1px 5px 0 rgba(233,30,99,.15)}.btn-primary:hover,.btn.bg-gradient-primary:hover{background-color:#e91e63;border-color:#e91e63;box-shadow:0 14px 26px -12px rgba(233,30,99,.4),0 4px 23px 0 rgba(233,30,99,.15),0 8px 10px -5px rgba(233,30,99,.2)}.btn-primary .btn.bg-outline-primary,.btn.bg-gradient-primary .btn.bg-outline-primary{border:1px solid #e91e63}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.btn.bg-gradient-primary:not(:disabled):not(.disabled).active,.btn.bg-gradient-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle,.show>.btn.bg-gradient-primary.dropdown-toggle{color:color-yiq(#e91e63);background-color:#e91e63}.btn-primary.focus,.btn-primary:focus,.btn.bg-gradient-primary.focus,.btn.bg-gradient-primary:focus{color:#fff}.btn-outline-primary{box-shadow:none}.btn-outline-primary:hover:not(.active){background-color:transparent;opacity:.75;box-shadow:none;color:#e91e63}.btn-secondary,.btn.bg-gradient-secondary{box-shadow:0 3px 3px 0 rgba(,128,154,.15),0 3px 1px -2px rgba(,128,154,.2),0 1px 5px 0 rgba(,128,154,.15)}.btn-secondary:hover,.btn.bg-gradient-secondary:hover{background-color:#7b809a;border-color:#7b809a;box-shadow:0 14px 26px -12px rgba(,128,154,.4),0 4px 23px 0 rgba(,128,1Line truncated
|
||||||
|
|
||||||
|
/*! nouislider - 14.6.3 - 11/19/2020 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;box-sizing:border-box}.noUi-target{position:relative}.noUi-base,.noUi-connects{width:100%;height:2px;position:relative;z-index:1;top:0}.noUi-connects{z-index:0;overflow:hidden}.noUi-connect,.noUi-origin{will-change:transform;position:absolute;z-index:1;top:0;right:0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;-webkit-transform-style:preserve-3d;transform-origin:0 0;transform-style:flat}.noUi-connect{height:100%;width:100%;border-radius:.25rem}.noUi-origin{height:10%;width:10%}.noUi-txt-dir-rtl.noUi-horizontal .noUi-origin{left:0;right:auto}.noUi-vertical .noUi-origin{width:0}.noUi-horizontal .noUi-origin{height:0}.noUi-handle{backface-visibility:hidden;position:absolute}.noUi-touch-area{height:100%;width:100%}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{transition:transform .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-horizontal{height:2px}.noUi-horizontal .noUi-handle{border-radius:50%;background-color:#fff;box-shadow:0 1px 13px 0 rgba(0,0,0,.2);height:14px;width:14px;cursor:pointer;margin-top:-6px;outline:none;right:-10px}.noUi-vertical{width:3px}.noUi-vertical .noUi-handle{width:28px;height:34px;right:-6px;top:-17px}.noUi-txt-dir-rtl.noUi-horizontal .noUi-handle{left:-17px;right:auto}.noUi-target{background:#f0f2f5;border-radius:.25rem}.noUi-connects{border-radius:3px}.noUi-connect{background:#e91e63}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-resize}.noUi-handle{border:1px solid #e91e63;border-radius:3px;background:#fff;cursor:default;box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ebebeb,0 3px 6px -3px #bbb;webkit-transition:.3s ease 0s;-moz-transition:.3s ease 0s;-ms-transition:.3s ease 0s;-o-transform:.3s ease 0s;transition:.3s ease 0s}.noUi-active{box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ddd,0 3px 6px -3px #bbb;transform:scale3d(1.5,1.5,1)}[disabled] .noUi-connect{background:#b8b8b8}[disabled].noUi-handle,[disabled] .noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;white-space:nowrap;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#ccc}.noUi-marker-large,.noUi-marker-sub{background:#aaa}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{transform:translate(-50%,50%)}.noUi-rtl .noUi-value-horizontal{transform:translate(50%,50%)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{transform:translateY(-50%);padding-left:25px}.noUi-rtl .noUi-value-vertical{transform:translateY(50%)}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #d9d9d9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center;white-space:nowrap}.noUi-horizontal .noUi-tooltip{transform:translate(-50%);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{transform:translateY(-50%);top:50%;right:120%}.noUi-horizontal .noUi-origin>.noUi-tooltip{transform:translate(50%);left:auto;bottom:10px}.noUi-vertical .noUi-origin>.noUi-tooltip{transform:translateY(-18px);top:auto;right:28px}code[class*=language-],pre[class*=language-]{color:#000;background:none;text-shadow:0 1px #fff;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-]::selection,code[class*=language-] ::selection,pre[class*=language-]::selection,pre[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;overflow:auto;border-radius:.75rem}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f8f9fa}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,Line truncated
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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>© 2025 Chat | Developed by <a href='www.aimloperations.com'>AI ML Operations, LLC</a></MDTypography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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 }
|
||||||
@@ -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 }
|
||||||
@@ -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 }
|
||||||
@@ -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}
|
||||||
@@ -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 }
|
||||||
@@ -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 }
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*!
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
* Material Dashboard 2 - v3.1.0
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* Product Page: https://www.creative-tim.com/product/material-dashboard
|
||||||
|
* Copyright 2023 Creative Tim (https://www.creative-tim.com)
|
||||||
|
* Licensed under MIT (https://github.com/creativetimofficial/material-dashboard/blob/master/LICENSE.md)
|
||||||
|
|
||||||
|
* Coded by www.creative-tim.com
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
* Bootstrap v5.2.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2022 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2022 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/:root{--bs-blue:#63b3ed;--bs-indigo:#596cff;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#f56565;--bs-orange:#fd7e14;--bs-yellow:#fbd38d;--bs-green:#81e6d9;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#f0f2f5;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#e91e63;--bs-secondary:#7b809a;--bs-success:#4caf50;--bs-info:#1a73e8;--bs-warning:#fb8c00;--bs-danger:#f44335;--bs-light:#f0f2f5;--bs-dark:#344767;--bs-white:#fff;--bs-dark-blue:#1a237e;--bs-primary-rgb:233,30,99;--bs-secondary-rgb:,128,154;--bs-success-rgb:76,175,80;--bs-info-rgb:26,115,232;--bs-warning-rgb:251,140,0;--bs-danger-rgb:244,67,53;--bs-light-rgb:240,242,245;--bs-dark-rgb:52,71,103;--bs-dark-blue-rgb:26,35,126;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:,128,154;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:"Roboto",Helvetica,Arial,sans-serif;--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg,hsla(0,0%,100%,.15),hsla(0,0%,100%,0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#7b809a;--bs-body-bg:#fff;--bs-border-color:#dee2e6;border:1px solid rgba(0,0,0,.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.125rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:0.75rem;--bs-border-radius-2xl:1rem;--bs-border-radius-pill:50rem;--bs-link-color:#e91e63;--bs-link-hover-color:#e91e63;--bs-code-color:#d63384;--bs-highlight-bg:#fcf8e3}*,:after,:before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:#344767}.h1,h1{font-size:calc(1.425rem + 2.1vw)}@media (min-width:1200px){.h1,h1{font-size:3rem}}.h2,h2{font-size:calc(1.35rem + 1.2vw)}@media (min-width:1200px){.h2,h2{font-size:2.25rem}}.h3,h3{font-size:calc(1.3125rem + .75vw)}@media (min-width:1200px){.h3,h3{font-size:1.875rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}p{margin-top:0;margin-bottom:1rem}abbr[title]{text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:600}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:700}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:var(--bs-link-color)}a,a:hover{text-decoration:none}a:hover{color:var(--bs-link-hover-color)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.125rem}kbd kbd{padding:0;font-size:1em;font-weight:600}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;borLine truncated
|
||||||
|
|
||||||
|
/*!
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
* Material Dashboard 2 - v3.1.0
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* Product Page: https://www.creative-tim.com/product/material-dashboard
|
||||||
|
* Copyright 2023 Creative Tim (https://www.creative-tim.com)
|
||||||
|
* Licensed under MIT (site.license)
|
||||||
|
|
||||||
|
* Coded by www.creative-tim.com
|
||||||
|
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
*/.alert-primary{background-image:linear-gradient(195deg,#ec407a,#d81b60)}.alert-secondary{background-image:linear-gradient(195deg,#747b8a,#495361)}.alert-success{background-image:linear-gradient(195deg,#66bb6a,#43a047)}.alert-info{background-image:linear-gradient(195deg,#49a3f1,#1a73e8)}.alert-warning{background-image:linear-gradient(195deg,#ffa726,#fb8c00)}.alert-danger{background-image:linear-gradient(195deg,#ef5350,#e53935)}.alert-light{background-image:linear-gradient(195deg,#ebeff4,#ced4da)}.alert-dark{background-image:linear-gradient(195deg,#42424a,#191919)}.btn-close:focus{box-shadow:none}.alert-dismissible .btn-close{background-image:none}.avatar{color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:1rem;border-radius:50rem;height:48px;width:48px;transition:all .2s ease-in-out}.avatar img{width:100%}.avatar+.avatar-content{display:inline-block;margin-left:.75rem}.avatar.avatar-raised{margin-top:-24px}.active .avatar.avatar-scale-up,.avatar.avatar-scale-up:hover{transform:scale(1.2)}.avatar-xxl{width:110px!important;height:110px!important}.avatar-xxl.avatar-raised{margin-top:-55px}.avatar-xl{width:74px!important;height:74px!important}.avatar-xl.avatar-raised{margin-top:-37px}.avatar-lg{width:58px!important;height:58px!important;font-size:.875rem}.avatar-lg.avatar-raised{margin-top:-29px}.avatar-sm{width:36px!important;height:36px!important;font-size:.875rem}.avatar-sm.avatar-raised{margin-top:-18px}.avatar-xs{width:24px!important;height:24px!important;font-size:.75rem}.avatar-xs.avatar-raised{margin-top:-12px}.avatar-group .avatar{position:relative;z-index:2;border:2px solid #fff}.avatar-group .avatar:hover{z-index:3}.avatar-group .avatar+.avatar{margin-left:-1rem}.badge.bg-primary{background:#e91e63}.badge.bg-secondary{background:#7b809a}.badge.bg-success{background:#4caf50}.badge.bg-info{background:#1a73e8}.badge.bg-warning{background:#fb8c00}.badge.bg-danger{background:#f44335}.badge.bg-light{background:#f0f2f5}.badge.bg-dark{background:#344767}.badge.bg-white{background:#fff}.badge.bg-dark-blue{background:#1a237e}.badge,.btn{text-transform:uppercase}.btn{margin-bottom:1rem;letter-spacing:0;background-size:150%;background-position-x:25%;position:relative;overflow:hidden;-webkit-appearance:none}.btn:not([class*=btn-outline-]){border:0}.btn:active,.btn:active:focus,.btn:active:hover{box-shadow:0 3px 5px -1px rgba(0,0,0,.09),0 2px 3px -1px rgba(0,0,0,.07);transform:none;opacity:.85}.btn.bg-white:hover{color:#7b809a}.btn.btn-link{font-weight:700}.btn.btn-link,.btn.btn-link:focus,.btn.btn-link:hover{box-shadow:none}.btn.btn-round{border-radius:1.875rem}.btn.btn-icon-only{width:2.375rem;height:2.375rem;padding:.7rem}.btn-group-sm>.btn.btn-icon-only,.btn.btn-sm.btn-icon-only{width:1.5rem;height:1.5rem;padding:.3rem}.btn-group-sm>.btn i,.btn.btn-sm i{font-size:.5rem}.btn-group-lg>.btn.btn-icon-only,.btn.btn-lg.btn-icon-only{width:3.25rem;height:3.25rem;padding:1rem}.btn-group-lg>.btn i,.btn.btn-lg i{font-size:1.2rem;position:relative;top:0}.btn.btn-rounded{border-radius:1.875rem}.btn .material-icons{vertical-align:middle;margin-top:-1px;margin-bottom:-1px;font-size:1.1rem;display:inline-block;top:0}.btn-check:checked+.btn svg .color-background{fill:#fff}.btn-check:checked+.btn:hover svg .color-background{fill:#344767}.icon-move-right i{transition:all .2s cubic-bezier(.34,1.61,.7,1.3)}.icon-move-right:focus i,.icon-move-right:hover i{transform:translateX(5px)}.icon-move-left i{transition:all .2s cubic-bezier(.34,1.61,.7,1.3)}.icon-move-left:focus i,.icon-move-left:hover i{transform:translateX(-5px)}.btn-primary,.btn.bg-gradient-primary{box-shadow:0 3px 3px 0 rgba(233,30,99,.15),0 3px 1px -2px rgba(233,30,99,.2),0 1px 5px 0 rgba(233,30,99,.15)}.btn-primary:hover,.btn.bg-gradient-primary:hover{background-color:#e91e63;border-color:#e91e63;box-shadow:0 14px 26px -12px rgba(233,30,99,.4),0 4px 23px 0 rgba(233,30,99,.15),0 8px 10px -5px rgba(233,30,99,.2)}.btn-primary .btn.bg-outline-primary,.btn.bg-gradient-primary .btn.bg-outline-primary{border:1px solid #e91e63}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.btn.bg-gradient-primary:not(:disabled):not(.disabled).active,.btn.bg-gradient-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle,.show>.btn.bg-gradient-primary.dropdown-toggle{color:color-yiq(#e91e63);background-color:#e91e63}.btn-primary.focus,.btn-primary:focus,.btn.bg-gradient-primary.focus,.btn.bg-gradient-primary:focus{color:#fff}.btn-outline-primary{box-shadow:none}.btn-outline-primary:hover:not(.active){background-color:transparent;opacity:.75;box-shadow:none;color:#e91e63}.btn-secondary,.btn.bg-gradient-secondary{box-shadow:0 3px 3px 0 rgba(,128,154,.15),0 3px 1px -2px rgba(,128,154,.2),0 1px 5px 0 rgba(,128,154,.15)}.btn-secondary:hover,.btn.bg-gradient-secondary:hover{background-color:#7b809a;border-color:#7b809a;box-shadow:0 14px 26px -12px rgba(,128,154,.4),0 4px 23px 0 rgba(,128,1Line truncated
|
||||||
|
|
||||||
|
/*! nouislider - 14.6.3 - 11/19/2020 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;box-sizing:border-box}.noUi-target{position:relative}.noUi-base,.noUi-connects{width:100%;height:2px;position:relative;z-index:1;top:0}.noUi-connects{z-index:0;overflow:hidden}.noUi-connect,.noUi-origin{will-change:transform;position:absolute;z-index:1;top:0;right:0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;-webkit-transform-style:preserve-3d;transform-origin:0 0;transform-style:flat}.noUi-connect{height:100%;width:100%;border-radius:.25rem}.noUi-origin{height:10%;width:10%}.noUi-txt-dir-rtl.noUi-horizontal .noUi-origin{left:0;right:auto}.noUi-vertical .noUi-origin{width:0}.noUi-horizontal .noUi-origin{height:0}.noUi-handle{backface-visibility:hidden;position:absolute}.noUi-touch-area{height:100%;width:100%}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{transition:transform .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-horizontal{height:2px}.noUi-horizontal .noUi-handle{border-radius:50%;background-color:#fff;box-shadow:0 1px 13px 0 rgba(0,0,0,.2);height:14px;width:14px;cursor:pointer;margin-top:-6px;outline:none;right:-10px}.noUi-vertical{width:3px}.noUi-vertical .noUi-handle{width:28px;height:34px;right:-6px;top:-17px}.noUi-txt-dir-rtl.noUi-horizontal .noUi-handle{left:-17px;right:auto}.noUi-target{background:#f0f2f5;border-radius:.25rem}.noUi-connects{border-radius:3px}.noUi-connect{background:#e91e63}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-resize}.noUi-handle{border:1px solid #e91e63;border-radius:3px;background:#fff;cursor:default;box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ebebeb,0 3px 6px -3px #bbb;webkit-transition:.3s ease 0s;-moz-transition:.3s ease 0s;-ms-transition:.3s ease 0s;-o-transform:.3s ease 0s;transition:.3s ease 0s}.noUi-active{box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ddd,0 3px 6px -3px #bbb;transform:scale3d(1.5,1.5,1)}[disabled] .noUi-connect{background:#b8b8b8}[disabled].noUi-handle,[disabled] .noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;white-space:nowrap;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#ccc}.noUi-marker-large,.noUi-marker-sub{background:#aaa}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{transform:translate(-50%,50%)}.noUi-rtl .noUi-value-horizontal{transform:translate(50%,50%)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{transform:translateY(-50%);padding-left:25px}.noUi-rtl .noUi-value-vertical{transform:translateY(50%)}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #d9d9d9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center;white-space:nowrap}.noUi-horizontal .noUi-tooltip{transform:translate(-50%);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{transform:translateY(-50%);top:50%;right:120%}.noUi-horizontal .noUi-origin>.noUi-tooltip{transform:translate(50%);left:auto;bottom:10px}.noUi-vertical .noUi-origin>.noUi-tooltip{transform:translateY(-18px);top:auto;right:28px}code[class*=language-],pre[class*=language-]{color:#000;background:none;text-shadow:0 1px #fff;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-]::selection,code[class*=language-] ::selection,pre[class*=language-]::selection,pre[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;overflow:auto;border-radius:.75rem}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f8f9fa}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,Line truncated
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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*/
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 502 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 915 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 927 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="800px" height="400px" viewBox="0 0 800 400" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>pattern-2</title>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter-1">
|
||||||
|
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="pattern-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g filter="url(#filter-1)" id="1824-[Converted]">
|
||||||
|
<g transform="translate(0.000000, -63.000000)">
|
||||||
|
<path d="M347.514915,576.897114 C347.550896,575.344172 347.734799,573.467033 347.247056,571.966122 C346.37399,569.970865 346.257437,567.725396 346.919229,565.65029 C349.034115,561.33167 352.876093,557.981637 355.95847,554.367445 C359.648549,550.383129 363.039974,546.131684 366.105128,541.647727 L366.201077,541.711766 C362.774881,547.283146 358.23727,552.206133 353.851579,557.025057 C351.276935,560.114932 347.694821,563.068724 346.763311,567.139194 C346.735326,568.740165 346.623385,571.093593 347.814757,572.294321 C348.8742,571.689955 349.497871,569.936891 350.301448,569.016332 C351.784667,566.955082 353.359838,564.637676 355.022962,562.708505 L355.114914,562.780549 C353.323857,565.310084 351.28493,567.90766 349.581827,570.497231 C348.678303,571.54987 347.858734,572.95072 347.794768,574.375584 C347.734799,578.041809 347.794768,581.952181 347.299029,585.582384 C347.175094,588.203974 346.335536,591.261829 346.795294,593.835391 L347.195084,593.887423 C354.311337,589.340664 351.992558,579.902938 353.423804,572.798627 L353.539743,572.798627 C353.335851,575.668368 352.948055,578.77025 352.860101,581.651999 C352.660206,584.681837 352.388349,588.143938 350.685246,590.749519 C349.897472,591.842505 348.954712,592.814649 347.886719,593.63527 C347.135115,594.011498 346.439481,594.952068 346.067677,595.688515 C344.920281,598.634303 343.153212,601.944311 339.878936,602.852862 C338.539641,603.357168 337.296295,604.565901 336.056948,605.254319 C333.953892,606.14047 331.933669,607.212 330.020126,608.456262 C329.140589,608.892527 327.837275,609.304777 326.937749,609.701017 C325.294614,610.67761 323.627492,612.038435 321.860422,612.794894 C320.341222,613.963603 319.537645,616.120912 319.061896,617.914 C318.873995,618.846566 318.106399,620.41952 318.366262,621.39211 C323.691458,618.690471 328.920705,615.24438 333.958053,612.018423 C337.412234,609.793073 341.430119,608.204109 344.988246,606.202895 C347.870728,603.093008 350.381406,599.398766 352.840112,595.940668 C354.403289,593.395124 357.069885,591.029689 357.713546,588.035872 C357.689558,586.158733 358.077354,584.09348 358.337217,582.240356 C358.121331,578.349995 358.073356,574.235499 358.077354,570.329129 C358.157312,567.835616 359.332693,565.286069 359.964361,562.916632 C360.328169,561.207595 360.076302,559.110322 360.016333,557.385276 L360.13627,557.385276 C360.473196,559.236704 360.486723,561.132617 360.176249,562.988675 C359.728485,564.813783 358.820963,566.879036 358.477144,568.736162 C358.077354,571.509845 358.421173,574.651752 358.397186,577.445447 C358.505129,578.722221 358.157312,580.539324 358.76899,581.700028 L359.216754,581.724043 C360.464098,580.815491 362.067254,579.898935 362.81486,578.502088 C363.166675,577.701602 363.058731,576.500874 363.018753,575.668368 C363.009153,573.966972 363.164466,572.268657 363.482508,570.597292 C364.422014,566.995106 365.557416,563.148772 365.641372,559.390492 C365.521435,554.875753 366.109125,550.985393 369.167515,547.443243 L369.259467,547.507282 C364.945738,553.350828 366.348999,556.228574 365.553418,562.768542 C364.753839,567.803597 362.423066,573.014758 363.438532,578.149874 L363.922277,578.24193 C367.096606,576.809061 368.232008,573.114819 369.327431,570.096988 L369.439372,570.13301 C368.659783,573.102812 367.368462,576.44484 364.641898,578.137867 C363.510284,578.616274 362.496563,579.336108 361.671462,580.247147 C360.831904,581.047632 359.432641,581.575953 358.780984,582.57656 C358.317228,583.53314 358.169306,584.978017 358.089348,586.006641 C358.089348,586.74709 357.689558,588.007855 358.157312,588.652246 C359.964361,588.368074 361.623487,586.458915 363.298605,585.698454 L363.358574,585.810522 C361.759416,587.203367 359.52859,588.171955 357.925434,589.520773 C356.626118,590.945638 355.430748,592.982874 354.26736,594.523809 C352.644215,596.717139 350.997082,599.226662 349.349949,601.391976 C348.378461,602.94892 346.463469,604.465841 345.863785,606.19489 L346.263574,606.467055 C347.974673,606.511082 350.057577,606.370997 351.652737,605.706594 C353.339848,604.569904 355.250842,603.209078 356.450211,601.520053 C357.185824,599.799009 357.789506,597.721749 359.024855,596.244853 C361.859363,593.04291 365.765306,590.697487 369.167515,588.167952 L369.235479,588.260008 C364.889767,592.006281 358.609074,594.663894 357.033904,600.691551 L357.201815,601.159835 C358.533114,601.391976 360.220226,600.359349 361.491557,600.023145 C364.170146,599.082575 366.68882,597.121385 368.979614,595.528418 L369.04358,595.620474 C366.809214,597.553718 364.283274,599.120712 361.559521,600.263291 C360.124276,600.891672 358.229274,601.159835 356.842005,601.864262 C351.948581,606.050802 351.928591,606.767237 345.212128,606.815266 C338.911445,609.168694 333.106501,612.966999 327.509448,616.649233 C325.782357,617.737893 323.375624,619.174765 321.620549,620.211394 C320.884936,620.73171 319.221811,621.348084 318.822022,622.212608 C320.085357,623.377315 322.592037,623.493385 324.207187,623.873616 C328.7408,624.586048 331.547322,620.271431 334.8Line truncated
|
||||||
|
<path d="M578.064823,205.439017 C579.795584,205.771288 581.742691,206.091549 583.473452,206.415813 C583.9422,206.487871 584.67537,206.816139 585.112066,206.567937 C585.432578,206.027497 585.184182,205.142776 585.112066,204.566307 C584.955817,203.709609 584.755498,202.496621 583.910149,202.036246 C581.626505,201.80806 579.170587,201.511819 576.882937,201.29164 C574.559229,201.007408 572.139368,200.222769 569.815661,199.974567 C565.132188,199.914518 562.391816,197.060194 560.088141,193.293126 L560.184294,193.229074 C562.323707,196.103415 564.707511,199.502182 568.665826,199.63429 C570.989534,199.822443 573.42542,200.434942 575.705058,200.907327 C578.38934,201.347685 581.269936,201.399728 583.950213,201.864106 L584.14252,201.663943 C583.830075,199.842682 582.985328,198.154508 581.714646,196.811992 L581.20984,196.695897 C578.962254,198.129064 576.145761,197.352432 573.76997,196.695897 C570.741138,196.035359 567.544037,195.206684 564.955907,193.43324 L565.011997,193.329155 C567.765173,194.837632 570.740029,195.900987 573.82606,196.479721 C576.165793,197.160275 578.870107,197.776777 581.141732,196.53977 L581.273943,196.263545 C578.757928,193.681442 574.795606,192.952849 571.52639,191.679812 C566.249972,189.474016 563.112967,188.112907 557.235589,189.04967 L557.235589,188.933576 C562.307682,187.628513 567.019199,189.498035 571.618537,191.471643 C574.855702,192.672621 578.521551,193.473273 581.193815,195.795164 L581.502307,195.82719 C581.979068,194.418042 581.297981,192.596559 580.604875,191.351545 C579.306804,189.710208 576.999122,189.033657 575.128137,188.297057 C571.9698,187.116234 569.00057,185.481737 566.314074,183.445106 L566.370164,183.345024 C568.773999,184.802211 571.245943,186.575655 573.910194,187.564461 C576.570438,188.745422 580.236287,189.377938 581.434198,192.368373 C581.930991,193.569351 581.778748,195.058564 582.039164,196.279558 C582.439803,197.188298 583.201017,198.321221 583.665759,199.185925 C584.382179,200.913966 584.975272,202.690516 585.440591,204.502254 C585.55277,205.543102 585.49668,206.828149 586.02953,207.772918 C587.231448,209.774548 587.632087,212.328628 589.234644,214.126092 C590.568773,216.163751 591.534313,218.701818 593.641675,220.130982 C598.175816,223.915219 602.181825,228.289704 605.55268,233.137574 C606.137613,234.098357 606.778636,235.507504 607.43969,236.424251 C608.825902,237.973513 610.696887,239.626859 611.91483,241.312232 C614.83549,245.419577 620.252133,247.317122 623.89795,250.699877 C628.020527,254.150687 631.962817,258.438179 635.853024,262.137191 C636.550136,262.721667 637.571766,264.270929 638.541313,264.338984 C639.883455,263.061944 639.69916,260.35574 639.931531,258.658358 C639.675122,254.194723 637.155101,249.687052 634.322582,246.336323 C628.501294,242.705366 622.055009,239.398673 616.630354,235.103175 C610.404421,229.71879 605.095951,223.093395 599.89966,216.744224 C598.97819,215.543246 597.463774,214.622496 596.185735,213.893903 C593.541516,212.128465 591.518288,209.29816 590.244255,206.419816 L590.344415,206.359767 C592.11524,209.750528 594.539107,213.013186 598.024669,214.766613 L598.497423,214.766613 C598.709762,214.005994 598.140854,212.825032 597.972585,212.092436 C597.259448,209.65445 596.297914,206.92823 594.735421,204.886567 C592.511873,202.032243 589.038331,200.322851 586.538342,197.744751 L586.614464,197.652676 C588.725832,199.370075 591.021495,201.143519 593.132864,202.836898 C595.344392,204.75446 596.794706,207.64081 597.700151,210.363027 C598.377231,212.40469 598.669698,214.882708 600.059916,216.61612 C606.938891,224.754748 613.58149,233.702034 622.932409,239.166484 C625.035765,240.495567 627.952418,242.228978 630.103851,243.477995 C631.165545,244.022439 632.752076,245.339511 634.026109,244.995231 C634.907515,243.830282 634.330595,241.640499 634.282518,240.299407 C633.994058,238.065588 634.042135,234.754891 632.042945,233.285695 C620.825047,231.772463 611.910824,221.956469 605.600756,213.241371 C600.336357,203.885752 596.097594,197.564605 585.877287,193.06494 L585.925364,192.960855 C595.905287,196.836011 600.797092,204.210016 605.789057,213.121274 C612.199284,222.036534 620.792996,231.248036 632.123073,233.137574 L632.523712,232.797297 C631.554165,229.91495 628.745684,227.192733 626.882712,224.850826 C622.900358,220.691439 617.66801,217.532866 613.873956,213.161306 C611.113552,210.042766 608.70571,206.315731 605.909248,203.225214 C602.764231,199.890499 599.374823,196.491731 595.71298,193.729481 C590.60483,190.703017 585.384501,187.636519 580.128115,184.878273 L580.160166,184.766182 C585.564789,187.264216 590.680952,190.590925 595.837178,193.549335 C599.515046,196.319591 602.92048,199.730368 606.073511,203.077094 C608.877985,206.167611 611.28182,209.882636 614.038218,213.013186 C617.287402,216.700188 621.566229,219.682617 625.256117,222.901238 C627.237944,224.775333 629.052994,226.818011 630.680772,229.00621 C631.369871,229.850898 632.155124,231.780469 633.132684,232.28488 C634.190371,231.564293 634.146301,229.538643 634.334601,22Line truncated
|
||||||
|
<path d="M27.4593279,460.15312 C29.2163983,461.318437 31.8240505,461.75493 33.8167281,462.403663 C39.8706343,464.129613 44.762478,468.718798 48.2846055,473.79253 C49.9338557,475.882892 50.8922577,479.030448 53.5518234,480.087642 C55.0014065,480.724362 57.0739509,481.429158 58.5435007,482.017823 C59.2143821,482.390244 60.1408374,482.818728 60.9155457,482.418275 C61.8100543,481.333049 61.2390064,479.150584 61.1910863,477.877145 C61.0513194,476.439521 60.7917521,473.472168 60.7358454,472.026535 L60.8995724,472.026535 C61.4614557,475.145685 61.7779049,478.304309 61.8459944,481.473208 C62.0895882,484.008072 63.7627985,486.534926 64.936841,488.717392 C66.1676949,491.203812 67.2037659,493.782498 68.0356742,496.430106 C69.0839447,500.114361 69.9094065,503.858731 70.5075528,507.642774 C70.6233597,508.655919 70.5954063,510.073521 70.8869203,511.054629 C71.3341746,512.103814 72.2646232,513.35723 72.9035579,514.290285 C75.9744378,519.540216 80.7305079,523.84508 82.9827527,529.57155 C83.8566306,531.711376 84.4655016,533.950603 84.7957299,536.239083 C85.3907378,537.700734 87.3115352,538.19329 88.6453114,538.753924 C88.8889053,533.251707 86.5328336,527.733473 84.3524689,522.767863 C82.6113719,519.708406 80.2193602,516.797117 78.8296772,513.557457 C77.2043871,509.665059 76.8330063,505.147956 75.8147041,501.095377 C74.0057203,493.386668 71.9211958,485.477732 71.3182012,477.532756 L71.4340081,477.532756 C72.3285167,485.421669 74.1295139,493.36264 76.0343379,501.059336 C77.0366667,505.115919 77.4120408,509.600987 79.0333376,513.473362 C81.3454825,518.475013 85.3707711,522.683768 86.7604541,528.141934 C87.7067881,530.70875 88.480567,533.336263 89.0765923,536.00682 C89.4040464,537.412408 88.9967255,539.21044 90.5900689,539.923245 C94.1401498,542.177792 98.2333252,543.575371 102.046967,545.261276 C101.647632,540.660077 96.4842414,539.374625 93.313528,537.011956 L93.3734281,536.915847 C96.7677687,539.058268 101.687566,540.656073 102.725835,545.000982 C103.660277,546.815031 105.569094,548.236637 106.811023,549.854465 C108.671921,551.900777 110.688558,554.051207 112.082235,556.457926 C113.372084,558.684441 114.282566,561.327428 115.089221,563.762178 C115.488555,564.819373 115.624329,566.164893 115.911849,567.262133 C116.794378,568.863942 117.832647,570.637947 118.359768,572.395933 C119.581731,577.601814 121.953776,582.615479 122.588717,587.933487 C122.988051,590.988939 123.091878,594.340726 123.878566,597.33611 C124.904855,601.276562 127.101193,605.024797 128.898197,608.640883 L129.125818,608.560792 C131.921157,603.074594 129.676899,596.823531 128.50685,591.205184 L128.618663,591.177152 C130.307847,597.183939 131.989044,603.899526 129.473238,609.906312 C129.537132,614.255226 129.105851,618.816379 127.696201,622.965066 C125.69953,628.947826 122.045622,634.353933 118.910849,639.784068 C115.512515,645.390403 113.555778,650.2559 115.987723,656.715197 C117.489219,654.228388 119.645624,651.649474 120.887553,648.978456 C122.884224,644.997959 124.449614,640.524905 126.554105,636.592463 C128.107515,633.789295 130.08422,630.745857 131.202355,627.706423 C133.011339,622.652713 133.798028,617.094434 135.463251,612.000679 C136.916827,607.892037 137.188375,603.266811 136.661254,598.961947 C136.229973,596.274911 136.042286,593.083306 134.696529,590.672582 C133.602354,589.258985 131.677563,588.193781 130.43164,586.924347 C129.097864,585.322537 127.28888,583.848872 126.058931,582.190999 C124.853767,580.356043 123.954174,578.336773 123.395372,576.212245 C123.099865,575.010887 123.051945,573.497177 122.456937,572.387924 C121.07524,570.073309 120.296539,567.262133 119.19837,564.811364 C117.724827,561.243333 115.979736,557.60322 114.845627,553.923062 C114.51418,552.861863 113.571751,551.984872 112.81701,551.224013 C112.301868,550.607316 111.69488,549.682271 111.155779,549.081592 C109.582403,547.972339 107.925166,546.626819 107.126497,544.800756 C106.483569,543.511299 106.327829,541.757317 105.293554,540.700123 C104.147464,539.819127 103.165102,538.521661 102.142807,537.496503 C101.551792,537.064015 100.64131,536.611503 100.038316,536.195033 C99.435321,535.778562 98.6486327,534.965644 98.0416447,534.489105 C97.1391495,534.004558 96.0130271,533.503992 95.2463055,532.783178 C94.2719301,531.485712 92.4509662,531.105282 91.5324976,529.787794 C89.843314,527.405102 90.9814164,524.149424 89.9351608,521.602546 C89.0326656,520.365148 88.4696044,518.739311 87.7947296,517.381777 C86.2413196,515.779968 84.8955634,513.777706 84.3724357,511.55119 C83.8014904,508.810781 83.3934503,506.038768 83.1504731,503.249811 C83.0266795,501.107391 82.8270124,498.632594 81.7847501,496.710423 C78.6419901,489.606397 79.6283456,481.437167 81.3335025,474.088865 L81.4453161,474.112892 C80.007713,481.945742 78.4582964,491.068048 82.8909058,498.196101 L83.3461468,498.083975 C86.6765939,492.529699 85.9617857,485.669949 87.4792556,479.56305 L87.5910692,479.56305 C86.3211865,485.693976 86.900221,492.613794 83.5018871,498.095988 C82.7032188,500.246418 83.4459804,502.849359 83Line truncated
|
||||||
|
<path d="M449.224638,438.673886 C450.023667,436.154922 452.165065,434.31168 453.9469,432.524415 C457.578967,428.648883 461.578201,425.135192 465.888389,422.03273 L465.820471,421.936769 C459.903661,425.587268 455.325225,431.176969 450.131536,435.635135 L449.851876,435.271285 C449.728027,431.580803 448.58142,429.401699 452.189036,427.066659 C457.862142,421.361006 464.54602,415.447438 465.868413,407.074882 L465.756549,407.046893 C464.670687,411.866198 462.279354,416.293028 458.844948,419.841631 C456.0044,422.724445 453.175837,426.270987 449.783959,428.458087 C449.384444,427.426511 449.935774,425.783187 449.883837,424.671644 C449.946156,422.909576 450.834369,421.279877 452.280924,420.273453 C456.647618,415.875262 460.590826,410.081645 460.478962,403.648291 C459.959593,397.938639 456.655608,392.852731 454.825832,387.494934 C453.755133,384.58813 453.107919,381.40144 452.093153,378.474644 C450.399892,374.114027 448.991958,369.647757 447.878275,365.104143 C446.855517,361.349687 444.682158,357.979073 442.840396,354.588468 C441.757712,352.989126 442.976231,351.273831 443.171994,349.59852 L443.056134,349.558536 C442.70508,350.560151 442.412935,351.58148 442.181198,352.617278 L441.96546,352.789208 C440.894761,352.325399 440.251542,350.897986 439.736169,349.910392 C439.308688,348.710885 439.472489,347.231494 439.428543,346 L439.312683,346 C438.721402,348.94279 439.824062,351.381787 441.957469,353.356974 C443.203955,355.835955 444.682158,358.354919 445.952615,360.817906 C447.594619,364.068569 448.261808,367.779043 449.27258,371.241619 C450.335289,374.81215 451.949327,378.310711 452.868211,381.921227 C453.902953,386.271437 455.664812,390.477708 457.458633,394.564027 C458.577273,397.462835 460.083443,400.501585 460.215283,403.660286 C460.243249,410.74937 455.532973,417.386641 450.195459,421.604906 C449.428391,420.953174 449.512289,419.553749 449.252604,418.646123 C449.052847,417.782478 448.785172,416.646945 449.312532,415.847274 C452.944119,414.091996 455.221351,410.197597 456.55573,406.523108 L456.447861,406.475128 C454.849803,410.073648 452.708405,414.072004 448.948973,415.743316 C448.71326,413.284328 449.859866,410.789354 450.810711,408.546276 C451.60974,406.303199 453.495449,404.391985 453.843026,401.94899 C454.174623,399.973802 453.575351,397.810692 453.043997,395.927466 C452.350115,393.517033 451.425623,391.179173 450.283352,388.946337 C449.596187,387.31101 449.791949,385.311832 449.324517,383.608533 C448.557449,380.509807 447.386872,377.395088 446.551886,374.316354 C445.329372,370.226036 444.226712,365.971786 442.484829,362.065393 C440.927709,358.979087 439.066026,356.056401 436.927582,353.340981 L436.831698,353.408953 C438.429756,355.951907 440.427329,358.430888 441.777688,361.121781 C443.683372,364.928215 444.786032,369.174469 446.004551,373.236798 C447.147163,378.034825 449.600182,382.780873 449.556235,387.762824 L449.332507,387.826798 C444.574289,385.287842 443.871144,379.290309 442.700566,374.540262 C441.916233,371.143572 440.787757,367.835807 439.332659,364.668322 L439.22479,364.712304 C440.255538,367.575127 441.521999,370.505921 442.217154,373.480698 C442.976231,376.679382 443.595479,380.073986 444.909882,383.116735 C445.569109,384.819058 446.701797,386.29676 448.173915,387.374984 C448.897037,387.806806 449.771973,388.174655 450.099575,389.042298 C451.541269,391.992671 452.654608,395.09268 453.419541,398.286496 C454.130677,400.993383 453.443512,404.363996 451.421968,406.363174 L451.118337,406.319192 C449.052847,403.092519 449.120765,399.066175 447.89825,395.523632 C446.675736,391.981089 444.382523,388.970327 442.480833,385.85161 C441.254324,383.852432 440.083746,381.59736 439.612319,379.302304 C438.925154,375.623817 438.625518,371.705428 436.991504,368.282836 L436.883635,368.318821 C438.233994,371.849369 438.79731,375.643808 439.396581,379.350284 C440.367402,383.824444 443.431678,387.506929 445.70092,391.373339 C447.878275,394.72796 448.529483,398.782292 449.296551,402.62871 C449.577895,403.590561 449.940082,404.526852 450.379235,405.427559 C450.63093,405.851385 451.074391,406.439143 451.05042,406.962928 C450.327298,409.809757 448.421614,412.328721 448.653333,415.451437 L448.497522,415.631363 C446.875493,414.703744 445.085668,412.944468 444.813998,411.01726 C445.497427,408.774536 445.589638,406.393032 445.081673,404.104103 C444.374532,400.305666 443.651411,396.363287 442.017397,392.832739 C440.083746,388.218637 437.055426,383.95639 435.577223,379.162361 C435.013907,376.85531 435.177708,374.324351 434.957975,371.965321 L434.838121,371.965321 C434.734534,374.388443 434.910146,376.815442 435.361485,379.198346 C436.560028,383.264674 439.072975,386.919171 440.806868,390.765589 C442.56776,394.234715 443.780704,397.956048 444.402498,401.797052 C445.121624,404.799817 445.497168,407.97851 444.630222,410.99327 L444.398503,411.133212 C440.683018,408.110456 440.802872,402.696682 439.584353,398.430437 C438.38581,393.520456 435.561242,389.138258 434.430616,384.208286 L434.318752,384.236274 C434.949985,388.Line truncated
|
||||||
|
<path d="M799.562123,666.068265 C799.562123,664.959699 799.321811,663.627018 799.221681,662.53446 C799.160258,659.53324 799.160258,656.531079 799.221681,653.529859 C799.446309,652.02202 799.397468,650.486091 799.077494,648.995542 C799.137572,648.335205 799.357858,647.482769 799.365868,646.810426 C799.365868,645.789904 798.913281,644.609301 799.149587,643.60879 C799.550107,642.340141 799.221681,640.883397 799.093515,639.606745 C799.470003,637.941894 799.233697,636.197003 798.929302,634.536154 C799.730341,631.790751 799.465998,628.785216 799.474008,625.947766 C799.373878,622.558034 799.474008,618.964197 799.474008,615.570463 C799.373878,603.716407 799.610185,591.794315 799.421941,579.952264 L799.30579,579.952264 C799.145582,589.78929 799.329821,599.71036 799.225686,609.567396 C798.344543,604.032568 793.562339,602.239652 794.607695,595.768345 L794.491545,595.768345 C792.973576,601.72739 798.520772,604.692905 799.113541,610.131684 C799.329821,613.041171 799.157598,616.218795 799.245712,619.140287 C799.217676,618.672048 799.10553,617.363379 799.065478,616.879132 L798.941317,616.731056 C798.88925,622.333919 799.81445,628.224929 798.857208,633.779767 L798.456689,633.967864 C796.726444,633.211477 795.292584,631.274487 794.415446,629.649657 C793.758594,627.576598 794.815966,625.155361 795.392714,623.182353 L795.280568,623.142332 C790.558443,633.759757 800.259027,631.146422 798.941317,638.826346 L798.68098,639.130501 C797.347249,638.630246 796.081607,637.197514 794.763898,636.557187 L794.69581,636.665242 C797.539499,639.122497 799.754372,639.066469 798.901265,643.612792 L798.604881,643.928953 C795.617005,642.88442 792.85342,640.727317 790.081824,639.162518 L790.017741,639.262569 C792.128655,640.767192 794.333302,642.135992 796.618304,643.360663 C797.511462,643.760867 798.600876,644.277131 798.88925,645.305657 C798.993385,646.302166 799.321811,647.794929 798.604881,648.603342 C797.215078,648.42325 795.72915,647.290671 794.331337,646.958501 L794.283275,647.078563 C794.844002,647.398726 796.061582,647.979023 796.658356,648.231151 C797.535494,648.535307 798.608886,648.947518 798.881239,649.940025 C799.041447,650.912522 799.225686,652.209184 798.829172,653.141661 L798.58886,653.141661 C797.746248,652.48994 796.852923,651.906457 795.917394,651.396769 L795.845301,651.508826 C796.64634,652.361262 798.076195,652.833503 798.745063,653.793994 C799.117546,654.930575 799.273749,656.551403 798.745063,657.639959 C795.516875,656.471362 793.434173,653.193687 791.491653,650.512317 C790.894078,649.714392 790.412675,648.835881 790.061798,647.902984 C789.721357,646.70237 789.921616,645.197601 789.905596,643.96097 L789.789445,643.96097 C789.116572,646.814428 790.149913,649.431765 791.992303,651.596871 C793.193861,653.29774 794.563638,655.222724 796.217784,656.52739 C796.910683,657.151709 798.084205,657.575926 798.664959,658.3083 C799.158693,659.5354 799.204071,660.897065 798.793125,662.154266 C798.0802,662.074225 797.247119,661.329844 796.554221,661.089722 C795.328631,660.517429 793.814667,659.91312 792.689207,659.14873 C791.335451,658.256274 791.151211,656.451352 790.826791,655.006613 L790.71064,655.006613 C790.666583,660.461401 795.665067,660.037184 798.721031,662.818605 C799.289769,664.19931 799.09752,665.96021 799.177624,667.424959 C799.09752,668.781652 799.261733,670.478519 798.821161,671.771179 L798.568834,671.771179 C797.179031,670.79468 795.873337,669.429983 795.472818,667.729114 C794.952312,665.734273 793.65416,664.030708 791.868142,662.998697 L791.796048,663.090744 C792.705227,663.931173 793.938828,664.815625 794.547618,665.924192 C795.156407,667.032758 795.41274,668.609564 796.057576,669.734138 C796.702413,670.858713 798.060174,671.511046 798.717026,672.53557 C799.117546,674.044341 799.233697,676.033357 798.676974,677.494103 L798.324517,677.494103 C793.201872,674.604627 791.984292,670.414486 789.513086,665.523987 C788.928328,664.403415 788.027159,663.142771 787.594598,661.954163 C787.254156,659.176744 787.498473,656.167206 787.158031,653.369777 C786.385029,648.475276 784.570675,643.444706 785.672104,638.446152 C786.248852,636.517166 788.35959,635.552673 789.905596,634.512142 L789.845518,634.412091 C788.115273,635.240514 786.088644,636.453133 785.463834,638.378117 C784.630753,642.444195 785.463834,646.782411 786.481153,650.740434 C787.069917,653.229705 787.129995,655.995118 787.182062,658.540419 C787.121985,659.424871 787.450411,660.713529 787.117979,661.537951 L786.777538,661.537951 C783.761625,659.937133 783.529324,656.927595 783.389142,653.882039 C783.093655,650.750392 782.188901,647.706556 780.725687,644.92146 L780.617547,644.965483 C781.542747,647.430743 782.780352,650.04808 783.020664,652.705438 C783.345085,654.88255 783.124799,657.395834 784.16615,659.396857 C784.839023,660.59747 786.20079,661.329844 787.214104,662.198288 C788.880265,664.571501 790.157923,667.36893 791.371497,669.998273 C792.701222,672.967791 794.771908,675.813245 797.631618,677.466089 C798.99739,678.170449 798.969354,680.059414 798.97335Line truncated
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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-pinterest" transform="translate(76.000000, 21.000000)" fill="#707780" fill-rule="nonzero">
|
||||||
|
<path d="M45.8903226,17.823871 C48.0590323,17.823871 50.1396774,16.0183871 50.5016129,13.7909677 C50.9293548,11.6229032 49.4593548,9.81741935 47.2922581,9.81741935 C45.1245161,9.81741935 43.0435484,11.6229032 42.676129,13.7909677 C42.2532258,16.0183871 43.6629032,17.823871 45.8903226,17.823871 L45.8903226,17.823871 Z M193.05871,24.2680645 L197.91,24.2680645 C198.574839,24.2680645 198.964194,23.9980645 199.113871,23.3777419 C199.346135,22.416875 199.584958,21.4576054 199.830323,20.5 C199.973871,19.9416129 199.64871,19.5183871 199.029677,19.5183871 L194.242581,19.5183871 C194.587419,18.1464516 195.93,13.6922581 195.93,10.7883871 C195.93,4.98903226 191.005484,0.0561290323 184.018065,0.0561290323 C176.126774,0.0561290323 170.992903,5.35193548 170.992903,12.803871 C170.992903,14.9774194 171.803871,17.5109677 172.809032,19.0825806 C172.376464,19.0578659 171.943274,19.0456027 171.51,19.0458065 C165.513871,19.0458065 161.737097,22.2983871 161.737097,27.0312903 C161.737097,31.4732258 165.23871,33.1993548 168.535484,34.3480645 C171.463871,35.3687097 174.160645,35.8435484 174.160645,38.1312903 C174.160645,39.673871 172.676774,40.5790323 169.772903,40.5790323 C166.327419,40.5790323 164.433871,38.9564516 163.952581,38.3409677 C164.279032,38.0845161 164.764516,37.473871 164.764516,36.4564516 C164.764516,35.0332258 163.764839,33.8716129 161.908065,33.8716129 C160.337097,33.8716129 159.009032,35.1529032 158.675484,36.8383871 C156.152258,38.9025806 153.398387,40.2403226 149.492581,40.2403226 C145.646774,40.2403226 143.650645,38.24 143.650645,33.9835484 C144.552258,34.2580645 146.9,34.5877419 148.451613,34.5877419 C154.963548,34.5877419 159.401613,31.6290323 159.401613,25.703871 C159.401613,22.5177419 156.604516,19.1716129 151.070968,19.1716129 L151.058387,19.1716129 C143.780323,19.1716129 139.703548,23.7819355 137.923548,28.8529032 C137.424848,28.7326491 136.925264,28.6160867 136.424839,28.5032258 C136.822903,27.3783871 137.10871,26.0748387 137.10871,24.6296774 C137.10871,22.1251613 135.557742,19.1716129 131.407419,19.1716129 C128.516452,19.1716129 125.774194,21.0651613 123.873548,23.9032258 C124.346452,22.0519355 124.66,20.8190323 124.680323,20.7364516 C124.804194,20.2522581 124.602581,19.7129032 123.847419,19.7129032 L119.587742,19.7129032 C118.980645,19.7129032 118.552258,19.9274194 118.379677,20.6187097 C118.263871,21.0796774 116.800323,26.8222581 115.375484,32.413871 C114.478065,35.9003226 109.835484,40.2403226 104.000323,40.2403226 C100.153871,40.2403226 98.1593548,38.1083871 98.1593548,33.8503226 C99.1658065,34.2509677 101.408387,34.5874194 102.959355,34.5874194 C109.736129,34.5874194 113.909355,31.6287097 113.909355,25.7035484 C113.909355,22.5174194 111.112581,19.1712903 105.578387,19.1712903 L105.566452,19.1712903 C97.3709677,19.1712903 92.3506452,25.5367742 91.5,32.7541935 C91.146129,35.7545161 88.613871,39.8745161 85.8687097,39.8745161 C84.5312903,39.8745161 83.7854839,39.0345161 83.7854839,37.6083871 C83.7854839,36.3767742 84.6525806,33.3651613 85.6029032,29.5909677 C85.9212903,28.3283871 86.4045161,26.4022581 86.9403226,24.2680645 L91.3958065,24.2680645 C92.0590323,24.2680645 92.4480645,23.9980645 92.5987097,23.3777419 C92.8119355,22.4845161 93.2032258,20.926129 93.3141935,20.5 C93.4577419,19.9416129 93.1345161,19.5183871 92.5135484,19.5183871 L88.1322581,19.5183871 C88.1322581,19.5183871 90.1493548,11.47 90.1974194,11.2632258 C90.3916129,10.4448387 89.7067742,9.97258065 88.9903226,10.1232258 C88.9903226,10.1232258 85.603871,10.786129 84.9758065,10.9170968 C84.3445161,11.0448387 83.8545161,11.3916129 83.6264516,12.3016129 L81.8329032,19.5183871 L78.3148387,19.5183871 C77.6516129,19.5183871 77.2616129,19.7890323 77.1129032,20.4080645 C76.879179,21.3687064 76.6406815,22.3281806 76.3974194,23.2864516 C76.2519355,23.8448387 76.5767742,24.2680645 77.1970968,24.2680645 L80.6470968,24.2680645 C80.6225806,24.3667742 79.4009677,29.013871 78.4129032,33.1754839 C77.9477419,35.1767742 76.39,39.8596774 73.8390323,39.8596774 C72.3151613,39.8596774 71.6712903,39.0909677 71.6712903,37.4422581 C71.6712903,34.8458065 74.2270968,28.346129 74.2270968,25.3967742 C74.2270968,21.4570968 72.1135484,19.1745161 67.7835484,19.1745161 C65.0541935,19.1745161 62.2241935,20.9367742 61.0248387,22.4835484 C61.0248387,22.4835484 61.3887097,21.2174194 61.5132258,20.7306452 C61.646129,20.2164516 61.3703226,19.7112903 60.6951613,19.7112903 L56.4616129,19.7112903 C55.5606452,19.7112903 55.3116129,20.1929032 55.1793548,20.7225806 C55.1264516,20.9351613 53.6009677,26.9254839 52.1364516,32.676129 C51.1348387,36.613871 48.7009677,39.9041935 46.0987097,39.9041935 C44.7606452,39.9041935 44.1648387,39.0645161 44.1648387,37.6383871 C44.1648387,36.406129 44.9609677,33.3654839 45.9109677,29.5916129 C47.0667742,24.9970968 48.0864516,21.2135484 48.1954839,20.7716129 C48.3377419,20.2051613 48.0845161,19.7116129 47.3545161,19.7116129 L43.0954839,19.7116129 C42.323871,19.7116129 42.0483871,20.1164516 41.8858065,20.6790323 C41.8858065,20.6790323 40.68Line truncated
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
@@ -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-spotify" transform="translate(75.000000, 19.000000)" fill="#707780" fill-rule="nonzero">
|
||||||
|
<path d="M19.8257326,21.6103045 C11.9170377,19.7244691 10.5114121,18.4008839 10.5114121,15.6197142 C10.5114121,12.9922335 12.9835629,11.2239893 16.6644424,11.2239893 C20.2304653,11.2239893 23.763672,12.5667173 27.4718983,15.330932 C27.5812855,15.4146132 27.7234889,15.4479763 27.8602229,15.4260988 C27.9992372,15.40614 28.1238809,15.3297711 28.2047926,15.2149815 L32.0661609,9.77187417 C32.2244056,9.54787428 32.1813036,9.23945583 31.9677125,9.06742057 C27.5539387,5.52710366 22.5877596,3.80589598 16.779299,3.80589598 C8.24162756,3.80589598 2.27455549,8.93013962 2.27455549,16.2618171 C2.27455549,24.1240225 7.42122352,26.9079269 16.3144033,29.0568385 C23.8785286,30.7999236 25.1583589,32.2602428 25.1583589,34.8702216 C25.1583589,37.7635132 22.5768209,39.5629327 18.420107,39.5629327 C13.803967,39.5629327 10.0355777,38.004165 5.82417031,34.3615711 C5.71916847,34.2727829 5.58405881,34.2277464 5.44678445,34.2357758 C5.30458109,34.2467145 5.1787858,34.3123468 5.09127604,34.4162647 L0.759542693,39.568402 C0.579053804,39.7871765 0.600931245,40.1043994 0.808766936,40.2903576 C5.70931375,44.6658458 11.7365488,46.9739159 18.2450875,46.9739159 C27.4445515,46.9739159 33.3897972,41.9475738 33.3897972,34.1646741 C33.4061542,27.5976132 29.4736842,23.9604886 19.8421407,21.6174147 L19.8257326,21.6103045 Z M54.2061314,13.8109967 C50.2189677,13.8109967 46.9482903,15.381797 44.2464263,18.6005155 L44.2464263,14.9776113 C44.2476376,14.6923719 44.0175444,14.4600756 43.7323064,14.458569 L36.6494849,14.458569 C36.3650781,14.458569 36.135365,14.6915637 36.135365,14.9776113 L36.135365,55.2326499 C36.135365,55.5170566 36.3650781,55.7522391 36.6494849,55.7522391 L43.7323064,55.7522391 C44.0167132,55.7522391 44.2464263,55.5170566 44.2464263,55.2326499 L44.2464263,42.527326 C46.9482903,45.5518822 50.2189677,47.0340788 54.2061314,47.0340788 C61.6171145,47.0340788 69.1210888,41.3295361 69.1210888,30.4241786 C69.1320156,19.5166334 61.6280533,13.8104498 54.2116007,13.8104498 L54.2061314,13.8109967 Z M60.8842203,30.4241786 C60.8842203,35.9750323 57.4658701,39.8528088 52.5653233,39.8528088 C47.7249394,39.8528088 44.0714068,35.8000128 44.0714068,30.4241786 C44.0714068,25.0477974 47.7249394,20.9960953 52.5653233,20.9960953 C57.3838297,20.9955484 60.8842203,24.9597407 60.8842203,30.4236317 L60.8842203,30.4241786 Z M88.3458782,13.8109967 C78.8018445,13.8109967 71.325229,21.1596292 71.325229,30.5428637 C71.325229,39.825462 78.7526203,47.0942418 88.2310217,47.0942418 C97.8078715,47.0942418 105.306364,39.7707684 105.306364,30.4241786 C105.306364,21.1093111 97.8570958,13.8120906 88.3458782,13.8120906 L88.3458782,13.8109967 Z M88.3458782,39.9075024 C83.2703119,39.9075024 79.4417597,35.8273596 79.4417597,30.4214439 C79.4417597,24.9909161 83.1390472,21.050242 88.2310217,21.050242 C93.3394042,21.050242 97.1898338,25.128744 97.1898338,30.540129 C97.1898338,35.969563 93.4706688,39.9075024 88.3458782,39.9075024 L88.3458782,39.9075024 Z M125.685201,14.458569 L117.891362,14.458569 L117.891362,6.49189881 C117.891944,6.35496515 117.838104,6.22340908 117.741688,6.12617207 C117.645271,6.02893506 117.514176,5.97398245 117.377243,5.97340346 L110.294421,5.97340346 C110.010014,5.97340346 109.774832,6.2063982 109.774832,6.49189881 L109.774832,14.458569 L106.37289,14.458569 C106.088483,14.458569 105.85877,14.6915637 105.85877,14.9776113 L105.85877,21.0633685 C105.85877,21.3488691 106.088483,21.5824108 106.37289,21.5824108 L109.774832,21.5824108 L109.774832,37.3314337 C109.774832,43.6922997 112.941591,46.9192223 119.19307,46.9192223 C121.730853,46.9192223 123.836557,46.3941637 125.821935,45.2674755 C125.986016,45.1799657 126.084464,45.0049462 126.084464,44.8189879 L126.084464,39.021466 C126.084464,38.8464465 125.991485,38.6768963 125.838343,38.5839172 C125.686667,38.4869428 125.494682,38.4785956 125.335162,38.5620398 C123.973291,39.2457098 122.655175,39.5629327 121.178448,39.5629327 C118.908663,39.5629327 117.891362,38.5292236 117.891362,36.2211536 L117.891362,21.5851454 L125.685201,21.5851454 C125.969608,21.5851454 126.199321,21.3521507 126.199321,21.0661032 L126.199321,14.9808929 C126.20397,14.8436715 126.152222,14.7105323 126.056122,14.6124698 C125.960023,14.5144072 125.827958,14.4599771 125.69067,14.4618506 L125.685201,14.458569 Z M152.835105,14.4897443 L152.835105,13.5112758 C152.835105,10.6327515 153.939916,9.34909262 156.412067,9.34909262 C157.888794,9.34909262 159.075645,9.64225033 160.4047,10.0852685 C160.568781,10.1366805 160.738331,10.1109745 160.869596,10.013073 C161.004755,9.91556397 161.084262,9.75858789 161.082901,9.59193222 L161.082901,3.62486015 C161.085547,3.3958795 160.93596,3.19293286 160.716453,3.1276953 C159.316297,2.71038311 157.522347,2.2821322 154.831422,2.2821322 C148.295536,2.2821322 144.833431,5.96519941 144.833431,12.9293359 L144.833431,14.4279406 L141.431489,14.4279406 C141.147082,14.4279406 140.9119,14.6609353 140.9119,14.9464359 L140.9119,21.0633685 C140.9119,21.3488691 141.147082,21.5824108 141.431489,21.5824108 L144.833431,21.58241Line truncated
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.4 KiB |
@@ -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-vodafone" transform="translate(51.000000, 23.000000)" fill="#707780" fill-rule="nonzero">
|
||||||
|
<path d="M14.041801,44.8823529 L0.885374698,13.4195205 L9.49647462,13.4195205 L17.7322724,34.3113417 L25.6970185,13.4195205 L33.9119662,13.4195205 L21.1099919,44.8823529 L14.041801,44.8823529 Z M56.826249,28.9945608 C56.8357812,27.72901 56.6816016,26.4675403 56.3675463,25.2415391 C56.1020973,24.1565409 55.6353096,23.1310224 54.9914384,22.2182716 C54.411196,21.3908724 53.6568275,20.7005541 52.7813255,20.1958098 C51.8472692,19.6686254 50.7886884,19.4021799 49.7163578,19.4243554 C48.6439106,19.4011501 47.5850615,19.6676632 46.65139,20.1958098 C45.7667089,20.6958365 45.004582,21.3867366 44.4204271,22.2182716 C43.781555,23.138887 43.3217887,24.1715928 43.0651692,25.2623892 C42.7528938,26.4816359 42.5987282,27.7359829 42.6064666,28.9945608 C42.5959946,30.2601651 42.750189,31.5217558 43.0651692,32.7475826 C43.3277184,33.8292718 43.7718724,34.8585811 44.3787268,35.7917002 C45.5510932,37.5795578 47.5578412,38.6419537 49.6955077,38.6064666 C51.8345419,38.6715035 53.8486216,37.601028 54.9914384,35.7917002 C55.6262987,34.8738088 56.0923028,33.8500118 56.3675463,32.7684327 C56.6843268,31.535854 56.8385356,30.2671367 56.826249,28.9945608 M64.7701453,29.015411 C64.7704596,31.0672 64.4539797,33.106737 63.8318896,35.061946 C63.2253507,36.9908761 62.2714274,38.7927313 61.0171233,40.3787268 C58.2895446,43.8094904 54.0966142,45.7434988 49.7163578,45.5912571 C45.3322242,45.7334995 41.1372438,43.8021089 38.3947421,40.3787268 C37.1431701,38.7909506 36.1895232,36.9896176 35.5799758,35.061946 C34.340909,31.1474848 34.340909,26.9458875 35.5799758,23.0314263 C36.190024,21.1230447 37.1439755,19.3423353 38.3947421,17.7771958 C39.6674524,16.2110673 41.2690456,14.9440292 43.0860193,14.0658743 C45.1559448,13.0845092 47.4261027,12.5990666 49.7163578,12.6480661 C54.0659034,12.5278374 58.227816,14.4203152 60.9962732,17.7771958 C62.2472804,19.3201048 63.206386,21.0715149 63.8318896,23.0314263 C64.4573932,24.9913376 64.7701453,26.9929492 64.7701453,29.015411 M89.081386,20.3626108 C87.7001038,19.7806697 86.2220918,19.4624375 84.7237107,19.4243554 C82.343627,19.3091452 80.0673428,20.408041 78.6771757,22.3433723 C78.0185724,23.2585912 77.5246668,24.2816812 77.2176672,25.3666398 C76.8942319,26.4845867 76.7327382,27.6431285 76.7381144,28.8069098 C76.7309157,30.0698651 76.8708596,31.3293602 77.1551168,32.5599315 C77.4057754,33.6715295 77.8504324,34.7302366 78.4686745,35.6874496 C79.0316391,36.5683145 79.78736,37.3097766 80.6787873,37.8558622 C81.5544923,38.3979654 82.6178485,38.689867 83.8063054,38.689867 C84.8806884,38.6659834 85.9329216,38.3796614 86.8712732,37.8558622 C87.7704895,37.4320677 88.5352785,36.7682885 89.081386,35.9376511 L89.081386,20.3626108 Z M89.7277397,44.8823529 L89.4149879,42.4637389 C88.5843547,43.3661971 87.5949082,44.1082821 86.495971,44.6530016 C83.268388,46.0337589 79.5894449,45.8804696 76.487913,44.2359992 C74.8460857,43.3327293 73.4225727,42.080038 72.3178888,40.5663779 C71.1511707,38.9745912 70.2758266,37.188607 69.7324738,35.2912973 C69.1467935,33.2580708 68.8519654,31.1521553 68.856711,29.0362611 C68.856711,26.9720991 69.1486704,24.9704875 69.7533239,23.0105761 C70.3414825,21.1077219 71.2821798,19.332421 72.52639,17.7771958 C75.2511618,14.3989865 79.4061877,12.4991048 83.743755,12.6480661 C85.4117647,12.6480661 87.184025,12.9608179 89.081386,13.5654714 L89.081386,1.36815068 L97.1712329,1.36815068 L97.1712329,44.8823529 L89.7277397,44.8823529 Z M119.939565,30.1204674 C118.155899,30.2399194 116.380659,30.4626945 114.622784,30.7876712 C112.871374,31.1421233 111.662067,31.7259267 110.974013,32.5807816 C110.329893,33.3616606 109.976232,34.3415958 109.973207,35.3538477 C109.973207,36.3963537 110.348509,37.2303586 111.119964,37.8558622 C112.053868,38.5397228 113.195971,38.8786694 114.351732,38.8149678 C115.394239,38.8149678 116.436745,38.6064666 117.4167,38.168614 C118.343608,37.7640596 119.196327,37.2073262 119.939565,36.5214545 L119.939565,30.1204674 Z M120.585919,44.8406527 L120.273167,42.5054392 C119.348373,43.5067301 118.207452,44.2839823 116.937147,44.7781023 C114.265422,45.8355302 111.299338,45.8800215 108.597099,44.9032031 C104.613958,43.575255 101.965488,39.8013961 102.071011,35.6040492 C102.01656,32.8125969 103.274753,30.1572676 105.469581,28.4316076 C106.563729,27.5804058 107.779689,26.8986249 109.076652,26.4091459 C111.559923,25.5284871 114.145324,24.9679665 116.770346,24.7411362 L119.897865,24.4492345 L119.897865,23.9905318 C119.897865,22.2599718 119.439162,21.0298147 118.542607,20.2792103 C117.474277,19.485214 116.159936,19.0938653 114.831285,19.1741539 C112.468199,19.1859679 110.166685,19.9288617 108.242647,21.3008662 C107.822157,21.5679707 107.418308,21.8604127 107.03334,22.1765713 L104.343674,16.5053384 C105.633806,15.5264783 107.050597,14.7269032 108.555399,14.1284247 C112.474591,12.4993039 116.824447,12.2205611 120.919521,13.3361201 C123.772964,14.2280836 126.052138,16.3917596 127.091156,19.195004 C127.717829,20.939008 128.014777,22.7843273 127.966861,24.6368856 L127.966861,44.8615028 L120.5859Line truncated
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -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 |
@@ -0,0 +1,7 @@
|
|||||||
|
<?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>creative-tim</title>
|
||||||
|
<g id="creative-tim" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<image id="logo-ct" x="20" y="20" width="101" height="101" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABGdBTUEAALGOfPtRkwAAGuZJREFUeAHtnQt0VdWZx0lCXrwiDDQK4REeAQUVCQgdrWXa6Wpn9aHtOCqrazpVqlWoCNqF9VmtyGt8UIp1quLYGdfoFMZ22U5ttWKo+MAF8pBQoeEZQUIyEUJCXiSZ35feS0PIvWefk3Nu9j33O2vtnHPP+fbe3/5/33+/zsnevXrpoQgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAi0F0E0jwmkDF58uT+x48fL2pubh6WkZGRSzpe0/KoQriitbW1pVOic1pbW3ulp6cfS0tLaw1XCRNemraWlpb6zMzMQ3l5ebu3bt16Ag1a3Grh2qnHjx/fv76+/ksYci5GnIBhs8hU0nGdlltlU0A+I1JG14ZMAWzcFrGNCG34aBM++iGVzhO5ubm/27VrlxDF+HDl1BMnTuxHq3E7mX4Lgozk3Ns4JxVUBHoIAQhyCoIc4PwftCaPlZaW1pqqEq2xTOTT+/Tp82UEvwc5xkEON3FN0lcZRSAQBPDVdMhxDiTJZ0hwkEp+FxlJC+N4SL/X6CguLu5Pn242GRVJhkaRVEgRsASBCEmKxIfFl03VMnb0qqqqMSQ6ipBpmrjKKQKWISC+Oyriy0aqGROE5mkwKcpslR6KQDIjkBvxZaMyGBOEpimL7pWOO4xgVSFbERAfFl821c+YIKYJqpwiECYEApmmZUAUJowCKQs1mat0FVNnuNxi6pxir16+EwRDHkPRfWT+Cdf6NrhrK7SA0QTwGc5jp1a8FdlyZD9EVru4XeAJPoLhQDAq5PqcLkQ83/KVICgoihzkvJqB0BaUbfSsWYgjgk8rYT7vk66lmNkORW3mU571yK+IOIKDeOo9xteywfISSn4TOJ0DTr6B4CtBRCuUq0Xh3Q0NDdsqKiqUIF2bqm3EiBEVXT/q8m7FgQMHtvNE+65dwJOfn5/N0ReS1EKQLiS83/KdIBFV2vjuRb4nOuVdtZSIaVLVRWX0+6wYLoGv9YYcUZxiSHm77dT/9Zaq1nRecdN43hHwt+mI6BEUQbwXU2MqAhYhoASxyBiqin0IKEHss4lqZBECShCLjKGq2IeAEsQ+m6hGFiGgBLHIGKqKfQgoQeyziWpkEQJKEIuMoarYh4ASxD6bqEYWIaAEscgYqop9CChB7LOJamQRAkoQi4yhqtiHgBLEPpuoRhYhoASxyBiqin0IKEHss4lqZBECShCLjKGq2IeAEsQ+m6hGFiGgBLHIGKqKfQgoQeyziWpkEQJBLdpgRRHZzySrqanpApaazKypqfmQRYtdbZ5iRSEsUWLs2LED2DpgYt++fWt27txZaolagasRSoIMHTq0T+/evf+JfSDuYBmYIaCYlpOT0zR8+PB17HGyhF2GdnMvkFUwArdYYjNIY3miQlYMWcYyTpeCZfaJEydahw0bdpS1ul4YPXr0j0tKShoSq1JicwsdQQoLC2WTlPnAeD3hU6zT1XG1i+tOnjw5ZcyYMdfv2bNnC8915cfY/pZBhTIF/J6HGLJiYSahXZrf+VwMB8NxtCwPlJWVfRQ7meR+EqoxyMiRI8+DHHewcN1N1HpixI7kkEXtsgkTGxsbn0H2Yp6Hqvw+umI7OcDw5+A1jtB5T5h0ng2CKNeA5Y9GjRrl63KfPpaj20mFxkGKioqGYTQhx3fEeHGQkTJfxLjk3yHJZK5Dg0GcMrt5lF5QUFAMKZ6DABOIeEYl0zEhcJadmq4Eyzl0u/6m47OwXIfCOYQc7Lwr440bMNpAA+NIuS/EsNqSnAlWOo5+KbdWE+KSIxoNIg2iUlpAmEN3S8Z7oTqSniDUdu3kwEDfNiRH1IBS9ouJ8zSG1e4WLSkt6nQqmafA5QKCsW9Q0QwG/3kM5OeGjSTGIES9yqYzg8ihGPR2IQdGMmk5Oqsvu59egmGfTPExSQblnwGGPwWgiQTXfiEkYWZrbthI4hqIzh7WU78ZGJ6Lc8+HHNd7JEdU9XRINo00VqXomCSDqdxPnzp16icAchHBs08ISbBHqEjiGYyod/XEWZpxDHobxpjdTXK0qw/RhCTTSetxnEX2mUhKXDzYIoMu6uV0Mx8FS18mLEirnSTMbt08YcKEpB+4J50jCDkYkM/DoDdijHizVe3+guMb+Q0kkd2bLiPNZTL/z3XSYWNU0L8KZTAgv4JyLwOjqVJJ/PVR7CsTPIUkyM2rq6v7brJPARuBEhuuxD6hRiqij/sY5LgVIzjWThFjvoeWHxCaDbQVkszEWVbzwvFqrkP3IjWCgXSrZoLP02BpRA5kBb+tYFMawTWSVNenCEkWSqtMpVbQtZT9d5OGIIA8hhrpHiC9EiPlOUEbMeIm5Jbw2cmtnNdxr8kpHs+FJJN44XgvTvQNrsNGEulWfQYHXkTZRoOlyb6Hsqfi61lZWbdCqOVclxmSJA+5b9DdenDSpEny4jbpjqQgCI46mm7VPRjm6yAsL6fiHmI8ZA9z/lc+Vnxt//79G/gW627ubeCeCUkEl4k40b20JJJnWEgib8gvozzLCNMIjv1P8JKdrUoyMzPv3Lt37zt89Pkys1VLubdHcHY6wHAAclcT70HpHjvJ2/bceoKMHz9evgO6l5pLanMjciB3BKM8glFeZZ/EOn638L3QVgz7fdJ6i98m32C1k4SW5D4+ygvDwF26VTMo/6OUv5jg2HJEyLGBlmPBvn37dgiOx/5yvEQ6SwjGJEF2lrQk6DCQdJLmsJog0nLU1tbeB5pXAbBRt4oa6yiGXYJjP48tj3ewRCsbYW6DJPLG/X3uuyHJw0k+BSzvOS5lPPATyi0TEI7kQEZajnfpns6j5Sjl+jRefCV9jK96XwLLxdjFqLuF3ADSmMV5UXFxsaMtkbXisJYgMuYAzPswwtc5G9U6yFXi/A8xBfxfR44cqQLhzp+0t5MEmZt5tp1w2uhxrCEYzcS5HkGnZHzjLt9WTUX/n1FuU/0Fl/cgx9xIy9EZpzYhCXj/Ejnpbh0jbU7xD+Tlo8brjh49uphWOSlIYiVBAK+I5vh+QL+KFsH0S9Eq5B9A/sXDhw//H4boTI6o9VoPHjy4BeLNRv6MmjEq0PmMYTORvRydVlITmzpZ52R64reQYxoZP0MwfUMue7gLOeZADpn960yOaDnawFE6XC8jvxKMPok+iHdGTiq762jhF/MPbYPiydrwzDqC8M9OgwHvHkD/mkty3MeY4wUHckQxl5ZkK+OafyGfndyM5QRReRn0y/9DtH+OkSTdrXQG5DLWeJZg9G0VWAgOG6k8bmFiw6SFbaMLXMX/2PwbWK4grlRMTkdahCSzsNci2wfuVhFE3rxipPmEK12QoxLAb+f9yPM0+1KLxWo5OhtOSLKF2Zl/JL94NWXHeDKbJW/cn6NmNq2RO8ZP1LWQ4wJwWUuGpuSQMccG8Lgecmzj2rHSiBSmjX9l/hhnXyljEu5VRu7HO0VJ8s+0ystsbklsIkgaNdHnqYnkn51M+6cyzliAg6+trKysjWeRWM8YgO7GKa7huUmNKcmQXdqF6DifKeAhsdLtyfuDBg3qh34yNhhhogflOQWZ1oPDjXSrdhHHtJI5nbz0tRi4P4P9ZOB+9PSDOBfI9ePxVyGXjAlt8sXTWlujFDXeeRj1SjQT0EwOIcd8+sr/89FHH9WbRIglIyQhnVk4ylZkHGtODNuL2vIG3rGch7w1GEbKl87/3U+gLP8Qq7wd7+PQzciu497NgkPHZ26vq6ura2gRngabh8GowjD+AGQvp7IZZyifUDFrjAtIfQgjCJ3/vbMrQKQZXyDkoDvgy6IBpPMhhv0mzrKZtB1JElFqGGdrMIzolIHTT4hcxz1R1kbw/gMtx1wqmT/HFTZ8KO+dIMlqIQnpm5BE8BtMt3W4YRYJFbPGuDj7KQCtJTg5p4w57gAl38gRRVxIQv7f5vdGgpMe0teSbp3r7ghxgjxEHxmLxZ13RfdmZH7PS8B5LL5Q5qdCEZI8Sx6OLQkymLOtEVJ76iL7qXdXaVlDEAD6BLA2Exq7UpT74pBHkPs+57Xd7VZ1lYfcY+pyJ3ncSB7v8FMGrrGOfdSUh3joSKRYCQR0v4VZwO3oH6/2Fp3fl4rGb3JEyxQlCS1J3IE7OpxCZj8V5J5oXJvO1hCE/u9xQFpDU7sNB5Xa7fSBseW6nHAX45Q1QZEjmiEkKaXbcQt6lJD3WbpwT16SPYzMYeJY14J8zIGO8hl7DSFarOi5hXtvg/X94OhryxHNIHqOdrfAUUgi38ZFH0XPp7j4E+G/+RTIZPYrGi9hZ2sIIiVmBmU7YN7NZQkOKLVzNeEQwG7g9wJqmheDJgf5tR8Q9gOcaA75/pz89xKO8UAmBqR2Xs6XxWt553LyL9LW/W2C5PLmfAX6yxS2fH4jLbSMM35Bue4A61cTobWQhC8bnkIPset7nCvQQ7As57yOsBRdf5MIXbzkYdtXqm3l5eVv8iLuuwApCwgMoUWpxqCvA6J0GRJaW8usDnP0c5m+/CL6FEHeWs5voYvRy0UvBvExTj16PsiL11+hdzFY9gXHHZRlI5VMQvv7kYrkP0eNGrWeHsBnKaP81+FB9NkIxgd9LLPvSdlGkPYC8gJvHxcSevwoLS1tQolf97gi3hRoxTm3EFVCTx+tTILsRwkJSXNY1cVKGtRU0ZRBQAmSMqbWgnpBQAniBTWNkzIIKEFSxtRaUC8IKEG8oKZxUgYBJUjKmFoL6gUBJYgX1DROyiCgBEkZU2tBvSCgBPGCmsZJGQSUICljai2oFwSUIF5Q0zgpg4ASJGVMrQX1goASxAtqGidlEFCCpILine truncated
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 9.5 KiB |