import { Archive, ArchiveRestore, MoreHorizontal, Pencil, Pin, PinOff, Trash2, } from "lucide-react"; import { useTranslation } from "react-i18next"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { deriveTitle, relativeTime } from "@/lib/format"; import { cn } from "@/lib/utils"; import type { ChatSummary, SidebarDensity, SidebarSortMode } from "@/lib/types"; interface ChatListProps { sessions: ChatSummary[]; activeKey: string | null; onSelect: (key: string) => void; onRequestDelete: (key: string, label: string) => void; onTogglePin: (key: string) => void; onRequestRename: (key: string, label: string) => void; onToggleArchive: (key: string) => void; pinnedKeys?: string[]; archivedKeys?: string[]; titleOverrides?: Record; runningChatIds?: string[]; completedChatIds?: string[]; density?: SidebarDensity; showPreviews?: boolean; showTimestamps?: boolean; sort?: SidebarSortMode; showArchived?: boolean; actionMenuPortalContainer?: HTMLElement | null; loading?: boolean; emptyLabel?: string; } export function ChatList({ sessions, activeKey, onSelect, onRequestDelete, onTogglePin, onRequestRename, onToggleArchive, pinnedKeys = [], archivedKeys = [], titleOverrides = {}, runningChatIds = [], completedChatIds = [], density = "comfortable", showPreviews = false, showTimestamps = false, sort = "updated_desc", showArchived = false, actionMenuPortalContainer, loading, emptyLabel, }: ChatListProps) { const { t } = useTranslation(); if (loading && sessions.length === 0) { return (
{t("chat.loading")}
); } if (sessions.length === 0) { return (
{emptyLabel ?? t("chat.noSessions")}
); } const groups = groupSessions(sessions, { pinned: t("chat.groups.pinned"), all: t("chat.groups.all"), today: t("chat.groups.today"), yesterday: t("chat.groups.yesterday"), earlier: t("chat.groups.earlier"), archived: t("chat.groups.archived"), fallbackTitle: t("chat.newChat"), }, { pinnedKeys, archivedKeys, titleOverrides, showArchived, sort, }); const pinned = new Set(pinnedKeys); const archived = new Set(archivedKeys); const running = new Set(runningChatIds); const completed = new Set(completedChatIds); const compact = density === "compact"; return (
{groups.map((group) => (
{group.label}
    {group.sessions.map((s) => { const active = s.key === activeKey; const fallbackTitle = t("chat.fallbackTitle", { id: s.chatId.slice(0, 6), }); const generatedTitle = s.title?.trim() || ""; const title = displayTitle(s, titleOverrides, t("chat.newChat")); const tooltipTitle = titleOverrides[s.key]?.trim() || generatedTitle || deriveTitle(s.preview, fallbackTitle); const isPinned = pinned.has(s.key); const isArchived = archived.has(s.key); const preview = s.preview.trim(); const showPreview = showPreviews && preview && preview !== title; const timestamp = showTimestamps ? relativeTime(s.updatedAt ?? s.createdAt) : ""; const activityState = running.has(s.chatId) ? "running" : completed.has(s.chatId) ? "complete" : null; return (
  • event.preventDefault()} > onTogglePin(s.key)} > {isPinned ? ( ) : ( )} {isPinned ? t("chat.unpin") : t("chat.pin")} onRequestRename(s.key, title)} > {t("chat.rename")} onToggleArchive(s.key)} > {isArchived ? ( ) : ( )} {isArchived ? t("chat.unarchive") : t("chat.archive")} { window.setTimeout(() => onRequestDelete(s.key, title), 0); }} className="text-destructive focus:text-destructive" > {t("chat.delete")}
  • ); })}
))}
); } function SessionActivityIndicator({ state, }: { state: "running" | "complete" | null; }) { const { t } = useTranslation(); if (state === "running") { const label = t("chat.activity.running"); return ( ); } if (state === "complete") { const label = t("chat.activity.complete"); return ( ); } return