├── app ├── cancel │ └── page.tsx ├── chat │ └── page.tsx ├── api │ ├── chat │ │ ├── history │ │ │ └── route.ts │ │ └── route.ts │ ├── auth │ │ ├── signup │ │ │ └── route.ts │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── user │ │ ├── delete │ │ │ └── route.ts │ │ └── settings │ │ │ └── route.ts │ ├── verify-subscription │ │ └── route.ts │ ├── create-checkout-session │ │ └── route.ts │ ├── webhooks │ │ └── stripe │ │ │ └── route.ts │ └── upload │ │ └── route.ts ├── layout.tsx ├── page.tsx ├── success │ └── page.tsx ├── history │ └── page.tsx ├── auth │ ├── signin │ │ └── page.tsx │ └── signup │ │ └── page.tsx ├── settings │ └── page.tsx └── globals.css ├── src └── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── postcss.config.mjs ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── next.config.js ├── lib ├── prisma.ts ├── storage.ts └── textProcessor.ts ├── eslint.config.mjs ├── .gitignore ├── tsconfig.json ├── components ├── Header.tsx ├── FileUpload.tsx ├── Logo.tsx ├── Dashboard.tsx ├── ChatInterface.tsx ├── SubscriptionModal.tsx └── LoginModal.tsx ├── package.json ├── prisma └── schema.prisma ├── README.md └── middleware.ts /app/cancel/page.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eien0618/ChatBotPro/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | /* config options here */ 4 | }; 5 | 6 | module.exports = nextConfig; -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | const globalForPrisma = globalThis as unknown as { 4 | prisma: PrismaClient | undefined; 5 | }; 6 | 7 | export const prisma = globalForPrisma.prisma ?? new PrismaClient(); 8 | 9 | if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | :root { 4 | --background: #ffffff; 5 | --foreground: #171717; 6 | } 7 | 8 | @theme inline { 9 | --color-background: var(--background); 10 | --color-foreground: var(--foreground); 11 | --font-sans: var(--font-geist-sans); 12 | --font-mono: var(--font-geist-mono); 13 | } 14 | 15 | @media (prefers-color-scheme: dark) { 16 | :root { 17 | --background: #0a0a0a; 18 | --foreground: #ededed; 19 | } 20 | } 21 | 22 | body { 23 | background: var(--background); 24 | color: var(--foreground); 25 | font-family: Arial, Helvetica, sans-serif; 26 | } 27 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # IDE 39 | .vscode/ 40 | .idea/ 41 | 42 | # Prisma 43 | /prisma/migrations/ 44 | 45 | # AWS credentials 46 | .aws/ 47 | aws.json 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const geistSans = Geist({ 6 | variable: "--font-geist-sans", 7 | subsets: ["latin"], 8 | }); 9 | 10 | const geistMono = Geist_Mono({ 11 | variable: "--font-geist-mono", 12 | subsets: ["latin"], 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "Create Next App", 17 | description: "Generated by create next app", 18 | }; 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: Readonly<{ 23 | children: React.ReactNode; 24 | }>) { 25 | return ( 26 | 27 | 30 | {children} 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/chat/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { useSession } from 'next-auth/react'; 5 | import { useRouter } from 'next/navigation'; 6 | import ChatInterface from '@/components/ChatInterface'; 7 | 8 | export default function ChatPage() { 9 | const { data: session, status } = useSession(); 10 | const router = useRouter(); 11 | 12 | useEffect(() => { 13 | if (status === 'unauthenticated') { 14 | router.push('/auth/signin'); 15 | } 16 | }, [status, router]); 17 | 18 | if (status === 'loading') { 19 | return ( 20 |
21 |
22 | Loading... 23 |
24 |
25 | ); 26 | } 27 | 28 | if (!session) { 29 | return null; 30 | } 31 | 32 | return ( 33 |
34 | 35 |
36 | ); 37 | } -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { signOut, useSession } from 'next-auth/react'; 4 | import Link from 'next/link'; 5 | 6 | export default function Header() { 7 | const { data: session } = useSession(); 8 | 9 | return ( 10 | 38 | ); 39 | } -------------------------------------------------------------------------------- /lib/storage.ts: -------------------------------------------------------------------------------- 1 | import { S3Client } from '@aws-sdk/client-s3'; 2 | import { Upload } from '@aws-sdk/lib-storage'; 3 | import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; 4 | 5 | // Initialize S3 client 6 | const s3Client = new S3Client({ 7 | region: process.env.AWS_REGION || 'us-east-1', 8 | credentials: { 9 | accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', 10 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '', 11 | }, 12 | }); 13 | 14 | export async function uploadFile(file: Buffer, fileName: string, contentType: string) { 15 | const key = `uploads/${Date.now()}-${fileName}`; 16 | 17 | const upload = new Upload({ 18 | client: s3Client, 19 | params: { 20 | Bucket: process.env.AWS_S3_BUCKET || '', 21 | Key: key, 22 | Body: file, 23 | ContentType: contentType, 24 | }, 25 | }); 26 | 27 | await upload.done(); 28 | return key; 29 | } 30 | 31 | export async function getFileUrl(key: string) { 32 | const command = { 33 | Bucket: process.env.AWS_S3_BUCKET || '', 34 | Key: key, 35 | }; 36 | 37 | // Generate a signed URL that expires in 1 hour 38 | const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); 39 | return signedUrl; 40 | } -------------------------------------------------------------------------------- /app/api/chat/history/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { getServerSession } from 'next-auth'; 3 | import { authOptions } from '../../auth/[...nextauth]/route'; 4 | import { prisma } from '@/lib/prisma'; 5 | 6 | export async function GET() { 7 | try { 8 | const session = await getServerSession(authOptions); 9 | if (!session?.user?.email) { 10 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 11 | } 12 | 13 | const user = await prisma.user.findUnique({ 14 | where: { email: session.user.email }, 15 | }); 16 | 17 | if (!user) { 18 | return NextResponse.json({ error: 'User not found' }, { status: 404 }); 19 | } 20 | 21 | const history = await prisma.prompt.findMany({ 22 | where: { userId: user.id }, 23 | orderBy: { createdAt: 'desc' }, 24 | select: { 25 | id: true, 26 | content: true, 27 | response: true, 28 | createdAt: true, 29 | detectability: true, 30 | }, 31 | }); 32 | 33 | return NextResponse.json(history); 34 | } catch (error) { 35 | console.error('Error:', error); 36 | return NextResponse.json( 37 | { error: 'Internal server error' }, 38 | { status: 500 } 39 | ); 40 | } 41 | } -------------------------------------------------------------------------------- /app/api/auth/signup/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { prisma } from '@/lib/prisma'; 3 | import bcrypt from 'bcryptjs'; 4 | 5 | export async function POST(req: Request) { 6 | try { 7 | const { name, email, password } = await req.json(); 8 | 9 | if (!email || !password) { 10 | return NextResponse.json( 11 | { error: 'Email and password are required' }, 12 | { status: 400 } 13 | ); 14 | } 15 | 16 | const existingUser = await prisma.user.findUnique({ 17 | where: { email }, 18 | }); 19 | 20 | if (existingUser) { 21 | return NextResponse.json( 22 | { error: 'User already exists' }, 23 | { status: 400 } 24 | ); 25 | } 26 | 27 | const hashedPassword = await bcrypt.hash(password, 10); 28 | 29 | const user = await prisma.user.create({ 30 | data: { 31 | name, 32 | email, 33 | password: hashedPassword, 34 | }, 35 | }); 36 | 37 | return NextResponse.json( 38 | { message: 'User created successfully' }, 39 | { status: 201 } 40 | ); 41 | } catch (error) { 42 | console.error('Error:', error); 43 | return NextResponse.json( 44 | { error: 'Internal server error' }, 45 | { status: 500 } 46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-pro", 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 | "postinstall": "prisma generate" 11 | }, 12 | "dependencies": { 13 | "@auth/prisma-adapter": "^1.0.12", 14 | "@aws-sdk/client-s3": "^3.787.0", 15 | "@aws-sdk/lib-storage": "^3.787.0", 16 | "@aws-sdk/s3-request-presigner": "^3.787.0", 17 | "@popperjs/core": "^2.11.8", 18 | "@prisma/client": "^5.8.0", 19 | "@stripe/stripe-js": "^2.3.0", 20 | "bcryptjs": "^2.4.3", 21 | "bootstrap": "^5.3.2", 22 | "bootstrap-icons": "^1.11.3", 23 | "mongodb": "^6.3.0", 24 | "next": "14.0.4", 25 | "next-auth": "^4.24.5", 26 | "openai": "^4.24.1", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "stripe": "^14.10.0" 30 | }, 31 | "devDependencies": { 32 | "@types/bcryptjs": "^2.4.6", 33 | "@types/node": "^20.10.6", 34 | "@types/react": "^18.2.46", 35 | "@types/react-dom": "^18.2.18", 36 | "autoprefixer": "^10.4.16", 37 | "eslint": "^8.56.0", 38 | "eslint-config-next": "14.0.4", 39 | "postcss": "^8.4.32", 40 | "prisma": "^5.8.0", 41 | "typescript": "^5.3.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/api/user/delete/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { getServerSession } from 'next-auth'; 3 | import { authOptions } from '@/app/api/auth/[...nextauth]/route'; 4 | import { prisma } from '@/lib/prisma'; 5 | 6 | export async function DELETE() { 7 | try { 8 | const session = await getServerSession(authOptions); 9 | 10 | if (!session?.user?.email) { 11 | return NextResponse.json( 12 | { error: 'Unauthorized' }, 13 | { status: 401 } 14 | ); 15 | } 16 | 17 | // Get user from database 18 | const user = await prisma.user.findUnique({ 19 | where: { email: session.user.email }, 20 | include: { subscription: true } 21 | }); 22 | 23 | if (!user) { 24 | return NextResponse.json( 25 | { error: 'User not found' }, 26 | { status: 404 } 27 | ); 28 | } 29 | 30 | // If user has an active subscription, cancel it in Stripe 31 | if (user.subscription?.stripeSubscriptionId) { 32 | const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); 33 | await stripe.subscriptions.cancel(user.subscription.stripeSubscriptionId); 34 | } 35 | 36 | // Delete user and related data 37 | await prisma.user.delete({ 38 | where: { email: session.user.email } 39 | }); 40 | 41 | return NextResponse.json({ success: true }); 42 | } catch (error) { 43 | console.error('Error deleting account:', error); 44 | return NextResponse.json( 45 | { error: 'Failed to delete account' }, 46 | { status: 500 } 47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "mongodb" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model User { 11 | id String @id @default(auto()) @map("_id") @db.ObjectId 12 | name String? 13 | email String @unique 14 | password String 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @updatedAt 17 | subscription Subscription? 18 | prompts Prompt[] 19 | uploadedFiles UploadedFile[] 20 | credits Int @default(3) // Free trial credits 21 | } 22 | 23 | model Subscription { 24 | id String @id @default(auto()) @map("_id") @db.ObjectId 25 | userId String @unique @db.ObjectId 26 | user User @relation(fields: [userId], references: [id]) 27 | stripeCustomerId String @unique 28 | stripeSubscriptionId String? @unique 29 | status String @default("inactive") 30 | createdAt DateTime @default(now()) 31 | updatedAt DateTime @updatedAt 32 | } 33 | 34 | model Prompt { 35 | id String @id @default(auto()) @map("_id") @db.ObjectId 36 | content String 37 | response String 38 | userId String @db.ObjectId 39 | user User @relation(fields: [userId], references: [id]) 40 | createdAt DateTime @default(now()) 41 | updatedAt DateTime @updatedAt 42 | detectability Float // Score from 0-1 indicating how detectable the text is 43 | } 44 | 45 | model UploadedFile { 46 | id String @id @default(auto()) @map("_id") @db.ObjectId 47 | fileName String 48 | fileKey String // S3 key or other storage reference 49 | fileType String 50 | fileSize Int 51 | userId String @db.ObjectId 52 | user User @relation(fields: [userId], references: [id]) 53 | createdAt DateTime @default(now()) 54 | updatedAt DateTime @updatedAt 55 | } -------------------------------------------------------------------------------- /app/api/verify-subscription/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { getServerSession } from 'next-auth'; 3 | import { authOptions } from '../auth/[...nextauth]/route'; 4 | import { prisma } from '@/lib/prisma'; 5 | import Stripe from 'stripe'; 6 | 7 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { 8 | apiVersion: '2023-10-16', 9 | }); 10 | 11 | export async function GET(req: Request) { 12 | try { 13 | const session = await getServerSession(authOptions); 14 | if (!session?.user?.email) { 15 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 16 | } 17 | 18 | const { searchParams } = new URL(req.url); 19 | const sessionId = searchParams.get('session_id'); 20 | 21 | if (!sessionId) { 22 | return NextResponse.json( 23 | { error: 'Session ID is required' }, 24 | { status: 400 } 25 | ); 26 | } 27 | 28 | const stripeSession = await stripe.checkout.sessions.retrieve(sessionId); 29 | const user = await prisma.user.findUnique({ 30 | where: { email: session.user.email }, 31 | include: { subscription: true }, 32 | }); 33 | 34 | if (!user) { 35 | return NextResponse.json({ error: 'User not found' }, { status: 404 }); 36 | } 37 | 38 | if (stripeSession.payment_status === 'paid') { 39 | // Update subscription status 40 | await prisma.subscription.update({ 41 | where: { userId: user.id }, 42 | data: { 43 | status: 'active', 44 | stripeSubscriptionId: stripeSession.subscription as string, 45 | }, 46 | }); 47 | 48 | return NextResponse.json({ status: 'active' }); 49 | } 50 | 51 | return NextResponse.json( 52 | { error: 'Payment not completed' }, 53 | { status: 400 } 54 | ); 55 | } catch (error) { 56 | console.error('Error:', error); 57 | return NextResponse.json( 58 | { error: 'Internal server error' }, 59 | { status: 500 } 60 | ); 61 | } 62 | } -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | import CredentialsProvider from 'next-auth/providers/credentials'; 3 | import { PrismaAdapter } from '@auth/prisma-adapter'; 4 | import { prisma } from '@/lib/prisma'; 5 | import bcrypt from 'bcryptjs'; 6 | import { SessionStrategy } from 'next-auth'; 7 | 8 | export const authOptions = { 9 | adapter: PrismaAdapter(prisma), 10 | providers: [ 11 | CredentialsProvider({ 12 | name: 'credentials', 13 | credentials: { 14 | email: { label: 'Email', type: 'email' }, 15 | password: { label: 'Password', type: 'password' }, 16 | }, 17 | async authorize(credentials) { 18 | if (!credentials?.email || !credentials?.password) { 19 | throw new Error('Invalid credentials'); 20 | } 21 | 22 | const user = await prisma.user.findUnique({ 23 | where: { email: credentials.email }, 24 | }); 25 | 26 | if (!user) { 27 | throw new Error('User not found'); 28 | } 29 | 30 | const isPasswordValid = await bcrypt.compare( 31 | credentials.password, 32 | user.password 33 | ); 34 | 35 | if (!isPasswordValid) { 36 | throw new Error('Invalid password'); 37 | } 38 | 39 | return { 40 | id: user.id, 41 | email: user.email, 42 | name: user.name, 43 | }; 44 | }, 45 | }), 46 | ], 47 | session: { 48 | strategy: 'jwt' as SessionStrategy, 49 | }, 50 | pages: { 51 | signIn: '/auth/signin', 52 | }, 53 | callbacks: { 54 | async jwt({ token, user }: any) { 55 | if (user) { 56 | token.id = user.id; 57 | } 58 | return token; 59 | }, 60 | async session({ session, token }: any) { 61 | if (token) { 62 | session.user.id = token.id; 63 | } 64 | return session; 65 | }, 66 | }, 67 | }; 68 | 69 | const handler = NextAuth(authOptions); 70 | export { handler as GET, handler as POST }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT Pro 2 | 3 | A modern web application that provides an AI-powered chatbot with undetectable text generation capabilities. 4 | 5 | ## Features 6 | 7 | - AI-powered chatbot with natural language processing 8 | - Undetectable text generation algorithm 9 | - User authentication and authorization 10 | - Subscription management with Stripe integration 11 | - Free trial with 3 prompts 12 | - Modern and responsive UI 13 | 14 | ## Prerequisites 15 | 16 | - Node.js 18.x or later 17 | - PostgreSQL database 18 | - Stripe account 19 | - OpenAI API key 20 | 21 | ## Environment Variables 22 | 23 | Create a `.env` file in the root directory with the following variables: 24 | 25 | ```env 26 | DATABASE_URL="postgresql://user:password@localhost:5432/chatgpt_pro" 27 | NEXTAUTH_SECRET="your-secret-key" 28 | NEXTAUTH_URL="http://localhost:3000" 29 | STRIPE_SECRET_KEY="your-stripe-secret-key" 30 | STRIPE_WEBHOOK_SECRET="your-stripe-webhook-secret" 31 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="your-stripe-publishable-key" 32 | STRIPE_PRICE_ID="your-stripe-price-id" 33 | OPENAI_API_KEY="your-openai-api-key" 34 | ``` 35 | 36 | ## Installation 37 | 38 | 1. Clone the repository: 39 | ```bash 40 | git clone https://github.com/yourusername/chatgpt-pro.git 41 | cd chatgpt-pro 42 | ``` 43 | 44 | 2. Install dependencies: 45 | ```bash 46 | npm install 47 | ``` 48 | 49 | 3. Set up the database: 50 | ```bash 51 | npx prisma migrate dev 52 | ``` 53 | 54 | 4. Start the development server: 55 | ```bash 56 | npm run dev 57 | ``` 58 | okay 59 | The application will be available at `http://localhost:3000`. 60 | 61 | ## Stripe Setup 62 | 63 | 1. Create a Stripe account at https://stripe.com 64 | 2. Create a product and price in the Stripe dashboard 65 | 3. Set up webhooks in the Stripe dashboard: 66 | - Endpoint URL: `https://your-domain.com/api/webhooks/stripe` 67 | - Events to listen for: 68 | - `checkout.session.completed` 69 | - `customer.subscription.updated` 70 | - `customer.subscription.deleted` 71 | 72 | ## License 73 | 74 | This project is licensed under the MIT License - see the LICENSE file for details. 75 | -------------------------------------------------------------------------------- /app/api/user/settings/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { getServerSession } from 'next-auth'; 3 | import { authOptions } from '../../auth/[...nextauth]/route'; 4 | import { prisma } from '@/lib/prisma'; 5 | 6 | export async function GET() { 7 | try { 8 | const session = await getServerSession(authOptions); 9 | if (!session?.user?.email) { 10 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 11 | } 12 | 13 | const user = await prisma.user.findUnique({ 14 | where: { email: session.user.email }, 15 | select: { 16 | name: true, 17 | email: true, 18 | credits: true, 19 | subscription: { 20 | select: { 21 | status: true, 22 | stripeCustomerId: true, 23 | }, 24 | }, 25 | }, 26 | }); 27 | 28 | if (!user) { 29 | return NextResponse.json({ error: 'User not found' }, { status: 404 }); 30 | } 31 | 32 | return NextResponse.json(user); 33 | } catch (error) { 34 | console.error('Error:', error); 35 | return NextResponse.json( 36 | { error: 'Internal server error' }, 37 | { status: 500 } 38 | ); 39 | } 40 | } 41 | 42 | export async function PUT(req: Request) { 43 | try { 44 | const session = await getServerSession(authOptions); 45 | if (!session?.user?.email) { 46 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 47 | } 48 | 49 | const { name } = await req.json(); 50 | 51 | const user = await prisma.user.update({ 52 | where: { email: session.user.email }, 53 | data: { name }, 54 | select: { 55 | name: true, 56 | email: true, 57 | credits: true, 58 | subscription: { 59 | select: { 60 | status: true, 61 | stripeCustomerId: true, 62 | }, 63 | }, 64 | }, 65 | }); 66 | 67 | return NextResponse.json(user); 68 | } catch (error) { 69 | console.error('Error:', error); 70 | return NextResponse.json( 71 | { error: 'Internal server error' }, 72 | { status: 500 } 73 | ); 74 | } 75 | } -------------------------------------------------------------------------------- /app/api/create-checkout-session/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import Stripe from 'stripe'; 3 | import { getServerSession } from 'next-auth'; 4 | import { authOptions } from '../auth/[...nextauth]/route'; 5 | import { prisma } from '@/lib/prisma'; 6 | 7 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { 8 | apiVersion: '2023-10-16', 9 | }); 10 | 11 | export async function POST() { 12 | try { 13 | const session = await getServerSession(authOptions); 14 | if (!session?.user?.email) { 15 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 16 | } 17 | 18 | const user = await prisma.user.findUnique({ 19 | where: { email: session.user.email }, 20 | }); 21 | 22 | if (!user) { 23 | return NextResponse.json({ error: 'User not found' }, { status: 404 }); 24 | } 25 | 26 | // Create or get Stripe customer 27 | let customerId = user.subscription?.stripeCustomerId; 28 | if (!customerId) { 29 | const customer = await stripe.customers.create({ 30 | email: session.user.email, 31 | metadata: { 32 | userId: user.id, 33 | }, 34 | }); 35 | customerId = customer.id; 36 | 37 | // Update user with Stripe customer ID 38 | await prisma.user.update({ 39 | where: { id: user.id }, 40 | data: { 41 | subscription: { 42 | create: { 43 | stripeCustomerId: customerId, 44 | status: 'inactive', 45 | }, 46 | }, 47 | }, 48 | }); 49 | } 50 | 51 | // Create checkout session 52 | const checkoutSession = await stripe.checkout.sessions.create({ 53 | customer: customerId, 54 | payment_method_types: ['card'], 55 | line_items: [ 56 | { 57 | price: process.env.STRIPE_PRICE_ID, 58 | quantity: 1, 59 | }, 60 | ], 61 | mode: 'subscription', 62 | success_url: `${process.env.NEXTAUTH_URL}/success?session_id={CHECKOUT_SESSION_ID}`, 63 | cancel_url: `${process.env.NEXTAUTH_URL}/cancel`, 64 | metadata: { 65 | userId: user.id, 66 | }, 67 | }); 68 | 69 | return NextResponse.json({ sessionId: checkoutSession.id }); 70 | } catch (error) { 71 | console.error('Error:', error); 72 | return NextResponse.json( 73 | { error: 'Internal server error' }, 74 | { status: 500 } 75 | ); 76 | } 77 | } -------------------------------------------------------------------------------- /app/api/webhooks/stripe/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import Stripe from 'stripe'; 3 | import { prisma } from '@/lib/prisma'; 4 | 5 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { 6 | apiVersion: '2023-10-16', 7 | }); 8 | 9 | export async function POST(req: Request) { 10 | const body = await req.text(); 11 | const signature = req.headers.get('stripe-signature'); 12 | 13 | if (!signature) { 14 | return NextResponse.json( 15 | { error: 'Missing stripe-signature header' }, 16 | { status: 400 } 17 | ); 18 | } 19 | 20 | let event: Stripe.Event; 21 | 22 | try { 23 | event = stripe.webhooks.constructEvent( 24 | body, 25 | signature, 26 | process.env.STRIPE_WEBHOOK_SECRET! 27 | ); 28 | } catch (err) { 29 | console.error('Webhook signature verification failed:', err); 30 | return NextResponse.json( 31 | { error: 'Webhook signature verification failed' }, 32 | { status: 400 } 33 | ); 34 | } 35 | 36 | try { 37 | switch (event.type) { 38 | case 'checkout.session.completed': { 39 | const session = event.data.object as Stripe.Checkout.Session; 40 | const userId = session.metadata?.userId; 41 | 42 | if (userId) { 43 | await prisma.subscription.update({ 44 | where: { userId }, 45 | data: { 46 | status: 'active', 47 | stripeSubscriptionId: session.subscription as string, 48 | }, 49 | }); 50 | } 51 | break; 52 | } 53 | 54 | case 'customer.subscription.updated': 55 | case 'customer.subscription.deleted': { 56 | const subscription = event.data.object as Stripe.Subscription; 57 | const customer = await stripe.customers.retrieve(subscription.customer as string); 58 | const userId = customer.metadata.userId; 59 | 60 | if (userId) { 61 | await prisma.subscription.update({ 62 | where: { userId }, 63 | data: { 64 | status: subscription.status === 'active' ? 'active' : 'inactive', 65 | }, 66 | }); 67 | } 68 | break; 69 | } 70 | } 71 | 72 | return NextResponse.json({ received: true }); 73 | } catch (error) { 74 | console.error('Error processing webhook:', error); 75 | return NextResponse.json( 76 | { error: 'Error processing webhook' }, 77 | { status: 500 } 78 | ); 79 | } 80 | } -------------------------------------------------------------------------------- /components/FileUpload.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import SubscriptionModal from './SubscriptionModal'; 3 | 4 | interface FileUploadProps { 5 | isSubscribed: boolean; 6 | } 7 | 8 | export default function FileUpload({ isSubscribed }: FileUploadProps) { 9 | const [showSubscriptionModal, setShowSubscriptionModal] = useState(false); 10 | const [uploading, setUploading] = useState(false); 11 | 12 | const handleFileChange = async (event: React.ChangeEvent) => { 13 | const file = event.target.files?.[0]; 14 | if (!file) return; 15 | 16 | if (!isSubscribed) { 17 | setShowSubscriptionModal(true); 18 | event.target.value = ''; // Clear the file input 19 | return; 20 | } 21 | 22 | setUploading(true); 23 | // Here you would implement the actual file upload logic 24 | // For example, using FormData to send to your API endpoint 25 | try { 26 | const formData = new FormData(); 27 | formData.append('file', file); 28 | 29 | const response = await fetch('/api/upload', { 30 | method: 'POST', 31 | body: formData, 32 | }); 33 | 34 | if (!response.ok) { 35 | throw new Error('Upload failed'); 36 | } 37 | 38 | // Handle successful upload 39 | const data = await response.json(); 40 | console.log('File uploaded:', data); 41 | } catch (error) { 42 | console.error('Error uploading file:', error); 43 | } finally { 44 | setUploading(false); 45 | event.target.value = ''; // Clear the file input 46 | } 47 | }; 48 | 49 | return ( 50 | <> 51 |
52 | 63 | {uploading && ( 64 |
65 | Uploading... 66 |
67 | )} 68 |
69 | 70 | {showSubscriptionModal && ( 71 | setShowSubscriptionModal(false)} /> 72 | )} 73 | 74 | ); 75 | } -------------------------------------------------------------------------------- /components/Logo.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Link from 'next/link'; 4 | 5 | export default function Logo() { 6 | return ( 7 | 8 |
9 |
10 | 18 | {/* Gradient Definitions */} 19 | 20 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {/* Glow Effect */} 36 | 37 | 38 | 39 | 40 | 41 | 42 | {/* Main S Shape */} 43 | 51 | 52 | {/* Decorative Circles */} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 |
62 | ChatGPT 63 | Pro 64 |
65 |
66 | 67 | ); 68 | } -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import type { NextRequest } from 'next/server'; 3 | import { getToken } from 'next-auth/jwt'; 4 | 5 | // Rate limiting configuration 6 | const RATE_LIMIT = { 7 | windowMs: 60 * 1000, // 1 minute 8 | max: 60, // 60 requests per minute 9 | }; 10 | 11 | // Store for rate limiting 12 | const rateLimitStore = new Map(); 13 | 14 | export async function middleware(request: NextRequest) { 15 | // Only apply to API routes 16 | if (!request.nextUrl.pathname.startsWith('/api/')) { 17 | return NextResponse.next(); 18 | } 19 | 20 | // Get the IP address 21 | const ip = request.ip || 'unknown'; 22 | 23 | // Get the current timestamp 24 | const now = Date.now(); 25 | 26 | // Initialize or get rate limit data for this IP 27 | const rateLimitData = rateLimitStore.get(ip) || { count: 0, resetTime: now + RATE_LIMIT.windowMs }; 28 | 29 | // Reset if window has passed 30 | if (now > rateLimitData.resetTime) { 31 | rateLimitData.count = 0; 32 | rateLimitData.resetTime = now + RATE_LIMIT.windowMs; 33 | } 34 | 35 | // Check if rate limit exceeded 36 | if (rateLimitData.count >= RATE_LIMIT.max) { 37 | return new NextResponse( 38 | JSON.stringify({ error: 'Too many requests, please try again later.' }), 39 | { 40 | status: 429, 41 | headers: { 42 | 'Content-Type': 'application/json', 43 | 'Retry-After': Math.ceil((rateLimitData.resetTime - now) / 1000).toString(), 44 | }, 45 | } 46 | ); 47 | } 48 | 49 | // Increment the counter 50 | rateLimitData.count++; 51 | rateLimitStore.set(ip, rateLimitData); 52 | 53 | // For chat API, require authentication 54 | if (request.nextUrl.pathname.startsWith('/api/chat')) { 55 | const token = await getToken({ req: request }); 56 | 57 | if (!token) { 58 | return new NextResponse( 59 | JSON.stringify({ error: 'Authentication required' }), 60 | { 61 | status: 401, 62 | headers: { 'Content-Type': 'application/json' }, 63 | } 64 | ); 65 | } 66 | } 67 | 68 | // Add security headers 69 | const response = NextResponse.next(); 70 | 71 | response.headers.set('X-Content-Type-Options', 'nosniff'); 72 | response.headers.set('X-Frame-Options', 'DENY'); 73 | response.headers.set('X-XSS-Protection', '1; mode=block'); 74 | response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); 75 | response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); 76 | 77 | return response; 78 | } 79 | 80 | export const config = { 81 | matcher: [ 82 | '/api/:path*', 83 | ], 84 | }; -------------------------------------------------------------------------------- /app/api/upload/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { getServerSession } from 'next-auth'; 3 | import { authOptions } from '@/app/api/auth/[...nextauth]/route'; 4 | import { prisma } from '@/lib/prisma'; 5 | import { uploadFile } from '@/lib/storage'; 6 | 7 | export async function POST(request: Request) { 8 | try { 9 | // Check authentication 10 | const session = await getServerSession(authOptions); 11 | if (!session?.user?.email) { 12 | return NextResponse.json( 13 | { error: 'Unauthorized' }, 14 | { status: 401 } 15 | ); 16 | } 17 | 18 | // Check subscription status 19 | const user = await prisma.user.findUnique({ 20 | where: { email: session.user.email }, 21 | include: { subscription: true }, 22 | }); 23 | 24 | if (!user) { 25 | return NextResponse.json( 26 | { error: 'User not found' }, 27 | { status: 404 } 28 | ); 29 | } 30 | 31 | if (!user.subscription || user.subscription.status !== 'active') { 32 | return NextResponse.json( 33 | { error: 'Subscription required' }, 34 | { status: 403 } 35 | ); 36 | } 37 | 38 | // Get the form data 39 | const formData = await request.formData(); 40 | const file = formData.get('file') as File; 41 | 42 | if (!file) { 43 | return NextResponse.json( 44 | { error: 'No file provided' }, 45 | { status: 400 } 46 | ); 47 | } 48 | 49 | // Validate file type 50 | const allowedTypes = ['text/plain', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']; 51 | if (!allowedTypes.includes(file.type)) { 52 | return NextResponse.json( 53 | { error: 'Invalid file type' }, 54 | { status: 400 } 55 | ); 56 | } 57 | 58 | // Convert file to buffer 59 | const buffer = Buffer.from(await file.arrayBuffer()); 60 | 61 | // Upload file to storage 62 | const fileKey = await uploadFile(buffer, file.name, file.type); 63 | 64 | // Store file metadata in database 65 | const uploadedFile = await prisma.uploadedFile.create({ 66 | data: { 67 | fileName: file.name, 68 | fileKey: fileKey, 69 | fileType: file.type, 70 | fileSize: file.size, 71 | userId: user.id, 72 | }, 73 | }); 74 | 75 | return NextResponse.json({ 76 | success: true, 77 | message: 'File uploaded successfully', 78 | file: { 79 | id: uploadedFile.id, 80 | name: uploadedFile.fileName, 81 | type: uploadedFile.fileType, 82 | size: uploadedFile.fileSize, 83 | }, 84 | }); 85 | } catch (error) { 86 | console.error('Error processing file upload:', error); 87 | return NextResponse.json( 88 | { error: 'Internal server error' }, 89 | { status: 500 } 90 | ); 91 | } 92 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Inter } from 'next/font/google'; 4 | import { SessionProvider } from 'next-auth/react'; 5 | import './globals.css'; 6 | import 'bootstrap/dist/css/bootstrap.min.css'; 7 | import 'bootstrap-icons/font/bootstrap-icons.css'; 8 | import Logo from '@/components/Logo'; 9 | import { useSession } from 'next-auth/react'; 10 | import Link from 'next/link'; 11 | import { useState, useEffect } from 'react'; 12 | 13 | const inter = Inter({ subsets: ['latin'] }); 14 | 15 | function Header() { 16 | const { data: session } = useSession(); 17 | const [hasSubscription, setHasSubscription] = useState(false); 18 | 19 | useEffect(() => { 20 | if (session) { 21 | // Fetch user settings to check subscription status 22 | fetch('/api/user/settings') 23 | .then(res => res.json()) 24 | .then(data => { 25 | setHasSubscription(data.subscription?.status === 'active'); 26 | }) 27 | .catch(err => console.error('Error fetching subscription status:', err)); 28 | } 29 | }, [session]); 30 | 31 | return ( 32 |
33 | 51 |
52 | ); 53 | } 54 | 55 | export default function RootLayout({ 56 | children, 57 | }: { 58 | children: React.ReactNode; 59 | }) { 60 | return ( 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 |
71 |
72 | {children} 73 |
74 | 75 | 76 | 77 | ); 78 | } -------------------------------------------------------------------------------- /components/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import Link from 'next/link'; 5 | 6 | export default function Dashboard() { 7 | return ( 8 |
9 |
10 |
11 |
12 |
13 |

14 | 15 | New Chat 16 |

17 |

Start a new conversation with ChatGPT Pro.

18 | 19 | Start Chat 20 | 21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |

29 | 30 | Recent Chats 31 |

32 |

View and continue your recent conversations.

33 | 34 | View History 35 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 |

44 | 45 | Settings 46 |

47 |

Manage your account and preferences.

48 | 49 | Open Settings 50 | 51 |
52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 |

Quick Stats

61 |
62 |
63 |
64 | 65 |
66 |
Total Chats
67 |
24
68 |
69 |
70 |
71 |
72 |
73 | 74 |
75 |
Usage Time
76 |
3.5 hrs
77 |
78 |
79 |
80 |
81 |
82 | 83 |
84 |
Subscription
85 |
Pro
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | ); 96 | } -------------------------------------------------------------------------------- /app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { getServerSession } from 'next-auth'; 3 | import { authOptions } from '../auth/[...nextauth]/route'; 4 | import { prisma } from '@/lib/prisma'; 5 | import { OpenAI } from 'openai'; 6 | import { TextProcessor } from '@/lib/textProcessor'; 7 | import type { User } from '@prisma/client'; 8 | 9 | const openai = new OpenAI({ 10 | apiKey: process.env.OPENAI_API_KEY, 11 | }); 12 | 13 | export async function POST(req: Request) { 14 | try { 15 | const session = await getServerSession(authOptions); 16 | if (!session?.user?.email) { 17 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 18 | } 19 | 20 | const { message } = await req.json(); 21 | const user = await prisma.user.findUnique({ 22 | where: { email: session.user.email }, 23 | include: { subscription: true }, 24 | }); 25 | 26 | if (!user) { 27 | return NextResponse.json({ error: 'User not found' }, { status: 404 }); 28 | } 29 | 30 | // Check if user has credits or active subscription 31 | if (user.credits <= 0 && user.subscription?.status !== 'active') { 32 | return NextResponse.json( 33 | { error: 'No credits remaining. Please subscribe to continue.' }, 34 | { status: 403 } 35 | ); 36 | } 37 | 38 | // Generate response using OpenAI 39 | const completion = await openai.chat.completions.create({ 40 | model: 'gpt-3.5-turbo', 41 | messages: [ 42 | { 43 | role: 'system', 44 | content: `You are a helpful AI assistant. Your responses should be natural and human-like. 45 | Occasionally include minor grammatical variations and natural language patterns that make the text feel more authentic. 46 | Avoid perfect grammar and structure all the time.`, 47 | }, 48 | { role: 'user', content: message }, 49 | ], 50 | temperature: 0.8, 51 | max_tokens: 1000, 52 | }); 53 | 54 | let response = completion.choices[0].message.content || ''; 55 | 56 | // Process the text to make it more undetectable 57 | response = await TextProcessor.processText(response); 58 | 59 | // Calculate detectability score 60 | const detectability = calculateDetectability(response); 61 | 62 | // Save the prompt and response 63 | await prisma.prompt.create({ 64 | data: { 65 | content: message, 66 | response: response, 67 | userId: user.id, 68 | detectability, 69 | }, 70 | }); 71 | 72 | // Decrement credits if not subscribed 73 | if (user.subscription?.status !== 'active') { 74 | await prisma.user.update({ 75 | where: { id: user.id }, 76 | data: { credits: { decrement: 1 } }, 77 | }); 78 | } 79 | 80 | return NextResponse.json({ 81 | response, 82 | detectability, 83 | remainingCredits: user.subscription?.status === 'active' ? 'unlimited' : user.credits - 1, 84 | }); 85 | } catch (error) { 86 | console.error('Error:', error); 87 | return NextResponse.json( 88 | { error: 'Internal server error' }, 89 | { status: 500 } 90 | ); 91 | } 92 | } 93 | 94 | function calculateDetectability(text: string | null): number { 95 | if (!text) return 1; 96 | 97 | // This is a simplified version. In a real implementation, you would use 98 | // more sophisticated algorithms to analyze the text's characteristics 99 | const factors = [ 100 | // Check for perfect grammar (lower score is better) 101 | hasPerfectGrammar(text) ? 0.8 : 0.2, 102 | // Check for natural language patterns 103 | hasNaturalPatterns(text) ? 0.1 : 0.7, 104 | // Check for variation in sentence structure 105 | hasVariedStructure(text) ? 0.2 : 0.6, 106 | ]; 107 | 108 | return factors.reduce((a, b) => a + b) / factors.length; 109 | } 110 | 111 | function hasPerfectGrammar(text: string): boolean { 112 | // Implement grammar checking logic 113 | return false; 114 | } 115 | 116 | function hasNaturalPatterns(text: string): boolean { 117 | // Implement natural language pattern detection 118 | return true; 119 | } 120 | 121 | function hasVariedStructure(text: string): boolean { 122 | // Implement sentence structure variation analysis 123 | return true; 124 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { useSession } from 'next-auth/react'; 5 | import Dashboard from '@/components/Dashboard'; 6 | import LoginModal from '@/components/LoginModal'; 7 | import SubscriptionModal from '@/components/SubscriptionModal'; 8 | 9 | export default function Home() { 10 | const { data: session, status } = useSession(); 11 | const [showLoginModal, setShowLoginModal] = useState(false); 12 | const [showSubscriptionModal, setShowSubscriptionModal] = useState(false); 13 | 14 | if (status === 'loading') { 15 | return ( 16 |
17 |
18 | Loading... 19 |
20 |
21 | ); 22 | } 23 | 24 | return ( 25 |
26 |
27 | {session ? ( 28 | 29 | ) : ( 30 |
31 |
32 |

33 | Welcome to ChatGPT Pro 34 |

35 |

36 | Experience the next generation of AI conversation with undetectable text generation. 37 | Start with 3 free prompts and unlock unlimited access with our Pro subscription. 38 |

39 |
40 | 47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |

Undetectable Output

56 |

Advanced algorithms ensure your text remains undetectable by AI detectors.

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

Lightning Fast

67 |

Get instant responses powered by the latest AI technology.

68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 |

Secure & Private

78 |

Your conversations are encrypted and never stored or shared.

79 |
80 |
81 |
82 |
83 |
84 |
85 | )} 86 |
87 | 88 | {showLoginModal && ( 89 | setShowLoginModal(false)} /> 90 | )} 91 | 92 | {showSubscriptionModal && ( 93 | setShowSubscriptionModal(false)} /> 94 | )} 95 |
96 | ); 97 | } -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 | Next.js logo 15 |
    16 |
  1. 17 | Get started by editing{" "} 18 | 19 | src/app/page.tsx 20 | 21 | . 22 |
  2. 23 |
  3. 24 | Save and see your changes instantly. 25 |
  4. 26 |
27 | 28 | 53 |
54 | 101 |
102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /app/success/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect, useState } from 'react'; 4 | import { useRouter, useSearchParams } from 'next/navigation'; 5 | import Link from 'next/link'; 6 | 7 | export default function Success() { 8 | const router = useRouter(); 9 | const searchParams = useSearchParams(); 10 | const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); 11 | 12 | useEffect(() => { 13 | const sessionId = searchParams.get('session_id'); 14 | if (!sessionId) { 15 | setStatus('error'); 16 | return; 17 | } 18 | 19 | // Verify the subscription status 20 | const verifySubscription = async () => { 21 | try { 22 | const response = await fetch(`/api/verify-subscription?session_id=${sessionId}`); 23 | const data = await response.json(); 24 | 25 | if (response.ok) { 26 | setStatus('success'); 27 | } else { 28 | setStatus('error'); 29 | } 30 | } catch (error) { 31 | console.error('Error:', error); 32 | setStatus('error'); 33 | } 34 | }; 35 | 36 | verifySubscription(); 37 | }, [searchParams]); 38 | 39 | return ( 40 |
41 |
42 | {status === 'loading' && ( 43 |
44 |
45 |

Verifying subscription...

46 |
47 | )} 48 | 49 | {status === 'success' && ( 50 |
51 |
52 | 58 | 64 | 65 |
66 |

67 | Subscription Successful! 68 |

69 |

70 | Thank you for subscribing to ChatGPT Pro. You now have unlimited access to all features. 71 |

72 | 76 | Start Chatting 77 | 78 |
79 | )} 80 | 81 | {status === 'error' && ( 82 |
83 |
84 | 90 | 96 | 97 |
98 |

99 | Something went wrong 100 |

101 |

102 | We couldn't verify your subscription. Please contact support if the issue persists. 103 |

104 | 108 | Return Home 109 | 110 |
111 | )} 112 |
113 |
114 | ); 115 | } -------------------------------------------------------------------------------- /app/history/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import { useSession } from 'next-auth/react'; 5 | import { useRouter } from 'next/navigation'; 6 | 7 | interface ChatHistory { 8 | id: string; 9 | content: string; 10 | response: string; 11 | createdAt: string; 12 | detectability: number; 13 | } 14 | 15 | export default function History() { 16 | const { data: session, status } = useSession(); 17 | const router = useRouter(); 18 | const [history, setHistory] = useState([]); 19 | const [loading, setLoading] = useState(true); 20 | const [error, setError] = useState(''); 21 | 22 | useEffect(() => { 23 | if (status === 'unauthenticated') { 24 | router.push('/'); 25 | return; 26 | } 27 | 28 | if (status === 'authenticated') { 29 | fetchHistory(); 30 | } 31 | }, [status]); 32 | 33 | const fetchHistory = async () => { 34 | try { 35 | const response = await fetch('/api/chat/history'); 36 | if (!response.ok) throw new Error('Failed to fetch history'); 37 | const data = await response.json(); 38 | setHistory(data); 39 | } catch (err) { 40 | setError('Failed to load chat history'); 41 | console.error(err); 42 | } finally { 43 | setLoading(false); 44 | } 45 | }; 46 | 47 | if (loading) { 48 | return ( 49 |
50 |
51 | Loading... 52 |
53 |
54 | ); 55 | } 56 | 57 | return ( 58 |
59 |
60 |
61 |
62 |

Chat History

63 | 70 |
71 | 72 | {error && ( 73 |
74 | {error} 75 |
76 | )} 77 | 78 | {history.length === 0 ? ( 79 |
80 |
81 | 82 |

No Chat History

83 |

Start a new chat to begin your conversation journey.

84 | 90 |
91 |
92 | ) : ( 93 |
94 | {history.map((chat) => ( 95 |
96 |
97 |
98 |
99 |
100 |

{chat.content.substring(0, 100)}...

101 |
102 | {new Date(chat.createdAt).toLocaleDateString()} • 103 | Detectability: {(chat.detectability * 100).toFixed(1)}% 104 |
105 |
106 | 112 |
113 |

114 | {chat.response.substring(0, 200)}... 115 |

116 |
117 |
118 |
119 | ))} 120 |
121 | )} 122 |
123 |
124 |
125 | ); 126 | } -------------------------------------------------------------------------------- /app/auth/signin/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import { signIn } from 'next-auth/react'; 5 | import { useRouter, useSearchParams } from 'next/navigation'; 6 | import Link from 'next/link'; 7 | 8 | export default function SignIn() { 9 | const router = useRouter(); 10 | const searchParams = useSearchParams(); 11 | const [email, setEmail] = useState(''); 12 | const [password, setPassword] = useState(''); 13 | const [error, setError] = useState(''); 14 | const [success, setSuccess] = useState(''); 15 | const [isLoading, setIsLoading] = useState(false); 16 | 17 | useEffect(() => { 18 | if (searchParams.get('registered') === 'true') { 19 | setSuccess('Registration successful! Please sign in with your credentials.'); 20 | } 21 | }, [searchParams]); 22 | 23 | const handleSubmit = async (e: React.FormEvent) => { 24 | e.preventDefault(); 25 | setIsLoading(true); 26 | setError(''); 27 | 28 | try { 29 | const result = await signIn('credentials', { 30 | email, 31 | password, 32 | redirect: false, 33 | }); 34 | 35 | if (result?.error) { 36 | setError(result.error); 37 | } else { 38 | router.push('/'); 39 | } 40 | } catch (error) { 41 | setError('An error occurred. Please try again.'); 42 | } finally { 43 | setIsLoading(false); 44 | } 45 | }; 46 | 47 | return ( 48 |
49 |
50 |
51 |
52 |
53 |
54 |

Sign In

55 |

56 | Welcome back to ChatGPT Pro 57 |

58 | 59 |
60 | {error && ( 61 |
62 | {error} 63 |
64 | )} 65 | 66 | {success && ( 67 |
68 | {success} 69 |
70 | )} 71 | 72 |
73 | 76 | setEmail(e.target.value)} 82 | placeholder="Enter your email" 83 | required 84 | /> 85 |
86 | 87 |
88 | 91 | setPassword(e.target.value)} 97 | placeholder="Enter your password" 98 | required 99 | /> 100 |
101 | 102 | 118 | 119 |
120 | 124 | Don't have an account? Sign up 125 | 126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | ); 135 | } -------------------------------------------------------------------------------- /components/ChatInterface.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useRef, useEffect } from 'react'; 4 | import { useSession } from 'next-auth/react'; 5 | import FileUpload from './FileUpload'; 6 | 7 | interface Message { 8 | role: 'user' | 'assistant'; 9 | content: string; 10 | detectability?: number; 11 | } 12 | 13 | export default function ChatInterface() { 14 | const { data: session } = useSession(); 15 | const [messages, setMessages] = useState([]); 16 | const [input, setInput] = useState(''); 17 | const [isLoading, setIsLoading] = useState(false); 18 | const [hasSubscription, setHasSubscription] = useState(false); 19 | const messagesEndRef = useRef(null); 20 | 21 | const scrollToBottom = () => { 22 | messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); 23 | }; 24 | 25 | useEffect(() => { 26 | scrollToBottom(); 27 | }, [messages]); 28 | 29 | useEffect(() => { 30 | // Fetch subscription status when component mounts 31 | if (session) { 32 | fetch('/api/user/settings') 33 | .then(res => res.json()) 34 | .then(data => { 35 | setHasSubscription(data.subscription?.status === 'active'); 36 | }) 37 | .catch(err => console.error('Error fetching subscription status:', err)); 38 | } 39 | }, [session]); 40 | 41 | const handleSubmit = async (e: React.FormEvent) => { 42 | e.preventDefault(); 43 | if (!input.trim() || isLoading) return; 44 | 45 | const userMessage: Message = { role: 'user', content: input }; 46 | setMessages((prev) => [...prev, userMessage]); 47 | setInput(''); 48 | setIsLoading(true); 49 | 50 | try { 51 | const response = await fetch('/api/chat', { 52 | method: 'POST', 53 | headers: { 54 | 'Content-Type': 'application/json', 55 | }, 56 | body: JSON.stringify({ 57 | message: input, 58 | userId: session?.user?.email, 59 | }), 60 | }); 61 | 62 | const data = await response.json(); 63 | 64 | if (data.error) { 65 | throw new Error(data.error); 66 | } 67 | 68 | const assistantMessage: Message = { 69 | role: 'assistant', 70 | content: data.response, 71 | detectability: data.detectability, 72 | }; 73 | 74 | setMessages((prev) => [...prev, assistantMessage]); 75 | } catch (error) { 76 | console.error('Error:', error); 77 | setMessages((prev) => [ 78 | ...prev, 79 | { 80 | role: 'assistant', 81 | content: 'Sorry, there was an error processing your request.', 82 | }, 83 | ]); 84 | } finally { 85 | setIsLoading(false); 86 | } 87 | }; 88 | 89 | return ( 90 |
91 |
92 | {messages.length === 0 ? ( 93 |
94 |
95 |

Welcome to ChatGPT Pro

96 |

97 | Start a conversation with our advanced AI assistant. 98 |

99 |
100 |
101 | ) : ( 102 | messages.map((message, index) => ( 103 |
109 | {message.role === 'assistant' && ( 110 |
AI
111 | )} 112 |
113 |

{message.content}

114 |
115 |
116 | )) 117 | )} 118 | {isLoading && ( 119 |
120 |
AI
121 |
122 |
123 | 124 | 125 | 126 |
127 |
128 |
129 | )} 130 |
131 |
132 | 133 |
134 |
135 |
136 | 137 | setInput(e.target.value)} 141 | placeholder="Type your message..." 142 | disabled={isLoading} 143 | /> 144 | 151 |
152 |
153 |
154 |
155 | ); 156 | } -------------------------------------------------------------------------------- /app/auth/signup/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { useRouter } from 'next/navigation'; 5 | import Link from 'next/link'; 6 | 7 | export default function SignUp() { 8 | const router = useRouter(); 9 | const [name, setName] = useState(''); 10 | const [email, setEmail] = useState(''); 11 | const [password, setPassword] = useState(''); 12 | const [error, setError] = useState(''); 13 | const [success, setSuccess] = useState(''); 14 | const [isLoading, setIsLoading] = useState(false); 15 | 16 | const handleSubmit = async (e: React.FormEvent) => { 17 | e.preventDefault(); 18 | setIsLoading(true); 19 | setError(''); 20 | setSuccess(''); 21 | 22 | try { 23 | const response = await fetch('/api/auth/signup', { 24 | method: 'POST', 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | }, 28 | body: JSON.stringify({ 29 | name, 30 | email, 31 | password, 32 | }), 33 | }); 34 | 35 | const data = await response.json(); 36 | 37 | if (response.ok) { 38 | setSuccess('Registration successful! Please sign in with your credentials.'); 39 | // Wait 2 seconds before redirecting to show the success message 40 | setTimeout(() => { 41 | router.push('/auth/signin?registered=true'); 42 | }, 2000); 43 | } else { 44 | setError(data.error || 'An error occurred during sign up'); 45 | } 46 | } catch (error) { 47 | setError('An error occurred. Please try again.'); 48 | } finally { 49 | setIsLoading(false); 50 | } 51 | }; 52 | 53 | return ( 54 |
55 |
56 |
57 |
58 |
59 |
60 |

Create Account

61 |

62 | Join ChatGPT Pro and start your journey 63 |

64 | 65 |
66 | {error && ( 67 |
68 | {error} 69 |
70 | )} 71 | 72 | {success && ( 73 |
74 | {success} 75 |
76 | )} 77 | 78 |
79 | 82 | setName(e.target.value)} 88 | placeholder="Enter your name" 89 | required 90 | /> 91 |
92 | 93 |
94 | 97 | setEmail(e.target.value)} 103 | placeholder="Enter your email" 104 | required 105 | /> 106 |
107 | 108 |
109 | 112 | setPassword(e.target.value)} 118 | placeholder="Create a password" 119 | required 120 | /> 121 |
122 | 123 | 139 | 140 |
141 | 145 | Already have an account? Sign in 146 | 147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | ); 156 | } -------------------------------------------------------------------------------- /components/SubscriptionModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { loadStripe } from '@stripe/stripe-js'; 5 | 6 | interface SubscriptionModalProps { 7 | onClose: () => void; 8 | } 9 | 10 | export default function SubscriptionModal({ onClose }: SubscriptionModalProps) { 11 | const [isLoading, setIsLoading] = useState(false); 12 | 13 | const handleSubscribe = async () => { 14 | setIsLoading(true); 15 | try { 16 | const response = await fetch('/api/create-checkout-session', { 17 | method: 'POST', 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | }, 21 | }); 22 | 23 | const { sessionId } = await response.json(); 24 | const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); 25 | await stripe?.redirectToCheckout({ sessionId }); 26 | } catch (error) { 27 | console.error('Error:', error); 28 | } finally { 29 | setIsLoading(false); 30 | } 31 | }; 32 | 33 | return ( 34 |
35 |
36 |
37 |
38 |
Upgrade to Pro
39 | 45 |
46 |
47 |
48 |

49 | Unlock the full potential of ChatGPT Pro with our premium features 50 |

51 |
52 | 53 |
54 |
55 |
56 |
57 |
Free Trial
58 |

$0/month

59 |

Perfect for testing the waters

60 |
    61 |
  • 62 | 63 | Basic chat functionality 64 |
  • 65 |
  • 66 | 67 | 5 messages per day 68 |
  • 69 |
  • 70 | 71 | Standard response time 72 |
  • 73 |
74 | 77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
Pro Plan
85 |

$20/month

86 |

For power users and professionals

87 |
    88 |
  • 89 | 90 | Unlimited messages 91 |
  • 92 |
  • 93 | 94 | Priority response time 95 |
  • 96 |
  • 97 | 98 | Advanced features access 99 |
  • 100 |
  • 101 | 102 | 24/7 priority support 103 |
  • 104 |
105 | 121 |
122 |
123 |
124 |
125 | 126 |
127 |

128 | 129 | Secure payment powered by Stripe 130 |

131 |
132 |
133 |
134 |
135 |
136 | ); 137 | } -------------------------------------------------------------------------------- /components/LoginModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { signIn } from 'next-auth/react'; 5 | 6 | interface LoginModalProps { 7 | onClose: () => void; 8 | } 9 | 10 | export default function LoginModal({ onClose }: LoginModalProps) { 11 | const [email, setEmail] = useState(''); 12 | const [password, setPassword] = useState(''); 13 | const [isLoading, setIsLoading] = useState(false); 14 | const [error, setError] = useState(''); 15 | const [isSignUp, setIsSignUp] = useState(false); 16 | const [name, setName] = useState(''); 17 | 18 | const handleSubmit = async (e: React.FormEvent) => { 19 | e.preventDefault(); 20 | setIsLoading(true); 21 | setError(''); 22 | 23 | try { 24 | if (isSignUp) { 25 | // Handle signup 26 | const response = await fetch('/api/auth/signup', { 27 | method: 'POST', 28 | headers: { 29 | 'Content-Type': 'application/json', 30 | }, 31 | body: JSON.stringify({ 32 | name, 33 | email, 34 | password, 35 | }), 36 | }); 37 | 38 | const data = await response.json(); 39 | 40 | if (response.ok) { 41 | // Add a small delay before signing in 42 | await new Promise(resolve => setTimeout(resolve, 1000)); 43 | 44 | // Sign in after successful signup 45 | const result = await signIn('credentials', { 46 | redirect: false, 47 | email, 48 | password, 49 | }); 50 | 51 | if (result?.error) { 52 | setError('Error signing in after registration. Please try again.'); 53 | } else { 54 | onClose(); 55 | } 56 | } else { 57 | setError(data.error || 'An error occurred during sign up'); 58 | } 59 | } else { 60 | // Handle sign in 61 | const result = await signIn('credentials', { 62 | redirect: false, 63 | email, 64 | password, 65 | }); 66 | 67 | if (result?.error) { 68 | setError(result.error); 69 | } else { 70 | onClose(); 71 | } 72 | } 73 | } catch (err) { 74 | setError('An unexpected error occurred'); 75 | } finally { 76 | setIsLoading(false); 77 | } 78 | }; 79 | 80 | return ( 81 |
82 |
83 |
84 |
85 |
86 | {isSignUp ? 'Create Account' : 'Welcome Back'} 87 |
88 | 94 |
95 |
96 |

97 | {isSignUp ? 'Start your journey with ChatGPT Pro' : 'Sign in to continue your conversation'} 98 |

99 | 100 |
101 |
102 | 105 | setName(e.target.value)} 111 | placeholder="Enter your name" 112 | required 113 | /> 114 |
115 | 116 |
117 | 120 | setEmail(e.target.value)} 126 | placeholder="Enter your email" 127 | required 128 | /> 129 |
130 | 131 |
132 | 135 | setPassword(e.target.value)} 141 | placeholder="Enter your password" 142 | required 143 | /> 144 |
145 | 146 | {error && ( 147 |
148 | {error} 149 |
150 | )} 151 | 152 | 168 | 169 |
170 | 177 |
178 |
179 |
180 |
181 |
182 |
183 | ); 184 | } -------------------------------------------------------------------------------- /app/settings/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import { useSession } from 'next-auth/react'; 5 | import { useRouter } from 'next/navigation'; 6 | import SubscriptionModal from '@/components/SubscriptionModal'; 7 | 8 | interface UserSettings { 9 | name: string; 10 | email: string; 11 | subscription: { 12 | status: string; 13 | stripeCustomerId: string; 14 | } | null; 15 | credits: number; 16 | } 17 | 18 | export default function Settings() { 19 | const { data: session, status } = useSession(); 20 | const router = useRouter(); 21 | const [settings, setSettings] = useState(null); 22 | const [loading, setLoading] = useState(true); 23 | const [error, setError] = useState(''); 24 | const [name, setName] = useState(''); 25 | const [updateSuccess, setUpdateSuccess] = useState(false); 26 | const [showSubscriptionModal, setShowSubscriptionModal] = useState(false); 27 | const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); 28 | const [deleteLoading, setDeleteLoading] = useState(false); 29 | 30 | useEffect(() => { 31 | if (status === 'unauthenticated') { 32 | router.push('/'); 33 | return; 34 | } 35 | 36 | if (status === 'authenticated') { 37 | fetchSettings(); 38 | } 39 | }, [status]); 40 | 41 | const fetchSettings = async () => { 42 | try { 43 | const response = await fetch('/api/user/settings'); 44 | if (!response.ok) throw new Error('Failed to fetch settings'); 45 | const data = await response.json(); 46 | setSettings(data); 47 | setName(data.name || ''); 48 | } catch (err) { 49 | setError('Failed to load settings'); 50 | console.error(err); 51 | } finally { 52 | setLoading(false); 53 | } 54 | }; 55 | 56 | const handleUpdateProfile = async (e: React.FormEvent) => { 57 | e.preventDefault(); 58 | setError(''); 59 | setUpdateSuccess(false); 60 | 61 | try { 62 | const response = await fetch('/api/user/settings', { 63 | method: 'PUT', 64 | headers: { 'Content-Type': 'application/json' }, 65 | body: JSON.stringify({ name }), 66 | }); 67 | 68 | if (!response.ok) throw new Error('Failed to update profile'); 69 | setUpdateSuccess(true); 70 | } catch (err) { 71 | setError('Failed to update profile'); 72 | console.error(err); 73 | } 74 | }; 75 | 76 | const handleManageSubscription = async () => { 77 | try { 78 | const response = await fetch('/api/create-portal-session', { 79 | method: 'POST', 80 | }); 81 | 82 | if (!response.ok) throw new Error('Failed to create portal session'); 83 | const { url } = await response.json(); 84 | window.location.href = url; 85 | } catch (err) { 86 | setError('Failed to open subscription portal'); 87 | console.error(err); 88 | } 89 | }; 90 | 91 | const handleDeleteAccount = async () => { 92 | setDeleteLoading(true); 93 | setError(''); 94 | 95 | try { 96 | const response = await fetch('/api/user/delete', { 97 | method: 'DELETE', 98 | }); 99 | 100 | if (!response.ok) throw new Error('Failed to delete account'); 101 | 102 | // Sign out and redirect to home page 103 | router.push('/api/auth/signout?callbackUrl=/'); 104 | } catch (err) { 105 | setError('Failed to delete account. Please try again.'); 106 | setDeleteLoading(false); 107 | } 108 | }; 109 | 110 | if (loading) { 111 | return ( 112 |
113 |
114 | Loading... 115 |
116 |
117 | ); 118 | } 119 | 120 | return ( 121 |
122 |
123 |
124 |

Settings

125 | 126 | {error && ( 127 |
128 | {error} 129 |
130 | )} 131 | 132 | {updateSuccess && ( 133 |
134 | Profile updated successfully! 135 |
136 | )} 137 | 138 |
139 |
140 |

Profile Information

141 |
142 |
143 | 144 | setName(e.target.value)} 150 | placeholder="Enter your name" 151 | /> 152 |
153 |
154 | 155 | 162 |
163 | 166 |
167 |
168 |
169 | 170 |
171 |
172 |
173 |

Subscription

174 | 175 | {settings?.subscription?.status === 'active' ? 'Active' : 'Inactive'} 176 | 177 |
178 |

179 | {settings?.subscription?.status === 'active' 180 | ? 'You currently have an active Pro subscription.' 181 | : `You have ${settings?.credits} credits remaining. Upgrade to Pro for unlimited access.`} 182 |

183 | 193 |
194 |
195 | 196 |
197 |
198 |

Danger Zone

199 |

200 | Once you delete your account, there is no going back. Please be certain. 201 |

202 | 209 |
210 |
211 |
212 |
213 | 214 | {showDeleteConfirm && ( 215 |
216 |
217 |
218 |
219 |
Delete Account
220 | 225 |
226 |
227 |

Are you sure you want to delete your account? This action cannot be undone.

228 |
229 |
230 | 237 | 245 |
246 |
247 |
248 |
249 | )} 250 | 251 | {showSubscriptionModal && ( 252 | setShowSubscriptionModal(false)} /> 253 | )} 254 |
255 | ); 256 | } -------------------------------------------------------------------------------- /lib/textProcessor.ts: -------------------------------------------------------------------------------- 1 | interface TextAnalysis { 2 | readability: number; 3 | naturalness: number; 4 | variation: number; 5 | personality: number; 6 | grammar: number; 7 | } 8 | 9 | interface GrammarVariation { 10 | pattern: RegExp; 11 | replacement: string | ((match: string, ...args: any[]) => string); 12 | } 13 | 14 | export class TextProcessor { 15 | private static readonly PERSONALITY_TRAITS = [ 16 | 'casual', 'professional', 'academic', 'creative', 'technical' 17 | ]; 18 | 19 | private static readonly GRAMMAR_VARIATIONS: GrammarVariation[] = [ 20 | // Sentence structure variations 21 | { pattern: /\.\s+[A-Z]/g, replacement: '. ' }, 22 | { pattern: /,\s+[a-z]/g, replacement: ', ' }, 23 | // Natural language patterns 24 | { 25 | pattern: /\b(I|we|you|they)\b/g, 26 | replacement: (match: string) => Math.random() > 0.9 ? match.toLowerCase() : match 27 | }, 28 | // Common human writing patterns 29 | { 30 | pattern: /\b(very|really|quite|somewhat)\b/g, 31 | replacement: () => Math.random() > 0.8 ? '' : 'very' 32 | }, 33 | ]; 34 | 35 | public static async processText(text: string): Promise { 36 | // 1. Initial text analysis 37 | const analysis = this.analyzeText(text); 38 | 39 | // 2. Apply natural language variations 40 | let processedText = this.applyNaturalVariations(text); 41 | 42 | // 3. Add controlled imperfections 43 | processedText = this.addControlledImperfections(processedText); 44 | 45 | // 4. Adjust personality based on context 46 | processedText = this.adjustPersonality(processedText, analysis); 47 | 48 | // 5. Final polish 49 | processedText = this.finalPolish(processedText); 50 | 51 | return processedText; 52 | } 53 | 54 | private static analyzeText(text: string): TextAnalysis { 55 | return { 56 | readability: this.calculateReadability(text), 57 | naturalness: this.calculateNaturalness(text), 58 | variation: this.calculateVariation(text), 59 | personality: this.calculatePersonality(text), 60 | grammar: this.calculateGrammarScore(text) 61 | }; 62 | } 63 | 64 | private static applyNaturalVariations(text: string): string { 65 | let result = text; 66 | 67 | // Apply grammar variations with proper typing 68 | for (const variation of this.GRAMMAR_VARIATIONS) { 69 | if (typeof variation.replacement === 'string') { 70 | result = result.replace(variation.pattern, variation.replacement); 71 | } else { 72 | result = result.replace(variation.pattern, variation.replacement); 73 | } 74 | } 75 | 76 | // Add natural pauses and transitions 77 | result = this.addNaturalPauses(result); 78 | 79 | return result; 80 | } 81 | 82 | private static addControlledImperfections(text: string): string { 83 | const words = text.split(' '); 84 | const result = words.map((word, index) => { 85 | // Occasionally add minor typos (1% chance) 86 | if (Math.random() < 0.01 && word.length > 3) { 87 | const pos = Math.floor(Math.random() * (word.length - 2)) + 1; 88 | return word.slice(0, pos) + word[pos + 1] + word[pos] + word.slice(pos + 2); 89 | } 90 | return word; 91 | }).join(' '); 92 | 93 | // Occasionally add filler words (2% chance) 94 | if (Math.random() < 0.02) { 95 | const fillers = ['um', 'well', 'you know', 'like']; 96 | const filler = fillers[Math.floor(Math.random() * fillers.length)]; 97 | const sentences = result.split('.'); 98 | const insertPos = Math.floor(Math.random() * sentences.length); 99 | sentences[insertPos] = sentences[insertPos].trim() + `, ${filler}, `; 100 | return sentences.join('.'); 101 | } 102 | 103 | return result; 104 | } 105 | 106 | private static adjustPersonality(text: string, analysis: TextAnalysis): string { 107 | const personalityTrait = this.PERSONALITY_TRAITS[ 108 | Math.floor(Math.random() * this.PERSONALITY_TRAITS.length) 109 | ]; 110 | 111 | // Adjust text based on selected personality 112 | switch (personalityTrait) { 113 | case 'casual': 114 | return this.makeMoreCasual(text); 115 | case 'professional': 116 | return this.makeMoreProfessional(text); 117 | case 'academic': 118 | return this.makeMoreAcademic(text); 119 | case 'creative': 120 | return this.makeMoreCreative(text); 121 | case 'technical': 122 | return this.makeMoreTechnical(text); 123 | default: 124 | return text; 125 | } 126 | } 127 | 128 | private static finalPolish(text: string): string { 129 | // Ensure the text maintains readability while being undetectable 130 | return text 131 | .replace(/\s+/g, ' ') 132 | .replace(/\s+([.,!?])/g, '$1') 133 | .trim(); 134 | } 135 | 136 | private static calculateReadability(text: string): number { 137 | // Implement Flesch-Kincaid readability score 138 | const words = text.split(' ').length; 139 | const sentences = text.split(/[.!?]+/).length; 140 | const syllables = text.toLowerCase().replace(/[^aeiouy]+/g, ' ').trim().split(/\s+/).length; 141 | 142 | return 206.835 - 1.015 * (words / sentences) - 84.6 * (syllables / words); 143 | } 144 | 145 | private static calculateNaturalness(text: string): number { 146 | // Analyze natural language patterns 147 | const patterns = [ 148 | /\b(um|uh|well|you know|like)\b/g, 149 | /[.!?]\s+[A-Z]/g, 150 | /,\s+[a-z]/g 151 | ]; 152 | 153 | let score = 0; 154 | patterns.forEach(pattern => { 155 | const matches = text.match(pattern); 156 | if (matches) score += matches.length; 157 | }); 158 | 159 | return score / patterns.length; 160 | } 161 | 162 | private static calculateVariation(text: string): number { 163 | // Calculate sentence structure variation 164 | const sentences = text.split(/[.!?]+/); 165 | const lengths = sentences.map(s => s.trim().split(' ').length); 166 | const avgLength = lengths.reduce((a, b) => a + b, 0) / lengths.length; 167 | const variance = lengths.reduce((a, b) => a + Math.pow(b - avgLength, 2), 0) / lengths.length; 168 | 169 | return Math.min(variance / 100, 1); 170 | } 171 | 172 | private static calculatePersonality(text: string): number { 173 | // Analyze personality traits in text 174 | const traits = { 175 | casual: /\b(hey|hi|cool|awesome|great)\b/gi, 176 | professional: /\b(regards|sincerely|best|kind|respectfully)\b/gi, 177 | academic: /\b(research|study|analysis|findings|conclusion)\b/gi, 178 | creative: /\b(imagine|creative|artistic|inspired|unique)\b/gi, 179 | technical: /\b(algorithm|function|parameter|variable|system)\b/gi 180 | }; 181 | 182 | let maxScore = 0; 183 | Object.values(traits).forEach(pattern => { 184 | const matches = text.match(pattern); 185 | if (matches) maxScore = Math.max(maxScore, matches.length); 186 | }); 187 | 188 | return maxScore / 5; 189 | } 190 | 191 | private static calculateGrammarScore(text: string): number { 192 | // Calculate grammar perfection score (lower is better for undetectability) 193 | const perfectPatterns = [ 194 | /[A-Z][a-z]+ [A-Z][a-z]+/g, // Proper nouns 195 | /[^.!?]+[.!?]\s+[A-Z]/g, // Perfect sentence structure 196 | /[^,]+,\s+[a-z]/g // Perfect comma usage 197 | ]; 198 | 199 | let score = 0; 200 | perfectPatterns.forEach(pattern => { 201 | const matches = text.match(pattern); 202 | if (matches) score += matches.length; 203 | }); 204 | 205 | return Math.min(score / 10, 1); 206 | } 207 | 208 | private static addNaturalPauses(text: string): string { 209 | const sentences = text.split('.'); 210 | return sentences.map((sentence, index) => { 211 | if (index < sentences.length - 1 && Math.random() < 0.3) { 212 | const pauses = ['...', ' - ', ', you see,', ', you know,']; 213 | const pause = pauses[Math.floor(Math.random() * pauses.length)]; 214 | return sentence.trim() + pause; 215 | } 216 | return sentence.trim(); 217 | }).join('. '); 218 | } 219 | 220 | private static makeMoreCasual(text: string): string { 221 | const casualPhrases = [ 222 | 'you know what I mean', 223 | 'to be honest', 224 | 'I think', 225 | 'in my opinion', 226 | 'basically' 227 | ]; 228 | 229 | return this.insertRandomPhrases(text, casualPhrases); 230 | } 231 | 232 | private static makeMoreProfessional(text: string): string { 233 | const professionalPhrases = [ 234 | 'in accordance with', 235 | 'with respect to', 236 | 'as per', 237 | 'in light of', 238 | 'with regard to' 239 | ]; 240 | 241 | return this.insertRandomPhrases(text, professionalPhrases); 242 | } 243 | 244 | private static makeMoreAcademic(text: string): string { 245 | const academicPhrases = [ 246 | 'according to research', 247 | 'studies have shown', 248 | 'the literature suggests', 249 | 'empirical evidence indicates', 250 | 'theoretical framework' 251 | ]; 252 | 253 | return this.insertRandomPhrases(text, academicPhrases); 254 | } 255 | 256 | private static makeMoreCreative(text: string): string { 257 | const creativePhrases = [ 258 | 'imagine if', 259 | 'picture this', 260 | 'what if', 261 | 'consider the possibility', 262 | 'envision' 263 | ]; 264 | 265 | return this.insertRandomPhrases(text, creativePhrases); 266 | } 267 | 268 | private static makeMoreTechnical(text: string): string { 269 | const technicalPhrases = [ 270 | 'the algorithm', 271 | 'the system', 272 | 'the process', 273 | 'the implementation', 274 | 'the architecture' 275 | ]; 276 | 277 | return this.insertRandomPhrases(text, technicalPhrases); 278 | } 279 | 280 | private static insertRandomPhrases(text: string, phrases: string[]): string { 281 | const sentences = text.split('.'); 282 | return sentences.map(sentence => { 283 | if (Math.random() < 0.3) { 284 | const phrase = phrases[Math.floor(Math.random() * phrases.length)]; 285 | return sentence.trim() + ', ' + phrase; 286 | } 287 | return sentence.trim(); 288 | }).join('. '); 289 | } 290 | } -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @import 'bootstrap/dist/css/bootstrap.min.css'; 2 | @import url('https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css'); 3 | 4 | :root { 5 | --ai-dark: #0a0b1e; 6 | --ai-accent: #1a1b3d; 7 | --ai-glow: rgba(99, 102, 241, 0.15); 8 | --ai-grid: rgba(99, 102, 241, 0.05); 9 | --neon-blue: #00f3ff; 10 | --neon-purple: #ff00ff; 11 | --neon-green: #00ff99; 12 | --primary-gradient: linear-gradient(to right, #9333ea, #3b82f6); 13 | --dark-gradient: linear-gradient(to bottom, #111827, #000000); 14 | --orb-1: radial-gradient(600px circle at 0% 0%, rgba(0, 243, 255, 0.1), transparent 70%); 15 | --orb-2: radial-gradient(800px circle at 100% 0%, rgba(255, 0, 255, 0.1), transparent 70%); 16 | --orb-3: radial-gradient(600px circle at 50% 100%, rgba(0, 255, 153, 0.1), transparent 70%); 17 | } 18 | 19 | * { 20 | font-family: 'Space Grotesk', sans-serif; 21 | } 22 | 23 | /* AI-themed background */ 24 | body { 25 | background-color: var(--ai-dark); 26 | position: relative; 27 | overflow-x: hidden; 28 | color: #fff; 29 | min-height: 100vh; 30 | } 31 | 32 | body::before { 33 | content: ''; 34 | position: fixed; 35 | top: 0; 36 | left: 0; 37 | right: 0; 38 | bottom: 0; 39 | background: 40 | var(--orb-1), 41 | var(--orb-2), 42 | var(--orb-3); 43 | animation: orbFloat 20s ease-in-out infinite alternate; 44 | z-index: -1; 45 | } 46 | 47 | @keyframes orbFloat { 48 | 0% { 49 | transform: translate(0, 0) scale(1); 50 | } 51 | 33% { 52 | transform: translate(-5%, 5%) scale(1.1); 53 | } 54 | 66% { 55 | transform: translate(5%, -5%) scale(0.9); 56 | } 57 | 100% { 58 | transform: translate(0, 0) scale(1); 59 | } 60 | } 61 | 62 | .animate-glow { 63 | animation: glow 4s ease-in-out infinite alternate; 64 | } 65 | 66 | @keyframes glow { 67 | 0% { 68 | box-shadow: 0 0 20px var(--neon-blue), 69 | 0 0 30px var(--neon-blue), 70 | 0 0 40px var(--neon-blue); 71 | } 72 | 50% { 73 | box-shadow: 0 0 20px var(--neon-purple), 74 | 0 0 30px var(--neon-purple), 75 | 0 0 40px var(--neon-purple); 76 | } 77 | 100% { 78 | box-shadow: 0 0 20px var(--neon-green), 79 | 0 0 30px var(--neon-green), 80 | 0 0 40px var(--neon-green); 81 | } 82 | } 83 | 84 | .glass-effect { 85 | background: rgba(255, 255, 255, 0.05); 86 | -webkit-backdrop-filter: blur(10px); 87 | backdrop-filter: blur(10px); 88 | border: 1px solid rgba(255, 255, 255, 0.1); 89 | box-shadow: 0 0 20px rgba(0, 243, 255, 0.2); 90 | } 91 | 92 | .text-gradient { 93 | background: var(--primary-gradient); 94 | -webkit-background-clip: text; 95 | -webkit-text-fill-color: transparent; 96 | background-clip: text; 97 | color: transparent; 98 | animation: flow 4s linear infinite; 99 | background-size: 200% auto; 100 | } 101 | 102 | @keyframes flow { 103 | 0% { background-position: 0% center; } 104 | 100% { background-position: 200% center; } 105 | } 106 | 107 | .hover-glow { 108 | transition: all 0.3s ease; 109 | position: relative; 110 | overflow: hidden; 111 | } 112 | 113 | .hover-glow::before { 114 | content: ''; 115 | position: absolute; 116 | top: -2px; 117 | left: -2px; 118 | right: -2px; 119 | bottom: -2px; 120 | z-index: -1; 121 | background: linear-gradient(45deg, 122 | var(--neon-blue), 123 | var(--neon-purple), 124 | var(--neon-green), 125 | var(--neon-blue) 126 | ); 127 | background-size: 400%; 128 | animation: borderGlow 4s linear infinite; 129 | filter: blur(10px); 130 | opacity: 0; 131 | transition: opacity 0.3s ease; 132 | } 133 | 134 | .hover-glow:hover::before { 135 | opacity: 1; 136 | } 137 | 138 | @keyframes borderGlow { 139 | 0% { background-position: 0 0; } 140 | 50% { background-position: 400% 0; } 141 | 100% { background-position: 0 0; } 142 | } 143 | 144 | .animate-float { 145 | animation: float 3s ease-in-out infinite; 146 | } 147 | 148 | @keyframes float { 149 | 0% { transform: translateY(0px); } 150 | 50% { transform: translateY(-10px); } 151 | 100% { transform: translateY(0px); } 152 | } 153 | 154 | /* Custom scrollbar */ 155 | ::-webkit-scrollbar { 156 | width: 8px; 157 | } 158 | 159 | ::-webkit-scrollbar-track { 160 | background: rgba(0, 0, 0, 0.2); 161 | box-shadow: inset 0 0 5px rgba(0, 243, 255, 0.1); 162 | } 163 | 164 | ::-webkit-scrollbar-thumb { 165 | background: linear-gradient( 166 | 45deg, 167 | var(--neon-blue), 168 | var(--neon-purple), 169 | var(--neon-green) 170 | ); 171 | border-radius: 4px; 172 | box-shadow: 0 0 10px rgba(0, 243, 255, 0.5); 173 | } 174 | 175 | ::-webkit-scrollbar-thumb:hover { 176 | background: linear-gradient( 177 | 45deg, 178 | var(--neon-green), 179 | var(--neon-blue), 180 | var(--neon-purple) 181 | ); 182 | } 183 | 184 | /* Bootstrap Dark Theme Overrides */ 185 | .btn-gradient { 186 | background: var(--primary-gradient); 187 | border: none; 188 | color: white; 189 | transition: opacity 0.2s; 190 | } 191 | 192 | .btn-gradient:hover { 193 | opacity: 0.9; 194 | color: white; 195 | } 196 | 197 | .btn-gradient:disabled { 198 | opacity: 0.65; 199 | cursor: not-allowed; 200 | } 201 | 202 | .bg-glass { 203 | background: rgba(255, 255, 255, 0.05); 204 | -webkit-backdrop-filter: blur(10px); 205 | backdrop-filter: blur(10px); 206 | border: 1px solid rgba(255, 255, 255, 0.1); 207 | } 208 | 209 | .form-control.dark { 210 | background: rgba(255, 255, 255, 0.05); 211 | border: 1px solid rgba(255, 255, 255, 0.1); 212 | color: white; 213 | } 214 | 215 | .form-control.dark:focus { 216 | background: rgba(255, 255, 255, 0.08); 217 | border-color: #9333ea; 218 | box-shadow: 0 0 0 0.25rem rgba(147, 51, 234, 0.25); 219 | color: white; 220 | } 221 | 222 | .form-control.dark::placeholder { 223 | color: rgba(255, 255, 255, 0.5); 224 | } 225 | 226 | .modal-dark { 227 | background: var(--dark-gradient); 228 | border: 1px solid rgba(255, 255, 255, 0.1); 229 | } 230 | 231 | .card-dark { 232 | background: rgba(255, 255, 255, 0.05); 233 | border: 1px solid rgba(255, 255, 255, 0.1); 234 | } 235 | 236 | .spinner-border-light { 237 | border-color: rgba(255, 255, 255, 0.25); 238 | border-right-color: white; 239 | } 240 | 241 | .text-light-50 { 242 | color: rgba(255, 255, 255, 0.5) !important; 243 | } 244 | 245 | /* Fix for modal backdrop */ 246 | .modal { 247 | background-color: rgba(0, 0, 0, 0.7); 248 | } 249 | 250 | /* Fix for button focus states */ 251 | .btn:focus { 252 | box-shadow: none; 253 | } 254 | 255 | .btn-close-white { 256 | filter: invert(1) grayscale(100%) brightness(200%); 257 | } 258 | 259 | /* Fix for navbar */ 260 | .navbar { 261 | height: 60px; 262 | padding: 0 24px !important; 263 | background: #0D0D0D !important; 264 | border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; 265 | } 266 | 267 | .navbar-brand { 268 | font-weight: 500; 269 | font-size: 1.25rem; 270 | color: #fff; 271 | text-decoration: none; 272 | } 273 | 274 | .navbar-brand:hover { 275 | color: #fff; 276 | } 277 | 278 | .upgrade-btn { 279 | background: #7C3AED !important; 280 | color: #fff !important; 281 | border: none !important; 282 | padding: 6px 12px !important; 283 | font-size: 0.875rem !important; 284 | border-radius: 4px !important; 285 | font-weight: 500 !important; 286 | } 287 | 288 | .upgrade-btn:hover { 289 | background: #6D28D9 !important; 290 | } 291 | 292 | /* Brilliant button styles */ 293 | .brilliant-button { 294 | position: relative; 295 | background: rgba(0, 243, 255, 0.1); 296 | border: none; 297 | padding: 1rem 2rem; 298 | color: var(--neon-blue); 299 | text-transform: uppercase; 300 | letter-spacing: 0.2em; 301 | overflow: hidden; 302 | transition: 0.5s; 303 | } 304 | 305 | .brilliant-button:hover { 306 | background: var(--neon-blue); 307 | color: #000; 308 | box-shadow: 0 0 20px var(--neon-blue); 309 | transition-delay: 0.5s; 310 | } 311 | 312 | .brilliant-button span { 313 | position: absolute; 314 | display: block; 315 | } 316 | 317 | .brilliant-button span:nth-child(1) { 318 | top: 0; 319 | left: -100%; 320 | width: 100%; 321 | height: 2px; 322 | background: linear-gradient(90deg, transparent, var(--neon-blue)); 323 | animation: btn-anim1 1s linear infinite; 324 | } 325 | 326 | @keyframes btn-anim1 { 327 | 0% { left: -100%; } 328 | 50%, 100% { left: 100%; } 329 | } 330 | 331 | /* Chat Interface Styles */ 332 | .bg-gradient { 333 | background: linear-gradient(to right, #9333ea, #3b82f6); 334 | } 335 | 336 | .btn-gradient { 337 | background: linear-gradient(to right, #9333ea, #3b82f6); 338 | border: none; 339 | color: white; 340 | } 341 | 342 | .btn-gradient:hover { 343 | opacity: 0.9; 344 | color: white; 345 | } 346 | 347 | .text-gradient { 348 | background: linear-gradient(to right, #9333ea, #3b82f6); 349 | -webkit-background-clip: text; 350 | background-clip: text; 351 | color: transparent; 352 | } 353 | 354 | .bg-gradient-purple-blue { 355 | background: linear-gradient(135deg, rgba(147, 51, 234, 0.1), rgba(59, 130, 246, 0.1)); 356 | } 357 | 358 | .border-purple { 359 | border-color: rgba(147, 51, 234, 0.3) !important; 360 | } 361 | 362 | .messages-container { 363 | height: calc(100vh - 13rem); 364 | } 365 | 366 | .typing-dot { 367 | width: 8px; 368 | height: 8px; 369 | animation: bounce 1s infinite; 370 | } 371 | 372 | .animation-delay-100 { 373 | animation-delay: 0.1s; 374 | } 375 | 376 | .animation-delay-200 { 377 | animation-delay: 0.2s; 378 | } 379 | 380 | @keyframes bounce { 381 | 0%, 100% { 382 | transform: translateY(0); 383 | } 384 | 50% { 385 | transform: translateY(-6px); 386 | } 387 | } 388 | 389 | .white-space-pre-wrap { 390 | white-space: pre-wrap; 391 | } 392 | 393 | /* Dark theme form controls */ 394 | .form-control { 395 | background-color: rgba(255, 255, 255, 0.05); 396 | border-color: rgba(255, 255, 255, 0.1); 397 | color: white; 398 | } 399 | 400 | .form-control:focus { 401 | background-color: rgba(255, 255, 255, 0.1); 402 | border-color: #9333ea; 403 | box-shadow: 0 0 0 0.25rem rgba(147, 51, 234, 0.25); 404 | color: white; 405 | } 406 | 407 | .form-control::placeholder { 408 | color: rgba(255, 255, 255, 0.5); 409 | } 410 | 411 | /* Custom scrollbar */ 412 | .messages-container::-webkit-scrollbar { 413 | width: 8px; 414 | } 415 | 416 | .messages-container::-webkit-scrollbar-track { 417 | background: rgba(255, 255, 255, 0.05); 418 | } 419 | 420 | .messages-container::-webkit-scrollbar-thumb { 421 | background: rgba(147, 51, 234, 0.5); 422 | border-radius: 4px; 423 | } 424 | 425 | .messages-container::-webkit-scrollbar-thumb:hover { 426 | background: rgba(147, 51, 234, 0.7); 427 | } 428 | 429 | /* Update chat container background */ 430 | .chat-container { 431 | background: rgba(10, 11, 30, 0.6); 432 | -webkit-backdrop-filter: blur(10px); 433 | backdrop-filter: blur(10px); 434 | border: 1px solid var(--ai-accent); 435 | border-radius: 12px; 436 | } 437 | 438 | /* Update message backgrounds */ 439 | .user-message { 440 | background: rgba(99, 102, 241, 0.1) !important; 441 | border: 1px solid rgba(99, 102, 241, 0.2) !important; 442 | } 443 | 444 | .assistant-message { 445 | background: rgba(10, 11, 30, 0.6) !important; 446 | border: 1px solid var(--ai-accent) !important; 447 | } 448 | 449 | /* Chat Window Styles */ 450 | .chat-window { 451 | position: fixed; 452 | top: 80px; 453 | left: 50%; 454 | transform: translateX(-50%); 455 | width: 800px; 456 | height: calc(100vh - 120px); 457 | background: rgba(82, 82, 92, 0.7); 458 | border-radius: 16px; 459 | display: flex; 460 | flex-direction: column; 461 | overflow: hidden; 462 | backdrop-filter: blur(10px); 463 | -webkit-backdrop-filter: blur(10px); 464 | margin: 20px auto; 465 | } 466 | 467 | .messages-area { 468 | flex: 1; 469 | overflow-y: auto; 470 | padding: 20px; 471 | } 472 | 473 | .message-container { 474 | display: flex; 475 | margin-bottom: 20px; 476 | padding: 0 20px; 477 | } 478 | 479 | .message-container.user { 480 | justify-content: flex-end; 481 | } 482 | 483 | .message-container.ai { 484 | justify-content: flex-start; 485 | } 486 | 487 | .message-bubble { 488 | background: rgba(64, 65, 79, 0.9); 489 | border-radius: 12px; 490 | padding: 12px 16px; 491 | max-width: 80%; 492 | position: relative; 493 | } 494 | 495 | .user .message-bubble { 496 | background: rgba(82, 82, 92, 0.9); 497 | } 498 | 499 | .ai-badge { 500 | position: absolute; 501 | left: 8px; 502 | top: -10px; 503 | background: linear-gradient(to right, #9333ea, #3b82f6); 504 | color: white; 505 | padding: 2px 8px; 506 | border-radius: 8px; 507 | font-size: 12px; 508 | font-weight: bold; 509 | } 510 | 511 | .message-bubble p { 512 | margin: 0; 513 | color: #fff; 514 | line-height: 1.5; 515 | } 516 | 517 | .input-area { 518 | padding: 20px; 519 | background: rgba(64, 65, 79, 0.9); 520 | border-top: 1px solid rgba(255, 255, 255, 0.1); 521 | } 522 | 523 | .input-container { 524 | display: flex; 525 | align-items: center; 526 | gap: 10px; 527 | background: rgba(255, 255, 255, 0.05); 528 | padding: 10px; 529 | border-radius: 8px; 530 | border: 1px solid rgba(255, 255, 255, 0.1); 531 | } 532 | 533 | .input-container input[type="file"] { 534 | width: 0.1px; 535 | height: 0.1px; 536 | opacity: 0; 537 | overflow: hidden; 538 | position: absolute; 539 | z-index: -1; 540 | } 541 | 542 | .input-container .btn-outline-primary { 543 | padding: 8px 16px; 544 | font-size: 14px; 545 | border-radius: 6px; 546 | display: flex; 547 | align-items: center; 548 | gap: 6px; 549 | transition: all 0.3s ease; 550 | } 551 | 552 | .input-container .btn-outline-primary:hover { 553 | background: var(--primary-gradient); 554 | border-color: transparent; 555 | color: white; 556 | } 557 | 558 | .input-container input[type="text"] { 559 | flex: 1; 560 | background: transparent; 561 | border: none; 562 | color: white; 563 | padding: 10px; 564 | font-size: 16px; 565 | outline: none; 566 | } 567 | 568 | .input-container input[type="text"]:focus { 569 | outline: none; 570 | } 571 | 572 | .input-container .send-button { 573 | padding: 8px 20px; 574 | border-radius: 6px; 575 | background: var(--primary-gradient); 576 | color: white; 577 | border: none; 578 | font-weight: 500; 579 | transition: opacity 0.3s ease; 580 | } 581 | 582 | .input-container .send-button:hover { 583 | opacity: 0.9; 584 | } 585 | 586 | .input-container .send-button:disabled { 587 | opacity: 0.5; 588 | cursor: not-allowed; 589 | } 590 | 591 | .typing-indicator { 592 | display: flex; 593 | gap: 4px; 594 | padding: 4px 8px; 595 | } 596 | 597 | .typing-indicator span { 598 | width: 6px; 599 | height: 6px; 600 | background: #fff; 601 | border-radius: 50%; 602 | animation: typing 1s infinite; 603 | } 604 | 605 | .typing-indicator span:nth-child(2) { animation-delay: 0.2s; } 606 | .typing-indicator span:nth-child(3) { animation-delay: 0.4s; } 607 | 608 | @keyframes typing { 609 | 0%, 100% { transform: translateY(0); } 610 | 50% { transform: translateY(-4px); } 611 | } 612 | 613 | .welcome-message { 614 | height: 100%; 615 | display: flex; 616 | align-items: center; 617 | justify-content: center; 618 | text-align: center; 619 | color: white; 620 | padding: 20px; 621 | } 622 | 623 | /* Logo Styles */ 624 | .text-gradient-primary { 625 | background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); 626 | -webkit-background-clip: text; 627 | background-clip: text; 628 | -webkit-text-fill-color: transparent; 629 | } 630 | 631 | .text-gradient-secondary { 632 | background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%); 633 | -webkit-background-clip: text; 634 | background-clip: text; 635 | -webkit-text-fill-color: transparent; 636 | } 637 | 638 | .bg-gradient-secondary { 639 | background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%); 640 | color: white; 641 | } 642 | 643 | .chat-bubble { 644 | position: relative; 645 | z-index: 1; 646 | } 647 | 648 | .glow-effect { 649 | position: absolute; 650 | top: 50%; 651 | left: 50%; 652 | transform: translate(-50%, -50%); 653 | width: 140%; 654 | height: 140%; 655 | background: radial-gradient(circle, rgba(99, 102, 241, 0.3) 0%, rgba(139, 92, 246, 0) 70%); 656 | border-radius: 50%; 657 | z-index: -1; 658 | animation: pulse 2s infinite; 659 | } 660 | 661 | @keyframes pulse { 662 | 0% { 663 | transform: translate(-50%, -50%) scale(1); 664 | opacity: 0.5; 665 | } 666 | 50% { 667 | transform: translate(-50%, -50%) scale(1.2); 668 | opacity: 0.3; 669 | } 670 | 100% { 671 | transform: translate(-50%, -50%) scale(1); 672 | opacity: 0.5; 673 | } 674 | } 675 | 676 | .logo-icon:hover .glow-effect { 677 | animation: pulse 1s infinite; 678 | } 679 | 680 | .btn-outline-glow { 681 | color: var(--neon-purple); 682 | border: 2px solid var(--neon-purple); 683 | background: transparent; 684 | transition: all 0.3s ease; 685 | } 686 | 687 | .btn-outline-glow:hover { 688 | background: var(--neon-purple); 689 | color: var(--ai-dark); 690 | box-shadow: 0 0 15px var(--neon-purple); 691 | } 692 | 693 | .logo-svg { 694 | filter: drop-shadow(0 0 10px rgba(255, 77, 106, 0.2)); 695 | transition: all 0.3s ease; 696 | } 697 | 698 | .logo-svg:hover { 699 | filter: drop-shadow(0 0 15px rgba(156, 63, 228, 0.4)); 700 | transform: scale(1.05); 701 | } 702 | 703 | .logo-svg path { 704 | stroke-dasharray: 500; 705 | stroke-dashoffset: 0; 706 | animation: flow 4s ease-in-out infinite; 707 | } 708 | 709 | @keyframes flow { 710 | 0% { 711 | stroke-dashoffset: 500; 712 | } 713 | 50% { 714 | stroke-dashoffset: 0; 715 | } 716 | 100% { 717 | stroke-dashoffset: -500; 718 | } 719 | } 720 | 721 | .logo-svg circle { 722 | animation: pulse 2s ease-in-out infinite; 723 | } 724 | 725 | @keyframes pulse { 726 | 0%, 100% { 727 | transform: scale(1); 728 | opacity: 0.8; 729 | } 730 | 50% { 731 | transform: scale(1.2); 732 | opacity: 1; 733 | } 734 | } 735 | 736 | .logo-icon { 737 | position: relative; 738 | display: inline-block; 739 | } 740 | 741 | .logo-icon::after { 742 | content: ''; 743 | position: absolute; 744 | top: 50%; 745 | left: 50%; 746 | width: 200%; 747 | height: 200%; 748 | transform: translate(-50%, -50%); 749 | background: radial-gradient(circle, 750 | rgba(156, 63, 228, 0.1) 0%, 751 | rgba(55, 223, 217, 0.05) 50%, 752 | transparent 70% 753 | ); 754 | pointer-events: none; 755 | z-index: -1; 756 | } --------------------------------------------------------------------------------