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