import type { LiveRuntimeInfo, PersistedSessionInfo } from "@/lib/types"; export type ProjectSummary = { cwd: string; name: string; chatCount: number; activeCount: number; streamingCount: number; lastActivity?: string; }; export function basename(path: string) { const trimmed = path.replace(/\/$/, ""); const parts = trimmed.split("/"); return parts[parts.length - 1] || path; } export function shortenPath(path: string) { const parts = path.split("/").filter(Boolean); if (parts.length <= 3) { return path; } return `…/${parts.slice(-2).join("/")}`; } export function formatRelativeDate(value: string) { const date = new Date(value); const diffMs = Date.now() - date.getTime(); const diffMin = Math.floor(diffMs / 60_000); const diffHr = Math.floor(diffMs / 3_600_000); const diffDay = Math.floor(diffMs / 86_400_000); if (diffMin < 1) { return "Just now"; } if (diffMin < 60) { return `${diffMin}m ago`; } if (diffHr < 24) { return `${diffHr}h ago`; } if (diffDay === 1) { return "Yesterday"; } if (diffDay < 7) { return `${diffDay}d ago`; } return date.toLocaleDateString(undefined, { month: "short", day: "numeric" }); } export function formatMessageCount(count: number) { return count === 1 ? "1 message" : `${count} messages`; } export function formatChatCount(count: number) { return count === 1 ? "1 chat" : `${count} chats`; } export function chatTitle(lastMessage: string | undefined, sessionPath: string) { const preview = lastMessage?.replace(/\s+/g, " ").trim(); return preview || basename(sessionPath) || "Untitled chat"; } export function projectKey(cwd: string) { return cwd || "(unknown)"; } export function projectDisplayName(cwd: string) { if (!cwd) { return "Other sessions"; } return basename(cwd); } function upsertProject( projects: Map, cwd: string, ): ProjectSummary { const key = projectKey(cwd); let project = projects.get(key); if (!project) { project = { cwd, name: projectDisplayName(cwd), chatCount: 0, activeCount: 0, streamingCount: 0, }; projects.set(key, project); } return project; } export function groupByProject( savedProjectCwds: string[], liveRuntimes: LiveRuntimeInfo[], persistedSessions: PersistedSessionInfo[], ): ProjectSummary[] { const projects = new Map(); for (const cwd of savedProjectCwds) { upsertProject(projects, cwd); } for (const session of persistedSessions) { const project = projects.get(projectKey(session.cwd)); if (!project) { continue; } project.chatCount += 1; if (!project.lastActivity || session.modified > project.lastActivity) { project.lastActivity = session.modified; } } for (const runtime of liveRuntimes) { const project = projects.get(projectKey(runtime.cwd)); if (!project) { continue; } project.activeCount += 1; if (runtime.isStreaming) { project.streamingCount += 1; } } return savedProjectCwds .map((cwd) => projects.get(projectKey(cwd))) .filter((project): project is ProjectSummary => project !== undefined); } export function sessionsForProject( cwd: string, liveRuntimes: LiveRuntimeInfo[], persistedSessions: PersistedSessionInfo[], ) { const key = projectKey(cwd); const live = liveRuntimes.filter((runtime) => projectKey(runtime.cwd) === key); const persisted = persistedSessions .filter((session) => projectKey(session.cwd) === key) .sort((a, b) => Date.parse(b.modified) - Date.parse(a.modified)); return { live, persisted }; } export function projectMeta(project: ProjectSummary) { const parts: string[] = []; if (project.chatCount > 0) { parts.push(formatChatCount(project.chatCount)); } if (project.activeCount > 0) { parts.push( project.streamingCount > 0 ? `${project.activeCount} active · working` : `${project.activeCount} active`, ); } if (project.lastActivity) { parts.push(formatRelativeDate(project.lastActivity)); } return parts.join(" · ") || "No chats yet"; }