# Project export: NeuroPlot

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: Cal Hacks 12.0
- Tagline: A multimodal AI–robotics system that converts audio prompts into vectorized curves via diffusion-based vision, Claude semantic parsing, and Fetch.AI coordination creating precise physical drawings.
- Devpost: https://devpost.com/software/neuroplot
- GitHub: https://github.com/pranavsant/NeuroPlot
- Video: https://www.youtube.com/embed/Qu0_-tLaeJc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — pranavsant (11 commits), Claude (4 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# Parametric Drawing Generator

> Transform natural language and voice prompts into mathematical parametric curves and rendered images using AI.

**Built for Cal Hacks 12.0**

![Architecture](https://img.shields.io/badge/Frontend-Next.js_16-black?logo=next.js)
![Backend](https://img.shields.io/badge/Backend-FastAPI-009688?logo=fastapi)
![AI](https://img.shields.io/badge/AI-Claude_Sonnet_4.5-8B5CF6)

---

## What It Does

Describe an image with **text** or **voice**, and watch AI generate it using parametric equations:

- **Natural Language to Math** - "Draw a butterfly" → parametric curves
- **Voice Input Support** - Record or upload audio descriptions
- **Iterative Refinement** - AI self-improves drawings through multi-agent evaluation
- **Robot-Ready Output** - Generate programs for physical drawing robots
- **Beautiful Visualization** - High-quality rendered images

---

## Quick Start (Run the Full Application)

### Prerequisites

- **Python 3.11+** with pip
- **Node.js 18+** with npm
- **Anthropic API Key** ([Get one here](https://console.anthropic.com/))

### 1. Clone & Set Up Environment

```bash
# Clone the repository
git clone <repository-url>
cd CalHacks12

# Set up environment variables
cp .env.example .env
# Edit .env and add your ANTHROPIC_API_KEY
```

### 2. Install Dependencies

#### Backend:
```bash
cd backend
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install -r requirements.txt
cd ..
```

#### Frontend:
```bash
cd frontend
npm install
cd ..
```

### 3. Run the Application

**Option A: Two Terminal Windows (Recommended)**

Terminal 1 - Backend:
```bash
cd backend
source venv/bin/activate  # On Windows: venv\Scripts\activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

Terminal 2 - Frontend:
```bash
cd frontend
npm run dev
```

**Option B: Using tmux (Advanced)**
```bash
# Start backend in background pane
tmux new-session -d -s calhacks 'cd backend && source venv/bin/activate && uvicorn app.main:app --reload'
# Start frontend in foreground
tmux split-window -h 'cd frontend && npm run dev'
tmux attach -t calhacks
```

### 4. Open Your Browser

- **Frontend**: http://localhost:3000
- **Backend API Docs**: http://localhost:8000/docs
- **Health Check**: http://localhost:8000/health

### 5. Try It Out!

1. Type a prompt: `"Draw a spiral flower with 5 petals"`
2. Or record audio: Click the microphone button and describe your image
3. Hit **"Generate Drawing"**
4. Watch the AI create parametric equations and render your image!

---

## 📁 Project Structure

```
CalHacks12/
├── frontend/                    # Next.js web application
│   ├── app/
│   │   ├── page.tsx            # Main page component
│   │   └── api/draw/route.ts   # API proxy to backend
│   ├── components/
│   │   ├── drawing-input.tsx   # Input form (text/voice)
│   │   └── drawing-results.tsx # Results display
│   ├── types/drawing.ts        # TypeScript interfaces
│   └── package.json
│
├── backend/                     # FastAPI backend
│   ├── app/
│   │   ├── main.py             # FastAPI application
│   │   ├── pipeline.py         # Main orchestration pipeline
│   │   ├── schemas.py          # Pydantic data models
│   │   ├── claude_client.py    # Claude AI integration
│   │   ├── renderer_agent.py   # Image rendering
│   │   ├── evaluator_agent.py  # Quality evaluation
│   │   └── utils_relative.py   # Robot coordinate transforms
│   ├── static/                 # Generated images (runtime)
│   ├── exports/                # Robot programs (runtime)
│   ├── requirements.txt
│   └── Dockerfile
│
├── hardware/                    # Robot control (optional)
│   ├── robot_plotter.py        # Differential drive robot controller
│   ├── README.md               # Hardware documentation
│   └── requirements.txt
│
├── tests/                       # Test suite
├── .env.example                 # Environment template
├── .env                         # Your secrets (git-ignored)
└── README.md                    # This file
```

---

## Development Setup

### Backend Setup

1. **Create virtual environment:**
   ```bash
   cd backend
   python -m venv venv
   source venv/bin/activate
   ```

2. **Install dependencies:**
   ```bash
   pip install -r requirements.txt
   ```

3. **Configure environment variables** (in project root `.env`):
   ```bash
   # Required
   ANTHROPIC_API_KEY=sk-ant-...

   # Optional
   VAPI_API_KEY=your_vapi_key      # For voice transcription
   LETTA_API_KEY=your_letta_key    # For persistent memory
   PORT=8000                        # Backend port (default: 8000)
   ```

4. **Run backend:**
   ```bash
   # Development mode with auto-reload
   uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

   # Or use the provided script:
   bash scripts/run_server.sh
   ```

5. **Verify it's running:**
   - API: http://localhost:8000
   - Docs: http://localhost:8000/docs
   - Health: http://localhost:8000/health

### Frontend Setup

1. **Install dependencies:**
   ```bash
   cd frontend
   npm install
   ```

2. **Configure backend URL** (optional):

   Create `frontend/.env.local` if you need to change the backend URL:
   ```bash
   BACKEND_URL=http://localhost:8000
   ```

   **Default:** If not set, it uses `http://localhost:8000`

3. **Run frontend:**
   ```bash
   npm run dev
   ```

4. **Verify it's running:**
   - Frontend: http://localhost:3000

### Running Both Together

**Recommended workflow:**

1. Start backend first (wait for "Application startup complete")
2. Start frontend second
3. Frontend will automatically connect to backend at `http://localhost:8000`

**Troubleshooting Connection:**
- Backend running? Check http://localhost:8000/health
- Frontend running? Check http://localhost:3000
- CORS enabled? (Backend automatically allows all origins in dev mode)
- Ports not in use? Change with `--port` or `PORT` env var

---

## How to Use

### Text Input

1. Open http://localhost:3000
2. Type your prompt: `"Draw a heart shape"`
3. Optionally toggle "Use Letta Memory" for contextual awareness
4. Click **"Generate Drawing"**
5. View your image, parametric equations, and AI evaluation score!

### Voice Input

1. Click the **"Record Audio"** button (grant microphone permission)
2. Describe your image: *"Draw a butterfly with rainbow wings"*
3. Click **"Stop Recording"**
4. The system will transcribe and generate your drawing
5. See the transcribed prompt displayed with your image

**Or upload an audio file:**
1. Click **"Upload Audio"**
2. Select a .wav, .mp3, or other audio file
3. Generate!

---

## Architecture

### System Flow

```
┌─────────────────────────────────────────────────────────────┐
│                        FRONTEND                             │
│  (Next.js 16 + React 19 + Tailwind CSS + shadcn/ui)       │
│                                                             │
│  ┌──────────────┐      ┌──────────────┐                   │
│  │ Text Input   │      │ Voice Input  │                   │
│  │  Component   │      │  Component   │                   │
│  └──────┬───────┘      └──────┬───────┘                   │
│         │                     │                            │
│         └─────────┬───────────┘                            │
│                   │                                        │
│         ┌─────────▼──────────┐                             │
│         │   API Route        │                             │
│         │  /api/draw         │                             │
│         └─────────┬──────────┘                             │
└───────────────────┼─────────────────────────────────────┘
                    │ HTTP POST
                    │
┌───────────────────▼─────────────────────────────────────┐
│                      BACKEND                             │
│            (FastAPI + Python 3.11)                       │
│                                                           │
│  ┌─────────────┐         ┌─────────────┐                │
│  │ POST /draw  │         │P

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 106 recognized source files, 533 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — 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
- JavaScript (language) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 120)

```
.env.example
.gitignore
backend/.env.example
backend/app/__init__.py
backend/app/claude_client.py
backend/app/color_utils.py
backend/app/evaluator_agent.py
backend/app/main.py
backend/app/memory_manager.py
backend/app/pipeline.py
backend/app/renderer_agent.py
backend/app/schemas.py
backend/app/utils_relative.py
backend/app/vapi_client.py
backend/Dockerfile
backend/docs/IMPLEMENTATION_SUMMARY.md
backend/docs/IMPLEMENTATION.md
backend/docs/PROJECT_SUMMARY.md
backend/docs/QUICKSTART.md
backend/docs/ROBOT_API_IMPLEMENTATION.md
backend/example_client.py
backend/exports/.gitkeep
backend/requirements.txt
backend/scripts/run_server.sh
backend/scripts/sim_diffdrive.py
backend/static/.gitkeep
docker-compose.yml
frontend/.gitignore
frontend/app/api/draw/route.ts
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/page.tsx
frontend/components.json
frontend/components/drawing-input.tsx
frontend/components/drawing-results.tsx
frontend/components/theme-provider.tsx
frontend/components/ui/accordion.tsx
frontend/components/ui/alert-dialog.tsx
frontend/components/ui/alert.tsx
frontend/components/ui/aspect-ratio.tsx
frontend/components/ui/avatar.tsx
frontend/components/ui/badge.tsx
frontend/components/ui/breadcrumb.tsx
frontend/components/ui/button-group.tsx
frontend/components/ui/button.tsx
frontend/components/ui/calendar.tsx
frontend/components/ui/card.tsx
frontend/components/ui/carousel.tsx
frontend/components/ui/chart.tsx
frontend/components/ui/checkbox.tsx
frontend/components/ui/collapsible.tsx
frontend/components/ui/command.tsx
frontend/components/ui/context-menu.tsx
frontend/components/ui/dialog.tsx
frontend/components/ui/drawer.tsx
frontend/components/ui/dropdown-menu.tsx
frontend/components/ui/empty.tsx
frontend/components/ui/field.tsx
frontend/components/ui/form.tsx
frontend/components/ui/hover-card.tsx
frontend/components/ui/input-group.tsx
frontend/components/ui/input-otp.tsx
frontend/components/ui/input.tsx
frontend/components/ui/item.tsx
frontend/components/ui/kbd.tsx
frontend/components/ui/label.tsx
frontend/components/ui/menubar.tsx
frontend/components/ui/navigation-menu.tsx
frontend/components/ui/pagination.tsx
frontend/components/ui/popover.tsx
frontend/components/ui/progress.tsx
frontend/components/ui/radio-group.tsx
frontend/components/ui/resizable.tsx
frontend/components/ui/scroll-area.tsx
frontend/components/ui/select.tsx
frontend/components/ui/separator.tsx
frontend/components/ui/sheet.tsx
frontend/components/ui/sidebar.tsx
frontend/components/ui/skeleton.tsx
frontend/components/ui/slider.tsx
frontend/components/ui/sonner.tsx
frontend/components/ui/spinner.tsx
frontend/components/ui/switch.tsx
frontend/components/ui/table.tsx
frontend/components/ui/tabs.tsx
frontend/components/ui/textarea.tsx
frontend/components/ui/toast.tsx
frontend/components/ui/toaster.tsx
frontend/components/ui/toggle-group.tsx
frontend/components/ui/toggle.tsx
frontend/components/ui/tooltip.tsx
frontend/components/ui/use-mobile.tsx
frontend/components/ui/use-toast.ts
frontend/hooks/use-mobile.ts
frontend/hooks/use-toast.ts
frontend/next.config.mjs
frontend/package.json
frontend/postcss.config.mjs
frontend/styles/globals.css
frontend/tsconfig.json
frontend/types/drawing.ts
HARDWARE_INTEGRATION.md
hardware/ARCHITECTURE.md
hardware/IMPLEMENTATION_SUMMARY.md
hardware/QUICKSTART.md
hardware/README.md
hardware/requirements.txt
hardware/robot_plotter.py
hardware/test_simulation.py
LICENSE
README.md
tests/test_degenerate_derivative.py
tests/test_pen_color_default.py
tests/test_pen_color_normalization.py
tests/test_relative_chaining.py
tests/test_relative_program_colors.py
tests/test_response_schema.py
tests/test_robot_endpoint.py
tests/test_robot_integration_simple.py
tests/test_sample_prompt.py
```

### Dependencies

- backend/requirements.txt: anthropic@>=0.71.0, fastapi@==0.109.0, matplotlib@>=3.9.0, numpy@>=1.26.4, Pillow@>=10.3.0, pydantic@>=2.0.0, pydub@>=0.25.1, pytest@>=8.0.0, python-dotenv@==1.0.0, python-multipart@==0.0.6, requests@==2.31.0, SpeechRecognition@>=3.10.0, uvicorn[standard]@==0.27.0
- frontend/package.json: @hookform/resolvers@^3.10.0, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@1.1.3, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@1.1.1, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@1.2.2, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@1.2.2, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@1.1.2, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@1.2.4, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @tailwindcss/postcss@^4.1.9, @types/node@^22, @types/react@^19, @types/react-dom@^19, @vercel/analytics@latest, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.0.4, date-fns@4.1.0, embla-carousel-react@8.5.1, input-otp@1.4.1, lucide-react@^0.454.0, next@16.0.0, next-themes@^0.4.6, postcss@^8.5, react@19.2.0, react-day-picker@9.8.0, react-dom@19.2.0, react-hook-form@^7.60.0, react-resizable-panels@^2.1.7, recharts@2.15.4, sonner@^1.7.4, tailwind-merge@^2.5.5, tailwindcss@^4.1.9, tailwindcss-animate@^1.0.7, tw-animate-css@1.3.3, typescript@^5, vaul@^1.1.2, zod@3.25.76
- hardware/requirements.txt: requests@>=2.31.0, RPi.GPIO@>=0.7.1

### Recent commits (newest first)

- Merge pull request #2 from pranavsant/refactor/move-backend-into-folder
- Edited README
- Merge pull request #1 from pranavsant/refactor/move-backend-into-folder
- change backend to robot api
- Add speech_recognition library for fallback transcription and hardware integration files
- feat: add Next.js frontend and align with backend API
- fix: update sim_diffdrive.py paths for new backend/ structure
- refactor: move all backend code into backend/ directory
- Add directory structure, exports and static files
- feat(colors): restrict drawing palette to black, blue, and pen-up only
- fix: correct rotation matrix in relative coordinate transformation
- fix: insert travel segments between disconnected curves in relative program
- feat: add robot fetch API and relative coordinate transformation system
- fix: resolve API key loading and Claude JSON parsing issues
- feat(backend): complete Parametric Curve Drawing System (FastAPI) w/ agents, Claude integration, tests & Docker

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

### HARDWARE_INTEGRATION.md

```markdown
# Hardware Integration Complete ✓

The CalHacks12 Parametric Drawing System now includes a complete robot hardware control module!

## What Was Added

### New `hardware/` Directory

A production-ready differential drive robot controller with:

- **robot_plotter.py** (717 lines) - Main control script
  - Fetches drawing programs from backend API
  - Implements accurate differential drive kinematics
  - Synchronized dual motor control (Bresenham algorithm)
  - GPIO control with simulation fallback
  - Pen up/down logic for color changes
  
- **Complete Documentation** (47KB total)
  - README.md - Full technical documentation
  - QUICKSTART.md - 5-minute start guide
  - ARCHITECTURE.md - System diagrams and architecture
  - IMPLEMENTATION_SUMMARY.md - Technical deep dive
  
- **Testing Support**
  - test_simulation.py - Simulation test script
  - Works without Raspberry Pi hardware
  - Validates control logic before deployment

## Usage

```bash
# On Raspberry Pi
cd hardware
pip install -r requirements.txt
python robot_plotter.py <run_id>

# Test without hardware
python robot_plotter.py <run_id> --simulate
```

## Technical Highlights

✓ Accurate differential drive math (no drift)
✓ Sub-millimeter resolution (0.078mm per step)
✓ Bresenham stepping for synchronized motors
✓ Handles pen up/down and color changes
✓ Works with 28BYJ-48 stepper motors
✓ Complete error handling and cleanup
✓ Comprehensive documentation

## Integration with Backend

The robot controller seamlessly integrates with the existing backend:
- Fetches programs via `/robot/<run_id>` endpoint
- No backend modifications required
- Uses existing relative coordinate system
- Supports all backend features

## Files Created

1. hardware/robot_plotter.py
2. hardware/test_simulation.py
3. hardware/README.md
4. hardware/QUICKSTART.md
5. hardware/ARCHITECTURE.md
6. hardware/IMPLEMENTATION_SUMMARY.md
7. hardware/requirements.txt
8. Updated main README.md

Total: 1028 lines of Python code, 47KB documentation

## Next Steps

1. Connect hardware (see hardware/README.md for wiring)
2. Test in simulation mode first
3. Calibrate with test patterns
4. Add servo for pen control (optional)
5. Deploy and draw!

See `hardware/README.md` for complete documentation.

```

### hardware/QUICKSTART.md

```markdown
# Quick Start Guide - Robot Plotter

Get your robot drawing in 5 minutes!

## Prerequisites

- Raspberry Pi with Raspbian/Raspberry Pi OS
- Two 28BYJ-48 stepper motors with ULN2003 drivers
- Proper GPIO connections (see wiring diagram below)
- Backend API running (on Pi or accessible via network)

## Installation

### 1. Install Dependencies

```bash
pip install requests RPi.GPIO
```

### 2. Verify Backend is Running

```bash
# On the same machine as backend
curl http://localhost:8000/health

# Or from Pi to remote backend
curl http://<backend-ip>:8000/health
```

Expected response:
```json
{"status": "healthy", "services": {...}}
```

## Running Your First Drawing

### Step 1: Create a Drawing

Use the frontend or API to create a drawing:

```bash
# Example: Create a simple drawing via API
curl -X POST http://localhost:8000/draw \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Draw a simple heart shape"}'
```

Save the `run_id` from the response.

### Step 2: Run the Robot Plotter

```bash
cd CalHacks12/hardware
python robot_plotter.py <run_id>
```

Or with remote backend:

```bash
python robot_plotter.py <run_id> --backend-url http://192.168.1.100:8000
```

### Step 3: Watch It Draw!

The robot will:
1. Fetch the drawing program from the backend
2. Lower the pen and start drawing
3. Automatically lift pen for color changes
4. Complete the drawing and cleanup

## Testing Without Hardware

Test the complete control logic in simulation mode:

```bash
# Test with simulation
python robot_plotter.py <run_id> --simulate

# Or run the built-in test
python test_simulation.py
```

## Wiring Diagram

### Left Motor (ULN2003 Driver)
```
Raspberry Pi (BCM)    ULN2003 Driver
------------------    --------------
GPIO 5         -->    IN1
GPIO 6         -->    IN2
GPIO 26        -->    IN3
GPIO 21        -->    IN4

5V             -->    VCC (external power supply)
GND            -->    GND
```

### Right Motor (ULN2003 Driver)
```
Raspberry Pi (BCM)    ULN2003 Driver
------------------    --------------
GPIO 17        -->    IN1
GPIO 27        -->    IN2
GPIO 22        -->    IN3
GPIO 16        -->    IN4

5V             -->    VCC (external power supply)
GND            -->    GND
```

**IMPORTANT**:
- Use BCM pin numbering (not BOARD numbering)
- Connect motors to external 5V power supply (NOT the Pi's 5V pin)
- Common ground between Pi and motor power supply

## Troubleshooting

### Robot Doesn't Move

**Check connections:**
```bash
# Test GPIO with LED
python3 -c "import RPi.GPIO as GPIO; GPIO.setmode(GPIO.BCM); GPIO.setup(5, GPIO.OUT); GPIO.output(5, GPIO.HIGH); import time; time.sleep(2); GPIO.cleanup()"
```

LED should light up on GPIO 5.

**Check power:**
- Verify 5V power supply is connected to motor drivers
- Ensure sufficient current capacity (>500mA per motor)

### Backend Connection Failed

```bash
# Test connectivity
ping <backend-ip>

# Test API
curl http://<backend-ip>:8000/health

# Check run_id exists
curl http://<backend-ip>:8000/
[truncated — 2024 more characters]
```

### docker-compose.yml

```yaml
version: '3.8'

services:
  parametric-drawing:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: parametric-curve-drawing
    ports:
      - "8000:8000"
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - VAPI_API_KEY=${VAPI_API_KEY:-}
      - LETTA_API_KEY=${LETTA_API_KEY:-}
    volumes:
      - ./backend/static:/app/backend/static
      - ./backend/exports:/app/backend/exports
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

```

### hardware/requirements.txt

```
# Hardware Control Module Requirements
# For Raspberry Pi robot plotter

# HTTP client for backend API communication
requests>=2.31.0

# Raspberry Pi GPIO control (only available/needed on Raspberry Pi)
# Note: This will fail on non-Pi systems, which is expected
# The script will fall back to dummy GPIO automatically
RPi.GPIO>=0.7.1; platform_machine=="armv7l" or platform_machine=="aarch64"

# Optional: For future enhancements
# pyserial>=3.5  # If adding serial communication
# pigpio>=1.78   # Alternative GPIO library with better PWM support

```

### backend/requirements.txt

```
# FastAPI and web server
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6
pydantic>=2.0.0

# AI/ML APIs
anthropic>=0.71.0

# Plotting and visualization
matplotlib>=3.9.0
numpy>=1.26.4

# HTTP requests
requests==2.31.0

# Image processing
Pillow>=10.3.0

# Environment variables
python-dotenv==1.0.0

# Testing
pytest>=8.0.0

# Voice transcription (optional, choose one or more)
# For free fallback using Google Speech Recognition:
SpeechRecognition>=3.10.0
pydub>=0.25.1  # For audio format conversion

# Optional: Letta Cloud integration (if available)
# letta-client

# Optional: Fetch.ai integration (if available)
# fetchai

```

### backend/Dockerfile

```
# Use Python 3.11 slim image as base
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Install system dependencies for matplotlib
RUN apt-get update && apt-get install -y \
    libfreetype6-dev \
    libpng-dev \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements file
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY app/ ./backend/app/
COPY static/ ./backend/static/
COPY exports/ ./backend/exports/

# Create static and exports directories if they don't exist
RUN mkdir -p backend/static backend/exports

# Expose port
EXPOSE 8000

# Set environment variables
ENV PYTHONUNBUFFERED=1

# Run the application
CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "8000"]

```

### frontend/package.json

```
{
  "name": "my-v0-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "lint": "eslint .",
    "start": "next start"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.10.0",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "1.1.3",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "1.1.1",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "1.2.2",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "1.2.2",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "1.1.2",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "1.2.4",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "@vercel/analytics": "latest",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.0.4",
    "date-fns": "4.1.0",
    "embla-carousel-react": "8.5.1",
    "input-otp": "1.4.1",
    "lucide-react": "^0.454.0",
    "next": "16.0.0",
    "next-themes": "^0.4.6",
    "react": "19.2.0",
    "react-day-picker": "9.8.0",
    "react-dom": "19.2.0",
    "react-hook-form": "^7.60.0",
    "react-resizable-panels": "^2.1.7",
    "recharts": "2.15.4",
    "sonner": "^1.7.4",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^1.1.2",
    "zod": "3.25.76"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.1.9",
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8.5",
    "tailwindcss": "^4.1.9",
    "tw-animate-css": "1.3.3",
    "typescript": "^5"
  }
}
```

### frontend/app/layout.tsx

```typescript
import type React from "react"
import type { Metadata } from "next"
import { Geist, Geist_Mono } from "next/font/google"
import { Analytics } from "@vercel/analytics/next"
import "./globals.css"

const _geist = Geist({ subsets: ["latin"] })
const _geistMono = Geist_Mono({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "Parametric Drawing Generator",
  description: "Generate AI-drawn artwork using parametric curves from text or voice prompts",
  generator: "v0.app",
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body className={`font-sans antialiased`}>
        {children}
        <Analytics />
      </body>
    </html>
  )
}

```

### frontend/app/page.tsx

```typescript
"use client"

import { useState } from "react"
import { DrawingInput } from "@/components/drawing-input"
import { DrawingResults } from "@/components/drawing-results"
import type { DrawingResponse } from "@/types/drawing"

export default function Home() {
  const [result, setResult] = useState<DrawingResponse | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const handleSubmit = async (prompt: string, audioFile: File | null, useLetta: boolean) => {
    setIsLoading(true)
    setResult(null)
    setError(null)

    try {
      let response: Response

      if (audioFile) {
        // Send audio file as multipart/form-data
        const formData = new FormData()
        formData.append("file", audioFile)
        formData.append("use_letta", useLetta.toString())

        response = await fetch("/api/draw", {
          method: "POST",
          body: formData,
        })
      } else {
        // Send text prompt as JSON
        response = await fetch("/api/draw", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            prompt,
            use_letta: useLetta,
          }),
        })
      }

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}))
        const errorMessage = errorData.error || `Server error: ${response.status}`
        throw new Error(errorMessage)
      }

      const data = await response.json()

      // Check if backend returned an error in the response
      if (data.error) {
        throw new Error(data.error)
      }

      setResult(data)
    } catch (error) {
      console.error("[v0] Error generating drawing:", error)
      const errorMessage = error instanceof Error ? error.message : "Failed to generate drawing. Please try again."
      setError(errorMessage)
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <main className="min-h-screen bg-background">
      <div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
        <div className="mb-12 text-center">
          <h1 className="mb-3 text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
            NeuroPlot
          </h1>
          <p className="mx-auto max-w-2xl text-lg text-muted-foreground leading-relaxed">
            Describe an image with text or voice, and watch AI create it using parametric curves
          </p>
        </div>

        <DrawingInput onSubmit={handleSubmit} isLoading={isLoading} />

        {error && (
          <div className="mx-auto mt-8 max-w-3xl rounded-lg border border-red-200 bg-red-50 p-4">
            <p className="text-sm text-red-800">
              <strong className="font-semibold">Error:</strong> {error}
            </p>
          </div>
        )}

        {result && <DrawingResults result={result} />}
      </div>
    </main>
  )
}

```

### backend/app/main.py

```python
"""
FastAPI Main Application - Entry point for the Parametric Curve Drawing System API.
"""

import os
import json
import logging
import tempfile
from pathlib import Path
from dotenv import load_dotenv

# Load environment variables FIRST before importing any backend modules
load_dotenv()

from fastapi import FastAPI, HTTPException, UploadFile, File, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import Optional

from . import pipeline
from .schemas import DrawResult

# Set up logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Initialize FastAPI app
app = FastAPI(
    title="Parametric Curve Drawing System",
    description="Transform natural language prompts into mathematical parametric curves and rendered images",
    version="1.0.0"
)

# Configure CORS for frontend access
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # In production, specify actual origins
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Mount static files directory
# __file__ = backend/app/main.py -> parent = backend/app -> parent = backend/ -> backend/static
STATIC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static")
os.makedirs(STATIC_DIR, exist_ok=True)
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")


# Pydantic models for request/response
class DrawRequest(BaseModel):
    """Request model for text-based drawing."""
    prompt: str
    use_letta: Optional[bool] = False


class DrawResponse(DrawResult):
    """
    Response model for drawing results.
    Extends DrawResult schema with all fields including relative_program.
    """
    pass


@app.on_event("startup")
async def startup_event():
    """Initialize services on startup."""
    logger.info("Starting Parametric Curve Drawing System API")

    # Check for required API keys
    anthropic_key = os.getenv("ANTHROPIC_API_KEY")
    if not anthropic_key:
        logger.warning("ANTHROPIC_API_KEY not found - Claude API calls will fail")
    else:
        logger.info("Anthropic API key configured")

    vapi_key = os.getenv("VAPI_API_KEY")
    if not vapi_key:
        logger.info("VAPI_API_KEY not found - voice input will not work")
    else:
        logger.info("Vapi API key configured")

    letta_key = os.getenv("LETTA_API_KEY")
    if letta_key:
        logger.info("Letta API key configured")


@app.on_event("shutdown")
async def shutdown_event():
    """Clean up on shutdown."""
    logger.info("Shutting down Parametric Curve Drawing System API")


@app.get("/")
async def root():
    """Root endpoint with API information."""
    return {
        "name": "Parametric Curve Drawing System",
        "version": "1.0.0",
        "description": "Transform natural language into parametric curves",
        "endpoints": {
            "POST /draw": "Create a drawing from text prompt",
            "POST /draw/audio": "Create a drawing from audio file",
            "GET /robot/{run_id}": "Fetch relative program for robot execution",
            "GET /health": "Health check",
            "GET /static/{filename}": "Access generated images"
        },
        "docs": "/docs"
    }


@app.get("/health")
async def health_check():
    """Health check endpoint."""
    # Check if required services are configured
    anthropic_ok = bool(os.getenv("ANTHROPIC_API_KEY"))

    return {
        "status": "healthy" if anthropic_ok else "degraded",
        "services": {
            "anthropic_claude": "configured" if anthropic_ok else "missing_api_key",
            "vapi_voice": "configured" if os.getenv("VAPI_API_KEY") else "not_configured",
            "letta_memory": "configured" if os.getenv("LETTA_API_KEY") else "not_configured"
        }
    }


@app.post("/draw", response_model=DrawResponse)
async def create_drawing(request: DrawRequest):
    """
    Create a parametric curve drawing from a text prompt.

    Args:
        request: DrawRequest with prompt text

    Returns:
        DrawResponse with curves, image, and metadata
    """
    logger.info(f"Received drawing request: '{request.prompt}'")

    try:
        # Validate prompt
        if not request.prompt or not request.prompt.strip():
            raise HTTPException(
                status_code=400,
                detail="Prompt cannot be empty"
            )

        # Run the pipeline
        result = pipeline.run_pipeline(
            prompt_text=request.prompt.strip(),
            use_letta=request.use_letta
        )

        # Return the result
        return DrawResponse(**result)

    except ValueError as e:
        logger.error(f"Validation error: {e}")
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"Error processing request: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")


@app.post("/draw/audio")
async def create_drawing_from_audio(
    audio: UploadFile = File(...),
    use_letta: Optional[bool] = Form(False)
):
    """
    Create a parametric curve drawing from an audio file.

    Args:
        audio: Audio file (WAV, MP3, etc.)
        use_letta: Whether to use Letta Cloud for memory

    Returns:
        JSON response with curves, image, and metadata
    """
    logger.info(f"Received audio drawing request: {audio.filename}")

    temp_audio_path = None

    try:
        # Validate file type
        if not audio.filename:
            raise HTTPException(status_code=400, detail="No file provided")

        # Save uploaded file to temporary location
        suffix = os.path.splitext(audio.filename)[1]
        with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
            content = await audio.read()
            
[truncated — 5746 more characters]
```

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