# Project export: WonderLandAI

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: Ever wondered what your dream looked like in a comic strip? WonderLandAI lets you speak your dream and instantly see it drawn as a comic—your subconscious, visualized.
- Devpost: https://devpost.com/software/wonderlandai
- GitHub: https://github.com/jnoahbaier/AI-Hackathon-2025
- Team: 1 GitHub contributor(s) — noahbaier (2 commits)

## Devpost submission (written by the team)

### Inspiration

Have you ever woken from the most vivid dream, where the events play out in your head like a film and you’re the star; only for, in the next minute, it all disappears like smoke in the wind. Dreaming is a universal human experience that has been subject of both elusive fascination and wonderment since the advent of human communication. From the mystic to the scientific, it is only natural that we wonder about the basic question: “What do my dreams mean?” Research from the McGovern Institute at MIT theorizes that dreaming is a byproduct of the biological process reorganizing memories in our brain. While they state that dreams “aren’t instilled with meaning, symbolism, and wisdom in the way we’ve always imagined,” because of how much emotion and sensory experience is involved, a look into our dreams may also be a look into ourselves. To bridge the gap between the fleeting nature of dreams and our desire to preserve and understand them, many people turn to dream journals. Regularly recording dreams can significantly improve recall, helping individuals identify recurring themes, emotions, and imagery. However, the process of documenting dreams, especially right after waking, can be tedious. Some try using voice memos as a quicker method, but reviewing and transcribing these recordings often becomes a chore in itself. This inspired us to build WanderLandAI.

### What it does

A mobile application that streamlines this process: users can simply record themselves describing their dreams upon waking, and the app automatically transcribes the audio into text and generates a dreamy, watercolor-styled comic strip based on the description.

### How we built it

We utilized a multi-layered LLM API approach that relies on our beloved sponsor, Google, and their Gemini API. First audio is recorded from the user’s device where the mp3 file is then passed to Gemini for audio transcription. Next, the transcribed audio is parsed into a detailed summary and emotion and sentiment analysis for the dream. This detailed description is then chunked into 6 dream chapters where each of those chapters is passed into the text to image API with a prompt template that creates watercolor style comic images of the dream. The backend was built in node JS. As we are first time hackers, we used Cursor to help us with setting up the backend. Our designer created a UI that mirrored the dreamlike quality of the generated images, using flowing visuals and a gentle color palette to evoke the feeling of being between sleep and memory. Meanwhile, our front-end developer bridged the gap between the user interface and back-end logic, ensuring seamless communication that brought the entire experience to life.

### Challenges we ran into

We are new to hackathons so we faced various challenges. The biggest challenges were converting the web design into a mobile app frontend. Additionally, we had trouble connecting the backend with the frontend. We were also struggling with the API credits as we only had $5 worth of Gemini API credits.

### Accomplishments we're proud of

3/4 of our team are first-time hackers. No one on our team had prior experience in mobile development. From setting up the development environment to integrating speech-to-text and image generation models like Gemini, the learning curve was steep but ultimately rewarding as we troubleshooted, pivoted, pivoted again, and again, and had to find creative ways of working through a development space we had little knowledge of.

### What we learned

Throughout the hackathon, one of the biggest learning curves was mobile app development. None of us had prior experience building a mobile app from scratch, so we had to quickly familiarize ourselves with mobile frameworks, UI/UX design principles, and the intricacies of debugging across different front-end and back-end codes. We experimented with different toolkits, read through documentation, and learned how to design user-friendly interfaces that felt intuitive and engaging. This hands-on crash course not only taught us how to bring an idea to life on a mobile platform, but also gave us a deep appreciation for the design process. We also discovered the power of collaboration tools like Cursor AI, which helped us streamline our codebase, troubleshoot bugs more efficiently, and even learn from AI-generated suggestions that sped up our development process. Beyond the technical skills, perhaps the most important thing we learned was how to stay motivated and support one another. When things didn’t work, and they often didn’t, we reminded each other of what our goals were for this project, why we were here, and realigning between cookies and energy drinks. In moments of frustration or burnout, it was our mutual encouragement, shared vision, and long hour breakthroughs that kept us moving. This experience wasn’t just about building an app, it was about learning how to build as a team.

### What's next

Looking ahead, our next goal is to build out user account functionality so the public can securely save and revisit their dream logs within the app. This will allow users to build a personal dream archive and track patterns or changes over time. One feature we were especially excited about, but didn’t have time to implement, was a conversational AI component. We envision that a future iteration of the app would include an interactive chat that would generate thoughtful, reflective prompts based on a user's dream content. This feature would encourage deeper introspection and help users explore the emotional layers of their dreams in a more guided and meaningful way.

## README (from the GitHub repository)

# Dream Recorder Backend

A backend API for recording dreams via audio, transcribing them, and generating comic strip visualizations.

## Features

- 🎙️ Audio file upload and storage
- 📝 Dream transcription (placeholder endpoints)
- 🎨 Comic strip generation (placeholder endpoints)
- 📊 Dream statistics and analytics
- 🏷️ Dream tagging and mood tracking
- 🔍 Filtering and search capabilities
- 💾 Simple file-based persistence (easily replaceable with a database)

## Quick Start

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

2. **Set up environment variables:**
   ```bash
   cp .env.example .env
   # Edit .env with your configuration
   ```

3. **Start the server:**
   ```bash
   # Development mode with auto-reload
   npm run dev

   # Production mode
   npm start
   ```

4. **The server will run on http://localhost:3000**

## API Endpoints

### Health Check
- `GET /api/health` - Basic health check
- `GET /api/health/detailed` - Detailed health check with statistics

### Dreams
- `GET /api/dreams` - Get all dreams (with optional filters)
- `GET /api/dreams/:id` - Get specific dream
- `POST /api/dreams/upload` - Upload audio file and create dream
- `POST /api/dreams` - Create dream without audio (for testing)
- `PUT /api/dreams/:id` - Update dream
- `DELETE /api/dreams/:id` - Delete dream
- `GET /api/dreams/stats/overview` - Get dream statistics

### Processing (Placeholder endpoints)
- `POST /api/dreams/:id/transcribe` - Start transcription process
- `POST /api/dreams/:id/generate-comic` - Generate comic from transcription

## Dream Data Structure

```json
{
  "id": "uuid",
  "title": "Dream Title",
  "audioFilePath": "/path/to/audio.mp3",
  "transcription": "Transcribed dream text...",
  "comicImages": ["image1.jpg", "image2.jpg"],
  "tags": ["flying", "adventure"],
  "mood": "exciting",
  "createdAt": "2023-12-01T10:00:00Z",
  "updatedAt": "2023-12-01T10:05:00Z",
  "userId": "user-uuid",
  "status": "completed"
}
```

## API Usage Examples

### Upload Audio Dream
```bash
curl -X POST http://localhost:3000/api/dreams/upload \
  -F "audio=@dream.mp3" \
  -F "title=My Amazing Dream" \
  -F "mood=exciting" \
  -F "tags=[\"flying\", \"adventure\"]"
```

### Get All Dreams
```bash
curl http://localhost:3000/api/dreams
```

### Filter Dreams by Mood
```bash
curl "http://localhost:3000/api/dreams?mood=exciting"
```

### Update Dream with Transcription
```bash
curl -X PUT http://localhost:3000/api/dreams/{dream-id} \
  -H "Content-Type: application/json" \
  -d '{"transcription": "I was flying over a beautiful landscape..."}'
```

## File Structure

```
├── server.js              # Main server file
├── package.json           # Dependencies and scripts
├── .env.example          # Environment variables template
├── models/
│   └── Dream.js          # Dream data model
├── services/
│   └── DreamService.js   # Business logic and data management
├── routes/
│   ├── health.js         # Health check endpoints
│   └── dreams.js         # Dream API endpoints
├── uploads/              # Audio file storage (auto-created)
└── data/
    └── dreams.json       # Simple file-based storage (auto-created)
```

## Available Moods
- happy
- sad
- scary
- weird
- exciting
- peaceful
- confusing
- romantic

## Dream Status Flow
1. `uploaded` - Audio file uploaded
2. `transcribing` - Transcription in progress
3. `transcribed` - Transcription completed
4. `generating_images` - Comic generation in progress
5. `completed` - All processing done
6. `error` - Error occurred during processing

## Adding API Integrations

The backend includes placeholder endpoints for:

1. **Transcription** (`POST /api/dreams/:id/transcribe`)
   - Add your speech-to-text API integration here
   - Popular options: OpenAI Whisper, Google Speech-to-Text, Azure Speech

2. **Image Generation** (`POST /api/dreams/:id/generate-comic`)
   - Add your text-to-image API integration here
   - Popular options: DALL-E, Midjourney, Stable Diffusion

## Future Enhancements

- [ ] Replace file-based storage with a proper database (PostgreSQL, MongoDB)
- [ ] Add user authentication and authorization
- [ ] Implement real-time notifications for processing status
- [ ] Add audio format conversion and compression
- [ ] Implement caching for frequently accessed dreams
- [ ] Add backup and restore functionality
- [ ] Implement rate limiting for API endpoints
- [ ] Add comprehensive logging and monitoring

## Development

### Running Tests
```bash
npm test
```

### Project Structure Guidelines
- **Models**: Data structures and business entities
- **Services**: Business logic and data management
- **Routes**: API endpoint definitions
- **Middleware**: Request/response processing

### Adding New Features
1. Define the data model in `/models`
2. Implement business logic in `/services`
3. Create API routes in `/routes`
4. Add appropriate validation and error handling 

## Detected evidence (automated analysis)

Indexed codebase: 15 recognized source files, 154 KB.
- Express (technology) — detected in the code
- Google Gemini (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- React (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (17 of 17)

```
.gitignore
migrate-dream-titles.js
models/Dream.js
package.json
public/app.js
public/index.html
README.md
regenerate-ai-titles.js
routes/dreams.js
routes/health.js
routes/test.js
server.js
services/DreamProcessingService.js
services/DreamService.js
services/GeminiTranscriptionService.js
services/ImageGenerationService.js
services/TranscriptionService.js
```

### Dependencies

- package.json: @google/genai@^1.6.0, @google/generative-ai@^0.21.0, body-parser@^1.20.2, cors@^2.8.5, dotenv@^16.3.1, express@^4.21.2, express-validator@^7.0.1, helmet@^7.1.0, jest@^29.7.0, morgan@^1.10.0, multer@^1.4.5-lts.1, nodemon@^3.0.2, openai@^4.20.1, uuid@^9.0.1

### Recent commits (newest first)

- Modified Journal UI
- Initial commit: Dream Recording App with AI transcription, processing, and comic generation

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

### package.json

```
{
  "name": "dream-recorder-backend",
  "version": "1.0.0",
  "description": "Backend for dream recording and comic strip generation app",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest"
  },
  "dependencies": {
    "@google/genai": "^1.6.0",
    "@google/generative-ai": "^0.21.0",
    "body-parser": "^1.20.2",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.21.2",
    "express-validator": "^7.0.1",
    "helmet": "^7.1.0",
    "morgan": "^1.10.0",
    "multer": "^1.4.5-lts.1",
    "openai": "^4.20.1",
    "uuid": "^9.0.1"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "nodemon": "^3.0.2"
  },
  "keywords": [
    "dreams",
    "audio",
    "transcription",
    "comic",
    "backend"
  ],
  "author": "",
  "license": "ISC"
}

```

### server.js

```javascript
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const path = require('path');
require('dotenv').config();

const dreamRoutes = require('./routes/dreams');
const healthRoutes = require('./routes/health');
const testRoutes = require('./routes/test');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(bodyParser.json({ limit: '10mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));

// Serve static files (uploaded audio files and frontend)
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.use(express.static(path.join(__dirname, 'public')));

// Serve generated images
app.use('/images', express.static('generated_images'));

// Routes
app.use('/api/health', healthRoutes);
app.use('/api/dreams', dreamRoutes);
app.use('/api/test', testRoutes);

// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  
  if (err.code === 'LIMIT_FILE_SIZE') {
    return res.status(400).json({
      error: 'File too large',
      message: 'Audio file size exceeds the maximum allowed limit'
    });
  }
  
  res.status(500).json({
    error: 'Internal Server Error',
    message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong'
  });
});

// 404 handler
app.use('*', (req, res) => {
  res.status(404).json({
    error: 'Not Found',
    message: 'The requested resource was not found'
  });
});

// Create uploads directory if it doesn't exist
const fs = require('fs');
const uploadDir = process.env.UPLOAD_DIR || 'uploads';
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true });
}

app.listen(PORT, () => {
  console.log(`🚀 Dream Recorder Backend running on port ${PORT}`);
  console.log(`📁 Upload directory: ${uploadDir}`);
  console.log(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
}); 
```

### public/app.js

```javascript
// Test that JavaScript is loading
console.log('🚀 Dream Recorder JavaScript file loaded!');
console.log('Browser info:', {
    userAgent: navigator.userAgent,
    mediaDevices: !!navigator.mediaDevices,
    getUserMedia: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
    MediaRecorder: !!window.MediaRecorder
});

class DreamRecorder {
    constructor() {
        console.log('🏗️ Constructing DreamRecorder...');
        
        this.mediaRecorder = null;
        this.audioChunks = [];
        this.isRecording = false;
        this.startTime = null;
        this.timerInterval = null;
        this.currentDreamId = null;
        this.currentStep = 0;

        // DOM elements
        this.recordButton = document.getElementById('recordButton');
        this.status = document.getElementById('status');
        this.timer = document.getElementById('timer');
        this.transcriptionText = document.getElementById('transcriptionText');
        this.metadata = document.getElementById('metadata');
        this.errorMessage = document.getElementById('errorMessage');
        
        // New elements for full pipeline
        this.processingSteps = document.getElementById('processingSteps');
        this.progressContainer = document.getElementById('progressContainer');
        this.progressBar = document.getElementById('progressBar');
        this.progressText = document.getElementById('progressText');
        this.dreamProcessingSection = document.getElementById('dreamProcessingSection');
        this.dreamSummary = document.getElementById('dreamSummary');
        this.summaryText = document.getElementById('summaryText');
        this.comicSection = document.getElementById('comicSection');
        this.comicGrid = document.getElementById('comicGrid');

        // Journal elements
        this.journalButton = document.getElementById('journalButton');
        this.journalModal = document.getElementById('journalModal');
        this.journalClose = document.getElementById('journalClose');
        this.bookPages = document.getElementById('bookPages');
        this.pageNavigation = document.getElementById('pageNavigation');
        this.pageIndicator = document.getElementById('pageIndicator');
        this.prevPageBtn = document.getElementById('prevPageBtn');
        this.nextPageBtn = document.getElementById('nextPageBtn');
        
        // Journal state
        this.currentPage = 0;
        this.totalPages = 0;
        this.dreams = [];

        console.log('📍 DOM elements found:', {
            recordButton: !!this.recordButton,
            status: !!this.status,
            timer: !!this.timer,
            transcriptionText: !!this.transcriptionText,
            metadata: !!this.metadata,
            errorMessage: !!this.errorMessage,
            processingSteps: !!this.processingSteps,
            progressContainer: !!this.progressContainer,
            dreamProcessingSection: !!this.dreamProcessingSection,
            comicSection: !!this.comicSection
        });

        if (!this.recordButton) {
            throw new Error('Record button not found in DOM');
        }

        this.bindEvents();
        this.checkMicrophonePermission();
        
        console.log('✅ DreamRecorder constructor completed');
    }

    bindEvents() {
        console.log('🔗 Binding events...');
        
        if (!this.recordButton) {
            console.error('❌ Record button not found for event binding');
            return;
        }

        this.recordButton.addEventListener('click', (event) => {
            event.preventDefault();
            console.log('🔘🔘🔘 BUTTON CLICKED! 🔘🔘🔘');
            
            if (this.isRecording) {
                console.log('⏹️ Stopping recording...');
                this.stopRecording();
            } else {
                console.log('🎙️ Starting recording...');
                this.startRecording();
            }
        });

        this.journalButton.addEventListener('click', () => this.openJournal());
        this.journalClose.addEventListener('click', () => this.closeJournal());
        this.journalModal.addEventListener('click', (e) => {
            if (e.target === this.journalModal) {
                this.closeJournal();
            }
        });

        // Page navigation events
        this.prevPageBtn.addEventListener('click', () => this.previousPage());
        this.nextPageBtn.addEventListener('click', () => this.nextPage());

        console.log('✅ Event listeners attached');
    }

    async checkMicrophonePermission() {
        try {
            if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                throw new Error('MediaRecorder API not supported in this browser');
            }

            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            stream.getTracks().forEach(track => track.stop());
            this.updateStatus('✅ Microphone ready! Click to record your dream.', 'ready');
            console.log('✅ Microphone permission granted');
        } catch (error) {
            console.error('❌ Microphone permission error:', error);
            if (error.name === 'NotAllowedError') {
                this.updateStatus('❌ Microphone access denied. Please allow microphone access and refresh the page.', 'error');
            } else if (error.name === 'NotFoundError') {
                this.updateStatus('❌ No microphone found. Please connect a microphone.', 'error');
            } else {
                this.updateStatus('❌ Microphone error: ' + error.message, 'error');
            }
        }
    }

    async startRecording() {
        try {
            console.log('🎙️ Starting recording...');
            this.clearError();
            this.resetPipeline();
            this.audioChunks = [];
            
            if (!window.MediaRecorder) {
                throw new Error('MediaRecorder not supported in this browser');
            }

            const strea
[truncated — 30599 more characters]
```

### regenerate-ai-titles.js

```javascript
require('dotenv').config();

const dreamService = require('./services/DreamService');
const dreamProcessingService = require('./services/DreamProcessingService');

/**
 * Generate a better title using Gemini AI
 * @param {Object} dream - Dream object with processed data
 * @returns {Promise<string>} - Generated title
 */
async function generateBetterTitle(dream) {
  try {
    if (!dreamProcessingService.isConfigured()) {
      throw new Error('Gemini API not configured');
    }

    // Use the summary for better title generation
    const summary = dream.processedData?.summary || dream.transcription || '';
    const themes = dream.processedData?.themes || [];
    const mood = dream.processedData?.mood || '';
    
    if (!summary || summary.length < 10) {
      throw new Error('No content available for title generation');
    }
    
    // Create a more sophisticated prompt
    const titlePrompt = `Generate a creative, evocative title (3-8 words) for this dream. Make it poetic and memorable, capturing the essence and emotion:

DREAM SUMMARY: "${summary}"
MOOD: ${mood}
THEMES: ${themes.join(', ')}

Examples of good dream titles:
- "The Glass Forest Journey"
- "Racing Through Time"
- "Billionaire's Malibu Awakening"
- "Dancing with Smoke Spirits"
- "Echoes of Childhood Home"

Return ONLY the title, no quotes or additional text.`;

    const model = dreamProcessingService.genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
    const result = await model.generateContent(titlePrompt);
    
    if (result?.response) {
      const generatedTitle = result.response.text().trim().replace(/^["']|["']$/g, '');
      return generatedTitle.length > 0 && generatedTitle.length <= 60 ? generatedTitle : null;
    }
    
    return null;
  } catch (error) {
    console.warn(`⚠️ AI title generation failed for dream ${dream.id}:`, error.message);
    return null;
  }
}

/**
 * Main regeneration function
 */
async function regenerateAITitles() {
  console.log('🔄 Starting AI title regeneration for processed dreams...');
  
  try {
    // Check if dream processing service is configured
    if (!dreamProcessingService.isConfigured()) {
      console.error('❌ Gemini API not configured. Please check your GEMINI_API_KEY in .env file');
      process.exit(1);
    }
    
    // Wait for DreamService to load dreams from file
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // Get dreams that have processed data (summaries)
    const dreams = dreamService.getAllDreams();
    console.log(`📚 Found ${dreams.length} total dreams`);
    
    // Filter dreams that have processed data and could benefit from better titles
    const dreamsWithProcessedData = dreams.filter(dream => 
      dream.processedData && 
      dream.processedData.summary && 
      dream.processedData.summary.length > 20
    );
    
    console.log(`🎯 ${dreamsWithProcessedData.length} dreams have processed data for AI title generation`);
    
    if (dreamsWithProcessedData.length === 0) {
      console.log('✅ No dreams with processed data found. Process some dreams first!');
      return;
    }
    
    let successCount = 0;
    let errorCount = 0;
    
    for (const dream of dreamsWithProcessedData) {
      try {
        console.log(`\n🔄 Processing dream ${dream.id}...`);
        console.log(`📝 Current title: "${dream.title}"`);
        console.log(`📖 Summary: "${dream.processedData.summary.substring(0, 100)}..."`);
        
        const newTitle = await generateBetterTitle(dream);
        
        if (newTitle && newTitle !== dream.title) {
          // Update the dream
          await dreamService.updateDream(dream.id, { title: newTitle });
          console.log(`✅ Updated dream ${dream.id} with AI title: "${newTitle}"`);
          successCount++;
        } else {
          console.log(`⚠️ Keeping existing title for dream ${dream.id}`);
        }
        
        // Add delay to respect API limits
        await new Promise(resolve => setTimeout(resolve, 2000));
        
      } catch (error) {
        console.error(`❌ Failed to update dream ${dream.id}:`, error.message);
        errorCount++;
      }
    }
    
    console.log(`\n🎉 AI title regeneration completed!`);
    console.log(`✅ Successfully updated: ${successCount} dreams`);
    console.log(`⚠️ Kept existing titles: ${dreamsWithProcessedData.length - successCount - errorCount} dreams`);
    console.log(`❌ Failed to update: ${errorCount} dreams`);
    
  } catch (error) {
    console.error('❌ Regeneration failed:', error.message);
    process.exit(1);
  }
}

// Run the regeneration if this script is called directly
if (require.main === module) {
  regenerateAITitles()
    .then(() => {
      console.log('🏁 AI title regeneration script completed');
      process.exit(0);
    })
    .catch(error => {
      console.error('💥 AI title regeneration script failed:', error);
      process.exit(1);
    });
}

module.exports = { regenerateAITitles, generateBetterTitle }; 
```

### migrate-dream-titles.js

```javascript
require('dotenv').config();

const dreamService = require('./services/DreamService');
const dreamProcessingService = require('./services/DreamProcessingService');

/**
 * Generate a fallback title from a summary or transcription
 * @param {string} text - Summary or transcription text
 * @returns {string} - Generated title
 */
function generateFallbackTitle(text) {
  if (!text || text.length < 10) return 'Mysterious Dream';
  
  // Extract key words and themes
  const words = text.toLowerCase().split(/\s+/);
  const dreamKeywords = {
    flying: 'Flying Dream',
    falling: 'The Fall',
    chase: 'The Chase',
    water: 'Water Dreams',
    forest: 'Forest Journey',
    house: 'Dream House',
    family: 'Family Reunion',
    school: 'Back to School',
    work: 'Work Nightmare',
    animal: 'Animal Encounter',
    car: 'Road Trip',
    fire: 'Flames of Dreams',
    dark: 'Dark Visions',
    light: 'Light Dreams',
    mountain: 'Mountain Quest',
    ocean: 'Ocean Dreams',
    city: 'City Adventures',
    childhood: 'Childhood Memories',
    lost: 'Lost and Found',
    running: 'The Run',
    monster: 'Monster Encounter',
    ghost: 'Ghostly Visions',
    magic: 'Magic Dreams',
    death: 'Life and Death',
    love: 'Love Dreams',
    fear: 'Fear Unleashed',
    happy: 'Joyful Dreams',
    sad: 'Melancholy Dreams',
    strange: 'Strange Visions',
    weird: 'Weird Dreams'
  };
  
  // Check for keyword matches
  for (const [keyword, title] of Object.entries(dreamKeywords)) {
    if (words.some(word => word.includes(keyword))) {
      return title;
    }
  }
  
  // Generate title from first few meaningful words
  const meaningfulWords = words.filter(word => 
    word.length > 3 && 
    !['the', 'and', 'was', 'were', 'had', 'have', 'that', 'this', 'with', 'from', 'they', 'them', 'there', 'then'].includes(word)
  );
  
  if (meaningfulWords.length >= 2) {
    const title = meaningfulWords.slice(0, 3)
      .map(word => word.charAt(0).toUpperCase() + word.slice(1))
      .join(' ');
    return `${title} Dream`;
  }
  
  // Fallback based on text characteristics
  if (text.includes('!') || text.includes('scared') || text.includes('afraid')) {
    return 'Intense Dream';
  } else if (text.includes('beautiful') || text.includes('wonderful') || text.includes('amazing')) {
    return 'Beautiful Dream';
  } else if (text.includes('strange') || text.includes('weird') || text.includes('odd')) {
    return 'Strange Dream';
  }
  
  return 'Mysterious Dream';
}

/**
 * Generate a title using Gemini AI for a single dream
 * @param {Object} dream - Dream object
 * @returns {Promise<string>} - Generated title
 */
async function generateAITitle(dream) {
  try {
    // Use the transcription or summary to generate a title
    const textToAnalyze = dream.transcription || dream.processedData?.summary || '';
    
    if (!textToAnalyze || textToAnalyze.length < 10) {
      return generateFallbackTitle(textToAnalyze);
    }
    
    // Use a simple title generation prompt with Gemini
    const titlePrompt = `Generate a catchy, descriptive title (3-8 words) for this dream. Make it evocative and memorable:

"${textToAnalyze.substring(0, 500)}"

Return ONLY the title, no quotes or additional text.`;

    const result = await dreamProcessingService.genAI?.getGenerativeModel({ model: "gemini-1.5-pro" })
      .generateContent(titlePrompt);
    
    if (result?.response) {
      const generatedTitle = result.response.text().trim().replace(/^["']|["']$/g, '');
      return generatedTitle.length > 0 && generatedTitle.length <= 50 ? generatedTitle : generateFallbackTitle(textToAnalyze);
    }
    
    return generateFallbackTitle(textToAnalyze);
  } catch (error) {
    console.warn(`⚠️ AI title generation failed for dream ${dream.id}:`, error.message);
    return generateFallbackTitle(dream.transcription || dream.processedData?.summary || '');
  }
}

/**
 * Main migration function
 */
async function migrateDreamTitles() {
  console.log('🔄 Starting dream title migration...');
  
  try {
    // Check if dream processing service is configured
    if (!dreamProcessingService.isConfigured()) {
      console.log('⚠️ Gemini API not configured, using fallback title generation only');
    }
    
    // Wait for DreamService to load dreams from file
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // Get all dreams
    const dreams = dreamService.getAllDreams();
    console.log(`📚 Found ${dreams.length} dreams to check`);
    
    // Filter dreams that need title updates
    const dreamsNeedingTitles = dreams.filter(dream => 
      !dream.title || 
      dream.title === 'Untitled Dream' || 
      dream.title === '' ||
      dream.title.startsWith('Dream Recording') || // Generic titles like "Dream Recording 6/21/2025"
      dream.title.startsWith('Dream ') // Generic titles like "Dream 12/21/2024"
    );
    
    console.log(`🎯 ${dreamsNeedingTitles.length} dreams need title updates`);
    
    if (dreamsNeedingTitles.length === 0) {
      console.log('✅ All dreams already have meaningful titles!');
      return;
    }
    
    let successCount = 0;
    let errorCount = 0;
    
    for (const dream of dreamsNeedingTitles) {
      try {
        console.log(`\n🔄 Processing dream ${dream.id}...`);
        
        let newTitle;
        
        // Try AI generation first if available
        if (dreamProcessingService.isConfigured()) {
          newTitle = await generateAITitle(dream);
          console.log(`🤖 AI generated title: "${newTitle}"`);
          
          // Add a small delay to respect API limits
          await new Promise(resolve => setTimeout(resolve, 1000));
        } else {
          // Use fallback generation
          const textSource = dream.transcription || dream.processedData?.summary || '';
          newTitle = generateFallbackTitle(textSource);
          console.log(`🔧 Fallback generated title: "${newTitle}"`);
        }
        
        // Update the dream
        await dreamService.u
[truncated — 996 more characters]
```

### routes/health.js

```javascript
const express = require('express');
const router = express.Router();
const dreamService = require('../services/DreamService');

// Basic health check
router.get('/', (req, res) => {
  res.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    version: process.env.npm_package_version || '1.0.0'
  });
});

// Detailed health check with statistics
router.get('/detailed', (req, res) => {
  try {
    const stats = dreamService.getStatistics();
    
    res.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
      version: process.env.npm_package_version || '1.0.0',
      memory: process.memoryUsage(),
      stats
    });
  } catch (error) {
    res.status(500).json({
      status: 'unhealthy',
      error: error.message,
      timestamp: new Date().toISOString()
    });
  }
});

module.exports = router; 
```

### models/Dream.js

```javascript
const { v4: uuidv4 } = require('uuid');

class Dream {
  constructor({
    title = '',
    audioFilePath = null,
    transcription = null,
    processedData = null,
    comicImages = [],
    tags = [],
    mood = null,
    createdAt = new Date(),
    userId = null
  } = {}) {
    this.id = uuidv4();
    this.title = title;
    this.audioFilePath = audioFilePath;
    this.transcription = transcription;
    this.processedData = processedData; // Stores summary, scenes, themes, characters from Gemini processing
    this.comicImages = comicImages; // Array of image URLs/paths
    this.tags = tags; // Array of strings
    this.mood = mood; // String: happy, sad, scary, weird, etc.
    this.createdAt = createdAt;
    this.updatedAt = new Date();
    this.userId = userId; // For future user authentication
    this.status = 'uploaded'; // uploaded, transcribing, transcribed, processing, processed, generating_images, completed, error
  }

  // Update dream status
  updateStatus(status) {
    this.status = status;
    this.updatedAt = new Date();
  }

  // Add transcription
  setTranscription(transcription) {
    this.transcription = transcription;
    this.status = 'transcribed';
    this.updatedAt = new Date();
  }

  // Set processed dream data
  setProcessedData(processedData) {
    this.processedData = processedData;
    this.status = 'processed';
    this.updatedAt = new Date();
  }

  // Add comic images
  setComicImages(images) {
    this.comicImages = images;
    this.status = 'completed';
    this.updatedAt = new Date();
  }

  // Add tags
  addTags(tags) {
    this.tags = [...new Set([...this.tags, ...tags])]; // Remove duplicates
    this.updatedAt = new Date();
  }

  // Set mood
  setMood(mood) {
    this.mood = mood;
    this.updatedAt = new Date();
  }

  // Convert to JSON (for API responses)
  toJSON() {
    return {
      id: this.id,
      title: this.title,
      audioFilePath: this.audioFilePath,
      transcription: this.transcription,
      processedData: this.processedData,
      comicImages: this.comicImages,
      tags: this.tags,
      mood: this.mood,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
      userId: this.userId,
      status: this.status
    };
  }
}

module.exports = Dream; 
```

### services/DreamService.js

```javascript
const Dream = require('../models/Dream');
const fs = require('fs').promises;
const path = require('path');

class DreamService {
  constructor() {
    this.dreams = new Map(); // In-memory storage (replace with database later)
    this.dataFile = path.join(__dirname, '../data/dreams.json');
    this.loadDreams();
  }

  // Load dreams from file (simple persistence)
  async loadDreams() {
    try {
      const data = await fs.readFile(this.dataFile, 'utf8');
      const dreamsData = JSON.parse(data);
      
      dreamsData.forEach(dreamData => {
        const dream = new Dream(dreamData);
        this.dreams.set(dream.id, dream);
      });
      
      console.log(`📚 Loaded ${this.dreams.size} dreams from storage`);
    } catch (error) {
      if (error.code !== 'ENOENT') {
        console.error('Error loading dreams:', error);
      }
      // Create data directory if it doesn't exist
      await fs.mkdir(path.dirname(this.dataFile), { recursive: true });
    }
  }

  // Save dreams to file
  async saveDreams() {
    try {
      const dreamsArray = Array.from(this.dreams.values()).map(dream => dream.toJSON());
      await fs.writeFile(this.dataFile, JSON.stringify(dreamsArray, null, 2));
    } catch (error) {
      console.error('Error saving dreams:', error);
    }
  }

  // Create a new dream
  async createDream(dreamData) {
    const dream = new Dream(dreamData);
    this.dreams.set(dream.id, dream);
    await this.saveDreams();
    return dream;
  }

  // Get dream by ID
  getDreamById(id) {
    return this.dreams.get(id);
  }

  // Get all dreams
  getAllDreams(filters = {}) {
    let dreams = Array.from(this.dreams.values());

    // Apply filters
    if (filters.mood) {
      dreams = dreams.filter(dream => dream.mood === filters.mood);
    }

    if (filters.status) {
      dreams = dreams.filter(dream => dream.status === filters.status);
    }

    if (filters.tag) {
      dreams = dreams.filter(dream => dream.tags.includes(filters.tag));
    }

    if (filters.userId) {
      dreams = dreams.filter(dream => dream.userId === filters.userId);
    }

    // Sort by creation date (newest first)
    dreams.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));

    return dreams;
  }

  // Update dream
  async updateDream(id, updates) {
    const dream = this.dreams.get(id);
    if (!dream) {
      throw new Error('Dream not found');
    }

    // Update allowed fields
    const allowedUpdates = ['title', 'tags', 'mood', 'transcription', 'processedData', 'comicImages', 'status'];
    allowedUpdates.forEach(field => {
      if (updates[field] !== undefined) {
        if (field === 'transcription') {
          dream.setTranscription(updates[field]);
        } else if (field === 'processedData') {
          dream.setProcessedData(updates[field]);
        } else if (field === 'comicImages') {
          dream.setComicImages(updates[field]);
        } else if (field === 'mood') {
          dream.setMood(updates[field]);
        } else if (field === 'tags') {
          dream.addTags(updates[field]);
        } else {
          dream[field] = updates[field];
          dream.updatedAt = new Date();
        }
      }
    });

    await this.saveDreams();
    return dream;
  }

  // Delete dream
  async deleteDream(id) {
    const dream = this.dreams.get(id);
    if (!dream) {
      throw new Error('Dream not found');
    }

    // Delete associated audio file if it exists
    if (dream.audioFilePath) {
      try {
        await fs.unlink(dream.audioFilePath);
      } catch (error) {
        console.error('Error deleting audio file:', error);
      }
    }

    this.dreams.delete(id);
    await this.saveDreams();
    return dream;
  }

  // Get dream statistics
  getStatistics() {
    const dreams = Array.from(this.dreams.values());
    const totalDreams = dreams.length;
    
    const statusCounts = dreams.reduce((acc, dream) => {
      acc[dream.status] = (acc[dream.status] || 0) + 1;
      return acc;
    }, {});

    const moodCounts = dreams.reduce((acc, dream) => {
      if (dream.mood) {
        acc[dream.mood] = (acc[dream.mood] || 0) + 1;
      }
      return acc;
    }, {});

    const recentDreams = dreams
      .filter(dream => {
        const weekAgo = new Date();
        weekAgo.setDate(weekAgo.getDate() - 7);
        return new Date(dream.createdAt) > weekAgo;
      }).length;

    return {
      totalDreams,
      recentDreams,
      statusCounts,
      moodCounts
    };
  }
}

module.exports = new DreamService(); 
```

### services/TranscriptionService.js

```javascript
const OpenAI = require('openai');
const fs = require('fs');
const path = require('path');

class TranscriptionService {
  constructor() {
    this.openai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY
    });
  }

  /**
   * Transcribe audio file using OpenAI Whisper with retry logic
   * @param {string} audioFilePath - Path to the audio file
   * @param {number} retries - Number of retries (default: 3)
   * @returns {Promise<string>} - Transcribed text
   */
  async transcribeAudio(audioFilePath, retries = 3) {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        // Check if file exists
        if (!fs.existsSync(audioFilePath)) {
          throw new Error('Audio file not found');
        }

        // Get file stats to check size
        const stats = fs.statSync(audioFilePath);
        const fileSizeInMB = stats.size / (1024 * 1024);
        
        console.log(`🎙️ Transcribing audio file: ${path.basename(audioFilePath)} (${fileSizeInMB.toFixed(2)} MB) - Attempt ${attempt}/${retries}`);

        // OpenAI Whisper has a 25MB file size limit
        if (fileSizeInMB > 25) {
          throw new Error('Audio file too large for transcription (max 25MB)');
        }

        // Create a readable stream for the audio file
        const audioFile = fs.createReadStream(audioFilePath);

        // Call OpenAI Whisper API with timeout
        const transcription = await Promise.race([
          this.openai.audio.transcriptions.create({
            file: audioFile,
            model: "whisper-1",
            language: "en", // You can make this configurable or auto-detect
            response_format: "text",
            temperature: 0.2 // Lower temperature for more consistent results
          }),
          new Promise((_, reject) => 
            setTimeout(() => reject(new Error('Request timeout after 60 seconds')), 60000)
          )
        ]);

        console.log(`✅ Transcription completed for ${path.basename(audioFilePath)}`);
        
        return transcription;
      } catch (error) {
        console.error(`❌ Transcription error (attempt ${attempt}/${retries}):`, error.message);
        
        // Handle specific OpenAI errors (don't retry these)
        if (error.code === 'invalid_request_error') {
          throw new Error('Invalid audio file format or corrupted file');
        } else if (error.code === 'rate_limit_exceeded' || error.status === 429) {
          throw new Error('OpenAI quota exceeded. Please check your billing at https://platform.openai.com/account/billing');
        } else if (error.code === 'insufficient_quota') {
          throw new Error('OpenAI API quota exceeded. Please add credits to your account.');
        } else if (error.message && error.message.includes('quota')) {
          throw new Error('OpenAI quota exceeded. Please check your billing and add credits.');
        }
        
        // For connection errors, retry if we have attempts left
        if (attempt < retries && (
          error.message.includes('ECONNRESET') ||
          error.message.includes('Connection error') ||
          error.message.includes('timeout') ||
          error.message.includes('ENOTFOUND')
        )) {
          console.log(`🔄 Retrying in ${attempt * 2} seconds...`);
          await new Promise(resolve => setTimeout(resolve, attempt * 2000));
          continue;
        }
        
        // If we've exhausted retries or it's a non-retryable error
        throw new Error(`Transcription failed after ${attempt} attempts: ${error.message}`);
      }
    }
  }

  /**
   * Transcribe with additional processing and metadata
   * @param {string} audioFilePath - Path to the audio file
   * @returns {Promise<Object>} - Transcription result with metadata
   */
  async transcribeWithMetadata(audioFilePath) {
    try {
      const startTime = Date.now();
      const transcriptionText = await this.transcribeAudio(audioFilePath);
      const endTime = Date.now();
      
      const stats = fs.statSync(audioFilePath);
      
      return {
        text: transcriptionText,
        metadata: {
          filePath: audioFilePath,
          fileName: path.basename(audioFilePath),
          fileSize: stats.size,
          fileSizeMB: (stats.size / (1024 * 1024)).toFixed(2),
          transcriptionTime: endTime - startTime,
          timestamp: new Date().toISOString(),
          wordCount: transcriptionText.split(/\s+/).length,
          model: "whisper-1"
        }
      };
    } catch (error) {
      throw error;
    }
  }

  /**
   * Check if the service is properly configured
   * @returns {boolean} - Whether the service can be used
   */
  isConfigured() {
    return !!this.openai.apiKey;
  }

  /**
   * Get supported audio formats
   * @returns {Array<string>} - List of supported audio formats
   */
  getSupportedFormats() {
    return [
      'mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm'
    ];
  }
}

module.exports = new TranscriptionService(); 
```

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