Files
pi-mobile/apps/mobile/lib/session-utils.ts
T
2026-07-09 12:07:57 -05:00

162 lines
4.1 KiB
TypeScript

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<string, ProjectSummary>,
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<string, ProjectSummary>();
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";
}