├── 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 |
11 |
12 |
13 | ChatGPT Pro
14 |
15 |
16 | {session?.user?.email && (
17 |
18 | {session.user.email}
19 |
20 | )}
21 | {session?.user && (
22 | signOut({ callbackUrl: '/' })}
24 | className="btn btn-outline-light btn-sm text-light opacity-75"
25 | >
26 | Logout
27 |
28 | )}
29 |
33 | Upgrade to Pro
34 |
35 |
36 |
37 |
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 |
53 |
54 | Upload File
55 |
62 |
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 |
34 |
35 |
36 | {session && (
37 |
38 | {session.user?.email}
39 |
40 | Logout
41 |
42 | {!hasSubscription && (
43 |
44 | Upgrade to Pro
45 |
46 | )}
47 |
48 | )}
49 |
50 |
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 | setShowLoginModal(true)}
42 | className="btn btn-gradient btn-lg"
43 | >
44 |
45 | Get Started
46 |
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 |
15 |
16 |
17 | Get started by editing{" "}
18 |
19 | src/app/page.tsx
20 |
21 | .
22 |
23 |
24 | Save and see your changes instantly.
25 |
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 |
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 |
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 | router.push('/chat')}
65 | className="btn btn-gradient"
66 | >
67 |
68 | New Chat
69 |
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 |
router.push('/chat')}
86 | className="btn btn-gradient"
87 | >
88 | Start Chatting
89 |
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 |
router.push(`/chat/${chat.id}`)}
108 | className="btn btn-sm btn-outline-primary"
109 | >
110 | View Chat
111 |
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 |
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 |
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 |
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 |
75 | Current Plan
76 |
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 |
110 | {isLoading ? (
111 |
112 |
113 | Loading...
114 |
115 |
Processing...
116 |
117 | ) : (
118 | 'Upgrade Now'
119 | )}
120 |
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 |
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 |
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 |
settings?.subscription?.status === 'active'
185 | ? handleManageSubscription()
186 | : setShowSubscriptionModal(true)}
187 | className="btn btn-gradient"
188 | >
189 | {settings?.subscription?.status === 'active'
190 | ? 'Manage Subscription'
191 | : 'Upgrade to Pro'}
192 |
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 |
setShowDeleteConfirm(true)}
204 | className="btn btn-outline-danger"
205 | disabled={deleteLoading}
206 | >
207 | {deleteLoading ? 'Deleting...' : 'Delete Account'}
208 |
209 |
210 |
211 |
212 |
213 |
214 | {showDeleteConfirm && (
215 |
216 |
217 |
218 |
219 |
Delete Account
220 | setShowDeleteConfirm(false)}
224 | >
225 |
226 |
227 |
Are you sure you want to delete your account? This action cannot be undone.
228 |
229 |
230 | setShowDeleteConfirm(false)}
234 | >
235 | Cancel
236 |
237 |
243 | {deleteLoading ? 'Deleting...' : 'Delete Account'}
244 |
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 | }
--------------------------------------------------------------------------------