Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {
Expand Down
71 changes: 71 additions & 0 deletions server/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -754,6 +824,7 @@ export {
parseJsonlSessions,
renameProject,
deleteSession,
updateSessionSummary,
isProjectEmpty,
deleteProject,
addProjectManually,
Expand Down
34 changes: 33 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -551,6 +581,7 @@ function AppContent() {
onSessionSelect={handleSessionSelect}
onNewSession={handleNewSession}
onSessionDelete={handleSessionDelete}
onUpdateSessionSummary={handleUpdateSessionSummary}
onProjectDelete={handleProjectDelete}
isLoading={isLoadingProjects}
onRefresh={handleSidebarRefresh}
Expand Down Expand Up @@ -596,6 +627,7 @@ function AppContent() {
onSessionSelect={handleSessionSelect}
onNewSession={handleNewSession}
onSessionDelete={handleSessionDelete}
onUpdateSessionSummary={handleUpdateSessionSummary}
onProjectDelete={handleProjectDelete}
isLoading={isLoadingProjects}
onRefresh={handleSidebarRefresh}
Expand Down
9 changes: 7 additions & 2 deletions src/components/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function Sidebar({
onSessionSelect,
onNewSession,
onSessionDelete,
onUpdateSessionSummary,
onProjectDelete,
isLoading,
onRefresh,
Expand Down Expand Up @@ -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('');
Expand All @@ -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"
>
Expand Down
5 changes: 5 additions & 0 deletions src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down