aboutsummaryrefslogtreecommitdiff
path: root/apps/web/components
diff options
context:
space:
mode:
authorMahesh Sanikommmu <[email protected]>2025-08-23 00:38:57 -0700
committerMahesh Sanikommmu <[email protected]>2025-08-23 00:38:57 -0700
commit3816666e2d9b5eaa6d8a0d0f0c838ede41a69f44 (patch)
tree92ad10b35b9b9a07155304b560a283b53602a16c /apps/web/components
parentfix: env vars (diff)
downloadsupermemory-3816666e2d9b5eaa6d8a0d0f0c838ede41a69f44.tar.xz
supermemory-3816666e2d9b5eaa6d8a0d0f0c838ede41a69f44.zip
ui (memory detail): improved memory detail view and open chat
Diffstat (limited to 'apps/web/components')
-rw-r--r--apps/web/components/memories/index.tsx53
-rw-r--r--apps/web/components/memories/memory-detail.tsx375
-rw-r--r--apps/web/components/memory-list-view.tsx497
3 files changed, 435 insertions, 490 deletions
diff --git a/apps/web/components/memories/index.tsx b/apps/web/components/memories/index.tsx
new file mode 100644
index 00000000..97ef57bd
--- /dev/null
+++ b/apps/web/components/memories/index.tsx
@@ -0,0 +1,53 @@
+import type { DocumentWithMemories } from "@ui/memory-graph/types";
+
+export const formatDate = (date: string | Date) => {
+ const dateObj = new Date(date);
+ const now = new Date();
+ const currentYear = now.getFullYear();
+ const dateYear = dateObj.getFullYear();
+
+ const monthNames = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ const month = monthNames[dateObj.getMonth()];
+ const day = dateObj.getDate();
+
+ const getOrdinalSuffix = (n: number) => {
+ const s = ["th", "st", "nd", "rd"];
+ const v = n % 100;
+ return n + (s[(v - 20) % 10] || s[v] || s[0]!);
+ };
+
+ const formattedDay = getOrdinalSuffix(day);
+
+ if (dateYear !== currentYear) {
+ return `${month} ${formattedDay}, ${dateYear}`;
+ }
+
+ return `${month} ${formattedDay}`;
+};
+
+export const getSourceUrl = (document: DocumentWithMemories) => {
+ if (document.type === "google_doc" && document.customId) {
+ return `https://docs.google.com/document/d/${document.customId}`;
+ }
+ if (document.type === "google_sheet" && document.customId) {
+ return `https://docs.google.com/spreadsheets/d/${document.customId}`;
+ }
+ if (document.type === "google_slide" && document.customId) {
+ return `https://docs.google.com/presentation/d/${document.customId}`;
+ }
+ // Fallback to existing URL for all other document types
+ return document.url;
+}; \ No newline at end of file
diff --git a/apps/web/components/memories/memory-detail.tsx b/apps/web/components/memories/memory-detail.tsx
new file mode 100644
index 00000000..eeef6d89
--- /dev/null
+++ b/apps/web/components/memories/memory-detail.tsx
@@ -0,0 +1,375 @@
+import { getDocumentIcon } from '@/lib/document-icon';
+import {
+ Drawer,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+} from '@repo/ui/components/drawer';
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@repo/ui/components/sheet';
+import { colors } from '@repo/ui/memory-graph/constants';
+import type { DocumentsWithMemoriesResponseSchema } from '@repo/validation/api';
+import { Badge } from '@ui/components/badge';
+import { Brain, Calendar, ExternalLink, Sparkles } from 'lucide-react';
+import { memo, useState } from 'react';
+import type { z } from 'zod';
+import { formatDate, getSourceUrl } from '.';
+import { Label1Regular } from '@ui/text/label/label-1-regular';
+
+type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>;
+type DocumentWithMemories = DocumentsResponse['documents'][0];
+type MemoryEntry = DocumentWithMemories['memoryEntries'][0];
+
+const formatDocumentType = (type: string) => {
+ // Special case for PDF
+ if (type.toLowerCase() === 'pdf') return 'PDF';
+
+ // Replace underscores with spaces and capitalize each word
+ return type
+ .split('_')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
+ .join(' ');
+};
+
+const MemoryDetailItem = memo(({ memory }: { memory: MemoryEntry }) => {
+ return (
+ <button
+ className="p-4 rounded-lg transition-all relative overflow-hidden cursor-pointer"
+ style={{
+ backgroundColor: memory.isLatest
+ ? colors.memory.primary
+ : 'rgba(255, 255, 255, 0.02)',
+ }}
+ tabIndex={0}
+ type="button"
+ >
+ <div className="flex items-start gap-2 relative z-10">
+ <div
+ className="p-1 rounded"
+ style={{
+ backgroundColor: memory.isLatest
+ ? colors.memory.secondary
+ : 'transparent',
+ }}
+ >
+ <Brain
+ className={`w-4 h-4 flex-shrink-0 transition-all ${
+ memory.isLatest ? 'text-blue-400' : 'text-blue-400/50'
+ }`}
+ />
+ </div>
+ <div className="flex-1 space-y-2">
+ <Label1Regular
+ className="text-sm leading-relaxed text-left"
+ style={{ color: colors.text.primary }}
+ >
+ {memory.memory}
+ </Label1Regular>
+ <div className="flex gap-2 justify-between">
+ <div
+ className="flex items-center gap-4 text-xs"
+ style={{ color: colors.text.muted }}
+ >
+ <span className="flex items-center gap-1">
+ <Calendar className="w-3 h-3" />
+ {formatDate(memory.createdAt)}
+ </span>
+ <span className="font-mono">v{memory.version}</span>
+ {memory.sourceRelevanceScore && (
+ <span
+ className="flex items-center gap-1"
+ style={{
+ color:
+ memory.sourceRelevanceScore > 70
+ ? colors.accent.emerald
+ : colors.text.muted,
+ }}
+ >
+ <Sparkles className="w-3 h-3" />
+ {memory.sourceRelevanceScore}%
+ </span>
+ )}
+ </div>
+ <div className="flex items-center gap-2 flex-wrap">
+ {memory.isForgotten && (
+ <Badge
+ className="text-xs border-red-500/30 backdrop-blur-sm"
+ style={{
+ backgroundColor: colors.status.forgotten,
+ color: '#dc2626',
+ backdropFilter: 'blur(4px)',
+ WebkitBackdropFilter: 'blur(4px)',
+ }}
+ variant="destructive"
+ >
+ Forgotten
+ </Badge>
+ )}
+ {memory.isLatest && (
+ <Badge
+ className="text-xs"
+ style={{
+ backgroundColor: colors.memory.secondary,
+ color: colors.text.primary,
+ backdropFilter: 'blur(4px)',
+ WebkitBackdropFilter: 'blur(4px)',
+ }}
+ variant="default"
+ >
+ Latest
+ </Badge>
+ )}
+ {memory.forgetAfter && (
+ <Badge
+ className="text-xs backdrop-blur-sm"
+ style={{
+ color: colors.status.expiring,
+ backgroundColor: 'rgba(251, 165, 36, 0.1)',
+ backdropFilter: 'blur(4px)',
+ WebkitBackdropFilter: 'blur(4px)',
+ }}
+ variant="outline"
+ >
+ Expires: {formatDate(memory.forgetAfter)}
+ </Badge>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </button>
+ );
+});
+
+export const MemoryDetail = memo(
+ ({
+ document,
+ isOpen,
+ onClose,
+ isMobile,
+ }: {
+ document: DocumentWithMemories | null;
+ isOpen: boolean;
+ onClose: () => void;
+ isMobile: boolean;
+ }) => {
+ if (!document) return null;
+
+ const [isSummaryExpanded, setIsSummaryExpanded] = useState(false);
+ const activeMemories = document.memoryEntries.filter((m) => !m.isForgotten);
+ const forgottenMemories = document.memoryEntries.filter(
+ (m) => m.isForgotten
+ );
+
+ const HeaderContent = ({
+ TitleComponent,
+ }: {
+ TitleComponent: typeof SheetTitle | typeof DrawerTitle;
+ }) => (
+ <div className="flex items-start justify-between gap-2">
+ <div className="flex items-start gap-3 flex-1">
+ <div
+ className="p-2 rounded-lg"
+ style={{
+ backgroundColor: colors.background.secondary,
+ }}
+ >
+ {getDocumentIcon(document.type, 'w-5 h-5')}
+ </div>
+ <div className="flex-1">
+ <TitleComponent style={{ color: colors.text.primary }}>
+ {document.title || 'Untitled Document'}
+ </TitleComponent>
+ <div
+ className="flex items-center gap-2 mt-1 text-xs"
+ style={{ color: colors.text.muted }}
+ >
+ <span>{formatDocumentType(document.type)}</span>
+ <span>•</span>
+ <span>{formatDate(document.createdAt)}</span>
+ {document.url && (
+ <>
+ <span>•</span>
+ <button
+ className="flex items-center gap-1 transition-all hover:gap-2"
+ onClick={() => {
+ const sourceUrl = getSourceUrl(document);
+ window.open(sourceUrl ?? undefined, '_blank');
+ }}
+ style={{ color: colors.accent.primary }}
+ type="button"
+ >
+ View source
+ <ExternalLink className="w-3 h-3" />
+ </button>
+ </>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+
+ const SummarySection = () => {
+ if (!document.summary) return null;
+
+ const shouldShowToggle = document.summary.length > 200; // Show toggle for longer summaries
+
+ return (
+ <div
+ className="mt-4 p-3 rounded-lg"
+ style={{
+ backgroundColor: 'rgba(255, 255, 255, 0.03)',
+ border: '1px solid rgba(255, 255, 255, 0.08)',
+ }}
+ >
+ <p
+ className={`text-sm ${!isSummaryExpanded ? 'line-clamp-3' : ''}`}
+ style={{ color: colors.text.muted }}
+ >
+ {document.content}
+ </p>
+ {shouldShowToggle && (
+ <button
+ onClick={() => setIsSummaryExpanded(!isSummaryExpanded)}
+ className="mt-2 text-xs hover:underline transition-all"
+ style={{ color: colors.accent.primary }}
+ type="button"
+ >
+ {isSummaryExpanded ? 'Show less' : 'Show more'}
+ </button>
+ )}
+ </div>
+ );
+ };
+
+ const MemoryContent = () => (
+ <div className="space-y-6 px-6">
+ {activeMemories.length > 0 && (
+ <div>
+ <div
+ className="text-sm font-medium mb-2 flex items-start gap-2 py-2"
+ style={{
+ color: colors.text.secondary,
+ }}
+ >
+ Active Memories ({activeMemories.length})
+ </div>
+ <div className="space-y-3">
+ {activeMemories.map((memory, index) => (
+ <div
+ key={memory.id}
+ >
+ <MemoryDetailItem memory={memory} />
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+
+ {forgottenMemories.length > 0 && (
+ <div>
+ <div
+ className="text-sm font-medium mb-4 px-3 py-2 rounded-lg opacity-60"
+ style={{
+ color: colors.text.muted,
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
+ }}
+ >
+ Forgotten Memories ({forgottenMemories.length})
+ </div>
+ <div className="space-y-3 opacity-40">
+ {forgottenMemories.map((memory) => (
+ <MemoryDetailItem key={memory.id} memory={memory} />
+ ))}
+ </div>
+ </div>
+ )}
+
+ {activeMemories.length === 0 && forgottenMemories.length === 0 && (
+ <div
+ className="text-center py-12 rounded-lg"
+ style={{
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
+ }}
+ >
+ <Brain
+ className="w-12 h-12 mx-auto mb-4 opacity-30"
+ style={{ color: colors.text.muted }}
+ />
+ <p style={{ color: colors.text.muted }}>
+ No memories found for this document
+ </p>
+ </div>
+ )}
+ </div>
+ );
+
+ if (isMobile) {
+ return (
+ <Drawer onOpenChange={onClose} open={isOpen}>
+ <DrawerContent
+ className="border-0 p-0 overflow-hidden max-h-[90vh]"
+ style={{
+ backgroundColor: colors.background.secondary,
+ borderTop: `1px solid ${colors.document.border}`,
+ backdropFilter: 'blur(20px)',
+ WebkitBackdropFilter: 'blur(20px)',
+ }}
+ >
+ {/* Header section with glass effect */}
+ <div
+ className="p-4 relative border-b"
+ style={{
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
+ borderBottom: `1px solid ${colors.document.border}`,
+ }}
+ >
+ <DrawerHeader className="pb-0 px-0 text-left">
+ <HeaderContent TitleComponent={DrawerTitle} />
+ </DrawerHeader>
+
+ <SummarySection />
+ </div>
+
+ <div className="flex-1 memory-drawer-scroll overflow-y-auto">
+ <MemoryContent />
+ </div>
+ </DrawerContent>
+ </Drawer>
+ );
+ }
+
+ return (
+ <Sheet onOpenChange={onClose} open={isOpen}>
+ <SheetContent
+ className="w-full sm:max-w-2xl border-0 p-0 overflow-hidden"
+ style={{
+ backgroundColor: colors.background.secondary,
+ }}
+ >
+ <div
+ className="p-6 relative"
+ style={{
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
+ }}
+ >
+ <SheetHeader className="pb-0">
+ <HeaderContent TitleComponent={SheetTitle} />
+ </SheetHeader>
+
+ <SummarySection />
+ </div>
+
+ <div className="h-[calc(100vh-200px)] memory-sheet-scroll overflow-y-auto">
+ <MemoryContent />
+ </div>
+ </SheetContent>
+ </Sheet>
+ );
+ }
+);
diff --git a/apps/web/components/memory-list-view.tsx b/apps/web/components/memory-list-view.tsx
index 8269562a..2cff96fd 100644
--- a/apps/web/components/memory-list-view.tsx
+++ b/apps/web/components/memory-list-view.tsx
@@ -2,42 +2,14 @@
import { useIsMobile } from "@hooks/use-mobile";
import { cn } from "@lib/utils";
-import {
- GoogleDocs,
- GoogleDrive,
- GoogleSheets,
- GoogleSlides,
- MicrosoftExcel,
- MicrosoftOneNote,
- MicrosoftPowerpoint,
- MicrosoftWord,
- NotionDoc,
- OneDrive,
- PDF,
-} from "@repo/ui/assets/icons";
import { Badge } from "@repo/ui/components/badge";
import { Card, CardContent, CardHeader } from "@repo/ui/components/card";
-import {
- Drawer,
- DrawerContent,
- DrawerHeader,
- DrawerTitle,
-} from "@repo/ui/components/drawer";
-import {
- Sheet,
- SheetContent,
- SheetHeader,
- SheetTitle,
-} from "@repo/ui/components/sheet";
import { colors } from "@repo/ui/memory-graph/constants";
import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api";
import { useVirtualizer } from "@tanstack/react-virtual";
-import { Label1Regular } from "@ui/text/label/label-1-regular";
import {
Brain,
- Calendar,
ExternalLink,
- FileText,
Sparkles,
} from "lucide-react";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -45,9 +17,12 @@ import type { z } from "zod";
import useResizeObserver from "@/hooks/use-resize-observer";
import { analytics } from "@/lib/analytics";
+import { MemoryDetail } from "./memories/memory-detail";
+import { getDocumentIcon } from "@/lib/document-icon";
+import { formatDate, getSourceUrl } from "./memories";
+
type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>;
type DocumentWithMemories = DocumentsResponse["documents"][0];
-type MemoryEntry = DocumentWithMemories["memoryEntries"][0];
interface MemoryListViewProps {
children?: React.ReactNode;
@@ -85,222 +60,6 @@ const GreetingMessage = memo(() => {
);
});
-const formatDate = (date: string | Date) => {
- const dateObj = new Date(date);
- const now = new Date();
- const currentYear = now.getFullYear();
- const dateYear = dateObj.getFullYear();
-
- const monthNames = [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ];
- const month = monthNames[dateObj.getMonth()];
- const day = dateObj.getDate();
-
- const getOrdinalSuffix = (n: number) => {
- const s = ["th", "st", "nd", "rd"];
- const v = n % 100;
- return n + (s[(v - 20) % 10] || s[v] || s[0]!);
- };
-
- const formattedDay = getOrdinalSuffix(day);
-
- if (dateYear !== currentYear) {
- return `${month} ${formattedDay}, ${dateYear}`;
- }
-
- return `${month} ${formattedDay}`;
-};
-
-const formatDocumentType = (type: string) => {
- // Special case for PDF
- if (type.toLowerCase() === "pdf") return "PDF";
-
- // Replace underscores with spaces and capitalize each word
- return type
- .split("_")
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
- .join(" ");
-};
-
-const getDocumentIcon = (type: string, className: string) => {
- const iconProps = {
- className,
- style: { color: colors.text.muted },
- };
-
- switch (type) {
- case "google_doc":
- return <GoogleDocs {...iconProps} />;
- case "google_sheet":
- return <GoogleSheets {...iconProps} />;
- case "google_slide":
- return <GoogleSlides {...iconProps} />;
- case "google_drive":
- return <GoogleDrive {...iconProps} />;
- case "notion":
- case "notion_doc":
- return <NotionDoc {...iconProps} />;
- case "word":
- case "microsoft_word":
- return <MicrosoftWord {...iconProps} />;
- case "excel":
- case "microsoft_excel":
- return <MicrosoftExcel {...iconProps} />;
- case "powerpoint":
- case "microsoft_powerpoint":
- return <MicrosoftPowerpoint {...iconProps} />;
- case "onenote":
- case "microsoft_onenote":
- return <MicrosoftOneNote {...iconProps} />;
- case "onedrive":
- return <OneDrive {...iconProps} />;
- case "pdf":
- return <PDF {...iconProps} />;
- default:
- return <FileText {...iconProps} />;
- }
-};
-
-const getSourceUrl = (document: DocumentWithMemories) => {
- if (document.type === "google_doc" && document.customId) {
- return `https://docs.google.com/document/d/${document.customId}`;
- }
- if (document.type === "google_sheet" && document.customId) {
- return `https://docs.google.com/spreadsheets/d/${document.customId}`;
- }
- if (document.type === "google_slide" && document.customId) {
- return `https://docs.google.com/presentation/d/${document.customId}`;
- }
- // Fallback to existing URL for all other document types
- return document.url;
-};
-
-const MemoryDetailItem = memo(({ memory }: { memory: MemoryEntry }) => {
- return (
- <button
- className="p-4 rounded-lg border transition-all relative overflow-hidden cursor-pointer"
- style={{
- backgroundColor: memory.isLatest
- ? colors.memory.primary
- : "rgba(255, 255, 255, 0.02)",
- borderColor: memory.isLatest
- ? colors.memory.border
- : "rgba(255, 255, 255, 0.1)",
- backdropFilter: "blur(8px)",
- WebkitBackdropFilter: "blur(8px)",
- }}
- tabIndex={0}
- type="button"
- >
- <div className="flex items-start gap-2 relative z-10">
- <div
- className="p-1 rounded"
- style={{
- backgroundColor: memory.isLatest
- ? colors.memory.secondary
- : "transparent",
- }}
- >
- <Brain
- className={`w-4 h-4 flex-shrink-0 transition-all ${
- memory.isLatest ? "text-blue-400" : "text-blue-400/50"
- }`}
- />
- </div>
- <div className="flex-1 space-y-2">
- <Label1Regular
- className="text-sm leading-relaxed text-left"
- style={{ color: colors.text.primary }}
- >
- {memory.memory}
- </Label1Regular>
- <div className="flex items-center gap-2 flex-wrap">
- {memory.isForgotten && (
- <Badge
- className="text-xs border-red-500/30 backdrop-blur-sm"
- style={{
- backgroundColor: colors.status.forgotten,
- color: "#dc2626",
- backdropFilter: "blur(4px)",
- WebkitBackdropFilter: "blur(4px)",
- }}
- variant="destructive"
- >
- Forgotten
- </Badge>
- )}
- {memory.isLatest && (
- <Badge
- className="text-xs border-blue-400/30 backdrop-blur-sm"
- style={{
- backgroundColor: colors.memory.secondary,
- color: colors.accent.primary,
- backdropFilter: "blur(4px)",
- WebkitBackdropFilter: "blur(4px)",
- }}
- variant="default"
- >
- Latest
- </Badge>
- )}
- {memory.forgetAfter && (
- <Badge
- className="text-xs backdrop-blur-sm"
- style={{
- borderColor: colors.status.expiring,
- color: colors.status.expiring,
- backgroundColor: "rgba(251, 165, 36, 0.1)",
- backdropFilter: "blur(4px)",
- WebkitBackdropFilter: "blur(4px)",
- }}
- variant="outline"
- >
- Expires: {formatDate(memory.forgetAfter)}
- </Badge>
- )}
- </div>
- <div
- className="flex items-center gap-4 text-xs"
- style={{ color: colors.text.muted }}
- >
- <span className="flex items-center gap-1">
- <Calendar className="w-3 h-3" />
- {formatDate(memory.createdAt)}
- </span>
- <span className="font-mono">v{memory.version}</span>
- {memory.sourceRelevanceScore && (
- <span
- className="flex items-center gap-1"
- style={{
- color:
- memory.sourceRelevanceScore > 70
- ? colors.accent.emerald
- : colors.text.muted,
- }}
- >
- <Sparkles className="w-3 h-3" />
- {memory.sourceRelevanceScore}%
- </span>
- )}
- </div>
- </div>
- </div>
- </button>
- );
-});
-
const DocumentCard = memo(
({
document,
@@ -361,12 +120,12 @@ const DocumentCard = memo(
</div>
</CardHeader>
<CardContent className="relative z-10 px-0">
- {document.summary && (
+ {document.content && (
<p
className="text-xs line-clamp-2 mb-3"
style={{ color: colors.text.muted }}
>
- {document.summary}
+ {document.content}
</p>
)}
<div className="flex items-center gap-2 flex-wrap">
@@ -402,248 +161,6 @@ const DocumentCard = memo(
},
);
-const DocumentDetailSheet = memo(
- ({
- document,
- isOpen,
- onClose,
- isMobile,
- }: {
- document: DocumentWithMemories | null;
- isOpen: boolean;
- onClose: () => void;
- isMobile: boolean;
- }) => {
- if (!document) return null;
-
- const [isSummaryExpanded, setIsSummaryExpanded] = useState(false);
- const activeMemories = document.memoryEntries.filter((m) => !m.isForgotten);
- const forgottenMemories = document.memoryEntries.filter(
- (m) => m.isForgotten,
- );
-
- const HeaderContent = ({
- TitleComponent,
- }: {
- TitleComponent: typeof SheetTitle | typeof DrawerTitle;
- }) => (
- <div className="flex items-start justify-between gap-2">
- <div className="flex items-start gap-3 flex-1">
- <div
- className="p-2 rounded-lg"
- style={{
- backgroundColor: colors.document.secondary,
- border: `1px solid ${colors.document.border}`,
- }}
- >
- {getDocumentIcon(document.type, "w-5 h-5")}
- </div>
- <div className="flex-1">
- <TitleComponent style={{ color: colors.text.primary }}>
- {document.title || "Untitled Document"}
- </TitleComponent>
- <div
- className="flex items-center gap-2 mt-1 text-xs"
- style={{ color: colors.text.muted }}
- >
- <span>{formatDocumentType(document.type)}</span>
- <span>•</span>
- <span>{formatDate(document.createdAt)}</span>
- {document.url && (
- <>
- <span>•</span>
- <button
- className="flex items-center gap-1 transition-all hover:gap-2"
- onClick={() => {
- const sourceUrl = getSourceUrl(document);
- window.open(sourceUrl ?? undefined, "_blank");
- }}
- style={{ color: colors.accent.primary }}
- type="button"
- >
- View source
- <ExternalLink className="w-3 h-3" />
- </button>
- </>
- )}
- </div>
- </div>
- </div>
- </div>
- );
-
- const SummarySection = () => {
- if (!document.summary) return null;
-
- const shouldShowToggle = document.summary.length > 200; // Show toggle for longer summaries
-
- return (
- <div
- className="mt-4 p-3 rounded-lg"
- style={{
- backgroundColor: "rgba(255, 255, 255, 0.03)",
- border: "1px solid rgba(255, 255, 255, 0.08)",
- }}
- >
- <p
- className={`text-sm ${!isSummaryExpanded ? "line-clamp-3" : ""}`}
- style={{ color: colors.text.muted }}
- >
- {document.summary}
- </p>
- {shouldShowToggle && (
- <button
- onClick={() => setIsSummaryExpanded(!isSummaryExpanded)}
- className="mt-2 text-xs hover:underline transition-all"
- style={{ color: colors.accent.primary }}
- type="button"
- >
- {isSummaryExpanded ? "Show less" : "Show more"}
- </button>
- )}
- </div>
- );
- };
-
- const MemoryContent = () => (
- <div className="p-6 space-y-6">
- {activeMemories.length > 0 && (
- <div>
- <div
- className="text-sm font-medium mb-4 flex items-start gap-2 px-3 py-2 rounded-lg"
- style={{
- color: colors.text.secondary,
- backgroundColor: colors.memory.primary,
- border: `1px solid ${colors.memory.border}`,
- }}
- >
- <Brain className="w-4 h-4 text-blue-400" />
- Active Memories ({activeMemories.length})
- </div>
- <div className="space-y-3">
- {activeMemories.map((memory, index) => (
- <div
- className="animate-in fade-in slide-in-from-right-2"
- key={memory.id}
- style={{ animationDelay: `${index * 50}ms` }}
- >
- <MemoryDetailItem memory={memory} />
- </div>
- ))}
- </div>
- </div>
- )}
-
- {forgottenMemories.length > 0 && (
- <div>
- <div
- className="text-sm font-medium mb-4 px-3 py-2 rounded-lg opacity-60"
- style={{
- color: colors.text.muted,
- backgroundColor: "rgba(255, 255, 255, 0.02)",
- border: "1px solid rgba(255, 255, 255, 0.08)",
- }}
- >
- Forgotten Memories ({forgottenMemories.length})
- </div>
- <div className="space-y-3 opacity-40">
- {forgottenMemories.map((memory) => (
- <MemoryDetailItem key={memory.id} memory={memory} />
- ))}
- </div>
- </div>
- )}
-
- {activeMemories.length === 0 && forgottenMemories.length === 0 && (
- <div
- className="text-center py-12 rounded-lg"
- style={{
- backgroundColor: "rgba(255, 255, 255, 0.02)",
- border: "1px solid rgba(255, 255, 255, 0.08)",
- }}
- >
- <Brain
- className="w-12 h-12 mx-auto mb-4 opacity-30"
- style={{ color: colors.text.muted }}
- />
- <p style={{ color: colors.text.muted }}>
- No memories found for this document
- </p>
- </div>
- )}
- </div>
- );
-
- if (isMobile) {
- return (
- <Drawer onOpenChange={onClose} open={isOpen}>
- <DrawerContent
- className="border-0 p-0 overflow-hidden max-h-[90vh]"
- style={{
- backgroundColor: colors.background.secondary,
- borderTop: `1px solid ${colors.document.border}`,
- backdropFilter: "blur(20px)",
- WebkitBackdropFilter: "blur(20px)",
- }}
- >
- {/* Header section with glass effect */}
- <div
- className="p-4 relative border-b"
- style={{
- backgroundColor: "rgba(255, 255, 255, 0.02)",
- borderBottom: `1px solid ${colors.document.border}`,
- }}
- >
- <DrawerHeader className="pb-0 px-0 text-left">
- <HeaderContent TitleComponent={DrawerTitle} />
- </DrawerHeader>
-
- <SummarySection />
- </div>
-
- <div className="flex-1 memory-drawer-scroll overflow-y-auto">
- <MemoryContent />
- </div>
- </DrawerContent>
- </Drawer>
- );
- }
-
- return (
- <Sheet onOpenChange={onClose} open={isOpen}>
- <SheetContent
- className="w-full sm:max-w-2xl border-0 p-0 overflow-hidden"
- style={{
- backgroundColor: colors.background.secondary,
- borderLeft: `1px solid ${colors.document.border}`,
- backdropFilter: "blur(20px)",
- WebkitBackdropFilter: "blur(20px)",
- }}
- >
- {/* Header section with glass effect */}
- <div
- className="p-6 relative"
- style={{
- backgroundColor: "rgba(255, 255, 255, 0.02)",
- borderBottom: `1px solid ${colors.document.border}`,
- }}
- >
- <SheetHeader className="pb-0">
- <HeaderContent TitleComponent={SheetTitle} />
- </SheetHeader>
-
- <SummarySection />
- </div>
-
- <div className="h-[calc(100vh-200px)] memory-sheet-scroll overflow-y-auto">
- <MemoryContent />
- </div>
- </SheetContent>
- </Sheet>
- );
- },
-);
-
export const MemoryListView = ({
children,
documents,
@@ -831,7 +348,7 @@ export const MemoryListView = ({
)}
</div>
- <DocumentDetailSheet
+ <MemoryDetail
document={selectedDocument}
isOpen={isDetailOpen}
onClose={handleCloseDetails}