aboutsummaryrefslogtreecommitdiff
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
parentfix: env vars (diff)
downloadsupermemory-3816666e2d9b5eaa6d8a0d0f0c838ede41a69f44.tar.xz
supermemory-3816666e2d9b5eaa6d8a0d0f0c838ede41a69f44.zip
ui (memory detail): improved memory detail view and open chat
-rw-r--r--apps/web/app/page.tsx556
-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
-rw-r--r--apps/web/lib/document-icon.tsx54
-rw-r--r--packages/ui/components/sheet.tsx2
6 files changed, 768 insertions, 769 deletions
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 (
- <div className="relative h-screen bg-[#0f1419] overflow-hidden touch-none">
- {/* Main content area */}
- <motion.div
- animate={{
- marginRight: isOpen && !isMobile ? 600 : 0,
- }}
- className="h-full relative"
- transition={{
- duration: 0.2,
- ease: [0.4, 0, 0.2, 1], // Material Design easing - snappy but smooth
- }}
- >
- <motion.div
- animate={{ opacity: 1, y: 0 }}
- className="absolute md:top-4 md:right-4 md:bottom-auto md:left-auto bottom-8 left-6 z-20 rounded-xl overflow-hidden"
- id={TOUR_STEP_IDS.VIEW_TOGGLE}
- initial={{ opacity: 0, y: -20 }}
- transition={{ type: "spring", stiffness: 300, damping: 25 }}
- >
- <GlassMenuEffect rounded="rounded-xl" />
- <div className="relative z-10 p-2 flex gap-1">
- <motion.button
- animate={{
- color: viewMode === "graph" ? "#93c5fd" : "#cbd5e1",
- }}
- className="relative h-8 px-3 flex items-center gap-2 text-sm font-medium rounded-md transition-colors"
- onClick={() => handleViewModeChange("graph")}
- transition={{ duration: 0.2 }}
- whileHover={{ scale: 1.02 }}
- whileTap={{ scale: 0.98 }}
- >
- {viewMode === "graph" && (
- <motion.div
- className="absolute inset-0 bg-blue-500/20 rounded-md"
- layoutId="activeBackground"
- transition={{
- type: "spring",
- stiffness: 400,
- damping: 30,
- }}
- />
- )}
- <span className="relative z-10 flex items-center gap-2">
- <LayoutGrid className="w-4 h-4" />
- <span className="hidden md:inline">Graph</span>
- </span>
- </motion.button>
-
- <motion.button
- animate={{
- color: viewMode === "list" ? "#93c5fd" : "#cbd5e1",
- }}
- className="relative h-8 px-3 flex items-center gap-2 text-sm font-medium rounded-md transition-colors"
- onClick={() => handleViewModeChange("list")}
- transition={{ duration: 0.2 }}
- whileHover={{ scale: 1.02 }}
- whileTap={{ scale: 0.98 }}
- >
- {viewMode === "list" && (
- <motion.div
- className="absolute inset-0 bg-blue-500/20 rounded-md"
- layoutId="activeBackground"
- transition={{
- type: "spring",
- stiffness: 400,
- damping: 30,
- }}
- />
- )}
- <span className="relative z-10 flex items-center gap-2">
- <List className="w-4 h-4" />
- <span className="hidden md:inline">List</span>
- </span>
- </motion.button>
- </div>
- </motion.div>
-
- {/* Animated content switching */}
- <AnimatePresence mode="wait">
- {viewMode === "graph" ? (
- <motion.div
- animate={{ opacity: 1, scale: 1 }}
- className="absolute inset-0"
- exit={{ opacity: 0, scale: 0.95 }}
- id={TOUR_STEP_IDS.MEMORY_GRAPH}
- initial={{ opacity: 0, scale: 0.95 }}
- key="graph"
- transition={{
- type: "spring",
- stiffness: 500,
- damping: 30,
- }}
- >
- <MemoryGraph
- documents={allDocuments}
- error={error}
- hasMore={hasMore}
- isLoading={isPending}
- isLoadingMore={isLoadingMore}
- legendId={TOUR_STEP_IDS.LEGEND}
- loadMoreDocuments={loadMoreDocuments}
- showSpacesSelector={false}
- totalLoaded={totalLoaded}
- variant="consumer"
- highlightDocumentIds={allHighlightDocumentIds}
- highlightsVisible={isOpen}
- occludedRightPx={isOpen && !isMobile ? 600 : 0}
- autoLoadOnViewport={false}
- isExperimental={isCurrentProjectExperimental}
- >
- <div className="absolute inset-0 flex items-center justify-center">
- <div className="rounded-xl overflow-hidden">
- <div className="relative z-10 text-slate-200 px-6 py-4 text-center">
- <p className="text-lg font-medium mb-2">
- No Memories to Visualize
- </p>
- <button
- type="button"
- className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer underline"
- onClick={() => setShowAddMemoryView(true)}
- >
- Create one?
- </button>
- </div>
- </div>
- </div>
- </MemoryGraph>
- </motion.div>
- ) : (
- <motion.div
- animate={{ opacity: 1, scale: 1 }}
- className="absolute inset-0 md:ml-18"
- exit={{ opacity: 0, scale: 0.95 }}
- id={TOUR_STEP_IDS.MEMORY_LIST}
- initial={{ opacity: 0, scale: 0.95 }}
- key="list"
- transition={{
- type: "spring",
- stiffness: 500,
- damping: 30,
- }}
- >
- <MemoryListView
- documents={allDocuments}
- error={error}
- hasMore={hasMore}
- isLoading={isPending}
- isLoadingMore={isLoadingMore}
- loadMoreDocuments={loadMoreDocuments}
- totalLoaded={totalLoaded}
- >
- <div className="absolute inset-0 flex items-center justify-center">
- <div className="rounded-xl overflow-hidden">
- <div className="relative z-10 text-slate-200 px-6 py-4 text-center">
- <p className="text-lg font-medium mb-2">
- No Memories to Visualize
- </p>
- <button
- className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer underline"
- onClick={() => setShowAddMemoryView(true)}
- type="button"
- >
- Create one?
- </button>
- </div>
- </div>
- </div>
- </MemoryListView>
- </motion.div>
- )}
- </AnimatePresence>
-
- {/* Top Bar */}
- <div className="absolute top-2 left-0 right-0 z-10 p-4 flex items-center justify-between">
- <div className="flex items-center gap-3 justify-between w-full md:w-fit md:justify-start">
- <Link
- className="pointer-events-auto"
- href="https://supermemory.ai"
- rel="noopener noreferrer"
- target="_blank"
- >
- <LogoFull
- className="h-8 hidden md:block"
- id={TOUR_STEP_IDS.LOGO}
- />
- <Logo className="h-8 md:hidden" id={TOUR_STEP_IDS.LOGO} />
- </Link>
-
- <div className="hidden sm:block">
- <ProjectSelector />
- </div>
-
- <ConnectAIModal>
- <Button
- variant="outline"
- size="sm"
- className="bg-white/5 hover:bg-white/10 border-white/20 text-white hover:text-white px-2 sm:px-3"
- >
- <Unplug className="h-4 w-4" />
- <span className="hidden sm:inline ml-2">
- Connect to your AI
- </span>
- <span className="sm:hidden ml-1">Connect AI</span>
- </Button>
- </ConnectAIModal>
- </div>
-
- <div>
- <Menu />
- </div>
- </div>
-
- {/* Floating Open Chat Button */}
- {!isOpen && !isMobile && (
- <motion.div
- animate={{ opacity: 1, scale: 1 }}
- className="fixed bottom-6 right-6 z-50"
- initial={{ opacity: 0, scale: 0.8 }}
- transition={{
- type: "spring",
- stiffness: 300,
- damping: 25,
- }}
- >
- <Button
- className="h-14 px-4 bg-blue-600 hover:bg-blue-700 text-white shadow-lg hover:shadow-xl transition-all duration-200 rounded-full flex items-center gap-2"
- onClick={() => setIsOpen(true)}
- size="lg"
- >
- <MessageSquare className="h-5 w-5" />
- <span className="font-medium">Open Chat</span>
- </Button>
- </motion.div>
- )}
- </motion.div>
-
- {/* Chat panel - positioned absolutely */}
- <motion.div
- className="fixed top-0 right-0 h-full z-50 md:z-auto"
- style={{
- width: isOpen ? (isMobile ? "100vw" : "600px") : 0,
- pointerEvents: isOpen ? "auto" : "none",
- }}
- id={TOUR_STEP_IDS.FLOATING_CHAT}
- >
- <motion.div
- animate={{ x: isOpen ? 0 : isMobile ? "100%" : 600 }}
- className="absolute inset-0"
- exit={{ x: isMobile ? "100%" : 600 }}
- initial={{ x: isMobile ? "100%" : 600 }}
- key="chat"
- transition={{
- type: "spring",
- stiffness: 500,
- damping: 40,
- }}
- >
- <ChatRewrite />
- </motion.div>
- </motion.div>
-
- {showAddMemoryView && (
- <AddMemoryView
- initialTab="note"
- onClose={() => setShowAddMemoryView(false)}
- />
- )}
-
- {/* Tour Alert Dialog */}
- <TourAlertDialog onOpenChange={setShowTourDialog} open={showTourDialog} />
-
- {/* Referral/Upgrade Modal */}
- <ReferralUpgradeModal
- isOpen={showReferralModal}
- onClose={() => setShowReferralModal(false)}
- />
- </div>
- );
+ <div className="relative h-screen bg-[#0f1419] overflow-hidden touch-none">
+ {/* Main content area */}
+ <motion.div
+ animate={{
+ marginRight: isOpen && !isMobile ? 600 : 0,
+ }}
+ className="h-full relative"
+ transition={{
+ duration: 0.2,
+ ease: [0.4, 0, 0.2, 1], // Material Design easing - snappy but smooth
+ }}
+ >
+ <motion.div
+ animate={{ opacity: 1, y: 0 }}
+ className="absolute md:top-4 md:right-4 md:bottom-auto md:left-auto bottom-8 left-6 z-20 rounded-xl overflow-hidden"
+ id={TOUR_STEP_IDS.VIEW_TOGGLE}
+ initial={{ opacity: 0, y: -20 }}
+ transition={{ type: 'spring', stiffness: 300, damping: 25 }}
+ >
+ <GlassMenuEffect rounded="rounded-xl" />
+ <div className="relative z-10 p-2 flex gap-1">
+ <motion.button
+ animate={{
+ color: viewMode === 'graph' ? '#93c5fd' : '#cbd5e1',
+ }}
+ className="relative h-8 px-3 flex items-center gap-2 text-sm font-medium rounded-md transition-colors"
+ onClick={() => handleViewModeChange('graph')}
+ transition={{ duration: 0.2 }}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {viewMode === 'graph' && (
+ <motion.div
+ className="absolute inset-0 bg-blue-500/20 rounded-md"
+ layoutId="activeBackground"
+ transition={{
+ type: 'spring',
+ stiffness: 400,
+ damping: 30,
+ }}
+ />
+ )}
+ <span className="relative z-10 flex items-center gap-2">
+ <LayoutGrid className="w-4 h-4" />
+ <span className="hidden md:inline">Graph</span>
+ </span>
+ </motion.button>
+
+ <motion.button
+ animate={{
+ color: viewMode === 'list' ? '#93c5fd' : '#cbd5e1',
+ }}
+ className="relative h-8 px-3 flex items-center gap-2 text-sm font-medium rounded-md transition-colors"
+ onClick={() => handleViewModeChange('list')}
+ transition={{ duration: 0.2 }}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
+ >
+ {viewMode === 'list' && (
+ <motion.div
+ className="absolute inset-0 bg-blue-500/20 rounded-md"
+ layoutId="activeBackground"
+ transition={{
+ type: 'spring',
+ stiffness: 400,
+ damping: 30,
+ }}
+ />
+ )}
+ <span className="relative z-10 flex items-center gap-2">
+ <List className="w-4 h-4" />
+ <span className="hidden md:inline">List</span>
+ </span>
+ </motion.button>
+ </div>
+ </motion.div>
+
+ {/* Animated content switching */}
+ <AnimatePresence mode="wait">
+ {viewMode === 'graph' ? (
+ <motion.div
+ animate={{ opacity: 1, scale: 1 }}
+ className="absolute inset-0"
+ exit={{ opacity: 0, scale: 0.95 }}
+ id={TOUR_STEP_IDS.MEMORY_GRAPH}
+ initial={{ opacity: 0, scale: 0.95 }}
+ key="graph"
+ transition={{
+ type: 'spring',
+ stiffness: 500,
+ damping: 30,
+ }}
+ >
+ <MemoryGraph
+ documents={allDocuments}
+ error={error}
+ hasMore={hasMore}
+ isLoading={isPending}
+ isLoadingMore={isLoadingMore}
+ legendId={TOUR_STEP_IDS.LEGEND}
+ loadMoreDocuments={loadMoreDocuments}
+ showSpacesSelector={false}
+ totalLoaded={totalLoaded}
+ variant="consumer"
+ highlightDocumentIds={allHighlightDocumentIds}
+ highlightsVisible={isOpen}
+ occludedRightPx={isOpen && !isMobile ? 600 : 0}
+ autoLoadOnViewport={false}
+ isExperimental={isCurrentProjectExperimental}
+ >
+ <div className="absolute inset-0 flex items-center justify-center">
+ <div className="rounded-xl overflow-hidden">
+ <div className="relative z-10 text-slate-200 px-6 py-4 text-center">
+ <p className="text-lg font-medium mb-2">
+ No Memories to Visualize
+ </p>
+ <button
+ type="button"
+ className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer underline"
+ onClick={() => setShowAddMemoryView(true)}
+ >
+ Create one?
+ </button>
+ </div>
+ </div>
+ </div>
+ </MemoryGraph>
+ </motion.div>
+ ) : (
+ <motion.div
+ animate={{ opacity: 1, scale: 1 }}
+ className="absolute inset-0 md:ml-18"
+ exit={{ opacity: 0, scale: 0.95 }}
+ id={TOUR_STEP_IDS.MEMORY_LIST}
+ initial={{ opacity: 0, scale: 0.95 }}
+ key="list"
+ transition={{
+ type: 'spring',
+ stiffness: 500,
+ damping: 30,
+ }}
+ >
+ <MemoryListView
+ documents={allDocuments}
+ error={error}
+ hasMore={hasMore}
+ isLoading={isPending}
+ isLoadingMore={isLoadingMore}
+ loadMoreDocuments={loadMoreDocuments}
+ totalLoaded={totalLoaded}
+ >
+ <div className="absolute inset-0 flex items-center justify-center">
+ <div className="rounded-xl overflow-hidden">
+ <div className="relative z-10 text-slate-200 px-6 py-4 text-center">
+ <p className="text-lg font-medium mb-2">
+ No Memories to Visualize
+ </p>
+ <button
+ className="text-sm text-blue-400 hover:text-blue-300 transition-colors cursor-pointer underline"
+ onClick={() => setShowAddMemoryView(true)}
+ type="button"
+ >
+ Create one?
+ </button>
+ </div>
+ </div>
+ </div>
+ </MemoryListView>
+ </motion.div>
+ )}
+ </AnimatePresence>
+
+ {/* Top Bar */}
+ <div className="absolute top-2 left-0 right-0 z-10 p-4 flex items-center justify-between">
+ <div className="flex items-center gap-3 justify-between w-full md:w-fit md:justify-start">
+ <Link
+ className="pointer-events-auto"
+ href="https://supermemory.ai"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ <LogoFull
+ className="h-8 hidden md:block"
+ id={TOUR_STEP_IDS.LOGO}
+ />
+ <Logo className="h-8 md:hidden" id={TOUR_STEP_IDS.LOGO} />
+ </Link>
+
+ <div className="hidden sm:block">
+ <ProjectSelector />
+ </div>
+
+ <ConnectAIModal>
+ <Button
+ variant="outline"
+ size="sm"
+ className="bg-white/5 hover:bg-white/10 border-white/20 text-white hover:text-white px-2 sm:px-3"
+ >
+ <Unplug className="h-4 w-4" />
+ <span className="hidden sm:inline ml-2">
+ Connect to your AI
+ </span>
+ <span className="sm:hidden ml-1">Connect AI</span>
+ </Button>
+ </ConnectAIModal>
+ </div>
+
+ <div>
+ <Menu />
+ </div>
+ </div>
+
+ {/* Floating Open Chat Button */}
+ {!isOpen && !isMobile && (
+ <motion.div
+ animate={{ opacity: 1, scale: 1 }}
+ className="fixed bottom-6 right-6 z-50"
+ initial={{ opacity: 0, scale: 0.8 }}
+ transition={{
+ type: 'spring',
+ stiffness: 300,
+ damping: 25,
+ }}
+ >
+ <Button
+ className="px-4 bg-white hover:bg-white/80 text-[#001A39] shadow-lg hover:shadow-xl transition-all duration-200 rounded-full flex items-center gap-2 cursor-pointer"
+ onClick={() => setIsOpen(true)}
+ size="lg"
+ >
+ <MessageSquare className="h-5 w-5" />
+ <span className="font-medium">Open Chat</span>
+ </Button>
+ </motion.div>
+ )}
+ </motion.div>
+
+ {/* Chat panel - positioned absolutely */}
+ <motion.div
+ className="fixed top-0 right-0 h-full z-50 md:z-auto"
+ style={{
+ width: isOpen ? (isMobile ? '100vw' : '600px') : 0,
+ pointerEvents: isOpen ? 'auto' : 'none',
+ }}
+ id={TOUR_STEP_IDS.FLOATING_CHAT}
+ >
+ <motion.div
+ animate={{ x: isOpen ? 0 : isMobile ? '100%' : 600 }}
+ className="absolute inset-0"
+ exit={{ x: isMobile ? '100%' : 600 }}
+ initial={{ x: isMobile ? '100%' : 600 }}
+ key="chat"
+ transition={{
+ type: 'spring',
+ stiffness: 500,
+ damping: 40,
+ }}
+ >
+ <ChatRewrite />
+ </motion.div>
+ </motion.div>
+
+ {showAddMemoryView && (
+ <AddMemoryView
+ initialTab="note"
+ onClose={() => setShowAddMemoryView(false)}
+ />
+ )}
+
+ {/* Tour Alert Dialog */}
+ <TourAlertDialog onOpenChange={setShowTourDialog} open={showTourDialog} />
+
+ {/* Referral/Upgrade Modal */}
+ <ReferralUpgradeModal
+ isOpen={showReferralModal}
+ onClose={() => setShowReferralModal(false)}
+ />
+ </div>
+ );
};
// 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..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}
diff --git a/apps/web/lib/document-icon.tsx b/apps/web/lib/document-icon.tsx
new file mode 100644
index 00000000..3a80b2e0
--- /dev/null
+++ b/apps/web/lib/document-icon.tsx
@@ -0,0 +1,54 @@
+import { colors } from '@repo/ui/memory-graph/constants';
+import {
+ GoogleDocs,
+ MicrosoftWord,
+ NotionDoc,
+ GoogleDrive,
+ GoogleSheets,
+ GoogleSlides,
+ PDF,
+ OneDrive,
+ MicrosoftOneNote,
+ MicrosoftPowerpoint,
+ MicrosoftExcel,
+} from '@ui/assets/icons';
+import { FileText } from 'lucide-react';
+
+export 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} />;
+ }
+};
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 (
<div
- className={cn("flex flex-col gap-1.5 p-4", className)}
+ className={cn("flex flex-col gap-1.5 py-4", className)}
data-slot="sheet-header"
{...props}
/>