├── .env.example ├── .eslintrc.json ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── (routes) │ ├── c │ │ └── [slug] │ │ │ └── page.tsx │ ├── chat │ │ └── page.tsx │ ├── gateway │ │ └── page.tsx │ ├── memories │ │ └── page.tsx │ ├── page.tsx │ └── spaces │ │ └── page.tsx ├── api │ ├── add │ │ └── route.ts │ ├── history │ │ └── route.ts │ ├── oauth │ │ └── route.ts │ └── stream │ │ └── reply │ │ └── route.ts ├── favicon.ico ├── globals.css └── layout.tsx ├── bun.lockb ├── components.json ├── components ├── custom │ ├── animated-bg.tsx │ ├── annc.tsx │ ├── chat │ │ ├── chat-comp.tsx │ │ ├── message.tsx │ │ └── msgs-wrapper.tsx │ ├── cover.tsx │ ├── credits-comp.tsx │ ├── dot-patern.tsx │ ├── features-sec.tsx │ ├── file-upload-comp.tsx │ ├── hero │ │ ├── credits.tsx │ │ ├── features.tsx │ │ └── orbit.tsx │ ├── loading-screen.tsx │ ├── memories-card.tsx │ ├── memories-panel.tsx │ ├── orbiting-circles.tsx │ ├── redirecting-screen.tsx │ ├── res-type.tsx │ ├── shiny-button.tsx │ ├── shiny-text.tsx │ ├── sidebar │ │ ├── add-res-comp.tsx │ │ ├── history-viewer.tsx │ │ ├── mob-sidebar.tsx │ │ ├── search-comp.tsx │ │ └── spaces-comp.tsx │ ├── sign-btn.tsx │ ├── sparkle-text.tsx │ ├── sparkles.tsx │ ├── text-gen.tsx │ ├── theme.provider.tsx │ └── user-btn.tsx └── ui │ ├── alert-dialog.tsx │ ├── animated-beam.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ ├── command.tsx │ ├── context-menu.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── file-upload.tsx │ ├── input.tsx │ ├── label.tsx │ ├── marquee.tsx │ ├── popover.tsx │ ├── scroll-area.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── skeleton.tsx │ ├── sonner.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── toolbar-panel.tsx │ ├── tooltip.tsx │ ├── transition-panel.tsx │ ├── typing-animation.tsx │ └── use-toast.ts ├── context ├── auth.context.tsx └── routes.context.tsx ├── db ├── defaults.tsx ├── firebase │ └── config.ts ├── func.ts └── supabase │ ├── client.tsx │ ├── middleware.ts │ └── server.tsx ├── lib ├── func.ts ├── local-storage.ts ├── rag.ts ├── redis.ts └── utils.ts ├── middleware.ts ├── next.config.mjs ├── package.json ├── postcss.config.mjs ├── public ├── assets │ ├── github-dark.svg │ ├── github.svg │ ├── medium-dark.svg │ ├── medium.svg │ ├── notion-dark.svg │ ├── notion.svg │ ├── reddit-dark.svg │ ├── reddit.svg │ ├── wikipedia.svg │ ├── x-dark.svg │ └── x.svg └── brands │ ├── acternity.png │ ├── nextjs.svg │ ├── supabase.svg │ ├── upstash.svg │ └── vercel.svg ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | 2 | # SUPABASE ENVS 3 | NEXT_PUBLIC_SUPABASE_URL= [YOUR-OWN-CREDENTIALS] 4 | NEXT_PUBLIC_SUPABASE_ANON_KEY= [YOUR-OWN-CREDENTIALS] 5 | 6 | # UPSTASH ENVS 7 | OPENAI_API_KEY= [YOUR-OWN-CREDENTIALS] 8 | UPSTASH_REDIS_REST_URL= [YOUR-OWN-CREDENTIALS] 9 | UPSTASH_REDIS_REST_TOKEN= [YOUR-OWN-CREDENTIALS] 10 | QSTASH_TOKEN= [YOUR-OWN-CREDENTIALS] 11 | UPSTASH_VECTOR_REST_URL= [YOUR-OWN-CREDENTIALS] 12 | UPSTASH_VECTOR_REST_TOKEN= [YOUR-OWN-CREDENTIALS] -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [SkidGod4444] 4 | 5 | -------------------------------------------------------------------------------- /.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 | .env 31 | .dev*.vars 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Saidev Dhal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Introducing x0-GPT 3 | 4 | ![App Screenshot](https://i.imgur.com/ffeIgzW.png) 5 | 6 | 7 | # What is x0-GPT ? 8 | 9 | x0-GPT is an advanced AI-powered tool that enables you to interact seamlessly with any website or document (including PDFs) using natural language. Whether you're looking to extract specific data, automate tasks, or gain insights, x0-GPT makes it possible with ease. Best of all, it's free and accessible to everyone. 10 | 11 | 12 | 13 | ## Features 14 | 15 | - User friendly interface 16 | - Realtime updates 17 | - Dynamic routing 18 | - Supports almost all ai 19 | - Chat with websites 20 | - Chat with PDFs 21 | - Chat with CSVs 22 | - Chat with notes 23 | 24 | 25 | ## Tech Stack 26 | 27 | This project is by the following tech stack: 28 | 29 | - Nextjs 14 30 | - Typescript 31 | - Tailwind CSS 32 | - Supabase 33 | - Upstash 34 | - Acternity UI 35 | - Shadcn UI 36 | 37 | 38 | ## Environment Variables 39 | 40 | To run this project, you will need to rename .env.example file to .env.local and add your keys. 41 | 42 | #### SUPABASE ENVS 43 | `NEXT_PUBLIC_SUPABASE_URL= "YOUR-KEYS"` 44 | `NEXT_PUBLIC_SUPABASE_ANON_KEY= "YOUR-KEYS"` 45 | 46 | #### UPSTASH ENVS 47 | `UPSTASH_REDIS_REST_URL= "YOUR-KEYS"` 48 | `UPSTASH_REDIS_REST_TOKEN= "YOUR-KEYS"` 49 | `QSTASH_TOKEN= "YOUR-KEYS"` 50 | `UPSTASH_VECTOR_REST_URL= "YOUR-KEYS"` 51 | `UPSTASH_VECTOR_REST_TOKEN= "YOUR-KEYS"` 52 | 53 | #### TOGETHER AI (optional) 54 | 55 | `OPENAI_API_KEY= "YOUR-KEYS"` 56 | 57 | 58 | ## Installation 59 | 60 | Clone the github repo 61 | ```bash 62 | git clone https://github.com/SkidGod4444/x0-GPT 63 | cd x0-GPT 64 | ``` 65 | Install dependencies with bun. 66 | 67 | ```bash 68 | bun add 69 | bun run dev 70 | ``` 71 | 72 | ## Production 73 | 74 | https://x0-gpt.devwtf.in/ 75 | 76 | 77 | ## Contributing 78 | 79 | Contributions are always welcome! 80 | 81 | Do whatever you feel can improve this app. 82 | 83 | 84 | ## Feedback 85 | 86 | If you have any feedback, please reach out to us at connect.saidev@gmail.com 87 | 88 | -------------------------------------------------------------------------------- /app/(routes)/c/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import ErrorPage from "next/error"; 5 | import { ChatComp } from "@/components/custom/chat/chat-comp"; 6 | import { getChatBySlug } from "@/db/func"; 7 | import { Message } from "ai/react"; 8 | import LoadingScreen from "@/components/custom/loading-screen"; 9 | import { createClient } from "@/db/supabase/client"; 10 | import { useRouter } from "next/navigation"; 11 | 12 | interface PageProps { 13 | params: { 14 | slug: string; 15 | }; 16 | } 17 | 18 | export default function ChatsPage({ params }: PageProps) { 19 | const [ok, setOk] = useState(false); 20 | const [loading, setLoading] = useState(true); // Add loading state 21 | const [history, setHistory] = useState([]); 22 | const chatID = params.slug.replace(/^\//, ""); 23 | const router = useRouter(); 24 | 25 | useEffect(() => { 26 | async function handleSlug() { 27 | setLoading(true); // Set loading to true at the start 28 | const isChat = await getChatBySlug(chatID); 29 | if (isChat) { 30 | try { 31 | const response = await fetch("/api/history", { 32 | method: "POST", 33 | headers: { 34 | "Content-Type": "application/json", 35 | }, 36 | body: JSON.stringify({ chatID }), 37 | }); 38 | 39 | if (response.ok) { 40 | const chatHistory = await response.json(); 41 | setHistory(chatHistory); 42 | setOk(true); 43 | } else { 44 | console.error("Failed to fetch chat history"); 45 | setOk(false); 46 | } 47 | } catch (error) { 48 | console.error("Error fetching chat history:", error); 49 | setOk(false); 50 | } 51 | } else { 52 | setOk(false); 53 | } 54 | setLoading(false); 55 | } 56 | 57 | handleSlug(); 58 | }, [params.slug, chatID]); 59 | 60 | useEffect(() => { 61 | const supabase = createClient(); 62 | const subscription = supabase 63 | .channel("chats-channel") 64 | .on( 65 | "postgres_changes", 66 | { 67 | event: "DELETE", 68 | schema: "public", 69 | table: "CHATS", 70 | filter: `slug=eq.${chatID}`, 71 | }, 72 | (payload) => { 73 | if (payload.eventType === "DELETE" && chatID) { 74 | router.replace("/chat"); 75 | } 76 | }, 77 | ) 78 | .subscribe(); 79 | 80 | return () => { 81 | supabase.removeChannel(subscription); 82 | }; 83 | }, [router, chatID]); 84 | 85 | if (loading) { 86 | return ; 87 | } 88 | 89 | if (!ok) { 90 | return ; 91 | } 92 | 93 | return ( 94 |
95 | 96 |
97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /app/(routes)/chat/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { ChatComp } from "@/components/custom/chat/chat-comp"; 5 | import { useAuth } from "@/context/auth.context"; 6 | import { getUserCurrenChat } from "@/db/func"; 7 | import LoadingScreen from "@/components/custom/loading-screen"; 8 | 9 | export default function Chat() { 10 | const { user } = useAuth(); 11 | const uid = user?.id!; 12 | const [chatID, setChatID] = useState(null); 13 | const [isMsg, setMsg] = useState(false); 14 | 15 | useEffect(() => { 16 | const fetchChatID = async () => { 17 | const id = await getUserCurrenChat(uid); 18 | if (!chatID) { 19 | setChatID(id); 20 | } 21 | }; 22 | 23 | if (user) { 24 | fetchChatID(); 25 | } 26 | }, [user, uid, chatID]); 27 | 28 | useEffect(() => { 29 | if (chatID && isMsg) { 30 | window.history.replaceState(null, "", `/c/${chatID}`); 31 | } 32 | }, [chatID, isMsg]); 33 | 34 | // Conditional rendering based on chatID 35 | if (!chatID) { 36 | return ; 37 | } 38 | 39 | return ( 40 |
41 | 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /app/(routes)/gateway/page.tsx: -------------------------------------------------------------------------------- 1 | import SignInBtn from "@/components/custom/sign-btn"; 2 | import SparklesText from "@/components/custom/sparkle-text"; 3 | import Link from "next/link"; 4 | import React from "react"; 5 | 6 | export default function Login() { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | {/* Background grid pattern */} 14 |
15 | 16 | {/* Content */} 17 |
18 |

19 | x0-GPT 20 |

21 | 22 |

23 | Hey, Human! 24 |

25 | 26 |

27 | Welcome! Give x0-GPT a try, and experience reliable, AI-powered 28 | GPT on your own data. 29 |

30 | 31 |
32 | 33 |
34 | 35 |
36 | By continuing, you agree to the{" "} 37 | 38 | Terms of Service 39 | {" "} 40 | and{" "} 41 | 42 | Privacy Policy 43 | 44 | . 45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 | 53 | Talk to any website using{" "} 54 | 55 | 56 | just for $0. 57 |
58 |
59 | 60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /app/(routes)/memories/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import MemoriesPanel from "@/components/custom/memories-panel"; 4 | import { Button } from "@/components/ui/button"; 5 | import { IconArrowBackUp } from "@tabler/icons-react"; 6 | import { useRouter } from "next/navigation"; 7 | import React from "react"; 8 | 9 | export default function Memories() { 10 | const router = useRouter(); 11 | return ( 12 |
13 |
14 | 22 |

Your Memories

23 |
24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/(routes)/page.tsx: -------------------------------------------------------------------------------- 1 | import { AnncBtn } from "@/components/custom/annc"; 2 | import { Cover } from "@/components/custom/cover"; 3 | import DotPattern from "@/components/custom/dot-patern"; 4 | import Credits from "@/components/custom/hero/credits"; 5 | import Features from "@/components/custom/hero/features"; 6 | import Orbits from "@/components/custom/hero/orbit"; 7 | import { TextEffect } from "@/components/custom/text-gen"; 8 | import { Button } from "@/components/ui/button"; 9 | import { cn } from "@/lib/utils"; 10 | import { ChevronRight } from "lucide-react"; 11 | import Link from "next/link"; 12 | import React from "react"; 13 | 14 | export default function Home() { 15 | return ( 16 | <> 17 |
18 |
19 |
20 |
21 | 22 | {/* Title */} 23 |

24 | Talk to whatever you want using x0-GPT for free! 25 |

26 | 32 | Seamlessly interact with websites, PDFs, and various other content 33 | directly. Whether you're seeking answers, exploring content, or 34 | just curious, x0-GPT brings conversations to your fingertips, making 35 | information more accessible and engaging than ever before. 36 | 37 |
38 | 39 | 42 | 43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 | 60 | 61 | 62 |
63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /app/(routes)/spaces/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Spaces() { 4 | return
Lol
; 5 | } 6 | -------------------------------------------------------------------------------- /app/api/add/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { 3 | AddCSVContext, 4 | AddPDFContext, 5 | AddTXTContext, 6 | AddWEBContext, 7 | } from "../../../lib/rag"; 8 | 9 | export const POST = async (req: NextRequest) => { 10 | try { 11 | const { type, content, namespace } = await req.json(); 12 | 13 | // Check the type and call the corresponding function 14 | if (type === "pdf") { 15 | await AddPDFContext(namespace, content); 16 | } else if (type === "csv") { 17 | await AddCSVContext(namespace, content); 18 | } else if (type === "html") { 19 | await AddWEBContext(namespace, content); 20 | } else if (type === "txt") { 21 | await AddTXTContext(namespace, content); 22 | } else { 23 | return NextResponse.json( 24 | { success: false, message: "Unsupported type" }, 25 | { status: 400 }, 26 | ); 27 | } 28 | 29 | // Return true as the response 30 | return NextResponse.json({ success: true }, { status: 200 }); 31 | } catch (error) { 32 | console.error("Error handling request:", error); 33 | return NextResponse.json( 34 | { success: false, message: "An error occurred" }, 35 | { status: 500 }, 36 | ); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /app/api/history/route.ts: -------------------------------------------------------------------------------- 1 | import { ragChat } from "@/lib/rag"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export const POST = async (req: NextRequest) => { 5 | try { 6 | const { chatID } = await req.json(); 7 | const chatHistory = await ragChat.history.getMessages({ 8 | amount: 100, 9 | sessionId: chatID, 10 | }); 11 | 12 | return NextResponse.json(chatHistory); 13 | } catch (error) { 14 | console.error("Error fetching chat history:", error); 15 | return new NextResponse("Failed to fetch chat history", { status: 500 }); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /app/api/oauth/route.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@/db/supabase/server"; 2 | import { NextResponse } from "next/server"; 3 | 4 | export async function GET(request: Request) { 5 | const { searchParams, origin } = new URL(request.url); 6 | const code = searchParams.get("code"); 7 | const next = searchParams.get("next") ?? "/"; 8 | 9 | if (code) { 10 | const supabase = createClient(); 11 | const { error } = await supabase.auth.exchangeCodeForSession(code); 12 | if (!error) { 13 | const forwardedHost = request.headers.get("x-forwarded-host"); 14 | const isLocalEnv = process.env.NODE_ENV === "development"; 15 | if (isLocalEnv) { 16 | return NextResponse.redirect(`${origin}${next}`); 17 | } else if (forwardedHost) { 18 | return NextResponse.redirect(`https://${forwardedHost}${next}`); 19 | } else { 20 | return NextResponse.redirect(`${origin}${next}`); 21 | } 22 | } 23 | } 24 | 25 | return NextResponse.redirect(`${origin}/auth/auth-code-error`); 26 | } 27 | -------------------------------------------------------------------------------- /app/api/stream/reply/route.ts: -------------------------------------------------------------------------------- 1 | import { ragChat } from "@/lib/rag"; 2 | import { NextRequest } from "next/server"; 3 | import { aiUseChatAdapter } from "@upstash/rag-chat/nextjs"; 4 | 5 | export const POST = async (req: NextRequest) => { 6 | const { messages, chatId, namespace } = await req.json(); 7 | let disableRAG = !namespace; 8 | 9 | console.log("Namespace:", namespace); 10 | console.log("Disable RAG:", disableRAG); 11 | 12 | const lastMsg = messages[messages.length - 1].content; 13 | const res = await ragChat.chat(lastMsg, { 14 | namespace: namespace, 15 | streaming: true, 16 | sessionId: chatId, 17 | historyLength: 100, 18 | historyTTL: 604_800, 19 | similarityThreshold: 0.7, 20 | disableRAG: disableRAG, 21 | }); 22 | 23 | return aiUseChatAdapter(res); 24 | }; 25 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkidGod4444/x0-GPT/2214ca71bae2ac5a2cb3e3f27e8f7aa5546ad05e/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 0 0% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 0 0% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 0 0% 3.9%; 13 | --primary: 0 0% 9%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 0 0% 96.1%; 16 | --secondary-foreground: 0 0% 9%; 17 | --muted: 0 0% 96.1%; 18 | --muted-foreground: 0 0% 45.1%; 19 | --accent: 0 0% 96.1%; 20 | --accent-foreground: 0 0% 9%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 0 0% 89.8%; 24 | --input: 0 0% 89.8%; 25 | --ring: 0 0% 3.9%; 26 | --radius: 0.5rem; 27 | --chart-1: 12 76% 61%; 28 | --chart-2: 173 58% 39%; 29 | --chart-3: 197 37% 24%; 30 | --chart-4: 43 74% 66%; 31 | --chart-5: 27 87% 67%; 32 | } 33 | 34 | .dark { 35 | --background: 0 0% 3.9%; 36 | --foreground: 0 0% 98%; 37 | --card: 0 0% 3.9%; 38 | --card-foreground: 0 0% 98%; 39 | --popover: 0 0% 3.9%; 40 | --popover-foreground: 0 0% 98%; 41 | --primary: 0 0% 98%; 42 | --primary-foreground: 0 0% 9%; 43 | --secondary: 0 0% 14.9%; 44 | --secondary-foreground: 0 0% 98%; 45 | --muted: 0 0% 14.9%; 46 | --muted-foreground: 0 0% 63.9%; 47 | --accent: 0 0% 14.9%; 48 | --accent-foreground: 0 0% 98%; 49 | --destructive: 0 62.8% 30.6%; 50 | --destructive-foreground: 0 0% 98%; 51 | --border: 0 0% 14.9%; 52 | --input: 0 0% 14.9%; 53 | --ring: 0 0% 83.1%; 54 | --chart-1: 220 70% 50%; 55 | --chart-2: 160 60% 45%; 56 | --chart-3: 30 80% 55%; 57 | --chart-4: 280 65% 60%; 58 | --chart-5: 340 75% 55%; 59 | } 60 | } 61 | 62 | ::selection { 63 | background-color: #ffffff; 64 | color: black; 65 | } 66 | 67 | @layer base { 68 | * { 69 | @apply border-border; 70 | } 71 | body { 72 | @apply bg-background text-foreground; 73 | } 74 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Bricolage_Grotesque } from "next/font/google"; 3 | import "./globals.css"; 4 | import { ThemeProvider } from "@/components/custom/theme.provider"; 5 | import { Analytics } from "@vercel/analytics/react"; 6 | import { SpeedInsights } from "@vercel/speed-insights/next"; 7 | import { RoutesContext } from "@/context/routes.context"; 8 | import { Toaster } from "@/components/ui/sonner"; 9 | 10 | const inter = Bricolage_Grotesque({ subsets: ["latin"] }); 11 | 12 | export const metadata: Metadata = { 13 | title: "x0-GPT - AI-Powered Personal Assistant GPT", 14 | description: 15 | "Explore and interact with any WEBPAGE, PDF, CSV, TXT and many more effortlessly using x0-GPT, the free AI-powered tool designed for everyone.", 16 | keywords: [ 17 | "Saidev Dhal", 18 | "Upstash rag chat", 19 | "Upstash sdk", 20 | "Chat with pdf", 21 | "Chat with website", 22 | "Chat with csv", 23 | "Acternity UI", 24 | "x0 gpt", 25 | "x0-gpt", 26 | "Ai", 27 | "Gpt", 28 | "Personal assistant", 29 | "Chat with ai", 30 | "Chat with gpt", 31 | "Chat with x0-gpt", 32 | "Chat with x0 gpt", 33 | ], 34 | authors: [ 35 | { 36 | name: "Saidev Dhal", 37 | url: "https://devwtf.in", 38 | }, 39 | ], 40 | creator: "Saidev Dhal", 41 | openGraph: { 42 | images: [ 43 | { 44 | url: "https://i.imgur.com/ffeIgzW.png", 45 | width: 1200, 46 | height: 627, 47 | alt: "x0-GPT - AI-Powered Personal Assistant GPT", 48 | }, 49 | ], 50 | }, 51 | metadataBase: { 52 | host: "https://x0-gpt.devwtf.in", 53 | href: "/", 54 | origin: "https://x0-gpt.devwtf.in", 55 | password: "x0-gpt", 56 | hash: "x0-gpt", 57 | pathname: "/", 58 | search: "", 59 | username: "devwtf", 60 | hostname: "x0-gpt.devwtf.in", 61 | port: "", 62 | protocol: "https:", 63 | searchParams: new URLSearchParams(""), 64 | toString: () => "https://x0-gpt.devwtf.in/", 65 | toJSON: () => "https://x0-gpt.devwtf.in/", 66 | }, 67 | twitter: { 68 | card: "summary_large_image", 69 | site: "https://x0-gpt.devwtf.in", 70 | creator: "https://x0-gpt.devwtf.in", 71 | title: "x0-GPT - AI-Powered Personal Assistant GPT", 72 | description: 73 | "Explore and interact with any WEBPAGE, PDF, CSV, TXT and many more effortlessly using x0-GPT, the free AI-powered tool designed for everyone.", 74 | images: [ 75 | { 76 | url: "https://i.imgur.com/ffeIgzW.png", 77 | width: 1200, 78 | height: 627, 79 | alt: "x0-GPT - AI-Powered Personal Assistant GPT", 80 | }, 81 | ], 82 | }, 83 | }; 84 | 85 | const protectedRoutes = ["/chat", "/memories", "/c", "/spaces"]; 86 | const publicRoutes = ["/", "/gatway"]; 87 | 88 | export default function RootLayout({ 89 | children, 90 | }: Readonly<{ 91 | children: React.ReactNode; 92 | }>) { 93 | return ( 94 | 95 | 96 | 100 | 106 | {children} 107 | 108 | 109 | 110 | {/* vercel analytics and speed insights */} 111 | 112 | 113 | 114 | 115 | ); 116 | } 117 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkidGod4444/x0-GPT/2214ca71bae2ac5a2cb3e3f27e8f7aa5546ad05e/bun.lockb -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /components/custom/animated-bg.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { cn } from "@/lib/utils"; 3 | import { AnimatePresence, Transition, motion } from "framer-motion"; 4 | import { 5 | Children, 6 | cloneElement, 7 | ReactElement, 8 | useEffect, 9 | useState, 10 | useId, 11 | } from "react"; 12 | 13 | type AnimatedBackgroundProps = { 14 | children: 15 | | ReactElement<{ "data-id": string }>[] 16 | | ReactElement<{ "data-id": string }>; 17 | defaultValue?: string; 18 | onValueChange?: (newActiveId: string | null) => void; 19 | className?: string; 20 | transition?: Transition; 21 | enableHover?: boolean; 22 | }; 23 | 24 | export default function AnimatedBackground({ 25 | children, 26 | defaultValue, 27 | onValueChange, 28 | className, 29 | transition, 30 | enableHover = false, 31 | }: AnimatedBackgroundProps) { 32 | const [activeId, setActiveId] = useState(null); 33 | const uniqueId = useId(); 34 | 35 | const handleSetActiveId = (id: string | null) => { 36 | setActiveId(id); 37 | 38 | if (onValueChange) { 39 | onValueChange(id); 40 | } 41 | }; 42 | 43 | useEffect(() => { 44 | if (defaultValue !== undefined) { 45 | setActiveId(defaultValue); 46 | } 47 | }, [defaultValue]); 48 | 49 | return Children.map(children, (child: any, index) => { 50 | const id = child.props["data-id"]; 51 | 52 | const interactionProps = enableHover 53 | ? { 54 | onMouseEnter: () => handleSetActiveId(id), 55 | onMouseLeave: () => handleSetActiveId(null), 56 | } 57 | : { 58 | onClick: () => handleSetActiveId(id), 59 | }; 60 | 61 | return cloneElement( 62 | child, 63 | { 64 | key: index, 65 | className: cn("relative inline-flex", child.props.className), 66 | "aria-selected": activeId === id, 67 | "data-checked": activeId === id ? "true" : "false", 68 | ...interactionProps, 69 | }, 70 | <> 71 | 72 | {activeId === id && ( 73 | 85 | )} 86 | 87 | {child.props.children} 88 | , 89 | ); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /components/custom/annc.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { ArrowRightIcon } from "@radix-ui/react-icons"; 3 | import { GitBranch } from "lucide-react"; 4 | import AnimatedShinyText from "./shiny-text"; 5 | import { cn } from "@/lib/utils"; 6 | import { MyLinks } from "@/db/defaults"; 7 | 8 | export function AnncBtn() { 9 | return ( 10 |
11 | 12 |
17 | 18 | 19 | 20 | Open Source 21 | 22 | 23 | 24 |
25 | 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /components/custom/chat/message.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { Bot, CheckCheck } from "lucide-react"; 3 | import React from "react"; 4 | import { Badge } from "@/components/ui/badge"; 5 | 6 | interface MsgProps { 7 | content: string; 8 | isUser: boolean; 9 | } 10 | 11 | export default function Message({ content, isUser }: MsgProps) { 12 | return ( 13 |
14 |
15 |
21 | {!isUser && ( 22 |
27 | 28 |
29 | )} 30 | 31 |
37 |
43 | 44 | {!isUser && "x0-GPT"} 45 | 46 | {!isUser && ( 47 | 48 | 49 | Bot 50 | 51 | )} 52 |
53 | 54 |

60 | {content} 61 |

62 |
63 |
64 |
65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /components/custom/chat/msgs-wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect, useRef } from "react"; 4 | import { type Message as TMsg } from "ai/react"; 5 | import Message from "./message"; 6 | 7 | interface MsgsProps { 8 | msgs: TMsg[]; 9 | } 10 | 11 | export default function MsgsWrapper({ msgs }: MsgsProps) { 12 | const containerRef = useRef(null); 13 | 14 | useEffect(() => { 15 | if (containerRef.current) { 16 | containerRef.current.scrollTop = containerRef.current.scrollHeight; 17 | } 18 | }, [msgs]); 19 | 20 | return ( 21 |
25 | {msgs.map((message, i) => ( 26 | 31 | ))} 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /components/custom/credits-comp.tsx: -------------------------------------------------------------------------------- 1 | import { BrandLinks } from "@/db/defaults"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | 5 | export function CreditsComp() { 6 | return ( 7 |
8 | 9 | upstash-brand 15 | 16 | 17 | 18 | supabase-brand 24 | 25 | 26 | 27 | nextjs-brand 33 | 34 | 35 | 36 | vercel-brand 42 | 43 | 44 | 45 | acternity-brand 51 | 52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /components/custom/dot-patern.tsx: -------------------------------------------------------------------------------- 1 | import { useId } from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | interface DotPatternProps { 6 | width?: any; 7 | height?: any; 8 | x?: any; 9 | y?: any; 10 | cx?: any; 11 | cy?: any; 12 | cr?: any; 13 | className?: string; 14 | [key: string]: any; 15 | } 16 | export function DotPattern({ 17 | width = 16, 18 | height = 16, 19 | x = 0, 20 | y = 0, 21 | cx = 1, 22 | cy = 1, 23 | cr = 1, 24 | className, 25 | ...props 26 | }: DotPatternProps) { 27 | const id = useId(); 28 | 29 | return ( 30 | 53 | ); 54 | } 55 | 56 | export default DotPattern; 57 | -------------------------------------------------------------------------------- /components/custom/features-sec.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { 3 | IconNetwork, 4 | IconFileTypePdf, 5 | IconCurrencyDollar, 6 | IconEaseInOut, 7 | IconSpy, 8 | IconBrandGithubCopilot, 9 | IconMessage, 10 | IconCloud, 11 | } from "@tabler/icons-react"; 12 | 13 | export function FeaturesSection() { 14 | const features = [ 15 | { 16 | title: "Free for Everyone", 17 | description: 18 | "No barriers, no fees. x0-GPT is a free tool accessible to anyone, anywhere.", 19 | icon: , 20 | }, 21 | { 22 | title: "Ease of use", 23 | description: "It's as easy as pressing some keys & buttons.", 24 | icon: , 25 | }, 26 | { 27 | title: "Universal Web Chat", 28 | description: 29 | "Easily chat with any website on the internet. Get answers, insights, and information directly from the source.", 30 | icon: , 31 | }, 32 | { 33 | title: "PDF Conversation", 34 | description: 35 | "Interact with PDF documents like never before. Ask questions, extract details, and navigate complex documents effortlessly.", 36 | icon: , 37 | }, 38 | { 39 | title: "Instant Information", 40 | description: 41 | "Get real-time responses from websites and documents, saving time and enhancing productivity.", 42 | icon: , 43 | }, 44 | { 45 | title: "AI-Powered by Vercel", 46 | description: 47 | "Leverage advanced AI to understand and respond to your queries, making interactions smarter and more accurate.", 48 | icon: , 49 | }, 50 | { 51 | title: "Versatile Connectivity", 52 | description: 53 | "Connect with a wide range of online sources, from news sites to academic journals, all within a single platform.", 54 | icon: , 55 | }, 56 | { 57 | title: "Privacy First", 58 | description: 59 | "Your data is yours. x0-GPT ensures secure interactions, keeping your information safe and encrypted.", 60 | icon: , 61 | }, 62 | ]; 63 | return ( 64 |
65 | {features.map((feature, index) => ( 66 | 67 | ))} 68 |
69 | ); 70 | } 71 | 72 | const Feature = ({ 73 | title, 74 | description, 75 | icon, 76 | index, 77 | }: { 78 | title: string; 79 | description: string; 80 | icon: React.ReactNode; 81 | index: number; 82 | }) => { 83 | return ( 84 |
91 | {index < 4 && ( 92 |
93 | )} 94 | {index >= 4 && ( 95 |
96 | )} 97 |
98 | {icon} 99 |
100 |
101 |
102 | 103 | {title} 104 | 105 |
106 |

107 | {description} 108 |

109 |
110 | ); 111 | }; 112 | -------------------------------------------------------------------------------- /components/custom/hero/credits.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CreditsComp } from "../credits-comp"; 3 | import TypingAnimation from "@/components/ui/typing-animation"; 4 | 5 | export default function Credits() { 6 | return ( 7 |
8 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/custom/hero/features.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FeaturesSection } from "../features-sec"; 3 | import { TextEffect } from "../text-gen"; 4 | 5 | export default function Features() { 6 | return ( 7 |
8 | 9 | Why x0-GPT? 10 | 11 | 16 | Why not? If it's all you want, then it's all we can provide. 17 | Haha, love from Saidev Dhal. 18 | 19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /components/custom/hero/orbit.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Image from "next/image"; 4 | import React, { useEffect, useState } from "react"; 5 | import OrbitingCircles from "../orbiting-circles"; 6 | import { TextEffect } from "../text-gen"; 7 | 8 | export default function Orbits() { 9 | const [OuterRadius, setOuterRadius] = useState(200); 10 | const [InnerRadius, setInnerRadius] = useState(100); 11 | const [isMob, setIsMob] = useState(false); 12 | 13 | useEffect(() => { 14 | const updateRadius = () => { 15 | const screenWidth = window.innerWidth; 16 | const isMobile = screenWidth < 600 ? true : false; 17 | const outerRadius = screenWidth < 600 ? 180 : 200; 18 | const innerRadius = screenWidth < 600 ? 90 : 100; 19 | setIsMob(isMobile); 20 | setInnerRadius(innerRadius); 21 | setOuterRadius(outerRadius); 22 | }; 23 | 24 | updateRadius(); 25 | 26 | window.addEventListener("resize", updateRadius); 27 | 28 | return () => { 29 | window.removeEventListener("resize", updateRadius); 30 | }; 31 | }, []); 32 | return ( 33 |
34 |
35 | 36 | Train & chat with any data from any thing ... 37 | 38 | 43 | Supports any website on the internet, PDFs, documents, and much more, 44 | providing you with seamless and comprehensive access to information. 45 | 46 |
47 |
48 | 49 | x0 50 | 51 | {/* Inner Circles */} 52 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | 85 | 86 | 87 | 88 | {/* reverse */} 89 | 90 | 97 | 98 | 99 | 100 | 107 | 108 | 109 |
110 |
111 | ); 112 | } 113 | 114 | const Icons = { 115 | github: () => ( 116 | github logo 122 | ), 123 | githubDark: () => ( 124 | github logo 130 | ), 131 | 132 | medium: () => ( 133 | medium logo 139 | ), 140 | mediumDark: () => ( 141 | medium logo 147 | ), 148 | 149 | x: () => x logo, 150 | xDark: () => ( 151 | x logo 152 | ), 153 | 154 | notion: () => ( 155 | notion logo 161 | ), 162 | notionDark: () => ( 163 | notion logo 169 | ), 170 | 171 | reddit: () => ( 172 | reddit logo 178 | ), 179 | redditDark: () => ( 180 | reddit logo 186 | ), 187 | 188 | wiki: () => ( 189 | wikipedia logo 196 | ), 197 | }; 198 | -------------------------------------------------------------------------------- /components/custom/loading-screen.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const LoadingScreen = () => { 4 | return ( 5 |
6 |
7 | {/*
*/} 8 |

9 | x0-GPT is syncing... 10 |

11 |
12 |
13 | ); 14 | }; 15 | 16 | export default LoadingScreen; 17 | -------------------------------------------------------------------------------- /components/custom/memories-panel.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useState } from "react"; 3 | import { Button } from "../ui/button"; 4 | import { Badge } from "../ui/badge"; 5 | import ToolbarPanel from "../ui/toolbar-panel"; 6 | import { useAuth } from "@/context/auth.context"; 7 | import { getResourcesByUserId } from "@/db/func"; 8 | import MemoCard from "./memories-card"; 9 | import { ScrollArea } from "../ui/scroll-area"; 10 | import { createClient } from "@/db/supabase/client"; 11 | 12 | const TITLES = ["All", "Webpages", "Notes", "PDFs", "CSVs"]; 13 | 14 | interface MemoCardProps { 15 | id: string; 16 | title: string; 17 | type: string; 18 | content: string; 19 | space: string; 20 | } 21 | 22 | export default function MemoriesPanel() { 23 | const { user } = useAuth(); 24 | const [activeIndex, setActiveIndex] = useState(0); 25 | const [currentMemo, setCurrentMemo] = useState(null); 26 | const [displayMemo, setDisplayMemo] = useState(null); 27 | const [isSyncing, setIsSyncing] = useState(false); 28 | 29 | // Fetching memories from the database 30 | useEffect(() => { 31 | const fetchMemories = async () => { 32 | if (user) { 33 | setIsSyncing(true); 34 | try { 35 | const mem = await getResourcesByUserId(user.id); 36 | if (mem) { 37 | const formattedMemo = mem.map((meme: any) => ({ 38 | id: meme.id, 39 | title: meme.title, 40 | type: meme.type, 41 | content: meme.content, 42 | space: meme.spaces, 43 | })); 44 | setCurrentMemo(formattedMemo); 45 | } 46 | } catch (error) { 47 | console.error("Failed to fetch memories:", error); 48 | } finally { 49 | setIsSyncing(false); 50 | } 51 | } 52 | }; 53 | 54 | if (user && !currentMemo) { 55 | fetchMemories(); 56 | } 57 | }, [user, currentMemo]); 58 | 59 | useEffect(() => { 60 | const supabase = createClient(); 61 | const subscription = supabase 62 | .channel("resource-channel") 63 | .on( 64 | "postgres_changes", 65 | { 66 | event: "*", 67 | schema: "public", 68 | table: "RESOURCES", 69 | filter: `user_id=eq.${user?.id!}`, 70 | }, 71 | (payload) => { 72 | if (payload.eventType === "DELETE") { 73 | setCurrentMemo((prev) => 74 | prev ? prev.filter((memo) => memo.id !== payload.old.id) : null, 75 | ); 76 | } else if (payload.eventType === "UPDATE") { 77 | setCurrentMemo((prev) => 78 | prev 79 | ? prev.map((memo) => 80 | memo.id === payload.new.id 81 | ? { 82 | ...memo, 83 | title: payload.new.title, 84 | type: payload.new.type, 85 | content: payload.new.content, 86 | space: payload.new.spaces, 87 | } 88 | : memo, 89 | ) 90 | : null, 91 | ); 92 | } 93 | }, 94 | ) 95 | .subscribe(); 96 | 97 | return () => { 98 | supabase.removeChannel(subscription); 99 | }; 100 | }, [user]); 101 | // Filtering memos based on activeIndex 102 | useEffect(() => { 103 | if (currentMemo) { 104 | let filteredMemo; 105 | switch (activeIndex) { 106 | case 0: 107 | filteredMemo = currentMemo; 108 | break; 109 | case 1: 110 | filteredMemo = currentMemo.filter((memo) => memo.type === "html"); 111 | break; 112 | case 2: 113 | filteredMemo = currentMemo.filter((memo) => memo.type === "txt"); 114 | break; 115 | case 3: 116 | filteredMemo = currentMemo.filter((memo) => memo.type === "pdf"); 117 | break; 118 | case 4: 119 | filteredMemo = currentMemo.filter((memo) => memo.type === "csv"); 120 | break; 121 | default: 122 | filteredMemo = currentMemo; 123 | } 124 | setDisplayMemo(filteredMemo); 125 | } 126 | }, [activeIndex, currentMemo]); 127 | 128 | // Handle toolbar value change 129 | const handleValueChange = (id: string | null) => { 130 | const newIndex = TITLES.findIndex((title) => title === id); 131 | if (newIndex !== -1) { 132 | setActiveIndex(newIndex); 133 | } 134 | }; 135 | 136 | return ( 137 |
138 |
139 |
140 | 149 | {TITLES.map((label, index) => ( 150 | 158 | ))} 159 | 160 |
161 | 164 | ({activeIndex + 1}/{TITLES.length}){" "} 165 | {isSyncing ? "Syncing..." : "Synchronized"} 166 | 167 |
168 |
169 | 170 | {displayMemo && displayMemo.length > 0 ? ( 171 |
172 | {displayMemo.map((memo, index) => ( 173 | 181 | ))} 182 |
183 | ) : ( 184 |
185 | Memory is empty 186 |
187 | )} 188 |
189 |
190 |
191 | ); 192 | } 193 | -------------------------------------------------------------------------------- /components/custom/orbiting-circles.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | export interface OrbitingCirclesProps { 4 | className?: string; 5 | children?: React.ReactNode; 6 | reverse?: boolean; 7 | duration?: number; 8 | delay?: number; 9 | radius?: number; 10 | path?: boolean; 11 | } 12 | 13 | export default function OrbitingCircles({ 14 | className, 15 | children, 16 | reverse, 17 | duration = 20, 18 | delay = 10, 19 | radius = 50, 20 | path = true, 21 | }: OrbitingCirclesProps) { 22 | return ( 23 | <> 24 | {path && ( 25 | 30 | 37 | 38 | )} 39 | 40 |
54 | {children} 55 |
56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /components/custom/redirecting-screen.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const RedirectingScreen = () => { 4 | return ( 5 |
6 |
7 | {/*
*/} 8 |

9 | x0-GPT is redirecting... 10 |

11 |
12 |
13 | ); 14 | }; 15 | 16 | export default RedirectingScreen; 17 | -------------------------------------------------------------------------------- /components/custom/res-type.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Check, ChevronsUpDown } from "lucide-react"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import { Button } from "@/components/ui/button"; 8 | import { 9 | Command, 10 | CommandGroup, 11 | CommandItem, 12 | CommandList, 13 | } from "@/components/ui/command"; 14 | import { 15 | Popover, 16 | PopoverContent, 17 | PopoverTrigger, 18 | } from "@/components/ui/popover"; 19 | 20 | const Rtypes = [ 21 | { 22 | value: "txt", 23 | label: "It's a note.", 24 | }, 25 | { 26 | value: "html", 27 | label: "It's a webpage", 28 | }, 29 | { 30 | value: "pdf", 31 | label: "It's a PDF.", 32 | }, 33 | { 34 | value: "csv", 35 | label: "It's a CSV", 36 | }, 37 | ]; 38 | 39 | interface AddresInt { 40 | onRtypeSelect?: (selectedRtype: string | null) => void; 41 | } 42 | export function ResourceType({ onRtypeSelect }: AddresInt) { 43 | const [open, setOpen] = React.useState(false); 44 | const [value, setValue] = React.useState(""); 45 | 46 | React.useEffect(() => { 47 | if (onRtypeSelect) { 48 | onRtypeSelect(value); 49 | } 50 | }, [value, onRtypeSelect]); 51 | 52 | return ( 53 | 54 | 55 | 66 | 67 | 68 | 69 | {/* */} 70 | 71 | {/* No framework found. */} 72 | 73 | {Rtypes.map((framework) => ( 74 | { 78 | setValue(currentValue === value ? "" : currentValue); 79 | setOpen(false); 80 | }} 81 | className="rounded-xl cursor-pointer" 82 | > 83 | 89 | {framework.label} 90 | 91 | ))} 92 | 93 | 94 | 95 | 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /components/custom/shiny-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion, type AnimationProps } from "framer-motion"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const animationProps = { 8 | initial: { "--x": "100%", scale: 0.8 }, 9 | animate: { "--x": "-100%", scale: 1 }, 10 | whileTap: { scale: 0.95 }, 11 | transition: { 12 | repeat: Infinity, 13 | repeatType: "loop", 14 | repeatDelay: 1, 15 | type: "spring", 16 | stiffness: 20, 17 | damping: 15, 18 | mass: 2, 19 | scale: { 20 | type: "spring", 21 | stiffness: 200, 22 | damping: 5, 23 | mass: 0.5, 24 | }, 25 | }, 26 | } as AnimationProps; 27 | interface ShinyButtonProps { 28 | text: string; 29 | className?: string; 30 | } 31 | const ShinyButton = ({ 32 | text = "shiny-button", 33 | className, 34 | }: ShinyButtonProps) => { 35 | return ( 36 | 43 | 50 | {text} 51 | 52 | 59 | 60 | ); 61 | }; 62 | 63 | export default ShinyButton; 64 | -------------------------------------------------------------------------------- /components/custom/shiny-text.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { CSSProperties, FC, ReactNode } from "react"; 3 | 4 | interface AnimatedShinyTextProps { 5 | children: ReactNode; 6 | className?: string; 7 | shimmerWidth?: number; 8 | } 9 | 10 | const AnimatedShinyText: FC = ({ 11 | children, 12 | className, 13 | shimmerWidth = 200, 14 | }) => { 15 | return ( 16 |

34 | {children} 35 |

36 | ); 37 | }; 38 | 39 | export default AnimatedShinyText; 40 | -------------------------------------------------------------------------------- /components/custom/sidebar/add-res-comp.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { CirclePlus, SaveAll } from "lucide-react"; 3 | import { 4 | Dialog, 5 | DialogContent, 6 | DialogDescription, 7 | DialogFooter, 8 | DialogHeader, 9 | DialogTitle, 10 | DialogTrigger, 11 | } from "@/components/ui/dialog"; 12 | import { Input } from "../../ui/input"; 13 | import { Label } from "../../ui/label"; 14 | import { SpaceComp } from "./spaces-comp"; 15 | import { Textarea } from "../../ui/textarea"; 16 | import { Button } from "../../ui/button"; 17 | import { ResourceType } from "../res-type"; 18 | import { storeResource } from "@/db/func"; 19 | import { useAuth } from "@/context/auth.context"; 20 | import { toast } from "sonner"; 21 | 22 | export default function AddResComp() { 23 | const { user } = useAuth(); 24 | const [open, setOpen] = useState(false); 25 | const [ph, setPh] = useState(null); 26 | const [title, setTitle] = useState(null); 27 | const [space, setSpace] = useState(null); 28 | const [content, setContent] = useState(null); 29 | const [contentType, setContentType] = useState(null); 30 | 31 | const handleSubmit = async () => { 32 | if (user && contentType && content && space) { 33 | try { 34 | const response = await fetch("/api/add", { 35 | method: "POST", 36 | headers: { 37 | "Content-Type": "application/json", 38 | }, 39 | body: JSON.stringify({ 40 | type: contentType, 41 | content: content, 42 | namespace: space, 43 | }), 44 | }); 45 | 46 | if (!response.ok) { 47 | throw new Error("Failed to save resource"); 48 | } 49 | 50 | await storeResource(user?.id, title!, contentType!, content!, space!); 51 | setOpen(false); 52 | const currentDate = new Date().toLocaleString(); 53 | toast.success(`Resource has been saved!`, { 54 | description: currentDate, 55 | }); 56 | } catch (error) { 57 | console.error("Error saving resource:", error); 58 | toast.error("Failed to save resource. Please try again."); 59 | } 60 | } else { 61 | toast.error("Please fill all the required fields."); 62 | } 63 | }; 64 | 65 | useEffect(() => { 66 | if (contentType === "pdf") { 67 | setPh("Provide your pdf url."); 68 | } else if (contentType === "csv") { 69 | setPh("Provide your csv url."); 70 | } else if (contentType === "txt") { 71 | setPh("Enter your text."); 72 | } else if (contentType === "html") { 73 | setPh("Provide your webpage url."); 74 | } 75 | }, [contentType]); 76 | 77 | useEffect(() => { 78 | const down = (e: KeyboardEvent) => { 79 | if (e.key === "q" && (e.metaKey || e.ctrlKey)) { 80 | e.preventDefault(); 81 | setOpen((open) => !open); 82 | } 83 | }; 84 | 85 | document.addEventListener("keydown", down); 86 | return () => document.removeEventListener("keydown", down); 87 | }, []); 88 | 89 | return ( 90 | 91 | 92 |
93 | 94 | Add Resource 95 | 96 | Q 97 | 98 |
99 |
100 | 101 | 102 | Add your resources! 103 | 104 | Give me anything, I will remember for you. 105 | 106 | 107 | 108 | ) => 113 | setTitle(e.target.value) 114 | } 115 | /> 116 | 117 | setSpace(space)} 120 | /> 121 | 122 | setContentType(rtype)} /> 123 | 124 |