# Project export: EasyRead

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 2025
- Tagline: EasyRead makes reading effortless! Double-click a complex word for a simpler version, single-click to undo the change. Build a personal dictionary as you go. Read smarter, faster, and stress-free!
- Devpost: https://devpost.com/software/easyread-5axzc3
- GitHub: https://github.com/ym256-stanford/tree-hacks-2025.git
- Video: https://www.youtube.com/embed/phPByN5oowo?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Angelique CD (23 commits), ym256 (9 commits)

## Devpost submission (written by the team)

### Inspiration

EasyRead was a feature we always dreamed of - an effortless way to simplify complex words while reading. We thought it would be amazing to have this as a web app or a Chrome extension to help users read smoothly without interruptions.

### What it does

EasyRead makes reading easier by allowing users to double-click on complex words to see a simpler version. If they want to revert, a single click brings back the original word. Additionally, it builds a personal dictionary of all clicked words, helping users track and learn new vocabulary effortlessly.

### How we built it

This was our first hackathon and our first experience with web development. We built EasyRead using: HTML & CSS for the frontend UI/UX React to make the web app interactive Node.js for backend processing OpenAI's API with gpt-4o for linguistic tasks, such as finding simpler word replacements Dictionary API to find the definition for words Since we were new to web development, we spent a lot of time researching and experimenting with different implementations.

### Challenges we ran into

Building a website from scratch was much harder than we expected. Even basic functionalities took time. HTML & CSS were trickier than they seemed. Integrating OpenAPI required some trial and error to set properly and upgrade Requests Per Minute Rate, but it turned out to be one of the easier parts.

### Accomplishments we're proud of

We successfully built a functional prototype of EasyRead. We learned full-stack development (React, Node.js, APIs). We participated in our first hackathon and completed the project! Despite challenges, we created a working tool that can genuinely help people read better.

### What's next

for ReadEase We’re excited about the potential of ReadEase! Some next steps include: Turning it into a Chrome extension for seamless browser integration. Improving the dictionary feature, allowing users to save and review words. Adding more languages to help non-native speakers read better. Enhancing AI-powered simplifications for more natural word replacements. We’re excited to keep building!

## README (from the GitHub repository)

# tree-hacks-2025

## Detected evidence (automated analysis)

Indexed codebase: 21 recognized source files, 47 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (26 of 26)

```
app-react/.gitignore
app-react/backend/package.json
app-react/backend/server.js
app-react/eslint.config.js
app-react/index.html
app-react/package.json
app-react/README.md
app-react/src/.env
app-react/src/App.css
app-react/src/App.jsx
app-react/src/components/Chat.jsx
app-react/src/components/ClickableWords.jsx
app-react/src/components/ClickableWordsDefinition.jsx
app-react/src/components/ClickableWordsPreloaded.jsx
app-react/src/components/Navbar.jsx
app-react/src/index.css
app-react/src/layouts/MainLayout.jsx
app-react/src/main.jsx
app-react/src/pages/HomePage.jsx
app-react/src/pages/TextPage.jsx
app-react/src/pages/UploadPage.jsx
app-react/tailwind.config.js
app-react/theme_files/styles.css
app-react/vite.config.js
package.json
README.md
```

### Dependencies

- app-react/backend/package.json: cors@^2.8.5, dotenv@^16.4.7, express@^4.21.2, openai@^4.85.1
- app-react/package.json: @eslint/js@^9.19.0, @types/react@^19.0.8, @types/react-dom@^19.0.3, @vitejs/plugin-react@^4.3.4, autoprefix@^1.0.1, axios@^1.7.9, eslint@^9.19.0, eslint-plugin-react@^7.37.4, eslint-plugin-react-hooks@^5.0.0, eslint-plugin-react-refresh@^0.4.18, globals@^15.14.0, postcss@^8.5.2, react@^19.0.0, react-dom@^19.0.0, react-router-dom@^7.1.5, tailwindcss@^3.4.17, vite@^6.1.0

### Recent commits (newest first)

- bug fix
- merged with dictionary features
- Merge branch 'main' of https://github.com/ym256-stanford/tree-hacks-2025
- edits to prompt and casing issues
- small edits for testing
- Added Dictionary List generator
- Resolved merge conflict in backend/server.js. Added dictionary lookup.
- Updated server.js with my changes
- Merge branch 'main' of https://github.com/ym256-stanford/tree-hacks-2025
- removed repeated synonyms
- preloaded version with api calls
- Added style features to TextPage.jsx and updated HomePage.jsx
- Added API-key
- added double-click vs single-click feature
- added skeleton for preloading approach - no api calls yet
- ignoring .env secret
- Merge branch 'main' of https://github.com/ym256-stanford/tree-hacks-2025
- removing secret
- minor changes to layout
- Updated MainLayout, HomePage,  and UploadPage; added assets and package.json

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

### package.json

```
{
  "name": "tree-hacks-2025",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ym256-stanford/tree-hacks-2025.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/ym256-stanford/tree-hacks-2025/issues"
  },
  "homepage": "https://github.com/ym256-stanford/tree-hacks-2025#readme"
}

```

### app-react/package.json

```
{
  "name": "app-react",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.7.9",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-router-dom": "^7.1.5"
  },
  "devDependencies": {
    "@eslint/js": "^9.19.0",
    "@types/react": "^19.0.8",
    "@types/react-dom": "^19.0.3",
    "@vitejs/plugin-react": "^4.3.4",
    "autoprefix": "^1.0.1",
    "eslint": "^9.19.0",
    "eslint-plugin-react": "^7.37.4",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.18",
    "globals": "^15.14.0",
    "postcss": "^8.5.2",
    "tailwindcss": "^3.4.17",
    "vite": "^6.1.0"
  },
  "description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
  "main": "eslint.config.js",
  "keywords": [],
  "author": "",
  "license": "ISC"
}

```

### app-react/backend/package.json

```
{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.4.7",
    "express": "^4.21.2",
    "openai": "^4.85.1"
  }
}

```

### app-react/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### app-react/src/App.jsx

```javascript
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
//backend
import axios from "axios";
// pages
import {Route, createBrowserRouter, createRoutesFromElements, RouterProvider} from 'react-router-dom'
import MainLayout from './layouts/MainLayout'
import HomePage from './pages/HomePage'
import UploadPage from './pages/UploadPage'
import TextPage from './pages/TextPage'

const router = createBrowserRouter(
  createRoutesFromElements(
  <Route path = '/' element={<MainLayout/>}>
    <Route index element={<HomePage/>}/>,
    <Route path = '/upload' element = {<UploadPage/>}/>,
    <Route  path = '/text' element = {<TextPage/>}/>
 </Route>
));

function App() {

  return (
    <RouterProvider router = {router}/>
  );
}

export default App

```

### app-react/backend/server.js

```javascript
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import OpenAI from "openai";
import fetch from "node-fetch"; // Ensure node-fetch is installed if using older Node.js versions

dotenv.config();
const app = express();
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const custom_prompt = "Replace the following word with a simpler one. Limit your response to a single unhyphenated word. Be creative."

app.use(express.json());
app.use(cors());

console.log("API Key Loaded:", process.env.OPENAI_API_KEY ? "Yes" : "No");

app.post("/chat/all", async (req, res) => {
  try {
    console.log("Received request body:", req.body);
    const { message } = req.body;
    if (!message) {
    }

    const response = await openai.chat.completions.create({
      model: "gpt-4o",

      messages: [{role: "system", content : custom_prompt},
        { role: "user", content: message }],
        n : 3
    })
    console.log("OpenAI response:", response);
    console.log("as list", response.choices.map((x) => x.message.content))
    res.json({ reply: response.choices.map((x) => x.message.content) });

  } catch (error) {
    console.error("Error:", error);
    res.status(500).json({ error: error.message });
  }
});

// Dictionary API for definitions
app.post("/define", async (req, res) => {
  try {
    console.log("Received request body:", req.body);
    const { message } = req.body;
    if (!message) {
      return res.status(400).json({ error: "Message is required" });
    }

    const apiUrl = `https://api.dictionaryapi.dev/api/v2/entries/en/${message}`;
    const response = await fetch(apiUrl);
    if (!response.ok) {
      return res.status(404).json({ error: "Definition not found" });
    }

    const data = await response.json();
    const definition = data[0]?.meanings[0]?.definitions[0]?.definition || "No definition found.";

    console.log("Dictionary API response:", definition);
    res.json({ reply: definition });
  } catch (error) {
    console.error("Error:", error);
    res.status(500).json({ error: error.message });
  }
});

app.post("/chat/any", async (req, res) => {
    try {
      console.log("Received request body:", req.body);
      const { message } = req.body;
      if (!message) {
        return res.status(400).json({ error: "Message is required" });
      }
      const response = await openai.chat.completions.create({
        model: "gpt-4o",
        messages: [{role: "system", content : custom_prompt},
          { role: "user", content: message }],
      });
      console.log("OpenAI response:", response);
      res.json({ reply: response.choices[0].message.content });
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  });

app.listen(5005, () => console.log("Server running on port 5005"));

```

### app-react/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}


```

### app-react/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {port: 3000}
})

```

### app-react/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### app-react/eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default [
  { ignores: ['dist'] },
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    settings: { react: { version: '18.3' } },
    plugins: {
      react,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...js.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...react.configs['jsx-runtime'].rules,
      ...reactHooks.configs.recommended.rules,
      'react/jsx-no-target-blank': 'off',
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
]

```

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