import React, { useState, useEffect, useCallback } from 'react'; import { Project } from '../../projects'; import { Session, fetchSessions } from '../../sessions'; import { getProject as fetchProject, removeSessionFromProject, deleteProject, addSessionToProject, } from '../../projects'; import { Button } from '../ui/button'; import { ArrowLeft, Loader, RefreshCcw, Edit, Trash2, Folder, MessageSquareText, ChevronLeft, LoaderCircle, AlertCircle, Calendar, Target, } from 'lucide-react'; import { toastError, toastSuccess } from '../../toasts'; import { formatMessageTimestamp } from '../../utils/timeUtils'; import AddSessionToProjectModal from './AddSessionToProjectModal'; import UpdateProjectModal from './UpdateProjectModal'; import { MainPanelLayout } from '../Layout/MainPanelLayout'; import { ScrollArea } from '../ui/scroll-area'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '../ui/alert-dialog'; import { ChatSmart } from '../icons'; import { View, ViewOptions } from '../../App'; import { Card } from '../ui/card'; interface ProjectDetailsViewProps { projectId: string; onBack: () => void; setView: (view: View, viewOptions?: ViewOptions) => void; } // Custom ProjectHeader component similar to SessionHistoryView style const ProjectHeader: React.FC<{ onBack: () => void; children: React.ReactNode; title: string; actionButtons?: React.ReactNode; }> = ({ onBack, children, title, actionButtons }) => { return (

{title}

{children}
{actionButtons &&
{actionButtons}
}
); }; // New component for displaying project sessions with consistent styling const ProjectSessions: React.FC<{ sessions: Session[]; isLoading: boolean; error: string | null; onRetry: () => void; onRemoveSession: (sessionId: string) => void; onAddSession: () => void; }> = ({ sessions, isLoading, error, onRetry }) => { return (
{isLoading ? (
) : error ? (

Error Loading Project Details

{error}

) : sessions?.length > 0 ? (
{sessions.map((session) => (

{session.metadata.description || session.id}

{formatMessageTimestamp(Date.parse(session.modified) / 1000)}
{session.metadata.working_dir}
{session.metadata.message_count}
{session.metadata.total_tokens !== null && (
{session.metadata.total_tokens.toLocaleString()}
)}
{/* */}
))}
) : (

No sessions in this project

Add sessions to this project to keep your work organized

{/* */}
)}
); }; const ProjectDetailsView: React.FC = ({ projectId, onBack, setView }) => { const [project, setProject] = useState(null); const [sessions, setSessions] = useState([]); const [allSessions, setAllSessions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isAddSessionModalOpen, setIsAddSessionModalOpen] = useState(false); const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const loadProjectData = useCallback(async () => { setLoading(true); setError(null); try { // Fetch the project details const projectData = await fetchProject(projectId); setProject(projectData); // Fetch all sessions const allSessionsData = await fetchSessions(); setAllSessions(allSessionsData); // Filter sessions that belong to this project const projectSessions = allSessionsData.filter((session: Session) => projectData.sessionIds.includes(session.id) ); setSessions(projectSessions); } catch (err) { console.error('Failed to load project data:', err); setError('Failed to load project data'); toastError({ title: 'Error', msg: 'Failed to load project data' }); } finally { setLoading(false); } }, [projectId]); // Fetch project details and associated sessions useEffect(() => { loadProjectData(); }, [projectId, loadProjectData]); // Set up session creation listener to automatically associate new sessions with this project useEffect(() => { if (!project) return; const handleSessionCreated = async () => { console.log( 'ProjectDetailsView: Session created event received, checking for new sessions...' ); // Wait a bit for the session to be fully created setTimeout(async () => { try { // Fetch all sessions to find the newest one const allSessionsData = await fetchSessions(); // Find sessions that are not in this project but were created recently const recentSessions = allSessionsData.filter((session: Session) => { const sessionDate = new Date(session.modified); const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); const isRecent = sessionDate > fiveMinutesAgo; const isNotInProject = !project.sessionIds.includes(session.id); const isInProjectDirectory = session.metadata.working_dir === project.defaultDirectory; return isRecent && isNotInProject && isInProjectDirectory; }); // Add recent sessions to this project for (const session of recentSessions) { try { await addSessionToProject(project.id, session.id); console.log(`Automatically added session ${session.id} to project ${project.id}`); } catch (err) { console.error(`Failed to add session ${session.id} to project:`, err); } } // Refresh project data if we added any sessions if (recentSessions.length > 0) { loadProjectData(); } } catch (err) { console.error('Error checking for new sessions:', err); } }, 2000); // Wait 2 seconds for session to be created }; // Listen for session creation events window.addEventListener('session-created', handleSessionCreated); window.addEventListener('message-stream-finished', handleSessionCreated); return () => { window.removeEventListener('session-created', handleSessionCreated); window.removeEventListener('message-stream-finished', handleSessionCreated); }; }, [project, loadProjectData]); const handleRemoveSession = async (sessionId: string) => { if (!project) return; try { await removeSessionFromProject(project.id, sessionId); // Update local state setProject((prev) => { if (!prev) return null; return { ...prev, sessionIds: prev.sessionIds.filter((id) => id !== sessionId), }; }); setSessions((prev) => prev.filter((s) => s.id !== sessionId)); toastSuccess({ title: 'Success', msg: 'Session removed from project' }); } catch (err) { console.error('Failed to remove session from project:', err); toastError({ title: 'Error', msg: 'Failed to remove session from project' }); } }; const getSessionsNotInProject = () => { if (!project) return []; return allSessions.filter((session) => !project.sessionIds.includes(session.id)); }; const handleDeleteProject = async () => { if (!project) return; setIsDeleting(true); try { await deleteProject(project.id); toastSuccess({ title: 'Success', msg: `Project "${project.name}" deleted successfully` }); onBack(); // Go back to projects list } catch (err) { console.error('Failed to delete project:', err); toastError({ title: 'Error', msg: 'Failed to delete project' }); } finally { setIsDeleting(false); setIsDeleteDialogOpen(false); } }; const handleNewSession = () => { if (!project) return; console.log(`Navigating to chat page for project: ${project.name}`); // Update the working directory in localStorage to the project's directory try { const currentConfig = JSON.parse(localStorage.getItem('gooseConfig') || '{}'); const updatedConfig = { ...currentConfig, GOOSE_WORKING_DIR: project.defaultDirectory, }; localStorage.setItem('gooseConfig', JSON.stringify(updatedConfig)); } catch (error) { console.error('Failed to update working directory in localStorage:', error); } // Navigate to the pair page setView('pair'); toastSuccess({ title: 'New Session', msg: `Starting new session in ${project.name}`, }); }; if (loading) { return (

Loading project...

); } if (error || !project) { return (

{error || 'Project not found'}

); } // Define action buttons const actionButtons = ( <> {/* */} ); return ( <>
{!loading && ( <>
{sessions.length} {sessions.length === 1 ? 'session' : 'sessions'}
{project.defaultDirectory}
{project.description && (
{project.description}
)} )}
setIsAddSessionModalOpen(true)} />
setIsAddSessionModalOpen(false)} project={project} availableSessions={getSessionsNotInProject()} onSessionsAdded={loadProjectData} /> setIsUpdateModalOpen(false)} project={{ ...project, sessionCount: sessions.length, }} onRefresh={loadProjectData} /> Are you sure you want to delete this project? This will delete the project "{project.name}". The sessions within this project won't be deleted, but they will no longer be part of this project. Cancel { e.preventDefault(); handleDeleteProject(); }} disabled={isDeleting} > {isDeleting ? 'Deleting...' : 'Delete'} ); }; export default ProjectDetailsView;