import React, { useState, useEffect } from 'react'; import { fetchSessions, updateProject, deleteProject, generateProjectSummary, fetchProjectOverviewSummary } from '../api/orchestration'; import ProjectModal from './ProjectModal'; export default function ProjectView({ project, onNavigate, onBack, onSelectSession, onNewProjectChat, onProjectsChange }) { const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(true); const [input, setInput] = useState(''); const [menuOpen, setMenuOpen] = useState(false); const [modal, setModal] = useState(null); const [overview, setOverview] = useState(null); const [overviewLoading, setOverviewLoading] = useState(true); const [generating, setGenerating] = useState(false); const [generateError, setGenerateError] = useState(null); useEffect(() => { load(); }, [project.id]); useEffect(() => { async function loadOverview() { setOverviewLoading(true); try { setOverview(await fetchProjectOverviewSummary(project.id)); } catch (err) { console.error('[ProjectView] Failed to load overview:', err.message); } finally { setOverviewLoading(false); } } loadOverview(); }, [project.id]); async function load() { setLoading(true); try { setSessions(await fetchSessions(50, 0, project.id)); } catch (err) { console.error('[ProjectView] Failed to load sessions:', err.message); } finally { setLoading(false); } } function handleSend() { const text = input.trim(); if (!text) return; setInput(''); onNewProjectChat(text); } function handleKeyDown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } } async function handleSave({ name, description, colour, icon, isolated, system_prompt }) { try { await updateProject(project.id, { name, description, colour, icon, isolated, system_prompt }); onProjectsChange?.(); setModal(null); } catch (err) { console.error('[ProjectView] Update failed:', err.message); } } async function handleDelete() { try { await deleteProject(project.id); onProjectsChange?.(); onBack(); } catch (err) { console.error('[ProjectView] Delete failed:', err.message); } } function formatTimestamp(ts) { if (!ts) return '—'; const date = new Date(ts * 1000); const now = new Date(); const diffMs = now - date; const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays === 1) return 'Yesterday'; return date.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' }); } async function handleGenerateSummary() { setGenerating(true); setGenerateError(null); try { setOverview(await generateProjectSummary(project.id)); } catch (err) { // 422 means no session summaries exist yet — surface a friendly message setGenerateError( err.message.includes('422') ? 'No conversations found in this project yet.' : 'Failed to generate summary. Please try again.' ); } finally { setGenerating(false); } } return (
{/* Colour accent bar */}
{/* Header */}
{menuOpen && ( <>
setMenuOpen(false)} />
{ setMenuOpen(false); setModal({ mode: 'edit' }); }}> ✎ Edit details { setMenuOpen(false); setModal({ mode: 'confirm-delete' }); }}> ✕ Delete project
)}
{/* Scrollable content */}
{/* Project title + description */}

{project.name}

{project.description && (

{project.description}

)}
{/* ── Conversations ── */}

Conversations

{loading ? (
Loading...
) : sessions.length === 0 ? (

No conversations yet — start one below

) : ( <>
{sessions.map((session, i) => ( ))}
)}
{/* ── Project Memory ── */}

Project Memory

{overviewLoading ? (

Loading…

) : generateError ? (

{generateError}

) : overview ? ( <>

{overview.content}

Last generated {formatTimestamp(overview.created_at)}

) : ( // No overview exists yet — explain what this section is for
No project summary yet

Generate a summary to create a concise overview of this project's goals, progress, and key decisions — built from your session summaries.

)}
{/* ── Notes ── */}
{/* Modal */} {modal && ( setModal(null)} /> )}
); } // ── Sub-components ───────────────────────────────────────── function ChatInput({ value, onChange, onSend, placeholder, autoFocus }) { function handleKeyDown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSend(); } } return (