aboutsummaryrefslogtreecommitdiff
path: root/packages/memory-graph/src/components/node-detail-panel.tsx
diff options
context:
space:
mode:
authornexxeln <[email protected]>2025-12-02 18:37:24 +0000
committernexxeln <[email protected]>2025-12-02 18:37:24 +0000
commitdfb0c05ab33cb20537002eaeb896e6b2ab35af25 (patch)
tree49ecaa46903671d96f2f9ebc5af688ab2ea2c7bd /packages/memory-graph/src/components/node-detail-panel.tsx
parentFix: Update discord links in README.md and CONTRIBUTING.md (#598) (diff)
downloadsupermemory-update-memory-graph.tar.xz
supermemory-update-memory-graph.zip
add spaces selector with search (#600)update-memory-graph
relevant files to review: \- memory-graph.tsx \- spaces-dropdown.tsx \- spaces-dropdown.css.ts
Diffstat (limited to 'packages/memory-graph/src/components/node-detail-panel.tsx')
-rw-r--r--packages/memory-graph/src/components/node-detail-panel.tsx384
1 files changed, 184 insertions, 200 deletions
diff --git a/packages/memory-graph/src/components/node-detail-panel.tsx b/packages/memory-graph/src/components/node-detail-panel.tsx
index e2ae0133..b022364d 100644
--- a/packages/memory-graph/src/components/node-detail-panel.tsx
+++ b/packages/memory-graph/src/components/node-detail-panel.tsx
@@ -1,11 +1,11 @@
-"use client";
-
-import { Badge } from "@/ui/badge";
-import { Button } from "@/ui/button";
-import { GlassMenuEffect } from "@/ui/glass-effect";
-import { Brain, Calendar, ExternalLink, FileText, Hash, X } from "lucide-react";
-import { motion } from "motion/react";
-import { memo } from "react";
+"use client"
+
+import { Badge } from "@/ui/badge"
+import { Button } from "@/ui/button"
+import { GlassMenuEffect } from "@/ui/glass-effect"
+import { Brain, Calendar, ExternalLink, FileText, Hash, X } from "lucide-react"
+import { motion } from "motion/react"
+import { memo } from "react"
import {
GoogleDocs,
GoogleDrive,
@@ -18,249 +18,233 @@ import {
NotionDoc,
OneDrive,
PDF,
-} from "@/assets/icons";
-import { HeadingH3Bold } from "@/ui/heading";
-import type {
- DocumentWithMemories,
- MemoryEntry,
-} from "@/types";
-import type { NodeDetailPanelProps } from "@/types";
-import * as styles from "./node-detail-panel.css";
+} from "@/assets/icons"
+import { HeadingH3Bold } from "@/ui/heading"
+import type { DocumentWithMemories, MemoryEntry } from "@/types"
+import type { NodeDetailPanelProps } from "@/types"
+import * as styles from "./node-detail-panel.css"
const formatDocumentType = (type: string) => {
// Special case for PDF
- if (type.toLowerCase() === "pdf") return "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(" ");
-};
+ .join(" ")
+}
const getDocumentIcon = (type: string) => {
- const iconProps = { className: "w-5 h-5 text-slate-300" };
+ const iconProps = { className: "w-5 h-5 text-slate-300" }
switch (type) {
case "google_doc":
- return <GoogleDocs {...iconProps} />;
+ return <GoogleDocs {...iconProps} />
case "google_sheet":
- return <GoogleSheets {...iconProps} />;
+ return <GoogleSheets {...iconProps} />
case "google_slide":
- return <GoogleSlides {...iconProps} />;
+ return <GoogleSlides {...iconProps} />
case "google_drive":
- return <GoogleDrive {...iconProps} />;
+ return <GoogleDrive {...iconProps} />
case "notion":
case "notion_doc":
- return <NotionDoc {...iconProps} />;
+ return <NotionDoc {...iconProps} />
case "word":
case "microsoft_word":
- return <MicrosoftWord {...iconProps} />;
+ return <MicrosoftWord {...iconProps} />
case "excel":
case "microsoft_excel":
- return <MicrosoftExcel {...iconProps} />;
+ return <MicrosoftExcel {...iconProps} />
case "powerpoint":
case "microsoft_powerpoint":
- return <MicrosoftPowerpoint {...iconProps} />;
+ return <MicrosoftPowerpoint {...iconProps} />
case "onenote":
case "microsoft_onenote":
- return <MicrosoftOneNote {...iconProps} />;
+ return <MicrosoftOneNote {...iconProps} />
case "onedrive":
- return <OneDrive {...iconProps} />;
+ return <OneDrive {...iconProps} />
case "pdf":
- return <PDF {...iconProps} />;
+ return <PDF {...iconProps} />
default:
- {/*@ts-ignore */}
- return <FileText {...iconProps} />;
+ {
+ /*@ts-ignore */
+ }
+ return <FileText {...iconProps} />
}
-};
-
-export const NodeDetailPanel = memo(
- function NodeDetailPanel({ node, onClose, variant = "console" }: NodeDetailPanelProps) {
- if (!node) return null;
-
- const isDocument = node.type === "document";
- const data = node.data;
+}
+
+export const NodeDetailPanel = memo(function NodeDetailPanel({
+ node,
+ onClose,
+ variant = "console",
+}: NodeDetailPanelProps) {
+ if (!node) return null
+
+ const isDocument = node.type === "document"
+ const data = node.data
+
+ return (
+ <motion.div
+ animate={{ opacity: 1 }}
+ className={styles.container}
+ exit={{ opacity: 0 }}
+ initial={{ opacity: 0 }}
+ transition={{
+ duration: 0.2,
+ ease: "easeInOut",
+ }}
+ >
+ {/* Glass effect background */}
+ <GlassMenuEffect rounded="xl" />
- return (
<motion.div
animate={{ opacity: 1 }}
- className={styles.container}
- exit={{ opacity: 0 }}
+ className={styles.content}
initial={{ opacity: 0 }}
- transition={{
- duration: 0.2,
- ease: "easeInOut",
- }}
+ transition={{ delay: 0.05, duration: 0.15 }}
>
- {/* Glass effect background */}
- <GlassMenuEffect rounded="xl" />
-
- <motion.div
- animate={{ opacity: 1 }}
- className={styles.content}
- initial={{ opacity: 0 }}
- transition={{ delay: 0.05, duration: 0.15 }}
- >
- <div className={styles.header}>
- <div className={styles.headerLeft}>
- {isDocument ? (
- getDocumentIcon((data as DocumentWithMemories).type ?? "")
- ) : (
+ <div className={styles.header}>
+ <div className={styles.headerLeft}>
+ {isDocument ? (
+ getDocumentIcon((data as DocumentWithMemories).type ?? "")
+ ) : (
// @ts-ignore
- <Brain className={styles.headerIconMemory} />
- )}
- <HeadingH3Bold>
- {isDocument ? "Document" : "Memory"}
- </HeadingH3Bold>
- </div>
- <motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
- <Button
- className={styles.closeButton}
- onClick={onClose}
- size="sm"
- variant="ghost"
- >
- {/* @ts-ignore */}
- <X className={styles.closeIcon} />
- </Button>
- </motion.div>
+ <Brain className={styles.headerIconMemory} />
+ )}
+ <HeadingH3Bold>{isDocument ? "Document" : "Memory"}</HeadingH3Bold>
</div>
+ <motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
+ <Button
+ className={styles.closeButton}
+ onClick={onClose}
+ size="sm"
+ variant="ghost"
+ >
+ {/* @ts-ignore */}
+ <X className={styles.closeIcon} />
+ </Button>
+ </motion.div>
+ </div>
+
+ <div className={styles.sections}>
+ {isDocument ? (
+ <>
+ <div className={styles.section}>
+ <span className={styles.sectionLabel}>Title</span>
+ <p className={styles.sectionValue}>
+ {(data as DocumentWithMemories).title || "Untitled Document"}
+ </p>
+ </div>
- <div className={styles.sections}>
- {isDocument ? (
- <>
+ {(data as DocumentWithMemories).summary && (
<div className={styles.section}>
- <span className={styles.sectionLabel}>
- Title
- </span>
- <p className={styles.sectionValue}>
- {(data as DocumentWithMemories).title ||
- "Untitled Document"}
+ <span className={styles.sectionLabel}>Summary</span>
+ <p className={styles.sectionValueTruncated}>
+ {(data as DocumentWithMemories).summary}
</p>
</div>
+ )}
- {(data as DocumentWithMemories).summary && (
- <div className={styles.section}>
- <span className={styles.sectionLabel}>
- Summary
- </span>
- <p className={styles.sectionValueTruncated}>
- {(data as DocumentWithMemories).summary}
- </p>
- </div>
- )}
+ <div className={styles.section}>
+ <span className={styles.sectionLabel}>Type</span>
+ <p className={styles.sectionValue}>
+ {formatDocumentType(
+ (data as DocumentWithMemories).type ?? "",
+ )}
+ </p>
+ </div>
- <div className={styles.section}>
- <span className={styles.sectionLabel}>
- Type
- </span>
- <p className={styles.sectionValue}>
- {formatDocumentType((data as DocumentWithMemories).type ?? "")}
- </p>
- </div>
+ <div className={styles.section}>
+ <span className={styles.sectionLabel}>Memory Count</span>
+ <p className={styles.sectionValue}>
+ {(data as DocumentWithMemories).memoryEntries.length} memories
+ </p>
+ </div>
+ {((data as DocumentWithMemories).url ||
+ (data as DocumentWithMemories).customId) && (
<div className={styles.section}>
- <span className={styles.sectionLabel}>
- Memory Count
- </span>
- <p className={styles.sectionValue}>
- {(data as DocumentWithMemories).memoryEntries.length}{" "}
- memories
- </p>
+ <span className={styles.sectionLabel}>URL</span>
+ <a
+ className={styles.link}
+ href={(() => {
+ const doc = data as DocumentWithMemories
+ if (doc.type === "google_doc" && doc.customId) {
+ return `https://docs.google.com/document/d/${doc.customId}`
+ }
+ if (doc.type === "google_sheet" && doc.customId) {
+ return `https://docs.google.com/spreadsheets/d/${doc.customId}`
+ }
+ if (doc.type === "google_slide" && doc.customId) {
+ return `https://docs.google.com/presentation/d/${doc.customId}`
+ }
+ return doc.url ?? undefined
+ })()}
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ {/* @ts-ignore */}
+ <ExternalLink className={styles.linkIcon} />
+ View Document
+ </a>
</div>
-
- {((data as DocumentWithMemories).url ||
- (data as DocumentWithMemories).customId) && (
- <div className={styles.section}>
- <span className={styles.sectionLabel}>
- URL
- </span>
- <a
- className={styles.link}
- href={(() => {
- const doc = data as DocumentWithMemories;
- if (doc.type === "google_doc" && doc.customId) {
- return `https://docs.google.com/document/d/${doc.customId}`;
- }
- if (doc.type === "google_sheet" && doc.customId) {
- return `https://docs.google.com/spreadsheets/d/${doc.customId}`;
- }
- if (doc.type === "google_slide" && doc.customId) {
- return `https://docs.google.com/presentation/d/${doc.customId}`;
- }
- return doc.url ?? undefined;
- })()}
- rel="noopener noreferrer"
- target="_blank"
- >
- {/* @ts-ignore */}
- <ExternalLink className={styles.linkIcon} />
- View Document
- </a>
- </div>
+ )}
+ </>
+ ) : (
+ <>
+ <div className={styles.section}>
+ <span className={styles.sectionLabel}>Memory</span>
+ <p className={styles.sectionValue}>
+ {(data as MemoryEntry).memory}
+ </p>
+ {(data as MemoryEntry).isForgotten && (
+ <Badge className={styles.badge} variant="destructive">
+ Forgotten
+ </Badge>
)}
- </>
- ) : (
- <>
- <div className={styles.section}>
- <span className={styles.sectionLabel}>
- Memory
- </span>
- <p className={styles.sectionValue}>
- {(data as MemoryEntry).memory}
+ {(data as MemoryEntry).forgetAfter && (
+ <p className={styles.expiryText}>
+ Expires:{" "}
+ {(data as MemoryEntry).forgetAfter
+ ? new Date(
+ (data as MemoryEntry).forgetAfter!,
+ ).toLocaleDateString()
+ : ""}{" "}
+ {"forgetReason" in data && (data as any).forgetReason
+ ? `- ${(data as any).forgetReason}`
+ : null}
</p>
- {(data as MemoryEntry).isForgotten && (
- <Badge className={styles.badge} variant="destructive">
- Forgotten
- </Badge>
- )}
- {(data as MemoryEntry).forgetAfter && (
- <p className={styles.expiryText}>
- Expires:{" "}
- {(data as MemoryEntry).forgetAfter
- ? new Date(
- (data as MemoryEntry).forgetAfter!,
- ).toLocaleDateString()
- : ""}{" "}
- {("forgetReason" in data &&
- (data as any).forgetReason
- ? `- ${(data as any).forgetReason}`
- : null)}
- </p>
- )}
- </div>
-
- <div className={styles.section}>
- <span className={styles.sectionLabel}>
- Space
- </span>
- <p className={styles.sectionValue}>
- {(data as MemoryEntry).spaceId || "Default"}
- </p>
- </div>
- </>
- )}
+ )}
+ </div>
- <div className={styles.footer}>
- <div className={styles.metadata}>
- <span className={styles.metadataItem}>
- {/* @ts-ignore */}
- <Calendar className={styles.metadataIcon} />
- {new Date(data.createdAt).toLocaleDateString()}
- </span>
- <span className={styles.metadataItem}>
- {/* @ts-ignore */}
- <Hash className={styles.metadataIcon} />
- {node.id}
- </span>
+ <div className={styles.section}>
+ <span className={styles.sectionLabel}>Space</span>
+ <p className={styles.sectionValue}>
+ {(data as MemoryEntry).spaceId || "Default"}
+ </p>
</div>
+ </>
+ )}
+
+ <div className={styles.footer}>
+ <div className={styles.metadata}>
+ <span className={styles.metadataItem}>
+ {/* @ts-ignore */}
+ <Calendar className={styles.metadataIcon} />
+ {new Date(data.createdAt).toLocaleDateString()}
+ </span>
+ <span className={styles.metadataItem}>
+ {/* @ts-ignore */}
+ <Hash className={styles.metadataIcon} />
+ {node.id}
+ </span>
</div>
</div>
- </motion.div>
+ </div>
</motion.div>
- );
- },
-);
+ </motion.div>
+ )
+})
-NodeDetailPanel.displayName = "NodeDetailPanel";
+NodeDetailPanel.displayName = "NodeDetailPanel"