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 (
{project.description}
)}Conversations
{loading ? (No conversations yet — start one below
Project Memory
Loading…
) : generateError ? ({generateError}
) : overview ? ( <>{overview.content}
Last generated {formatTimestamp(overview.created_at)}
> ) : ( // No overview exists yet — explain what this section is forGenerate a summary to create a concise overview of this project's goals, progress, and key decisions — built from your session summaries.
Enter to send · Shift+Enter for new line
Project Notes
{isDirty && ( )}