200 lines
6.2 KiB
TypeScript
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 }
|
|
);
|
|
}
|
|
}
|
|
|