Skip to content

Commit 259d20b

Browse files
KrishnaAgarwal7531krishna7531Simantak Dabhade
authored
Migration lego-hunter to Tinyfish SDK (#208)
Migrates Lego Restock Hunter from OpenAI-based URL generation to the TinyFish SDK, removing the @ai-sdk/openai and ai dependencies entirely. The original generate-urls route used GPT-4o-mini to hallucinate search URLs for 15 hardcoded retailers — results were often wrong or outdated. The new /api/discover-retailers runs two parallel TinyFish Search queries targeted at the specific LEGO set name, finding real current product pages across retailers without any LLM involvement. /api/search-lego replaces the raw Mino SSE fetch with client.agent.stream — typed EventType.STREAMING_URL, EventType.PROGRESS, EventType.COMPLETE + RunStatus.COMPLETED, with result content validation. Deal analysis is now built-in price sorting logic rather than a second GPT-4o-mini call — no LLM needed to determine which price is lower. --------- Co-authored-by: Krishna Agarwal <krishna.laptop3727@gmail.com> Co-authored-by: Simantak Dabhade <simantak@mac.local>
1 parent b98bc9a commit 259d20b

11 files changed

Lines changed: 793 additions & 1033 deletions

File tree

lego-hunter/.env.example

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# TinyFish API key for browser automation agents
2-
# Get yours at https://tinyfish.ai
1+
# TinyFish Web Agent API key (server-side only)
2+
# Get yours at: https://agent.tinyfish.ai/api-keys
33
TINYFISH_API_KEY=
4-
5-
# OpenAI API key for URL generation and deal analysis (uses gpt-4o-mini)
6-
# Get yours at https://platform.openai.com/api-keys
7-
OPENAI_API_KEY=

lego-hunter/.gitignore

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,22 @@
1-
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
1+
# Dependencies
2+
node_modules/
23

3-
# dependencies
4-
/node_modules
5-
/.pnp
6-
.pnp.*
7-
.yarn/*
8-
!.yarn/patches
9-
!.yarn/plugins
10-
!.yarn/releases
11-
!.yarn/versions
12-
13-
# testing
14-
/coverage
15-
16-
# next.js
17-
/.next/
18-
/out/
19-
20-
# production
21-
/build
22-
23-
# misc
24-
.DS_Store
25-
*.pem
4+
# Next.js
5+
.next/
6+
out/
7+
next-env.d.ts
268

27-
# debug
28-
npm-debug.log*
29-
yarn-debug.log*
30-
yarn-error.log*
31-
.pnpm-debug.log*
9+
# Environment files
10+
.env
11+
.env.local
12+
.env.*.local
3213

33-
# env files
34-
.env*
35-
!.env.example
14+
# Build outputs
15+
*.tsbuildinfo
3616

37-
# vercel
17+
# Vercel
3818
.vercel
3919

40-
# typescript
41-
*.tsbuildinfo
42-
next-env.d.ts
20+
# OS
21+
.DS_Store
22+
Thumbs.db

lego-hunter/README.md

Lines changed: 121 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,152 @@
11
# Lego Restock Hunter
2+
**Live Demo:** _add URL after deploy_
23

3-
Search 15+ retailers simultaneously to find sold-out Lego sets back in stock. Uses TinyFish browser agents for parallel scraping and OpenAI for deal analysis.
4+
**Find sold-out LEGO sets across multiple retailers simultaneously — TinyFish Search discovers real product pages, then parallel browser agents check stock and price in real time.**
45

5-
**Live Demo:** https://cookbook-lego-hunter.vercel.app/
6+
Enter a LEGO set name or number and the app runs two parallel TinyFish Search queries to find real retailer product pages for that specific set. One browser agent fires per retailer simultaneously — each checking stock availability, extracting price and shipping, and streaming results back as it finishes.
67

7-
![Lego Hunter Demo](./demo-screenshot.jpg)
8+
## Architecture
89

9-
## What This Project Is
10+
```
11+
┌─────────────────────────────────────────────────────────────┐
12+
│ Browser (Client) │
13+
│ │
14+
│ Search input + budget → RetailerStatusCard grid │
15+
│ Best deal banner → Results table │
16+
│ (results + iframes stream in as agents finish) │
17+
└──────────────────────────┬──────────────────────────────────┘
18+
19+
┌────────┴────────┐
20+
▼ ▼
21+
POST /api/discover-retailers POST /api/search-lego
22+
│ │
23+
▼ ▼
24+
┌─────────────────────┐ ┌──────────────────────────────────┐
25+
│ TinyFish Search API │ │ TinyFish SDK │
26+
│ │ │ │
27+
│ Two parallel queries│ │ client.agent.stream({ url, goal })│
28+
│ │ │ │
29+
│ 1. Targeted at known│ │ EventType.STREAMING_URL │
30+
│ LEGO retailers │ │ → live iframe per agent │
31+
│ (lego.com, │ │ EventType.PROGRESS │
32+
│ amazon, target, │ │ → step updates │
33+
│ bricklink, etc.) │ │ EventType.COMPLETE │
34+
│ │ │ + RunStatus.COMPLETED │
35+
│ 2. Broader search │ │ → stock/price JSON → SSE │
36+
│ for any retailer │ │ │
37+
│ carrying the set │ │ Promise.allSettled (all parallel) │
38+
│ │ │ │
39+
│ Aggregators filtered│ │ Built-in deal analysis │
40+
│ Deduped by domain │ │ (no LLM needed) │
41+
└─────────────────────┘ └──────────────────────────────────┘
42+
43+
No database. No cache. No OpenAI. Pure in-memory — results fetched live.
44+
```
1045

11-
A cookbook example showing how to use the [TinyFish API](https://tinyfish.ai) to deploy parallel browser automation agents. Given a Lego set name, the app scrapes 15 retailer websites at the same time, extracts stock and pricing data, and recommends the best deal.
46+
### TinyFish SDK event flow
1247

13-
## How It Works
48+
```
49+
client.agent.stream({ url, goal })
50+
51+
├── EventType.STREAMING_URL → live iframe URL forwarded to client
52+
├── EventType.PROGRESS → step description forwarded to client
53+
└── EventType.COMPLETE
54+
└── RunStatus.COMPLETED
55+
// COMPLETED only means the browser ran without crashing
56+
// — always validate result content, not just the status
57+
→ parse event.result → { inStock, price, shipping, productUrl }
58+
→ retailer_stock_found (if inStock) + retailer_complete → SSE
59+
```
1460

15-
1. **User enters a Lego set name and budget** -- e.g. "75192 Millennium Falcon", $900
16-
2. **OpenAI generates retailer search URLs** -- AI creates direct search URLs for 15 retailers (Amazon, Walmart, LEGO.com, Target, BrickLink, etc.)
17-
3. **TinyFish agents scrape each retailer in parallel** -- 15 browser agents launch simultaneously, each navigating a retailer site and extracting stock status, price, currency, and shipping info as structured JSON
18-
4. **OpenAI analyzes the best deal** -- all results are passed to OpenAI, which recommends the best retailer based on price, shipping, and reliability
61+
## What Each Agent Extracts
1962

20-
## TinyFish API Usage
63+
Each agent navigates the retailer's product or search page and returns:
2164

22-
The core integration lives in `app/api/search-lego/route.ts`. Each retailer gets its own TinyFish agent via the SSE endpoint:
65+
- **In stock** — true/false
66+
- **Price** — numeric, in local currency
67+
- **Shipping** — free / cost / check website
68+
- **Product URL** — direct link to the listing
2369

24-
```typescript
25-
const response = await fetch('https://agent.tinyfish.ai/v1/automation/run-sse', {
26-
method: 'POST',
27-
headers: {
28-
'X-API-Key': TINYFISH_API_KEY,
29-
'Content-Type': 'application/json'
30-
},
31-
body: JSON.stringify({
32-
url: retailer.url,
33-
goal: 'Search for "75192 Millennium Falcon" Lego set. Extract inStock, price, currency, shipping, and productUrl as JSON.',
34-
browser_profile: 'lite'
35-
})
36-
})
70+
## Search Flow
3771

38-
// Stream SSE events for progress and results
39-
const reader = response.body.getReader()
40-
// ... parse SSE events with streaming_url, purpose, status, result_json
41-
```
72+
1. User enters LEGO set name/number and optional max budget
73+
2. `/api/discover-retailers` runs two parallel TinyFish Search queries to find real product pages
74+
3. All discovered retailer URLs fire browser agents simultaneously via `Promise.allSettled`
75+
4. Each agent navigates the page, extracts stock and price data
76+
5. `EventType.STREAMING_URL` → live browser iframe shown in the retailer card
77+
6. `EventType.COMPLETE` + `RunStatus.COMPLETED` → parse result → stream to client
78+
7. Results appear as each retailer finishes — no waiting for the slowest one
79+
8. Best deal calculated in-memory (lowest price among in-stock results)
4280

43-
The SSE stream provides:
44-
- `streaming_url` -- live browser preview URL
45-
- `purpose` -- progress updates describing what the agent is doing
46-
- `status: "COMPLETED"` + `result_json` -- final extracted data
47-
- `status: "FAILED"` + `error` -- error information
81+
## Setup
4882

49-
## Tech Stack
83+
### Prerequisites
5084

51-
- **Next.js 16** (App Router) -- full-stack framework
52-
- **TinyFish API** -- parallel browser automation agents
53-
- **OpenAI GPT-4o Mini** (via Vercel AI SDK) -- URL generation and deal analysis
54-
- **Tailwind CSS 4** -- styling with custom Lego brick theme
55-
- **canvas-confetti** -- celebration effects when stock is found
56-
- **TypeScript** -- end-to-end type safety
85+
- Node.js 22.x
86+
- TinyFish API key
5787

58-
## Setup
88+
### Environment Variables
5989

60-
1. Install dependencies:
61-
```bash
62-
npm install
63-
```
64-
65-
2. Copy `.env.example` to `.env.local` and fill in your keys:
6690
```bash
6791
cp .env.example .env.local
6892
```
6993

70-
```
71-
TINYFISH_API_KEY=your-tinyfish-api-key
72-
OPENAI_API_KEY=your-openai-api-key
94+
Then fill in:
95+
96+
```env
97+
# TinyFish Web Agent API key (server-side only)
98+
# Get yours at: https://agent.tinyfish.ai/api-keys
99+
TINYFISH_API_KEY=
73100
```
74101

75-
3. Start the dev server:
102+
### Install & Run
103+
76104
```bash
105+
npm install
77106
npm run dev
78107
```
79108

80-
4. Open [http://localhost:3000](http://localhost:3000)
109+
Open http://localhost:3000
81110

82-
## Architecture
111+
## Project Structure
83112

84113
```
85-
User
86-
|
87-
v
88-
[Next.js Frontend] -- page.tsx (SSE consumer, Lego brick UI)
89-
|
90-
|-- POST /api/generate-urls
91-
| |
92-
| v
93-
| [GPT-4o Mini] -- generates 15 retailer search URLs
94-
|
95-
|-- POST /api/search-lego (SSE stream)
96-
|
97-
|-- [TinyFish Agent 1] --> Amazon
98-
|-- [TinyFish Agent 2] --> Walmart
99-
|-- [TinyFish Agent 3] --> LEGO.com
100-
|-- [TinyFish Agent 4] --> Target
101-
|-- ... --> (11 more retailers)
102-
|
103-
v
104-
[GPT-4o Mini] -- analyzes all results, picks best deal
105-
|
106-
v
107-
SSE: analysis_complete (best retailer + reasoning)
114+
lego-hunter/
115+
├── app/
116+
│ ├── layout.tsx
117+
│ ├── page.tsx # Main UI — search, agent grid, results
118+
│ ├── globals.css # LEGO brick design system
119+
│ └── api/
120+
│ ├── discover-retailers/route.ts # POST — TinyFish Search → retailer URLs
121+
│ └── search-lego/route.ts # POST — TinyFish Agent stream → SSE
122+
├── components/
123+
│ └── lego-confetti.tsx # Confetti on stock found
124+
├── lib/
125+
│ └── retailers.ts # Default retailer logos
126+
├── types/
127+
│ └── index.ts # TypeScript definitions
128+
├── .env.example
129+
├── .gitignore
130+
└── package.json
108131
```
132+
133+
## Constraint Checklist
134+
135+
| Constraint | Status |
136+
|---|---|
137+
| External database used? | NO (pure in-memory) |
138+
| OpenAI / LLM for URL generation? | NO (TinyFish Search finds real pages) |
139+
| LLM for deal analysis? | NO (built-in price sort logic) |
140+
| Scraping parallel? | YES (`Promise.allSettled` across all retailers) |
141+
| Live browser preview? | YES (`EventType.STREAMING_URL` → iframe per agent) |
142+
| Result validation? | YES (COMPLETED status ≠ goal achieved — content validated) |
143+
| Aggregator sites filtered? | YES (reddit, quora, youtube, etc. excluded) |
144+
145+
## Tech Stack
146+
147+
- **Framework:** Next.js 16 (App Router), TypeScript, Tailwind CSS 4
148+
- **Browser Agents:** TinyFish SDK (`client.agent.stream`)
149+
- **URL Discovery:** TinyFish Search API (`client.search.query`)
150+
- **Confetti:** canvas-confetti
151+
- **Icons:** Lucide React
152+
- **Deployment:** Vercel
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { TinyFish } from "@tiny-fish/sdk";
3+
4+
export const runtime = "nodejs";
5+
6+
const AGGREGATORS = [
7+
"reddit", "quora", "youtube", "twitter", "facebook",
8+
"instagram", "pinterest", "trustpilot", "yelp",
9+
];
10+
11+
export async function POST(req: NextRequest) {
12+
const apiKey = process.env.TINYFISH_API_KEY;
13+
if (!apiKey) {
14+
return NextResponse.json({ error: "Missing TINYFISH_API_KEY" }, { status: 500 });
15+
}
16+
17+
const { legoSetName } = await req.json();
18+
if (!legoSetName) {
19+
return NextResponse.json({ error: "legoSetName is required" }, { status: 400 });
20+
}
21+
22+
try {
23+
const client = new TinyFish({ apiKey });
24+
25+
// Two parallel searches — one targeting known retailers, one broader
26+
const [res1, res2] = await Promise.all([
27+
client.search.query({
28+
query: `LEGO "${legoSetName}" buy in stock lego.com OR amazon.com OR target.com OR walmart.com OR bricklink.com OR smythstoys.com OR argos.co.uk OR johnlewis.com OR zavvi.com OR entertainmentearth.com`,
29+
}),
30+
client.search.query({
31+
query: `"${legoSetName}" LEGO set price stock retailer 2025`,
32+
}),
33+
]);
34+
35+
const allResults = [
36+
...(res1.results || []),
37+
...(res2.results || []),
38+
];
39+
40+
const seenDomains = new Set<string>();
41+
const retailers: { name: string; url: string }[] = [];
42+
43+
for (const r of allResults) {
44+
try {
45+
const urlObj = new URL(r.url);
46+
const domain = urlObj.hostname.replace("www.", "");
47+
const isAggregator = AGGREGATORS.some((agg) => domain.includes(agg));
48+
if (isAggregator || seenDomains.has(domain)) continue;
49+
seenDomains.add(domain);
50+
51+
// Derive clean retailer name from title
52+
const name = r.title?.split(/[-|]/)[0].trim() || domain.split(".")[0];
53+
retailers.push({ name, url: r.url });
54+
if (retailers.length >= 12) break;
55+
} catch {
56+
// skip malformed URLs
57+
}
58+
}
59+
60+
return NextResponse.json({ retailers });
61+
} catch (error) {
62+
return NextResponse.json(
63+
{ error: error instanceof Error ? error.message : "Discovery failed" },
64+
{ status: 500 }
65+
);
66+
}
67+
}

lego-hunter/app/api/generate-urls/route.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)