# Project export: Food finder

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 10.0
- Tagline: Use food finder chat service to find restaurants near you and get intelligent recommendations
- Devpost: https://devpost.com/software/chatapp-np4hab
- GitHub: https://github.com/am831/cal_hacks
- Team: 4 GitHub contributor(s) — Alisha Maddy (54 commits), theboredkb (20 commits), Han K Ngo (6 commits), lena6zp (3 commits)

## Devpost submission (written by the team)

### Overview

##

### Inspiration

The inspiration for our restaurant recommender app came from a desire to make dining out a more personalized and enjoyable experience. We wanted to create a tool that would take user preferences and references into account, providing highly tailored restaurant recommendations. By integrating the Yelp API for business data and the OpenAI API for generating responses, we aimed to deliver a unique and interactive way for users to discover great places to eat.

### What it does

Our app is a chat bot that provides intelligent restaurant recommendations based on the user's preferences and location.

### How we built it

Our tech stack is fastAPI back end and react front end. We used Yelp API to get restaurants near the users current location, retrieved from the user's IP. We then constructed a prompt for OpenAI using relevant information from the Yelp API response. This provides the chat bot with context for the conversation, allowing it to have knowledge nearby food options. For example, the user can ask for "vegan" and the bot is intelligent enough to recommend something like "Indian".

### Challenges we ran into

One of the challenges we faced was getting the app running and getting the frontend and backend to communicate with one another. We were using flask at first and post requests were being redirected to get requests. We thought it was the network or something wrong with the frontend but once we refactored to fastAPI that resolved the issues we were facing.

### Accomplishments we're proud of

We are able to make the connection between the frontend and backend, and the chatbot is able to provide the user with intelligent restaurant recommendations near the user's location.

### What we learned

We learn to work with web frameworks and tools like Flask, FastAPI, OpenAI, React/TypeSrcipt, YelpAPI and we got good practice with teamwork and problem solving.

### What's next

We want to use MapsAPI to calculate the distance between each restaurant and the user. So the user can ask for recommendations within a certain distance.

## README (from the GitHub repository)

## GPT Food Finder
GPT Food Finder is a chat bot that provides intelligent restaurant recommendations based on the user's preferences and location.

## Tech Stack and Tools
Our tech stack is fastAPI back end and react front end. We used Yelp API to get restaurants near the users current location, retrieved from the user's IP. We then constructed a prompt for OpenAI using relevant information from the Yelp API response. This provides the chat bot with context for the conversation, allowing it to have knowledge nearby food options. For example, the user can ask for "vegan" and the bot is intelligent enough to recommend something like "Indian".

## How to Use
- clone the project
- Get a free openAI and YelpFusion API keys
  
Run frontend server:
- cd frontend/react-typescript
- npm install
- npm run dev
  
Run backend server:
- pip install -r requirements.txt
- uvicorn main:app --reload

## Example
![hacks](https://github.com/am831/cal_hacks/assets/59581465/0a2c11aa-2a33-4f17-a2ed-147ae10cf374)


## Detected evidence (automated analysis)

Indexed codebase: 23 recognized source files, 15 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — 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
- FastAPI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (31 of 31)

```
.gitignore
env_example.txt
frontend/react-typescript/.eslintrc.cjs
frontend/react-typescript/.gitignore
frontend/react-typescript/index.html
frontend/react-typescript/package.json
frontend/react-typescript/postcss.config.js
frontend/react-typescript/README.md
frontend/react-typescript/src/App.tsx
frontend/react-typescript/src/components/ChatBubble.tsx
frontend/react-typescript/src/components/ChatPage.tsx
frontend/react-typescript/src/components/Footer.tsx
frontend/react-typescript/src/components/Login.tsx
frontend/react-typescript/src/components/NavBar.tsx
frontend/react-typescript/src/components/SettingsModal.tsx
frontend/react-typescript/src/index.css
frontend/react-typescript/src/main.tsx
frontend/react-typescript/src/models/location.ts
frontend/react-typescript/src/models/messages.ts
frontend/react-typescript/src/services/location.ts
frontend/react-typescript/src/services/messages.ts
frontend/react-typescript/src/services/settings.ts
frontend/react-typescript/src/vite-env.d.ts
frontend/react-typescript/tailwind.config.js
frontend/react-typescript/tsconfig.json
frontend/react-typescript/tsconfig.node.json
frontend/react-typescript/vite.config.ts
main.py
README.md
requirements.txt
templates/index.html
```

### Dependencies

- frontend/react-typescript/package.json: @types/react@^18.2.15, @types/react-dom@^18.2.7, @typescript-eslint/eslint-plugin@^6.0.0, @typescript-eslint/parser@^6.0.0, @vitejs/plugin-react-swc@^3.3.2, autoprefixer@^10.4.16, axios@^1.6.0, daisyui@^3.9.4, dotenv@^16.3.1, eslint@^8.45.0, eslint-plugin-react-hooks@^4.6.0, eslint-plugin-react-refresh@^0.4.3, postcss@^8.4.31, react@^18.2.0, react-dom@^18.2.0, tailwindcss@^3.3.5, typescript@^5.0.2, vite@^4.4.5

### Recent commits (newest first)

- Update README.md
- Update README.md
- Update README.md
- change html render
- Built the react page
- Merge branch 'main' of github.com:am831/cal_hacks
- fix logic
- Updated loading logic
- fix html render
- Updated services to 8000 port
- Updated services
- return html from base route
- fix openai error
- fix openai
- switch to fast api
- fix unique id type error
- added unique id in message_sent
- Merge branch 'main' of github.com:am831/cal_hacks
- Updated routes
- Delete

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

### requirements.txt

```
��a i o h t t p = = 3 . 8 . 6  
 a i o s i g n a l = = 1 . 3 . 1  
 a n n o t a t e d - t y p e s = = 0 . 6 . 0  
 a n y i o = = 3 . 7 . 1  
 a s g i r e f = = 3 . 7 . 2  
 a s y n c - t i m e o u t = = 4 . 0 . 3  
 a t t r s = = 2 3 . 1 . 0  
 b l i n k e r = = 1 . 6 . 3  
 c e r t i f i = = 2 0 2 3 . 7 . 2 2  
 c h a r s e t - n o r m a l i z e r = = 3 . 3 . 1  
 c l i c k = = 8 . 1 . 7  
 c o l o r a m a = = 0 . 4 . 6  
 f a s t a p i = = 0 . 1 0 4 . 0  
 f r o z e n l i s t = = 1 . 4 . 0  
 g e o g r a p h i c l i b = = 2 . 0  
 g e o p y = = 2 . 4 . 0  
 h 1 1 = = 0 . 1 4 . 0  
 i d n a = = 3 . 4  
 i t s d a n g e r o u s = = 2 . 1 . 2  
 J i n j a 2 = = 3 . 1 . 2  
 M a r k u p S a f e = = 2 . 1 . 3  
 m u l t i d i c t = = 6 . 0 . 4  
 o p e n a i = = 0 . 2 8 . 1  
 p y d a n t i c = = 2 . 4 . 2  
 p y d a n t i c _ c o r e = = 2 . 1 0 . 1  
 r e q u e s t s = = 2 . 3 1 . 0  
 s n i f f i o = = 1 . 3 . 0  
 s t a r l e t t e = = 0 . 2 7 . 0  
 t q d m = = 4 . 6 6 . 1  
 t y p i n g _ e x t e n s i o n s = = 4 . 8 . 0  
 u r l l i b 3 = = 2 . 0 . 7  
 u v i c o r n = = 0 . 2 3 . 2  
 W e r k z e u g = = 3 . 0 . 1  
 y a r l = = 1 . 9 . 2  
 
```

### frontend/react-typescript/package.json

```
{
  "name": "react-typescript",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.6.0",
    "dotenv": "^16.3.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "@vitejs/plugin-react-swc": "^3.3.2",
    "autoprefixer": "^10.4.16",
    "daisyui": "^3.9.4",
    "eslint": "^8.45.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "postcss": "^8.4.31",
    "tailwindcss": "^3.3.5",
    "typescript": "^5.0.2",
    "vite": "^4.4.5"
  }
}

```

### main.py

```python
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Any, List
import requests
import os
import json
import openai
import time
import uuid
from fastapi.templating import Jinja2Templates
import geocoder
from fastapi.staticfiles import StaticFiles


app = FastAPI()

# Enable CORS to allow cross-origin requests
origins = ["*"]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_methods=["*"],
    allow_headers=["*"],
)

def load_env(file_path='.env'):
    try:
        with open(file_path, 'r') as file:
            for line in file:
                if '=' in line:
                    key, value = line.strip().split('=', 1)
                    os.environ[key] = value
    except FileNotFoundError:
        print(f"{file_path} not found. Make sure the .env file exists.")

load_env()
yelp_api_key = os.environ.get("YELP_API")
openai_api_key = os.environ.get("OPENAI_API")
YELP_HOST = 'https://api.yelp.com'
SEARCH_PATH = '/v3/businesses/search'
YELP_SEARCH_LIMIT = 20
app.mount("/fontend/react-typescript", StaticFiles(directory="frontend/react-typescript"), name="index.html")

class Location(BaseModel):
    latitude: float
    longitude: float

class UserMessage(BaseModel):
    text: str

messages = []

def _request(host, path, api_key, url_params=None):
    url_params = url_params or {}
    url = f"{host}{path}"
    headers = {
        'Authorization': f'Bearer {api_key}',
    }
    print(f'Querying {url} ...')
    response = requests.get(url, headers=headers, params=url_params)
    return response.json()

def get_restaurant_info(latitude, longitude):
    url_params = {
        'term': "food",
        'latitude': latitude,
        'longitude': longitude,
        'limit': YELP_SEARCH_LIMIT,
        'radius': 25
    }
    json_data = _request(YELP_HOST, SEARCH_PATH, yelp_api_key, url_params)
    restaurants = json_data["businesses"]
    data = {}  # name and cuisine type
    extra = {}  # address and rating
    for restaurant in restaurants:
        entry = {}
        entry2 = {}
        name = restaurant["name"]
        categories = restaurant["categories"]
        category_aliases = [category["title"] for category in categories]
        id = restaurant["id"]
        coords2 = restaurant["coordinates"]
        location = restaurant["location"]
        rating = restaurant["rating"]
        entry = {"name": name, "categories": category_aliases}
        entry2 = {"name": name, "location": location, "rating": rating, "coordinates": coords2}
        data[id] = entry
        extra[id] = entry2
    data_json = json.dumps(data)
    extra_json = json.dumps(extra)
    return data_json, extra_json

location = geocoder.ip('me')
latitude, longitude = location.latlng[0], location.latlng[1]
data, extra_info = get_restaurant_info(latitude, longitude)
data_string = json.dumps(data)
messages.append({"role": "system", "content": "You are a professional restaurant reviewer and reocmmender."})
messages.append({"role": "system", "content": "The user is located in San Francisco."})
messages.append({"role": "user", "content": "Here is data about restaurants in JSON format. Use this data to answer my questions. " + data_string})
messages.append({"role": "user", "content": "Here is extra info about restaurants in JSON format. Use this data to answer my questions. " + extra_info})

@app.get("/", response_class=HTMLResponse)
async def render_html(request: Request):
    with open("frontend/dist/index.html", "r") as f:
        html_content = f.read()
    return HTMLResponse(content=html_content)

@app.post("/location")
def init(location: Location):
    """
    location = geocoder.ip('me')
    latitude, longitude = location.latlng.split(',')
    data, extra_info = get_restaurant_info(latitude, longitude)
    data_string = json.dumps(data)
    messages.append({"role": "user", "content": "Here is data about restaurants in JSON format. Use this data to answer my questions. " + data_string})
    messages.append({"role": "user", "content": "Here is extra info about restaurants in JSON format. Use this data to answer my questions. " + extra_info})
    """

    return {"success": True}

@app.post("/messages/")
def message_sent(data: UserMessage):
    
    user_message = data.text
    messages.append({"role": "user", "content": user_message})
    response = _send_chat_request()
    messages.append({"role": "assistant", "content": response['choices'][0]['message']['content']})
    unique_id = abs(hash(uuid.uuid4()))
    message_dto = {
        "id": unique_id,
        "date": int(time.time() * 1000),
        "text": response['choices'][0]['message']['content'],
        "sender": "bot",
    }
    return message_dto

def _send_chat_request():
    '''
    Helper function that handles the chat request to the OpenAI API.
    '''
    response = ""
    openai.api_key = openai_api_key
    try:
        response: Any = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=messages,
        )
    except Exception as exception:
        raise OpenAIServiceError(
            f"OpenAI service failed to complete the chat: {exception}"
        ) from exception
    return response

class OpenAIServiceError(Exception):
    """
    Custom exception for OpenAI service errors.
    """

    def __init__(self, message):
        super().__init__(message)

if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000, debug=True)
```

### frontend/react-typescript/src/main.tsx

```typescript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

```

### frontend/react-typescript/src/App.tsx

```typescript
import { useEffect } from 'react'
import { ChatPage } from './components/ChatPage'
import { NavBar } from './components/NavBar'
import { postUserLocation } from './services/location'

function App() {
  useEffect(() => {
    postUserLocation()
  }, [])

  return (
    <div className="flex flex-col overflow-hidden">
      <NavBar></NavBar>
      <ChatPage></ChatPage>
    </div>
  )
}

export default App

```

### templates/index.html

```html
<!DOCTYPE html>
<html>
<head>
    <title>My HTML Page</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>
```

### frontend/react-typescript/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/react-typescript/vite.config.ts

```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### frontend/react-typescript/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [require('daisyui')],
}

```

### frontend/react-typescript/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

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