# Project export: MessageMinder

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: A messaging mindfulness application for bad texters
- Devpost: https://devpost.com/software/messageminder
- GitHub: https://github.com/hughmarch/treehacks24
- Video: https://www.youtube.com/embed/KC9eMCcj1Y4?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — hmarch (6 commits), Yifei Lu (1 commits)

## Devpost submission (written by the team)

### Inspiration

Are you a bad texter? Do you not know what to say to your friends over text? MessageMinder tries to help maintain meaningful connections with friends by suggesting responses to difficult texts.

### What it does

MessageMinder finds meaningful responses to difficult texts. MessageMinder gets your friend's texts that you haven't replied to, and based on your thoughts on the texts and from learning your texting style/mannerisms, suggests responses to text your friends back.

### How we built it

We built the frontend using NextJS and power our response suggestion engine with a LLM using Together.ai.

### Challenges we ran into

Prompt engineering was a large challenge, and a lot of effort was put into fine-tuning our prompts to yield the most clean and relevant response suggestions.

### What's next

We want to integrate MessageMinder with messaging platforms such as iMessage, Whatsapp, etc, so you can view all your messages in one place, and directly send suggestions from the app.

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 10 recognized source files, 12 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code

## Codebase structure (from repository index)

### Files (16 of 16)

```
.eslintrc.json
.gitignore
app/api/messages.json
app/clients/messageminder.js
app/clients/messageminder.test.js
app/components/Message.js
app/components/TypewriterEffect.js
app/globals.css
app/layout.js
app/page.js
jsconfig.json
next.config.mjs
package.json
postcss.config.js
README.md
tailwind.config.js
```

### Dependencies

- package.json: autoprefixer@^10.0.1, dotenv@^16.4.4, eslint@^8, eslint-config-next@14.1.0, jest@^29.7.0, next@14.1.0, postcss@^8, react@^18, react-dom@^18, tailwindcss@^3.3.0

### Recent commits (newest first)

- rename placeholder
- integrate messageminder client
- fix
- implement messageminder client
- added frontend
- mock get suggested responses
- Initial commit from Create Next App

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

### package.json

```
{
  "name": "treehacks24",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "dotenv": "^16.4.4",
    "jest": "^29.7.0",
    "next": "14.1.0",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.1.0",
    "postcss": "^8",
    "tailwindcss": "^3.3.0"
  }
}

```

### app/layout.js

```javascript
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

```

### app/page.js

```javascript
'use client';

import { useState } from 'react';
import Message from './components/Message';
import message_data from './api/messages.json';
import './globals.css';

export default function Home() {
  const [messages, setMessages] = useState(message_data);

  return (
    <div>
      <h1 className="mb-4 text-6xl font-extrabold leading-none tracking-tight text-blue-600 md:text-5xl lg:text-6xls mx-auto text-center mt-4">
        MessageMinder
      </h1>
      <div className="main mb-4">
        {messages.map((message, index) => (
          <Message
            key={index}
            image={message.image}
            name={message.name}
            message={message.message}
            time={message.time}
            status={message.status}
          />
        ))}
      </div>
    </div>
  );
}

```

### postcss.config.js

```javascript
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

```

### tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
};

```

### app/globals.css

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

:root {
  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 214, 219, 220;
  --background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

body {
  color: rgb(var(--foreground-rgb));
  background: linear-gradient(
      to bottom,
      transparent,
      rgb(var(--background-end-rgb))
    )
    rgb(var(--background-start-rgb));
}

@layer utilities {
  .text-balance {
    text-wrap: balance;
  }
}

.main {
  display: flex;
  flex-direction: column;
  gap: 3rem;
  margin-top: 2rem;
}

```

### app/clients/messageminder.test.js

```javascript
const getSuggestedResponses = require('./messageminder');

describe("messageminder client tests", () => {
  test("sample input and output", async () => {
    const suggestions = await getSuggestedResponses(
      "can you help me with my homework?",
      "I don't want to so I want to make an excuse",
      5
    );

    console.log("Suggestions:", suggestions);
  }, 50000);
});

```

### app/components/TypewriterEffect.js

```javascript
import React, { useState, useEffect } from 'react';

// SingleMessageTypewriter applies the typewriter effect to a single message
const SingleMessageTypewriter = ({ message }) => {
  const [displayedText, setDisplayedText] = useState('');
  const words = message.split(" ");
  const [wordIndex, setWordIndex] = useState(0);

  useEffect(() => {
    if (words.length === 0) return; // Do nothing if the message is empty

    const intervalId = setInterval(() => {
      // Add the next word to the displayed text
      if (!words[wordIndex]) {
        clearInterval(intervalId);
        return;
      }
      setDisplayedText((currentText) => currentText + (currentText ? " " : "") + words[wordIndex]);
      setWordIndex((currentIndex) => currentIndex + 1);
    }, 75); // Adjust timing as needed

    return () => clearInterval(intervalId); // Clean up on unmount
  }, [words, wordIndex]);

  return <p className="text-black my-2 text-left">{displayedText}</p>;
};

// TypewriterEffect maps each suggested response to its own SingleMessageTypewriter
const TypewriterEffect = ({ suggestedResponses }) => {
  return (
    <>
      {suggestedResponses.length !== 0 &&
        suggestedResponses.map((response, index) => (
          <SingleMessageTypewriter key={index} message={response} />
        ))}
    </>
  );
};

export default TypewriterEffect;

```

### app/clients/messageminder.js

```javascript
require('dotenv').config();

const url = 'https://api.together.xyz/v1/chat/completions';
const apiKey = process.env.NEXT_PUBLIC_API_KEY;

// parameters:
// message - the message we are looking for a response to, i.e. "can you help me with my homework"
// userPrompt - extra input by user, "I don't want to so I want to make an excuse", or empty string
// numSuggestedResponses - number of suggested responses to return
// returns:
// suggestedResponses - list of suggestions provided by LLM model
const getSuggestedResponses = async (message, userPrompt, numSuggestedResponses) => {
  
  const prompt = `
    Imagine you are texting your friend. Come up with a response to their text. Use exclusively lowercase
    and don't use any punctuation. Abbreviate words like "you" to "u", "right now" to "rn," etc. PROVIDE
    YOUR RESPONSE AND NOTHING ELSE! NO EXTRA COMMENTS!

    FRIEND'S TEXT:
    ${message}
    
    ${userPrompt === "" ? "" : "HERE'S HOW YOU FEEL ABOUT IT:"}
    ${userPrompt}
    
    MAKE YOUR RESPONSES SIMILAR (CAPITALIZATION, MANNERISMS) TO:
    can u send me ur notes
    oh yea im down
    cant wait to get off
    what if i die fr
    why r u there
    u left me alone
    oh so niceeee
    when is he getting there
    daamn`;

  const headers = new Headers({
  'Content-Type': 'application/json',
  'Authorization': `Bearer ${apiKey}`
  });

  const data = {
    model: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
    max_tokens: 1024,
    messages: [
      {
        role: "user",
        content: prompt
      }
    ],
    temperature: 1.5,
    top_p: 0.7,
    top_k: 50,
    repetition_penalty: 1,
    n: numSuggestedResponses
  };

  const options = {
    method: 'POST',
    headers,
    body: JSON.stringify(data)
  };

  try {
    const response = await fetch(url, options);
    const json = await response.json();

    if (!response.ok) {
      throw new Error(JSON.stringify(json));
    }

    return json.choices
      .map((choice) => {
        return choice.message.content.replace(/\s+/g, ' ').trim();
      });
  } catch (e) {
    console.error('Error:', e);
    throw e;
  }
}

module.exports = getSuggestedResponses;

```

### app/components/Message.js

```javascript
import Image from 'next/image';
import { useState } from 'react';
import getSuggestedResponses from '../clients/messageminder';
import TypewriterEffect from './TypewriterEffect';

const NUM_RESPONSES = 3;

export default function Message({ image, name, message, time, status }) {
  const [isLoading, setIsLoading] = useState(false);
  const [suggestedResponses, setSuggestedResponses] = useState([]);
  const [userPrompt, setUserPrompt] = useState("");

  const onClickGenerate = async () => {
    setIsLoading(true);
    setSuggestedResponses(await getSuggestedResponses(message, userPrompt, NUM_RESPONSES));
    setIsLoading(false);
  }

  return (
    <div className="bg-white rounded-[20px] p-5 inline-block flex items-center w-[700px] mx-auto">
      <div className="flex items-center gap-5 flex-col">
        <div className="flex flex-row justify-center gap-2">
          <div className="relative w-8 h-8">
            <Image
              className="rounded-full"
              src={image}
              alt="Me image"
              layout="fill"
              objectFit="cover"
            />
          </div>
          <div className="flex flex-col flex-grow w-[600px] p-4 border border-gray-200 bg-blue-300 rounded-lg dark:bg-blue-700">
            <div className="flex items-center justify-between">
              <span className="text-sm font-semibold text-gray-900 dark:text-white">
                {name}
              </span>
              <span className="text-sm font-normal text-gray-500 dark:text-gray-400">
                {time}
              </span>
            </div>
            <p className="text-sm font-normal py-2.5 text-gray-900 dark:text-white">
              {message}
            </p>
            <span className="text-sm font-normal text-gray-500 dark:text-gray-400">
              {status}
            </span>
          </div>
        </div>
        <div className="w-[80%]">
          {(isLoading || suggestedResponses.length !== 0) && (
            <p className="text-black"><strong>Generated Responses:</strong></p>
          )}
          {!isLoading && suggestedResponses.length !== 0 && (
            <TypewriterEffect suggestedResponses={suggestedResponses} />
          )}
        </div>

        <div className="ml-[2rem]">
          {!isLoading && suggestedResponses.length === 0 && (
            <form className="w-full border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-700">
              <div className="px-4 py-2 bg-white rounded-t-lg dark:bg-gray-800 w-[600px]">
                <label htmlFor="comment" className="sr-only">
                  Your message
                </label>
                <textarea
                  id="comment"
                  rows="4"
                  className="w-full px-0 text-sm text-gray-900 bg-white border-0 dark:bg-gray-800 focus:ring-0 dark:text-white dark:placeholder-gray-400"
                  placeholder="Thoughts on this?"
                  value={userPrompt}
                  onChange={(event) => setUserPrompt(event.target.value)}
                  required
                ></textarea>
              </div>
              <div className="flex items-center justify-between px-3 py-2 border-t border-gray-200 dark:border-gray-600">
                <button
                  onClick={() => onClickGenerate()}
                  className="inline-flex items-center py-2.5 px-4 text-xs font-medium text-white bg-blue-700 rounded-lg focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-900 hover:bg-blue-800"
                >
                  Generate
                </button>
              </div>
            </form>
          )}

          {isLoading && (
            <div role="status">
              <svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
                <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
              </svg>
              <span class="sr-only">Loading...</span>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

```