VIBN Frontend for Coolify deployment
This commit is contained in:
199
app/api/projects/[projectId]/research/market/route.ts
Normal file
199
app/api/projects/[projectId]/research/market/route.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user