# Project export: Tissue.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: Supercharging open source contributions. Paste any GitHub issue URL and our agent analyzes the codebase to provides step-by-step guidance to help newcomers confidently tackle their first contribution
- Devpost: https://devpost.com/software/tissue-ai
- GitHub: https://github.com/aaron-ang/tissue-ai
- Video: https://www.youtube.com/embed/WbQA3UuMFew?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Aaron Ang (16 commits), KairiNavratil (12 commits), Elizabeth (7 commits), BootyChu (4 commits)

## Devpost submission (written by the team)

### Inspiration

One of the biggest barriers for new developers is knowing where to start when contributing to open-source projects. Staring at unfamiliar codebases with hundreds of files can be overwhelming, especially when trying to understand how to fix a specific GitHub issue. We wanted to create an AI-powered tool that bridges this gap by providing newcomers with the context, guidance, and confidence they need to make their first meaningful. Furthermore, LLM agents are still not (yet) great at solving real-world coding problems— the best performing agent in SWE-bench (popular coding benchmark) as of this writing can only solve ~50% of GitHub PRs. Human engineers are still essential in the open-source software ecosystem today, especially as the amount of software continues to grow.

### What it does

Our project helps users understand and guide them along the process of fixing github repo issues. The client pastes the link of an issue they're working on, and we use agent orchestration to process the repository and the specific files that the issue targets. Our system collects data persistently through Letta's MemGPT technology and synthesizes information in an interactive and easy-to-grok format for users. The structured and specific data can greatly benefit developers new to the project. Developers can also use this data to continue solving the issue through agentic code agents like Cursor and Copilot.

### How we built it

Front-end: We use Next.js for server-side rendering and routing. All UI components are built with React. Styling is handled with Tailwind CSS utility classes, along with custom CSS that creates a “liquid glass” (glassmorphism) effect for a modern interface. The Monaco Editor is integrated to provide an interactive, IDE-like code viewer, allowing users to explore code with syntax highlighting and a smooth user experience. Back-end: The backend is built with FastAPI and leverages Letta and MCP tools to analyze GitHub issues. When a user submits a GitHub issue URL, the backend fetches the issue details and repository structure, analyzes the codebase, architecture, and build/test commands, and generates a summary of the issue, project context, and implementation steps. This information is returned as a JSON object that matches a Pydantic model schema, making it easy for the frontend to process and display the data.

### Challenges we ran into

Letta Integration Complexity: Learning to work with Letta's agent-based architecture and coordinating multiple AI agents required significant experimentation MCP Server Configuration: Setting up and properly connecting Model Context Protocol servers for GitHub integration Schema Design: Creating comprehensive Pydantic schemas that could handle the variety of repository structures and issue types Asynchronous Workflow Management: Coordinating between supervisor and worker agents while maintaining response reliability CORS and Environment Configuration: Ensuring proper API communication between frontend and backend across different environments

### Accomplishments we're proud of

Successfully implemented Letta's multi-agent system for complex repository analysis Mastered MCP server integration to directly interface with GitHub's API and repository contents Created an intuitive glassmorphism UI that makes complex technical information accessible Built a full-stack application from scratch during the hackathon timeframe Designed a scalable architecture that can handle repositories of varying sizes and complexity Delivered real value by solving a genuine problem faced by new open-source contributors

### What's next

We're considering making the output more interactive, such as adding a chatbot so users can ask more questions about the current issue and get more tailored responses than the general overview. Beyond this, we plan to expand support for multi-repository analysis to handle issues that span across multiple codebases or dependencies. We're also exploring the development of personalized learning path generation that would recommend relevant tutorials and concepts based on the technologies encountered in each issue. To foster collaboration, we want to introduce community features that allow users to share their analysis results and work together on solutions. Additionally, we're considering building IDE extensions that would bring Tissue.AI directly into developers' workflows within popular code editors. Finally, we're looking into implementing an AI-powered difficulty scoring system that would help users find issues that match their current skill level, creating a more tailored and progressive learning experience for new contributors.

## README (from the GitHub repository)

# Tissue.AI 🤖

Supercharging open source contributions. Paste any GitHub issue URL and our agent analyzes the codebase to provide step-by-step guidance to help newcomers confidently tackle their first contribution.

## Inspiration 💡

One of the biggest barriers for new developers is knowing where to start when contributing to open-source projects. Staring at unfamiliar codebases with hundreds of files can be overwhelming, especially when trying to understand how to fix a specific GitHub issue. We wanted to create an AI-powered tool that bridges this gap by providing newcomers with the context, guidance, and confidence they need to make their first meaningful contribution.

## What It Does 🎯

Our project helps users understand and guide them along the process of fixing GitHub repo issues. The client pastes the link of an issue they're working on, and we use agent orchestration to process the repository and the specific files that the issue targets. Our system collects data persistently through Letta's MemGPT technology and synthesizes information in an interactive and easy-to-grok format for users. The structured and specific data can greatly benefit developers new to the project.

## How We Built It 🛠️

### Frontend:

- **Next.js**: For server-side rendering and routing.
- **React**: For building UI components.
- **Tailwind CSS**: For utility-first styling.
- **Monaco Editor**: For an interactive, IDE-like code viewer.

### Backend:

- **FastAPI**: For building the backend server.
- **Letta & MCP**: To analyze GitHub issues and repositories.
- **Pydantic**: For data validation and schema management.

## Key Features ✨

- **Multi-Agent System**: Utilizes Letta for complex repository analysis.
- **Intuitive UI**: A glassmorphism UI that makes technical information accessible.
- **Scalable Architecture**: Can handle repositories of varying sizes and complexity.
- **Full-Stack Application**: Built from scratch during the hackathon timeframe.

## Getting Started 🚀

### Prerequisites

- Node.js 20+
- Python 3.12+
- uv

### Installation

1. **Clone the repository:**
   ```bash
   git clone https://github.com/aaron-ang/tissue-ai.git
   ```
2. **Install frontend dependencies:**
   ```bash
   cd frontend
   npm install
   ```
3. **Install backend dependencies:**
   ```bash
   cd backend
   uv sync
   cp .env.example .env
   ```
   Then, add your `LETTA_API_KEY` to the `.env` file.

### Running the Application

1. **Start the backend server:**
   ```bash
   cd backend
   uv run fastapi dev
   ```
2. **Start the frontend development server:**
   ```bash
   cd ../frontend
   npm run dev
   ```

## Usage 📖

1. Open your browser and navigate to `http://localhost:3000`.
2. Paste a GitHub issue URL into the input field.
3. Click the "Analyze" button.
4. View the analysis, which includes a summary of the issue, project context, and implementation steps.

## What's Next 🔮

- **Interactive Chatbot**: To provide more tailored responses.
- **Multi-Repository Analysis**: To handle issues that span across multiple codebases.
- **Personalized Learning Paths**: To recommend relevant tutorials and concepts.
- **Community Features**: To allow users to share analysis results.
- **IDE Extensions**: To bring Tissue.AI directly into developers' workflows.

## Meet the Team 👋

- [Kai Navratil](https://www.linkedin.com/in/kairi-navratil/)
- [Elizabeth Chen](https://www.linkedin.com/in/elizabeth-c-059762238/)
- [Bode Chiu](https://www.linkedin.com/in/bodechiu/)
- [Aaron Ang](https://www.linkedin.com/in/aaron-ayd/)


## Detected evidence (automated analysis)

Indexed codebase: 17 recognized source files, 66 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- FastAPI (technology) — claimed on Devpost, not found in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (25 of 25)

```
backend/.env.example
backend/.gitignore
backend/.python-version
backend/agent.py
backend/delete_agents.py
backend/main.py
backend/mcp_client.py
backend/pyproject.toml
backend/README.md
backend/schema.py
backend/uv.lock
frontend/.gitignore
frontend/app/globals.css
frontend/app/issues/issues.css
frontend/app/issues/page.tsx
frontend/app/layout.tsx
frontend/app/page.tsx
frontend/lib/api.ts
frontend/next.config.js
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/tailwind.config.js
frontend/tsconfig.json
README.md
```

### Dependencies

- backend/pyproject.toml: fastapi[standard]@>=0.115.13, letta-client@>=0.1.167, mcp@>=1.9.4, pydantic@>=2.11.7, python-dotenv@>=1.1.0
- frontend/package.json: @monaco-editor/react@^4.7.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, autoprefixer@^10.0.1, axios@^1.10.0, eslint@^8, eslint-config-next@14.0.4, monaco-editor@^0.52.2, next@^14.2.30, postcss@^8, react@^18, react-dom@^18, tailwindcss@^3.3.0, typescript@^5

### Recent commits (newest first)

- update socials to LinkedIn
- update README
- Merge branches 'main' and 'main' of https://github.com/aaron-ang/berkeley-ai-25
- home button and main title fix
- remove deepwiki mcp
- fix scrollable sidebar final
- sidebar scrollable
- dont add list index
- improve API call and rendering
- update schema
- refactor sidebar layout to improve responsiveness and scrolling behavior
- add logo
- styling refactors and make API call in issues route
- remove node files from backend
- merge backend integration and ui changes
- use string JSON format for now
- Connected front-end to back-end by routing the main page to the issue page after inputting a github issue URL
- add description to files
- update FastAPI startup command
- fix sizing for issues page

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

### backend/pyproject.toml

```
[project]
name = "berkeley-ai-25"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi[standard]>=0.115.13",
    "letta-client>=0.1.167",
    "mcp>=1.9.4",
    "pydantic>=2.11.7",
    "python-dotenv>=1.1.0",
]

```

### frontend/package.json

```
{
  "name": "berkeley-ai-25-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@monaco-editor/react": "^4.7.0",
    "axios": "^1.10.0",
    "monaco-editor": "^0.52.2",
    "next": "^14.2.30",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.0.4",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

```

### backend/main.py

```python
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, HttpUrl

from agent import analyze_gh_issue
from schema import create_mock_analysis

app = FastAPI(title="Berkeley AI 25 - GitHub Issue Analysis API")

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"],  # Frontend URL
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["*"],
)


class GitHubIssueRequest(BaseModel):
    github_url: HttpUrl


@app.get("/")
def root():
    return {"message": "Berkeley AI 25 - GitHub Issue Analysis API"}


@app.post("/analyze")
def analyze_github_issue(request: GitHubIssueRequest):
    """
    Analyze a GitHub issue URL and return structured analysis data.

    This endpoint processes a GitHub issue URL using Letta AI agents to extract:
    - Issue summary (title, description, labels, status)
    - Relevant files with directory structure and relevance scores
    - Technical analysis with problem type, complexity, and suggested approaches

    Args:
        request: GitHubIssueRequest containing the GitHub issue URL

    Returns:
        GitHubIssueResponse: Structured analysis data following GitHubIssueAnalysis schema
    """
    try:
        # For now, return mock data instead of calling the actual agent
        # TODO: Replace with actual agent call when ready
        issue_url = str(request.github_url)
        analysis_result = analyze_gh_issue(issue_url)

        # analysis_result = create_mock_analysis()
        return analysis_result

    except ValueError as e:
        # Handle validation errors (missing API keys, invalid URLs, etc.)
        raise HTTPException(status_code=400, detail=f"Validation error: {str(e)}")
    except Exception as e:
        # Handle unexpected errors
        raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")


@app.get("/health")
def health_check():
    """Health check endpoint to verify the API is running."""
    return {"service": "github-issue-analyzer"}

```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
  title: 'Berkeley AI 25',
  description: 'Berkeley AI Hackathon 2025 Project',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
```

### frontend/app/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { analyzeGitHubIssue } from "../lib/api";

export default function Home() {
  const router = useRouter();
  const [githubUrl, setGithubUrl] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!githubUrl.trim()) {
      setError('Please enter a GitHub issue URL');
      return;
    }

    setError(null);
    setLoading(true);

    try {
      const result = await analyzeGitHubIssue(githubUrl);

      // Store the analysis data in localStorage for the issues page
      localStorage.setItem('analysisData', JSON.stringify({
        analysis: result.analysis,
        flattenedFiles: result.flattenedFiles
      }));

      // Navigate to issues page
      router.push('/issues');
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An unexpected error occurred');
      setLoading(false);
    }
  };

  // Show loading state
  if (loading) {
    return (
      <div className="min-h-screen relative flex items-center justify-center">
        <div className="absolute inset-0 bg-black z-0" />
        <div
          className="absolute inset-0 z-9"
          style={{
            backgroundImage: 'url("/purpleBackground2.png")',
            backgroundSize: "cover",
            backgroundPosition: "center",
            backgroundRepeat: "no-repeat",
            opacity: 0.9,
          }}
        />
        <div className="relative z-10 text-center">
          <div className="w-12 h-12 border-4 border-white/20 border-t-white rounded-full animate-spin mx-auto mb-4"></div>
          <p className="text-white text-xl">Analyzing GitHub issue...</p>
          <p className="text-white/60 text-sm mt-2">This may take a few moments</p>
        </div>
      </div>
    );
  }

  return (
    <div className="relative min-h-screen">
      {/* Black background layer (100% opacity) */}
      <div className="absolute inset-0 bg-black z-0" />

      {/* Background image layer (90% opacity) */}
      <div
        className="absolute inset-0 z-9"
        style={{
          backgroundImage: 'url("/purpleBackground.png")',
          backgroundSize: 'cover',
          backgroundPosition: 'center',
          backgroundRepeat: 'no-repeat',
          opacity: 0.9,
        }}
      />

      {/* Main Content */}
      <main className="flex flex-col items-center justify-center min-h-screen px-6 text-center">
        {/* Logo Circle with exact specifications */}
        <div className="relative mb-10">
          <div className="glass-circle relative flex items-center justify-center">
            {/* Logo placeholder - easy to replace with an image */}
            <div className="logo-container flex items-center justify-center w-full h-full">
              <img src="/logo.png" alt="Logo" className="w-auto h-auto max-w-[80%] max-h-[80%] object-contain" />
            </div>
          </div>
        </div>

        {/* Description Text */}
        <p className="text-white/80 text-lg leading-relaxed max-w-4xl mb-10 font-light z-10">
          Analyze GitHub issues with AI-powered insights. Paste a GitHub issue URL below to get detailed analysis and relevant files.
        </p>

        {/* Error Display */}
        {error && (
          <div className="max-w-2xl mx-auto mb-6 p-4 bg-red-500/20 border border-red-500/30 rounded-lg text-red-200 relative z-10">
            <strong>Error:</strong> {error}
            <button
              onClick={() => setError(null)}
              className="ml-4 px-3 py-1 bg-red-600/30 hover:bg-red-600/50 rounded text-sm transition-colors"
            >
              Dismiss
            </button>
          </div>
        )}

        {/* Search Input with exact specifications */}
        <form onSubmit={handleSubmit} className="relative mb-10">
          <div className="glass-search relative flex items-center">
            <div className="absolute left-6 flex items-center pointer-events-none z-10">
              <svg
                className="w-6 h-6 text-gray-300"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
                />
              </svg>
            </div>
            <input
              type="text"
              value={githubUrl}
              onChange={(e) => setGithubUrl(e.target.value)}
              placeholder="Paste your GitHub issue URL here"
              className="w-full h-full pl-16 pr-6 text-white placeholder-gray-300 bg-transparent border-none outline-none relative z-10"
              style={{ fontSize: '18px' }}
              disabled={loading}
            />
          </div>
          <button
            type="submit"
            disabled={loading}
            className="mt-4 px-8 py-3 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-600 text-white rounded-full transition-colors duration-200"
          >
            {loading ? 'Analyzing...' : 'Analyze Issue'}
          </button>
        </form>

        <style jsx global>{`
          .glass-circle {
            width: 200px;
            height: 200px;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 50%;
          }
          
          .glass-search {
            width: 600px;
            height: 80px;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 40px;
            position: relative;
          }
        `}</style>
      </main>

[truncated — 17 more characters]
```

### frontend/app/issues/page.tsx

```typescript
"use client";

import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import Editor from "@monaco-editor/react";

import "./issues.css";
import { GitHubIssueAnalysis, FlattenedFile } from "../../lib/api";

export default function IssuesPage() {
  const router = useRouter();
  const [showSummary, setShowSummary] = useState(true);
  const [analysis, setAnalysis] = useState<GitHubIssueAnalysis | null>(null);
  const [flattenedFiles, setFlattenedFiles] = useState<FlattenedFile[]>([]);
  const [selectedFile, setSelectedFile] = useState<FlattenedFile | null>(null);

  useEffect(() => {
    // Load analysis data from localStorage
    const analysisDataStr = localStorage.getItem("analysisData");

    if (analysisDataStr) {
      try {
        const analysisData = JSON.parse(analysisDataStr);
        setAnalysis(analysisData.analysis);
        setFlattenedFiles(analysisData.flattenedFiles);

        if (analysisData.flattenedFiles.length > 0) {
          setSelectedFile(analysisData.flattenedFiles[0]);
        }
      } catch (error) {
        console.error("Error parsing analysis data:", error);
        router.push("/");
      }
    } else {
      // No data available, redirect to home
      router.push("/");
    }
  }, [router]);

  // Show loading if no analysis data yet
  if (!analysis) {
    return (
      <div className="min-h-screen relative flex items-center justify-center">
        <div className="absolute inset-0 bg-black z-0" />
        <div
          className="absolute inset-0 z-9"
          style={{
            backgroundImage: 'url("/purpleBackground2.png")',
            backgroundSize: "cover",
            backgroundPosition: "center",
            backgroundRepeat: "no-repeat",
            opacity: 0.9,
          }}
        />
        <div className="relative z-10 text-center">
          <div className="w-12 h-12 border-4 border-white/20 border-t-white rounded-full animate-spin mx-auto mb-4"></div>
          <p className="text-white text-xl">Loading analysis...</p>
        </div>
      </div>
    );
  }

  return (
    <div className="min-h-screen relative max-h-screen">
      {/* Background layers */}
      <div className="absolute inset-0 bg-black z-0" />
      <div
        className="absolute inset-0 z-9"
        style={{
          backgroundImage: 'url("/purpleBackground2.png")',
          backgroundSize: "cover",
          backgroundPosition: "center",
          backgroundRepeat: "no-repeat",
          opacity: 0.9,
        }}
      />

      {/* Main Content Grid */}
      <div className="relative z-10 max-h-screen flex flex-col p-4 min-h-screen">
        <header className="w-full px-4 py-4 flex items-center justify-center z-10 mb-3">
          {/* Title (centered, truncated with ellipsis) */}
          <h1
            className="text-white text-4xl md:text-5xl lg:text-4xl text-center truncate max-w-full px-16"
            style={{
              fontFamily: "Montserrat, sans-serif",
              fontWeight: 400,
              lineHeight: "100%",
              letterSpacing: "0%",
            }}
            title={analysis?.issue_summary.title || "Loading..."}
          >
            {analysis?.issue_summary.title || "Loading..."}
          </h1>

          {/* Home Button (top-right) */}
          <Link
            href="/"
            className="absolute right-4 top-1 glass-logo-feature w-16 h-16 rounded-full flex items-center justify-center mb-2"
          >
            <img src="/logo.png" alt="Logo" />
          </Link>
        </header>

        {/* Main Content */}
        <main className="grid grid-cols-[500px_1fr] gap-6 flex-1 overflow-hidden min-h-0">
          {/* Left Sidebar */}
          <div className="flex flex-col gap-4 min-h-0">
            {/* Toggle Buttons */}
            <div className="flex gap-4">
              <button
                className={`glass-logo-feature w-16 h-16 rounded-full flex items-center justify-center cursor-pointer ${
                  showSummary ? "opacity-100" : "opacity-60"
                }`}
                onClick={() => setShowSummary(true)}
              >
                <svg
                  version="1.1"
                  xmlns="http://www.w3.org/2000/svg"
                  width="42"
                  height="42"
                >
                  <path
                    d="M0 0 C3.41865168 1.81864319 5.56456983 4.03408009 8 7 C8.68231689 7.58096436 9.36463379 8.16192871 10.06762695 8.76049805 C12.51482853 11.59665472 12.50290747 13.04990671 12.49609375 16.75390625 C12.49673828 17.85541016 12.49738281 18.95691406 12.49804688 20.09179688 C12.47806641 21.23712891 12.45808594 22.38246094 12.4375 23.5625 C12.44458984 24.71169922 12.45167969 25.86089844 12.45898438 27.04492188 C12.39560074 35.36768042 12.39560074 35.36768042 10.85595703 37.82861328 C8.16173552 39.52907037 6.03932164 39.3718459 2.87109375 39.36328125 C1.67162109 39.36263672 0.47214844 39.36199219 -0.76367188 39.36132812 C-2.01728516 39.34521484 -3.27089844 39.32910156 -4.5625 39.3125 C-6.4487207 39.31733398 -6.4487207 39.31733398 -8.37304688 39.32226562 C-17.73311942 39.26688058 -17.73311942 39.26688058 -20 37 C-20.22406625 34.45449442 -20.32810413 32.02075208 -20.3359375 29.47265625 C-20.3425943 28.72793564 -20.3492511 27.98321503 -20.35610962 27.21592712 C-20.36624041 25.63982618 -20.37092651 24.0636823 -20.37060547 22.48754883 C-20.37497741 20.0749651 -20.41128472 17.66421276 -20.44921875 15.25195312 C-20.45508712 13.72136043 -20.45905638 12.19075904 -20.4609375 10.66015625 C-20.47530853 9.93820572 -20.48967957 9.21625519 -20.50448608 8.47242737 C-20.47503432 5.24630225 -20.43680861 3.47116899 -18.19677734 1.05493164 C-12.74524951 -1.5629892 -5.83781748 -0.82239095 0 0 Z M-15 5 C-15 14.57 -15 24.14 -15 34 C-7.74 34 -0.48 34 7 34 C7 27.73 7 21.46 7 15 C3.7 15 0.4 15 -3 15 C-3 11.7 -3 8.4 -3 5 C-6.96 5 -10.92 5 -15 5 Z"
                    fill="#FFFFFF"
                    transfor
[truncated — 19687 more characters]
```

### frontend/postcss.config.js

```javascript
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
```

### frontend/next.config.js

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {}

module.exports = nextConfig
```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [],
}
```

### backend/delete_agents.py

```python
import os
from dotenv import load_dotenv

from letta_client import Letta

load_dotenv()

LETTA_API_KEY = os.getenv("LETTA_API_KEY")

letta_client = Letta(token=LETTA_API_KEY)

for agent in letta_client.agents.list():
    # Keep this agent for testing purposes
    if agent.id in [
        "agent-d234d3b9-6dd2-4e8d-930a-8111163704fb",
        "agent-9431f02a-d92a-4e64-971e-130a5cad8187",
    ]:
        continue

    print(f"Deleting agent {agent.name} with id {agent.id}")
    letta_client.agents.delete(agent.id)

```

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