Inspiration
Friends often spend more time agreeing on what to do than actually doing it. Everyone has different tastes, recommendations tend to favor the loudest voice, and a group chat makes it hard to see where people's tastes overlap. We wanted a lightweight way for a group to say what they like and get a plan that shows why each option fits, with no accounts and no sign-up.
What it does
Taste Bridge turns the confirmed favorites of 3 to 6 friends into group plans using Qloo's taste graph.
- A host creates a room, picks categories, and adds a city or area for dining.
- Friends join from a shared link with a nickname. No account is needed.
- Members chat in the room, search Qloo, and confirm 2 to 4 favorites each. Nothing is ever saved automatically.
- Once at least three members are ready, the host generates a plan.
- Each pick shows Qloo's overlap evidence and the AI explanation separately. Members vote, and the host can ask for new picks. Earlier picks are never repeated.
A Suggest from chat button reads only the requesting member's recent messages and proposes up to three Qloo entities, which the member can accept or ignore. A Try a demo mode seeds two labeled demo profiles from live Qloo searches so a judge can see a full plan in one click.
How we built it
- TypeScript monorepo with npm workspaces (
shared,server,web) - Fastify API that also serves the built React + Vite app from one Node process
- SQLite (
better-sqlite3, WAL mode) for rooms, members, favorites, chat, plans and votes - Qloo entity search over the hackathon API, and recommendations through the Qloo MCP harness running as a child process of the API
- Gemini (optional) for chat keyword extraction and per-friend explanations, with deterministic fallback text when it is unavailable or returns invalid output
- Docker image deployed on Fly.io as a single machine with a persistent volume
How picks are ranked
Each candidate gets a group overlap score from its affinity values across members:
$$\text{score} = \operatorname{round}\Big(100 \times \big(0.5\,\bar{a} + 0.5\,a_{\min}\big)\Big)$$
where ( \bar{a} ) is the mean affinity and ( a_{\min} ) is the lowest affinity in the group. Using the minimum as well as the mean stops a pick from winning just because three people love it and one person dislikes it. When a member has both chat-derived and other favorites, chat-derived signals get 70% of the combined affinity and the rest get 30%.
Challenges we ran into
- Keeping evidence honest. Qloo's score and the AI's explanation are labeled separately in the UI, so the AI's wording is never presented as data.
- Privacy without accounts. Rooms use anonymous, room-scoped cookies (the database stores only a token hash). Nicknames are never sent to Qloo or Gemini, browser coordinates are never stored, and rooms are deleted after 72 hours by default.
- Reliable plans when services fail. Gemini can be slow or return invalid output, so every plan has deterministic fallback reasons.
- Rate limits. API requests, Qloo searches and plan regeneration are all limited so one room cannot use up the API budget.
- Deployment. SQLite and the local Qloo MCP process mean the app must run as one instance with persistent storage, so serverless hosting was not an option. We deployed on Fly.io with a mounted volume.
What we learned
Grounding an AI explanation in real taste data makes recommendations more trustworthy than letting a model guess. Designing fallbacks early, and labeling clearly what comes from Qloo and what comes from AI, kept the app useful even when an outside service failed.
What's next
- A persistent database option so the app can run on more than one instance
- Better handling when Qloo is rate limited or unavailable
- More UI tests covering room creation, favorites, the demo, and plan regeneration
Built With
- better-sqlite3
- docker
- fastify
- fly.io
- gemini
- node.js
- openstreetmap
- qloo
- qloo-mcp
- react
- sqlite
- typescript
- vite
- vitest
Log in or sign up for Devpost to join the conversation.