├── .env.sample ├── .gitignore ├── README.md ├── middleware.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public └── assets │ ├── gemini-banner.png │ ├── gemini-features.png │ ├── gemini-logo.svg │ ├── gemini-phone-banner.png │ ├── projects-img │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png │ └── readme-banner.png ├── src ├── actions │ └── actions.ts ├── app │ ├── (routes) │ │ └── (general) │ │ │ ├── app │ │ │ ├── [chat] │ │ │ │ └── page.tsx │ │ │ ├── loading.tsx │ │ │ ├── page.tsx │ │ │ └── prompt-gallery │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ ├── api │ │ └── auth │ │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── loading.tsx │ ├── models │ │ ├── chat.model.ts │ │ └── user.model.ts │ └── page.tsx ├── auth.ts ├── components │ ├── chat-provider-components │ │ ├── chat-actions-btns.tsx │ │ ├── chat-provider.tsx │ │ ├── code-block.tsx │ │ ├── gradient-loader.tsx │ │ ├── modify-response.tsx │ │ ├── msg-loader.tsx │ │ ├── optimistic-chat.tsx │ │ ├── share-chat.tsx │ │ ├── speech-to-text.tsx │ │ └── text-to-speech.tsx │ ├── dev-components │ │ ├── dev-button.tsx │ │ ├── dev-drawer.tsx │ │ ├── dev-emoji-picker.tsx │ │ ├── dev-input.tsx │ │ ├── dev-modal.tsx │ │ ├── dev-popover.tsx │ │ ├── dev-toast.tsx │ │ ├── react-tooltip.tsx │ │ └── sleek-toggle.tsx │ ├── header-components │ │ ├── custom-geminikey.tsx │ │ ├── gemini-logo.tsx │ │ ├── header.tsx │ │ ├── portfolio-projects.tsx │ │ ├── signin-now.tsx │ │ └── top-loader.tsx │ ├── input-prompt-components │ │ ├── input-actions.tsx │ │ └── input-prompt.tsx │ ├── prompt-gallery-components │ │ └── prompt-cards.tsx │ ├── sidebar-components │ │ ├── sidebar-chat-list.tsx │ │ ├── sidebar.tsx │ │ └── theme-switch.tsx │ └── temp-components │ │ ├── demo-cmp.tsx │ │ ├── extension.ts │ │ └── home-cards.tsx ├── types │ └── types.ts └── utils │ ├── db.ts │ ├── emoji.json │ ├── gemini-zustand.ts │ ├── prev-chat-initializer.tsx │ ├── prompts-array.json │ ├── shadow.ts │ └── theme-providers.tsx ├── tailwind.config.ts └── tsconfig.json /.env.sample: -------------------------------------------------------------------------------- 1 | # Google OAuth credentials 2 | GOOGLE_ID=your_google_id_here 3 | GOOGLE_SECRET=your_google_secret_here 4 | 5 | # MongoDB connection string 6 | MONGODB_URI=mongodb://localhost:27017 7 | 8 | # NextAuth secret 9 | NEXTAUTH_SECRET=your_nextauth_secret_here 10 | 11 | # Base URL for the application 12 | BASE_URL=http://localhost:3000 13 | 14 | # Google API Key 15 | NEXT_PUBLIC_API_KEY=your_google_api_key_here -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Gemini Clone 2 | 3 | ![Gemini Clone Logo](public/assets/readme-banner.png) 4 | 5 | An advanced GEMINI Clone built with Next.js, featuring enhanced functionalities and faster response times. 6 | 7 | ## Table of Contents 8 | - [Overview](#overview) 9 | - [Features](#features) 10 | - [Technology Stack](#technology-stack) 11 | - [Getting Started](#getting-started) 12 | - [Usage](#usage) 13 | - [License](#license) 14 | 15 | ## Overview 16 | 17 | This project is an advanced recreation of the GEMINI AI platform, leveraging Next.js as a full-stack React framework. It incorporates all core functionalities of the actual GEMINI while providing natural, optimized responses that outperform the original in terms of speed. 18 | 19 | ## Features 20 | ![Gemini Features](public/assets/gemini-features.png) 21 | 22 | ### Authentication and State Management 23 | - 🔐 Robust authentication using Next Auth v5 24 | - 🔄 Efficient state management with Zustand 25 | 26 | ### User Interface and Experience 27 | - ✨ Micro-animations powered by Framer Motion 28 | - 🎨 Custom in-house components for UI flexibility 29 | - 🌓 Dark and light mode toggle 30 | - 📱 Fully responsive design for both desktop and mobile 31 | 32 | ### Chat Functionality 33 | - 💬 Advanced chat features including rename, delete, and pin 34 | - 🗣️ Text-to-speech and speech-to-text capabilities 35 | - 🔗 Share chats and copy to clipboard 36 | - ⏹️ Abort functionality for stopping responses 37 | - 🖼️ Chat with images, including mobile support 38 | - 🔄 Response modification and regeneration 39 | 40 | ### Advanced Features 41 | - 🎭 Prompt Gallery for model-specific outputs 42 | - ✏️ Edit Prompt functionality 43 | - 🌈 Syntax highlighting for code outputs 44 | - 💡 Random prompt suggestions on homepage 45 | 46 | ## Technology Stack 47 | 48 | - **Frontend Framework**: Next.js (React) 49 | - **Authentication**: Next Auth v5 50 | - **State Management**: Zustand 51 | - **Animations**: Framer Motion 52 | - **UI Components**: Custom dev-components 53 | - **Theming**: Next Themes 54 | 55 | ## Getting Started 56 | 57 | To get a local copy up and running, follow these steps: 58 | 59 | ```bash 60 | # Clone the repository 61 | git clone https://github.com/yourusername/dev-gemini-clone.git 62 | 63 | # Navigate to the project directory 64 | cd dev-gemini-clone 65 | 66 | # Install dependencies 67 | npm install 68 | 69 | # Start the development server 70 | npm run dev -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | export { auth as middleware } from "@/auth" 2 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: 'https', 7 | hostname: 'lh3.googleusercontent.com', 8 | port: '', 9 | }, 10 | ], 11 | }, 12 | 13 | }; 14 | 15 | export default nextConfig; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dev-gemini", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@google/generative-ai": "^0.14.1", 13 | "@tiptap/extension-bubble-menu": "^2.4.0", 14 | "@tiptap/extension-code-block-lowlight": "^2.4.0", 15 | "@tiptap/pm": "^2.4.0", 16 | "@tiptap/react": "^2.4.0", 17 | "@tiptap/starter-kit": "^2.4.0", 18 | "axios": "^1.7.2", 19 | "clsx": "^2.1.1", 20 | "framer-motion": "^11.2.13", 21 | "highlight.js": "^11.10.0", 22 | "lowlight": "^2.9.0", 23 | "mongoose": "^7.0.3", 24 | "nanoid": "^5.0.7", 25 | "next": "14.2.4", 26 | "next-auth": "^5.0.0-beta.19", 27 | "next-themes": "^0.3.0", 28 | "nextjs-toploader": "^1.6.12", 29 | "prop-types": "^15.8.1", 30 | "react": "^18", 31 | "react-dom": "^18", 32 | "react-icons": "^5.2.1", 33 | "react-markdown": "^9.0.1", 34 | "react-masonry-css": "^1.0.16", 35 | "react-modern-drawer": "^1.3.1", 36 | "react-shadow": "^20.5.0", 37 | "react-share": "^5.1.0", 38 | "react-tooltip": "^5.27.1", 39 | "react-use": "^17.5.0", 40 | "sonner": "^1.5.0", 41 | "styled-components": "^6.1.11", 42 | "tailwindcss-all": "^0.0.2", 43 | "tiptap-markdown": "^0.8.10", 44 | "use-ripple-hook": "^1.0.24", 45 | "zustand": "^4.5.4" 46 | }, 47 | "devDependencies": { 48 | "@types/node": "^20", 49 | "@types/react": "^18", 50 | "@types/react-dom": "^18", 51 | "postcss": "^8", 52 | "tailwindcss": "^3.4.1", 53 | "typescript": "^5" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/assets/gemini-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/gemini-banner.png -------------------------------------------------------------------------------- /public/assets/gemini-features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/gemini-features.png -------------------------------------------------------------------------------- /public/assets/gemini-phone-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/gemini-phone-banner.png -------------------------------------------------------------------------------- /public/assets/projects-img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/1.png -------------------------------------------------------------------------------- /public/assets/projects-img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/2.png -------------------------------------------------------------------------------- /public/assets/projects-img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/3.png -------------------------------------------------------------------------------- /public/assets/projects-img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/4.png -------------------------------------------------------------------------------- /public/assets/projects-img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/5.png -------------------------------------------------------------------------------- /public/assets/projects-img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/6.png -------------------------------------------------------------------------------- /public/assets/projects-img/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/7.png -------------------------------------------------------------------------------- /public/assets/projects-img/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/8.png -------------------------------------------------------------------------------- /public/assets/projects-img/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/projects-img/9.png -------------------------------------------------------------------------------- /public/assets/readme-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/public/assets/readme-banner.png -------------------------------------------------------------------------------- /src/actions/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import Chat from "@/app/models/chat.model"; 3 | import { Message } from "../types/types"; 4 | import connectDB from "../utils/db"; 5 | import { Types } from "mongoose"; 6 | import { auth } from "@/auth"; 7 | import { revalidatePath } from "next/cache"; 8 | 9 | 10 | export const createChat = async ( 11 | chat: Message & { userID: string; chatID: string; imgName?: string } 12 | ) => { 13 | try { 14 | const session = await auth(); 15 | if (!session) { 16 | throw new Error("User not authenticated"); 17 | } 18 | const {user}=session 19 | await connectDB(); 20 | const { userPrompt, llmResponse, chatID, imgName } = chat; 21 | const data = await Chat.create({ 22 | participant: user?.id, 23 | chatID, 24 | message: { userPrompt, llmResponse, imgName}, 25 | }); 26 | revalidatePath(`/app/[${chatID}]`); 27 | // Serialize the data 28 | const serializedData = JSON.parse(JSON.stringify(data)); 29 | return { message: serializedData, success: true }; 30 | } catch (error: any) { 31 | console.error(error); 32 | return { success: false, message: error.message }; 33 | } 34 | }; 35 | 36 | export const getSidebarChat = async (userID: string) => { 37 | try { 38 | await connectDB(); 39 | const data = await Chat.aggregate([ 40 | { $match: { participant: new Types.ObjectId(userID) } }, 41 | { 42 | $group: { 43 | _id: "$chatID", 44 | doc: { $first: "$$ROOT" }, 45 | }, 46 | }, 47 | { $replaceRoot: { newRoot: "$doc" } }, 48 | { $sort: { isPinned: -1, createdAt: -1 } }, // Sort by isPinned (descending) then createdAt (descending) 49 | ]); 50 | return { success: true, message: JSON.parse(JSON.stringify(data)) }; 51 | } catch (error) { 52 | console.error("Error in getSidebarChat:", error); 53 | return { success: false }; 54 | } 55 | }; 56 | 57 | export const getChatHistory = async ({ 58 | userID, 59 | chatID, 60 | }: { 61 | userID: string; 62 | chatID: string; 63 | }) => { 64 | try { 65 | await connectDB(); 66 | const data = await Chat.find({ 67 | participant: new Types.ObjectId(userID), 68 | chatID: chatID, 69 | }); 70 | return { success: true, message: JSON.parse(JSON.stringify(data)) }; 71 | } catch (error) { 72 | console.error("Error in getChatHistory:", error); 73 | return { success: false }; 74 | } 75 | }; 76 | 77 | export const deleteChat = async (chatID: string) => { 78 | try { 79 | await connectDB(); 80 | const session = await auth(); 81 | if(!session){ 82 | throw new Error("User not authenticated") 83 | } 84 | const {user}=session 85 | const data = await Chat.deleteMany({ 86 | participant: new Types.ObjectId(user?.id), 87 | chatID: chatID, 88 | }); 89 | revalidatePath("/app"); 90 | return { success: true, message: JSON.parse(JSON.stringify(data)) }; 91 | } catch (error) { 92 | console.error("Error in deleteChat:", error); 93 | return { success: false }; 94 | } 95 | }; 96 | 97 | export const renameChat = async ( 98 | chatID: string, 99 | message: Partial<{ title: string | null; icon: string | null }> 100 | ) => { 101 | try { 102 | const session = await auth(); 103 | if(!session){ 104 | throw new Error("User not authenticated") 105 | } 106 | const {user}=session 107 | 108 | const chatInfo = { 109 | ...(message.title ? { title: message.title } : {}), 110 | ...(message.icon ? { icon: message.icon } : {}), 111 | }; 112 | 113 | const result = await Chat.updateMany( 114 | { 115 | participant: new Types.ObjectId(user?.id), 116 | chatID, 117 | }, 118 | { $set: { chatInfo } }, 119 | { new: true } 120 | ); 121 | 122 | if (!result) { 123 | return { 124 | success: false, 125 | message: "Chat not found or user not authorized", 126 | }; 127 | } 128 | revalidatePath("/app"); 129 | return { success: true, message: JSON.parse(JSON.stringify(result)) }; 130 | } catch (error) { 131 | console.error("Error in renameChat:", error); 132 | return { 133 | success: false, 134 | message: "An error occurred while renaming the chat", 135 | }; 136 | } 137 | }; 138 | 139 | export const pinChat = async (chatID: string, pinStatus: boolean) => { 140 | try { 141 | const session = await auth(); 142 | if(!session){ 143 | throw new Error("User not authenticated") 144 | } 145 | const {user}=session 146 | await connectDB(); 147 | 148 | 149 | const result = await Chat.updateMany( 150 | { 151 | participant: new Types.ObjectId(user?.id), 152 | chatID, 153 | }, 154 | { $set: { isPinned: pinStatus } }, 155 | { new: true } 156 | ); 157 | if (!result) { 158 | return { 159 | success: false, 160 | message: "Chat not found or user not authorized", 161 | }; 162 | } 163 | revalidatePath("/app"); 164 | return { success: true, message: JSON.parse(JSON.stringify(result)) }; 165 | } catch (error) { 166 | console.error("Error in pinChat:", error); 167 | return { 168 | success: false, 169 | message: "An error occurred while pinning the chat", 170 | }; 171 | } 172 | }; 173 | 174 | export const updateResponse = async ({ 175 | chatUniqueId, 176 | updatedResponse, 177 | }: { 178 | chatUniqueId: string; 179 | updatedResponse: string; 180 | }) => { 181 | try { 182 | const updatedChat = await Chat.findByIdAndUpdate( 183 | chatUniqueId, 184 | { 185 | $set: { 186 | "message.llmResponse": updatedResponse, 187 | }, 188 | }, 189 | { new: true, runValidators: true } 190 | ); 191 | 192 | if (!updatedChat) { 193 | return { 194 | success: false, 195 | message: "Chat not found", 196 | }; 197 | } 198 | return { 199 | success: true, 200 | message: JSON.parse(JSON.stringify(updatedChat)), 201 | }; 202 | } catch (error) { 203 | console.error("Error updating response:", error); 204 | return { 205 | success: false, 206 | message: "An error occurred while updating response", 207 | }; 208 | } 209 | }; 210 | 211 | // export const generateResponse = async (prompt: string) => { 212 | // try { 213 | // await connectDB(); 214 | // if (!prompt) { 215 | // throw new Error("Prompt is empty"); 216 | // } 217 | // const res = await axios.post( 218 | // `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${process.env.NEXT_PUBLIC_API_KEY}`, 219 | // { 220 | // contents: [{ parts: [{ text: prompt }] }], 221 | // } 222 | // ); 223 | // const generatedResponse = res.data.candidates[0].content.parts[0].text; 224 | // if (!res || !generatedResponse) { 225 | // throw new Error("Failed to generate response"); 226 | // } 227 | 228 | // return { 229 | // success: true, 230 | // message: generatedResponse, 231 | // }; 232 | // } catch (error) { 233 | // return { 234 | // success: false, 235 | // message: error, 236 | // }; 237 | // } 238 | // }; -------------------------------------------------------------------------------- /src/app/(routes)/(general)/app/[chat]/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@/auth"; 2 | import { getChatHistory } from "@/actions/actions"; 3 | import { redirect } from "next/navigation"; 4 | import MsgLoader from "@/components/chat-provider-components/msg-loader"; 5 | import OptimisticChat from "@/components/chat-provider-components/optimistic-chat"; 6 | 7 | const Page = async ({ params }: { params: { chat: string } }) => { 8 | const session = await auth(); 9 | const { chat } = params; 10 | 11 | const fetchedData = await getChatHistory({ 12 | chatID: chat, 13 | userID: session?.user?.id as string, 14 | }); 15 | if (!fetchedData.success || !session) redirect("/app"); 16 | const { message } = fetchedData; 17 | const { name, image } = session?.user as { name: string; image: string }; 18 | 19 | return ( 20 |
21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | export default Page; 28 | -------------------------------------------------------------------------------- /src/app/(routes)/(general)/app/loading.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import GradientLoader from "@/components/chat-provider-components/gradient-loader"; 3 | import geminiZustand from "@/utils/gemini-zustand"; 4 | import Image from "next/image"; 5 | import { BsImage } from "react-icons/bs"; 6 | import { MdImageSearch, MdOutlineImage } from "react-icons/md"; 7 | import { SiGooglegemini } from "react-icons/si"; 8 | 9 | export default function Loading() { 10 | const { currChat, userData, msgLoader, inputImgName } = geminiZustand(); 11 | return ( 12 |
13 | { 14 | msgLoader ? ( 15 |
16 |
17 |
18 | {userData?.name 25 |