mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-05 08:06:19 +00:00
added memory card improvements
This commit is contained in:
parent
df173d90b0
commit
14bd1dc39b
4 changed files with 167 additions and 11 deletions
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
49
apps/web/components/new/document-cards/youtube-preview.tsx
Normal file
49
apps/web/components/new/document-cards/youtube-preview.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
63
apps/web/components/new/document-cards/youtube-utils.ts
Normal file
63
apps/web/components/new/document-cards/youtube-utils.ts
Normal 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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -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" ||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue