+
+
+ {/* Header */}
+
+
+
+
Memories
+ {health && (
+
+ {health.memoryCount} stored
+
+ )}
+
+ {health && (
+
+
+
{health.healthy ? "Sidecar online" : "Sidecar offline"}
+ {health.lastConsolidation && (
+
+ · Last consolidation: {new Intl.DateTimeFormat(undefined, { dateStyle: "short", timeStyle: "short" }).format(new Date(health.lastConsolidation))}
+
+ )}
+
+ )}
+
+
+ {/* Add memory */}
+
+
+ {/* Search */}
+
+
+ setSearch(e.target.value)}
+ placeholder="Search memories..."
+ className="pl-9"
+ />
+ {total > 0 && (
+
+ {total} result{total !== 1 ? "s" : ""}
+
+ )}
+
+
+ {/* Memory list */}
+ {loading ? (
+
+
+
+ ) : memories.length === 0 ? (
+
+
+
{search ? "No memories matched your search" : "No memories stored yet"}
+ {!search &&
Start chatting — memories will appear here automatically
}
+
+ ) : (
+
+ {memories.map((m) => (
+
+ ))}
+
+ )}
+
+ {/* Pagination */}
+ {totalPages > 1 && (
+
+
+
+ {page} / {totalPages}
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/chat/AssistantMessage.tsx b/src/components/chat/AssistantMessage.tsx
index 9f0b21e..b7b15c7 100644
--- a/src/components/chat/AssistantMessage.tsx
+++ b/src/components/chat/AssistantMessage.tsx
@@ -4,6 +4,8 @@ import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { ThinkingBlock } from "./ThinkingBlock";
import { ToolCallCard } from "./ToolCallCard";
+import { MemoryRecallCard } from "./MemoryRecallCard";
+import type { MemoryRecallState } from "@/hooks/useChat";
import { Button } from "@/components/ui/button";
import { Copy, Check } from "lucide-react";
import { useState, useCallback } from "react";
@@ -22,6 +24,7 @@ interface AssistantMessageProps {
content: string;
thinking?: string | null;
toolCalls?: ActiveToolCall[];
+ memoryRecall?: MemoryRecallState | null;
isStreaming?: boolean;
}
@@ -74,10 +77,18 @@ export function AssistantMessage({
content,
thinking,
toolCalls = [],
+ memoryRecall,
isStreaming,
}: AssistantMessageProps) {
return (
+ {memoryRecall && (
+
+ )}
{thinking &&
}
{toolCalls.map((tc) => (
diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx
index c9f1406..54eb6a0 100644
--- a/src/components/chat/ChatInput.tsx
+++ b/src/components/chat/ChatInput.tsx
@@ -10,7 +10,7 @@ import {
} from "react";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
-import { Send, Square, Paperclip, X, Youtube, Globe } from "lucide-react";
+import { Send, Square, Paperclip, X, Youtube, Globe, Brain } from "lucide-react";
import { cn } from "@/lib/utils";
import { YouTubeDialog } from "./YouTubeDialog";
import { WebpageDialog } from "./WebpageDialog";
@@ -27,7 +27,7 @@ interface WebpageAttachment {
}
interface ChatInputProps {
- onSend: (content: string, images: string[], transcripts?: TranscriptAttachment[], webpages?: WebpageAttachment[]) => void;
+ onSend: (content: string, images: string[], transcripts?: TranscriptAttachment[], webpages?: WebpageAttachment[], memoryEnabled?: boolean) => void;
onCancel: () => void;
isGenerating: boolean;
disabled?: boolean;
@@ -50,6 +50,7 @@ export function ChatInput({ onSend, onCancel, isGenerating, disabled }: ChatInpu
const [isDragging, setIsDragging] = useState(false);
const [youtubeOpen, setYoutubeOpen] = useState(false);
const [webpageOpen, setWebpageOpen] = useState(false);
+ const [memoryEnabled, setMemoryEnabled] = useState(true);
const textareaRef = useRef
(null);
const fileInputRef = useRef(null);
@@ -84,13 +85,14 @@ export function ChatInput({ onSend, onCancel, isGenerating, disabled }: ChatInpu
images,
transcripts.length > 0 ? transcripts : undefined,
webpages.length > 0 ? webpages : undefined,
+ memoryEnabled,
);
setValue("");
setImages([]);
setTranscripts([]);
setWebpages([]);
textareaRef.current?.focus();
- }, [value, images, transcripts, webpages, isGenerating, onSend]);
+ }, [value, images, transcripts, webpages, isGenerating, onSend, memoryEnabled]);
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
@@ -263,6 +265,17 @@ export function ChatInput({ onSend, onCancel, isGenerating, disabled }: ChatInpu
}}
/>
+
+
{isGenerating ? (
diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx
index f26909c..7291f5b 100644
--- a/src/components/layout/AppLayout.tsx
+++ b/src/components/layout/AppLayout.tsx
@@ -4,7 +4,7 @@ import { useState } from "react";
import Link from "next/link";
import { Sidebar } from "./Sidebar";
import { Button } from "@/components/ui/button";
-import { Menu, Settings } from "lucide-react";
+import { Menu, Settings, Brain } from "lucide-react";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
@@ -43,6 +43,16 @@ export function AppLayout({ children }: AppLayoutProps) {