# Project export: Big Backtivity

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: UC Berkeley AI Hackathon 2025
- Tagline: Instead of endless “I don’t care, where do you want to go?” debates, Big Backtivity listens to what everyone actually wants and uses AI to turn it into smart restaurant recommendations.
- Devpost: https://devpost.com/software/big-backtivity
- GitHub: https://github.com/samanthasu12/aihackathon
- Team: 4 GitHub contributor(s) — Anderson Liu (7 commits), Kevin Zhang (3 commits), emkwan (3 commits), samanthasu12 (2 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# aihackathon

# Big Backtivity 🍽️

<img width="541" alt="Screenshot 2025-06-22 at 12 37 46 PM" src="https://github.com/user-attachments/assets/3cccea05-b9c7-471c-9f7d-fba79944b333" />


**Big Backtivity** is a web app that helps groups decide where to eat by gathering and combining individual preferences, such as cuisine, price range, and dietary restrictions, into tailored restaurant recommendations. 

It’s designed to reduce decision fatigue and make group dining easier!
 
Built with:

- React + TypeScript (frontend)
- Letta (backend)
- Groq (Voice input) 

---

## Features

Group members can:
- Input individual food preferences through text or speech 
- View combined group preferences
- View restaurant recommendations

---

## Contributors
- Anderson Liu
- Samantha Su
- Kevin Zhang
- Elise Kwan



## Detected evidence (automated analysis)

Indexed codebase: 28 recognized source files, 38 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — 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 (36 of 36)

```
.gitignore
backend/app.py
backend/static/chat.js
backend/static/script.js
backend/static/style.css
backend/templates/chat.html
backend/templates/index.html
frontend/.gitignore
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.cjs
frontend/README.md
frontend/src/App.css
frontend/src/App.tsx
frontend/src/components/add_preference_form.tsx
frontend/src/components/EditModal.tsx
frontend/src/components/Entry.tsx
frontend/src/components/EntryList.tsx
frontend/src/components/group_preference_list.tsx
frontend/src/components/LandingView.tsx
frontend/src/components/MainView.tsx
frontend/src/index.css
frontend/src/main.tsx
frontend/src/stores/UserEntryStore.tsx
frontend/src/types.ts
frontend/src/utils/makeLettaCall.ts
frontend/src/utils/speechToText.ts
frontend/src/vite-env.d.ts
frontend/tailwind.config.js
frontend/tsconfig.app.json
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
README.md
requirements.txt
```

### Dependencies

- frontend/package.json: @eslint/js@^9.25.0, @headlessui/react@^2.2.4, @heroicons/react@^2.2.0, @letta-ai/letta-client@^0.1.132, @tailwindcss/postcss@^4.1.10, @tailwindcss/vite@^4.1.10, @types/react@^19.1.2, @types/react-dom@^19.1.2, @vitejs/plugin-react@^4.4.1, autoprefixer@^10.4.21, eslint@^9.25.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.19, globals@^16.0.0, mobx@^6.13.7, mobx-react-lite@^4.1.0, postcss@^8.5.6, react@^19.1.0, react-dom@^19.1.0, tailwindcss@^4.1.10, typescript@~5.8.3, typescript-eslint@^8.30.1, vite@^6.3.5
- requirements.txt: Flask, Flask-Cors

### Recent commits (newest first)

- Update README.md
- Update README.md
- revert cors change
- attempt to fix
- Create makeLettaCall.ts
- Added README
- change app route back to original
- Delete .env
- add mobx store
- add mobx for state storage
- Merge branch 'main' of https://github.com/samanthasu12/aihackathon
- updated front end
- add tailwind
- Merge branch 'main' of https://github.com/samanthasu12/aihackathon
- Add frontend form components and integrate Preference type
- Adaptive Memory Agent
- add frontend folder
- rename flask folder to backend
- Starting Code
- Create README.md

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

### requirements.txt

```
Flask
Flask-Cors
```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@headlessui/react": "^2.2.4",
    "@heroicons/react": "^2.2.0",
    "@letta-ai/letta-client": "^0.1.132",
    "@tailwindcss/vite": "^4.1.10",
    "mobx": "^6.13.7",
    "mobx-react-lite": "^4.1.0",
    "postcss": "^8.5.6",
    "react": "^19.1.0",
    "react-dom": "^19.1.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.25.0",
    "@tailwindcss/postcss": "^4.1.10",
    "@types/react": "^19.1.2",
    "@types/react-dom": "^19.1.2",
    "@vitejs/plugin-react": "^4.4.1",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.25.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^16.0.0",
    "tailwindcss": "^4.1.10",
    "typescript": "~5.8.3",
    "typescript-eslint": "^8.30.1",
    "vite": "^6.3.5"
  }
}

```

### backend/app.py

```python
import os
from flask import Flask, render_template, request, jsonify
import requests
from dotenv import load_dotenv

from letta_client import Letta

load_dotenv()

LETTA_API_KEY = os.getenv("LETTA_API_KEY")
LETTA_AGENT_ID = os.getenv("LETTA_AGENT_ID")

client = Letta(token=LETTA_API_KEY)

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("chat.html")

@app.route("/chat", methods=["POST"])
def chat():
    user_message = request.json.get("message")
    
    response = client.agents.messages.create(
        agent_id=LETTA_AGENT_ID,
        messages=[
            {
                "role": "user",
                "content": user_message
            }, 
            {
                "role": "system",
                "content": "Briefly summarize (less than 200 characters) new thoughts and append this to core memory in suggestion_context. Additionally, if restaurants are provided, ensure that they are valid by using the web search tool."
            }
        ]
    )

    letta_reply = []
    for msg in response.messages:
        if msg.message_type == "assistant_message":
            letta_reply.append("RESPONSE: " + msg.content)
        elif msg.message_type == "reasoning_message":
            letta_reply.append("REASON: " + msg.reasoning)
        elif msg.message_type == "tool_call_message":
            letta_reply.append("USE: " + msg.tool_call.name + " : " + msg.tool_call.arguments)
        elif msg.message_type == "tool_return_message":
            letta_reply.append("RETURN: " + msg.tool_return)

    return jsonify({"reply": letta_reply})

if __name__ == "__main__":
    app.run(debug=True)
```

### frontend/src/main.tsx

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

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

```

### frontend/src/App.tsx

```typescript
import { useEffect, useState } from "react";
import type { Preference } from "./types";
import GroupPreferencesList from "./components/group_preference_list";
import { recordAndTranscribe } from "./utils/speechToText";
import { StoreProvider, useUserEntryStore } from "./stores/UserEntryStore";
import { EntryList } from "./components/EntryList";
import { LandingView } from "./components/LandingView";
import { MainView } from "./components/MainView";

const App = () => {
  const store = useUserEntryStore();
  const [preferences, setPreferences] = useState<Preference[]>([]);
  const [inputs, setInputs] = useState<string[]>([]);
  const [recording, setRecording] = useState<number | null>(null);
  const [countdowns, setCountdowns] = useState<number[]>([]);
  const [numPeople, setNumPeople] = useState<number | null>(null);
  const [submittedPeopleCount, setSubmittedPeopleCount] = useState(false);

  useEffect(() => {
    fetch("http://localhost:3001/api/preferences")
      .then(res => res.json())
      .then(data => setPreferences(data))
      .catch(err => console.error("Failed to fetch preferences:", err));
  }, []);

  const handleAddPreference = async () => {
    const trimmedInputs = inputs.map(input => input.trim()).filter(Boolean);
    if (trimmedInputs.length === 0) return;

    for (const name of trimmedInputs) {
      try {
        const res = await fetch("http://localhost:3001/api/preferences", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ name }),
        });

        const result = await res.json();
        if (result.message === "Added preference!") {
          setPreferences(prev => [...prev, { name }]);
        } else {
          console.error("Failed to add preference:", result);
        }
      } catch (error) {
        console.error("Error adding preference:", error);
      }
    }

    setInputs(Array(numPeople || 0).fill(""));
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    //handleAddPreference();
    store.submitInputs(inputs);
  };

  const handleMicClick = async (index: number) => {
    const newCountdowns = [...countdowns];
    newCountdowns[index] = 5;
    setCountdowns(newCountdowns);
    setRecording(index);

    const countdownInterval = setInterval(() => {
      setCountdowns(prev => {
        const updated = [...prev];
        if (updated[index] <= 1) {
          clearInterval(countdownInterval);
        }
        updated[index] -= 1;
        return updated;
      });
    }, 1000);

    const text = await recordAndTranscribe();
    const newInputs = [...inputs];
    newInputs[index] = text;
    setInputs(newInputs);
    setRecording(null);
  };

  const handlePeopleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!numPeople || numPeople < 1) return;
    setInputs(Array(numPeople).fill(""));
    setCountdowns(Array(numPeople).fill(0));
    setSubmittedPeopleCount(true);
  };
  

  return (
    <div className="min-h-screen bg-gradient-to-br from-rose-100 via-orange-50 to-amber-100 flex justify-center items-center py-12 px-4">
      <div className="absolute inset-0 overflow-hidden z-0">
        {["🍣", "🍜", "🌮", "🍩", "🍕", "🍔", "🥗", "🥟", "🍝", "🍦", "🍇", "🍓", "🍍", "🍛", "🧁", "🥘"].map((emoji, idx) => (
          <span
            key={idx}
            className="floating-emoji"
            style={{
              top: `${Math.random() * 100}%`,
              left: `${Math.random() * 100}%`,
              animationDelay: `${Math.random() * 5}s`,
              animationDuration: `${5 + Math.random() * 5}s`,
              position: "absolute",
              fontSize: "2rem",
              opacity: 0.4,
              pointerEvents: "none",
              animation: "float 6s ease-in-out infinite"
            }}
          >
            {emoji}
          </span>
        ))}
      </div>

      <div className="max-w-3xl w-full shadow-xl rounded-3xl bg-white/70 backdrop-blur-md p-10 border border-[#f3e3d3] z-10 relative">
        <header className="text-center mb-8">
          <h1 className="text-4xl font-extrabold text-[#e76f51] mb-2">Big Backtivity</h1>
          <p className="text-gray-700 max-w-2xl mx-auto">
            <strong>Big Backtivity</strong> is a simple way for groups to figure out what to eat together.
            Everyone just shares what they’re craving—maybe ramen, vegan tacos, or “literally anything but mushrooms.”
            You can type it out or say it out loud, and the app gathers everyone’s preferences in one place.
            No more endless group chats or indecisive loops—just an easier way to agree on dinner.
          </p>
          <p className="text-gray-700 mt-4 max-w-2xl mx-auto italic">
            This project was born out of one universal truth: we were all really, really hungry. As a group
            of unapologetic foodies, we realized that deciding on dinner with friends shouldn’t be so complicated.
            So we built Big Backtivity to turn our collective cravings into ACTUALLY GOOD final decisions. No more
            “I don’t know, what do *you* want?", because we all know that's a lie.
          </p>
        </header>

        {!submittedPeopleCount ? (
          <form onSubmit={handlePeopleSubmit} className="flex flex-col items-center gap-4">
            <label className="text-xl font-medium">How many people are deciding?</label>
            <input
              type="number"
              min="1"
              value={numPeople || ""}
              onChange={e => setNumPeople(parseInt(e.target.value))}
              className="border p-2 rounded-md text-center"
            />
            <button type="submit" className="bg-[#e76f51] text-white px-4 py-2 rounded-xl shadow hover:bg-[#d4553d] transition-colors">
              Continue
            </button>
          </form>
        ) : (
          <form onSubmit={handleSubmit} className="mt-6 flex flex-col items-center justify-center gap-6">
 
[truncated — 1998 more characters]
```

### frontend/tailwind.config.js

```javascript
// tailwind.config.js
module.exports = {
  darkMode: 'class',
  content: [
    './index.html',
    './src/**/*.{js,ts,jsx,tsx}',   // your React code
    './src/**/*.{css}'              // any global CSS files with @apply
  ],
  theme: { extend: {} },
  plugins: [],
}

```

### frontend/vite.config.ts

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

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
  server: {
    proxy: {
      '/api':  'http://localhost:5000'
    }
  }
})

```

### frontend/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 + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### frontend/eslint.config.js

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

export default tseslint.config(
  { ignores: ['dist'] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
)

```

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

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

```

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