162 lines
4.1 KiB
TypeScript
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";
|
|
}
|