import { useState, useCallback, useEffect } from "react"; import type { Conversation } from "../types"; import { readConversations, writeConversations, titleFromMessages } from "../lib/storage"; export interface UseConversationsReturn { conversations: Conversation[]; upsertConversation: (conv: Conversation) => void; deleteConversation: (id: string) => void; loadConversation: (id: string) => Conversation | undefined; } /** * Manages conversation persistence in localStorage via storage.ts helpers. * * - Reads `hmc-conversations` on mount. * - Exposes `upsertConversation(conv)` to insert or update a conversation, * automatically deriving the title from the first user message and updating * `last_active_at` to the current time. * - Exposes `deleteConversation(id)` to remove a conversation by ID. * - Exposes `loadConversation(id)` to retrieve a conversation by ID from * the current in-memory list (no server round-trip). * * Requirements: 6.1, 6.2, 6.3, 6.5, 6.6, 6.7 */ export function useConversations(): UseConversationsReturn { const [conversations, setConversations] = useState(() => readConversations() ); // Sync from localStorage on mount (handles cases where another tab wrote) useEffect(() => { setConversations(readConversations()); }, []); /** * Insert or update a conversation. If a conversation with the same `id` * already exists, it is replaced. The title is re-derived from messages * and `last_active_at` is set to now. * * The cap of 20 conversations is enforced by `writeConversations` in storage.ts. */ const upsertConversation = useCallback((conv: Conversation): void => { setConversations((prev) => { const now = new Date().toISOString(); const updated: Conversation = { ...conv, title: titleFromMessages(conv.messages), last_active_at: now, created_at: conv.created_at || now, }; const existing = prev.findIndex((c) => c.id === conv.id); let next: Conversation[]; if (existing >= 0) { next = [...prev]; next[existing] = updated; } else { next = [updated, ...prev]; } writeConversations(next); return readConversations(); // re-read to get the sorted, capped result }); }, []); /** * Remove a conversation by ID from localStorage and update state. */ const deleteConversation = useCallback((id: string): void => { setConversations((prev) => { const next = prev.filter((c) => c.id !== id); writeConversations(next); return next; }); }, []); /** * Retrieve a conversation by ID from the current in-memory list. * Returns undefined if not found. No server round-trip. */ const loadConversation = useCallback( (id: string): Conversation | undefined => { return conversations.find((c) => c.id === id); }, [conversations] ); return { conversations, upsertConversation, deleteConversation, loadConversation }; }