VIBN Frontend for Coolify deployment
This commit is contained in:
63
app/api/debug/append-conversation/route.ts
Normal file
63
app/api/debug/append-conversation/route.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAdminDb } from '@/lib/firebase/admin';
|
||||
import { FieldValue } from 'firebase-admin/firestore';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const projectId = (body.projectId ?? '').trim();
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'projectId is required' },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const adminDb = getAdminDb();
|
||||
const docRef = adminDb.collection('chat_conversations').doc(projectId);
|
||||
|
||||
await adminDb.runTransaction(async (tx) => {
|
||||
const snapshot = await tx.get(docRef);
|
||||
const existing = (snapshot.exists ? (snapshot.data()?.messages as unknown[]) : []) ?? [];
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const newMessages = [
|
||||
{
|
||||
role: 'user' as const,
|
||||
content: '[debug] test user message',
|
||||
createdAt: now,
|
||||
},
|
||||
{
|
||||
role: 'assistant' as const,
|
||||
content: '[debug] test assistant reply',
|
||||
createdAt: now,
|
||||
},
|
||||
];
|
||||
|
||||
tx.set(
|
||||
docRef,
|
||||
{
|
||||
projectId,
|
||||
messages: [...existing, ...newMessages],
|
||||
updatedAt: FieldValue.serverTimestamp(),
|
||||
},
|
||||
{ merge: true },
|
||||
);
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('[debug/append-conversation] Failed to append messages', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to append debug conversation messages',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
88
app/api/debug/check-links/route.ts
Normal file
88
app/api/debug/check-links/route.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Debug API to check session links
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const idToken = authHeader.split('Bearer ')[1];
|
||||
const adminAuth = getAdminAuth();
|
||||
const adminDb = getAdminDb();
|
||||
|
||||
let userId: string;
|
||||
try {
|
||||
const decodedToken = await adminAuth.verifyIdToken(idToken);
|
||||
userId = decodedToken.uid;
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get all user's sessions
|
||||
const sessionsSnapshot = await adminDb
|
||||
.collection('sessions')
|
||||
.where('userId', '==', userId)
|
||||
.get();
|
||||
|
||||
const linked: any[] = [];
|
||||
const unlinked: any[] = [];
|
||||
|
||||
sessionsSnapshot.docs.forEach(doc => {
|
||||
const data = doc.data();
|
||||
const sessionInfo = {
|
||||
id: doc.id,
|
||||
workspaceName: data.workspaceName || 'Unknown',
|
||||
workspacePath: data.workspacePath,
|
||||
projectId: data.projectId,
|
||||
needsProjectAssociation: data.needsProjectAssociation,
|
||||
createdAt: data.createdAt?.toDate?.() || data.createdAt,
|
||||
};
|
||||
|
||||
if (data.projectId) {
|
||||
linked.push(sessionInfo);
|
||||
} else {
|
||||
unlinked.push(sessionInfo);
|
||||
}
|
||||
});
|
||||
|
||||
// Get all user's projects
|
||||
const projectsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.where('userId', '==', userId)
|
||||
.get();
|
||||
|
||||
const projects = projectsSnapshot.docs.map(doc => ({
|
||||
id: doc.id,
|
||||
name: doc.data().productName || doc.data().name,
|
||||
workspacePath: doc.data().workspacePath,
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
summary: {
|
||||
totalSessions: sessionsSnapshot.size,
|
||||
linkedSessions: linked.length,
|
||||
unlinkedSessions: unlinked.length,
|
||||
totalProjects: projects.length,
|
||||
},
|
||||
linked,
|
||||
unlinked,
|
||||
projects,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Debug check error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to check links',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
62
app/api/debug/check-project/route.ts
Normal file
62
app/api/debug/check-project/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing projectId parameter' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if project exists
|
||||
const projectDoc = await adminDb.collection('projects').doc(projectId).get();
|
||||
|
||||
if (!projectDoc.exists) {
|
||||
// List all projects to help debug
|
||||
const allProjectsSnapshot = await adminDb.collection('projects').limit(20).get();
|
||||
const allProjects = allProjectsSnapshot.docs.map(doc => ({
|
||||
id: doc.id,
|
||||
name: doc.data().name,
|
||||
userId: doc.data().userId,
|
||||
createdAt: doc.data().createdAt
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
exists: false,
|
||||
projectId,
|
||||
message: 'Project not found',
|
||||
availableProjects: allProjects
|
||||
});
|
||||
}
|
||||
|
||||
const projectData = projectDoc.data();
|
||||
|
||||
return NextResponse.json({
|
||||
exists: true,
|
||||
projectId,
|
||||
project: {
|
||||
name: projectData?.name,
|
||||
userId: projectData?.userId,
|
||||
createdAt: projectData?.createdAt,
|
||||
githubRepo: projectData?.githubRepo
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error checking project:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to check project', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
44
app/api/debug/context-sources/route.ts
Normal file
44
app/api/debug/context-sources/route.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAdminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const projectId = url.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
const adminDb = getAdminDb();
|
||||
|
||||
// Get contextSources subcollection
|
||||
const contextSourcesRef = adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('contextSources');
|
||||
|
||||
const snapshot = await contextSourcesRef.get();
|
||||
|
||||
const sources = snapshot.docs.map(doc => ({
|
||||
id: doc.id,
|
||||
...doc.data(),
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
projectId,
|
||||
count: sources.length,
|
||||
sources,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[debug/context-sources] Error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to fetch context sources',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
72
app/api/debug/cursor-analysis/route.ts
Normal file
72
app/api/debug/cursor-analysis/route.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get workspace metadata
|
||||
const workspaceMetaDoc = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorData')
|
||||
.doc('workspace-meta')
|
||||
.get();
|
||||
|
||||
const workspaceMeta = workspaceMetaDoc.exists ? workspaceMetaDoc.data() : null;
|
||||
|
||||
// Get all conversations
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.get();
|
||||
|
||||
// Analyze by score
|
||||
const negative = conversationsSnapshot.docs
|
||||
.map(doc => ({ name: doc.data().name, score: doc.data().relevanceScore || 0 }))
|
||||
.filter(c => c.score < 0)
|
||||
.sort((a, b) => a.score - b.score);
|
||||
|
||||
const positive = conversationsSnapshot.docs
|
||||
.map(doc => ({ name: doc.data().name, score: doc.data().relevanceScore || 0 }))
|
||||
.filter(c => c.score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
const neutral = conversationsSnapshot.docs
|
||||
.filter(doc => (doc.data().relevanceScore || 0) === 0)
|
||||
.length;
|
||||
|
||||
return NextResponse.json({
|
||||
workspacePath: workspaceMeta?.workspacePath,
|
||||
githubUrl: workspaceMeta?.githubUrl,
|
||||
workspaceFilesCount: workspaceMeta?.workspaceFiles?.length || 0,
|
||||
workspaceFilesSample: (workspaceMeta?.workspaceFiles || []).slice(0, 20),
|
||||
totalGenerations: workspaceMeta?.totalGenerations || 0,
|
||||
totalConversations: conversationsSnapshot.size,
|
||||
scoreBreakdown: {
|
||||
negative: negative.length,
|
||||
neutral,
|
||||
positive: positive.length
|
||||
},
|
||||
negativeScoreConversations: negative,
|
||||
topPositiveConversations: positive.slice(0, 10),
|
||||
sampleNeutralConversations: conversationsSnapshot.docs
|
||||
.filter(doc => (doc.data().relevanceScore || 0) === 0)
|
||||
.slice(0, 10)
|
||||
.map(doc => doc.data().name)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error analyzing conversations:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to analyze', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
72
app/api/debug/cursor-content-sample/route.ts
Normal file
72
app/api/debug/cursor-content-sample/route.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get 5 random conversations with content
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.limit(5)
|
||||
.get();
|
||||
|
||||
const samples = conversationsSnapshot.docs.map(doc => {
|
||||
const data = doc.data();
|
||||
return {
|
||||
name: data.name,
|
||||
messageCount: data.messageCount || 0,
|
||||
promptCount: data.prompts?.length || 0,
|
||||
generationCount: data.generations?.length || 0,
|
||||
filesCount: data.files?.length || 0,
|
||||
sampleFiles: (data.files || []).slice(0, 3),
|
||||
samplePrompt: data.prompts?.[0]?.text?.substring(0, 100) || 'none',
|
||||
hasContent: !!(data.prompts?.length || data.generations?.length)
|
||||
};
|
||||
});
|
||||
|
||||
// Get overall stats
|
||||
const allConversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.get();
|
||||
|
||||
let totalWithContent = 0;
|
||||
let totalWithFiles = 0;
|
||||
let totalMessages = 0;
|
||||
|
||||
allConversationsSnapshot.docs.forEach(doc => {
|
||||
const data = doc.data();
|
||||
if (data.prompts?.length || data.generations?.length) {
|
||||
totalWithContent++;
|
||||
}
|
||||
if (data.files?.length) {
|
||||
totalWithFiles++;
|
||||
}
|
||||
totalMessages += data.messageCount || 0;
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
totalConversations: allConversationsSnapshot.size,
|
||||
totalWithContent,
|
||||
totalWithFiles,
|
||||
totalMessages,
|
||||
samples
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching content sample:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch sample', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
55
app/api/debug/cursor-conversations/route.ts
Normal file
55
app/api/debug/cursor-conversations/route.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get cursor conversations for this project
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.orderBy('createdAt', 'desc')
|
||||
.get();
|
||||
|
||||
const conversations = conversationsSnapshot.docs.map(doc => ({
|
||||
id: doc.id,
|
||||
...doc.data()
|
||||
}));
|
||||
|
||||
// Get the messages data
|
||||
const messagesDoc = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorData')
|
||||
.doc('messages')
|
||||
.get();
|
||||
|
||||
const messagesData = messagesDoc.exists ? messagesDoc.data() : null;
|
||||
|
||||
return NextResponse.json({
|
||||
projectId,
|
||||
conversationCount: conversations.length,
|
||||
conversations,
|
||||
messagesData: messagesData ? {
|
||||
promptCount: messagesData.prompts?.length || 0,
|
||||
generationCount: messagesData.generations?.length || 0,
|
||||
importedAt: messagesData.importedAt
|
||||
} : null
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching cursor conversations:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch conversations', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
56
app/api/debug/cursor-relevant/route.ts
Normal file
56
app/api/debug/cursor-relevant/route.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
const minScore = parseInt(request.nextUrl.searchParams.get('minScore') || '0');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all conversations
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.get();
|
||||
|
||||
const conversations = conversationsSnapshot.docs
|
||||
.map(doc => {
|
||||
const data = doc.data();
|
||||
return {
|
||||
name: data.name,
|
||||
relevanceScore: data.relevanceScore || 0,
|
||||
createdAt: data.createdAt,
|
||||
workspacePath: data.workspacePath
|
||||
};
|
||||
})
|
||||
.filter(c => c.relevanceScore >= minScore)
|
||||
.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
||||
|
||||
// Group by score
|
||||
const scoreGroups: Record<number, number> = {};
|
||||
conversationsSnapshot.docs.forEach(doc => {
|
||||
const score = doc.data().relevanceScore || 0;
|
||||
scoreGroups[score] = (scoreGroups[score] || 0) + 1;
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
totalConversations: conversationsSnapshot.size,
|
||||
filteredConversations: conversations.length,
|
||||
minScore,
|
||||
scoreDistribution: scoreGroups,
|
||||
conversations: conversations.slice(0, 50) // First 50
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching relevant conversations:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch conversations', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
41
app/api/debug/cursor-sample-dates/route.ts
Normal file
41
app/api/debug/cursor-sample-dates/route.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get 10 conversations with their dates
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.orderBy('createdAt', 'desc')
|
||||
.limit(10)
|
||||
.get();
|
||||
|
||||
const samples = conversationsSnapshot.docs.map(doc => {
|
||||
const data = doc.data();
|
||||
return {
|
||||
name: data.name,
|
||||
createdAt: data.createdAt,
|
||||
workspacePath: data.workspacePath
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json({ samples });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching samples:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch samples', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
app/api/debug/cursor-session-summary/route.ts
Normal file
55
app/api/debug/cursor-session-summary/route.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all conversations sorted by time
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.orderBy('createdAt', 'asc')
|
||||
.get();
|
||||
|
||||
const conversations = conversationsSnapshot.docs.map(doc => {
|
||||
const data = doc.data();
|
||||
return {
|
||||
name: data.name,
|
||||
createdAt: new Date(data.createdAt),
|
||||
score: data.relevanceScore || 0
|
||||
};
|
||||
});
|
||||
|
||||
// Find NHL work sessions (negative scores clustered in time)
|
||||
const nhlConversations = conversations.filter(c => c.score < 0);
|
||||
|
||||
return NextResponse.json({
|
||||
totalConversations: conversations.length,
|
||||
nhlConversations: nhlConversations.length,
|
||||
nhlDates: nhlConversations.map(c => ({
|
||||
date: c.createdAt.toISOString().split('T')[0],
|
||||
name: c.name
|
||||
})),
|
||||
// Find if NHL conversations cluster
|
||||
nhlDateCounts: nhlConversations.reduce((acc: any, c) => {
|
||||
const date = c.createdAt.toISOString().split('T')[0];
|
||||
acc[date] = (acc[date] || 0) + 1;
|
||||
return acc;
|
||||
}, {})
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error analyzing session summary:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to analyze', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
124
app/api/debug/cursor-sessions/route.ts
Normal file
124
app/api/debug/cursor-sessions/route.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
const sessionGapMinutes = parseInt(request.nextUrl.searchParams.get('gap') || '120'); // 2 hours default
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all conversations sorted by time
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.orderBy('createdAt', 'asc')
|
||||
.get();
|
||||
|
||||
const conversations = conversationsSnapshot.docs.map(doc => {
|
||||
const data = doc.data();
|
||||
return {
|
||||
id: doc.id,
|
||||
name: data.name,
|
||||
createdAt: new Date(data.createdAt),
|
||||
relevanceScore: data.relevanceScore || 0
|
||||
};
|
||||
});
|
||||
|
||||
// Group into sessions based on time gaps
|
||||
const sessions: any[] = [];
|
||||
let currentSession: any = null;
|
||||
|
||||
for (const conv of conversations) {
|
||||
if (!currentSession) {
|
||||
// Start first session
|
||||
currentSession = {
|
||||
startTime: conv.createdAt,
|
||||
endTime: conv.createdAt,
|
||||
conversations: [conv],
|
||||
relevanceScores: [conv.relevanceScore]
|
||||
};
|
||||
} else {
|
||||
// Check time gap from last conversation
|
||||
const gapMs = conv.createdAt.getTime() - currentSession.endTime.getTime();
|
||||
const gapMinutes = gapMs / (1000 * 60);
|
||||
|
||||
if (gapMinutes <= sessionGapMinutes) {
|
||||
// Same session
|
||||
currentSession.conversations.push(conv);
|
||||
currentSession.relevanceScores.push(conv.relevanceScore);
|
||||
currentSession.endTime = conv.createdAt;
|
||||
} else {
|
||||
// New session - close current and start new
|
||||
sessions.push(currentSession);
|
||||
currentSession = {
|
||||
startTime: conv.createdAt,
|
||||
endTime: conv.createdAt,
|
||||
conversations: [conv],
|
||||
relevanceScores: [conv.relevanceScore]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add last session
|
||||
if (currentSession) {
|
||||
sessions.push(currentSession);
|
||||
}
|
||||
|
||||
// Analyze each session
|
||||
const analyzedSessions = sessions.map((session, idx) => {
|
||||
const durationMinutes = Math.round((session.endTime.getTime() - session.startTime.getTime()) / (1000 * 60));
|
||||
|
||||
// Calculate session relevance score (average of all conversations)
|
||||
const avgScore = session.relevanceScores.reduce((a: number, b: number) => a + b, 0) / session.relevanceScores.length;
|
||||
|
||||
// Count negative/positive conversations
|
||||
const negative = session.relevanceScores.filter((s: number) => s < 0).length;
|
||||
const positive = session.relevanceScores.filter((s: number) => s > 0).length;
|
||||
const neutral = session.relevanceScores.filter((s: number) => s === 0).length;
|
||||
|
||||
// Determine likely project based on majority
|
||||
let likelyProject = 'unknown';
|
||||
if (negative > positive && negative > neutral) {
|
||||
likelyProject = 'other (NHL/market)';
|
||||
} else if (positive > negative && positive > neutral) {
|
||||
likelyProject = 'vibn (likely)';
|
||||
} else if (positive > 0 || avgScore > 0) {
|
||||
likelyProject = 'vibn (mixed)';
|
||||
} else {
|
||||
likelyProject = 'unclear';
|
||||
}
|
||||
|
||||
return {
|
||||
sessionNumber: idx + 1,
|
||||
startTime: session.startTime.toISOString(),
|
||||
endTime: session.endTime.toISOString(),
|
||||
durationMinutes,
|
||||
conversationCount: session.conversations.length,
|
||||
avgRelevanceScore: Math.round(avgScore * 100) / 100,
|
||||
scoreBreakdown: { negative, neutral, positive },
|
||||
likelyProject,
|
||||
conversationNames: session.conversations.slice(0, 5).map((c: any) => c.name)
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
totalConversations: conversations.length,
|
||||
totalSessions: sessions.length,
|
||||
sessionGapMinutes,
|
||||
sessions: analyzedSessions
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error analyzing sessions:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to analyze sessions', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
69
app/api/debug/cursor-stats/route.ts
Normal file
69
app/api/debug/cursor-stats/route.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all conversations
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.get();
|
||||
|
||||
const conversations = conversationsSnapshot.docs.map(doc => doc.data());
|
||||
|
||||
// Find date range
|
||||
const dates = conversations
|
||||
.filter(c => c.createdAt)
|
||||
.map(c => new Date(c.createdAt))
|
||||
.sort((a, b) => a.getTime() - b.getTime());
|
||||
|
||||
if (dates.length === 0) {
|
||||
return NextResponse.json({ error: 'No conversations with dates found' });
|
||||
}
|
||||
|
||||
const earliest = dates[0];
|
||||
const latest = dates[dates.length - 1];
|
||||
const span = Math.floor((latest.getTime() - earliest.getTime()) / (1000 * 60 * 60 * 24));
|
||||
|
||||
// Find the actual conversation names for earliest and latest
|
||||
const earliestConv = conversations.find(c =>
|
||||
new Date(c.createdAt).getTime() === earliest.getTime()
|
||||
);
|
||||
const latestConv = conversations.find(c =>
|
||||
new Date(c.createdAt).getTime() === latest.getTime()
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
totalConversations: conversations.length,
|
||||
dateRange: {
|
||||
earliest: earliest.toISOString(),
|
||||
latest: latest.toISOString(),
|
||||
spanDays: span
|
||||
},
|
||||
oldestConversation: {
|
||||
name: earliestConv?.name || 'Unknown',
|
||||
date: earliest.toISOString()
|
||||
},
|
||||
newestConversation: {
|
||||
name: latestConv?.name || 'Unknown',
|
||||
date: latest.toISOString()
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching cursor stats:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch stats', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
59
app/api/debug/cursor-unknown-sessions/route.ts
Normal file
59
app/api/debug/cursor-unknown-sessions/route.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all unknown conversations grouped by session
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.where('sessionProject', '==', 'unknown')
|
||||
.get();
|
||||
|
||||
const sessionMap: Record<number, any[]> = {};
|
||||
|
||||
conversationsSnapshot.docs.forEach(doc => {
|
||||
const data = doc.data();
|
||||
const sessionId = data.sessionId;
|
||||
|
||||
if (!sessionMap[sessionId]) {
|
||||
sessionMap[sessionId] = [];
|
||||
}
|
||||
|
||||
sessionMap[sessionId].push({
|
||||
name: data.name,
|
||||
date: data.sessionDate,
|
||||
createdAt: data.createdAt
|
||||
});
|
||||
});
|
||||
|
||||
// Convert to array and take sample
|
||||
const sessions = Object.entries(sessionMap).map(([sessionId, conversations]) => ({
|
||||
sessionId: parseInt(sessionId),
|
||||
date: conversations[0].date,
|
||||
conversationCount: conversations.length,
|
||||
conversationNames: conversations.map(c => c.name)
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
totalUnknownSessions: sessions.length,
|
||||
totalUnknownConversations: conversationsSnapshot.size,
|
||||
sample: sessions.slice(0, 30)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching unknown sessions:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
59
app/api/debug/cursor-workspaces/route.ts
Normal file
59
app/api/debug/cursor-workspaces/route.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { adminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const projectId = request.nextUrl.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all conversations
|
||||
const conversationsSnapshot = await adminDb
|
||||
.collection('projects')
|
||||
.doc(projectId)
|
||||
.collection('cursorConversations')
|
||||
.get();
|
||||
|
||||
const conversations = conversationsSnapshot.docs.map(doc => doc.data());
|
||||
|
||||
// Group by workspace path
|
||||
const workspaceGroups: Record<string, number> = {};
|
||||
const githubGroups: Record<string, number> = {};
|
||||
|
||||
conversations.forEach(conv => {
|
||||
const workspace = conv.workspacePath || 'unknown';
|
||||
const github = conv.githubUrl || 'none';
|
||||
|
||||
workspaceGroups[workspace] = (workspaceGroups[workspace] || 0) + 1;
|
||||
githubGroups[github] = (githubGroups[github] || 0) + 1;
|
||||
});
|
||||
|
||||
// Sort by count
|
||||
const workspaceList = Object.entries(workspaceGroups)
|
||||
.map(([path, count]) => ({ path, count }))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
|
||||
const githubList = Object.entries(githubGroups)
|
||||
.map(([url, count]) => ({ url, count }))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
|
||||
return NextResponse.json({
|
||||
totalConversations: conversations.length,
|
||||
uniqueWorkspaces: workspaceList.length,
|
||||
uniqueRepos: githubList.length,
|
||||
workspaces: workspaceList,
|
||||
repos: githubList
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching workspace breakdown:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch breakdown', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
app/api/debug/env/route.ts
vendored
Normal file
18
app/api/debug/env/route.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
firebaseProjectId: process.env.FIREBASE_PROJECT_ID ? 'SET' : 'NOT SET',
|
||||
firebaseClientEmail: process.env.FIREBASE_CLIENT_EMAIL ? 'SET' : 'NOT SET',
|
||||
firebasePrivateKey: process.env.FIREBASE_PRIVATE_KEY ? 'SET (length: ' + process.env.FIREBASE_PRIVATE_KEY.length + ')' : 'NOT SET',
|
||||
|
||||
publicApiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY ? 'SET' : 'NOT SET',
|
||||
publicAuthDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN ? 'SET' : 'NOT SET',
|
||||
publicProjectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID ? 'SET' : 'NOT SET',
|
||||
|
||||
nodeEnv: process.env.NODE_ENV,
|
||||
|
||||
tip: 'If any Firebase vars show NOT SET, restart your dev server after updating .env.local'
|
||||
});
|
||||
}
|
||||
|
||||
33
app/api/debug/first-project/route.ts
Normal file
33
app/api/debug/first-project/route.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAdminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const adminDb = getAdminDb();
|
||||
const snapshot = await adminDb.collection('projects').limit(1).get();
|
||||
|
||||
if (snapshot.empty) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No projects found' },
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const doc = snapshot.docs[0];
|
||||
return NextResponse.json({
|
||||
id: doc.id,
|
||||
data: doc.data(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[debug/first-project] Failed to load project', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to load project',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
43
app/api/debug/knowledge-items/route.ts
Normal file
43
app/api/debug/knowledge-items/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAdminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const projectId = url.searchParams.get('projectId');
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
const adminDb = getAdminDb();
|
||||
|
||||
// Get knowledge_items collection
|
||||
const knowledgeItemsRef = adminDb.collection('knowledge_items');
|
||||
const snapshot = await knowledgeItemsRef
|
||||
.where('projectId', '==', projectId)
|
||||
.limit(20)
|
||||
.get();
|
||||
|
||||
const items = snapshot.docs.map(doc => ({
|
||||
id: doc.id,
|
||||
...doc.data(),
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
projectId,
|
||||
count: items.length,
|
||||
items,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[debug/knowledge-items] Error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to fetch knowledge items',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
36
app/api/debug/knowledge/route.ts
Normal file
36
app/api/debug/knowledge/route.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAdminDb } from '@/lib/firebase/admin';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const projectId = (url.searchParams.get('projectId') ?? '').trim();
|
||||
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
|
||||
}
|
||||
|
||||
const adminDb = getAdminDb();
|
||||
|
||||
// Use a simple filter query without orderBy to avoid requiring a composite index.
|
||||
const snapshot = await adminDb
|
||||
.collection('knowledge_items')
|
||||
.where('projectId', '==', projectId)
|
||||
.get();
|
||||
|
||||
const items = snapshot.docs.map((doc) => doc.data());
|
||||
|
||||
return NextResponse.json({ count: items.length, items });
|
||||
} catch (error) {
|
||||
console.error('[debug/knowledge] Failed to list knowledge items', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to list knowledge items',
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user