# Project export: Dreamscape

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: Speak your dreams into reality.
- Devpost: https://devpost.com/software/dreamscape-ab6uo0
- GitHub: https://github.com/chrstnlai/aihack
- Demo: https://aihack-mu.vercel.app/
- Video: https://www.youtube.com/embed/n-7MTmQZHf8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Jason Son (32 commits), chrstnlai (14 commits)

## Devpost submission (written by the team)

### Inspiration

(ง •̀_•́)ง Dreams are often left forgotten, fading with the morning light before they can be understood or remembered. Most vivid dreams happen during REM sleep, a stage of the sleep cycle that typically occurs every 90 minutes - when your brain is highly active, similar to when your awake. Because the brain's memory-consolidation systems are not fully active during REM, dreams are lost within minutes of waking. That’s why we built Dreamscape, a web app that captures your dreams through voice recording and brings them to life with AI-generated video, preserving the emotions and memories before they're gone

### What it does

(｡•̀ᴗ-)✧ When users wake, they can open Dreamscape to instantly record a voice memo capturing the details of their dream. Once the recording is complete, an AI-generated video brings the experience to life. Each entry is saved in a personal gallery, allowing users to revisit and reflect on the stories their minds create while they sleep.

### How we built it

( •̀ .̫ •́ )✧ Set up a STT model, set it up in parallel and ran 5-second chunk inference to detect emoji, and total STT inference to obtain raw transcript Translate and detail the raw transcript into JSON and prompt engineer to Veo 2 Veo 2 takes in this output and outputs video based on preferences (optional) + prompt

### Challenges we ran into

(ノ°Д°)ノ We didn't have access to Veo 3 - most of our prompt-to-video generation was fairly behind Google API keys had to be rotated constantly due to usage limit

### Accomplishments we're proud of

(๑˃̵ᴗ˂̵)و The UI of the Dreamscape looks very ethereal 😻 - it captures the essence of a dream which is what we initially wanted to go for. We bootstrapped fairly fast and led UI overhaul for the last 6 hours of the hackathon.

### What we learned

(•‿•) We both learned how to connect Veo 3 with NexTJS apps. Jason I learned how to cheat Google by using all 10 of your links for credits. Additionally, I learned how to run tasks in parallel and persist components across states. Christine Got more experience with Next.JS and implementing the front-end :D

### What's next

(¬‿¬) For future iterations, Dreamscape would be adapted into a mobile app for easier access for users to easily record their dreams. More specifically, when users would prompt alarms/use action buttons on iOS devices, we would want Dreamscape to take in all notes. For an unrealistic expectation, we would want to connect to users physically through hardware throughout the night and interpret dreams.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 23 recognized source files, 86 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (28 of 28)

```
.gitignore
eslint.config.mjs
next.config.ts
package.json
postcss.config.mjs
README.md
src/app/api/emoji/route.ts
src/app/api/structure/route.ts
src/app/api/transcribe/route.ts
src/app/api/veo/route.ts
src/app/globals.css
src/app/layout.tsx
src/app/page.tsx
src/components/Dashboard.tsx
src/components/DreamArchive.tsx
src/components/DreamDetails.tsx
src/components/DreamGallery.tsx
src/components/RecordingPanel.tsx
src/components/SettingsProfile.tsx
src/components/Toasts.tsx
src/components/ui/input.tsx
src/lib/dreamStore.ts
src/lib/supabase.ts
src/lib/utils.ts
src/presets/meta.tsx
src/services/groqClient.ts
src/services/veoClient.ts
tsconfig.json
```

### Dependencies

- package.json: @eslint/eslintrc@^3, @google/genai@^1.6.0, @radix-ui/react-icons@^1.3.2, @radix-ui/react-toast@^1.2.14, @supabase/supabase-js@^2.50.0, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, autoprefixer@^10.4.21, axios@^1.10.0, clsx@^2.1.1, eslint@^9, eslint-config-next@15.3.4, framer-motion@^12.18.1, fs@^0.0.1-security, groq-sdk@^0.25.0, next@15.3.4, postcss@^8.5.6, react@^19.0.0, react-dom@^19.0.0, tailwind-merge@^3.3.1, tailwindcss@^4.1.10, typescript@^5, zustand@^5.0.5

### Recent commits (newest first)

- add: change to meta
- add: meta added to site
- Merge branch 'main' of https://github.com/chrstnlai/aihack
- feat: meta
- Update README.md
- add: toast for parallel task
- Final commit
- feat: changed ui for everything
- no idea wtf i just did
- ui: main menu overhaul, sidebar overhaul, recording start overhaul, personalization overhaul
- chore: deps
- Merge branch 'main' of https://github.com/chrstnlai/aihack
- Merge branch 'main' of https://github.com/chrstnlai/aihack
- add:font family
- add: tailwind file
- add:bernstein font
- feat: supabase is now added, replaces localStorage for saving items
- chore: deps
- Merge branch 'main' of https://github.com/chrstnlai/aihack
- layout change

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

### package.json

```
{
  "name": "aihack",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@google/genai": "^1.6.0",
    "@radix-ui/react-icons": "^1.3.2",
    "@radix-ui/react-toast": "^1.2.14",
    "@supabase/supabase-js": "^2.50.0",
    "axios": "^1.10.0",
    "clsx": "^2.1.1",
    "framer-motion": "^12.18.1",
    "fs": "^0.0.1-security",
    "groq-sdk": "^0.25.0",
    "next": "15.3.4",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "tailwind-merge": "^3.3.1",
    "zustand": "^5.0.5"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "autoprefixer": "^10.4.21",
    "eslint": "^9",
    "eslint-config-next": "15.3.4",
    "postcss": "^8.5.6",
    "tailwindcss": "^4.1.10",
    "typescript": "^5"
  }
}

```

### src/app/page.tsx

```typescript
import Dashboard from "@/components/Dashboard"

export default function Home() {
  return <Dashboard />
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ToastProvider } from '@radix-ui/react-toast';
import Toasts from '../components/Toasts';
import { HeadComponent } from "@/presets/meta";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Dreamscape",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <HeadComponent />
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <ToastProvider>
          <Toasts />
          {children}
        </ToastProvider>
      </body>
    </html>
  );
}

```

### src/app/api/emoji/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { GroqClient } from '@/services/groqClient';

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { transcript } = body;

    if (!transcript) {
      return NextResponse.json(
        { error: 'Transcript is required' },
        { status: 400 }
      );
    }

    console.log('🎵 Emoji API Request:', {
      transcript: transcript.substring(0, 100) + '...'
    });

    const groqClient = new GroqClient();
    const emoji = await groqClient.detectEmojiFromTranscript(transcript);

    console.log('🎵 Emoji API Response:', {
      transcript: transcript.substring(0, 100) + '...',
      detectedEmoji: emoji
    });

    return NextResponse.json({
      success: true,
      emoji,
      debug: {
        inputTranscript: transcript.substring(0, 100) + '...',
        detectedEmoji: emoji
      }
    });

  } catch (error) {
    console.error('❌ Emoji API Error:', error);
    
    return NextResponse.json(
      { 
        error: 'Failed to detect emoji',
        details: error instanceof Error ? error.message : 'Unknown error'
      },
      { status: 500 }
    );
  }
} 
```

### src/app/api/structure/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { GroqClient } from '@/services/groqClient';

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { transcript } = body;

    if (!transcript) {
      return NextResponse.json(
        { error: 'Transcript is required' },
        { status: 400 }
      );
    }

    console.log('📊 Structure API Request:', {
      transcript: transcript.substring(0, 100) + '...'
    });

    const groqClient = new GroqClient();
    const structuredData = await groqClient.structureTranscript(transcript);

    console.log('📊 Structure API Response:', {
      transcriptLength: transcript.length,
      structuredDataKeys: structuredData ? Object.keys(structuredData) : null
    });

    return NextResponse.json({
      success: true,
      structuredData,
      debug: {
        inputTranscriptLength: transcript.length,
        outputStructureKeys: structuredData ? Object.keys(structuredData) : null
      }
    });

  } catch (error) {
    console.error('❌ Structure API Error:', error);
    
    return NextResponse.json(
      { 
        error: 'Failed to structure transcript',
        details: error instanceof Error ? error.message : 'Unknown error'
      },
      { status: 500 }
    );
  }
} 
```

### src/app/api/veo/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { veoClient } from '@/services/veoClient';

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { transcript, options } = body;

    if (!transcript) {
      return NextResponse.json(
        { error: 'Transcript is required' },
        { status: 400 }
      );
    }

    console.log('🚀 Veo API Request:', {
      transcript: transcript.substring(0, 100) + '...',
      options
    });

    // Call the Veo client
    const videoUrls = await veoClient.generateVideo(transcript, options);

    console.log('📹 Veo API Response - Video URLs:', videoUrls);
    console.log('📊 Veo API Response - Count:', videoUrls.length);

    return NextResponse.json({
      success: true,
      videoUrls,
      message: `Generated ${videoUrls.length} video(s) successfully`,
      debug: {
        requestTranscript: transcript.substring(0, 100) + '...',
        requestOptions: options,
        responseCount: videoUrls.length
      }
    });

  } catch (error) {
    console.error('❌ Veo API Error:', error);
    console.error('❌ Veo API Error Details:', {
      name: error instanceof Error ? error.name : 'Unknown',
      message: error instanceof Error ? error.message : 'Unknown error',
      stack: error instanceof Error ? error.stack : undefined
    });
    
    return NextResponse.json(
      { 
        error: 'Failed to generate video',
        details: error instanceof Error ? error.message : 'Unknown error',
        debug: {
          errorName: error instanceof Error ? error.name : 'Unknown',
          errorStack: error instanceof Error ? error.stack : undefined
        }
      },
      { status: 500 }
    );
  }
} 
```

### src/app/api/transcribe/route.ts

```typescript
import { NextResponse } from "next/server";
import fs from "fs";
import { GroqClient } from "@/services/groqClient";
import { writeFile, unlink } from "fs/promises";
import path from "path";

export async function POST(req: Request) {
    try {
      const formData = await req.formData();
      const file = formData.get("audio") as File;
  
      if (!file) {
        console.error("❌ No file received in request");
        return NextResponse.json({ success: false, error: "No audio file provided" }, { status: 400 });
      }
  
      // Validate file size (minimum 1KB, maximum 50MB)
      const fileSize = file.size;
      if (fileSize < 1024) {
        console.error("❌ File too small:", fileSize, "bytes");
        return NextResponse.json({ success: false, error: "Audio file too small" }, { status: 400 });
      }
      if (fileSize > 50 * 1024 * 1024) {
        console.error("❌ File too large:", fileSize, "bytes");
        return NextResponse.json({ success: false, error: "Audio file too large" }, { status: 400 });
      }
  
      const buffer = Buffer.from(await file.arrayBuffer());
      const tmpDir = path.join(process.cwd(), "tmp");
  
      // Ensure tmp directory exists
      if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
  
      // Determine file extension based on content type and filename
      let fileExtension = 'wav'; // Default to WAV
      const fileName = file.name?.toLowerCase() || '';
      const contentType = file.type?.toLowerCase() || '';
      
      if (contentType.includes('webm') || fileName.includes('.webm')) {
        fileExtension = 'webm';
      } else if (contentType.includes('mp3') || fileName.includes('.mp3')) {
        fileExtension = 'mp3';
      } else if (contentType.includes('ogg') || fileName.includes('.ogg')) {
        fileExtension = 'ogg';
      } else if (contentType.includes('m4a') || fileName.includes('.m4a')) {
        fileExtension = 'm4a';
      }
      
      const filePath = path.join(tmpDir, `${Date.now()}-audio.${fileExtension}`);
      
      console.log(`📁 Writing audio file: ${filePath}`);
      console.log(`📊 File info: size=${fileSize}, type=${contentType}, name=${fileName}`);
      
      await writeFile(filePath, buffer);
  
      const transcriber = new GroqClient();
      const result = await transcriber.transcribeFromFilePath(filePath);
  
      // Clean up temp file
      try {
      await unlink(filePath);
        console.log(`🗑️ Cleaned up temp file: ${filePath}`);
      } catch (cleanupError) {
        console.warn("⚠️ Failed to cleanup temp file:", cleanupError);
      }
  
      if (result && result.text) {
        const emoji = await transcriber.detectEmojiFromTranscript(result.text);
        return NextResponse.json({ 
          success: true, 
          result,
          emoji,
          debug: {
            transcript: result.text,
            detectedEmoji: emoji,
            fileSize,
            fileType: contentType,
            fileName
          }
        });
      }
  
      return NextResponse.json({ success: true, result, emoji: "🎵" });
    } catch (err: any) {
      console.error("🔥 Transcription API Error:", err);
      
      // Provide more specific error messages
      let errorMessage = "Failed to process audio file";
      if (err.message?.includes("could not process file")) {
        errorMessage = "Invalid audio file format or corrupted file";
      } else if (err.message?.includes("file too large")) {
        errorMessage = "Audio file is too large";
      } else if (err.message?.includes("file too small")) {
        errorMessage = "Audio file is too small or empty";
      }
      
      return NextResponse.json({ 
        success: false, 
        error: errorMessage,
        details: err.message 
      }, { status: 500 });
    }
  }
  

```

### next.config.ts

```typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  eslint: {
    ignoreDuringBuilds: true,
  },
};

export default nextConfig;

```

### src/lib/utils.ts

```typescript
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
} 
```

### src/app/globals.css

```css
@import "tailwindcss";

:root {
  --background: #ffffff;
  --foreground: #171717;
}


@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  background: var(--background);
  color: var(--foreground);
}

@font-face {
  font-family: "Berstein";
  src: url("/Berstein.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

```

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