# Project export: Cuisine Alchemy

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: Pair up LLM chefs to design never-seen-before meals.
- Devpost: https://devpost.com/software/cuisine-alchemy
- GitHub: https://github.com/Shaurnav/cuisine-alchemy
- Video: https://www.youtube.com/embed/dfFiVqah8ig?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Joy G (22 commits), Aditya (9 commits), Xiao215 (8 commits), Shreyas Sharma (3 commits)

## Devpost submission (written by the team)

### Inspiration

The culinary world is one steeped in tradition. There's beauty in several-generations-old recipes, classic local ingredients, and learning how to cook from your grandma. With rapid globalization, the culinary world is also one that has innovated at every turn - big bold flavors, melting pots of cultures, and food fairs. People from opposite ends of the world connect and bond through our fundamental love for food! Our goal is to join these aspects of food and use it to connect foodies!

### What it does

Users can pick between chef personas from different countries. Two LLM agents are then separately prompted, and each of them represent one of the two chosen chefs. The agents build off each other, brainstorm different ideas, and reason about how varied ingredients' taste profiles would interact. Finally, end up with an innovative idea, DALL-E generated image, and a recipe for a brand new dish combining the two cuisines and cultures. We also allow the user to generate their own persona for the LLM agent to assume. The user can enter their cultural and cuisine background, their favorite recipes, and any dietary restrictions. The user-generated agent can then proceed to interact with any of the other agents to come up with new dish ideas tailor-made to the user's own culture! To share their new-found food combinations, we generate a tweet, recipe, and image of the new dish for users to share on their social media.

### How we built it

We built the LLM agents using OpenAI API and LangChain. We built the front-end using next.js, Tailwind CSS and the back-end with Flask. We used DALL-E 3 to generate images. What we accomplished One of the most challenging parts of this project was innovating in this unique sector of food, AI and technology. We are most proud of being able to simulate convergent dialogue between two artificially intelligent agents.

## README (from the GitHub repository)

Cuisine Alchemy


## Detected evidence (automated analysis)

Indexed codebase: 23 recognized source files, 31 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- JavaScript (language) — detected in the code
- LangChain (technology) — 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
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (29 of 29)

```
backend/.gitignore
backend/app.py
backend/requirements.txt
backend/simulation.py
backend/utils.py
frontend/.gitignore
frontend/components/button/index.tsx
frontend/components/chatbox/index.tsx
frontend/components/feedIcons/AllFeed.tsx
frontend/components/feedIcons/feedicon.tsx
frontend/components/icons/Card.tsx
frontend/components/icons/CountryCards.tsx
frontend/components/message/index.tsx
frontend/components/modal/index.tsx
frontend/components/navbar/index.tsx
frontend/interfaces/message.ts
frontend/next.config.mjs
frontend/package.json
frontend/pages/_app.tsx
frontend/pages/_document.tsx
frontend/pages/api/hello.ts
frontend/pages/feed.tsx
frontend/pages/index.tsx
frontend/postcss.config.js
frontend/README.md
frontend/styles/globals.css
frontend/tailwind.config.ts
frontend/tsconfig.json
README.md
```

### Dependencies

- backend/requirements.txt: aiohttp@==3.9.3, aiosignal@==1.3.1, annotated-types@==0.6.0, anyio@==4.2.0, arxiv@==2.1.0, attrs@==23.2.0, beautifulsoup4@==4.12.3, blinker@==1.7.0, certifi@==2024.2.2, cffi@==1.16.0, charset-normalizer@==3.3.2, click@==8.1.7, curl_cffi@==0.6.0b9, dataclasses-json@==0.6.4, distro@==1.9.0, duckduckgo_search@==4.4.3, feedparser@==6.0.10, Flask@==3.0.2, frozenlist@==1.4.1, google_search_results@==2.4.2, h11@==0.14.0, httpcore@==1.0.3, httpx@==0.26.0, idna@==3.6, itsdangerous@==2.1.2, Jinja2@==3.1.3, jsonpatch@==1.33, jsonpointer@==2.4, langchain@==0.1.7, langchain-community@==0.0.20, langchain-core@==0.1.23, langsmith@==0.1.2, lxml@==5.1.0, MarkupSafe@==2.1.5, marshmallow@==3.20.2, multidict@==6.0.5, mypy-extensions@==1.0.0, nest-asyncio@==1.6.0, numpy@==1.26.4, openai@==1.12.0, packaging@==23.2, pycparser@==2.21, pydantic@==2.6.1, pydantic_core@==2.16.2, python-dotenv@==1.0.1, PyYAML@==6.0.1, requests@==2.31.0, sgmllib3k@==1.0.0, sniffio@==1.3.0, soupsieve@==2.5, SQLAlchemy@==2.0.27, tenacity@==8.2.3, tqdm@==4.66.2, typing_extensions@==4.9.0, typing-inspect@==0.9.0, urllib3@==2.2.1, Werkzeug@==3.0.1, wikipedia@==1.4.0, yarl@==1.9.4
- frontend/package.json: @emotion/react@^11.11.3, @emotion/styled@^11.11.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, autoprefixer@^10.0.1, next@14.1.0, postcss@^8, react@^18, react-dom@^18, tailwindcss@^3.3.0, typescript@^5, typewriter-effect@^2.21.0

### Recent commits (newest first)

- Update README.md
- fix the ui
- changed recipe thing
- goated with the sauce question mark?
- towards the end now
- backend update for system message generation
- Merge branch 'main' of https://github.com/Shaurnav/foodagents into main
- added user submission
- Add requirement feature.
- Fix the prompt for final endpoint.
- added submit for input
- added the add new picture
- feed updated, cleanup other
- Merge branch 'main' of https://github.com/Shaurnav/foodagents into main
- DALL-E generated question mark character
- added basic streaming and parsing intto left and right
- Fix bug in final end point
- Fix bug in final end point
- Add files via upload
- added support and fixed CORS issues for now

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

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@emotion/react": "^11.11.3",
    "@emotion/styled": "^11.11.0",
    "next": "14.1.0",
    "react": "^18",
    "react-dom": "^18",
    "typewriter-effect": "^2.21.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

```

### backend/requirements.txt

```
aiohttp==3.9.3
aiosignal==1.3.1
annotated-types==0.6.0
anyio==4.2.0
arxiv==2.1.0
attrs==23.2.0
beautifulsoup4==4.12.3
blinker==1.7.0
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
curl_cffi==0.6.0b9
dataclasses-json==0.6.4
distro==1.9.0
duckduckgo_search==4.4.3
feedparser==6.0.10
Flask==3.0.2
frozenlist==1.4.1
google_search_results==2.4.2
h11==0.14.0
httpcore==1.0.3
httpx==0.26.0
idna==3.6
itsdangerous==2.1.2
Jinja2==3.1.3
jsonpatch==1.33
jsonpointer==2.4
langchain==0.1.7
langchain-community==0.0.20
langchain-core==0.1.23
langsmith==0.1.2
lxml==5.1.0
MarkupSafe==2.1.5
marshmallow==3.20.2
multidict==6.0.5
mypy-extensions==1.0.0
nest-asyncio==1.6.0
numpy==1.26.4
openai==1.12.0
packaging==23.2
pycparser==2.21
pydantic==2.6.1
pydantic_core==2.16.2
python-dotenv==1.0.1
PyYAML==6.0.1
requests==2.31.0
sgmllib3k==1.0.0
sniffio==1.3.0
soupsieve==2.5
SQLAlchemy==2.0.27
tenacity==8.2.3
tqdm==4.66.2
typing-inspect==0.9.0
typing_extensions==4.9.0
urllib3==2.2.1
Werkzeug==3.0.1
wikipedia==1.4.0
yarl==1.9.4

```

### backend/app.py

```python
from flask import Flask, request, jsonify
from flask_cors import CORS
from dotenv import load_dotenv
from simulation import DialogueSimulator, DialogueAgent
from utils import process_simulation_input, summarize_message, get_recipe
from openai import OpenAI

load_dotenv()
app = Flask(__name__)
CORS(app)

simulator = None
iteration = 0
client = OpenAI()

@app.route('/api/create-sim', methods=['POST'])
def create_sim():
    global iteration
    iteration = 0
    data = request.json
    chef = data.get('chefs', [])
    custom = data.get('custom', None)
    global simulator
    simulator = process_simulation_input(chef, custom)
    return jsonify({"status": "Simulation processed"}), 200

end_iteration = 8
last_message = ""
@app.route('/api/sim-step', methods=['GET'])
def sim_step():
    global iteration
    name, message = simulator.step()
    iteration += 1
    global last_message
    last_message = message
    return jsonify({"is_last": iteration >= end_iteration, "name": name, "text": message}), 200

@app.route('/api/final', methods=['GET'])
def final():
    summarized_message = summarize_message(last_message)
    recipe = get_recipe(summarized_message)
    image = client.images.generate(
        model="dall-e-3",
        prompt=f'A realistic food picture described by: {summarized_message}',
        size="1024x1024",
        quality="standard",
        n=1,
    )
    return jsonify({"summarized_message": summarized_message, "image": image.data[0].url, "recipe":recipe}), 200

@app.route('/', methods=['GET'])
def index():
    return "Welcome!"

if __name__ == '__main__':
    app.run(port=5000, debug=True)

```

### frontend/pages/index.tsx

```typescript
import Image from "next/image";
import { Inter } from "next/font/google";
import Navbar from "@/components/navbar";
import ChatBox from "@/components/chatbox";
import { MessageProps } from "@/interfaces/message";
import { useEffect, useState } from "react";
import CountryCards from "@/components/icons/CountryCards";
import Modal from "@/components/modal";
import Button from "@/components/button";

const inter = Inter({ subsets: ["latin"] });

export default function Home() {
  const [selectedCountries, setSelectedCountries] = useState<any[]>([]);
  const [messages, setMessages] = useState<MessageProps[]>([]);
  const [submitted, setSubmitted] = useState<boolean>();
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [chefMapping, setChefMapping] = useState<{
    countryOne: string;
    countryTwo: string;
  }>();
  const [input, setInputText] = useState<string>("");
  const [finalData, setFinalData] = useState();
  const [isModalOpen, setIsModalOpen] = useState(false);

  const handleSubmit = async () => {
    if (selectedCountries.length < 2) {
      window.alert("Please select at least two chefs!");
      return;
    }

    setIsLoading(true);
    setTimeout(async () => {
      try {
        const countryOne: string = selectedCountries[0].name;
        const countryTwo: string = selectedCountries[1].name;

        const CHEF_MAPPING = {
          countryOne: countryOne,
          countryTwo: countryTwo,
        };

        setChefMapping(CHEF_MAPPING);

        const response = await fetch("http://127.0.0.1:5000/api/create-sim", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Headers":
              "Origin, X-Requested-With, Content-Type, Accept, Authorization",
            "Access-Control-Request-Method": "GET, POST, DELETE, PUT, OPTIONS",
          },
          body: JSON.stringify({
            chefs: selectedCountries.map((item) => item.name),
            custom: input,
          }),
        });
        const data = await response.json();
          setSubmitted(true);
      } catch (error) {
        console.error("Error submitting data:", error);
      } finally {
        setIsLoading(false);
      }
    }, 2000);
  };

  useEffect(() => {
    const fetchNextMessage = async () => {
      if (!submitted && messages.length == 0) {
        return;
      }

      try {
        const response = await fetch("http://127.0.0.1:5000/api/sim-step", {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Headers":
              "Origin, X-Requested-With, Content-Type, Accept, Authorization",
            "Access-Control-Request-Method": "GET, POST, DELETE, PUT, OPTIONS",
          },
        });
        const data = await response.json();
        setTimeout(() => {
          setMessages((messages) => [...messages, data]);
        }, 1000); 

        

        if (data.is_last) {
          console.log('in here');
          const finalResponse = await fetch("http://127.0.0.1:5000/api/final", {
            method: "GET",
            headers: {
              "Content-Type": "application/json",
              Accept: "application/json",
              "Access-Control-Allow-Origin": "*",
              "Access-Control-Allow-Headers":
                "Origin, X-Requested-With, Content-Type, Accept, Authorization",
              "Access-Control-Request-Method": "GET, POST, DELETE, PUT, OPTIONS",
            },
          });
          
          const finalDataFromResponse = await finalResponse.json();
          console.table(finalDataFromResponse);
          
          setFinalData(finalDataFromResponse);
          
        }

      } catch (error) {
        console.error("Error fetching next message:", error);
      }
    };

    if (messages.length < 8) {
      fetchNextMessage();
    }

    //maybe a num processed...
  }, [messages, submitted]);

  const hasCountry = selectedCountries.some(
    (item) => item.name === "ADD YOUR OWN"
  );

  return (
    <main
      className={`flex min-h-screen flex-col items-center justify-between p-4 ${inter.className}`}
    >
      <div className="flex flex-col w-full">
        <Navbar />
        <div className="flex flex-col container mx-auto mt-3 pt-4">
          <CountryCards
            selectedCountries={selectedCountries}
            setSelectedCountries={setSelectedCountries}
          />
          <input
            className="p-4 h-10 w-1/2 text-xl self-center border-solid border-2 rounded-lg border-gray-600"
            onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
              setInputText(event.target.value)
            }
            placeholder={
              hasCountry
                ? "Please describe your custom chef"
                : "Please enter your dietary preferences"
            }
          />
          <button
            className="mt-4 flex rounded-2xl flex-row text-white justify-center self-center bg-primary w-[100px] h-[25px]"
            onClick={handleSubmit}
            disabled={isLoading}
          >
            {isLoading ? "Loading..." : "Submit"}
          </button>
          {submitted && (
            <div>
              <h1 className="text-2xl font-bold mb-1">Chat</h1>
              <ChatBox chefMapping={chefMapping} messages={messages} />
            </div>
          )}
          {finalData &&  
            <Button
              onClick={() => setIsModalOpen(!isModalOpen)}
              colorClass="bg-secondary w-1/4 rounded-xl self-center"
              title="Share!"
              textColor="text-black"
              marginClass="m-5"
            ></Button>
          }
          {isModalOpen && 
            <Modal>
              <div className="bg-primary w-[30re
[truncated — 1233 more characters]
```

### frontend/components/modal/index.tsx

```typescript
import React, { ReactNode } from 'react';

export type ModalProps = {
  children: ReactNode | undefined,
}

const Modal = ({children}: ModalProps) => {
  return (
    <div className="fixed inset-0 flex items-center justify-center z-50 overflow-y-scroll">
      <div className="fixed inset-0 bg-black opacity-50"></div>
      <div className="min-h-50 bg-lightblack p-6 rounded-md z-10">
        {children}
      </div>
    </div>
  );
};

export default Modal;
```

### frontend/components/chatbox/index.tsx

```typescript

import { MessageProps } from "@/interfaces/message";
import Message from "../message";

interface ChatBoxProps {
  messages: MessageProps[];
  chefMapping: { countryOne: string; countryTwo: string; } | undefined;
}

export default function ChatBox({ messages, chefMapping }: ChatBoxProps) {
  

  return (
    <div className="flex flex-col gap-2 p-4 border border-gray-300 rounded-lg">
      {messages.map((message, index) => (
        <Message key={index} text={message.text} name={message.name} chefMapping={chefMapping}/>
      ))}
    </div>
  );
};


```

### frontend/components/navbar/index.tsx

```typescript
import Link from "next/link";

export default function Navbar() {
  return (
    <header className="w-full">
      <nav
        className="flex flex-row justify-between items-center w-full"
        style={{ backgroundColor: "#FF8C00", borderRadius: "8px" }}
      >
        <Link href="/">
          <h1 className="text-4xl m-2 p-2">ByteBite Fusion</h1>
        </Link>
        <div className="flex flex-row">
          <Link href="/">
            <h1 className="text-2xl p-2 m-2">Home</h1>
          </Link>
          <Link href="/feed">
            <h1 className="text-2xl p-2 m-2">Feed</h1>
          </Link>
        </div>
      </nav>
    </header>
  );
}

```

### frontend/components/button/index.tsx

```typescript
import React, { MouseEventHandler } from "react"

export type ButtonProps = {
  title: string,
  colorClass: string,
  textColor: string,
  onClick: MouseEventHandler<HTMLButtonElement> | undefined,
  marginClass: string,
};

const Button = ({ title, colorClass, textColor, onClick, marginClass }: ButtonProps) => {
  return (
    <button
      onClick={onClick}
      className={`${colorClass} ${textColor} ${marginClass} px-3 py-2 rounded-lg text-sm transform scale-100 transition hover:scale-110 active:scal
    -95 focus:outline-none focus:ring-1 focus:ring-offset-1 sm:width-full md:width-full lg:width-full`}
    >
      {title}
    </button>
  )
}

export default Button;

```

### frontend/components/message/index.tsx

```typescript
import { MessageProps } from "@/interfaces/message";
import { useEffect, useState } from "react";
import Typewriter from 'typewriter-effect';

export default function Message({ text, name, chefMapping }: MessageProps) {
  console.log(name);
  console.log(chefMapping);

  if (name === chefMapping?.countryOne) {
    name = 'chefone';
  } else {
    name = 'cheftwo';
  }

  const alignmentClass = name === 'chefone' ? 'justify-end' : 'justify-start';
  const backgroundColorClass = name === 'chefone' ? 'bg-secondary' : 'bg-primary';
  const textColorClass = name === 'chefone' ? 'text-gray' : 'text-white';

  return (
    <div className={`flex ${alignmentClass} mb-2`}>
      <div className={`rounded-lg p-2 ${backgroundColorClass} ${textColorClass} max-w-[36rem]`}>
        {text}
      </div>
    </div>
  );
};



```

### frontend/postcss.config.js

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

```

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