Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions apps/web/app/(navigation)/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
)
}
4 changes: 2 additions & 2 deletions apps/web/app/(navigation)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
326 changes: 166 additions & 160 deletions apps/web/components/views/chat/chat-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
: []

return (
<ExpandableMemories
foundCount={foundCount}
key={`${message.id}-${part.type}-${index}`}
results={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}
/>
)
}
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
Expand All @@ -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
Expand Down
Loading