ui: mobile chatbot improvements (#1155)
This commit is contained in:
parent
fd8ed4d863
commit
31de38ab18
16 changed files with 547 additions and 449 deletions
|
|
@ -1,15 +1,14 @@
|
|||
import { PreviewMessage, ThinkingMessage } from './message';
|
||||
import { Greeting } from './greeting';
|
||||
import { memo } from 'react';
|
||||
import { memo, useEffect } from 'react';
|
||||
import type { Vote } from '@/lib/db/schema';
|
||||
import equal from 'fast-deep-equal';
|
||||
import type { UseChatHelpers } from '@ai-sdk/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useMessages } from '@/hooks/use-messages';
|
||||
import type { ChatMessage } from '@/lib/types';
|
||||
import { useDataStream } from './data-stream-provider';
|
||||
import { Conversation, ConversationContent, ConversationScrollButton } from './elements/conversation';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Conversation, ConversationContent } from './elements/conversation';
|
||||
import { ArrowDownIcon } from 'lucide-react';
|
||||
|
||||
interface MessagesProps {
|
||||
chatId: string;
|
||||
|
|
@ -20,6 +19,7 @@ interface MessagesProps {
|
|||
regenerate: UseChatHelpers<ChatMessage>['regenerate'];
|
||||
isReadonly: boolean;
|
||||
isArtifactVisible: boolean;
|
||||
selectedModelId: string;
|
||||
}
|
||||
|
||||
function PureMessages({
|
||||
|
|
@ -31,12 +31,13 @@ function PureMessages({
|
|||
regenerate,
|
||||
isReadonly,
|
||||
isArtifactVisible,
|
||||
selectedModelId,
|
||||
}: MessagesProps) {
|
||||
const {
|
||||
containerRef: messagesContainerRef,
|
||||
endRef: messagesEndRef,
|
||||
onViewportEnter,
|
||||
onViewportLeave,
|
||||
isAtBottom,
|
||||
scrollToBottom,
|
||||
hasSentMessage,
|
||||
} = useMessages({
|
||||
chatId,
|
||||
|
|
@ -45,10 +46,28 @@ function PureMessages({
|
|||
|
||||
useDataStream();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'submitted') {
|
||||
requestAnimationFrame(() => {
|
||||
const container = messagesContainerRef.current;
|
||||
if (container) {
|
||||
container.scrollTo({
|
||||
top: container.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
return (
|
||||
<div ref={messagesContainerRef} className="flex-1 overflow-y-auto">
|
||||
<Conversation className="flex flex-col min-w-0 gap-6 pt-4 pb-32 px-4 max-w-4xl mx-auto">
|
||||
<ConversationContent className="flex flex-col gap-6">
|
||||
<div
|
||||
ref={messagesContainerRef}
|
||||
className="overflow-y-scroll flex-1 touch-pan-y overscroll-behavior-contain -webkit-overflow-scrolling-touch"
|
||||
style={{ overflowAnchor: 'none' }}
|
||||
>
|
||||
<Conversation className="flex flex-col gap-4 px-2 pt-4 pb-4 mx-auto min-w-0 max-w-4xl md:gap-6 md:px-4">
|
||||
<ConversationContent className="flex flex-col gap-4 md:gap-6">
|
||||
{messages.length === 0 && <Greeting />}
|
||||
|
||||
{messages.map((message, index) => (
|
||||
|
|
@ -56,7 +75,9 @@ function PureMessages({
|
|||
key={message.id}
|
||||
chatId={chatId}
|
||||
message={message}
|
||||
isLoading={status === 'streaming' && messages.length - 1 === index}
|
||||
isLoading={
|
||||
status === 'streaming' && messages.length - 1 === index
|
||||
}
|
||||
vote={
|
||||
votes
|
||||
? votes.find((vote) => vote.messageId === message.id)
|
||||
|
|
@ -74,17 +95,28 @@ function PureMessages({
|
|||
|
||||
{status === 'submitted' &&
|
||||
messages.length > 0 &&
|
||||
messages[messages.length - 1].role === 'user' && <ThinkingMessage />}
|
||||
messages[messages.length - 1].role === 'user' &&
|
||||
selectedModelId !== 'chat-model-reasoning' && (
|
||||
<ThinkingMessage />
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
<div
|
||||
ref={messagesEndRef}
|
||||
className="shrink-0 min-w-[24px] min-h-[24px]"
|
||||
onViewportLeave={onViewportLeave}
|
||||
onViewportEnter={onViewportEnter}
|
||||
/>
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
|
||||
{!isAtBottom && (
|
||||
<button
|
||||
className="absolute bottom-40 left-1/2 z-10 p-2 rounded-full border shadow-lg transition-colors -translate-x-1/2 bg-background hover:bg-muted"
|
||||
onClick={() => scrollToBottom('smooth')}
|
||||
type="button"
|
||||
aria-label="Scroll to bottom"
|
||||
>
|
||||
<ArrowDownIcon className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -93,6 +125,7 @@ export const Messages = memo(PureMessages, (prevProps, nextProps) => {
|
|||
if (prevProps.isArtifactVisible && nextProps.isArtifactVisible) return true;
|
||||
|
||||
if (prevProps.status !== nextProps.status) return false;
|
||||
if (prevProps.selectedModelId !== nextProps.selectedModelId) return false;
|
||||
if (prevProps.messages.length !== nextProps.messages.length) return false;
|
||||
if (!equal(prevProps.messages, nextProps.messages)) return false;
|
||||
if (!equal(prevProps.votes, nextProps.votes)) return false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue