From 198e3da89b353780f53a91888384da9118995e81 Mon Sep 17 00:00:00 2001 From: PaloSP <32291845+PaloSP@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:11:26 +0100 Subject: [PATCH] feat: implement session rename with SQLite storage (#413) * feat: implement session rename with SQLite storage (closes #72, fixes #358) - Add session_names table to store custom display names per provider - Add PUT /api/sessions/:sessionId/rename endpoint - Replace stub updateSessionSummary with real API call - Apply custom names across all providers (Claude, Codex, Cursor) - Fix project rename destroying config (spread merge instead of overwrite) - Thread provider parameter through sidebar component chain - Add i18n error messages for rename failures (en, ja, ko, zh-CN) * fix: address CodeRabbit review feedback for session rename - Log migration errors instead of swallowing them silently (db.js) - Add try/catch to applyCustomSessionNames to prevent getProjects abort - Move applyCustomSessionNames to db.js as shared helper (DRY) - Fix Cursor getSessionName to check session.summary for custom names - Move edit state clearing to finally block in updateSessionSummary - Sanitize sessionId, add 500-char summary limit, validate provider whitelist - Remove dead applyCustomSessionNames call on empty manual project sessions * fix: reject sessionId on mismatch instead of silent normalization Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: enable rename for all providers, add Gemini support, clean up orphans - Enable rename UI (pencil icon) for Codex, Cursor, and Gemini sessions - Keep delete button hidden for Cursor (no backend delete endpoint) - Add 'gemini' to VALID_PROVIDERS and hoist to module scope - Add sessionNamesDb.deleteName on session delete (claude, codex, gemini) - Fix token-usage endpoint sessionId mismatch validation - Remove redundant try/catch in sessionNamesDb methods - Let session_names migration errors propagate to outer handler --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Haileyesus <118998054+blackmammoth@users.noreply.github.com> --- server/database/db.js | 68 +++++++++++++++++++ server/database/init.sql | 15 +++- server/index.js | 34 +++++++++- server/projects.js | 15 +++- server/routes/codex.js | 3 + server/routes/cursor.js | 7 +- server/routes/gemini.js | 2 + .../sidebar/hooks/useSidebarController.ts | 30 ++++++-- src/components/sidebar/utils/utils.ts | 2 +- src/components/sidebar/view/Sidebar.tsx | 6 +- .../view/subcomponents/SidebarProjectItem.tsx | 2 +- .../view/subcomponents/SidebarProjectList.tsx | 2 +- .../subcomponents/SidebarProjectSessions.tsx | 2 +- .../view/subcomponents/SidebarSessionItem.tsx | 42 ++++++------ src/i18n/locales/en/sidebar.json | 2 + src/i18n/locales/ja/sidebar.json | 2 + src/i18n/locales/ko/sidebar.json | 2 + src/i18n/locales/zh-CN/sidebar.json | 2 + src/utils/api.js | 5 ++ 19 files changed, 201 insertions(+), 42 deletions(-) diff --git a/server/database/db.js b/server/database/db.js index e8055457..cd112087 100644 --- a/server/database/db.js +++ b/server/database/db.js @@ -91,6 +91,18 @@ const runMigrations = () => { db.exec('ALTER TABLE users ADD COLUMN has_completed_onboarding BOOLEAN DEFAULT 0'); } + // Create session_names table if it doesn't exist (for existing installations) + db.exec(`CREATE TABLE IF NOT EXISTS session_names ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT NOT NULL, + provider TEXT NOT NULL DEFAULT 'claude', + custom_name TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(session_id, provider) + )`); + db.exec('CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider)'); + console.log('Database migrations completed successfully'); } catch (error) { console.error('Error running migrations:', error.message); @@ -348,6 +360,60 @@ const credentialsDb = { } }; +// Session custom names database operations +const sessionNamesDb = { + // Set (insert or update) a custom session name + setName: (sessionId, provider, customName) => { + db.prepare(` + INSERT INTO session_names (session_id, provider, custom_name) + VALUES (?, ?, ?) + ON CONFLICT(session_id, provider) + DO UPDATE SET custom_name = excluded.custom_name, updated_at = CURRENT_TIMESTAMP + `).run(sessionId, provider, customName); + }, + + // Get a single custom session name + getName: (sessionId, provider) => { + const row = db.prepare( + 'SELECT custom_name FROM session_names WHERE session_id = ? AND provider = ?' + ).get(sessionId, provider); + return row?.custom_name || null; + }, + + // Batch lookup — returns Map + getNames: (sessionIds, provider) => { + if (!sessionIds.length) return new Map(); + const placeholders = sessionIds.map(() => '?').join(','); + const rows = db.prepare( + `SELECT session_id, custom_name FROM session_names + WHERE session_id IN (${placeholders}) AND provider = ?` + ).all(...sessionIds, provider); + return new Map(rows.map(r => [r.session_id, r.custom_name])); + }, + + // Delete a custom session name + deleteName: (sessionId, provider) => { + return db.prepare( + 'DELETE FROM session_names WHERE session_id = ? AND provider = ?' + ).run(sessionId, provider).changes > 0; + }, +}; + +// Apply custom session names from the database (overrides CLI-generated summaries) +function applyCustomSessionNames(sessions, provider) { + if (!sessions?.length) return; + try { + const ids = sessions.map(s => s.id); + const customNames = sessionNamesDb.getNames(ids, provider); + for (const session of sessions) { + const custom = customNames.get(session.id); + if (custom) session.summary = custom; + } + } catch (error) { + console.warn(`[DB] Failed to apply custom session names for ${provider}:`, error.message); + } +} + // Backward compatibility - keep old names pointing to new system const githubTokensDb = { createGithubToken: (userId, tokenName, githubToken, description = null) => { @@ -373,5 +439,7 @@ export { userDb, apiKeysDb, credentialsDb, + sessionNamesDb, + applyCustomSessionNames, githubTokensDb // Backward compatibility }; \ No newline at end of file diff --git a/server/database/init.sql b/server/database/init.sql index e52daef4..bbab5f40 100644 --- a/server/database/init.sql +++ b/server/database/init.sql @@ -49,4 +49,17 @@ CREATE TABLE IF NOT EXISTS user_credentials ( CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id); CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type); -CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active); \ No newline at end of file +CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active); + +-- Session custom names (provider-agnostic display name overrides) +CREATE TABLE IF NOT EXISTS session_names ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT NOT NULL, + provider TEXT NOT NULL DEFAULT 'claude', + custom_name TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(session_id, provider) +); + +CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider); \ No newline at end of file diff --git a/server/index.js b/server/index.js index 7d64a9ad..07e635db 100755 --- a/server/index.js +++ b/server/index.js @@ -64,10 +64,12 @@ import cliAuthRoutes from './routes/cli-auth.js'; import userRoutes from './routes/user.js'; import codexRoutes from './routes/codex.js'; import geminiRoutes from './routes/gemini.js'; -import { initializeDatabase } from './database/db.js'; +import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js'; import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js'; import { IS_PLATFORM } from './constants/config.js'; +const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini']; + // File system watchers for provider project/session folders const PROVIDER_WATCH_PATHS = [ { provider: 'claude', rootPath: path.join(os.homedir(), '.claude', 'projects') }, @@ -493,6 +495,7 @@ app.get('/api/projects/:projectName/sessions', authenticateToken, async (req, re try { const { limit = 5, offset = 0 } = req.query; const result = await getSessions(req.params.projectName, parseInt(limit), parseInt(offset)); + applyCustomSessionNames(result.sessions, 'claude'); res.json(result); } catch (error) { res.status(500).json({ error: error.message }); @@ -541,6 +544,7 @@ app.delete('/api/projects/:projectName/sessions/:sessionId', authenticateToken, const { projectName, sessionId } = req.params; console.log(`[API] Deleting session: ${sessionId} from project: ${projectName}`); await deleteSession(projectName, sessionId); + sessionNamesDb.deleteName(sessionId, 'claude'); console.log(`[API] Session ${sessionId} deleted successfully`); res.json({ success: true }); } catch (error) { @@ -549,6 +553,32 @@ app.delete('/api/projects/:projectName/sessions/:sessionId', authenticateToken, } }); +// Rename session endpoint +app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res) => { + try { + const { sessionId } = req.params; + const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9._-]/g, ''); + if (!safeSessionId || safeSessionId !== String(sessionId)) { + return res.status(400).json({ error: 'Invalid sessionId' }); + } + const { summary, provider } = req.body; + if (!summary || typeof summary !== 'string' || summary.trim() === '') { + return res.status(400).json({ error: 'Summary is required' }); + } + if (summary.trim().length > 500) { + return res.status(400).json({ error: 'Summary must not exceed 500 characters' }); + } + if (!provider || !VALID_PROVIDERS.includes(provider)) { + return res.status(400).json({ error: `Provider must be one of: ${VALID_PROVIDERS.join(', ')}` }); + } + sessionNamesDb.setName(safeSessionId, provider, summary.trim()); + res.json({ success: true }); + } catch (error) { + console.error(`[API] Error renaming session ${req.params.sessionId}:`, error); + res.status(500).json({ error: error.message }); + } +}); + // Delete project endpoint (force=true to delete with sessions) app.delete('/api/projects/:projectName', authenticateToken, async (req, res) => { try { @@ -2112,7 +2142,7 @@ app.get('/api/projects/:projectName/sessions/:sessionId/token-usage', authentica // Allow only safe characters in sessionId const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9._-]/g, ''); - if (!safeSessionId) { + if (!safeSessionId || safeSessionId !== String(sessionId)) { return res.status(400).json({ error: 'Invalid sessionId' }); } diff --git a/server/projects.js b/server/projects.js index bbe92cbe..d48bcb2b 100755 --- a/server/projects.js +++ b/server/projects.js @@ -66,6 +66,7 @@ import sqlite3 from 'sqlite3'; import { open } from 'sqlite'; import os from 'os'; import sessionManager from './sessionManager.js'; +import { applyCustomSessionNames } from './database/db.js'; // Import TaskMaster detection functions async function detectTaskMasterFolder(projectPath) { @@ -458,6 +459,7 @@ async function getProjects(progressCallback = null) { total: 0 }; } + applyCustomSessionNames(project.sessions, 'claude'); // Also fetch Cursor sessions for this project try { @@ -466,6 +468,7 @@ async function getProjects(progressCallback = null) { console.warn(`Could not load Cursor sessions for project ${entry.name}:`, e.message); project.cursorSessions = []; } + applyCustomSessionNames(project.cursorSessions, 'cursor'); // Also fetch Codex sessions for this project try { @@ -476,6 +479,7 @@ async function getProjects(progressCallback = null) { console.warn(`Could not load Codex sessions for project ${entry.name}:`, e.message); project.codexSessions = []; } + applyCustomSessionNames(project.codexSessions, 'codex'); // Also fetch Gemini sessions for this project try { @@ -484,6 +488,7 @@ async function getProjects(progressCallback = null) { console.warn(`Could not load Gemini sessions for project ${entry.name}:`, e.message); project.geminiSessions = []; } + applyCustomSessionNames(project.geminiSessions, 'gemini'); // Add TaskMaster detection try { @@ -567,6 +572,7 @@ async function getProjects(progressCallback = null) { } catch (e) { console.warn(`Could not load Cursor sessions for manual project ${projectName}:`, e.message); } + applyCustomSessionNames(project.cursorSessions, 'cursor'); // Try to fetch Codex sessions for manual projects too try { @@ -576,6 +582,7 @@ async function getProjects(progressCallback = null) { } catch (e) { console.warn(`Could not load Codex sessions for manual project ${projectName}:`, e.message); } + applyCustomSessionNames(project.codexSessions, 'codex'); // Try to fetch Gemini sessions for manual projects too try { @@ -583,6 +590,7 @@ async function getProjects(progressCallback = null) { } catch (e) { console.warn(`Could not load Gemini sessions for manual project ${projectName}:`, e.message); } + applyCustomSessionNames(project.geminiSessions, 'gemini'); // Add TaskMaster detection for manual projects try { @@ -1071,10 +1079,13 @@ async function renameProject(projectName, newDisplayName) { if (!newDisplayName || newDisplayName.trim() === '') { // Remove custom name if empty, will fall back to auto-generated - delete config[projectName]; + if (config[projectName]) { + delete config[projectName].displayName; + } } else { - // Set custom display name + // Set custom display name, preserving other properties (manuallyAdded, originalPath) config[projectName] = { + ...config[projectName], displayName: newDisplayName.trim() }; } diff --git a/server/routes/codex.js b/server/routes/codex.js index 94737702..5ed52914 100644 --- a/server/routes/codex.js +++ b/server/routes/codex.js @@ -5,6 +5,7 @@ import path from 'path'; import os from 'os'; import TOML from '@iarna/toml'; import { getCodexSessions, getCodexSessionMessages, deleteCodexSession } from '../projects.js'; +import { applyCustomSessionNames, sessionNamesDb } from '../database/db.js'; const router = express.Router(); @@ -59,6 +60,7 @@ router.get('/sessions', async (req, res) => { } const sessions = await getCodexSessions(projectPath); + applyCustomSessionNames(sessions, 'codex'); res.json({ success: true, sessions }); } catch (error) { console.error('Error fetching Codex sessions:', error); @@ -88,6 +90,7 @@ router.delete('/sessions/:sessionId', async (req, res) => { try { const { sessionId } = req.params; await deleteCodexSession(sessionId); + sessionNamesDb.deleteName(sessionId, 'codex'); res.json({ success: true }); } catch (error) { console.error(`Error deleting Codex session ${req.params.sessionId}:`, error); diff --git a/server/routes/cursor.js b/server/routes/cursor.js index 4471ab75..02269e45 100644 --- a/server/routes/cursor.js +++ b/server/routes/cursor.js @@ -7,6 +7,7 @@ import sqlite3 from 'sqlite3'; import { open } from 'sqlite'; import crypto from 'crypto'; import { CURSOR_MODELS } from '../../shared/modelConstants.js'; +import { applyCustomSessionNames } from '../database/db.js'; const router = express.Router(); @@ -560,8 +561,10 @@ router.get('/sessions', async (req, res) => { return new Date(b.createdAt) - new Date(a.createdAt); }); - res.json({ - success: true, + applyCustomSessionNames(sessions, 'cursor'); + + res.json({ + success: true, sessions: sessions, cwdId: cwdId, path: cursorChatsPath diff --git a/server/routes/gemini.js b/server/routes/gemini.js index 2a30f993..7c2f3425 100644 --- a/server/routes/gemini.js +++ b/server/routes/gemini.js @@ -1,5 +1,6 @@ import express from 'express'; import sessionManager from '../sessionManager.js'; +import { sessionNamesDb } from '../database/db.js'; const router = express.Router(); @@ -36,6 +37,7 @@ router.delete('/sessions/:sessionId', async (req, res) => { } await sessionManager.deleteSession(sessionId); + sessionNamesDb.deleteName(sessionId, 'gemini'); res.json({ success: true }); } catch (error) { console.error(`Error deleting Gemini session ${req.params.sessionId}:`, error); diff --git a/src/components/sidebar/hooks/useSidebarController.ts b/src/components/sidebar/hooks/useSidebarController.ts index 33090d80..e1a7a167 100644 --- a/src/components/sidebar/hooks/useSidebarController.ts +++ b/src/components/sidebar/hooks/useSidebarController.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import type React from 'react'; import type { TFunction } from 'i18next'; import { api } from '../../../utils/api'; -import type { Project, ProjectSession } from '../../../types/app'; +import type { Project, ProjectSession, SessionProvider } from '../../../types/app'; import type { AdditionalSessionsByProject, DeleteProjectConfirmation, @@ -405,12 +405,30 @@ export function useSidebarController({ }, [onRefresh]); const updateSessionSummary = useCallback( - async (_projectName: string, _sessionId: string, _summary: string) => { - // Session rename endpoint is not currently exposed on the API. - setEditingSession(null); - setEditingSessionName(''); + async (_projectName: string, sessionId: string, summary: string, provider: SessionProvider) => { + const trimmed = summary.trim(); + if (!trimmed) { + setEditingSession(null); + setEditingSessionName(''); + return; + } + try { + const response = await api.renameSession(sessionId, trimmed, provider); + if (response.ok) { + await onRefresh(); + } else { + console.error('[Sidebar] Failed to rename session:', response.status); + alert(t('messages.renameSessionFailed')); + } + } catch (error) { + console.error('[Sidebar] Error renaming session:', error); + alert(t('messages.renameSessionError')); + } finally { + setEditingSession(null); + setEditingSessionName(''); + } }, - [], + [onRefresh, t], ); const collapseSidebar = useCallback(() => { diff --git a/src/components/sidebar/utils/utils.ts b/src/components/sidebar/utils/utils.ts index 89fb7df2..a3ec377c 100644 --- a/src/components/sidebar/utils/utils.ts +++ b/src/components/sidebar/utils/utils.ts @@ -53,7 +53,7 @@ export const getSessionDate = (session: SessionWithProvider): Date => { export const getSessionName = (session: SessionWithProvider, t: TFunction): string => { if (session.__provider === 'cursor') { - return session.name || t('projects.untitledSession'); + return session.summary || session.name || t('projects.untitledSession'); } if (session.__provider === 'codex') { diff --git a/src/components/sidebar/view/Sidebar.tsx b/src/components/sidebar/view/Sidebar.tsx index 842bda80..3658880d 100644 --- a/src/components/sidebar/view/Sidebar.tsx +++ b/src/components/sidebar/view/Sidebar.tsx @@ -9,7 +9,7 @@ import { useTasksSettings } from '../../../contexts/TasksSettingsContext'; import SidebarCollapsed from './subcomponents/SidebarCollapsed'; import SidebarContent from './subcomponents/SidebarContent'; import SidebarModals from './subcomponents/SidebarModals'; -import type { Project } from '../../../types/app'; +import type { Project, SessionProvider } from '../../../types/app'; import type { SidebarProjectListProps } from './subcomponents/SidebarProjectList'; import type { MCPServerStatus, SidebarProps } from '../types/types'; @@ -172,8 +172,8 @@ function Sidebar({ setEditingSession(null); setEditingSessionName(''); }, - onSaveEditingSession: (projectName, sessionId, summary) => { - void updateSessionSummary(projectName, sessionId, summary); + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => { + void updateSessionSummary(projectName, sessionId, summary, provider); }, touchHandlerFactory: handleTouchClick, t, diff --git a/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx b/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx index 4d28b3b2..7b77aa6a 100644 --- a/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx @@ -45,7 +45,7 @@ type SidebarProjectItemProps = { onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; touchHandlerFactory: TouchHandlerFactory; t: TFunction; }; diff --git a/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx b/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx index 1e1dbd55..2ec0a4ca 100644 --- a/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx @@ -50,7 +50,7 @@ export type SidebarProjectListProps = { onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; touchHandlerFactory: TouchHandlerFactory; t: TFunction; }; diff --git a/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx b/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx index e4ff9557..c16734f3 100644 --- a/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx @@ -18,7 +18,7 @@ type SidebarProjectSessionsProps = { onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; onProjectSelect: (project: Project) => void; onSessionSelect: (session: SessionWithProvider, projectName: string) => void; onDeleteSession: ( diff --git a/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx b/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx index 6149aba9..3e2ff7c4 100644 --- a/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx @@ -19,7 +19,7 @@ type SidebarSessionItemProps = { onEditingSessionNameChange: (value: string) => void; onStartEditingSession: (sessionId: string, initialName: string) => void; onCancelEditingSession: () => void; - onSaveEditingSession: (projectName: string, sessionId: string, summary: string) => void; + onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void; onProjectSelect: (project: Project) => void; onSessionSelect: (session: SessionWithProvider, projectName: string) => void; onDeleteSession: ( @@ -58,7 +58,7 @@ export default function SidebarSessionItem({ }; const saveEditedSession = () => { - onSaveEditingSession(project.name, session.id, editingSessionName); + onSaveEditingSession(project.name, session.id, editingSessionName, session.__provider); }; const requestDeleteSession = () => { @@ -161,9 +161,8 @@ export default function SidebarSessionItem({ - {!sessionView.isCursorSession && ( -
- {editingSession === session.id && !sessionView.isCodexSession ? ( +
+ {editingSession === session.id ? ( <> ) : ( <> - {!sessionView.isCodexSession && ( - - )} + {!sessionView.isCursorSession && ( + + )} )}
- )}
); diff --git a/src/i18n/locales/en/sidebar.json b/src/i18n/locales/en/sidebar.json index f85e8876..630ad7df 100644 --- a/src/i18n/locales/en/sidebar.json +++ b/src/i18n/locales/en/sidebar.json @@ -93,6 +93,8 @@ "enterProjectPath": "Please enter a project path", "deleteSessionFailed": "Failed to delete session. Please try again.", "deleteSessionError": "Error deleting session. Please try again.", + "renameSessionFailed": "Failed to rename session. Please try again.", + "renameSessionError": "Error renaming session. Please try again.", "deleteProjectFailed": "Failed to delete project. Please try again.", "deleteProjectError": "Error deleting project. Please try again.", "createProjectFailed": "Failed to create project. Please try again.", diff --git a/src/i18n/locales/ja/sidebar.json b/src/i18n/locales/ja/sidebar.json index 6ef510d4..17813411 100644 --- a/src/i18n/locales/ja/sidebar.json +++ b/src/i18n/locales/ja/sidebar.json @@ -93,6 +93,8 @@ "enterProjectPath": "プロジェクトのパスを入力してください", "deleteSessionFailed": "セッションの削除に失敗しました。もう一度お試しください。", "deleteSessionError": "セッションの削除でエラーが発生しました。もう一度お試しください。", + "renameSessionFailed": "セッション名の変更に失敗しました。もう一度お試しください。", + "renameSessionError": "セッション名の変更でエラーが発生しました。もう一度お試しください。", "deleteProjectFailed": "プロジェクトの削除に失敗しました。もう一度お試しください。", "deleteProjectError": "プロジェクトの削除でエラーが発生しました。もう一度お試しください。", "createProjectFailed": "プロジェクトの作成に失敗しました。もう一度お試しください。", diff --git a/src/i18n/locales/ko/sidebar.json b/src/i18n/locales/ko/sidebar.json index 1fdb4164..2eaca911 100644 --- a/src/i18n/locales/ko/sidebar.json +++ b/src/i18n/locales/ko/sidebar.json @@ -93,6 +93,8 @@ "enterProjectPath": "프로젝트 경로를 입력해주세요", "deleteSessionFailed": "세션 삭제 실패. 다시 시도해주세요.", "deleteSessionError": "세션 삭제 오류. 다시 시도해주세요.", + "renameSessionFailed": "세션 이름 변경 실패. 다시 시도해주세요.", + "renameSessionError": "세션 이름 변경 오류. 다시 시도해주세요.", "deleteProjectFailed": "프로젝트 삭제 실패. 다시 시도해주세요.", "deleteProjectError": "프로젝트 삭제 오류. 다시 시도해주세요.", "createProjectFailed": "프로젝트 생성 실패. 다시 시도해주세요.", diff --git a/src/i18n/locales/zh-CN/sidebar.json b/src/i18n/locales/zh-CN/sidebar.json index e75aad02..5e823f45 100644 --- a/src/i18n/locales/zh-CN/sidebar.json +++ b/src/i18n/locales/zh-CN/sidebar.json @@ -93,6 +93,8 @@ "enterProjectPath": "请输入项目路径", "deleteSessionFailed": "删除会话失败,请重试。", "deleteSessionError": "删除会话时出错,请重试。", + "renameSessionFailed": "重命名会话失败,请重试。", + "renameSessionError": "重命名会话时出错,请重试。", "deleteProjectFailed": "删除项目失败,请重试。", "deleteProjectError": "删除项目时出错,请重试。", "createProjectFailed": "创建项目失败,请重试。", diff --git a/src/utils/api.js b/src/utils/api.js index a058145c..47c7c2eb 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -77,6 +77,11 @@ export const api = { authenticatedFetch(`/api/projects/${projectName}/sessions/${sessionId}`, { method: 'DELETE', }), + renameSession: (sessionId, summary, provider) => + authenticatedFetch(`/api/sessions/${sessionId}/rename`, { + method: 'PUT', + body: JSON.stringify({ summary, provider }), + }), deleteCodexSession: (sessionId) => authenticatedFetch(`/api/codex/sessions/${sessionId}`, { method: 'DELETE',