feat: add smart project suggestions endpoint
Added GET /api/projects/suggestions endpoint that provides intelligent project suggestions based on session context. The endpoint: - Takes sessionId as a required query parameter - Retrieves session from in-memory or historical sessions - Calculates scores for each project using multiple criteria: * Directory match (90 points): session workingDir === project path * Subdirectory match (50 points): session workingDir starts with project path * Used today (20 points): project lastActivity < 1 day ago * Used this week (10 points): project lastActivity < 7 days ago * Name similarity (15 points): overlap between session dir name and project name - Returns top 3 scored suggestions with reasons - Also returns all projects sorted alphabetically - Filters out projects with zero scores from suggestions - Handles missing sessions with appropriate error responses Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
114
server.js
114
server.js
@@ -1199,6 +1199,120 @@ app.get('/api/recycle-bin', requireAuth, (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/projects/suggestions - Get smart project suggestions for a session
|
||||||
|
app.get('/api/projects/suggestions', requireAuth, (req, res) => {
|
||||||
|
try {
|
||||||
|
const { sessionId } = req.query;
|
||||||
|
|
||||||
|
// Validate sessionId parameter
|
||||||
|
if (!sessionId) {
|
||||||
|
return res.status(400).json({ error: 'sessionId parameter is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the session from in-memory or historical
|
||||||
|
let session = claudeService.sessions.get(sessionId);
|
||||||
|
if (!session) {
|
||||||
|
// Try historical sessions
|
||||||
|
const historicalSessions = claudeService.loadHistoricalSessions();
|
||||||
|
session = historicalSessions.find(s => s.id === sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return res.status(404).json({ error: 'Session not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all active projects
|
||||||
|
const projects = db.prepare(`
|
||||||
|
SELECT id, name, icon, color, path, lastActivity
|
||||||
|
FROM projects
|
||||||
|
WHERE deletedAt IS NULL
|
||||||
|
`).all();
|
||||||
|
|
||||||
|
const sessionWorkingDir = session.workingDir || '';
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// Calculate scores for each project
|
||||||
|
const scoredProjects = projects.map(project => {
|
||||||
|
let score = 0;
|
||||||
|
const reasons = [];
|
||||||
|
|
||||||
|
// 1. Directory match: session workingDir === project path (90 points)
|
||||||
|
if (sessionWorkingDir === project.path) {
|
||||||
|
score += 90;
|
||||||
|
reasons.push('Same directory');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Subdirectory: session workingDir startsWith project path (50 points)
|
||||||
|
// Only if not already an exact match
|
||||||
|
else if (sessionWorkingDir.startsWith(project.path + '/')) {
|
||||||
|
score += 50;
|
||||||
|
reasons.push('Subdirectory');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Recent use: project used today (<1 day ago) (20 points)
|
||||||
|
if (project.lastActivity) {
|
||||||
|
const lastActivityDate = new Date(project.lastActivity);
|
||||||
|
const daysSinceActivity = Math.floor((now - lastActivityDate) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (daysSinceActivity < 1) {
|
||||||
|
score += 20;
|
||||||
|
reasons.push('Used today');
|
||||||
|
}
|
||||||
|
// 4. Week-old use: project used this week (<7 days ago) (10 points)
|
||||||
|
else if (daysSinceActivity < 7) {
|
||||||
|
score += 10;
|
||||||
|
reasons.push(`Used ${daysSinceActivity} days ago`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Name overlap: check if session workingDir contains project name or vice versa (15 points)
|
||||||
|
const sessionDirName = path.basename(sessionWorkingDir).toLowerCase();
|
||||||
|
const projectNameLower = project.name.toLowerCase();
|
||||||
|
|
||||||
|
if (sessionDirName.includes(projectNameLower) || projectNameLower.includes(sessionDirName)) {
|
||||||
|
score += 15;
|
||||||
|
reasons.push('Similar name');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: project.id,
|
||||||
|
name: project.name,
|
||||||
|
icon: project.icon,
|
||||||
|
color: project.color,
|
||||||
|
score,
|
||||||
|
reasons
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by score descending
|
||||||
|
scoredProjects.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
// Get top 3 suggestions
|
||||||
|
const suggestions = scoredProjects
|
||||||
|
.filter(p => p.score > 0) // Only include projects with positive scores
|
||||||
|
.slice(0, 3);
|
||||||
|
|
||||||
|
// Get all projects sorted by name
|
||||||
|
const allProjects = projects
|
||||||
|
.map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
icon: p.icon,
|
||||||
|
color: p.color
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
suggestions,
|
||||||
|
allProjects
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting project suggestions:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to get project suggestions' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ===== Terminal API Endpoints =====
|
// ===== Terminal API Endpoints =====
|
||||||
|
|
||||||
// Create a new terminal
|
// Create a new terminal
|
||||||
|
|||||||
Reference in New Issue
Block a user