Files
vibn-frontend/app/api/projects/[projectId]/research/market/route.ts

200 lines
6.2 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin';
import { GeminiLlmClient } from '@/lib/ai/gemini-client';
import { z } from 'zod';
const MarketResearchSchema = z.object({
targetNiches: z.array(z.object({
name: z.string(),
description: z.string(),
marketSize: z.string(),
competitionLevel: z.enum(['low', 'medium', 'high']),
opportunity: z.string(),
})),
competitors: z.array(z.object({
name: z.string(),
positioning: z.string(),
strengths: z.array(z.string()),
weaknesses: z.array(z.string()),
})),
marketGaps: z.array(z.object({
gap: z.string(),
impact: z.enum(['low', 'medium', 'high']),
reasoning: z.string(),
})),
recommendations: z.array(z.string()),
sources: z.array(z.string()),
});
export async function POST(
request: Request,
{ params }: { params: Promise<{ projectId: string }> }
) {
try {
const { projectId } = await params;
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const token = authHeader.substring(7);
const auth = getAdminAuth();
const decoded = await auth.verifyIdToken(token);
if (!decoded?.uid) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
// Get project data
const adminDb = getAdminDb();
const projectRef = adminDb.collection('projects').doc(projectId);
const projectDoc = await projectRef.get();
if (!projectDoc.exists) {
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
}
const projectData = projectDoc.data();
const productVision = projectData?.productVision || '';
const productName = projectData?.productName || '';
const phaseData = projectData?.phaseData || {};
const canonicalModel = phaseData.canonicalProductModel || {};
// Build context for the agent
const ideaContext = canonicalModel.oneLiner || productVision ||
`${productName}: Help users build and launch products faster`;
console.log('[Market Research] Starting research for:', ideaContext);
// Initialize LLM client
const llm = new GeminiLlmClient();
// Conduct market research using the agent
const systemPrompt = `You are a market research analyst specializing in finding product-market fit and identifying underserved niches.
Your task is to analyze the given product idea and conduct comprehensive market research to:
1. Identify specific target niches that would benefit most from this product
2. Analyze competitors and their positioning
3. Find market gaps and opportunities
4. Provide actionable recommendations
Be specific, data-driven, and focused on actionable insights.`;
const userPrompt = `Analyze this product idea and conduct market research:
Product Idea: "${ideaContext}"
${canonicalModel.problem ? `Problem Being Solved: ${canonicalModel.problem}` : ''}
${canonicalModel.targetUser ? `Target User: ${canonicalModel.targetUser}` : ''}
${canonicalModel.coreSolution ? `Core Solution: ${canonicalModel.coreSolution}` : ''}
Provide a comprehensive market research analysis including:
- Target niches with high potential
- Competitor analysis
- Market gaps and opportunities
- Strategic recommendations
Focus on finding specific, underserved niches where this product can win.`;
const research = await llm.structuredCall({
model: 'gemini',
systemPrompt,
messages: [
{
role: 'user',
content: userPrompt,
},
],
schema: MarketResearchSchema,
temperature: 0.7,
});
console.log('[Market Research] Research completed:', {
niches: research.targetNiches.length,
competitors: research.competitors.length,
gaps: research.marketGaps.length,
});
// Store research results in Firestore
const researchRef = adminDb.collection('marketResearch').doc();
await researchRef.set({
id: researchRef.id,
projectId,
userId: decoded.uid,
research,
ideaContext,
createdAt: new Date(),
updatedAt: new Date(),
});
// Also store as knowledge items for vector search
const knowledgePromises = [];
// Store each niche as a knowledge item
for (const niche of research.targetNiches) {
const nicheRef = adminDb.collection('knowledge').doc();
knowledgePromises.push(
nicheRef.set({
id: nicheRef.id,
projectId,
userId: decoded.uid,
sourceType: 'research',
title: `Target Niche: ${niche.name}`,
content: `${niche.description}\n\nMarket Size: ${niche.marketSize}\nCompetition: ${niche.competitionLevel}\n\nOpportunity: ${niche.opportunity}`,
sourceMeta: {
origin: 'vibn',
researchType: 'market_niche',
researchId: researchRef.id,
},
createdAt: new Date(),
updatedAt: new Date(),
})
);
}
// Store market gaps
for (const gap of research.marketGaps) {
const gapRef = adminDb.collection('knowledge').doc();
knowledgePromises.push(
gapRef.set({
id: gapRef.id,
projectId,
userId: decoded.uid,
sourceType: 'research',
title: `Market Gap: ${gap.gap.substring(0, 50)}`,
content: `${gap.gap}\n\nImpact: ${gap.impact}\n\nReasoning: ${gap.reasoning}`,
sourceMeta: {
origin: 'vibn',
researchType: 'market_gap',
researchId: researchRef.id,
},
createdAt: new Date(),
updatedAt: new Date(),
})
);
}
await Promise.all(knowledgePromises);
console.log('[Market Research] Stored', knowledgePromises.length, 'knowledge items');
return NextResponse.json({
success: true,
research,
researchId: researchRef.id,
knowledgeItemsCreated: knowledgePromises.length,
});
} catch (error) {
console.error('[Market Research] Error:', error);
return NextResponse.json(
{
error: 'Failed to conduct market research',
details: error instanceof Error ? error.message : 'Unknown error'
},
{ status: 500 }
);
}
}