# Project export: Nina - Your Mental Inbox

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: Turn voice memos into organized, searchable insights instantly.
- Devpost: https://devpost.com/software/nina-your-mental-inbox
- GitHub: https://github.com/AndyG6/nina-calhacks-ai
- Video: https://www.youtube.com/embed/-pP3FjF6yo8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Andy Guo (7 commits)

## Devpost submission (written by the team)

### Inspiration

You know when.. you know you are going to forget something. Yea dont. Use Nina. Narrate said thought into a savable audio. Parse through it like a library afterwards.

### What it does

We help people not forget things. We help people not worry about memorizing things. We help people get over the fear of missed opportunity. Just narrate your thought, we store it. Pick them up whenever you want. DB of thoughts

### How we built it

Expo (React Native) for the iOS app frontend FastAPI for the backend Groq API for the speech-to-text transcription and analyzing stored thoughts to find the most relevant ones to a user's query Local SQLite database to store and retrieve thoughts ##

### Challenges we ran into

Parsing Groq's responses: the model's output was often unpredictable and inconsistent, making it difficult to enforce a reliable message structure even with clear formatting instructions Implementing effective search: we spent significant time experimenting with ChromaDB's similarity search to find relevant "thoughts" from a database, but couldn't achieve good accuracy. Eventually we settled with using Groq to scan all stored thoughts, which works but can introduce hallucinations as the database of thoughts grows Debugging app behavior: with multiple asynchronous components interacting, diagnosing where issues originated was challenging ##

### Accomplishments we're proud of

A few areas where Nina innovates is its UI accessibility and search framework. With future goals of integrating across Apple’s ecosystem, Nina aims to be one click away from the closest device listening to the user’s voice. The minimalist design makes users feel productive, lack of button further emphasizing the core function of recording thoughts. For the search framework, we chose Groq because it's uniquely optimized for real-time speech-to-text and search that use both keywords and context workflows. Its low-latency voice API lets us transcribe audio with almost no delay; crucial for creating a frictionless, live-search experience.

### What we learned

How to use and integrate Groq API into our project The importance of clear communication and coordination in a team How to estimate task complexity and deadlines more realistically ##

### What's next

Optimize the "Search Your Thoughts" feature with ChromaDB's similarity search to improve relevance and reduce hallucinations as the number of stored thoughts grows Add capabilities for Nina to perform actions, such as writing emails or booking dinners directly through user commands, which will expand Nina into a true personal assistant that can not only remember and understand your thoughts but also take actions on your behalf

## README (from the GitHub repository)

<div id="top">

<!-- HEADER STYLE: CLASSIC -->
<div align="center">


# NINA-AI

<em>Your Thoughts in a Database</em>

<!-- BADGES -->
<img src="https://img.shields.io/github/last-commit/AndyG6/nina-ai?style=flat&logo=git&logoColor=white&color=0080ff" alt="last-commit">
<img src="https://img.shields.io/github/languages/top/AndyG6/nina-ai?style=flat&color=0080ff" alt="repo-top-language">
<img src="https://img.shields.io/github/languages/count/AndyG6/nina-ai?style=flat&color=0080ff" alt="repo-language-count">

<em>Built with the tools and technologies:</em>

<img src="https://img.shields.io/badge/JSON-000000.svg?style=flat&logo=JSON&logoColor=white" alt="JSON">
<img src="https://img.shields.io/badge/Markdown-000000.svg?style=flat&logo=Markdown&logoColor=white" alt="Markdown">
<img src="https://img.shields.io/badge/npm-CB3837.svg?style=flat&logo=npm&logoColor=white" alt="npm">
<img src="https://img.shields.io/badge/SQLAlchemy-D71F00.svg?style=flat&logo=SQLAlchemy&logoColor=white" alt="SQLAlchemy">
<img src="https://img.shields.io/badge/JavaScript-F7DF1E.svg?style=flat&logo=JavaScript&logoColor=black" alt="JavaScript">
<img src="https://img.shields.io/badge/FastAPI-009688.svg?style=flat&logo=FastAPI&logoColor=white" alt="FastAPI">
<br>
<img src="https://img.shields.io/badge/React-61DAFB.svg?style=flat&logo=React&logoColor=black" alt="React">
<img src="https://img.shields.io/badge/Python-3776AB.svg?style=flat&logo=Python&logoColor=white" alt="Python">
<img src="https://img.shields.io/badge/TypeScript-3178C6.svg?style=flat&logo=TypeScript&logoColor=white" alt="TypeScript">
<img src="https://img.shields.io/badge/Expo-000020.svg?style=flat&logo=Expo&logoColor=white" alt="Expo">
<img src="https://img.shields.io/badge/ESLint-4B32C3.svg?style=flat&logo=ESLint&logoColor=white" alt="ESLint">
<img src="https://img.shields.io/badge/Pydantic-E92063.svg?style=flat&logo=Pydantic&logoColor=white" alt="Pydantic">

</div>
<br>

---

## Table of Contents

- [Overview](#overview)
- [Getting Started](#getting-started)
    - [Prerequisites](#prerequisites)
    - [Installation](#installation)
    - [Usage](#usage)
    - [Testing](#testing)

---

## Overview

nina-ai is an all-in-one developer toolkit designed to accelerate the creation of cross-platform, AI-powered applications. It was built for the Groq AI track at Cal Hacks 2025. Combining a React Native and Expo-based frontend with a robust FastAPI backend, it enables seamless development across Android, iOS, and web platforms.

**Why nina-ai?**

This project empowers developers to build engaging, voice-enabled apps with ease. The core features include:

- 🧩 **Puzzle Piece:** Modular architecture with reusable components and hooks for rapid customization
- 🎨 **Palette:** Theme-aware UI components that adapt seamlessly to light and dark modes
- 🎯 **Target:** Cross-platform support ensuring consistent experiences on mobile and web
- 🚀 **Rocket:** Integrated backend APIs for speech transcription, TTS, and content metadata extraction
- 🔧 **Wrench:** Developer-friendly setup with configuration files and project reset scripts
- 🔍 **Magnifying Glass:** Powerful search and content management capabilities

### 🧠 Retrieval-Augmented Intelligence

Nina-AI is built on a **Retrieval-Augmented Generation (RAG)** pipeline that gives your thoughts structure and memory. Every voice note you record is transcribed, semantically embedded, and stored in a searchable database. When you ask a question or recall an idea, Nina-AI retrieves the most relevant notes and feeds them into an LLM to generate context-aware responses.

This RAG-driven design transforms Nina-AI into a **personal knowledge assistant** — capable of reasoning over your past recordings, summarizing key moments, and helping you rediscover insights you’ve already expressed.

---

## Getting Started

### Prerequisites

This project requires the following dependencies:

- **Programming Language:** TypeScript
- **Package Manager:** Npm, Pip

### Installation

Build nina-ai from the source and install dependencies:

1. **Clone the repository:**

    ```sh
    ❯ git clone https://github.com/AndyG6/nina-ai
    ```

2. **Navigate to the project directory:**

    ```sh
    ❯ cd nina-ai
    ```

3. **Install the dependencies:**

**Using [npm](https://www.npmjs.com/):**

```sh
❯ npm install
```
**Using [pip](https://pypi.org/project/pip/):**

```sh
❯ pip install -r backend/requirements.txt
```

### Usage

Run the project with:

**Using [npm](https://www.npmjs.com/):**

```sh
npm start
```
**Using [pip](https://pypi.org/project/pip/):**

```sh
python {entrypoint}
```

### Testing

Nina-ai uses the {__test_framework__} test framework. Run the test suite with:

**Using [npm](https://www.npmjs.com/):**

```sh
npm test
```
**Using [pip](https://pypi.org/project/pip/):**

```sh
pytest
```

---

<div align="left"><a href="#top">⬆ Return</a></div>

---


## Detected evidence (automated analysis)

Indexed codebase: 37 recognized source files, 82 KB.
- FastAPI (technology) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (45 of 45)

```
.gitignore
.vscode/extensions.json
.vscode/settings.json
app.json
app/_layout.tsx
app/(tabs)/_layout.tsx
app/(tabs)/explore.tsx
app/(tabs)/index.tsx
app/(tabs)/results.tsx
app/modal.tsx
babel.config.js
backend/app/__init__.py
backend/app/config.py
backend/app/db.py
backend/app/main.py
backend/app/routers/__init__.py
backend/app/routers/audio.py
backend/app/routers/search.py
backend/app/routers/thoughts.py
backend/app/routers/vapi_tools.py
backend/app/schemas.py
backend/app/services/__init__.py
backend/app/services/metadata.py
backend/app/services/transcription.py
backend/app/services/tts.py
backend/local.db
backend/requirements.txt
components/external-link.tsx
components/haptic-tab.tsx
components/hello-wave.tsx
components/parallax-scroll-view.tsx
components/themed-text.tsx
components/themed-view.tsx
components/ui/collapsible.tsx
components/ui/icon-symbol.ios.tsx
components/ui/icon-symbol.tsx
constants/theme.ts
eslint.config.js
hooks/use-color-scheme.ts
hooks/use-color-scheme.web.ts
hooks/use-theme-color.ts
package.json
README.md
scripts/reset-project.js
tsconfig.json
```

### Dependencies

- backend/requirements.txt: fastapi@==0.115.0, httpx@==0.27.2, pydantic@==2.9.2, pydantic-settings@==2.6.1, python-dotenv@==1.0.1, python-multipart@==0.0.9, SQLAlchemy@==2.0.36, uvicorn[standard]@==0.30.6
- package.json: @config-plugins/react-native-webrtc@^13.0.0, @daily-co/react-native-daily-js@^0.78.0, @daily-co/react-native-webrtc@^118.0.3-daily.4, @expo/vector-icons@^15.0.3, @react-native-async-storage/async-storage@^1.24.0, @react-navigation/bottom-tabs@^7.4.0, @react-navigation/elements@^2.6.3, @react-navigation/native@^7.1.8, @types/react@~19.1.0, @vapi-ai/react-native@^0.3.0, eslint@^9.25.0, eslint-config-expo@~10.0.0, expo@~54.0.20, expo-av@~16.0.7, expo-blur@~15.0.7, expo-build-properties@^1.0.9, expo-constants@~18.0.10, expo-file-system@~19.0.17, expo-font@~14.0.9, expo-haptics@~15.0.7, expo-image@~3.0.10, expo-linear-gradient@~15.0.7, expo-linking@~8.0.8, expo-router@~6.0.13, expo-splash-screen@~31.0.10, expo-status-bar@~3.0.8, expo-symbols@~1.0.7, expo-system-ui@~6.0.8, expo-web-browser@~15.0.8, react@19.1.0, react-dom@19.1.0, react-native@0.81.5, react-native-background-timer@^2.4.1, react-native-gesture-handler@~2.28.0, react-native-get-random-values@^1.11.0, react-native-reanimated@~4.1.1, react-native-safe-area-context@~5.6.0, react-native-screens@~4.16.0, react-native-web@~0.21.0, react-native-worklets@0.5.1, typescript@~5.9.2

### Recent commits (newest first)

- Update project overview in README
- Revise README with project details and setup instructions
- created new results view; refactored the cards from explore tab into a new view
- redesigned second tab to be like figma mockup
- redesigned first page to figma mockup
- Add .env to .gitignore
- replace Vapi with native voice recording and search
- version 1
- Initial commit

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

### package.json

```
{
  "name": "my-app",
  "main": "expo-router/entry",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start",
    "reset-project": "node ./scripts/reset-project.js",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web",
    "lint": "expo lint"
  },
  "dependencies": {
    "@daily-co/react-native-daily-js": "^0.78.0",
    "@daily-co/react-native-webrtc": "^118.0.3-daily.4",
    "@expo/vector-icons": "^15.0.3",
    "@react-native-async-storage/async-storage": "^1.24.0",
    "@react-navigation/bottom-tabs": "^7.4.0",
    "@react-navigation/elements": "^2.6.3",
    "@react-navigation/native": "^7.1.8",
    "@vapi-ai/react-native": "^0.3.0",
    "expo": "~54.0.20",
    "expo-av": "~16.0.7",
    "expo-blur": "~15.0.7",
    "expo-constants": "~18.0.10",
    "expo-file-system": "~19.0.17",
    "expo-font": "~14.0.9",
    "expo-haptics": "~15.0.7",
    "expo-image": "~3.0.10",
    "expo-linear-gradient": "~15.0.7",
    "expo-linking": "~8.0.8",
    "expo-router": "~6.0.13",
    "expo-splash-screen": "~31.0.10",
    "expo-status-bar": "~3.0.8",
    "expo-symbols": "~1.0.7",
    "expo-system-ui": "~6.0.8",
    "expo-web-browser": "~15.0.8",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "react-native": "0.81.5",
    "react-native-background-timer": "^2.4.1",
    "react-native-gesture-handler": "~2.28.0",
    "react-native-get-random-values": "^1.11.0",
    "react-native-reanimated": "~4.1.1",
    "react-native-safe-area-context": "~5.6.0",
    "react-native-screens": "~4.16.0",
    "react-native-web": "~0.21.0",
    "react-native-worklets": "0.5.1"
  },
  "devDependencies": {
    "@config-plugins/react-native-webrtc": "^13.0.0",
    "@types/react": "~19.1.0",
    "eslint": "^9.25.0",
    "eslint-config-expo": "~10.0.0",
    "expo-build-properties": "^1.0.9",
    "typescript": "~5.9.2"
  },
  "private": true
}

```

### backend/requirements.txt

```
fastapi==0.115.0
uvicorn[standard]==0.30.6
pydantic==2.9.2
pydantic-settings==2.6.1
httpx==0.27.2
python-dotenv==1.0.1
SQLAlchemy==2.0.36
python-multipart==0.0.9

```

### app/_layout.tsx

```typescript
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-get-random-values';
import 'react-native-reanimated';
import 'react-native-gesture-handler';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

import { useColorScheme } from '@/hooks/use-color-scheme';

export const unstable_settings = {
  anchor: '(tabs)',
};

export default function RootLayout() {
  const colorScheme = useColorScheme();

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
        <Stack>
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
        </Stack>
        <StatusBar style="auto" />
      </ThemeProvider>
    </GestureHandlerRootView>
  );
}

```

### app/(tabs)/_layout.tsx

```typescript
import { Tabs } from 'expo-router';
import React from 'react';

import { HapticTab } from '@/components/haptic-tab';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';

export default function TabLayout() {
  const colorScheme = useColorScheme();

  return (
    <Tabs
      screenOptions={{
        tabBarStyle: { display: 'none' },
        tabBarShowLabel: false,
        headerShown: false,
      }}>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Chat',
          tabBarIcon: ({ color }) => (
            <IconSymbol size={28} name="bubble.left.and.bubble.right.fill" color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="results"
        options={{
          title: 'Results',
        }}
      />
    </Tabs>
  );
}

```

### backend/app/main.py

```python
import json
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.config import settings
from app.db import init_db, Thought, SessionLocal, upsert_thought_fts
from app.routers.thoughts import router as thoughts_router
from app.routers.search import router as search_router
from app.routers.vapi_tools import router as vapi_tools_router
from app.routers.audio import router as audio_router

app = FastAPI(title="Backend", version="1.0.0")

origins = [o.strip() for o in settings.ALLOW_ORIGINS.split(",") if o.strip()]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins or ["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(thoughts_router, prefix="/v1")
app.include_router(search_router, prefix="/v1")
app.include_router(vapi_tools_router, prefix="/v1")
app.include_router(audio_router, prefix="/v1")


@app.on_event("startup")
def on_startup():
    init_db()
    db = SessionLocal()
    try:
        user_id = "demo"
        samples = [
            {"title": "Fishing startup note", "content": "Comment about the fishing startup last week.", "tags": ["fishing", "startup"]},
            {"title": "Pitch deck reminder", "content": "Reminder: prepare the pitch deck for the fintech demo on Tuesday.", "tags": ["reminder", "fintech", "pitch"]},
            {"title": "Hiring meeting notes", "content": "Meeting notes: talked to Sarah about hiring a full-stack engineer.", "tags": ["hiring", "meeting"]},
            {"title": "Creator CRM idea", "content": "Idea: lightweight CRM for solo creators with AI summaries.", "tags": ["idea", "crm", "ai"]},
            {"title": "Workout log", "content": "Workout log: 5k run in 26 minutes at the park.", "tags": ["fitness", "running"]},
            {"title": "Mic research", "content": "Research: best microphones for iOS recording in noisy rooms.", "tags": ["research", "audio", "ios"]},
        ]
        for s in samples:
            t = Thought(
                user_id=user_id,
                source="manual",
                title=s.get("title"),
                summary=None,
                content=s.get("content", ""),
                tags_json=json.dumps(s.get("tags", []), ensure_ascii=False),
                entities_json=json.dumps(s.get("entities", []), ensure_ascii=False),
                interpretation=None,
            )
            db.add(t)
        db.commit()
        rows = db.query(Thought).filter(Thought.user_id == user_id).all()
        for r in rows:
            upsert_thought_fts(r)
    finally:
        db.close()


@app.get("/health")
def health():
    return {"ok": True}

```

### app/(tabs)/index.tsx

```typescript
import { Audio } from 'expo-av';
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { StyleSheet, Animated, View, Dimensions } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import Constants from 'expo-constants';
import { ThemedText } from '@/components/themed-text';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import { useRouter } from 'expo-router';

const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');

export default function HomeScreen() {
  const router = useRouter();
  const [transcribedText, setTranscribedText] = useState('');
  const [hasDetectedSpeech, setHasDetectedSpeech] = useState(false);
  const recordingRef = useRef<Audio.Recording | null>(null);
  const intervalRef = useRef<any>(null);
  const switchingRef = useRef<boolean>(false);

  // Blinking cursor animation
  const cursorOpacity = useRef(new Animated.Value(1)).current;

  // Use useFocusEffect to start/stop recording when tab is focused/unfocused
  useFocusEffect(
    useCallback(() => {
      // Small delay to ensure previous tab has cleaned up
      const timer = setTimeout(() => {
        startRecording();
      }, 100);

      // Cleanup when tab loses focus
      return () => {
        clearTimeout(timer);
        // Stop recording
        const rec = recordingRef.current;
        if (rec) {
          rec.stopAndUnloadAsync().catch(() => {});
          recordingRef.current = null;
        }
        // Clear interval
        if (intervalRef.current) {
          clearInterval(intervalRef.current);
          intervalRef.current = null;
        }
        // Reset audio mode
        Audio.setAudioModeAsync({ allowsRecordingIOS: false }).catch(() => {});
        // Reset state
        setHasDetectedSpeech(false);
        setTranscribedText('');
      };
    }, [])
  );

  const onPanGestureEvent = useCallback((e: any) => {
    const ty = e?.nativeEvent?.translationY ?? 0;
    const vy = e?.nativeEvent?.velocityY ?? 0;
    if (!switchingRef.current && ty < -90 && vy < -200) {
      switchingRef.current = true;
      router.replace('/(tabs)/explore');
    }
  }, [router]);

  const onPanStateChange = useCallback((e: any) => {
    const st = e?.nativeEvent?.state;
    if (st === State.END || st === State.CANCELLED || st === State.FAILED) {
      switchingRef.current = false;
    }
  }, []);

  // Cursor blinking animation - only when no speech detected
  useEffect(() => {
    if (!hasDetectedSpeech) {
      Animated.loop(
        Animated.sequence([
          Animated.timing(cursorOpacity, {
            toValue: 0,
            duration: 530,
            useNativeDriver: true,
          }),
          Animated.timing(cursorOpacity, {
            toValue: 1,
            duration: 530,
            useNativeDriver: true,
          }),
        ])
      ).start();
    } else {
      cursorOpacity.setValue(1);
    }
  }, [hasDetectedSpeech]);

  const startRecording = async () => {
    try {
      const perm = await Audio.requestPermissionsAsync();
      if (!perm.granted) return;
      await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true });
      const recording = new Audio.Recording();
      const baseOpts: any = Audio.RecordingOptionsPresets.HIGH_QUALITY as any;
      const options: any = {
        ...baseOpts,
        isMeteringEnabled: true,
      };
      await recording.prepareToRecordAsync(options);
      await recording.startAsync();
      recordingRef.current = recording;

      // Monitor for speech detection
      monitorSpeech(recording);
    } catch (e) {
      console.log('startRecording error', e);
    }
  };

  const monitorSpeech = (recording: Audio.Recording) => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }
    intervalRef.current = setInterval(async () => {
      try {
        const status: any = await recording.getStatusAsync();
        if (!status?.isRecording) {
          clearInterval(intervalRef.current);
          intervalRef.current = null;
          return;
        }
        const level = typeof status.metering === 'number' ? status.metering : null;

        // Detect speech (level > -35 dB indicates voice activity)
        if (level !== null && level > -35 && !hasDetectedSpeech) {
          setHasDetectedSpeech(true);
          // Stop after detecting speech and some silence
          setTimeout(() => {
            stopRecordingAndProcess();
            if (intervalRef.current) {
              clearInterval(intervalRef.current);
              intervalRef.current = null;
            }
          }, 2000);
        }
      } catch (e) {
        if (intervalRef.current) {
          clearInterval(intervalRef.current);
          intervalRef.current = null;
        }
      }
    }, 200);
  };

  const stopRecordingAndProcess = async () => {
    const rec = recordingRef.current;
    if (!rec) return;
    try {
      await rec.stopAndUnloadAsync();
      const uri = rec.getURI();
      recordingRef.current = null;
      if (!uri) return;

      const baseUrl =
        ((Constants.expoConfig?.extra as any)?.backendUrl as string) ||
        (process.env.BACKEND_URL as string) ||
        'http://127.0.0.1:8082';
      const apiKey =
        ((Constants.expoConfig?.extra as any)?.backendApiKey as string) ||
        (process.env.API_KEY as string) ||
        '';
      const headers: any = {};
      if (apiKey) headers['X-API-Key'] = apiKey;
      const userId =
        ((Constants.expoConfig?.extra as any)?.backendUserId as string) ||
        (process.env.BACKEND_USER_ID as string) ||
        '';
      if (userId) headers['X-User-Id'] = userId;
      const fileName = uri.split('/').pop() || 'recording.m4a';
      const form = new FormData();
      form.append('file', { uri, name: fileName, type: 'audio/m4a' } as any);

      const response = await fetch(`${baseUrl}/v1/transcribe`, {
        method: 'POST',
        headers,
       
[truncated — 3680 more characters]
```

### babel.config.js

```javascript
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      'react-native-reanimated/plugin', // must be last
    ],
  };
};

```

### eslint.config.js

```javascript
// https://docs.expo.dev/guides/using-eslint/
const { defineConfig } = require('eslint/config');
const expoConfig = require('eslint-config-expo/flat');

module.exports = defineConfig([
  expoConfig,
  {
    ignores: ['dist/*'],
  },
]);

```

### hooks/use-color-scheme.ts

```typescript
export { useColorScheme } from 'react-native';

```

### components/hello-wave.tsx

```typescript
import Animated from 'react-native-reanimated';

export function HelloWave() {
  return (
    <Animated.Text
      style={{
        fontSize: 28,
        lineHeight: 32,
        marginTop: -6,
        animationName: {
          '50%': { transform: [{ rotate: '25deg' }] },
        },
        animationIterationCount: 4,
        animationDuration: '300ms',
      }}>
      👋
    </Animated.Text>
  );
}

```

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