VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
import { getApiUrl } from '@/lib/utils/api-url';
/**
* Complete Project Context API
* Returns everything an AI needs to understand the project state
*/
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ projectId: string }> }
) {
try {
const { projectId } = await params;
// 1. Load project metadata
const projectDoc = await adminDb
.collection('projects')
.doc(projectId)
.get();
if (!projectDoc.exists) {
return NextResponse.json(
{ error: 'Project not found' },
{ status: 404 }
);
}
const projectData = projectDoc.data();
// 2. Load timeline data
const timelineResponse = await fetch(
getApiUrl(`/api/projects/${projectId}/timeline`, request)
);
const timeline = timelineResponse.ok ? await timelineResponse.json() : null;
// 3. Load Git history summary
const gitResponse = await fetch(
getApiUrl(`/api/projects/${projectId}/git-history`, request)
);
const gitHistory = gitResponse.ok ? await gitResponse.json() : null;
// 4. Load extension activity
const activityResponse = await fetch(
getApiUrl(`/api/projects/${projectId}/activity`, request)
);
const activity = activityResponse.ok ? await activityResponse.json() : null;
// 5. Load uploaded documents
const documentsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('documents')
.orderBy('uploadedAt', 'desc')
.get();
const documents = documentsSnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// 6. Get recent conversations (last 7 days)
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.where('createdAt', '>=', sevenDaysAgo.toISOString())
.orderBy('createdAt', 'desc')
.limit(10)
.get();
const recentConversations = [];
for (const convDoc of conversationsSnapshot.docs) {
const conv = convDoc.data();
const messagesSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.doc(convDoc.id)
.collection('messages')
.orderBy('createdAt', 'desc')
.limit(5)
.get();
recentConversations.push({
id: convDoc.id,
name: conv.name,
createdAt: conv.createdAt,
recentMessages: messagesSnapshot.docs.map(m => ({
type: m.data().type === 1 ? 'user' : 'assistant',
text: m.data().text?.substring(0, 200) + '...',
createdAt: m.data().createdAt
}))
});
}
// 7. Calculate key metrics
const activeDays = timeline?.days?.filter((d: any) =>
d.summary.totalGitCommits > 0 ||
d.summary.totalExtensionSessions > 0 ||
d.summary.totalCursorMessages > 0
).length || 0;
const topFiles = activity?.fileActivity?.slice(0, 10) || [];
// 8. Extract key milestones (commits with significant changes)
const keyMilestones = gitHistory?.commits
?.filter((c: any) => c.insertions + c.deletions > 1000)
.slice(0, 5)
.map((c: any) => ({
date: c.date,
message: c.message,
author: c.author,
impact: `+${c.insertions}/-${c.deletions} lines`
})) || [];
// 9. Generate AI-friendly summary
const context = {
project: {
id: projectId,
name: projectData?.name || 'Untitled Project',
vision: projectData?.vision || null,
description: projectData?.description || null,
createdAt: projectData?.createdAt || null,
githubRepo: projectData?.githubRepo || null
},
timeline: {
dateRange: {
earliest: timeline?.dateRange?.earliest,
latest: timeline?.dateRange?.latest,
totalDays: timeline?.dateRange?.totalDays || 0,
activeDays
},
dataSources: {
git: {
available: timeline?.dataSources?.git?.available || false,
totalCommits: timeline?.dataSources?.git?.totalRecords || 0,
dateRange: {
first: timeline?.dataSources?.git?.firstDate,
last: timeline?.dataSources?.git?.lastDate
}
},
extension: {
available: timeline?.dataSources?.extension?.available || false,
totalSessions: timeline?.dataSources?.extension?.totalRecords || 0,
dateRange: {
first: timeline?.dataSources?.extension?.firstDate,
last: timeline?.dataSources?.extension?.lastDate
}
},
cursor: {
available: timeline?.dataSources?.cursor?.available || false,
totalMessages: timeline?.dataSources?.cursor?.totalRecords || 0,
dateRange: {
first: timeline?.dataSources?.cursor?.firstDate,
last: timeline?.dataSources?.cursor?.lastDate
}
}
}
},
codebase: {
totalCommits: gitHistory?.totalCommits || 0,
totalLinesAdded: gitHistory?.totalInsertions || 0,
totalLinesRemoved: gitHistory?.totalDeletions || 0,
contributors: gitHistory?.authors || [],
topFiles: gitHistory?.topFiles?.slice(0, 20) || []
},
activity: {
totalSessions: activity?.totalSessions || 0,
uniqueFilesEdited: activity?.fileActivity?.length || 0,
topEditedFiles: topFiles,
recentConversations
},
milestones: keyMilestones,
documents: documents.map(doc => ({
id: doc.id,
title: doc.title,
type: doc.type,
uploadedAt: doc.uploadedAt,
contentPreview: doc.content?.substring(0, 500) + '...'
})),
summary: generateProjectSummary({
projectData,
timeline,
gitHistory,
activity,
documents
})
};
return NextResponse.json(context);
} catch (error) {
console.error('Error loading project context:', error);
return NextResponse.json(
{
error: 'Failed to load project context',
details: error instanceof Error ? error.message : String(error)
},
{ status: 500 }
);
}
}
// Helper to generate human-readable summary
function generateProjectSummary(data: any): string {
const { projectData, timeline, gitHistory, activity, documents } = data;
const parts = [];
// Project basics
if (projectData?.name) {
parts.push(`Project: ${projectData.name}`);
}
if (projectData?.vision) {
parts.push(`Vision: ${projectData.vision}`);
}
// Timeline
if (timeline?.dateRange?.totalDays) {
parts.push(`Development span: ${timeline.dateRange.totalDays} days`);
}
// Git stats
if (gitHistory?.totalCommits) {
parts.push(
`Code: ${gitHistory.totalCommits} commits, ` +
`+${gitHistory.totalInsertions.toLocaleString()}/-${gitHistory.totalDeletions.toLocaleString()} lines`
);
}
// Activity
if (activity?.totalSessions) {
parts.push(`Activity: ${activity.totalSessions} development sessions`);
}
// Documents
if (documents?.length) {
parts.push(`Documentation: ${documents.length} documents uploaded`);
}
return parts.join(' | ');
}