mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
fix: chat messages scroll effect (#544)
This commit is contained in:
parent
440b923225
commit
399f7beeeb
3 changed files with 169 additions and 169 deletions
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue