diff --git a/server.js b/server.js index c598941c..61ae2ce0 100644 --- a/server.js +++ b/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 ===== // Create a new terminal