import { useLoaderData, useNavigate } from '@remix-run/react'; import { useState, useEffect } from 'react'; import { atom } from 'nanostores'; import type { Message } from 'ai'; import { toast } from 'react-toastify'; import { AnalyticsAction, sendAnalyticsEvent } from '~/lib/analytics'; import { workbenchStore } from '~/lib/stores/workbench'; import { getMessages, getNextId, getUrlId, openDatabase, setMessages } from './db'; export interface ChatHistoryItem { id: string; urlId?: string; description?: string; messages: Message[]; timestamp: string; } const persistenceEnabled = !import.meta.env.VITE_DISABLE_PERSISTENCE; export const db = persistenceEnabled ? await openDatabase() : undefined; export const chatId = atom(undefined); export const description = atom(undefined); export function useChatHistory() { const navigate = useNavigate(); const { id: mixedId } = useLoaderData<{ id?: string }>(); const [initialMessages, setInitialMessages] = useState([]); const [ready, setReady] = useState(false); const [urlId, setUrlId] = useState(); useEffect(() => { if (!db) { setReady(true); if (persistenceEnabled) { toast.error(`Chat persistence is unavailable`); } return; } if (mixedId) { getMessages(db, mixedId) .then((storedMessages) => { if (storedMessages && storedMessages.messages.length > 0) { setInitialMessages(storedMessages.messages); setUrlId(storedMessages.urlId); description.set(storedMessages.description); chatId.set(storedMessages.id); } else { navigate(`/`, { replace: true }); } setReady(true); }) .catch((error) => { toast.error(error.message); }); } }, []); return { ready: !mixedId || ready, initialMessages, storeMessageHistory: async (messages: Message[]) => { if (!db || messages.length === 0) { return; } const { firstArtifact } = workbenchStore; if (!urlId && firstArtifact?.id) { const urlId = await getUrlId(db, firstArtifact.id); navigateChat(urlId); setUrlId(urlId); } if (!description.get() && firstArtifact?.title) { description.set(firstArtifact?.title); } if (initialMessages.length === 0 && !chatId.get()) { const nextId = await getNextId(db); chatId.set(nextId); if (!urlId) { navigateChat(nextId); } } await setMessages(db, chatId.get() as string, messages, urlId, description.get()); }, }; } function navigateChat(nextId: string) { /** * FIXME: Using the intended navigate function causes a rerender for that breaks the app. * * `navigate(`/chat/${nextId}`, { replace: true });` */ const url = new URL(window.location.href); url.pathname = `/chat/${nextId}`; window.history.replaceState({}, '', url); // since the `replaceState` call doesn't trigger a page reload, we need to manually log this event sendAnalyticsEvent({ action: AnalyticsAction.Page, payload: { properties: { url: url.href, }, }, }); }