OwnGPT.v2 / client /src /components /Chat /ChatArea.jsx
parthib07's picture
Upload 199 files
212c959 verified
import { useEffect, useRef, useState } from 'react'
import { ArrowDown, Sparkles } from 'lucide-react'
import MessageBubble from './MessageBubbleNext'
function getDisplayName(user) {
const preferred = user?.name || user?.username || 'there'
const rawName = String(preferred).trim()
if (!rawName || rawName === 'there') return 'there'
const withoutDomain = rawName.includes('@') ? rawName.split('@')[0] : rawName
const readable = withoutDomain
.replace(/[._-]+/g, ' ')
.replace(/\d+/g, ' ')
.trim()
const firstName = readable.split(/\s+/).filter(Boolean)[0] || 'there'
return firstName.charAt(0).toUpperCase() + firstName.slice(1)
}
export default function ChatArea({
messages,
loading,
user,
onCopyMessage,
onRegenerate,
onEdit,
onSpeak,
onPreviewFile,
speakingMessageId,
}) {
const scrollRef = useRef(null)
const autoScrollRef = useRef(true)
const previousMessageCountRef = useRef(messages.length)
const [showScrollButton, setShowScrollButton] = useState(false)
const displayName = getDisplayName(user)
const scrollToBottom = (behavior = 'smooth') => {
if (!scrollRef.current) return
scrollRef.current.scrollTo({
top: scrollRef.current.scrollHeight,
behavior,
})
}
const handleScroll = () => {
if (!scrollRef.current) return
const distanceFromBottom =
scrollRef.current.scrollHeight - scrollRef.current.scrollTop - scrollRef.current.clientHeight
const isNearBottom = distanceFromBottom < 120
autoScrollRef.current = isNearBottom
setShowScrollButton(!isNearBottom && messages.length > 0)
}
useEffect(() => {
if (!messages.length) return
const lastMessage = messages[messages.length - 1]
const nextBehavior =
previousMessageCountRef.current < messages.length && !lastMessage?.streaming ? 'smooth' : 'auto'
if (autoScrollRef.current || lastMessage?.role === 'user' || lastMessage?.streaming) {
const frame = window.requestAnimationFrame(() => {
scrollToBottom(nextBehavior)
})
previousMessageCountRef.current = messages.length
return () => window.cancelAnimationFrame(frame)
}
previousMessageCountRef.current = messages.length
}, [messages])
if (!messages.length && loading) {
return (
<div className="rich-scroll min-h-0 flex-1 overflow-y-auto px-4 py-8 sm:px-6">
<div className="mx-auto flex w-full max-w-3xl flex-col gap-4">
{Array.from({ length: 4 }).map((_, index) => (
<div
key={index}
className={`animate-pulse rounded-lg bg-secondary p-4 ${
index % 2 === 0 ? 'mr-auto w-10/12' : 'ml-auto w-7/12'
}`}
>
<div className="space-y-3">
<div className="h-3 rounded-full bg-background/80" />
<div className="h-3 w-11/12 rounded-full bg-background/80" />
<div className="h-3 w-8/12 rounded-full bg-background/80" />
</div>
</div>
))}
</div>
</div>
)
}
if (!messages.length && !loading) {
return (
<div className="flex min-h-0 flex-1 items-center justify-center overflow-y-auto px-4 py-10 sm:px-6">
<div className="flex flex-col items-center gap-3 text-center">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-secondary text-muted-foreground">
<Sparkles className="h-4 w-4" />
</div>
<h2 className="text-2xl font-semibold text-foreground sm:text-3xl">
Hi {displayName}, what can we make today?
</h2>
</div>
</div>
)
}
return (
<div className="relative min-h-0 flex-1">
<div
ref={scrollRef}
onScroll={handleScroll}
className="rich-scroll h-full min-h-0 overflow-y-auto"
>
<div className="py-4">
{messages.map((message) => (
<MessageBubble
key={message.id}
message={message}
user={user}
onCopyMessage={onCopyMessage}
onRegenerate={onRegenerate}
onEdit={onEdit}
onSpeak={onSpeak}
onPreviewFile={onPreviewFile}
speaking={speakingMessageId === message.id}
/>
))}
</div>
</div>
{showScrollButton ? (
<button
type="button"
onClick={() => {
autoScrollRef.current = true
setShowScrollButton(false)
scrollToBottom('smooth')
}}
className="absolute bottom-5 right-5 inline-flex h-10 w-10 items-center justify-center rounded-lg border border-border bg-card text-foreground shadow-panel transition hover:bg-secondary"
aria-label="Scroll to latest message"
>
<ArrowDown className="h-4 w-4" />
</button>
) : null}
</div>
)
}