Skip to content
Open
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
7 changes: 7 additions & 0 deletions customer-discovery-app/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# OpenRouter API Key - Used for company analysis and synthesis
# Get your key at https://openrouter.ai/keys
OPENROUTER_API_KEY=

# TinyFish API Key - Used for TinyFish browser agent research
# Get your key at https://tinyfish.ai
TINYFISH_API_KEY=
37 changes: 37 additions & 0 deletions customer-discovery-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# dependencies
/node_modules

# next.js
/.next/
/out/

# production
/build

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (keep .env.example tracked)
.env
.env*.local
!.env.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# OS files
.DS_Store
Thumbs.db

# IDE
.idea/
.vscode/
*.swp
*.swo
134 changes: 134 additions & 0 deletions customer-discovery-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Customer Discovery

**Live:** [https://customer-discovery-app-ten.vercel.app](https://customer-discovery-app-ten.vercel.app)

Customer Discovery is an AI-powered market research tool that identifies customer segments by analyzing companies similar to a given seed company. It uses the TinyFish SDK to dispatch up to 10 parallel web agents — one per similar company — that each navigate the target website, extract case studies, testimonials, pricing, features, and customer types, then synthesizes all findings into actionable customer segment profiles.

## Demo

https://github.com/user-attachments/assets/3a7fd32d-8d8d-4233-9cce-ce3ddeeb360d

## TinyFish SDK Usage

The app uses the TinyFish TypeScript SDK (`@tiny-fish/sdk`) to stream live events from parallel browser agents. Each agent navigates a company's website, inspects key pages (customers, pricing, about), and returns structured JSON findings:

```typescript
import { TinyFish } from "@tiny-fish/sdk";

const client = new TinyFish({ apiKey });

const agentStream = await client.agent.stream({
url: company.url,
goal: `You are a customer discovery agent analyzing ${company.name} (${company.url}).

IMPORTANT: Stay ONLY on this company's website. Do NOT visit external websites.

STEP 1 — Navigate to the homepage and get an overview of what the company does.

STEP 2 — Quickly check these pages if they exist (skip if not found):
- /customers or /case-studies
- /pricing
- /about

STEP 3 — Extract this structured data:
- Company overview (1-2 sentences)
- Customer types they serve (e.g., startups, enterprises, agencies, SMBs)
- Case studies: customer name, industry, brief summary (up to 5)
- Testimonials: quote, author, company, role (up to 5)
- Pricing tiers: plan names and who each is for
- Key features: main product capabilities (up to 8)
- Integrations: tools/platforms they connect with

STEP 4 — Return findings as JSON:
{
"overview": "What the company does",
"customerTypes": ["type1", "type2"],
"caseStudies": [{"customer": "name", "industry": "industry", "summary": "summary"}],
"testimonials": [{"quote": "quote", "author": "name", "company": "company", "role": "role"}],
"pricingTiers": ["Free - for individuals", "Pro $X/mo - for teams"],
"keyFeatures": ["feature1", "feature2"],
"integrations": ["integration1", "integration2"]
}

FINAL RULE: Return ONLY the JSON object above. No planning text, no step reports, no reasoning traces.`,
});

for await (const event of agentStream) {
// STREAMING_URL → live browser preview (iframe)
// PROGRESS → agent step updates
// COMPLETE → structured company findings JSON
}
```

All 10 agents run concurrently via a single batch SSE connection, bypassing browser connection limits. Events are streamed in real time — each agent card shows a live browser preview while browsing.

## How to Run

### Prerequisites

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

### Setup

1. Install dependencies:

```bash
cd customer-discovery-app
npm install
```

2. Create a `.env` file with your API keys:

```
OPENROUTER_API_KEY=your_openrouter_api_key_here
TINYFISH_API_KEY=your_tinyfish_api_key_here
```

3. Start the dev server:

```bash
npm run dev
```

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

## Architecture Diagram

```
┌──────────────────────────────────────────────────────────────┐
│ User (Browser) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Next.js Frontend (Tailwind + shadcn/ui + Framer) │ │
│ │ │ │
│ │ 1. Enter seed company name + URL │ │
│ │ 2. AI finds 10 similar companies (via OpenRouter) │ │
│ │ 3. 10 TinyFish agents launch in parallel │ │
│ │ 4. Watch live browser previews as agents research │ │
│ │ 5. View synthesized customer segments + insights │ │
│ └───────────┬────────────────────────────┬───────────────┘ │
└──────────────┼────────────────────────────┼──────────────────┘
│ │
POST /api/analyze-company POST /api/tinyfish-agents-batch
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────────────┐
│ OpenRouter API │ │ TinyFish SDK (agent.tinyfish.ai) │
│ │ │ │
│ Gemini Flash model │ │ client.agent.stream() x 10 │
│ finds 10 similar │ │ Each agent navigates one company │
│ companies + profile │ │ │
└──────────────────────┘ │ SSE Stream Events: │
│ • STREAMING_URL → live preview │
POST /api/synthesize │ • PROGRESS → step updates │
│ │ • COMPLETE → structured JSON │
▼ └─────┬────┬────┬────┬───────────────┘
┌──────────────────────┐ │ │ │ │
│ OpenRouter API │ ▼ ▼ ▼ ▼
│ │ ┌──────┐┌──────┐┌──────┐ ... (10 companies)
│ Combines all agent │ │ Co.A ││ Co.B ││ Co.C │
│ findings into │ │ site ││ site ││ site │
│ customer segments │ └──────┘└──────┘└──────┘
└──────────────────────┘
```
115 changes: 115 additions & 0 deletions customer-discovery-app/app/api/analyze-company/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { NextRequest, NextResponse } from "next/server";
import type { SimilarCompany } from "@/lib/types";
import { callOpenRouterWithRetry, parseOpenRouterJson } from "@/lib/openrouter";

interface AnalysisResult {
companyProfile: {
industry: string;
positioning: string;
targetMarket: string;
};
similarCompanies: SimilarCompany[];
}

export async function POST(request: NextRequest) {
try {
const { companyName, companyUrl } = await request.json();

if (!companyName || !companyUrl) {
return NextResponse.json(
{ error: "Company name and URL are required" },
{ status: 400 }
);
}

const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: "OPENROUTER_API_KEY is not configured" },
{ status: 500 }
);
}

const messages = [
{
role: "system" as const,
content: `You are an expert market research analyst specializing in competitive intelligence and customer discovery. You have deep knowledge of SaaS, technology companies, and business models across industries. Return ONLY valid JSON — no markdown, no code fences.`,
},
{
role: "user" as const,
content: `Analyze the company "${companyName}" (${companyUrl}).

1. Provide a brief company profile:
- Industry/sector they operate in
- Their market positioning and value proposition
- Who their target market is

2. Find 10 similar companies that:
- Operate in the same or adjacent industry
- Serve similar customer segments
- Have comparable business models
- Are REAL companies with working websites (not fictional)

For each similar company provide:
- name: Company name
- url: Website URL (must be real and working, starting with https://)
- description: 1-2 sentence description
- industry: Their industry/category
- targetAudience: Who they sell to

Return this exact JSON structure:
{
"companyProfile": {
"industry": "string",
"positioning": "string",
"targetMarket": "string"
},
"similarCompanies": [
{
"name": "Company Name",
"url": "https://company.com",
"description": "Brief description",
"industry": "Industry",
"targetAudience": "Target audience"
}
]
}

Return exactly 10 similar companies.`,
},
];

const { text } = await callOpenRouterWithRetry(messages, apiKey);
const result = parseOpenRouterJson<AnalysisResult>(text);

if (!result.companyProfile || !result.similarCompanies) {
return NextResponse.json(
{ error: "Invalid response structure from AI" },
{ status: 500 }
);
}

const similarCompanies: SimilarCompany[] = result.similarCompanies
.filter((c) => c && c.name && c.url && c.url.startsWith("http"))
.slice(0, 10);

return NextResponse.json({
companyProfile: result.companyProfile,
similarCompanies,
});
} catch (error) {
console.error("Error analyzing company:", error);

const errorMessage =
error instanceof Error ? error.message : "Failed to analyze company";

if (errorMessage.includes("429") || errorMessage.includes("quota")) {
return NextResponse.json(
{ error: "API rate limit exceeded. Please wait a moment and try again." },
{ status: 429 }
);
}

return NextResponse.json({ error: errorMessage }, { status: 500 });
}
}
Loading