├── .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 | 
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 | 
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 |
25 |
31 |
32 | {inputImgName &&
33 |
38 | }
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ) : (
48 |
49 | )
50 | }
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/src/app/(routes)/(general)/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from "@/auth";
2 | import HomeCards from "@/components/temp-components/home-cards";
3 | import React from "react";
4 |
5 |
6 | const page = async () => {
7 | const session = await auth();
8 |
9 | return (
10 |
11 |
12 | Hello, {session?.user ? session?.user.name?.split(" ")[0] : "Guest"}
13 |
14 |
15 | {session?.user ? "How can I help you today?" : "Sign in to get started"}
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default page;
23 |
--------------------------------------------------------------------------------
/src/app/(routes)/(general)/app/prompt-gallery/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import PromptCards from '@/components/prompt-gallery-components/prompt-cards'
3 | import React from 'react'
4 | import Prompts from "../../../../../utils/prompts-array.json"
5 | import Masonry from "react-masonry-css";
6 |
7 |
8 |
9 | const page = () => {
10 | const breakpointColumnsObj = {
11 | default: 4,
12 | 1100: 3,
13 | 700: 2,
14 | 500: 1
15 | };
16 | return (
17 |
18 | Prompt Gallery
19 |
23 |
24 | {
25 | Prompts.map((item, index) => (
26 |
27 | ))
28 | }
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default page
--------------------------------------------------------------------------------
/src/app/(routes)/(general)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from "@/auth";
2 | import { getSidebarChat } from "@/actions/actions";
3 | import React from "react";
4 | import SideBar from "@/components/sidebar-components/sidebar";
5 | import Header from "@/components/header-components/header";
6 | import InputPrompt from "@/components/input-prompt-components/input-prompt";
7 | import DevToast from "@/components/dev-components/dev-toast";
8 |
9 | const GeneralLayout = async ({ children }: { children: React.ReactNode }) => {
10 | const session = await auth();
11 | const sidebarList = await getSidebarChat(session?.user?.id as string);
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default GeneralLayout;
29 |
--------------------------------------------------------------------------------
/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from "@/auth"
2 | export const { GET, POST } = handlers
3 |
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devyanshyadav/dev-gemini-clone/6eb9836d3349bd51b256dfade216da17d49adcb3/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | /* Gradient DropDown loader */
6 | .dropdown-loader {
7 | background-color: #f6f7f8;
8 | background: linear-gradient(
9 | 33deg,
10 | rgb(148 163 184 / 0.15) 0%,
11 | rgb(148 163 184 / 0.15) 25%,
12 | rgba(72, 127, 237, 1) 50%,
13 | rgb(148 163 184 / 0.15) 75%,
14 | rgb(148 163 184 / 0.15) 100%
15 | );
16 | background-size: 800px 50px;
17 | border: none;
18 | animation: dropdown-loader 3s infinite linear -0.5s;
19 | }
20 |
21 | @keyframes dropdown-loader {
22 | 0% {
23 | background-position: -800px 0px;
24 | }
25 |
26 | 100% {
27 | background-position: 800px 0px;
28 | }
29 | }
30 |
31 | .text-animation {
32 | background: -webkit-linear-gradient(
33 | 15deg,
34 | #4285f4 0%,
35 | #9b72cb 10%,
36 | #d96570 20%,
37 | #d96570 25%,
38 | #9b72cb 35%,
39 | #4285f4 45%,
40 | #9b72cb 55%,
41 | #d96570 60%,
42 | transparent 80%,
43 | transparent 100%
44 | );
45 | background: linear-gradient(
46 | 76deg,
47 | 15deg,
48 | #4285f4 0%,
49 | #9b72cb 10%,
50 | #d96570 20%,
51 | #d96570 25%,
52 | #9b72cb 35%,
53 | #4285f4 45%,
54 | #9b72cb 55%,
55 | #d96570 60%,
56 | transparent 80%,
57 | transparent 100%
58 | );
59 | color: transparent !important;
60 | background-size: 400% 100%;
61 | background-clip: text;
62 | -webkit-background-clip: text;
63 | -webkit-text-fill-color: transparent;
64 | animation: gradient-animation 3s;
65 | }
66 |
67 | @keyframes gradient-animation {
68 | 0% {
69 | background-position: 100% 50%;
70 | }
71 |
72 | 100% {
73 | background-position: 0% 50%;
74 | }
75 | }
76 |
77 | @keyframes fadeInSection {
78 | from {
79 | opacity: 0;
80 | }
81 | to {
82 | opacity: 1;
83 | }
84 | }
85 |
86 | .fade-in-section {
87 | opacity: 1;
88 | animation: fadeInSection 1s ease-in-out;
89 | }
90 |
91 | /* Top Loader */
92 | .loader {
93 | width: 100%;
94 | height: 3px;
95 | position: relative;
96 | background: rgb(78, 130, 238, 0.4);
97 | overflow: hidden;
98 | }
99 | .loader::after {
100 | content: "";
101 | width: 192px;
102 | height: 3px;
103 | background: rgb(78, 130, 238);
104 | position: absolute;
105 | top: 0;
106 | left: 0;
107 | box-sizing: border-box;
108 | animation: animloader 2s linear infinite;
109 | }
110 |
111 | @keyframes animloader {
112 | 0% {
113 | left: 0;
114 | transform: translateX(-100%);
115 | }
116 | 100% {
117 | left: 100%;
118 | transform: translateX(0%);
119 | }
120 | }
121 |
122 | /* gradient skeleton loader */
123 | .gradient-loader hr {
124 | background-color: #f6f7f8;
125 | background: linear-gradient(
126 | 33deg,
127 | rgb(148 163 184 / 0.15) 0%,
128 | rgb(148 163 184 / 0.15) 25%,
129 | rgba(72, 127, 237, 1) 50%,
130 | rgb(148 163 184 / 0.15) 75%,
131 | rgb(148 163 184 / 0.15) 100%
132 | );
133 | background-size: 800px 50px;
134 | height: 15px;
135 | border-radius: 4px;
136 | border: none;
137 | }
138 |
139 | .gradient-loader hr:nth-child(1) {
140 | animation: gemini-loader 3s infinite linear -0.5s;
141 | }
142 |
143 | .gradient-loader hr:nth-child(3) {
144 | animation: gemini-loader 3s infinite linear -1s;
145 | width: 66%;
146 | }
147 |
148 | .gradient-loader hr:nth-child(2) {
149 | animation: gemini-loader 3s infinite linear -1.5s;
150 | }
151 |
152 | @keyframes gemini-loader {
153 | 0% {
154 | background-position: -800px 0px;
155 | }
156 |
157 | 100% {
158 | background-position: 800px 0px;
159 | }
160 | }
161 |
162 | /* customScrollbar */
163 | *::-webkit-scrollbar {
164 | width: 8px;
165 | height: 8px;
166 | }
167 | *::-webkit-scrollbar-track {
168 | background: transparent;
169 | }
170 | *::-webkit-scrollbar-thumb {
171 | border-radius: 10px;
172 | background: #94a3b846;
173 | }
174 | *::-webkit-scrollbar-thumb:hover {
175 | background: #888;
176 | }
177 |
178 | textarea {
179 | field-sizing: content;
180 | }
181 |
182 | .copyBtn {
183 | display: flex;
184 | align-items: center;
185 | justify-content: space-between;
186 | gap: 0.5rem;
187 | position: absolute;
188 | top: 10px;
189 | right: 10px;
190 | }
191 |
192 | .copyBtn button {
193 | background: transparent;
194 | border: none;
195 | cursor: pointer;
196 | border-radius: 6px;
197 | }
198 |
199 | .copyBtn span {
200 | font-size: 0.8rem;
201 | font-weight: 500;
202 | }
203 |
204 | /* ModalLoader */
205 | .modal-loader {
206 | width: 20px;
207 | height: 20px;
208 | border: 3px solid rgb(78, 130, 238);
209 | border-bottom-color: transparent;
210 | border-radius: 50%;
211 | display: inline-block;
212 | box-sizing: border-box;
213 | animation: rotation 1s linear infinite;
214 | }
215 |
216 | @keyframes rotation {
217 | 0% {
218 | transform: rotate(0deg);
219 | }
220 | 100% {
221 | transform: rotate(360deg);
222 | }
223 | }
224 |
225 | /* For div animation */
226 | .fade-in-element {
227 | animation: fadeIn 0.3s;
228 | }
229 | @keyframes fadeIn {
230 | 0% {
231 | opacity: 0;
232 | transform: translateY(10px);
233 | }
234 | 100% {
235 | opacity: 1;
236 | transform: translateY(0);
237 | }
238 | }
239 |
240 | /* Dev Toast animation */
241 | .dev-toast {
242 | animation: toast 0.1s;
243 | }
244 | @keyframes toast {
245 | 0% {
246 | opacity: 0;
247 | transform: scale(0.9);
248 | }
249 | 100% {
250 | opacity: 1;
251 | transform: scale(1);
252 | }
253 | }
254 |
255 | /* microphone blink animation */
256 | .animate-pulse-border {
257 | animation: pulse-border 2s infinite;
258 | }
259 |
260 | @keyframes pulse-border {
261 | 0% {
262 | border-color: transparent;
263 | }
264 | 50% {
265 | border-color: rgba(
266 | 59,
267 | 130,
268 | 246,
269 | 0.5
270 | ); /* tailwind blue-500 at 50% opacity */
271 | }
272 | 100% {
273 | border-color: transparent;
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Outfit } from "next/font/google";
3 | import "./globals.css";
4 | import { ThemeProviders } from "@/utils/theme-providers";
5 | const OutfitFont = Outfit({
6 | subsets: ["latin"],
7 | weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
8 | });
9 |
10 |
11 | export const metadata: Metadata = {
12 | title: "Dev Gemini Clone",
13 | description: "A Gemini-inspired AI assistant built with Next.js",
14 | keywords: ["AI", "assistant", "Gemini", "clone", "Next.js"],
15 | authors: [{ name: "Your Name" }],
16 | creator: "Your Name or Company",
17 | publisher: "Your Name or Company",
18 | openGraph: {
19 | title: "Dev Gemini Clone",
20 | description: "An advanced GEMINI Clone built with Next.js, featuring enhanced functionalities and faster response times.",
21 | url: "https://dev-gemini-clone.vercel.app",
22 | siteName: "Dev Gemini Clone",
23 | images: [
24 | {
25 | url: "/assets/gemini-banner.png",
26 | width: 1200,
27 | height: 630,
28 | },
29 | ],
30 | locale: "en_US",
31 | type: "website",
32 | },
33 |
34 | twitter: {
35 | card: "summary_large_image",
36 | title: "Dev Gemini Clone",
37 | description: "Experience the power of AI with our Gemini-inspired assistant",
38 | creator: "@yourTwitterHandle",
39 | images: ["/assets/gemini-banner.png"],
40 | },
41 | viewport: {
42 | width: "device-width",
43 | initialScale: 1,
44 | maximumScale: 1,
45 | },
46 | };
47 |
48 | export default function RootLayout({
49 | children,
50 | }: Readonly<{
51 | children: React.ReactNode;
52 | }>) {
53 | return (
54 |
55 |
58 | {children}
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/app/loading.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FcGoogle } from 'react-icons/fc'
3 |
4 | const Loading = () => {
5 | return (
6 |
12 | )
13 | }
14 |
15 | export default Loading
--------------------------------------------------------------------------------
/src/app/models/chat.model.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const chatSchema = new Schema(
4 | {
5 | participant: { type: Schema.Types.ObjectId, ref: "User" },
6 |
7 | chatInfo: { icon: { type: String, default: null }, title: { type: String, default:null } },
8 | isPinned: { type: Boolean, default: false },
9 | chatID: { type: String, required: [true, "Chat ID is required"] },
10 | message: {
11 | userPrompt: String,
12 | llmResponse: String,
13 | imgName: { type: String, default: null },
14 | },
15 | },
16 | { timestamps: true }
17 | );
18 |
19 | const Chat = models.Chat || model("Chat", chatSchema);
20 |
21 | export default Chat;
22 |
--------------------------------------------------------------------------------
/src/app/models/user.model.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const userSchema = new Schema({
4 | username: {
5 | type: String,
6 | required: [true, "Name is required"],
7 | },
8 | email: {
9 | type: String,
10 | unique: true,
11 | required: [true, "Email is required"],
12 | },
13 | image: {
14 | type: String,
15 | required: [true, "Image is required"],
16 | },
17 | });
18 |
19 | const User = models?.User || model("User", userSchema);
20 | export default User;
21 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | const page = async () => {
4 | redirect("/app");
5 | };
6 |
7 | export default page;
8 |
--------------------------------------------------------------------------------
/src/auth.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth";
2 | import GoogleProvider from "next-auth/providers/google";
3 | import connectDB from "./utils/db";
4 | import User from "./app/models/user.model";
5 |
6 | export const { handlers, signIn, signOut, auth } = NextAuth({
7 | providers: [
8 | GoogleProvider({
9 | clientId: process.env.GOOGLE_ID as string,
10 | clientSecret: process.env.GOOGLE_SECRET as string,
11 | }),
12 | ],
13 | callbacks: {
14 | async session({ session }) {
15 | try {
16 | await connectDB();
17 | const sessionUser = await User.findOne({ email: session?.user?.email });
18 | if (session.user) {
19 | session.user.id = sessionUser?._id.toString();
20 | }
21 | return session;
22 | } catch (error) {
23 | console.error(error);
24 | return session;
25 | }
26 | },
27 | async signIn({ profile }) {
28 | try {
29 | const email = profile?.email;
30 | if (!email) return false;
31 | await connectDB();
32 | let user = await User.findOne({ email: email });
33 | if (!user) {
34 | user = await User.create({
35 | email: profile?.email,
36 | username: profile?.given_name?.replace(" ", "").toLowerCase(),
37 | image: profile?.picture,
38 | });
39 | }
40 | return true;
41 | } catch (error) {
42 | console.error(error);
43 | return false;
44 | }
45 | },
46 | },
47 | pages: {
48 | signIn: "/",
49 | error: "/",
50 | },
51 | secret: process.env.NEXTAUTH_SECRET,
52 | });
--------------------------------------------------------------------------------
/src/components/chat-provider-components/chat-actions-btns.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import { BiDislike, BiLike } from "react-icons/bi";
4 | import DevButton from "../dev-components/dev-button";
5 | import ReactTooltip from "../dev-components/react-tooltip";
6 | import ModifyResponse from "./modify-response";
7 | import ShareChat from "./share-chat";
8 | import { FiMoreVertical } from "react-icons/fi";
9 | import DevPopover from "../dev-components/dev-popover";
10 | import { MdContentCopy, MdOutlineFlag } from "react-icons/md";
11 | import geminiZustand from "@/utils/gemini-zustand";
12 | import { FcGoogle } from "react-icons/fc";
13 | import { GoogleGenerativeAI } from "@google/generative-ai";
14 | import Link from "next/link";
15 | import { IoMdSearch } from "react-icons/io";
16 |
17 |
18 | const ChatActionsBtns = ({
19 | chatID,
20 | llmResponse,
21 | userPrompt,
22 | shareMsg,
23 | }: {
24 | chatID: string;
25 | llmResponse: string;
26 | userPrompt: string;
27 | shareMsg: string;
28 | }) => {
29 | const { devToast, setToast,geminiApiKey } = geminiZustand();
30 | const genAI = new GoogleGenerativeAI(geminiApiKey as string);
31 | const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
32 | const [googleRes, setGoogleRes] = useState(null)
33 | const [loader, setLoader] = useState(false)
34 |
35 | const copyToClipboard = async () => {
36 | try {
37 | await navigator.clipboard.writeText(shareMsg);
38 | setToast('Copied to clipboard')
39 | } catch (err) {
40 | console.error("Failed to copy text: ", err);
41 | }
42 | };
43 |
44 | const handleDoubleCheck = async () => {
45 | const prompt = `
46 | Generate a list of at least 5 different Google search queries based strictly on the user prompt. Provide the queries in an array json format without any unnecessary responses. Ensure the queries are relevant and varied but aligned with the user's prompt.
47 | Previous chats:
48 | Current User Query:
49 | ${userPrompt}`
50 | try {
51 | setLoader(true)
52 | const result = await model.generateContent(prompt);
53 | const response = await result.response;
54 | const text = response.text();
55 | const googleResArray = JSON.parse(text.replace(/^```json\s*|\s*```$/gm, "").trim());
56 | setGoogleRes(googleResArray)
57 |
58 | } catch (error) {
59 | console.log(error)
60 | }
61 | finally {
62 | setLoader(false)
63 | }
64 | }
65 | return (
66 | <>
67 |
68 | {[
69 | { icon: BiLike, tipdata: "Good job" },
70 | { icon: BiDislike, tipdata: "Bad job" },
71 | ].map((item, index) => (
72 |
73 |
80 |
81 |
82 |
83 | ))}
84 |
85 |
86 |
87 |
95 | {loader ? : }
96 |
97 |
98 |
102 |
109 |
110 |
111 |
112 | }
113 | >
114 |
115 |
121 |
122 | Copy
123 |
124 |
129 |
130 | Report legal issue
131 |
132 |
133 |
134 |
135 |
136 | {googleRes && googleRes.length > 0 &&
137 |
Search related topics
138 |
139 | {
140 | googleRes.map((item, index) =>
141 |
142 | {item}
143 | )
144 | }
145 |
146 |
}
147 | >
148 | );
149 | };
150 |
151 | export default ChatActionsBtns;
152 |
--------------------------------------------------------------------------------
/src/components/chat-provider-components/chat-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useEffect, useRef } from "react";
3 | import CodeBlock from "@/components/chat-provider-components/code-block";
4 | import {
5 | BubbleMenu,
6 | EditorContent,
7 | ReactNodeViewRenderer,
8 | useEditor,
9 | } from "@tiptap/react";
10 | import StarterKit from "@tiptap/starter-kit";
11 | import { CodeBlockLowlight } from "@tiptap/extension-code-block-lowlight";
12 | import { lowlight } from "lowlight";
13 | import { Markdown as TipTapMkd } from "tiptap-markdown";
14 | import { FormatOutput } from "@/utils/shadow";
15 | import root from "react-shadow/styled-components";
16 | import { GoogleGenerativeAI } from "@google/generative-ai";
17 | import geminiZustand from "@/utils/gemini-zustand";
18 | import { FaWandMagicSparkles } from "react-icons/fa6";
19 | import { createPortal } from "react-dom";
20 | import { updateResponse } from "@/actions/actions";
21 | import DevButton from "../dev-components/dev-button";
22 | import { MdImageSearch, MdOutlineImage, MdOutlineModeEditOutline } from "react-icons/md";
23 | import { SiGooglegemini } from "react-icons/si";
24 |
25 | import Image from "next/image";
26 | import ReactTooltip from "../dev-components/react-tooltip";
27 | import TextToSpeech from "./text-to-speech";
28 | import ChatActionsBtns from "./chat-actions-btns";
29 | import { BsImage } from "react-icons/bs";
30 |
31 | const extensions = [
32 | StarterKit,
33 | TipTapMkd,
34 | CodeBlockLowlight.extend({
35 | addNodeView: () => ReactNodeViewRenderer(CodeBlock),
36 | }).configure({ lowlight }),
37 | ];
38 |
39 | const PROMPT_TYPES = {
40 | Longer: "Lengthen",
41 | Shorter: "Shorten",
42 | Regenerate: "Regenerate",
43 | Remove: "Remove",
44 | Simplify: "Simplify",
45 | Elaborate: "Elaborate on",
46 | Formalize: "Make more formal",
47 | Casual: "Make more casual",
48 | Persuasive: "Make more persuasive",
49 | Technical: "Make more technical",
50 | Metaphor: "Add a metaphor to",
51 | Examples: "Add examples to",
52 | Counterargument: "Add a counterargument to",
53 | Summary: "Summarize",
54 | };
55 |
56 | const ChatProvider: React.FC<{
57 | llmResponse: string;
58 | chatUniqueId: string;
59 | userPrompt: string;
60 | imgName?: string;
61 | imgInfo: { imgSrc: string; imgAlt: string };
62 | }> = ({ llmResponse, chatUniqueId, userPrompt, imgInfo, imgName }) => {
63 | const { topLoader, setCurrChat, setTopLoader, currChat,geminiApiKey } = geminiZustand();
64 | const [dropdown, setDropdown] = useState(false);
65 | const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
66 | const [initialResponse, setInitialResponse] = useState(llmResponse);
67 | const buttonRef = useRef(null);
68 | const [selectedNode, setSelectedNode] = useState("");
69 | const [updateLoader, setUpdateLoader] = useState(false);
70 | const inputRef = useRef(null);
71 | const [initialPrompt, setInitialPrompt] = useState(userPrompt);
72 | const [promptModify, setPromptModify] = useState(false);
73 |
74 | const dropdownRef = useRef(null);
75 | const genAI = new GoogleGenerativeAI(geminiApiKey as string);
76 | const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
77 |
78 | const editor = useEditor({
79 | extensions,
80 | content: initialResponse,
81 | onUpdate: ({ editor }) => {
82 | editor.commands.setContent(initialResponse);
83 | },
84 | });
85 |
86 | const handleSelectNode = () => {
87 | const { state } = editor!;
88 | const { from, to } = state.selection;
89 | const selectedNode = state.doc.textBetween(from, to, " ");
90 | setSelectedNode(selectedNode);
91 | };
92 |
93 | const handlePrompt = async (
94 | promptType: keyof typeof PROMPT_TYPES | "Custom"
95 | ) => {
96 | let prompt;
97 | if (promptType === "Custom") {
98 | prompt = `This is the whole response: ${initialResponse}. ${inputRef.current?.value} Specifically focus on this part: "${selectedNode}". Ensure the modified part aligns seamlessly with the rest of the response. Provide the entire modified response back, preserving the essential introductory and concluding phrases without adding any new non-contextual information.`;
99 | } else {
100 | const promptInstructions = {
101 | Longer: "Lengthen",
102 | Shorter: "Shorten",
103 | Regenerate: "Regenerate",
104 | Remove: "Remove",
105 | Simplify: "Simplify the language of",
106 | Elaborate: "Elaborate on",
107 | Formalize: "Rewrite in a more formal tone",
108 | Casual: "Rewrite in a more casual tone",
109 | Persuasive: "Rewrite to be more persuasive",
110 | Technical: "Add more technical details to",
111 | Metaphor: "Incorporate a relevant metaphor into",
112 | Examples: "Add relevant examples to",
113 | Counterargument: "Present a counterargument to",
114 | Summary: "Provide a concise summary of",
115 | };
116 | prompt = `This is the whole response: ${initialResponse}. ${promptInstructions[promptType]} a specific part of the response, specifically "${selectedNode}". Ensure it aligns seamlessly with the rest of the response. Provide the entire modified response back, preserving the essential introductory and concluding phrases without adding any new non-contextual information.`;
117 | }
118 |
119 | try {
120 | setUpdateLoader(true);
121 | const result = await model.generateContent(prompt);
122 | const response = await result.response;
123 | const text = response.text();
124 | if (!text) throw new Error("Error while generating prompt");
125 | const updatedContent = await updateResponse({
126 | chatUniqueId,
127 | updatedResponse: text,
128 | });
129 | setInitialResponse(updatedContent.message.message.llmResponse as string);
130 | editor?.commands.setContent(
131 | updatedContent.message.message.llmResponse as string
132 | );
133 | setDropdown(false);
134 | } catch (error) {
135 | console.error("Error generating response:", error);
136 | } finally {
137 | setUpdateLoader(false);
138 | }
139 | };
140 |
141 | useEffect(() => {
142 | const handleClickOutside = (event: MouseEvent) => {
143 | if (
144 | dropdownRef.current &&
145 | !dropdownRef.current.contains(event.target as Node)
146 | ) {
147 | setDropdown(false);
148 | }
149 | };
150 |
151 | document.addEventListener("mousedown", handleClickOutside);
152 | return () => document.removeEventListener("mousedown", handleClickOutside);
153 | }, []);
154 |
155 |
156 |
157 | const handleButtonClick = () => {
158 | handleSelectNode();
159 | if (buttonRef.current) {
160 | const rect = buttonRef.current.getBoundingClientRect();
161 | setDropdownPosition({
162 | top: rect.bottom + window.scrollY,
163 | left: rect.left + window.scrollX,
164 | });
165 | setDropdown(true);
166 | }
167 | };
168 | const DropdownContent = () => (
169 |
178 |
179 |
{
182 | e.key === "Enter" && handlePrompt("Custom");
183 | }}
184 | type="text"
185 | className="p-2 rounded-lg w-full outline-none "
186 | placeholder="Modify with a prompt"
187 | />
188 |
189 | {Object.keys(PROMPT_TYPES).map((type) => (
190 | handlePrompt(type as keyof typeof PROMPT_TYPES)}
192 | className="p-1 px-2 rounded-lg hover:bg-accentGray/10 outline-none text-left text-sm"
193 | key={type}
194 | >
195 | {type}
196 |
197 | ))}
198 |
199 |
200 |
201 | );
202 |
203 | const handleToSetPrompt = () => {
204 | if (initialPrompt) {
205 | setCurrChat("userPrompt", initialPrompt);
206 | setPromptModify(false);
207 | setInitialPrompt(userPrompt);
208 | }
209 | };
210 | const handleTxtToSpeech = () => {
211 | return editor?.getText() as string
212 | }
213 | return (
214 | <>
215 |
216 |
223 |
245 | {promptModify && (
246 |
247 | {" "}
248 | {
250 | setInitialPrompt(userPrompt);
251 | setPromptModify(false);
252 | setCurrChat("userPrompt", null);
253 | }}
254 | rounded="full"
255 | variant="v3"
256 | className="text-accentBlue px-4"
257 | >
258 | Cancel
259 |
260 |
267 | Update
268 |
269 |
270 | )}
271 | {imgName &&
272 |
277 | }
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 | {!dropdown && (
288 |
303 |
304 |
305 | )}
306 |
307 |
308 |
309 |
310 |
311 |
312 |
318 |
319 | {dropdown && createPortal( , document.body)}
320 | >
321 | );
322 | };
323 |
324 | export default ChatProvider;
325 |
--------------------------------------------------------------------------------
/src/components/chat-provider-components/code-block.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { NodeViewContent, NodeViewWrapper } from '@tiptap/react';
4 | import React, { useRef, useState } from 'react';
5 | import { IoCheckmarkDoneSharp } from 'react-icons/io5';
6 | import { MdContentCopy } from 'react-icons/md';
7 |
8 | interface NodeAttrs {
9 | language: string;
10 | // Add other properties of the attrs object here
11 | }
12 |
13 | interface Node {
14 | attrs: NodeAttrs;
15 | // Add other properties of the node object here
16 | }
17 |
18 | const CodeBlock = ({
19 | node: {
20 | attrs: { language: defaultLanguage },
21 | },
22 | updateAttributes,
23 | extension,
24 | }: {
25 | node: Node;
26 | updateAttributes: (attributes: { language: string }) => void;
27 | extension: any; // Add the correct type for the extension prop
28 | }) => {
29 | const codeRef = useRef(null);
30 | const [isCopying, setIsCopying] = useState(false);
31 |
32 | const handleCopy = () => {
33 | if (codeRef.current) {
34 | const codeElement = codeRef.current.querySelector('code');
35 | if (codeElement) {
36 | setIsCopying(true);
37 | navigator.clipboard.writeText(codeElement.textContent || '')
38 | .then(() => {
39 | setTimeout(() => {
40 | setIsCopying(false);
41 | }, 1000);
42 | })
43 | .catch(err => {
44 | console.error('Failed to copy code: ', err);
45 | setIsCopying(false);
46 | });
47 | }
48 | }
49 | };
50 |
51 | // Safely capitalize the first letter of the language
52 | const capitalizedLanguage = defaultLanguage
53 | ? defaultLanguage.charAt(0).toUpperCase() + defaultLanguage.slice(1)
54 | : 'Code';
55 |
56 | return (
57 |
58 |
59 |
60 |
{capitalizedLanguage}
61 |
65 | {isCopying ? (
66 |
67 | ) : (
68 |
69 | )}
70 |
71 |
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | export default CodeBlock;
--------------------------------------------------------------------------------
/src/components/chat-provider-components/gradient-loader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const GradientLoader = () => {
4 | return (
5 |
6 | {[1, 2, 3].map((i) => (
7 |
8 | ))}
9 |
10 | )
11 | }
12 |
13 | export default GradientLoader
--------------------------------------------------------------------------------
/src/components/chat-provider-components/modify-response.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import React from 'react';
3 | import DevPopover from '../dev-components/dev-popover';
4 | import DevButton from '../dev-components/dev-button';
5 | import { LuSlidersHorizontal } from "react-icons/lu";
6 | import ReactTooltip from '../dev-components/react-tooltip';
7 | import { MdOutlineShortText } from 'react-icons/md';
8 | import { BsTextLeft } from 'react-icons/bs';
9 | import { PiSuitcaseSimple, PiUmbrella } from "react-icons/pi";
10 | import { CgPlayListCheck } from 'react-icons/cg';
11 | import geminiZustand from '@/utils/gemini-zustand';
12 |
13 | const PROMPT_TYPES: { [key: string]: string } = {
14 | Longer: "Lengthen",
15 | Shorter: "Shorten",
16 | Simplify: "Simplify",
17 | Elaborate: "Elaborate on",
18 | Formalize: "Make more formal",
19 | };
20 |
21 | const ModifyResponse = ({ chatUniqueId, llmResponse }: { chatUniqueId: string, llmResponse: string }) => {
22 | const { setCurrChat } = geminiZustand();
23 |
24 | const modifyButtons = [
25 | { icon: MdOutlineShortText, label: 'Shorter', promptType: 'Shorter' },
26 | { icon: BsTextLeft, label: 'Longer', promptType: 'Longer' },
27 | { icon: CgPlayListCheck, label: 'Simple', promptType: 'Simplify' },
28 | { icon: PiUmbrella, label: 'Simpler', promptType: 'Simplify' },
29 | { icon: PiSuitcaseSimple, label: 'Professional', promptType: 'Formalize' },
30 | ];
31 |
32 | const handleButtonClick = (promptType: string) => {
33 | setCurrChat("userPrompt", `${PROMPT_TYPES[promptType]} this response: ${llmResponse}`);
34 | };
35 |
36 | return (
37 |
41 |
46 |
47 |
48 |
49 | }
50 | >
51 |
52 |
Generate Response
53 | {modifyButtons.map(({ icon: Icon, label, promptType }) => (
54 |
handleButtonClick(promptType)}
58 | className="text-md group !w-full !justify-start gap-3"
59 | rounded="none"
60 | >
61 | {label}
62 |
63 | ))}
64 |
65 |
66 | );
67 | };
68 |
69 | export default ModifyResponse;
70 |
--------------------------------------------------------------------------------
/src/components/chat-provider-components/msg-loader.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import geminiZustand from "@/utils/gemini-zustand";
3 | import { FormatOutput } from "@/utils/shadow";
4 | import Image from "next/image";
5 | import React, { useEffect } from "react";
6 | import { SiGooglegemini } from "react-icons/si";
7 | import Markdown from "react-markdown";
8 | import root from "react-shadow/styled-components";
9 | import GradientLoader from "./gradient-loader";
10 | import { MdImageSearch, MdOutlineImage } from "react-icons/md";
11 | import { BsImage } from "react-icons/bs";
12 |
13 | const MsgLoader = ({
14 | name,
15 | image,
16 | }: {
17 | name: string;
18 | image: string;
19 | }) => {
20 | const { currChat, msgLoader, inputImgName } = geminiZustand();
21 | return (
22 | msgLoader && (
23 |
24 |
25 |
32 |
37 |
38 | {inputImgName &&
39 |
44 | }
45 |
46 |
47 |
48 |
49 | {!currChat.llmResponse ? (
50 |
51 | ) : (
52 |
53 |
54 | {currChat.llmResponse}
55 |
56 |
57 | )}
58 |
59 |
60 | )
61 | );
62 | };
63 |
64 | export default MsgLoader;
65 |
--------------------------------------------------------------------------------
/src/components/chat-provider-components/optimistic-chat.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { MessageProps } from "@/types/types";
4 | import React, { useEffect, useOptimistic } from "react";
5 | import ChatProvider from "./chat-provider";
6 | import ChatActionsBtns from "./chat-actions-btns";
7 | import geminiZustand from "@/utils/gemini-zustand";
8 |
9 | const OptimisticChat = ({
10 | message,
11 | name,
12 | image,
13 | }: {
14 | message: MessageProps[];
15 | name: string;
16 | image: string;
17 | }) => {
18 | const [optimisticChats, addOptimisticChat] = useOptimistic(
19 | message,
20 | (state, newChat: MessageProps) => [...state, newChat]
21 | );
22 | const { currChat, setPrevChat, setCurrChat, optimisticPrompt, optimisticResponse, inputImgName, setOptimisticResponse } = geminiZustand();
23 |
24 | useEffect(() => {
25 | if (optimisticResponse) {
26 | addOptimisticChat({
27 | _id: Date.now().toString(),
28 | message: {
29 | imgName: inputImgName ?? undefined,
30 | userPrompt: optimisticPrompt ?? "",
31 | llmResponse: optimisticResponse ?? "",
32 | },
33 | });
34 | }
35 | // setCurrChat("userPrompt", null);
36 | if (message && message.length > 0) {
37 | setPrevChat(message[message.length - 1].message)
38 | }
39 | }, [optimisticResponse, message]);
40 |
41 | return (
42 | <>
43 | {optimisticChats.map((chat: MessageProps) => (
44 |
45 |
52 |
53 | ))}
54 | >
55 | );
56 | };
57 |
58 | export default OptimisticChat;
59 |
--------------------------------------------------------------------------------
/src/components/chat-provider-components/share-chat.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import React from 'react'
3 | import {
4 | WhatsappShareButton, TwitterShareButton, LinkedinShareButton,
5 | FacebookShareButton, EmailShareButton, TelegramShareButton, RedditShareButton,
6 | WhatsappIcon, XIcon, LinkedinIcon, FacebookIcon, EmailIcon, TelegramIcon, RedditIcon
7 | } from "react-share";
8 | import DevPopover from '../dev-components/dev-popover'
9 | import ReactTooltip from '../dev-components/react-tooltip'
10 | import DevButton from '../dev-components/dev-button'
11 | import { IoMdShare } from 'react-icons/io'
12 |
13 | const ShareChat = ({ shareMsg }: { shareMsg: string }) => {
14 | const shareConfig = [
15 | {
16 | shareWrapper: WhatsappShareButton,
17 | shareIcon: WhatsappIcon,
18 | },
19 | {
20 | shareWrapper: TwitterShareButton,
21 | shareIcon: XIcon,
22 | },
23 | {
24 | shareWrapper: LinkedinShareButton,
25 | shareIcon: LinkedinIcon,
26 | },
27 | {
28 | shareWrapper: FacebookShareButton,
29 | shareIcon: FacebookIcon,
30 | },
31 | {
32 | shareWrapper: TelegramShareButton,
33 | shareIcon: TelegramIcon,
34 | },
35 | {
36 | shareWrapper: RedditShareButton,
37 | shareIcon: RedditIcon,
38 | },
39 | {
40 | shareWrapper: EmailShareButton,
41 | shareIcon: EmailIcon,
42 | }
43 | ]
44 | return (
45 |
46 |
51 |
52 |
53 | }>
54 |
55 | {shareConfig && shareConfig.map((share, index) => (
56 |
57 |
58 |
59 | ))}
60 |
61 |
62 | )
63 | }
64 |
65 | export default ShareChat
--------------------------------------------------------------------------------
/src/components/chat-provider-components/speech-to-text.tsx:
--------------------------------------------------------------------------------
1 | import geminiZustand from '@/utils/gemini-zustand';
2 | import React, { useState, useEffect } from 'react';
3 | import { IoMdMic } from 'react-icons/io';
4 | import DevButton from '../dev-components/dev-button';
5 | import ReactTooltip from '../dev-components/react-tooltip';
6 |
7 | // Declare the necessary types
8 | interface SpeechRecognitionEvent extends Event {
9 | results: SpeechRecognitionResultList;
10 | }
11 |
12 | interface SpeechRecognitionResultList {
13 | readonly length: number;
14 | item(index: number): SpeechRecognitionResult;
15 | [index: number]: SpeechRecognitionResult;
16 | }
17 |
18 | interface SpeechRecognitionResult {
19 | readonly length: number;
20 | item(index: number): SpeechRecognitionAlternative;
21 | [index: number]: SpeechRecognitionAlternative;
22 | }
23 |
24 | interface SpeechRecognitionAlternative {
25 | transcript: string;
26 | confidence: number;
27 | }
28 |
29 | interface SpeechRecognition extends EventTarget {
30 | continuous: boolean;
31 | interimResults: boolean;
32 | onresult: (event: SpeechRecognitionEvent) => void;
33 | onerror: (event: SpeechRecognitionErrorEvent) => void;
34 | start(): void;
35 | stop(): void;
36 | }
37 |
38 | interface SpeechRecognitionErrorEvent extends Event {
39 | error: string;
40 | }
41 |
42 | interface Window {
43 | webkitSpeechRecognition: new () => SpeechRecognition;
44 | }
45 |
46 |
47 | const SpeechToText: React.FC = () => {
48 | const [isListening, setIsListening] = useState(false);
49 | const [recognition, setRecognition] = useState(null);
50 | const [isMicAvailable, setIsMicAvailable] = useState(false);
51 | const { currChat, setCurrChat, setToast } = geminiZustand()
52 |
53 | useEffect(() => {
54 | if ('webkitSpeechRecognition' in window) {
55 | const recognitionInstance = new (window as any).webkitSpeechRecognition() as SpeechRecognition;
56 | recognitionInstance.continuous = true;
57 | recognitionInstance.interimResults = true;
58 |
59 | recognitionInstance.onresult = (event: SpeechRecognitionEvent) => {
60 | const currentTranscript = Array.from(event.results)
61 | .map(result => result[0].transcript)
62 | .join('');
63 | setCurrChat("userPrompt", currentTranscript);
64 | };
65 |
66 | recognitionInstance.onerror = (event: SpeechRecognitionErrorEvent) => {
67 | if (event.error === 'not-allowed') {
68 | setToast('Microphone access denied');
69 | setIsMicAvailable(false);
70 | } else {
71 | setToast('Unable to access the microphone');
72 | }
73 | setIsListening(false);
74 | };
75 |
76 | setRecognition(recognitionInstance);
77 | setIsMicAvailable(true);
78 | } else {
79 | setToast('Speech recognition is not supported in this browser');
80 | }
81 | }, []);
82 |
83 | const toggleListening = () => {
84 | if (recognition && isMicAvailable) {
85 | if (isListening) {
86 | recognition.stop();
87 | } else {
88 | recognition.start();
89 | }
90 | setIsListening(!isListening);
91 | } else if (!isMicAvailable) {
92 | setToast('Microphone is not available');
93 | }
94 | };
95 |
96 | const isActive = recognition && isMicAvailable && isListening;
97 | return (
98 |
99 |
100 |
114 |
115 |
116 |
117 |
118 | );
119 | };
120 |
121 | export default SpeechToText;
--------------------------------------------------------------------------------
/src/components/chat-provider-components/text-to-speech.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useEffect, useRef } from "react";
3 | import DevButton from "../dev-components/dev-button";
4 | import { BiSolidVolumeFull } from "react-icons/bi";
5 | import { IoPauseSharp } from "react-icons/io5";
6 | import ReactTooltip from "../dev-components/react-tooltip";
7 |
8 | const TextToSpeech = ({
9 | handleTxtToSpeech,
10 | }: {
11 | handleTxtToSpeech: () => string;
12 | }) => {
13 | const [isPlaying, setIsPlaying] = useState(false);
14 | const [isPaused, setIsPaused] = useState(false);
15 | const utteranceRef = useRef(
16 | typeof window !== "undefined" &&
17 | "speechSynthesis" in window &&
18 | new SpeechSynthesisUtterance(handleTxtToSpeech())
19 | );
20 | const synthRef = useRef(
21 | typeof window !== "undefined" &&
22 | "speechSynthesis" in window &&
23 | window.speechSynthesis
24 | );
25 |
26 | useEffect(() => {
27 | utteranceRef.current = new SpeechSynthesisUtterance(handleTxtToSpeech());
28 | utteranceRef.current.onend = () => {
29 | setIsPlaying(false);
30 | setIsPaused(false);
31 | };
32 |
33 | utteranceRef.current.onpause = () => {
34 | setIsPlaying(false);
35 | setIsPaused(true);
36 | };
37 |
38 | utteranceRef.current.onresume = () => {
39 | setIsPlaying(true);
40 | setIsPaused(false);
41 | };
42 |
43 | return () => {
44 | if (synthRef.current) {
45 | synthRef.current.cancel();
46 | }
47 | };
48 | }, [handleTxtToSpeech]);
49 |
50 | const handlePlay = () => {
51 | if (isPaused) {
52 | if (synthRef.current) {
53 | synthRef.current.resume();
54 | }
55 | } else {
56 | if (isPlaying) {
57 | if (synthRef.current) {
58 | synthRef.current.cancel();
59 | }
60 | }
61 | if (synthRef.current) {
62 | if (utteranceRef.current) {
63 | synthRef.current.speak(utteranceRef.current);
64 | }
65 | }
66 | }
67 | setIsPlaying(true);
68 | setIsPaused(false);
69 | };
70 |
71 | const handleStop = () => {
72 | if (synthRef.current) {
73 | synthRef.current.cancel();
74 | }
75 | setIsPlaying(false);
76 | setIsPaused(false);
77 | };
78 | return (
79 |
80 | {isPlaying ? (
81 |
82 |
83 |
84 |
85 |
86 | ) : (
87 |
88 |
89 |
90 |
91 |
92 | )}
93 |
94 | );
95 | };
96 |
97 | export default TextToSpeech;
98 |
--------------------------------------------------------------------------------
/src/components/dev-components/dev-button.tsx:
--------------------------------------------------------------------------------
1 | "use client"; // Require when ripple functionality is needed in NextJS
2 | import React from "react";
3 | import clsx from "clsx";
4 | import useRipple from "use-ripple-hook";
5 | import Link from "next/link";
6 |
7 | type DevButton = {
8 | variant?: "default" | "v2" | "v3" | "v1";//| "border" | "light" | "flat" | "ghost";
9 | size?: "sm" | "md" | "lg" | "xl";
10 | href?: string;
11 | rounded?: "sm" | "md" | "lg" | "full" | "none";
12 | ripple?: boolean;
13 | asIcon?: boolean;
14 | children: React.ReactNode;
15 | className?: string;
16 | } & React.ComponentProps<"button"> &
17 | React.ComponentProps<"a">;
18 |
19 | const DevButton = ({
20 | variant = "default",
21 | size = "md",
22 | href = "",
23 | rounded = "md",
24 | ripple = true,
25 | asIcon,
26 | children,
27 | className,
28 | ...rest
29 | }: DevButton) => {
30 | const commonStyle =
31 | "transition-all flex items-center gap-1 text-nowrap justify-center w-fit h-fit ";
32 |
33 | const buttonVariants = {
34 | default: "bg-transparent",
35 | v3: "bg-transparent hover:bg-accentGray/20",
36 | v2: "active:bg-accentBlue/50",
37 | v1: "bg-accentGray/20",
38 |
39 | // border: "text-accent font-semibold border-2 border-accent", // Changed from 'outline'
40 | // light:
41 | // "hover:bg-accent/30 font-semibold border-2 border-accent",
42 | // flat: "border-accent/5 bg-accent/20 font-semibold backdrop-blur-sm ",
43 | // ghost:
44 | // "hover:bg-accent font-semibold border-2 border-accent",
45 | };
46 |
47 | const buttonSizes = {
48 | sm: asIcon ? "p-[4px] aspect-square" : "p-1 px-2",
49 | md: asIcon ? "p-1 aspect-square" : "p-2 px-3",
50 | lg: asIcon ? "p-2 aspect-square" : "p-3 px-7",
51 | xl: asIcon ? "p-3 aspect-square" : "p-3 px-7",
52 |
53 | };
54 | const buttonRoundness = {
55 | sm: "rounded-sm",
56 | md: "rounded-lg",
57 | lg: "rounded-2xl",
58 | full: "rounded-full",
59 | none: "rounded-none",
60 | };
61 |
62 | const buttonVariant = buttonVariants[variant] || buttonVariants.default;
63 | const buttonSizeClass = buttonSizes[size] || buttonSizes.md;
64 | const buttonRoundnessClass = buttonRoundness[rounded] || buttonRoundness.md;
65 | const [rippleState, event] = useRipple();
66 | const ButtonComponent = href ? Link : 'button';
67 |
68 | return (
69 |
82 | {children}
83 |
84 | );
85 | };
86 |
87 | export default DevButton;
88 |
--------------------------------------------------------------------------------
/src/components/dev-components/dev-drawer.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import React, { useEffect, useState } from 'react'
3 | import { createPortal } from 'react-dom'
4 | import { motion, Variants } from 'framer-motion'
5 |
6 | interface DevDrawerProps {
7 | openBtn: React.ReactNode
8 | open: boolean
9 | setOpen: (open: boolean) => void
10 | children: React.ReactNode
11 | position?: 'left' | 'right' | 'top' | 'bottom'
12 | }
13 |
14 | const getVariants = (position: 'left' | 'right' | 'top' | 'bottom'): {
15 | section: Variants
16 | div: Variants
17 | } => {
18 | const translateProperty = position === 'left' || position === 'right' ? 'translateX' : 'translateY'
19 | const translateValue = position === 'left' || position === 'top' ? '-100%' : '100%'
20 |
21 | return {
22 | section: {
23 | open: { visibility: "visible", opacity: 1 },
24 | closed: {
25 | opacity: 0,
26 | transitionEnd: {
27 | visibility: "hidden",
28 | },
29 | },
30 | },
31 | div: {
32 | open: { [translateProperty]: '0%' },
33 | closed: { [translateProperty]: translateValue },
34 | },
35 | }
36 | }
37 |
38 | const DevDrawer: React.FC = ({
39 | openBtn,
40 | open,
41 | setOpen,
42 | children,
43 | position = 'right',
44 | }) => {
45 | const [mounted, setMounted] = useState(false)
46 | const { section: sectionVariants, div: divVariants } = getVariants(position)
47 |
48 | useEffect(() => {
49 | setMounted(true)
50 | return () => setMounted(false)
51 | }, [])
52 |
53 | if (!mounted) return openBtn
54 | const getPositionClasses = () => {
55 | switch (position) {
56 | case 'left': return `left-0 top-0 bottom-0 w-80 border-r`
57 | case 'right': return `right-0 top-0 bottom-0 w-80 border-l`
58 | case 'top': return `top-0 left-0 right-0 h-80 border-b`
59 | case 'bottom': return `bottom-0 left-0 right-0 h-80 border-t`
60 | }
61 | }
62 |
63 | return (
64 | <>
65 | {openBtn}
66 | {createPortal(
67 | setOpen(false)}
73 | className="fixed inset-0 z-50 bg-black/30"
74 | >
75 | e.stopPropagation()}
80 | className={`absolute ${getPositionClasses()} bg-slate-50 p-3 dark:bg-slate-800 border-cyan-500/50 overflow-auto`}
81 | >
82 |
83 | Drawer
84 |
85 | setOpen(false)}
88 | aria-label="Close drawer"
89 | >
90 | 🗙
91 |
92 | {children}
93 |
94 | ,
95 | document.body
96 | )}
97 | >
98 | )
99 | }
100 |
101 | export default DevDrawer
--------------------------------------------------------------------------------
/src/components/dev-components/dev-emoji-picker.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Emoji from "../../utils/emoji.json";
3 | // Create Emoji.json file and get all emojis json from https://github.com/github/gemoji/blob/master/db/emoji.json
4 | import { FiSearch } from "react-icons/fi";
5 |
6 | type EmojiItem = {
7 | category: string;
8 | emoji: string;
9 | aliases: string[];
10 | };
11 |
12 | const DevEmojiPicker = ({
13 | setSelectedEmoji,
14 | }: {
15 | setSelectedEmoji: (emoji: string) => void;
16 | }) => {
17 | const [uniqueCategories, setUniqueCategories] = useState<
18 | { ctgName: string; emoji: string }[]
19 | >([]);
20 | const [currentCategory, setCurrentCategory] =
21 | useState("Smileys & Emotion");
22 | const [filteredEmojis, setFilteredEmojis] = useState<
23 | { ctgName: string; emoji: string }[]
24 | >([]);
25 | const [hoveredEmoji, setHoveredEmoji] = useState(null);
26 |
27 | const getData = () => {
28 | const categories = new Map();
29 | Emoji.forEach((item: { category: string; emoji: string }) => {
30 | const { category, emoji } = item;
31 | if (!categories.has(category)) {
32 | categories.set(category, { ctgName: category, emoji: emoji });
33 | }
34 | });
35 | setUniqueCategories(Array.from(categories.values()));
36 | };
37 |
38 | // search Functionality from emoji Json
39 | const handleFindEmoji = (e: React.ChangeEvent) => {
40 | const { value } = e.target;
41 | setHoveredEmoji(value);
42 |
43 | if (value.trim() === "") {
44 | setFilteredEmojis([]);
45 | return;
46 | }
47 |
48 | const searchResults = Emoji.filter(
49 | (item: EmojiItem) =>
50 | item.aliases.some((alias) =>
51 | alias.toLowerCase().includes(value.toLowerCase())
52 | ) || item.emoji.includes(value)
53 | ).map((item) => ({
54 | ctgName: item.category,
55 | emoji: item.emoji,
56 | }));
57 | setFilteredEmojis(searchResults);
58 | };
59 |
60 | useEffect(() => {
61 | getData();
62 | }, []);
63 |
64 | return (
65 |
66 |
67 |
74 |
75 |
76 |
77 | {uniqueCategories?.map((elem, index) => (
78 | {
82 | setFilteredEmojis([]);
83 | setCurrentCategory(elem.ctgName);
84 | }}
85 | >
86 | {elem.emoji}
87 |
88 | ))}
89 |
90 |
91 |
92 | {!filteredEmojis.length
93 | ? Emoji.filter((e, i) => e.category === currentCategory).map(
94 | (elem, index) => (
95 | setSelectedEmoji(elem.emoji)}
97 | className="cursor-pointer hover:scale-95 transition-all"
98 | onMouseEnter={() =>
99 | setHoveredEmoji(elem.emoji + elem.aliases[0])
100 | }
101 | onMouseLeave={() => setHoveredEmoji(null)}
102 | key={index}
103 | >
104 | {elem.emoji}
105 |
106 | )
107 | )
108 | : filteredEmojis.map((elem, index) => (
109 | setSelectedEmoji(elem.emoji)}
111 | className="cursor-pointer hover:scale-95 transition-all"
112 | key={index}
113 | >
114 | {elem.emoji}
115 |
116 | ))}
117 |
118 |
119 | );
120 | };
121 |
122 | export default DevEmojiPicker;
123 |
--------------------------------------------------------------------------------
/src/components/dev-components/dev-input.tsx:
--------------------------------------------------------------------------------
1 | //TSX code
2 | import React, { forwardRef } from "react";
3 |
4 | type InputProps = {
5 | variant?: "base" | "bordered" | "faded" | "underline";
6 | size?: "sm" | "md" | "lg";
7 | labelName?: string;
8 | rounded?: "none" | "sm" | "md" | "lg" | "full";
9 | reverseIcon?: boolean;
10 | icon?: React.ReactNode;
11 | className?: string;
12 | } & Omit, "size">;
13 |
14 | const cn = (...classes: (string | undefined | null | false)[]) => {
15 | return classes.filter(Boolean).join(" ");
16 | };
17 |
18 | const DevInput = forwardRef(
19 | (
20 | {
21 | variant = "base",
22 | size = "md",
23 | labelName,
24 | className,
25 | icon,
26 | rounded = "full",
27 | reverseIcon = false,
28 | ...props
29 | },
30 | ref
31 | ) => {
32 | const commonStyle = `w-full flex border border-accentGray/50 transition-all ring-accentBlue/50 items-center ${
33 | icon && " gap-2 "
34 | },
35 | ${reverseIcon && "flex-row-reverse"}`;
36 |
37 | const inputVariants = {
38 | base: "bg-rtlLight dark:bg-rtlDark",
39 | bordered: "bg-transparent",
40 | faded: "bg-accentBlue/20 text-accentBlue",
41 | underline:
42 | "border-0 !ring-0 !border-accentBlue/50 border-b-4 relative after:content-[''] after:absolute after:h-1 after:bg-accentBlue after:-bottom-1 after:w-full after:scale-x-0 after:transition after:duration-300 after:origin-center rounded-none px-0 has-[:focus]:after:scale-x-100",
43 | };
44 |
45 | const inputRoundness = {
46 | none: "rounded-none",
47 | sm: "rounded-md",
48 | md: "rounded-lg",
49 | lg: "rounded-xl",
50 | full: "rounded-full",
51 | };
52 |
53 | const inputSizes: { [key: string]: string } = {
54 | sm: "p-1",
55 | md: "p-2",
56 | lg: "p-3",
57 | };
58 |
59 | const inputSize = inputSizes[size] || inputSizes.md;
60 | const inputVariant = inputVariants[variant] || inputVariants.base;
61 | const inputRound = inputRoundness[rounded] || inputRoundness.full;
62 |
63 | return (
64 |
65 | {labelName && (
66 |
67 | {labelName}
68 |
69 | )}
70 |
71 |
81 | {icon}
82 |
88 |
89 |
90 | );
91 | }
92 | );
93 |
94 | export default DevInput;
95 |
--------------------------------------------------------------------------------
/src/components/dev-components/dev-modal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useEffect, useState } from "react";
3 | import { createPortal } from "react-dom";
4 | import { IoIosCloseCircleOutline } from "react-icons/io";
5 | import { motion } from "framer-motion";
6 |
7 | type devModalProps = {
8 | children: React.ReactNode;
9 | open: boolean;
10 | isOpen: (open: boolean) => void;
11 | openBtn: React.ReactNode;
12 | modalTitle: string;
13 | loader?: boolean;
14 | };
15 | const DevModal = ({
16 | children,
17 | open,
18 | loader =false,
19 | isOpen,
20 | openBtn,
21 | modalTitle,
22 | }: devModalProps) => {
23 | const [mounted, isMounted] = useState(false);
24 | const mainVariants: any = {
25 | open: { visibility: "visible", opacity: 1 },
26 | close: {
27 | opacity: 0,
28 | transitionEnd: {
29 | visibility: "hidden",
30 | },
31 | },
32 | };
33 | const sectionVariants = {
34 | open: { scale: 1 },
35 | close: { scale: 0.95 },
36 | };
37 |
38 | useEffect(() => {
39 | isMounted(true);
40 | }, []);
41 | return (
42 | <>
43 | isOpen(true)}>
44 | {openBtn}
45 |
46 |
47 | {mounted &&
48 | createPortal(
49 | isOpen(false)}
54 | initial={{ visibility: "hidden", opacity: 0 }}
55 | className="bg-black/50 z-[1000] fixed inset-0 h-screen w-screen grid place-content-center"
56 | >
57 | e.stopPropagation()}
62 | className="relative w-[95vw] flex flex-col md:w-[40vw] min-h-40 rounded-3xl shadow-md bg-rtlLight dark:bg-rtlDark overflow-hidden p-5"
63 | >
64 | {modalTitle}
65 | { loader && }
66 |
67 | {children}
68 |
69 |
70 | ,
71 | document.body
72 | )}
73 | >
74 | );
75 | };
76 |
77 | export default DevModal;
78 |
--------------------------------------------------------------------------------
/src/components/dev-components/dev-popover.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useEffect, useState, useRef, useId } from "react";
3 | import { Tooltip as Popover } from "react-tooltip";
4 | import { createPortal } from "react-dom";
5 | import { motion } from "framer-motion"
6 |
7 | type DevPopoverProps = {
8 | children: React.ReactNode;
9 | popButton: React.ReactNode;
10 | contentClick?: boolean;
11 | place?: "left" | "right" | "top" | "bottom" | "bottom-start" | "bottom-end" | "top-start" | "top-end" | "left-start" | "left-end" | "right-start" | "right-end";
12 | };
13 |
14 | const DevPopover = ({
15 | children = "Popover Content",
16 | popButton,
17 | contentClick = true,
18 | place = "bottom",
19 | }: DevPopoverProps) => {
20 | const [mounted, setMounted] = useState(false);
21 | const desktopPopoverRef = useRef(null);
22 | const mobilePopoverRef = useRef(null);
23 | const buttonRef = useRef(null);
24 | const randomId = useId();
25 |
26 | useEffect(() => {
27 | const handleClickOutside = (event: MouseEvent) => {
28 | if (
29 | (desktopPopoverRef.current && !desktopPopoverRef.current.contains(event.target as Node)) &&
30 | (mobilePopoverRef.current && !mobilePopoverRef.current.contains(event.target as Node))&&
31 | (buttonRef.current && !buttonRef.current.contains(event.target as Node))
32 |
33 | ) {
34 | setMounted(false);
35 | }
36 | };
37 | document.addEventListener("click", handleClickOutside, true);
38 | return () => {
39 | document.removeEventListener("click", handleClickOutside, true);
40 | };
41 | }, []);
42 |
43 | return (
44 | <>
45 | {mounted &&
46 | createPortal(
47 |
56 | contentClick && setMounted(!mounted)}
62 | >
63 | {children && children}
64 |
65 | {
72 | if (contentClick) {
73 | setMounted(!mounted);
74 | }
75 | }}
76 | className="shadow-md border-t border-accentGray/30 text-black dark:text-white bg-rtlLight dark:bg-rtlDark min-w-36 p-1 *:!w-full flex-col z-50 block fixed bottom-0 left-0 right-0 md:hidden min-h-4 text-lg"
77 | >
78 | {children && children}
79 |
80 | ,
81 | document.body
82 | )}
83 | setMounted(!mounted)}
86 | data-tooltip-id={randomId}
87 | >
88 | {popButton}
89 |
90 | >
91 | );
92 | };
93 |
94 | export default DevPopover;
--------------------------------------------------------------------------------
/src/components/dev-components/dev-toast.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import geminiZustand from "@/utils/gemini-zustand";
3 | import React, { useEffect } from "react";
4 |
5 | const DevToast = () => {
6 | const { devToast, setToast } = geminiZustand();
7 |
8 | useEffect(() => {
9 | if (devToast) {
10 | setTimeout(() => {
11 | setToast(null);
12 | }, 2000);
13 | }
14 | }, [devToast]);
15 | return (
16 | devToast && (
17 |
18 | {devToast}
19 |
20 | )
21 | );
22 | };
23 |
24 | export default DevToast;
25 |
--------------------------------------------------------------------------------
/src/components/dev-components/react-tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useEffect, useId, useState } from "react";
3 | import { Tooltip } from "react-tooltip";
4 | import {motion} from "framer-motion"
5 | import { createPortal } from "react-dom";
6 |
7 | type reactTooltipProps = {
8 | children: React.ReactNode;
9 | place?:'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' ;
10 | tipData: string;
11 | occupy?: boolean;
12 | };
13 |
14 | const ReactTooltip = ({
15 | children,
16 | place = "top",
17 | tipData,
18 | occupy= true,
19 | }: reactTooltipProps) => {
20 | const Id = useId();
21 | const [mounted, setMounted] = useState(false)
22 | useEffect(() => {
23 | setMounted(true)
24 | }, [])
25 |
26 | return (
27 | <>
28 | {mounted && createPortal(
35 |
39 | {tipData}
40 |
41 | , document.body)}
42 |
43 | {children}
44 |
45 | >
46 | );
47 | };
48 |
49 | export default ReactTooltip;
50 |
--------------------------------------------------------------------------------
/src/components/dev-components/sleek-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import clsx from "clsx";
3 | import React from "react";
4 |
5 | type devToggleProps = {
6 | toggle: string;
7 | size?: "sm" | "md" | "lg";
8 | setTheme: (theme: string) => void;
9 | };
10 |
11 | const SleekToggle = ({ toggle, size = "md", setTheme }: devToggleProps) => {
12 | const sizes = {
13 | sm: {
14 | width: 2,
15 | height: 1,
16 | },
17 | md: {
18 | width: 2.5,
19 | height: 1.3,
20 | },
21 | lg: {
22 | width: 3.5,
23 | height: 1.5,
24 | },
25 | };
26 |
27 | const toggleSize = sizes[size] || sizes["md"];
28 |
29 | return (
30 | <>
31 |
39 |
48 | setTheme(e.target.checked ? "dark" : "light")}
54 | />
55 |
65 |
66 | >
67 | );
68 | };
69 |
70 | export default SleekToggle;
71 |
--------------------------------------------------------------------------------
/src/components/header-components/custom-geminikey.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useEffect, useState } from "react";
3 | import DevPopover from "../dev-components/dev-popover";
4 | import DevButton from "../dev-components/dev-button";
5 | import { SiGooglegemini } from "react-icons/si";
6 | import DevInput from "../dev-components/dev-input";
7 | import { IoMdArrowForward } from "react-icons/io";
8 | import { MdArrowOutward } from "react-icons/md";
9 | import { MdDelete } from "react-icons/md";
10 | import geminiZustand from "@/utils/gemini-zustand";
11 |
12 | const CustomGeminiKey = () => {
13 | const [geminiApi, setGeminiApi] = useState("");
14 | const [hasKey, setHasKey] = useState(false);
15 | const { setGeminiApiKey, setToast } = geminiZustand();
16 |
17 | useEffect(() => {
18 | const storedKey = localStorage.getItem("geminiApiKey");
19 | if (storedKey) {
20 | setGeminiApi(storedKey);
21 | setGeminiApiKey(storedKey);
22 | setHasKey(true);
23 | } else {
24 | setGeminiApiKey(process.env.NEXT_PUBLIC_API_KEY as string);
25 | }
26 | }, []);
27 |
28 | const handleAddGeminiKey = () => {
29 | if (geminiApi) {
30 | setGeminiApiKey(geminiApi);
31 | localStorage.setItem("geminiApiKey", geminiApi);
32 | setToast("API Key added successfully");
33 | setHasKey(true);
34 | }
35 | };
36 |
37 | const handleRemoveGeminiKey = () => {
38 | setGeminiApiKey(process.env.NEXT_PUBLIC_API_KEY as string);
39 | localStorage.removeItem("geminiApiKey");
40 | setToast("API Key removed successfully");
41 | setGeminiApi("");
42 | setHasKey(false);
43 | };
44 |
45 | return (
46 |
50 |
51 | Try Gemini Advanced
52 |
53 | }
54 | >
55 |
56 |
61 | Add Your API Key
62 |
63 |
64 |
67 | e.key === "Enter" && handleAddGeminiKey()}
74 | onChange={(e) => setGeminiApi(e.target.value)}
75 | reverseIcon
76 | icon={
77 |
78 | {hasKey ? (
79 |
80 | ) : (
81 |
82 | )}
83 |
84 | }
85 | />
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | export default CustomGeminiKey;
93 |
--------------------------------------------------------------------------------
/src/components/header-components/gemini-logo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { FaCaretDown, FaRegCheckCircle } from "react-icons/fa";
3 | import { SiGooglegemini } from "react-icons/si";
4 | import DevButton from "../dev-components/dev-button";
5 | import DevPopover from "../dev-components/dev-popover";
6 |
7 | const GeminiLogo = () => {
8 | return (
9 |
12 | Gemini
13 |
14 |
15 | }
16 | >
17 |
18 |
23 |
24 |
25 | Gemini
26 |
27 |
28 |
29 |
34 |
35 |
36 | Gemini Advanced
37 |
38 |
39 |
40 | Upgrade
41 |
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default GeminiLogo
49 |
--------------------------------------------------------------------------------
/src/components/header-components/header.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SignInNow from "@/components/header-components/signin-now";
3 | import { auth } from "@/auth";
4 | import TopLoader from "./top-loader";
5 | import GeminiLogo from "./gemini-logo";
6 | import { IoMdAdd } from "react-icons/io";
7 | import DevButton from "../dev-components/dev-button";
8 |
9 |
10 | const Header = async () => {
11 | const session = await auth();
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default Header;
25 |
--------------------------------------------------------------------------------
/src/components/header-components/portfolio-projects.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Image from "next/image";
3 | import Link from "next/link";
4 |
5 | const PortfolioProjects = () => {
6 | const projects = [
7 | {
8 | name: 'Do paste',
9 | desr: 'Do-Paste allows users to share single pages that are editable by the creator and can be shared with everyone.',
10 | link: 'https://do-paste.vercel.app/',
11 | },
12 | {
13 | name: 'Devvarena',
14 | desr: 'Devvarena is a simple, powerful, and feature-packed frontend editor designed specifically for beginners.',
15 | link: 'https://devvarena.com/',
16 | },
17 |
18 | {
19 | name: 'Dev Gemini Clone',
20 | desr: 'Experience the power of AI with our Gemini-inspired assistant.',
21 | link: 'https://dev-gemini-clone.vercel.app/',
22 | }
23 | ,
24 | {
25 | name: 'CSS Button Generator',
26 | desr: 'Create custom CSS code for stylish and interactive buttons.',
27 | link: 'https://buttons.devvarena.com/',
28 | },
29 | {
30 | name: 'Palette Paradise ',
31 | desr: 'Manage color palettes for web designs with ease.',
32 | link: 'https://palette-paradise.devvarena.com/',
33 |
34 | },
35 | {
36 | name: 'CSS Box Shadows Generator',
37 | desr: 'Generate CSS code for custom box shadows.',
38 | link: 'https://box-shadows.devvarena.com/',
39 |
40 | },
41 | {
42 | name: 'CSS Glassmorphism Generator',
43 | desr: 'Create CSS code for the glassmorphism effect in UI design.',
44 | link: 'https://glassmorphism.devvarena.com/',
45 |
46 | }, {
47 | name: 'Codepen project',
48 | desr: 'All the codepen projects',
49 | link: "https://codepen.io/Devyansh-coder"
50 | }, {
51 | name: 'Dev components',
52 | desr: 'Crafting a Lightweight Website using customizable and functional React Components for Maximum Control',
53 | link: 'https://dev-components.vercel.app/'
54 | }
55 |
56 | ]
57 | return (
58 |
59 |
Other projects
60 |
61 | {
62 | projects.map((item, i) => (
63 |
64 | {item.name}
65 |
66 |
67 | ))
68 | }
69 |
70 |
71 | )
72 | }
73 |
74 | export default PortfolioProjects
--------------------------------------------------------------------------------
/src/components/header-components/signin-now.tsx:
--------------------------------------------------------------------------------
1 | import DevButton from "@/components/dev-components/dev-button";
2 | import Image from "next/image";
3 | import { GoSignOut } from "react-icons/go";
4 | import { IoApps } from "react-icons/io5";
5 | import { signOut, signIn } from '@/auth'
6 | import DevPopover from "../dev-components/dev-popover";
7 | import ReactTooltip from "../dev-components/react-tooltip";
8 | import PortfolioProjects from "./portfolio-projects";
9 | import CustomGeminiKey from "./custom-geminikey";
10 |
11 | export default function SignInNow({ userData }: any) {
12 | const handleSign = async () => {
13 | 'use server'
14 | await signIn('google')
15 | }
16 | const handleSignOut = async () => {
17 | 'use server'
18 | await signOut()
19 | }
20 |
21 | const projects = [
22 | {
23 | name: 'Do paste',
24 | desr: 'Do-Paste allows users to share single pages that are editable by the creator and can be shared with everyone.',
25 | link: 'https://do-paste.vercel.app/',
26 | },
27 | {
28 | name: 'Devvarena',
29 | desr: 'Devvarena is a simple, powerful, and feature-packed frontend editor designed specifically for beginners.',
30 | link: 'https://devvarena.com/',
31 | },
32 |
33 | {
34 | name: 'Dev Gemini Clone',
35 | desr: 'Experience the power of AI with our Gemini-inspired assistant.',
36 | link: 'https://dev-gemini-clone.vercel.app/',
37 | }
38 | ,
39 | {
40 | name: 'CSS Button Generator',
41 | desr: 'Create custom CSS code for stylish and interactive buttons.',
42 | link: 'https://buttons.devvarena.com/',
43 | },
44 | {
45 | name: 'Palette Paradise ',
46 | desr: 'Manage color palettes for web designs with ease.',
47 | link: 'https://palette-paradise.devvarena.com/',
48 |
49 | },
50 | {
51 | name: 'CSS Box Shadows Generator',
52 | desr: 'Generate CSS code for custom box shadows.',
53 | link: 'https://box-shadows.devvarena.com/',
54 |
55 | },
56 | {
57 | name: 'CSS Glassmorphism Generator',
58 | desr: 'Create CSS code for the glassmorphism effect in UI design.',
59 | link: 'https://glassmorphism.devvarena.com/',
60 |
61 | }, {
62 | name: 'Codepen project',
63 | desr: 'All the codepen projects',
64 | link: "https://codepen.io/Devyansh-coder"
65 | }
66 |
67 | ]
68 | return (
69 |
70 |
71 |
76 |
77 |
78 |
79 |
80 |
81 | }
82 | >
83 |
84 |
85 |
86 | {
87 | userData ? (
88 | }>
89 |
95 |
96 | ) : (
97 |
105 |
106 | )
107 | }
108 |
109 |
110 | );
111 | }
--------------------------------------------------------------------------------
/src/components/header-components/top-loader.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import geminiZustand from '@/utils/gemini-zustand'
3 | import React from 'react'
4 |
5 | const TopLoader = () => {
6 | const { topLoader } = geminiZustand()
7 | return (
8 |
11 | )
12 | }
13 |
14 | export default TopLoader
--------------------------------------------------------------------------------
/src/components/input-prompt-components/input-actions.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import DevButton from "../dev-components/dev-button";
5 |
6 | import geminiZustand from "@/utils/gemini-zustand";
7 | import { GoSquareFill } from "react-icons/go";
8 | import SpeechToText from "../chat-provider-components/speech-to-text";
9 | import { RiImageAddFill } from "react-icons/ri";
10 | import { SlCamera } from "react-icons/sl";
11 | import ReactTooltip from "../dev-components/react-tooltip";
12 |
13 | const InputActions = ({
14 | generateMsg,
15 | handleCancel,
16 | handleImageUpload
17 | }: {
18 | generateMsg: () => void;
19 | handleCancel: () => void;
20 | handleImageUpload: (e: React.ChangeEvent) => void
21 | }) => {
22 | const { currChat, msgLoader } = geminiZustand();
23 | return (
24 |
27 | {currChat.userPrompt && msgLoader ? (
28 |
29 |
35 |
36 |
37 |
38 |
39 | )
40 | : (<>
41 |
42 |
43 |
49 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
68 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
95 |
100 |
104 |
105 |
106 |
107 |
108 | >)}
109 |
110 |
111 | );
112 | };
113 |
114 | export default InputActions;
--------------------------------------------------------------------------------
/src/components/input-prompt-components/input-prompt.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useCallback, useEffect, useRef, useState } from "react";
3 | import geminiZustand from "@/utils/gemini-zustand";
4 | import { useParams, useRouter } from "next/navigation";
5 | import { createChat } from "@/actions/actions";
6 | import { nanoid } from "nanoid";
7 | import { useMeasure } from "react-use";
8 | import { GoogleGenerativeAI } from "@google/generative-ai";
9 | import { User } from "next-auth";
10 | import InputActions from "./input-actions";
11 | import Link from "next/link";
12 | import { MdImageSearch } from "react-icons/md";
13 | import { IoMdClose } from "react-icons/io";
14 |
15 | const InputPrompt = ({ user }: { user?: User }) => {
16 | const { currChat, setCurrChat, setToast, customPrompt, setInputImgName, inputImgName, setMsgLoader, prevChat, msgLoader, optimisticResponse, setUserData, setOptimisticResponse, setOptimisticPrompt, geminiApiKey } =
17 | geminiZustand();
18 | const [inputImg, setInputImg] = useState(null)
19 |
20 | const { chat } = useParams();
21 | const router = useRouter();
22 | const [inputRref, { height }] = useMeasure();
23 | const chatID = (chat as string) || nanoid();
24 | const genAI = new GoogleGenerativeAI(geminiApiKey as string);
25 | const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
26 | const cancelRef = useRef(false);
27 |
28 | const generateMsg = useCallback(async () => {
29 | if (!currChat.userPrompt?.trim() || !user) return;
30 | router.push(`/app/${chatID}#new-chat`);
31 | const date = new Date().toISOString().split("T")[0];
32 | // const userName = user?.name?.split(" ")[0] || "User";
33 | let rawPrompt = currChat.userPrompt;
34 | let rawImage = inputImgName;
35 | const detailedPrompt = `
36 | Date: ${date}
37 |
38 | ${customPrompt.prompt ? customPrompt : `User, is seeking a wise and impressive response. Consider including necessary details, context, and thoughtful insights. Aim to provide a comprehensive, well-structured, and articulate answer. Respond in a friendly and natural manner, using terms like "buddy" or other friendly expressions. Provide a complete and final response without asking any further questions.`}
39 |
40 | Previous chats:
41 | User: ${prevChat.userPrompt}
42 | LLM Response: ${prevChat.llmResponse}
43 |
44 | Current User Query:
45 | ${rawPrompt}
46 | `;
47 |
48 | const fileToGenerativePart = (file: File) => {
49 | return new Promise((resolve) => {
50 | const reader = new FileReader();
51 | reader.onloadend = () => resolve({
52 | inlineData: {
53 | data: typeof reader?.result === 'string' ? reader?.result.split(',')[1] : undefined,
54 | mimeType: file.type
55 | },
56 | });
57 | reader.readAsDataURL(file);
58 | });
59 | };
60 |
61 | try {
62 | setMsgLoader(true);
63 | let text = '';
64 | if (!inputImg) {
65 | const result = await model.generateContentStream(detailedPrompt);
66 | for await (const chunk of result.stream) {
67 | const chunkText = chunk.text();
68 | text += chunkText;
69 | setCurrChat("llmResponse", text);
70 | if (cancelRef.current) {
71 | text = "User has aborted the request";
72 | }
73 | }
74 |
75 | }
76 | else {
77 | if (!inputImg) {
78 | setToast('Please upload an image before analyzing.');
79 | return;
80 | }
81 | const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
82 | try {
83 | const imagePart = await fileToGenerativePart(inputImg);
84 | const result = await model.generateContent([detailedPrompt, imagePart as string]);
85 | text = result.response.text();
86 | setCurrChat("llmResponse", text);
87 | if (cancelRef.current) {
88 | text = "User has aborted the request";
89 | }
90 | } catch (error: any) {
91 | console.log(error.message)
92 | setToast(`Error: ${error.message}`);
93 | }
94 | }
95 | if (!text) return;
96 | setOptimisticPrompt(rawPrompt);
97 | setOptimisticResponse(text);
98 | setMsgLoader(false);
99 | setCurrChat("userPrompt", null);
100 |
101 | await createChat({
102 | chatID,
103 | userID: user?.id as string,
104 | imgName: rawImage ?? undefined,
105 | userPrompt: rawPrompt,
106 | llmResponse: text,
107 | });
108 | } catch (error) {
109 | console.error("Error generating message:", error);
110 | } finally {
111 | setMsgLoader(false);
112 | setInputImg(null)
113 | setInputImgName(null)
114 | setCurrChat("userPrompt", null);
115 | setCurrChat("llmResponse", null);
116 | setOptimisticResponse(null);
117 | setOptimisticPrompt(null);
118 |
119 | }
120 | }, [
121 | currChat.userPrompt,
122 | user,
123 | chat,
124 | prevChat,
125 | setCurrChat,
126 | setMsgLoader,
127 | router,
128 | ]);
129 |
130 | const handleTextareaChange = useCallback(
131 | (e: React.ChangeEvent) => {
132 | setCurrChat("userPrompt", e.target.value);
133 | },
134 | [setCurrChat]
135 | );
136 | const handleCancel = useCallback(() => {
137 | cancelRef.current = true;
138 | setOptimisticResponse("User has aborted the request");
139 | setMsgLoader(false);
140 | }, [setOptimisticResponse, setMsgLoader]);
141 |
142 | const handleKeyDown = useCallback(
143 | (e: React.KeyboardEvent) => {
144 | if (!user) { setToast('Please sign in to use Gemini!') }
145 | if (e.key === "Enter" && !e.shiftKey) {
146 | cancelRef.current = false;
147 | generateMsg();
148 | }
149 | },
150 | [generateMsg]
151 | );
152 |
153 | useEffect(() => {
154 | if (user) {
155 | setUserData(user);
156 | }
157 |
158 | }, [user]);
159 |
160 | const handleImageUpload = (event: React.ChangeEvent) => {
161 | if (event.target && event.target.files) {
162 | const file = event.target.files[0];
163 | setInputImg(file);
164 | setInputImgName(file.name);
165 | }
166 | };
167 |
168 | return (
169 |
170 | {inputImgName &&
171 |
172 |
173 |
174 |
{inputImgName}
175 |
{ setInputImgName(null); setInputImg(null) }} className="absolute top-1 right-1 text-2xl rounded-full cursor-pointer hover:opacity-100 hidden group-hover:block opacity-80 bg-accentGray/40 p-1" />
176 |
177 |
178 | }
179 |
182 |
183 |
193 |
194 |
195 |
196 |
Gemini may display inaccurate info, including about people, so double-check its responses. Your privacy & Gemini Apps
197 |
198 | );
199 | };
200 |
201 | export default InputPrompt;
202 |
--------------------------------------------------------------------------------
/src/components/prompt-gallery-components/prompt-cards.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import geminiZustand from '@/utils/gemini-zustand'
3 | import React from 'react'
4 |
5 |
6 | const PromptCards = ({ item }: { item: any }) => {
7 | const {setCustomPrompt} = geminiZustand()
8 | return (
9 | setCustomPrompt({prompt:item.prompt, placeholder:item.placeholder})} className='my-2 rounded-lg cursor-pointer border-2 border-transparent hover:border-accentBlue/50 bg-rtlLight dark:bg-rtlDark p-5 overflow-hidden space-y-2'>
10 |
{item.title}
11 |
{
12 | item.tags.map((e: any, i: number) => (
13 | {e}
14 | ))
15 | }
16 |
17 | )
18 | }
19 |
20 | export default PromptCards
--------------------------------------------------------------------------------
/src/components/sidebar-components/sidebar-chat-list.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useCallback } from "react";
3 | import DevButton from "../dev-components/dev-button";
4 | import clsx from "clsx";
5 | import Link from "next/link";
6 | import { MdDeleteOutline, MdOutlineChatBubbleOutline } from "react-icons/md";
7 | import { HiOutlineDotsVertical, HiOutlinePencil } from "react-icons/hi";
8 | import DevPopover from "../dev-components/dev-popover";
9 | import { BsPin } from "react-icons/bs";
10 | import { useParams, useSearchParams } from "next/navigation";
11 | import DevModal from "../dev-components/dev-modal";
12 | import { deleteChat, pinChat, renameChat } from "@/actions/actions";
13 | import DevEmojiPicker from "../dev-components/dev-emoji-picker";
14 | import { TbMessageChatbot, TbPinned } from "react-icons/tb";
15 | import geminiZustand from "@/utils/gemini-zustand";
16 | import { createPortal } from "react-dom";
17 | import { useRouter } from "next/navigation";
18 | import { IoMdAdd } from "react-icons/io";
19 |
20 | const SidebarChatList = ({ sidebarList }: any) => {
21 | const { chat } = useParams()
22 | const [settings, setSettings] = useState(null);
23 | const [modalOpen, setModalOpen] = useState(false);
24 | const { setTopLoader, setToast } = geminiZustand();
25 | const router = useRouter();
26 | const [chatInfo, setChatInfo] = useState<{
27 | title: string | null;
28 | icon: string | null;
29 | }>({ title: null, icon: null });
30 | const [modalLoader, setModalLoader] = useState(false);
31 | const [modalContent, setModalContent] = useState<{
32 | content: string;
33 | title: string;
34 | chatID: string;
35 | }>({
36 | content: "",
37 | chatID: "",
38 | title: "",
39 | });
40 |
41 | const handleEmojiSelect = useCallback((emoji: string) => {
42 | setChatInfo((prev) => ({ ...prev, icon: emoji }));
43 | }, []);
44 |
45 | const handleDeleteChat = async (chatID: string) => {
46 | setModalLoader(true);
47 | try {
48 | await deleteChat(chatID);
49 | setModalOpen(false);
50 | setToast("Chat deleted successfully");
51 | } catch (error) {
52 | console.error("Failed to delete chat:", error);
53 | } finally {
54 | setModalLoader(false);
55 | }
56 | };
57 |
58 | const handleRenameChat = async (
59 | chatID: string,
60 | chatInfo: { title: string | null; icon: string | null }
61 | ) => {
62 | setModalLoader(true);
63 | try {
64 | await renameChat(chatID, chatInfo);
65 | setModalOpen(false);
66 | setToast("Chat renamed successfully");
67 | } catch (error) {
68 | console.error("Failed to rename chat:", error);
69 | } finally {
70 | setModalLoader(false);
71 | }
72 | };
73 |
74 | const renderModalContent = useCallback(() => {
75 | switch (modalContent.content) {
76 | case "delete":
77 | return (
78 |
79 |
80 | You'll no longer see this chat here. This will also delete related
81 | activity like prompts, responses, and feedback from your Gemini
82 | Apps Activity.
83 |
84 |
85 | Learn more
86 |
87 |
88 | setModalOpen(false)}
92 | className="text-lg gap-2"
93 | >
94 | Cancel
95 |
96 | handleDeleteChat(modalContent.chatID)}
100 | className="text-lg gap-2"
101 | >
102 | Delete
103 |
104 |
105 |
106 | );
107 | case "rename":
108 | return (
109 |
110 |
111 |
121 | {chatInfo.icon || }
122 |
123 | +
124 |
125 |
126 | }
127 | >
128 |
129 |
130 |
135 | setChatInfo({ ...chatInfo, title: e.target.value })
136 | }
137 | type="text"
138 | className="w-full bg-transparent outline-none border-2 border-accentGray/50 rounded-xl px-3 py-1 focus:border-accentBlue"
139 | />
140 |
141 |
142 | setModalOpen(false)}
146 | className="text-lg gap-2"
147 | >
148 | Cancel
149 |
150 | handleRenameChat(modalContent.chatID, chatInfo)}
154 | className="text-lg gap-2"
155 | >
156 | Rename
157 |
158 |
159 |
160 | );
161 | default:
162 | return <>>;
163 | }
164 | }, [modalContent, chatInfo, handleEmojiSelect]);
165 |
166 | return (
167 | <>
168 |
169 | {sidebarList.success && sidebarList.message.length > 0 &&
170 | sidebarList.message.map((item: any) => (
171 |
172 | setSettings(item.chatID)}
176 | className={clsx(
177 | "text-sm overflow-hidden group !w-full justify-between relative !pr-2 gap-4",
178 | item.chatID === chat
179 | && "!bg-accentBlue/50"
180 | )}
181 | >
182 |
186 | {item?.chatInfo?.icon ? (
187 |
188 | {item?.chatInfo?.icon}
189 |
190 | ) : (
191 |
192 | )}
193 |
194 | {item?.chatInfo?.title || item.message.userPrompt}
195 |
196 |
197 | {item?.isPinned && (
198 |
199 | )}
200 |
204 |
210 |
211 | {
212 | chat && item.chatID === chat && createPortal(
213 |
214 | setSettings(item.chatID)}
217 | rounded="full" variant="v3" asIcon className={`${item.chatID === chat ? " !flex " : " !hidden "}`}>
218 |
219 |
220 |
221 |
, document.body
222 | )
223 | }
224 | >
225 | }
226 | >
227 |
228 | {
230 | try {
231 | setTopLoader(true);
232 | await pinChat(item.chatID, !item.isPinned);
233 | if (item.isPinned) {
234 | setToast("Unpinned successfully");
235 | }
236 | else {
237 | setToast("Pinned successfully");
238 | }
239 | } catch (error) {
240 | console.error("Failed to pin/unpin chat:", error);
241 | } finally {
242 | setTopLoader(false);
243 | }
244 | }}
245 | variant="v3"
246 | className="w-full !justify-start gap-3 group"
247 | rounded="none"
248 | >
249 |
250 | {item.isPinned ? "Unpin" : "Pin"}
251 |
252 | {
257 | setChatInfo({
258 | icon: item?.chatInfo?.icon,
259 | title: item?.chatInfo?.title,
260 | });
261 | setModalContent({
262 | content: "rename",
263 | chatID: item.chatID as string,
264 | title: "Rename this chat",
265 | });
266 | setModalOpen(true);
267 | }}
268 | >
269 |
270 | Rename
271 |
272 |
273 | {
275 | setModalContent({
276 | content: "delete",
277 | chatID: item.chatID as string,
278 | title: "Delete Chat?",
279 | });
280 | setModalOpen(true);
281 | }}
282 | variant="v3"
283 | className="w-full !justify-start gap-3 group"
284 | rounded="none"
285 | >
286 |
287 | Delete
288 |
289 |
290 |
291 |
292 |
293 | ))}
294 |
295 | >}
301 | >
302 | {renderModalContent()}
303 |
304 | >
305 | );
306 | };
307 |
308 | export default SidebarChatList;
309 |
--------------------------------------------------------------------------------
/src/components/sidebar-components/sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import DevButton from "../dev-components/dev-button";
4 | import { FiMenu } from "react-icons/fi";
5 | import { IoMdAdd, IoMdHelpCircleOutline } from "react-icons/io";
6 | import { MdOutlineDarkMode } from "react-icons/md";
7 | import { RxCounterClockwiseClock } from "react-icons/rx";
8 | import {
9 | IoExtensionPuzzleOutline,
10 | IoLinkSharp,
11 | IoSettingsOutline,
12 | } from "react-icons/io5";
13 | import ReactTooltip from "../dev-components/react-tooltip";
14 | import { GoDotFill } from "react-icons/go";
15 | import DevPopover from "../dev-components/dev-popover";
16 | import ThemeSwitch from "./theme-switch";
17 | import { useParams, useRouter } from "next/navigation";
18 | import { User } from "next-auth";
19 | import SidebarChatList from "./sidebar-chat-list";
20 | import { createPortal } from "react-dom";
21 | import GeminiLogo from "../header-components/gemini-logo";
22 | import { SiGooglegemini } from "react-icons/si";
23 | import { LuGalleryHorizontalEnd } from "react-icons/lu";
24 | import { FaGithub } from "react-icons/fa";
25 |
26 | const SideBar = ({ user, sidebarList }: { user?: User; sidebarList: any }) => {
27 | const [open, setOpen] = useState(false);
28 | const router = useRouter();
29 | const { chat } = useParams()
30 |
31 | return (
32 |
35 |
36 | {
37 | createPortal(
38 |
39 | setOpen(!open)}
41 | asIcon
42 | size="xl"
43 | rounded="full"
44 | variant="v3"
45 | >
46 |
47 |
48 |
49 |
50 |
51 | {chat &&
56 |
57 | }
58 |
59 | , document.body)
60 | }
61 |
62 | router.push(`/app`)}
64 | rounded="full"
65 | asIcon={open ? false : true}
66 | variant="v1"
67 | className=" mt-5 text-sm gap-3 px-[13px] justify-between md:!flex !hidden"
68 | >
69 | {open && "New chat"}
70 |
71 |
72 | {open &&
{sidebarList.success && sidebarList.message.length > 0 && "Recent"} }
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
85 |
92 |
93 | {open && "Github"}
94 |
95 |
96 |
97 |
98 | {" "}
99 |
100 |
105 |
106 | {open && "Help"}
107 |
108 |
109 |
110 |
111 |
116 |
121 |
122 | {open && "Activity"}
123 |
124 |
125 |
126 |
127 |
128 |
137 |
138 | {open && "Settings"}
139 |
140 | }
141 | >
142 |
143 |
149 |
150 | Prompt gallery
151 |
152 | {/*
157 |
158 | Your public links
159 | */}
160 |
165 |
169 |
170 | Dark theme
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | Try Gemini Advanced
183 |
184 |
195 |
196 |
197 | );
198 | };
199 |
200 | export default SideBar;
201 |
--------------------------------------------------------------------------------
/src/components/sidebar-components/theme-switch.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useTheme } from "next-themes";
3 | import { useEffect, useState } from "react";
4 | import SleekToggle from "../dev-components/sleek-toggle";
5 |
6 | export default function ThemeSwitch() {
7 | const { theme, setTheme } = useTheme();
8 | const [mounted, setMounted] = useState(false);
9 |
10 | useEffect(() => {
11 | setMounted(true);
12 | }, []);
13 |
14 | if (!mounted) {
15 | return;
16 | }
17 |
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/temp-components/demo-cmp.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { NodeViewWrapper } from '@tiptap/react'
3 | import React from 'react'
4 | import root from "react-shadow/styled-components";
5 | import { FormatOutput } from '@/utils/shadow';
6 |
7 |
8 | export default (props:any) => {
9 | return (
10 |
11 | React Component
12 |
13 |
14 | {props.node.attrs.count}
15 |
16 |
17 |
18 |
19 | )
20 | }
--------------------------------------------------------------------------------
/src/components/temp-components/extension.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { mergeAttributes, Node } from '@tiptap/core'
3 | import { ReactNodeViewRenderer } from '@tiptap/react'
4 | import demoComp from './demo-cmp'
5 |
6 |
7 | export default Node.create({
8 | name: 'reactComponent',
9 |
10 | group: 'block',
11 |
12 | atom: true,
13 |
14 | addAttributes() {
15 | return {
16 | count: {
17 | default: 0,
18 | },
19 | }
20 | },
21 |
22 | parseHTML() {
23 | return [
24 | {
25 | tag: 'react-component',
26 | },
27 | ]
28 | },
29 |
30 | renderHTML({ HTMLAttributes }) {
31 | return ['react-component', mergeAttributes(HTMLAttributes)]
32 | },
33 |
34 | addNodeView() {
35 | return ReactNodeViewRenderer(demoComp)
36 | },
37 | })
--------------------------------------------------------------------------------
/src/components/temp-components/home-cards.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import geminiZustand from '@/utils/gemini-zustand'
3 | import React, { useMemo } from 'react'
4 | import { AiOutlineBulb, AiOutlineExperiment } from "react-icons/ai";
5 | import { IoCompassOutline, IoFitnessOutline } from "react-icons/io5";
6 | import { MdOutlineDraw, MdOutlineNoFood, MdOutlineArchitecture } from "react-icons/md";
7 | import { RiImageAddFill, RiMusic2Line } from "react-icons/ri";
8 | import { FaChessKnight, FaTheaterMasks } from "react-icons/fa";
9 | import { GiCook, GiMaterialsScience } from "react-icons/gi";
10 | import { BsBookHalf, BsCodeSlash, BsCameraReels, BsGlobe } from "react-icons/bs";
11 | import { TbMathFunction } from "react-icons/tb";
12 |
13 | const promptArray = [
14 | {
15 | icon: RiImageAddFill,
16 | prompt: "Give me ideas for what to do with what's in this image?",
17 | },
18 | {
19 | icon: AiOutlineBulb,
20 | prompt: "As a social trend expert, explain a term",
21 | },
22 | {
23 | icon: IoCompassOutline,
24 | prompt: "Recommend new types of water sports, including pros & cons",
25 | },
26 | {
27 | icon: MdOutlineDraw,
28 | prompt: "Create vibrant & playful image with lots of details",
29 | },
30 | {
31 | icon: BsCodeSlash,
32 | prompt: "Explain a complex programming concept in simple terms",
33 | },
34 | {
35 | icon: GiCook,
36 | prompt: "Suggest a unique recipe using uncommon ingredients",
37 | },
38 | {
39 | icon: FaChessKnight,
40 | prompt: "Describe an interesting chess strategy for beginners",
41 | },
42 | {
43 | icon: BsBookHalf,
44 | prompt: "Recommend a book based on my favorite movie",
45 | },
46 | {
47 | icon: RiMusic2Line,
48 | prompt: "Create a playlist for a specific mood or activity",
49 | },
50 | {
51 | icon: IoFitnessOutline,
52 | prompt: "Design a 15-minute workout routine for busy professionals",
53 | },
54 | {
55 | icon: MdOutlineNoFood,
56 | prompt: "Explain the cultural significance of a traditional dish",
57 | },
58 | {
59 | icon: BsCameraReels,
60 | prompt: "Pitch an idea for a new sci-fi TV series",
61 | },
62 | {
63 | icon: AiOutlineExperiment,
64 | prompt: "Describe a fun science experiment to do at home",
65 | },
66 | {
67 | icon: FaTheaterMasks,
68 | prompt: "Write a short dramatic monologue for an audition",
69 | },
70 | {
71 | icon: BsGlobe,
72 | prompt: "Suggest an off-the-beaten-path travel destination",
73 | },
74 | {
75 | icon: TbMathFunction,
76 | prompt: "Explain a complex mathematical concept using a real-world analogy",
77 | },
78 | {
79 | icon: GiMaterialsScience,
80 | prompt: "Describe a potential future technology and its implications",
81 | },
82 | {
83 | icon: MdOutlineArchitecture,
84 | prompt: "Design a unique tiny house with innovative features",
85 | },
86 | {
87 | icon: IoCompassOutline,
88 | prompt: "Create a treasure hunt with clues for a birthday party",
89 | },
90 | ];
91 |
92 | const HomeCards = () => {
93 | const { setCurrChat } = geminiZustand()
94 |
95 | const randomPrompts = useMemo(() => {
96 | const shuffled = [...promptArray].sort(() => 0.5 - Math.random());
97 | return shuffled.slice(0, 4);
98 | }, []);
99 |
100 | return (
101 |
102 | {randomPrompts.map((item, index) => (
103 |
setCurrChat('userPrompt', item.prompt)}
106 | className="dark:bg-rtlDark md:aspect-square bg-rtlLight hover:!bg-accentGray/20 cursor-pointer rounded-xl relative p-4 font-light"
107 | >
108 |
{item.prompt}
109 |
110 |
111 | ))}
112 |
113 | )
114 | }
115 |
116 | export default HomeCards
--------------------------------------------------------------------------------
/src/types/types.ts:
--------------------------------------------------------------------------------
1 | export type Message = {
2 | userPrompt: string;
3 | llmResponse:string;
4 | imgName?:string;
5 | }
6 |
7 | export type SessionProps = {
8 | email: string;
9 | id: string;
10 | name: string;
11 | image: string;
12 | }
13 |
14 | export type MessageProps = {
15 | _id: string,
16 | message: Message
17 | }
18 |
19 | export type ChatSectionProps = {
20 | data: {
21 | message?: MessageProps[]
22 | },
23 | image: string,
24 | name: string
25 | }
--------------------------------------------------------------------------------
/src/utils/db.ts:
--------------------------------------------------------------------------------
1 | import mongoose, { ConnectOptions } from "mongoose";
2 |
3 | let isConnected = false;
4 |
5 | const connectDB = async (): Promise => {
6 | mongoose.set("strictQuery", true);
7 |
8 | if (isConnected) {
9 | console.info("MongoDB is already connected");
10 | return;
11 | }
12 |
13 | try {
14 | await mongoose.connect(process.env.MONGODB_URI as string, {
15 | dbName: "gemini-clone",
16 | useNewUrlParser: true,
17 | useUnifiedTopology: true,
18 | } as ConnectOptions);
19 |
20 | isConnected = true;
21 | console.info("MongoDB is now connected");
22 | } catch (error) {
23 | console.error("Failed to connect to MongoDB", error);
24 | throw new Error("MongoDB connection failed");
25 | }
26 | };
27 |
28 | export default connectDB;
29 |
--------------------------------------------------------------------------------
/src/utils/gemini-zustand.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { create } from "zustand";
3 | import { Message } from "../types/types";
4 | import { User } from "next-auth";
5 |
6 | interface GeminiState {
7 | msgLoader: boolean;
8 | setMsgLoader: (msgLoader: boolean) => void;
9 | setPrevChat: (newChat: Message) => void;
10 | prevChat: Message;
11 | topLoader: boolean;
12 | setTopLoader: (topLoader: boolean) => void;
13 | currChat: Message;
14 | setCurrChat: (name: string | null, value: string | null) => void;
15 | userData:User,
16 | setUserData:(userData:User)=>void,
17 | optimisticResponse:string | null,
18 | setOptimisticResponse:(optimisticResponse:string | null)=>void,
19 | setToast:(toast:string|null)=>void,
20 | devToast:string | null,
21 | inputImgName:string | null,
22 | setInputImgName:(inputImgName:string | null)=>void,
23 | optimisticPrompt:string | null,
24 | setOptimisticPrompt:(optimisticPrompt:string | null)=>void
25 | customPrompt:{prompt:string|null, placeholder:string|null},
26 | setCustomPrompt:(value:{prompt:string|null, placeholder:string|null})=>void
27 | geminiApiKey:string | null,
28 | setGeminiApiKey:(geminiApiKey:string | null)=>void
29 | }
30 |
31 | const geminiZustand = create()((set) => ({
32 | msgLoader: false,
33 | devToast:null,
34 | prevChat: { userPrompt: "", llmResponse: "" },
35 | topLoader: false,
36 | setToast:(value:string|null)=>set({devToast:value}),
37 | userData:{},
38 | optimisticResponse:null,
39 | optimisticPrompt:null,
40 | inputImgName:null,
41 | customPrompt:{prompt:null, placeholder:null},
42 | setCustomPrompt:(value:{prompt:string|null, placeholder:string|null})=>set({customPrompt:value}),
43 | setOptimisticPrompt:(value:string|null)=>set({optimisticPrompt:value}),
44 | setInputImgName:(value:string|null)=>set({inputImgName:value}),
45 | currChat: { userPrompt: "", llmResponse: "" },
46 | setTopLoader: (topLoader) => set({ topLoader }),
47 | setMsgLoader: (msgLoader) => set({ msgLoader }),
48 | setOptimisticResponse:(optimisticResponse:string | null)=>set({optimisticResponse}),
49 | setPrevChat: (newChat: Message) => set({ prevChat: newChat }),
50 | geminiApiKey:process.env.NEXT_PUBLIC_API_KEY as string,
51 | setGeminiApiKey:(geminiApiKey:string | null)=>set({geminiApiKey}),
52 | setCurrChat: (name: string | null, value: string | null) =>
53 | set((state) => ({
54 | currChat: { ...state.currChat, [name as string]: value },
55 | })),
56 | setUserData:(userData:User)=>set({userData})
57 | }));
58 |
59 | export default geminiZustand;
60 |
61 |
--------------------------------------------------------------------------------
/src/utils/prev-chat-initializer.tsx:
--------------------------------------------------------------------------------
1 | 'use client' //Not in use now *for zustand in server side
2 |
3 | import geminiZustand from "@/utils/gemini-zustand"
4 |
5 | export default function PrevChatInitializer({ prevChat, children }:{prevChat:any, children:any}) {
6 | geminiZustand.setState({prevChat:prevChat || {userPrompt: "", llmResponse: ""}})
7 | return children
8 | }
--------------------------------------------------------------------------------
/src/utils/prompts-array.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "SEO",
4 | "tags": ["seo", "marketing"],
5 | "placeholder": "Hi, I can help with local SEO strategies.",
6 | "prompt": "Create a local SEO strategy for [insert company name] with [insert number] locations in [insert region/country]. Include GMB optimization, local directories, citation opportunities, localized content, and review management."
7 | },
8 | {
9 | "title": "Note-taking",
10 | "tags": ["note-taking"],
11 | "placeholder": "Hi, I can guide you on note-taking techniques.",
12 | "prompt": "Guide me on effective note-taking techniques for lectures, meetings, and studying. Include methods like Cornell and mind mapping, and offer tips on summarizing, highlighting, and revising notes."
13 | },
14 | {
15 | "title": "Social Media Strategy",
16 | "tags": ["social media", "marketing"],
17 | "placeholder": "Hi, I can help with social media strategies.",
18 | "prompt": "Develop a social media strategy for [company name] to increase engagement and followers across platforms. Include content ideas, posting schedules, and engagement tactics."
19 | },
20 | {
21 | "title": "Content Marketing",
22 | "tags": ["content marketing", "marketing"],
23 | "placeholder": "Hi, I can help with content marketing plans.",
24 | "prompt": "Create a content marketing plan for [company name] focusing on blog posts, videos, and social media. Include topics, frequency, and promotion strategies."
25 | },
26 | {
27 | "title": "Email Marketing",
28 | "tags": ["email marketing", "marketing"],
29 | "placeholder": "Hi, I can help with email marketing campaigns.",
30 | "prompt": "Design an email marketing campaign for [company name] to boost customer retention and sales. Include subject lines, email content, and scheduling."
31 | },
32 | {
33 | "title": "Project Management",
34 | "tags": ["project management", "productivity"],
35 | "placeholder": "Hi, I can assist with project management tips.",
36 | "prompt": "Provide tips for managing a software development project using Agile methodologies. Include sprint planning, task prioritization, and team communication strategies."
37 | },
38 | {
39 | "title": "Time Management",
40 | "tags": ["time management", "productivity"],
41 | "placeholder": "Hi, I can offer time management techniques.",
42 | "prompt": "Share effective time management techniques for balancing work and personal life. Include methods like Pomodoro, time blocking, and prioritization."
43 | },
44 | {
45 | "title": "Remote Work",
46 | "tags": ["remote work", "productivity"],
47 | "placeholder": "Hi, I can offer tips for remote work.",
48 | "prompt": "Offer advice for staying productive while working remotely. Include tips on setting up a home office, maintaining work-life balance, and using remote collaboration tools."
49 | },
50 | {
51 | "title": "Web Development",
52 | "tags": ["web development", "coding"],
53 | "placeholder": "Hi, I can guide you on web development.",
54 | "prompt": "Provide best practices for developing a responsive website using HTML, CSS, and JavaScript. Include tips on layout, accessibility, and performance optimization."
55 | },
56 | {
57 | "title": "UX Design",
58 | "tags": ["ux design", "design"],
59 | "placeholder": "Hi, I can help with UX design strategies.",
60 | "prompt": "Create a UX design strategy for a mobile app. Include user research methods, wireframing techniques, and usability testing."
61 | },
62 | {
63 | "title": "Career Development",
64 | "tags": ["career development", "self-improvement"],
65 | "placeholder": "Hi, I can offer career development advice.",
66 | "prompt": "Offer advice on advancing in a tech career. Include tips on skill development, networking, and finding mentorship opportunities."
67 | },
68 | {
69 | "title": "Public Speaking",
70 | "tags": ["public speaking", "communication"],
71 | "placeholder": "Hi, I can help with public speaking tips.",
72 | "prompt": "Provide tips for improving public speaking skills. Include techniques for managing anxiety, structuring a speech, and engaging an audience."
73 | },
74 | {
75 | "title": "Entrepreneurship",
76 | "tags": ["entrepreneurship", "business"],
77 | "placeholder": "Hi, I can offer advice on entrepreneurship.",
78 | "prompt": "Give advice on starting a new business. Include tips on market research, creating a business plan, and securing funding."
79 | },
80 | {
81 | "title": "Digital Marketing",
82 | "tags": ["digital marketing", "marketing"],
83 | "placeholder": "Hi, I can help with digital marketing strategies.",
84 | "prompt": "Develop a digital marketing strategy for [company name]. Include SEO, social media, content marketing, and paid advertising tactics."
85 | },
86 | {
87 | "title": "Health and Wellness",
88 | "tags": ["health", "wellness"],
89 | "placeholder": "Hi, I can offer health and wellness tips.",
90 | "prompt": "Share tips for maintaining a healthy lifestyle. Include advice on nutrition, exercise, and mental well-being."
91 | },
92 | {
93 | "title": "Financial Planning",
94 | "tags": ["financial planning", "personal finance"],
95 | "placeholder": "Hi, I can help with financial planning.",
96 | "prompt": "Provide strategies for personal financial planning. Include tips on budgeting, saving, investing, and managing debt."
97 | },
98 | {
99 | "title": "Software Development",
100 | "tags": ["software development", "coding"],
101 | "placeholder": "Hi, I can guide you on software development.",
102 | "prompt": "Offer best practices for developing scalable software applications. Include tips on code quality, version control, and testing."
103 | },
104 | {
105 | "title": "Machine Learning",
106 | "tags": ["machine learning", "ai"],
107 | "placeholder": "Hi, I can explain machine learning basics.",
108 | "prompt": "Explain the basics of machine learning. Include an overview of algorithms, model training, and real-world applications."
109 | },
110 | {
111 | "title": "Travel Planning",
112 | "tags": ["travel", "planning"],
113 | "placeholder": "Hi, I can help with travel planning.",
114 | "prompt": "Provide tips for planning an international trip. Include advice on finding flights, booking accommodations, and creating an itinerary."
115 | },
116 | {
117 | "title": "Event Planning",
118 | "tags": ["event planning", "organization"],
119 | "placeholder": "Hi, I can guide you on event planning.",
120 | "prompt": "Share strategies for planning a successful event. Include tips on budgeting, venue selection, and marketing the event."
121 | },
122 | {
123 | "title": "Blog Writing",
124 | "tags": ["blogging", "writing"],
125 | "placeholder": "Hi, I can help with blog writing tips.",
126 | "prompt": "Give tips for writing engaging blog posts. Include advice on topic selection, structuring content, and SEO optimization."
127 | },
128 | {
129 | "title": "Graphic Design",
130 | "tags": ["graphic design", "design"],
131 | "placeholder": "Hi, I can offer graphic design tips.",
132 | "prompt": "Provide best practices for creating visually appealing graphics. Include tips on color theory, typography, and layout design."
133 | },
134 | {
135 | "title": "Personal Branding",
136 | "tags": ["personal branding", "self-improvement"],
137 | "placeholder": "Hi, I can help with personal branding.",
138 | "prompt": "Offer advice on building a strong personal brand. Include tips on social media presence, networking, and creating a personal website."
139 | },
140 | {
141 | "title": "Investment Strategies",
142 | "tags": ["investment", "finance"],
143 | "placeholder": "Hi, I can share investment strategies.",
144 | "prompt": "Share strategies for long-term investing. Include tips on diversification, risk management, and selecting investment vehicles."
145 | },
146 | {
147 | "title": "Customer Service",
148 | "tags": ["customer service", "business"],
149 | "placeholder": "Hi, I can help with customer service tips.",
150 | "prompt": "Provide tips for delivering excellent customer service. Include advice on communication skills, handling complaints, and building customer loyalty."
151 | },
152 | {
153 | "title": "Data Analysis",
154 | "tags": ["data analysis", "analytics"],
155 | "placeholder": "Hi, I can guide you on data analysis.",
156 | "prompt": "Explain the basics of data analysis. Include an overview of data collection methods, tools for analysis, and interpreting results."
157 | },
158 | {
159 | "title": "Cybersecurity",
160 | "tags": ["cybersecurity", "technology"],
161 | "placeholder": "Hi, I can help with cybersecurity practices.",
162 | "prompt": "Give an overview of essential cybersecurity practices. Include tips on protecting personal data, securing networks, and responding to breaches."
163 | },
164 | {
165 | "title": "Team Leadership",
166 | "tags": ["leadership", "management"],
167 | "placeholder": "Hi, I can offer team leadership tips.",
168 | "prompt": "Share strategies for effective team leadership. Include advice on communication, conflict resolution, and motivating team members."
169 | },
170 | {
171 | "title": "E-commerce",
172 | "tags": ["e-commerce", "business"],
173 | "placeholder": "Hi, I can help with e-commerce strategies.",
174 | "prompt": "Develop a strategy for launching an e-commerce store. Include tips on selecting a platform, product listing, and marketing."
175 | },
176 | {
177 | "title": "Digital Transformation",
178 | "tags": ["digital transformation", "business"],
179 | "placeholder": "Hi, I can explain digital transformation.",
180 | "prompt": "Explain how businesses can undergo digital transformation. Include steps for adopting new technologies and changing business processes."
181 | },
182 | {
183 | "title": "Market Research",
184 | "tags": ["market research", "business"],
185 | "placeholder": "Hi, I can help with market research.",
186 | "prompt": "Provide steps for conducting market research. Include methods for data collection, analyzing trends, and identifying target audiences."
187 | },
188 | {
189 | "title": "Sales Strategies",
190 | "tags": ["sales", "business"],
191 | "placeholder": "Hi, I can offer sales strategy tips.",
192 | "prompt": "Share effective sales strategies for B2B companies. Include tips on prospecting, pitching, and closing deals."
193 | },
194 | {
195 | "title": "Negotiation Skills",
196 | "tags": ["negotiation", "business"],
197 | "placeholder": "Hi, I can guide you on negotiation skills.",
198 | "prompt": "Offer tips for improving negotiation skills. Include techniques for preparing, building rapport, and finding win-win solutions."
199 | },
200 | {
201 | "title": "Personal Finance",
202 | "tags": ["personal finance", "money management"],
203 | "placeholder": "Hi, I can help with personal finance management.",
204 | "prompt": "Share tips for managing personal finances. Include advice on budgeting, saving, and planning for retirement."
205 | },
206 | {
207 | "title": "Fitness Routine",
208 | "tags": ["fitness", "health"],
209 | "placeholder": "Hi, I can guide you on fitness routines.",
210 | "prompt": "Provide tips for creating an effective fitness routine. Include advice on workout types, frequency, and goal setting."
211 | },
212 | {
213 | "title": "Creative Writing",
214 | "tags": ["creative writing", "writing"],
215 | "placeholder": "Hi, I can help with creative writing tips.",
216 | "prompt": "Share tips for improving creative writing skills. Include techniques for developing characters, plotting stories, and overcoming writer's block."
217 | },
218 | {
219 | "title": "Language Learning",
220 | "tags": ["language learning", "education"],
221 | "placeholder": "Hi, I can offer language learning strategies.",
222 | "prompt": "Provide strategies for learning a new language. Include tips on practice routines, using language apps, and immersing in the culture."
223 | },
224 | {
225 | "title": "Photography",
226 | "tags": ["photography", "art"],
227 | "placeholder": "Hi, I can help with photography tips.",
228 | "prompt": "Share tips for improving photography skills. Include advice on camera settings, composition, and post-processing techniques."
229 | },
230 | {
231 | "title": "Mindfulness",
232 | "tags": ["mindfulness", "well-being"],
233 | "placeholder": "Hi, I can guide you on mindfulness practices.",
234 | "prompt": "Provide tips for incorporating mindfulness into daily life. Include advice on meditation techniques, breathing exercises, and mindful living."
235 | }
236 | ]
237 |
--------------------------------------------------------------------------------
/src/utils/shadow.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const FormatOutput = styled.div`
4 | .ProseMirror {
5 | outline: none;
6 | caret-color: transparent;
7 |
8 | .codeHeader button {
9 | background: transparent;
10 | border: none;
11 | cursor: pointer;
12 | border-radius: 6px;
13 | }
14 |
15 | .codeHeader {
16 | border-bottom: 2px solid #94a3b873;
17 | display: flex;
18 | justify-content: space-between;
19 | }
20 |
21 | .codeHeader button {
22 | font-size: 1.5rem;
23 | position: sticky;
24 | top: 0;
25 | z-index: 10;
26 | }
27 |
28 | .codeHeader .code-title {
29 | font-size: 17px;
30 | font-family: Verdana, Geneva, Tahoma, sans-serif;
31 | pointer-events: none;
32 | text-select: none;
33 | opacity: 0.8;
34 | }
35 |
36 |
37 |
38 | .hljs-comment,
39 | .hljs-quote {
40 | color: #616161;
41 | }
42 |
43 | .hljs-variable,
44 | .hljs-template-variable,
45 | .hljs-attribute,
46 | .hljs-tag,
47 | .hljs-name,
48 | .hljs-regexp,
49 | .hljs-link,
50 | .hljs-name,
51 | .hljs-selector-id,
52 | .hljs-selector-class {
53 | color: #f98181;
54 | }
55 |
56 | .hljs-number,
57 | .hljs-meta,
58 | .hljs-built_in,
59 | .hljs-builtin-name,
60 | .hljs-literal,
61 | .hljs-type,
62 | .hljs-params {
63 | color: #fbbc88;
64 | }
65 |
66 | .hljs-string,
67 | .hljs-symbol,
68 | .hljs-bullet {
69 | color: #b9f18d;
70 | }
71 |
72 | .hljs-title,
73 | .hljs-section {
74 | color: #faf594;
75 | }
76 |
77 | .hljs-keyword,
78 | .hljs-selector-tag {
79 | color: #70cff8;
80 | }
81 |
82 | .hljs-emphasis {
83 | font-style: italic;
84 | }
85 |
86 | .hljs-strong {
87 | font-weight: 700;
88 | }
89 |
90 | }
91 |
92 | pre {
93 | background: #94a3b81f;
94 | position: relative;
95 | font-family: "JetBrainsMono", monospace;
96 | border-radius: 1rem;
97 | padding: 3px 1rem;
98 | overflow: auto;
99 | max-width: 100%;
100 | }
101 |
102 | pre code {
103 | display: inline-block;
104 | min-width: 100%;
105 | color: inherit;
106 | background: none !important;
107 | font-size: 0.9rem;
108 | white-space: pre;
109 | word-wrap: normal;
110 | word-break: keep-all;
111 | }
112 |
113 | /* Styling for webkit browsers (Chrome, Safari, etc.) */
114 | pre::-webkit-scrollbar {
115 | height: 8px;
116 | width: 8px;
117 | }
118 |
119 | pre::-webkit-scrollbar-thumb {
120 | background-color: #888;
121 | border-radius: 4px;
122 | }
123 |
124 | pre::-webkit-scrollbar-track {
125 | background-color: #f1f1f1;
126 | border-radius: 4px;
127 | }
128 |
129 | /* Styling for Firefox */
130 | pre {
131 | scrollbar-width: thin;
132 | scrollbar-color: #888 #f1f1f1;
133 | }
134 | code {
135 | color: inherit;
136 | background: #94a3b81f !important;
137 | padding:2px 5px;
138 | font-size: 0.9rem;
139 | border-radius: 6px;
140 | }
141 | `;
142 |
--------------------------------------------------------------------------------
/src/utils/theme-providers.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from "next-themes";
2 |
3 | export function ThemeProviders({ children }: { children: React.ReactNode }) {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | darkMode: "class",
10 | theme: {
11 | extend: {
12 | colors: {
13 | rtlDark: '#1E1F20',
14 | rtlLight:'#F0F4F9',
15 | accentGray:'#94a3b8',
16 | accentBlue:"#3b82f6"
17 | }
18 | },
19 | },
20 | plugins: [],
21 | };
22 | export default config;
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/components/dev-components/dev-drawertsx"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------