# Project export: menu_generator

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 10.0
- Tagline: generate menu
- Devpost: https://devpost.com/software/menu_generator
- GitHub: https://github.com/dingxianger/calhacks.git
- Team: 2 GitHub contributor(s) — Xintong Ding (8 commits), Ginger Chang (8 commits)

## Devpost submission (written by the team)

### Inspiration

need menus!!!!

### What it does

Users input their dietary restrictions, fitness goals (calories limit), and budget. The website will form a query to chatGPT and ask for suggestions of meals. The suggestions will be reflected on the website in a clean, easy-to-understand table.

### How we built it

Using the built-in database of Convex and connecting to OpenAI ChatGPT's API.

### What we learned

How to use APIs

### What's next

Frontend!!! (and maybe more functions such as refreshing/reruning single menu entry)

## README (from the GitHub repository)

# calhacks

## Detected evidence (automated analysis)

Indexed codebase: 28 recognized source files, 44 KB.
- CSS (language) — 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
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (38 of 38)

```
.DS_Store
.gitignore
@/lib/utils.tsx
components.json
convex/_generated/api.d.ts
convex/_generated/api.js
convex/_generated/dataModel.d.ts
convex/_generated/server.d.ts
convex/_generated/server.js
convex/init.ts
convex/input01.ts
convex/messages.ts
convex/openai.ts
convex/output01.ts
convex/README.md
convex/schema.ts
convex/tsconfig.json
index.html
LICENSE
package.json
README.md
src/.DS_Store
src/App.tsx
src/components/.DS_Store
src/components/typography/code.tsx
src/components/typography/link.tsx
src/components/ui/button.tsx
src/components/ui/checkbox.tsx
src/components/ui/input.tsx
src/components/ui/label.tsx
src/index.css
src/lib/.DS_Store
src/lib/utils.tsx
src/main.tsx
src/vite-env.d.ts
tailwind.config.js
tsconfig.json
vite.config.ts
```

### Dependencies

- package.json: @faker-js/faker@^8.0.2, @radix-ui/primitive@^1.0.1, @radix-ui/react-checkbox@^1.0.4, @radix-ui/react-icons@^1.3.0, @radix-ui/react-label@^2.0.2, @radix-ui/react-slot@^1.0.2, @types/babel__core@^7.20.0, @types/node@^16.11.12, @types/react@^17.0.47, @types/react-dom@^17.0.17, @vitejs/plugin-react@3.1.0, class-variance-authority@^0.7.0, clsx@^2.0.0, convex@^1.5.1, lucide-react@^0.290.0, npm-run-all@^1.7.0, openai@^4.14.0, react@^17.0.2, react-dom@^17.0.2, tailwind-merge@^1.14.0, tailwindcss-animate@^1.0.7, typescript@~5.0.3, vite@^4.1.4

### Recent commits (newest first)

- more fix not sure
- more import fix
- fix lib import
- final??
- add visual menu table
- trying to render menu, lib error
- able to add response to databse output01
- ??
- change input type to string
- add input01 schema
- solved ui issues although still very ugly
- removed seed messages 2
- removed seed messages
- template
- starter template
- Initial commit

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

### package.json

```
{
  "name": "typescript-demo",
  "version": "0.0.0",
  "scripts": {
    "dev": "npm-run-all dev:init --parallel dev:frontend dev:backend",
    "build": "tsc && vite build",
    "dev:backend": "convex dev",
    "dev:frontend": "vite --open --clearScreen false",
    "dev:init": "convex dev --run init --until-success"
  },
  "dependencies": {
    "@faker-js/faker": "^8.0.2",
    "@radix-ui/primitive": "^1.0.1",
    "@radix-ui/react-checkbox": "^1.0.4",
    "@radix-ui/react-icons": "^1.3.0",
    "@radix-ui/react-label": "^2.0.2",
    "@radix-ui/react-slot": "^1.0.2",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.0.0",
    "convex": "^1.5.1",
    "lucide-react": "^0.290.0",
    "openai": "^4.14.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "tailwind-merge": "^1.14.0",
    "tailwindcss-animate": "^1.0.7"
  },
  "devDependencies": {
    "@types/babel__core": "^7.20.0",
    "@types/node": "^16.11.12",
    "@types/react": "^17.0.47",
    "@types/react-dom": "^17.0.17",
    "@vitejs/plugin-react": "3.1.0",
    "npm-run-all": "^1.7.0",
    "typescript": "~5.0.3",
    "vite": "^4.1.4"
  }
}

```

### src/main.tsx

```typescript
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { ConvexProvider, ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

ReactDOM.render(
  <StrictMode>
    <ConvexProvider client={convex}>
      <App />
    </ConvexProvider>
  </StrictMode>,
  document.getElementById("root")
);

```

### src/App.tsx

```typescript
import { useEffect, useState } from "react";
import { useMutation, useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
import { faker } from "@faker-js/faker";
import { Button } from "../src/components/ui/button";
import { Input } from "../src/components/ui/input"
import { Checkbox } from "../src/components/ui/checkbox"
import { Label } from "../src/components/ui/label"
import { clear } from "console";



// For demo purposes. In a real app, you'd have real user data.
const NAME = faker.person.firstName();

export default function App() {
  const messages = useQuery(api.messages.list);
  const sendMessage = useMutation(api.messages.send);
  const sendUserInput = useMutation(api.input01.sendUserInput01);
  const sendWeeklyInput = useMutation(api.input01.sendWeeklyInput);
  const clearMenu = useMutation(api.output01.clearMenu);
  const answer = useQuery(api.output01.list);
  const [newMessageText, setNewMessageText] = useState("");
  const [rememberMyPreferences, setRememberMyPreferences] = useState(false)


  const [newIdea, setNewIdea] = useState("")
  const [newCalories, setNewCalories] = useState("")
  const [newPrice, setNewPrice] = useState("")
  const [menuCreate, setMenuCreate] = useState(false);
  var days = ["Monday", "Monday", "Monday", "Tuesday", "Tuesday", "Tuesday", "Wednesday", "Wednesday", "Wednesday", "Thursday", "Thursday", "Thursday", "Friday", "Friday", "Friday", "Saturday", "Saturday", "Saturday", "Sunday", "Sunday", "Sunday"]
  var times = ["Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner"]
  //const [includeRandom, setIncludeRandom] = useState(true)

  // const ideas = useQuery(api.myFunctions.listIdeas)
  // const saveIdea = useMutation(api.myFunctions.saveIdea)
  // const generateIdea = useAction(api.myFunctions.fetchRandomIdea)

  useEffect(() => {
    window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
  }, [messages]);


  const showMenu = () => {
    console.log("show menu");
    return (
      <ul>
          {answer?.map((document, i) => (
            <li key={i}>
              {document.name}
              {document.calories}
              {document.price}
              {document.description}
            </li>
          ))}
      </ul>
    )
  }

  
  return (
    // chat
    <main className="container max-w-2xl flex flex-col gap-8">
        <div className="flex gap-2">
          <Input
            type="text"
            value={newIdea}
            onChange={(event) => setNewIdea(event.target.value)}
            placeholder="What can't you eat?"
          />
          <Input
            type="number"
            value={newCalories}
            onChange={(event) => setNewCalories(event.target.value)}
            placeholder="What's your calories goal?"
          />
          <Input
            type="number"
            value={newPrice}
            onChange={(event) => setNewPrice(event.target.value)}
            placeholder="What's the maximum you'd spend (for a week's meals)?"
          />
          
          <Button
            disabled={!newIdea}
            title={
              newIdea
                ? "Save your idea to the database"
                : "You must enter an idea first"
            }
            onClick={async () => {
              await sendUserInput({dietaryRestriction: newIdea, calories: newCalories, price: newPrice});
              if (!rememberMyPreferences) {
                setNewIdea("")
                setNewCalories("")
                setNewPrice("")
              }
              console.log("set ideas, calories, price");
              showMenu();
            }}
            className="min-w-fit"
          >
            Save idea
          </Button>

          <Button
            disabled={!newIdea}
            title={
              newIdea
                ? "Save your idea to the database"
                : "You must enter an idea first"
            }
            onClick={async () => {
              await clearMenu();
              console.log("before send weekly input");
              await sendWeeklyInput({dietaryRestriction: newIdea, calories: newCalories, price: newPrice});
              if (!rememberMyPreferences) {
                setNewIdea("")
                setNewCalories("")
                setNewPrice("")
              }
              console.log("set ideas, calories, price");
              showMenu();
            }}
            className="min-w-fit"
          >
            Generate Week Menu
          </Button>



          <Checkbox
              id="rememberMyPreferences"
              checked={rememberMyPreferences}
              onCheckedChange={() => setRememberMyPreferences(!rememberMyPreferences)}
            />
            <Label htmlFor="show-random">Remember My Preferences</Label>
          <div>
            {true && <p>hiddne</p>}
          </div>
          <ul>
          
      </ul>
      <table>
        <tr>
          <th>Day</th>
          <th>Meal</th>
          <th>Name</th>
          <th>Calories</th>
          <th>Price</th>
          <th>Description</th>
        </tr>
        {answer?.map((document, i) => (
            <tr key={i}>
              <td>{days[i]}</td>
              <td>{times[i]}</td>
              <td>{document.name}</td>
              <td>{document.calories}</td>
              <td>{document.price}</td>
              <td>{document.description}</td>
            </tr>
            
          ))}
      </table>
        </div>
        
      {/* <header>
        <h1>Acme Chat</h1>
        <p>
          Connected as <strong>{NAME}</strong>
        </p>
      </header>
      {messages?.map((message) => (
        <article
          key={message._id}
          className={message.author === NAME ? "message-mine" : ""}
        >
          <div>{message.author}</div>
        
[truncated — 539 more characters]
```

### convex/_generated/server.js

```javascript
/* eslint-disable */
/**
 * Generated utilities for implementing server-side Convex query and mutation functions.
 *
 * THIS CODE IS AUTOMATICALLY GENERATED.
 *
 * Generated by convex@1.5.1.
 * To regenerate, run `npx convex dev`.
 * @module
 */

import {
  actionGeneric,
  httpActionGeneric,
  queryGeneric,
  mutationGeneric,
  internalActionGeneric,
  internalMutationGeneric,
  internalQueryGeneric,
} from "convex/server";

/**
 * Define a query in this Convex app's public API.
 *
 * This function will be allowed to read your Convex database and will be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const query = queryGeneric;

/**
 * Define a query that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to read from your Convex database. It will not be accessible from the client.
 *
 * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
 * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
 */
export const internalQuery = internalQueryGeneric;

/**
 * Define a mutation in this Convex app's public API.
 *
 * This function will be allowed to modify your Convex database and will be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const mutation = mutationGeneric;

/**
 * Define a mutation that is only accessible from other Convex functions (but not from the client).
 *
 * This function will be allowed to modify your Convex database. It will not be accessible from the client.
 *
 * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
 * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
 */
export const internalMutation = internalMutationGeneric;

/**
 * Define an action in this Convex app's public API.
 *
 * An action is a function which can execute any JavaScript code, including non-deterministic
 * code and code with side-effects, like calling third-party services.
 * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
 * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
 *
 * @param func - The action. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
 */
export const action = actionGeneric;

/**
 * Define an action that is only accessible from other Convex functions (but not from the client).
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument.
 * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
 */
export const internalAction = internalActionGeneric;

/**
 * Define a Convex HTTP action.
 *
 * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
 * as its second.
 * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
 */
export const httpAction = httpActionGeneric;

```

### vite.config.ts

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import {resolve} from 'node:path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: [{ find: "@", replacement: resolve(__dirname, "./src") }]
  },
});


```

### index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Acme Chat</title>

    <meta
      name="theme-color"
      content="#7c3aed"
      media="(prefers-color-scheme: light)"
    />
    <meta
      name="theme-color"
      content="#6d28d9"
      media="(prefers-color-scheme: dark)"
    />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
	],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        "accordion-down": {
          from: { height: 0 },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: 0 },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
}
```

### src/vite-env.d.ts

```typescript
/// <reference types="vite/client" />

```

### convex/schema.ts

```typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  messages: defineTable({
    author: v.string(),
    body: v.string(),
  }),
  input01: defineTable({
    dietaryRestriction: v.string(),
    calories: v.string(),
    price: v.string(),
  }),
  output01: defineTable({
    name: v.string(),
    calories: v.string(),
    price: v.string(),
    description: v.string(),
  }),
});

```

### convex/init.ts

```typescript
import { api } from "./_generated/api";
import { internalMutation } from "./_generated/server";

const seedMessages = [
     ["Ian", "Hey, glad you're here.", 0],
  // ["Abhi", "What's up?", 1000],
  // ["Ian", "I'm hoping to show how reactive Convex is.", 1500],
  // ["Abhi", "Could you show streaming a ChatGPT response?", 1700],
  // ["Ian", "By updating the DB and having the query reflow?", 3000],
  // ["Abhi", "Yeah. @gpt do you think that's a good idea?", 2000],
  // ["Ian", "Very clever! Let's see what it thinks.", 600],
  // ["Ian", "Thanks @gpt!", 5000],
] as const;

if (!process.env.OPENAI_API_KEY) {
  const deploymentName = process.env.CONVEX_CLOUD_URL?.slice(8).replace(
    ".convex.cloud",
    ""
  );
  throw new Error(
    "\n  Missing OPENAI_API_KEY in environment variables.\n\n" +
      "  Get one at https://openai.com/ and paste it on the Convex dashboard:\n" +
      `  https://dashboard.convex.dev/d/${deploymentName}/settings?var=OPENAI_API_KEY`
  );
}

export const seed = internalMutation({
  handler: async (ctx) => {
    let totalDelay = 0;
    for (const [author, body, delay] of seedMessages) {
      totalDelay += delay;
      await ctx.scheduler.runAfter(totalDelay, api.messages.send, {
        author,
        body,
      });
    }
  },
});

export default internalMutation({
  handler: async (ctx) => {
    const anyMessage = await ctx.db.query("messages").first();
    if (anyMessage) return;
    await seed(ctx, {});
  },
});

```

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