feat: chat restore

added a basic chat restore
This commit is contained in:
Dustin Loring 2025-01-18 12:00:18 -05:00 committed by GitHub
commit adcd6978d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import { Menu } from '~/components/sidebar/Menu.client';
import { IconButton } from '~/components/ui/IconButton'; import { IconButton } from '~/components/ui/IconButton';
import { Workbench } from '~/components/workbench/Workbench.client'; import { Workbench } from '~/components/workbench/Workbench.client';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { ChatHistory } from './ChatHistory.client';
interface BaseChatProps { interface BaseChatProps {
textareaRef?: React.RefObject<HTMLTextAreaElement | null> | undefined; textareaRef?: React.RefObject<HTMLTextAreaElement | null> | undefined;
@ -198,6 +199,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
> >
<div className="i-ph:image text-xl" /> <div className="i-ph:image text-xl" />
</IconButton> </IconButton>
<ClientOnly>{() => <ChatHistory />}</ClientOnly>
</div> </div>
{input.length > 3 ? ( {input.length > 3 ? (
<div className="text-xs text-bolt-elements-textTertiary"> <div className="text-xs text-bolt-elements-textTertiary">

View File

@ -0,0 +1,81 @@
import { useEffect, useState } from 'react';
import type { ChatHistoryItem } from '~/lib/persistence/useChatHistory';
import { db } from '~/lib/persistence/useChatHistory';
import { getAll } from '~/lib/persistence/db';
import { useNavigate, useLoaderData } from '@remix-run/react';
import { IconButton } from '~/components/ui/IconButton';
import { classNames } from '~/utils/classNames';
import { createScopedLogger } from '~/utils/logger';
const logger = createScopedLogger('ChatHistory');
export function ChatHistory() {
const [history, setHistory] = useState<ChatHistoryItem[]>([]);
const [isOpen, setIsOpen] = useState(false);
const navigate = useNavigate();
const { id: currentId } = useLoaderData<{ id?: string }>();
useEffect(() => {
if (!db) return;
const loadHistory = async () => {
try {
const items = await getAll(db!);
// Filter items to only show history for the current chat
const filteredItems = items
.filter(item => item.urlId === currentId || item.id === currentId)
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
setHistory(filteredItems);
} catch (error) {
logger.error('Failed to load chat history', error);
}
};
loadHistory();
}, [currentId]);
const handleRestore = (item: ChatHistoryItem) => {
navigate(`/chat/${item.id}`);
setIsOpen(false);
};
if (!db || history.length === 0) return null;
return (
<div className="relative">
<IconButton
title="Chat History"
onClick={() => setIsOpen(!isOpen)}
className={classNames({
'text-bolt-elements-item-contentAccent!': isOpen,
})}
>
<div className="i-ph:clock-counter-clockwise text-xl" />
</IconButton>
{isOpen && (
<div className="absolute bottom-full left-0 mb-2 w-[300px] max-h-[400px] overflow-y-auto bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor rounded-lg shadow-lg">
<div className="p-2">
<h3 className="text-sm font-medium text-bolt-elements-textPrimary px-2 py-1">Chat History</h3>
<div className="mt-1">
{history.map((item) => (
<button
key={item.id}
onClick={() => handleRestore(item)}
className="w-full text-left px-2 py-1.5 hover:bg-bolt-elements-item-backgroundHover rounded transition-colors"
>
<div className="text-sm text-bolt-elements-textPrimary truncate">
{item.description || `Version ${new Date(item.timestamp).toLocaleTimeString()}`}
</div>
<div className="text-xs text-bolt-elements-textTertiary">
{new Date(item.timestamp).toLocaleDateString()} {new Date(item.timestamp).toLocaleTimeString()}
</div>
</button>
))}
</div>
</div>
</div>
)}
</div>
);
}