inital check in
2
llm-fe/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
|
||||
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/
|
||||
2
llm-fe/.env.development
Normal file
@@ -0,0 +1,2 @@
|
||||
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
|
||||
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/
|
||||
2
llm-fe/.env.production
Normal file
@@ -0,0 +1,2 @@
|
||||
REACT_APP_BACKEND_REST_API_BASE_URL=https://chatbackend.aimloperations.com/api/
|
||||
REACT_APP_BACKEND_WS_API_BASE_URL=wss://chatbackend.aimloperations.com/ws/chat_again/
|
||||
23
llm-fe/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
46
llm-fe/README.md
Normal file
@@ -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/).
|
||||
BIN
llm-fe/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
21303
llm-fe/package-lock.json
generated
Normal file
73
llm-fe/package.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "llm-fe",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^5.16.11",
|
||||
"@mui/material": "^5.16.11",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.2",
|
||||
"axios": "^1.7.9",
|
||||
"babel-loader": "^9.2.1",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chroma-js": "^3.1.2",
|
||||
"formik": "^2.4.6",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-to-jsx": "^7.7.2",
|
||||
"mini.css": "^3.0.1",
|
||||
"react-bootstrap": "^2.10.6",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"recharts": "^2.15.1",
|
||||
"styled-components": "^6.1.14",
|
||||
"stylis-plugin-rtl": "^2.1.1",
|
||||
"web-vitals": "^4.2.4",
|
||||
"webpack": "^5.97.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"yup": "^1.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=development react-scripts start",
|
||||
"build": "NODE_ENV=production react-scripts build",
|
||||
"test": "NODE_ENV=development react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootstrap": "~5.2.10",
|
||||
"@types/lodash": "~4.17.13",
|
||||
"@types/react": "^18.3.16",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
BIN
llm-fe/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
43
llm-fe/public/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Chat by AI ML Operations. LCC</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
llm-fe/public/logo192.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
llm-fe/public/logo512.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
15
llm-fe/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Chat by AI ML Operations. LLC",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
llm-fe/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
1
llm-fe/scp_command.txt
Normal file
@@ -0,0 +1 @@
|
||||
scp -r ./* westfarn@10.0.0.103:/var/www/chat.aimloperations/html
|
||||
71
llm-fe/src/App.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* .app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.sidebar {
|
||||
background-color: #f4f4f4;
|
||||
padding: 20px;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.message-box {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #f4f4f4;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #ddd;
|
||||
} */
|
||||
9
llm-fe/src/App.test.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
76
llm-fe/src/App.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { Component, useContext } from 'react';
|
||||
import './App.css';
|
||||
import Dashboard from './llm-fe/pages/Dashboard/Dashboard';
|
||||
import { Routes, Route, createRoutesFromElements, Outlet, Navigate, useLocation } from 'react-router-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import SignIn from './llm-fe/pages/SignIn/SignIn';
|
||||
import PasswordReset from './llm-fe/pages/PasswordReset/PasswordReset';
|
||||
import NotFound from './llm-fe/pages/NotFound/NotFound';
|
||||
import { AuthContext, AuthProvider } from './llm-fe/contexts/AuthContext';
|
||||
import AccountPage from './llm-fe/pages/Account/Account';
|
||||
import AsyncDashboard from './llm-fe/pages/AsyncDashboard/AsyncDashboard';
|
||||
import TermsOfService from './llm-fe/pages/TermsOfService/TermsOfService';
|
||||
import SetPassword from './llm-fe/components/SetPassword/SetPassword';
|
||||
import FeedbackPage from './llm-fe/pages/FeedbackPage/FeedbackPage';
|
||||
import { Typography } from '@mui/material';
|
||||
import AsyncDashboard2 from './llm-fe/pages/AsyncDashboard2/AsyncDashboard2';
|
||||
import FeedbackPage2 from './llm-fe/pages/FeedbackPage2/FeedbackPage2';
|
||||
import Account2 from './llm-fe/pages/Account2/Account2';
|
||||
import AnalyticsPage from './llm-fe/pages/Analytics/Analytics';
|
||||
|
||||
const ProtectedRoutes = () => {
|
||||
const { authenticated, needsNewPassword, loading } = useContext(AuthContext);
|
||||
if(loading){
|
||||
return(
|
||||
<div>Loading</div>
|
||||
)
|
||||
}
|
||||
if(!authenticated) return <Navigate to='/signin/' replace />
|
||||
//if(!needsNewPassword) return <Navigate to='/password_reset/' replace/>
|
||||
|
||||
return <Outlet key={Date.now()}/>
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='site'>
|
||||
<main>
|
||||
<div className="main-container">
|
||||
|
||||
|
||||
<Routes>
|
||||
<Route path='*' element={<NotFound />} />
|
||||
|
||||
<Route path={"/signin/"} Component={SignIn}/>
|
||||
<Route path={"/password_reset/"} Component={PasswordReset}/>
|
||||
<Route path={'/set_password/'} Component={SetPassword}/>
|
||||
|
||||
|
||||
|
||||
<Route element={<ProtectedRoutes />}>
|
||||
<Route path={"/"} index={true} Component={AsyncDashboard2}/>
|
||||
<Route path={"/account/"} Component={Account2}/>
|
||||
<Route path={"/terms_of_service/"} Component={TermsOfService}/>
|
||||
<Route path={"/feedback/"} Component={FeedbackPage2}/>
|
||||
<Route path={"/analytics/"} Component={AnalyticsPage} />
|
||||
|
||||
</Route>
|
||||
|
||||
</Routes>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
75
llm-fe/src/axiosApi.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import axios from "axios";
|
||||
|
||||
//const baseURL = 'http://127.0.0.1:8001/api/';
|
||||
const baseURL = 'https://chatbackend.aimloperations.com/api/';
|
||||
//const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL;
|
||||
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
"Authorization": 'JWT ' + localStorage.getItem('access_token'),
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.request.use(config => {
|
||||
config.timeout = 100000;
|
||||
return config;
|
||||
})
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// Prevent infinite loop
|
||||
if (error.response.status === 401 && originalRequest.url === baseURL+'/token/refresh/') {
|
||||
window.location.href = '/signin/';
|
||||
//console.log('remove the local storage here')
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if(error.response.data.code === "token_not_valid" &&
|
||||
error.response.status == 401 &&
|
||||
error.response.statusText == 'Unauthorized')
|
||||
{
|
||||
const refresh_token = localStorage.getItem('refresh_token');
|
||||
|
||||
if (refresh_token){
|
||||
const tokenParts = JSON.parse(atob(refresh_token.split('.')[1]));
|
||||
|
||||
const now = Math.ceil(Date.now() / 1000);
|
||||
//console.log(tokenParts.exp)
|
||||
|
||||
if(tokenParts.exp > now){
|
||||
return axiosInstance.post('/token/refresh/', {refresh: refresh_token}).then((response) => {
|
||||
localStorage.setItem('access_token', response.data.access);
|
||||
localStorage.setItem('refresh_token', response.data.refresh);
|
||||
|
||||
axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access;
|
||||
originalRequest.headers['Authorization'] = 'JWT ' + response.data.access;
|
||||
|
||||
return axiosInstance(originalRequest);
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
});
|
||||
|
||||
}else{
|
||||
console.log('Refresh token is expired');
|
||||
window.location.href = '/signin/';
|
||||
|
||||
}
|
||||
}else {
|
||||
console.log('Refresh token not available');
|
||||
window.location.href = '/signin/';
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
BIN
llm-fe/src/bg.jpeg
Normal file
|
After Width: | Height: | Size: 324 KiB |
42
llm-fe/src/index.css
Normal file
37
llm-fe/src/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { AuthProvider } from './llm-fe/contexts/AuthContext';
|
||||
import { AccountProvider } from './llm-fe/contexts/AccountContext';
|
||||
import { WebSocketProvider } from './llm-fe/contexts/WebSocketContext';
|
||||
import { MaterialUIControllerProvider } from './llm-fe/ui-kit/context';
|
||||
import { ConversationProvider } from './llm-fe/contexts/ConversationContext';
|
||||
import { MessageProvider } from './llm-fe/contexts/MessageContext';
|
||||
import { PreferenceProvider } from './llm-fe/contexts/PreferencesContext';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<AccountProvider>
|
||||
<WebSocketProvider>
|
||||
<PreferenceProvider>
|
||||
<ConversationProvider>
|
||||
<MessageProvider>
|
||||
<MaterialUIControllerProvider>
|
||||
<App />
|
||||
</MaterialUIControllerProvider>
|
||||
</MessageProvider>
|
||||
</ConversationProvider>
|
||||
</PreferenceProvider>
|
||||
</WebSocketProvider>
|
||||
</AccountProvider>
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
311
llm-fe/src/llm-fe/components/AsyncChat/AsyncChat.tsx
Normal file
@@ -0,0 +1,311 @@
|
||||
import { Box, Button, InputAdornment, TextField, Typography } from '@mui/material';
|
||||
import React, {useRef, useContext, useEffect, useState } from 'react';
|
||||
import { Conversation, ConversationPrompt, ConversationPromptType, ConversationType } from '../../data';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import ConversationDetailCard from '../ConversationDetailCard/ConversationDetailCard';
|
||||
import { ErrorMessage, Field, Form, Formik } from 'formik';
|
||||
import { WebSocketContext } from '../../contexts/WebSocketContext';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import * as Yup from 'yup';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { AttachFile, Send } from '@mui/icons-material';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
|
||||
type RenderMessageProps= {
|
||||
response: string
|
||||
index: number
|
||||
};
|
||||
|
||||
type AsyncChatProps = {
|
||||
selectedConversation: number | undefined
|
||||
conversationTitle: string
|
||||
conversations: Conversation[]
|
||||
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>
|
||||
setSelectedConversation: React.Dispatch<React.SetStateAction<number | undefined>>
|
||||
drawerWidth: number;
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
prompt: Yup.string().min(1, "Need to have at least one character").required("This is requried")
|
||||
}
|
||||
)
|
||||
|
||||
const VisuallyHiddenInput = styled('input')({
|
||||
clip: 'rect(0 0 0 0)',
|
||||
clipPath: 'inset(50%)',
|
||||
height: 1,
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
width: 1,
|
||||
});
|
||||
|
||||
const AsyncChat = ({selectedConversation, conversationTitle, conversations, setConversations, setSelectedConversation, drawerWidth}: AsyncChatProps): JSX.Element => {
|
||||
|
||||
|
||||
const messageEndRef = useRef(null);
|
||||
|
||||
const [conversationDetails, setConversationDetails] = useState<ConversationPrompt[]>([])
|
||||
const [disableInput, setDisableInput] = useState<boolean>(false);
|
||||
|
||||
const [subscribe, unsubscribe, socket, sendMessage] = useContext(WebSocketContext)
|
||||
const { account, setAccount } = useContext(AccountContext)
|
||||
const messageRef = useRef('')
|
||||
const messageResponsePart = useRef(0);
|
||||
const conversationRef = useRef(conversationDetails)
|
||||
const [stateMessage, setStateMessage] = useState<string>('')
|
||||
const selectedConversationRef = useRef<undefined | number>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
/* register a consistent channel name for identifing this chat messages */
|
||||
const channelName = `ACCOUNT_ID_${account?.email}`
|
||||
|
||||
/* subscribe to channel and register callback */
|
||||
subscribe(channelName, (message: string) => {
|
||||
/* when a message is received just add it to the UI */
|
||||
|
||||
if (message === 'END_OF_THE_STREAM_ENDER_GAME_42'){
|
||||
messageResponsePart.current = 0
|
||||
|
||||
conversationRef.current.pop()
|
||||
|
||||
//handleAssistantPrompt({prompt: messageRef.current})
|
||||
setConversationDetails([...conversationRef.current, new ConversationPrompt({message: `${messageRef.current}`, user_created:false})])
|
||||
messageRef.current = ''
|
||||
setStateMessage('')
|
||||
}
|
||||
else if (message === 'START_OF_THE_STREAM_ENDER_GAME_42'){
|
||||
messageResponsePart.current = 2
|
||||
|
||||
}else if (message === 'CONVERSATION_ID'){
|
||||
messageResponsePart.current = 1
|
||||
}else{
|
||||
if (messageResponsePart.current === 1){
|
||||
// this has to do with the conversation id
|
||||
if(!selectedConversation){
|
||||
//console.log("we have a new conversation")
|
||||
setSelectedConversation(Number(message))
|
||||
}
|
||||
}
|
||||
else if (messageResponsePart.current === 2){
|
||||
messageRef.current += message
|
||||
setStateMessage(messageRef.current)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return () => {
|
||||
/* unsubscribe from channel during cleanup */
|
||||
unsubscribe(channelName)
|
||||
}
|
||||
}, [account, subscribe, unsubscribe])
|
||||
|
||||
|
||||
async function GetConversationDetails(){
|
||||
if(selectedConversation){
|
||||
|
||||
try{
|
||||
//console.log('GetConversationDetails')
|
||||
//setPromptProcessing(true)
|
||||
selectedConversationRef.current = selectedConversation;
|
||||
const {data, }: AxiosResponse<ConversationPromptType[]> = await axiosInstance.get(`conversation_details?conversation_id=${selectedConversation}`)
|
||||
|
||||
const tempConversations: ConversationPrompt[] = data.map((item) => new ConversationPrompt({
|
||||
message: item.message,
|
||||
user_created: item.user_created,
|
||||
created_timestamp: item.created_timestamp
|
||||
}))
|
||||
conversationRef.current = tempConversations
|
||||
setConversationDetails(tempConversations)
|
||||
|
||||
}finally{
|
||||
//setPromptProcessing(false)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
GetConversationDetails();
|
||||
}, [selectedConversation])
|
||||
|
||||
|
||||
// Function to render each message
|
||||
|
||||
const renderMessage = ({response, index}: RenderMessageProps) => (
|
||||
<div key={index}>
|
||||
<p>{response}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
type PromptValues = {
|
||||
prompt: string;
|
||||
file: Blob | null;
|
||||
fileType: string | null;
|
||||
|
||||
};
|
||||
|
||||
const handlePromptSubmit = async ({prompt, file, fileType}: PromptValues, {resetForm}: any): Promise<void> => {
|
||||
|
||||
// send the prompt to be saved
|
||||
console.log(fileType)
|
||||
try{
|
||||
const tempConversations: ConversationPrompt[] = [...conversationDetails, new ConversationPrompt({message: prompt, user_created:true}), new ConversationPrompt({message: '', user_created:false})]
|
||||
conversationRef.current = tempConversations
|
||||
setConversationDetails(tempConversations)
|
||||
// TODO: add the file here
|
||||
console.log(`Sending message. ${prompt} ${selectedConversation} ${fileType}`)
|
||||
sendMessage(prompt, selectedConversation, file, fileType)
|
||||
resetForm();
|
||||
|
||||
|
||||
}catch(e){
|
||||
console.log(`error ${e}`)
|
||||
// TODO: make this user friendly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return(
|
||||
<Box
|
||||
sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { sm: `${drawerWidth}px` }, mt: 5 }}
|
||||
position='fixed'
|
||||
>
|
||||
<div className="card" style= {{height: 'auto'}}>
|
||||
<div className='card-header'>
|
||||
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
<Typography variant="h6" style={{ flexGrow: 1, marginLeft: '1rem' }} className='text-white font-weight-bold'>
|
||||
{conversationTitle}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body bg-gradient-dark border-radius-lg py-3 pe-1">
|
||||
<Box sx={{
|
||||
mb: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: 700,
|
||||
overflow: "hidden",
|
||||
overflowY: "scroll",
|
||||
//justifyContent="flex-end" //# DO NOT USE THIS WITH 'scroll'
|
||||
}}>
|
||||
{selectedConversation ?
|
||||
conversationDetails.map((convo_detail) =>
|
||||
convo_detail.message.length >0 ?
|
||||
<ConversationDetailCard message={convo_detail.message} user_created={convo_detail.user_created} key={convo_detail.id}/> : <ConversationDetailCard message={stateMessage} user_created={convo_detail.user_created} key={convo_detail.id}/>
|
||||
)
|
||||
:
|
||||
<Markdown className='text-light text-center'>Either select a previous conversation on start a new one.</Markdown>
|
||||
}
|
||||
<div ref={messageEndRef} />
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='card-footer'>
|
||||
<Formik
|
||||
initialValues={{
|
||||
prompt: '',
|
||||
file: null,
|
||||
fileType: null,
|
||||
}}
|
||||
onSubmit={handlePromptSubmit}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div className='row' style={{
|
||||
position: 'sticky',
|
||||
bottom: 0
|
||||
}}>
|
||||
<div className='col-12'>
|
||||
<Field
|
||||
name={"prompt"}
|
||||
fullWidth
|
||||
as={TextField}
|
||||
label={'Prompt'}
|
||||
errorstring={<ErrorMessage name={"prompt"}/>}
|
||||
size={"small"}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
|
||||
|
||||
margin={"dense"}
|
||||
variant={"outlined"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end' >
|
||||
|
||||
<Button
|
||||
component="label"
|
||||
startIcon={<AttachFile/>}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
|
||||
>
|
||||
<VisuallyHiddenInput
|
||||
type="file"
|
||||
accept='.csv,.xlsx,.txt'
|
||||
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
//console.log(file)
|
||||
if (file) {
|
||||
formik.setFieldValue('file',file)
|
||||
formik.setFieldValue('fileType',file.type)
|
||||
} else {
|
||||
formik.setFieldValue('file',null)
|
||||
formik.setFieldValue('fileType',null)
|
||||
}
|
||||
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<Send />}
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid}>
|
||||
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
>
|
||||
</Field>
|
||||
</div>
|
||||
{/* <div className='col-1'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
// disabled={selectedConversation === undefined ? true : false || !formik.isValid}
|
||||
>Send
|
||||
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
</Form>
|
||||
}
|
||||
|
||||
</Formik>
|
||||
</div>
|
||||
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default AsyncChat
|
||||
16
llm-fe/src/llm-fe/components/Card/Card.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React, { JSX, ReactNode } from 'react';
|
||||
|
||||
export type CardProps = {
|
||||
children?: ReactNode
|
||||
};
|
||||
|
||||
const Card = ({children}: CardProps): JSX.Element => {
|
||||
return(
|
||||
<div className='card card-body'>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Card;
|
||||
@@ -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;
|
||||
113
llm-fe/src/llm-fe/components/ConversationLog/ConversationLog.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { Conversation, ConversationType } from "../../data";
|
||||
import { Box, Button, ButtonBase, Card, CardContent, IconButton, Typography } from "@mui/material";
|
||||
import { Delete } from "@mui/icons-material";
|
||||
import { Form, Formik } from "formik";
|
||||
import CustomTextField from "../CustomTextField/CustomTextField";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import { AxiosResponse } from "axios";
|
||||
import * as Yup from 'yup';
|
||||
|
||||
type ConversationCardProps = {
|
||||
title: string,
|
||||
conversation_id: number | undefined,
|
||||
setSelectConversation: Dispatch<SetStateAction<number | undefined>>; // state function to set the selected conversation
|
||||
deleteConversation: (conversation_id: number | undefined)=>void;
|
||||
};
|
||||
|
||||
const ConversationCard = ({title, conversation_id, setSelectConversation, deleteConversation}: ConversationCardProps): JSX.Element => {
|
||||
return (
|
||||
<Card style={{width: "100%", margin:'0px'}}>
|
||||
<div className="row">
|
||||
<div className='col-10'>
|
||||
<ButtonBase onClick={() => {console.log(conversation_id); setSelectConversation(conversation_id)}} style={{width: "100%", margin:'1px'}}>
|
||||
<CardContent style={{width: "100%", margin:'0px'}}>{title}</CardContent>
|
||||
</ButtonBase>
|
||||
|
||||
</div>
|
||||
<div className='col-1'>
|
||||
<IconButton aria-label="settings" onClick={() => deleteConversation(conversation_id)}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
type ConversationLogProps = {
|
||||
conversations: Conversation[];
|
||||
setSelectConversation: Dispatch<SetStateAction<number | undefined>>; // state function to set the selected conversation
|
||||
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>;
|
||||
deleteConversation: (conversation_id: number | undefined)=>void;
|
||||
};
|
||||
|
||||
type ConversationValues = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
const validataionSchema = Yup.object().shape({
|
||||
name: Yup.string().min(1, "Need to have at least one character").required("This is requried")
|
||||
}
|
||||
)
|
||||
|
||||
const initialValues = {name: ''}
|
||||
|
||||
const ConversationLog = ({conversations, setConversations, setSelectConversation, deleteConversation}: ConversationLogProps): JSX.Element => {
|
||||
const handleConversationSubmit = async ({name}: ConversationValues): Promise<void> => {
|
||||
try{
|
||||
const {data, }: AxiosResponse<ConversationType> = await axiosInstance.post('/conversations', {
|
||||
name: name
|
||||
})
|
||||
setConversations([...conversations, new Conversation({title: data.title, id: data.id})])
|
||||
|
||||
}catch{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div className="card" style= {{height: 'auto'}}>
|
||||
<Box sx={{
|
||||
mb: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: 950,
|
||||
overflow: "hidden",
|
||||
overflowY: "scroll",
|
||||
// justifyContent="flex-end" # DO NOT USE THIS WITH 'scroll'
|
||||
}}>
|
||||
|
||||
{
|
||||
conversations ? (
|
||||
conversations.map((conversation ) =>
|
||||
<ConversationCard key={conversation.id} title={conversation.title} conversation_id={conversation.id} setSelectConversation={setSelectConversation} deleteConversation={deleteConversation}/>
|
||||
// <ButtonBase onClick={() => setSelectConversation(conversation.id)} style={{width: "100%", margin:'1px'}}>
|
||||
|
||||
|
||||
// </ButtonBase>
|
||||
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
) : (
|
||||
<p>Conversations will show up here</p>
|
||||
)
|
||||
}
|
||||
|
||||
</Box>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default ConversationLog;
|
||||
@@ -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;
|
||||
75
llm-fe/src/llm-fe/components/CustomSelect/CustomSelect.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { Field, ErrorMessage, FieldInputProps } from "formik";
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
|
||||
export interface CustomSelectItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface FormikSelectProps {
|
||||
name: string;
|
||||
items: CustomSelectItem[];
|
||||
label: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
interface MaterialUISelectFieldProps extends FieldInputProps<string> {
|
||||
errorString?: string;
|
||||
children: ReactNode;
|
||||
label: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
const MaterialUISelectField = ({
|
||||
errorString,
|
||||
label,
|
||||
children,
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
onBlur,
|
||||
required
|
||||
}: MaterialUISelectFieldProps): JSX.Element => {
|
||||
return (
|
||||
<FormControl fullWidth
|
||||
|
||||
|
||||
variant={"outlined"}>
|
||||
<InputLabel required={required}
|
||||
|
||||
|
||||
variant={"outlined"}>{label}</InputLabel>
|
||||
<Select name={name} onChange={onChange} onBlur={onBlur} value={value} size={"small"}
|
||||
|
||||
variant={"outlined"}>
|
||||
{children}
|
||||
</Select>
|
||||
<FormHelperText>{errorString}</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomSelect = ({ name, items, label, required = false }: FormikSelectProps):JSX.Element => {
|
||||
return (
|
||||
<div className="FormikSelect">
|
||||
<Field
|
||||
name={name}
|
||||
as={MaterialUISelectField}
|
||||
label={label}
|
||||
errorString={<ErrorMessage name={name} />}
|
||||
required={required}
|
||||
|
||||
variant={"outlined"}
|
||||
>
|
||||
{items.map(item => (
|
||||
<MenuItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomSelect;
|
||||
@@ -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;
|
||||
14
llm-fe/src/llm-fe/components/Footer/Footer.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import MDTypography from '../../ui-kit/components/MDTypography';
|
||||
|
||||
const Footer = ({}): JSX.Element => {
|
||||
return (
|
||||
|
||||
<div className='continer-fluid'>
|
||||
<MDTypography>© 2025 Chat | Developed by <a href='www.aimloperations.com'>AI ML Operations, LLC</a></MDTypography>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
110
llm-fe/src/llm-fe/components/Header/Header.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { AppBar, Button, IconButton, Toolbar, Typography, useMediaQuery, Theme, useTheme } from '@mui/material';
|
||||
import React, { useContext } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { AuthContext } from '../../contexts/AuthContext';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import { AccountBox, Dashboard, Feedback, Logout } from '@mui/icons-material';
|
||||
|
||||
type HeaderProps = {
|
||||
drawerWidth?: number;
|
||||
handleDrawerToggle?: () => void
|
||||
}
|
||||
|
||||
const Header = ({drawerWidth=0, handleDrawerToggle}: HeaderProps): JSX.Element => {
|
||||
const {authenticated, setAuthentication} = useContext(AuthContext);
|
||||
const { account, setAccount } = useContext(AccountContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSignOut = async () => {
|
||||
try{
|
||||
const response = await axiosInstance.post('blacklist/',{
|
||||
'refresh_token': localStorage.getItem("refresh_token")
|
||||
})
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
axiosInstance.defaults.headers['Authorization'] = null;
|
||||
setAuthentication(false)
|
||||
setAccount(undefined);
|
||||
navigate('/signin/')
|
||||
}finally{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handleDashboardClick = async () => {
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
const handleAccountClick = async () => {
|
||||
navigate('/account/')
|
||||
}
|
||||
|
||||
const handleFeedbackClick = async () => {
|
||||
navigate('/feedback/')
|
||||
}
|
||||
const theme = useTheme()
|
||||
const isLargeScreen = useMediaQuery(theme.breakpoints.up('md'))
|
||||
return (
|
||||
<AppBar position='fixed'
|
||||
sx={{
|
||||
width: { sm: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { sm: `${drawerWidth}px` },
|
||||
}}
|
||||
>
|
||||
<Toolbar className='bg-gradient-dark shadow-dark'>
|
||||
{ handleDrawerToggle &&
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={handleDrawerToggle}
|
||||
sx={{ mr: 2, display: { sm: 'none' } }}>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
}
|
||||
{isLargeScreen ? (
|
||||
<>
|
||||
<Typography variant="h6" style={{ flexGrow: 1 }} className='text-white font-weight-bold'>
|
||||
Chat | AI Ml Operations
|
||||
</Typography >
|
||||
<Button color="inherit" onClick={handleDashboardClick}>
|
||||
Dashboard</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleAccountClick}>Account</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleFeedbackClick}>Feedback</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleSignOut}>Sign Out</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Typography variant="h6" style={{ flexGrow: 1 }} className='text-white font-weight-bold'>
|
||||
Chat
|
||||
</Typography >
|
||||
<IconButton color="inherit" onClick={handleDashboardClick}>
|
||||
<Dashboard />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" onClick={handleAccountClick}>
|
||||
<AccountBox />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" onClick={handleFeedbackClick}>
|
||||
<Feedback />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" onClick={handleSignOut}>
|
||||
<Logout />
|
||||
</IconButton>
|
||||
</>
|
||||
|
||||
)}
|
||||
|
||||
|
||||
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
98
llm-fe/src/llm-fe/components/Header2/Header2.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { AppBar, Button, Toolbar } from '@mui/material';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useMaterialUIController } from '../../ui-kit/context';
|
||||
import { boolean } from 'yup';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import {
|
||||
navbar,
|
||||
navbarContainer,
|
||||
navbarRow,
|
||||
navbarIconButton,
|
||||
navbarMobileMenu,
|
||||
} from "../../ui-kit/examples/Navbars/DashboardNavbar/styles";
|
||||
import Breadcrumbs from '../../ui-kit/examples/Breadcrumbs';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { AuthContext } from '../../contexts/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
|
||||
|
||||
|
||||
type Header2Props = {
|
||||
absolute?: Boolean;
|
||||
light?: Boolean;
|
||||
isMini?: Boolean;
|
||||
}
|
||||
|
||||
const Header2 = ({ absolute=false, light=false, isMini=false }: Header2Props): JSX.Element => {
|
||||
const {authenticated, setAuthentication} = useContext(AuthContext);
|
||||
const { account, setAccount } = useContext(AccountContext);
|
||||
const navigate = useNavigate();
|
||||
const handleSignOut = async () => {
|
||||
try{
|
||||
const response = await axiosInstance.post('blacklist/',{
|
||||
'refresh_token': localStorage.getItem("refresh_token")
|
||||
})
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
axiosInstance.defaults.headers['Authorization'] = null;
|
||||
setAuthentication(false)
|
||||
setAccount(undefined);
|
||||
navigate('/signin/')
|
||||
}finally{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handleDashboardClick = async () => {
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
const handleAccountClick = async () => {
|
||||
navigate('/account/')
|
||||
}
|
||||
|
||||
const handleFeedbackClick = async () => {
|
||||
navigate('/feedback/')
|
||||
}
|
||||
|
||||
const handleAnalyticsClick = async () => {
|
||||
navigate('/analytics/')
|
||||
}
|
||||
const [navbarType, setNavbarType] = useState();
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const { miniSidenav, transparentNavbar, fixedNavbar, openConfigurator, darkMode } = controller;
|
||||
return (
|
||||
<AppBar
|
||||
position={absolute ? 'absolute' : navbarType}
|
||||
color='info'
|
||||
sx={(theme) => navbar(theme, { transparentNavbar, absolute, light, darkMode })}
|
||||
>
|
||||
{/* <Toolbar> sx={(theme) => navbarContainer(theme)}> */}
|
||||
<Toolbar sx={{justifyContent: 'start'}}>
|
||||
<MDBox>
|
||||
<Button color="inherit" onClick={handleDashboardClick}>Dashboard</Button>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{marginLeft: "auto"}}>
|
||||
<Button color="inherit" onClick={handleAccountClick}>Account</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleAnalyticsClick}>Analytics</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleFeedbackClick} >Feedback</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleSignOut} >Sign Out</Button>
|
||||
|
||||
</MDBox>
|
||||
|
||||
|
||||
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Header2;
|
||||
@@ -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;
|
||||
173
llm-fe/src/llm-fe/components/SetPassword/SetPassword.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
|
||||
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
|
||||
import { Alert, Button, Stack, Typography } from '@mui/material';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
|
||||
import background from '../../../bg.jpeg'
|
||||
import * as Yup from 'yup';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
|
||||
export type PasswordResetValues = {
|
||||
password1: string;
|
||||
password2: string;
|
||||
};
|
||||
|
||||
const initialValues = {password1: '', password2: ''}
|
||||
const validationSchema = Yup.object().shape({
|
||||
password1: Yup.string().min(6, "Passwords have to be at least 6 digits").required(),
|
||||
password2: Yup.string().min(6, "Passwords have to be at least 6 digits").required().oneOf([Yup.ref('password1')], "Passwords must match"),
|
||||
})
|
||||
|
||||
const contains_number = (item: string): boolean => {
|
||||
const numbers = ['1','2','3','4','5','6','7','8','9','0'];
|
||||
const hasNumber = numbers.some((character) => item.includes(character));
|
||||
if(hasNumber){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const contains_special_character = (item: string): boolean => {
|
||||
const specialCharacters = ['!','@','#','$',',%','^','&','*','(',')','-','_','=','+','/','*','\\','|','`','~','<','>','.','?'];
|
||||
const hasSpecialChacater = specialCharacters.some((character) => item.includes(character));
|
||||
if(hasSpecialChacater){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const SetPassword = ({}): JSX.Element => {
|
||||
const navigate = useNavigate();
|
||||
// see if the user is allowed to come here first
|
||||
const [queryParameters] = useSearchParams()
|
||||
console.log(queryParameters.get("slug"))
|
||||
const slug = queryParameters.get("slug");
|
||||
|
||||
|
||||
try{
|
||||
// make sure it comes back as 200 for a good request. Else go to the homepage
|
||||
axiosInstance.get(`user/set_password/${slug}`)
|
||||
}catch{
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
const handleSetPassword = ({password1, password2}: PasswordResetValues): void => {
|
||||
try{
|
||||
// verify
|
||||
if(password1 === password2){
|
||||
axiosInstance.post(`user/set_password/${slug}/`, {
|
||||
'password': password1,
|
||||
});
|
||||
|
||||
navigate('/')
|
||||
}
|
||||
}catch(error){
|
||||
console.log('catching the error');
|
||||
<CustomToastMessage message={error as string} />
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat"}}>
|
||||
<div className='container my-auto'>
|
||||
<div className='row'>
|
||||
<div className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
<div className='card z-index-0 fadeIn3 fadeInBottom'>
|
||||
<div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
<h4 className='text-white font-weight-bold text-center'>Set your password</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className='card-body text-center'>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSetPassword}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Password'
|
||||
name="password1"
|
||||
changeHandler={(e) => formik.setFieldValue('password1', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Confirm Password'
|
||||
name="password2"
|
||||
changeHandler={(e) => formik.setFieldValue('password2', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{formik.values.password1 === formik.values.password2 ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
|
||||
<Typography variant="body1">Passwords Match</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{formik.values.password1.length > 5 ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
<Typography variant="body1">At least 6 characters</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{contains_special_character(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
<Typography variant="body1">At least one special character</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
|
||||
{contains_number(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
<Typography variant="body1">At least one number</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid || formik.isSubmitting ||
|
||||
!contains_special_character(formik.values.password1)
|
||||
|| !contains_number(formik.values.password1)
|
||||
}
|
||||
|
||||
// type={'submit'}
|
||||
// loading={formik.isSubmitting}
|
||||
// disabled={
|
||||
// !formik.isValid || !formik.dirty || formik.isSubmitting
|
||||
// }
|
||||
>
|
||||
Set password
|
||||
</Button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
)}
|
||||
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetPassword;
|
||||
87
llm-fe/src/llm-fe/components/ThemeCard/ThemeCard.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Card, CardContent, Divider, Switch } from "@mui/material";
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import {
|
||||
useMaterialUIController,
|
||||
setDarkMode,
|
||||
} from "../../ui-kit/context";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import { PreferencesType } from "../../data";
|
||||
import { PreferenceContext } from "../../contexts/PreferencesContext";
|
||||
|
||||
type PreferencesValues = {
|
||||
order: boolean;
|
||||
}
|
||||
|
||||
const ThemeCard = ({}): JSX.Element => {
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
darkMode,
|
||||
} = controller;
|
||||
|
||||
const [order, setOrder] = useState<boolean>(true);
|
||||
const {updatePreferences} = useContext(PreferenceContext);
|
||||
|
||||
const handleConversationOrder = async ({order}: PreferencesValues): Promise<void> => {
|
||||
try{
|
||||
const {data, }: AxiosResponse<PreferencesType> = await axiosInstance.post('/conversation_preferences', {
|
||||
order: order
|
||||
})
|
||||
updatePreferences();
|
||||
}catch{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function getConversationOrder(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<PreferencesType> = await axiosInstance.get(`/conversation_preferences`);
|
||||
setOrder(data.order);
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getConversationOrder();
|
||||
}, [])
|
||||
|
||||
const handleDarkMode = () => setDarkMode(dispatch, !darkMode);
|
||||
const isThemeReady=false;
|
||||
return(
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Account Preferences
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
{isThemeReady ? (
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
|
||||
<MDTypography variant="h6">Light / Dark</MDTypography>
|
||||
<Switch checked={darkMode} onChange={handleDarkMode} disabled={false} />
|
||||
</MDBox>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
|
||||
<MDTypography variant="h6">Converastion Order</MDTypography>
|
||||
<Switch checked={order} onChange={() => {
|
||||
setOrder(!order);
|
||||
handleConversationOrder({order: !order})
|
||||
}} />
|
||||
</MDBox>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeCard;
|
||||
64
llm-fe/src/llm-fe/contexts/AccountContext.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { ReactNode, useState, createContext, useEffect, useContext } from "react"
|
||||
import { Account, AccountType } from "../data";
|
||||
import { AuthContext } from "./AuthContext";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../axiosApi";
|
||||
|
||||
type AccountProviderProps ={
|
||||
children? : ReactNode;
|
||||
}
|
||||
|
||||
type IAccountContext = {
|
||||
account: Account | undefined;
|
||||
setAccount: (account: Account | undefined) => void;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
account: undefined,
|
||||
setAccount: () => {}
|
||||
}
|
||||
|
||||
const AccountContext = createContext<IAccountContext>(initialValues);
|
||||
|
||||
const AccountProvider = ({children}: AccountProviderProps) => {
|
||||
const [account, setAccount] = useState<Account | undefined>(initialValues.account);
|
||||
const { authenticated, loading } = useContext(AuthContext);
|
||||
|
||||
async function getAccount (){
|
||||
const get_user_response: AxiosResponse<AccountType> = await axiosInstance.get('/user/get/')
|
||||
|
||||
const account: Account = new Account({
|
||||
email: get_user_response.data.email,
|
||||
first_name: get_user_response.data.first_name,
|
||||
last_name: get_user_response.data.last_name,
|
||||
is_company_manager: get_user_response.data.is_company_manager,
|
||||
has_signed_tos: get_user_response.data.has_signed_tos,
|
||||
company: {
|
||||
id: get_user_response.data.company?.id,
|
||||
name: get_user_response.data.company?.name,
|
||||
state: get_user_response.data.company?.state,
|
||||
zipcode: get_user_response.data.company?.zipcode,
|
||||
address: get_user_response.data.company?.address,
|
||||
}
|
||||
|
||||
});
|
||||
setAccount(account);
|
||||
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(!loading && authenticated){
|
||||
getAccount();
|
||||
|
||||
}
|
||||
|
||||
}, [authenticated])
|
||||
return (
|
||||
<AccountContext.Provider value={{account, setAccount}}>
|
||||
{children}
|
||||
</AccountContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { AccountContext, AccountProvider }
|
||||
67
llm-fe/src/llm-fe/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Token } from "@mui/icons-material";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { createContext, ReactNode, useState, useEffect } from "react"
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
type AuthProviderProps = {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
type IAuthContext = {
|
||||
authenticated: boolean;
|
||||
setAuthentication: (newState: boolean) => void;
|
||||
needsNewPassword: boolean;
|
||||
setNeedsNewPassword: (newState: boolean) => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
authenticated: false,
|
||||
setAuthentication: () => {},
|
||||
needsNewPassword: false,
|
||||
setNeedsNewPassword: () => {},
|
||||
loading: true,
|
||||
}
|
||||
|
||||
const AuthContext = createContext<IAuthContext>(initialValues);
|
||||
|
||||
|
||||
const AuthProvider = ({children}: AuthProviderProps) => {
|
||||
|
||||
|
||||
const [ authenticated, setAuthentication ] = useState(initialValues.authenticated);
|
||||
const [loading, setLoading] = useState(true); // Add a loading state
|
||||
|
||||
useEffect(() => {
|
||||
//console.log('we are in the auth provider')
|
||||
const accessToken = localStorage.getItem('access_token');
|
||||
if (accessToken) {
|
||||
const decodedToken = jwtDecode(accessToken)
|
||||
//console.log(decodedToken)
|
||||
if(decodedToken.exp){
|
||||
//console.log(decodedToken.exp * 1000)
|
||||
//console.log(Date.now())
|
||||
if (decodedToken.exp * 1000> Date.now()) {
|
||||
//console.log('We are setting that we are authenticated')
|
||||
setAuthentication(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
|
||||
}, [])
|
||||
//console.log(authenticated)
|
||||
const [ needsNewPassword, setNeedsNewPassword] = useState(initialValues.needsNewPassword)
|
||||
const navigation = useNavigate();
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ authenticated, setAuthentication, needsNewPassword, setNeedsNewPassword, loading}}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { AuthContext, AuthProvider }
|
||||
80
llm-fe/src/llm-fe/contexts/ConversationContext.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { createContext, ReactNode, useContext, useEffect, useState } from "react";
|
||||
import { Conversation, ConversationType } from "../data";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../axiosApi";
|
||||
import { AuthContext } from "./AuthContext";
|
||||
import { PreferenceContext } from "./PreferencesContext";
|
||||
|
||||
type ConversationProviderProps ={
|
||||
children? : ReactNode;
|
||||
}
|
||||
|
||||
type IConversationContext = {
|
||||
conversations: Conversation[];
|
||||
setConversations: (conversations: Conversation[]) => void;
|
||||
selectedConversation: number | undefined;
|
||||
setSelectedConversation: (conversation_id: number | undefined) => void;
|
||||
deleteConversation: (conversation_id: number | undefined) => void;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
conversations: [],
|
||||
setConversations: () => {},
|
||||
selectedConversation: undefined,
|
||||
setSelectedConversation: () => {},
|
||||
deleteConversation: () => {},
|
||||
}
|
||||
|
||||
const ConversationContext = createContext<IConversationContext>(initialValues);
|
||||
|
||||
const ConversationProvider = ({children}: ConversationProviderProps) => {
|
||||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||
const [selectedConversation, setSelectedConversation] = useState<number | undefined>(undefined);
|
||||
const { authenticated, loading } = useContext(AuthContext);
|
||||
const {preferencesUpdated} = useContext(PreferenceContext);
|
||||
|
||||
function deleteConversation(conversation_id: number | undefined){
|
||||
//console.log(`detele ${conversation_id}`)
|
||||
|
||||
try{
|
||||
axiosInstance.delete(`conversation_details`, {
|
||||
data: {'conversation_id':conversation_id}
|
||||
})
|
||||
// remove it from the list now
|
||||
setConversations(conversations.filter((conversation) => conversation.id !== conversation_id));
|
||||
|
||||
// if it the current selected one, update the selected conversation
|
||||
if (selectedConversation === conversation_id){
|
||||
setSelectedConversation(undefined)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}catch{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function GetConversations(){
|
||||
const {data, }: AxiosResponse<ConversationType[]> = await axiosInstance.get(`conversations`)
|
||||
setConversations(data.map((item) => new Conversation({
|
||||
id: item.id,
|
||||
title: item.title
|
||||
})))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(!loading && authenticated){
|
||||
GetConversations();
|
||||
}
|
||||
}, [selectedConversation, authenticated, preferencesUpdated])
|
||||
return(
|
||||
<ConversationContext.Provider value={{conversations, setConversations, selectedConversation, setSelectedConversation, deleteConversation}}>
|
||||
{children}
|
||||
</ConversationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { ConversationContext, ConversationProvider }
|
||||
143
llm-fe/src/llm-fe/contexts/MessageContext.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { createContext, ReactNode, useContext, useEffect, useRef, useState } from "react";
|
||||
import { AuthContext } from "./AuthContext";
|
||||
import { WebSocketContext } from "./WebSocketContext";
|
||||
import { AccountContext } from "./AccountContext";
|
||||
import { ConversationContext } from "./ConversationContext";
|
||||
import { ConversationPrompt, ConversationPromptType } from "../data";
|
||||
import { axiosInstance } from "../../axiosApi";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
type MessageProviderProps ={
|
||||
children? : ReactNode;
|
||||
}
|
||||
|
||||
type IMessageContext = {
|
||||
stateMessage: string;
|
||||
setStateMessage: (message: string) => void;
|
||||
conversationDetails: ConversationPrompt[];
|
||||
setConversationDetails: (conversationPrompts: ConversationPrompt[]) => void;
|
||||
isGeneratingMessage: boolean;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
stateMessage: '',
|
||||
setStateMessage: () => {},
|
||||
conversationDetails: [],
|
||||
setConversationDetails: () => {},
|
||||
isGeneratingMessage: false
|
||||
}
|
||||
|
||||
const MessageContext = createContext<IMessageContext>(initialValues);
|
||||
|
||||
const MessageProvider = ( {children}: MessageProviderProps) => {
|
||||
const { authenticated, loading } = useContext(AuthContext);
|
||||
const [subscribe, unsubscribe, socket, sendMessage]= useContext(WebSocketContext)
|
||||
const { account } = useContext(AccountContext)
|
||||
const {conversations, selectedConversation, setSelectedConversation} = useContext(ConversationContext);
|
||||
|
||||
const [stateMessage, setStateMessage] = useState<string>('')
|
||||
const [conversationDetails, setConversationDetails] = useState<ConversationPrompt[]>([])
|
||||
const [isGeneratingMessage, setIsGeneratingMessage] = useState<boolean>(false)
|
||||
|
||||
const messageRef = useRef('')
|
||||
const messageResponsePart = useRef(0);
|
||||
const conversationRef = useRef(conversationDetails)
|
||||
const selectedConversationRef = useRef<undefined | number>(undefined)
|
||||
|
||||
async function GetConversationDetails(){
|
||||
if(selectedConversation){
|
||||
|
||||
try{
|
||||
//setPromptProcessing(true)
|
||||
selectedConversationRef.current = selectedConversation;
|
||||
const {data, }: AxiosResponse<ConversationPromptType[]> = await axiosInstance.get(`conversation_details?conversation_id=${selectedConversation}`)
|
||||
|
||||
const tempConversations: ConversationPrompt[] = data.map((item) => new ConversationPrompt({
|
||||
message: item.message,
|
||||
user_created: item.user_created,
|
||||
created_timestamp: item.created_timestamp
|
||||
}))
|
||||
if(tempConversations.length === 1){
|
||||
// we need to add another card because this is the first message
|
||||
tempConversations.push(new ConversationPrompt({message: '', user_created:false}))
|
||||
}
|
||||
conversationRef.current = tempConversations
|
||||
setConversationDetails(tempConversations)
|
||||
|
||||
}finally{
|
||||
//setPromptProcessing(false)
|
||||
|
||||
}
|
||||
|
||||
}else{
|
||||
setConversationDetails([])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
GetConversationDetails();
|
||||
|
||||
}, [selectedConversation])
|
||||
|
||||
useEffect(() => {
|
||||
/* register a consistent channel name for identifing this chat messages */
|
||||
const channelName = `ACCOUNT_ID_${account?.email}`
|
||||
|
||||
/* subscribe to channel and register callback */
|
||||
subscribe(channelName, (message: string) => {
|
||||
/* when a message is received just add it to the UI */
|
||||
|
||||
if (message === 'END_OF_THE_STREAM_ENDER_GAME_42'){
|
||||
messageResponsePart.current = 0
|
||||
|
||||
conversationRef.current.pop()
|
||||
|
||||
//handleAssistantPrompt({prompt: messageRef.current})
|
||||
setConversationDetails([...conversationRef.current, new ConversationPrompt({message: `${messageRef.current}`, user_created:false})])
|
||||
console.log([...conversationRef.current, new ConversationPrompt({message: `${messageRef.current}`, user_created:false})])
|
||||
messageRef.current = ''
|
||||
setStateMessage('')
|
||||
setIsGeneratingMessage(false)
|
||||
}
|
||||
else if (message === 'START_OF_THE_STREAM_ENDER_GAME_42'){
|
||||
conversationRef.current = conversationDetails
|
||||
setIsGeneratingMessage(true)
|
||||
messageResponsePart.current = 2
|
||||
|
||||
}else if (message === 'CONVERSATION_ID'){
|
||||
setIsGeneratingMessage(true)
|
||||
messageResponsePart.current = 1
|
||||
}else{
|
||||
setIsGeneratingMessage(true)
|
||||
if (messageResponsePart.current === 1){
|
||||
// this has to do with the conversation id
|
||||
if(!selectedConversation){
|
||||
setSelectedConversation(Number(message))
|
||||
}
|
||||
}
|
||||
else if (messageResponsePart.current === 2){
|
||||
messageRef.current += message
|
||||
setStateMessage(messageRef.current)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return () => {
|
||||
/* unsubscribe from channel during cleanup */
|
||||
unsubscribe(channelName)
|
||||
}
|
||||
}, [account, subscribe, unsubscribe, conversationDetails])
|
||||
|
||||
|
||||
return(
|
||||
<MessageContext.Provider value={{stateMessage, setStateMessage, conversationDetails, setConversationDetails, isGeneratingMessage}}>
|
||||
{children}
|
||||
</MessageContext.Provider>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export { MessageContext, MessageProvider}
|
||||
33
llm-fe/src/llm-fe/contexts/PreferencesContext.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createContext, ReactNode, useState } from "react";
|
||||
|
||||
type PrefernceProviderProps ={
|
||||
children? : ReactNode;
|
||||
}
|
||||
|
||||
type IPreferenceContext = {
|
||||
updatePreferences: () => void;
|
||||
preferencesUpdated: boolean;
|
||||
}
|
||||
|
||||
const initialValues = {
|
||||
updatePreferences: () => {},
|
||||
preferencesUpdated: true,
|
||||
}
|
||||
|
||||
const PreferenceContext = createContext<IPreferenceContext>(initialValues);
|
||||
|
||||
const PreferenceProvider = ({children}: PrefernceProviderProps) => {
|
||||
const [preferencesUpdated, setPreferencesUpdated] = useState<boolean>(true);
|
||||
|
||||
function updatePreferences(){
|
||||
setPreferencesUpdated(!preferencesUpdated);
|
||||
}
|
||||
|
||||
return (
|
||||
<PreferenceContext.Provider value={{updatePreferences, preferencesUpdated}}>
|
||||
{children}
|
||||
</PreferenceContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { PreferenceContext, PreferenceProvider }
|
||||
111
llm-fe/src/llm-fe/contexts/WebSocketContext.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import { AccountContext } from "./AccountContext"
|
||||
import { AuthContext } from "./AuthContext";
|
||||
|
||||
const { useEffect, createContext, useRef, useState, useContext } = require("react")
|
||||
|
||||
|
||||
const WebSocketContext = createContext()
|
||||
|
||||
function WebSocketProvider({ children }) {
|
||||
const { authenticated, loading } = useContext(AuthContext);
|
||||
const ws = useRef(null)
|
||||
const [socket, setSocket] = useState(null)
|
||||
const channels = useRef({}) // maps each channel to the callback
|
||||
const { account, setAccount } = useContext(AccountContext)
|
||||
const [currentChannel, setCurrentChannel] = useState('')
|
||||
/* called from a component that registers a callback for a channel */
|
||||
const subscribe = (channel, callback) => {
|
||||
//console.log(`Subbing to ${channel}`)
|
||||
setCurrentChannel(channel)
|
||||
channels.current[channel] = callback
|
||||
}
|
||||
/* remove callback */
|
||||
const unsubscribe = (channel) => {
|
||||
delete channels.current[channel]
|
||||
}
|
||||
|
||||
const sendMessage = (message, conversation_id, file, fileType) => {
|
||||
if (socket && socket.readyState === WebSocket.OPEN){
|
||||
|
||||
if (file){
|
||||
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64File = reader.result?.toString().split(',')[1];
|
||||
if (base64File){
|
||||
const data = {
|
||||
message: message,
|
||||
conversation_id: conversation_id,
|
||||
email: account?.email,
|
||||
file: base64File,
|
||||
fileType: fileType,
|
||||
}
|
||||
socket.send(JSON.stringify(data))
|
||||
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}else{
|
||||
const data = {
|
||||
message: message,
|
||||
conversation_id: conversation_id,
|
||||
email: account?.email,
|
||||
file: null,
|
||||
fileType: null
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(data))
|
||||
}
|
||||
//socket.send(`${conversation_id} | ${message}`)
|
||||
|
||||
}else{
|
||||
console.log('Error sending message. WebSocket is not open')
|
||||
}
|
||||
|
||||
}
|
||||
useEffect(() => {
|
||||
/* WS initialization and cleanup */
|
||||
if (account){
|
||||
|
||||
|
||||
//ws.current = new WebSocket(`ws://127.0.0.1:8001/ws/chat_again/`);
|
||||
ws.current = new WebSocket('wss://chatbackend.aimloperations.com/ws/chat_again/')
|
||||
//ws.current = process.env.REACT_APP_BACKEND_WS_API_BASE_URL;
|
||||
|
||||
ws.current.onopen = () => { setSocket(ws.current); }
|
||||
ws.current.onclose = () => { }
|
||||
ws.current.onmessage = (message) => {
|
||||
const data = message.data
|
||||
// lookup for an existing chat in which this message belongs
|
||||
// if no chat is subscribed send message to generic channel
|
||||
const chatChannel = Object.entries(channels.current)[0][0]
|
||||
|
||||
if (channels.current[chatChannel]) {
|
||||
/* in chat component the subscribed channel is `MESSAGE_CREATE_${id}` */
|
||||
channels.current[chatChannel](data)
|
||||
} else {
|
||||
/* in notifications wrapper the subscribed channel is `MESSAGE_CREATE` */
|
||||
console.log('Error')
|
||||
// channels.current[type]?.(data)
|
||||
}
|
||||
}
|
||||
return () => { ws.current.close() }
|
||||
|
||||
}
|
||||
|
||||
|
||||
}, [account])
|
||||
|
||||
|
||||
|
||||
/* WS provider dom */
|
||||
/* subscribe and unsubscribe are the only required prop for the context */
|
||||
return (
|
||||
<WebSocketContext.Provider value={[subscribe, unsubscribe, socket, sendMessage]}>
|
||||
{children}
|
||||
</WebSocketContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { WebSocketContext, WebSocketProvider }
|
||||
42
llm-fe/src/llm-fe/csss/material-dashboard.min.css
vendored
Normal file
241
llm-fe/src/llm-fe/data.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/* Classes for the project */
|
||||
export interface ConversationPromptType {
|
||||
id: number,
|
||||
message: string,
|
||||
user_created: boolean,
|
||||
created_timestamp: Date,
|
||||
}
|
||||
|
||||
export class ConversationPrompt{
|
||||
id: number | undefined;
|
||||
message: string = '';
|
||||
user_created: boolean = false;
|
||||
created_timestamp: Date = new Date();
|
||||
// TODO: add a date time stamp
|
||||
|
||||
constructor(initializer?: any){
|
||||
if(!initializer) return;
|
||||
if (initializer.id) this.id = initializer.id;
|
||||
if (initializer.message) this.message = initializer.message;
|
||||
if (initializer.user_created) this.user_created = initializer.user_created;
|
||||
if (initializer.created_timestamp) this.created_timestamp = initializer.created_timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface CompanyType {
|
||||
id: number;
|
||||
name: string;
|
||||
state: string;
|
||||
zipcode: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export class Company {
|
||||
id: number | undefined;
|
||||
name: string = '';
|
||||
state: string = '';
|
||||
zipcode: number | undefined;
|
||||
address: string = '';
|
||||
|
||||
constructor(initializer?: any){
|
||||
if (!initializer) return;
|
||||
if (initializer.id) this.id = initializer.id;
|
||||
if (initializer.name) this.name = initializer.name;
|
||||
if (initializer.state) this.state = initializer.state;
|
||||
if (initializer.zipcode) this.zipcode = initializer.zipcode;
|
||||
if (initializer.address) this.address = initializer.address;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export interface ConversationType {
|
||||
id: number;
|
||||
title: string;
|
||||
conversationDetail: ConversationPrompt[];
|
||||
}
|
||||
|
||||
export class Conversation {
|
||||
id: number | undefined;
|
||||
title: string ='';
|
||||
conversationDetail: ConversationPrompt[] = [];
|
||||
account: Account | undefined;
|
||||
|
||||
constructor(initializer?: any){
|
||||
if(!initializer) return;
|
||||
if (initializer.id) this.id = initializer.id;
|
||||
if (initializer.title) this.title = initializer.title;
|
||||
if (initializer.conversationDetail) this.conversationDetail = initializer.conversationDetail;
|
||||
if (initializer.account) this.account = initializer.account;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AnnouncementType {
|
||||
status: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class Announcement {
|
||||
status: string = 'default';
|
||||
message: string ='';
|
||||
|
||||
constructor(initializer?: any){
|
||||
if (!initializer) return;
|
||||
if (initializer.status) this.status = initializer.status;
|
||||
if (initializer.message) this.message = initializer.message;
|
||||
}
|
||||
}
|
||||
|
||||
export interface FeedbackType {
|
||||
id: number;
|
||||
title: string;
|
||||
status: string;
|
||||
text: string;
|
||||
category: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class Feedback {
|
||||
id: number = 0;
|
||||
title: string = '';
|
||||
status: string = '';
|
||||
text: string = '';
|
||||
category: string = '';
|
||||
constructor(initializer?: any){
|
||||
if(!initializer) return;
|
||||
if (initializer.id) this.id = initializer.id;
|
||||
if (initializer.status) this.status = initializer.status;
|
||||
if (initializer.title) this.title = initializer.title;
|
||||
if (initializer.text) this.text = initializer.text;
|
||||
if (initializer.category) this.category = initializer.category;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserPromptAnalyticsType {
|
||||
month: string,
|
||||
you: number,
|
||||
others: number,
|
||||
all: number,
|
||||
}
|
||||
|
||||
export class UserPromptAnalytics {
|
||||
month: string = "";
|
||||
you: number = 0;
|
||||
others: number = 0;
|
||||
all: number = 0;
|
||||
constructor(initializer?: any){
|
||||
if(!initializer) return;
|
||||
if (initializer.id) this.month = initializer.month;
|
||||
if (initializer.id) this.you = initializer.you;
|
||||
if (initializer.id) this.others = initializer.others;
|
||||
if (initializer.id) this.all = initializer.all;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface UserConvesationAnalyticsType {
|
||||
month: string,
|
||||
you: number,
|
||||
others: number,
|
||||
all: number,
|
||||
}
|
||||
|
||||
export class UserConversationAnalytics {
|
||||
month: string = "";
|
||||
you: number = 0;
|
||||
others: number = 0;
|
||||
all: number = 0;
|
||||
constructor(initializer?: any){
|
||||
if(!initializer) return;
|
||||
if (initializer.id) this.month = initializer.month;
|
||||
if (initializer.id) this.you = initializer.you;
|
||||
if (initializer.id) this.others = initializer.others;
|
||||
if (initializer.id) this.all = initializer.all;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface CompanyUsageAnalyticsType {
|
||||
month: string,
|
||||
used: number,
|
||||
not_used: number,
|
||||
}
|
||||
|
||||
export class CompanyUsageAnalytics {
|
||||
month: string = "";
|
||||
used: number = 0;
|
||||
not_used: number = 0;
|
||||
constructor(initializer?: any){
|
||||
if(!initializer) return;
|
||||
if (initializer.id) this.month = initializer.month;
|
||||
if (initializer.id) this.used = initializer.used;
|
||||
if (initializer.id) this.not_used = initializer.not_used;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface AdminAnalyticsType {
|
||||
month: string,
|
||||
range: [number, number],
|
||||
avg: number,
|
||||
median: number,
|
||||
}
|
||||
|
||||
export class AdminAnalytics {
|
||||
month: string = "";
|
||||
range: [number, number] = [0, 0];
|
||||
avg: number = 0;
|
||||
median: number = 0;
|
||||
constructor(initializer?: any){
|
||||
if(!initializer) return;
|
||||
if (initializer.id) this.month = initializer.month;
|
||||
if (initializer.id) this.range = initializer.range;
|
||||
if (initializer.id) this.avg = initializer.avg;
|
||||
if (initializer.id) this.median = initializer.median;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface AccountType {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
role: string | undefined;
|
||||
company: Company | undefined;
|
||||
has_usable_password: boolean;
|
||||
is_company_manager: boolean;
|
||||
is_active: boolean;
|
||||
has_signed_tos: boolean;
|
||||
}
|
||||
|
||||
export interface PreferencesType {
|
||||
order: boolean;
|
||||
}
|
||||
|
||||
export class Account {
|
||||
email: string = '';
|
||||
first_name: string ='';
|
||||
last_name: string = '';
|
||||
role: string | undefined;
|
||||
company: Company | undefined;
|
||||
is_company_manager: boolean = false;
|
||||
has_password: boolean = false;
|
||||
is_active: boolean = false;
|
||||
has_signed_tos: boolean = false;
|
||||
constructor(initializer?: any){
|
||||
if (!initializer) return;
|
||||
if (initializer.email) this.email = initializer.email;
|
||||
if (initializer.first_name) this.first_name = initializer.first_name;
|
||||
if (initializer.is_company_manager) this.is_company_manager = initializer.is_company_manager;
|
||||
if (initializer.has_password) this.has_password = initializer.has_password;
|
||||
if (initializer.is_active) this.is_active = initializer.is_active;
|
||||
if (initializer.has_signed_tos) this.has_signed_tos = initializer.has_signed_tos;
|
||||
if (initializer.last_name) this.last_name = initializer.last_name;
|
||||
if (initializer.role) this.role = initializer.role;
|
||||
if (initializer.company) this.company = initializer.company;
|
||||
|
||||
}
|
||||
}
|
||||
183
llm-fe/src/llm-fe/mockData.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { Account, Announcement, Company, Conversation, ConversationPrompt } from "./data";
|
||||
|
||||
export const MOCK_ANNOUNCEMENTS: Announcement[] = [
|
||||
new Announcement({
|
||||
status: 'primary',
|
||||
message: 'Primary',
|
||||
}),
|
||||
new Announcement({
|
||||
status: 'warning',
|
||||
message: 'Warning',
|
||||
}),
|
||||
new Announcement({
|
||||
status: 'info',
|
||||
message: 'Info',
|
||||
})
|
||||
,
|
||||
new Announcement({
|
||||
status: 'light',
|
||||
message: 'Light',
|
||||
})
|
||||
,
|
||||
new Announcement({
|
||||
status: 'dark',
|
||||
message: 'Dark',
|
||||
})
|
||||
,
|
||||
new Announcement({
|
||||
status: 'success',
|
||||
message: 'success',
|
||||
})
|
||||
,
|
||||
new Announcement({
|
||||
status: 'danger',
|
||||
message: 'Danger',
|
||||
})
|
||||
,
|
||||
new Announcement({
|
||||
status: 'secondary',
|
||||
message: 'Secondary',
|
||||
})
|
||||
]
|
||||
|
||||
export const MOCK_COMPANY: Company[] = [
|
||||
new Company({
|
||||
id: 0,
|
||||
name: "Company A",
|
||||
state: 'IL',
|
||||
zipcode: 60189,
|
||||
address: '123 Something'
|
||||
}),
|
||||
new Company({
|
||||
id: 1,
|
||||
name: "Company B",
|
||||
state: 'OH',
|
||||
zipcode: 44512,
|
||||
address: '91235 Boardwalk'
|
||||
})
|
||||
]
|
||||
|
||||
export const MOCK_ACCOUNTS: Account[] = [
|
||||
new Account({
|
||||
email: 'rufus.t.firefly@newtonia.com',
|
||||
first_name: 'Rufus',
|
||||
last_name: 'Firefly',
|
||||
role: 'company manager',
|
||||
company: MOCK_COMPANY[0]
|
||||
|
||||
}),
|
||||
new Account({
|
||||
email: 'something@something.com',
|
||||
first_name: 'Billy Bob',
|
||||
last_name: 'Thorton',
|
||||
role: 'user',
|
||||
company: MOCK_COMPANY[0]
|
||||
}),
|
||||
new Account({
|
||||
email: 'mom@mi6.com',
|
||||
first_name: 'Mallory',
|
||||
last_name: 'Something',
|
||||
role: 'company manager',
|
||||
company: MOCK_COMPANY[1]
|
||||
|
||||
}),
|
||||
new Account({
|
||||
email: '007@mi6.com',
|
||||
first_name: 'James',
|
||||
last_name: 'Bond',
|
||||
role: 'user',
|
||||
company: MOCK_COMPANY[1]
|
||||
}),
|
||||
]
|
||||
|
||||
export const MOCK_CONVESATION_DETAIL: ConversationPrompt[] = [
|
||||
new ConversationPrompt({
|
||||
id: 0,
|
||||
message: "Hello, World!",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id:1,
|
||||
message: "Hello Dave",
|
||||
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id: 2,
|
||||
message: "Can you help me with a math problem",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id:3,
|
||||
message: "I cannot do that Dave",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id: 4,
|
||||
message: "Let's start over",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id:5,
|
||||
message: "Hello Dave",
|
||||
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id: 6,
|
||||
message: "Can you help me with a math problem, pretty please",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id:7,
|
||||
message: "Absolutely!",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id: 8,
|
||||
message: "What is 2 + 2?",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id: 9,
|
||||
message: "Dave, you didn't say please.",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id: 10,
|
||||
message: "Fine. Please tell me what is 2 + 2",
|
||||
}),
|
||||
new ConversationPrompt({
|
||||
id:11,
|
||||
message: "4. 2 + 2 = 4.",
|
||||
}),
|
||||
];
|
||||
|
||||
export const MOCK_CONVERSATION: Conversation[] = [
|
||||
new Conversation({
|
||||
id: 0,
|
||||
title: "First One",
|
||||
account: MOCK_ACCOUNTS[0]
|
||||
}),
|
||||
new Conversation({
|
||||
id: 1,
|
||||
title: "Math problem",
|
||||
account: MOCK_ACCOUNTS[0],
|
||||
conversationDetail: [
|
||||
...MOCK_CONVESATION_DETAIL.slice(0,4) // update this to be a slice
|
||||
],
|
||||
}),
|
||||
new Conversation({
|
||||
id: 2,
|
||||
title: "Math problem but nice",
|
||||
account: MOCK_ACCOUNTS[0],
|
||||
conversationDetail: [
|
||||
...MOCK_CONVESATION_DETAIL.slice(4,12) // update this to be a slice
|
||||
],
|
||||
}),
|
||||
new Conversation({
|
||||
id: 3,
|
||||
title: "Create Graph",
|
||||
account: MOCK_ACCOUNTS[0]
|
||||
}),
|
||||
new Conversation({
|
||||
id: 4,
|
||||
title: "Company Report",
|
||||
account: MOCK_ACCOUNTS[1]
|
||||
}),
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
/* add more mockk data*/
|
||||
373
llm-fe/src/llm-fe/pages/Account/Account.tsx
Normal file
@@ -0,0 +1,373 @@
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import Footer from '../../components/Footer/Footer';
|
||||
import Header from '../../components/Header/Header';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import Card from '../../components/Card/Card';
|
||||
import { Box, Button, CardContent, CardHeader, IconButton, InputAdornment, Modal, Paper, Switch, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, ToggleButton, Typography } from '@mui/material';
|
||||
import CustomTextField from '../../components/CustomTextField/CustomTextField';
|
||||
import { ErrorMessage, Field, Form, Formik, FormikContext } from 'formik';
|
||||
import { Account, AccountType } from '../../data';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Add, BusinessTwoTone, Delete, DeleteForever, Send } from '@mui/icons-material';
|
||||
import * as Yup from 'yup';
|
||||
import { initial } from 'lodash';
|
||||
|
||||
|
||||
type CompanyAccountLineProps = {
|
||||
user: Account
|
||||
handleUserUpdate: (email: string, field: string, value: string) => void
|
||||
}
|
||||
|
||||
const CompanyAccountLine = ({user, handleUserUpdate}: CompanyAccountLineProps): JSX.Element => {
|
||||
const {account} = useContext(AccountContext);
|
||||
|
||||
return(
|
||||
<TableRow key={user.email}>
|
||||
<TableCell>{user.email}</TableCell>
|
||||
<TableCell>{user.first_name}</TableCell>
|
||||
<TableCell>{user.last_name}</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.is_company_manager} onChange={(event) => handleUserUpdate(user.email, 'company_manager', event.target.value)} disabled={account?.email === user.email} />
|
||||
</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.is_active} onChange={(event) => handleUserUpdate(user.email, 'is_active', event.target.value)} />
|
||||
</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.has_password && user.has_signed_tos} onChange={(event) => handleUserUpdate(user.email, 'has_password', event.target.value)} disabled={!user.has_password} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton>
|
||||
<DeleteForever color="warning" onClick={() => handleUserUpdate(user.email, 'delete', '')} />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
type InviteValues = {
|
||||
email: string
|
||||
}
|
||||
const validationSchema = Yup.object().shape({
|
||||
email: Yup.string().email().required("This is requried")
|
||||
}
|
||||
)
|
||||
|
||||
type AddUserCardProps = {
|
||||
getCompanyUsers: ()=> void;
|
||||
}
|
||||
|
||||
const AddUserCard = ({getCompanyUsers}: AddUserCardProps): JSX.Element => {
|
||||
const initialValues = {'email': '',}
|
||||
const handleInvite = async ({email}: InviteValues, {resetForm}: any): Promise<void> => {
|
||||
try{
|
||||
await axiosInstance.post('/user/invite/', {
|
||||
'email': email
|
||||
})
|
||||
getCompanyUsers()
|
||||
|
||||
} catch{
|
||||
// put a message here
|
||||
}
|
||||
resetForm();
|
||||
|
||||
}
|
||||
return(
|
||||
<Card>
|
||||
<div className='card-header'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
|
||||
<h4 className='text-white font-weight-bold '>
|
||||
Add a user
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleInvite}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div className="row">
|
||||
<div className='col-12'>
|
||||
<Field
|
||||
name={"email"}
|
||||
fullWidth
|
||||
as={TextField}
|
||||
label={"Email"}
|
||||
errorstring={<ErrorMessage name={"prompt"}/>}
|
||||
size={'small'}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
margin={"dense"}
|
||||
variant={"outlined"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
startIcon={<Send/>}
|
||||
disabled={!formik.isValid || formik.isSubmitting}>
|
||||
|
||||
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
>
|
||||
|
||||
</Field>
|
||||
</div>
|
||||
{/* <div className='col-11'>
|
||||
<TextField
|
||||
label='Email'
|
||||
name='email'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.email}
|
||||
fullWidth={true}
|
||||
|
||||
variant={"outlined"} //enables special material-ui styling
|
||||
size={"small"}
|
||||
multiline={true}
|
||||
margin={"dense"}
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
<div className='col-1'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid}>Invite
|
||||
|
||||
</Button>
|
||||
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyManagerCard = ({}): JSX.Element => {
|
||||
const [users, setUsers] = useState<Account[]>([]);
|
||||
const {account}= useContext(AccountContext);
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
|
||||
const handleUserUpdate = async(email: string, field: string, value: string): Promise<void> => {
|
||||
//console.log(email, field, value)
|
||||
if(field === 'delete'){
|
||||
await axiosInstance.delete(`/company_users`, {
|
||||
data: {'email':email}
|
||||
|
||||
})
|
||||
}else {
|
||||
await axiosInstance.post(`/company_users`, {
|
||||
'email':email,
|
||||
'field': field,
|
||||
'value': value
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// get all of th edata again
|
||||
try{
|
||||
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getCompanyUsers(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
console.log(data)
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
has_signed_tos: item.has_signed_tos,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getCompanyUsers();
|
||||
}, [])
|
||||
console.log(users)
|
||||
|
||||
return(
|
||||
<>
|
||||
<Card>
|
||||
<div className='card-header'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
|
||||
<h4 className='text-white font-weight-bold '>
|
||||
Company Accounts
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<CardContent>
|
||||
<TableContainer component={Paper}>
|
||||
<Table >
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Email</TableCell>
|
||||
<TableCell>First Name</TableCell>
|
||||
<TableCell>Last Name</TableCell>
|
||||
<TableCell>Is Manager</TableCell>
|
||||
<TableCell>Is Active</TableCell>
|
||||
<TableCell>Has Password</TableCell>
|
||||
<TableCell>Delete</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users.map((user) => <CompanyAccountLine user={user} handleUserUpdate={handleUserUpdate} key={user.email}/>)}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AddUserCard getCompanyUsers={getCompanyUsers} />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
type AccountInformationProps = {
|
||||
account: Account | undefined
|
||||
}
|
||||
|
||||
const AccountInformation = ({account}: AccountInformationProps): JSX.Element => {
|
||||
|
||||
const updateAccount = async({}: AccountUpdateValues): Promise<void> => {
|
||||
//console.log('Need to update the account')
|
||||
|
||||
}
|
||||
return (
|
||||
|
||||
<Card>
|
||||
<div className='card-header'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
<h4 className='text-white font-weight-bold '>
|
||||
Account Information
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<CardContent>
|
||||
<Formik initialValues={{
|
||||
email: account ? account?.email : '',
|
||||
first_name: account ? account.first_name : '',
|
||||
last_name: account ? account.last_name : '',
|
||||
|
||||
}}
|
||||
onSubmit={updateAccount}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<div className='row'>
|
||||
|
||||
{/* <p>{ account?.first_name} {account?.last_name}</p>
|
||||
<p>{account?.email}</p> */}
|
||||
<div className='col-12'>
|
||||
<CustomTextField label='Email' name='email' changeHandler={(e) => {formik.setFieldValue('email', e.target.value)}} isMultline={false}/>
|
||||
</div>
|
||||
<div className='col-12'>
|
||||
<CustomTextField label='First Name' name='first_name' changeHandler={(e) => {formik.setFieldValue('first_name', e.target.value)}} isMultline={false}/>
|
||||
</div>
|
||||
<div className='col-12'>
|
||||
<CustomTextField label='Last Name' name='last_name' changeHandler={(e) => {formik.setFieldValue('last_name', e.target.value)}} isMultline={false}/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<Button type={'submit'}>Update Account</Button>
|
||||
|
||||
</Form>
|
||||
|
||||
)}
|
||||
|
||||
|
||||
</Formik>
|
||||
|
||||
|
||||
</CardContent>
|
||||
|
||||
<p>Something</p>
|
||||
</Card>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
type AccountUpdateValues = {
|
||||
email: string,
|
||||
}
|
||||
|
||||
const AccountPage = ({}): JSX.Element => {
|
||||
const { account } = useContext(AccountContext)
|
||||
console.log(account)
|
||||
return (
|
||||
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
|
||||
<Box
|
||||
sx={{ flexGrow: 1, p: 3, mt: 5, width: { sm: `calc(100% - ${0}px)`} }}
|
||||
position='fixed'
|
||||
>
|
||||
<div className='container-fluid'>
|
||||
<div className='row'>
|
||||
<div className='col-12' style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
}}>
|
||||
<Header />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <AccountInformation account={account}/> */}
|
||||
|
||||
{ account?.is_company_manager ?
|
||||
<CompanyManagerCard />
|
||||
:
|
||||
<Typography>Account and prompt information will be available soon</Typography>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountPage;
|
||||
292
llm-fe/src/llm-fe/pages/Account2/Account2.tsx
Normal file
@@ -0,0 +1,292 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout";
|
||||
import { AccountContext } from "../../contexts/AccountContext";
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import { Account, AccountType } from "../../data";
|
||||
import * as Yup from 'yup';
|
||||
import { Card, CardContent, Divider, IconButton, InputAdornment, Paper, Switch, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from "@mui/material";
|
||||
import { DeleteForever, Send } from "@mui/icons-material";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { ErrorMessage, Field, Form, Formik } from "formik";
|
||||
import MDButton from "../../ui-kit/components/MDButton";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import Header2 from "../../components/Header2/Header2";
|
||||
import Footer from "../../components/Footer/Footer";
|
||||
import ThemeCard from "../../components/ThemeCard/ThemeCard";
|
||||
|
||||
type AccountUpdateValues = {
|
||||
email: string,
|
||||
}
|
||||
|
||||
type AccountInformationProps = {
|
||||
account: Account | undefined
|
||||
}
|
||||
|
||||
type InviteValues = {
|
||||
email: string
|
||||
}
|
||||
const validationSchema = Yup.object().shape({
|
||||
email: Yup.string().email().required("This is requried")
|
||||
}
|
||||
)
|
||||
|
||||
type AddUserCardProps = {
|
||||
getCompanyUsers: ()=> void;
|
||||
}
|
||||
|
||||
type CompanyAccountLineProps = {
|
||||
user: Account
|
||||
handleUserUpdate: (email: string, field: string, value: string) => void
|
||||
}
|
||||
|
||||
const CompanyAccountLine = ({user, handleUserUpdate}: CompanyAccountLineProps): JSX.Element => {
|
||||
const {account} = useContext(AccountContext);
|
||||
|
||||
return(
|
||||
<TableRow key={user.email}>
|
||||
<TableCell><MDTypography> {user.email}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography> {user.first_name}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>{user.last_name}</MDTypography></TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.is_company_manager} onChange={(event) => handleUserUpdate(user.email, 'company_manager', event.target.value)} disabled={account?.email === user.email} />
|
||||
</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.is_active} onChange={(event) => handleUserUpdate(user.email, 'is_active', event.target.value)} />
|
||||
</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.has_password && user.has_signed_tos} onChange={(event) => handleUserUpdate(user.email, 'has_password', event.target.value)} disabled={!user.has_password} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton onClick={() => handleUserUpdate(user.email, 'delete', '')}>
|
||||
<DeleteForever color="warning" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const AddUserCard = ({getCompanyUsers}: AddUserCardProps): JSX.Element => {
|
||||
const initialValues = {'email': '',}
|
||||
const handleInvite = async ({email}: InviteValues, {resetForm}: any): Promise<void> => {
|
||||
try{
|
||||
await axiosInstance.post('/user/invite/', {
|
||||
'email': email
|
||||
})
|
||||
getCompanyUsers()
|
||||
|
||||
} catch{
|
||||
// put a message here
|
||||
}
|
||||
resetForm();
|
||||
|
||||
}
|
||||
return(
|
||||
<Card sx={{mt:1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Invite A User
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
|
||||
<CardContent>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleInvite}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div className="row">
|
||||
<div className='col-12'>
|
||||
<Field
|
||||
name={"email"}
|
||||
fullWidth
|
||||
as={TextField}
|
||||
label={"Email"}
|
||||
errorstring={<ErrorMessage name={"prompt"}/>}
|
||||
size={'small'}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
margin={"dense"}
|
||||
variant={"outlined"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
startIcon={<Send/>}
|
||||
disabled={!formik.isValid || formik.isSubmitting}>
|
||||
<></>
|
||||
|
||||
</MDButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
>
|
||||
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyManagerCard = ({}): JSX.Element => {
|
||||
const [users, setUsers] = useState<Account[]>([]);
|
||||
const {account}= useContext(AccountContext);
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
|
||||
const handleUserUpdate = async(email: string, field: string, value: string): Promise<void> => {
|
||||
//console.log(email, field, value)
|
||||
if(field === 'delete'){
|
||||
await axiosInstance.delete(`/company_users`, {
|
||||
data: {'email':email}
|
||||
|
||||
})
|
||||
}else {
|
||||
await axiosInstance.post(`/company_users`, {
|
||||
'email':email,
|
||||
'field': field,
|
||||
'value': value
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// get all of th edata again
|
||||
try{
|
||||
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getCompanyUsers(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
has_signed_tos: item.has_signed_tos,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getCompanyUsers();
|
||||
}, [])
|
||||
|
||||
return(
|
||||
<>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Company Accounts
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
|
||||
<CardContent>
|
||||
<TableContainer component={Paper}>
|
||||
<Table >
|
||||
<TableHead sx={{ display: "table-header-group" }}>
|
||||
<TableRow>
|
||||
<TableCell><MDTypography>Email</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>First Name</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Last Name</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Is Manager</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Is Active</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Has Password</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Delete</MDTypography></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users.map((user) => <CompanyAccountLine user={user} handleUserUpdate={handleUserUpdate} key={user.email}/>)}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AddUserCard getCompanyUsers={getCompanyUsers} />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const AccountPage =({}): JSX.Element => {
|
||||
const { account } = useContext(AccountContext)
|
||||
if(account?.is_company_manager){
|
||||
|
||||
return(
|
||||
<>
|
||||
<ThemeCard />
|
||||
<CompanyManagerCard />
|
||||
</>
|
||||
|
||||
)
|
||||
|
||||
}else{
|
||||
return(
|
||||
<>
|
||||
<ThemeCard />
|
||||
<MDTypography>Account and prompt information will be available soon</MDTypography>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Account2 = ({}): JSX.Element => {
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<AccountPage />
|
||||
<Footer />
|
||||
</MDBox>
|
||||
|
||||
|
||||
</PageWrapperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Account2
|
||||
257
llm-fe/src/llm-fe/pages/Analytics/Analytics.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
import { useContext, useEffect, useState } from "react"
|
||||
import Footer from "../../components/Footer/Footer"
|
||||
import Header2 from "../../components/Header2/Header2"
|
||||
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout"
|
||||
import MDBox from "../../ui-kit/components/MDBox"
|
||||
import { AccountContext } from "../../contexts/AccountContext"
|
||||
import { Card, CardContent, Divider } from "@mui/material"
|
||||
import MDTypography from "../../ui-kit/components/MDTypography"
|
||||
import { Area, Bar, BarChart, ComposedChart, Legend, Line, LineChart, Rectangle, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"
|
||||
import { axiosInstance } from "../../../axiosApi"
|
||||
import { AxiosResponse } from "axios"
|
||||
import { AdminAnalytics, AdminAnalyticsType, CompanyUsageAnalytics, CompanyUsageAnalyticsType, UserConversationAnalytics, UserConvesationAnalyticsType, UserPromptAnalytics, UserPromptAnalyticsType } from "../../data"
|
||||
|
||||
const UserPromptAnalyticsCard = ({}): JSX.Element => {
|
||||
const [data, setData] = useState<UserPromptAnalytics[]>([])
|
||||
|
||||
async function getUserPromptAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<UserPromptAnalyticsType[]> = await axiosInstance.get(`/analytics/user_prompts/`);
|
||||
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
getUserPromptAnalytics();
|
||||
}, [])
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Prompt Usage
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={data}>
|
||||
<XAxis />
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Bar dataKey="you" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
|
||||
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
<Bar dataKey="all" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const UserConversationAnalyticsCard = ({}): JSX.Element => {
|
||||
const [data, setData] = useState<UserConversationAnalytics[]>([])
|
||||
async function getUserConversationAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<UserConvesationAnalyticsType[]> = await axiosInstance.get(`/analytics/user_conversations/`);
|
||||
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
getUserConversationAnalytics();
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Conversation Usage
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<ResponsiveContainer>
|
||||
|
||||
|
||||
<BarChart data={data}>
|
||||
<XAxis />
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Bar dataKey="you" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
|
||||
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
<Bar dataKey="all" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyUsageAnalyticsCard = ({}): JSX.Element => {
|
||||
const [data, setData] = useState<CompanyUsageAnalytics[]>([])
|
||||
async function getCompanyUsageAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<CompanyUsageAnalyticsType[]> = await axiosInstance.get(`/analytics/company_usage/`);
|
||||
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
getCompanyUsageAnalytics();
|
||||
}, [])
|
||||
// const exampleData = [
|
||||
// {
|
||||
// month: 'Feb',
|
||||
// used: 10,
|
||||
// not_used: 5
|
||||
// },
|
||||
// {
|
||||
// month: 'Mar',
|
||||
// used: 7,
|
||||
// not_used: 8
|
||||
// },
|
||||
// {
|
||||
// month: 'Apr',
|
||||
// used: 15,
|
||||
// not_used: 0
|
||||
// },
|
||||
// ]
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Account Usage
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={data}>
|
||||
<XAxis dataKey="month"/>
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Tooltip />
|
||||
<Bar dataKey="used" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
|
||||
<Bar dataKey="not_used" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const AdminAnalyticsCard = ({}): JSX.Element => {
|
||||
const [data, setData] = useState<AdminAnalytics[]>([])
|
||||
async function getAdminAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<AdminAnalyticsType[]> = await axiosInstance.get(`/analytics/admin/`);
|
||||
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
getAdminAnalytics();
|
||||
}, [])
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Response Times
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<ResponsiveContainer>
|
||||
<ComposedChart data={data}>
|
||||
<XAxis dataKey="month"/>
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Tooltip />
|
||||
<Area
|
||||
dataKey="range"
|
||||
dot={false}
|
||||
connectNulls
|
||||
activeDot={false}
|
||||
/>
|
||||
<Line type="natural" dataKey="avg" connectNulls />
|
||||
<Line type="monotone" dataKey="median" connectNulls />
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const AnalyticsInner =({}): JSX.Element => {
|
||||
const { account } = useContext(AccountContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
<div className='col-6 col-xs-12'>
|
||||
<UserConversationAnalyticsCard />
|
||||
|
||||
</div>
|
||||
<div className='col-6 col-xs-12'>
|
||||
<UserPromptAnalyticsCard />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{account?.is_company_manager ? <CompanyUsageAnalyticsCard /> : <></>}
|
||||
{account?.email === "ryan+admin@aimloperations.com" ? <AdminAnalyticsCard /> : <></>}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const AnalyticsPage = ({}): JSX.Element => {
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<AnalyticsInner />
|
||||
<Footer />
|
||||
</MDBox>
|
||||
</PageWrapperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnalyticsPage;
|
||||
184
llm-fe/src/llm-fe/pages/AsyncDashboard/AsyncDashboard.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useRef, useState } from 'react';
|
||||
import Footer from '../../components/Footer/Footer';
|
||||
import Header from '../../components/Header/Header';
|
||||
import { Announcement, AnnouncementType, Conversation, ConversationType } from '../../data';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import { JsxEmit } from 'typescript';
|
||||
import AsyncChat from '../../components/AsyncChat/AsyncChat';
|
||||
import ConversationLog from '../../components/ConversationLog/ConversationLog';
|
||||
import { Col, Container, Row } from 'react-bootstrap';
|
||||
import { Box, Drawer, Typography } from '@mui/material';
|
||||
import ConversationDrawer from '../../components/ConversationDrawer/ConversationDrawer';
|
||||
interface ConfirmEntryProps {
|
||||
status: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const drawerWidth = 320;
|
||||
|
||||
const AnnouncementBadge = ({status, message}: PropsWithChildren<ConfirmEntryProps>): JSX.Element => {
|
||||
return(
|
||||
<div className={`alert alert-${status}`}>
|
||||
{ message }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const AsyncDashboard = ({}): JSX.Element => {
|
||||
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
|
||||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||
const [selectedConversation, setSelectedConversation] = useState<number | undefined>(undefined);
|
||||
const [reconnectAttempts, setReconnectAttempts] = useState<number>(0);
|
||||
const maxReconnectAttempts = 5;
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
|
||||
const messageResponse = useRef('');
|
||||
|
||||
async function GetAnnouncements(){
|
||||
const response: AxiosResponse<AnnouncementType[]> = await axiosInstance.get('announcment/get/')
|
||||
setAnnouncement(response.data.map((status, message) => new Announcement({
|
||||
status: status,
|
||||
message: message
|
||||
})))
|
||||
|
||||
}
|
||||
|
||||
async function GetConversations(){
|
||||
const {data, }: AxiosResponse<ConversationType[]> = await axiosInstance.get(`conversations`)
|
||||
//console.log(data)
|
||||
setConversations(data.map((item) => new Conversation({
|
||||
id: item.id,
|
||||
title: item.title
|
||||
})))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
GetAnnouncements();
|
||||
GetConversations();
|
||||
}, [selectedConversation])
|
||||
|
||||
const handleDrawerClose = () => {
|
||||
setIsClosing(true);
|
||||
setMobileOpen(false);
|
||||
};
|
||||
|
||||
const handleDrawerTransitionEnd = () => {
|
||||
setIsClosing(false);
|
||||
};
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
if (!isClosing) {
|
||||
setMobileOpen(!mobileOpen);
|
||||
}
|
||||
};
|
||||
|
||||
function deleteConversation(conversation_id: number | undefined){
|
||||
//console.log(`detele ${conversation_id}`)
|
||||
try{
|
||||
axiosInstance.delete(`conversation_details`, {
|
||||
data: {'conversation_id':conversation_id}
|
||||
})
|
||||
// remove it from the list now
|
||||
setConversations(conversations.filter((conversation) => conversation.id !== conversation_id));
|
||||
|
||||
// if it the current selected one, update the selected conversation
|
||||
if (selectedConversation === conversation_id){
|
||||
setSelectedConversation(undefined)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}catch{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const conversationTitle = conversations.find(item => item.id === selectedConversation)?.title ?? 'New Conversation';
|
||||
return(<>
|
||||
<Box>
|
||||
|
||||
|
||||
<Box component="nav"
|
||||
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}>
|
||||
<Drawer
|
||||
variant='temporary'
|
||||
open={mobileOpen}
|
||||
onTransitionEnd={handleDrawerTransitionEnd}
|
||||
onClose={handleDrawerClose}
|
||||
ModalProps={{
|
||||
keepMounted: true
|
||||
}}
|
||||
sx = {{
|
||||
display: { xs: 'block', sm: 'none' },
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
||||
}}
|
||||
>
|
||||
<ConversationDrawer conversations={conversations}
|
||||
setSelectConversation={setSelectedConversation}
|
||||
deleteConversation={deleteConversation}
|
||||
selectedConversation={selectedConversation}
|
||||
// setConversations={setConversations}
|
||||
/>
|
||||
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: 'none', sm: 'block' },
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
||||
}}
|
||||
open
|
||||
>
|
||||
<ConversationDrawer conversations={conversations}
|
||||
setSelectConversation={setSelectedConversation}
|
||||
deleteConversation={deleteConversation}
|
||||
selectedConversation={selectedConversation}
|
||||
// setConversations={setConversations}
|
||||
/>
|
||||
|
||||
</Drawer>
|
||||
|
||||
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
|
||||
<Header drawerWidth={drawerWidth} handleDrawerToggle={handleDrawerToggle}/>
|
||||
<AsyncChat drawerWidth={drawerWidth} selectedConversation={selectedConversation} conversationTitle={conversationTitle} setConversations={setConversations} conversations={conversations} setSelectedConversation={setSelectedConversation}/>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
</>)
|
||||
|
||||
// return(
|
||||
// <>
|
||||
// <Header />
|
||||
// <Container fluid>
|
||||
// <Row>
|
||||
// <Col xs={3} className="sidebar">
|
||||
// <Typography variant="h6">Conversations</Typography>
|
||||
// <ConversationLog conversations={conversations} setSelectConversation={setSelectedConversation} deleteConversation={deleteConversation} setConversations={setConversations}/>
|
||||
|
||||
// </Col>
|
||||
// <Col xs={9} className="main-content">
|
||||
// <Box className="message-box">
|
||||
// {/* {messages.map((msg, index) => (
|
||||
// <MessageCard key={index} message={msg} />
|
||||
// ))} */}
|
||||
// <AsyncChat selectedConversation={selectedConversation} conversationTitle={conversationTitle} setConversations={setConversations} conversations={conversations} setSelectedConversation={setSelectedConversation}/>
|
||||
// </Box>
|
||||
// </Col>
|
||||
// </Row>
|
||||
// </Container>
|
||||
|
||||
// <Footer />
|
||||
// </>
|
||||
// )
|
||||
}
|
||||
|
||||
export default AsyncDashboard;
|
||||
287
llm-fe/src/llm-fe/pages/AsyncDashboard2/AsyncDashboard2.tsx
Normal file
@@ -0,0 +1,287 @@
|
||||
import { Card, CardContent, Divider, InputAdornment, } from "@mui/material"
|
||||
|
||||
import DashboardLayout from "../../ui-kit/examples/LayoutContainers/DashboardLayout"
|
||||
|
||||
import MDBox from "../../ui-kit/components/MDBox"
|
||||
import { useMaterialUIController } from "../../ui-kit/context"
|
||||
import { useContext, useEffect, useRef, useState } from "react"
|
||||
|
||||
// Images
|
||||
import MDTypography from "../../ui-kit/components/MDTypography"
|
||||
import { CardFooter } from "react-bootstrap"
|
||||
import MDInput from "../../ui-kit/components/MDInput"
|
||||
import { ErrorMessage, Field, Form, Formik } from "formik"
|
||||
import MDButton from "../../ui-kit/components/MDButton"
|
||||
import { AttachFile, Send } from "@mui/icons-material"
|
||||
import Header2 from "../../components/Header2/Header2"
|
||||
import DashboardWrapperLayout from "../../components/DashboardWrapperLayout/DashboardWrapperLayout"
|
||||
import * as Yup from 'yup';
|
||||
import { Announcement, AnnouncementType, Conversation, ConversationPrompt, ConversationPromptType, ConversationType } from "../../data"
|
||||
import { axiosInstance } from "../../../axiosApi"
|
||||
import { AxiosResponse } from "axios"
|
||||
import { ConversationContext } from "../../contexts/ConversationContext"
|
||||
import Markdown from "markdown-to-jsx"
|
||||
import ConversationDetailCard from "../../components/ConversationDetailCard/ConversationDetailCard"
|
||||
import { WebSocketContext } from "../../contexts/WebSocketContext"
|
||||
import { AccountContext } from "../../contexts/AccountContext"
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Footer from "../../components/Footer/Footer"
|
||||
import { MessageContext } from "../../contexts/MessageContext"
|
||||
import CustomSelect, { CustomSelectItem } from "../../components/CustomSelect/CustomSelect"
|
||||
|
||||
type RenderMessageProps= {
|
||||
response: string
|
||||
index: number
|
||||
};
|
||||
|
||||
type AsyncChatProps = {
|
||||
selectedConversation: number | undefined
|
||||
conversationTitle: string
|
||||
conversations: Conversation[]
|
||||
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>
|
||||
setSelectedConversation: React.Dispatch<React.SetStateAction<number | undefined>>
|
||||
drawerWidth: number;
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
prompt: Yup.string().min(1, "Need to have at least one character").required("This is requried")
|
||||
}
|
||||
)
|
||||
|
||||
const VisuallyHiddenInput = styled('input')({
|
||||
clip: 'rect(0 0 0 0)',
|
||||
clipPath: 'inset(50%)',
|
||||
height: 1,
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
width: 1,
|
||||
});
|
||||
|
||||
type PromptValues = {
|
||||
prompt: string;
|
||||
file: Blob | null;
|
||||
fileType: string | null;
|
||||
modelName: string;
|
||||
|
||||
};
|
||||
|
||||
const models: CustomSelectItem[] = [
|
||||
{
|
||||
label: "General",
|
||||
value: "GENERAL"
|
||||
},
|
||||
{
|
||||
label: "Code",
|
||||
value: "CODE"
|
||||
},
|
||||
{
|
||||
label: "Reasoning",
|
||||
value: "REASONING"
|
||||
}
|
||||
];
|
||||
|
||||
const AlwaysScrollToBottom = (): JSX.Element => {
|
||||
const elementRef = useRef<any>();
|
||||
useEffect(() => elementRef.current.scrollIntoView());
|
||||
return <div ref={elementRef} />;
|
||||
};
|
||||
|
||||
const AsyncDashboardInner =({}): JSX.Element => {
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
darkMode,
|
||||
} = controller;
|
||||
const buttonColor = darkMode? 'dark' : 'light';
|
||||
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
|
||||
const [subscribe, unsubscribe, socket, sendMessage] = useContext(WebSocketContext)
|
||||
const { account } = useContext(AccountContext)
|
||||
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const {conversations, selectedConversation, setSelectedConversation} = useContext(ConversationContext);
|
||||
|
||||
|
||||
const {conversationDetails, setConversationDetails, stateMessage, isGeneratingMessage} = useContext(MessageContext);
|
||||
|
||||
const conversationRef = useRef(conversationDetails)
|
||||
|
||||
|
||||
|
||||
|
||||
async function GetAnnouncements(){
|
||||
const response: AxiosResponse<AnnouncementType[]> = await axiosInstance.get('announcment/get/')
|
||||
setAnnouncement(response.data.map((status, message) => new Announcement({
|
||||
status: status,
|
||||
message: message
|
||||
})))
|
||||
|
||||
}
|
||||
|
||||
const conversationTitle = conversations.find(item => item.id === selectedConversation)?.title ?? 'New Conversation';
|
||||
const colorName = darkMode ? 'light' : 'dark';
|
||||
|
||||
|
||||
const handlePromptSubmit = async ({prompt, file, fileType, modelName}: PromptValues, {resetForm}: any): Promise<void> => {
|
||||
|
||||
// send the prompt to be saved
|
||||
|
||||
try{
|
||||
const tempConversations: ConversationPrompt[] = [...conversationDetails, new ConversationPrompt({message: prompt, user_created:true}), new ConversationPrompt({message: '', user_created:false})]
|
||||
|
||||
conversationRef.current = tempConversations
|
||||
|
||||
setConversationDetails(tempConversations)
|
||||
// TODO: add the file here
|
||||
|
||||
sendMessage(prompt, selectedConversation, file, fileType)
|
||||
resetForm();
|
||||
|
||||
|
||||
}catch(e){
|
||||
console.log(`error ${e}`)
|
||||
// TODO: make this user friendly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return(
|
||||
<DashboardLayout>
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:5}}>
|
||||
|
||||
</MDBox>1
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3" >
|
||||
{conversationTitle}
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<>
|
||||
{conversationDetails.length > 0 ?
|
||||
conversationDetails.map((convo_detail) =>
|
||||
convo_detail.message.length >0 ?
|
||||
<ConversationDetailCard message={convo_detail.message} user_created={convo_detail.user_created} key={convo_detail.id}/> :
|
||||
<ConversationDetailCard message={stateMessage} user_created={convo_detail.user_created} key={convo_detail.id}/>
|
||||
)
|
||||
:
|
||||
<Markdown className='text-center' color='inherit'>Either select a previous conversation on start a new one.</Markdown>
|
||||
}
|
||||
<AlwaysScrollToBottom/>
|
||||
</>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardFooter>
|
||||
<Formik
|
||||
initialValues={{
|
||||
prompt: '',
|
||||
file: null,
|
||||
fileType: null,
|
||||
modelName: '',
|
||||
}}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handlePromptSubmit}
|
||||
>
|
||||
{(formik)=>
|
||||
<Form>
|
||||
|
||||
|
||||
<Field
|
||||
name={"prompt"}
|
||||
fullWidth
|
||||
as={MDInput}
|
||||
label={'Prompt'}
|
||||
errorstring={<ErrorMessage name={'prompt'}/>}
|
||||
size={'large'}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
variant={"outlined"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
{/* <CustomSelect
|
||||
name="category"
|
||||
items={models}
|
||||
label="Category"
|
||||
required
|
||||
|
||||
/> */}
|
||||
<MDButton
|
||||
component="label"
|
||||
startIcon={<AttachFile/>}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
color={buttonColor}
|
||||
>
|
||||
<VisuallyHiddenInput
|
||||
type="file"
|
||||
accept='.csv,.xlsx,.txt'
|
||||
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
//console.log(file)
|
||||
if (file) {
|
||||
formik.setFieldValue('file',file)
|
||||
formik.setFieldValue('fileType',file.type)
|
||||
} else {
|
||||
formik.setFieldValue('file',null)
|
||||
formik.setFieldValue('fileType',null)
|
||||
}
|
||||
|
||||
}}
|
||||
/>
|
||||
</MDButton>
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
startIcon={<Send />}
|
||||
disabled={!formik.isValid}
|
||||
color={buttonColor}
|
||||
>
|
||||
<></>
|
||||
</MDButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
|
||||
|
||||
>
|
||||
</Field>
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
|
||||
|
||||
{/* <MDInput
|
||||
label="Prompt"
|
||||
/> */}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Footer />
|
||||
</MDBox>
|
||||
|
||||
</DashboardLayout>
|
||||
)
|
||||
}
|
||||
|
||||
const AsyncDashboard2 = ({}): JSX.Element => {
|
||||
return(
|
||||
|
||||
|
||||
|
||||
<DashboardWrapperLayout>
|
||||
<AsyncDashboardInner />
|
||||
</DashboardWrapperLayout>
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default AsyncDashboard2
|
||||
296
llm-fe/src/llm-fe/pages/Dashboard/Dashboard.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import { MOCK_ACCOUNTS, MOCK_ANNOUNCEMENTS, MOCK_CONVERSATION } from '../../mockData';
|
||||
import Footer from '../../components/Footer/Footer';
|
||||
import Header from '../../components/Header/Header';
|
||||
import { Account, Announcement, AnnouncementType, Conversation, ConversationPrompt, ConversationPromptType, ConversationType } from '../../data';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { object, ref, string } from 'yup';
|
||||
import { Button, ButtonBase, Card, CardContent, CardHeader } from '@mui/material';
|
||||
import CustomTextField from '../../components/CustomTextField/CustomTextField';
|
||||
import ConversationLog from '../../components/ConversationLog/ConversationLog';
|
||||
import ConversationDetailCard from '../../components/ConversationDetailCard/ConversationDetailCard';
|
||||
|
||||
export type PromptValues = {
|
||||
prompt: string;
|
||||
};
|
||||
const validationSchema = object().shape({
|
||||
|
||||
})
|
||||
|
||||
interface ConfirmEntryProps {
|
||||
status: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const AnnouncementBadge = ({status, message}: PropsWithChildren<ConfirmEntryProps>): JSX.Element => {
|
||||
console.log(message)
|
||||
return(
|
||||
<div className={`alert alert-${status}`}>
|
||||
{ message }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type ConversationDetailProps = {
|
||||
selectedConversation: number | undefined
|
||||
conversationTitle: string | undefined
|
||||
conversations: Conversation[]
|
||||
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>
|
||||
setSelectedConversation: React.Dispatch<React.SetStateAction<number | undefined>>
|
||||
};
|
||||
|
||||
interface IResponseObject {
|
||||
role: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const ConversationDetail = ({selectedConversation, conversationTitle,conversations, setConversations, setSelectedConversation}: ConversationDetailProps): JSX.Element => {
|
||||
const [conversationDetails, setConversationDetails] = useState<ConversationPrompt[]>([])
|
||||
const [promptProcessing, setPromptProcessing] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
// now we want to stream the new response
|
||||
const eventSource = new EventSource("http://127.0.0.1:8000/api/streamed_response")
|
||||
try{
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
const responseObject = JSON.parse(event.data)
|
||||
console.log(responseObject)
|
||||
setResponse((prev: IResponseObject)=>{
|
||||
const responseRole = responseObject["role"] || "";
|
||||
const responseContent = responseObject["content"] || "";
|
||||
const combinedObject = {
|
||||
role: prev.role + responseRole,
|
||||
content: prev.content + responseContent
|
||||
}
|
||||
return combinedObject
|
||||
})
|
||||
}
|
||||
eventSource.onerror = (error) => {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
}finally{
|
||||
eventSource.close()
|
||||
}
|
||||
}, [])
|
||||
|
||||
async function GetConversationDetails(){
|
||||
if(selectedConversation){
|
||||
|
||||
try{
|
||||
setPromptProcessing(true)
|
||||
const {data, }: AxiosResponse<ConversationPromptType[]> = await axiosInstance.get(`conversation_details?conversation_id=${selectedConversation}`)
|
||||
console.log(data)
|
||||
setConversationDetails(data.map((item) => new ConversationPrompt({
|
||||
message: item.message,
|
||||
user_created: item.user_created,
|
||||
created_timestamp: item.created_timestamp
|
||||
})))
|
||||
|
||||
}finally{
|
||||
setPromptProcessing(false)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
GetConversationDetails();
|
||||
}, [selectedConversation])
|
||||
|
||||
const [response, setResponse] = useState<IResponseObject>({role: '', content: ''})
|
||||
|
||||
const handlePromptSubmit = async ({prompt}: PromptValues): Promise<void> => {
|
||||
setConversationDetails([...conversationDetails, new ConversationPrompt({message: prompt, user_created:true})])
|
||||
try{
|
||||
setPromptProcessing(true)
|
||||
if(!selectedConversation){
|
||||
const {data, }: AxiosResponse<ConversationType> = await axiosInstance.post('/conversations', {
|
||||
prompt: prompt
|
||||
})
|
||||
// add the conversation to the list
|
||||
setConversations([...conversations, new Conversation({title: data.title, id: data.id})])
|
||||
// set the conversation as the selected one
|
||||
// TODO
|
||||
setSelectedConversation(data.id)
|
||||
|
||||
|
||||
}
|
||||
console.log(selectedConversation)
|
||||
const {data }: AxiosResponse<ConversationPromptType> = await axiosInstance.post('/conversation_details', {
|
||||
prompt: prompt,
|
||||
conversation_id: selectedConversation
|
||||
})
|
||||
console.log(data)
|
||||
|
||||
setConversationDetails([...conversationDetails, new ConversationPrompt({
|
||||
message: data.message,
|
||||
user_created: data.user_created,
|
||||
created_timestamp: data.created_timestamp
|
||||
})] )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}finally{
|
||||
setPromptProcessing(false)
|
||||
}
|
||||
// try{
|
||||
|
||||
// setConversationDetails([...conversationDetails, new ConversationPrompt({
|
||||
// message: prompt,
|
||||
// user_created: true,
|
||||
// })])
|
||||
// const {data, }: AxiosResponse<ConversationPromptType[]> = await axiosInstance.post('/conversation_details', {
|
||||
// prompt: prompt,
|
||||
// conversation_id: selectedConversation ? selectedConversation : -1
|
||||
// })
|
||||
// setConversationDetails(data.map((item) => new ConversationPrompt({
|
||||
// message: item.message,
|
||||
// user_created: item.user_created,
|
||||
// created_timestamp: item.created_timestamp
|
||||
// })))
|
||||
|
||||
// }catch(error){
|
||||
// console.log(error)
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// we have conversation details here
|
||||
|
||||
|
||||
return (
|
||||
<div className='col-lg-8 col-md-6 col-sm-12 right-column'>
|
||||
<div className="card" style= {{height: 'auto'}}>
|
||||
<div className='card-header'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
<h5 className='text-white'>
|
||||
{selectedConversation ? conversationTitle : 'Conversation Detail'}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{selectedConversation ?
|
||||
conversationDetails.map((convo_detail) => <ConversationDetailCard message={convo_detail.message} user_created={convo_detail.user_created}/>)
|
||||
:
|
||||
<p>Either select a previous conversation on start a new one.</p>
|
||||
}
|
||||
</div>
|
||||
<div className='card-footer'>
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
prompt: '',
|
||||
}}
|
||||
onSubmit={handlePromptSubmit}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div className='row' style={{
|
||||
position: 'sticky',
|
||||
bottom: 0
|
||||
}}>
|
||||
<div className='col-10'>
|
||||
<CustomTextField label='Prompt' name='prompt' changeHandler={(e) => formik.setFieldValue('prompt',e.target.value)} isMultline={true}/>
|
||||
|
||||
|
||||
</div>
|
||||
<div className='col-2'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
disabled={promptProcessing}
|
||||
>Send
|
||||
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
}
|
||||
|
||||
</Formik>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// assume that we are using the first account at this point
|
||||
|
||||
const Dashboard = ({}): JSX.Element => {
|
||||
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
|
||||
const { account, setAccount} = useContext(AccountContext);
|
||||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||
const [selectedConversation, setSelectedConversation] = useState<number | undefined>(undefined);
|
||||
|
||||
|
||||
|
||||
async function GetAnnouncements(){
|
||||
const response: AxiosResponse<AnnouncementType[]> = await axiosInstance.get('announcment/get/')
|
||||
setAnnouncement(response.data.map((status, message) => new Announcement({
|
||||
status: status,
|
||||
message: message
|
||||
})))
|
||||
|
||||
}
|
||||
|
||||
async function GetConversations(){
|
||||
const {data, }: AxiosResponse<ConversationType[]> = await axiosInstance.get(`conversations`)
|
||||
setConversations(data.map((item) => new Conversation({
|
||||
id: item.id,
|
||||
title: item.title
|
||||
})))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
GetAnnouncements();
|
||||
GetConversations();
|
||||
}, [selectedConversation])
|
||||
|
||||
|
||||
const conversationTitle = selectedConversation ? conversations.find(item => item.id === selectedConversation)?.title : 'New Conversation';
|
||||
|
||||
return (
|
||||
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
|
||||
<div className='container-fluid'>
|
||||
<div className='row'>
|
||||
<div className='col-12' style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
}}>
|
||||
<Header />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ announcements.map((x) => <AnnouncementBadge status={x.status} message={x.message} />)}
|
||||
<div className='row flex-fill'>
|
||||
{/* <ConversationLog conversations={conversations} setSelectConversation={setSelectedConversation}/> */}
|
||||
<ConversationDetail selectedConversation={selectedConversation} conversationTitle={conversationTitle} setConversations={setConversations} conversations={conversations} setSelectedConversation={setSelectedConversation}/>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
296
llm-fe/src/llm-fe/pages/FeedbackPage/FeedbackPage.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import Footer from '../../components/Footer/Footer';
|
||||
import Header from '../../components/Header/Header';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import Card from '../../components/Card/Card';
|
||||
import { Box, Button, CardContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from '@mui/material';
|
||||
import CustomTextField from '../../components/CustomTextField/CustomTextField';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { Account, AccountType, Feedback, FeedbackType } from '../../data';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Add, BusinessTwoTone, Delete, DeleteForever } from '@mui/icons-material';
|
||||
import * as Yup from 'yup';
|
||||
import CustomSelect, { CustomSelectItem } from '../../components/CustomSelect/CustomSelect';
|
||||
|
||||
type FeedbackSubmitValues = {
|
||||
text: string
|
||||
title: string
|
||||
category: string
|
||||
}
|
||||
|
||||
type FeedbackSubmitCardProps = {
|
||||
feedbacks: Feedback[],
|
||||
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
|
||||
}
|
||||
|
||||
const validataionSchema = Yup.object().shape({
|
||||
text: Yup.string().min(1, "Need to have at least one character").required("This is requried"),
|
||||
title: Yup.string().min(1, "Need to have at least one character").required("This is requried"),
|
||||
category: Yup.string().required("Required")
|
||||
}
|
||||
)
|
||||
|
||||
const categories: CustomSelectItem[] = [
|
||||
{
|
||||
label: "Bug",
|
||||
value: "BUG"
|
||||
},
|
||||
{
|
||||
label: "Enhancement",
|
||||
value: "ENCHANCEMENT"
|
||||
},
|
||||
{
|
||||
label: "Other",
|
||||
value: "OTHER"
|
||||
}
|
||||
];
|
||||
|
||||
const FeedbackSubmitCard = ({feedbacks, setFeedbacks}: FeedbackSubmitCardProps) => {
|
||||
const handleFeedbackSubmit = async ({text, title, category}: FeedbackSubmitValues, {resetForm}: any): Promise<void> => {
|
||||
//console.log(text, title, category)
|
||||
try{
|
||||
await axiosInstance.post('/feedbacks/', {
|
||||
text: text,
|
||||
title: title,
|
||||
category: category
|
||||
})
|
||||
resetForm();
|
||||
// put message here
|
||||
setFeedbacks([...feedbacks, new Feedback({
|
||||
|
||||
title: title,
|
||||
text: text,
|
||||
status: 'SUBMITTED',
|
||||
category: category,
|
||||
|
||||
})])
|
||||
} catch{
|
||||
// put a message here
|
||||
}
|
||||
}
|
||||
return(
|
||||
<Card>
|
||||
<div className='card-header'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
|
||||
<h4 className='text-white font-weight-bold '>
|
||||
Submit Feedback
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent>
|
||||
<Formik
|
||||
initialValues={{
|
||||
text: '',
|
||||
title: '',
|
||||
category: '',
|
||||
}}
|
||||
onSubmit={handleFeedbackSubmit}
|
||||
validationSchema={validataionSchema}
|
||||
validateOnMount>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div className="row">
|
||||
<div className='col-6'>
|
||||
<TextField
|
||||
label='Title'
|
||||
name='title'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.title}
|
||||
fullWidth={true}
|
||||
|
||||
variant={"outlined"} //enables special material-ui styling
|
||||
size={"small"}
|
||||
multiline={false}
|
||||
margin={"dense"}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{/* <div className='col'>
|
||||
<CustomTextField label='Category' name='category' changeHandler={(e) => formik.setFieldValue('category',e.target.value)} isMultline={false}/>
|
||||
|
||||
|
||||
</div> */}
|
||||
<div className='col-6'>
|
||||
<CustomSelect
|
||||
name="category"
|
||||
items={categories}
|
||||
label="Category"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className='col'>
|
||||
<TextField
|
||||
label='Text'
|
||||
name='text'
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.text}
|
||||
fullWidth={true}
|
||||
|
||||
variant={"outlined"} //enables special material-ui styling
|
||||
size={"small"}
|
||||
multiline={false}
|
||||
margin={"dense"}
|
||||
rows={3}
|
||||
minRows={3}
|
||||
maxRows={10}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className='row'>
|
||||
|
||||
|
||||
<div className='col'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid}
|
||||
>Submit
|
||||
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
type FeedbackLineProps = {
|
||||
feedback: Feedback
|
||||
}
|
||||
|
||||
const FeedbackLine = ({feedback}: FeedbackLineProps): JSX.Element => {
|
||||
return(
|
||||
<TableRow key={feedback.id}>
|
||||
<TableCell>{feedback.title}</TableCell>
|
||||
<TableCell>{feedback.category}</TableCell>
|
||||
<TableCell>{feedback.text}</TableCell>
|
||||
<TableCell>{feedback.status}</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
type FeedbackTableCardProps = {
|
||||
feedbacks: Feedback[],
|
||||
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
|
||||
}
|
||||
|
||||
const FeedbackTableCard = ({feedbacks, setFeedbacks}: FeedbackTableCardProps) => {
|
||||
|
||||
|
||||
async function getCompanyUsers(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<FeedbackType[]> = await axiosInstance.get(`/feedbacks/`);
|
||||
setFeedbacks(data.map((item) => new Feedback({
|
||||
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
text: item.text,
|
||||
status: item.status,
|
||||
category: item.category,
|
||||
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getCompanyUsers();
|
||||
}, [])
|
||||
|
||||
return(
|
||||
<Card>
|
||||
<div className='card-header'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1 d-flex'>
|
||||
<h4 className='text-white font-weight-bold '>
|
||||
Feedback
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<CardContent>
|
||||
<TableContainer component={Paper}>
|
||||
<Table >
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Title</TableCell>
|
||||
<TableCell>Category</TableCell>
|
||||
<TableCell>Text</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{feedbacks.map((feedback) => <FeedbackLine key={feedback.id} feedback={feedback}/>)}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const FeedbackPage = ({}): JSX.Element => {
|
||||
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
||||
|
||||
|
||||
return (
|
||||
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
|
||||
<Box
|
||||
sx={{ flexGrow: 1, p: 3, mt: 5, width: { sm: `calc(100% - ${0}px)` } }}
|
||||
position='fixed'
|
||||
>
|
||||
<div className='container-fluid'>
|
||||
<div className='row'>
|
||||
<div className='col-12' style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
}}>
|
||||
<Header />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<Box sx={{
|
||||
mb: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: 900,
|
||||
overflow: "hidden",
|
||||
overflowY: "scroll",
|
||||
// justifyContent="flex-end" # DO NOT USE THIS WITH 'scroll'
|
||||
}}>
|
||||
<FeedbackSubmitCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
|
||||
<FeedbackTableCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
|
||||
|
||||
</Box>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedbackPage;
|
||||
105
llm-fe/src/llm-fe/pages/FeedbackPage2/FeedbackPage2.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import { CardContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
import { Feedback, FeedbackType } from "../../data";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import Header2 from "../../components/Header2/Header2";
|
||||
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout";
|
||||
import Footer from "../../components/Footer/Footer";
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import FeedbackSubmitCard from "../../components/FeedbackSubmitCard/FeedbackSubmitCard";
|
||||
|
||||
type FeedbackLineProps = {
|
||||
feedback: Feedback
|
||||
}
|
||||
|
||||
const FeedbackLine = ({feedback}: FeedbackLineProps): JSX.Element => {
|
||||
return(
|
||||
<TableRow key={feedback.id}>
|
||||
<TableCell><MDTypography>{feedback.title}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>{feedback.category}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>{feedback.text}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>{feedback.status}</MDTypography></TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
type FeedbackTableCardProps = {
|
||||
feedbacks: Feedback[],
|
||||
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
|
||||
}
|
||||
|
||||
const FeedbackTableCard =({feedbacks, setFeedbacks}: FeedbackTableCardProps): JSX.Element => {
|
||||
async function getCompanyUsers(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<FeedbackType[]> = await axiosInstance.get(`/feedbacks/`);
|
||||
setFeedbacks(data.map((item) => new Feedback({
|
||||
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
text: item.text,
|
||||
status: item.status,
|
||||
category: item.category,
|
||||
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getCompanyUsers();
|
||||
}, [])
|
||||
return (
|
||||
<CardContent>
|
||||
<TableContainer component={Paper}>
|
||||
<Table >
|
||||
<TableHead sx={{ display: "table-header-group" }}>
|
||||
<TableRow>
|
||||
<TableCell><MDTypography>Title</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Category</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Text</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Status</MDTypography></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{feedbacks.map((feedback) => <FeedbackLine key={feedback.id} feedback={feedback}/>)}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
</CardContent>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackPageInner =({}): JSX.Element => {
|
||||
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
||||
return(
|
||||
<>
|
||||
<FeedbackSubmitCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
|
||||
<FeedbackTableCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackPage2 = ({}): JSX.Element => {
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<FeedbackPageInner />
|
||||
<Footer />
|
||||
</MDBox>
|
||||
|
||||
</PageWrapperLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeedbackPage2
|
||||
40
llm-fe/src/llm-fe/pages/NotFound/NotFound.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
|
||||
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import { Card, CardContent, Divider } from '@mui/material';
|
||||
import MDTypography from '../../ui-kit/components/MDTypography';
|
||||
import Footer from '../../components/Footer/Footer';
|
||||
import Header2 from '../../components/Header2/Header2';
|
||||
|
||||
const NotFound = ({}): JSX.Element => {
|
||||
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Not Found
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<MDTypography >
|
||||
The page is not found
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Footer/>
|
||||
|
||||
</MDBox>
|
||||
</PageWrapperLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
100
llm-fe/src/llm-fe/pages/PasswordReset/PasswordReset.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
|
||||
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
|
||||
import { Button } from '@mui/material';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
|
||||
|
||||
export type PasswordResetValues = {
|
||||
password1: string;
|
||||
password2: string;
|
||||
};
|
||||
|
||||
|
||||
const PasswordReset = ({}): JSX.Element => {
|
||||
const handlePasswordReset = ({password1, password2}: PasswordResetValues): void => {
|
||||
try{
|
||||
// verify
|
||||
if(password1 === password2){
|
||||
const response = axiosInstance.post('token/obtain', {
|
||||
'password': password1,
|
||||
});
|
||||
//console.log(response)
|
||||
}
|
||||
}catch(error){
|
||||
console.log('catching the error');
|
||||
<CustomToastMessage message={error as string} />
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
|
||||
<div className='container my-auto'>
|
||||
<div className='row'>
|
||||
<div className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
<div className='card z-index-0 fadeIn3 fadeInBottom'>
|
||||
<div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
<h4 className='text-white font-weight-bold text-center'>Password Reset</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className='card-body text-center'>
|
||||
<Formik
|
||||
initialValues={{
|
||||
password1: '',
|
||||
password2: '',
|
||||
}}
|
||||
onSubmit={handlePasswordReset}>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Password'
|
||||
name="password1"
|
||||
changeHandler={(e) => formik.setFieldValue('password1', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Confirm Password'
|
||||
name="password2"
|
||||
changeHandler={(e) => formik.setFieldValue('password2', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
disabled={ formik.isSubmitting}
|
||||
// type={'submit'}
|
||||
// loading={formik.isSubmitting}
|
||||
// disabled={
|
||||
// !formik.isValid || !formik.dirty || formik.isSubmitting
|
||||
// }
|
||||
>
|
||||
Reset Password
|
||||
</Button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
)}
|
||||
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordReset;
|
||||
68
llm-fe/src/llm-fe/pages/Sample/Sample.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { AppBar, Toolbar, Typography, Button, Box } from '@mui/material';
|
||||
import ConversationCard from './components/ConversationCard';
|
||||
import MessageCard from './components/MessageCard';
|
||||
|
||||
|
||||
const SamplePage: React.FC = () => {
|
||||
const conversations = [
|
||||
{ id: 1, title: 'Conversation 1' },
|
||||
{ id: 2, title: 'Conversation 2' },
|
||||
{ id: 3, title: 'Conversation 3' },
|
||||
];
|
||||
|
||||
const messages = [
|
||||
'Message 1',
|
||||
'Message 2',
|
||||
'Message 3',
|
||||
'Message 4',
|
||||
'Message 5',
|
||||
'Message 6',
|
||||
'Message 7',
|
||||
'Message 8',
|
||||
'Message 9',
|
||||
'Message 10',
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="app-container">
|
||||
<AppBar position="static">
|
||||
<Toolbar>
|
||||
<Typography variant="h6" style={{ flexGrow: 1 }}>
|
||||
Dashboard
|
||||
</Typography>
|
||||
<Button color="inherit">Dashboard</Button>
|
||||
<Button color="inherit">Account</Button>
|
||||
<Button color="inherit">Sign Out</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Container fluid>
|
||||
<Row>
|
||||
<Col xs={3} className="sidebar">
|
||||
<Typography variant="h6">Conversations</Typography>
|
||||
{conversations.map((conv) => (
|
||||
<ConversationCard key={conv.id} title={conv.title} />
|
||||
))}
|
||||
</Col>
|
||||
<Col xs={9} className="main-content">
|
||||
<Box className="message-box">
|
||||
{messages.map((msg, index) => (
|
||||
<MessageCard key={index} message={msg} />
|
||||
))}
|
||||
</Box>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
||||
<footer className="footer">
|
||||
<a href="https://aimloperations.com" target="_blank" rel="noopener noreferrer">
|
||||
Visit AIMLOperations.com
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SamplePage;
|
||||
@@ -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;
|
||||
18
llm-fe/src/llm-fe/pages/Sample/components/MessageCard.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent, Typography } from '@mui/material';
|
||||
|
||||
interface MessageCardProps {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const MessageCard: React.FC<MessageCardProps> = ({ message }) => {
|
||||
return (
|
||||
<Card style={{ marginBottom: '10px' }}>
|
||||
<CardContent>
|
||||
<Typography variant="body1">{message}</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageCard;
|
||||
240
llm-fe/src/llm-fe/pages/SignIn/SignIn.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Button, Card, CardContent, Divider, TextField } from '@mui/material';
|
||||
import { object, ref, string } from 'yup';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import CustomTextField, { CustomTextFieldProps } from '../../components/CustomTextField/CustomTextField';
|
||||
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
|
||||
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
|
||||
import { AuthContext } from '../../contexts/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { MOCK_ACCOUNTS } from '../../mockData';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Account, AccountType } from '../../data';
|
||||
|
||||
import background from '../../../bg.jpeg'
|
||||
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import MDTypography from '../../ui-kit/components/MDTypography';
|
||||
import { Col, Row } from 'react-bootstrap';
|
||||
import MDButton from '../../ui-kit/components/MDButton';
|
||||
import MDAlert from '../../ui-kit/components/MDAlert';
|
||||
|
||||
|
||||
|
||||
export type SignInValues = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const validationSchema = object().shape({
|
||||
|
||||
})
|
||||
|
||||
interface GetUserResponse {
|
||||
email: string
|
||||
}
|
||||
|
||||
const SignIn = ({}): JSX.Element => {
|
||||
const {authenticated, setAuthentication, setNeedsNewPassword} = useContext(AuthContext);
|
||||
const { account, setAccount} = useContext(AccountContext);
|
||||
const navigate = useNavigate();
|
||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||
|
||||
const handleSignIn = async ({email, password}: SignInValues): Promise<void> => {
|
||||
//const curr_account = MOCK_ACCOUNTS.find((account) => account.email === email)
|
||||
//if (curr_account){
|
||||
try{
|
||||
const response = await axiosInstance.post('/token/obtain/',{
|
||||
username: email,
|
||||
password: password,
|
||||
|
||||
})
|
||||
axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access;
|
||||
localStorage.setItem('access_token', response.data.access);
|
||||
localStorage.setItem('refresh_token', response.data.refresh);
|
||||
|
||||
const get_user_response: AxiosResponse<AccountType> = await axiosInstance.get('/user/get/')
|
||||
|
||||
const account = new Account({
|
||||
email: get_user_response.data.email,
|
||||
first_name: get_user_response.data.first_name,
|
||||
last_name: get_user_response.data.last_name,
|
||||
is_company_manager: get_user_response.data.is_company_manager,
|
||||
has_signed_tos: get_user_response.data.has_signed_tos,
|
||||
company: {
|
||||
id: get_user_response.data.company?.id,
|
||||
name: get_user_response.data.company?.name,
|
||||
state: get_user_response.data.company?.state,
|
||||
zipcode: get_user_response.data.company?.zipcode,
|
||||
address: get_user_response.data.company?.address,
|
||||
}
|
||||
|
||||
});
|
||||
setAccount(account);
|
||||
setAuthentication(true)
|
||||
setNeedsNewPassword(get_user_response.data.has_usable_password)
|
||||
// TODO: terms of service
|
||||
if (account.has_signed_tos){
|
||||
navigate('/');
|
||||
} else {
|
||||
navigate('/terms_of_service/')
|
||||
}
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
setErrorMessage('Error retrieving account. Try again');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<MDBox sx={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",}}>
|
||||
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<Row>
|
||||
<Col className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
<Card sx={{mt:30}} >
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Sign In
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
password: '',
|
||||
}}
|
||||
onSubmit={handleSignIn}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
{errorMessage &&
|
||||
<MDAlert color="error" dismissible={true}>{errorMessage}</MDAlert>
|
||||
|
||||
}
|
||||
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomTextField
|
||||
label='Email'
|
||||
name="email"
|
||||
changeHandler={(e) => formik.setFieldValue('email', e.target.value)} isMultline={false}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Password'
|
||||
name="password"
|
||||
changeHandler={(e) => formik.setFieldValue('password', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
>
|
||||
Sign In
|
||||
</MDButton>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
)}
|
||||
|
||||
</Formik>
|
||||
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
|
||||
|
||||
</Row>
|
||||
</MDBox>
|
||||
|
||||
</MDBox>
|
||||
</PageWrapperLayout>
|
||||
|
||||
// <div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
|
||||
// backgroundRepeat: "no-repeat",}}>
|
||||
// <div className='container my-auto'>
|
||||
// <div className='row'>
|
||||
// <div className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
// <div className='card z-index-0 fadeIn3 fadeInBottom'>
|
||||
// <div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
|
||||
// <div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
// <h4 className='text-white font-weight-bold text-center'>Sign In</h4>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='card-body text-center'>
|
||||
// <Formik
|
||||
// initialValues={{
|
||||
// email: '',
|
||||
// password: '',
|
||||
// }}
|
||||
// onSubmit={handleSignIn}
|
||||
// validationSchema={validationSchema}
|
||||
// >
|
||||
// {(formik) => (
|
||||
// <Form>
|
||||
// {errorMessage &&
|
||||
// <Alert severity="error">{errorMessage}</Alert>
|
||||
|
||||
// }
|
||||
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <CustomTextField
|
||||
// label='Email'
|
||||
// name="email"
|
||||
// changeHandler={(e) => formik.setFieldValue('email', e.target.value)} isMultline={false}/>
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <CustomPasswordField
|
||||
// label='Password'
|
||||
// name="password"
|
||||
// changeHandler={(e) => formik.setFieldValue('password', e.target.value)} />
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <Button
|
||||
// type={'submit'}
|
||||
// >
|
||||
// Sign In
|
||||
// </Button>
|
||||
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// </Form>
|
||||
// )}
|
||||
|
||||
// </Formik>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignIn;
|
||||
105
llm-fe/src/llm-fe/pages/TermsOfService/TermsOfService.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Box, Button, Card, CardContent, CardHeader, Divider, TextField } from '@mui/material';
|
||||
import { object, ref, string } from 'yup';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import CustomTextField, { CustomTextFieldProps } from '../../components/CustomTextField/CustomTextField';
|
||||
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
|
||||
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
|
||||
import { AuthContext } from '../../contexts/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { MOCK_ACCOUNTS } from '../../mockData';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Account, AccountType } from '../../data';
|
||||
import { Label } from '@mui/icons-material';
|
||||
import background from '../../../bg.jpeg'
|
||||
import MDTypography from '../../ui-kit/components/MDTypography';
|
||||
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import MDButton from '../../ui-kit/components/MDButton';
|
||||
|
||||
const TermsOfService = ({}): JSX.Element => {
|
||||
const {authenticated, setAuthentication, setNeedsNewPassword} = useContext(AuthContext);
|
||||
const { account, setAccount} = useContext(AccountContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleAcknowledge = async (): Promise<void> => {
|
||||
|
||||
try{
|
||||
const response = await axiosInstance.post('/user/acknowledge_tos/')
|
||||
navigate('/')
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Terms of service
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<MDTypography>
|
||||
Welcome to chat.aimloperations.com! We're excited to have you on board. Before you start using our large language model, supplied by AI ML Operations, LLC, we need you to agree to our Terms of Service.
|
||||
</MDTypography>
|
||||
<MDTypography variant="h4">
|
||||
1. Acceptance of Terms
|
||||
</MDTypography>
|
||||
<MDTypography>By logging in, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service. If you don't agree, please don't use our services.</MDTypography>
|
||||
<MDTypography variant="h4">2. Data Confidentiality</MDTypography>
|
||||
<MDTypography>We respect your data. All information entered by you will be contained within your organization and will not be sold, shared, or disclosed to any third parties without your explicit consent.</MDTypography>
|
||||
<MDTypography variant="h4">3. Use of Services</MDTypography>
|
||||
<MDTypography>You agree to use our large language model for legitimate purposes only. You're responsible for ensuring that your use of our services complies with all applicable laws and regulations.</MDTypography>
|
||||
<MDTypography variant="h4">4. Intellectual Property</MDTypography>
|
||||
<MDTypography>All intellectual property rights in our services, including the large language model, remain the property of AI ML Operations, LLC. Any information generated by our service is for your use and AI ML Operations, LLC makes no claim to it.</MDTypography>
|
||||
<MDTypography variant="h4">5. Limitation of Liability</MDTypography>
|
||||
<MDTypography>In no event will we be liable for any damages, including consequential, incidental, or punitive damages, arising from your use of our services.</MDTypography>
|
||||
<MDTypography variant="h4">6. Changes to Terms</MDTypography>
|
||||
<MDTypography>We may update these Terms of Service from time to time. We'll notify you of significant changes, and your continued use of our services will constitute acceptance of the updated terms.</MDTypography>
|
||||
<MDTypography variant="h4">7. Governing Law</MDTypography>
|
||||
<MDTypography>These Terms of Service will be governed by and construed in accordance with the laws of the United States and the state of Illinois.</MDTypography>
|
||||
<MDTypography>By logging in, you acknowledge that you have read and agree to these Terms of Service. If you have any questions, please don't hesitate to contact us.</MDTypography>
|
||||
Happy using our services!
|
||||
<Divider />
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
password: '',
|
||||
}}
|
||||
onSubmit={handleAcknowledge}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
disabled={ formik.isSubmitting}
|
||||
|
||||
loading={formik.isSubmitting}
|
||||
// disabled={
|
||||
// !formik.isValid || !formik.dirty || formik.isSubmitting
|
||||
// }
|
||||
>
|
||||
Acknowledge
|
||||
</MDButton>
|
||||
|
||||
</Form>
|
||||
)}
|
||||
|
||||
</Formik>
|
||||
|
||||
</Card>
|
||||
</MDBox>
|
||||
</PageWrapperLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsOfService;
|
||||
12
llm-fe/src/llm-fe/pages/Unauthorized/Unauthorized.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useState } from 'react';
|
||||
|
||||
const NotFound = ({}): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
|
||||
<p>401</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/apple-icon.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/bg-profile.jpeg
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/bg-reset-cover.jpeg
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/bg-sign-in-basic.jpeg
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/bg-sign-up-cover.jpeg
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/bruce-mars.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/favicon.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/home-decor-1.jpg
Normal file
|
After Width: | Height: | Size: 915 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/home-decor-2.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/home-decor-3.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/home-decor-4.jpeg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/icons/flags/AU.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/icons/flags/BR.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/icons/flags/DE.png
Normal file
|
After Width: | Height: | Size: 927 B |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/icons/flags/GB.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/icons/flags/US.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/ivana-square.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/kal-visuals-square.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/logo-ct-dark.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/logo-ct.png
Normal file
|
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 |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 10 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/logos/mastercard.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/logos/visa.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
llm-fe/src/llm-fe/ui-kit/assets/images/marie.jpg
Normal file
|
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 |
|
After Width: | Height: | Size: 9.5 KiB |