# Project export: Coffee Compass

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: TreeHacks 2024
- Tagline: Coffee Recommendations for Bay Area Cafficionados
- Devpost: https://devpost.com/software/coffaimate
- GitHub: https://github.com/bortpro/treehacks
- Demo: https://www.figma.com/proto/ib8I5Eo0N7h0UwbZQECK10/TreeHacks-2024?page-id=2%3A19&type=design&node-id=10-382&viewport=165%2C148%2C0.03&t=7eJD2mJO0lpFOGoK-1&scaling=min-zoom&starting-point-node-id=10%3A382&show-proto-sidebar=1&mode=design
- Team: 1 GitHub contributor(s) — bortpro (2 commits)

## Devpost submission (written by the team)

### Inspiration

Searching for your next coffee haunt can be time-consuming. Browsing Yelp or Google Maps involves a lot of filtering out Starbucks and McDonald's. Lists of the best coffee spots around town are scattered across various blogs and articles. And determining whether there are enough outlets or public WiFi available is simply a gamble you have to take. We wanted to make it easy to find ALL the information about your next coffee shop - including the little things.

### What it does

Coffee Compass is a web application for coffee newcomers and enthusiasts alike, providing a large variety of recommendations for places to visit and can handle specialized questions so that you can find out anything about coffee and how you want to enjoy it.

### How we built it

We scraped a ton of information from review sites, blogs, and informational sites and compiled it all into a corpus for reference. We adapted an LLM to be able to answer questions related to coffee shops and their specialities with a sense of personality as well as dynamically recommend places according to user info. The web application was designed with Figma, incorporating a chatbot conversational experience and passport incentive system to engage with users and keep them returning to Coffee Compass.

### Challenges we ran into

Verifying information across different sources was super important. LLMs are more likely to hallucinate if they don't have accurate, verified information. It's a huge bummer to go to a great coffee shop by the beach, only to realize it doesn't have WiFi for your nifty MacBook Air. For the remote working crowd, even little things like outlets can matter. Being able to answer these questions gives a lot of value.

### Accomplishments we're proud of

Prioritizing design and making sure the user experience is something that a user would really enjoy using. We really liked gamifying some aspects to reward coffee aficionados for their passion.

### What we learned

Figuring out user workflow can be challenging, and there are lots of ways to tackle problems. Different kinds of users have their own problems, and that can potentially lead to design conflicts.

### What's next

Build out the gamified passport experience so that visitors who visit coffee shops feel rewarded for their passion. Continuing to build out an extensive database of features for coffee shops that people care about and getting enhanced visuals for every coffee shop, so that users can really feel like they can explore the coffee shop before heading out there for a cup of joe.

## README (from the GitHub repository)

# treehacks

## Detected evidence (automated analysis)

Indexed codebase: 6 recognized source files, 11 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (7 of 7)

```
app.py
README.md
requirements.txt
static/css/styles.css
static/js/gallery.js
static/js/script.js
templates/index.html
```

### Dependencies

- requirements.txt: annotated-types@==0.6.0, anyio@==3.7.1, blinker@==1.7.0, certifi@==2023.7.22, click@==8.1.7, distro@==1.8.0, exceptiongroup@==1.1.3, Flask@==3.0.0, h11@==0.14.0, httpcore@==1.0.2, httpx@==0.25.1, idna@==3.4, importlib-metadata@==6.8.0, itsdangerous@==2.1.2, Jinja2@==3.1.2, MarkupSafe@==2.1.3, openai@==1.2.3, pydantic@==2.4.2, pydantic_core@==2.10.1, sniffio@==1.3.0, tqdm@==4.66.1, typing_extensions@==4.8.0, urllib3@==1.26.6, Werkzeug@==3.0.1, zipp@==3.17.0

### Recent commits (newest first)

- Add files via upload
- Initial commit

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

### requirements.txt

```
annotated-types==0.6.0
anyio==3.7.1
blinker==1.7.0
certifi==2023.7.22
click==8.1.7
distro==1.8.0
exceptiongroup==1.1.3
Flask==3.0.0
h11==0.14.0
httpcore==1.0.2
httpx==0.25.1
idna==3.4
importlib-metadata==6.8.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
openai==1.2.3
pydantic==2.4.2
pydantic_core==2.10.1
sniffio==1.3.0
tqdm==4.66.1
typing_extensions==4.8.0
urllib3==1.26.6
Werkzeug==3.0.1
zipp==3.17.0

```

### app.py

```python
from flask import Flask, render_template, request, jsonify
from cgpt import llm_complete

app = Flask(__name__)

# Initialize global_history as an empty list
global_history = []

@app.route('/')
def index():
    featured_images = ['coffee1.png']
    favorite_images = ['fav_1.png', 'fav_2.png', 'fav_3.png']
    category_images = ['category.png', 'category_2.png', 'category_3.png', 'category_4.png']

    imageFilenames = featured_images  # Set imageFilenames to the list of featured_images

    return render_template('index.html', favorite_images=favorite_images, category_images=category_images,
                           imageFilenames=imageFilenames)


@app.route('/get_response', methods=['POST'])
def get_response():
    global global_history  # Use the global_history variable
    data = request.get_json()
    user_message = data['message']

    # Append the user message to the global_history
    global_history.append({"role": "user", "content": user_message})

    # Use the global_history in llm_complete function
    reply = llm_complete(global_history)

    # Append the chatbot response to the global_history
    global_history.append({"role": "system", "content": reply})

    # Return the reply to the client
    return jsonify({"reply": reply})


if __name__ == '__main__':
    app.run(debug=True)

```

### templates/index.html

```html
<!DOCTYPE html>
<html>
<head>
    <title>☕ CoffeeCompass</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body>
    <div class="brown-bar">
        <img src="{{ url_for('static', filename='images/header_icon.png') }}" alt="Header Icon" class="header-icon">
    </div>

    <div class="content-container">
        <div class="image-content">
            <div class="image-gallery">
                <div class="featured-images">
                    <a href="https://www.vervecoffee.com/" target="_blank">
                        <img id="featured-image" src="/static/images/featured_cafe.png" alt="Featured Cafe Image">
                    </a>
                </div>
            </div>
        </div>
    </div>


            <div class="image-grid">
                <h2>Favorite Cafes</h2>
                <div class="favorite-images">
                    {% for image in favorite_images %}
                        <img src="{{ url_for('static', filename='images/faves/' + image) }}" alt="Favorite Cafe Image">
                    {% endfor %}
                </div>

                <h2>By Category</h2>
                <div class="category-images">
                    {% for image in category_images %}
                        <img src="{{ url_for('static', filename='images/category/' + image) }}" alt="Category Cafe Image">
                    {% endfor %}
                </div>
            </div>
        </div>
        
        <div class="chatbot-container">
            <div class="chat-container">
                <div class="chatbot-message">Hello Tree! Let's talk ☕</div>
            </div>
            <input type="text" id="user-input" placeholder="Type your message...">
            <button id="send-button">Send</button>
        </div>
    </div>

    <script src="/static/js/script.js"></script>
</body>
</html>

```

### static/js/gallery.js

```javascript
// // Get the featured image element and the next button element
// const featuredImage = document.getElementById('featured-image');
// const nextButton = document.getElementById('next-button');

// // Define an array of image filenames
// const imageFilenames = ['coffee.png', 'coffee1.png']; // Add more filenames as needed

// let currentIndex = 0; // Index of the current image

// // Function to show the next image
// function showNextImage() {
//     currentIndex = (currentIndex + 1) % imageFilenames.length;
//     const nextImage = imageFilenames[currentIndex];
//     featuredImage.src = `/static/images/${nextImage}`;
// }

// // Add a click event listener to the next button
// nextButton.addEventListener('click', showNextImage);

```

### static/js/script.js

```javascript
document.addEventListener('DOMContentLoaded', function() {
    const user_input = document.getElementById('user-input');
    const chat_container = document.querySelector('.chat-container');

    document.getElementById('send-button').addEventListener('click', function() {
        const user_message = user_input.value.trim();
        if (user_message !== '') {
            const user_message_element = document.createElement('div');
            user_message_element.classList.add('user-message');
            user_message_element.textContent = user_message;
            chat_container.appendChild(user_message_element);

            // Clear the input field after sending the message
            user_input.value = '';

            // Scroll to the bottom of the chat container
            chat_container.scrollTop = chat_container.scrollHeight;

            // Send the user message to the server and get the chatbot's response
            fetch('/get_response', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ message: user_message })
            })
            .then(response => response.json())
            .then(data => {
                const chatbot_message_element = document.createElement('div');
                chatbot_message_element.classList.add('chatbot-message');
                chatbot_message_element.textContent = data.reply;
                chat_container.appendChild(chatbot_message_element);

                // Scroll to the bottom of the chat container after receiving the chatbot's response
                chat_container.scrollTop = chat_container.scrollHeight;
            });
        }
    });
});

```

### static/css/styles.css

```css
/* styles.css */

body {
    font-family: 'Open Sans', sans-serif;
    margin: 0;
    padding: 0;
    background-color: beige; /* Set background color to beige for the entire page */
}

h2 {
    font-family: 'Open Sans', sans-serif; /* Apply Open Sans font to h2 elements */
}


.brown-bar {
    background-color: #8B4513; /* Brown color for the brown bar */
    height: 50px; /* Set the height of the brown bar */
    text-align: center; /* Center the text within the brown bar */
    display: flex;
    align-items: center;
    justify-content: center;
}

.header-icon {
    max-height: 40px; /* Set the maximum height of the header icon */
    max-width: 100%; /* Set the maximum width of the header icon */
}

.image-gallery {
    text-align: center;
    padding: 20px;
    max-height:140px;
    background-color: beige; /* Set background color to beige for the image gallery */
}

.image-grid {
    display: grid;
    text-align: center;
    grid-template-columns: repeat(1, 1fr);
    grid-gap: 20px;
    padding: 10px;
    margin: 0 auto; /* Center the grid container horizontally */
    background-color: beige; /* Set background color to beige for the image grid */
}

.image-grid h2 {
    grid-column: 1 / -1;
}

.favorite-images h2,
.category-images h2 {
    text-align: center; /* Center the text */
}

.featured-images img {
    max-width: 50%;
    height: auto;
    max-height: 180px; /* Set maximum height for the images */
}

.featured-images img.active {
    display: block;
}

.favorite-images,
.category-images {
    display: flex; /* Use flexbox to align images horizontally */
    justify-content: center; /* Center images along the main axis */
    overflow-x: auto; /* Allow horizontal scrolling */
    white-space: nowrap; /* Prevent images from wrapping */
    background-color: beige; /* Set background color to beige for the image containers */
}

.favorite-images img,
.category-images img {
    flex-shrink: 0; /* Prevent images from shrinking */
    max-width: 80%; /* Adjust the width of the images as needed */
    height: auto;
    max-height: 250px; /* Set maximum height for the images */
    margin: 5px; /* Add some space between images */
}

.chatbot-container {
    position: fixed;
    bottom: 0;
    right: 0;
    width: 300px;
    max-height: 400px; /* Set maximum height for the chat container */
    background-color: #f9f9f9; /* Off-white background color for the chatbot container */
    padding: 20px;
    box-shadow: -2px 0px 5px rgba(0, 0, 0, 0.1); /* Add a shadow to the chatbot container */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    overflow: hidden; /* Hide overflow to prevent resizing */
}

.chat-container {
    flex: 1 1 auto; /* Allow the chat container to grow to fill available space */
    overflow-y: auto; /* Allow vertical scrolling in the chat container */
    /*margin-bottom: 200px; /* Add margin at the bottom to prevent overlapping with input */
}


.chatbot-message {
    background-color: #f6b26b; /* Light gray background color for the chatbot messages */
    padding: 10px;
    margin: 5px 0;
}

.user-message {
    background-color: #4CAF50; /* Lighter gray background color for the user messages */
    padding: 10px;
    margin: 5px 0;
}

.chat-input {
    margin-top: 10px;
}

.chat-input input {
    width: calc(100% - 60px); /* Set the width of the input field */
    padding: 5px;
    margin-right: 5px;
    border: 1px solid #ccc; /* Add a border to the input field */
}

.chat-input button {
    padding: 5px 10px;
    border: none;
    background-color: #4CAF50 !important; /* Green color for the send button */
    color: white;
    cursor: pointer;
    border-radius: 5px; /* Add some border-radius to make it more stylish */
}

.chat-input button:hover {
    background-color: #45a049 !important; /* Darker green color on hover for the send button */
}

.chat-input input {
    width: calc(100% - 60px); /* Set the width of the input field */
    padding: 10px; /* Increase the padding to make it taller */
    margin-right: 5px;
    border: 1px solid #ccc; /* Add a border to the input field */
    border-radius: 20px; /* Add some border-radius to make it more fun */
    background-color: #f6f6f6; /* Light gray background color */
    font-size: 16px; /* Increase the font size */
}


#send-button {
    padding: 5px 10px;
    border: none;
    background-color: #4CAF50; /* Green color for the send button */
    color: white;
    cursor: pointer;
    border-radius: 5px; /* Add some border-radius to make it more stylish */
    width: 66.66%; /* Set the width to 2/3 of the container width */
    margin: 0 auto; /* Center the button horizontally */
}

#send-button:hover {
    background-color: #45a049; /* Darker green color on hover for the send button */
}

#user-input {
    width: 90%; /* Set the width of the input field */
    padding: 10px;
    font-size: 16px;
    margin-top: 5px;
    margin-right: 5px;
    border: 1px solid #ccc; /* Add a border to the input field */
    border-radius: 20px; /* Add some border-radius to make it more fun */
    background-color: #f9f9f9; /* Off-white background color */
    margin-bottom: 10px;
}

.chatbot-message,
.user-message {
    font-size: 16px; /* Set the font size for chatbot and user messages */
}



```