ai-travel-companion-platform, the AI trip planner that refuses to trust its own output

Gemini drafts the itinerary, OpenStreetMap grounds it in reality, and the app lets you regenerate one day or one activity without blowing up the whole trip.

9 min read • View on GitHub • More from Gaurav-Mishra-2005

A wide editorial scene shows a traveler standing between an AI printer and a paper map checkpoint. A generated itinerary flows out of a machine on one side, then passes through a magnifying glass, stamped place markers, and a map atlas on the other side. The image explains that the app verifies AI trip plans against real geography before the user sees them.
The project’s core move is simple: let Gemini draft fast, then force the draft through a geocoding checkpoint before it becomes a trip.
Key Takeaways

AI travel planning is easy to demo and hard to trust. This repo treats that as the main problem, not a side effect. Gemini writes the first draft, but OpenStreetMap and Nominatim decide whether the draft deserves to survive.

The app does not stop at a pretty itinerary

Most travel bots are good at producing something that reads like confidence. They are worse at making sure the places exist, the order makes sense, and one bad activity does not force a full restart. This project is built around that gap.

The result is less a chatbot than a correction engine. It accepts that the model is fast, useful, and occasionally messy, then spends the engineering effort where users feel the difference: grounding, repair, and selective edits.

The whole product is a trust pipeline: generate, clean, verify, store, then edit locally instead of rerolling the whole trip.

The real product is a trust pipeline

The backend flow is straightforward once you strip away the AI mystique. User preferences go into a Gemini prompt, Gemini returns itinerary JSON, the service strips markdown wrappers, parses the object, enriches each location through Nominatim, and persists the result in MongoDB. The important detail is not that each step exists. It is that the code expects the model to be imperfect.

const cleaned = rawResponse
  .replace(/^```json\s*/i, '')
  .replace(/```$/i, '')
  .trim();

const itinerary = JSON.parse(cleaned);
const enriched = await Promise.all(
  itinerary.days.flatMap(day =>
    day.activities.map(activity => enrichLocation(activity.location))
  )
);

That cleaning step is a tell. The app does not pretend the LLM will always emit pristine machine-ready output. It defends against markdown fences, parse failures, and brittle place strings, then keeps moving.

Why the app feels more editable than most AI planners

The product choice that stands out is granularity. Users can regenerate an entire day or just one activity. That sounds small, but it changes the whole mental model. The user is not starting over. They are editing.

A close-up shows a single itinerary card on a desk with one activity line highlighted. A replacement card slides into that exact slot while the surrounding day blocks stay locked in place. The image explains that the app can regenerate one activity without disturbing the rest of the trip.
Atomic regeneration is the difference between a useful editor and a restart button.
ApproachStrengthWeaknessWhat this repo does differently
Generic LLM travel chatFast suggestions and conversational toneNo grounding, no durable structureUses the model for draft generation, then verifies the output against real geography
Traditional itinerary plannerGood organization and predictable layoutWeak personalization and poor iterative editingLets the user regenerate only the broken part of the trip
This repoGenerates, validates, and repairs in one loopRequires more backend logicTreats AI as a draft engine, not the source of truth

That difference matters because travel intent changes locally. A bad lunch stop should not destroy the rest of the day. This app behaves like an editor because that is the only way AI output stays usable after the first draft.

The backend chooses flexibility over schema purity

The itinerary lives in a flexible Mongoose field rather than a rigid nested schema. That is a deliberate tradeoff. AI-shaped data changes too much for a hard schema to feel comfortable, but once you accept flexibility, you have to tell MongoDB when deep nested edits happen.

trip.itinerary.days[dayIndex].activities[activityIndex] = updatedActivity;
trip.markModified('itinerary');
await trip.save();

That `markModified` call is the price of admission. It says the author chose practical AI integration over schema purity, then handled the consequences explicitly instead of hoping the ODM would infer them.

The performance trick hiding in plain sight

The geocoding step could easily become the slowest part of the pipeline if each place were resolved one by one. Instead, the code fans out lookups with `Promise.all`, then recombines them. That is a small implementation choice with an outsized effect on perceived speed.

const resolvedPlaces = await Promise.all(
  places.flatMap(place => place.names.map(name => enrichLocation(name)))
);

This is the kind of detail that makes an AI app feel sharp instead of sticky. The model may be the dramatic part, but concurrency is what keeps the user from staring at a spinner while the system checks dozens of locations.

Frontend polish matters because the backend is slow by nature

The frontend is doing more than rendering results. React 19, TanStack Query, and auth bootstrapping give the app a resilient shell around expensive operations. Query caching and loading states absorb some of the mess that comes with AI latency and network calls.

LayerWhat it handlesWhy it matters
React 19 + ViteFast rendering and component stateKeeps the interface lightweight while the backend works
TanStack QueryCaching and async server statePrevents every edit from feeling like a cold start
AuthContext bootstrapJWT validation on mountMakes the app feel ready before the user begins planning
Framer Motion and LeafletMotion and map interactionAdds clarity without turning the UI into a novelty

The frontend is not trying to hide latency completely. It is trying to make latency legible and manageable, which is the right goal for a tool that depends on external APIs and model calls.

What this project is really competing with

The nearest competitors are not just other AI trip planners. They are every workflow that leaves the user to clean up after the model. Generic chat tools can suggest destinations. Traditional planners can organize bookings. Few systems do both generation and grounding while preserving editability.

CategoryExamplesWhat they optimize forWhat they usually miss
AI travel chatGeneric LLM assistantsSpeed and conversational breadthVerified locations and stable structure
Travel organizersTripIt-style toolsEmail parsing and itinerary storagePersonalized draft generation
AI itinerary buildersInspirock, UtripPrebuilt trip creationSurgical repair of a single day or activity
This repoai-travel-companion-platformDraft, verify, and edit in one loopIt accepts that the first answer is not the final answer

That last line is the point. The repo is not trying to make AI magical. It is trying to make AI accountable.

Why this repo feels production-minded

The codebase reads like something built for real failure modes. There is retry logic for flaky AI responses, defensive parsing, explicit state updates, security middleware, and clean service boundaries. None of that is flashy. All of it matters.

This is the kind of repo that understands where AI systems fail in practice. Not in the demo. In the seams between generation, verification, storage, and repair.