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
25 changes: 6 additions & 19 deletions scholarship-finder/.env.example
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
# Supabase project URL (e.g. https://abcdefgh.supabase.co)
# Found in: Supabase Dashboard → Settings → API → Project URL
VITE_SUPABASE_URL=https://your-project.supabase.co
# TinyFish Web Agent API key — used for parallel browser agents that scrape scholarship pages
# Get yours at: https://agent.tinyfish.ai/api-keys
TINYFISH_API_KEY=

# Supabase public/anon key (safe to expose in the browser)
# Found in: Supabase Dashboard → Settings → API → Project API keys → anon/public
VITE_SUPABASE_PUBLISHABLE_KEY=your_supabase_anon_key

# ---------------------------------------------------------------
# The following secrets must be set on the Supabase Edge Function
# (via `supabase secrets set KEY=value`), NOT in this .env file.
# ---------------------------------------------------------------

# TinyFish API key for web automation / browser agents
# Get one at: https://tinyfish.ai
# Note: the edge function reads this as TINYFISH_API_KEY
# TINYFISH_API_KEY=your_tinyfish_api_key

# Lovable AI Gateway key (used for LLM-based URL discovery)
# LOVABLE_API_KEY=your_lovable_api_key
# Google Gemini API key — used for LLM-powered URL discovery (gemini-2.0-flash)
# Get yours at: https://aistudio.google.com/apikey
GEMINI_API_KEY=
36 changes: 8 additions & 28 deletions scholarship-finder/.gitignore
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
# 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 files
.env*
!.env.example

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
node_modules/
.next/
.env
.env.local
.env*.local
next-env.d.ts
.vercel
*.tsbuildinfo
216 changes: 73 additions & 143 deletions scholarship-finder/README.md
Original file line number Diff line number Diff line change
@@ -1,166 +1,96 @@
# Scholarship Finder

**Live Link:** https://tinyfishscholarshipfinder.lovable.app/
A Next.js app that finds real scholarships in real time. Enter a scholarship type, university, and region — Gemini discovers relevant sources, then parallel TinyFish browser agents scrape each one and stream results back as they arrive.

**Demo Video:** https://drive.google.com/file/d/1GXZhJOjiVUP5XcGvTAvRGcYhTWoKXlsE/view?usp=sharing
## Demo

## What This Project Is

An AI-powered scholarship discovery engine that automatically finds, scrapes, and structures scholarship information from official websites worldwide. Instead of relying on outdated databases, the system pulls live data directly from source websites and returns it in a clean, comparable format.

Users search by scholarship type, university, or region. The system uses an LLM to identify relevant scholarship websites, then dispatches parallel TinyFish browser agents to scrape each site and extract structured scholarship data in real time.
Each agent card shows a live browser preview as it scrapes. Results appear as each agent completes rather than waiting for all to finish.

## How It Works

1. **User searches** -- Enters scholarship type (e.g., "need-based"), optionally a university and region.
2. **LLM finds URLs** -- An LLM (via Lovable AI Gateway) identifies 5-8 official scholarship websites matching the query.
3. **TinyFish agents scrape in parallel** -- Each URL is sent to the TinyFish web automation API. Browser agents visit each site, navigate pages, and extract structured scholarship data. All agents run concurrently.
4. **Results streamed back** -- The Supabase Edge Function streams SSE events to the frontend: agent status updates, live browser previews, and extracted scholarships as they arrive. The UI renders results progressively.

## TinyFish API Usage

The core integration lives in the Supabase Edge Function. Here is the key pattern from `supabase/functions/search-scholarships/index.ts`:

```typescript
// Launch a TinyFish browser agent against a scholarship website
const response = await fetch("https://agent.tinyfish.ai/v1/automation/run-sse", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": TINYFISH_API_KEY,
},
body: JSON.stringify({
url: site.url, // e.g. "https://sfs.mit.edu/undergraduate-students/"
goal: goal, // Prompt describing what scholarship data to extract
}),
});

// Process the SSE stream from TinyFish
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
const { done, value } = await reader.read();
if (done) break;

buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";

for (const line of lines) {
if (line.startsWith("data: ")) {
const data = JSON.parse(line.slice(6).trim());

// Live browser view URL (embed in an iframe)
if (data.type === "STREAMING_URL" && data.streamingUrl) {
console.log("Live view:", data.streamingUrl);
}

// Progress updates while the agent navigates
if (data.type === "PROGRESS" && data.purpose) {
console.log("Agent status:", data.purpose);
}

// Final structured scholarship data
if (data.type === "COMPLETE" && data.resultJson) {
console.log("Extracted scholarships:", data.resultJson);
}
}
}
}
```

## Tech Stack

- **Frontend:** React + TypeScript + Vite + Tailwind CSS + shadcn/ui
- **Backend:** Supabase Edge Functions (Deno)
- **AI:** Lovable AI Gateway (Gemini) for URL discovery
- **Web Automation:** TinyFish API for parallel browser scraping
- **Hosting:** Lovable

## Setup

### 1. Install dependencies

```bash
npm install
User submits search
/api/search — Gemini (gemini-2.0-flash) discovers 5-8 relevant scholarship URLs
Parallel TinyFish browser agents scrape each URL simultaneously
Results stream back to the UI as each agent completes
```

### 2. Set up environment variables
## Architecture

Copy the example env file and fill in your values:

```bash
cp .env.example .env
```

You need:
- `VITE_SUPABASE_URL` -- Your Supabase project URL
- `VITE_SUPABASE_PUBLISHABLE_KEY` -- Your Supabase anon/public key

### 3. Set Supabase Edge Function secrets

```bash
supabase secrets set TINYFISH_API_KEY=your_tinyfish_api_key
supabase secrets set LOVABLE_API_KEY=your_lovable_api_key
```

### 4. Deploy the Edge Function

```bash
supabase functions deploy search-scholarships
src/
├── app/
│ ├── api/
│ │ └── search/route.ts ← single route: Gemini discovery + parallel agent scraping
│ └── page.tsx
├── components/
│ ├── SearchForm.tsx ← search inputs
│ ├── SearchResults.tsx ← results grid
│ ├── ScholarshipCard.tsx ← individual scholarship card
│ ├── SelectableScholarshipCard.tsx
│ ├── CompareDashboard.tsx ← side-by-side comparison
│ ├── CompareButton.tsx
│ ├── LoadingAnimation.tsx ← live agent status feed
│ └── Header.tsx
├── hooks/
│ └── useScholarshipSearch.ts ← SSE stream consumer
└── types/
└── scholarship.ts
```

### 5. Run locally
## Code Snippet

```bash
npm run dev
```typescript
// /api/search — Gemini discovers URLs, then TinyFish agents scrape in parallel
import { GoogleGenerativeAI } from "@google/generative-ai";
import { TinyFish, EventType, RunStatus } from "@tiny-fish/sdk";

const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
const result = await model.generateContent(prompt);
const urls = JSON.parse(result.response.text());

const client = new TinyFish({ apiKey: process.env.TINYFISH_API_KEY });

await Promise.all(
scholarshipUrls.map(async (site, index) => {
const agentStream = await client.agent.stream({ url: site.url, goal });
for await (const event of agentStream) {
if (event.type === EventType.COMPLETE) {
send({ type: "AGENT_COMPLETE", scholarships: event.result?.scholarships });
return;
}
}
})
);
```

## Architecture Diagram

```mermaid
flowchart TB

%% =======================
%% UI LAYER
%% =======================
UI["USER INTERFACE<br/>(React + Tailwind + Dashboard)"]
## Running Locally

%% =======================
%% INPUT & ORCHESTRATION
%% =======================
ORCH["Search Orchestration Layer<br/>(Supabase Edge Function)"]
1. **Install dependencies**
```bash
npm install
```

%% =======================
%% INTELLIGENCE LAYER
%% =======================
LLM["LLM Intelligence Layer<br/>(Gemini via Lovable AI Gateway)"]
2. **Set up environment**
```bash
cp .env.example .env.local
# Add your API keys to .env.local
```

%% =======================
%% AUTOMATION LAYER
%% =======================
TF["TinyFish Web Automation<br/>(Scholarship Discovery & Extraction)"]
3. **Run the dev server**
```bash
npm run dev
```

%% =======================
%% DETAIL NODES
%% =======================
LLMD["- Interpret user intent<br/>- Region / University filtering<br/>- Generate authoritative scholarship links"]
TFD["- Visit scholarship websites<br/>- Extract visible scholarship details<br/>- SSE streaming of results"]
4. Open [http://localhost:3000](http://localhost:3000)

%% =======================
%% CONNECTIONS
%% =======================
UI --> ORCH
## Environment Variables

ORCH --> LLM
LLM --> LLMD

ORCH --> TF
TF --> TFD

TF --> ORCH

ORCH --> UI
```
| Variable | Description |
|----------|-------------|
| `TINYFISH_API_KEY` | Browser agents that scrape scholarship pages. Get yours at [agent.tinyfish.ai/api-keys](https://agent.tinyfish.ai/api-keys) |
| `GEMINI_API_KEY` | LLM-powered URL discovery via gemini-2.0-flash. Get yours at [aistudio.google.com/apikey](https://aistudio.google.com/apikey) |
Binary file removed scholarship-finder/bun.lockb
Binary file not shown.
20 changes: 0 additions & 20 deletions scholarship-finder/components.json

This file was deleted.

26 changes: 0 additions & 26 deletions scholarship-finder/eslint.config.js

This file was deleted.

26 changes: 0 additions & 26 deletions scholarship-finder/index.html

This file was deleted.

1 change: 1 addition & 0 deletions scholarship-finder/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import type { NextConfig } from "next"; const nextConfig: NextConfig = {}; export default nextConfig;
Loading