/* Copyright 2026 Marimo. All rights reserved. */ import { useAtomValue } from "jotai"; import { BotMessageSquareIcon, ClockIcon, SearchIcon, Trash2Icon, } from "lucide-react"; import { useMemo, useRef, useState } from "react"; import { useLocale } from "react-aria"; import { useImperativeModal } from "@/components/modal/ImperativeModal"; import { AlertDialogDestructiveAction } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tooltip } from "@/components/ui/tooltip"; import { type ChatId, chatStateAtom } from "@/core/ai/state"; import { cn } from "@/utils/cn"; import { timeAgo } from "@/utils/dates"; import { PanelEmptyState } from "../editor/chrome/panels/empty-state"; import { groupChatsByDate } from "./chat-history-utils"; const CHAT_HISTORY_WARNING_THRESHOLD = 50; interface ChatHistoryPopoverProps { activeChatId: ChatId | undefined; setActiveChat: (id: ChatId | null) => void; onDeleteChats: (id: ChatId | null) => void; isLoading: boolean; } export const ChatHistoryPopover: React.FC = ({ activeChatId, setActiveChat, onDeleteChats, isLoading, }) => { const chatState = useAtomValue(chatStateAtom); const { locale } = useLocale(); const [searchQuery, setSearchQuery] = useState(""); const searchInputRef = useRef(null); const [open, setOpen] = useState(false); const { openConfirm, closeModal } = useImperativeModal(); const confirmDeleteAllChats = () => { // Close the popover before opening a modal so focus moves to the dialog. setOpen(false); openConfirm({ title: "Delete all chats?", description: "This will permanently delete all chat history for all notebooks on this site in this browser. This cannot be undone.", variant: "destructive", confirmAction: ( { onDeleteChats(null); closeModal(); }} > Delete all chats ), }); }; const chats = useMemo(() => { return [...chatState.chats.values()].toSorted( (a, b) => b.updatedAt - a.updatedAt, ); }, [chatState.chats]); const filteredChats = useMemo(() => { if (!searchQuery.trim()) { return chats; } return chats.filter((chat) => chat.title.toLowerCase().includes(searchQuery.toLowerCase()), ); }, [chats, searchQuery]); const groupedChats = useMemo(() => { return groupChatsByDate(filteredChats); }, [filteredChats]); return (
setSearchQuery(e.target.value)} className="text-xs" />
{chats.length === 0 && ( } /> )} {filteredChats.length === 0 && searchQuery && chats.length > 0 && ( } /> )} {groupedChats.map((group, idx) => (
{group.label}
{group.chats.map((chat) => (
))}
{/* If last group, don't show a divider */} {idx !== groupedChats.length - 1 &&
}
))}
{chats.length > 0 && (
{chats.length > CHAT_HISTORY_WARNING_THRESHOLD ? (

You have over {CHAT_HISTORY_WARNING_THRESHOLD} chats. Delete old chats to free up browser storage.

) : (

Chats are shared across notebooks.

)}
)}
); };