diff --git a/server/index.js b/server/index.js index ca451337..0995061b 100755 --- a/server/index.js +++ b/server/index.js @@ -36,7 +36,7 @@ import pty from 'node-pty'; import fetch from 'node-fetch'; import mime from 'mime-types'; -import { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache } from './projects.js'; +import { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, updateSessionSummary, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache } from './projects.js'; import { spawnClaude, abortClaudeSession } from './claude-cli.js'; import { spawnCursor, abortCursorSession } from './cursor-cli.js'; import gitRoutes from './routes/git.js'; @@ -262,6 +262,18 @@ app.delete('/api/projects/:projectName/sessions/:sessionId', authenticateToken, } }); +// Update session summary endpoint +app.put('/api/projects/:projectName/sessions/:sessionId/summary', authenticateToken, async (req, res) => { + try { + const { projectName, sessionId } = req.params; + const { summary } = req.body; + await updateSessionSummary(projectName, sessionId, summary); + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + // Delete project endpoint (only if empty) app.delete('/api/projects/:projectName', authenticateToken, async (req, res) => { try { diff --git a/server/projects.js b/server/projects.js index 1f228951..3c0df059 100755 --- a/server/projects.js +++ b/server/projects.js @@ -635,6 +635,76 @@ async function addProjectManually(projectPath, displayName = null) { }; } +async function updateSessionSummary(projectName, sessionId, newSummary) { + const projectDir = path.join(process.env.HOME, '.claude', 'projects', projectName); + + try { + const files = await fs.readdir(projectDir); + const jsonlFiles = files.filter(file => file.endsWith('.jsonl')); + + if (jsonlFiles.length === 0) { + throw new Error('No session files found for this project'); + } + + // Find the file containing the session + for (const file of jsonlFiles) { + const jsonlFile = path.join(projectDir, file); + const content = await fs.readFile(jsonlFile, 'utf8'); + const lines = content.split('\n').filter(line => line.trim()); + + // Check if this file contains the session + const hasSession = lines.some(line => { + try { + const data = JSON.parse(line); + return data.sessionId === sessionId; + } catch (e) { + return false; + } + }); + + if (hasSession) { + // Check if summary entry already exists + let summaryEntryExists = false; + const updatedLines = lines.map(line => { + try { + const data = JSON.parse(line); + if (data.sessionId === sessionId && data.type === 'summary') { + summaryEntryExists = true; + return JSON.stringify({ + ...data, + summary: newSummary, + timestamp: new Date().toISOString() + }); + } + return line; + } catch (e) { + return line; + } + }); + + // If no summary entry exists, create one + if (!summaryEntryExists) { + const summaryEntry = { + sessionId: sessionId, + type: 'summary', + summary: newSummary, + timestamp: new Date().toISOString() + }; + updatedLines.unshift(JSON.stringify(summaryEntry)); + } + + // Write back to file + await fs.writeFile(jsonlFile, updatedLines.join('\n') + '\n', 'utf8'); + return; + } + } + + throw new Error('Session not found'); + } catch (error) { + throw new Error(`Failed to update session summary: ${error.message}`); + } +} + // Fetch Cursor sessions for a given project path async function getCursorSessions(projectPath) { try { @@ -754,6 +824,7 @@ export { parseJsonlSessions, renameProject, deleteSession, + updateSessionSummary, isProjectEmpty, deleteProject, addProjectManually, diff --git a/src/App.jsx b/src/App.jsx index 1cbd7eb8..adb1a157 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -347,7 +347,37 @@ function AppContent() { ); }; - + const handleUpdateSessionSummary = async (projectName, sessionId, newSummary) => { + try { + const response = await api.updateSessionSummary(projectName, sessionId, newSummary); + if (response.ok) { + // Update the session summary in local state + setProjects(prevProjects => + prevProjects.map(project => + project.name === projectName + ? { + ...project, + sessions: project.sessions?.map(session => + session.id === sessionId + ? { ...session, summary: newSummary } + : session + ) || [] + } + : project + ) + ); + + // Update selected session if it's the one being renamed + if (selectedSession?.id === sessionId) { + setSelectedSession(prev => ({ ...prev, summary: newSummary })); + } + } else { + console.error('Failed to update session summary'); + } + } catch (error) { + console.error('Error updating session summary:', error); + } + }; const handleSidebarRefresh = async () => { // Refresh only the sessions for all projects, don't change selected state @@ -551,6 +581,7 @@ function AppContent() { onSessionSelect={handleSessionSelect} onNewSession={handleNewSession} onSessionDelete={handleSessionDelete} + onUpdateSessionSummary={handleUpdateSessionSummary} onProjectDelete={handleProjectDelete} isLoading={isLoadingProjects} onRefresh={handleSidebarRefresh} @@ -596,6 +627,7 @@ function AppContent() { onSessionSelect={handleSessionSelect} onNewSession={handleNewSession} onSessionDelete={handleSessionDelete} + onUpdateSessionSummary={handleUpdateSessionSummary} onProjectDelete={handleProjectDelete} isLoading={isLoadingProjects} onRefresh={handleSidebarRefresh} diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 36f3bbfe..242eba77 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -44,6 +44,7 @@ function Sidebar({ onSessionSelect, onNewSession, onSessionDelete, + onUpdateSessionSummary, onProjectDelete, isLoading, onRefresh, @@ -1128,7 +1129,9 @@ function Sidebar({ onKeyDown={(e) => { e.stopPropagation(); if (e.key === 'Enter') { - updateSessionSummary(project.name, session.id, editingSessionName); + onUpdateSessionSummary(project.name, session.id, editingSessionName); + setEditingSession(null); + setEditingSessionName(''); } else if (e.key === 'Escape') { setEditingSession(null); setEditingSessionName(''); @@ -1142,7 +1145,9 @@ function Sidebar({ className="w-6 h-6 bg-green-50 hover:bg-green-100 dark:bg-green-900/20 dark:hover:bg-green-900/40 rounded flex items-center justify-center" onClick={(e) => { e.stopPropagation(); - updateSessionSummary(project.name, session.id, editingSessionName); + onUpdateSessionSummary(project.name, session.id, editingSessionName); + setEditingSession(null); + setEditingSessionName(''); }} title="Save" > diff --git a/src/utils/api.js b/src/utils/api.js index 22978512..b1be1027 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -62,6 +62,11 @@ export const api = { authenticatedFetch(`/api/projects/${projectName}/sessions/${sessionId}`, { method: 'DELETE', }), + updateSessionSummary: (projectName, sessionId, summary) => + authenticatedFetch(`/api/projects/${projectName}/sessions/${sessionId}/summary`, { + method: 'PUT', + body: JSON.stringify({ summary }), + }), deleteProject: (projectName) => authenticatedFetch(`/api/projects/${projectName}`, { method: 'DELETE',