fix: chat messages scroll effect (#544)

This commit is contained in:
Mahesh Sanikommu 2025-10-29 15:35:57 -07:00 committed by GitHub
parent 440b923225
commit 399f7beeeb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 169 additions and 169 deletions

View file

@ -18,14 +18,8 @@ export default function ChatPage() {
}, [chatId, setCurrentChatId])
return (
<div className="flex flex-col w-full">
<div className="flex flex-col h-[93vh]">
<div className="flex-1 flex justify-center min-h-0 w-full md:px-4">
<div className="flex flex-col min-h-0 w-full max-w-4xl">
<ChatMessages />
</div>
</div>
</div>
<div className="h-full overflow-hidden">
<ChatMessages />
</div>
)
}

View file

@ -42,11 +42,11 @@ export default function NavigationLayout({
}
}, [])
return (
<div className="relative min-h-screen">
<div className="relative h-screen flex flex-col">
<div className="sticky top-0 z-50 bg-background/80 backdrop-blur-md border-b border-white/10">
<Header onAddMemory={() => setShowAddMemoryView(true)} />
</div>
{children}
<div className="flex-1">{children}</div>
{showAddMemoryView && (
<AddMemoryView
initialTab="note"

View file

@ -412,177 +412,183 @@ export function ChatMessages() {
<div className="h-full flex flex-col w-full">
<div className="flex-1 relative">
<div
className="flex flex-col gap-2 absolute inset-0 overflow-y-auto px-4 pt-4 pb-7 scroll-pb-7 custom-scrollbar"
className="absolute inset-0 overflow-y-auto custom-scrollbar"
onScroll={onScroll}
ref={scrollContainerRef}
>
{messages.map((message) => (
<div
className={cn(
"flex my-2",
message.role === "user"
? "items-center flex-row-reverse gap-2"
: "flex-col",
)}
key={message.id}
>
<div className="flex flex-col gap-2 max-w-4xl mx-auto px-4 md:px-2 pt-4 pb-7 scroll-pb-7">
{messages.map((message) => (
<div
className={cn(
"flex flex-col gap-2 md:max-w-4/5",
"flex my-2",
message.role === "user"
? "bg-accent/50 px-3 py-1.5 border border-border rounded-lg"
: "",
? "items-center flex-row-reverse gap-2"
: "flex-col",
)}
key={message.id}
>
{message.parts
.filter((part) =>
["text", "tool-searchMemories", "tool-addMemory"].includes(
part.type,
),
)
.map((part, index) => {
switch (part.type) {
case "text":
return (
<div key={`${message.id}-${part.type}-${index}`}>
<Streamdown>{part.text}</Streamdown>
</div>
)
case "tool-searchMemories": {
switch (part.state) {
case "input-available":
case "input-streaming":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Spinner className="size-4" /> Searching
memories...
</div>
)
case "output-error":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<X className="size-4" /> Error recalling
memories
</div>
)
case "output-available": {
const output = part.output
const foundCount =
typeof output === "object" &&
output !== null &&
"count" in output
? Number(output.count) || 0
: 0
// @ts-expect-error
const results = Array.isArray(output?.results)
? // @ts-expect-error
output.results
: []
<div
className={cn(
"flex flex-col gap-2 ",
message.role === "user"
? "bg-accent/50 px-3 py-1.5 border border-border rounded-lg"
: "",
)}
>
{message.parts
.filter((part) =>
[
"text",
"tool-searchMemories",
"tool-addMemory",
].includes(part.type),
)
.map((part, index) => {
switch (part.type) {
case "text":
return (
<div key={`${message.id}-${part.type}-${index}`}>
<Streamdown>{part.text}</Streamdown>
</div>
)
case "tool-searchMemories": {
switch (part.state) {
case "input-available":
case "input-streaming":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Spinner className="size-4" /> Searching
memories...
</div>
)
case "output-error":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<X className="size-4" /> Error recalling
memories
</div>
)
case "output-available": {
const output = part.output
const foundCount =
typeof output === "object" &&
output !== null &&
"count" in output
? Number(output.count) || 0
: 0
// @ts-expect-error
const results = Array.isArray(output?.results)
? // @ts-expect-error
output.results
: []
return (
<ExpandableMemories
foundCount={foundCount}
key={`${message.id}-${part.type}-${index}`}
results={results}
/>
)
return (
<ExpandableMemories
foundCount={foundCount}
key={`${message.id}-${part.type}-${index}`}
results={results}
/>
)
}
default:
return null
}
default:
return null
}
}
case "tool-addMemory": {
switch (part.state) {
case "input-available":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Spinner className="size-4" /> Adding memory...
</div>
)
case "output-error":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<X className="size-4" /> Error adding memory
</div>
)
case "output-available":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Check className="size-4" /> Memory added
</div>
)
case "input-streaming":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Spinner className="size-4" /> Adding memory...
</div>
)
default:
return null
case "tool-addMemory": {
switch (part.state) {
case "input-available":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Spinner className="size-4" /> Adding
memory...
</div>
)
case "output-error":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<X className="size-4" /> Error adding memory
</div>
)
case "output-available":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Check className="size-4" /> Memory added
</div>
)
case "input-streaming":
return (
<div
className="text-sm flex items-center gap-2 text-muted-foreground"
key={`${message.id}-${part.type}-${index}`}
>
<Spinner className="size-4" /> Adding
memory...
</div>
)
default:
return null
}
}
default:
return null
}
default:
return null
}
})}
</div>
{message.role === "assistant" && (
<div className="flex items-center gap-0.5 mt-0.5">
<Button
className="size-7 text-muted-foreground hover:text-foreground"
onClick={() => {
navigator.clipboard.writeText(
message.parts
.filter((p) => p.type === "text")
?.map((p) => (p as MessagePart).text ?? "")
.join("\n") ?? "",
)
toast.success("Copied to clipboard")
}}
size="icon"
variant="ghost"
>
<Copy className="size-3.5" />
</Button>
<Button
className="size-6 text-muted-foreground hover:text-foreground"
onClick={() => regenerate({ messageId: message.id })}
size="icon"
variant="ghost"
>
<RotateCcw className="size-3.5" />
</Button>
})}
</div>
)}
</div>
))}
{status === "submitted" && (
<div className="flex text-muted-foreground justify-start gap-2 px-4 py-3 items-center w-full">
<Spinner className="size-4" />
<TextShimmer className="text-sm" duration={1.5}>
Thinking...
</TextShimmer>
</div>
)}
<div ref={bottomRef} />
{message.role === "assistant" && (
<div className="flex items-center gap-0.5 mt-0.5">
<Button
className="size-7 text-muted-foreground hover:text-foreground"
onClick={() => {
navigator.clipboard.writeText(
message.parts
.filter((p) => p.type === "text")
?.map((p) => (p as MessagePart).text ?? "")
.join("\n") ?? "",
)
toast.success("Copied to clipboard")
}}
size="icon"
variant="ghost"
>
<Copy className="size-3.5" />
</Button>
<Button
className="size-6 text-muted-foreground hover:text-foreground"
onClick={() => regenerate({ messageId: message.id })}
size="icon"
variant="ghost"
>
<RotateCcw className="size-3.5" />
</Button>
</div>
)}
</div>
))}
{status === "submitted" && (
<div className="flex text-muted-foreground justify-start gap-2 px-4 py-3 items-center w-full">
<Spinner className="size-4" />
<TextShimmer className="text-sm" duration={1.5}>
Thinking...
</TextShimmer>
</div>
)}
<div ref={bottomRef} />
</div>
</div>
<Button
@ -605,9 +611,9 @@ export function ChatMessages() {
</Button>
</div>
<div className="px-4 pb-4 pt-1 relative flex-shrink-0">
<div className="pb-4 px-4 md:px-2 max-w-4xl mx-auto w-full">
<form
className="flex flex-col items-end gap-3 bg-card border border-border rounded-[22px] p-3 relative shadow-lg dark:shadow-2xl"
className="flex flex-col items-end gap-3 border border-border rounded-[22px] p-3 relative shadow-lg dark:shadow-2xl"
onSubmit={(e) => {
e.preventDefault()
if (status === "submitted") return