- Implement discuss mode toggle and UI in chat box - Add quick action buttons for file, message, implement and link actions - Extend markdown parser to handle quick action elements - Update message components to support discuss mode and quick actions - Add discuss prompt for technical consulting responses - Refactor chat components to support new functionality The changes introduce a new discuss mode that allows users to switch between code implementation and technical discussion modes. Quick action buttons provide immediate interaction options like opening files, sending messages, or switching modes.
117 lines
4.4 KiB
TypeScript
117 lines
4.4 KiB
TypeScript
import type { Message } from 'ai';
|
|
import { Fragment } from 'react';
|
|
import { classNames } from '~/utils/classNames';
|
|
import { AssistantMessage } from './AssistantMessage';
|
|
import { UserMessage } from './UserMessage';
|
|
import { useLocation } from '@remix-run/react';
|
|
import { db, chatId } from '~/lib/persistence/useChatHistory';
|
|
import { forkChat } from '~/lib/persistence/db';
|
|
import { toast } from 'react-toastify';
|
|
import { useStore } from '@nanostores/react';
|
|
import { profileStore } from '~/lib/stores/profile';
|
|
import { forwardRef } from 'react';
|
|
import type { ForwardedRef } from 'react';
|
|
|
|
interface MessagesProps {
|
|
id?: string;
|
|
className?: string;
|
|
isStreaming?: boolean;
|
|
messages?: Message[];
|
|
append?: (message: Message) => void;
|
|
chatMode?: 'discuss' | 'build';
|
|
setChatMode?: (mode: 'discuss' | 'build') => void;
|
|
}
|
|
|
|
export const Messages = forwardRef<HTMLDivElement, MessagesProps>(
|
|
(props: MessagesProps, ref: ForwardedRef<HTMLDivElement> | undefined) => {
|
|
const { id, isStreaming = false, messages = [] } = props;
|
|
const location = useLocation();
|
|
const profile = useStore(profileStore);
|
|
|
|
const handleRewind = (messageId: string) => {
|
|
const searchParams = new URLSearchParams(location.search);
|
|
searchParams.set('rewindTo', messageId);
|
|
window.location.search = 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 (
|
|
<div id={id} className={props.className} ref={ref}>
|
|
{messages.length > 0
|
|
? messages.map((message, index) => {
|
|
const { role, content, id: messageId, annotations } = message;
|
|
const isUserMessage = role === 'user';
|
|
const isFirst = index === 0;
|
|
const isLast = index === messages.length - 1;
|
|
const isHidden = annotations?.includes('hidden');
|
|
|
|
if (isHidden) {
|
|
return <Fragment key={index} />;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={index}
|
|
className={classNames('flex gap-4 p-6 py-5 w-full rounded-[calc(0.75rem-1px)]', {
|
|
'bg-bolt-elements-messages-background': isUserMessage || !isStreaming || (isStreaming && !isLast),
|
|
'bg-gradient-to-b from-bolt-elements-messages-background from-30% to-transparent':
|
|
isStreaming && isLast,
|
|
'mt-4': !isFirst,
|
|
})}
|
|
>
|
|
{isUserMessage && (
|
|
<div className="flex items-center justify-center w-[40px] h-[40px] overflow-hidden bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-500 rounded-full shrink-0 self-start">
|
|
{profile?.avatar ? (
|
|
<img
|
|
src={profile.avatar}
|
|
alt={profile?.username || 'User'}
|
|
className="w-full h-full object-cover"
|
|
loading="eager"
|
|
decoding="sync"
|
|
/>
|
|
) : (
|
|
<div className="i-ph:user-fill text-2xl" />
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="grid grid-col-1 w-full">
|
|
{isUserMessage ? (
|
|
<UserMessage content={content} />
|
|
) : (
|
|
<AssistantMessage
|
|
content={content}
|
|
annotations={message.annotations}
|
|
messageId={messageId}
|
|
onRewind={handleRewind}
|
|
onFork={handleFork}
|
|
append={props.append}
|
|
chatMode={props.chatMode}
|
|
setChatMode={props.setChatMode}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})
|
|
: null}
|
|
{isStreaming && (
|
|
<div className="text-center w-full text-bolt-elements-item-contentAccent i-svg-spinners:3-dots-fade text-4xl mt-4"></div>
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
);
|