# Project export: WanderWise

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: WanderWise leverages OpenAI using MindsDB AI integration to curate travel itineraries based on your destination and date. Optimize with WanderWise, a smart and simple way to plan your next trip.
- Devpost: https://devpost.com/software/wanderwise-bwp7kz
- GitHub: https://github.com/winnie-zhu/calhacks-wanderwise
- Demo: https://www.figma.com/file/x21ERBzNIlYsMOxu6iCzyb/wonderwise?type=design&node-id=0%3A1&mode=design&t=ffIULGw2G4jG8gr8-1
- Team: 3 GitHub contributor(s) — maggiemah (15 commits), Winnie Zhu (13 commits), Rachel Lee (2 commits)

## Devpost submission (written by the team)

### Inspiration

A few weeks prior to this hackathon, our team was struggling to plan a trip to Japan. That's when we realized how nice it would be to have a tool that plans the trip for us! So, we decided to take on the challenge to build this ourselves.

### What it does

WanderWise uses an AI model to generate itineraries based on users' desired destination and timeframe. In addition to itinerary generation, WanderWise's simple and clean design makes it easy to visualize your itineraries the next time you plan to travel.

### How we built it

We used React to build the frontend and Express for the backend server. We utilized MindsDB to link our real-time data stored in CockroachDB to an OpenAI model API.

### Challenges we ran into

It was our first times using MindsDB and CockroachDB, so it took a while for us to get started and familiarize ourselves with the new technology. Also, integrating all different technologies caused unexpected issues.

### Accomplishments we're proud of

/

### What we learned

We were able to connect our MindsDB application to our CockroachDB database. With that, we were able to take advantage of the AI workflows that MindsDB offers. This is just one example the many new technologies we've learned to use while building this project.

### What's next

We have several goals for our app in the future: Persist itinerary data for different users so that users can look at their previously generated itineraries Improved UI for better UX (e.g. adjustable schedules, multimodal representation for landmarks) Adjusting queries to match users' budget as well as displaying the estimated costs Share/Save/Export itineraries Calendar integration for a better curated response

## README (from the GitHub repository)

# WanderWise


## Inspiration
A few weeks prior to this hackathon, our team was struggling to plan a trip to Japan. That's when we realized how nice it would be to have a tool that plans the trip for us! So, we decided to take on the challenge to build this ourselves. 

## What it does
WanderWise uses an AI model to generate itineraries based on users' desired destination and timeframe. In addition to itinerary generation, WanderWise's simple and clean design makes it easy to visualize your itineraries the next time you plan to travel.

## How we built it
We used React to build the frontend and Express for the backend server. We utilized MindsDB to link our real-time data stored in CockroachDB to an OpenAI model API.

## Challenges we ran into
It was our first times using MindsDB and CockroachDB, so it took a while for us to get started and familiarize ourselves with the new technology. Also, integrating all different technologies caused unexpected issues.

## Accomplishments that we're proud of / What we learned
We were able to connect our MindsDB application to our CockroachDB database. With that, we were able to take advantage of the AI workflows that MindsDB offers. This is just one example the many new technologies we've learned to use while building this project.

## What's next for WanderWise
We have several goals for our app in the future:
- Persist itinerary data for different users so that users can look at their previously generated itineraries
- Improved UI for better UX (e.g. adjustable schedules, multimodal representation for landmarks)
- Adjusting queries to match users' budget as well as displaying the estimated costs
- Share/Save/Export itineraries
- Calendar integration for a better curated response



## Detected evidence (automated analysis)

Indexed codebase: 21 recognized source files, 23 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
- React (technology) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code
- SQL (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (26 of 26)

```
backend/.gitignore
backend/models.js
backend/package.json
backend/server.js
frontend/.eslintrc.cjs
frontend/.gitignore
frontend/index.html
frontend/package.json
frontend/src/App.css
frontend/src/App.jsx
frontend/src/Components/DayCard.jsx
frontend/src/Components/Loading.jsx
frontend/src/Components/Login.jsx
frontend/src/Components/NavigationBar.css
frontend/src/Components/NavigationBar.jsx
frontend/src/Components/TextBlock.css
frontend/src/Components/TextBlock.jsx
frontend/src/data.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/pages/Home.jsx
frontend/src/pages/Planning.jsx
frontend/src/pages/Results.css
frontend/src/pages/Results.jsx
frontend/vite.config.js
README.md
```

### Dependencies

- backend/package.json: axios@^1.6.0, body-parser@^1.20.2, cors@^2.8.5, dotenv@^16.3.1, express@^4.18.2, express-async-handler@^1.2.0, mindsdb-js-sdk@^2.2.2, nodemon@^3.0.1, pactum@^3.5.1, sequelize@^6.33.0, sequelize-cockroachdb@^6.0.5
- frontend/package.json: @react-oauth/google@^0.11.1, @types/react@^18.2.15, @types/react-dom@^18.2.7, @vitejs/plugin-react-swc@^3.3.2, bootstrap@^5.3.2, eslint@^8.45.0, eslint-plugin-react@^7.32.2, eslint-plugin-react-hooks@^4.6.0, eslint-plugin-react-refresh@^0.4.3, jwt-decode@^4.0.0, react@^18.2.0, react-bootstrap@^2.9.1, react-dom@^18.2.0, react-router-dom@^6.17.0, vite@^4.4.5

### Recent commits (newest first)

- improve query answer parsing
- minor fix to query answer parsing
- Merge branch 'main' of https://github.com/winnie-zhu/calhacks-wanderwise
- change title and icon
- reademe
- Merge branch 'main' of https://github.com/winnie-zhu/calhacks-wanderwise
- update google signed in bar
- yaya!
- animated button
- it kinda works????
- gah
- Merge pull request #3 from winnie-zhu/result-planning
- merge
- Merge pull request #2 from winnie-zhu/thanks-maggie
- Merge branch 'thanks-maggie' of https://github.com/winnie-zhu/calhacks-wanderwise into thanks-maggie
- gahhh
- gahhh
- move google sign in to navbar
- add google sign in
- what i have so far

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

### backend/package.json

```
{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "dev": "nodemon server.js",
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.6.0",
    "body-parser": "^1.20.2",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "express-async-handler": "^1.2.0",
    "mindsdb-js-sdk": "^2.2.2",
    "nodemon": "^3.0.1",
    "pactum": "^3.5.1",
    "sequelize": "^6.33.0",
    "sequelize-cockroachdb": "^6.0.5"
  }
}

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@react-oauth/google": "^0.11.1",
    "bootstrap": "^5.3.2",
    "jwt-decode": "^4.0.0",
    "react": "^18.2.0",
    "react-bootstrap": "^2.9.1",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.17.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@vitejs/plugin-react-swc": "^3.3.2",
    "eslint": "^8.45.0",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "vite": "^4.4.5"
  }
}

```

### backend/server.js

```javascript
const express = require("express");
const bodyParser = require("body-parser");
const dotenv = require("dotenv");
const Sequelize = require("sequelize-cockroachdb");
const cors = require("cors");

const MindsDB = require("mindsdb-js-sdk").default;

dotenv.config({ path: ".env" });

const connectToMindsDB = async () => {
  try {
    await MindsDB.connect({
      user: process.env.MINDSDB_USER,
      password: process.env.MINDSDB_PASS,
    });
  } catch (error) {
    console.log(`Error: ${error}`);
  }
};

// Connect to CockroachDB through Sequelize.
const sequelize = new Sequelize({
  username: process.env.COCKROACHLABS_USER,
  password: process.env.COCKROACHLABS_PASS,
  host: process.env.COCKROACHLABS_HOST,
  port: process.env.COCKROACHLABS_PORT,
  database: process.env.COCKROACHLABS_DB,
  logging: false,
  dialect: "postgres",
  dialectOptions: {
    ssl: {
      // ca: process.env.COCKROACHLABS_CERT,
    },
  },
});

// defining the model
const Trip = sequelize.define("trip", {
  trip_id: {
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV4,
    allowNull: false,
    primaryKey: true,
  },
  numDays: {
    type: Sequelize.INTEGER,
  },
  location: {
    type: Sequelize.STRING,
  },
  email: {
    type: Sequelize.STRING,
  },
});

const Day = sequelize.define("day", {
  trip_id: {
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV4,
    allowNull: false,
    primaryKey: true,
  },
  dayNum: {
    type: Sequelize.INTEGER,
    primaryKey: true,
  },
});

const Event = sequelize.define("event", {
  trip_id: {
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV4,
    allowNull: false,
    primaryKey: true,
  },
  dayNum: {
    type: Sequelize.INTEGER,
    primaryKey: true,
  },
  description: {
    type: Sequelize.STRING,
  },
  category: {
    type: Sequelize.STRING,
  },
});

const convertAnswer = (itinerary) => {
  const itineraryLines = itinerary.answer.split("\n");
  const itineraryData = [];
  let currentDay = null;

  for (const line of itineraryLines) {
    if (line.startsWith("Day ")) {
      currentDay = {
        day: parseInt(line.replace(/\D/g, ""), 10),
        activities: [],
        foodRecommendations: [],
      };
      itineraryData.push(currentDay);
    } else if (line.toUpperCase().startsWith("ACTIVITIES:")) {
      currentDay.activities = [];
    } else if (line.toUpperCase().startsWith("FOOD RECOMMENDATIONS:")) {
      currentDay.foodRecommendations = [];
    } else if (line.trim() !== "") {
      const textWithoutNumbering = line.replace(/^Activity \d+: /, '').replace(/^\d+\.\s*/, ""); // Remove numbering
      if (currentDay.activities.length < 4) {
        currentDay.activities.push(textWithoutNumbering.trim());
      } else {
        currentDay.foodRecommendations.push(textWithoutNumbering.trim());
      }
    }
  }

  return itineraryData;
};

const getItinerary = async (data) => {
  const query = `SELECT question, answer
    FROM project_itinerary.openai
    WHERE question = 'Create a ${data.numDays}-day itinerary for ${data.location}
        separated by day with 4 activities and 3 food recommendations.'
    USING max_tokens=2000;
    `;
  try {
    const queryResult = await MindsDB.SQL.runQuery(query);
    if (queryResult.rows.length > 0) {
      const matchingUserRow = queryResult.rows[0];
      return convertAnswer(matchingUserRow);
    }
  } catch (error) {}
  return query;
};

const app = express();

app.use(bodyParser.json());
app.use(cors());

app.get("/", function (req, res) {
  return res.json("Hello world!");
});

app.get("/itinerary", async function (req, res) {
  try {
    const events = await Event.findAll();
    // console.log("events:", events);
    const separatedData = events.reduce((result, item) => {
      const dayNum = item.dayNum;

      if (!result[dayNum]) {
        result[dayNum] = [];
      }

      result[dayNum].push(item);
      return result;
    }, {});

    res.json(separatedData);
  } catch (error) {
    console.error("Error:", error);
    res.status(500).json({ error: "Internal Server Error" });
  }
});



app.post("/itinerary", async function (req, res) {
  let text = req.body;
  console.log("req body:", text);
  await connectToMindsDB();
  console.log("connected to minds");
  let itinerary = await getItinerary(text);
  console.log("itinerary:", itinerary);
  await sendTrip(text);
  await sendDayAndEvents(itinerary);
  res.json(itinerary);
});

const sendTrip = async (body) => {
  try {
    await Trip.destroy({ where: {} });
    const cypher = await Trip.create({
      // trip_id: body.trip_id,
      numDays: body.numDays,
      location: body.location,
      email: null,
    });
    console.log("trip:", cypher);
  } catch (error) {
    console.log(error);
  }
};

const sendDayAndEvents = async (data) => {
  try {
    await Day.destroy({ where: {} });
    await Event.destroy({ where: {} });
    for (i = 0; i < data.length; i++) {
      const day6 = await Day.create({
        // trip_id: data.trip_id,
        dayNum: data[i].day,
      });
      // console.log("day:", day6);

      for (j = 0; j < data[i].activities.length; j++) {
        const event = await Event.create({
          // trip_id: data.trip_id,
          dayNum: data[i].day,
          description: data[i].activities[j],
          category: "activity",
        });
        // console.log("event:", event);
      }

      for (k = 0; k < data[i].foodRecommendations.length; k++) {
        const event = await Event.create({
          // trip_id: data.trip_id,
          dayNum: data[i].day,
          description: data[i].foodRecommendations[k],
          category: "food",
        });
        // console.log("event:", event);
      }
    }
  } catch (error) {
    console.log(error);
  }
};

// app.post("/test", async function (req, res) {
//   try {
//     await Day.sync({ force: true });
//     console.log("table created????");
//   } catch (error) {
//     console.log(error);
//   }
// });

// Run the API
const port = 9000;
app.listen(port, () =
[truncated — 75 more characters]
```

### frontend/src/main.jsx

```javascript
import React from "react";
import ReactDOM from "react-dom/client";
import { GoogleOAuthProvider } from "@react-oauth/google";
import App from "./App.jsx";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <GoogleOAuthProvider clientId="307900395724-6rmst9k49dakpbu4jclq5u85njtn9snd.apps.googleusercontent.com">
      <App />
    </GoogleOAuthProvider>
  </React.StrictMode>
);

```

### frontend/src/App.jsx

```javascript
import "bootstrap/dist/css/bootstrap.min.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

import "./App.css";
import Planning from "./pages/Planning";
import Home from "./pages/Home";
import Results from "./pages/Results";

function App() {
  const router = createBrowserRouter([
    {
      path: "/",
      element: <Home />,
    },
    {
      path: "/planning",
      element: <Planning />,
    },
    {
      path: "/results",
      element: <Results />,
    },
  ]);

  return (
    <>
      <RouterProvider router={router} />
    </>
  );
}

export default App;

```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/assets/planes.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>WanderWise</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### frontend/src/index.css

```css
:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
}


button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  /* background-color: #1a1a1a; */
  cursor: pointer;
  transition: border-color 0.25s;
}

/* button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}  */
/*
@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
} */

```

### frontend/src/App.css

```css
body {
  min-height: 100vh;
}

.home-content {
  display: flex;
  justify-content: flex-start;
  margin-left: 32px;
  height: 70vh;
}

.home-content-item {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 16px;
}


.home-text > h1 {
  max-width: 653px;
  color: #000;
  font-size: 64px;
  font-style: normal;
  font-weight: 500;
  line-height: normal;
}

.home-text > h2 {
  margin-top: 12px;
  max-width: 653px;
  color: #000;
  font-size: 44px;
  font-style: normal;
  font-weight: 400;
  line-height: normal;
}

.form-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  /* margin-top: 32px;
  margin-bottom: 32px; */
  width: 200px;
}

.btn-hover {
  background-color: transparent;
  border: 2px solid rgb(199, 222, 230) !important;
  border-radius: 40px;
  width: 300px;
  height: 80px;
  font-size: 20px;
  -webkit-transition: ease-out .5s;
  -moz-transition: ease-out .5s;
  transition: ease-out .7s;
}

.btn-hover:hover {
  background-color: rgb(199, 222, 230);
  border: 2px solid rgb(199, 222, 230) !important;
  box-shadow: inset 400px 0 0 0 rgb(199, 222, 230);
}

#profile-pic {
  width: 40px;
  height: 40px;
  border-radius: 50%;
}
```

### frontend/src/data.js

```javascript
export const data =
[
  {
    day: 1,
    activities: [
      'Visit the iconic Tokyo Tower and enjoy the panoramic view of the city.',
      'Explore the bustling streets of Shibuya and experience the famous Shibuya Crossing.',
      'Take a stroll through the beautiful gardens of the Imperial Palace.',
      'Visit the Meiji Shrine, one of the most important Shinto shrines in Tokyo.'
    ],
    foodRecommendations: [
      'Try some delicious sushi at Tsukiji Fish Market.',
      'Have a bowl of ramen at Ichiran, a popular ramen chain in Japan.',
      'Indulge in some traditional Japanese sweets at a local confectionery shop.'
    ]
  },
  {
    day: 2,
    activities: [
      'Take a day trip to the historic city of Kyoto and visit the famous Kiyomizu-dera Temple.',
      'Explore the traditional streets of Gion and spot geishas.',
      'Visit the Fushimi Inari Shrine and hike through the thousands of torii gates.',
      'Relax and unwind at a traditional onsen (hot spring) in Arashiyama.'
    ],
    foodRecommendations: [
      'Try some delicious kaiseki (multi-course) meal at a traditional ryokan (inn).',
      'Have some matcha (green tea) flavored desserts at a local tea house.',
      'Enjoy a bowl of udon noodles at a local restaurant.'
    ]
  },
  {
    day: 3,
    activities: [
      "Visit the Hiroshima Peace Memorial Park and Museum to learn about the city's history.",
      'Take a ferry to Miyajima Island and see the famous floating torii gate.',
      'Explore the Itsukushima Shrine and its beautiful surroundings.',
      'Take a walk through the Hiroshima Castle and its gardens.'
    ],
    foodRecommendations: [
      'Try some okonomiyaki, a savory pancake, at a local restaurant.',
      'Have some oysters, a specialty of Hiroshima, at a seafood market.',
      'Enjoy some sake (Japanese rice wine) at a local brewery.'
    ]
  },
  {
    day: 4,
    activities: [
      'Take a day trip to Nara and visit the Todaiji Temple, home to the Great Buddha.',
      'Feed the friendly deer at Nara Park.',
      'Visit the Kasuga Taisha Shrine and its beautiful lanterns.',
      'Explore the traditional streets of Naramachi and try on a kimono.'
    ],
    foodRecommendations: [
      'Have some kakinoha-zushi, a type of sushi wrapped in persimmon leaves.',
      'Try some mochi (rice cakes) at a local shop.',
      'Enjoy a bowl of soba noodles at a traditional restaurant.'
    ]
  },
  {
    day: 5,
    activities: [
      'Take a bullet train to Hakone and enjoy the scenic views of Mount Fuji.',
      'Visit the Hakone Open-Air Museum and see the impressive collection of sculptures.',
      'Take a boat ride on Lake Ashi and see the iconic torii gate.',
      'Relax in a traditional onsen (hot spring) and enjoy the beautiful natural surroundings.'
    ],
    foodRecommendations: [
      'Have a traditional kaiseki meal at a ryokan with a view of Mount Fuji.',
      'Try some black eggs, a local specialty cooked in the hot springs.',
      'Enjoy some yuba (tofu skin) dishes at a local restaurant.'
    ]
  },
  {
    day: 6,
    activities: [
      'Visit the bustling city of Osaka and explore the vibrant Dotonbori area.',
      'Take a walk through the Osaka Castle and its beautiful gardens.',
      'Visit the Umeda Sky Building and enjoy the panoramic view of the city.',
      'Experience the lively atmosphere of a traditional izakaya (pub).'
    ],
    foodRecommendations: [
      'Try some takoyaki (octopus balls) at a street food stall.',
      'Have a bowl of kushikatsu (deep-fried skewers) at a local restaurant.',
      'Enjoy some okonomiyaki (savory pancake) at a traditional restaurant.'
    ]
  },
  {
    day: 7,
    activities: [
      'Take a day trip to the beautiful island of Miyajima and visit the Itsukushima Shrine.',
      'Hike to the top of Mount Misen for a breathtaking view of the island.',
      'Explore the traditional streets of Omotesando and try some local snacks.',
      'Take a ferry back to Hiroshima and visit the Shukkei-en Garden.'
    ],
    foodRecommendations: [
      'Have some momiji manju, a maple leaf-shaped cake filled with red bean paste.',
      'Try some anago (conger eel) dishes at a local restaurant.',
      'Enjoy some Hiroshima-style okonomiyaki at a local eatery.'
    ]
  }
]

```

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