VIBN Frontend for Coolify deployment
This commit is contained in:
254
app/api/projects/[projectId]/context/route.ts
Normal file
254
app/api/projects/[projectId]/context/route.ts
Normal 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(' | ');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user