diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 335d79e3..d6edc122 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -388,284 +388,284 @@ const MemoryGraphPage = () => { }, []); return ( -
- {/* Main content area */} - - - -
- handleViewModeChange("graph")} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === "graph" && ( - - )} - - - Graph - - +
+ {/* Main content area */} + + + +
+ handleViewModeChange('graph')} + transition={{ duration: 0.2 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {viewMode === 'graph' && ( + + )} + + + Graph + + - handleViewModeChange("list")} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === "list" && ( - - )} - - - List - - -
-
+ handleViewModeChange('list')} + transition={{ duration: 0.2 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {viewMode === 'list' && ( + + )} + + + List + + +
+ - {/* Animated content switching */} - - {viewMode === "graph" ? ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- ) : ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- )} -
+ {/* Animated content switching */} + + {viewMode === 'graph' ? ( + + +
+
+
+

+ No Memories to Visualize +

+ +
+
+
+
+
+ ) : ( + + +
+
+
+

+ No Memories to Visualize +

+ +
+
+
+
+
+ )} +
- {/* Top Bar */} -
-
- - - - + {/* Top Bar */} +
+
+ + + + -
- -
+
+ +
- - - -
+ + + +
-
- -
-
+
+ +
+
- {/* Floating Open Chat Button */} - {!isOpen && !isMobile && ( - - - - )} - + {/* Floating Open Chat Button */} + {!isOpen && !isMobile && ( + + + + )} + - {/* Chat panel - positioned absolutely */} - - - - - + {/* Chat panel - positioned absolutely */} + + + + + - {showAddMemoryView && ( - setShowAddMemoryView(false)} - /> - )} + {showAddMemoryView && ( + setShowAddMemoryView(false)} + /> + )} - {/* Tour Alert Dialog */} - + {/* Tour Alert Dialog */} + - {/* Referral/Upgrade Modal */} - setShowReferralModal(false)} - /> -
- ); + {/* Referral/Upgrade Modal */} + setShowReferralModal(false)} + /> +
+ ); }; // Wrapper component to handle auth and waitlist checks 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..dad2a8a3 --- /dev/null +++ b/apps/web/components/memories/memory-detail.tsx @@ -0,0 +1,415 @@ +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 { + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from '@repo/ui/components/tabs'; +import { colors } from '@repo/ui/memory-graph/constants'; +import type { DocumentsWithMemoriesResponseSchema } from '@repo/validation/api'; +import { Badge } from '@ui/components/badge'; +import { Brain, Calendar, CircleUserRound, ExternalLink, List, Sparkles } from 'lucide-react'; +import { memo } from 'react'; +import type { z } from 'zod'; +import { formatDate, getSourceUrl } from '.'; +import { Label1Regular } from '@ui/text/label/label-1-regular'; + +type DocumentsResponse = z.infer; +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 ( + + ); +}); + +export const MemoryDetail = memo( + ({ + document, + isOpen, + onClose, + isMobile, + }: { + document: DocumentWithMemories | null; + isOpen: boolean; + onClose: () => void; + isMobile: boolean; + }) => { + if (!document) return null; + + const activeMemories = document.memoryEntries.filter((m) => !m.isForgotten); + const forgottenMemories = document.memoryEntries.filter( + (m) => m.isForgotten + ); + + const HeaderContent = ({ + TitleComponent, + }: { + TitleComponent: typeof SheetTitle | typeof DrawerTitle; + }) => ( +
+
+
+ {getDocumentIcon(document.type, 'w-5 h-5')} +
+
+ + {document.title || 'Untitled Document'} + +
+ {formatDocumentType(document.type)} + + {formatDate(document.createdAt)} + {document.url && ( + <> + + + + )} +
+
+
+
+ ); + + const ContentAndSummarySection = () => { + const hasContent = document.content && document.content.trim().length > 0; + const hasSummary = document.summary && document.summary.trim().length > 0; + + if (!hasContent && !hasSummary) return null; + + const defaultTab = hasContent ? 'content' : 'summary'; + + return ( +
+ + + {hasContent && ( + + + Original Content + + )} + {hasSummary && ( + + + Summary + + )} + + + {hasContent && ( + +
+

+ {document.content} +

+
+
+ )} + + {hasSummary && ( + +
+

+ {document.summary} +

+
+
+ )} +
+
+ ); + }; + + const MemoryContent = () => ( +
+ {activeMemories.length > 0 && ( +
+
+ Active Memories ({activeMemories.length}) +
+
+ {activeMemories.map((memory) => ( +
+ +
+ ))} +
+
+ )} + + {forgottenMemories.length > 0 && ( +
+
+ Forgotten Memories ({forgottenMemories.length}) +
+
+ {forgottenMemories.map((memory) => ( + + ))} +
+
+ )} + + {activeMemories.length === 0 && forgottenMemories.length === 0 && ( +
+ +

+ No memories found for this document +

+
+ )} +
+ ); + + if (isMobile) { + return ( + + + {/* Header section with glass effect */} +
+ + + + + +
+ +
+ +
+
+
+ ); + } + + return ( + + +
+ + + + + +
+ +
+ +
+
+
+ ); + } +); 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; 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 ; - case "google_sheet": - return ; - case "google_slide": - return ; - case "google_drive": - return ; - case "notion": - case "notion_doc": - return ; - case "word": - case "microsoft_word": - return ; - case "excel": - case "microsoft_excel": - return ; - case "powerpoint": - case "microsoft_powerpoint": - return ; - case "onenote": - case "microsoft_onenote": - return ; - case "onedrive": - return ; - case "pdf": - return ; - default: - return ; - } -}; - -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 ( - - ); -}); - const DocumentCard = memo( ({ document, @@ -361,12 +120,12 @@ const DocumentCard = memo( - {document.summary && ( + {document.content && (

- {document.summary} + {document.content}

)}
@@ -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; - }) => ( -
-
-
- {getDocumentIcon(document.type, "w-5 h-5")} -
-
- - {document.title || "Untitled Document"} - -
- {formatDocumentType(document.type)} - - {formatDate(document.createdAt)} - {document.url && ( - <> - - - - )} -
-
-
-
- ); - - const SummarySection = () => { - if (!document.summary) return null; - - const shouldShowToggle = document.summary.length > 200; // Show toggle for longer summaries - - return ( -
-

- {document.summary} -

- {shouldShowToggle && ( - - )} -
- ); - }; - - const MemoryContent = () => ( -
- {activeMemories.length > 0 && ( -
-
- - Active Memories ({activeMemories.length}) -
-
- {activeMemories.map((memory, index) => ( -
- -
- ))} -
-
- )} - - {forgottenMemories.length > 0 && ( -
-
- Forgotten Memories ({forgottenMemories.length}) -
-
- {forgottenMemories.map((memory) => ( - - ))} -
-
- )} - - {activeMemories.length === 0 && forgottenMemories.length === 0 && ( -
- -

- No memories found for this document -

-
- )} -
- ); - - if (isMobile) { - return ( - - - {/* Header section with glass effect */} -
- - - - - -
- -
- -
-
-
- ); - } - - return ( - - - {/* Header section with glass effect */} -
- - - - - -
- -
- -
-
-
- ); - }, -); - export const MemoryListView = ({ children, documents, @@ -831,7 +348,7 @@ export const MemoryListView = ({ )}
- { + const iconProps = { + className, + style: { color: colors.text.muted }, + }; + + switch (type) { + case 'google_doc': + return ; + case 'google_sheet': + return ; + case 'google_slide': + return ; + case 'google_drive': + return ; + case 'notion': + case 'notion_doc': + return ; + case 'word': + case 'microsoft_word': + return ; + case 'excel': + case 'microsoft_excel': + return ; + case 'powerpoint': + case 'microsoft_powerpoint': + return ; + case 'onenote': + case 'microsoft_onenote': + return ; + case 'onedrive': + return ; + case 'pdf': + return ; + default: + return ; + } +}; diff --git a/packages/ui/components/sheet.tsx b/packages/ui/components/sheet.tsx index 242a4688..fc49af38 100644 --- a/packages/ui/components/sheet.tsx +++ b/packages/ui/components/sheet.tsx @@ -83,7 +83,7 @@ function SheetContent({ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { return (