├── server ├── .gitignore ├── .dockerignore ├── Dockerfile ├── helper │ ├── cookieHandler.js │ └── tokenVerify.js ├── model │ ├── chatHistory.js │ ├── chat.js │ └── user.js ├── router │ ├── auth.js │ └── public.js ├── package.json ├── middleware │ ├── rateLimit.js │ └── auth.js ├── app.js ├── service │ └── auth_service.js └── controller │ ├── auth.js │ └── public.js ├── public ├── .dockerignore ├── public │ ├── netlify.toml │ ├── robots.txt │ ├── favicon.ico │ ├── icon-192x192.png │ ├── icon-256x256.png │ ├── icon-384x384.png │ ├── icon-512x512.png │ ├── index.html │ └── manifest.json ├── src │ ├── asset │ │ ├── avater-icon.png │ │ ├── icons8-hotel-48.png │ │ ├── icons8-flight-64.png │ │ ├── icons8-google-144.png │ │ ├── icons8-google-48.png │ │ ├── icons8-youtube-48.png │ │ ├── icons8-man-winner-48.png │ │ ├── darkIcon │ │ │ ├── icons8-code-50.png │ │ │ ├── icons8-copy-48.png │ │ │ ├── icons8-done-48.png │ │ │ ├── icons8-dot-30.png │ │ │ ├── icons8-help-50.png │ │ │ ├── icons8-idea-64.png │ │ │ ├── icons8-link-64.png │ │ │ ├── icons8-memo-64.png │ │ │ ├── icons8-menu-64.png │ │ │ ├── icons8-moon-64.png │ │ │ ├── icons8-ok-64.png │ │ │ ├── icons8-plus-50.png │ │ │ ├── icons8-send-48.png │ │ │ ├── icons8-wall-64.png │ │ │ ├── icons8-cross-50.png │ │ │ ├── icons8-activity-48.png │ │ │ ├── icons8-drop-down-50.png │ │ │ ├── icons8-message-48.png │ │ │ ├── icons8-settings-64.png │ │ │ ├── icons8-sign-out-50.png │ │ │ ├── icons8-up-arrow-25.png │ │ │ ├── icons8-collapse-arrow-30.png │ │ │ ├── icons8-collapse-arrow-50.png │ │ │ ├── icons8-collapse-arrow-64.png │ │ │ ├── icons8-expand-arrow-64.png │ │ │ ├── icons8-menu-vertical-30.png │ │ │ ├── icons8-circumnavigation-64.png │ │ │ └── darkIcon.js │ │ ├── icons8-google-maps-48.png │ │ ├── lightIcon │ │ │ ├── icons8-dot-48.png │ │ │ ├── icons8-ok-48.png │ │ │ ├── icons8-code-50.png │ │ │ ├── icons8-copy-48.png │ │ │ ├── icons8-cross-50.png │ │ │ ├── icons8-help-50.png │ │ │ ├── icons8-idea-64.png │ │ │ ├── icons8-link-50.png │ │ │ ├── icons8-memo-64.png │ │ │ ├── icons8-menu-50.png │ │ │ ├── icons8-menu-64.png │ │ │ ├── icons8-moon-64.png │ │ │ ├── icons8-plus-50.png │ │ │ ├── icons8-send-48.png │ │ │ ├── icons8-message-48.png │ │ │ ├── icons8-message-50.png │ │ │ ├── icons8-setting-48.png │ │ │ ├── icons8-sign-out-50.png │ │ │ ├── icons8-brick-wall-64.png │ │ │ ├── icons8-drop-down-50.png │ │ │ ├── icons8-delivery-time-50.png │ │ │ ├── icons8-expand-arrow-50.png │ │ │ ├── icons8-menu-vertical-30.png │ │ │ ├── icons8-collapse-arrow-50.png │ │ │ ├── icons8-circumnavigation-64.png │ │ │ └── lightIcon.js │ │ ├── bard_sparkle_processing_v2_loader.gif │ │ ├── gemini_sparkle_red_4ed1cbfcbc6c9e84c31b987da73fc4168aec8445.svg │ │ ├── gemini_sparkle_blue_33c17e77c4ebbdd9490b683b9812247e257b6f70.svg │ │ ├── bard_sparkle_v2.svg │ │ └── index.js │ ├── components │ │ ├── Ui │ │ │ ├── Loader.js │ │ │ ├── Loader.module.css │ │ │ ├── CopyBtn.module.css │ │ │ ├── CopyBtn.js │ │ │ ├── AdvanceGmini.js │ │ │ └── AdvanceGemini.module.css │ │ ├── NewChat │ │ │ ├── ScrollChat │ │ │ │ ├── NewChatGemini.js │ │ │ │ ├── ReplyByGemini.js │ │ │ │ ├── ScrollChat.module.css │ │ │ │ ├── ScrollChatModule.css │ │ │ │ └── ScrollChat.js │ │ │ ├── NewChat.js │ │ │ ├── PromptSection │ │ │ │ ├── PromptSection.js │ │ │ │ └── PromptSection.module.css │ │ │ └── NewChat.module.css │ │ ├── SettingSection │ │ │ ├── ToggleButton.js │ │ │ ├── ToggleButton.module.css │ │ │ ├── SettingSection.module.css │ │ │ └── SettingSecion.js │ │ ├── ChatSection │ │ │ ├── ChatSection.module.css │ │ │ └── ChatSection.js │ │ ├── UserIntroPrompt │ │ │ ├── UserIntroPrompt.js │ │ │ └── UserIntroPrompt.module.css │ │ ├── UserDetails │ │ │ ├── UserDetails.js │ │ │ └── UserDetails.module.css │ │ ├── InputSection │ │ │ ├── InputSection.js │ │ │ └── InputSection.module.css │ │ ├── Header │ │ │ ├── Header.module.css │ │ │ └── Header.js │ │ └── Sidebar │ │ │ ├── Sidebar.js │ │ │ └── Sidebar.module.css │ ├── store │ │ ├── index.js │ │ ├── auth.js │ │ ├── user.js │ │ ├── ui-gemini.js │ │ ├── user-action.js │ │ ├── chat.js │ │ ├── auth-action.js │ │ └── chat-action.js │ ├── App.css │ ├── index.js │ ├── utils │ │ └── getGoogleOauthUrl.js │ ├── App.js │ └── index.css ├── Dockerfile ├── .gitignore └── package.json ├── docker-compose.yaml ├── DISCLAIMER.md ├── PRIVACY-POLICY.md └── README.md /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /public/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .env -------------------------------------------------------------------------------- /public/public/netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/index.html" 4 | status = 200 -------------------------------------------------------------------------------- /public/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/public/favicon.ico -------------------------------------------------------------------------------- /public/public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/public/icon-192x192.png -------------------------------------------------------------------------------- /public/public/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/public/icon-256x256.png -------------------------------------------------------------------------------- /public/public/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/public/icon-384x384.png -------------------------------------------------------------------------------- /public/public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/public/icon-512x512.png -------------------------------------------------------------------------------- /public/src/asset/avater-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/avater-icon.png -------------------------------------------------------------------------------- /public/src/asset/icons8-hotel-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/icons8-hotel-48.png -------------------------------------------------------------------------------- /public/src/asset/icons8-flight-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/icons8-flight-64.png -------------------------------------------------------------------------------- /public/src/asset/icons8-google-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/icons8-google-144.png -------------------------------------------------------------------------------- /public/src/asset/icons8-google-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/icons8-google-48.png -------------------------------------------------------------------------------- /public/src/asset/icons8-youtube-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/icons8-youtube-48.png -------------------------------------------------------------------------------- /public/src/asset/icons8-man-winner-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/icons8-man-winner-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-code-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-code-50.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-copy-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-copy-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-done-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-done-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-dot-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-dot-30.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-help-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-help-50.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-idea-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-idea-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-link-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-link-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-memo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-memo-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-menu-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-menu-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-moon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-moon-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-ok-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-ok-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-plus-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-plus-50.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-send-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-send-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-wall-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-wall-64.png -------------------------------------------------------------------------------- /public/src/asset/icons8-google-maps-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/icons8-google-maps-48.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-dot-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-dot-48.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-ok-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-ok-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-cross-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-cross-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-code-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-code-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-copy-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-copy-48.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-cross-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-cross-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-help-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-help-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-idea-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-idea-64.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-link-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-link-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-memo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-memo-64.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-menu-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-menu-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-menu-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-menu-64.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-moon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-moon-64.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-plus-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-plus-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-send-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-send-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-activity-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-activity-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-drop-down-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-drop-down-50.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-message-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-message-48.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-settings-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-settings-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-sign-out-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-sign-out-50.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-up-arrow-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-up-arrow-25.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-message-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-message-48.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-message-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-message-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-setting-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-setting-48.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-sign-out-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-sign-out-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-brick-wall-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-brick-wall-64.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-drop-down-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-drop-down-50.png -------------------------------------------------------------------------------- /public/src/asset/bard_sparkle_processing_v2_loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/bard_sparkle_processing_v2_loader.gif -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-collapse-arrow-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-collapse-arrow-30.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-collapse-arrow-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-collapse-arrow-50.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-collapse-arrow-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-collapse-arrow-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-expand-arrow-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-expand-arrow-64.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-menu-vertical-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-menu-vertical-30.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-delivery-time-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-delivery-time-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-expand-arrow-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-expand-arrow-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-menu-vertical-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-menu-vertical-30.png -------------------------------------------------------------------------------- /public/src/asset/darkIcon/icons8-circumnavigation-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/darkIcon/icons8-circumnavigation-64.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-collapse-arrow-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-collapse-arrow-50.png -------------------------------------------------------------------------------- /public/src/asset/lightIcon/icons8-circumnavigation-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuvra-matrix/Gemini-Ai--MERN/HEAD/public/src/asset/lightIcon/icons8-circumnavigation-64.png -------------------------------------------------------------------------------- /public/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20.11.0 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | 7 | COPY package-lock.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY . . 12 | 13 | EXPOSE 3000 14 | 15 | CMD npm start -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20.11.0 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json ./ 6 | 7 | COPY package-lock.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY . . 12 | 13 | EXPOSE 3030 14 | 15 | CMD npm run dev -------------------------------------------------------------------------------- /public/src/components/Ui/Loader.js: -------------------------------------------------------------------------------- 1 | import styles from "./Loader.module.css"; 2 | 3 | const Loader = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Loader; 12 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .env 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /public/src/components/Ui/Loader.module.css: -------------------------------------------------------------------------------- 1 | .loader-container { 2 | width: 100%; 3 | height: 4px; 4 | background-color: #045f9c67; 5 | overflow: hidden; 6 | } 7 | 8 | .loader-bar { 9 | height: 100%; 10 | width: 0; 11 | background-color: #3498db; 12 | animation: progressAnimation 1s infinite; 13 | } 14 | 15 | @keyframes progressAnimation { 16 | 0% { 17 | width: 0; 18 | } 19 | 100% { 20 | width: 100%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | 3 | import uiReducer from "./ui-gemini"; 4 | import chatReducer from "./chat"; 5 | import userReducer from "./user"; 6 | import authReducer from "./auth"; 7 | 8 | const store = configureStore({ 9 | reducer: { 10 | ui: uiReducer, 11 | chat: chatReducer, 12 | user: userReducer, 13 | auth: authReducer, 14 | }, 15 | }); 16 | 17 | export default store; 18 | -------------------------------------------------------------------------------- /server/helper/cookieHandler.js: -------------------------------------------------------------------------------- 1 | export const getCookieValue = (cookieString, cookieName) => { 2 | try { 3 | const cookies = cookieString.split(";").map((cookie) => cookie.trim()); 4 | 5 | for (const cookie of cookies) { 6 | const [name, value] = cookie.split("="); 7 | if (name === cookieName) { 8 | return value; 9 | } 10 | } 11 | 12 | return null; 13 | } catch (err) { 14 | return null; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /public/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | min-height: 100dvh; 4 | display: flex; 5 | overflow: hidden; 6 | position: relative; 7 | } 8 | 9 | .bg-focus-dark { 10 | position: absolute; 11 | background-color: var(--bg-focus-pc); 12 | width: 100%; 13 | height: 100dvh; 14 | left: 0px; 15 | z-index: 5; 16 | } 17 | 18 | @media (max-width: 960px) { 19 | .bg-focus-dark { 20 | background-color: var(--bg-focus-mobile); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/src/store/auth.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const loginValue = localStorage.getItem("isLogin") || false; 4 | 5 | const authInitialState = { isLogin: loginValue }; 6 | 7 | const authSlice = createSlice({ 8 | name: "auth slice", 9 | initialState: authInitialState, 10 | reducers: { 11 | isLoginHandler(state, action) { 12 | state.isLogin = action.payload.isLogin; 13 | }, 14 | }, 15 | }); 16 | 17 | export const authAction = authSlice.actions; 18 | 19 | export default authSlice.reducer; 20 | -------------------------------------------------------------------------------- /public/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import { Provider } from "react-redux"; 6 | import { BrowserRouter } from "react-router-dom"; 7 | 8 | import store from "./store"; 9 | 10 | const root = ReactDOM.createRoot(document.getElementById("root")); 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /server/model/chatHistory.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const chatHistorySchema = new Schema({ 6 | title: { 7 | type: String, 8 | required: true, 9 | }, 10 | user: { 11 | type: Schema.Types.ObjectId, 12 | ref: "User", 13 | required: true, 14 | }, 15 | chat: { 16 | type: Schema.Types.ObjectId, 17 | ref: "Chat", 18 | }, 19 | timestamp: { 20 | type: Date, 21 | default: Date.now, 22 | }, 23 | }); 24 | 25 | export const chatHistory = mongoose.model("ChatHistory", chatHistorySchema); 26 | -------------------------------------------------------------------------------- /server/router/auth.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | const routes = express.Router(); 4 | 5 | import { 6 | googleOauthHandler, 7 | loginValidation, 8 | logoutHandler, 9 | refreshToken, 10 | } from "../controller/auth.js"; 11 | import { authMiddleware } from "../middleware/auth.js"; 12 | 13 | routes.get("/api/auth/google", authMiddleware, googleOauthHandler); 14 | 15 | routes.get("/api/auth/login", loginValidation); 16 | 17 | routes.get("/api/auth/logout", authMiddleware, logoutHandler); 18 | routes.get("/api/auth/resetToken", refreshToken); 19 | 20 | export default routes; 21 | -------------------------------------------------------------------------------- /public/src/components/NewChat/ScrollChat/NewChatGemini.js: -------------------------------------------------------------------------------- 1 | import DOMPurify from "dompurify"; 2 | import { useEffect } from "react"; 3 | import hljs from "highlight.js"; 4 | import "highlight.js/styles/github.css"; 5 | import "./ScrollChatModule.css"; 6 | 7 | const NewChatByGemini = (props) => { 8 | useEffect(() => { 9 | hljs.highlightAll(); 10 | }, [props.gemini]); 11 | 12 | return ( 13 |

19 | ); 20 | }; 21 | 22 | export default NewChatByGemini; 23 | -------------------------------------------------------------------------------- /public/src/components/SettingSection/ToggleButton.js: -------------------------------------------------------------------------------- 1 | import styles from "./ToggleButton.module.css"; 2 | 3 | const ToggleButton = (props) => { 4 | const buttonMainMode = props.theme === "dark" ? "on-main" : "off-main"; 5 | const buttonRoundMode = props.theme === "dark" ? "on-round" : "off-round"; 6 | 7 | return ( 8 |
12 |
15 |
16 | ); 17 | }; 18 | 19 | export default ToggleButton; 20 | -------------------------------------------------------------------------------- /public/src/store/user.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const userInitialState = { 4 | user: { name: "User", email: "", profileImg: "" }, 5 | location: "", 6 | }; 7 | 8 | const userSclice = createSlice({ 9 | name: "userSlice", 10 | initialState: userInitialState, 11 | reducers: { 12 | setLocation(state, action) { 13 | state.location = action.payload.location; 14 | }, 15 | setUserData(state, action) { 16 | state.user = action.payload.userData; 17 | }, 18 | }, 19 | }); 20 | 21 | export const userAction = userSclice.actions; 22 | 23 | export default userSclice.reducer; 24 | -------------------------------------------------------------------------------- /server/model/chat.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const chatSchema = new Schema({ 6 | chatHistory: { 7 | type: Schema.Types.ObjectId, 8 | ref: "ChatHistory", 9 | }, 10 | messages: [ 11 | { 12 | sender: { 13 | type: Schema.Types.ObjectId, 14 | ref: "User", 15 | required: true, 16 | }, 17 | message: { 18 | type: Object, 19 | required: true, 20 | }, 21 | timestamp: { 22 | type: Date, 23 | default: Date.now, 24 | }, 25 | }, 26 | ], 27 | }); 28 | 29 | export const chat = mongoose.model("Chat", chatSchema); 30 | -------------------------------------------------------------------------------- /public/src/utils/getGoogleOauthUrl.js: -------------------------------------------------------------------------------- 1 | export const continueWithGoogleOauth = () => { 2 | const rootUrl = "https://accounts.google.com/o/oauth2/v2/auth"; 3 | 4 | const options = { 5 | redirect_uri: process.env.REACT_APP_GOOGLE_OAUTH_REDIRECT_URL, 6 | client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID, 7 | access_type: "offline", 8 | response_type: "code", 9 | prompt: "consent", 10 | scope: [ 11 | "https://www.googleapis.com/auth/userinfo.profile", 12 | "https://www.googleapis.com/auth/userinfo.email", 13 | ].join(" "), 14 | }; 15 | 16 | const qs = new URLSearchParams(options); 17 | return `${rootUrl}?${qs.toString()}`; 18 | }; 19 | -------------------------------------------------------------------------------- /server/router/public.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | const router = express.Router(); 4 | 5 | import { 6 | getGeminiHome, 7 | postGemini, 8 | getChatHistory, 9 | postChat, 10 | updateLocation, 11 | } from "../controller/public.js"; 12 | import { authMiddleware } from "../middleware/auth.js"; 13 | import { rateLimit } from "../middleware/rateLimit.js"; 14 | 15 | router.get("/api", getGeminiHome); 16 | router.post("/api/chat", authMiddleware, rateLimit, postGemini); 17 | router.get("/api/getchathistory", authMiddleware, getChatHistory); 18 | router.post("/api/chatdata", authMiddleware, postChat); 19 | router.put("/api/updatelocation", authMiddleware, updateLocation); 20 | 21 | export default router; 22 | -------------------------------------------------------------------------------- /public/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | Gemini 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@google/generative-ai": "^0.2.1", 4 | "bcryptjs": "^2.4.3", 5 | "cookie-parser": "^1.4.6", 6 | "cors": "^2.8.5", 7 | "dotenv": "^16.4.5", 8 | "express": "^4.18.2", 9 | "jsonwebtoken": "^9.0.2", 10 | "mongoose": "^8.1.3", 11 | "nodemailer": "^6.9.10", 12 | "request-ip": "^3.3.0" 13 | }, 14 | "name": "gemini-ai-api", 15 | "type": "module", 16 | "version": "1.0.0", 17 | "main": "app.js", 18 | "devDependencies": { 19 | "nodemon": "^3.0.3" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1", 23 | "start": "node app.js", 24 | "dev": "nodemon app.js" 25 | }, 26 | "author": "", 27 | "license": "ISC", 28 | "description": "" 29 | } 30 | -------------------------------------------------------------------------------- /public/src/asset/gemini_sparkle_red_4ed1cbfcbc6c9e84c31b987da73fc4168aec8445.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/src/asset/gemini_sparkle_blue_33c17e77c4ebbdd9490b683b9812247e257b6f70.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/src/components/NewChat/ScrollChat/ReplyByGemini.js: -------------------------------------------------------------------------------- 1 | import DOMPurify from "dompurify"; 2 | import { useState, useEffect } from "react"; 3 | import "./ScrollChatModule.css"; 4 | 5 | const ReplyByGemini = (props) => { 6 | const [currentIndex, setCurrentIndex] = useState(0); 7 | 8 | useEffect(() => { 9 | const intervalId = setInterval(() => { 10 | if (currentIndex < props?.gemini?.length) { 11 | setCurrentIndex(currentIndex + 1); 12 | } 13 | }, 5); 14 | 15 | return () => clearInterval(intervalId); 16 | }, [props?.gemini, currentIndex]); 17 | 18 | return ( 19 |

25 | ); 26 | }; 27 | 28 | export default ReplyByGemini; 29 | -------------------------------------------------------------------------------- /public/src/components/SettingSection/ToggleButton.module.css: -------------------------------------------------------------------------------- 1 | .btn-main { 2 | width: 40px; 3 | height: 15px; 4 | border-radius: 18px; 5 | border: 0px; 6 | position: absolute; 7 | right: 20px; 8 | cursor: pointer; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | transition: all 0.4s ease; 13 | } 14 | 15 | .on-main { 16 | background-color: #1f75e9; 17 | } 18 | 19 | .off-main { 20 | background-color: #676767; 21 | } 22 | 23 | .btn-round { 24 | width: 22px; 25 | height: 22px; 26 | border: 0px; 27 | border-radius: 50%; 28 | position: absolute; 29 | transition: all 0.4s ease; 30 | } 31 | 32 | .on-round { 33 | background-color: #aecbfa; 34 | left: 24px; 35 | } 36 | 37 | .off-round { 38 | background-color: #cecece; 39 | left: -2px; 40 | } 41 | 42 | .off-round:hover { 43 | background-color: rgb(231, 231, 231); 44 | } 45 | -------------------------------------------------------------------------------- /public/src/components/NewChat/NewChat.js: -------------------------------------------------------------------------------- 1 | import styles from "./NewChat.module.css"; 2 | import PromptSection from "./PromptSection/PromptSection"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { uiAction } from "../../store/ui-gemini"; 5 | 6 | const NewChat = () => { 7 | const dispatch = useDispatch(); 8 | const isSideBarLong = useSelector((state) => state.ui.isSidebarLong); 9 | const userDetails = useSelector((state) => state.user.user); 10 | 11 | const sideBarCloseHandler = () => { 12 | if (isSideBarLong === true) { 13 | dispatch(uiAction.toggleSideBar()); 14 | } 15 | }; 16 | return ( 17 |
18 |
19 |

Hello, {userDetails?.name.split(" ")[0]}.

20 |

How can I help you today?

21 |
22 | 23 |
24 | ); 25 | }; 26 | 27 | export default NewChat; 28 | -------------------------------------------------------------------------------- /public/src/components/ChatSection/ChatSection.module.css: -------------------------------------------------------------------------------- 1 | .chat-section-main { 2 | width: 100%; 3 | min-height: 100dvh; 4 | background-color: var(--chat-section-bg); 5 | position: relative; 6 | } 7 | 8 | .warning-text { 9 | background-color: var(--chat-section-bg); 10 | width: 100%; 11 | position: absolute; 12 | bottom: -15px; 13 | left: 50%; 14 | transform: translate(-50%, -50%); 15 | margin: 12px auto; 16 | padding: 10px 0px; 17 | } 18 | 19 | .warning-text p, 20 | .warning-text a { 21 | color: var(--warning-text-color); 22 | font-size: 12px; 23 | line-height: 16px; 24 | letter-spacing: 0.1px; 25 | text-align: center; 26 | } 27 | 28 | @media (max-width: 1106px) { 29 | .warning-text { 30 | padding: 10px 20px; 31 | } 32 | } 33 | 34 | @media (max-width: 680px) { 35 | .warning-text { 36 | bottom: -10px; 37 | margin: 2px auto; 38 | padding: 1px 20px; 39 | } 40 | } 41 | 42 | @media (max-width: 360px) { 43 | .warning-text { 44 | margin: 0px auto; 45 | bottom: 4px; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/middleware/rateLimit.js: -------------------------------------------------------------------------------- 1 | export const rateLimit = async (req, res, next) => { 2 | const user = req.user; 3 | if (req.auth === "auth") { 4 | user.currentLimit = 0; 5 | await user.save(); 6 | return next(); 7 | } 8 | try { 9 | const maxRateLimit = user.maxRateLimit; 10 | const currentTime = Date.now(); 11 | const timeDifference = currentTime - user.recentRateLimitTime; 12 | if (timeDifference > 60 * 60 * 1000) { 13 | user.currentLimit = 0; 14 | user.recentRateLimitTime = currentTime; 15 | } else { 16 | if (user.currentLimit > maxRateLimit) { 17 | const error = new Error("Rate limit exceeded"); 18 | error.data = 19 | "Rate Limit Exceeded. Please wait for one hour before trying again. Thank you for your patience."; 20 | error.statusCode = 429; 21 | return next(error); 22 | } 23 | } 24 | await user.save(); 25 | next(); 26 | } catch (error) { 27 | return res.status(500).json({ error: "Internal server error" }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /server/model/user.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const userSchema = new Schema({ 6 | name: { 7 | type: String, 8 | }, 9 | email: { 10 | type: String, 11 | }, 12 | profileImg: { 13 | type: String, 14 | }, 15 | timestamp: { 16 | type: Date, 17 | default: Date.now, 18 | }, 19 | resetToken: { 20 | type: String, 21 | }, 22 | expireAccessToken: [ 23 | { 24 | type: Object, 25 | }, 26 | ], 27 | expireRefreshToken: [ 28 | { 29 | type: Object, 30 | }, 31 | ], 32 | ip: { 33 | type: String, 34 | }, 35 | location: { 36 | type: String, 37 | }, 38 | chatHistory: [ 39 | { 40 | type: Schema.Types.ObjectId, 41 | ref: "ChatHistory", 42 | }, 43 | ], 44 | maxRateLimit: { 45 | type: Number, 46 | default: 10, 47 | }, 48 | currentLimit: { 49 | type: Number, 50 | default: 0, 51 | }, 52 | recentRateLimitTime: { 53 | type: Number, 54 | default: 0, 55 | }, 56 | }); 57 | 58 | export const user = mongoose.model("User", userSchema); 59 | -------------------------------------------------------------------------------- /public/src/components/UserIntroPrompt/UserIntroPrompt.js: -------------------------------------------------------------------------------- 1 | import styles from "./UserIntroPrompt.module.css"; 2 | import { commonIcon } from "../../asset"; 3 | import { themeIcon } from "../../asset"; 4 | import { useDispatch } from "react-redux"; 5 | import { uiAction } from "../../store/ui-gemini"; 6 | 7 | const UserIntroPrompt = () => { 8 | const dispatch = useDispatch(); 9 | const icon = themeIcon(); 10 | 11 | const crossHndler = () => { 12 | dispatch(uiAction.userIntroPromptHandler({ introPrompt: false })); 13 | }; 14 | 15 | return ( 16 |
17 | google big 18 |

19 | Experience limitless conversations! Sign in to unlock 20 | unlimited chat and browse through your entire chat 21 | history—more than the last 5 interactions. 22 |

23 |
24 | cross 25 |
26 |
27 | ); 28 | }; 29 | 30 | export default UserIntroPrompt; 31 | -------------------------------------------------------------------------------- /public/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "public", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^2.2.1", 7 | "@testing-library/jest-dom": "^5.17.0", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "dompurify": "^3.0.9", 11 | "highlight.js": "^11.9.0", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-redux": "^9.1.0", 15 | "react-router-dom": "^6.22.1", 16 | "react-scripts": "5.0.1", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/src/components/Ui/CopyBtn.module.css: -------------------------------------------------------------------------------- 1 | .copy-icon { 2 | position: absolute; 3 | width: 35px; 4 | height: 35px; 5 | bottom: -40px; 6 | right: 0px; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | cursor: pointer; 11 | border-radius: 50%; 12 | user-select: none; 13 | -webkit-user-select: none; 14 | } 15 | 16 | .copy-icon:hover { 17 | background-color: var(--prompt-bg-hover); 18 | } 19 | 20 | .copy-icon img { 21 | width: 18px; 22 | height: 18px; 23 | } 24 | 25 | .copy-icon-one:hover::after { 26 | content: "Copy"; 27 | position: absolute; 28 | width: 40px; 29 | font-size: 12px; 30 | left: -45px; 31 | color: var(--plus-icon-before-font-color); 32 | background-color: var(--plus-icon-before-bg-color); 33 | padding: 4px 5px; 34 | border-radius: 5px; 35 | } 36 | 37 | .copy-message { 38 | position: absolute; 39 | display: inline; 40 | width: 60px; 41 | color: var(--plus-icon-before-font-color); 42 | background-color: var(--plus-icon-before-bg-color); 43 | font-size: 12px; 44 | left: -65px; 45 | padding: 4px 5px; 46 | border-radius: 5px; 47 | } 48 | 49 | .copy-message.fade-out { 50 | opacity: 0; 51 | } 52 | -------------------------------------------------------------------------------- /public/src/components/Ui/CopyBtn.js: -------------------------------------------------------------------------------- 1 | import styles from "./CopyBtn.module.css"; 2 | import { themeIcon } from "../../asset"; 3 | import { Fragment, useState } from "react"; 4 | 5 | const CopyBtn = (props) => { 6 | const [copied, setCopied] = useState(false); 7 | 8 | const copyHandler = () => { 9 | let text = props.data; 10 | const regex = /```([^`]+?)```/g; 11 | let code = text.match(regex); 12 | 13 | if (code) { 14 | text = code.reduce((acc, element) => acc + element, ""); 15 | } 16 | 17 | navigator.clipboard 18 | .writeText(text) 19 | .then(() => { 20 | setCopied(true); 21 | setTimeout(() => setCopied(false), 2000); 22 | }) 23 | .catch((err) => { 24 | console.error(err); 25 | }); 26 | }; 27 | 28 | const icon = themeIcon(); 29 | return ( 30 | 31 |
37 | copy icon 38 | {copied && Copied!} 39 |
40 |
41 | ); 42 | }; 43 | 44 | export default CopyBtn; 45 | -------------------------------------------------------------------------------- /public/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_color": "#131314", 3 | "background_color": "#131314", 4 | "display": "standalone", 5 | "scope": "https://geminichatai.netlify.app", 6 | "start_url": "https://geminichatai.netlify.app", 7 | "name": "Gemini Ai", 8 | "short_name": "Gemini AI", 9 | "description": "Revolutionize your online interactions with our web app \u2013 a stellar clone of Google Gemini Chat AI. Seamlessly blending cutting-edge technology with a sleek interface, our application provides users with an immersive conversational experience. Engage in natural language conversations, access advanced language models, and enjoy the power of interactive communication. Elevate your online interactions with our user-friendly and feature-rich AI-powered chat application.", 10 | "icons": [ 11 | { 12 | "src": "/icon-192x192.png", 13 | "sizes": "192x192", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "/icon-256x256.png", 18 | "sizes": "256x256", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "/icon-384x384.png", 23 | "sizes": "384x384", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "/icon-512x512.png", 28 | "sizes": "512x512", 29 | "type": "image/png" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /server/helper/tokenVerify.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { user } from "../model/user.js"; 3 | 4 | export const tokenVerify = (token) => { 5 | return new Promise((res, rej) => { 6 | const secret = process.env.ACCESS_TOKEN_JWT_SECRET; 7 | const decodeToken = jwt.verify(token, secret); 8 | 9 | if (!decodeToken) { 10 | const err = new Error("Token Invalid"); 11 | err.statusCode = 401; 12 | err.data = "invalid token"; 13 | return next(err); 14 | } 15 | 16 | const userEmail = decodeToken.email; 17 | 18 | user 19 | .findOne({ email: userEmail }) 20 | .then((userData) => { 21 | if (!userData) { 22 | const error = new Error("user not found"); 23 | error.statusCode = 403; 24 | throw error; 25 | } 26 | 27 | const isTokenPresent = userData.expireAccessToken.some( 28 | (blockedToken) => blockedToken === token 29 | ); 30 | 31 | if (isTokenPresent) { 32 | const error = new Error("invalid token"); 33 | throw error; 34 | } 35 | 36 | res(userData); 37 | }) 38 | .catch((err) => { 39 | if (!err.statusCode) { 40 | err.statusCode = 500; 41 | } 42 | rej(err); 43 | }); 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | services: 3 | client: 4 | image: gemini-client-image:latest 5 | container_name: gemini-client-container 6 | build: 7 | context: ./public 8 | dockerfile: Dockerfile 9 | ports: 10 | - "3000:3000" 11 | env_file: 12 | - ./public/.env 13 | depends_on: 14 | - server 15 | networks: 16 | - gemini-network 17 | 18 | develop: 19 | watch: 20 | - path: ./public/package.json 21 | action: rebuild 22 | - path: ./public/package-lock.json 23 | action: rebuild 24 | - path: ./public 25 | target: /app 26 | action: sync 27 | 28 | server: 29 | image: gemini-server-image:latest 30 | container_name: gemini-server-container 31 | build: 32 | context: ./server 33 | dockerfile: Dockerfile 34 | ports: 35 | - "3030:3030" 36 | env_file: 37 | - ./server/.env 38 | networks: 39 | - gemini-network 40 | develop: 41 | watch: 42 | - path: ./server/package.json 43 | action: rebuild 44 | - path: ./server/package-lock.json 45 | action: rebuild 46 | - path: ./server 47 | target: /app 48 | action: sync 49 | 50 | networks: 51 | gemini-network: 52 | driver: bridge 53 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import mongoose from "mongoose"; 3 | import requestIp from "request-ip"; 4 | import "dotenv/config"; 5 | import cros from "cors"; 6 | 7 | const MONGODB_URL = `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASS}mymongoinit.6md0cxy.mongodb.net/gemini?retryWrites=true&w=majority`; 8 | const PORT_NO = 3030; 9 | 10 | const app = express(); 11 | 12 | app.use(express.json()); 13 | app.use(requestIp.mw()); 14 | 15 | const originUrl = process.env.CLIENT_REDIRECT_URL; 16 | 17 | const crosOption = { 18 | origin: originUrl, 19 | optionsSuccessStatus: 200, 20 | credentials: true, 21 | }; 22 | 23 | app.use(cros(crosOption)); 24 | 25 | import publicRoutes from "./router/public.js"; 26 | import authRoutes from "./router/auth.js"; 27 | 28 | app.use("/gemini", publicRoutes); 29 | app.use(authRoutes); 30 | 31 | app.use((error, req, res, next) => { 32 | console.log(error); 33 | const status = error.statusCode || 500; 34 | const message = error.message; 35 | const data = error.data; 36 | 37 | res.status(status).json({ message: message, data: data, error: error }); 38 | }); 39 | 40 | mongoose 41 | .connect(MONGODB_URL) 42 | .then((result) => { 43 | app.listen(process.env.PORT || PORT_NO, () => { 44 | console.log(`Gemini server is running on port ${PORT_NO}`); 45 | }); 46 | }) 47 | .catch((err) => { 48 | console.log(err); 49 | }); 50 | -------------------------------------------------------------------------------- /public/src/asset/bard_sparkle_v2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/src/components/Ui/AdvanceGmini.js: -------------------------------------------------------------------------------- 1 | import styles from "./AdvanceGemini.module.css"; 2 | import { commonIcon } from "../../asset"; 3 | import { themeIcon } from "../../asset"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { useNavigate } from "react-router-dom"; 6 | import { chatAction } from "../../store/chat"; 7 | 8 | const AdvanceGemini = () => { 9 | const dispatch = useDispatch(); 10 | const navigate = useNavigate(); 11 | const isAdvanceGeminiPrompt = useSelector((state) => state.ui.isAdvanceShow); 12 | const advanceClass = isAdvanceGeminiPrompt ? "advance-on" : "advance-off"; 13 | 14 | const icon = themeIcon(); 15 | 16 | const newChatHandler = () => { 17 | dispatch(chatAction.replaceChat({ chats: [] })); 18 | dispatch(chatAction.newChatHandler()); 19 | navigate("/"); 20 | }; 21 | 22 | return ( 23 |
24 |

Model

25 | 26 |
27 | gemini icon 28 |

Gemini

29 | ok icon 30 |
31 | 32 |
33 | advance gemini 34 |

Gemini Advance

35 | 36 |
37 |
38 | ); 39 | }; 40 | 41 | export default AdvanceGemini; 42 | -------------------------------------------------------------------------------- /public/src/components/ChatSection/ChatSection.js: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom"; 2 | import Header from "../Header/Header"; 3 | import InputSection from "../InputSection/InputSection"; 4 | import NewChat from "../NewChat/NewChat"; 5 | import AdvanceGemini from "../Ui/AdvanceGmini"; 6 | import styles from "./ChatSection.module.css"; 7 | import { useSelector } from "react-redux"; 8 | import ScrollChat from "../NewChat/ScrollChat/ScrollChat"; 9 | import Loader from "../Ui/Loader"; 10 | 11 | const ChatSection = () => { 12 | const isLoader = useSelector((state) => state.chat.isLoader); 13 | return ( 14 |
15 |
16 | 17 | {isLoader && } 18 | 19 | }> 20 | }> 21 | }> 22 | 23 | 24 | 25 |
26 |

27 | Gemini may display inaccurate info, including about people, so 28 | double-check its responses. 29 | 30 | 31 | Your privacy & Gemini Apps 32 | 33 | 34 |

35 |
36 |
37 | ); 38 | }; 39 | 40 | export default ChatSection; 41 | -------------------------------------------------------------------------------- /public/src/store/ui-gemini.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const uiInitialState = { 4 | isDark: true, 5 | isSidebarLong: false, 6 | isRealTimeResponse: false, 7 | isSettingsShow: false, 8 | isAdvanceShow: false, 9 | isUserDetailsShow: false, 10 | showIntroUserPrompt: false, 11 | }; 12 | 13 | const uiCreteSlice = createSlice({ 14 | name: "uiSlice", 15 | initialState: uiInitialState, 16 | reducers: { 17 | toggleSideBar(state) { 18 | state.isSidebarLong = !state.isSidebarLong; 19 | }, 20 | toggleTheme(state) { 21 | state.isDark = !state.isDark; 22 | const theme = state.isDark ? "dark" : "light"; 23 | localStorage.setItem("theme", theme); 24 | }, 25 | toggleRealTimeResponse(state) { 26 | state.isRealTimeResponse = !state.isRealTimeResponse; 27 | const realtime = state.isRealTimeResponse ? "yes" : "no"; 28 | localStorage.setItem("realtime", realtime); 29 | }, 30 | toggleSettings(state) { 31 | state.isSettingsShow = !state.isSettingsShow; 32 | }, 33 | toggleAdvanceShow(state) { 34 | state.isAdvanceShow = !state.isAdvanceShow; 35 | }, 36 | toggleUserDetailsShow(state) { 37 | state.isUserDetailsShow = !state.isUserDetailsShow; 38 | }, 39 | userIntroPromptHandler(state, action) { 40 | state.showIntroUserPrompt = action.payload.introPrompt; 41 | }, 42 | }, 43 | }); 44 | 45 | export const uiAction = uiCreteSlice.actions; 46 | 47 | export default uiCreteSlice.reducer; 48 | -------------------------------------------------------------------------------- /public/src/components/UserIntroPrompt/UserIntroPrompt.module.css: -------------------------------------------------------------------------------- 1 | .intro-main { 2 | position: absolute; 3 | background-color: var(--user-details-bg); 4 | max-width: 350px; 5 | width: 100%; 6 | height: 200px; 7 | z-index: 6; 8 | border-radius: 24px; 9 | top: 80px; 10 | right: 20px; 11 | display: flex; 12 | gap: 15px; 13 | flex-flow: nowrap column; 14 | align-items: center; 15 | padding: 15px; 16 | color: var(--user-details-font-color); 17 | box-shadow: var(--user-details-box-shadow); 18 | user-select: none; 19 | -webkit-user-select: none; 20 | } 21 | 22 | .intro-main img { 23 | width: 50px; 24 | height: 50px; 25 | } 26 | 27 | .intro-main p { 28 | text-align: justify; 29 | font-weight: 500; 30 | font-size: 1.1rem; 31 | } 32 | 33 | .intro-main p span { 34 | font-weight: 700; 35 | color: var(--userprompt-text-span-color); 36 | } 37 | 38 | .cross { 39 | position: absolute; 40 | top: 10px; 41 | right: 10px; 42 | cursor: pointer; 43 | display: flex; 44 | justify-content: center; 45 | align-items: center; 46 | width: 35px; 47 | height: 35px; 48 | border-radius: 50%; 49 | } 50 | 51 | .cross:hover { 52 | background-color: var(--cross-hover-bg); 53 | } 54 | 55 | .cross img { 56 | width: 28px; 57 | height: 28px; 58 | } 59 | 60 | @media (max-width: 410px) { 61 | .intro-main { 62 | top: auto; 63 | bottom: 250px; 64 | right: 0px; 65 | border-radius: 0px; 66 | max-width: 100%; 67 | height: 230px; 68 | } 69 | .intro-main p { 70 | text-align: left; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /public/src/components/NewChat/PromptSection/PromptSection.js: -------------------------------------------------------------------------------- 1 | import styles from "./PromptSection.module.css"; 2 | import { suggestPrompt } from "../../../asset"; 3 | import { useState, useEffect } from "react"; 4 | import { themeIcon } from "../../../asset"; 5 | import { useDispatch } from "react-redux"; 6 | import { chatAction } from "../../../store/chat"; 7 | 8 | const PromptSection = () => { 9 | const dispatch = useDispatch(); 10 | const [randPrompt, setRandPrompt] = useState([]); 11 | const icon = themeIcon(); 12 | 13 | useEffect(() => { 14 | const getRandomPrompts = (list) => { 15 | const promptsCopy = [...list]; 16 | promptsCopy.sort(() => Math.random() - 0.5); 17 | return promptsCopy.slice(0, 4); 18 | }; 19 | const randomSuggestions = getRandomPrompts(suggestPrompt); 20 | setRandPrompt(randomSuggestions); 21 | }, []); 22 | 23 | const promptOnClick = (mainText) => { 24 | dispatch(chatAction.suggestPromptHandler({ prompt: mainText })); 25 | }; 26 | return ( 27 |
28 | {randPrompt.map((p) => ( 29 |
promptOnClick(p.long)} 33 | > 34 |

{p.sort}

35 |
36 | {icon[p.icon] ? ( 37 | icon 38 | ) : ( 39 | icon 40 | )} 41 |
42 |
43 | ))} 44 |
45 | ); 46 | }; 47 | 48 | export default PromptSection; 49 | -------------------------------------------------------------------------------- /public/src/store/user-action.js: -------------------------------------------------------------------------------- 1 | import { userAction } from "./user"; 2 | 3 | const SERVER_ENDPOINT = process.env.REACT_APP_SERVER_ENDPOINT; 4 | 5 | export const userUpdateLocation = () => { 6 | return (dispatch) => { 7 | if (navigator.geolocation) { 8 | navigator.geolocation.getCurrentPosition( 9 | (position) => { 10 | const { latitude, longitude } = position.coords; 11 | 12 | const url = `${SERVER_ENDPOINT}/gemini/api/updatelocation`; 13 | 14 | fetch(url, { 15 | method: "PUT", 16 | credentials: "include", 17 | headers: { 18 | "Content-Type": "application/json", 19 | }, 20 | body: JSON.stringify({ 21 | location: { 22 | lat: latitude, 23 | long: longitude, 24 | }, 25 | }), 26 | }) 27 | .then((response) => { 28 | if (!response.ok) { 29 | throw new Error("Update Location Failed"); 30 | } 31 | 32 | return response.json(); 33 | }) 34 | .then((data) => { 35 | dispatch(userAction.setLocation({ location: data.location })); 36 | localStorage.setItem("location", data.location); 37 | }) 38 | .catch((error) => { 39 | console.log(error); 40 | }); 41 | }, 42 | (error) => { 43 | console.error("Error getting messsage", error.message); 44 | } 45 | ); 46 | } else { 47 | console.error("Geolocation is not supported"); 48 | } 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /public/src/asset/darkIcon/darkIcon.js: -------------------------------------------------------------------------------- 1 | import plusIcon from "./icons8-plus-50.png"; 2 | import menuIcon from "./icons8-menu-64.png"; 3 | import dropIconSmall from "./icons8-drop-down-50.png"; 4 | import ideaIcon from "./icons8-idea-64.png"; 5 | import sendIcon from "./icons8-send-48.png"; 6 | import messageIcon from "./icons8-message-48.png"; 7 | import expandIcon from "./icons8-expand-arrow-64.png"; 8 | import threeDotIcon from "./icons8-menu-vertical-30.png"; 9 | import helpIcon from "./icons8-help-50.png"; 10 | import activityIcon from "./icons8-activity-48.png"; 11 | import settingsIcon from "./icons8-settings-64.png"; 12 | import dotIcon from "./icons8-dot-30.png"; 13 | import upArrowIcon from "./icons8-collapse-arrow-64.png"; 14 | import wallIcon from "./icons8-wall-64.png"; 15 | import moonIcon from "./icons8-moon-64.png"; 16 | import linkIcon from "./icons8-link-64.png"; 17 | import okIcon from "./icons8-ok-64.png"; 18 | import copyIcon from "./icons8-copy-48.png"; 19 | import crossIcon from "./icons8-cross-50.png"; 20 | import signOutIcon from "./icons8-sign-out-50.png"; 21 | import navigateIcon from "./icons8-circumnavigation-64.png"; 22 | import codeIcon from "./icons8-code-50.png"; 23 | import writeIcon from "./icons8-memo-64.png"; 24 | 25 | export const darkIcon = { 26 | plusIcon, 27 | menuIcon, 28 | dropIconSmall, 29 | ideaIcon, 30 | sendIcon, 31 | messageIcon, 32 | threeDotIcon, 33 | expandIcon, 34 | helpIcon, 35 | activityIcon, 36 | settingsIcon, 37 | dotIcon, 38 | upArrowIcon, 39 | wallIcon, 40 | moonIcon, 41 | linkIcon, 42 | okIcon, 43 | copyIcon, 44 | crossIcon, 45 | signOutIcon, 46 | navigateIcon, 47 | codeIcon, 48 | writeIcon, 49 | }; 50 | -------------------------------------------------------------------------------- /public/src/asset/lightIcon/lightIcon.js: -------------------------------------------------------------------------------- 1 | import plusIcon from "./icons8-plus-50.png"; 2 | import menuIcon from "./icons8-menu-64.png"; 3 | import dropIconSmall from "./icons8-drop-down-50.png"; 4 | import ideaIcon from "./icons8-idea-64.png"; 5 | import sendIcon from "./icons8-send-48.png"; 6 | import messageIcon from "./icons8-message-48.png"; 7 | import expandIcon from "./icons8-expand-arrow-50.png"; 8 | import threeDotIcon from "./icons8-menu-vertical-30.png"; 9 | import helpIcon from "./icons8-help-50.png"; 10 | import activityIcon from "./icons8-delivery-time-50.png"; 11 | import settingsIcon from "./icons8-setting-48.png"; 12 | import dotIcon from "./icons8-dot-48.png"; 13 | import upArrowIcon from "./icons8-collapse-arrow-50.png"; 14 | import wallIcon from "./icons8-brick-wall-64.png"; 15 | import moonIcon from "./icons8-moon-64.png"; 16 | import linkIcon from "./icons8-link-50.png"; 17 | import okIcon from "./icons8-ok-48.png"; 18 | import copyIcon from "./icons8-copy-48.png"; 19 | import crossIcon from "./icons8-cross-50.png"; 20 | import signOutIcon from "./icons8-sign-out-50.png"; 21 | import navigateIcon from "./icons8-circumnavigation-64.png"; 22 | import codeIcon from "./icons8-code-50.png"; 23 | import writeIcon from "./icons8-memo-64.png"; 24 | 25 | export const lightIcon = { 26 | plusIcon, 27 | menuIcon, 28 | dropIconSmall, 29 | ideaIcon, 30 | sendIcon, 31 | messageIcon, 32 | threeDotIcon, 33 | expandIcon, 34 | helpIcon, 35 | activityIcon, 36 | settingsIcon, 37 | dotIcon, 38 | upArrowIcon, 39 | wallIcon, 40 | moonIcon, 41 | linkIcon, 42 | okIcon, 43 | copyIcon, 44 | crossIcon, 45 | signOutIcon, 46 | navigateIcon, 47 | codeIcon, 48 | writeIcon, 49 | }; 50 | -------------------------------------------------------------------------------- /public/src/store/chat.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const inittalState = { 4 | chats: [], 5 | newChat: false, 6 | isLoader: false, 7 | recentChat: [], 8 | previousChat: [], 9 | chatHistoryId: "", 10 | suggestPrompt: "", 11 | }; 12 | 13 | const chatSlice = createSlice({ 14 | name: "chat", 15 | initialState: inittalState, 16 | reducers: { 17 | loaderHandler(state) { 18 | state.isLoader = !state.isLoader; 19 | }, 20 | newChatHandler(state) { 21 | state.chats.length > 0 ? (state.newChat = true) : (state.newChat = false); 22 | }, 23 | replaceChat(state, action) { 24 | state.chats = action.payload.chats; 25 | }, 26 | recentChatHandler(state, action) { 27 | state.recentChat = action.payload.recentChat; 28 | }, 29 | chatStart(state, action) { 30 | state.chats.push({ 31 | user: action.payload.useInput.user, 32 | isLoader: action.payload.useInput.isLoader, 33 | gemini: action.payload.useInput.gemini, 34 | id: Math.random(), 35 | newChat: true, 36 | }); 37 | }, 38 | popChat(state) { 39 | state.chats.pop(); 40 | }, 41 | previousChatHandler(state, action) { 42 | state.previousChat.push( 43 | action.payload.previousChat[0], 44 | action.payload.previousChat[1] 45 | ); 46 | }, 47 | replacePreviousChat(state, action) { 48 | state.previousChat = action.payload.previousChat; 49 | }, 50 | chatHistoryIdHandler(state, action) { 51 | state.chatHistoryId = action.payload.chatHistoryId; 52 | }, 53 | suggestPromptHandler(state, action) { 54 | state.suggestPrompt = action.payload.prompt; 55 | }, 56 | }, 57 | }); 58 | 59 | export const chatAction = chatSlice.actions; 60 | 61 | export default chatSlice.reducer; 62 | -------------------------------------------------------------------------------- /public/src/components/UserDetails/UserDetails.js: -------------------------------------------------------------------------------- 1 | import styles from "./UserDetails.module.css"; 2 | import { themeIcon } from "../../asset"; 3 | import { uiAction } from "../../store/ui-gemini"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { logoutHandler } from "../../store/auth-action"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | const UserDetails = () => { 9 | const navigate = useNavigate(); 10 | const dispatch = useDispatch(); 11 | const userDetails = useSelector((state) => state.user.user); 12 | const icon = themeIcon(); 13 | 14 | const userDetsilsClose = () => { 15 | dispatch(uiAction.toggleUserDetailsShow()); 16 | }; 17 | 18 | const onLogoutHandler = () => { 19 | dispatch(logoutHandler()); 20 | navigate("/"); 21 | }; 22 | 23 | return ( 24 |
25 |
26 |

{userDetails?.email}

27 | user icon 32 | 33 |

Hi, {userDetails?.name.split(" ")[0]}

34 | 35 |
36 | signout 37 |

Sign out

38 |
39 |
40 |
41 | 42 |

Privacy Policy

43 |
44 |

.

45 | 46 |

Terms of Service

47 |
48 |
49 |
50 | cross 51 |
52 |
53 | ); 54 | }; 55 | 56 | export default UserDetails; 57 | -------------------------------------------------------------------------------- /public/src/components/SettingSection/SettingSection.module.css: -------------------------------------------------------------------------------- 1 | .setting-main { 2 | position: absolute; 3 | width: 320px; 4 | height: 200px; 5 | background-color: var(--setting-main-bg); 6 | display: flex; 7 | justify-content: center; 8 | flex-direction: column; 9 | flex-wrap: nowrap; 10 | padding: 10px 0px; 11 | gap: 2px; 12 | z-index: 6; 13 | border: 0; 14 | border-radius: 6px; 15 | box-shadow: var(--setting-main-box-shadow); 16 | transition: all 0.2s ease-out; 17 | user-select: none; 18 | -webkit-user-select: none; 19 | } 20 | 21 | .settngs-show { 22 | left: 0px; 23 | bottom: 180px; 24 | } 25 | 26 | .settings-hide { 27 | opacity: 0; 28 | left: -400px; 29 | bottom: 100px; 30 | } 31 | 32 | .title h4 { 33 | font-size: 16px; 34 | font-weight: 500; 35 | color: var(--settings-h4-color); 36 | } 37 | 38 | .title, 39 | .public-link, 40 | .theme, 41 | .real-time { 42 | display: flex; 43 | align-items: center; 44 | gap: 15px; 45 | padding: 8px 20px; 46 | cursor: pointer; 47 | } 48 | 49 | .public-link:hover, 50 | .theme:hover, 51 | .real-time:hover { 52 | background-color: var(--settings-option-hover-bg); 53 | } 54 | 55 | .setting-main img { 56 | width: 25px; 57 | height: 25px; 58 | } 59 | 60 | .setting-main p { 61 | font-size: 16px; 62 | font-weight: 400; 63 | color: var(--settings-p-color); 64 | } 65 | 66 | @media (max-width: 960px) { 67 | .setting-main { 68 | width: 100%; 69 | bottom: 0px; 70 | gap: 8px; 71 | height: 220px; 72 | transition: all 0.4s ease-out; 73 | justify-content: flex-start; 74 | height: 250px; 75 | } 76 | .title, 77 | .public-link, 78 | .theme, 79 | .real-time { 80 | padding: 10px 20px; 81 | } 82 | 83 | .settngs-show { 84 | left: 0px; 85 | bottom: 0px; 86 | } 87 | 88 | .settings-hide { 89 | opacity: 0; 90 | left: 0px; 91 | bottom: -400px; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /public/src/components/InputSection/InputSection.js: -------------------------------------------------------------------------------- 1 | import styles from "./InputSection.module.css"; 2 | import { themeIcon } from "../../asset"; 3 | import { sendChatData } from "../../store/chat-action"; 4 | import { useEffect, useState } from "react"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | const InputSection = () => { 9 | const navigate = useNavigate(); 10 | const dispatch = useDispatch(); 11 | const [userInput, setUserInput] = useState(""); 12 | const previousChat = useSelector((state) => state.chat.previousChat); 13 | const chatHistoryId = useSelector((state) => state.chat.chatHistoryId); 14 | const suggestPrompt = useSelector((state) => state.chat.suggestPrompt); 15 | 16 | const userInputHandler = (e) => { 17 | setUserInput(e.target.value); 18 | }; 19 | 20 | const onSubmitHandler = (e) => { 21 | e.preventDefault(); 22 | dispatch( 23 | sendChatData({ 24 | user: userInput, 25 | gemini: "", 26 | isLoader: "yes", 27 | previousChat, 28 | chatHistoryId, 29 | }) 30 | ); 31 | setUserInput(""); 32 | navigate("/app"); 33 | }; 34 | 35 | useEffect(() => { 36 | if (suggestPrompt.length > 0) { 37 | setUserInput(suggestPrompt); 38 | } 39 | }, [suggestPrompt]); 40 | 41 | const icon = themeIcon(); 42 | return ( 43 |
44 |
45 | 53 | 56 |
57 |
58 | ); 59 | }; 60 | 61 | export default InputSection; 62 | -------------------------------------------------------------------------------- /public/src/components/InputSection/InputSection.module.css: -------------------------------------------------------------------------------- 1 | .input-main { 2 | margin: auto; 3 | position: absolute; 4 | bottom: 5px; 5 | max-width: 830px; 6 | left: 50%; 7 | width: 100%; 8 | transform: translate(-50%, -50%); 9 | z-index: 1; 10 | background-color: var(--chat-section-bg); 11 | padding: 15px 0px 15px; 12 | animation: widthChange 1s ease-out; 13 | } 14 | 15 | .input-main input { 16 | width: 100%; 17 | height: 65px; 18 | padding-left: 25px; 19 | padding-right: 60px; 20 | font-size: 16px; 21 | line-height: 24px; 22 | color: var(--input-font-color); 23 | background-color: var(--input-bg); 24 | border: 0px; 25 | border-radius: 30px; 26 | } 27 | 28 | .input-main input:focus { 29 | outline: none; 30 | background-color: var(--input-focus-bg); 31 | } 32 | 33 | .input-main input::placeholder { 34 | font-size: 16px; 35 | font-weight: 400; 36 | color: var(--input-placeholder-font-color); 37 | } 38 | 39 | .input-main button { 40 | background-color: transparent; 41 | border: 0px; 42 | position: absolute; 43 | right: 10px; 44 | top: 50%; 45 | transform: translate(-50%, -50%); 46 | cursor: pointer; 47 | user-select: none; 48 | -webkit-user-select: none; 49 | } 50 | 51 | .input-main button img { 52 | width: 30px; 53 | height: 30px; 54 | transform: rotate(45deg); 55 | } 56 | 57 | @keyframes widthChange { 58 | from { 59 | width: 10%; 60 | } 61 | to { 62 | width: 100%; 63 | } 64 | } 65 | 66 | @media (max-width: 1106px) { 67 | .input-main { 68 | padding: 10px 20px 15px; 69 | } 70 | 71 | .input-main button { 72 | right: 25px; 73 | } 74 | } 75 | 76 | @media (max-width: 680px) { 77 | .input-main { 78 | bottom: -4px; 79 | padding: 15px 20px 15px; 80 | } 81 | } 82 | 83 | @media (max-width: 360px) { 84 | .input-main { 85 | bottom: 28px; 86 | } 87 | } 88 | 89 | @media (max-width: 260px) { 90 | .input-main { 91 | bottom: 52px; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /server/service/auth_service.js: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import jwt from "jsonwebtoken"; 3 | 4 | export const getGooleOAuthToken = (code) => { 5 | const url = "https://oauth2.googleapis.com/token"; 6 | 7 | const redirect_url = process.env.GOOGLE_OAUTH_REDIRECT_URL; 8 | 9 | const values = { 10 | code, 11 | client_id: process.env.GOOGLE_CLIENT_ID, 12 | client_secret: process.env.GOOGLE_CLIENT_SECRET, 13 | redirect_uri: redirect_url, 14 | grant_type: "authorization_code", 15 | }; 16 | 17 | return new Promise((res, rej) => { 18 | fetch(url, { 19 | method: "POST", 20 | headers: { 21 | "Content-Type": "application/x-www-form-urlencoded", 22 | }, 23 | body: new URLSearchParams(values).toString(), 24 | }) 25 | .then((response) => { 26 | if (!response.ok) { 27 | throw new Error("Failed to fetch Google Oauth Tokens"); 28 | } 29 | return response.json(); 30 | }) 31 | .then((data) => { 32 | res(data); 33 | }) 34 | .catch((err) => { 35 | rej(err); 36 | }); 37 | }); 38 | }; 39 | 40 | export const getGoogleUser = (id_token, access_token) => { 41 | return new Promise((res, rej) => { 42 | const url = `https://www.googleapis.com/oauth2/v3/userinfo?alt=json&access_token=${access_token}`; 43 | 44 | fetch(url, { 45 | method: "GET", 46 | headers: { 47 | Authorization: `Bearer ${id_token}`, 48 | }, 49 | }) 50 | .then((response) => { 51 | if (!response.ok) { 52 | throw new Error("No User Found"); 53 | } 54 | return response.json(); 55 | }) 56 | .then((data) => { 57 | res(data); 58 | }) 59 | .catch((err) => { 60 | rej(err); 61 | }); 62 | }); 63 | }; 64 | 65 | export const jwtSignIn = (userData, secret, expireTime) => { 66 | const token = jwt.sign(userData, secret, { expiresIn: expireTime }); 67 | 68 | return token; 69 | }; 70 | -------------------------------------------------------------------------------- /public/src/components/NewChat/PromptSection/PromptSection.module.css: -------------------------------------------------------------------------------- 1 | .prompt-main { 2 | width: 100%; 3 | margin: auto; 4 | display: flex; 5 | overflow: scroll; 6 | gap: 10px; 7 | margin-top: 7rem; 8 | } 9 | 10 | .prompt-main::-webkit-scrollbar { 11 | width: 0px; 12 | } 13 | 14 | .prompt-main::-webkit-scrollbar-thumb { 15 | background-color: transparent; 16 | } 17 | 18 | .prompt-main::-webkit-scrollbar-track { 19 | background-color: transparent; 20 | } 21 | 22 | .prompt { 23 | min-height: 200px; 24 | min-width: 200px; 25 | background-color: var(--prompt-bg); 26 | border: 0px; 27 | border-radius: 12px; 28 | padding: 15px; 29 | position: relative; 30 | cursor: pointer; 31 | } 32 | 33 | .prompt:hover { 34 | background-color: var(--prompt-bg-hover); 35 | } 36 | 37 | .is-selected { 38 | background-color: var(--prompt-bg-selected); 39 | } 40 | 41 | .prompt p { 42 | font-size: 16px; 43 | line-height: 22px; 44 | font-weight: 400; 45 | color: var(--prompt-font-color); 46 | text-align: left; 47 | -webkit-box-orient: vertical; 48 | display: -webkit-box; 49 | overflow: hidden; 50 | -webkit-line-clamp: 3; 51 | } 52 | 53 | .icon { 54 | position: absolute; 55 | width: 35px; 56 | height: 35px; 57 | background-color: var(--prompt-icon-bg); 58 | border: 0px; 59 | border-radius: 50%; 60 | display: flex; 61 | justify-content: center; 62 | align-items: center; 63 | bottom: 15px; 64 | right: 20px; 65 | } 66 | 67 | .icon img { 68 | width: 25px; 69 | height: 25px; 70 | } 71 | 72 | @media (max-width: 1106px) { 73 | .prompt:first-child { 74 | margin-inline: 20px 0px; 75 | } 76 | .prompt:last-child { 77 | margin-inline: 0px 20px; 78 | } 79 | } 80 | 81 | @media (max-width: 700px) { 82 | .prompt-main { 83 | margin-top: 5rem; 84 | } 85 | } 86 | 87 | @media (max-width: 400px) { 88 | .prompt-main { 89 | margin-top: 2rem; 90 | } 91 | } 92 | 93 | @media (max-width: 250px) { 94 | .prompt-main { 95 | margin-top: 0.8rem; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /public/src/components/SettingSection/SettingSecion.js: -------------------------------------------------------------------------------- 1 | import styles from "./SettingSection.module.css"; 2 | import ToggleButton from "./ToggleButton"; 3 | import { themeIcon } from "../../asset"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { uiAction } from "../../store/ui-gemini"; 6 | 7 | const SettingSection = () => { 8 | const dispatch = useDispatch(); 9 | const themeMode = useSelector((state) => state.ui.isDark); 10 | const realTimeMode = useSelector((state) => state.ui.isRealTimeResponse); 11 | const isSettingsShow = useSelector((state) => state.ui.isSettingsShow); 12 | 13 | const themeHandler = () => { 14 | dispatch(uiAction.toggleTheme()); 15 | }; 16 | 17 | const realTimeResponseHandler = () => { 18 | dispatch(uiAction.toggleRealTimeResponse()); 19 | }; 20 | 21 | const icon = themeIcon(); 22 | const settingShow = isSettingsShow ? "settngs-show" : "settings-hide"; 23 | const getLocalTheme = localStorage.getItem("theme"); 24 | const theme = getLocalTheme || "dark"; 25 | const realTime = localStorage.getItem("realtime"); 26 | const realTimeTheme = realTime === "yes" ? "dark" : "light"; 27 | 28 | return ( 29 |
30 |
31 |

Settings

32 |
33 |
34 | link icon 35 |

Your public links

36 |
37 |
38 | moon icon 39 |

Dark theme

40 | 41 |
42 |
43 | wall icon 44 |

Real-time responses

45 | 50 |
51 |
52 | ); 53 | }; 54 | 55 | export default SettingSection; 56 | -------------------------------------------------------------------------------- /server/middleware/auth.js: -------------------------------------------------------------------------------- 1 | import { getCookieValue } from "../helper/cookieHandler.js"; 2 | import { tokenVerify } from "../helper/tokenVerify.js"; 3 | import { user } from "../model/user.js"; 4 | 5 | export const authMiddleware = (req, res, next) => { 6 | const cookieSting = req.headers.cookie; 7 | const cookieName = "access_token"; 8 | 9 | const token = getCookieValue(cookieSting, cookieName); 10 | 11 | if (token) { 12 | tokenVerify(token) 13 | .then((userData) => { 14 | req.user = userData; 15 | req.auth = "auth"; 16 | next(); 17 | }) 18 | .catch((err) => { 19 | next(err); 20 | }); 21 | } else { 22 | const ip = req.clientIp; 23 | 24 | user 25 | .findOne({ ip: ip }) 26 | .then((userData) => { 27 | if (userData) { 28 | req.user = userData; 29 | req.auth = "noauth"; 30 | return next(); 31 | } 32 | 33 | const ip = req.clientIp; 34 | const url = `https://api.ipgeolocation.io/ipgeo?apiKey=${process.env.GEO_API_KEY}&ip=${ip}&fields=geo`; 35 | 36 | return fetch(url) 37 | .then((response) => { 38 | return response.json(); 39 | }) 40 | .then((data) => { 41 | let location; 42 | 43 | if (data.city) { 44 | location = 45 | data.city + ", " + data.state_prov + ", " + data.country_name; 46 | } else { 47 | location = ip; 48 | } 49 | const newUser = new user({ 50 | ip: ip, 51 | location: location, 52 | }); 53 | 54 | return newUser 55 | .save() 56 | .then((result) => { 57 | return user.findOne({ ip: ip }); 58 | }) 59 | .then((userData) => { 60 | if (!userData) { 61 | const error = new Error("User not found"); 62 | error.statusCode = 403; 63 | throw error; 64 | } 65 | req.user = userData; 66 | req.auth = "noauth"; 67 | return next(); 68 | }); 69 | }); 70 | }) 71 | .catch((err) => { 72 | if (!err.statusCode) { 73 | err.statusCode = 500; 74 | } 75 | next(err); 76 | }); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /public/src/components/NewChat/ScrollChat/ScrollChat.module.css: -------------------------------------------------------------------------------- 1 | .scroll-chat-main { 2 | width: 100%; 3 | display: flex; 4 | flex-flow: nowrap column; 5 | height: 78dvh; 6 | overflow-y: scroll; 7 | overflow-x: hidden; 8 | padding: 0px 0px 20px; 9 | gap: 20px; 10 | } 11 | 12 | .scroll-chat-main::-webkit-scrollbar { 13 | width: 10px; 14 | } 15 | 16 | .scroll-chat-main::-webkit-scrollbar-track { 17 | background-color: transparent; 18 | } 19 | 20 | .scroll-chat-main::-webkit-scrollbar-thumb { 21 | background-color: #80868b; 22 | border-radius: 6px; 23 | } 24 | 25 | .scroll-chat-main::-webkit-scrollbar-thumb:hover { 26 | background-color: #60666a; 27 | } 28 | 29 | .single-chat { 30 | width: 100%; 31 | max-width: 712px; 32 | display: flex; 33 | flex-flow: nowrap column; 34 | gap: 55px; 35 | margin: 20px auto; 36 | word-break: break-word; 37 | position: relative; 38 | } 39 | 40 | .single-chat:last-child { 41 | margin-block-end: 6rem; 42 | } 43 | 44 | .user, 45 | .gemini { 46 | display: flex; 47 | flex-flow: nowrap row; 48 | position: relative; 49 | } 50 | 51 | .gemini { 52 | min-height: 56px; 53 | } 54 | 55 | .user p, 56 | .gemini p { 57 | font-size: 16px; 58 | font-weight: 400; 59 | color: var(--chat-font-color); 60 | text-align: left; 61 | padding-left: 20px; 62 | } 63 | 64 | .gemini p p { 65 | padding-left: 0px; 66 | } 67 | 68 | .user p { 69 | line-height: 24px; 70 | } 71 | 72 | .gemini p { 73 | line-height: 28px; 74 | } 75 | 76 | .user img { 77 | border: 0px; 78 | border-radius: 50%; 79 | height: 32px; 80 | width: 32px; 81 | } 82 | 83 | .gemini img { 84 | width: 30px; 85 | height: 30px; 86 | } 87 | 88 | @media (max-width: 960px) { 89 | .scroll-chat-main { 90 | padding: 0px 20px 40px; 91 | } 92 | 93 | .user img { 94 | border: 0px; 95 | border-radius: 50%; 96 | height: 30px; 97 | width: 30px; 98 | } 99 | 100 | .single-chat { 101 | gap: 30px; 102 | } 103 | 104 | .user, 105 | .gemini { 106 | flex-flow: nowrap column; 107 | align-items: flex-start; 108 | gap: 15px; 109 | } 110 | 111 | .user p, 112 | .gemini p { 113 | flex: 1; 114 | padding-left: 0px; 115 | } 116 | } 117 | 118 | @media (max-width: 255px) { 119 | .scroll-chat-main { 120 | padding: 0px 20px 60px; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /public/src/components/NewChat/NewChat.module.css: -------------------------------------------------------------------------------- 1 | .newchat-main { 2 | max-width: 830px; 3 | width: 100%; 4 | margin: auto; 5 | word-wrap: break-word; 6 | } 7 | 8 | .text-section { 9 | padding-top: 30px; 10 | width: 100%; 11 | text-align: left; 12 | } 13 | 14 | .text-section h1, 15 | h2 { 16 | font-size: 56px; 17 | line-height: 64px; 18 | font-weight: 500; 19 | } 20 | 21 | @keyframes gradientAnimation { 22 | from { 23 | width: 0%; 24 | } 25 | to { 26 | width: 100%; 27 | } 28 | } 29 | 30 | .text-section h1 { 31 | color: transparent; 32 | background: -webkit-linear-gradient( 33 | 16deg, 34 | var(--bard-color-brand-text-gradient-stop-1) 0, 35 | var(--bard-color-brand-text-gradient-stop-2) 9%, 36 | var(--bard-color-brand-text-gradient-stop-3) 20%, 37 | var(--bard-color-brand-text-gradient-stop-3) 24%, 38 | var(--bard-color-brand-text-gradient-stop-2) 35%, 39 | var(--bard-color-brand-text-gradient-stop-1) 44%, 40 | var(--bard-color-brand-text-gradient-stop-2) 50%, 41 | var(--bard-color-brand-text-gradient-stop-3) 56% 42 | ); 43 | background: linear-gradient( 44 | 74deg, 45 | var(--bard-color-brand-text-gradient-stop-1) 0, 46 | var(--bard-color-brand-text-gradient-stop-2) 9%, 47 | var(--bard-color-brand-text-gradient-stop-3) 20%, 48 | var(--bard-color-brand-text-gradient-stop-3) 24%, 49 | var(--bard-color-brand-text-gradient-stop-2) 35%, 50 | var(--bard-color-brand-text-gradient-stop-1) 44%, 51 | var(--bard-color-brand-text-gradient-stop-2) 50%, 52 | var(--bard-color-brand-text-gradient-stop-3) 56% 53 | ); 54 | overflow: hidden; 55 | -webkit-background-clip: text; 56 | -webkit-text-fill-color: transparent; 57 | background-size: 200% 100%; 58 | animation: gradientAnimation 2s ease; 59 | -webkit-box-orient: vertical; 60 | display: -webkit-inline-box; 61 | overflow: hidden; 62 | -webkit-line-clamp: 1; 63 | } 64 | 65 | .text-section h2 { 66 | color: var(--new-chat-h2-color); 67 | } 68 | 69 | @media (max-width: 1106px) { 70 | .text-section { 71 | padding: 20px 20px; 72 | } 73 | 74 | .text-section h1, 75 | h2 { 76 | font-size: 46px; 77 | line-height: 46px; 78 | } 79 | } 80 | 81 | @media (max-width: 360px) { 82 | .text-section, 83 | h1, 84 | h2 { 85 | font-size: 40px; 86 | line-height: 46px; 87 | } 88 | 89 | .text-section h1 { 90 | -webkit-line-clamp: 2; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /DISCLAIMER.md: -------------------------------------------------------------------------------- 1 | # Disclaimer 2 | 3 | #### This application is provided as-is with no warranties or guarantees. The authors are not responsible for any damages or liabilities arising from its use. The responses generated by Gemini AI are for informational purposes only and should not be taken as professional advice. 4 | 5 | **Gemini AI Web App** is provided "as is" and without any warranty or representation, either express or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the application is with you. 6 | 7 | While we strive for accuracy and reliability, **Gemini AI Web App** and its developers are not liable for any direct, indirect, incidental, consequential, or special damages arising out of or in any way connected with the use of this application. This includes but is not limited to damages for loss of profits, business interruption, loss of programs, or information, and the like. 8 | 9 | **Gemini AI Web App** is not responsible for the content, privacy policies, or practices of external websites linked within the application. The inclusion of any links does not imply endorsement. 10 | 11 | The information provided by **Gemini AI Web App** is for general informational purposes only. It may be subject to change without notice. 12 | 13 | ## Location Tracking 14 | 15 | Gemini AI Web App employs location tracking features for enhanced user experience. The following APIs are utilized on the server side to obtain location and IP details: 16 | 17 | 1. **IP Geolocation API:** 18 | 19 | - Used to retrieve initial location details based on the user's IP address. 20 | - API Endpoint: [https://ipgeolocation.io/](https://ipgeolocation.io/) 21 | 22 | 2. **Geocode API:** 23 | - Utilized to get precise user location details by updating location based on latitude and longitude. 24 | - API Endpoint: [https://geocode.maps.co/](https://geocode.maps.co/) 25 | 26 | Please note that the use of location data is subject to our [Privacy Policy](https://github.com/shuvra-matrix/Gemini-Ai--MERN/blob/main/PRIVACY-POLICY.md), and user consent is prioritized. The obtained location information is solely used to enhance user experience within the Gemini AI Web App. We do not share this information with third parties. 27 | 28 | If you have any concerns or questions regarding the use of location tracking in Gemini AI Web App, please contact us at [shuvrachakrabarty97@gmail.com](shuvrachakrabarty97@gmail.com). 29 | -------------------------------------------------------------------------------- /public/src/components/Header/Header.module.css: -------------------------------------------------------------------------------- 1 | .header-main { 2 | width: 100%; 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | padding: 20px; 7 | user-select: none; 8 | -webkit-user-select: none; 9 | } 10 | 11 | .left-section, 12 | .right-section { 13 | display: flex; 14 | align-items: center; 15 | gap: 15px; 16 | } 17 | 18 | .left-section { 19 | z-index: 4; 20 | } 21 | 22 | .menu-icon, 23 | .plus-icon { 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | cursor: pointer; 28 | } 29 | 30 | .menu-icon { 31 | display: none; 32 | } 33 | 34 | .plus-icon { 35 | height: 32px; 36 | width: 32px; 37 | background-color: var(--header-plus-bg); 38 | border: 0px; 39 | border-radius: 50%; 40 | display: none; 41 | } 42 | 43 | .left-section img, 44 | .right-section img { 45 | width: 24px; 46 | height: 24px; 47 | } 48 | 49 | .name { 50 | display: flex; 51 | align-items: center; 52 | gap: 8px; 53 | cursor: pointer; 54 | } 55 | 56 | .name p { 57 | font-size: 20px; 58 | font-weight: 400; 59 | color: var(--logo-font-color); 60 | } 61 | 62 | .name img { 63 | width: 12px; 64 | height: 12px; 65 | } 66 | 67 | .user { 68 | border: none; 69 | width: 38px; 70 | height: 38px; 71 | display: flex; 72 | justify-content: center; 73 | align-items: center; 74 | border-radius: 50%; 75 | cursor: pointer; 76 | } 77 | 78 | .user:hover { 79 | background-color: var(--header-user-icon-bg); 80 | } 81 | 82 | .clicked-user { 83 | background-color: var(--header-user-icon-clicked-bg); 84 | } 85 | 86 | .user img { 87 | width: 32px; 88 | height: 32px; 89 | border: none; 90 | border-radius: 50%; 91 | } 92 | 93 | .login { 94 | background-color: var(--sidebar-bg-color); 95 | color: var(--logo-font-color); 96 | padding: 7px 12px; 97 | border-radius: 20px; 98 | border: 1px solid var(--header-login-border); 99 | cursor: pointer; 100 | font-weight: 500; 101 | display: flex; 102 | align-items: center; 103 | flex-flow: nowrap row; 104 | gap: 6px; 105 | font-size: 16px; 106 | } 107 | 108 | .login:hover { 109 | background-color: var(--header-login-hover-bg); 110 | } 111 | 112 | @media (max-width: 960px) { 113 | .login { 114 | padding: 7px 7px; 115 | } 116 | 117 | .menu-icon { 118 | display: flex; 119 | } 120 | 121 | .plus-icon { 122 | display: flex; 123 | } 124 | } 125 | 126 | @media (max-width: 320px) { 127 | .login p { 128 | display: none; 129 | } 130 | .right-section { 131 | gap: 8px; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /public/src/components/UserDetails/UserDetails.module.css: -------------------------------------------------------------------------------- 1 | .user-main { 2 | position: absolute; 3 | background-color: var(--user-details-bg); 4 | width: 350px; 5 | height: 330px; 6 | z-index: 6; 7 | border-radius: 24px; 8 | top: 80px; 9 | right: 20px; 10 | display: flex; 11 | flex-flow: nowrap column; 12 | align-items: center; 13 | padding: 15px; 14 | color: var(--user-details-font-color); 15 | box-shadow: var(--user-details-box-shadow); 16 | user-select: none; 17 | -webkit-user-select: none; 18 | } 19 | 20 | .user-main a { 21 | text-decoration: none; 22 | color: var(--user-details-font-color); 23 | } 24 | 25 | .user-data { 26 | width: 100%; 27 | } 28 | 29 | .userIcon { 30 | width: 80px; 31 | height: 80px; 32 | border-radius: 50%; 33 | margin-top: 18px; 34 | margin-bottom: 5px; 35 | } 36 | 37 | .name { 38 | font-weight: 500; 39 | font-size: 1.4rem; 40 | } 41 | 42 | .signout { 43 | margin-top: 20px; 44 | width: 100%; 45 | display: flex; 46 | flex-flow: nowrap row; 47 | gap: 10px; 48 | align-items: center; 49 | background-color: var(--signout-bg); 50 | padding: 12px 20px; 51 | border-radius: 24px; 52 | font-size: 1.1rem; 53 | font-weight: 500; 54 | cursor: pointer; 55 | align-items: center; 56 | justify-content: center; 57 | } 58 | 59 | .signout:hover { 60 | background-color: var(--signout-hover-bg); 61 | } 62 | 63 | .signout img { 64 | width: 25px; 65 | height: 25px; 66 | } 67 | 68 | .privacy { 69 | display: flex; 70 | flex-flow: nowrap row; 71 | gap: 6px; 72 | position: absolute; 73 | bottom: 10px; 74 | font-size: 13px; 75 | font-weight: 500; 76 | } 77 | 78 | .privacy a { 79 | padding: 5px 5px; 80 | border-radius: 4px; 81 | } 82 | 83 | .privacy a:hover { 84 | background-color: var(--user-details-provacy-hover-bg); 85 | } 86 | 87 | .cross { 88 | position: absolute; 89 | top: 10px; 90 | right: 10px; 91 | cursor: pointer; 92 | display: flex; 93 | justify-content: center; 94 | align-items: center; 95 | width: 35px; 96 | height: 35px; 97 | border-radius: 50%; 98 | } 99 | 100 | .cross:hover { 101 | background-color: var(--cross-hover-bg); 102 | } 103 | 104 | .cross img { 105 | width: 28px; 106 | height: 28px; 107 | } 108 | 109 | @media (max-width: 400px) { 110 | .user-main { 111 | width: 100%; 112 | height: 100%; 113 | top: 0; 114 | right: 0; 115 | border-radius: 0px; 116 | padding: 20px 15px; 117 | } 118 | 119 | .cross { 120 | top: 15px; 121 | } 122 | 123 | .privacy { 124 | bottom: 20px; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /public/src/components/NewChat/ScrollChat/ScrollChatModule.css: -------------------------------------------------------------------------------- 1 | .h1-bold { 2 | font-size: 16px; 3 | font-weight: 500; 4 | } 5 | 6 | .list { 7 | font-size: 6px; 8 | margin-right: 5px; 9 | } 10 | 11 | .gemini-p { 12 | width: 100%; 13 | } 14 | 15 | .email-div { 16 | background-color: var(--code-bg); 17 | max-width: 600px; 18 | width: 100%; 19 | border-radius: 12px; 20 | overflow: auto; 21 | } 22 | 23 | .email-div { 24 | padding: 20px 20px 20px 20px; 25 | } 26 | 27 | pre { 28 | background: var(--code-bg) !important; 29 | color: #c4c4c4 !important; 30 | border-radius: 12px !important; 31 | padding: 5px 5px; 32 | overflow: auto !important; 33 | width: 95%; 34 | position: relative; 35 | top: -28px; 36 | } 37 | 38 | code::-webkit-scrollbar { 39 | width: 5px; 40 | height: 5px; 41 | } 42 | 43 | code::-webkit-scrollbar-track { 44 | background-color: transparent; 45 | } 46 | 47 | code::-webkit-scrollbar-thumb { 48 | background-color: #80868b; 49 | border-radius: 6px; 50 | } 51 | 52 | code::-webkit-scrollbar-thumb:hover { 53 | background-color: #60666a; 54 | } 55 | 56 | .hljs { 57 | background-color: transparent !important; 58 | color: var(--code-text-color) !important; 59 | } 60 | 61 | .hljs-type { 62 | color: #cd4545 !important; 63 | } 64 | 65 | .hljs-deletion, 66 | .hljs-number, 67 | .hljs-quote, 68 | .hljs-selector-class, 69 | .hljs-selector-id, 70 | .hljs-string, 71 | .hljs-template-tag, 72 | .hljs-type { 73 | color: var(--code-type-color) !important; 74 | } 75 | 76 | .hljs-section, 77 | .hljs-title { 78 | color: var(--code-title-color) !important; 79 | font-weight: 700 !important; 80 | } 81 | 82 | .hljs-addition, 83 | .hljs-built_in, 84 | .hljs-bullet, 85 | .hljs-code { 86 | color: var(--code-code-color) !important; 87 | font-weight: 700 !important; 88 | } 89 | 90 | .hljs-attribute, 91 | .hljs-doctag, 92 | .hljs-keyword, 93 | .hljs-meta .hljs-keyword, 94 | .hljs-name, 95 | .hljs-selector-tag { 96 | font-weight: 700 !important; 97 | color: var(--code-keyword) !important; 98 | } 99 | 100 | .hljs-punctuation, 101 | .hljs-tag { 102 | color: var(--code-tag) !important; 103 | } 104 | 105 | .hljs-name { 106 | font-weight: 700 !important; 107 | color: var(--code-name) !important; 108 | } 109 | 110 | .hljs-attr { 111 | color: var(--code-attr) !important; 112 | } 113 | 114 | pre code.hljs { 115 | padding: 10px 15px !important; 116 | } 117 | 118 | @media (max-width: 930px) { 119 | pre { 120 | width: 98%; 121 | top: -28px; 122 | } 123 | } 124 | 125 | @media (max-width: 750px) { 126 | pre { 127 | width: 100%; 128 | top: -28px; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /public/src/components/Ui/AdvanceGemini.module.css: -------------------------------------------------------------------------------- 1 | .advance-main { 2 | position: absolute; 3 | left: 10px; 4 | top: 60px; 5 | width: 240px; 6 | height: 120px; 7 | background-color: var(--advance-gimini-gb); 8 | border: 0px; 9 | border-radius: 8px; 10 | flex-flow: column nowrap; 11 | justify-content: center; 12 | gap: 5px; 13 | box-shadow: var(--advance-gimini-box-shadow); 14 | transition: all 0.3s ease; 15 | z-index: 6; 16 | display: none; 17 | user-select: none; 18 | -webkit-user-select: none; 19 | } 20 | 21 | .advance-on { 22 | opacity: 1; 23 | 24 | display: flex; 25 | } 26 | 27 | .advance-off { 28 | opacity: 0; 29 | } 30 | 31 | .advance-main h4 { 32 | font-size: 14px; 33 | color: var(--advance-gimini-h4-color); 34 | width: 100%; 35 | text-align: left; 36 | display: none; 37 | } 38 | 39 | .gemini, 40 | .advance-gemini { 41 | display: flex; 42 | flex-flow: row nowrap; 43 | align-items: center; 44 | gap: 10px; 45 | padding: 10px 10px; 46 | } 47 | 48 | .gemini img:last-child { 49 | width: 35px; 50 | width: 35px; 51 | position: absolute; 52 | right: 20px; 53 | } 54 | 55 | .gemini p, 56 | .advance-gemini p { 57 | font-size: 14px; 58 | line-height: 20px; 59 | font-weight: 400; 60 | } 61 | 62 | .gemini p { 63 | color: var(--gemini-p-color); 64 | } 65 | 66 | .advance-gemini p { 67 | color: var(--gemini-advance-p-color); 68 | } 69 | 70 | .advance-gemini button { 71 | width: 81px; 72 | height: 36px; 73 | border: 0px; 74 | border-radius: 4px; 75 | color: var(--advance-button-font-color); 76 | font-size: 12px; 77 | background-color: var(--advance-button-bg); 78 | cursor: pointer; 79 | position: absolute; 80 | right: 10px; 81 | } 82 | 83 | .advance-gemini button:hover { 84 | background-color: var(--advance-button-hover-bg); 85 | } 86 | 87 | .gemini { 88 | cursor: pointer; 89 | } 90 | 91 | .gemini:hover { 92 | background-color: var(--settings-option-hover-bg); 93 | } 94 | 95 | @media (max-width: 960px) { 96 | .advance-main { 97 | position: absolute; 98 | left: 0px; 99 | width: 100%; 100 | height: 200px; 101 | border-radius: 0px; 102 | transition: all 0.4s ease; 103 | gap: 5px; 104 | justify-content: flex-start; 105 | } 106 | 107 | .advance-main h4 { 108 | padding-left: 20px; 109 | padding-top: 15px; 110 | padding-bottom: 10px; 111 | } 112 | 113 | .gemini, 114 | .advance-gemini { 115 | display: flex; 116 | flex-flow: row nowrap; 117 | align-items: center; 118 | gap: 5px; 119 | padding: 16px 20px; 120 | } 121 | 122 | .advance-on { 123 | opacity: 1; 124 | bottom: 0px; 125 | top: auto; 126 | } 127 | 128 | .advance-off { 129 | opacity: 1; 130 | bottom: -400px; 131 | } 132 | .advance-main h4 { 133 | display: inline; 134 | } 135 | 136 | .advance-gemini button { 137 | right: 20px; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /public/src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import styles from "./Header.module.css"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { uiAction } from "../../store/ui-gemini"; 4 | import { themeIcon } from "../../asset"; 5 | import { commonIcon } from "../../asset"; 6 | import { useNavigate } from "react-router-dom"; 7 | import { chatAction } from "../../store/chat"; 8 | import { continueWithGoogleOauth } from "../../utils/getGoogleOauthUrl"; 9 | 10 | const Header = () => { 11 | const dispatch = useDispatch(); 12 | const navigate = useNavigate(); 13 | const isNewChat = useSelector((state) => state.chat.newChat); 14 | const isUserDetails = useSelector((state) => state.ui.isUserDetailsShow); 15 | const isLogin = useSelector((state) => state.auth.isLogin); 16 | const userDetails = useSelector((state) => state.user.user); 17 | 18 | const toggleSideBarHandler = () => { 19 | dispatch(uiAction.toggleSideBar()); 20 | }; 21 | 22 | const toggleAadvanceGeminiHandler = () => { 23 | dispatch(uiAction.toggleAdvanceShow()); 24 | }; 25 | 26 | const icon = themeIcon(); 27 | 28 | const newChatHandler = () => { 29 | dispatch(chatAction.replaceChat({ chats: [] })); 30 | dispatch(chatAction.newChatHandler()); 31 | dispatch(chatAction.chatHistoryIdHandler({ chatHistoryId: "" })); 32 | navigate("/"); 33 | }; 34 | 35 | const userDetailsOpen = () => { 36 | dispatch(uiAction.toggleUserDetailsShow()); 37 | }; 38 | 39 | const loginHandler = () => { 40 | window.open(continueWithGoogleOauth(), "_self"); 41 | }; 42 | 43 | return ( 44 |
45 |
46 |
47 | menu icon 48 |
49 |
50 |

Gemini

51 | drop down button 52 |
53 |
54 |
55 | {isNewChat ? ( 56 |
60 | plus icon 61 |
62 | ) : null} 63 | 64 | {isLogin ? ( 65 |
71 | {userDetails?.profileImg.length > 0 ? ( 72 | 73 | ) : ( 74 | "" 75 | )} 76 |
77 | ) : ( 78 |
79 | google logo 80 |

Sign in

81 |
82 | )} 83 |
84 |
85 | ); 86 | }; 87 | 88 | export default Header; 89 | -------------------------------------------------------------------------------- /public/src/store/auth-action.js: -------------------------------------------------------------------------------- 1 | import { authAction } from "./auth"; 2 | import { userAction } from "./user"; 3 | import { chatAction } from "./chat"; 4 | 5 | const SERVER_ENDPOINT = process.env.REACT_APP_SERVER_ENDPOINT; 6 | 7 | export const loginHandler = () => { 8 | return (dispatch) => { 9 | const url = `${SERVER_ENDPOINT}/api/auth/login`; 10 | 11 | fetch(url, { 12 | method: "GET", 13 | credentials: "include", 14 | }) 15 | .then((response) => { 16 | if (!response.ok) { 17 | throw new Error("Invalid Credential"); 18 | } 19 | 20 | return response.json(); 21 | }) 22 | .then((data) => { 23 | dispatch( 24 | userAction.setUserData({ 25 | userData: { 26 | name: data.name, 27 | email: data.email, 28 | profileImg: data.profileImg, 29 | }, 30 | }) 31 | ); 32 | dispatch(authAction.isLoginHandler({ isLogin: true })); 33 | localStorage.setItem("isLogin", true); 34 | }) 35 | .catch((err) => { 36 | console.log(err); 37 | dispatch(authAction.isLoginHandler({ isLogin: false })); 38 | localStorage.removeItem("isLogin"); 39 | localStorage.removeItem("loginCheck"); 40 | }); 41 | }; 42 | }; 43 | 44 | export const logoutHandler = () => { 45 | return (dispatch) => { 46 | const url = `${SERVER_ENDPOINT}/api/auth/logout`; 47 | 48 | fetch(url, { 49 | method: "GET", 50 | credentials: "include", 51 | }) 52 | .then((response) => { 53 | if (!response.ok) { 54 | throw new Error("Invalid Credential"); 55 | } 56 | dispatch(authAction.isLoginHandler({ isLogin: false })); 57 | localStorage.removeItem("isLogin"); 58 | localStorage.removeItem("loginCheck"); 59 | dispatch(chatAction.replaceChat({ chats: [] })); 60 | dispatch(chatAction.recentChatHandler({ recentChat: [] })); 61 | dispatch(chatAction.replacePreviousChat({ previousChat: [] })); 62 | dispatch(chatAction.chatHistoryIdHandler({ chatHistoryId: "" })); 63 | dispatch(chatAction.newChatHandler()); 64 | dispatch( 65 | userAction.setUserData({ 66 | userData: { 67 | name: "User", 68 | email: "", 69 | profileImg: "", 70 | }, 71 | }) 72 | ); 73 | }) 74 | .catch((err) => { 75 | console.log(err); 76 | }); 77 | }; 78 | }; 79 | 80 | export const refreshToken = () => { 81 | return (dispatch) => { 82 | const url = `${SERVER_ENDPOINT}/api/auth/resetToken`; 83 | 84 | fetch(url, { 85 | method: "GET", 86 | credentials: "include", 87 | }) 88 | .then((response) => { 89 | if (!response.ok) { 90 | throw new Error("Invalid Credential"); 91 | } 92 | localStorage.setItem("isLogin", true); 93 | }) 94 | .catch((err) => { 95 | console.log(err); 96 | dispatch(logoutHandler()); 97 | }); 98 | }; 99 | }; 100 | -------------------------------------------------------------------------------- /PRIVACY-POLICY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | This Privacy Policy outlines how Gemini AI Web App collects, uses, and protects user information, specifically focusing on the utilization of location and IP details. By using the Gemini AI Web App, you agree to the terms outlined in this policy. 4 | 5 | ## Information Collection and Use 6 | 7 | ### Non-Authenticated Users 8 | 9 | For non-authenticated users, Gemini AI Web App collects and stores the following information: 10 | 11 | - Chat history (limited to the last 5 entries ,only what user see we store full chat history). 12 | - Usage metrics for analytical purposes. 13 | 14 | ### Authenticated Users 15 | 16 | For authenticated users, in addition to the information collected from non-authenticated users, Gemini AI Web App collects and stores: 17 | 18 | - Full chat history. 19 | - User authentication details via Google OAuth V2. 20 | 21 | ### Location and IP Details 22 | 23 | The application uses the following APIs to enhance user experience by obtaining location and IP details: 24 | 25 | 1. **IP Geolocation API:** 26 | 27 | - Used to retrieve initial location details based on the user's IP address. 28 | - API Endpoint: [https://ipgeolocation.io/](https://ipgeolocation.io/) 29 | 30 | 2. **Geocode API:** 31 | - Utilized to get precise user location details by updating location based on latitude and longitude. 32 | - API Endpoint: [https://geocode.maps.co/](https://geocode.maps.co/) 33 | 34 | This information is solely used to provide a more personalized experience within the Gemini AI Web App. 35 | 36 | ## Information Protection 37 | 38 | Gemini AI Web App is committed to ensuring the security of your information. We implement industry-standard measures to protect against unauthorized access, alteration, disclosure, or destruction of your personal information. 39 | 40 | ## Information Sharing 41 | 42 | Gemini AI Web App does not share user information, including location and IP details, with third parties. User data is used solely for the purpose of improving the user experience and maintaining the functionality of the application. 43 | 44 | ## Cookies 45 | 46 | Gemini AI Web App may use cookies, including those related to location tracking, to enhance user experience. Users can modify their browser settings to disable cookies; however, this may impact the functionality of the application. 47 | 48 | ## Disclaimer 49 | 50 | Refer to the [Disclaimer](https://github.com/shuvra-matrix/Gemini-Ai--MERN/blob/main/DISCLAIMER.md) for additional information on the terms and conditions of using Gemini AI Web App. 51 | 52 | ## Changes to Terms 53 | 54 | Gemini AI Web App reserves the right to modify or replace these terms at any time. Users are responsible for reviewing this policy periodically. Continued use of the application after changes implies acceptance of the updated terms. 55 | 56 | This application respects your privacy. We do not collect any personal information from users on the free tier. If you choose to login with Google OAuth, we will only access your basic profile information (name and email) provided by Google for authentication purposes. We do not store or share this information with any third parties. 57 | 58 | If you have any questions or concerns regarding our Privacy Policy or Disclaimer, please contact us at [shuvrachakrabarty97@gmail.com](mailto:shuvrachakrabarty97@gmail.com). 59 | -------------------------------------------------------------------------------- /public/src/App.js: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import ChatSection from "./components/ChatSection/ChatSection"; 3 | import SettingSection from "./components/SettingSection/SettingSecion"; 4 | import Sidebar from "./components/Sidebar/Sidebar"; 5 | import { useSelector, useDispatch } from "react-redux"; 6 | import { uiAction } from "./store/ui-gemini"; 7 | import { useEffect } from "react"; 8 | import { getRecentChat } from "./store/chat-action"; 9 | import UserDetails from "./components/UserDetails/UserDetails"; 10 | import { refreshToken } from "./store/auth-action"; 11 | import { loginHandler } from "./store/auth-action"; 12 | import UserIntroPrompt from "./components/UserIntroPrompt/UserIntroPrompt"; 13 | 14 | function App() { 15 | const dispatch = useDispatch(); 16 | const settingsShow = useSelector((state) => state.ui.isSettingsShow); 17 | const isAdvanceGeminiPrompt = useSelector((state) => state.ui.isAdvanceShow); 18 | const newChat = useSelector((state) => state.chat.newChat); 19 | const isDark = useSelector((state) => state.ui.isDark); 20 | const isUserDetails = useSelector((state) => state.ui.isUserDetailsShow); 21 | const isLogin = useSelector((state) => state.auth.isLogin); 22 | const isIntroPrompt = useSelector((state) => state.ui.showIntroUserPrompt); 23 | 24 | const settingHandler = () => { 25 | if (settingsShow === true) { 26 | dispatch(uiAction.toggleSettings()); 27 | } 28 | if (isAdvanceGeminiPrompt === true) { 29 | dispatch(uiAction.toggleAdvanceShow()); 30 | } 31 | 32 | if (isUserDetails === true) { 33 | dispatch(uiAction.toggleUserDetailsShow()); 34 | } 35 | }; 36 | 37 | useEffect(() => { 38 | const getLocalTheme = localStorage.getItem("theme"); 39 | const theme = getLocalTheme || "dark"; 40 | document.documentElement.setAttribute("data-theme", theme); 41 | }, [isDark]); 42 | 43 | useEffect(() => { 44 | if (newChat === false) { 45 | dispatch(getRecentChat()); 46 | } 47 | }, [dispatch, newChat]); 48 | 49 | useEffect(() => { 50 | dispatch(loginHandler()); 51 | }, [dispatch]); 52 | 53 | useEffect(() => { 54 | const showUserPrompt = setInterval(() => { 55 | const isShowIntroAlready = localStorage.getItem("isIntroShow") || false; 56 | if (!isShowIntroAlready) { 57 | dispatch(uiAction.userIntroPromptHandler({ introPrompt: true })); 58 | localStorage.setItem("isIntroShow", true); 59 | } 60 | }, 2 * 1000); 61 | 62 | return () => clearInterval(showUserPrompt); 63 | }, [dispatch]); 64 | 65 | useEffect(() => { 66 | const refreshTokenHandler = setInterval(() => { 67 | const isLoginLocal = localStorage.getItem("isLogin"); 68 | if (isLoginLocal) { 69 | dispatch(refreshToken()); 70 | } 71 | }, 14 * 60 * 1000); 72 | 73 | return () => clearInterval(refreshTokenHandler); 74 | }, [dispatch]); 75 | 76 | return ( 77 |
78 | 79 | 80 | 81 | {isUserDetails && isLogin && } 82 | {!isLogin && isIntroPrompt && } 83 | {settingsShow && ( 84 |
85 | )} 86 | {isAdvanceGeminiPrompt && ( 87 |
88 | )} 89 | {isUserDetails && isLogin && ( 90 |
91 | )} 92 |
93 | ); 94 | } 95 | 96 | export default App; 97 | -------------------------------------------------------------------------------- /public/src/components/NewChat/ScrollChat/ScrollChat.js: -------------------------------------------------------------------------------- 1 | import styles from "./ScrollChat.module.css"; 2 | import { commonIcon } from "../../../asset"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import React, { useRef, useEffect, Fragment } from "react"; 5 | import { useNavigate, useParams } from "react-router-dom"; 6 | import { getChat } from "../../../store/chat-action"; 7 | import ReplyByGemini from "./ReplyByGemini"; 8 | import NewChatByGemini from "./NewChatGemini"; 9 | import CopyBtn from "../../Ui/CopyBtn"; 10 | import "./ScrollChatModule.css"; 11 | 12 | const ScrollChat = () => { 13 | const navigate = useNavigate(); 14 | const dispatch = useDispatch(); 15 | const { historyId } = useParams(); 16 | const chatRef = useRef(null); 17 | const chat = useSelector((state) => state.chat.chats); 18 | const chatHistoryId = useSelector((state) => state.chat.chatHistoryId); 19 | const realTimeResponse = localStorage.getItem("realtime") || "no"; 20 | const userImage = useSelector((state) => state.user.user.profileImg); 21 | 22 | const userLogo = userImage || commonIcon.avatarIcon; 23 | 24 | useEffect(() => { 25 | if (chat.length === 0 && !historyId) { 26 | navigate("/"); 27 | } else if (historyId && historyId !== chatHistoryId) { 28 | dispatch(getChat(historyId)); 29 | } else { 30 | navigate(`/app/${chatHistoryId}`); 31 | } 32 | }, [dispatch, historyId, chatHistoryId, navigate, chat]); 33 | 34 | useEffect(() => { 35 | chatRef.current.scrollTop = chatRef.current.scrollHeight; 36 | }, [chat]); 37 | 38 | const loadText = (text) => { 39 | return text 40 | ?.replace(/\n/g, "
") 41 | ?.replace(/\*\*(.*?)\*\*/g, '$1') 42 | ?.replace(/
\*/g, "
") 43 | ?.replace(/```([\s\S]*?)```/g, (_, codeBlock) => { 44 | let code = codeBlock 45 | .replace(/
/g, "\n") 46 | .replace(//g, ">"); 48 | let highlighted = `\`\`\`` + code + `\`\`\``; 49 | return `
${highlighted}
`; 50 | }) 51 | ?.replace(/```([\s\S]*?)```/g, "
$1
"); 52 | }; 53 | 54 | const lastElemetId = chat[chat.length - 1]?.id; 55 | 56 | const chatSection = chat.map((c, index) => ( 57 | 58 | {!c.error ? ( 59 |
64 |
65 | avater icon 66 |

{c.user}

67 |
68 |
69 | {c?.isLoader === "yes" && ( 70 | avater icon 71 | )} 72 | {c?.isLoader === "no" && ( 73 | avater icon 74 | )} 75 | {c?.newChat && 76 | !c?.gemini.includes("```") && 77 | lastElemetId === c?.id && 78 | realTimeResponse === "no" ? ( 79 | 80 | ) : ( 81 | 82 | )} 83 |
84 | {c?.gemini?.length > 0 && } 85 |
86 | ) : ( 87 | navigate("/") 88 | )} 89 |
90 | )); 91 | 92 | return ( 93 |
94 | {chatSection} 95 |
96 | ); 97 | }; 98 | 99 | export default ScrollChat; 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gemini AI Web App 2 | 3 | Gemini AI is an innovative web application that brings an interactive chat experience with the help of Google Gemini Pro api. Built using Node.js, React.js, MongoDB, and Redux Toolkit, this app allows users to engage in text-based conversations with Gemini AI. The application features Google OAuth V2 for user authentication, enabling enhanced capabilities and personalization. 4 | 5 | ## Description 6 | 7 | Gemini AI Web App is a Google Gemini clone, providing users with a seamless chat interface powered by advanced AI capabilities. Users can enjoy conversations with Gemini AI, receiving text-based responses. The application supports two user types: 8 | 9 | 1. **Non-authenticated Users:** 10 | 11 | - Limited to 10 chat requests per hour. 12 | - Access to the last 5 chat history entries. 13 | 14 | 2. **Authenticated Users:** 15 | - Unlimited chat responses. 16 | - Full chat history access upon login. 17 | 3. **Modern Interface:** 18 | - Enjoy a clean and user-friendly interface for a seamless chat experience. 19 | 20 | ## Live Demo 21 | 22 | Explore the live demo of Gemini AI: [https://geminichatai.netlify.app/](https://geminichatai.netlify.app/) 23 | 24 | ![Demo Screenshot](https://res.cloudinary.com/dqone7ala/image/upload/v1710036366/Screenshot_2024-03-10_073520_wkcnwj.png) 25 | 26 | ## Installation 27 | 28 | ### Server Side 29 | 30 | 1. Clone the repository: 31 | 32 | ```bash 33 | git clone https://github.com/shuvra-matrix/Gemini-Ai--MERN.git 34 | ``` 35 | 36 | 2. Navigate to the server folder: 37 | 38 | ```bash 39 | cd ./server 40 | ``` 41 | 42 | 3. Install dependencies: 43 | 44 | ```bash 45 | npm install 46 | ``` 47 | 48 | 4. Set up environment variables in a `.env` file: 49 | 50 | ``` 51 | MONGO_USER=mongodb_username 52 | MONGO_PASS=mongodb_password 53 | GEMINI_API_KEY=gemini_api_key 54 | CLIENT_API_KEY=server_client_verify_api_key(generate by user) 55 | GEO_API_KEY=ipgeolocation_api_key 56 | LOCATION_API_KEY=geocode_api_key 57 | GOOGLE_CLIENT_ID=google_oauth_client_id 58 | GOOGLE_CLIENT_SECRET=google_oauth_client_secret 59 | GOOGLE_OAUTH_REDIRECT_URL=google_oauth_redirect_url 60 | CLIENT_REDIRECT_URL="http://localhost:3000" 61 | ACCESS_TOKEN_JWT_SECRET=cookie_secret 62 | REFRESH_TOKEN_JWT_SECRET=cookie_secret 63 | ACCESS_TOKEN_EXPIRETIME=15m 64 | REFRESH_TOKEN_EXPIRETIME=7d 65 | APPLICATION_TYPE="dev" or"production" 66 | COOKIE_DOMAIN=serverid_domain 67 | ``` 68 | 69 | 5. Run the server: 70 | 71 | ```bash 72 | npm start 73 | ``` 74 | 75 | ### Client Side 76 | 77 | 1. Navigate to the root directory: 78 | 79 | ```bash 80 | cd Gemini-Ai--MERN 81 | ``` 82 | 83 | 2. Install client dependencies: 84 | 85 | ```bash 86 | npm install 87 | ``` 88 | 89 | 3. Set up client-side environment variables in a `.env` file: 90 | 91 | ``` 92 | REACT_APP_GEMINI_KEY=user_generated_key) 93 | REACT_APP_GOOGLE_CLIENT_ID=google_oauth_client_id 94 | REACT_APP_GOOGLE_CLIENT_SECRET=google_oauth_client_secret 95 | REACT_APP_GOOGLE_OAUTH_REDIRECT_URL=google_oauth_redirect_url 96 | REACT_APP_SERVER_ENDPOINT=http://localhost:3030 97 | ``` 98 | 99 | 4. Run the client: 100 | 101 | ```bash 102 | npm start 103 | ``` 104 | 105 | ## Docker Setup 106 | 107 | If you have Docker installed, use the following command in the root directory: 108 | 109 | ```bash 110 | docker-compose -f docker-compose.yaml up 111 | ``` 112 | 113 | ## Location Tracking 114 | 115 | Gemini AI Web App employs location tracking features for enhanced user experience. The following APIs are utilized on the server side to obtain location and IP details: 116 | 117 | 1. **IP Geolocation API:** 118 | 119 | - Used to retrieve initial location details based on the user's IP address. 120 | - API Endpoint: [https://ipgeolocation.io/](https://ipgeolocation.io/) 121 | - This information helps in providing a general idea of the user's location. 122 | 123 | 2. **Geocode API:** 124 | - Utilized to get precise user location details by updating location based on latitude and longitude. 125 | - API Endpoint: [https://geocode.maps.co/](https://geocode.maps.co/) 126 | - The app uses this API to convert latitude and longitude coordinates into an accurate user location. 127 | 128 | Please note that the use of location data is subject to our [Privacy Policy](https://github.com/shuvra-matrix/Gemini-Ai--MERN/blob/main/PRIVACY-POLICY.md), and user consent is prioritized. The obtained location information is solely used to enhance user experience within the Gemini AI Web App. We do not share this information with third parties. 129 | 130 | If you have any concerns or questions regarding the use of location tracking in Gemini AI Web App, please contact us at [shuvrachakrabarty97@gmail.com](shuvrachakrabarty97@gmail.com). 131 | 132 | ## Issues and Contributions 133 | 134 | Report issues or contribute to the project on [GitHub](https://github.com/shuvra-matrix/Gemini-Ai--MERN). 135 | 136 | ## Privacy Policy 137 | 138 | Read our [Privacy Policy](https://github.com/shuvra-matrix/Gemini-Ai--MERN/blob/main/PRIVACY-POLICY.md) before using Gemini AI. Your use of this web app is subject to our privacy terms. 139 | 140 | ## Disclaimer 141 | 142 | Read the [Disclaimer](https://github.com/shuvra-matrix/Gemini-Ai--MERN/blob/main/DISCLAIMER.md) before using Gemini AI. Use of this web app implies acceptance of the terms stated in the disclaimer. 143 | 144 | Enjoy chatting with Gemini AI! 🚀 145 | -------------------------------------------------------------------------------- /public/src/store/chat-action.js: -------------------------------------------------------------------------------- 1 | import { chatAction } from "./chat"; 2 | import { userAction } from "./user"; 3 | 4 | const SERVER_ENDPOINT = process.env.REACT_APP_SERVER_ENDPOINT; 5 | 6 | export const getRecentChat = () => { 7 | return (dispatch) => { 8 | const url = `${SERVER_ENDPOINT}/gemini/api/getchathistory`; 9 | 10 | fetch(url, { 11 | method: "GET", 12 | credentials: "include", 13 | }) 14 | .then((response) => { 15 | if (!response.ok) { 16 | throw new Error("server error"); 17 | } 18 | 19 | return response.json(); 20 | }) 21 | .then((data) => { 22 | dispatch( 23 | chatAction.recentChatHandler({ recentChat: data.chatHistory }) 24 | ); 25 | dispatch(userAction.setLocation({ location: data.location })); 26 | }) 27 | .catch((err) => { 28 | console.log(err); 29 | }); 30 | }; 31 | }; 32 | 33 | export const sendChatData = (useInput) => { 34 | return (dispatch) => { 35 | dispatch(chatAction.chatStart({ useInput: useInput })); 36 | 37 | const apiKey = process.env.REACT_APP_GEMINI_KEY; 38 | 39 | const url = `${SERVER_ENDPOINT}/gemini/api/chat`; 40 | 41 | fetch(url, { 42 | method: "POST", 43 | credentials: "include", 44 | headers: { 45 | "Content-Type": "application/json", 46 | "X-Api-Key": apiKey, 47 | }, 48 | body: JSON.stringify({ 49 | userInput: useInput.user, 50 | previousChat: useInput.previousChat, 51 | chatHistoryId: useInput.chatHistoryId, 52 | }), 53 | }) 54 | .then((response) => { 55 | if (!response.ok) { 56 | const statusCode = response.status; 57 | const error = new Error(`Server Error: ${statusCode}`); 58 | error.statusCode = statusCode; 59 | throw error; 60 | } 61 | 62 | return response.json(); 63 | }) 64 | .then((data) => { 65 | dispatch( 66 | chatAction.previousChatHandler({ 67 | previousChat: [ 68 | { role: "user", parts: data.user }, 69 | { role: "model", parts: data.gemini }, 70 | ], 71 | }) 72 | ); 73 | dispatch(chatAction.popChat()); 74 | dispatch( 75 | chatAction.chatStart({ 76 | useInput: { 77 | user: data.user, 78 | gemini: data.gemini, 79 | isLoader: "no", 80 | }, 81 | }) 82 | ); 83 | if (useInput.chatHistoryId.length < 2) { 84 | dispatch(getRecentChat()); 85 | } 86 | dispatch(chatAction.newChatHandler()); 87 | dispatch( 88 | chatAction.chatHistoryIdHandler({ chatHistoryId: data.chatHistoryId }) 89 | ); 90 | }) 91 | .catch((err) => { 92 | const statusCode = err.statusCode || 500; 93 | 94 | dispatch(chatAction.popChat()); 95 | if (statusCode === 429) { 96 | dispatch( 97 | chatAction.chatStart({ 98 | useInput: { 99 | user: useInput.user, 100 | gemini: 101 | "Rate Limit Exceeded. Please wait for one hour before trying again. Thank you for your patience.", 102 | isLoader: "no", 103 | }, 104 | }) 105 | ); 106 | } else { 107 | dispatch( 108 | chatAction.chatStart({ 109 | useInput: { 110 | user: useInput.user, 111 | gemini: 112 | "Oops! Something went wrong on our end. Please refresh the page and try again. If the issue persists, please contact us for assistance.", 113 | isLoader: "no", 114 | }, 115 | }) 116 | ); 117 | } 118 | dispatch(chatAction.newChatHandler()); 119 | }); 120 | }; 121 | }; 122 | 123 | export const getChat = (chatHistoryId) => { 124 | return (dispatch) => { 125 | dispatch(chatAction.loaderHandler()); 126 | const url = `${SERVER_ENDPOINT}/gemini/api/chatdata`; 127 | 128 | fetch(url, { 129 | method: "POST", 130 | body: JSON.stringify({ chatHistoryId }), 131 | credentials: "include", 132 | headers: { 133 | "Content-Type": "application/json", 134 | }, 135 | }) 136 | .then((response) => { 137 | if (!response.ok) { 138 | throw new Error("server error"); 139 | } 140 | return response.json(); 141 | }) 142 | .then((data) => { 143 | dispatch(chatAction.loaderHandler()); 144 | const previousChat = data.chats.flatMap((c) => [ 145 | { role: "user", parts: c.message.user }, 146 | { role: "model", parts: c.message.gemini }, 147 | ]); 148 | 149 | const chats = data.chats.map((c) => { 150 | return { 151 | user: c.message.user, 152 | gemini: c.message.gemini, 153 | id: c._id, 154 | isLoader: "no", 155 | }; 156 | }); 157 | 158 | const chatHistoryId = data.chatHistory; 159 | 160 | dispatch(chatAction.replacePreviousChat({ previousChat })); 161 | dispatch(chatAction.replaceChat({ chats })); 162 | dispatch(chatAction.chatHistoryIdHandler({ chatHistoryId })); 163 | dispatch(chatAction.newChatHandler()); 164 | }) 165 | .catch((err) => { 166 | console.log(err); 167 | dispatch( 168 | chatAction.replaceChat({ 169 | chats: [ 170 | { 171 | error: true, 172 | user: "Hi, is there any issue ? ", 173 | gemini: "", 174 | id: 34356556565, 175 | isLoader: "Oops! I cound't find you chat history", 176 | }, 177 | ], 178 | }) 179 | ); 180 | dispatch(chatAction.loaderHandler()); 181 | dispatch(chatAction.chatHistoryIdHandler({ chatHistoryId })); 182 | }); 183 | }; 184 | }; 185 | -------------------------------------------------------------------------------- /public/src/components/Sidebar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import styles from "./Sidebar.module.css"; 2 | import { themeIcon } from "../../asset"; 3 | import { commonIcon } from "../../asset"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { uiAction } from "../../store/ui-gemini"; 6 | import { useEffect, useState } from "react"; 7 | import { chatAction } from "../../store/chat"; 8 | import { Link, useNavigate } from "react-router-dom"; 9 | import { userUpdateLocation } from "../../store/user-action"; 10 | 11 | const Sidebar = () => { 12 | const dispatch = useDispatch(); 13 | const navigate = useNavigate(); 14 | const isSidebarLong = useSelector((state) => state.ui.isSidebarLong); 15 | const isNewChat = useSelector((state) => state.chat.newChat); 16 | const recentChat = useSelector((state) => state.chat.recentChat); 17 | const [isShowMore, setisShowMore] = useState(false); 18 | const [isActiveChat, setIsActiveChat] = useState(""); 19 | const chatHistoryId = useSelector((state) => state.chat.chatHistoryId); 20 | const location = useSelector((state) => state.user.location); 21 | 22 | const sideBarWidthHandler = () => { 23 | dispatch(uiAction.toggleSideBar()); 24 | }; 25 | 26 | const showMoreHandler = () => { 27 | setisShowMore((pre) => !pre); 28 | }; 29 | 30 | useEffect(() => { 31 | const id = chatHistoryId || ""; 32 | setIsActiveChat(id); 33 | }, [chatHistoryId]); 34 | 35 | const settingsHandler = (e) => { 36 | dispatch(uiAction.toggleSettings()); 37 | if (e.view.innerWidth <= 960) { 38 | dispatch(uiAction.toggleSideBar()); 39 | } 40 | }; 41 | 42 | const newChatHandler = () => { 43 | dispatch(chatAction.replaceChat({ chats: [] })); 44 | dispatch(chatAction.newChatHandler()); 45 | dispatch(chatAction.chatHistoryIdHandler({ chatHistoryId: "" })); 46 | navigate("/"); 47 | }; 48 | 49 | const icon = themeIcon(); 50 | const sideBarWidthClass = isSidebarLong ? "side-bar-long" : "side-bar-sort"; 51 | const showMoreArrowIcon = isShowMore ? icon.upArrowIcon : icon.expandIcon; 52 | 53 | console.log("sidebar"); 54 | 55 | const updateLocationHandler = () => { 56 | const location = localStorage.getItem("location"); 57 | if (!location) { 58 | dispatch(userUpdateLocation()); 59 | } 60 | }; 61 | 62 | return ( 63 |
64 |
65 | menu icon 66 |
67 | 68 |
69 | {isNewChat ? ( 70 |
74 | plus icon 75 | {isSidebarLong &&

New chat

} 76 |
77 | ) : ( 78 |
79 | plus icon 80 | {isSidebarLong &&

New chat

} 81 |
82 | )} 83 | {isSidebarLong && ( 84 |
85 |

Recent

86 | 87 | {recentChat.slice(0, 5).map((chat) => ( 88 | 89 |
{ 96 | setIsActiveChat(chat._id); 97 | }} 98 | > 99 | message 100 |

{chat.title.slice(0, 20)}

101 |
102 | 103 | ))} 104 | {recentChat.length > 5 && ( 105 |
106 | drop down 107 |

Show more

108 |
109 | )} 110 | 111 | {isShowMore && 112 | recentChat.slice(5, recentChat.length).map((chat) => ( 113 | 114 |
{ 121 | setIsActiveChat(chat._id); 122 | }} 123 | key={chat._id} 124 | > 125 | message 126 |

{chat.title.slice(0, 20)}

127 |
128 | 129 | ))} 130 |
131 | )} 132 |
133 | 134 |
135 |
136 | help icon 137 | {isSidebarLong &&

Help

} 138 |
139 |
140 | activity icon 141 | {isSidebarLong &&

Activity

} 142 |
143 |
144 | settings icon 145 | {isSidebarLong &&

Settings

} 146 |
147 | {isSidebarLong && ( 148 |
149 | gemini-logo 150 |

Upgrade to Gemini Advanced

151 |
152 | )} 153 |
154 |
155 | dot icon 156 |
157 |

158 | {location} From 159 | your IP address . 160 | Update location 161 |

162 |
163 |
164 |
165 | ); 166 | }; 167 | 168 | export default Sidebar; 169 | -------------------------------------------------------------------------------- /public/src/index.css: -------------------------------------------------------------------------------- 1 | *, 2 | ::after, 3 | ::before { 4 | padding: 0; 5 | margin: 0; 6 | box-sizing: border-box; 7 | -webkit-tap-highlight-color: transparent; 8 | } 9 | 10 | :root { 11 | --bard-color-brand-text-gradient-stop-1: #4285f4; 12 | --bard-color-brand-text-gradient-stop-1-rgb: rgb(66, 133, 244); 13 | --bard-color-brand-text-gradient-stop-2: #9b72cb; 14 | --bard-color-brand-text-gradient-stop-2-rgb: rgb(155, 114, 203); 15 | --bard-color-brand-text-gradient-stop-3: #d96570; 16 | --bard-color-brand-text-gradient-stop-3-rgb: rgb(217, 101, 112); 17 | --bard-color-surface: #131314; 18 | } 19 | 20 | [data-theme="dark"] { 21 | --chat-section-bg: #131314; 22 | 23 | --logo-font-color: #caccce; 24 | --header-plus-bg: #282a2c; 25 | --header-login-border: rgba(0, 0, 0, 0.616); 26 | --header-login-hover-bg: #333537; 27 | --header-user-icon-bg: #1c1c1f; 28 | --header-user-icon-clicked-bg: #252628; 29 | 30 | --userprompt-text-span-color: #d96570; 31 | 32 | --new-chat-h2-color: #444746; 33 | 34 | --prompt-bg: #1e1f20; 35 | --prompt-bg-hover: #333537; 36 | --prompt-bg-selected: #3b4456; 37 | --prompt-font-color: rgb(227, 227, 227); 38 | --prompt-icon-bg: #131314; 39 | 40 | --input-font-color: rgb(255, 255, 255); 41 | --input-bg: #282a2c; 42 | --input-focus-bg: #1e1f20; 43 | --input-placeholder-font-color: #bdc1c6; 44 | 45 | --warning-text-color: #c4c7c5; 46 | 47 | --advance-gimini-gb: #282a2c; 48 | --advance-gimini-box-shadow: 0px 0px 10px 10px rgba(0, 0, 0, 0.096); 49 | --advance-gimini-h4-color: #ffffff; 50 | --gemini-p-color: #e3e3e3; 51 | --gemini-advance-p-color: #a0a0a0; 52 | --advance-button-bg: #1b1b1b; 53 | --advance-button-font-color: white; 54 | --advance-button-hover-bg: #131313; 55 | 56 | --sidebar-bg-color: #1e1f20; 57 | --menuicon-hover-bg: #393b3d; 58 | --newchat-bg: #282a2c; 59 | --new-chat-hover-bg: #393b3d; 60 | --new-chat-new-bg: #1a1a1c; 61 | --new-chat-new-opacity: 0.4; 62 | --icon-p-color: rgb(227, 227, 227); 63 | --plus-icon-before-font-color: white; 64 | --plus-icon-before-bg-color: #3c4043; 65 | --recent-chat-font-color: #c2e7ff; 66 | --recent-active-chat-bg: #004a77; 67 | --show-more-font-color: #c4c7c5; 68 | --three-dot-hover-bg: rgba(0, 0, 0, 0.342); 69 | --recent-chat-hover-bg: #282a2c; 70 | --recent-chat-active-bg: #043d61; 71 | --help-before-font-color: white; 72 | --help-before-bg: #3c4043; 73 | --help-hover-bg: #303131; 74 | --upgrade-gimini-settings-bg: #47494a; 75 | --upgrade-gimini-p: #ffffff; 76 | --location-font-color: #a8c7fa; 77 | --location-name-color: #ffffff; 78 | --dot-color: #8e918f; 79 | 80 | --setting-main-bg: #424242; 81 | --setting-main-box-shadow: 0px 0px 40px 15px rgb(0 0 0 / 25%); 82 | --settings-h4-color: rgb(255, 255, 255); 83 | --settings-option-hover-bg: #515151; 84 | --settings-p-color: rgb(255, 255, 255); 85 | 86 | --bg-focus-pc: rgba(0, 0, 0, 0.082); 87 | --bg-focus-mobile: rgba(0, 0, 0, 0.274); 88 | 89 | --chat-font-color: #e3e3e3; 90 | 91 | --code-bg: #1e1f20; 92 | --code-text-color: #d8d6d6; 93 | --code-type-color: #22d3eb; 94 | --code-title-color: #18cea0; 95 | --code-code-color: #f52358e8; 96 | --code-keyword: #d1ad48; 97 | --code-tag: #ffffffc7; 98 | --code-name: #cb63b7; 99 | --code-attr: #cd4545; 100 | 101 | --user-details-bg: #282a2c; 102 | --user-details-font-color: #dbdbdb; 103 | --signout-bg: #1b1b1b; 104 | --signout-hover-bg: #37393b; 105 | --cross-hover-bg: #37393b; 106 | --user-details-box-shadow: 0px 0px 10px 2px #03030360; 107 | --user-details-provacy-hover-bg: #37393b; 108 | } 109 | 110 | [data-theme="light"] { 111 | --chat-section-bg: #ffffff; 112 | 113 | --userprompt-text-span-color: #9c72cb; 114 | 115 | --logo-font-color: #5f6368; 116 | --header-plus-bg: #e9eef6; 117 | --header-login-border: rgba(51, 219, 248, 0.096); 118 | --header-login-hover-bg: #dde3ea; 119 | --header-user-icon-bg: #d4d4d4; 120 | --header-user-icon-clicked-bg: rgb(191, 196, 201); 121 | 122 | --new-chat-h2-color: #c4c7c5; 123 | 124 | --prompt-bg: #f0f4f9; 125 | --prompt-bg-hover: #dde3ea; 126 | --prompt-bg-selected: #d3e3fd; 127 | --prompt-font-color: #1f1f1f; 128 | --prompt-icon-bg: #ffffff; 129 | 130 | --input-font-color: 1f1f1f; 131 | --input-bg: #f0f4f9; 132 | --input-focus-bg: #e9eef6; 133 | --input-placeholder-font-color: #5f6368; 134 | 135 | --warning-text-color: #444746; 136 | 137 | --advance-gimini-gb: #e9eef6; 138 | --advance-gimini-box-shadow: 0px 0px 10px 10px rgba(0, 0, 0, 0.096); 139 | --advance-gimini-h4-color: #000000; 140 | --gemini-p-color: #2f3030; 141 | --gemini-advance-p-color: #9c9fa4; 142 | --advance-button-bg: #f8fafd; 143 | --advance-button-font-color: #000000; 144 | --advance-button-hover-bg: #eef0f3; 145 | 146 | --sidebar-bg-color: #f0f4f9; 147 | --menuicon-hover-bg: #e9edf2; 148 | --newchat-bg: #d4dae1; 149 | --new-chat-hover-bg: #cad0d8; 150 | --new-chat-new-bg: #e5eaf1; 151 | --new-chat-new-opacity: 0.35; 152 | --icon-p-color: #1f1f1f; 153 | --plus-icon-before-font-color: white; 154 | --plus-icon-before-bg-color: #3c4043; 155 | --recent-chat-font-color: #1f1f1f; 156 | --recent-active-chat-bg: #d3e3fd; 157 | --show-more-font-color: #444746; 158 | --three-dot-hover-bg: #ffffff; 159 | --recent-chat-hover-bg: #e9eef6; 160 | --recent-chat-active-bg: #c4d7f7; 161 | --help-before-font-color: white; 162 | --help-before-bg: #3c4043; 163 | --help-hover-bg: #e7eaef; 164 | --upgrade-gimini-settings-bg: #d4dae1; 165 | --upgrade-gimini-p: #000015; 166 | --location-font-color: #0b97e2; 167 | --location-name-color: #1f1f1f; 168 | --dot-color: #747775; 169 | 170 | --setting-main-bg: #ffffff; 171 | --setting-main-box-shadow: 0px 0px 40px 15px rgb(0 0 0 / 25%); 172 | --settings-h4-color: #000000; 173 | --settings-option-hover-bg: #f5f5f5; 174 | --settings-p-color: #1f1f1f; 175 | 176 | --bg-focus-pc: rgba(0, 0, 0, 0.062); 177 | --bg-focus-mobile: rgba(0, 0, 0, 0.068); 178 | 179 | --chat-font-color: #1f1f1f; 180 | 181 | --code-bg: #f0f4f9; 182 | --code-text-color: #333030; 183 | --code-type-color: #095ba8; 184 | --code-title-color: #089270; 185 | --code-code-color: #d60b3ee8; 186 | --code-keyword: #730ad6e0; 187 | --code-tag: #444141ef; 188 | --code-name: #cb63b7; 189 | --code-attr: #d43b3b; 190 | 191 | --user-details-bg: #e9eef6; 192 | --user-details-font-color: #302f2f; 193 | --signout-bg: #f8fafd; 194 | --signout-hover-bg: #dce1e8; 195 | --cross-hover-bg: #dce1e8; 196 | --user-details-box-shadow: 0px 0px 8px 4px #c8c8c8a1; 197 | --user-details-provacy-hover-bg: #dce1e8; 198 | } 199 | 200 | html { 201 | height: 100dvb; 202 | } 203 | 204 | body { 205 | margin: 0; 206 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 207 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 208 | sans-serif; 209 | -webkit-font-smoothing: antialiased; 210 | -moz-osx-font-smoothing: grayscale; 211 | min-height: 100dvh; 212 | background-color: #131314; 213 | overflow-y: auto; 214 | } 215 | 216 | body::-webkit-scrollbar { 217 | width: 0px; 218 | } 219 | 220 | body::-webkit-scrollbar-thumb { 221 | background-color: transparent; 222 | } 223 | 224 | body::-webkit-scrollbar-track { 225 | background-color: transparent; 226 | } 227 | 228 | code { 229 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 230 | monospace; 231 | } 232 | -------------------------------------------------------------------------------- /server/controller/auth.js: -------------------------------------------------------------------------------- 1 | import { getGooleOAuthToken, getGoogleUser } from "../service/auth_service.js"; 2 | import { user } from "../model/user.js"; 3 | import "dotenv/config"; 4 | import { jwtSignIn } from "../service/auth_service.js"; 5 | import { tokenVerify } from "../helper/tokenVerify.js"; 6 | import { getCookieValue } from "../helper/cookieHandler.js"; 7 | import jwt from "jsonwebtoken"; 8 | 9 | const clientRedirectUrl = process.env.CLIENT_REDIRECT_URL; 10 | const accessTokenSecret = process.env.ACCESS_TOKEN_JWT_SECRET; 11 | const refreshTokenSecret = process.env.REFRESH_TOKEN_JWT_SECRET; 12 | const accessTokenExpire = process.env.ACCESS_TOKEN_EXPIRETIME; 13 | const refreshTokenExpire = process.env.REFRESH_TOKEN_EXPIRETIME; 14 | const applicationType = process.env.APPLICATION_TYPE; 15 | const cookieDomain = process.env.COOKIE_DOMAIN || "localhost"; 16 | 17 | export const googleOauthHandler = (req, res, next) => { 18 | const code = req.query.code; 19 | 20 | let newUserData; 21 | 22 | getGooleOAuthToken(code) 23 | .then((tokenData) => { 24 | const { access_token, id_token } = tokenData; 25 | 26 | return getGoogleUser(id_token, access_token); 27 | }) 28 | .then((userData) => { 29 | if (!userData.email_verified) { 30 | const error = new Error("Email address is not verified"); 31 | error.statusCode = 403; 32 | throw error; 33 | } 34 | newUserData = userData; 35 | 36 | return user.findOne({ email: newUserData.email }); 37 | }) 38 | .then((dbUser) => { 39 | if (!dbUser) { 40 | req.user.name = newUserData.name; 41 | req.user.email = newUserData.email; 42 | req.user.profileImg = newUserData.picture; 43 | return req.user.save(); 44 | } 45 | 46 | dbUser.name = newUserData.name; 47 | dbUser.email = newUserData.email; 48 | dbUser.profileImg = newUserData.picture; 49 | 50 | return dbUser.save(); 51 | }) 52 | .then((result) => { 53 | if (!result) { 54 | const error = new Error("user data update failed"); 55 | error.statusCode = 500; 56 | throw error; 57 | } 58 | 59 | const clientUserAgent = req.headers["user-agent"]; 60 | const tokenUserData = { 61 | email: newUserData.email, 62 | name: newUserData.name, 63 | userAgent: clientUserAgent, 64 | }; 65 | 66 | const accessToken = jwtSignIn( 67 | tokenUserData, 68 | accessTokenSecret, 69 | accessTokenExpire 70 | ); 71 | 72 | const refreshToken = jwtSignIn( 73 | tokenUserData, 74 | refreshTokenSecret, 75 | refreshTokenExpire 76 | ); 77 | 78 | const accessCookieOption = { 79 | maxAge: 900000, 80 | httpOnly: true, 81 | domain: cookieDomain, 82 | }; 83 | 84 | const refreshCookieOption = { 85 | maxAge: 604800000, 86 | httpOnly: true, 87 | domain: cookieDomain, 88 | }; 89 | 90 | if (applicationType === "production") { 91 | accessCookieOption.secure = true; 92 | accessCookieOption.sameSite = "None"; 93 | refreshCookieOption.secure = true; 94 | refreshCookieOption.sameSite = "None"; 95 | } 96 | 97 | res.cookie("access_token", accessToken, accessCookieOption); 98 | res.cookie("refresh_token", refreshToken, refreshCookieOption); 99 | res.cookie("isLogin", "yes", accessCookieOption); 100 | res.redirect(clientRedirectUrl); 101 | }) 102 | .catch((error) => { 103 | console.log(error); 104 | }); 105 | }; 106 | 107 | let a = 0; 108 | 109 | export const loginValidation = (req, res, next) => { 110 | const cookieSting = req.headers.cookie; 111 | const cookieName = "access_token"; 112 | const token = getCookieValue(cookieSting, cookieName); 113 | 114 | if (!token) { 115 | const err = new Error("Invalid Token"); 116 | err.statusCode = 401; 117 | throw err; 118 | } 119 | 120 | a += 1; 121 | 122 | console.log("Login Validation ", a); 123 | 124 | tokenVerify(token) 125 | .then((user) => { 126 | if (!user) { 127 | const error = new Error("user not found"); 128 | error.statusCode = 403; 129 | throw error; 130 | } 131 | res.status(200).json({ 132 | message: "Login Success", 133 | email: user.email, 134 | name: user.name, 135 | profileImg: user.profileImg, 136 | }); 137 | }) 138 | .catch((err) => { 139 | if (!err.statusCode) { 140 | err.statusCode = 500; 141 | } 142 | next(err); 143 | }); 144 | }; 145 | 146 | export const logoutHandler = (req, res, next) => { 147 | const cookieSting = req.headers.cookie; 148 | const acessToken = getCookieValue(cookieSting, "access_token"); 149 | const refresh_token = getCookieValue(cookieSting, "refresh_token"); 150 | 151 | req.user.expireAccessToken.push(acessToken); 152 | req.user.expireRefreshToken.push(refresh_token); 153 | req.user 154 | .save() 155 | .then((result) => { 156 | if (!result) { 157 | const error = new Error("Token Expire Add Failed"); 158 | error.statusCode = 403; 159 | throw error; 160 | } 161 | 162 | const accessCookieOption = { 163 | maxAge: 900000, 164 | httpOnly: true, 165 | domain: cookieDomain, 166 | }; 167 | 168 | const refreshCookieOption = { 169 | maxAge: 604800000, 170 | httpOnly: true, 171 | domain: cookieDomain, 172 | }; 173 | 174 | if (applicationType === "production") { 175 | accessCookieOption.secure = true; 176 | accessCookieOption.sameSite = "None"; 177 | refreshCookieOption.secure = true; 178 | refreshCookieOption.sameSite = "None"; 179 | } 180 | 181 | res.clearCookie("access_token", accessCookieOption); 182 | res.clearCookie("refresh_token", refreshCookieOption); 183 | res.clearCookie("isLogin", accessCookieOption); 184 | 185 | res.status(200).json({ message: "Logout" }); 186 | }) 187 | .catch((err) => { 188 | if (!err.statusCode) { 189 | err.statusCode = 500; 190 | } 191 | next(err); 192 | }); 193 | }; 194 | 195 | export const refreshToken = (req, res, next) => { 196 | const cookieSting = req.headers.cookie; 197 | const refreshToken = getCookieValue(cookieSting, "refresh_token"); 198 | const accessToken = getCookieValue(cookieSting, "access_token"); 199 | 200 | if (!accessToken) { 201 | const error = new Error("access token not found"); 202 | error.statusCode = 401; 203 | return next(error); 204 | } 205 | 206 | if (!refreshToken) { 207 | const error = new Error("Refresh token not found"); 208 | error.statusCode = 401; 209 | return next(error); 210 | } 211 | 212 | const decodeToken = jwt.verify(refreshToken, refreshTokenSecret); 213 | 214 | if (!decodeToken) { 215 | const err = new Error("Token Invalid"); 216 | err.statusCode = 401; 217 | err.data = "invalid token"; 218 | return next(err); 219 | } 220 | 221 | user 222 | .findOne({ email: decodeToken.email }) 223 | .then((userData) => { 224 | if (!userData) { 225 | const error = new Error("user not found"); 226 | error.statusCode = 403; 227 | throw error; 228 | } 229 | 230 | const isAccessTokenPresent = userData.expireAccessToken.some( 231 | (blockedToken) => blockedToken === accessToken 232 | ); 233 | 234 | if (isAccessTokenPresent) { 235 | const error = new Error("invalid access token"); 236 | throw error; 237 | } 238 | 239 | const isRefreshTokenPresent = userData.expireRefreshToken.some( 240 | (blockedToken) => blockedToken === refreshToken 241 | ); 242 | 243 | if (isRefreshTokenPresent) { 244 | const error = new Error("invalid refresh token"); 245 | throw error; 246 | } 247 | 248 | const clientUserAgent = req.headers["user-agent"]; 249 | const tokenUserData = { 250 | email: decodeToken.email, 251 | name: decodeToken.name, 252 | userAgent: clientUserAgent, 253 | }; 254 | 255 | const newAccessToken = jwtSignIn( 256 | tokenUserData, 257 | accessTokenSecret, 258 | accessTokenExpire 259 | ); 260 | 261 | const accessCookieOption = { 262 | maxAge: 900000, 263 | httpOnly: true, 264 | domain: cookieDomain, 265 | }; 266 | if (applicationType === "production") { 267 | accessCookieOption.secure = true; 268 | accessCookieOption.sameSite = "None"; 269 | } 270 | res.cookie("access_token", newAccessToken, accessCookieOption); 271 | res.cookie("isLogin", "yes", accessCookieOption); 272 | 273 | res.status(200).json({ message: "Token Reset Successful" }); 274 | }) 275 | .catch((err) => { 276 | if (!err.statusCode) { 277 | err.statusCode = 500; 278 | } 279 | next(err); 280 | }); 281 | }; 282 | -------------------------------------------------------------------------------- /public/src/asset/index.js: -------------------------------------------------------------------------------- 1 | import geminiIcon from "./gemini_sparkle_blue_33c17e77c4ebbdd9490b683b9812247e257b6f70.svg"; 2 | import advanceGeminiIcon from "./gemini_sparkle_red_4ed1cbfcbc6c9e84c31b987da73fc4168aec8445.svg"; 3 | import avatarIcon from "./avater-icon.png"; 4 | import chatGeminiIcon from "./bard_sparkle_v2.svg"; 5 | import geminiLaoder from "./bard_sparkle_processing_v2_loader.gif"; 6 | import googleLogo from "./icons8-google-48.png"; 7 | import ytIcon from "./icons8-youtube-48.png"; 8 | import flightIcon from "./icons8-flight-64.png"; 9 | import mapIcon from "./icons8-google-maps-48.png"; 10 | import hotelIcon from "./icons8-hotel-48.png"; 11 | import sportsIcon from "./icons8-man-winner-48.png"; 12 | import googleBigIcon from "./icons8-google-144.png"; 13 | 14 | import { darkIcon } from "./darkIcon/darkIcon"; 15 | import { lightIcon } from "./lightIcon/lightIcon"; 16 | 17 | export const commonIcon = { 18 | geminiIcon, 19 | advanceGeminiIcon, 20 | avatarIcon, 21 | chatGeminiIcon, 22 | geminiLaoder, 23 | googleLogo, 24 | ytIcon, 25 | flightIcon, 26 | mapIcon, 27 | hotelIcon, 28 | googleBigIcon, 29 | }; 30 | 31 | export const themeIcon = () => { 32 | const localTheme = localStorage.getItem("theme") || "dark"; 33 | const icon = localTheme === "dark" ? darkIcon : lightIcon; 34 | 35 | return icon; 36 | }; 37 | 38 | export const suggestPrompt = [ 39 | { 40 | id: 1, 41 | sort: "Help me plan a game night with 5 friends for under $100 ", 42 | long: "Help me plan a game night with 5 friends. I have dice and cards, but no board games. I would be willing to get board games for under $100", 43 | icon: "ideaIcon", 44 | }, 45 | { 46 | id: 2, 47 | sort: "Help write SQL to generate a report", 48 | long: `Given the schema below, write BigQuery compatible SQL to generate a report with the top 10 users by total price of all purchases. 49 | 50 | 51 | 52 | CREATE TABLE users ( 53 | 54 | id SERIAL PRIMARY KEY, 55 | 56 | name VARCHAR(255) NOT NULL, 57 | 58 | email VARCHAR(255) NOT NULL UNIQUE 59 | 60 | ); 61 | 62 | 63 | 64 | CREATE TABLE orders ( 65 | 66 | id SERIAL PRIMARY KEY, 67 | 68 | user_id INTEGER REFERENCES users (id) ON DELETE CASCADE, 69 | 70 | date TIMESTAMP NOT NULL, 71 | 72 | shipping_destination VARCHAR(255) NOT NULL, 73 | 74 | model_number VARCHAR(255) NOT NULL, 75 | 76 | price DECIMAL(10,2) NOT NULL 77 | 78 | ); 79 | `, 80 | icon: "codeIcon", 81 | }, 82 | { 83 | id: 3, 84 | sort: "Settle a debate: how should you store bread?", 85 | long: "Settle a debate: should bread be left out, refrigerated, or put in the freezer?", 86 | icon: "ideaIcon", 87 | }, 88 | { 89 | id: 4, 90 | sort: "Help me understand American football", 91 | long: "Help me understand the hype and intrigue about American football. Explain as you would to someone who is new to the sport.", 92 | icon: "ideaIcon", 93 | }, 94 | { 95 | id: 5, 96 | sort: "Suggest beautiful places to see on an upcoming road trip", 97 | long: "I'm doing a road trip to the U.S. Southwest in November. What are the most beautiful places to visit?", 98 | icon: "navigateIcon", 99 | }, 100 | { 101 | id: 6, 102 | sort: "Explain the key rules of rugby, starting with the basics", 103 | long: "Explain the key rules of rugby. Start with the basics and go step-by-step.", 104 | icon: "ideaIcon", 105 | }, 106 | { 107 | id: 7, 108 | sort: "Find hotels for a New Year’s trip to San Francisco", 109 | long: "Find hotels for a 4-day trip to San Francisco for new years eve", 110 | icon: "navigateIcon", 111 | }, 112 | { 113 | id: 8, 114 | sort: "What are tips to improve public speaking skills?", 115 | long: "What are some tips to improve public speaking skills for beginners?", 116 | icon: "ideaIcon", 117 | }, 118 | { 119 | id: 9, 120 | sort: "Write a product description for a new type of toothbrush", 121 | long: "I’m selling a new type of toothbrush and need a description that will help me sell it. Here are the details: it’s electric, solar powered, and has a 2-minute cleaning cycle", 122 | icon: "writeIcon", 123 | }, 124 | { 125 | id: 10, 126 | sort: "Evaluate and rank common camera categories", 127 | long: "Evaluate and rank the following: film, digital, and polaroid cameras across price, photo quality, and trendiness.", 128 | icon: "ideaIcon", 129 | }, 130 | { 131 | id: 11, 132 | sort: "Write a thank you note to my colleague", 133 | long: "Help me write a thank you note to my colleague for going above and beyond", 134 | icon: "writeIcon", 135 | }, 136 | { 137 | id: 12, 138 | sort: "Recommend new types of water sports, including pros & cons", 139 | long: "Recommend 3-5 different types of water sports that might be a good fit for me. Include a brief overview of each sport, as well as the pros and cons of each one.", 140 | icon: sportsIcon, 141 | }, 142 | { 143 | id: 13, 144 | sort: "Help me plan a game night with 5 friends for under $100", 145 | long: "Help me plan a game night with 5 friends. I have dice and cards, but no board games. I would be willing to get board games for under $100", 146 | icon: "writeIcon", 147 | }, 148 | { 149 | id: 14, 150 | sort: "Suggest a Python library to solve a problem", 151 | long: "Suggest Python libraries I should use if I want to perform a k-means clustering analysis on a dataset", 152 | icon: "codeIcon", 153 | }, 154 | { 155 | id: 15, 156 | sort: "List power words for my resume that show teamwork", 157 | long: "List some power words to use on my resume that show teamwork.", 158 | icon: "writeIcon", 159 | }, 160 | { 161 | id: 16, 162 | sort: "Generate unit tests for the following C# function", 163 | long: `Generate four unit tests for the following C# function 164 | 165 | 166 | 167 | CountDatesMatchingToday(List dates) that counts how many dates in the input list fall on the same day of the week as today. Test with an empty list, a list with some matches, a list with no matches, and a list with all elements matching. Make sure the tests are robust and work independently of when they are run (don't hard code today's date).`, 168 | icon: "codeIcon", 169 | }, 170 | { 171 | id: 17, 172 | sort: "Act like Mowgli from The Jungle Book and answer questions", 173 | long: "Act like Mowgli from The Jungle Book. Answer this question: What's your favorite memory with Baloo?", 174 | icon: "ideaIcon", 175 | }, 176 | { 177 | id: 18, 178 | sort: "Write code for a specific task, including edge cases", 179 | long: "Write a Java function that takes a path as an input and creates a file storing the current system date. Consider edge cases.", 180 | icon: "codeIcon", 181 | }, 182 | { 183 | id: 19, 184 | sort: "Draft an email with a packing list for an upcoming trip", 185 | long: "Draft a packing list for my weekend fishing and camping trip in Yosemite with friends. Make a table for the list, with a column for if I have the item yet or not. Draft an email with the table included.", 186 | icon: "writeIcon", 187 | }, 188 | { 189 | id: 20, 190 | sort: "Help me get organized with a list of 10 tips", 191 | long: "Give me 10 tips for room organization.", 192 | icon: "navigateIcon", 193 | }, 194 | { 195 | id: 21, 196 | sort: "Flights to Tokyo and Seoul, and things to do", 197 | long: "Show me flights to Tokyo and give me ideas of things to do. How about Seoul too?", 198 | icon: flightIcon, 199 | }, 200 | { 201 | id: 22, 202 | sort: "Suggest videos to quickly solve a problem", 203 | long: "Find videos of how to quickly get grape juice out of a wool rug", 204 | icon: ytIcon, 205 | }, 206 | { 207 | id: 23, 208 | sort: "Find YouTube videos with inspiring best man speeches", 209 | long: "Show me YouTube videos about inspiring best man speeches and give me tips on how to write my own", 210 | icon: ytIcon, 211 | }, 212 | { 213 | id: 24, 214 | sort: "What's the time it takes to walk to several landmarks", 215 | long: "How long does it take to walk from Buckingham Palace to Big Ben in London? What about from Big Ben to Trafalgar Square?", 216 | icon: mapIcon, 217 | }, 218 | { 219 | id: 25, 220 | sort: "Road trip drive time and kid entertainment ideas", 221 | long: "How long is the drive from Atlanta to Orlando and give me some ideas for how to keep 3 kids entertained in the car", 222 | icon: mapIcon, 223 | }, 224 | { 225 | id: 26, 226 | sort: "Find hotels in Recoleta in Buenos Aires, and things to do", 227 | long: "Can you find me some hotels in the Recoleta area of Buenos Aires and suggest things to see while there?", 228 | icon: hotelIcon, 229 | }, 230 | { 231 | id: 27, 232 | sort: "Find flights and weather for an upcoming trip", 233 | long: "Find flights to Miami for New Years. What's the usual temperature then?", 234 | icon: flightIcon, 235 | }, 236 | { 237 | id: 28, 238 | sort: "Create an image of an intergalactic event", 239 | long: "Generate an image of a grand intergalactic bake-off, where star-forged chefs compete with otherworldly confections amidst swirling nebulae and shimmering galaxies.", 240 | icon: "writeIcon", 241 | }, 242 | ]; 243 | -------------------------------------------------------------------------------- /server/controller/public.js: -------------------------------------------------------------------------------- 1 | import { user } from "../model/user.js"; 2 | import { chat } from "../model/chat.js"; 3 | import { chatHistory } from "../model/chatHistory.js"; 4 | import { GoogleGenerativeAI } from "@google/generative-ai"; 5 | import { Error } from "mongoose"; 6 | 7 | export const getGeminiHome = (req, res, next) => { 8 | res.status(200).json({ message: "Welcome to Gemini Ai Api" }); 9 | }; 10 | 11 | // post gemini data add to db condition 12 | 13 | // if chatHistoryId -> check old chatHistory else create new chatHistory 14 | 15 | // if chatHistoryId -> push old chat -> create new chat 16 | 17 | // add chat to chatHistory only first one 18 | 19 | // add chatHistory to user 20 | 21 | let b = 0; 22 | 23 | export const postGemini = async (req, res, next) => { 24 | const clientApikey = String(req.headers["x-api-key"]); 25 | const serverSideClientApiKey = String(process.env.CLIENT_API_KEY); 26 | 27 | if (clientApikey !== serverSideClientApiKey) { 28 | const error = new Error("Invalid Api Key"); 29 | error.statusCode = 401; 30 | error.data = "Invalid Api Key"; 31 | return next(error); 32 | } 33 | const query = String(req.body.userInput); 34 | const previousChat = req.body.previousChat; 35 | const chatHistoryId = req.body.chatHistoryId; 36 | 37 | let history = [ 38 | { 39 | role: "user", 40 | parts: "Hello, who are you.", 41 | }, 42 | { 43 | role: "model", 44 | parts: "I am a large language model, trained by Google.", 45 | }, 46 | ]; 47 | 48 | if (previousChat.length > 0) history = [...history, ...previousChat]; 49 | 50 | const genAi = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); 51 | 52 | const model = genAi.getGenerativeModel({ model: "gemini-pro" }); 53 | 54 | const chats = model.startChat({ 55 | history: history, 56 | }); 57 | 58 | let text; 59 | let newChatHistoryId; 60 | let chatId; 61 | 62 | chats 63 | .sendMessage(query) 64 | .then((result) => { 65 | return result.response; 66 | }) 67 | .then((response) => { 68 | text = response.text(); 69 | 70 | if (text.length < 5) { 71 | const error = new Error("result not found"); 72 | error.statusCode = 403; 73 | throw error; 74 | } 75 | 76 | if (chatHistoryId.length < 5) { 77 | const newChatHistory = new chatHistory({ 78 | user: req.user._id, 79 | title: query, 80 | }); 81 | 82 | return newChatHistory.save(); 83 | } else { 84 | return chatHistory.findById(chatHistoryId); 85 | } 86 | }) 87 | .then((chatHistory) => { 88 | if (!chatHistory) { 89 | const error = new Error("Chat History not found"); 90 | error.statusCode = 403; 91 | throw error; 92 | } 93 | 94 | newChatHistoryId = chatHistory._id; 95 | 96 | if (chatHistoryId.length < 5) { 97 | const newChat = new chat({ 98 | chatHistory: newChatHistoryId, 99 | messages: [ 100 | { 101 | sender: req.user._id, 102 | message: { 103 | user: query, 104 | gemini: text, 105 | }, 106 | }, 107 | ], 108 | }); 109 | 110 | return newChat.save(); 111 | } else { 112 | return chat 113 | .findOne({ chatHistory: chatHistory._id }) 114 | .then((chatData) => { 115 | if (!chatData) { 116 | const error = new Error("no chat found"); 117 | error.statusCode = 403; 118 | throw error; 119 | } 120 | 121 | chatData.messages.push({ 122 | sender: req.user._id, 123 | message: { 124 | user: query, 125 | gemini: text, 126 | }, 127 | }); 128 | 129 | return chatData.save(); 130 | }); 131 | } 132 | }) 133 | .then((result) => { 134 | chatId = result._id; 135 | 136 | if (!result) { 137 | throw new Error("Server Error"); 138 | } 139 | 140 | if (chatHistoryId.length < 5) { 141 | return chatHistory.findById(newChatHistoryId).then((chatHistory) => { 142 | if (!chatHistory) { 143 | const error = new Error("Chat History not found"); 144 | error.statusCode = 403; 145 | throw error; 146 | } 147 | 148 | chatHistory.chat = chatId; 149 | return chatHistory.save(); 150 | }); 151 | } else { 152 | return true; 153 | } 154 | }) 155 | .then((result) => { 156 | if (!result) { 157 | throw new Error("Server Error"); 158 | } 159 | 160 | return user.findById(req.user._id); 161 | }) 162 | .then((userData) => { 163 | if (!userData) { 164 | const error = new Error("No user found"); 165 | error.statusCode = 403; 166 | throw error; 167 | } 168 | 169 | if (chatHistoryId.length < 5) { 170 | userData.chatHistory.push(newChatHistoryId); 171 | } 172 | if (req.auth === "noauth") { 173 | userData.currentLimit += 1; 174 | } 175 | return userData.save(); 176 | }) 177 | .then((result) => { 178 | if (!result) { 179 | throw new Error("Server Error"); 180 | } 181 | 182 | b += 1; 183 | 184 | console.log("new chat ", b); 185 | 186 | res.status(200).json({ 187 | user: query, 188 | gemini: text, 189 | chatHistoryId: newChatHistoryId || chatHistoryId, 190 | }); 191 | }) 192 | .catch((err) => { 193 | if (!err.statusCode) { 194 | err.statusCode = 500; 195 | } 196 | next(err); 197 | }); 198 | }; 199 | 200 | let c = 0; 201 | 202 | export const getChatHistory = (req, res, next) => { 203 | user 204 | .findById(req.user._id) 205 | .populate({ path: "chatHistory" }) 206 | .then((userData) => { 207 | if (!user) { 208 | const error = new Error("User Not Found"); 209 | error.statusCode = 403; 210 | throw error; 211 | } 212 | c += 1; 213 | console.log("chat history", c); 214 | 215 | let chatHistory; 216 | 217 | if (req.auth === "auth") { 218 | chatHistory = userData.chatHistory.reverse(); 219 | } else { 220 | chatHistory = userData.chatHistory.reverse().slice(0, 5); 221 | } 222 | 223 | res.status(200).json({ 224 | chatHistory: chatHistory, 225 | location: userData.location, 226 | }); 227 | }) 228 | .catch((error) => { 229 | if (!error.statusCode) { 230 | error.statusCode = 500; 231 | } 232 | next(error); 233 | }); 234 | }; 235 | 236 | let a = 0; 237 | 238 | export const postChat = (req, res, next) => { 239 | const chatHistoryId = req.body.chatHistoryId; 240 | const userId = req.user._id; 241 | const isNotAuthUser = req.auth === "noauth"; 242 | 243 | const findChatHistory = isNotAuthUser 244 | ? chatHistory.find({ user: userId }) 245 | : chatHistory.findOne({ user: userId, _id: chatHistoryId }); 246 | 247 | findChatHistory 248 | .sort({ _id: -1 }) 249 | .limit(isNotAuthUser ? 5 : 1) 250 | .then((chatHistories) => { 251 | if (isNotAuthUser) { 252 | const recentChatHistoryIds = chatHistories.map((history) => 253 | history._id.toString() 254 | ); 255 | 256 | if (!recentChatHistoryIds.includes(chatHistoryId)) { 257 | const error = new Error("You are not a auth user"); 258 | error.statusCode = 403; 259 | throw error; 260 | } 261 | } 262 | 263 | return chatHistory 264 | .findOne({ user: userId, _id: chatHistoryId }) 265 | .populate({ 266 | path: "chat", 267 | }); 268 | }) 269 | .then((chatData) => { 270 | if (!chatData) { 271 | const error = new Error("No Chat history found"); 272 | error.statusCode = 403; 273 | throw error; 274 | } 275 | 276 | a += 1; 277 | console.log("Old chats ", a); 278 | 279 | res.status(200).json({ 280 | chatHistory: chatData._id, 281 | chats: chatData.chat.messages, 282 | }); 283 | }) 284 | .catch((err) => { 285 | if (!err.statusCode) { 286 | err.statusCode = 500; 287 | } 288 | next(err); 289 | }); 290 | }; 291 | 292 | let d = 0; 293 | 294 | export const updateLocation = (req, res, next) => { 295 | const { lat, long } = req.body.location; 296 | 297 | const apiKey = process.env.LOCATION_API_KEY; 298 | 299 | const url = `https://geocode.maps.co/reverse?lat=${lat}&lon=${long}&api_key=${apiKey}`; 300 | 301 | let location; 302 | 303 | fetch(url) 304 | .then((response) => { 305 | if (!response) { 306 | const error = new Error("Location Not Found"); 307 | error.statusCode = 403; 308 | throw error; 309 | } 310 | 311 | return response.json(); 312 | }) 313 | .then((data) => { 314 | location = `${data.address.city}, ${data.address.state}, ${data.address.country}`; 315 | 316 | return user.findById(req.user._id); 317 | }) 318 | .then((user) => { 319 | if (!user) { 320 | const error = new Error("User Not Found"); 321 | error.statusCode = 403; 322 | throw error; 323 | } 324 | 325 | user.location = location; 326 | 327 | return user.save(); 328 | }) 329 | .then((result) => { 330 | if (!result) { 331 | const error = new Error("No Result"); 332 | error.statusCode = 403; 333 | throw error; 334 | } 335 | d += 1; 336 | console.log("location", d); 337 | res.status(200).json({ location: location }); 338 | }) 339 | .catch((error) => { 340 | if (!res.statusCode) { 341 | res.statusCode = 500; 342 | } 343 | next(error); 344 | }); 345 | }; 346 | -------------------------------------------------------------------------------- /public/src/components/Sidebar/Sidebar.module.css: -------------------------------------------------------------------------------- 1 | .sidebar-main { 2 | width: 100%; 3 | min-height: 100dvh; 4 | background-color: var(--sidebar-bg-color); 5 | display: flex; 6 | flex-flow: column nowrap; 7 | align-items: center; 8 | padding: 20px; 9 | gap: 30px; 10 | transition: all 0.4s ease; 11 | position: relative; 12 | user-select: none; 13 | -webkit-user-select: none; 14 | } 15 | 16 | .side-bar-long { 17 | max-width: 268px; 18 | min-width: 268px; 19 | } 20 | 21 | .side-bar-sort { 22 | max-width: 68px; 23 | min-width: 68px; 24 | } 25 | 26 | .sidebar-main .menu-icon { 27 | height: 32px; 28 | width: 32px; 29 | border: 0px; 30 | border-radius: 50%; 31 | cursor: pointer; 32 | display: flex; 33 | align-items: center; 34 | } 35 | 36 | .menu-icon { 37 | position: absolute; 38 | left: 14px; 39 | height: 40px !important; 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | width: 40px !important; 44 | border: 0px; 45 | border-radius: 50%; 46 | } 47 | 48 | .menu-icon img { 49 | height: 20px; 50 | width: 20px; 51 | } 52 | 53 | .menu-icon:hover { 54 | background-color: var(--menuicon-hover-bg); 55 | } 56 | 57 | .pluc-icon { 58 | display: flex; 59 | align-items: center; 60 | gap: 20px; 61 | border: 0px; 62 | transition: width 0.4s ease; 63 | position: absolute; 64 | left: 15px; 65 | padding: 8px; 66 | } 67 | 68 | .new-plus-icon { 69 | background-color: var(--newchat-bg); 70 | cursor: pointer; 71 | } 72 | 73 | .new-plus-icon:hover { 74 | background-color: var(--new-chat-hover-bg); 75 | } 76 | 77 | .old-plus-icon { 78 | background-color: var(--new-chat-new-bg); 79 | } 80 | 81 | .old-plus-icon img { 82 | opacity: var(--new-chat-new-opacity); 83 | } 84 | 85 | .pluc-icon img { 86 | position: absolute; 87 | left: 9px; 88 | } 89 | 90 | .pluc-icon p { 91 | padding-left: 35px; 92 | -webkit-box-orient: vertical; 93 | display: -webkit-inline-box; 94 | overflow: hidden; 95 | -webkit-line-clamp: 1; 96 | } 97 | 98 | .side-bar-long .pluc-icon { 99 | height: 40px; 100 | border-radius: 18px; 101 | width: 150px; 102 | } 103 | 104 | .side-bar-sort .pluc-icon { 105 | height: 40px; 106 | border-radius: 24px; 107 | width: 40px; 108 | } 109 | 110 | .pluc-icon p, 111 | .recent-chat-main p, 112 | .settings-section p { 113 | font-size: 14px; 114 | line-height: 20px; 115 | font-weight: 500; 116 | color: var(--icon-p-color); 117 | } 118 | 119 | .pluc-icon:hover::before, 120 | .menu-icon:hover::before { 121 | position: absolute; 122 | font-size: 12px; 123 | font-weight: 400; 124 | border: 0px; 125 | border-radius: 5px; 126 | color: var(--plus-icon-before-font-color); 127 | background-color: var(--plus-icon-before-bg-color); 128 | z-index: 5; 129 | display: flex; 130 | justify-content: center; 131 | align-items: center; 132 | } 133 | 134 | .pluc-icon:hover::before { 135 | content: "New Chat"; 136 | width: 65px; 137 | height: 30px; 138 | } 139 | 140 | .menu-icon:hover::before { 141 | width: 90px; 142 | height: 30px; 143 | } 144 | 145 | .side-bar-long .menu-icon:hover::before { 146 | content: "Collapse menu"; 147 | left: 50px; 148 | } 149 | 150 | .side-bar-sort .menu-icon:hover::before { 151 | content: "Expand menu"; 152 | bottom: 6px; 153 | left: 60px; 154 | } 155 | 156 | .side-bar-long .pluc-icon:hover::before { 157 | bottom: 6px; 158 | left: 170px; 159 | } 160 | 161 | .side-bar-sort .pluc-icon:hover::before { 162 | bottom: 6px; 163 | left: 60px; 164 | } 165 | 166 | /* 167 | resent chat section */ 168 | 169 | .recent-chat-section, 170 | .recent-chat-main { 171 | display: flex; 172 | flex-flow: nowrap column; 173 | align-items: flex-start; 174 | } 175 | 176 | .recent-chat-section { 177 | gap: 20px; 178 | overflow: hidden; 179 | padding-bottom: 5px; 180 | margin-top: 4rem; 181 | } 182 | 183 | .recent-chat-section::-webkit-scrollbar { 184 | width: 0px; 185 | } 186 | 187 | .recent-chat-main { 188 | height: calc(78vh - 235px); 189 | width: 92%; 190 | gap: 2px; 191 | margin-top: 4rem; 192 | position: absolute; 193 | left: 18px; 194 | overflow-y: scroll; 195 | overflow-x: hidden; 196 | } 197 | 198 | .recent-chat-main a { 199 | width: 95%; 200 | text-decoration: none; 201 | } 202 | 203 | .recent-chat-main::-webkit-scrollbar { 204 | width: 6px; 205 | } 206 | 207 | .recent-chat-main::-webkit-scrollbar-track { 208 | background-color: transparent; 209 | } 210 | 211 | .recent-chat-main::-webkit-scrollbar-thumb { 212 | background-color: #80868b; 213 | border-radius: 6px; 214 | } 215 | 216 | .recent-chat-main::-webkit-scrollbar-thumb:hover { 217 | background-color: #60666a; 218 | } 219 | 220 | .recent-chat-main p { 221 | padding-left: 10px; 222 | } 223 | 224 | .recent-chat-main p:first-child { 225 | padding-left: 8px; 226 | padding-bottom: 5px; 227 | } 228 | 229 | .recent-chat-section img, 230 | .settings-section img { 231 | width: 22px; 232 | height: 22px; 233 | } 234 | 235 | .recent-chat, 236 | .show-more { 237 | display: flex; 238 | flex-flow: nowrap row; 239 | align-items: center; 240 | padding-left: 8px; 241 | cursor: pointer; 242 | } 243 | 244 | .show-more { 245 | padding: 8px 7px; 246 | width: 95%; 247 | border-radius: 24px; 248 | } 249 | 250 | .recent-chat { 251 | padding: 7px 7px; 252 | color: var(--recent-chat-font-color) !important; 253 | border: 0px; 254 | border-radius: 24px; 255 | position: relative; 256 | } 257 | 258 | .active-recent-chat { 259 | background-color: var(--recent-active-chat-bg); 260 | } 261 | 262 | .recent-chat { 263 | width: 100%; 264 | } 265 | 266 | .recent-chat img { 267 | display: flex; 268 | justify-content: center; 269 | align-items: center; 270 | width: 20px; 271 | height: 20px; 272 | } 273 | 274 | .show-more img { 275 | width: 20px; 276 | height: 20px; 277 | } 278 | 279 | .recent-chat p, 280 | .show-more p { 281 | font-size: 14px; 282 | line-height: 24px; 283 | font-weight: 500; 284 | font-style: normal; 285 | color: var(--show-more-font-color); 286 | } 287 | 288 | .three-dot { 289 | padding-left: 4px; 290 | position: absolute; 291 | right: 15px; 292 | opacity: 0; 293 | display: flex; 294 | justify-content: flex-start; 295 | align-items: center; 296 | border: 0; 297 | border-radius: 50%; 298 | width: 30px; 299 | height: 30px; 300 | } 301 | 302 | .three-dot:hover { 303 | background-color: var(--three-dot-hover-bg); 304 | } 305 | 306 | .recent-chat:hover, 307 | .show-more:hover { 308 | background-color: var(--recent-chat-hover-bg); 309 | } 310 | 311 | .recent-chat.active-recent-chat:hover { 312 | background-color: var(--recent-chat-active-bg); 313 | } 314 | 315 | .recent-chat:hover .three-dot { 316 | opacity: 1; 317 | } 318 | 319 | /* setting section start */ 320 | 321 | .settings-section { 322 | display: flex; 323 | flex-flow: nowrap column; 324 | gap: 10px; 325 | position: absolute; 326 | transition: all 0.4s ease; 327 | } 328 | 329 | .side-bar-long .settings-section { 330 | height: 230px; 331 | bottom: 35px; 332 | width: 90%; 333 | left: 20px; 334 | } 335 | 336 | .side-bar-sort .settings-section { 337 | height: 100px; 338 | bottom: 60px; 339 | } 340 | 341 | .settings-section div { 342 | display: flex; 343 | gap: 10px; 344 | cursor: pointer; 345 | } 346 | 347 | .help, 348 | .activity, 349 | .settings { 350 | border: 0px; 351 | padding: 7px 0px; 352 | position: relative; 353 | } 354 | 355 | .help:hover::before, 356 | .activity:hover::before, 357 | .settings:hover::before { 358 | position: absolute; 359 | top: -2px; 360 | font-size: 12px; 361 | color: var(--help-before-font-color); 362 | font-weight: 400; 363 | z-index: 5; 364 | background: var(--help-before-bg); 365 | height: 35px; 366 | display: flex; 367 | justify-content: center; 368 | align-items: center; 369 | border: 0px; 370 | border-radius: 5px; 371 | } 372 | 373 | .side-bar-sort .help:hover::before, 374 | .side-bar-sort .activity:hover::before, 375 | .side-bar-sort .settings:hover::before { 376 | left: 60px; 377 | } 378 | 379 | .side-bar-long .help:hover::before, 380 | .side-bar-long .activity:hover::before, 381 | .side-bar-long .settings:hover::before { 382 | left: 260px; 383 | } 384 | 385 | .help:hover::before { 386 | content: "Help"; 387 | width: 45px; 388 | } 389 | 390 | .activity:hover::before { 391 | content: "Activity"; 392 | width: 55px; 393 | } 394 | 395 | .settings:hover::before { 396 | content: "Settings"; 397 | width: 60px; 398 | } 399 | 400 | .side-bar-long .help, 401 | .side-bar-long .activity, 402 | .side-bar-long .settings { 403 | padding: 7px 7px; 404 | border-radius: 20px; 405 | } 406 | 407 | .side-bar-sort .help, 408 | .side-bar-sort .activity, 409 | .side-bar-sort .settings { 410 | padding: 8px 8px; 411 | border-radius: 50%; 412 | justify-content: center; 413 | } 414 | 415 | .help:hover, 416 | .activity:hover, 417 | .settings:hover { 418 | background-color: var(--help-hover-bg); 419 | } 420 | 421 | .upgrade-gimini { 422 | background-color: var(--upgrade-gimini-settings-bg); 423 | border: 0px; 424 | padding: 12px 8px; 425 | border-radius: 20px; 426 | display: flex; 427 | justify-content: flex-start; 428 | align-items: center; 429 | cursor: pointer; 430 | width: 220px; 431 | } 432 | 433 | .upgrade-gimini img { 434 | width: 20px; 435 | height: 20px; 436 | } 437 | 438 | .upgrade-gimini p { 439 | font-size: 12px; 440 | color: var(--upgrade-gimini-p); 441 | } 442 | 443 | .location { 444 | display: flex; 445 | } 446 | 447 | .dot img { 448 | width: 15px; 449 | height: 15px; 450 | } 451 | 452 | .dot { 453 | padding-left: 8px !important; 454 | } 455 | 456 | .location p { 457 | font-size: 11px; 458 | line-height: 16px; 459 | color: var(--location-font-color); 460 | text-align: left; 461 | } 462 | 463 | .location-name { 464 | display: block; 465 | color: var(--location-name-color); 466 | text-transform: capitalize; 467 | } 468 | 469 | .span-dot { 470 | font-size: 20px; 471 | color: var(--dot-color); 472 | } 473 | 474 | .side-bar-sort .location { 475 | display: none; 476 | } 477 | 478 | @media (max-width: 960px) { 479 | .sidebar-main { 480 | position: absolute; 481 | max-width: 85%; 482 | z-index: 4; 483 | } 484 | 485 | .side-bar-sort.sidebar-main { 486 | left: -200px; 487 | max-width: 0px; 488 | } 489 | 490 | .side-bar-long.sidebar-main { 491 | left: 0px; 492 | } 493 | 494 | .menu-icon { 495 | opacity: 0; 496 | } 497 | 498 | .pluc-icon { 499 | display: none; 500 | } 501 | 502 | .recent-chat-main { 503 | margin-top: 0rem; 504 | width: 90%; 505 | gap: 4px; 506 | height: calc(85.5vh - 235px); 507 | } 508 | 509 | .recent-chat-section p:first-child { 510 | margin-bottom: 10px; 511 | } 512 | 513 | .recent-chat-main p { 514 | font-size: 17px; 515 | } 516 | 517 | .recent-chat { 518 | padding: 10px 7px; 519 | border-radius: 24px; 520 | } 521 | 522 | .show-more { 523 | padding: 10px 7px; 524 | } 525 | 526 | .recent-chat p { 527 | -webkit-box-orient: vertical; 528 | overflow: hidden; 529 | display: -webkit-box; 530 | -webkit-line-clamp: 1; 531 | } 532 | 533 | .help:hover::before, 534 | .activity:hover::before, 535 | .settings:hover::before { 536 | display: none; 537 | } 538 | } 539 | 540 | @media (max-width: 450px) { 541 | .recent-chat-main { 542 | width: 90%; 543 | } 544 | 545 | .three-dot { 546 | display: none; 547 | } 548 | } 549 | 550 | @media (max-width: 400px) { 551 | .side-bar-long .settings-section { 552 | height: 220px; 553 | bottom: 35px; 554 | } 555 | } 556 | --------------------------------------------------------------------------------