mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-05 14:07:40 +00:00
* 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>
164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
import { useEffect } from 'react';
|
|
import type { TFunction } from 'i18next';
|
|
import type { LoadingProgress, Project, ProjectSession, SessionProvider } from '../../../../types/app';
|
|
import type {
|
|
LoadingSessionsByProject,
|
|
MCPServerStatus,
|
|
SessionWithProvider,
|
|
TouchHandlerFactory,
|
|
} from '../../types/types';
|
|
import SidebarProjectItem from './SidebarProjectItem';
|
|
import SidebarProjectsState from './SidebarProjectsState';
|
|
|
|
export type SidebarProjectListProps = {
|
|
projects: Project[];
|
|
filteredProjects: Project[];
|
|
selectedProject: Project | null;
|
|
selectedSession: ProjectSession | null;
|
|
isLoading: boolean;
|
|
loadingProgress: LoadingProgress | null;
|
|
expandedProjects: Set<string>;
|
|
editingProject: string | null;
|
|
editingName: string;
|
|
loadingSessions: LoadingSessionsByProject;
|
|
initialSessionsLoaded: Set<string>;
|
|
currentTime: Date;
|
|
editingSession: string | null;
|
|
editingSessionName: string;
|
|
deletingProjects: Set<string>;
|
|
tasksEnabled: boolean;
|
|
mcpServerStatus: MCPServerStatus;
|
|
getProjectSessions: (project: Project) => SessionWithProvider[];
|
|
isProjectStarred: (projectName: string) => boolean;
|
|
onEditingNameChange: (value: string) => void;
|
|
onToggleProject: (projectName: string) => void;
|
|
onProjectSelect: (project: Project) => void;
|
|
onToggleStarProject: (projectName: string) => void;
|
|
onStartEditingProject: (project: Project) => void;
|
|
onCancelEditingProject: () => void;
|
|
onSaveProjectName: (projectName: string) => void;
|
|
onDeleteProject: (project: Project) => void;
|
|
onSessionSelect: (session: SessionWithProvider, projectName: string) => void;
|
|
onDeleteSession: (
|
|
projectName: string,
|
|
sessionId: string,
|
|
sessionTitle: string,
|
|
provider: SessionProvider,
|
|
) => void;
|
|
onLoadMoreSessions: (project: Project) => void;
|
|
onNewSession: (project: Project) => void;
|
|
onEditingSessionNameChange: (value: string) => void;
|
|
onStartEditingSession: (sessionId: string, initialName: string) => void;
|
|
onCancelEditingSession: () => void;
|
|
onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void;
|
|
touchHandlerFactory: TouchHandlerFactory;
|
|
t: TFunction;
|
|
};
|
|
|
|
export default function SidebarProjectList({
|
|
projects,
|
|
filteredProjects,
|
|
selectedProject,
|
|
selectedSession,
|
|
isLoading,
|
|
loadingProgress,
|
|
expandedProjects,
|
|
editingProject,
|
|
editingName,
|
|
loadingSessions,
|
|
initialSessionsLoaded,
|
|
currentTime,
|
|
editingSession,
|
|
editingSessionName,
|
|
deletingProjects,
|
|
tasksEnabled,
|
|
mcpServerStatus,
|
|
getProjectSessions,
|
|
isProjectStarred,
|
|
onEditingNameChange,
|
|
onToggleProject,
|
|
onProjectSelect,
|
|
onToggleStarProject,
|
|
onStartEditingProject,
|
|
onCancelEditingProject,
|
|
onSaveProjectName,
|
|
onDeleteProject,
|
|
onSessionSelect,
|
|
onDeleteSession,
|
|
onLoadMoreSessions,
|
|
onNewSession,
|
|
onEditingSessionNameChange,
|
|
onStartEditingSession,
|
|
onCancelEditingSession,
|
|
onSaveEditingSession,
|
|
touchHandlerFactory,
|
|
t,
|
|
}: SidebarProjectListProps) {
|
|
const state = (
|
|
<SidebarProjectsState
|
|
isLoading={isLoading}
|
|
loadingProgress={loadingProgress}
|
|
projectsCount={projects.length}
|
|
filteredProjectsCount={filteredProjects.length}
|
|
t={t}
|
|
/>
|
|
);
|
|
|
|
useEffect(() => {
|
|
let baseTitle = 'CloudCLI UI';
|
|
const displayName = selectedProject?.displayName?.trim();
|
|
if (displayName) {
|
|
baseTitle = `${displayName} - ${baseTitle}`;
|
|
}
|
|
document.title = baseTitle;
|
|
}, [selectedProject]);
|
|
|
|
const showProjects = !isLoading && projects.length > 0 && filteredProjects.length > 0;
|
|
|
|
return (
|
|
<div className="md:space-y-1 pb-safe-area-inset-bottom">
|
|
{!showProjects
|
|
? state
|
|
: filteredProjects.map((project) => (
|
|
<SidebarProjectItem
|
|
key={project.name}
|
|
project={project}
|
|
selectedProject={selectedProject}
|
|
selectedSession={selectedSession}
|
|
isExpanded={expandedProjects.has(project.name)}
|
|
isDeleting={deletingProjects.has(project.name)}
|
|
isStarred={isProjectStarred(project.name)}
|
|
editingProject={editingProject}
|
|
editingName={editingName}
|
|
sessions={getProjectSessions(project)}
|
|
initialSessionsLoaded={initialSessionsLoaded.has(project.name)}
|
|
isLoadingSessions={Boolean(loadingSessions[project.name])}
|
|
currentTime={currentTime}
|
|
editingSession={editingSession}
|
|
editingSessionName={editingSessionName}
|
|
tasksEnabled={tasksEnabled}
|
|
mcpServerStatus={mcpServerStatus}
|
|
onEditingNameChange={onEditingNameChange}
|
|
onToggleProject={onToggleProject}
|
|
onProjectSelect={onProjectSelect}
|
|
onToggleStarProject={onToggleStarProject}
|
|
onStartEditingProject={onStartEditingProject}
|
|
onCancelEditingProject={onCancelEditingProject}
|
|
onSaveProjectName={onSaveProjectName}
|
|
onDeleteProject={onDeleteProject}
|
|
onSessionSelect={onSessionSelect}
|
|
onDeleteSession={onDeleteSession}
|
|
onLoadMoreSessions={onLoadMoreSessions}
|
|
onNewSession={onNewSession}
|
|
onEditingSessionNameChange={onEditingSessionNameChange}
|
|
onStartEditingSession={onStartEditingSession}
|
|
onCancelEditingSession={onCancelEditingSession}
|
|
onSaveEditingSession={onSaveEditingSession}
|
|
touchHandlerFactory={touchHandlerFactory}
|
|
t={t}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|