# Project export: Boss.AI

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: Create any character from any media or source and fight bosses and enemies with your friends. Here the only thing that matters is imagination and creativity.
- Devpost: https://devpost.com/software/boss-ai-wld8iz
- GitHub: https://github.com/JonathanSaleh123/boss-hunter-frontend
- Demo: https://github.com/JonathanSaleh123/boss-hunter-backend
- Team: 1 GitHub contributor(s) — JonathanSaleh123 (12 commits)

## Devpost submission (written by the team)

### Inspiration

Inspired by previously played turn based games, Boss raids, and RPGs, want to give a very quick and accessible experience of role playing as a character and fighting bosses with your friends. All of this without grinding for hours upgrading your character, and with so much customization available.

### What it does

We generate our own character based on our Character Name and Description, and we write/describe actions we do. It uses a Boss Agent and a GameMaster Agent with tools and prompts to calculate damage and effects, with long term memory to give it more freedom and capability to assess new actions.

### How we built it

Built using Next.js Frontend and Express.js Backend with Socket.io to connect up to 4 players. Uses Letta for creating and easy access to AI agents, tools and long term memory.

### Challenges we ran into

Optimizing between using AI to control the flow of the game and our own algorithms/mechanics.

### Accomplishments we're proud of

Building a fully functional game prototype within 1 day and solo.

### What we learned

How AI agents and LLMs interact with context and prompt provided, and integrating that within our application. How to add tools for an LLM to use and communicate with each other.

### What's next

Game testing, mechanics refinement, Output Formatting improvement, Creating our own bosses, Scaling for multiple rooms and saving game states/previous rooms in a database, adding new features.

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 37 recognized source files, 115 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Express (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (42 of 42)

```
.gitignore
AUTH_SETUP.md
eslint.config.mjs
next.config.ts
package.json
postcss.config.mjs
README.md
src/app/api/auth/[...nextauth]/route.ts
src/app/api/create-character/route.ts
src/app/api/generate-character-image/route.ts
src/app/arena/page.tsx
src/app/auth/error/page.tsx
src/app/auth/signin/page.tsx
src/app/auth/signout/page.tsx
src/app/globals.css
src/app/layout.tsx
src/app/page.tsx
src/components/arena/CharacterDetailsModal.tsx
src/components/arena/GameOverModal.tsx
src/components/arena/GameStatusDisplay.tsx
src/components/arena/PlayerCard.tsx
src/components/arena/types.ts
src/components/ArenaUI.tsx
src/components/auth/LoadingSpinner.tsx
src/components/auth/UserProfile.tsx
src/components/landing/BossDisplay.tsx
src/components/landing/CharacterCreationUI.tsx
src/components/landing/CharacterDisplay.tsx
src/components/landing/CharacterForm.tsx
src/components/landing/StatInput.tsx
src/components/landing/styles.tsx
src/components/landing/types.ts
src/components/LandingPage.tsx
src/components/providers/SessionProvider.tsx
src/components/UserStats.tsx
src/hooks/useAuth.ts
src/hooks/usePrevious.ts
src/lib/api.ts
src/lib/auth.ts
src/middleware.ts
src/tailwind.config.js
tsconfig.json
```

### Dependencies

- package.json: @auth0/nextjs-auth0@^4.7.0, @eslint/eslintrc@^3, @letta-ai/letta-client@^0.1.132, @tailwindcss/postcss@^4.1.10, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@15.3.4, framer-motion@^12.18.1, lucide-react@^0.522.0, next@15.3.4, next-auth@^4.24.11, postcss@^8.5.6, react@^19.0.0, react-dom@^19.0.0, socket.io-client@^4.8.1, tailwindcss@^4.1.10, typescript@^5

### Recent commits (newest first)

- UX for importing
- Added Importing Characters
- Storing characters to MongoDB
- Auth0 authentication
- Refactored Landing Page
- Final
- Abilities
- Editing Stats
- Image Generation
- Checkpoint
- Divided Components
- Details Checkpoint
- Simulation Fighting
- routed to the arena
- Divided Landing Page
- Character Creation Works
- Inital Demo of Frontend
- Initial commit from Create Next App

## Key source files (fetched from GitHub, selected and truncated for size)

### AUTH_SETUP.md

```markdown
# Auth0 Authentication Setup

This application uses NextAuth.js with Auth0 for authentication and MongoDB for data storage. Follow these steps to set up authentication and database integration:

## 1. Create an Auth0 Application

1. Go to [Auth0 Dashboard](https://manage.auth0.com/)
2. Create a new application or use an existing one
3. Choose "Single Page Application" as the application type
4. Go to the "Settings" tab and note down:
   - Domain
   - Client ID
   - Client Secret

## 2. Configure Auth0 Application Settings

In your Auth0 application settings:

### Allowed Callback URLs:
```
http://localhost:3000/api/auth/callback/auth0
```

### Allowed Logout URLs:
```
http://localhost:3000
```

### Allowed Web Origins:
```
http://localhost:3000
```

## 3. Set Up MongoDB Database

### Option A: MongoDB Atlas (Recommended)
1. Go to [MongoDB Atlas](https://www.mongodb.com/atlas)
2. Create a free cluster
3. Create a database user with read/write permissions
4. Get your connection string

### Option B: Local MongoDB
1. Install MongoDB locally
2. Start the MongoDB service
3. Use connection string: `mongodb://localhost:27017/boss-hunter`

## 4. Environment Variables

Create a `.env.local` file in the `boss-hunter-frontend` directory with the following variables:

```env
# NextAuth.js Configuration
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-nextauth-secret-key-here

# Auth0 Configuration
AUTH0_CLIENT_ID=your-auth0-client-id
AUTH0_CLIENT_SECRET=your-auth0-client-secret
AUTH0_ISSUER=https://your-domain.auth0.com

# Backend API Configuration
NEXT_PUBLIC_API_URL=http://localhost:3001/api

# Letta AI Configuration (for character generation)
LETTA_API_KEY=your-letta-api-key
LETTA_AGENT_ID=your-character-creator-agent-id
LETTA_IMAGE_AGENT_ID=your-image-generator-agent-id
```

### Generate NEXTAUTH_SECRET

You can generate a secure secret using:
```bash
openssl rand -base64 32
```

## 5. Backend Environment Variables

Create a `.env` file in the `boss-hunter-backend` directory:

```env
# MongoDB Configuration
MONGODB_URI=mongodb://localhost:27017/boss-hunter
# or for MongoDB Atlas: mongodb+srv://username:password@cluster.mongodb.net/boss-hunter

# Letta AI Configuration
LETTA_API_KEY=your-letta-api-key
LETTA_AGENT_ID=your-character-creator-agent-id
LETTA_BOSS_AGENT_ID=your-boss-agent-id
LETTA_IMAGE_AGENT_ID=your-image-generator-agent-id

# Server Configuration
PORT=3001
```

## 6. Start the Application

1. Start the backend server:
```bash
cd boss-hunter-backend
npm install
npm start
```

2. Start the frontend application:
```bash
cd boss-hunter-frontend
npm install
npm run dev
```

## 7. Test Authentication

1. Visit `http://localhost:3000`
2. You should be redirected to the sign-in page
3. Click "Sign in with Auth0"
4. Complete the Auth0 authentication flow
5. You should be redirected back to the application
6. Your user data will be automatically stored in MongoDB

## Features

- **Protected Routes**: All routes except authentication pages requir
[truncated — 1534 more characters]
```

### package.json

```
{
  "name": "boss-hunter-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@auth0/nextjs-auth0": "^4.7.0",
    "@letta-ai/letta-client": "^0.1.132",
    "framer-motion": "^12.18.1",
    "lucide-react": "^0.522.0",
    "next": "15.3.4",
    "next-auth": "^4.24.11",
    "postcss": "^8.5.6",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "socket.io-client": "^4.8.1"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@tailwindcss/postcss": "^4.1.10",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.3.4",
    "tailwindcss": "^4.1.10",
    "typescript": "^5"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import SessionProvider from '@/components/providers/SessionProvider'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Arena Battle UI',
  description: 'A 4-player arena battle interface',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className="h-full">
      <body className={`${inter.className} h-full`}>
        <SessionProvider>
          {children}
        </SessionProvider>
      </body>
    </html>
  )
}
```

### src/app/page.tsx

```typescript
"use client"

import { useAuth } from '../hooks/useAuth'
import ArenaUI from '../components/ArenaUI'
import AIBossBattle from '../components/LandingPage'
import UserProfile from '../components/auth/UserProfile'
import LoadingSpinner from '../components/auth/LoadingSpinner'

export default function Home() {
  const { session, isLoading } = useAuth("/auth/signin")

  if (isLoading) {
    return <LoadingSpinner />
  }

  if (!session) {
    return null
  }

  return (
    <main className="w-full h-screen relative">
      <div className="absolute top-4 right-4 z-50">
        <UserProfile />
      </div>
      {/* <ArenaUI /> */}
      <AIBossBattle />
    </main>
  )
}
```

### src/app/arena/page.tsx

```typescript
"use client"

import { useAuth } from '../../hooks/useAuth'
import ArenaUI from '../../components/ArenaUI'
import UserProfile from '../../components/auth/UserProfile'
import LoadingSpinner from '../../components/auth/LoadingSpinner'

export default function ArenaPage() {
  const { session, isLoading } = useAuth("/auth/signin")

  if (isLoading) {
    return <LoadingSpinner />
  }

  if (!session) {
    return null
  }

  return (
    <div className="relative">
      <div className="absolute top-4 right-4 z-50">
        <UserProfile />
      </div>
      <ArenaUI />
    </div>
  )
} 
```

### src/app/auth/signout/page.tsx

```typescript
"use client"

import { signOut } from "next-auth/react"
import { useEffect } from "react"
import { useRouter } from "next/navigation"

export default function SignOut() {
  const router = useRouter()

  useEffect(() => {
    const handleSignOut = async () => {
      await signOut({ callbackUrl: "/auth/signin" })
    }
    handleSignOut()
  }, [])

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-900">
      <div className="max-w-md w-full space-y-8">
        <div>
          <h2 className="mt-6 text-center text-3xl font-extrabold text-white">
            Signing out...
          </h2>
          <p className="mt-2 text-center text-sm text-gray-400">
            You will be redirected to the sign-in page
          </p>
        </div>
        <div className="flex justify-center">
          <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white"></div>
        </div>
      </div>
    </div>
  )
} 
```

### src/app/auth/error/page.tsx

```typescript
"use client"

import { useSearchParams } from "next/navigation"
import Link from "next/link"

export default function AuthError() {
  const searchParams = useSearchParams()
  const error = searchParams.get("error")

  const getErrorMessage = (error: string | null) => {
    switch (error) {
      case "Configuration":
        return "There is a problem with the server configuration."
      case "AccessDenied":
        return "You do not have permission to sign in."
      case "Verification":
        return "The verification token has expired or has already been used."
      default:
        return "An error occurred during authentication."
    }
  }

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-900">
      <div className="max-w-md w-full space-y-8">
        <div>
          <h2 className="mt-6 text-center text-3xl font-extrabold text-white">
            Authentication Error
          </h2>
          <p className="mt-2 text-center text-sm text-red-400">
            {getErrorMessage(error)}
          </p>
        </div>
        <div className="mt-8 space-y-6">
          <Link
            href="/auth/signin"
            className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
          >
            Try Again
          </Link>
          <Link
            href="/"
            className="group relative w-full flex justify-center py-2 px-4 border border-gray-300 text-sm font-medium rounded-md text-gray-300 bg-transparent hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
          >
            Go Home
          </Link>
        </div>
      </div>
    </div>
  )
} 
```

### src/app/auth/signin/page.tsx

```typescript
"use client"

import { signIn, getSession } from "next-auth/react"
import { useEffect, useState } from "react"
import { useRouter } from "next/navigation"

export default function SignIn() {
  const [isLoading, setIsLoading] = useState(false)
  const router = useRouter()

  useEffect(() => {
    // Check if user is already signed in
    getSession().then((session) => {
      if (session) {
        router.push("/")
      }
    })
  }, [router])

  const handleSignIn = async () => {
    setIsLoading(true)
    try {
      await signIn("auth0", { callbackUrl: "/" })
    } catch (error) {
      console.error("Sign in error:", error)
      setIsLoading(false)
    }
  }

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-900">
      <div className="max-w-md w-full space-y-8">
        <div>
          <h2 className="mt-6 text-center text-3xl font-extrabold text-white">
            Sign in to Boss Hunter
          </h2>
          <p className="mt-2 text-center text-sm text-gray-400">
            Enter the arena and battle your way to victory
          </p>
        </div>
        <div className="mt-8 space-y-6">
          <button
            onClick={handleSignIn}
            disabled={isLoading}
            className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {isLoading ? (
              <div className="flex items-center">
                <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
                Signing in...
              </div>
            ) : (
              "Sign in with Auth0"
            )}
          </button>
        </div>
      </div>
    </div>
  )
} 
```

### src/app/api/generate-character-image/route.ts

```typescript
import { type NextRequest, NextResponse } from "next/server"
import { LettaClient } from "@letta-ai/letta-client"

// Initialize Letta client for Letta Cloud using the environment variable
const client = new LettaClient({
  token: process.env.LETTA_API_KEY,
})

// Get the pre-existing image-generating agent's ID from your environment variables.
// This should be the ID of the agent you created that has the `generate_image_with_dalle` tool.
const imageGeneratorAgentId = process.env.LETTA_IMAGE_AGENT_ID;

// Define the interface for the incoming request body
interface CharacterImageRequest {
  name: string;
  description: string;
}

export async function POST(request: NextRequest) {
  try {
    // Ensure the Agent ID is configured on the server
    if (!imageGeneratorAgentId) {
        console.error("LETTA_IMAGE_AGENT_ID environment variable is not set.");
        return NextResponse.json({ error: "Application is not configured correctly for image generation." }, { status: 500 });
    }

    const { name, description }: CharacterImageRequest = await request.json()

    // Validate that we received a character object
    if (!name || !description) {
      return NextResponse.json({ error: "Character name and description are required." }, { status: 400 })
    }

    // Construct a detailed prompt for the DALL-E tool.
    // A more descriptive prompt leads to better images.
    const imagePrompt = `Generate a fantasy character portrait. The character's name is ${name}. Description: "${description}". The style should be a detailed digital painting with an epic fantasy theme.`

    // Call the Letta agent and ask it to use its tool
    const response = await client.agents.messages.create(imageGeneratorAgentId, {
      messages: [
        {
          role: "user",
          content: `Please generate an image using the following prompt: "${imagePrompt}"`,
        },
      ],
    })

    // Extract the image URL from the tool's return message
    let imageUrl = null;
    for (const msg of response.messages) {
        // The image URL will be inside the 'tool_return_message'
        if (msg.messageType === "tool_return_message" && msg.toolReturn) {
            // Assuming the tool returns the URL directly as a string
            const toolOutput = msg.toolReturn as any; // Cast to any to access properties
            if (typeof toolOutput === 'string' && toolOutput.startsWith('http')) {
                 imageUrl = toolOutput;
                 break;
            }
        }
    }

    if (!imageUrl) {
      console.error("Agent did not return a valid image URL. Full response:", response);
      return NextResponse.json({ error: "AI failed to generate an image URL." }, { status: 500 })
    }

    // Return the image URL to the frontend
    return NextResponse.json({ imageUrl })

  } catch (error) {
    console.error("Image generation error:", error)
    return NextResponse.json({ error: "Failed to generate character image due to a server error." }, { status: 500 })
  }
}
```

### src/app/api/create-character/route.ts

```typescript
import { type NextRequest, NextResponse } from "next/server"
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"
import { LettaClient } from "@letta-ai/letta-client"

// Initialize Letta client for Letta Cloud using the environment variable
const client = new LettaClient({
  token: process.env.LETTA_API_KEY,
})

const characterCreatorAgentId = process.env.LETTA_AGENT_ID;

export async function POST(request: NextRequest) {
  try {
    // Get the user session
    const session = await getServerSession(authOptions);
    if (!session?.user?.email) {
      return NextResponse.json({ error: "Authentication required" }, { status: 401 });
    }

    if (!characterCreatorAgentId) {
        console.error("LETTA_AGENT_ID environment variable is not set.");
        return NextResponse.json({ error: "Application is not configured correctly." }, { status: 500 });
    }

    const { name, description } = await request.json()

    if (!name || !description) {
      return NextResponse.json({ error: "Name and description are required" }, { status: 400 })
    }

    // Generate character stats and info
    const response = await client.agents.messages.create(characterCreatorAgentId, {
      messages: [
        {
          role: "user",
          content: `Create a character with the name "${name}" and description "${description}". 
          
          Please provide a JSON response with the following structure:
          {
            "name": "character name",
            "description": "character description", 
            "background_info": {
              "backstory": "detailed backstory",
              "personality": "personality traits",
              "voice": "voice description",
              "alignment": "alignment (Good/Neutral/Evil)"
            },
            "game_stats": {
              "base_stats": {
                "general": {
                  "max_health": number (100-200),
                  "speed": number (50-150), 
                  "attack": number (50-150),
                  "defense": number (50-150)
                },
                "advanced": {
                  "luck": number (10-50),
                  "intelligence": number (50-150),
                  "agility": number (50-150), 
                  "endurance": number (50-150)
                },
                "total_stat_points": number (should equal sum of all stats)
              },
              "abilities": [
                {
                  "name": "ability name",
                  "type": "Passive|Buff|Attack|Debuff",
                  "description": "ability description",
                  "cooldown": number or null
                }
              ],
              "statusEffects": ["effect1", "effect2"]
            }
          }
          
          Make sure the character is balanced and interesting. The total_stat_points should be the sum of all base stats (general + advanced).`
        }
      ]
    });

    // Extract character data from the last assistant message
    let characterData = null;
    for (const msg of response.messages) {
      if (msg.messageType === "assistant_message" && 'content' in msg && msg.content) {
        // Handle the content which might be an array of text content
        if (Array.isArray(msg.content)) {
          characterData = msg.content.map(item => item.text || '').join('');
        } else if (typeof msg.content === 'string') {
          characterData = msg.content;
        }
        break;
      }
    }

    if (!characterData) {
      return NextResponse.json({ error: "AI failed to generate valid character data. Please try again." }, { status: 500 });
    }

    // Parse the JSON response
    let parsedCharacterData;
    try {
      // Extract JSON from the response (it might be wrapped in markdown)
      const jsonMatch = characterData.match(/```json\n([\s\S]*?)\n```/) || 
                       characterData.match(/```\n([\s\S]*?)\n```/) ||
                       characterData.match(/\{[\s\S]*\}/);
      
      if (jsonMatch) {
        parsedCharacterData = JSON.parse(jsonMatch[1] || jsonMatch[0]);
      } else {
        parsedCharacterData = JSON.parse(characterData);
      }
    } catch (parseError) {
      console.error("Failed to parse character data:", parseError);
      console.error("Raw response:", characterData);
      return NextResponse.json({ error: "AI failed to generate valid character data. Please try again." }, { status: 500 });
    }

    // Validate the character data structure
    if (!parsedCharacterData.name || !parsedCharacterData.description || !parsedCharacterData.game_stats) {
      return NextResponse.json({ error: "AI generated incomplete character data. Please try again." }, { status: 500 });
    }

    // Get the user's auth0Id from the session
    const auth0Id = (session.user as any).sub || session.user.email;
    
    // First try to get the existing user
    let userResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api'}/users/${auth0Id}`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
    });

    let user;
    if (userResponse.ok) {
      // User exists, get the user data
      const userData = await userResponse.json();
      user = userData.user;
    } else {
      // User doesn't exist, create them
      userResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api'}/users`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email: session.user.email,
          name: session.user.name || '',
          image: session.user.image,
          auth0Id: auth0Id,
        }),
      });

      if (!userResponse.ok) {
        const errorData = await userResponse.json();
        console.error('User creation error:', errorData);
        return NextResponse.json({ error: "Failed to create user record" }, { status
[truncated — 1016 more characters]
```

[27 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]