Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion restaurant-comparison-tool/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
VITE_TINYFISH_API_KEY=your_tinyfish_api_key_here
# TinyFish API key — get yours at https://agent.tinyfish.ai/api-keys
TINYFISH_API_KEY=your-tinyfish-api-key
3 changes: 3 additions & 0 deletions restaurant-comparison-tool/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
32 changes: 7 additions & 25 deletions restaurant-comparison-tool/.gitignore
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
.env

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.env.local
.next/
node_modules/
dist/
build/
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

.vercel
*.log
next-env.d.ts
225 changes: 144 additions & 81 deletions restaurant-comparison-tool/README.md
Original file line number Diff line number Diff line change
@@ -1,115 +1,178 @@
# SafeDine
**Live Demo: https://restaurant-comparison-tool.vercel.app**

**Live:** [https://restaurant-comparison-tool.vercel.app](https://restaurant-comparison-tool.vercel.app)
**Pre-visit restaurant safety intelligence — AI agents compare 2–5 restaurants simultaneously before you dine.**

SafeDine is a pre-visit restaurant safety intelligence tool that compares 2–5 restaurants before dining by analyzing Google Maps reviews, menu photos, and allergen signals. It uses the TinyFish API to dispatch parallel web agents — one per restaurant — that each navigate Google Maps, read 8–12 reviews, check menu images, and return a structured safety report with scores, allergen risks, and dietary suitability ratings.
Enter a city and up to 5 restaurant names, select your allergens and dietary preferences, and SafeDine dispatches one TinyFish browser agent per restaurant in parallel. Each agent navigates Google Maps, reads 8–12 reviews, checks menu photos for allergen signals, and returns a structured safety report. Results stream back as each agent completes.

## Demo
## Architecture

https://github.com/user-attachments/assets/c684dac5-5e89-43fe-9592-0665a31513f6


## TinyFish API Usage
```
┌─────────────────────────────────────────────────────────────┐
│ Browser (Client) │
│ │
│ SearchForm → AllergenSelector → ComparisonDashboard │
│ (live previews + results stream in) │
└──────────────────────────┬──────────────────────────────────┘
│ POST /api/scrape (one per restaurant, parallel)
│ (SSE — results stream as agents finish)
┌──────────────────────────▼──────────────────────────────────┐
│ Next.js API Route │
│ /api/scrape/route.ts │
│ │
│ TinyFish SDK — client.agent.stream() per restaurant │
│ TINYFISH_API_KEY stored server-side in .env.local │
│ │
│ SSE events: │
│ STREAMING_URL → live browser iframe per agent │
│ STEP → agent progress updates │
│ COMPLETE → structured safety JSON │
│ ERROR → failure message │
└──────────┬──────────────┬──────────────┬────────────────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Google │ │ Google │ │ Google │ ... (2–5)
│ Maps: │ │ Maps: │ │ Maps: │
│ Rest. A │ │ Rest. B │ │ Rest. C │
└────────────┘ └────────────┘ └────────────┘

No database. No cache. Pure in-memory — results fetched live every search.
```

The app calls the TinyFish SSE endpoint once per restaurant, in parallel. Each agent navigates Google Maps, samples reviews for safety signals, checks menu photos for allergen labeling, and returns a structured JSON report:
### TinyFish SDK event flow

```typescript
const response = await fetch("https://agent.tinyfish.ai/v1/automation/run-sse", {
method: "POST",
headers: {
"X-API-Key": import.meta.env.VITE_TINYFISH_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://www.google.com/maps",
goal: `You are a fast food-safety research agent. Investigate "${restaurantName}" in ${city}.
Stay ONLY on Google Maps — do NOT visit external websites.
```
client.agent.stream({ url, goal }, {
onStreamingUrl: (event) => forward iframe URL to client,
onProgress: (event) => forward step description to client,
onComplete: (event) => {
if (event.status === RunStatus.FAILED) → ERROR
else → event.result → structured safety JSON → COMPLETE → SSE
}
})

// for-await drains the stream as fallback
for await (const event of stream) {
if (event.type === EventType.COMPLETE) → handle result
}
```

STEP 1 — FIND THE RESTAURANT on Google Maps:
Search "${restaurantName} ${city}". Confirm the correct listing.
## What Each Agent Extracts

STEP 2 — SAMPLE REVIEWS (keep it fast):
Open the Reviews tab. Read 8–12 recent reviews. Prioritize mentions of:
- Food poisoning, allergic reactions, cross-contamination
- Hygiene, cleanliness, staff responsiveness
Focus on user allergens: ${allergenList}
Each agent navigates Google Maps for the restaurant and returns:

STEP 3 — CHECK MENU IMAGES (if available on Maps):
Look at the Menu tab or Photos section (3–4 images max).
- **Safety score** (0–100) based on review signals and allergen mentions
- **Allergen risks** — detected allergens flagged against your selections
- **Dietary suitability** — vegan, vegetarian, gluten-free, halal, kosher ratings
- **Review highlights** — safety-relevant quotes from real reviews
- **Menu photo analysis** — allergen labeling visible in menu images
- **Confidence level** — how much data the agent found

STEP 4 — RETURN RESULTS as JSON:
{ "restaurantName": "...", "overallSafetyScore": 75,
"allergenRisks": [...], "safetySignals": [...], ... }`,
}),
});
```
## Features

The response streams SSE events including a `streamingUrl` (live view of the agent navigating Google Maps) and a final `COMPLETE` event with the extracted safety data JSON.
- Compare **2–5 restaurants** simultaneously
- Select from **common allergens** (nuts, dairy, gluten, shellfish, etc.)
- Set **dietary preferences** (vegan, vegetarian, halal, etc.)
- Watch **live browser agent iframes** as each agent navigates Google Maps
- Results ranked by safety score — worst risks surfaced first

## How to Run
## Setup

### Prerequisites

- Node.js 18+
- A TinyFish API key ([get one here](https://agent.tinyfish.ai))

### Setup
- TinyFish API key

1. Install dependencies:
### Environment Variables

```bash
cd restaurant-comparison-tool
npm install
cp .env.example .env.local
```

2. Create a `.env` file with your TinyFish API key:
Then fill in:

```
VITE_TINYFISH_API_KEY=your_tinyfish_api_key_here
```env
# TinyFish (required) — https://agent.tinyfish.ai/api-keys
TINYFISH_API_KEY=your-tinyfish-api-key
```

3. Start the dev server:
### Install & Run

```bash
npm install
npm run dev
```

4. Open [http://localhost:5173](http://localhost:5173)
Open http://localhost:3000

## Architecture Diagram
## Project Structure

```
┌──────────────────────────────────────────────────────────────┐
│ User (Browser) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ React + Vite Frontend (Tailwind + shadcn + Framer) │ │
│ │ │ │
│ │ 1. Enter city + 2–5 restaurant names │ │
│ │ 2. Select allergens & dietary preferences │ │
│ │ 3. Click "Compare Restaurants" │ │
│ │ 4. Watch live browser previews as agents research │ │
│ │ 5. View ranked safety cards + detail panel │ │
│ └────────────────────┬───────────────────────────────────┘ │
└───────────────────────┼──────────────────────────────────────┘
│ POST /v1/automation/run-sse (x N restaurants, parallel)
┌──────────────────────────────────────────────────────────────┐
│ TinyFish API (agent.tinyfish.ai) │
│ │
│ Receives goal prompt + Google Maps URL per restaurant │
│ Spins up a web agent for each request │
│ │
│ SSE Stream Events: │
│ • streamingUrl → live browser preview (iframe) │
│ • STEP → agent progress updates │
│ • COMPLETE → structured safety JSON │
│ • ERROR → failure message │
└────────┬──────────────┬──────────────┬───────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Google │ │ Google │ │ Google │ ... (2–5 restaurants)
│ Maps: │ │ Maps: │ │ Maps: │
│ Rest. A │ │ Rest. B │ │ Rest. C │
└───────────┘ └───────────┘ └───────────┘
restaurant-comparison-tool/
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Main UI
│ │ ├── globals.css
│ │ └── api/
│ │ └── scrape/route.ts # POST /api/scrape — SSE + TinyFish per restaurant
│ ├── components/
│ │ ├── search/
│ │ │ ├── SearchForm.tsx # City + restaurant name inputs
│ │ │ ├── AllergenSelector.tsx # Allergen multi-select
│ │ │ ├── PreferenceSelector.tsx # Dietary preference selector
│ │ │ └── RestaurantInput.tsx # Individual restaurant input row
│ │ ├── live/
│ │ │ ├── LiveSearchPanel.tsx # Live agent status panel
│ │ │ ├── LiveBrowserPreview.tsx # Agent iframe grid
│ │ │ ├── RestaurantAgentCard.tsx # Per-agent status card
│ │ │ └── AgentStatusIndicator.tsx # Running/done indicator
│ │ ├── results/
│ │ │ ├── ComparisonDashboard.tsx # Ranked results layout
│ │ │ ├── RestaurantResultCard.tsx # Per-restaurant result card
│ │ │ ├── SafetyScoreRing.tsx # Score visualisation
│ │ │ ├── AllergenRiskPanel.tsx # Allergen risk breakdown
│ │ │ ├── AllergenRiskBadge.tsx # Individual allergen badge
│ │ │ ├── FitExplanation.tsx # Dietary fit explanation
│ │ │ ├── ConfidenceIndicator.tsx # Data confidence level
│ │ │ ├── ResultDetailPanel.tsx # Full detail drawer
│ │ │ ├── GoogleMapsLink.tsx # Link to Google Maps listing
│ │ │ └── AgentLoadingCard.tsx # Skeleton while agent runs
│ │ ├── layout/
│ │ │ ├── Header.tsx
│ │ │ └── Footer.tsx
│ │ └── ui/ # badge, button, card, dialog, input, etc.
│ ├── hooks/
│ │ └── useRestaurantSearch.ts # SSE client + state management
│ ├── context/
│ │ └── SearchContext.tsx # Search state context
│ └── lib/
│ ├── goal-builder.ts # Builds TinyFish goal prompt per restaurant
│ ├── score-calculator.ts # Safety score computation
│ ├── allergens.ts # Allergen definitions
│ ├── tinyfish-client.ts # SDK wrapper
│ ├── constants.ts # App-wide constants
│ └── utils.ts # Shared helpers
├── next.config.ts
└── package.json
```

## Constraint Checklist

| Constraint | Status |
|---|---|
| External database used? | NO (pure in-memory) |
| Cache layer used? | NO (all results fetched live) |
| Scraping parallel? | YES (one `client.agent.stream` per restaurant, all concurrent) |
| Live browser preview? | YES (`onStreamingUrl` → iframe per agent) |
| Allergen risk detection? | YES (review + menu photo analysis) |
| Results ranked? | YES (by safety score, worst risks first) |

## Tech Stack

- **Framework:** Next.js 15 (App Router), TypeScript, Tailwind CSS
- **UI Components:** shadcn/ui
- **Browser Agents:** TinyFish SDK (`client.agent.stream`)
- **Icons:** Lucide React
- **Deployment:** Vercel
2 changes: 1 addition & 1 deletion restaurant-comparison-tool/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
Expand Down
23 changes: 0 additions & 23 deletions restaurant-comparison-tool/eslint.config.js

This file was deleted.

16 changes: 0 additions & 16 deletions restaurant-comparison-tool/index.html

This file was deleted.

3 changes: 3 additions & 0 deletions restaurant-comparison-tool/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {};
export default nextConfig;
Loading