# Project export: LifePuzzle

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: The Discord Puzzle bot that pieces your life together.
- Devpost: https://devpost.com/software/lifepuzzle
- GitHub: https://github.com/calhacks-lifepuzzle/lifepuzzle-discord-bot
- Team: 1 GitHub contributor(s) — RyanC (15 commits)

## Devpost submission (written by the team)

### Inspiration

As students still figuring out how to plan our lives, it can be hard to break down our goals into something manageable. We wanted to create a program for people like us who need a little extra help working towards our goals, and on a daily basis.

### What it does

LifePuzzle will take a goal given by a user, and break it into various tasks. LifePuzzle will give you one task a day, and as you complete your tasks, it will give you puzzle pieces until you have an entire puzzle to represent your progress.

### How we built it

We utilized Together AI’s API to generate tasks for the user each day based on the goal they input. We also made it a bot for Discord, because it is already populated with a very similar user base, marking it easy for us to get users, and making it very convenient for them to use our bot.

### Challenges we ran into

It was very hard for us to figure out what medium we wanted to present our program in. At first we considered a website or make it its own standalone chatbot, but we felt that those options did not encourage easy and convenient use, nor were they easy to develop.

### Accomplishments we're proud of

We are really proud of the overall presentation! The bot is able to provide meaningful and beneficial tasks that align with the user's goals, and the UI is also very easy for users to get the hang of.

### What we learned

We learned how to use Generative AI in our projects in order to implement functionality that would've been hard/impossible to implement otherwise. Together AI's API was extremely easy to use for this project, and we hope to utilize it more even after the hackathon.

### What's next

We would love to work on it more to give it more features and possibly make it a Duolingo-like mobile app. We would also like to create a better reward system for it to give users even more incentives to complete their tasks.

## README (from the GitHub repository)

# calhacks-transfer-app
App to help community college students figure out their course plan, regardless of where they want to go.

Utilizing:
- MindsDB for data storage & preliminary data clean up with AI
- together.ai for AI data processing on the user's end
- ??? for frontend

---
Idea

Background: John is a CS student at De Anza College (a community college), who wishes to transfer to a university in the future. he's a very ambitious student, and wishes to go to either a UC, an Ivy League college, or MIT.

Since John is studying at a California Community College, figuring out his plan for the UCs is easy! He just has to go on assist.org, enter his college and select one of the UCs, enter his major, download the articulation agreement, copy down the classes that the articulation agreement tells him to take, and repeat this whole process all over again for each UC campus. How easy!

For Ivy League and MIT, it's even easier! He just has to dig through countless Google searches to eventually find a page from a specific college, denoting what they're generally looking for in transfer applicants, then compare their criteria to the different classes his college offers, then repeat again for the next college.

There is an easier way to do this. Enter [PLACEHOLDER NAME].

With [PLACEHOLDER NAME], it can:
- automatically analyze the assist.org articulation agreement for multiple colleges
- provide students with an easy way to figure out the transfer requirements/criteria of out-of-state/private colleges
- merge all of the information it has into one simple and easy to read page, with Generative AI being able to further clean up the information to make it more human-digestable.

---
Data Handling for Individual Universities

DB Structure:

__classes_requirements (each row is a class requirement)__  
uni_name - University Name - For convenience's sake  
uni_id - University ID - We either generate this ourselves or we borrow from assist.org whenever possible  
data_type - Source of the Data - e.g. "california_assist", "private_scraped", "public_scraped"  
major - Major this data point is for - Can be specific (e.g. "Computer Science") or general if it applies to all majors (GE reqs)  
class_code - A specific class needed by the school - Needs to be the specific course code required by the school  
class_name - Name of the class

[TODO] Add a row for AI to process

__california_assist (each row is a class equivalency between two schools)__  
sending_name - Sender College Name  
sending_id - Sender College ID  
receive_name - Receiving College Name  
receive_id - Receiving College ID  
sending_course_code - Sender College Class Code  
sending_course_name - Sender College Class Name  
receiving_course_code - Receiving College Class Code  
receiving_course_name - Receiving College Class Name  


## Detected evidence (automated analysis)

Indexed codebase: 3 recognized source files, 24 KB.
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (5 of 5)

```
.env
.gitignore
demo.py
main.py
README.md
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- helpful commit message
- :3
- Update .env
- puzzles <3333
- Create .env
- minor changes
- i Please enter the commit message for your changes. Lines starting
- :3
- Update README.md
- Update README.md
- Delete data.md
- wqqq
- Update README.md
- Update README.md
- Initial commit

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

### main.py

```python
import discord
from discord.app_commands import CommandTree
import random
import together
import time
import io
import os
import asyncio
import functools
import PIL
from PIL import Image
import glob
from dotenv import load_dotenv

load_dotenv(".env")

together.api_key = os.getenv("TOGETHER_AI_API_KEY")
bot_token = os.getenv("BOT_TOKEN")

# User ID
#   - task_complete | Today's Task Completion [bool]
#   - resets | Daily Resets [int]
#   - today_task | Today's Task [Str]
#   - today_task_reasoning | Today's Task Reasoning [Str]
#   - puzzle | Puzzle Status [list]
#   - past_days | Past Day Goals [dict]
#   - goal | Goal [Str]
data = {}

allowed_mentions = discord.AllowedMentions(everyone=False, users=True, roles=True, replied_user=False)
intents = discord.Intents.all()
intents.presences = False
intents.members = False

puzzle_list = {}

async def setup_hook():
    for filename in glob.glob('./puzzle/*.png'):
        im=Image.open(filename)
        name_proc = filename.split("/")[-1].replace(".png", "").split("_")
        puzzle_list[(int(name_proc[0]), int(name_proc[1]))] = im


client = discord.AutoShardedClient(intents=intents, allowed_mentions=allowed_mentions, chunk_guilds_at_startup=False)
# client.activity = discord.Activity(
#         name='Activity', 
#         type=discord.ActivityType.watching,)

client.setup_hook = setup_hook
client.tree = CommandTree(client)

@client.event
async def on_ready():
    print("Bot started")

@client.event
async def on_message(message):
    global data

    if message.author.bot:
        return
    
    if message.author.id == 396545298069061642 and message.content.lower() == "!reloadcmd":
        await client.tree.sync(guild=None)
        await message.reply("Reloaded global")

    if message.content.lower() == "!reset":
        data = {}
        await message.delete()

    if message.author.id == 396545298069061642 and message.content.lower() == "!openlifepuzzlebutton":
        embed=discord.Embed(title="Open LifePuzzle", description="Click the button below to open LifePuzzle.", color=0xfc766a)
        embed.set_author(name="LifePuzzle")
        buttons = discord.ui.View()
        buttons.add_item(discord.ui.Button(style=discord.ButtonStyle.green, label="Open LifePuzzle", row=1, custom_id="display_task_button", disabled=False))
        await message.channel.send(embed=embed, view=buttons)

    if message.content.lower() == "!demodata":
        data[message.author.id] = {
            "task_complete": False,
            "resets": 1,
            "today_task": None,
            "today_task_reasoning": None,
            "puzzle": [True] * 27,
            "past_days": {},
            "goal": "eat healthier, talk to people more, be more fit"
        }
        await message.delete()

    if message.content.lower() == "!demooops":
        data[message.author.id] = {
            "task_complete": True,
            "resets": 1,
            "today_task": "Take your dog out for a walk after lunch.",
            "today_task_reasoning": "Taking your dog out after lunch will give you the opportunity to spend quality time with your dog, therefore achieving your goal of spending more time with your dog.",
            "puzzle": [True] * 16 + [False] + [True] * 11,
            "past_days": {},
            "goal": "spend more time with my dog"
        }
        await message.delete()

    if message.content.lower() == "!demohalf":
        data[message.author.id] = {
            "task_complete": True,
            "resets": 1,
            "today_task": None,
            "today_task_reasoning": None,
            "puzzle": [True] * 16,
            "past_days": {},
            "goal": "spend more time with my dog"
        }
        await message.delete()

    return

async def together_prompt(prompt):
    return await client.loop.run_in_executor(None, functools.partial(
        together.Complete.create,
        prompt = prompt, 
        model = "togethercomputer/llama-2-7b-chat", 
        max_tokens = 256,
        temperature = 0.8,
        top_k = 60,
        top_p = 0.6,
        repetition_penalty = 1.1,
        stop = ['<human>', '\n\n']
        ))

async def generate_puzzle(completion):
    canvas = Image.new('RGBA', (1890, 1080), (0,0,0,0))
    for y in range(4):
        for x in range(7):
            id = 7 * y + x
            if len(completion) < id + 1 or completion[id] == False:
                continue
            else:
                im = puzzle_list[(x, y)]
                canvas.paste(im, (x * 270, y * 270))
    
    return canvas


async def set_goal(interaction):
    user_id = interaction.user.id
    embed=discord.Embed(title="Set Goal", description=("Your current goal: " + data[user_id]["goal"] + "\n\nIf you want to change your goal, please enter it in chat!" if data[user_id]["goal"] != None else "Set a couple goals that you want to achieve. It's recommended that you set multiple goals.") + "\n\nSome great examples:\n- Eat healthier\n- Exercise more\n- Be more thankful", color=0xfc766a)
    embed.set_author(name="LifePuzzle")
    buttons = discord.ui.View()
    buttons.add_item(discord.ui.Button(style=discord.ButtonStyle.grey, label="Waiting for Chat Input", row=1, custom_id="chatinputindicator", disabled=True))
    if data[user_id]["goal"] != None:
        buttons.add_item(discord.ui.Button(style=discord.ButtonStyle.red, label="Cancel", row=1, custom_id="cancel", disabled=False))
    msg = await interaction.followup.send(embed=embed, view=buttons)

    def check_msg(m):
        return m.channel.id == interaction.channel.id and m.author.id == user_id
    def check_interaction(m):
        return m.type not in [discord.InteractionType.application_command, discord.InteractionType.autocomplete] and m.user.id == user_id and m.message.id == msg.id
    done, pending = await asyncio.wait([
                asyncio.create_task(client.wait_for('message', timeout=120.0, check=check_msg)),
                asyncio.create_task(client.wait_for('interaction', timeout=121.0,
[truncated — 11438 more characters]
```

### demo.py

```python
import random
import together
import time

together.api_key = "93620393f8368d94029be74aae01279d90adeb5ce407803ecaf974354af59263"

while True: 
    goal = input("What are some goals you want to achieve? ")

    prompt = """
    The user has stated that they want their goal to be: "[GOAL PLACEHOLDER]"
    """.replace("[GOAL PLACEHOLDER]", goal.strip())

    prompt += """
    Is this a good long-term goal? Is this goal realistic, achievable, positive and clear?

    RESPONSE FORMAT, STRICTLY FOLLOW THIS FORMAT:
    Result: ["T" or "F", signifying whether or not the goal meets the criteria]
    Reasoning: [Message to the user explaining why the goal meets/doesn't meet the criteria, be personal and considerate.]

    RESPONSE IN ABOVE FORMAT:
    """

    output = together.Complete.create(
        prompt = prompt, 
        model = "togethercomputer/llama-2-13b-chat", 
        max_tokens = 256,
        temperature = 0.8,
        top_k = 60,
        top_p = 0.6,
        repetition_penalty = 1.1,
        stop = ['<human>', '\n\n']
        )

    try:
        result = output['output']['choices'][0]['text'].split("Result: ")[1]
        reasoning = result.split("Reasoning: ")[1].strip()
        result = result.split("Reasoning: ")[0].upper().replace("TRUE", "T").replace("Y", "T").replace("YES", "T").strip()
    except Exception as e:
        print(str(e))
        print("Got bad response in task checker, skipping :(")
        break

    if result == "T":
        break
    else:
        print(reasoning)
        print()
        action = input("Enter a new goal? (Y/N) ").upper()
        if action == "Y":
            continue
        else:
            break

past_goals = {}

errors = 0

while True:
    if errors > 2:
        print("TOO MANY ERRORS")
        break
    
    prompt = """
    Your goal is to generate a simple task for the user to do today. The task should not be complicated, should take very little time, should clearly assist with the user's goals and should easily fit into the user's schedule for the day. The user has previously indicated their long-term goal(s) to be the following: "[GOAL PLACEHOLDER]"
    """.replace("[GOAL PLACEHOLDER]", goal)

    if len(past_goals) > 0:
        successes = 0
        fails = 0
        try:
            random.shuffle(past_goals)
        except Exception:
            pass
        past_goals = {x[0]:x[1] for x in list(past_goals.items()) if (x[1] and successes <= 3) or (fails <= 3)}

        prompt += "\n\nPrevious goals the user has been instructed to complete in the past:\n" + '\n'.join([x[0] + " (" + ("SUCCEEDED" if x[1] else "FAILED") + ")" for x in list(past_goals.items())]) + "\nAvoid repeating tasks the user previously failed to complete, unless they are really beneficial.\n"

    prompt += """
    RESPONSE FORMAT:
    Task: [The task for the user to complete]
    Reasoning: [How this can help the user achieve their goal]

    RESPONSE:
    """

    output = together.Complete.create(
        prompt = prompt, 
        model = "togethercomputer/llama-2-7b-chat", 
        max_tokens = 256,
        temperature = 0.8,
        top_k = 60,
        top_p = 0.6,
        repetition_penalty = 1.1,
        stop = ['<human>', '\n\n']
        )

    try:
        task = output['output']['choices'][0]['text'].split("Task: ")[1].strip()
        reasoning = task.split("Reasoning: ")[1].strip()
        task = task.split("Reasoning: ")[0]
    except Exception:
        errors += 1
        print("Got bad response, retrying in 3...")
        time.sleep(3)
        continue

    print()
    print("Your task is: " + task)
    print()
    print(reasoning)
    action = input("(R)etry, (S)ucceeded, (F)ailed, (Any)Stop ").upper()
    if action == "R":
        continue
    elif action == "S":
        past_goals[task] = True
        continue
    elif action == "F":
        past_goals[task] = False
        continue
    break
```