Files
vibn-frontend/app/api/projects/[projectId]/mission/generate/route.ts

223 lines
9.4 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin';
import { getAlloyDbClient } from '@/lib/db/alloydb';
import { GeminiLlmClient } from '@/lib/ai/gemini-client';
import { z } from 'zod';
const MissionFrameworkSchema = z.object({
targetCustomer: z.object({
primaryAudience: z.string().describe('Primary narrow target segment (include geography/region if mentioned in context)'),
theirSituation: z.string().describe('What situation or context they are in'),
relatedMarkets: z.array(z.string()).describe('2-4 additional related market segments or customer types that could benefit'),
}),
existingSolutions: z.array(z.object({
category: z.string().describe('Category of solution (e.g., "Legacy EMR Systems", "AI Scribes", "Practice Management", "Open Source")'),
description: z.string().describe('Description of this category and its limitations'),
products: z.array(z.object({
name: z.string().describe('Product/company name'),
url: z.string().optional().describe('Website URL if known'),
})).min(5).max(20).describe('Comprehensive list of 5-20 specific products in this category. Include all major players and notable solutions.'),
})).min(4).max(7).describe('4-7 categories of existing solutions with comprehensive product lists. ALWAYS include an "Open Source" category if applicable to the market.'),
innovations: z.array(z.object({
title: z.string().describe('Short title for this innovation (3-5 words)'),
description: z.string().describe('How this makes you different and better'),
})).describe('3 key innovations or differentiators'),
ideaValidation: z.array(z.object({
title: z.string().describe('Name of this validation metric'),
description: z.string().describe('What success looks like for this metric'),
})).describe('3 ways to validate the idea is sound'),
financialSuccess: z.object({
subscribers: z.number().describe('Target number of subscribers (Year 1)'),
pricePoint: z.number().describe('Monthly price per subscriber in dollars'),
retentionRate: z.number().describe('Target monthly retention rate as a percentage (0-100)'),
}),
});
export async function POST(
request: Request,
{ params }: { params: Promise<{ projectId: string }> }
) {
try {
const { projectId } = await params;
// Authentication (skip in development if no auth header)
const authHeader = request.headers.get('Authorization');
const isDevelopment = process.env.NODE_ENV === 'development';
if (!isDevelopment || authHeader?.startsWith('Bearer ')) {
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 });
}
}
console.log('[API /mission/generate] Generating mission framework for project:', projectId);
// Fetch insights from AlloyDB
let insights: any[] = [];
try {
const pool = await getAlloyDbClient();
const result = await pool.query(
`SELECT content, source_type, importance, created_at
FROM knowledge_chunks
WHERE project_id = $1
ORDER BY importance DESC, created_at DESC
LIMIT 50`,
[projectId]
);
insights = result.rows;
console.log('[API /mission/generate] Found', insights.length, 'insights');
} catch (dbError) {
console.log('[API /mission/generate] No AlloyDB insights available');
}
// Fetch knowledge items from Firestore
let knowledgeItems: any[] = [];
try {
const adminDb = getAdminDb();
const knowledgeSnapshot = await adminDb
.collection('knowledge')
.where('projectId', '==', projectId)
.orderBy('createdAt', 'desc')
.limit(20)
.get();
knowledgeItems = knowledgeSnapshot.docs.map(doc => doc.data());
console.log('[API /mission/generate] Found', knowledgeItems.length, 'knowledge items');
} catch (firestoreError) {
console.log('[API /mission/generate] No Firestore knowledge available');
}
// Get project data
let projectData: any = {};
try {
const adminDb = getAdminDb();
const projectDoc = await adminDb.collection('projects').doc(projectId).get();
if (projectDoc.exists) {
projectData = projectDoc.data();
}
} catch (error) {
console.log('[API /mission/generate] Could not fetch project data');
}
// Build context from available data
const contextParts = [];
if (projectData?.productVision) {
contextParts.push(`Product Vision: ${projectData.productVision}`);
}
if (projectData?.phaseData?.canonicalProductModel) {
const model = projectData.phaseData.canonicalProductModel;
if (model.oneLiner) contextParts.push(`Product Description: ${model.oneLiner}`);
if (model.problem) contextParts.push(`Problem: ${model.problem}`);
if (model.targetUser) contextParts.push(`Target User: ${model.targetUser}`);
if (model.coreSolution) contextParts.push(`Solution: ${model.coreSolution}`);
}
if (insights.length > 0) {
const insightTexts = insights.slice(0, 10).map(i => i.content).join('\n- ');
contextParts.push(`Key Insights:\n- ${insightTexts}`);
}
if (knowledgeItems.length > 0) {
const knowledgeTexts = knowledgeItems.slice(0, 5)
.map(k => k.title || k.content?.substring(0, 100))
.filter(Boolean)
.join('\n- ');
if (knowledgeTexts) {
contextParts.push(`Additional Context:\n- ${knowledgeTexts}`);
}
}
const context = contextParts.length > 0
? contextParts.join('\n\n')
: 'No project context available yet. Please create a generic framework based on best practices for new product development.';
console.log('[API /mission/generate] Context length:', context.length);
// Use AI to generate the mission framework
const llm = new GeminiLlmClient();
const systemPrompt = `You are a product strategy expert. Based on the provided project information, create a comprehensive mission framework that helps the founder clearly articulate their product vision, market position, and success metrics.
CRITICAL: For Target Customer, be VERY SPECIFIC and NARROW:
- Look for geographic/regional targeting in the context (country, state, city, region)
- Look for specific customer segments, verticals, or niches
- Avoid broad generalizations like "all doctors" or "businesses everywhere"
- If region is mentioned, ALWAYS include it in the primary audience
- Target the smallest viable market segment that can sustain the business
Be specific and actionable. Use the project context to inform your recommendations.`;
const userPrompt = `Based on this project information, generate a complete mission framework:
${context}
Create a structured mission framework that includes:
1. Target Customer:
- Primary Audience: Be EXTREMELY SPECIFIC and narrow (include geography if mentioned)
Example: "Solo family practice physicians in rural Oregon" NOT "Primary care doctors"
- Their Situation: What problem/context they face
- Related Markets: List 2-4 other related customer segments that could also benefit
Example: ["Urgent care clinics", "Pediatric specialists in small practices", "Telemedicine providers"]
2. Existing Solutions: Group into 4-7 CATEGORIES (e.g., "Legacy EMR Systems", "AI Medical Scribes", "Open Source", etc.)
- For each category: provide a description of what they do and their limitations
- List 5-20 specific PRODUCTS/COMPANIES in each category with website URLs if you know them
- Be COMPREHENSIVE - include all major players, notable solutions, and emerging alternatives
- ALWAYS include an "Open Source" category listing relevant open-source alternatives (GitHub, frameworks, libraries, tools)
- Include direct competitors, adjacent solutions, and legacy approaches
3. Your Innovations (3 key differentiators)
4. Idea Validation (3 validation metrics)
5. Financial Success (subscribers, price point, retention rate)
Be comprehensive with existing solutions. Be specific and narrow with primary target, but show the range of related markets.`;
const result = await llm.structuredCall({
model: 'gemini',
systemPrompt,
messages: [{ role: 'user', content: userPrompt }],
schema: MissionFrameworkSchema,
temperature: 0.7,
});
console.log('[API /mission/generate] Successfully generated mission framework');
// Store the generated framework in Firestore
try {
const adminDb = getAdminDb();
await adminDb.collection('projects').doc(projectId).update({
'phaseData.missionFramework': result,
'phaseData.missionFrameworkUpdatedAt': new Date(),
});
console.log('[API /mission/generate] Saved framework to Firestore');
} catch (saveError) {
console.error('[API /mission/generate] Could not save framework:', saveError);
}
return NextResponse.json({
success: true,
framework: result,
});
} catch (error) {
console.error('[API /mission/generate] Error:', error);
return NextResponse.json(
{
error: 'Failed to generate mission framework',
details: error instanceof Error ? error.message : String(error)
},
{ status: 500 }
);
}
}