# Project export: Groundhog

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: Using Together AI and Reflex we developed a auto generated text based adventure game for users to play. If you ever don't like an outcome of a choice you can go back in time to replay your own actions
- Devpost: https://devpost.com/software/groundhog-jrnux5
- GitHub: https://github.com/pnavab/calhacks2023
- Result: winner (Best Use of Together AI)
- Team: 2 GitHub contributor(s) — bobo (37 commits), pnavab (35 commits)

## Devpost submission (written by the team)

### Inspiration

We love spending time playing role based games as well as chatting with AI, so we figured a great app idea would be to combine the two.

### What it does

Creates a fun and interactive AI powered story game where you control the story and the AI continues it for as long as you want to play. If you ever don't like where the story is going, simply double click the last point you want to travel back to and restart from there! (Just like in Groundhog Day)

### How we built it

We used Reflex as the full-stack Python framework to develop an aesthetic frontend as well as a robust backend. We implemented 2 of TogetherAI's models to add the main functionality of our web application.

### Challenges we ran into

From the beginning, we were unsure of the best tech stack to use since it was most members' first hackathon. After settling on using Reflex, there were various bugs that we were able to resolve by collaborating with the Reflex co-founder and employee on site.

### Accomplishments we're proud of

All our members are inexperienced in UI/UX and frontend design, especially when using an unfamiliar framework. However, we were able to figure it out by reading the documentation and peer programming. We were also proud of optimizing all our background processes by using Reflex's asynchronous background tasks, which sped up our website API calls and overall created a much better user experience.

### What we learned

We learned an entirely new but very interesting tech stack, since we had never even heard of using Python as a frontend language. We also learned about the value and struggles that go into creating a user friendly web app we were happy with in such a short amount of time.

### What's next

More features are in planning, such as allowing multiple users to connect across the internet and roleplay on a single story as different characters. We hope to continue optimizing the speeds of our background processes in order to make the user experience seamless.

## README (from the GitHub repository)

### Live Version

Check out our deployed site at https://groundhog1.reflex.run/ to test it yourself!


## Detected evidence (automated analysis)

Indexed codebase: 19 recognized source files, 216 KB.
- HTML (language) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (21 of 21)

```
.gitignore
assets/ben-redblock-loading-circle-gif-19361051.html
calhacks2023/__init__.py
calhacks2023/backend/ai.py
calhacks2023/calhacks2023.py
calhacks2023/components/__init__.py
calhacks2023/components/sidebar.py
calhacks2023/components/textbox.py
calhacks2023/components/timetravelmodal.py
calhacks2023/pages/__init__.py
calhacks2023/pages/dashboard_style.py
calhacks2023/pages/dashboard.py
calhacks2023/pages/index.py
calhacks2023/pages/landing.py
calhacks2023/state.py
calhacks2023/styles.py
calhacks2023/templates/__init__.py
calhacks2023/templates/template.py
README.md
requirements.txt
rxconfig.py
```

### Dependencies

- requirements.txt: reflex@==0.3.0, together@==0.2.5

### Recent commits (newest first)

- Update README.md
- added stream to ai generated text
- Merge branch 'main' of https://github.com/pnavab/calhacks2023
- finishing touches on the website and its styling
- added font family lato the project
- added the loading functionality
- added some styling to the create chat modal
- Add files via upload
- Add files via upload
- gradeint to vertical on update
- Merge branch 'main' of https://github.com/pnavab/calhacks2023
- made the gradient vertical
- Add files via upload
- adjusted add new
- Merge branch 'main' of github.com:pnavab/calhacks2023
- adjusted add new
- backend parralellized
- delete characters on enter/submit form
- add async
- changed styling with feedback

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

### requirements.txt

```

reflex==0.3.0
together==0.2.5

```

### calhacks2023/pages/index.py

```python
"""The home page of the app."""

from calhacks2023 import styles
from calhacks2023.pages import landing
from calhacks2023.state import State
from calhacks2023.pages import dashboard
import reflex as rx

def index() -> rx.Component:
    return dashboard()

```

### rxconfig.py

```python
import reflex as rx

config = rx.Config(
    app_name="calhacks2023",
)
```

### calhacks2023/__init__.py

```python
"""Base template for Reflex."""

```

### calhacks2023/calhacks2023.py

```python
"""Welcome to Reflex!."""

from calhacks2023 import styles

# Import all the pages.
from calhacks2023.pages import *
from calhacks2023.backend.ai import *
import reflex as rx

# Create the app and compile it.
app = rx.App(
    stylesheets=[
        "https://fonts.googleapis.com/css?family=Lato",
    ],
    style=styles.base_style
)
app.api.add_api_route(
    path="/",
    endpoint=root
)

app.api.add_api_route(
    path="/get-ai-response",
    endpoint=get_ai_response
)
app.compile()

```

### calhacks2023/styles.py

```python
"""Styles for the app."""

import reflex as rx
from .state import State

primary_color = "#1C1C1C"
secondary_color = "#939791"

border_radius = "0.375rem"
box_shadow = "0px 0px 0px 1px rgba(84, 82, 95, 0.14)"
border = f"1px solid {State.accent_color_three}"
text_color = "black"
accent_text_color = "#1A1060"
accent_color = "#F5EFFE"
hover_accent_color = {"_hover": {"color": accent_color}}
hover_accent_bg = {"_hover": {"bg": accent_color}}
content_width_vw = "90vw"
sidebar_width = "20em"
background_color = "#434654"

template_page_style = {"padding_top": "2em", "padding_x": ["auto", "2em"]}

template_content_style = {
    "width": "100%",
    "align_items": "flex-start",
    "box_shadow": box_shadow,
    "border_radius": border_radius,
    "padding": "1em",
    "margin_bottom": "2em",
}

link_style = {
    "color": text_color,
    "text_decoration": "none",
    **hover_accent_color,
}

overlapping_button_style = {
    "background_color": "white",
    "border": border,
    "border_radius": border_radius,
}

stable_styles = {
    "question_row": {
        "display": "flex",
        "flex-direction": "row",
        "justify-content": "right",
        "margin": "10px 0px",
    },
    "answer_row": {
        "display": "flex",
        "flex-direction": "row",
        "justify-content": "left",
        "margin": "10px 0px",
    },
    "action-style": {
        "width": "800px;",
    }
}


base_style = {
    "background-color": secondary_color,
    "font-family": "Lato",
    "::-webkit-scrollbar":  {
        "width": "10px;"  # Width of the scrollbar track */
    },
    "::-webkit-scrollbar-track": {
        # /* Background color of the scrollbar track */
        "background": State.accent_color_one
    },
    "::-webkit-scrollbar-thumb": {
        "background": State.accent_color_two,  # /* Color of the scrollbar thumb */
        "border-radius": "5px",
    },
    "site-title": {
        "font-size": "30px",
        "color": State.accent_color_three,
        "text-align": "center",
        "font-weight": "900",
    },
    "question": {
        "max-width": "85%",
        "background-color": State.accent_color_one,
        "border-radius": "10px 10px 0px 10px",
        "padding": "15px 15px",
        "box_shadow": f"0px 0px 1px 0px {State.accent_color_one}",
        "color": "white",
        "mix-blend-mode": "luminosity",
        "font-weight": "600",
        "background-color": State.accent_color_two,
    },
    "answer": {
        "max-width": "85%",
        "background-color": State.accent_color_two,
        "border-radius": "0px 10px 10px 10px",
        "padding": "15px 15px",
        "color": "white",
        "font-weight": "600",
        "mix-blend-mode": "luminosity",
        "background-color": State.accent_color_two,
    },
    "chat-style": {
        "width": "125%",
        "height": "40rem",
        "overflow-y": "scroll",
        "display": "flex",
        "flex-direction": "column-reverse",
        "align-items": "flex-end"
    },
    "cool_buttons": {
        "background-color": State.accent_color_three,
        "border-radius": "10px",
        "border": f"1px solid {secondary_color}",
        "box_shadow": f"0px 0px 3px 0px black",
        "font-weight": "600",
        "padding": "10px 16px",
    },
    "chat-container": {
        "border": f"1px solid {secondary_color}",
        "padding": "20px",
        "margin": "0px 0px 20px 20px"
    },
    "ask_button": {
        "background-color": State.accent_color_three,
        "border-radius": "10px",
        "border": f"1px solid {secondary_color}",
        "box_shadow": f"0px 0px 1px 0px {State.accent_color_one}",
        "padding": "10px",
        "margin-left": "10em",
        "margin-top": "2em",
    },
}

```

### calhacks2023/state.py

```python
"""Base state for the app."""

import reflex as rx
from calhacks2023.backend.ai import *
import random
import asyncio


class State(rx.State):
    """Base state for the app.

    The base state is used to store general vars used throughout the app.
    """
# The current question being asked.
    tabs: list[str] = []
    chats: dict[str, list[str, list[dict[str, str]]]] = {}

    scenarios = ["You are stranded on an island and don't remember how you got there. To your left is a coconut and a half sharpened rock that looks like someone else tried shaping into a knife. You hear a faint but aggressive pack of monkeys howling in the distance...", "You are lost in a forest, with no tools or weapons. You notice an old box near you, but are unsure whether you should open it...",
                 "You wake up in a maze with walls 3 times your height. Suddenly, you notice a looming figure emerge from the shadows behind you. It seems to be holding a large club...", "You are in a mansion, yet it seems eerily quiet. It seems abandoned, and you seem to be the only one home yet don't remember how you got there..."]

    question: str
    name: str
    show: bool = False
    show_revert_button = False
    index_to_revert: int = 0
    temp_question: str

    accent_color_one = "#a4dded"
    accent_color_two = "#A3C9A8"
    accent_color_three = "lightgrey"

    gradient_string = f"linear-gradient(180.0deg, {accent_color_one} 0.75%, {accent_color_two} 88.52%)"
    print(gradient_string)

    @rx.background
    async def change_background_color(self, prompt):
        colors = get_hex_codes(prompt)
        async with self:
            self.accent_color_one = "#"+colors[0]
            self.accent_color_two = "#"+colors[1]
            self.gradient_string = f"linear-gradient(180.0deg, {self.accent_color_one} 5%, {self.accent_color_two} 88.52%)"

    @rx.background
    async def set_background_image(self, prompt, art_style):
        code = get_ai_image(prompt, art_style)
        async with self:
            self.chat_history[-1]["image_code"] = code
            self.image_loaded = True

    forest: str = "#A9A9A9"
    ocean: str = "#A9A9A9"
    medieval: str = "#A9A9A9"
    steampunk: str = "#A9A9A9"
    cartoon: str = "#A9A9A9"

    cur_chat = "Default Story"
    tabs.append(cur_chat)
    default_art_style = "cartoon"
    default_user_message = "I just spawned..."
    default_model_message = "You are lost in a forest, with no tools or weapons. You notice an old box near you, but are unsure whether you should open it..."
    default_image_code = get_ai_image(default_model_message, default_art_style)

    chats[cur_chat] = [default_art_style, [
        {"user": default_user_message, "model": default_model_message, "image_code": default_image_code}]]
    chat_history: list[dict[str, str]] = chats[cur_chat][1]

    def truncate_chat_history(self):
        if len(self.chat_history) <= 20:
            return self.chat_history
        return self.chat_history[-20:]

    def set_question(self, input):
        self.question = input

    image_loaded: bool = True

    def toggle_loaded(self):
        self.image_loaded = not (self.image_loaded)

    @rx.background
    async def answer(self):
        # self.chats[self.cur_chat][1] is the current chat's chat history
        async with self:
            self.image_loaded = False
            self.chat_history.append(
                {"user": self.temp_question, "model": "loading", "image_code": ""})
            ai_answer = get_ai_response(
                self.truncate_chat_history(), self.temp_question)
            yield State.stream(ai_answer)
            yield State.change_background_color(ai_answer)
            yield State.set_background_image(ai_answer, self.chats[self.cur_chat][0])

    def handle_answer(self):
        self.temp_question = self.question
        self.question = ""
        yield State.answer()

    def handle_submit(self, form_data: dict):
        """Handle the form submit."""
        self.form_data = form_data

    def set_name(self, input):
        self.name = input

    def create_new(self):
        theme = ""
        if (self.forest == "black"):
            theme = "forest"
        elif (self.steampunk == "black"):
            theme = "steampunk"
        elif (self.ocean == "black"):
            theme = "ocean"
        elif (self.cartoon == "black"):
            theme = "cartoon"
        elif (self.medieval == "black"):
            theme = "medieval"
        if not self.name in self.tabs:
            self.tabs.append(self.name)

            random_scenario = random.choice(self.scenarios)
            image_code = get_ai_image(random_scenario, theme)
            self.chats[self.name] = [theme, [
                {"user": "I just spawned...", 'model': random_scenario, "image_code": image_code}]]

            # self.chats[self.name] = [theme, [{"user": "I just spawned...", 'model': random.choice(
            #     self.scenarios), "image_code": self.default_image_code}]]

            self.show = not (self.show)
            self.switch_tabs(self.name)
        else:
            print("name already exists")

    def change(self):
        self.show = not (self.show)

    def show_revert_modal(self, index):
        self.show_revert_button = True
        if index is not None:
            self.index_to_revert = index
        print(self.show_revert_button)

    def close_revert_modal(self):
        self.show_revert_button = False

    def switch_tabs(self, tab):
        self.cur_chat = tab
        self.chat_history = self.chats[tab][1]

    async def enter(self, key):
        if key == 'Enter':
            self.temp_question = self.question
            self.question = ""
            yield State.answer()

    def save_checkpoint(self):
        # print(f"double clicked, index is {self.index}")
        # saves all the context from beginning up until selected point
        cur_chat_history = self.chat_history
        # flip the index since it is passed in flipped fro
[truncated — 2243 more characters]
```

### calhacks2023/templates/__init__.py

```python
from .template import template

```

### calhacks2023/pages/__init__.py

```python
from .dashboard import dashboard
from .index import index

```

### calhacks2023/pages/landing.py

```python
"""The landing page."""

from calhacks2023.templates import template
import reflex as rx


def landing() -> rx.Component:
    return rx.vstack(
            rx.heading("Press create story to create stories!", font_size="3em"),
    )

```

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