|  | import { useLoaderData, useNavigate, useSearchParams } from '@remix-run/react'; | 
					
						
						|  | import { useState, useEffect } from 'react'; | 
					
						
						|  | import { atom } from 'nanostores'; | 
					
						
						|  | import type { Message } from 'ai'; | 
					
						
						|  | import { toast } from 'react-toastify'; | 
					
						
						|  | import { workbenchStore } from '~/lib/stores/workbench'; | 
					
						
						|  | import { logStore } from '~/lib/stores/logs'; | 
					
						
						|  | import { | 
					
						
						|  | getMessages, | 
					
						
						|  | getNextId, | 
					
						
						|  | getUrlId, | 
					
						
						|  | openDatabase, | 
					
						
						|  | setMessages, | 
					
						
						|  | duplicateChat, | 
					
						
						|  | createChatFromMessages, | 
					
						
						|  | type IChatMetadata, | 
					
						
						|  | } from './db'; | 
					
						
						|  |  | 
					
						
						|  | export interface ChatHistoryItem { | 
					
						
						|  | id: string; | 
					
						
						|  | urlId?: string; | 
					
						
						|  | description?: string; | 
					
						
						|  | messages: Message[]; | 
					
						
						|  | timestamp: string; | 
					
						
						|  | metadata?: IChatMetadata; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const persistenceEnabled = !import.meta.env.VITE_DISABLE_PERSISTENCE; | 
					
						
						|  |  | 
					
						
						|  | export const db = persistenceEnabled ? await openDatabase() : undefined; | 
					
						
						|  |  | 
					
						
						|  | export const chatId = atom<string | undefined>(undefined); | 
					
						
						|  | export const description = atom<string | undefined>(undefined); | 
					
						
						|  | export const chatMetadata = atom<IChatMetadata | undefined>(undefined); | 
					
						
						|  | export function useChatHistory() { | 
					
						
						|  | const navigate = useNavigate(); | 
					
						
						|  | const { id: mixedId } = useLoaderData<{ id?: string }>(); | 
					
						
						|  | const [searchParams] = useSearchParams(); | 
					
						
						|  |  | 
					
						
						|  | const [initialMessages, setInitialMessages] = useState<Message[]>([]); | 
					
						
						|  | const [ready, setReady] = useState<boolean>(false); | 
					
						
						|  | const [urlId, setUrlId] = useState<string | undefined>(); | 
					
						
						|  |  | 
					
						
						|  | useEffect(() => { | 
					
						
						|  | if (!db) { | 
					
						
						|  | setReady(true); | 
					
						
						|  |  | 
					
						
						|  | if (persistenceEnabled) { | 
					
						
						|  | const error = new Error('Chat persistence is unavailable'); | 
					
						
						|  | logStore.logError('Chat persistence initialization failed', error); | 
					
						
						|  | toast.error('Chat persistence is unavailable'); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (mixedId) { | 
					
						
						|  | getMessages(db, mixedId) | 
					
						
						|  | .then((storedMessages) => { | 
					
						
						|  | if (storedMessages && storedMessages.messages.length > 0) { | 
					
						
						|  | const rewindId = searchParams.get('rewindTo'); | 
					
						
						|  | const filteredMessages = rewindId | 
					
						
						|  | ? storedMessages.messages.slice(0, storedMessages.messages.findIndex((m) => m.id === rewindId) + 1) | 
					
						
						|  | : storedMessages.messages; | 
					
						
						|  |  | 
					
						
						|  | setInitialMessages(filteredMessages); | 
					
						
						|  | setUrlId(storedMessages.urlId); | 
					
						
						|  | description.set(storedMessages.description); | 
					
						
						|  | chatId.set(storedMessages.id); | 
					
						
						|  | chatMetadata.set(storedMessages.metadata); | 
					
						
						|  | } else { | 
					
						
						|  | navigate('/', { replace: true }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | setReady(true); | 
					
						
						|  | }) | 
					
						
						|  | .catch((error) => { | 
					
						
						|  | logStore.logError('Failed to load chat messages', error); | 
					
						
						|  | toast.error(error.message); | 
					
						
						|  | }); | 
					
						
						|  | } | 
					
						
						|  | }, []); | 
					
						
						|  |  | 
					
						
						|  | return { | 
					
						
						|  | ready: !mixedId || ready, | 
					
						
						|  | initialMessages, | 
					
						
						|  | updateChatMestaData: async (metadata: IChatMetadata) => { | 
					
						
						|  | const id = chatId.get(); | 
					
						
						|  |  | 
					
						
						|  | if (!db || !id) { | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | try { | 
					
						
						|  | await setMessages(db, id, initialMessages, urlId, description.get(), undefined, metadata); | 
					
						
						|  | chatMetadata.set(metadata); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | toast.error('Failed to update chat metadata'); | 
					
						
						|  | console.error(error); | 
					
						
						|  | } | 
					
						
						|  | }, | 
					
						
						|  | 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(), undefined, chatMetadata.get()); | 
					
						
						|  | }, | 
					
						
						|  | duplicateCurrentChat: async (listItemId: string) => { | 
					
						
						|  | if (!db || (!mixedId && !listItemId)) { | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | try { | 
					
						
						|  | const newId = await duplicateChat(db, mixedId || listItemId); | 
					
						
						|  | navigate(`/chat/${newId}`); | 
					
						
						|  | toast.success('Chat duplicated successfully'); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | toast.error('Failed to duplicate chat'); | 
					
						
						|  | console.log(error); | 
					
						
						|  | } | 
					
						
						|  | }, | 
					
						
						|  | importChat: async (description: string, messages: Message[], metadata?: IChatMetadata) => { | 
					
						
						|  | if (!db) { | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | try { | 
					
						
						|  | const newId = await createChatFromMessages(db, description, messages, metadata); | 
					
						
						|  | window.location.href = `/chat/${newId}`; | 
					
						
						|  | toast.success('Chat imported successfully'); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | if (error instanceof Error) { | 
					
						
						|  | toast.error('Failed to import chat: ' + error.message); | 
					
						
						|  | } else { | 
					
						
						|  | toast.error('Failed to import chat'); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | }, | 
					
						
						|  | exportChat: async (id = urlId) => { | 
					
						
						|  | if (!db || !id) { | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const chat = await getMessages(db, id); | 
					
						
						|  | const chatData = { | 
					
						
						|  | messages: chat.messages, | 
					
						
						|  | description: chat.description, | 
					
						
						|  | exportDate: new Date().toISOString(), | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  | const blob = new Blob([JSON.stringify(chatData, null, 2)], { type: 'application/json' }); | 
					
						
						|  | const url = URL.createObjectURL(blob); | 
					
						
						|  | const a = document.createElement('a'); | 
					
						
						|  | a.href = url; | 
					
						
						|  | a.download = `chat-${new Date().toISOString()}.json`; | 
					
						
						|  | document.body.appendChild(a); | 
					
						
						|  | a.click(); | 
					
						
						|  | document.body.removeChild(a); | 
					
						
						|  | URL.revokeObjectURL(url); | 
					
						
						|  | }, | 
					
						
						|  | }; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | function navigateChat(nextId: string) { | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const url = new URL(window.location.href); | 
					
						
						|  | url.pathname = `/chat/${nextId}`; | 
					
						
						|  |  | 
					
						
						|  | window.history.replaceState({}, '', url); | 
					
						
						|  | } | 
					
						
						|  |  |