├── client ├── src │ ├── page │ │ ├── chat1.jsx │ │ ├── index.js │ │ ├── error.jsx │ │ ├── login.jsx │ │ ├── forgot.jsx │ │ ├── signup.jsx │ │ ├── chat.jsx │ │ └── style.scss │ ├── config │ │ └── instance.js │ ├── assets │ │ ├── tick.jsx │ │ ├── stop.jsx │ │ ├── message.jsx │ │ ├── plus.jsx │ │ ├── xIcon.jsx │ │ ├── thunder.jsx │ │ ├── avatar.jsx │ │ ├── rocket.jsx │ │ ├── bar.jsx │ │ ├── logOut.jsx │ │ ├── tab.jsx │ │ ├── reload.jsx │ │ ├── trash.jsx │ │ ├── warning.jsx │ │ ├── microsoft.jsx │ │ ├── mail.jsx │ │ ├── sun.jsx │ │ ├── File.jsx │ │ ├── settings.jsx │ │ ├── eye.jsx │ │ ├── index.js │ │ ├── upload.jsx │ │ ├── eyeHide.jsx │ │ ├── google.jsx │ │ ├── Profile.jsx │ │ ├── gptIcon.jsx │ │ └── grant.jsx │ ├── redux │ │ ├── store.js │ │ ├── loading.js │ │ ├── user.js │ │ ├── history.js │ │ └── messages.js │ ├── components │ │ ├── index.js │ │ ├── loading │ │ │ ├── loading.jsx │ │ │ └── style.scss │ │ ├── auth │ │ │ ├── registerPendings.jsx │ │ │ ├── FormFeild.jsx │ │ │ ├── signup.jsx │ │ │ ├── login.jsx │ │ │ └── forgot.jsx │ │ ├── content │ │ │ ├── new.jsx │ │ │ ├── chat.jsx │ │ │ └── style.scss │ │ └── menu │ │ │ ├── style.scss │ │ │ └── menu.jsx │ ├── main.jsx │ ├── index.scss │ ├── protected.jsx │ └── App.jsx ├── public │ ├── _redirects │ ├── favicon.png │ ├── manifest │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png │ └── logo.svg ├── Dockerfile ├── index.html ├── package.json ├── vite.config.js └── .gitignore ├── server ├── db │ ├── collections.js │ └── connection.js ├── Dockerfile ├── package.json ├── mail │ ├── send.js │ ├── template.html │ └── otp.js ├── app.js ├── .gitignore ├── helpers │ └── chat.js └── routes │ └── chat.js ├── docker-compose.yml └── README.md /client/src/page/chat1.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /server/db/collections.js: -------------------------------------------------------------------------------- 1 | export default { 2 | CHAT: 'chat', 3 | USER: 'user', 4 | TEMP: 'temp' 5 | } -------------------------------------------------------------------------------- /client/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzalahwaheed/Proton-Screening-Test/HEAD/client/public/favicon.png -------------------------------------------------------------------------------- /client/public/manifest/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzalahwaheed/Proton-Screening-Test/HEAD/client/public/manifest/icon-192x192.png -------------------------------------------------------------------------------- /client/public/manifest/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzalahwaheed/Proton-Screening-Test/HEAD/client/public/manifest/icon-256x256.png -------------------------------------------------------------------------------- /client/public/manifest/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzalahwaheed/Proton-Screening-Test/HEAD/client/public/manifest/icon-384x384.png -------------------------------------------------------------------------------- /client/public/manifest/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzalahwaheed/Proton-Screening-Test/HEAD/client/public/manifest/icon-512x512.png -------------------------------------------------------------------------------- /client/src/config/instance.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const instance = axios.create({ 4 | withCredentials: true, 5 | baseURL: "http://localhost" 6 | }) 7 | export default instance -------------------------------------------------------------------------------- /client/src/page/index.js: -------------------------------------------------------------------------------- 1 | export { default as Main } from './chat' 2 | export { default as Error } from './error' 3 | export { default as Login } from './login' 4 | export { default as Signup } from './signup' 5 | export { default as Forgot } from './forgot' -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20.11.0-alpine 2 | RUN mkdir -p /client 3 | WORKDIR /client 4 | 5 | COPY package.json . 6 | COPY package-lock.json . 7 | 8 | RUN npm install 9 | RUN npm rebuild bcrypt 10 | 11 | COPY . . 12 | 13 | EXPOSE 80 14 | 15 | CMD npm run dev -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20.11.0-alpine 2 | RUN mkdir -p /server 3 | WORKDIR /server 4 | 5 | COPY package.json . 6 | COPY package-lock.json . 7 | 8 | RUN npm install 9 | RUN npm rebuild bcrypt 10 | RUN npm install nodeman 11 | 12 | COPY . . 13 | 14 | EXPOSE 5000 15 | 16 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | server: 4 | build: 5 | context: ./server 6 | dockerfile: Dockerfile 7 | ports: 8 | - '5000:5000' 9 | client: 10 | build: 11 | context: ./client 12 | dockerfile: Dockerfile 13 | ports: 14 | - '80:80' 15 | links: 16 | - server 17 | -------------------------------------------------------------------------------- /client/src/assets/tick.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Tick = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Tick -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import user from "./user"; 3 | import loading from "./loading"; 4 | import history from "./history"; 5 | import messages from "./messages"; 6 | 7 | export const store = configureStore({ 8 | reducer: { 9 | user, 10 | loading, 11 | history, 12 | messages 13 | } 14 | }) -------------------------------------------------------------------------------- /server/db/connection.js: -------------------------------------------------------------------------------- 1 | import { MongoClient } from "mongodb"; 2 | 3 | let db = null 4 | 5 | const connectDB = async (done) => { 6 | try { 7 | var data = await MongoClient.connect(process.env.MONGO_URL, { useNewUrlParser: true }) 8 | db = data.db('chatGPT') 9 | done() 10 | } catch (err) { 11 | done(err) 12 | } 13 | } 14 | 15 | export { connectDB, db } -------------------------------------------------------------------------------- /client/src/assets/stop.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Stop = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Stop 10 | -------------------------------------------------------------------------------- /client/src/assets/message.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Message = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Message -------------------------------------------------------------------------------- /client/src/assets/plus.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Plus = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Plus -------------------------------------------------------------------------------- /client/src/assets/xIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Xicon = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Xicon -------------------------------------------------------------------------------- /client/src/assets/thunder.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Thunder = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Thunder -------------------------------------------------------------------------------- /client/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Chat } from './content/chat' 2 | export { default as New } from './content/new' 3 | export { default as Menu } from './menu/menu' 4 | export { default as RegisterPendings } from './auth/registerPendings' 5 | export { default as LoginComponent } from './auth/login' 6 | export { default as SignupComponent } from './auth/signup' 7 | export { default as ForgotComponent } from './auth/forgot' -------------------------------------------------------------------------------- /client/src/assets/avatar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Avatar = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Avatar -------------------------------------------------------------------------------- /client/src/assets/rocket.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Rocket = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Rocket -------------------------------------------------------------------------------- /client/src/redux/loading.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | let loadingSlice = createSlice({ 4 | name: 'loading', 5 | initialState: true, 6 | reducers: ({ 7 | setLoading: (state, { payload }) => { 8 | state = payload 9 | return state 10 | } 11 | }) 12 | }) 13 | 14 | export const { setLoading } = loadingSlice.actions 15 | 16 | export default loadingSlice.reducer -------------------------------------------------------------------------------- /client/src/assets/bar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Bar -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | GE CoPilot™ 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/src/assets/logOut.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LogOut = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default LogOut -------------------------------------------------------------------------------- /client/src/assets/tab.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Tab = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Tab -------------------------------------------------------------------------------- /client/src/components/loading/loading.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Grant } from '../../assets' 3 | import './style.scss' 4 | 5 | const Loading = () => { 6 | return ( 7 |
8 |
9 | 10 |
Please stand by, while we are checking your browser...
11 |
12 |
13 | ) 14 | } 15 | 16 | export default Loading 17 | -------------------------------------------------------------------------------- /client/src/redux/user.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | let userSlice = createSlice({ 4 | name: 'user', 5 | initialState: null, 6 | reducers: { 7 | insertUser: (state, action) => { 8 | return action.payload 9 | }, 10 | emptyUser: (state, action) => { 11 | return null 12 | } 13 | } 14 | }) 15 | 16 | export const { insertUser, emptyUser } = userSlice.actions 17 | 18 | export default userSlice.reducer -------------------------------------------------------------------------------- /client/src/assets/reload.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Reload = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Reload 10 | -------------------------------------------------------------------------------- /client/src/assets/trash.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Trash = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Trash -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # test 2 | For screening test, clone the repo and follow the steps to run the app. 3 | 4 | How to run the app: 5 | 6 | 1. Ensure npm is Installed: 7 | Before starting, make sure that npm (Node Package Manager) is installed on your system. 8 | 2. Running the Server: 9 | Navigate to the server directory within the application's root folder using a command line tool. 10 | Execute the command npm start. 11 | 3. Running the Client: 12 | Open a new command line interface.Navigate to the directory. 13 | Execute the command npm run dev. 14 | 15 | -------------------------------------------------------------------------------- /client/src/assets/warning.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Warning = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Warning -------------------------------------------------------------------------------- /client/src/assets/microsoft.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Microsoft = () => { 4 | return ( 5 | 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | export default Microsoft 21 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "nodemon app.js" 9 | }, 10 | "author": "Yash Deep", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1560.0", 14 | "axios": "^1.3.5", 15 | "bcrypt": "^5.1.0", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.0.3", 19 | "express": "^4.18.2", 20 | "jsonwebtoken": "^9.0.0", 21 | "mongodb": "^5.1.0", 22 | "multer": "^1.4.5-lts.1", 23 | "nodemailer": "^6.9.13", 24 | "nodemon": "^2.0.22", 25 | "openai": "^4.28.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/src/main.jsx: -------------------------------------------------------------------------------- 1 | import { GoogleOAuthProvider } from '@react-oauth/google' 2 | import React from 'react' 3 | import ReactDOM from 'react-dom/client' 4 | import { Provider } from 'react-redux' 5 | import { store } from './redux/store' 6 | import App from './App' 7 | import { BrowserRouter } from 'react-router-dom' 8 | import './index.scss' 9 | 10 | ReactDOM.createRoot(document.getElementById('root')).render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | , 20 | ) 21 | -------------------------------------------------------------------------------- /client/src/assets/mail.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Mail = () => { 4 | return ( 5 | 12 | 16 | 17 | ) 18 | } 19 | 20 | export default Mail 21 | -------------------------------------------------------------------------------- /client/src/assets/sun.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Sun = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Sun -------------------------------------------------------------------------------- /server/mail/send.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer' 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | const transporter = nodemailer.createTransport({ 7 | service: 'gmail', 8 | // host:"smtp.gmail.com", 9 | auth: { 10 | user: process.env.MAIL_EMAIL, 11 | pass: process.env.MAIL_SECRET 12 | } 13 | }) 14 | 15 | export default ({ to, subject, html }) => { 16 | const options = { 17 | from: `GE CoPilot™ <${process.env.MAIL_EMAIL}>`, 18 | to, 19 | subject, 20 | html 21 | } 22 | 23 | transporter.sendMail(options, (err, done) =>{ 24 | if (err) { 25 | console.error('Failed to send email:',err); 26 | } else { 27 | console.log('Email sent: ', done?.response); 28 | } 29 | }); 30 | } -------------------------------------------------------------------------------- /client/src/page/error.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setLoading } from '../redux/loading' 4 | import './style.scss' 5 | 6 | const Error = ({ status, content }) => { 7 | const dispatch = useDispatch() 8 | 9 | useEffect(() => { 10 | setTimeout(() => { 11 | dispatch(setLoading({ site: false })) 12 | }, 1000) 13 | }, []) 14 | return ( 15 |
16 |
17 |
18 | {status} 19 |
20 |
21 | {content} 22 |
23 |
24 |
25 | ) 26 | } 27 | 28 | export default Error 29 | -------------------------------------------------------------------------------- /client/src/assets/File.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const File = () => { 4 | return ( 5 | <> 6 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default File; 20 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "private": true, 4 | "version": "1.0.0", 5 | "author": "Yash Deep", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vite build", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@react-oauth/google": "^0.9.0", 14 | "@reduxjs/toolkit": "^1.9.3", 15 | "axios": "^1.3.4", 16 | "buffer": "^6.0.3", 17 | "dotenv": "^16.4.4", 18 | "fs": "^0.0.1-security", 19 | "openai": "^4.28.0", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "react-redux": "^8.0.5", 23 | "react-router-dom": "^6.9.0", 24 | "react-s3": "^1.3.1", 25 | "sass": "^1.59.3" 26 | }, 27 | "devDependencies": { 28 | "@types/react": "^18.0.28", 29 | "@types/react-dom": "^18.0.11", 30 | "@vitejs/plugin-react": "^3.1.0", 31 | "vite": "^4.2.0", 32 | "vite-plugin-pwa": "^0.14.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/src/redux/history.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | let historySlice = createSlice({ 4 | name: 'history', 5 | initialState: [], 6 | reducers: { 7 | addHistory: (state, { payload }) => { 8 | return payload 9 | }, 10 | activePage: (state, { payload = null }) => { 11 | let pos = null 12 | state.forEach((obj, index) => { 13 | if (obj.chatId === payload) { 14 | obj.active = true 15 | pos = index 16 | } else { 17 | obj.active = false 18 | } 19 | }) 20 | 21 | if (pos) { 22 | let obj = state[pos] 23 | state.splice(pos,1) 24 | state.unshift(obj) 25 | } 26 | 27 | return state 28 | } 29 | } 30 | }) 31 | 32 | export const { addHistory, activePage } = historySlice.actions 33 | 34 | export default historySlice.reducer -------------------------------------------------------------------------------- /client/src/assets/settings.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Settings = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default Settings 10 | -------------------------------------------------------------------------------- /client/src/assets/eye.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Eye = () => { 4 | return ( 5 | 12 | 13 | 14 | {"eye"} 15 | 16 | 22 | 23 | 24 | ) 25 | } 26 | 27 | export default Eye 28 | -------------------------------------------------------------------------------- /client/src/assets/index.js: -------------------------------------------------------------------------------- 1 | export { default as Thunder } from './thunder' 2 | export { default as Sun } from './sun' 3 | export { default as Warning } from './warning' 4 | export { default as Rocket } from './rocket' 5 | export { default as Plus } from './plus' 6 | export { default as Avatar } from './avatar' 7 | export { default as Tab } from './tab' 8 | export { default as LogOut } from './logOut' 9 | export { default as Xicon } from './xIcon' 10 | export { default as Bar } from './bar' 11 | export { default as Trash } from './trash' 12 | export { default as Tick } from './tick' 13 | export { default as Message } from './message' 14 | export { default as GptIcon } from './gptIcon' 15 | export { default as Reload } from './reload' 16 | export { default as Stop } from './stop' 17 | export { default as Google } from './google' 18 | export { default as Microsoft } from './microsoft' 19 | export { default as Eye } from './eye' 20 | export { default as EyeHide } from './eyeHide' 21 | export { default as Mail } from './mail' 22 | export { default as Settings } from './settings' 23 | export { default as Grant } from './grant' 24 | export { default as File } from './File' -------------------------------------------------------------------------------- /client/src/components/loading/style.scss: -------------------------------------------------------------------------------- 1 | .light { 2 | div[data-for='Loading'] { 3 | background: white; 4 | 5 | svg { 6 | color: #343541; 7 | } 8 | 9 | div { 10 | color: #343541; 11 | } 12 | } 13 | } 14 | 15 | .dark { 16 | div[data-for='Loading'] { 17 | background: rgba(52, 53, 65, 1); 18 | 19 | svg { 20 | color: #D9D9E3; 21 | } 22 | 23 | div { 24 | color: #D9D9E3; 25 | } 26 | } 27 | } 28 | 29 | div[data-for='Loading'] { 30 | display: block; 31 | position: fixed; 32 | height: 100%; 33 | width: 100%; 34 | z-index: 3000; 35 | 36 | .inner { 37 | display: flex; 38 | flex-direction: column; 39 | height: 100%; 40 | width: 100%; 41 | align-items: center; 42 | justify-content: center; 43 | 44 | svg { 45 | height: 2.5rem; 46 | width: 2.5rem; 47 | } 48 | 49 | div[data-for="text"] { 50 | text-align: center; 51 | padding: 1em; 52 | font-size: 16px; 53 | line-height: 24px; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import cors from 'cors' 3 | import cookieParser from 'cookie-parser' 4 | import dotnet from 'dotenv' 5 | import { connectDB } from './db/connection.js' 6 | import ChatRoute from './routes/chat.js' 7 | import UserRoute from './routes/user.js' 8 | import path from 'path' 9 | dotnet.config() 10 | 11 | let app = express() 12 | let port = process.env.PORT 13 | 14 | // for production copy paste react js product build files in dist folder 15 | app.use(express.static('dist')) 16 | 17 | app.use(cors({ credentials: true, origin: process.env.SITE_URL , optionsSuccessStatus: 200})) 18 | app.use(cookieParser()) 19 | app.use(express.json({ limit: '50mb' })) 20 | 21 | // api route 22 | app.use('/api/chat/', ChatRoute) 23 | app.use('/api/user/', UserRoute) 24 | 25 | // front end react route 26 | // app.get('/*',(req,res)=>{ 27 | // res.sendFile(path.join(`${path.resolve(path.dirname(''))}/dist/index.html`)) 28 | // }) 29 | 30 | connectDB((err) => { 31 | if (err) return console.log("MongoDB Connect Failed : ", err) 32 | 33 | console.log("MongoDB Connected") 34 | 35 | app.listen(port, () => { 36 | console.log("server started") 37 | }) 38 | }) 39 | 40 | -------------------------------------------------------------------------------- /client/src/index.scss: -------------------------------------------------------------------------------- 1 | $ColorBlack : #343541; 2 | 3 | html { 4 | scroll-behavior: smooth; 5 | } 6 | 7 | * { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | body { 13 | height: 100%; 14 | overflow-y: auto; 15 | overflow-x: hidden; 16 | font-family: 'Lato', sans-serif; 17 | 18 | // scroll start; 19 | 20 | &::-webkit-scrollbar { 21 | width: 8px; 22 | } 23 | 24 | &::-webkit-scrollbar-track { 25 | background: transparent; 26 | } 27 | 28 | &:is(.light)::-webkit-scrollbar-thumb { 29 | background: rgba(217, 217, 227, .8); 30 | border-radius: 30px; 31 | 32 | &:hover { 33 | background: rgba(236, 236, 241, 1); 34 | } 35 | } 36 | 37 | &:is(.dark)::-webkit-scrollbar-thumb { 38 | background: rgba(86, 88, 105, 1); 39 | border-radius: 30px; 40 | 41 | &:hover { 42 | background: rgba(172, 172, 190, 1); 43 | } 44 | } 45 | 46 | //scroll end 47 | 48 | &:is(.light) { 49 | // body background 50 | background: white; 51 | 52 | .currentColor { 53 | color: $ColorBlack; 54 | } 55 | } 56 | 57 | &:is(.dark) { 58 | // body background 59 | background: rgba(52, 53, 65, 1); 60 | 61 | .currentColor { 62 | color: #ECECF1; 63 | } 64 | } 65 | } 66 | 67 | .main-grid { 68 | display: grid; 69 | grid-template-columns: 260px auto; 70 | 71 | @media screen and (max-width:767px) { 72 | grid-template-columns: 100%; 73 | } 74 | } -------------------------------------------------------------------------------- /client/src/assets/upload.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const upload = () => { 4 | return ( 5 | 14 | 19 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default upload; 38 | -------------------------------------------------------------------------------- /client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import { VitePWA } from "vite-plugin-pwa"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | server: { 8 | host: true, 9 | port: 80, 10 | strictPort: true, 11 | proxy: { 12 | "/api": { 13 | target: "http://localhost:5000/api", 14 | changeOrigin: true, 15 | rewrite: (path) => path.replace(/^\/api/, ""), 16 | }, 17 | }, 18 | }, 19 | plugins: [ 20 | react(), 21 | 22 | VitePWA({ 23 | devOptions: { 24 | enabled: false, 25 | }, 26 | manifest: { 27 | theme_color: "#fff", 28 | background_color: "#fff", 29 | display: "fullscreen", 30 | scope: "/", 31 | start_url: "/", 32 | name: "ChatGPT", 33 | short_name: "ChatGPT", 34 | description: "ChatGPT OpenAI", 35 | icons: [ 36 | { 37 | src: "/manifest/icon-192x192.png", 38 | sizes: "192x192", 39 | type: "image/png", 40 | }, 41 | { 42 | src: "/manifest/icon-256x256.png", 43 | sizes: "256x256", 44 | type: "image/png", 45 | }, 46 | { 47 | src: "/manifest/icon-384x384.png", 48 | sizes: "384x384", 49 | type: "image/png", 50 | }, 51 | { 52 | src: "/manifest/icon-512x512.png", 53 | sizes: "512x512", 54 | type: "image/png", 55 | }, 56 | ], 57 | }, 58 | }), 59 | ], 60 | }); 61 | -------------------------------------------------------------------------------- /client/src/assets/eyeHide.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const EyeHide = () => { 4 | return ( 5 | 12 | 13 | 14 | {"eye-slash"} 15 | 16 | 22 | 23 | 24 | ) 25 | } 26 | 27 | export default EyeHide 28 | -------------------------------------------------------------------------------- /client/src/assets/google.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Google = () => { 4 | return ( 5 | 12 | 16 | 20 | 24 | 28 | 29 | ) 30 | } 31 | 32 | export default Google 33 | -------------------------------------------------------------------------------- /client/src/protected.jsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { Outlet, useLocation, useNavigate } from "react-router-dom"; 4 | import { setLoading } from "./redux/loading"; 5 | import instance from "./config/instance"; 6 | import { emptyUser, insertUser } from "./redux/user"; 7 | import { emptyAllRes } from "./redux/messages"; 8 | 9 | const ProtectedRoute = ({ offline, authed }) => { 10 | const [component, setComponent] = useState(null); 11 | 12 | const location = useLocation(); 13 | const navigate = useNavigate(); 14 | const dispatch = useDispatch(); 15 | 16 | useLayoutEffect(() => { 17 | dispatch(setLoading(true)); 18 | const getResponse = async () => { 19 | let res = null; 20 | 21 | try { 22 | res = await instance.get("/api/user/checkLogged"); 23 | if (res?.data?.data) { 24 | dispatch(insertUser(res?.data?.data)); 25 | } 26 | } catch (err) { 27 | console.log(err); 28 | 29 | if (err?.response?.data?.status === 405) { 30 | dispatch(emptyUser()); 31 | dispatch(emptyAllRes()); 32 | if (authed) { 33 | navigate("/login"); 34 | } else { 35 | setComponent(); 36 | } 37 | } else if (err?.code !== "ERR_NETWORK") { 38 | navigate("/something-went-wrong"); 39 | } 40 | } finally { 41 | if (res?.data?.status === 208) { 42 | if (!authed) { 43 | navigate("/"); 44 | } else { 45 | setComponent(); 46 | } 47 | } 48 | } 49 | }; 50 | 51 | if (!offline) { 52 | getResponse(); 53 | } 54 | }, [location]); 55 | 56 | return component; 57 | }; 58 | 59 | export default ProtectedRoute; 60 | -------------------------------------------------------------------------------- /server/mail/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | GE CoPilot™ 9 | 10 | 11 | 13 |
14 |
15 | GE CoPilot™ 19 | 20 |

21 | [TITLE] 22 |

23 | 24 |

[CONTENT]

25 | 26 | 31 | [BTN_NAME] 32 | 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /client/src/assets/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Profile = ({ profilePicture }) => { 4 | return profilePicture ? ( 5 | profile 11 | ) : ( 12 | 18 | 19 | 24 | 25 | {" "} 26 | {" "} 34 | {" "} 42 | {" "} 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default Profile; 55 | -------------------------------------------------------------------------------- /client/src/page/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useLocation, useNavigate } from "react-router-dom"; 4 | import { Grant } from "../assets"; 5 | import { LoginComponent } from "../components"; 6 | import { setLoading } from "../redux/loading"; 7 | import "./style.scss"; 8 | 9 | const Login = () => { 10 | const location = useLocation(); 11 | 12 | const [auth, setAuth] = useState(false); 13 | 14 | const { user } = useSelector((state) => state); 15 | 16 | const dispatch = useDispatch(); 17 | 18 | const navigate = useNavigate(); 19 | 20 | useEffect(() => { 21 | if (!user) { 22 | if (location?.pathname === "/login/auth") { 23 | setAuth(true); 24 | setTimeout(() => { 25 | dispatch(setLoading(false)); 26 | }, 1000); 27 | } else { 28 | setAuth(false); 29 | setTimeout(() => { 30 | dispatch(setLoading(false)); 31 | }, 1000); 32 | } 33 | } 34 | }, [location]); 35 | 36 | return ( 37 |
38 |
39 | {auth ? ( 40 | 41 | ) : ( 42 |
43 |
44 | 45 |
46 | 47 |
48 |

Welcome to GE CoPilot™

49 |

Log in or Sign up with your account to continue

50 |
51 | 52 |
53 | 60 | 67 |
68 |
69 | )} 70 | 71 |
72 | 73 |
74 |
75 |
76 | ); 77 | }; 78 | 79 | export default Login; 80 | -------------------------------------------------------------------------------- /client/src/components/auth/registerPendings.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useNavigate } from 'react-router-dom' 3 | import { Grant } from '../../assets' 4 | import instance from '../../config/instance' 5 | import './style.scss' 6 | 7 | const RegisterPendings = ({ _id }) => { 8 | const navigate = useNavigate() 9 | 10 | const [formData, setFormData] = useState({ 11 | fName: '', 12 | lName: '' 13 | }) 14 | 15 | const formHandle = async (e) => { 16 | e.preventDefault() 17 | if (formData?.fName && formData?.lName) { 18 | let res = null 19 | try { 20 | res = await instance.put('/api/user/signup-finish', { 21 | fName: formData.fName, 22 | lName: formData.lName, 23 | _id 24 | }) 25 | } catch (err) { 26 | console.log(err) 27 | if (err?.response?.data?.status === 422) { 28 | alert("Already Registered") 29 | navigate('/login') 30 | } else { 31 | alert(err) 32 | } 33 | } finally { 34 | if (res?.data?.status === 208) { 35 | navigate('/') 36 | } else if (res) { 37 | navigate('/login') 38 | } 39 | } 40 | } else { 41 | alert("Enter full name") 42 | } 43 | } 44 | 45 | return ( 46 |
47 |
48 | 49 |
50 | 51 |

Tell us about you

52 | 53 |
54 |
55 | { 57 | setFormData({ ...formData, fName: e.target.value }) 58 | }} /> 59 | { 60 | setFormData({ ...formData, lName: e.target.value }) 61 | }} /> 62 |
63 | 64 | 65 | 66 |
67 |

By clicking "Continue", you agree to our ,
Privacy policy and confirm you're 18 years or older.

68 |
69 |
70 | 71 |
72 | ) 73 | } 74 | 75 | 76 | export default RegisterPendings 77 | -------------------------------------------------------------------------------- /client/src/page/forgot.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useLocation, useNavigate, useParams } from "react-router-dom"; 4 | import { ForgotComponent } from "../components"; 5 | import instance from "../config/instance"; 6 | import { setLoading } from "../redux/loading"; 7 | import "./style.scss"; 8 | 9 | const Forgot = () => { 10 | const { user } = useSelector((state) => state); 11 | 12 | const { userId = null, secret = null } = useParams(); 13 | 14 | const location = useLocation(); 15 | 16 | const dispatch = useDispatch(); 17 | 18 | const navigate = useNavigate(); 19 | 20 | const [isRequest, setIsRequest] = useState(true); 21 | 22 | useEffect(() => { 23 | if (!user) { 24 | if ( 25 | location?.pathname === "/forgot/" || 26 | location?.pathname === "/forgot" 27 | ) { 28 | setIsRequest(true); 29 | setTimeout(() => { 30 | dispatch(setLoading(false)); 31 | }, 1000); 32 | } else { 33 | const getResponse = async () => { 34 | let res = null; 35 | 36 | try { 37 | res = await instance.get("/api/user/forgot-check", { 38 | params: { 39 | userId, 40 | secret, 41 | }, 42 | }); 43 | } catch (err) { 44 | console.log(err); 45 | if (err?.response?.status === 404) { 46 | navigate("/404"); 47 | } else { 48 | alert(err); 49 | navigate("/forgot"); 50 | } 51 | } finally { 52 | if (res?.data?.status !== 208) { 53 | setIsRequest(false); 54 | setTimeout(() => { 55 | dispatch(setLoading(false)); 56 | }, 1000); 57 | } 58 | } 59 | }; 60 | 61 | getResponse(); 62 | } 63 | } 64 | }, [location]); 65 | 66 | return ( 67 |
68 |
69 | 74 | 75 |
76 | 77 |
78 |
79 |
80 | ); 81 | }; 82 | 83 | export default Forgot; 84 | -------------------------------------------------------------------------------- /client/src/page/signup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useLocation, useNavigate, useParams } from "react-router-dom"; 4 | import { RegisterPendings, SignupComponent } from "../components"; 5 | import instance from "../config/instance"; 6 | import { setLoading } from "../redux/loading"; 7 | import "./style.scss"; 8 | 9 | const Signup = () => { 10 | const { user } = useSelector((state) => state); 11 | 12 | const [pending, setPending] = useState(false); 13 | 14 | const { id } = useParams(); 15 | 16 | const dispatch = useDispatch(); 17 | 18 | const location = useLocation(); 19 | 20 | const navigate = useNavigate(); 21 | 22 | useEffect(() => { 23 | if (!user) { 24 | if ( 25 | location?.pathname === "/signup" || 26 | location?.pathname === "/signup/" 27 | ) { 28 | setPending(false); 29 | setTimeout(() => { 30 | dispatch(setLoading(false)); 31 | }, 1000); 32 | } else { 33 | const checkPending = async () => { 34 | let res = null; 35 | try { 36 | res = await instance.get("/api/user/checkPending", { 37 | params: { 38 | _id: id, 39 | }, 40 | }); 41 | } catch (err) { 42 | console.log(err); 43 | if (err?.response?.status === 404) { 44 | navigate("/404"); 45 | } else { 46 | alert(err); 47 | navigate("/signup"); 48 | } 49 | } finally { 50 | if (res?.data?.status !== 208) { 51 | setPending(true); 52 | setTimeout(() => { 53 | dispatch(setLoading(false)); 54 | }, 1000); 55 | } 56 | } 57 | }; 58 | 59 | checkPending(); 60 | } 61 | } 62 | }, [location]); 63 | 64 | return ( 65 |
66 |
67 | {pending ? ( 68 | 69 | ) : ( 70 | <> 71 | 72 | 73 |
74 | 75 |
76 | 77 | )} 78 |
79 |
80 | ); 81 | }; 82 | 83 | export default Signup; 84 | -------------------------------------------------------------------------------- /client/src/redux/messages.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | let messagesSlice = createSlice({ 4 | name: 'messages', 5 | initialState: { 6 | prompt: '', 7 | content: '', 8 | _id: null, 9 | latest: { 10 | prompt: '', 11 | content: '' 12 | }, 13 | all: [] 14 | }, 15 | reducers: { 16 | emptyAllRes: () => { 17 | return { 18 | prompt: '', 19 | content: '', 20 | _id: null, 21 | latest: { 22 | prompt: '', 23 | content: '' 24 | }, 25 | all: [] 26 | } 27 | }, 28 | addList: (state, { payload }) => { 29 | const { _id, items } = payload 30 | state._id = _id 31 | state.all = items 32 | return state 33 | }, 34 | insertNew: (state, { payload }) => { 35 | const { chatsId, content = null, 36 | resume = false, fullContent = null, 37 | _id = null, prompt = null } = payload 38 | 39 | if (_id) { 40 | state._id = _id 41 | } 42 | 43 | state.latest.id = chatsId 44 | 45 | if (prompt) { 46 | state.latest.prompt = prompt 47 | } 48 | 49 | const addToList = (latest) => { 50 | if (state['all'].find(obj => obj.id === latest.id)) { 51 | state['all'].forEach(obj => { 52 | if (obj.id === latest.id) { 53 | obj.content = latest.content 54 | } 55 | }) 56 | } else { 57 | state['all'].push(latest) 58 | } 59 | } 60 | 61 | if (content && resume) { 62 | state.latest.content += content 63 | addToList(state.latest) 64 | 65 | } else if (content) { 66 | state.latest.content = content 67 | addToList(state.latest) 68 | } 69 | 70 | if (fullContent) { 71 | state.content = fullContent 72 | } 73 | 74 | return state 75 | }, 76 | livePrompt: (state, { payload }) => { 77 | state.prompt = payload 78 | return state 79 | } 80 | } 81 | }) 82 | 83 | export const { emptyAllRes, insertNew, livePrompt, addList } = messagesSlice.actions 84 | export default messagesSlice.reducer -------------------------------------------------------------------------------- /server/mail/otp.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | const transporter = nodemailer.createTransport({ 7 | service: 'gmail', 8 | host: 'smtp.gmail.com', 9 | auth: { 10 | user: process.env.MAIL_EMAIL, 11 | pass: process.env.MAIL_SECRET 12 | } 13 | }); 14 | 15 | const sendOTP = ({ to, otp }) => { 16 | const html = generateOTPTemplate(otp); 17 | const subject = 'Your OTP from GE CoPilot™'; 18 | 19 | const options = { 20 | from: `GE CoPilot™ <${process.env.MAIL_EMAIL}>`, 21 | to, 22 | subject, 23 | html 24 | }; 25 | 26 | transporter.sendMail(options, (err, info) => { 27 | if (err) { 28 | console.error(err); 29 | } else { 30 | console.log('Email sent: ', info.response); 31 | } 32 | }); 33 | }; 34 | 35 | const generateOTPTemplate = (otp) => { 36 | const template = ` 37 | 38 | 39 | 40 | 41 | 42 | 43 | OTP from GE CoPilot™ 44 | 45 | 46 |
47 |
48 | OpenAI 49 |

Your OTP is: ${otp}

50 |

Use this OTP to proceed with your action.

51 |
52 |
53 | 54 | 55 | `; 56 | 57 | return template; 58 | }; 59 | 60 | export default sendOTP; 61 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* -------------------------------------------------------------------------------- /client/src/components/content/new.jsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { Sun, Thunder, Warning } from '../../assets' 4 | import { livePrompt } from '../../redux/messages' 5 | import './style.scss' 6 | 7 | const New = memo(() => { 8 | const dispatch = useDispatch() 9 | return ( 10 |
11 |
12 |

GE CoPilot™

13 |
14 | 15 |
16 |
17 |
18 | 19 |

Examples

20 |
21 | 22 |
{ 23 | dispatch(livePrompt("Explain quantum computing in simple terms")) 24 | }}> 25 |

"Explain quantum computing in simple terms" →

26 |
27 | 28 |
{ 29 | dispatch(livePrompt("Got any creative ideas for a 10 year old’s birthday?")) 30 | }}> 31 |

"Got any creative ideas for a 10 year old’s birthday?" →

32 |
33 | 34 |
{ 35 | dispatch(livePrompt("How do I make an HTTP request in Javascript?")) 36 | }}> 37 |

"How do I make an HTTP request in Javascript?" →

38 |
39 | 40 |
41 | 42 |
43 |
44 | 45 |

Capabilities

46 |
47 | 48 |
49 |

Remembers what user said earlier in the conversation

50 |
51 | 52 |
53 |

Allows user to provide follow-up corrections

54 |
55 | 56 |
57 |

Trained to decline inappropriate requests

58 |
59 | 60 |
61 | 62 |
63 |
64 | 65 |

Limitations

66 |
67 | 68 |
69 |

May occasionally generate incorrect information

70 |
71 | 72 |
73 |

May occasionally produce harmful instructions or biased content

74 |
75 | 76 |
77 |

Limited knowledge of world and events after 2021

78 |
79 | 80 |
81 |
82 |
83 | ) 84 | }) 85 | 86 | export default New -------------------------------------------------------------------------------- /client/src/components/auth/FormFeild.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useCallback, useRef, useState } from 'react' 2 | import { Eye, EyeHide } from '../../assets' 3 | 4 | const FormFeild = ({ label, 5 | value, name, type, handleInput, 6 | passwordClass, isDisabled, error }) => { 7 | 8 | const [showPass, setShowPass] = useState(false) 9 | 10 | let inputRef = useRef() 11 | let labelRef = useRef() 12 | 13 | const inputClass = useCallback((add, label, input) => { 14 | if (add) { 15 | labelRef.current?.classList.add(...label) 16 | inputRef.current?.classList.add(...input) 17 | } else { 18 | labelRef.current?.classList.remove(...label) 19 | inputRef.current?.classList.remove(...input) 20 | } 21 | }, []) 22 | 23 | return ( 24 | 25 | {label && } 28 | 29 | { 34 | inputClass(true, ["active-label", "active-label-green"], ["active-input", "active-input-green"]) 35 | }} 36 | 37 | onBlur={() => { 38 | if (inputRef.current?.value.length <= 0) { 39 | inputClass(false, ["active-label", "active-label-green"], ["active-input", "active-input-green"]) 40 | } else { 41 | inputClass(false, ["active-label-green"], ["active-input-green"]) 42 | } 43 | }} 44 | 45 | onInput={(e) => { 46 | handleInput(e) 47 | if (passwordClass) { 48 | document.querySelector('#alertBox').style.display = "block" 49 | 50 | if (e.target.value.length >= 8) { 51 | passwordClass('#passAlertError', "#passAlertDone") 52 | } else { 53 | passwordClass("#passAlertDone", "#passAlertError") 54 | } 55 | } 56 | }} 57 | 58 | disabled={isDisabled} readOnly={isDisabled} required 59 | /> 60 | 61 | { 62 | type === 'password' && <> 63 | {showPass ? 67 | : } 71 | 72 | } 73 | 74 | ) 75 | } 76 | 77 | export default FormFeild 78 | -------------------------------------------------------------------------------- /client/src/components/content/chat.jsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | forwardRef, 3 | Fragment, 4 | useImperativeHandle, useRef 5 | } from 'react' 6 | import { useDispatch, useSelector } from 'react-redux' 7 | import { Grant } from '../../assets' 8 | import { insertNew } from '../../redux/messages' 9 | import './style.scss' 10 | 11 | const Chat = forwardRef(({ error }, ref) => { 12 | 13 | const dispatch = useDispatch() 14 | 15 | const contentRef = useRef() 16 | 17 | const { user, messages } = useSelector((state) => state) 18 | const { latest, content, all } = messages 19 | 20 | const loadResponse = (stateAction, 21 | response = content, 22 | chatsId = latest?.id) => { 23 | 24 | clearInterval(window.interval) 25 | 26 | stateAction({ type: 'resume', status: true }) 27 | 28 | contentRef?.current?.classList?.add("blink") 29 | 30 | // let index = 0 31 | contentRef.current.innerHTML = response 32 | // window.interval = setInterval(() => { 33 | // if (index < response.length && contentRef?.current) { 34 | // if (index === 0) { 35 | dispatch(insertNew({ chatsId, content: response })) 36 | contentRef.current.innerHTML = response 37 | // } else { 38 | // dispatch(insertNew({ chatsId, content: response.charAt(index), resume: true })) 39 | // contentRef.current.innerHTML += response.charAt(index) 40 | // } 41 | // index++ 42 | // } else { 43 | stopResponse(stateAction) 44 | } 45 | // }, 20) 46 | 47 | // } 48 | 49 | const stopResponse = (stateAction) => { 50 | if (contentRef?.current) { 51 | contentRef.current.classList.remove('blink') 52 | } 53 | stateAction({ type: 'resume', status: false }) 54 | clearInterval(window.interval) 55 | } 56 | 57 | useImperativeHandle(ref, () => ({ 58 | stopResponse, 59 | loadResponse, 60 | clearResponse: () => { 61 | if (contentRef?.current) { 62 | contentRef.current.innerHTML = '' 63 | contentRef?.current?.classList.add("blink") 64 | } 65 | } 66 | })) 67 | 68 | return ( 69 |
70 | { 71 | all?.filter((obj) => { 72 | //console.log(all) 73 | return !obj.id ? true : obj?.id !== latest?.id 74 | })?.map((obj, key) => { 75 | //console.log(obj) 76 | return ( 77 | 78 |
79 |
80 | {user?.fName?.charAt(0)} 81 |
82 |
83 | {obj?.prompt} 84 |
85 |
86 | 87 |
88 |
89 | 90 |
91 |
92 | 93 | {obj?.content} 94 | 95 |
96 |
97 |
98 | ) 99 | }) 100 | } 101 | 102 | { 103 | latest?.prompt?.length > 0 && ( 104 | 105 |
106 |
107 | {user?.fName?.charAt(0)} 108 |
109 |
110 | {latest?.prompt} 111 |
112 |
113 | 114 |
115 |
116 | 117 | {error && !} 118 |
119 |
120 | { 121 | error ?
122 | Something went wrong. If this issue persists please contact us through our help center at help.openai.com. 123 |
: 124 | } 125 |
126 |
127 |
128 | ) 129 | } 130 |
131 | ) 132 | }) 133 | export default Chat -------------------------------------------------------------------------------- /client/src/assets/gptIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const GptIcon = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | 9 | export default GptIcon 10 | -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useLayoutEffect, useState } from "react"; 2 | import { Menu } from "./components"; 3 | import { Routes, Route } from "react-router-dom"; 4 | import { Error, Forgot, Login, Main, Signup } from "./page"; 5 | import { useSelector } from "react-redux"; 6 | import ProtectedRoute from "./protected"; 7 | import Loading from "./components/loading/loading"; 8 | import instance from "./config/instance"; 9 | 10 | export const documentsContext = createContext({ 11 | documents: [], 12 | setDocuments: () => {}, 13 | getFiles: () => {}, 14 | }); 15 | 16 | const App = () => { 17 | const [offline, setOffline] = useState(!window.navigator.onLine); 18 | const [file_id, set_file_id] = useState(null); 19 | const { loading, user } = useSelector((state) => state); 20 | const [documents, setDocuments] = useState([]); 21 | const { _id } = useSelector((state) => state.messages); 22 | const changeColorMode = (to) => { 23 | if (to) { 24 | localStorage.setItem("darkMode", true); 25 | 26 | document.body.className = "dark"; 27 | } else { 28 | localStorage.removeItem("darkMode"); 29 | 30 | document.body.className = "light"; 31 | } 32 | }; 33 | 34 | const getFiles = async () => { 35 | let res = null; 36 | if (!_id) return console.log("No chat id"); 37 | else { 38 | try { 39 | res = await instance.get("/api/chat/upload?chatId=" + _id); 40 | } catch (err) { 41 | console.log(err); 42 | } finally { 43 | if (res?.data) { 44 | console.log(res.data); 45 | setDocuments(res?.data?.data); 46 | } 47 | } 48 | } 49 | }; 50 | // Dark & Light Mode 51 | useLayoutEffect(() => { 52 | let mode = localStorage.getItem("darkMode"); 53 | 54 | if (mode) { 55 | changeColorMode(true); 56 | } else { 57 | changeColorMode(false); 58 | } 59 | }); 60 | 61 | // Offline 62 | useEffect(() => { 63 | window.addEventListener("online", (e) => { 64 | location.reload(); 65 | }); 66 | 67 | window.addEventListener("offline", (e) => { 68 | setOffline(true); 69 | }); 70 | }); 71 | 72 | return ( 73 | 74 |
75 | {user && ( 76 |
77 | 82 |
83 | )} 84 | 85 | {loading && } 86 | 87 | {offline && ( 88 | 92 | )} 93 | 94 | 95 | }> 96 | 104 | } 105 | /> 106 | 113 | } 114 | /> 115 | 122 | } 123 | /> 124 | 125 | 126 | }> 127 | } /> 128 | } /> 129 | } /> 130 | } /> 131 | } /> 132 | } /> 133 | 134 | 138 | } 139 | /> 140 | 141 |
142 |
143 | ); 144 | }; 145 | 146 | export default App; 147 | 148 | -------------------------------------------------------------------------------- /client/src/components/auth/signup.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useCallback, useReducer, useState } from 'react' 2 | import { Grant, Tick, Google, Microsoft, Mail, } from '../../assets' 3 | import { Link, useNavigate } from 'react-router-dom' 4 | import { useGoogleLogin } from '@react-oauth/google' 5 | import FormFeild from './FormFeild' 6 | import axios from 'axios' 7 | import instance from '../../config/instance' 8 | import './style.scss' 9 | 10 | const reducer = (state, { type, status }) => { 11 | switch (type) { 12 | case 'filled': 13 | return { filled: status } 14 | case 'error': 15 | return { error: status, filled: state.filled } 16 | case 'mail': 17 | return { mail: status, error: !status } 18 | default: return state 19 | } 20 | } 21 | 22 | const SignupComponent = () => { 23 | 24 | const navigate = useNavigate() 25 | 26 | const [state, stateAction] = useReducer(reducer, { 27 | filled: false, 28 | error: false 29 | }) 30 | 31 | const [formData, setFormData] = useState({ 32 | email: '', 33 | pass: '', 34 | manual: false 35 | }) 36 | 37 | const handleInput = (e) => { 38 | setFormData({ 39 | ...formData, 40 | [e.target.name]: e.target.value 41 | }) 42 | } 43 | 44 | const formHandle = async (e) => { 45 | e?.preventDefault() 46 | if (formData?.pass.length >= 8 && (formData?.email.endsWith("@protondatalabs.com") || formData?.email.endsWith("@grantengine.com"))) { 47 | let res = null 48 | try { 49 | res = await instance.post('/api/user/signup', formData) 50 | } catch (err) { 51 | console.log(err) 52 | if (err?.response?.data.message?.exists) { 53 | stateAction({ type: 'error', status: true }) 54 | } else { 55 | stateAction({ type: 'error', status: false }) 56 | } 57 | 58 | } finally { 59 | if (res?.data?.status === 208) { 60 | navigate('/') 61 | } else if (res?.['data']?.data?.manual) { 62 | stateAction({ type: 'mail', status: true }) 63 | } else if (res) { 64 | stateAction({ type: 'error', status: false }) 65 | if (res['data']?.data?._id) navigate(`/signup/pending/${res?.['data']?.data._id}`) 66 | } 67 | } 68 | } else { 69 | alert("Make sure you are using Grant Engine's Email and your password is greater that 8 characters") 70 | } 71 | } 72 | 73 | const googleAuth = useGoogleLogin({ 74 | onSuccess: async response => { 75 | let res = null 76 | try { 77 | res = await axios.get("https://www.googleapis.com/oauth2/v3/userinfo", { 78 | headers: { 79 | "Authorization": `Bearer ${response.access_token}` 80 | } 81 | }) 82 | } catch (err) { 83 | console.log(err) 84 | } finally { 85 | if (res?.data?.email_verified) { 86 | setFormData({ 87 | ...formData, 88 | manual: false, 89 | email: res.data.email, 90 | token: response.access_token 91 | }) 92 | 93 | stateAction({ type: 'filled', status: true }) 94 | } 95 | } 96 | 97 | } 98 | }) 99 | 100 | const passwordClass = useCallback((remove, add) => { 101 | document.querySelector(remove).classList?.remove('active') 102 | document.querySelector(add).classList?.add('active') 103 | }, []) 104 | 105 | return ( 106 |
107 |
108 | 109 |
110 | 111 | { 112 | !state.mail ? ( 113 | 114 |
115 |

Create your account

116 | 117 |

Please note that phone verification is required for signup. Your number will only be used to verify your identity for security purposes.

118 | 119 |
120 | 121 | { 122 | !state.filled ? ( 123 |
124 |
{ 125 | e.preventDefault() 126 | setFormData({ ...formData, manual: true }) 127 | stateAction({ type: 'filled', status: true }) 128 | }}> 129 |
130 | 131 | 138 |
139 |
140 | 141 |
142 |
143 | 144 |
145 | Already have an account? 146 | Log in 147 |
148 | 149 |
150 |
151 | OR 152 |
153 | 154 |
155 | 156 |
157 | 158 |
159 |
160 | ) : ( 161 |
162 |
163 |
164 | 167 | 168 | 174 |
175 | 176 |
177 | {state?.error &&
!
The user already exists.
} 178 |
179 | 180 |
181 | 182 | 190 | 191 |
192 | 193 |
194 | Your password must contain: 195 | 196 |

197 | 198 |   199 | At least 8 characters 200 |

201 | 202 |

203 | 204 |   205 | At least 8 characters 206 |

207 |
208 | 209 | 210 | 211 |
212 |
213 | Already have an account? 214 | Log in 215 |
216 |
217 | ) 218 | } 219 |
220 | ) : ( 221 |
222 |
223 | 224 |
225 | 226 |
227 |

Check Your Email

228 |
229 | 230 |
231 |

Please check the email address {formData.email} for instructions to signup.

232 |
233 | 234 | 235 |
236 | ) 237 | } 238 |
239 | ) 240 | } 241 | 242 | export default SignupComponent 243 | -------------------------------------------------------------------------------- /client/src/components/auth/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer, useState } from "react"; 2 | import { Grant, Google, Microsoft } from "../../assets"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import FormFeild from "./FormFeild"; 5 | import { useGoogleLogin } from "@react-oauth/google"; 6 | import { useDispatch } from "react-redux"; 7 | import { insertUser } from "../../redux/user"; 8 | import instance from "../../config/instance"; 9 | import "./style.scss"; 10 | 11 | const reducer = (state, { type, status }) => { 12 | switch (type) { 13 | case "filled": 14 | return { filled: status }; 15 | case "error": 16 | return { error: status, filled: state.filled }; 17 | default: 18 | return state; 19 | } 20 | }; 21 | 22 | const LoginComponent = () => { 23 | const dispatch = useDispatch(); 24 | const navigate = useNavigate(); 25 | const [email, setEmail] = useState(false); 26 | const [password, setPassword] = useState(false); 27 | const [otp, setOtp] = useState(""); 28 | const [state, stateAction] = useReducer(reducer, { 29 | filled: false, 30 | password: false, 31 | error: false, 32 | }); 33 | 34 | const [formData, setFormData] = useState({ 35 | email: "", 36 | pass: "", 37 | otp: "", 38 | manual: true, 39 | }); 40 | 41 | const handleInput = (e) => { 42 | setFormData({ 43 | ...formData, 44 | [e.target.name]: e.target.value, 45 | }); 46 | }; 47 | 48 | const googleAuth = useGoogleLogin({ 49 | onSuccess: (response) => { 50 | formHandle(null, { 51 | manual: false, 52 | token: response.access_token, 53 | }); 54 | }, 55 | }); 56 | 57 | const otpHandle = async (e) => { 58 | e.preventDefault(); 59 | const given_otp = e.target.otp.value; 60 | try { 61 | console.log(formData.email, given_otp) 62 | const otp_res = await instance.post("/api/user/verify_otp", { 63 | email: formData.email, 64 | otp: given_otp, 65 | }); 66 | console.log(otp_res.data); 67 | if (otp_res.status === 200 && otp_res.data.message === "Success") { 68 | dispatch(insertUser(otp_res.data.data)); 69 | navigate("/chat"); 70 | } 71 | } catch (err) { 72 | if (err.response.status === 422) { 73 | stateAction({ type: "error", status: true }); 74 | alert("Invalid OTP") 75 | } 76 | console.log(err); 77 | } 78 | }; 79 | 80 | const formHandle = async (e, googleData) => { 81 | e?.preventDefault(); 82 | let res = null; 83 | try { 84 | res = await instance.get("/api/user/login", { 85 | params: googleData || { email: formData.email, pass: formData.pass }, 86 | }); 87 | console.log(googleData); 88 | } catch (err) { 89 | console.log(err); 90 | if (err?.response?.data?.status === 422) { 91 | stateAction({ type: "error", status: true }); 92 | } 93 | } finally { 94 | if (res?.data?.message === "Success" && res?.data?.status === 200) { 95 | console.log(res.data); 96 | // stateAction({ type: "error", status: false }); 97 | const user_otp = Math.floor(1000 + Math.random() * 9000); 98 | setPassword(true); 99 | setOtp(user_otp); 100 | const otp_res = await instance.post("/api/user/send_otp", { 101 | email: formData.email, 102 | otp: user_otp, 103 | }); 104 | 105 | console.log(user_otp); 106 | console.log(otp_res.data) 107 | // dispatch(insertUser(res.data.data)); 108 | // navigate("/two_step_verification"); 109 | } 110 | } 111 | }; 112 | 113 | return ( 114 |
115 |
116 | 117 |
118 | 119 |
120 | {!state.filled ?

Welcome back

:

Enter your password

} 121 |
122 | 123 | {!email && !password ? ( 124 | 131 | ) : email && !password ? ( 132 | 139 | ) : ( 140 | 146 | )} 147 |
148 | ); 149 | }; 150 | 151 | const LoginEmail = ({ 152 | formData, 153 | handleInput, 154 | googleAuth, 155 | stateAction, 156 | setEmail, 157 | }) => { 158 | return ( 159 |
160 |
{ 163 | e.preventDefault(); 164 | stateAction({ type: "filled", status: true }); 165 | setEmail(true); 166 | }} 167 | > 168 |
169 | 176 |
177 |
178 | 179 |
180 |
181 | 182 |
183 | Don't have an account? 184 | Sign up 185 |
186 | 187 |
188 |
189 | OR 190 |
191 | 192 |
193 | 196 |
197 |
198 |
199 | ); 200 | }; 201 | 202 | const LoginEmailPassword = ({ 203 | formHandle, 204 | stateAction, 205 | formData, 206 | handleInput, 207 | state, 208 | }) => { 209 | return ( 210 |
211 |
212 |
213 | 221 | 222 | 228 |
229 | 230 |
231 | 239 |
240 | 241 |
242 | {state?.error && ( 243 |
244 |
!
Email or password wrong. 245 |
246 | )} 247 |
248 | 249 | 250 | 251 |
252 | Forgot password? 253 |
254 |
255 |
256 | Don't have an account? 257 | Sign up 258 |
259 |
260 | ); 261 | }; 262 | 263 | const Two_Step_Auth = ({ formData, handleInput, otpHandle, state }) => { 264 | return ( 265 |
266 |

Two Step Verification

267 |
268 |
269 |
270 | 278 |
279 | 280 |
281 |
282 | Don't have an account? 283 | Sign up 284 |
285 |
286 |
287 | ); 288 | }; 289 | 290 | export default LoginComponent; 291 | -------------------------------------------------------------------------------- /client/src/components/content/style.scss: -------------------------------------------------------------------------------- 1 | @import '../../index.scss'; 2 | 3 | @keyframes txtBlink { 4 | 0% { 5 | border-right: 4px solid; 6 | } 7 | 8 | 50% { 9 | border-right: none; 10 | } 11 | 12 | 100% { 13 | border-right: 4px solid; 14 | } 15 | } 16 | 17 | .light { 18 | .New { 19 | .card-bg { 20 | background: rgba(247, 247, 248, 1); 21 | border-radius: .375rem; 22 | padding: .75rem; 23 | 24 | p { 25 | font-size: 14px; 26 | } 27 | 28 | &:is(.hover):hover { 29 | background: rgba(236, 236, 241, 1); 30 | cursor: pointer; 31 | } 32 | } 33 | } 34 | 35 | .Chat { 36 | .qs { 37 | background: white; 38 | 39 | .txt { 40 | color: #343541; 41 | } 42 | } 43 | 44 | .res { 45 | border-top: 1px solid #d9d9e3; 46 | border-bottom: 1px solid #d9d9e3; 47 | background: rgba(247, 247, 248, 1); 48 | 49 | .txt { 50 | .error { 51 | color: #374151; 52 | } 53 | 54 | span { 55 | color: #374151; 56 | 57 | &span::after { 58 | border-color: #374151; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | .dark { 67 | .New { 68 | .card-bg { 69 | background: hsla(0, 0%, 100%, .05); 70 | border-radius: .375rem; 71 | padding: .75rem; 72 | 73 | p { 74 | font-size: 14px; 75 | } 76 | 77 | &:is(.hover):hover { 78 | background: rgba(32, 33, 35, 1); 79 | cursor: pointer; 80 | } 81 | } 82 | } 83 | 84 | .Chat { 85 | .qs { 86 | background: rgba(52, 53, 65, 1); 87 | 88 | .txt { 89 | color: #ECECF1; 90 | } 91 | } 92 | 93 | .res { 94 | background: #444654; 95 | border-top: 1px solid rgba(32, 33, 35, .5); 96 | border-bottom: 1px solid rgba(32, 33, 35, .5); 97 | 98 | .txt { 99 | .error { 100 | color: #D1D5DB; 101 | } 102 | 103 | span { 104 | color: #D1D5DB; 105 | 106 | &::after { 107 | border-color: #D1D5DB; 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | .New { 116 | display: block; 117 | padding-left: 12em; 118 | padding-right: 12em; 119 | padding-top: 7em; 120 | padding-bottom: 12rem; 121 | 122 | @media only screen and (max-width: 1300px) { 123 | padding-left: 5em; 124 | padding-right: 5em; 125 | } 126 | 127 | @media only screen and (max-width: 1000px) { 128 | padding-left: 1.5em; 129 | padding-right: 1.5em; 130 | } 131 | 132 | .title { 133 | font-size: 2.25rem; 134 | font-weight: 600; 135 | margin-bottom: 2em; 136 | } 137 | 138 | .flex { 139 | display: flex; 140 | flex-direction: row; 141 | gap: .875rem; 142 | 143 | @media screen and (max-width:767px) { 144 | flex-direction: column; 145 | } 146 | 147 | svg { 148 | width: 24px; 149 | height: 24px; 150 | } 151 | 152 | .inner { 153 | flex-basis: 100%; 154 | display: flex; 155 | flex-direction: column; 156 | 157 | @media screen and (max-width:767px) { 158 | flex-basis: 100%; 159 | } 160 | 161 | .card { 162 | margin-bottom: 1em; 163 | 164 | h4 { 165 | font-weight: 400; 166 | font-size: 1.125rem; 167 | line-height: 1.75rem; 168 | } 169 | } 170 | } 171 | 172 | } 173 | } 174 | 175 | .Chat { 176 | display: block; 177 | text-align: left; 178 | padding-bottom: 12rem; 179 | 180 | .qs { 181 | padding-top: 1.5rem; 182 | padding-bottom: 1.5rem; 183 | padding-left: 10em; 184 | padding-right: 12em; 185 | display: grid; 186 | grid-template-columns: calc(30px + 1.5rem) auto; 187 | 188 | @media only screen and (max-width: 1300px) { 189 | padding-left: 5em; 190 | padding-right: 5em; 191 | } 192 | 193 | @media only screen and (max-width: 1000px) { 194 | padding-left: 1em; 195 | padding-right: 1em; 196 | } 197 | 198 | @media only screen and (max-width: 767px) { 199 | padding-top: 4rem; 200 | grid-template-columns: calc(30px + 1rem) auto; 201 | } 202 | 203 | .acc { 204 | display: flex; 205 | width: 30px; 206 | height: 30px; 207 | border: none; 208 | background: green; 209 | color: white; 210 | justify-content: center; 211 | align-items: center; 212 | border-radius: 2px; 213 | font-weight: 500; 214 | margin-right: 1.5rem; 215 | 216 | @media only screen and (max-width: 767px) { 217 | margin-right: 1rem; 218 | } 219 | } 220 | 221 | .txt { 222 | line-height: 1.5rem; 223 | font-size: 1rem; 224 | } 225 | } 226 | 227 | .res { 228 | padding-top: 1.5rem; 229 | padding-bottom: 1.5rem; 230 | padding-left: 10em; 231 | padding-right: 12em; 232 | display: grid; 233 | grid-template-columns: calc(30px + 1.5rem) auto; 234 | 235 | @media only screen and (max-width: 1300px) { 236 | padding-left: 5em; 237 | padding-right: 5em; 238 | } 239 | 240 | @media only screen and (max-width: 1000px) { 241 | padding-left: 1em; 242 | padding-right: 1em; 243 | } 244 | 245 | @media only screen and (max-width: 767px) { 246 | grid-template-columns: calc(30px + 1rem) auto; 247 | } 248 | 249 | .icon { 250 | position: relative; 251 | display: flex; 252 | width: 30px; 253 | height: 30px; 254 | border: none; 255 | background: #191949; 256 | justify-content: center; 257 | align-items: center; 258 | border-radius: 2px; 259 | font-weight: 500; 260 | margin-right: 1.5rem; 261 | 262 | @media only screen and (max-width: 767px) { 263 | margin-right: 1rem; 264 | } 265 | 266 | svg { 267 | width: 1.5rem; 268 | height: 1.5rem; 269 | color: white; 270 | } 271 | 272 | span { 273 | position: absolute; 274 | background: rgba(239, 68, 68, 1); 275 | border-radius: 50%; 276 | font-size: 10px; 277 | width: 1rem; 278 | height: 1rem; 279 | display: flex; 280 | align-items: center; 281 | justify-content: center; 282 | right: -4px; 283 | bottom: -4px; 284 | color: rgba(255,255,255,1); 285 | border: 1px solid rgba(255,255,255,1); 286 | } 287 | } 288 | 289 | .txt { 290 | display: block; 291 | 292 | .error { 293 | background: rgba(239, 68, 68, .1); 294 | border: 1px solid rgba(239, 68, 68, 1); 295 | border-radius: .375rem; 296 | padding: 0.75rem; 297 | font-size: .875rem; 298 | line-height: 1.25rem; 299 | } 300 | 301 | span { 302 | line-height: 1.5rem; 303 | font-size: 1rem; 304 | white-space: pre-wrap; 305 | 306 | &:is(.blink) { 307 | &::after { 308 | content: ''; 309 | padding-left: 3px; 310 | border-right: 4px solid; 311 | animation: txtBlink 1s ease-in-out infinite; 312 | } 313 | } 314 | } 315 | } 316 | } 317 | } -------------------------------------------------------------------------------- /client/src/components/auth/forgot.jsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useCallback, useReducer, useState } from 'react' 2 | import { Grant, Tick, Mail } from '../../assets' 3 | import { useNavigate } from 'react-router-dom' 4 | import FormFeild from './FormFeild' 5 | import instance from '../../config/instance' 6 | import './style.scss' 7 | 8 | const reducer = (state, { type, status }) => { 9 | switch (type) { 10 | case 'mail': 11 | return { mail: status } 12 | case 'error': 13 | return { error: status } 14 | default: return state 15 | } 16 | } 17 | 18 | const ForgotComponent = ({ isRequest, userId, secret }) => { 19 | const [state, stateAction] = useReducer(reducer, { 20 | mail: false 21 | }) 22 | 23 | const navigate = useNavigate() 24 | 25 | const passwordClass = useCallback((remove, add) => { 26 | document.querySelector(remove).classList?.remove('active') 27 | document.querySelector(add).classList?.add('active') 28 | }, []) 29 | 30 | const [formData, setFormData] = useState({ 31 | email: '', 32 | newPass: '', 33 | reEnter: '' 34 | }) 35 | 36 | const handleInput = (e) => { 37 | setFormData({ 38 | ...formData, 39 | [e.target.name]: e.target.value 40 | }) 41 | } 42 | 43 | const formHandleMail = async (e) => { 44 | if (e) { e.preventDefault() } 45 | let res = null 46 | try { 47 | res = await instance.post('/api/user/forgot-request', { 48 | email: formData.email 49 | }) 50 | } catch (err) { 51 | console.log(err) 52 | if (err?.response?.data?.status === 422) { 53 | stateAction({ type: 'error', status: true }) 54 | } 55 | } finally { 56 | if (res?.data?.status === 208) { 57 | navigate('/') 58 | } else if (res) { 59 | stateAction({ type: 'mail', status: true }) 60 | } 61 | } 62 | } 63 | 64 | const formHandleReset = async (e) => { 65 | e.preventDefault() 66 | if (userId && secret && formData?.newPass.length >= 8) { 67 | if (formData?.newPass === formData?.reEnter) { 68 | stateAction({ type: 'error', status: false }) 69 | 70 | let res = null 71 | try { 72 | res = await instance.put('/api/user/forgot-finish', { 73 | userId, 74 | secret, 75 | newPass: formData.newPass, 76 | reEnter: formData.reEnter 77 | }) 78 | } catch (err) { 79 | console.log(err) 80 | alert(err) 81 | navigate('/forgot') 82 | } finally { 83 | if (res) { 84 | navigate('/login') 85 | } 86 | } 87 | } else { 88 | stateAction({ type: 'error', status: true }) 89 | } 90 | } 91 | } 92 | 93 | return ( 94 |
95 |
96 | 97 |
98 | 99 | { 100 | isRequest ? ( 101 | 102 | { 103 | !state.mail ? ( 104 | 105 |
106 |

Reset your password

107 | 108 |

Enter your email address and we will send you instructions to reset your password.

109 |
110 | 111 |
112 |
113 |
114 | 115 | 123 | 124 | {state?.error &&
!
The user not exists.
} 125 |
126 | 127 | 128 | 129 |
130 | 133 |
134 |
135 |
136 |
137 | ) 138 | : ( 139 |
140 |
141 | 142 |
143 | 144 |
145 |

Check Your Email

146 |
147 | 148 |
149 |

Please check the email address {formData?.email} for instructions to reset your password.

150 |
151 | 152 | 153 |
154 | ) 155 | } 156 |
157 | ) : ( 158 | 159 |
160 |

Enter your password

161 |

Enter a new password below to change your password.

162 |
163 |
164 |
165 |
166 | 167 | 176 | 177 |
178 | 179 |
180 | 181 | 189 | 190 |
191 | 192 | {state?.error &&
!
Password not match.
} 193 | 194 |
195 | Your password must contain: 196 | 197 |

198 | 199 |   200 | At least 8 characters 201 |

202 | 203 |

204 | 205 |   206 | At least 8 characters 207 |

208 |
209 | 210 | 211 |
212 |
213 |
214 | ) 215 | } 216 | 217 |
218 | ) 219 | } 220 | 221 | export default ForgotComponent 222 | -------------------------------------------------------------------------------- /client/public/logo.svg: -------------------------------------------------------------------------------- 1 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | -------------------------------------------------------------------------------- /client/src/assets/grant.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Grant = () => { 4 | return ( 5 | 14 | 19 | 24 | 29 | 34 | 39 | 44 | 49 | 54 | 59 | 64 | 69 | 74 | 79 | 84 | 89 | 94 | 99 | 104 | 105 | ) 106 | } 107 | 108 | export default Grant 109 | -------------------------------------------------------------------------------- /server/helpers/chat.js: -------------------------------------------------------------------------------- 1 | import { db } from "../db/connection.js"; 2 | import collections from "../db/collections.js"; 3 | import { ObjectId } from "mongodb"; 4 | import OpenAI from "openai"; 5 | 6 | export default { 7 | newResponse: (prompt, { openai }, userId) => { 8 | return new Promise(async (resolve, reject) => { 9 | let chatId = new ObjectId().toHexString(); 10 | let res = null; 11 | try { 12 | await db 13 | .collection(collections.CHAT) 14 | .createIndex({ user: 1 }, { unique: true }); 15 | let dataObj = { 16 | chatId, 17 | chats: [ 18 | { 19 | role: "user", 20 | content: prompt, 21 | }, 22 | { 23 | role: "assistant", 24 | content: openai, 25 | }, 26 | ], 27 | chat: [ 28 | { 29 | prompt: prompt, 30 | content: openai, 31 | }, 32 | ], 33 | }; 34 | 35 | res = await db.collection(collections.CHAT).insertOne({ 36 | user: userId.toString(), 37 | data: [dataObj], 38 | }); 39 | } catch (err) { 40 | if (err?.code === 11000) { 41 | let updateQuery = { 42 | user: userId.toString(), 43 | }; 44 | let pushQuery = { 45 | $push: { 46 | data: { 47 | chatId, 48 | chats: [ 49 | { 50 | role: "user", 51 | content: prompt, 52 | }, 53 | { 54 | role: "assistant", 55 | content: openai, 56 | }, 57 | ], 58 | chat: [ 59 | { 60 | prompt: prompt, 61 | content: openai, 62 | }, 63 | ], 64 | }, 65 | }, 66 | }; 67 | 68 | res = await db 69 | .collection(collections.CHAT) 70 | .updateOne(updateQuery, pushQuery); 71 | } else { 72 | reject(err); 73 | } 74 | } finally { 75 | if (res) { 76 | res.chatId = chatId; 77 | resolve(res); 78 | } else { 79 | reject({ text: "DB gets something wrong" }); 80 | } 81 | } 82 | }); 83 | }, 84 | Response: (prompt, { openai }, userId, chatId, assistant_id, file_name) => { 85 | return new Promise(async (resolve, reject) => { 86 | let res = null; 87 | try { 88 | let updateObj = { 89 | $push: { 90 | "data.$.chats": { 91 | $each: [ 92 | { role: "user", content: prompt }, 93 | { role: "assistant", content: openai }, 94 | ], 95 | }, 96 | "data.$.chat": { 97 | prompt: prompt, 98 | content: openai, 99 | }, 100 | }, 101 | }; 102 | // If file_name is not empty and not already present in the array, push it 103 | if (file_name && file_name.trim() !== "") { 104 | updateObj.$addToSet = { 105 | "data.$.file_name": file_name, 106 | }; 107 | } 108 | // If assistant_id is null, set it to the incoming assistant_id 109 | if (assistant_id !== null) { 110 | updateObj.$set = { 111 | "data.$.assistant_id": assistant_id, 112 | }; 113 | } 114 | 115 | // Execute the update operation 116 | res = await db.collection(collections.CHAT).updateOne( 117 | { 118 | user: userId.toString(), 119 | "data.chatId": chatId, 120 | }, 121 | updateObj 122 | ); 123 | } catch (err) { 124 | reject(err); 125 | } finally { 126 | if (res) { 127 | res.chatId = chatId; 128 | resolve(res); 129 | } else { 130 | reject({ text: "DB gets something wrong" }); 131 | } 132 | } 133 | }); 134 | }, 135 | 136 | updateChat: (chatId, prompt, { openai }, userId) => { 137 | return new Promise(async (resolve, reject) => { 138 | let res = await db 139 | .collection(collections.CHAT) 140 | .updateOne( 141 | { 142 | user: userId.toString(), 143 | "data.chatId": chatId, 144 | }, 145 | { 146 | $push: { 147 | data: { 148 | chatId, 149 | chats: [ 150 | { 151 | role: "user", 152 | content: prompt, 153 | }, 154 | { 155 | role: "assistant", 156 | content: openai, 157 | }, 158 | ], 159 | }, 160 | }, 161 | } 162 | ) 163 | .catch((err) => { 164 | reject(err); 165 | }); 166 | 167 | if (res) { 168 | resolve(res); 169 | } else { 170 | reject({ text: "DB gets something wrong" }); 171 | } 172 | }); 173 | }, 174 | getChat: (userId, chatId) => { 175 | return new Promise(async (resolve, reject) => { 176 | let res = await db 177 | .collection(collections.CHAT) 178 | .aggregate([ 179 | { 180 | $match: { 181 | user: userId.toString(), 182 | }, 183 | }, 184 | { 185 | $unwind: "$data", 186 | }, 187 | { 188 | $match: { 189 | "data.chatId": chatId, 190 | }, 191 | }, 192 | { 193 | $project: { 194 | _id: 0, 195 | chat: "$data.chat", 196 | }, 197 | }, 198 | ]) 199 | .toArray() 200 | .catch((err) => [reject(err)]); 201 | 202 | if (res && Array.isArray(res) && res[0]?.chat) { 203 | resolve(res[0].chat); 204 | } else { 205 | reject({ status: 404 }); 206 | } 207 | }); 208 | }, 209 | getHistory: (userId) => { 210 | return new Promise(async (resolve, reject) => { 211 | let res = await db 212 | .collection(collections.CHAT) 213 | .aggregate([ 214 | { 215 | $match: { 216 | user: userId.toString(), 217 | }, 218 | }, 219 | { 220 | $unwind: "$data", 221 | }, 222 | { 223 | $project: { 224 | _id: 0, 225 | chatId: "$data.chatId", 226 | chat: "$data.chat", // Project the entire 'chats' array 227 | }, 228 | }, 229 | ]) 230 | .toArray() 231 | .catch((err) => { 232 | reject(err); 233 | }); 234 | 235 | if (Array.isArray(res)) { 236 | resolve(res); 237 | } else { 238 | reject({ text: "DB Getting Some Error" }); 239 | } 240 | }); 241 | }, 242 | deleteAllChat: (userId) => { 243 | return new Promise((resolve, reject) => { 244 | db.collection(collections.CHAT) 245 | .deleteOne({ 246 | user: userId.toString(), 247 | }) 248 | .then((res) => { 249 | if (res?.deletedCount > 0) { 250 | resolve(res); 251 | } else { 252 | reject({ text: "DB Getting Some Error" }); 253 | } 254 | }) 255 | .catch((err) => { 256 | reject(err); 257 | }); 258 | }); 259 | }, 260 | 261 | //Get all message for OpenAI History 262 | Messages: (userId, chatId) => { 263 | return new Promise(async (resolve, reject) => { 264 | let res = await db 265 | .collection(collections.CHAT) 266 | .aggregate([ 267 | { 268 | $match: { 269 | user: userId.toString(), 270 | }, 271 | }, 272 | { 273 | $unwind: "$data", 274 | }, 275 | { 276 | $match: { 277 | "data.chatId": chatId, 278 | }, 279 | }, 280 | { 281 | $project: { 282 | _id: 0, 283 | chats: "$data.chats", // Project the entire 'chats' array 284 | }, 285 | }, 286 | ]) 287 | .toArray() 288 | .catch((err) => { 289 | reject(err); 290 | }); 291 | 292 | if (Array.isArray(res)) { 293 | resolve(res); 294 | } else { 295 | reject({ text: "DB Getting Some Error" }); 296 | } 297 | }); 298 | }, 299 | //Get all file name 300 | getFiles: (userId, chatId) => { 301 | return new Promise(async (resolve, reject) => { 302 | let res = await db 303 | .collection(collections.CHAT) 304 | .aggregate([ 305 | { 306 | $match: { 307 | user: userId, 308 | }, 309 | }, 310 | { 311 | $unwind: "$data", 312 | }, 313 | { 314 | $match: { 315 | "data.chatId": chatId, 316 | }, 317 | }, 318 | { 319 | $project: { 320 | _id: 0, 321 | file_name: "$data.file_name", // Project the entire 'FileName' array 322 | }, 323 | }, 324 | ]) 325 | .toArray() 326 | .catch((err) => { 327 | reject(err); 328 | }); 329 | 330 | if (Array.isArray(res)) { 331 | resolve(res); 332 | } else { 333 | reject({ text: "DB Getting Some Error" }); 334 | } 335 | }); 336 | }, 337 | deleteFile: (userId, chatId, file_name, file_id) => { 338 | const client = new OpenAI({ 339 | apiKey: "api -key", 340 | }); 341 | return new Promise(async (resolve, reject) => { 342 | try { 343 | const result = await db.collection(collections.CHAT).updateOne( 344 | { 345 | user: userId.toString(), 346 | "data.chatId": chatId, 347 | }, 348 | { 349 | $pull: { 350 | "data.$.file_name": file_name, 351 | "data.$.files": file_id, 352 | }, 353 | } 354 | ); 355 | const files_data = await db.collection(collections.CHAT).aggregate([ 356 | { 357 | $match: { 358 | user: userId.toString(), 359 | }, 360 | }, 361 | { 362 | $unwind: "$data", 363 | }, 364 | { 365 | $match: { 366 | "data.chatId": chatId, 367 | }, 368 | }, 369 | { 370 | $project: { 371 | _id: 0, 372 | file_id: "$data.files", 373 | }, 374 | }, 375 | ]).toArray(); 376 | let assistant = null; 377 | if (files_data[0]?.file_id?.length === 0) { 378 | assistant = { 379 | id: null 380 | } 381 | } else { 382 | assistant = await client.beta.assistants.create({ 383 | name: "GE CoPilot", 384 | instructions: 385 | "You are a helpful and that answers what is asked. Retrieve the relevant information from the files.", 386 | tools: [{ type: "retrieval" }, { type: "code_interpreter" }], 387 | model: "gpt-4-0125-preview", 388 | file_ids: files_data[0]?.file_id, 389 | }); 390 | } 391 | const result_chat_update = await db 392 | .collection(collections.CHAT) 393 | .updateOne( 394 | { 395 | user: userId.toString(), 396 | "data.chatId": chatId, 397 | }, 398 | { 399 | $set: { 400 | "data.$.assistant_id": assistant.id, 401 | }, 402 | } 403 | ); 404 | if (result_chat_update.modifiedCount === 0) { 405 | reject({ text: "No matching documents found" }); 406 | return; 407 | } 408 | resolve(result); 409 | } catch (err) { 410 | reject(err); // Reject with the caught error 411 | } 412 | }); 413 | }, 414 | }; 415 | -------------------------------------------------------------------------------- /client/src/components/menu/style.scss: -------------------------------------------------------------------------------- 1 | header { 2 | display: none; 3 | } 4 | 5 | .exitMenu { 6 | display: none; 7 | } 8 | .details-modal { 9 | background: #ffffff; 10 | border-radius: 0.5em; 11 | box-shadow: 0 10px 20px rgba(black, 0.2); 12 | left: 50%; 13 | max-width: 90%; 14 | pointer-events: none; 15 | position: absolute; 16 | top: 50%; 17 | transform: translate(-50%, -50%); 18 | width: 30em; 19 | text-align: left; 20 | max-height: 90vh; 21 | display: flex; 22 | flex-direction: column; 23 | 24 | // -------------- CLOSE 25 | 26 | .details-modal-close { 27 | align-items: center; 28 | color: #111827; 29 | display: flex; 30 | height: 4.5em; 31 | justify-content: center; 32 | pointer-events: none; 33 | position: absolute; 34 | right: 0; 35 | top: 0; 36 | width: 4.5em; 37 | 38 | svg { 39 | display: block; 40 | } 41 | } 42 | 43 | // -------------- TITLE 44 | 45 | .details-modal-title { 46 | color: #111827; 47 | padding: 1.5em 2em; 48 | pointer-events: all; 49 | position: relative; 50 | width: calc(100% - 4.5em); 51 | 52 | h1 { 53 | font-size: 1.25rem; 54 | font-weight: 600; 55 | line-height: normal; 56 | } 57 | } 58 | 59 | // -------------- CONTENT 60 | 61 | .details-modal-content { 62 | border-top: 1px solid #e0e0e0; 63 | padding: 2em; 64 | pointer-events: all; 65 | overflow: auto; 66 | } 67 | } 68 | 69 | // -------------- OVERLAY 70 | 71 | .details-modal-overlay { 72 | transition: opacity 0.2s ease-out; 73 | pointer-events: none; 74 | background: rgba(#0f172a, 0.8); 75 | position: fixed; 76 | opacity: 0; 77 | bottom: 0; 78 | right: 0; 79 | left: 0; 80 | top: 0; 81 | 82 | details[open] & { 83 | pointer-events: all; 84 | opacity: 0.5; 85 | } 86 | } 87 | 88 | @media screen and (max-width: 767px) { 89 | .exitMenu { 90 | display: none; 91 | position: fixed; 92 | width: calc(100vw - 260px); 93 | height: 100%; 94 | background: rgba(86, 88, 105, 0.75); 95 | top: 0; 96 | z-index: 2000; 97 | left: 260px; 98 | 99 | button { 100 | width: 40px; 101 | height: 40px; 102 | border: none; 103 | background: transparent; 104 | margin-top: 10px; 105 | margin-left: 10px; 106 | cursor: pointer; 107 | outline: none; 108 | 109 | &:focus { 110 | border: 2px solid white; 111 | } 112 | 113 | svg { 114 | color: white; 115 | width: 24px; 116 | height: 24px; 117 | } 118 | } 119 | } 120 | 121 | header { 122 | display: grid; 123 | grid-template-columns: auto auto auto; 124 | text-align: center; 125 | position: fixed; 126 | top: 0; 127 | width: 100%; 128 | background: rgba(52, 53, 65, 1); 129 | padding-top: 0.5em; 130 | padding-bottom: 0.3em; 131 | color: rgba(217, 217, 227, 1); 132 | z-index: 1000; 133 | box-shadow: 0px 0px 1px 1px hsla(0, 0%, 100%, 0.2); 134 | 135 | button { 136 | background: transparent; 137 | padding: 0; 138 | margin: 0; 139 | border: 0; 140 | color: rgba(217, 217, 227, 1); 141 | cursor: pointer; 142 | } 143 | 144 | .start { 145 | text-align: left; 146 | padding-left: 0.5em; 147 | } 148 | 149 | svg { 150 | width: 24px; 151 | height: 24px; 152 | } 153 | 154 | .title { 155 | margin-left: 1rem; 156 | margin-right: 0.8rem; 157 | padding-top: 3px; 158 | white-space: nowrap; 159 | text-overflow: ellipsis; 160 | overflow: hidden; 161 | } 162 | 163 | .end { 164 | text-align: right; 165 | padding-right: 0.5em; 166 | } 167 | } 168 | } 169 | 170 | .menu { 171 | position: fixed; 172 | z-index: 2000; 173 | width: 260px; 174 | background: #191949; // Changes 175 | height: 100%; 176 | display: flex; 177 | flex-direction: column; 178 | 179 | @media screen and (max-width: 767px) { 180 | display: none; 181 | 182 | &:is(.showMd), 183 | &:is(.showMd) + .exitMenu { 184 | display: flex; 185 | } 186 | } 187 | 188 | button { 189 | position: relative; 190 | color: rgba(255, 255, 255, 1); 191 | width: 100%; 192 | transition: all 0.2s ease-in-out; 193 | border: none; 194 | padding-top: 0.75rem; 195 | padding-bottom: 0.75rem; 196 | padding-left: 3em; 197 | border-radius: 0.375rem; 198 | background: transparent; 199 | text-align: left; 200 | font-weight: normal; 201 | font-size: 0.875rem; 202 | line-height: 1.25rem; 203 | 204 | &:hover { 205 | background: hsla(240, 9%, 59%, 0.1); 206 | cursor: pointer; 207 | } 208 | 209 | svg { 210 | position: absolute; 211 | padding-top: 2px; 212 | left: 0.75rem; 213 | width: 16px; 214 | height: 16px; 215 | } 216 | } 217 | 218 | button[aria-label="new"] { 219 | border: 1px solid hsla(0, 0%, 100%, 0.2); 220 | width: calc(260px - 1rem); 221 | margin-top: 0.5rem; 222 | margin-bottom: 0.6rem; 223 | margin-left: 0.5rem; 224 | } 225 | 226 | .history { 227 | height: 100%; 228 | overflow-y: auto; 229 | overflow-x: hidden; 230 | margin-bottom: 0.5rem; 231 | width: 259px; 232 | 233 | &::-webkit-scrollbar { 234 | width: 8px; 235 | } 236 | 237 | &::-webkit-scrollbar-track { 238 | background: transparent; 239 | } 240 | 241 | &::-webkit-scrollbar-thumb { 242 | background: rgba(86, 88, 105, 1); 243 | border-radius: 30px; 244 | 245 | &:hover { 246 | background: rgba(172, 172, 190, 1); 247 | } 248 | } 249 | 250 | .delete-btn { 251 | background: none; 252 | } 253 | 254 | button { 255 | width: calc(260px - 1rem); 256 | margin-left: 0.5rem; 257 | margin-bottom: 5px; 258 | padding-right: 0.75rem; 259 | white-space: nowrap; 260 | text-overflow: ellipsis; 261 | overflow: hidden; 262 | } 263 | 264 | .active { 265 | background: rgba(52, 53, 65, 1); 266 | } 267 | } 268 | 269 | .actions { 270 | width: calc(260px - 1rem); 271 | bottom: 0; 272 | border-top: 1px solid hsla(0, 0%, 100%, 0.2); 273 | padding-top: 5px; 274 | padding-bottom: 5px; 275 | background: #191949; //Changes 276 | margin-left: 0.5rem; 277 | 278 | button { 279 | margin-bottom: 3px; 280 | 281 | &:hover { 282 | background: hsla(240, 9%, 59%, 0.1); 283 | } 284 | 285 | svg { 286 | position: absolute; 287 | padding-top: 2px; 288 | left: 0.75rem; 289 | width: 16px; 290 | height: 16px; 291 | } 292 | 293 | img { 294 | position: absolute; 295 | left: 0.75rem; 296 | } 297 | 298 | span { 299 | margin-top: -2px; 300 | position: absolute; 301 | color: rgba(52, 53, 65, 1); 302 | background: rgba(250, 230, 158, 1); 303 | border-radius: 0.375rem; 304 | padding-left: 0.375rem; 305 | padding-right: 0.375rem; 306 | padding-top: 0.125rem; 307 | padding-bottom: 0.125rem; 308 | right: 0.75rem; 309 | } 310 | } 311 | } 312 | } 313 | 314 | .settingsModal { 315 | display: none; 316 | align-items: center; 317 | justify-content: center; 318 | position: fixed; 319 | z-index: 2100; 320 | width: 100%; 321 | height: 100%; 322 | overflow: hidden; 323 | transition: all 0.15s ease-in-out; 324 | -moz-transition: all 0.15s ease-in-out; 325 | 326 | @media screen and (max-width: 640px) { 327 | align-items: end; 328 | } 329 | 330 | .inner { 331 | display: block; 332 | background: white; 333 | padding: 1.5rem; 334 | border-radius: 0.5rem; 335 | height: auto; 336 | width: auto; 337 | margin: 1rem; 338 | 339 | .content { 340 | display: flex; 341 | flex-direction: column; 342 | gap: 20px; 343 | 344 | .content-input { 345 | display: flex; 346 | flex-direction: column; 347 | gap: 4px; 348 | } 349 | .content-submit { 350 | display: flex; 351 | justify-content: space-between; 352 | div { 353 | display: flex; 354 | gap: 4px; 355 | } 356 | 357 | .content-button { 358 | text-align: center; 359 | height: 30px; 360 | width: 100px; 361 | color: #ffffff; 362 | background: #191949; 363 | transition: all 0.1s ease-in-out; 364 | -moz-transition: all 0.1s ease-in-out; 365 | border-radius: 3px; 366 | border: none; 367 | outline: none; 368 | font-size: 16px; 369 | } 370 | } 371 | input { 372 | height: 30px; 373 | width: 95%; 374 | color: #2d333a; 375 | border: 1px solid #c2c8d0; 376 | background: transparent; 377 | border-radius: 3px; 378 | line-height: 1.1; 379 | outline: none; 380 | font-size: 16px; 381 | padding: 0.5rem 0.5rem; 382 | } 383 | 384 | &:is(.top) { 385 | h3 { 386 | font-weight: normal; 387 | line-height: 1.5rem; 388 | } 389 | 390 | button { 391 | background: none; 392 | border: none; 393 | width: 100%; 394 | text-align: right; 395 | cursor: pointer; 396 | 397 | svg { 398 | height: 20px; 399 | width: 20px; 400 | } 401 | } 402 | } 403 | 404 | &:is(.ceneter) { 405 | margin-top: 1rem; 406 | margin-bottom: 1rem; 407 | 408 | p { 409 | font-size: 0.875rem; 410 | line-height: 1.25rem; 411 | } 412 | 413 | button { 414 | width: 2.75rem; 415 | height: 1.5rem; 416 | border: none; 417 | border-radius: 9999px; 418 | position: relative; 419 | cursor: pointer; 420 | 421 | div { 422 | position: absolute; 423 | width: calc(1.5rem - 4px); 424 | border-radius: 50%; 425 | background: #fff; 426 | color: #fff; 427 | top: 2px; 428 | bottom: 2px; 429 | transition: all 0.15s ease-in-out; 430 | -moz-transition: all 0.15s ease-in-out; 431 | } 432 | } 433 | } 434 | } 435 | 436 | .bottum { 437 | button { 438 | font-size: 0.875rem; 439 | line-height: 1.25rem; 440 | background: none; 441 | border: none; 442 | margin-right: 1rem; 443 | cursor: pointer; 444 | 445 | // &:is(.end) { 446 | // padding-left: 1rem; 447 | // } 448 | } 449 | } 450 | } 451 | } 452 | 453 | .light { 454 | .settingsModal { 455 | background: hsla(240, 9%, 59%, 0.9); 456 | 457 | .inner { 458 | background: #fff; 459 | 460 | .top { 461 | h3 { 462 | color: rgba(32, 33, 35, 1); 463 | } 464 | 465 | svg { 466 | color: rgba(32, 33, 35, 1); 467 | } 468 | } 469 | 470 | .ceneter { 471 | p { 472 | color: rgba(86, 88, 105, 1); 473 | } 474 | 475 | button { 476 | background: rgba(217, 217, 227, 1); 477 | 478 | div { 479 | transform: translateX(2px); 480 | } 481 | } 482 | } 483 | } 484 | 485 | .bottum { 486 | button { 487 | color: rgba(86, 88, 105, 1); 488 | text-decoration: underline; 489 | 490 | // &:is(.end) { 491 | // border-left: 1px solid rgba(86, 88, 105, 1); 492 | // } 493 | } 494 | } 495 | } 496 | } 497 | 498 | .dark { 499 | .settingsModal { 500 | background: rgba(52, 53, 65, 0.9); 501 | 502 | .inner { 503 | background: rgba(32, 33, 35, 1); 504 | 505 | .top { 506 | h3 { 507 | color: #fff; 508 | } 509 | 510 | svg { 511 | color: #fff; 512 | } 513 | } 514 | 515 | .ceneter { 516 | p { 517 | color: rgba(197, 197, 210, 1); 518 | } 519 | 520 | button { 521 | background: #191949; //Changes 522 | 523 | div { 524 | transform: translateX(calc(2.75rem - 1.5rem + 2px)); 525 | } 526 | } 527 | } 528 | } 529 | 530 | .bottum { 531 | button { 532 | color: rgba(197, 197, 210, 1); 533 | text-decoration: underline; 534 | 535 | // &:is(.end) { 536 | // border-left: 1px solid rgba(197, 197, 210, 1); 537 | // } 538 | } 539 | } 540 | } 541 | } 542 | -------------------------------------------------------------------------------- /client/src/page/chat.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useReducer, useRef, useState } from "react"; 2 | import { Reload, Rocket, Stop } from "../assets"; 3 | import { Chat, New } from "../components"; 4 | import { useLocation, useNavigate, useParams } from "react-router-dom"; 5 | import { setLoading } from "../redux/loading"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import { addList, emptyAllRes, insertNew, livePrompt } from "../redux/messages"; 8 | import { emptyUser } from "../redux/user"; 9 | import { useContext } from "react"; 10 | import { documentsContext } from "./../App"; 11 | import instance from "../config/instance"; 12 | import Upload from "./../assets/upload"; 13 | import "./style.scss"; 14 | 15 | const reducer = (state, { type, status }) => { 16 | switch (type) { 17 | case "chat": 18 | return { 19 | chat: status, 20 | loading: status, 21 | resume: status, 22 | actionBtns: false, 23 | }; 24 | case "error": 25 | return { 26 | chat: true, 27 | error: status, 28 | resume: state.resume, 29 | loading: state.loading, 30 | actionBtns: state.actionBtns, 31 | }; 32 | case "resume": 33 | return { 34 | chat: true, 35 | resume: status, 36 | loading: status, 37 | actionBtns: true, 38 | }; 39 | default: 40 | return state; 41 | } 42 | }; 43 | 44 | const Main = ({ file_id, set_file_id }) => { 45 | let location = useLocation(); 46 | const navigate = useNavigate(); 47 | const dispatch = useDispatch(); 48 | const chatRef = useRef(); 49 | const { user } = useSelector((state) => state); 50 | const { id = null } = useParams(); 51 | const { _id } = useSelector((state) => state.messages); 52 | const { documents, getFiles } = useContext(documentsContext); 53 | const [status, stateAction] = useReducer(reducer, { 54 | chat: false, 55 | error: false, 56 | actionBtns: false, 57 | }); 58 | 59 | useEffect(() => { 60 | getFiles(); 61 | }, [_id]); 62 | 63 | useEffect(() => { 64 | if (user) { 65 | dispatch(emptyAllRes()); 66 | setTimeout(() => { 67 | if (id) { 68 | const getSaved = async () => { 69 | let res = null; 70 | try { 71 | res = await instance.get("/api/chat/saved", { 72 | params: { 73 | chatId: id, 74 | }, 75 | }); 76 | } catch (err) { 77 | console.log(err); 78 | if (err?.response?.data?.status === 404) { 79 | navigate("/404"); 80 | } else { 81 | alert(err); 82 | dispatch(setLoading(false)); 83 | } 84 | } finally { 85 | if (res?.data) { 86 | dispatch(addList({ _id: id, items: res?.data?.data })); 87 | stateAction({ type: "resume", status: false }); 88 | dispatch(setLoading(false)); 89 | } 90 | } 91 | }; 92 | 93 | getSaved(); 94 | } else { 95 | stateAction({ type: "chat", status: false }); 96 | dispatch(setLoading(false)); 97 | } 98 | }, 1000); 99 | } 100 | }, [location]); 101 | 102 | return ( 103 |
104 |
105 | {status.chat ? : } 106 |
107 | 108 | 117 |
118 | ); 119 | }; 120 | 121 | export default Main; 122 | 123 | //Input Area 124 | const InputArea = ({ 125 | status, 126 | chatRef, 127 | stateAction, 128 | file_id, 129 | set_file_id, 130 | getFiles, 131 | documents, 132 | }) => { 133 | let textAreaRef = useRef(); 134 | const navigate = useNavigate(); 135 | const dispatch = useDispatch(); 136 | const [last_prompt, set_last_prompt] = useState(null); 137 | let { prompt, content, _id } = useSelector((state) => state.messages); 138 | console.log(_id); 139 | 140 | useEffect(() => { 141 | getFiles(); 142 | }, [_id]); 143 | useEffect(() => { 144 | textAreaRef.current?.addEventListener("input", (e) => { 145 | textAreaRef.current.style.height = "auto"; 146 | textAreaRef.current.style.height = 147 | textAreaRef.current.scrollHeight + "px"; 148 | }); 149 | }); 150 | const handleFileUpload = async (e) => { 151 | let response = null; 152 | try { 153 | const file = e.target.files[0]; 154 | console.log(file); 155 | if ( 156 | file.name.endsWith(".pdf") || 157 | file.name.endsWith(".txt") || 158 | file.name.endsWith(".csv") || 159 | file.name.endsWith(".docx") 160 | ) { 161 | const formData = new FormData(); 162 | formData.append("file", file); 163 | formData.append("chatId", _id); 164 | response = await instance.post("/api/chat/upload", formData, { 165 | headers: { 166 | "Content-Type": "multipart/form-data", 167 | }, 168 | }); 169 | console.log(response.data); 170 | } else { 171 | alert("File type not supported"); 172 | } 173 | } catch (error) { 174 | console.log(error); 175 | } finally { 176 | if (response?.data?.data?.chatId) { 177 | dispatch(insertNew({ _id: response?.data?.data?.chatId })); 178 | console.log(response?.data?.data?.chatId); 179 | navigate(`/chat/${response?.data?.data?.chatId}`); 180 | alert("File uploaded successfully"); 181 | getFiles(); 182 | } else { 183 | alert(`File upload failed due to unsupported file type`); 184 | } 185 | } 186 | }; 187 | 188 | const deleteFile = async (e) => { 189 | let response = null; 190 | console.log(_id); 191 | try { 192 | const file = e.target.textContent; 193 | console.log(file, _id); 194 | response = await instance.post("/api/chat/deletefile", { 195 | chatId: _id, 196 | file_name: file, 197 | }); 198 | console.log(response?.data); 199 | } catch (error) { 200 | console.log(error); 201 | } finally { 202 | if (response?.status === 200) { 203 | getFiles(); 204 | } else { 205 | alert("File delete failed"); 206 | } 207 | } 208 | }; 209 | 210 | const FormHandle = async () => { 211 | prompt = last_prompt; 212 | if (prompt?.length > 0) { 213 | chatRef?.current?.clearResponse(); 214 | stateAction({ type: "chat", status: true }); 215 | 216 | let chatsId = Date.now(); 217 | 218 | dispatch(insertNew({ id: chatsId, content: "", prompt })); 219 | 220 | let res = null; 221 | 222 | try { 223 | if (_id) { 224 | console.log(prompt, content, _id); 225 | dispatch(livePrompt("")); 226 | res = await instance.put("/api/chat", { 227 | chatId: _id, 228 | prompt, 229 | file_id, 230 | }); 231 | console.log("PUT", res.data); 232 | } else { 233 | dispatch(livePrompt("")); 234 | res = await instance.post("/api/chat", { 235 | prompt, 236 | file_id, 237 | chatId: _id, 238 | }); 239 | console.log("POST", res.data); 240 | navigate(`/chat/${res?.data?.data?._id}`); 241 | } 242 | } catch (err) { 243 | console.log(err.response.data); 244 | if (err?.response?.data?.status === 405) { 245 | dispatch(emptyUser()); 246 | dispatch(emptyAllRes()); 247 | navigate("/login"); 248 | } else { 249 | stateAction({ type: "error", status: true }); 250 | } 251 | } finally { 252 | if (res?.data) { 253 | set_file_id(null); 254 | const { _id, content } = res?.data?.data; 255 | console.log(_id, content); 256 | dispatch(insertNew({ _id, fullContent: content, chatsId })); 257 | 258 | chatRef?.current?.loadResponse(stateAction, content, chatsId); 259 | 260 | stateAction({ type: "error", status: false }); 261 | } 262 | } 263 | } 264 | }; 265 | 266 | useEffect(() => { 267 | const handleInput = (e) => { 268 | if (e.key === "Enter" && e.shiftKey) { 269 | e.preventDefault(); 270 | FormHandle(textAreaRef.current.value.trim()); 271 | textAreaRef.current.value = ""; 272 | } 273 | }; 274 | 275 | textAreaRef.current?.addEventListener("keydown", handleInput); 276 | 277 | return () => { 278 | textAreaRef.current?.removeEventListener("keydown", handleInput); 279 | }; 280 | }, [FormHandle]); 281 | 282 | return ( 283 |
284 | {!status.error ? ( 285 | <> 286 |
287 | {status.chat && content?.length > 0 && status.actionBtns && ( 288 | <> 289 | {!status?.resume ? ( 290 | 298 | ) : ( 299 | 306 | )} 307 | 308 | )} 309 |
310 |
311 |
312 |
313 | 323 |
324 |
325 | {/*
326 | {documents?.length > 0 && 327 | documents?.map((doc, index) => { 328 | return ( 329 |
330 |

331 | {doc} 332 |

333 |
334 | ); 335 | })} 336 |
*/} 337 |