# Project export: Shameify

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: Combat overconsumption with shopping shame. Proceed to get roasted on your latest and greatest wish list item.
- Devpost: https://devpost.com/software/shamify
- GitHub: https://github.com/sazwu/Shameify
- Demo: https://shameify.reflex.run/
- Team: 3 GitHub contributor(s) — Jenny Peng (10 commits), sazwu (9 commits), Hellen Zhao (1 commits)

## Devpost submission (written by the team)

### Inspiration

In the era of constant new social media trends, same-day shipping, and easy online shopping, overconsumption has become a prevalent problem for sustainability, especially among our generation. According to Business Insider, while Generation Z has been “at the forefront of the climate movement”, we have also been fueling the climate crisis at the same time with our spending habits. This includes all of us on the team as well who have more than once impulse-bought something that we did not necessarily need, but rather purchased for the temporary satisfaction of doing-so or to follow the latest trends among our peers. However, with new products coming out daily and trends that follow just as fast, our generation along with the rest of the population are buying and consuming products at an unprecedented rate. This begs the questions: how much are people actually using? Are these mindful purchases? Where is the line where shopping becomes an addiction and our consumption becomes so dangerous that our current sustainability practices are unable to support it? And finally: what will get people to stop buying things they (probably) don’t need? To this problem, we say: shame.

### What it does

Shameify is a web app and a browser extension that adds friction to the effortlessness of online shopping. Upon buying anything, Shameify induces a sense of judgement in an attempt to make the user reconsider whether or not they really need to purchase that item. Upon enabling the browser extension, users can click on the disappointed looking face that is generated above the ‘add to cart’ button on a shopping website to get their latest and greatest wishlist item reviewed and judged by criterias of sustainability, quality, and usability. Or, as the new generation likes to say: absolutely roasted and toasted with shame. Our AI evaluates the item, critiquing various features of the product such as its ingredients or materials and then returns to the user snarky comments about how it is potentially harmful to the environment to have them take a much needed step back and consider whether or not the item is worth it. “Is it really the thing you want to drop a paycheck on? This? Something that will go into landfills and last for centuries after you?” Generated attitudes like this curbs/thwarts’ the user’s impulsive purchase, playfully mocking them into hesitation and reminding them of their carbon footprint. Ashamed, the user is now a step closer to environmental conscientousness. And like so, Shameify lives up to its name: replacing shop with shame.

### How we built it

We built Shameify mainly using the Reflex framework for our web application and JavaScript for our chrome extension. Our process was multi-staged, involving the careful ideation, precise planning, and efficient coding that is necessary to ensure a quality tool for sustainable shopping. To start, in-depth research on our problem (overconsumption) proved to be an invaluable first step. With this in mind, we laid out a logical framework for our web app and extension that would ensure our sustainable shopping tool was perfectly engaging and provoking. Using the Reflex framework, we initiated the construction of our web app. To enhance its capabilities, we incorporated web scraping functionalities to retrieve helpful information about each product. Furthermore, we harnessed Together API’s open source models to generate sassy but educational opinions about the product. To maximize accessibility and user engagement, we also crafted a web extension using JavaScript to seamlessly direct shoppers to our web app. We then dedicated efforts to refining the interfaces of both components, prioritizing user-friendly interactions and visual appeal.

### Challenges we ran into

In building the web application, we ran into challenges with getting the user to feel like they were talking to a chat bot. To further elaborate, we had difficulties with displaying the AI response in a way that it would ask one rhetoric or make one statement at a time and styling it so that it appeared as if it was being typed in real-time instead of just displaying it on the screen all at once. We wanted the responses to be generated one at a time so that we could add user responses in-between roasts and make the experience more interactive.

### Accomplishments we're proud of

We’re proud of all the new skills we picked up in these short 36 hours! For starters, most of us were unfamiliar with python, but we challenged ourselves to quickly learn this new language. We also gained experience combing through documentation to pick up new frameworks, which is what we did with Reflex, a web development framework that (surprise!) uses only python. Furthermore, we’re excited about harnessing the Together API to make our first project incorporating artificial intelligence! AI is a transformative and ever-growing industry, and Shameify marks our first step in exploring its potential.

### What we learned

By the end of development, we learned about new full-stack frameworks such as Reflex enough to build a basic working application. In addition, we learned about web scraping and certain methodologies to do so with Beautiful Soup and Selenium. We learned how to build a web extension to manipulate webpage elements in order to give users a wider range of functionalities. It was also our first time incorporating an AI model and doing some prompt engineering by querying a model from Together.AI. We got to see how different models perform differently, and how to leverage LLMs for cool functionality and creating a fun text experience.

### What's next

As most Hackathon projects are, the original ideas for Shameify were much broader than what we ended up submitting. Things we have in mind for Shameify’s future are plans to introduce a way to track shopping habits and history, as well as have multiple items evaluated. Additionally, it would be amazing to have the extension notify you on whether you have purchased a similar item recently or not, as well as provide alternative recommendations which would ideally be more sustainable and of higher quality, as well as try to support local businesses and brands with sustainability goals in mind. A potential concern that users may have are the credibility of the AI response and evaluation. Future plans will include citations for the AI’s claims to make sure no misinformation is spread. We hope to be able to support multiple different shop and store websites, and eventually expand to international audiences. We all have a duty to protect this Earth and the people and beings who live here. Environmental activists have tried empathy, they have tried fear, and they have tried anger. But in the end, will it be shame that helps us become more environmentally conscious beings?

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 28 recognized source files, 47 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code

## Codebase structure (from repository index)

### Files (38 of 38)

```
.gitignore
.web/.gitignore
.web/components/reflex/chakra_color_mode_provider.js
.web/components/reflex/radix_themes_color_mode_provider.js
.web/env.json
.web/jsconfig.json
.web/next.config.js
.web/package.json
.web/pages/_app.js
.web/pages/_document.js
.web/pages/404.js
.web/pages/index.js
.web/postcss.config.js
.web/reflex.install_frontend_packages.cached
.web/reflex.json
.web/styles/styles.css
.web/styles/tailwind.css
.web/tailwind.config.js
.web/utils/client_side_routing.js
.web/utils/components.js
.web/utils/context.js
.web/utils/helpers/dataeditor.js
.web/utils/helpers/range.js
.web/utils/state.js
.web/utils/stateful_components.js
.web/utils/theme.js
requirements.txt
rxconfig.py
shame_scraping.py
shame-ext/manifest.json
shame-ext/src/content.js
shame-ext/src/popup.html
shame-ext/src/popup.js
shame-ext/todo.txt
shame/__init__.py
shame/ai.py
shame/shame.py
shame/style.py
```

### Dependencies

- .web/package.json: @chakra-ui/react@2.6.1, @chakra-ui/system@2.5.7, @emotion/react@^11.11.1, @radix-ui/react-form@^0.0.3, @radix-ui/themes@^2.0.0, autoprefixer@10.4.14, axios@1.4.0, framer-motion@10.16.4, json5@2.2.3, next@14.0.1, next-sitemap@4.1.8, next-themes@0.2.0, postcss@8.4.31, react@18.2.0, react-dom@18.2.0, silly@^0.2.0, socket.io-client@4.6.1, tailwindcss@3.3.2, universal-cookie@4.0.4
- requirements.txt: bs4@==0.0.2, lbuild@==1.20.0, nltk@==3.8.1, openai@==1.12.0, pipenv@==2021.5.29, pyelftools@==0.27, reflex@==0.4.0

### Recent commits (newest first)

- jenny's stuff
- pls work
- oops
- url stuff
- hmmm
- working version
- pls tell me its fixed
- environmetn stuff
- fix merge stuff fr
- ugly card and hard coded loading
- add more styling
- Merge branch 'main' of https://github.com/sazwu/shame
- chrome extension works git pull
- Questions cleaned up and stylized
- array of questions prints
- basic roast works
- input field can scrape valid url
- Add files via upload
- testing json formatting
- scraper

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

### requirements.txt

```
bs4==0.0.2
lbuild==1.20.0
nltk==3.8.1
openai==1.12.0
pipenv==2021.5.29
pyelftools==0.27
reflex==0.4.0

```

### .web/package.json

```
{
  "name": "reflex",
  "scripts": {
    "dev": "next dev",
    "export": "next build",
    "export-sitemap": "next build && next-sitemap",
    "prod": "next start"
  },
  "dependencies": {
    "@chakra-ui/react": "2.6.1",
    "@chakra-ui/system": "2.5.7",
    "@emotion/react": "^11.11.1",
    "@radix-ui/react-form": "^0.0.3",
    "@radix-ui/themes": "^2.0.0",
    "axios": "1.4.0",
    "framer-motion": "10.16.4",
    "json5": "2.2.3",
    "next": "14.0.1",
    "next-sitemap": "4.1.8",
    "next-themes": "0.2.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "silly": "^0.2.0",
    "socket.io-client": "4.6.1",
    "universal-cookie": "4.0.4"
  },
  "devDependencies": {
    "autoprefixer": "10.4.14",
    "postcss": "8.4.31",
    "tailwindcss": "3.3.2"
  }
}

```

### .web/pages/index.js

```javascript
/** @jsxImportSource @emotion/react */


import { Fragment, useCallback, useContext } from "react"
import { EventLoopContext, StateContexts } from "/utils/context"
import { Event, getBackendURL, getRefValue, getRefValues, isTrue } from "/utils/state"
import { Button as RadixThemesButton, Card as RadixThemesCard, Dialog as RadixThemesDialog, Flex as RadixThemesFlex, Heading as RadixThemesHeading, Separator as RadixThemesSeparator, Text as RadixThemesText, TextField as RadixThemesTextField, Theme as RadixThemesTheme } from "@radix-ui/themes"
import env from "/env.json"
import { Root as RadixFormRoot } from "@radix-ui/react-form"
import { CircularProgress } from "@chakra-ui/react"
import "@radix-ui/themes/styles.css"
import theme from "/utils/theme.js"
import NextHead from "next/head"



export function Fragment_8055bc82b6f31384d2d035455207a101 () {
  const [addEvents, connectError] = useContext(EventLoopContext);


  return (
    <Fragment>
  {isTrue(connectError !== null) ? (
  <Fragment>
  <RadixThemesDialog.Root open={connectError !== null}>
  <RadixThemesDialog.Content>
  <RadixThemesDialog.Title>
  {`Connection Error`}
</RadixThemesDialog.Title>
  <RadixThemesText as={`p`} css={{"fontFamily": "cousine, roboto, sans serif", "color": "#3A4140"}}>
  {`Cannot connect to server: `}
  {(connectError !== null) ? connectError.message : ''}
  {`. Check if server is reachable at `}
  {getBackendURL(env.EVENT).href}
</RadixThemesText>
</RadixThemesDialog.Content>
</RadixThemesDialog.Root>
</Fragment>
) : (
  <Fragment/>
)}
</Fragment>
  )
}

export function Fragment_c163bd4a0e8501dacea7585efe19b40d () {
  const state__cond_simple_state = useContext(StateContexts.state__cond_simple_state)
  const state__form_input_state = useContext(StateContexts.state__form_input_state)


  return (
    <Fragment>
  {isTrue(state__cond_simple_state.show) ? (
  <Fragment>
  {state__form_input_state.roast_text.map((text, index_ae34b164937246bb0ec4c55dd7287e87) => (
  <RadixThemesText as={`p`} css={{"font-family": "Courier New", "font-weight": "bold", "max-width": "40%", "margin": "auto"}} key={index_ae34b164937246bb0ec4c55dd7287e87}>
  {JSON.stringify(text)}
</RadixThemesText>
))}
</Fragment>
) : (
  <Fragment/>
)}
</Fragment>
  )
}

export function Fragment_7d45218be1e35110c8e31b3288f75da1 () {
  const state__progress_example_state = useContext(StateContexts.state__progress_example_state)
  const [addEvents, connectError] = useContext(EventLoopContext);


  return (
    <Fragment>
  {isTrue(state__progress_example_state.show_progress) ? (
  <Fragment>
  <CircularProgress isIndeterminate={true}/>
</Fragment>
) : (
  <Fragment>
  <RadixThemesButton css={{"background-color": "#E1F4F1", "color": "#3A4140"}} onClick={(_e) => addEvents([Event("state.progress_example_state.increment", {})], (_e), {})} type={`submit`}>
  {`submit`}
</RadixThemesButton>
</Fragment>
)}
</Fragment>
  )
}

export function Fragment_f32ac219649607827dc97aa219ff6df5 () {
  const state__cond_simple_state = useContext(StateContexts.state__cond_simple_state)
  const state__form_input_state = useContext(StateContexts.state__form_input_state)


  return (
    <Fragment>
  {isTrue(state__cond_simple_state.show) ? (
  <Fragment>
  <RadixThemesCard css={{"width": "40%", "hide": state__cond_simple_state.show}} variant={`classic`}>
  <RadixThemesText as={`p`} css={{"fontFamily": "cousine, roboto, sans serif", "color": "#3A4140"}}>
  {state__form_input_state.product_name}
</RadixThemesText>
  <RadixThemesText as={`p`} css={{"fontFamily": "cousine, roboto, sans serif", "color": "#3A4140"}}>
  {state__form_input_state.product_materials}
</RadixThemesText>
  <RadixThemesText as={`p`} css={{"fontFamily": "cousine, roboto, sans serif", "color": "#3A4140"}}>
  {state__form_input_state.product_brand}
</RadixThemesText>
</RadixThemesCard>
</Fragment>
) : (
  <Fragment/>
)}
</Fragment>
  )
}

export function Root_52ce73acac0c32b8b6f0bf48bc5c70d5 () {
  
    const handleSubmit_0fcb6fd238a318a4fb76b13ac2b2dbac = useCallback((ev) => {
        const $form = ev.target
        ev.preventDefault()
        const form_data = {...Object.fromEntries(new FormData($form).entries()), ...{}}

        addEvents([Event("state.form_input_state.handle_submit", {form_data:form_data})])

        if (true) {
            $form.reset()
        }
    })
    
  const [addEvents, connectError] = useContext(EventLoopContext);


  return (
    <RadixFormRoot className={`Root`} css={{"width": "100%"}} onSubmit={handleSubmit_0fcb6fd238a318a4fb76b13ac2b2dbac}>
  <RadixThemesFlex align={`start`} css={{"width": "100%", "margin": "auto", "flexDirection": "column"}} gap={`2`}>
  <RadixThemesTextField.Input css={{"defaultValue": "", "width": "100%"}} name={`input`} placeholder={`you want to waste money on...`} required={true}/>
  <Fragment_7d45218be1e35110c8e31b3288f75da1/>
</RadixThemesFlex>
  <Fragment_f32ac219649607827dc97aa219ff6df5/>
  <Fragment_c163bd4a0e8501dacea7585efe19b40d/>
</RadixFormRoot>
  )
}

export default function Component() {

  return (
    <Fragment>
  <Fragment_8055bc82b6f31384d2d035455207a101/>
  <RadixThemesFlex align={`start`} css={{"margin": "auto", "flexDirection": "column"}} gap={`2`}>
  <RadixThemesFlex align={`start`} css={{"margin": "auto", "flexDirection": "column"}} gap={`2`}>
  <RadixThemesHeading css={{"padding": "5px", "color": "#BD5318"}}>
  {`shameify`}
</RadixThemesHeading>
  <RadixThemesSeparator css={{"margin-bottom": "5px", "width": "100vw"}} size={`4`}/>
</RadixThemesFlex>
  <Root_52ce73acac0c32b8b6f0bf48bc5c70d5/>
</RadixThemesFlex>
  <NextHead>
  <title>
  {`Reflex App`}
</title>
  <meta content={`A Reflex app.`} name={`description`}/>
  <meta content={`favicon.ico`} property={`og:image`}/>
</NextHead>
</Fragment>
  )
}

```

### rxconfig.py

```python
import reflex as rx

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

### shame_scraping.py

```python
from bs4 import BeautifulSoup
import pip._vendor.requests as requests

# url = "https://www.nordstrom.com/s/llbean-quilted-quarter-zip-pullover/5715454?origin=coordinating-5715454-0-2-PDP_1-recbot-also_viewed_graph&recs_placement=PDP_1&recs_strategy=also_viewed_graph&recs_source=recbot&recs_page_type=product&recs_seed=5785205&color=LIGHT%20GRAY%20HEATHER"

def remove_tags(html):
  soup = BeautifulSoup(html, "html.parser")
  
  for data in soup(['style', 'script', 'a', 'head', 'header', 'footer', 'form', 'iframe', 'link']):
      # Remove tags
      data.decompose()
  
  return ' '.join(soup.stripped_strings)

def get_product(url):
  headers = {"User-Agent":"Mozilla/5.0"}
  html = requests.get(url, headers=headers).text

  return remove_tags(html)

```

### .web/postcss.config.js

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

```

### .web/next.config.js

```javascript
module.exports = {basePath: "", compress: true, reactStrictMode: true, trailingSlash: true, output: "export", distDir: "_static"};

```

### .web/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
	content: ["./pages/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"],
	theme: null,
	plugins: [
	],
};
```

### shame/style.py

```python
import reflex as rx

style = {
    "rx.button ::hover": {
        "background-color" : "#B6DED7"
    },
    "margin": "10px",
    rx.text: {
        "font_family": "cousine, roboto, sans serif",
        "color" : "#3A4140",
    },
    rx.heading: {
        "color" : "#BD5318"
    },
    rx.container: {
        "align-items": "center"
    },
    rx.vstack: {
        "margin" : "auto"
    },
    rx.button: {
        "background-color" : "#E1F4F1",
        "color" : "#3A4140",
    }
}

app = rx.App(style=style)

```

### shame/ai.py

```python
from openai import OpenAI
import os
import sys

TOGETHER_API_KEY = os.environ.get("OPENAI_API_KEY")

#todo: figure out how to add api key to env
client = OpenAI(api_key="fb92edaf615a32e491e05ca30e3a40a53a8bd3f8b7be1d965806bc12454906ca",
  base_url='https://api.together.xyz',
)

def get_roasted(input, roast: bool):
    content = "You are given text scraped from a product page html. Give me the following: product_name, description, materials, brand"
    if roast:
        content = "You are a sassy, critical, well-intentioned friend that hates overconsumption and cares about sustainability. Use short snappy sentences as you roast this product I'm considering buying and encourage me to do better, but educate me on the issues of the product."
    messages = [
        {
        "role": "system",
        "content": {content},
        },
    ]
    messages.append(input)
    chat_completion = client.chat.completions.create(
    messages=messages,
    model="Qwen/Qwen1.5-14B-Chat",
    max_tokens=1024
    )

    return chat_completion.choices[0].message.content

```

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