added memory card improvements

This commit is contained in:
Mahesh Sanikommmu 2025-11-07 11:39:35 -08:00
parent df173d90b0
commit 14bd1dc39b
4 changed files with 167 additions and 11 deletions

View file

@ -79,18 +79,28 @@ function NoteIcon() {
export function NotePreview({ document }: { document: DocumentWithMemories }) {
return (
<div className="bg-[#0B1017] p-3 rounded-[18px] gap-3">
<div className="bg-[#0B1017] p-3 rounded-[18px] space-y-2">
<div className="flex items-center gap-1">
<NoteIcon />
<p className={cn(dmSansClassName(), "text-[12px] font-semibold")}>
Note
</p>
</div>
<div>
{
document.title && (
<p className={cn(dmSansClassName(), "text-[12px] font-semibold")}>
{document.title}
</p>
)
}
{document.content && (
<p className="text-[10px] text-[#737373] line-clamp-4">
{document.content}
</p>
)}
</div>
</div>
)
}

View file

@ -0,0 +1,49 @@
"use client"
import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
import type { z } from "zod"
import { dmSansClassName } from "@/utils/fonts"
import { cn } from "@lib/utils"
import { extractYouTubeVideoId } from "./youtube-utils"
type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>
type DocumentWithMemories = DocumentsResponse["documents"][0]
export function YoutubePreview({ document }: { document: DocumentWithMemories }) {
const videoId = extractYouTubeVideoId(document.url)
if (!videoId) {
return (
<div className="bg-[#0B1017] p-3 rounded-[18px] space-y-2">
{document.title && (
<p className={cn(dmSansClassName(), "text-[12px] font-semibold")}>
{document.title}
</p>
)}
{document.content && (
<p className="text-[10px] text-[#737373] line-clamp-4">
{document.content}
</p>
)}
</div>
)
}
const embedUrl = `https://www.youtube.com/embed/${videoId}`
return (
<div className="bg-[#0B1017] rounded-[18px] overflow-hidden">
<div className="relative w-full aspect-video bg-black">
<iframe
src={embedUrl}
title={document.title || "YouTube video"}
className="w-full h-full"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
loading="lazy"
/>
</div>
</div>
)
}

View file

@ -0,0 +1,63 @@
"use client"
import { useQuery } from "@tanstack/react-query"
export function isYouTubeUrl(url: string | undefined | null): boolean {
if (!url) return false
return (
url.includes("youtube.com") ||
url.includes("youtu.be") ||
url.includes("m.youtube.com")
)
}
export function extractYouTubeVideoId(url: string | undefined | null): string | null {
if (!url) return null
// Handle youtu.be format
const youtuBeMatch = url.match(/(?:youtu\.be\/)([a-zA-Z0-9_-]{11})/)
if (youtuBeMatch?.[1]) return youtuBeMatch[1]
// Handle youtube.com/watch?v= format
const watchMatch = url.match(/(?:youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/)
if (watchMatch?.[1]) return watchMatch[1]
// Handle youtube.com/embed/ format
const embedMatch = url.match(/(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/)
if (embedMatch?.[1]) return embedMatch[1]
// Handle m.youtube.com format
const mobileMatch = url.match(/(?:m\.youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/)
if (mobileMatch?.[1]) return mobileMatch[1]
return null
}
export function useYouTubeChannelName(url: string | undefined | null) {
const videoId = extractYouTubeVideoId(url)
const videoUrl = videoId
? `https://www.youtube.com/watch?v=${videoId}`
: url || ""
return useQuery({
queryKey: ["youtube-channel", videoUrl],
queryFn: async () => {
if (!videoUrl) return null
try {
const response = await fetch(
`https://www.youtube.com/oembed?url=${encodeURIComponent(videoUrl)}&format=json`,
)
if (!response.ok) return null
const data = (await response.json()) as { author_name?: string }
return data.author_name || null
} catch {
return null
}
},
enabled: !!videoUrl && isYouTubeUrl(url),
staleTime: 1000 * 60 * 60 * 24,
retry: 1,
})
}

View file

@ -19,6 +19,11 @@ import { WebsitePreview } from "./document-cards/website-preview"
import { GoogleDocsPreview } from "./document-cards/google-docs-preview"
import { FilePreview } from "./document-cards/file-preview"
import { NotePreview } from "./document-cards/note-preview"
import { YoutubePreview } from "./document-cards/youtube-preview"
import {
isYouTubeUrl,
useYouTubeChannelName,
} from "./document-cards/youtube-utils"
type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema>
type DocumentWithMemories = DocumentsResponse["documents"][0]
@ -193,6 +198,37 @@ export function MemoriesGrid() {
)
}
function DocumentUrlDisplay({ url }: { url: string }) {
const isYouTube = isYouTubeUrl(url)
const { data: channelName, isLoading } = useYouTubeChannelName(
isYouTube ? url : null,
)
if (isYouTube) {
return (
<p
className={cn(
dmSansClassName(),
"text-[10px] text-[#737373] line-clamp-1",
)}
>
{isLoading ? "YouTube" : channelName || "YouTube"}
</p>
)
}
return (
<p
className={cn(
dmSansClassName(),
"text-[10px] text-[#737373] line-clamp-1",
)}
>
{getAbsoluteUrl(url)}
</p>
)
}
const DocumentCard = memo(
({
index: _index,
@ -207,6 +243,7 @@ const DocumentCard = memo(
<div className="rounded-[22px] bg-[#1B1F24] px-1 space-y-2 pt-1" style={{ width }}>
<ContentPreview document={document} />
<div className="pb-[10px] space-y-1">
{document.url && !document.url.includes("x.com") && !document.url.includes("twitter.com") && !document.url.includes("files.supermemory.ai") && (
<div className="px-3">
<p
className={cn(
@ -216,17 +253,10 @@ const DocumentCard = memo(
>
{document.title}
</p>
{document.url && (
<p
className={cn(
dmSansClassName(),
"text-[10px] text-[#737373] line-clamp-1",
)}
>
{getAbsoluteUrl(document.url)}
</p>
)}
<DocumentUrlDisplay url={document.url} />
</div>
)}
<div className="flex items-center justify-between px-3">
<p
className={cn(
@ -353,6 +383,10 @@ function ContentPreview({ document }: { document: DocumentWithMemories }) {
)
}
if (isYouTubeUrl(document.url)) {
return <YoutubePreview document={document} />
}
if (
document.type === "pdf" ||
document.type === "image" ||