import { useEffect, useState } from 'react'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { ChevronRight } from 'lucide-react'; import { api } from '@/api/client'; import type { Project, Session as SessionType } from '@/api/types'; import { sessionEvents } from '@/hooks/sessionEvents'; import { useActivePane } from '@/hooks/useActivePane'; import { Workspace } from '@/components/Workspace'; import { ModelPicker } from '@/components/ModelPicker'; export function Session() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [session, setSession] = useState(null); const [project, setProject] = useState(null); const [name, setName] = useState(''); const [editingName, setEditingName] = useState(false); const active = useActivePane(); useEffect(() => { if (!id) return; setSession(null); setProject(null); let cancelled = false; api.sessions .get(id) .then((s) => { if (cancelled) return; setSession(s); setName(s.name); sessionEvents.emit({ type: 'session_loaded', session_id: id, project_id: s.project_id, }); // Load project for breadcrumb. Listing is fine — small N, cached by client. api.projects.list().then((projects) => { if (cancelled) return; const p = projects.find((x) => x.id === s.project_id); if (p) setProject(p); }).catch(() => {}); }) .catch(() => {}); return () => { cancelled = true; }; }, [id]); useEffect(() => { if (!id) return; return sessionEvents.subscribe((event) => { if (event.type === 'session_renamed' && event.session_id === id) { setSession((prev) => (prev ? { ...prev, name: event.name } : prev)); setName((prev) => (editingName ? prev : event.name)); return; } if ( (event.type === 'session_deleted' || event.type === 'session_archived') && event.session_id === id ) { navigate(`/project/${event.project_id}`); } }); }, [id, editingName, navigate]); async function saveName() { if (!id || !session) return; const trimmed = name.trim(); if (!trimmed || trimmed === session.name) { setName(session.name); setEditingName(false); return; } const updated = await api.sessions.update(id, { name: trimmed }); setSession(updated); setEditingName(false); // Server publishes session_renamed via broker.publishUser; no local emit needed. } // Workspace only sets activeFile for file-browser panes; checking it alone // suffices and is forward-compatible with future pane kinds. const showActiveFile = active.sessionId === id && !!active.activeFile; return (
Projects {project ? ( {project.name} ) : ( )} {editingName ? ( setName(e.target.value)} onBlur={() => void saveName()} onKeyDown={(e) => { if (e.key === 'Enter') void saveName(); if (e.key === 'Escape') { setName(session?.name ?? ''); setEditingName(false); } }} className="bg-transparent border-b border-border px-1 py-0.5 text-sm font-medium outline-none focus:border-ring" /> ) : ( )} {showActiveFile && active.activeFile && ( <> · {active.activeFile} )}
{session && (
{ const updated = await api.sessions.update(session.id, { model }); setSession(updated); }} />
)}
{id && session && ( )}
); }