├── logs.txt ├── app ├── config │ └── constants.ts ├── favicon.ico ├── api │ ├── meme-count │ │ └── route.ts │ ├── session │ │ └── route.ts │ └── chat │ │ └── route.ts ├── components │ ├── MemeProgress.tsx │ ├── Logo.tsx │ ├── GenerationInfo.tsx │ ├── StickyFooter.tsx │ ├── DebugUrlDisplay.tsx │ ├── MemeCounter.tsx │ ├── RecentlyGenerated.tsx │ ├── ImageChecker.tsx │ ├── Header.tsx │ └── MemeSkeleton.tsx ├── layout.tsx ├── globals.css ├── docs │ └── page.tsx ├── about │ └── page.tsx ├── faq │ └── page.tsx └── page.tsx ├── public ├── boom.gif ├── cat.gif ├── wait.png ├── parrot.gif ├── og-image.jpg ├── vercel.svg ├── window.svg ├── file.svg ├── favicon.svg ├── globe.svg └── next.svg ├── postcss.config.mjs ├── lib └── utils.ts ├── eslint.config.mjs ├── components.json ├── .gitignore ├── tsconfig.json ├── utils ├── llm-client.ts └── aisdk.ts ├── README.md ├── package.json ├── next.config.ts ├── tailwind.config.ts └── components └── ui └── shiny-button.tsx /logs.txt: -------------------------------------------------------------------------------- 1 | my command 2 | something im looking for 3 | another command 4 | -------------------------------------------------------------------------------- /app/config/constants.ts: -------------------------------------------------------------------------------- 1 | export const MAX_CONCURRENT_MEMES = Number(1); 2 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/brainrot/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/boom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/brainrot/HEAD/public/boom.gif -------------------------------------------------------------------------------- /public/cat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/brainrot/HEAD/public/cat.gif -------------------------------------------------------------------------------- /public/wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/brainrot/HEAD/public/wait.png -------------------------------------------------------------------------------- /public/parrot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/brainrot/HEAD/public/parrot.gif -------------------------------------------------------------------------------- /public/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/brainrot/HEAD/public/og-image.jpg -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /.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.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/api/meme-count/route.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from '@upstash/redis' 2 | import { NextResponse } from "next/server"; 3 | 4 | const redis = new Redis({ 5 | url: process.env.UPSTASH_REDIS_REST_URL, 6 | token: process.env.UPSTASH_REDIS_REST_TOKEN, 7 | }) 8 | 9 | export async function GET() { 10 | try { 11 | const count = await redis.get('meme-count') || 0; 12 | return NextResponse.json({ count }); 13 | } catch (error) { 14 | console.error('Redis GET error:', error); 15 | return NextResponse.json({ count: 0 }); 16 | } 17 | } 18 | 19 | export async function POST() { 20 | try { 21 | const count = await redis.incr('meme-count'); 22 | return NextResponse.json({ count }); 23 | } catch (error) { 24 | console.error('Redis POST error:', error); 25 | return NextResponse.json({ count: 0 }); 26 | } 27 | } -------------------------------------------------------------------------------- /app/components/MemeProgress.tsx: -------------------------------------------------------------------------------- 1 | interface MemeProgressProps { 2 | current: number; 3 | total: number; 4 | } 5 | 6 | export default function MemeProgress({ current, total }: MemeProgressProps) { 7 | const percentage = (current / total) * 100; 8 | 9 | return ( 10 |
11 |
12 | {current} / {total} memes generated 13 | {Math.round(percentage)}% 14 |
15 |
16 |
20 |
21 |
22 | ); 23 | } -------------------------------------------------------------------------------- /utils/llm-client.ts: -------------------------------------------------------------------------------- 1 | import { AvailableModel } from "@browserbasehq/stagehand"; 2 | // import { CoreMessage } from "ai"; 3 | 4 | type CoreMessage = { 5 | role: "system" | "user" | "assistant" | "tool"; 6 | content: string | Array<{ text?: string; image_url?: { url: string } }>; 7 | }; 8 | 9 | export interface CreateChatCompletionOptions { 10 | options: { 11 | messages: CoreMessage[]; 12 | response_model?: { 13 | schema: Record; 14 | }; 15 | tools?: { 16 | name: string; 17 | description: string; 18 | parameters: Record; 19 | }[]; 20 | }; 21 | } 22 | 23 | export abstract class LLMClient { 24 | protected modelId: AvailableModel; 25 | 26 | constructor(modelId: AvailableModel) { 27 | this.modelId = modelId; 28 | } 29 | 30 | abstract createChatCompletion(options: CreateChatCompletionOptions): Promise; 31 | } -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [brainrot.run](https://brainrot.run) 2 | 3 |
4 | brainrot 5 |
6 | 7 | ## Tech Stack: 8 | 9 | - [Browserbase](https://browserbase.com/) 10 | - [Stagehand](https://stagehand.dev/) 11 | - [Anthropic](https://www.anthropic.com/) 12 | - [Next.js](https://nextjs.org/) 13 | - [Vercel](https://vercel.com/) 14 | - [Upstash](https://upstash.com/) 15 | 16 | ## Getting Started 17 | 18 | Add environment variables: 19 | 20 | ```bash 21 | ANTHROPIC_API_KEY= 22 | BROWSERBASE_API_KEY= 23 | BROWSERBASE_PROJECT_ID= 24 | KV_URL= 25 | KV_REST_API_READ_ONLY_TOKEN= 26 | KV_REST_API_TOKEN= 27 | KV_REST_API_URL= 28 | UPSTASH_REST_URL= 29 | UPSTASH_REDIS_REST_URL= 30 | UPSTASH_REDIS_REST_TOKEN= 31 | PRODUCTION_URL= 32 | ``` 33 | 34 | Run the development server: 35 | 36 | ```bash 37 | npm run dev 38 | # or 39 | yarn dev 40 | # or 41 | pnpm dev 42 | # or 43 | bun dev 44 | ``` 45 | 46 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 47 | -------------------------------------------------------------------------------- /app/api/session/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import Browserbase from "@browserbasehq/sdk"; 3 | 4 | export async function POST() { 5 | try { 6 | const client = new Browserbase({ 7 | apiKey: process.env["BROWSERBASE_API_KEY"], 8 | }); 9 | 10 | const session = await client.sessions.create({ 11 | projectId: process.env.BROWSERBASE_PROJECT_ID!, 12 | region: "us-east-1", 13 | }); 14 | 15 | const debugUrl = await client.sessions.debug(session.id); 16 | 17 | console.log("this is the valid debug URL:", debugUrl.debuggerFullscreenUrl); 18 | 19 | return NextResponse.json({ 20 | sessionId: session.id, 21 | debugUrl: debugUrl.debuggerFullscreenUrl 22 | .replace( 23 | "https://www.browserbase.com/devtools-fullscreen/inspector.html", 24 | "https://www.browserbase.com/devtools-internal-compiled/index.html" 25 | ), 26 | region: "us-east-1", 27 | }); 28 | } catch (error) { 29 | console.error("Error creating session:", error); 30 | return NextResponse.json( 31 | { error: "Failed to create session" }, 32 | { status: 500 } 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface LogoProps { 4 | className?: string; 5 | width?: number; 6 | height?: number; 7 | } 8 | 9 | export default function Logo({ className = '', width = 101, height = 100 }: LogoProps) { 10 | return ( 11 | 19 | 25 | 26 | ); 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bb-meme", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@ai-sdk/anthropic": "^1.0.7", 13 | "@ai-sdk/google": "^1.0.13", 14 | "@ai-sdk/groq": "^1.0.10", 15 | "@browserbasehq/sdk": "^2.0.0", 16 | "@browserbasehq/stagehand": "alpha", 17 | "@upstash/redis": "^1.34.3", 18 | "@vercel/analytics": "^1.4.1", 19 | "@vercel/kv": "^3.0.0", 20 | "ai": "^4.0.30", 21 | "class-variance-authority": "^0.7.1", 22 | "clsx": "^2.1.1", 23 | "lucide-react": "^0.471.0", 24 | "motion": "^11.17.0", 25 | "next": "15.2.6", 26 | "react": "19.2.1", 27 | "react-dom": "19.2.1", 28 | "tailwind-merge": "^2.6.0", 29 | "tailwindcss-animate": "^1.0.7", 30 | "twilio": "^5.4.1", 31 | "zod": "^3.24.1" 32 | }, 33 | "devDependencies": { 34 | "@eslint/eslintrc": "^3", 35 | "@types/node": "^20", 36 | "@types/react": "^19", 37 | "@types/react-dom": "^19", 38 | "eslint": "^9", 39 | "eslint-config-next": "15.1.4", 40 | "postcss": "^8", 41 | "tailwindcss": "^3.4.1", 42 | "typescript": "^5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/components/GenerationInfo.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from "framer-motion"; 2 | 3 | interface GenerationInfoProps { 4 | isVisible: boolean; 5 | // onPhoneNumberSubmit: (phoneNumber: string) => void; 6 | } 7 | 8 | export default function GenerationInfo({ isVisible }: GenerationInfoProps) { 9 | return ( 10 | 11 | {isVisible && ( 12 | 24 |

ℹ️ Please note:

25 |

26 | Each meme generation request takes approximately 1-3 minutes to 27 | complete. Not all memes will always be generated. 28 |

29 | 30 |

Here are some ideas to make the most of your time:

31 |
    32 |
  • Take a coffee break ☕
  • 33 |
  • Touch some grass 🌿
  • 34 |
  • Look through the weather app 🌤️
  • 35 |
  • Maybe scroll through some TikToks 📱
  • 36 |
37 |
38 | )} 39 |
40 | ); 41 | } -------------------------------------------------------------------------------- /app/components/StickyFooter.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import Link from "next/link"; 3 | 4 | export default function StickyFooter() { 5 | const text = "BUILT WITH STAGEHAND 🤟 ".repeat(20); 6 | 7 | return ( 8 | 9 | 29 |
30 | 41 | {text + text} 42 | 43 |
44 |
45 | 46 | ); 47 | } -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | domains: ['imgflip.com', 'i.imgflip.com'], 6 | remotePatterns: [ 7 | { 8 | protocol: 'https', 9 | hostname: 'i.imgflip.com', 10 | }, 11 | { 12 | protocol: 'https', 13 | hostname: 'imgflip.com', 14 | } 15 | ], 16 | unoptimized: true 17 | }, 18 | async headers() { 19 | return [ 20 | { 21 | source: '/:path*', 22 | headers: [ 23 | { 24 | key: 'X-Frame-Options', 25 | value: 'ALLOW-FROM https://www.browserbase.com' 26 | }, 27 | { 28 | key: 'Content-Security-Policy', 29 | value: "frame-ancestors 'self' https://www.browserbase.com" 30 | }, 31 | { 32 | key: 'Referrer-Policy', 33 | value: 'no-referrer' 34 | } 35 | ] 36 | }, 37 | { 38 | source: '/api/:path*', 39 | headers: [ 40 | { 41 | key: 'Access-Control-Allow-Origin', 42 | value: 'https://www.browserbase.com' 43 | }, 44 | { 45 | key: 'Access-Control-Allow-Methods', 46 | value: 'GET,POST,OPTIONS' 47 | }, 48 | { 49 | key: 'Access-Control-Allow-Headers', 50 | value: 'Content-Type, Authorization' 51 | }, 52 | { 53 | key: 'Access-Control-Allow-Credentials', 54 | value: 'true' 55 | } 56 | ] 57 | } 58 | ]; 59 | } 60 | }; 61 | 62 | export default nextConfig; 63 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { Galindo } from 'next/font/google' 4 | import Header from './components/Header'; 5 | import { Analytics } from "@vercel/analytics/react" 6 | 7 | export const metadata: Metadata = { 8 | title: 'Brainrot - Browserbase Meme Generator', 9 | description: 'Generate memes using AI and browser automation', 10 | icons: { 11 | icon: [ 12 | { 13 | url: '/favicon.svg', 14 | type: 'image/svg+xml', 15 | } 16 | ] 17 | }, 18 | openGraph: { 19 | title: 'Your Meme Generator', 20 | description: 'Create AI-powered memes in minutes', 21 | images: [ 22 | { 23 | url: `${process.env.NEXT_PUBLIC_BASE_URL}/og-image.jpg`, 24 | width: 1200, 25 | height: 630, 26 | alt: 'Meme Generator Preview', 27 | }, 28 | ], 29 | }, 30 | twitter: { 31 | card: 'summary_large_image', 32 | title: 'Your Meme Generator', 33 | description: 'Create AI-powered memes in minutes', 34 | creator: '@alexdphan', 35 | images: [`${process.env.NEXT_PUBLIC_BASE_URL}/og-image.jpg`], 36 | }, 37 | }; 38 | 39 | const galindo = Galindo({ 40 | weight: '400', 41 | subsets: ['latin'], 42 | variable: '--font-galindo', 43 | }) 44 | 45 | export default function RootLayout({ 46 | children, 47 | }: Readonly<{ 48 | children: React.ReactNode; 49 | }>) { 50 | return ( 51 | 52 | 53 |
54 | {children} 55 | 56 | 57 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | darkMode: "class", 10 | theme: { 11 | extend: { 12 | borderRadius: { 13 | lg: 'var(--radius)', 14 | md: 'calc(var(--radius) - 2px)', 15 | sm: 'calc(var(--radius) - 4px)' 16 | }, 17 | colors: { 18 | background: 'hsl(var(--background))', 19 | foreground: 'hsl(var(--foreground))', 20 | card: { 21 | DEFAULT: 'hsl(var(--card))', 22 | foreground: 'hsl(var(--card-foreground))' 23 | }, 24 | popover: { 25 | DEFAULT: 'hsl(var(--popover))', 26 | foreground: 'hsl(var(--popover-foreground))' 27 | }, 28 | primary: { 29 | DEFAULT: 'hsl(var(--primary))', 30 | foreground: 'hsl(var(--primary-foreground))' 31 | }, 32 | secondary: { 33 | DEFAULT: 'hsl(var(--secondary))', 34 | foreground: 'hsl(var(--secondary-foreground))' 35 | }, 36 | muted: { 37 | DEFAULT: 'hsl(var(--muted))', 38 | foreground: 'hsl(var(--muted-foreground))' 39 | }, 40 | accent: { 41 | DEFAULT: 'hsl(var(--accent))', 42 | foreground: 'hsl(var(--accent-foreground))' 43 | }, 44 | destructive: { 45 | DEFAULT: 'hsl(var(--destructive))', 46 | foreground: 'hsl(var(--destructive-foreground))' 47 | }, 48 | border: 'hsl(var(--border))', 49 | input: 'hsl(var(--input))', 50 | ring: 'hsl(var(--ring))', 51 | chart: { 52 | '1': 'hsl(var(--chart-1))', 53 | '2': 'hsl(var(--chart-2))', 54 | '3': 'hsl(var(--chart-3))', 55 | '4': 'hsl(var(--chart-4))', 56 | '5': 'hsl(var(--chart-5))' 57 | } 58 | }, 59 | fontFamily: { 60 | galindo: ['var(--font-galindo)'], 61 | }, 62 | } 63 | }, 64 | plugins: [], 65 | } satisfies Config; 66 | -------------------------------------------------------------------------------- /app/components/DebugUrlDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | interface DebugUrlDisplayProps { 4 | debugUrls: string[]; 5 | activeSessions: number; 6 | } 7 | 8 | export default function DebugUrlDisplay({ debugUrls, activeSessions }: DebugUrlDisplayProps) { 9 | const [currentIndex, setCurrentIndex] = useState(0); 10 | 11 | useEffect(() => { 12 | if (currentIndex >= activeSessions || !debugUrls[currentIndex]) { 13 | setCurrentIndex(0); 14 | } 15 | }, [currentIndex, activeSessions, debugUrls]); 16 | 17 | if (!debugUrls.length || activeSessions === 0) { 18 | return null; 19 | } 20 | 21 | return ( 22 |
23 |
24 |

25 | Live View ({currentIndex + 1}/{activeSessions}) 26 |

27 | 44 |
45 |
46 |