mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-05-08 06:04:44 +00:00
Add 'grok-3-beta' to xAI provider and 'gemini-2.5-flash-preview-04-17' to Google provider. Also, ensure file saving when content is updated in WorkbenchStore and update streaming indicator styling in chat messages.
111 lines
4.2 KiB
TypeScript
111 lines
4.2 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[];
|
|
}
|
|
|
|
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}
|
|
/>
|
|
)}
|
|
</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>
|
|
);
|
|
},
|
|
);
|