Added tooltips and fork

This commit is contained in:
eduardruzga 2024-11-18 20:31:56 +02:00
parent 23d7182d6d
commit 082339c242
4 changed files with 128 additions and 43 deletions

View File

@ -3,7 +3,11 @@ import React from 'react';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { AssistantMessage } from './AssistantMessage'; import { AssistantMessage } from './AssistantMessage';
import { UserMessage } from './UserMessage'; import { UserMessage } from './UserMessage';
import * as Tooltip from '@radix-ui/react-tooltip';
import { useLocation, useNavigate } from '@remix-run/react'; import { useLocation, useNavigate } from '@remix-run/react';
import { db, chatId } from '~/lib/persistence/useChatHistory';
import { forkChat } from '~/lib/persistence/db';
import { toast } from 'react-toastify';
interface MessagesProps { interface MessagesProps {
id?: string; id?: string;
@ -19,12 +23,26 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
const handleRewind = (messageId: string) => { const handleRewind = (messageId: string) => {
const searchParams = new URLSearchParams(location.search); const searchParams = new URLSearchParams(location.search);
searchParams.set('rewindId', messageId); searchParams.set('rewindTo', messageId);
window.location.search = searchParams.toString(); window.location.search = searchParams.toString();
//navigate(`${location.pathname}?${searchParams.toString()}`); };
const handleFork = async (messageId: string) => {
try {
if (!db || !chatId.get()) {
toast.error('Chat persistence is not available');
return;
}
const urlId = await forkChat(db, chatId.get()!, messageId);
window.location.href = `/chat/${urlId}`;
} catch (error) {
toast.error('Failed to fork chat: ' + (error as Error).message);
}
}; };
return ( return (
<Tooltip.Provider delayDuration={200}>
<div id={id} ref={ref} className={props.className}> <div id={id} ref={ref} className={props.className}>
{messages.length > 0 {messages.length > 0
? messages.map((message, index) => { ? messages.map((message, index) => {
@ -51,15 +69,53 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
<div className="grid grid-col-1 w-full"> <div className="grid grid-col-1 w-full">
{isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />} {isUserMessage ? <UserMessage content={content} /> : <AssistantMessage content={content} />}
</div> </div>
{messageId && ( <div className="flex gap-2">
<button <Tooltip.Root>
<Tooltip.Trigger asChild>
{messageId && (<button
onClick={() => handleRewind(messageId)} onClick={() => handleRewind(messageId)}
className="self-start p-2 text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary" key='i-ph:arrow-u-up-left'
title="Rewind to this message" className={classNames(
> 'i-ph:arrow-u-up-left',
<div className="i-ph:arrow-counter-clockwise text-xl"></div> 'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors'
</button>
)} )}
/>)}
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
sideOffset={5}
style={{zIndex: 1000}}
>
Revert to this message
<Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
onClick={() => handleFork(messageId)}
key='i-ph:git-fork'
className={classNames(
'i-ph:git-fork',
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors'
)}
/>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="bg-bolt-elements-tooltip-background text-bolt-elements-textPrimary px-3 py-2 rounded-lg text-sm shadow-lg"
sideOffset={5}
style={{zIndex: 1000}}
>
Fork chat from this message
<Tooltip.Arrow className="fill-bolt-elements-tooltip-background" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
</div> </div>
); );
}) })
@ -68,5 +124,6 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
<div className="text-center w-full text-bolt-elements-textSecondary i-svg-spinners:3-dots-fade text-4xl mt-4"></div> <div className="text-center w-full text-bolt-elements-textSecondary i-svg-spinners:3-dots-fade text-4xl mt-4"></div>
)} )}
</div> </div>
</Tooltip.Provider>
); );
}); });

View File

@ -159,6 +159,33 @@ async function getUrlIds(db: IDBDatabase): Promise<string[]> {
}); });
} }
export async function forkChat(db: IDBDatabase, chatId: string, messageId: string): Promise<string> {
const chat = await getMessages(db, chatId);
if (!chat) throw new Error('Chat not found');
// Find the index of the message to fork at
const messageIndex = chat.messages.findIndex(msg => msg.id === messageId);
if (messageIndex === -1) throw new Error('Message not found');
// Get messages up to and including the selected message
const messages = chat.messages.slice(0, messageIndex + 1);
// Generate new IDs
const newId = await getNextId(db);
const urlId = await getUrlId(db, newId);
// Create the forked chat
await setMessages(
db,
newId,
messages,
urlId,
chat.description ? `${chat.description} (fork)` : 'Forked chat'
);
return urlId;
}
export async function duplicateChat(db: IDBDatabase, id: string): Promise<string> { export async function duplicateChat(db: IDBDatabase, id: string): Promise<string> {
const chat = await getMessages(db, id); const chat = await getMessages(db, id);
if (!chat) { if (!chat) {

View File

@ -4,7 +4,7 @@ import { getNamingConventionRule, tsFileExtensions } from '@blitz/eslint-plugin/
export default [ export default [
{ {
ignores: ['**/dist', '**/node_modules', '**/.wrangler', '**/bolt/build'], ignores: ['**/dist', '**/node_modules', '**/.wrangler', '**/bolt/build', '**/.history'],
}, },
...blitzPlugin.configs.recommended(), ...blitzPlugin.configs.recommended(),
{ {

View File

@ -54,6 +54,7 @@
"@openrouter/ai-sdk-provider": "^0.0.5", "@openrouter/ai-sdk-provider": "^0.0.5",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-tooltip": "^1.1.4",
"@remix-run/cloudflare": "^2.10.2", "@remix-run/cloudflare": "^2.10.2",
"@remix-run/cloudflare-pages": "^2.10.2", "@remix-run/cloudflare-pages": "^2.10.2",
"@remix-run/react": "^2.10.2", "@remix-run/react": "^2.10.2",