# Project export: Heather AI

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: Heather AI acts as your secretary, aiming to maximize your productivity and help you in your day-to-day life through voice commands
- Devpost: https://devpost.com/software/heather-ai
- GitHub: https://github.com/anirudhramoo/Scheduler
- Demo: https://www.heatherai.in/
- Team: 3 GitHub contributor(s) — anirudhramoo (22 commits), rspapani (5 commits), beloiual (3 commits)

## Devpost submission (written by the team)

### Overview

What is Heather AI? Check out our demo! It is hard to keep a consistent schedule and it is very easy to lose track of things in your daily life. To provide some stability in one's life Heather AI acts as your secretary, aiming to maximize your productivity and help you in your day-to-day life. You can chat with Heather to ask questions but the main purpose of Heather is to help you schedule your day. By talking to Heather, you can add and remove events seamlessly on your Google Calendar, using a customized heuristic that takes into consideration practicality and development time. With this, we provide users with optimized scheduling where users can provide minimal information and generate helpful schedules. For example, all a user has to say is “I want to go to the gym 3 times this week” and Heather will automatically block out 3 times on your calendar to exercise, scheduling around other events. Access is here: Heather AI

### Accomplishments we're proud of

We are very proud of our robust scheduling algorithm and the conversion between voice to speech. Delving into the technical implementation of the scheduling algorithm, we wanted to schedule events based on what timeframe we could complete them given that the time it took to complete them wasn’t equal to this entire interval (i.e. we may have a 3-hour window for a 1-hour task). This is known as the interval scheduling problem with release times and deadlines, a famous np-hard problem proven to have no constant approximation. However, we applied a combination of linear programming and the earliest deadline first heuristic to get a method to schedule events quickly in the vast majority of cases and defer to user input for any significant changes. We also developed a robust way of taking the voice of a user, identifying events, and splitting them up into a machine-readable format for our scheduling algorithm. With this we can identify, modify, and delete events all seamlessly on the user's Google calendar, reducing the barrier of entry to the generation of a nice schedule.

### What's next

We just launched and we already have a growing waitlist (>100 people)! We plan to grow the functionality of our assistant and add integrations and monetization.

## README (from the GitHub repository)

# Scheduler
Our TreeHacks project.


## Detected evidence (automated analysis)

Indexed codebase: 25 recognized source files, 67 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
- LangChain (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Hugging Face (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (32 of 32)

```
README.md
scheduler-frontend/.gitignore
scheduler-frontend/package.json
scheduler-frontend/public/index.html
scheduler-frontend/public/manifest.json
scheduler-frontend/README.md
scheduler-frontend/src/App.tsx
scheduler-frontend/src/Components/Login/Auth.tsx
scheduler-frontend/src/Components/Login/Login.tsx
scheduler-frontend/src/Components/Scheduler/Scheduler.tsx
scheduler-frontend/src/Components/Scheduler/SchedulerButtonLarge.tsx
scheduler-frontend/src/Components/Scheduler/SchedulerButtonSmall.tsx
scheduler-frontend/src/Components/Scheduler/SlowTextRenderer.tsx
scheduler-frontend/src/hooks/useMicrophone.ts
scheduler-frontend/src/hooks/usePlayResponse.ts
scheduler-frontend/src/index.css
scheduler-frontend/src/index.tsx
scheduler-frontend/src/output.css
scheduler-frontend/tailwind.config.js
scheduler-frontend/tsconfig.json
server/.gitignore
server/app.py
server/endpoints/__init__.py
server/endpoints/google_login.py
server/endpoints/process_audio.py
server/endpoints/test.py
server/Procfile
server/requirements.txt
server/utils/__init__.py
server/utils/calendarapi.py
server/utils/sched_iter.py
server/utils/schedule.py
```

### Dependencies

- scheduler-frontend/package.json: @react-oauth/google@^0.12.1, @testing-library/jest-dom@^5.17.0, @testing-library/react@^13.4.0, @testing-library/user-event@^13.5.0, @types/jest@^27.5.2, @types/js-cookie@^3.0.6, @types/node@^16.18.82, @types/react@^18.2.56, @types/react-dom@^18.2.19, autoprefixer@^10.4.17, js-cookie@^3.0.5, postcss@^8.4.35, react@^18.2.0, react-dom@^18.2.0, react-icons@^5.0.1, react-scripts@5.0.1, tailwindcss@^3.4.1, typescript@^4.9.5, web-vitals@^2.1.4
- server/requirements.txt: Flask, flask-cors, flask-jwt-extended, google-api-python-client, google-auth, gunicorn, langchain, langchain-core, langchain-openai, langchainhub, python-dotenv, pytz, redis, requests

### Recent commits (newest first)

- modified:   server/utils/calendarapi.py
- Final commit
- Fuck yes
- simple fix
- modified origins
- update
- update
- Added Proc
- updated req
- Added req.txt
- bug fixes
- Scheduling Algo + Gcal Sync
- Done with frontend
- About to build
- Merge branch 'main' of https://github.com/anirudhramoo/Scheduler
- Added memory to chain
- new file:   server/utils/calendarapi.py
- Minor changes
- Done with frontend most of it
- Merge branch 'main' of https://github.com/anirudhramoo/Scheduler

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

### server/requirements.txt

```
Flask
flask-cors
python-dotenv
google-auth
google-api-python-client
requests
redis
flask-jwt-extended
langchain-openai
langchain
langchain-core
gunicorn
langchainhub
pytz

```

### scheduler-frontend/package.json

```
{
  "name": "scheduler-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@react-oauth/google": "^0.12.1",
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.82",
    "@types/react": "^18.2.56",
    "@types/react-dom": "^18.2.19",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-icons": "^5.0.1",
    "react-scripts": "5.0.1",
    "typescript": "^4.9.5",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@types/js-cookie": "^3.0.6",
    "autoprefixer": "^10.4.17",
    "js-cookie": "^3.0.5",
    "postcss": "^8.4.35",
    "tailwindcss": "^3.4.1"
  }
}

```

### server/app.py

```python
from flask import Flask,jsonify,request
from flask_cors import CORS
from endpoints.test import handle_test
from endpoints.google_login import login
from endpoints.process_audio import handle_execute
from dotenv import load_dotenv, find_dotenv
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

import os
load_dotenv(find_dotenv())


app = Flask(__name__)

app.config['JWT_SECRET_KEY'] = os.environ["JWT_SECRET"]
app.config['JWT_TOKEN_LOCATION'] = ['cookies']


CORS(app, supports_credentials=True, origins=["http://localhost:3000","https://www.heatherai.in"])

app.add_url_rule('/test', 'handle_test', handle_test, methods=['POST'])
app.add_url_rule('/google_login', 'login', login, methods=['POST'])


@app.route("/protected", methods=["POST"])
def protected():
    response={"data":"You are a dumb stupid fucker and I will not help you."}
    # request_data = request.get_json()  
    # profile = request_data.get('profile')
    # print(profile)
    # credentials = Credentials(token=profile["access_token"])

    # print(credentials)
    
    # # Build the Google Calendar API service
    # service = build('calendar', 'v3', credentials=credentials)
    
    # # Call the Calendar API to list the user's calendars
    # calendar_list = service.calendarList().list().execute()
    
    # # Print the calendars or return them
    # for calendar in calendar_list['items']:
    #     print(calendar['summary'])
    return response, 200

app.add_url_rule('/process-audio', 'handle_execute', handle_execute, methods=['POST'])

if __name__ == '__main__':
    app.run(port=5000)
```

### scheduler-frontend/src/index.tsx

```typescript
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```

### scheduler-frontend/src/App.tsx

```typescript
import React from "react";
import "./index.css";
import { Login } from "./Components/Login/Login";
import { Scheduler } from "./Components/Scheduler/Scheduler";
import { useState, useEffect } from "react";

function App() {
  const [profile, setProfile] = useState<any>(undefined);

  useEffect(() => {
    const profile = localStorage.getItem("profile");
    if (profile) {
      setProfile(JSON.parse(profile));
    }
  }, []);

  const handleLogout = () => {
    setProfile(undefined);
    localStorage.removeItem("profile");
  };

  return (
    <div className="w-screen h-screen  bg-gradient-to-r from-pink-500 to-orange-500 text-white">
      <div className=" flex justify-between items-center h-24 w-full bg-white bg-opacity-20 absolute top-0 border-b border-gray-300">
        <img
          src="/text_logo.png"
          alt="Descriptive Alt Text"
          className="w-44 ml-8"
        />

        {profile && (
          <div className="flex mr-10">
            <img
              className="w-10 h-10 p-1 rounded-full   mr-2"
              src={profile?.user_info?.picture ?? ""}
              alt="Bordered avatar"
            />
            <button
              onClick={handleLogout}
              className="text-white border border-white px-4 rounded transition duration-300 ease-in-out hover:bg-white hover:text-black"
            >
              Logout
            </button>
          </div>
        )}
      </div>
      {!profile ? (
        <Login setProfile={setProfile} />
      ) : (
        <Scheduler profile={profile} />
      )}

      {!profile && (
        <div className="flex items-center h-12 w-full bg-white bg-opacity-20 absolute bottom-0 border-t border-gray-300  shadow-md">
          <a
            className="text-white font-medium  ml-6 mr-4 cursor-pointer hover:text-slate-200"
            href="https://airtable.com/app7vdcIUagvg3HsW/shrLtZyCrPSFqjjxS"
          >
            Want to be informed when this goes public?
          </a>
        </div>
      )}
    </div>
  );
}

export default App;

```

### scheduler-frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
  theme: {
    extend: {
      keyframes: {
        fade: {
          "0%, 100%": { opacity: 0 },
          "50%": { opacity: 1 },
        },
      },
      animation: {
        "fade-in-out": "fade 2s ease-in-out infinite",
      },
    },
  },
  plugins: [],
};

```

### server/endpoints/test.py

```python

from flask import Flask, request, jsonify

from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())


def handle_test():
    return jsonify({"test": "passed"}), 200


```

### scheduler-frontend/src/index.css

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

@keyframes pulse {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
}

.radiating-circle {
  position: relative;
}

.radiating-circle::before,
.radiating-circle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  transform: translate(-50%, -50%);
  animation: radiate 2s infinite ease-out;
}

.radiating-circle::after {
  animation-delay: 1s; /* Creates a staggered effect */
}

@keyframes radiate {
  0% {
    transform: translate(-50%, -50%) scale(0.5);
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -50%) scale(1.5);
    opacity: 0;
  }
}

```

### server/endpoints/google_login.py

```python

from flask import Flask, request, jsonify
from dotenv import load_dotenv, find_dotenv
import requests
import os
from flask_jwt_extended import create_access_token
import redis


load_dotenv(find_dotenv())


r = redis.Redis(
host=os.environ["REDIS_HOST"],
port=11866,
password=os.environ["REDIS_PASSWORD"])

def login():
    auth_code = request.get_json()['code']
    data = {
        'code': auth_code,
        'client_id': os.environ["GOOGLE_CLIENT_ID"],  # client ID from the credential at google developer console
        'client_secret': os.environ["GOOGLE_SECRET_KEY"],  # client secret from the credential at google developer console
        'redirect_uri': 'postmessage',
        'grant_type': 'authorization_code'
    }

    response = requests.post('https://oauth2.googleapis.com/token', data=data).json()
    
    headers = {
        'Authorization': f'Bearer {response["access_token"]}'
    }
    user_info = requests.get('https://www.googleapis.com/oauth2/v3/userinfo', headers=headers).json()

    # USE REDIS TO CREATE A KEY VALUE MAPPING FROM THE REFRESH TOKEN TO THE SESSION TOKEN
    # print("STARTING REDIS")
    # print(r.ping())
    # # r.set(response["refresh_token"],response["access_token"])
    # print("DONE WITH REDIS")

    # response.pop('access_token', None)
    response['user_info'] = user_info
    

    return response, 200


```

### scheduler-frontend/public/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Heather is essentially our own personal secretary you can talk to and have a conversation with. Heather uses an advanced scheduling 
      algorithm to schedule your day to day with direct GCAL interactions, so you don't have to."
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>Heather.ai</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

```

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