# Project export: Ensemble

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 11.0
- Tagline: Your Personal AI Stylist
- Devpost: https://devpost.com/software/ensemble-xlvjih
- GitHub: https://github.com/sunnythecode/Ensemble
- Team: 3 GitHub contributor(s) — sunnythecode (13 commits), Isaac Kong (2 commits), Kazuma Hakushi (2 commits)

## Devpost submission (written by the team)

### Inspiration

The idea for an AI-powered outfit stylist came to life during a weekend hackathon when we noticed a common problem: people often overlook clothes they already own, spending unnecessary time picking outfits and falling back on the same combinations. Our goal was simple but ambitious—to create a tool that encourages users to wear more of their wardrobe, discover new styles, and save time each morning. We wanted the AI to inspire users to step outside their comfort zones and try new things with the clothes they already own, reducing overconsumption and fostering a more sustainable relationship with fashion.

### What it does

The user must first upload pictures of their clothes to the AI stylist database. We integrated a MongoDB backend to store each encoded image. Each day, the user can record a voice note, describing their plans, the type of occasion (formal or casual), or local weather conditions. With these inputs, the AI suggests outfits by combining the user’s clothing pieces in new ways. For instance, it might recommend wearing that forgotten denim jacket with chinos you always wear, or show how you can layer sweaters for colder days. A key feature we built was reviews, allowing users to get feedback on possible outfits to buy that would even better fit their activities.

### How we built it

Ensemble uses several services to function. Deepgram is used for voice to text, MongoDB accesses the wardrobe database, while Groq and the multimodal Llama 3.2 help interpret and decide which outfit is best.

### Challenges we ran into

One significant challenge was familiarizing ourselves with the variety of APIs and AIs available at CalHacks. We had to not only learn about what each one does, but decide which ones to use to serve our project best. Another issue was the internet connection, with both the CalHacks and public CV Events not working consistently. Ultimately, the only reliable connection we could find was through mobile hotspots, which drained our phones’ batteries quickly

### Accomplishments we're proud of

Many of our team members were doing fullstack development for the first time, so having our website perform successfully, use multiple APIs, and serve its purpose well was a first step for all of us. Our virtual wardrobe page was also a nice way of abstracting wardrobes, as well as enabling the website to handle large amounts of user-input pictures.

### What we learned

We learned how to implement a backend for images and how to tune LLM hyperparameters(top_p and temperature) to get the desired randomness in outfit recommendations. We also learned how to develop voice transcription with Deepgram and pass it to the Groq API through web query.

### What's next

This is an application that could include further considerations for improvement. Ideally it would be better if it took into account factors such as weather and even personalized fashion sense. As an example, an improved Ensemble would pull weather information directly from the internet, without user input, and could create a profile of a user’s tastes and preferences based on an initial survey as well as continuous feedback. The application was also built on a website, but a mobile application may serve more users conveniently. We have included designs (figmas) for a mobile application as well.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 17 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
- MongoDB (technology) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (26 of 26)

```
.gitignore
app.js
bin/www
LICENSE
package.json
pages/home.html
pages/home2.html
pages/login.html
pages/record.html
pages/results.html
pages/signup.html
pages/upload.html
public/scripts/record.js
public/stylesheets/style.css
README.md
routes/index.js
Speech-to-Text/.gitignore
Speech-to-Text/README.md
Speech-to-Text/recording_to_text.py
Speech-to-Text/requirements.txt
util/groq-client.js
util/mongodb-client.js
util/SpeechToText-Client.js
views/error.pug
views/results.pug
views/upload.pug
```

### Dependencies

- package.json: @deepgram/sdk@^3.8.1, body-parser@*, cookie-parser@*, cross-env@^7.0.3, debug@*, dotenv@^8.2.0, express@^4.21.1, groq-sdk@^0.7.0, mongodb@^6.9.0, morgan@*, multer@^1.4.5-lts.1, pug@^3.0.2, square@^33.0.0
- Speech-to-Text/requirements.txt: aenum@==3.1.15, aiofiles@==24.1.0, aiohappyeyeballs@==2.4.3, aiohttp@==3.10.10, aiosignal@==1.3.1, anyio@==4.6.2.post1, attrs@==24.2.0, certifi@==2024.8.30, cffi@==1.17.1, dataclasses-json@==0.6.7, deepgram-sdk@==3.7.4, deprecation@==2.1.0, frozenlist@==1.4.1, h11@==0.14.0, httpcore@==1.0.6, httpx@==0.27.2, idna@==3.10, marshmallow@==3.23.0, multidict@==6.1.0, mypy-extensions@==1.0.0, packaging@==24.1, propcache@==0.2.0, pycparser@==2.22, sniffio@==1.3.1, sounddevice@==0.5.1, soundfile@==0.12.1, typing_extensions@==4.12.2, typing-inspect@==0.9.0, websockets@==13.1, yarl@==1.15.5

### Recent commits (newest first)

- shoe code
- isaac's changes, new branch
- fixes
- saved the github omg
- Merge branch 'main' of https://github.com/sunnythecode/Clueless
- clueless --> ensemble
- missing semicolon oh no
- buttons
- updates
- it worksgit add .
- groq work + html additions
- groq + mongoDB connected
- remobed inline js from html file
- groq stuff
- added python enviroment setup
- removed DS Store
- added speech to text
- Speech-to-Text init
- removing template code
- init, basic website draft

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

### package.json

```
{
  "name": "node-payment",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "NODE_ENV=production node ./bin/www",
    "test": "NODE_ENV=sandbox node ./bin/www"
  },
  "dependencies": {
    "@deepgram/sdk": "^3.8.1",
    "body-parser": "*",
    "cookie-parser": "*",
    "debug": "*",
    "dotenv": "^8.2.0",
    "express": "^4.21.1",
    "groq-sdk": "^0.7.0",
    "mongodb": "^6.9.0",
    "morgan": "*",
    "multer": "^1.4.5-lts.1",
    "pug": "^3.0.2",
    "square": "^33.0.0"
  },
  "devDependencies": {
    "cross-env": "^7.0.3"
  }
}
```

### Speech-to-Text/requirements.txt

```
aenum==3.1.15
aiofiles==24.1.0
aiohappyeyeballs==2.4.3
aiohttp==3.10.10
aiosignal==1.3.1
anyio==4.6.2.post1
attrs==24.2.0
certifi==2024.8.30
cffi==1.17.1
dataclasses-json==0.6.7
deepgram-sdk==3.7.4
deprecation==2.1.0
frozenlist==1.4.1
h11==0.14.0
httpcore==1.0.6
httpx==0.27.2
idna==3.10
marshmallow==3.23.0
multidict==6.1.0
mypy-extensions==1.0.0
packaging==24.1
propcache==0.2.0
pycparser==2.22
sniffio==1.3.1
sounddevice==0.5.1
soundfile==0.12.1
typing-inspect==0.9.0
typing_extensions==4.12.2
websockets==13.1
yarl==1.15.5

```

### app.js

```javascript
const express = require('express');
const path = require('path');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');

const routes = require('./routes/index');

const app = express();
const environment = app.get('env');

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/pages', express.static(path.join(__dirname, 'pages')));
app.use(express.static(path.join(__dirname, '.well-known')));

app.use('/', routes);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  const err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// production error handler
// no stacktraces leaked to user
if (environment === 'production') {
  app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: {}
    });
  });
} else {
  // development error handler
  // will print stacktrace
  app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}


module.exports = app;

```

### routes/index.js

```javascript
const express = require('express');
const path = require('path');
const router = express.Router();
const { createClient } = require('@deepgram/sdk');
const { getRankingOfClothes } = require('../util/groq-client')

const app = express();
const env = app.get('env');
const { readFile } = require('fs');
const multer = require('multer');
const { createLogin, checkLogin, checkIdExists, uploadClothes, getClothesTypeByUserId} = require('../util/mongodb-client');
const deepgram = createClient("66d32a0950d3e2255dad552579573b58199d5f9a");


const storage = multer.memoryStorage(); // Store files in memory
const upload = multer({ storage: storage });

router.get('/login', async function (req, res) {
  readFile('./pages/login.html', 'utf8', (err, html) => {
    if (err) {
      res.status(500).send("Sorry Out of order")
    } else {
      res.send(html)
    }

  })

});

router.post('/login', async (req, res) => {
  const { email, password } = req.body; // Extract form data

  try {
    const check = await checkLogin(email, password);
    if (check.success) {
      res.redirect(`/record/${check.userID}`);
    } else {
      res.status(401).send('Invalid email or password.');
    }
  } catch (error) {
    console.error('Error during login:', error);
    res.status(500).send('Internal Server Error.');
  }
});

router.post('/signup', async (req, res) => {
  const { email, password } = req.body; // Extract form data

  try {
    const success = await createLogin(email, password);
    if (success) {
      res.redirect("/login");
    } 
  } catch (error) {
    console.error('Error during login:', error);
    res.status(500).send('Internal Server Error.');
  }
});

router.get('/signup', async (req, res) => {
  readFile('./pages/signup.html', 'utf8', (err, html) => {
    if (err) {
      res.status(500).send("Sorry Out of order")
    } else {
      res.send(html)
    }
  })
});

router.get('/results', async (req, res) => {
  const t = req.query.transcript;
  const userID = req.query.userID;
  console.log(t);
  console.log(userID);

  try {
    const idExists = await checkIdExists(req.query.userID);
    if (!idExists) 
    {
      res.status(500).send('Not logged in');
    } 
    else 
    {

      // Render the Pug template with the data
      const clothes = await getRankingOfClothes(userID, t);
      res.render("results", {clothes, t, userID} );
    }
    
  } catch (error) {
    console.error('Error fetching clothes:', error);
    res.status(500).send('Error loading wardrobe');
  }
  

});

router.get('/upload/:userID', async (req, res) => {
  try {
    const idExists = await checkIdExists(req.params.userID);
    if (!idExists) 
    {
      res.status(500).send('Not logged in');
    } 
    else 
    {
      const userId = req.params.userID;
      

      // Retrieve shirts and pants from MongoDB
      const shirts = await getClothesTypeByUserId(userId, "shirt");
      const pants = await getClothesTypeByUserId(userId, "pant");
      const shoes = await getClothesTypeByUserId(userId, "shoes");

      // Render the Pug template with the data
      res.render('upload', { userId, shirts, pants, shoes });
    }
    
  } catch (error) {
    console.error('Error fetching clothes:', error);
    res.status(500).send('Error loading wardrobe');
  }
  

  // readFile('./pages/upload.html', 'utf8', (err, html) => {
  //   if (idExists) {
  //     const modifiedHtml = html.replace('<%= userId %>', req.params.userID);
  //     res.send(modifiedHtml);
  //   } else {
  //     res.status(404).json({ message: "Not logged in" });
  //   }

  // })
});

router.post('/upload-clothes', upload.fields([{ name: 'shirts[]' }, { name: 'pants[]' }, { name: 'shoes[]' }]), uploadClothes);

router.post('/upload-audio', upload.single('audio'), async (req, res) => {
  if (!req.file) {
      return res.status(400).send('No audio file uploaded.');
  }

  // Create a Blob from the audio buffer
  const audioBlob = new Blob([req.file.buffer], { type: 'audio/webm' });

  //const audioFilePath = path.join(__dirname, req.file.path);
  const inputB = await audioBlob.arrayBuffer();
  // Call the Deepgram API for transcription
  try {
    const { result, error } = await deepgram.listen.prerecorded.transcribeFile(
      inputB,
      {
        model: "nova-2",
      }
    );

      if (error) {
          console.error('Deepgram Error:', error);
          return res.status(500).send('Error with Deepgram API');
      }

      // Print the results
      const transcriptResult = result.results.channels[0].alternatives[0].transcript;


      // Send the transcription result back to the client
      res.json(transcriptResult);
  } catch (error) {
      console.error('Error during transcription:', error);
      res.status(500).send('Error processing audio file');
  }
});




router.get('/', (request, response) => {

  readFile('./pages/home2.html', 'utf8', (err, html) => {
    if (err) {
      response.status(500).send("Sorry Out of order")
    } else {
      response.send(html)
    }

  })

});



router.get('/record/:userID', async (req, res) => {
  const idExists = await checkIdExists(req.params.userID);
  readFile('./pages/record.html', 'utf8', (err, html) => {
    if (idExists) {
      const modifiedHtml = html.replace(
        '<a href="/upload">',
        `<a href="/upload/${req.params.userID}">`
      );
      res.send(modifiedHtml);
    } else {
      res.status(404).json({ message: "Not logged in" });
    }

  })
  
});

router.post("/")



module.exports = router;

```

### pages/results.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Outfit Recommendation</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 20px;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: #fff;
            border-radius: 8px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        }
        h1 {
            text-align: center;
            color: #333;
        }
        .recommendation {
            margin-top: 20px;
            padding: 15px;
            background-color: #eaeaea;
            border-radius: 5px;
        }
        .item {
            margin: 10px 0;
            padding: 10px;
            background-color: #fff;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        .item h2 {
            margin: 0;
            font-size: 1.2em;
        }
        .item p {
            margin: 5px 0;
            color: #555;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Today's Outfit Recommendation</h1>
        <div class="recommendation">
            <div class="item">
                <h2>Recommended Shirt</h2>
                <p>Color: Blue</p>
                <p>Style: Casual Button-Up</p>
                <p>Material: Cotton</p>
            </div>
            <div class="item">
                <h2>Recommended Pants</h2>
                <p>Color: Khaki</p>
                <p>Style: Chinos</p>
                <p>Material: Twill</p>
            </div>
        </div>
    </div>
</body>
</html>

```

### pages/login.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login - Ensemble</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-image: url('../pages/image.jpg');
            background-size: cover; /* Make the image cover the entire background */
            background-position: center; /* Center the image */
        }
        .login-container {
            background-color: #ffffff;
            padding: 2rem;
            border-radius: 8px;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            width: 100%;
            max-width: 400px;
        }
        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 1.5rem;
        }
        form {
            display: flex;
            flex-direction: column;
        }
        label {
            margin-bottom: 0.5rem;
            color: #666;
        }
        input {
            padding: 0.5rem;
            margin-bottom: 1rem;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            background-color: #50b3a2;
            color: white;
            padding: 0.75rem;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
        }
        button:hover {
            background-color: #3a8f7f;
        }
        .signup-link {
            text-align: center;
            margin-top: 1rem;
        }
        .signup-link a {
            color: #50b3a2;
            text-decoration: none;
        }
        .signup-link a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h1>Login to Ensemble</h1>
        <form action="/login" method="POST">
            <label for="email">Email:</label>
            <input type="email" id="email" name="email" required>
            
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
            
            <button type="submit">Login</button>
        </form>
        <div class="signup-link">
            <p>Don't have an account? <a href="/signup">Sign up</a></p>
        </div>
    </div>
</body>
</html>
```

### pages/signup.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sign Up - Wardrobe Assistant</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            background-image: url('../pages/image.jpg');
            background-size: cover; /* Make the image cover the entire background */
            background-position: center; /* Center the image */
            height: 100vh;
        }
        .signup-container {
            background-color: #ffffff;
            padding: 2rem;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            width: 100%;
            max-width: 400px;
        }
        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 1.5rem;
        }
        form {
            display: flex;
            flex-direction: column;
        }
        label {
            margin-bottom: 0.5rem;
            color: #666;
        }
        input {
            padding: 0.5rem;
            margin-bottom: 1rem;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            background-color: #50b3a2;
            color: white;
            padding: 0.75rem;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
        }
        button:hover {
            background-color: #3a8f7f;
        }
        .login-link {
            text-align: center;
            margin-top: 1rem;
        }
        .login-link a {
            color: #50b3a2;
            text-decoration: none;
        }
        .login-link a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div class="signup-container">
        <h1>Sign Up for Ensemble</h1>
        <form action="/signup" method="POST">
            <label for="email">Email:</label>
            <input type="email" id="email" name="email" required>
            
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
            
            <label for="confirm-password">Confirm Password:</label>
            <input type="password" id="confirm-password" name="confirm-password" required>
            
            <button type="submit">Sign Up</button>
        </form>
        <div class="login-link">
            <p>Already have an account? <a href="/login">Log in</a></p>
        </div>
    </div>
</body>
</html>
```

### pages/home.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ensemble: Your Personal, Virtual Stylist</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            line-height: 1.6;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
        }
        .container {
            width: 80%;
            margin: auto;
            overflow: hidden;
            padding: 20px;
        }
        header {
            background: #50b3a2;
            color: white;
            padding-top: 30px;
            min-height: 70px;
            border-bottom: #e8491d 3px solid;
        }
        header h1 {
            margin: 0;
            text-align: center;
            padding-bottom: 20px;
        }
        .btn {
            display: inline-block;
            color: #fff;
            background: #e8491d;
            padding: 10px 20px;
            margin: 5px;
            border: none;
            cursor: pointer;
            text-decoration: none;
            font-size: 15px;
            border-radius: 5px;
        }
        .btn:hover {
            background: #ff6a3c;
        }
        .feature-box {
            background: #fff;
            padding: 20px;
            margin-top: 20px;
            border-radius: 5px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
        }
    </style>
</head>
<body>
    <header>
        <div class="container">
            <h1>Ensemble: Your Personal, Virtual Stylist</h1>
        </div>
    </header>

    <div class="container">
        <div class="feature-box">
            <h2>Welcome to Your Personal, Virtual Stylist</h2>
            <p>Get personalized outfit recommendations based on your plans for the day!</p>
            <a href="/login" class="btn">Login</a>
            <a href="/signup" class="btn">Sign Up</a>
        </div>

        <div class="feature-box">
            <h2>Upload Your Wardrobe</h2>
            <p>Add pictures of your clothes to your digital wardrobe.</p>
            <button class="btn">Upload Clothes</button>
        </div>

        <div class="feature-box">
            <h2>Get Outfit Recommendations</h2>
            <p>Tell us about your day, and we'll suggest the perfect outfit!</p>
            <button class="btn">Record Your Plans</button>
        </div>

        <div class="feature-box">
            <h2>Your Recommendations</h2>
            <p>Based on your voice note, here are today's outfit suggestions:</p>
            <div id="recommendations">
                <!-- Recommendations will be dynamically inserted here -->
                <p>Record your plans to see recommendations!</p>
            </div>
        </div>
    </div>
</body>
</html>
```

### pages/record.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Voice Memo Recorder</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            background-color: #f4f4f9;
            color: #333;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
            background-image: url('../pages/image.jpg');
            background-size: cover;
            background-position: center;
        }

        h1 {
            margin-bottom: 20px;
            font-size: 2.5em;
            color: #4a90e2;
        }

        .container {
            background: white;
            border-radius: 15px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            padding: 30px;
            text-align: center;
            width: 300px;
        }

        button {
            padding: 15px 20px;
            margin: 10px;
            border: none;
            border-radius: 5px;
            background-color: #4a90e2;
            color: white;
            font-size: 16px;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        button:hover {
            background-color: #357ab8;
        }

        audio {
            margin-top: 20px;
            display: block;
            width: 100%;
        }

        #downloadLink {
            display: none;
            margin-top: 10px;
            text-decoration: none;
            color: #4a90e2;
        }

        .recording-indicator {
            display: none;
            margin-top: 15px;
            font-size: 1.2em;
            color: red;
        }

        /* New button container */
        .button-container {
            position: absolute;
            top: 20px;
            right: 20px;
        }

        .button-container a {
            text-decoration: none;
        }

        .update-button {
            background-color: #50b3a2;
            padding: 10px 15px;
            font-size: 16px;
            border: none;
            border-radius: 5px;
            color: white;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        .update-button:hover {
            background-color: #3a8f7f;
        }
    </style>
</head>
<body>

    <!-- New Button Container -->
    <div class="button-container">
        <a href="/upload">
            <button class="update-button">Update Wardrobe</button>
        </a>
    </div>

    <div class="container">
        <h1>What are you planning to do today?</h1>
        <button id="recordButton">Start Recording</button>
        <button id="stopButton" disabled>Stop Recording</button>
        <div id="recordingIndicator" class="recording-indicator">Recording...</div>
        <audio id="audioPlayer" controls style="display: none;"></audio>
        <a id="downloadLink" download="voice_memo.webm">Download Memo</a>
        <button id="submitButton">Get Outfit Recommendation</button>
        <div id="transcriptionOutput"></div>
    </div>

    <script src="/scripts/record.js"></script>

</body>
</html>

```

### util/groq-client.js

```javascript
const Groq = require("groq-sdk"); // Use require to import the Groq SDK
const { getClothesTypeByUserId } = require('../util/mongodb-client');

const groq = new Groq({ apiKey: "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" }); // Initialize Groq with your API key

function findFirstDigit(str) {
    const match = str.match(/\d/); // \d matches a single digit
    return match ? match[0] : null; // Return the matched digit or null if not found
}

async function getRankingOfClothes(userID, context) {
    const shirts = await getClothesTypeByUserId(userID, "shirt");
    const pants = await getClothesTypeByUserId(userID, "pant");
    const shoes = await getClothesTypeByUserId(userID, "shoes");


    const updatedShirts = await Promise.all(
        shirts.map(async (shirt) => {
            const ranking = await getRankingSinglePiece(shirt, context);
            return { ...shirt, Ranking: ranking.Ranking, Message: ranking.Message };
        })
    );

    const maxRankingShirt = updatedShirts.reduce((max, shirt) => 
        shirt.Ranking > max.Ranking ? shirt : max
    );

    const updatedPants = await Promise.all(
        pants.map(async (pant) => {
            const ranking = await getRankingSinglePiece(pant, context);
            return { ...pant, Ranking: ranking.Ranking, Message: ranking.Message };
        })
    );

    const maxRankingPant = updatedPants.reduce((maxP, pant) => 
        pant.Ranking > maxP.Ranking ? pant : maxP
    );

    const updatedShoes = await Promise.all(
        shoes.map(async (shoe) => {
            const ranking = await getRankingSinglePiece(shoe, context);
            return { ...shoe, Ranking: ranking.Ranking, Message: ranking.Message };
        })
    );

    const maxRankingShoe = updatedShoes.reduce((max, shoeMax) => 
        shoeMax.Ranking > max.Ranking ? shoeMax : max
    );

    // console.log(maxRankingShirt.name);
    // console.log(maxRankingShirt.Message);

    // console.log(maxRankingPant.name);
    // console.log(maxRankingPant.Message);


    return {BestShirt: maxRankingShirt, BestPant: maxRankingPant, BestShoe: maxRankingShoe};

}
async function getRankingSinglePiece(pieceElem, contextString) {
    const base64URL = pieceElem.base64Image;
    const chatCompletion = await groq.chat.completions.create({
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": `Rank this piece of clothing out of 10 (10 is the best choice, 1 is the worst choice) based on it being worn for the following context: ${contextString}. Enter your ranking on one line, in the format: \n \n Rank: {ranking}/10 \n {Explanation}`
                    },
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": base64URL
                        }
                    }
                ]
            },
            {
                "role": "assistant",
                "content": ""
            }
        ],
        "model": "llama-3.2-90b-vision-preview",
        "temperature": 0.7,
        "max_tokens": 1024,
        "top_p": 0.6,
        "stream": false,
        "stop": null
    });

    const message = chatCompletion.choices[0].message.content;
    console.log(pieceElem.name);
    
    const rank = findFirstDigit(message);
    console.log(rank);
    console.log(message);
    return {Ranking: rank, Message: message}; 
}

//const k = getRankingOfClothes("67134047645b972a0f1ecdb5");
module.exports = { getRankingOfClothes };
```

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