# Project export: Eki

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 11.0
- Tagline: Explore the world through the collection of virtual sticker and stamps, with the vast knowledge of the internet by your side.
- Devpost: https://devpost.com/software/eki
- GitHub: https://github.com/andrewy1n/Eki
- Video: https://www.youtube.com/embed/dyC0rvYdpEY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Andrew Yin (8 commits), John Zhou (6 commits)

## Devpost submission (written by the team)

### Inspiration

Our main inspiration came from a hiking trip that our team of two had at Joshua Tree a few weeks back, and from that trip we got bought stickers as means of remembrance of our experience. It made us think about how powerful something so small can have on us, whether it be a sticker, a photo, or a necklace. The memories and experiences we make are what give these keepsakes and souvenirs their value, and it's also what makes them valuable in our mind. Which is why we decided to create Eki, a way we can create these memorabilia virtually for our travels.

### What it does

Eki is an iOS mobile application made to memorialize your experiences through the collection of AI generated stickers and stamps of your photos, which also includes an attraction recommender powered by Llama and internet access through LLM tools. The app is heavily integrated with the Google Maps API, which gives users easy access to general traveling information.

### How we built it

Technologies: React Native/Expo - Frontend Python, FastAPI - Backend Firebase - Database, Authentication, Bucket Storage Hyperbolic, Groq-API - Access To LLM, Vision, and Image Generation Models through Cloud For Frontend development we used the tools by Expo and React Native to develop in Expo Go, a sandboxed iOS development environment. For the backend server, we used FastAPI as the framework, and Firebase was used for our main database, as well as storage for images. We also used Firebase Auth for user authentication client side. We used Hyperbolic for our sticker image generation pipeline, using the Llama vision model and the Flux1.dev image generation model in conjunction. We used Groq-API Llama LLM model, Toolhouse, and Google Maps API for our attraction recommendation algorithm.

### Challenges we ran into

One our main problems was figuring a way to get relatively consistent results for turning an image into a sticker/stamp. We first tried many of the image-to-image generation models, including those on HuggingFace and Hyperbolic, thought their results were not ideal. We eventually tried the method of using a vision model to describe an image, then using a generation model to create an entirely new image from scratch, and this way was surprisingly better and more consistent, and was especially well with the Flux1.dev model. A bulk of our issues were more frontend related, as Expo released a new SDK recently and it caused a lot of package deprecations that rarely had solutions online. We were forced to pivot a lot on some of our initial ideas as a result of this.

### Accomplishments we're proud of

It was our first time working with image generation models for a hackathon project, and we believe we executed on that aspect of our app well. Even though we encountered many many bugs in this hack, we pushed through and got something done, in expense of our sleep.

### What we learned

We learned that being prepared is always important, even at hackathons. It's often the dumbest bugs that take the most time, and we just need to accept that struggling will eventually happen.

### What's next

We hope to add more social aspects into the app, such as friend and community postings, and sharing and customizing stickers. Users will be able to connect with friends, explore each other’s virtual stamp collections, and collaborate on scrapbooks. Variety in stamps, stickers, and pins a definitely a must, and we hope to create more interesting stickers through image captioning to image generation rather than image to image, as well as removing excessive background with content awareness. We also aim to introduce challenges, achievements, and leaderboards to enhance engagement and motivate exploration in the community.

## README (from the GitHub repository)

## Inspiration
Our main inspiration came from a hiking trip that our team of two had at Joshua Tree a few weeks back, and from that trip we got bought stickers as means of remembrance of our experience. It made us think about how powerful something so small can have on us, whether it be a sticker, a photo, or a necklace. The memories and experiences we make are what give these keepsakes and souvenirs their value, and it's also what makes them valuable in our mind. Which is why we decided to create Eki, a way we can create these memorabilia virtually for our travels.

## What it does
Eki is an iOS mobile application made to memorialize your experiences through the collection of AI generated stickers and stamps of your photos, which also includes an attraction recommender powered by Llama and internet access through LLM tools. The app is heavily integrated with the Google Maps API, which gives users easy access to general traveling information.

## How we built it
### Technologies:
- React Native/Expo - Frontend
- Python, FastAPI - Backend
- Firebase - Database, Authentication, Bucket Storage
- Hyperbolic, Groq-API - Access To LLM, Vision, and Image Generation Models through Cloud \
\
For Frontend development we used the tools by Expo and React Native to develop in Expo Go, a sandboxed iOS development environment. For the backend server, we used FastAPI as the framework, and Firebase was used for our main database, as well as storage for images. We also used Firebase Auth for user authentication client side. We used Hyperbolic for our sticker image generation pipeline, using the Llama vision model and the Flux1.dev image generation model in conjunction. We used Groq-API Llama LLM model, Toolhouse, and Google Maps API for our attraction recommendation algorithm.

## Challenges we ran into
- One our main problems was figuring a way to get relatively consistent results for turning an image into a sticker/stamp. We first tried many of the image-to-image generation models, including those on HuggingFace and Hyperbolic, their results were very poor. We eventually tried the method of using a vision model to describe an image, then using a generation model to create an entirely knew image from scratch, and this way was surprisingly better and more consistent, and was especially well with the Flux1.dev model.
- A bulk of our issues were more frontend related, as Expo released a new SDK recently and it caused a lot of package deprecations that rarely had solutions online. We were forced to pivot a lot on some of our initial ideas as a result of this.

## Accomplishments that we're proud of
It was our first time working with image generation models for a hackathon project, and we believe we executed on that aspect of our app well. Even though we encountered many many bugs in this hack, we pushed through and got something done, in expense of our sleep.

## What we learned
We learned that being prepared is always important, even at hackathons. It's often the dumbest bugs that take the most time, and we just need to accept that struggling will eventually happen.

## What's next for Eki
We hope to add more social aspects into the app, such as friend and community postings, and sharing and customizing stickers. Users will be able to connect with friends, explore each other’s virtual stamp collections, and collaborate on scrapbooks. We also aim to introduce challenges, achievements, and leaderboards to enhance engagement and motivate exploration.


## Detected evidence (automated analysis)

Indexed codebase: 53 recognized source files, 154 KB.
- Anthropic (technology) — detected in the code
- FastAPI (technology) — detected in the code
- Firebase (technology) — detected in the code
- JavaScript (language) — detected in the code
- LangChain (technology) — detected in the code
- OpenAI (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

## Codebase structure (from repository index)

### Files (60 of 60)

```
backend/main.py
backend/recommendation.py
backend/requirements.txt
backend/schemas.py
backend/tests/Groq __ Toolhouse.ipynb
backend/tests/test_image_generation.py
backend/tests/test_langchain.py
backend/tests/test_llama_vision.py
backend/tests/test_llm_query.py
backend/tests/test_maps_tool.py
backend/tests/test_upload.py
frontend/.gitignore
frontend/app.json
frontend/app/_layout.tsx
frontend/app/(tabs)/_layout.tsx
frontend/app/(tabs)/community.tsx
frontend/app/(tabs)/map.tsx
frontend/app/(tabs)/profile.tsx
frontend/app/(tabs)/stampbooks.tsx
frontend/app/+html.tsx
frontend/app/+not-found.tsx
frontend/app/createaccount.tsx
frontend/app/emptyeditor.tsx
frontend/app/index.tsx
frontend/app/stampededitor.tsx
frontend/app/stamppages.tsx
frontend/babel.config.js
frontend/components/DraggableImage.tsx
frontend/components/EmptyPage.tsx
frontend/components/EmptyPageEditor.tsx
frontend/components/LocationSearch.tsx
frontend/components/navigation/TabBarIcon.tsx
frontend/components/PageSpinner.tsx
frontend/components/SimpleBook.tsx
frontend/components/StampedPage.tsx
frontend/components/StampedPageEditor.tsx
frontend/components/TabSlider.tsx
frontend/components/ThemedText.tsx
frontend/components/ThemedView.tsx
frontend/constants/Colors.ts
frontend/context/AccountContext.tsx
frontend/countries+states+cities.json
frontend/data/testData.tsx
frontend/firebaseConfig.js
frontend/hooks/useAccount.ts
frontend/hooks/useCitySearch.ts
frontend/hooks/useColorScheme.ts
frontend/hooks/useColorScheme.web.ts
frontend/hooks/useThemeColor.ts
frontend/hooks/useUploadPhoto.ts
frontend/models/Account.ts
frontend/models/Location.ts
frontend/models/Page.ts
frontend/models/Stampbook.ts
frontend/package.json
frontend/README.md
frontend/tsconfig.json
frontend/utils/endpoints.ts
frontend/utils/storage.tsx
README.md
```

### Dependencies

- backend/requirements.txt: aiohappyeyeballs@==2.4.3, aiohttp@==3.10.10, aiosignal@==1.3.1, annotated-types@==0.7.0, anthropic@==0.36.2, anyio@==4.6.2.post1, asttokens@==2.4.1, attrs@==24.2.0, CacheControl@==0.14.0, cachetools@==5.5.0, certifi@==2024.8.30, cffi@==1.17.1, charset-normalizer@==3.4.0, click@==8.1.7, colorama@==0.4.6, comm@==0.2.2, cryptography@==43.0.3, debugpy@==1.8.7, decorator@==5.1.1, distro@==1.9.0, dnspython@==2.7.0, docstring_parser@==0.16, email_validator@==2.2.0, executing@==2.1.0, fastapi@==0.115.2, fastapi-cli@==0.0.5, filelock@==3.16.1, firebase-admin@==6.5.0, frozenlist@==1.4.1, fsspec@==2024.9.0, google-api-core@==2.21.0, google-api-python-client@==2.149.0, google-auth@==2.35.0, google-auth-httplib2@==0.2.0, google-cloud-core@==2.4.1, google-cloud-firestore@==2.19.0, google-cloud-storage@==2.18.2, google-crc32c@==1.6.0, google-resumable-media@==2.7.2, googleapis-common-protos@==1.65.0, greenlet@==3.1.1, groq@==0.11.0, grpcio@==1.67.0, grpcio-status@==1.67.0, h11@==0.14.0, http-exceptions@==0.2.10, httpcore@==1.0.6, httplib2@==0.22.0, httptools@==0.6.4, httpx@==0.27.2, huggingface-hub@==0.26.0, idna@==3.10, ipykernel@==6.29.5, ipython@==8.28.0, jedi@==0.19.1, Jinja2@==3.1.4, jiter@==0.5.0, jsonpatch@==1.33, jsonpointer@==3.0.0, jupyter_client@==8.6.3, jupyter_core@==5.7.2, langchain@==0.3.4, langchain-core@==0.3.12, langchain-groq@==0.2.0, langchain-text-splitters@==0.3.0, langsmith@==0.1.136, markdown-it-py@==3.0.0, MarkupSafe@==3.0.2, matplotlib-inline@==0.1.7, mdurl@==0.1.2, msgpack@==1.1.0, multidict@==6.1.0, nest-asyncio@==1.6.0, numpy@==1.26.4, openai@==1.52.0, orjson@==3.10.7, packaging@==24.1, pandas@==2.2.3, parso@==0.8.4, pillow@==11.0.0, platformdirs@==4.3.6, prompt_toolkit@==3.0.48, propcache@==0.2.0, proto-plus@==1.24.0, protobuf@==5.28.2, psutil@==6.1.0, pure_eval@==0.2.3, pyasn1@==0.6.1, pyasn1_modules@==0.4.1, pycparser@==2.22, pydantic@==2.9.2, pydantic_core@==2.23.4, Pygments@==2.18.0, PyJWT@==2.9.0, pyparsing@==3.2.0, python-dateutil@==2.9.0.post0, python-dotenv@==1.0.1, python-multipart@==0.0.12, pytz@==2024.2, pywin32@==308, PyYAML@==6.0.2, pyzmq@==26.2.0, requests@==2.32.3, requests-toolbelt@==1.0.0, rich@==13.9.2, rsa@==4.9, shellingham@==1.5.4, six@==1.16.0, sniffio@==1.3.1, SQLAlchemy@==2.0.36, stack-data@==0.6.3, starlette@==0.40.0, tenacity@==9.0.0, tokenizers@==0.20.1, toolhouse@==1.2.1, tornado@==6.4.1, tqdm@==4.66.5, traitlets@==5.14.3, typer@==0.12.5, typing_extensions@==4.12.2, tzdata@==2024.2, uritemplate@==4.1.1, urllib3@==2.2.3, uvicorn@==0.32.0, watchfiles@==0.24.0, wcwidth@==0.2.13, websockets@==13.1, yarl@==1.15.5
- frontend/package.json: @animatereactnative/marquee@^0.2.0, @babel/core@^7.20.0, @expo/vector-icons@^14.0.2, @react-native-async-storage/async-storage@1.23.1, @react-navigation/native@^6.0.2, @types/jest@^29.5.12, @types/react@~18.2.45, @types/react-native-maps@^0.24.2, @types/react-test-renderer@^18.0.7, axios@^1.7.7, expo@~51.0.28, expo-constants@~16.0.2, expo-font@~12.0.9, expo-haptics@~13.0.1, expo-image-picker@~15.0.7, expo-linear-gradient@~13.0.2, expo-linking@~6.3.1, expo-location@~17.0.1, expo-router@~3.5.23, expo-splash-screen@~0.27.5, expo-status-bar@~1.12.1, expo-system-ui@~3.0.7, expo-web-browser@~13.0.3, firebase@^9.23.0, jest@^29.2.1, jest-expo@~51.0.3, nativewind@^2.0.11, react@18.2.0, react-dom@18.2.0, react-native@0.74.5, react-native-gesture-handler@~2.16.1, react-native-maps@1.14.0, react-native-modal@^13.0.1, react-native-reanimated@~3.10.1, react-native-safe-area-context@4.10.5, react-native-screens@3.31.1, react-native-uuid@^2.0.2, react-native-web@~0.19.10, react-test-renderer@18.2.0, tailwindcss@^3.3.2, typescript@~5.3.3

### Recent commits (newest first)

- Update README.md
- done
- Merge branch 'main' of https://github.com/andrewy1n/Eki
- lotta bugs, finishing stampbooks
- Create README.md
- Merge branch 'main' of https://github.com/andrewy1n/Eki
- transformation and empty stamps
- requirements file
- stamp creation and get endpoints
- Merge branch 'main' of https://github.com/andrewy1n/Eki
- shi bar
- continued revisions, mostly done, need to finish create stampbook
- finished base work for stampbook shelf
- tabs created with styling and animation, working on stampbook page
- frontend boilerplate
- start

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

### frontend/package.json

```
{
  "name": "frontend",
  "main": "expo-router/entry",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start",
    "reset-project": "node ./scripts/reset-project.js",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "test": "jest --watchAll",
    "lint": "expo lint"
  },
  "jest": {
    "preset": "jest-expo"
  },
  "dependencies": {
    "@animatereactnative/marquee": "^0.2.0",
    "@expo/vector-icons": "^14.0.2",
    "@react-native-async-storage/async-storage": "1.23.1",
    "@react-navigation/native": "^6.0.2",
    "axios": "^1.7.7",
    "expo": "~51.0.28",
    "expo-constants": "~16.0.2",
    "expo-font": "~12.0.9",
    "expo-haptics": "~13.0.1",
    "expo-image-picker": "~15.0.7",
    "expo-linear-gradient": "~13.0.2",
    "expo-linking": "~6.3.1",
    "expo-location": "~17.0.1",
    "expo-router": "~3.5.23",
    "expo-splash-screen": "~0.27.5",
    "expo-status-bar": "~1.12.1",
    "expo-system-ui": "~3.0.7",
    "expo-web-browser": "~13.0.3",
    "firebase": "^9.23.0",
    "nativewind": "^2.0.11",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-native": "0.74.5",
    "react-native-gesture-handler": "~2.16.1",
    "react-native-maps": "1.14.0",
    "react-native-modal": "^13.0.1",
    "react-native-reanimated": "~3.10.1",
    "react-native-safe-area-context": "4.10.5",
    "react-native-screens": "3.31.1",
    "react-native-uuid": "^2.0.2",
    "react-native-web": "~0.19.10"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@types/jest": "^29.5.12",
    "@types/react": "~18.2.45",
    "@types/react-native-maps": "^0.24.2",
    "@types/react-test-renderer": "^18.0.7",
    "jest": "^29.2.1",
    "jest-expo": "~51.0.3",
    "react-test-renderer": "18.2.0",
    "tailwindcss": "^3.3.2",
    "typescript": "~5.3.3"
  },
  "private": true
}

```

### backend/requirements.txt

```
aiohappyeyeballs==2.4.3
aiohttp==3.10.10
aiosignal==1.3.1
annotated-types==0.7.0
anthropic==0.36.2
anyio==4.6.2.post1
asttokens==2.4.1
attrs==24.2.0
CacheControl==0.14.0
cachetools==5.5.0
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.4.0
click==8.1.7
colorama==0.4.6
comm==0.2.2
cryptography==43.0.3
debugpy==1.8.7
decorator==5.1.1
distro==1.9.0
dnspython==2.7.0
docstring_parser==0.16
email_validator==2.2.0
executing==2.1.0
fastapi==0.115.2
fastapi-cli==0.0.5
filelock==3.16.1
firebase-admin==6.5.0
frozenlist==1.4.1
fsspec==2024.9.0
google-api-core==2.21.0
google-api-python-client==2.149.0
google-auth==2.35.0
google-auth-httplib2==0.2.0
google-cloud-core==2.4.1
google-cloud-firestore==2.19.0
google-cloud-storage==2.18.2
google-crc32c==1.6.0
google-resumable-media==2.7.2
googleapis-common-protos==1.65.0
greenlet==3.1.1
groq==0.11.0
grpcio==1.67.0
grpcio-status==1.67.0
h11==0.14.0
http-exceptions==0.2.10
httpcore==1.0.6
httplib2==0.22.0
httptools==0.6.4
httpx==0.27.2
huggingface-hub==0.26.0
idna==3.10
ipykernel==6.29.5
ipython==8.28.0
jedi==0.19.1
Jinja2==3.1.4
jiter==0.5.0
jsonpatch==1.33
jsonpointer==3.0.0
jupyter_client==8.6.3
jupyter_core==5.7.2
langchain==0.3.4
langchain-core==0.3.12
langchain-groq==0.2.0
langchain-text-splitters==0.3.0
langsmith==0.1.136
markdown-it-py==3.0.0
MarkupSafe==3.0.2
matplotlib-inline==0.1.7
mdurl==0.1.2
msgpack==1.1.0
multidict==6.1.0
nest-asyncio==1.6.0
numpy==1.26.4
openai==1.52.0
orjson==3.10.7
packaging==24.1
pandas==2.2.3
parso==0.8.4
pillow==11.0.0
platformdirs==4.3.6
prompt_toolkit==3.0.48
propcache==0.2.0
proto-plus==1.24.0
protobuf==5.28.2
psutil==6.1.0
pure_eval==0.2.3
pyasn1==0.6.1
pyasn1_modules==0.4.1
pycparser==2.22
pydantic==2.9.2
pydantic_core==2.23.4
Pygments==2.18.0
PyJWT==2.9.0
pyparsing==3.2.0
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-multipart==0.0.12
pytz==2024.2
pywin32==308
PyYAML==6.0.2
pyzmq==26.2.0
requests==2.32.3
requests-toolbelt==1.0.0
rich==13.9.2
rsa==4.9
shellingham==1.5.4
six==1.16.0
sniffio==1.3.1
SQLAlchemy==2.0.36
stack-data==0.6.3
starlette==0.40.0
tenacity==9.0.0
tokenizers==0.20.1
toolhouse==1.2.1
tornado==6.4.1
tqdm==4.66.5
traitlets==5.14.3
typer==0.12.5
typing_extensions==4.12.2
tzdata==2024.2
uritemplate==4.1.1
urllib3==2.2.3
uvicorn==0.32.0
watchfiles==0.24.0
wcwidth==0.2.13
websockets==13.1
yarl==1.15.5

```

### backend/main.py

```python
from collections import defaultdict
from typing import Union
from fastapi import FastAPI, HTTPException, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import firebase_admin
from firebase_admin import credentials, auth, db, storage
from dotenv import load_dotenv
import os
from schemas import Book, Geocode, Location, Place, Size, Stamp, Transformation
import base64
import requests
from datetime import datetime, timedelta
from fastapi.encoders import jsonable_encoder
from recommendation import get_place_names

load_dotenv()

app = FastAPI()

origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

cred = credentials.Certificate(os.getenv("FIREBASE_CREDENTIALS"))
firebase_admin.initialize_app(cred, {
    'databaseURL': os.getenv("DATABASE_URL"),
    'storageBucket': os.getenv("STORAGE_BUCKET")
})

bucket = storage.bucket()

class UserCreateRequest(BaseModel):
    email: str
    password: str
    display_name: Union[str, None] = None
    profile_photo: Union[str, None] = None

class UserUpdateRequest(BaseModel):
    bio: Union[str, None] = None
    profile_photo: Union[str, None] = None

class BookCreateRequest(BaseModel):
    uid: str
    city: str
    state: str
    attractions: Union[list[Location], None] = None

class StampCreateRequest(BaseModel):
    uid: str
    bookid: str
    location_name: str
    geocode: Geocode
    photo_url: str
    stamp_url: str
    stamp_size: Size
    stamp_transformation: Transformation
    notes: Union[str, None] = None

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.post("/auth/create")
def create_user(user: UserCreateRequest):
    try:
        user_record = auth.create_user(
            email=user.email,
            password=user.password,
            display_name=user.display_name
        )

        user_data = {
            "email": user.email,
            "display_name": user.display_name,
            "profile_photo": user.profile_photo
        }

        ref = db.reference(f"/users/{user_record.uid}")
        ref.set(user_data)

        return {
            "message": "User created successfully",
            "user_id": user_record.uid,
            "email": user_record.email
        }
    
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.post("/account/update")
async def update_account(uid: str, update: UserUpdateRequest):
    user_ref = db.reference(f"/users/{uid}")
    updates = {}
    
    if update.profile_photo:
        updates['profile_photo'] = update.profile_photo
    
    if update.bio:
        updates['bio'] = update.bio
    
    if updates:
        user_ref.update(updates)
    
    return {
        "message": "User updated successfully",
    }

@app.post("/upload-photo")
async def upload_photo(file: UploadFile = File(...)):
    try:
        blob = bucket.blob(f"{file.filename}")
        blob.upload_from_file(file.file, content_type=file.content_type)
        url = blob.generate_signed_url(expiration=datetime.now() + timedelta(days=7))

        return {"message": "Photo uploaded successfully", "download_url": url}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/stampbook/create")
async def create_book(request: BookCreateRequest):
    try:
        url = "https://api.hyperbolic.xyz/v1/image/generation"
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {os.getenv('HYPERBOLIC_API_KEY')}"
        }

        data = {
            "model_name": "FLUX.1-dev",
            "prompt": f"make an illustration of {request.city}, {request.state}, include landmarks",
            "steps": 30,
            "cfg_scale": 5,
            "enable_refiner": False,
            "height": 1024,
            "width": 1024,
            "backend": "auto"
        }
        
        try:
            response = requests.post(url, headers=headers, json=data)
            response.raise_for_status()
            image_data = base64.b64decode(response.json().get('images', [{}])[0].get('image', ''))
            if not image_data:
                raise HTTPException(status_code=500, detail="Failed to retrieve image data from the API.")
        except requests.exceptions.RequestException as e:
            raise HTTPException(status_code=500, detail=f"Error with the API call for image generation: {str(e)}")
        except KeyError:
            raise HTTPException(status_code=500, detail="Unexpected response structure from the API when generating image.")
        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Error decoding image data: {str(e)}")
        
        blob = bucket.blob(f"ai_generated_image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png")
        blob.upload_from_string(image_data, content_type='image/png')
        url = blob.generate_signed_url(expiration=datetime.now() + timedelta(days=7))

        pages = None
        if request.attractions:
            pages = defaultdict(list)
            for attraction in request.attractions:
                print(attraction)
                starts_with = attraction.location_name[0].upper()
                pages[starts_with].append(
                    jsonable_encoder(attraction)
                )

        book_data = Book(
            cover=url,
            pages=pages,
            city=request.city,
            state=request.state
        )

        ref = db.reference(f"/users/{request.uid}/books").push(jsonable_encoder(book_data))

        return {
            "message": "Book created successfully",
            "book_id": ref.key,
            "book_data": jsonable_encoder(book_data)
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/stampbook/generate-stamp-image")
async def generate_stamp_image(reference_image: Up
[truncated — 8663 more characters]
```

### frontend/app/_layout.tsx

```typescript
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';
import 'react-native-reanimated';
import { useColorScheme } from '@/hooks/useColorScheme';

import { SafeAreaProvider } from 'react-native-safe-area-context';
import { AccountProvider } from '@/context/AccountContext';

// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
  const colorScheme = useColorScheme();
  const [loaded] = useFonts({
    SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
  });

  useEffect(() => {
    if (loaded) {
      SplashScreen.hideAsync();
    }
  }, [loaded]);

  if (!loaded) {
    return null;
  }

  return (
    <AccountProvider>
      <SafeAreaProvider>
          <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
            <Stack screenOptions={{headerShown: false}}>
              <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
              <Stack.Screen
                name="stamppages"
                options={{
                  headerShown: false,
                }}
              />
              <Stack.Screen
                name="emptyeditor"
                options={{
                  headerShown: false,
                }}
              />
              <Stack.Screen
                name="stampededitor"
                options={{
                  headerShown: false,
                }}
              />
              <Stack.Screen name="+not-found" />
            </Stack>
          </ThemeProvider>
      </SafeAreaProvider>
    </AccountProvider>
  );
}

```

### frontend/app/index.tsx

```typescript
import { router } from 'expo-router';
import React, { useEffect, useState } from 'react';
import { Platform, KeyboardAvoidingView, Dimensions, FlatList, Text, TextInput, Button, StyleSheet, Alert, TouchableOpacity, View, Image } from 'react-native';
import { signInWithEmailAndPassword } from "firebase/auth";
import AsyncStorage from '@react-native-async-storage/async-storage';
import { auth } from '../firebaseConfig'
import { Marquee } from '@animatereactnative/marquee';

const LoginScreen: React.FC = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const windowWidth = Dimensions.get('window').width;
  const images = [
    {
        id: '1',
        src: 'https://foyr.com/learn/wp-content/uploads/2019/01/Colosseum-Rome-Italy.jpg',
        styles: {width: '131px', height: '238px', marginLeft: '10px',}
    },
    {
        id: '2',
        src: 'https://foyr.com/learn/wp-content/uploads/2019/01/petronas-towers.jpg',
        styles: {width: '131px', height: '200px', marginLeft: '5px',}
    },
    {
        id: '3',
        src: 'https://foyr.com/learn/wp-content/uploads/2019/01/Palm-Jumeirah-1068x610.jpg',
        styles: {width: '131px', height: '220px', marginLeft: '5px',}
    },
    {
        id: '4',
        src: 'https://www.askanis.com/wp-content/uploads/2019/07/01-3.jpg',
        styles: {width: '131px', height: '178px', marginTop: '5px', marginLeft: '10px',}
    },
    {
        id: '5',
        src: 'https://www.cuddlynest.com/blog/wp-content/uploads/2020/09/neuschwanstein-castle-germany-most-beautiful-buildings-world.jpg',
        styles: {width: '131px', height: '216px', marginTop: '-32px', marginLeft: '5px',}
    },
    {
        id: '6',
        src: 'https://www.cuddlynest.com/blog/wp-content/uploads/2021/07/versailles-france-most-beautiful-buildings-world.jpg',
        styles: {width: '131px', height: '195px', marginTop: '-13px', marginLeft: '5px',}
    },
];

  const numColumns = 3;

  useEffect(() => { 
    const checkLoggedIn = async () => {
      const uid = await AsyncStorage.getItem('uid');
      if (uid) {
        router.replace('/(tabs)/map');
      }
    }
    checkLoggedIn();
  }, [])

  const handleLogin = async () => {
    signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
            const user = userCredential.user;
            AsyncStorage.setItem('uid', user.uid);
            router.push('/(tabs)/map');
        })
        .catch((error) => {
          const errorCode = error.code;
          const errorMessage = error.message;
          console.error(`Error: ${errorCode}, ${errorMessage}`);
          Alert.alert('Wrong password or email, please try again.')
        });
  };

  const handleCreateRoute = () => {
    router.push('/createaccount');
  }

  return (
    <KeyboardAvoidingView
      style={[styles.container, { }]}
      behavior={Platform.OS === 'ios' ? 'padding' : undefined}
    >
    <View style={styles.container}>
        <View>
            <Marquee spacing={-6} speed={1}>
                <FlatList
                    data={images}
                    keyExtractor={(item) => item.id}
                    renderItem={({ item }) => {
                      const { width, height, marginLeft, marginTop } = item.styles;
                    
                      return (
                        <Image
                          source={{ uri: item.src }}
                          style={[
                            {
                              width: parseInt(width.replace('px', '')),  // Convert to number
                              height: parseInt(height.replace('px', '')),  // Convert to number
                              marginLeft: marginLeft ? parseInt(marginLeft.replace('px', '')) : 0,  // Convert if exists
                              marginTop: marginTop ? parseInt(marginTop.replace('px', '')) : 0,  // Convert if exists
                              flex: 1,
                              borderRadius: 10,
                            },
                          ]}
                        />
                      );
                    }}
                    numColumns={numColumns}
                    bounces={false}
                    contentContainerStyle={{ width: windowWidth }}
                    showsHorizontalScrollIndicator={false}
                />
            </Marquee>
          </View>
          <View>
        <Text style={styles.title}>Login</Text>
        <TextInput
          style={styles.input}
          placeholder="Email"
          value={email}
          onChangeText={setEmail}
          keyboardType="email-address"
          autoCapitalize="none"
          placeholderTextColor={"gray"}
        />
        <TextInput
          style={styles.input}
          placeholder="Password"
          value={password}
          onChangeText={setPassword}
          secureTextEntry
          placeholderTextColor={"gray"}
        />
        <View style={styles.buttonRow}>
          <TouchableOpacity style={styles.button} onPress={handleLogin}>
            <Text style={styles.buttonText}>Login</Text>
          </TouchableOpacity>

          <TouchableOpacity style={styles.button} onPress={handleCreateRoute}>
            <Text style={styles.buttonText}>Crreate Account</Text>
          </TouchableOpacity>
        </View>
        </View>
    </View>
    </KeyboardAvoidingView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#000',
    paddingBottom:100,
    marginHorizontal: 10,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'left',
    marginBottom: 20,
    marginTop: 20,
    color: 'white',
  },
  buttonRow: {
    flexDirection: 'row',
    alignSelf: 'flex-end',
  },
  input: {
    height: 40,
    borderColor: '#ccc',
    borderWidth: 1,
    marginBottom: 10,
    paddingHorizontal: 10,
    borderRadius: 5,
    color: '#fff'
  },
  buttonText: {
    ma
[truncated — 243 more characters]
```

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

```typescript
import { Tabs } from 'expo-router';
import React from 'react';
import TabSlider from '@/components/TabSlider';
import { SafeAreaProvider } from 'react-native-safe-area-context';


export default function TabLayout() {
  return (
    <SafeAreaProvider>
        <Tabs
          initialRouteName="map"
          tabBar={(props) => <TabSlider {...props} />}
          screenOptions={{
            tabBarShowLabel: false,
            headerShown: false,
          }}
        >
          <Tabs.Screen name="map" options={{ title: 'Map' }} />
          <Tabs.Screen name="stampbooks" options={{ title: 'Stampbooks' }} />
          <Tabs.Screen name="profile" options={{ title: 'Profile' }} />
          <Tabs.Screen name="community" options={{ title: 'Community' }} />
        </Tabs>
    </SafeAreaProvider>
  );
}

```

### frontend/babel.config.js

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

```

### backend/schemas.py

```python
from pydantic import BaseModel
from typing import Union

class Geocode(BaseModel):
    lat: float
    lng: float

class Position(BaseModel):
    x: float
    y: float

class Size(BaseModel):
    width: float
    height: float

class Transformation(BaseModel):
    position: Position
    scale: float
    rotation: float

class Location(BaseModel):
    location_name: str
    geocode: Geocode

class Stamp(BaseModel):
    photo_url: str
    stamp_url: str
    stamp_transformation: Transformation
    stamp_size: Size
    date: str
    notes: Union[str, None] = None
    location_name: str
    geocode: Geocode

class Place(BaseModel):
    display_name: str
    google_maps_uri: str
    geocode: Geocode
    formatted_address: str
    rating: float
    photo_uri: str

class Book(BaseModel):
    cover: str
    pages: Union[dict[str, list[Location]], None] = None
    city: str
    state: str
```

### frontend/firebaseConfig.js

```javascript
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAuth } from 'firebase/auth';
import { getStorage } from "firebase/storage";

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyAFLTYEgyHV9dRaR0TcyA-Nk-lqmjwwnLo",
  authDomain: "calha-7b21c.firebaseapp.com",
  databaseURL: "https://calha-7b21c-default-rtdb.firebaseio.com",
  projectId: "calha-7b21c",
  storageBucket: "calha-7b21c.appspot.com",
  messagingSenderId: "883598294712",
  appId: "1:883598294712:web:d5238ce18328ad049d3e4d",
  measurementId: "G-LPMQND7JVH"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const storage = getStorage(app);
```

### backend/recommendation.py

```python
from typing import Union
from groq import Groq
from toolhouse import Toolhouse

def get_place_names(location: str, query: Union[str, None] = None):
    th = Toolhouse()
    
    client = Groq()
    MODEL = "llama3-groq-70b-8192-tool-use-preview"

    messages = [
        {
            "role": "system",
            "content": f"""Find a relevant website with attractions in {location} and about the keywords: {query}, then scrape from one website 5 attractions in {location}, 
            in a list format."""
        }
    ]

    response = client.chat.completions.create(
        model=MODEL,
        messages=messages,
        max_tokens=1000,
        tools=th.get_tools()
    )

    tool_run = th.run_tools(response)
    messages = messages + tool_run
    messages += [
        {
            "role": "user",
            "content": """Return a list of the names of 5 attractions in a list format,
            ex. [place1, place2, place3, place4, place5]"""
        }
    ]

    print(messages)
    
    final_response = client.chat.completions.create(
        model=MODEL,
        messages=messages,
        max_tokens=1000
    )

    return final_response.choices[0].message.content.strip().strip('[]').split(',')
```

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