# Project export: GPTasty Meals

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: Students have the time to cook. What they don't have, is the time to think about WHAT they should cook. That's where GPTasty Meals comes into play. List your pantry items, and get a custom recipe!
- Devpost: https://devpost.com/software/gptasty-meals
- GitHub: https://github.com/Ring-A-Bell/TreeHacks2024
- Demo: https://github.com/Ring-A-Bell/TreeHacks2024_fe
- Team: 1 GitHub contributor(s) — Aditya Ganti (28 commits)

## Devpost submission (written by the team)

### Inspiration

I use ChatGPT only for figuring out what I should be cooking for lunch/dinner. Which is ironic, considering I'm a student. A lot of the recipes I would find online would contain uncommon ingredients like Worcestershire sauce, or Water Chestnuts. As soon as I'd see this, I'd run to a different recipe. So I decided to conduct a market survey and figure out whether other students also face a similar problem.

### What it does

It uses RAG to enhance the results that are generated by the LLM. It takes in an input of ingredients in your pantry, and uses that to generate a recipe for you, making sure there's no other ingredients that you don't have at hand. How I built it React frontend, express backend, and a MongoDB data store. I also used LlamaIndex for managing the RAG knowledge base, which integrates into an OpenAI LLM API call. Challenges I ran into The lack of a team. As soon as I realized that I may have to go solo, I needed to prioritize building of the app. I had to cut off some features, and make sure that I have an MVP that's up and running. Integrating the RAG model was also super complex, since I didn't understand anything. Accomplishments that I'm proud of I learned an entirely new concept - Retrieval Augmented Generation (RAG) in under 24 hours. The entire core backend of the project works. I got stuck 100 times. Got unblocked 99 times. What I learned This is definitely a team effort. I wanted to give up. But there's something inside me that doesn't let me do that. What makes Stanford Stanford. Just listening to other students talk about their ideas, it felt amazing. I felt extremely out of place, but it was magical.

### What's next

A much more refined RAG infusion, complete with a vector store. The ability to adjust portion sizes based on the person's fitness activity levels, via a health/smartwatch API Users can rate the recipes they made. The app will generate a flavor profile, and adjust future recipes according to that. This might employ the use of AI Agents.

## README (from the GitHub repository)

# TreeHacks2024

## Detected evidence (automated analysis)

Indexed codebase: 34 recognized source files, 3290 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
- LlamaIndex (technology) — detected in the code
- MongoDB (technology) — detected in the code
- OpenAI (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- React (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (56 of 56)

```
.gitignore
App.ts
AppServer.ts
cicd.yaml
DataAccess.ts
example_pipeline.ts
example.ts
example2.ts
GooglePassport.ts
index.ts
package.json
public/asset-manifest.json
public/index.html
public/manifest.json
public/robots.txt
public/static/css/main.f855e6bc.css
public/static/css/main.f855e6bc.css.map
public/static/js/453.7134c757.chunk.js
public/static/js/453.7134c757.chunk.js.map
public/static/js/main.0f329364.js
public/static/js/main.0f329364.js.LICENSE.txt
public/static/js/main.0f329364.js.map
public/static/js/main.3de58bb2.js
public/static/js/main.3de58bb2.js.LICENSE.txt
public/static/js/main.3de58bb2.js.map
public/static/js/main.5bd504a1.js
public/static/js/main.5bd504a1.js.LICENSE.txt
public/static/js/main.5bd504a1.js.map
public/static/js/main.70a6fd63.js
public/static/js/main.70a6fd63.js.LICENSE.txt
public/static/js/main.70a6fd63.js.map
public/static/js/main.934f8c11.js
public/static/js/main.934f8c11.js.LICENSE.txt
public/static/js/main.934f8c11.js.map
public/static/js/main.ac1bd0f5.js
public/static/js/main.ac1bd0f5.js.LICENSE.txt
public/static/js/main.ac1bd0f5.js.map
public/static/js/main.e2e181f7.js
public/static/js/main.e2e181f7.js.LICENSE.txt
public/static/js/main.e2e181f7.js.map
README.md
src/interfaces/IConsumableModel.ts
src/interfaces/IFavoritesModel.ts
src/interfaces/IPantryModel.ts
src/interfaces/IRecipesModel.ts
src/interfaces/IUserModel.ts
src/models/ConsumableModel.ts
src/models/FavoritesModel.ts
src/models/PantryModel.ts
src/models/RecipesModel.ts
src/models/UserModel.ts
src/openai/test_connection.ts
src/togetherai/test_connection.ts
src/utils/PromptTemplate.ts
src/utils/RecipeTemplate.ts
tsconfig.json
```

### Dependencies

- package.json: @types/express@^4.17.21, @types/express-session@^1.17.10, @types/mongoose@^5.11.97, @types/node@^20.11.19, @types/passport@^1.0.16, @types/passport-google-oauth20@^2.0.14, api@^6.1.1, axios@^1.6.7, body-parser@^1.20.2, cors@^2.8.5, dotenv@^16.4.4, express@^4.18.2, express-session@^1.18.0, gh-pages@^6.1.1, llamaindex@^0.1.10, mongoose@^8.1.3, nanoid@^5.0.5, openai@^4.28.0, passport@^0.7.0, passport-google-oauth20@^2.0.0, ts-node-dev@^2.0.0, typescript@^5.3.3

### Recent commits (newest first)

- updated build files
- Create cicd.yaml
- add gh-pages
- frontend build files
- add LLM pipeline calls
- add build files from frontend
- add AI prompt engineering templates
- add callback route
- add passport to driver
- add Google Passport config
- add passport types
- add google passport
- add consumable routes
- add together ai connection
- create test for openai
- add put route for pantry
- typescript clean-up
- bug fix
- add Pantry routes
- add Recipe routes

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

### package.json

```
{
  "name": "treehacks2024",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "startserver": "tsc && node ./dist/AppServer.js",
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/express-session": "^1.17.10",
    "@types/mongoose": "^5.11.97",
    "@types/node": "^20.11.19",
    "@types/passport": "^1.0.16",
    "@types/passport-google-oauth20": "^2.0.14",
    "gh-pages": "^6.1.1",
    "ts-node-dev": "^2.0.0",
    "typescript": "^5.3.3"
  },
  "dependencies": {
    "api": "^6.1.1",
    "axios": "^1.6.7",
    "body-parser": "^1.20.2",
    "cors": "^2.8.5",
    "dotenv": "^16.4.4",
    "express": "^4.18.2",
    "express-session": "^1.18.0",
    "llamaindex": "^0.1.10",
    "mongoose": "^8.1.3",
    "nanoid": "^5.0.5",
    "openai": "^4.28.0",
    "passport": "^0.7.0",
    "passport-google-oauth20": "^2.0.0"
  }
}

```

### index.ts

```typescript
import express, { Express, Request, Response } from 'express';
import dotenv from 'dotenv';

dotenv.config();

const app: Express = express();
const port = process.env.PORT;

app.get('/', (req: Request, res: Response) => {
  res.send('Express + TypeScript Server');
});

app.listen(port, () => {
  console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
});
```

### App.ts

```typescript
import * as path from 'path';
import express from 'express';
import * as url from 'url';
import * as bodyParser from 'body-parser';
const cors = require('cors');
const https = require('https');
import GooglePassport from './GooglePassport';
import session from 'express-session';
import { nanoid } from 'nanoid';

import { UserModel } from './src/models/UserModel';
import { RecipesModel } from './src/models/RecipesModel';
import { PantryModel } from './src/models/PantryModel';
import { ConsumableModel } from './src/models/ConsumableModel';
import passport from 'passport';
import OpenAIConnection from './src/openai/test_connection';

// Creates and configures an ExpressJS web server.
class App {

  // ref to Express instance
  public Users: UserModel;
  public Recipes: RecipesModel;
  public Pantry: PantryModel;
  public Consumables: ConsumableModel;
  
  public express: express.Application;
  public googlePassport: GooglePassport;

  //Run configuration methods on the Express instance.
  constructor() {
    this.Users = new UserModel();
    this.Recipes = new RecipesModel();
    this.Pantry = new PantryModel();
    this.Consumables = new ConsumableModel();

    this.googlePassport = new GooglePassport();

    this.express = express();
    this.middleware();
    this.routes();
  }

  // Configure Express middleware.
  private middleware(): void {
    this.express.use(bodyParser.json());
    this.express.use(bodyParser.urlencoded({ extended: false }));
    this.express.use(cors());
    this.express.use(session({ secret: "tree_hacks" }));
    this.express.use(passport.initialize());
    this.express.use(passport.session());
  }

  // Configure API endpoints.
  private routes(): void {
    let router = express.Router();

    router.get('/', (req, res, next) => {
        res.send('Express + TypeScript Server');
    });

    router.get("/login/active", (req, res) => {
        if ((req.session as any)["uuid"]) {
            console.log("Session ID: ", (req.session as any)["uuid"]);
          res.send(true);
        } else {
        console.log("Session ID false: ", (req.session as any)["uuid"]);
          res.send(null);
        }
    });

    router.get(
        "/login/federated/google",
        passport.authenticate("google", {
          scope: ["profile", "email"]
        }),
        (req, res) => {
          res.send("Successful login");
        }
    );

    router.get(
        "/login/federated/google/callback",
        (req, res, next) => {
          req.session.save();
          next();
        },
        passport.authenticate("google", {
          failureRedirect: "/#/login"
        }),
        async (req, res) => {
          console.log("Successful login");
          console.log("req info - ", req.user);
  
        const googleProfile: any = JSON.parse(JSON.stringify(req.user));
        let doesUserExist: any = await this.Users.doesUserExist(res, googleProfile.id);

        if (!doesUserExist) {
            console.log("User doesn't exist. Creating a new entry for this user in the DB");
            let newUser: any = await this.Users.createUser(
                res,
                googleProfile.id,
                googleProfile.name.givenName,
                googleProfile.name.familyName,
                googleProfile.emails[0].value,
                googleProfile.photos[0].value
            );
            console.log("New user created with ID: ", newUser);
            (req.session as any)["uuid"] = newUser;
        } else {
            console.log("User already exists, logging in...");
            (req.session as any)["uuid"] = googleProfile.id;
        }
        req.session.save();
        console.log("Session ID: ", (req.session as any)["uuid"]);
        res.redirect("/#/dashboard");
        }
      );
  

    //
    // USER ROUTES
    // 

    router.get('/users/:userID', (req, res) => {
        const id = (req.session as any)["uuid"];
        const x = this.Users.getUserDetails(res, id);
        return x;
    });

    // router.post('/users', async (req, res) => {
    //     var details = req.body;
    //     const x = await this.Users.createUser(res, details);
    //     return x;
    // });

    //
    // RECIPE ROUTES
    //

    router.get('/recipes/:id', async (req, res) => {
        const id = req.params.id;
        const x = await this.Recipes.getRecipeDetails(res, id);
        return x;
    });

    router.post('/recipes', async (req, res) => {
        var details = req.body;
        const x = await this.Recipes.createRecipe(res, details);
        return x;
    })

    //
    // PANTRY ROUTES
    //

    router.post('/pantry', async (req, res) => {
        var details = req.body;
        const x = await this.Pantry.createPantry(res, details);
        return x;
    });

    router.get('/pantry/:id', async (req, res) => {
        const id = (req.session as any)["uuid"];
        const x = await this.Pantry.getPantryDetails(res, id);
        return x;
    })

    router.put('/pantry/:id', async (req, res) => {
        const id = (req.session as any)["uuid"];
        let details = req.body;
        const x = await this.Pantry.updatePantry(res, id, details);
        return x;
    });

    //
    // CONSUMABLE ROUTES
    //

    router.get('/consumables/:name', async (req, res) => {
        const name = req.params.name;
        const x = await this.Consumables.getConsumableByName(res, name);
        return x;
    });

    router.get('/consumables/:id', async (req, res) => {
        const id = req.params.id;
        const x = await this.Consumables.getConsumableByID(res, id);
        return x;
    });

    router.post('/consumables', async (req, res) => {
        var details = req.body;
        const x = await this.Consumables.createConsumable(res, details);
        return x;
    });

    //
    // OPENAI ROUTES
    //

    router.post('/openai', async (req, res) => {
        const mainIngredient = req.body;
        const id = (req.session as any)["uuid"];
        const pantryIn
[truncated — 476 more characters]
```

### AppServer.ts

```typescript
import dotenv from 'dotenv';
import {App} from './App';

dotenv.config();

const port = process.env.PORT;
let server: any = new App().express;
server.listen(port);
console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
```

### cicd.yaml

```yaml
name: CI
​
on:
    push:
        branches: [ master ]
    
jobs:
    deploy:
        runs-on: self-hosted
    
    strategy: 
        matrix:
            node-version: [12.x]
            
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
            node-version: ${{ matrix.node-version }}
      - run: npm install
      - run: npm run build --if-present
      - run: npm test

```

### example.ts

```typescript
// example.ts
import fs from "fs/promises";
import { Document, VectorStoreIndex } from "llamaindex";
import dotenv from "dotenv";

dotenv.config();

async function main() {
  // Load essay from abramov.txt in Node
  const essay = await fs.readFile(
    "node_modules/llamaindex/examples/abramov.txt",
    "utf-8",
  );

  // Create Document object with essay
  const document = new Document({ text: essay });

  // Split text and create embeddings. Store them in a VectorStoreIndex
  const index = await VectorStoreIndex.fromDocuments([document]);

  // Query the index
  const queryEngine = index.asQueryEngine();
  const response = await queryEngine.query({
    query: "What did the author do in college?",
  });

  // Output response
  console.log(response.toString());
}

main();
```

### DataAccess.ts

```typescript
import Mongoose from "mongoose";
require('dotenv').config();

class DataAccess {
    static mongooseInstance: any;
    static mongooseConnection: Mongoose.Connection;
    static DB_CONNECTION_STRING:string = process.env.MONGODB_CONNECTION_STRING || 'mongodb://localhost:27017/treehacks';

    
    constructor () {
        DataAccess.connect();
    }
    
    static connect (): Mongoose.Connection {
        if(this.mongooseInstance) return this.mongooseInstance;
        
        this.mongooseConnection  = Mongoose.connection;
        this.mongooseConnection.on("open", () => {
            console.log("Connected to mongodb.");
        });
        
        this.mongooseInstance = Mongoose.connect(this.DB_CONNECTION_STRING);
        return this.mongooseInstance;
    }
    
}
DataAccess.connect();
export {DataAccess};
```

### GooglePassport.ts

```typescript
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import dotenv from "dotenv";

dotenv.config();

class GooglePassport {
  constructor() {
    if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET && process.env.GOOGLE_CALLBACK_URL) {
      passport.use(
        new GoogleStrategy(
          {
            clientID: process.env.GOOGLE_CLIENT_ID,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET,
            callbackURL: process.env.GOOGLE_CALLBACK_URL
          },
          (accessToken: any, refreshToken: any, profile: any, done: any) => {
            done(null, profile);
          }
        )
      );

      passport.serializeUser((user: any, done: any) => {
        done(null, user);
      });

      passport.deserializeUser((user: any, done: any) => {
        done(null, user);
      });
    }
  }
}

export default GooglePassport;
```

### example_pipeline.ts

```typescript
import fs from "node:fs/promises";
import dotenv from "dotenv";

dotenv.config();

import {
  Document,
  IngestionPipeline,
  MetadataMode,
  OpenAIEmbedding,
  TitleExtractor,
  SimpleNodeParser,
} from "llamaindex";

async function main() {
  // Load essay from abramov.txt in Node
  const path = "C:\\Users\\Admin\\OneDrive\\GitHub\\kaun-paada\\output_chunk_1.csv";

  const essay = await fs.readFile(path, "utf-8");

  // Create Document object with essay
const document = new Document({ text: essay, id_: path });
const pipeline = new IngestionPipeline({
    transformations: [
        new SimpleNodeParser({ chunkSize: 1024, chunkOverlap: 20 }),
        new TitleExtractor(),
        new OpenAIEmbedding(), // Fix: Use 'maxTokens' instead of 'max_tokens'
    ],
});

// run the pipeline
const nodes = await pipeline.run({ documents: [document] });

  // print out the result of the pipeline run
  for (const node of nodes) {
    console.log(node.getContent(MetadataMode.NONE));
  }
}

main().catch(console.error);
```

### example2.ts

```typescript
import {
    TogetherLLM,
    Document,
    VectorStoreIndex,
    serviceContextFromDefaults,
  } from "llamaindex";
    import fs from "fs/promises";
  
  async function main() {
    // Create an instance of the LLM
    const togetherLLM = new TogetherLLM({
      apiKey: "0c8ec8dc3bef5da9caac0a8015afd179226c6fbe37b68a86b3f1df82f9b33fa0",
    });

    const essay = await fs.readFile(
        "node_modules/llamaindex/examples/abramov.txt",
        "utf-8",
      );
    // Create a service context
    const serviceContext = serviceContextFromDefaults({ llm: togetherLLM });
  
    const document = new Document({ text: essay, id_: "essay" });
    console.log(document);
  
    // Load and index documents
    const index = await VectorStoreIndex.fromDocuments([document], {
      serviceContext,
    });
  
    // get retriever
    const retriever = index.asRetriever();
  
    // Create a query engine
    const queryEngine = index.asQueryEngine({
      retriever,
    });
  
    const query = "What is the meaning of life?";
  
    // Query
    const response = await queryEngine.query({
      query,
    });
  
    // Log the response
    console.log(response);
  }
```

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