import { useMemo } from "react"; import { useFetch } from "../hooks"; import { fmtCost, fmtDate, fmtRelative, useT, type Key } from "../i18n"; import { navigate } from "../router"; import { useStore } from "../store"; import type { Session } from "../types"; import { Icon } from "./Icon"; import { basename } from "./FileFigure"; interface Overview { sessions: Record; check: { command: string; enabled: boolean } | null; defaults: { model?: string; agent?: string; permission_mode?: string; color?: string; instructions?: string; folder?: string } | null; currency: string; } const day = (ms: number) => new Date(ms).toDateString(); /** One project folder: its chats as a timeline (newest first) with what each one did. */ export function ProjectPage({ cwd, onMenu }: { cwd: string; onMenu: () => void }) { const t = useT(); const all = useStore((s) => s.sessions); const { data } = useFetch(`/projects/overview?cwd=${encodeURIComponent(cwd)}`); const sessions = useMemo(() => all.filter((s) => s.cwd === cwd && !s.archived).sort((a, b) => b.updated_at - a.updated_at), [all, cwd]); const archived = all.filter((s) => s.cwd === cwd && s.archived).length; // Day groups: "Today", "Yesterday", date. const groups = useMemo(() => { const out: { label: string; items: Session[] }[] = []; const today = day(Date.now()); const yesterday = day(Date.now() - 86_400_000); for (const s of sessions) { const d = day(s.updated_at); const label = d === today ? t("project.today") : d === yesterday ? t("project.yesterday") : fmtDate(s.updated_at, false); const last = out[out.length - 1]; if (last?.label === label) last.items.push(s); else out.push({ label, items: [s] }); } return out; }, [sessions, t]); const totalCost = sessions.reduce((n, s) => n + (data?.sessions[s.id]?.cost ?? 0), 0); const totalFiles = sessions.reduce((n, s) => n + (data?.sessions[s.id]?.files ?? 0), 0); const newChat = () => { try { localStorage.setItem("codecraft.lastDir", cwd); } catch { // Without storage the wizard starts at its usual folder. } navigate("/new"); }; return ( <>

{basename(cwd) || "/"}

{cwd}
{t("project.chats", { n: sessions.length })} {totalFiles > 0 && {t("project.files", { n: totalFiles })}} {data && totalCost > 0 && {fmtCost(totalCost, data.currency)}} {data?.check?.enabled && ( {data.check.command} )} {data?.defaults?.model && ( {data.defaults.model.split("/").pop()} )} {archived > 0 && {t("project.archived", { n: archived })}}
{sessions.length === 0 &&
{t("project.empty")}
} {groups.map((g) => (

{g.label}

    {g.items.map((s) => { const info = data?.sessions[s.id]; return (
  1. (e.preventDefault(), navigate(`/s/${s.id}`))}>
    {s.icon && } {s.title || t("session.untitled")} {fmtRelative(s.updated_at)}
    {s.summary &&
    {s.summary}
    }
    {s.agent} {s.model.split("/").pop()} {!!info?.files && {t("project.files", { n: info.files })}} {info?.check && ( {t(`check.${info.check}` as Key)} )} {!!info?.checks_failed && {t("project.checksFailed", { n: info.checks_failed })}} {!!info?.cost && data && {fmtCost(info.cost, data.currency)}}
  2. ); })}
))}
); }