diff options
| author | Dhravya Shah <[email protected]> | 2026-01-23 17:42:47 -0700 |
|---|---|---|
| committer | Dhravya Shah <[email protected]> | 2026-01-23 17:42:47 -0700 |
| commit | 4ca0f593a5d89695e101569f09debda5617c0ec6 (patch) | |
| tree | 60517a8e898965cf8120cc01c56f69baaff0d06e /apps/web/app | |
| parent | extract metadata ourselves (diff) | |
| parent | fix: cf build (#700) (diff) | |
| download | archived-supermemory-4ca0f593a5d89695e101569f09debda5617c0ec6.tar.xz archived-supermemory-4ca0f593a5d89695e101569f09debda5617c0ec6.zip | |
fix: merge conflicts
Diffstat (limited to 'apps/web/app')
| -rw-r--r-- | apps/web/app/(navigation)/layout.tsx | 12 | ||||
| -rw-r--r-- | apps/web/app/api/og/route.ts | 77 | ||||
| -rw-r--r-- | apps/web/app/new/page.tsx | 128 |
3 files changed, 214 insertions, 3 deletions
diff --git a/apps/web/app/(navigation)/layout.tsx b/apps/web/app/(navigation)/layout.tsx index 7b7628bb..68a67a93 100644 --- a/apps/web/app/(navigation)/layout.tsx +++ b/apps/web/app/(navigation)/layout.tsx @@ -3,6 +3,8 @@ import { GraphDialog } from "@/components/graph-dialog" import { Header } from "@/components/header" import { AddMemoryView } from "@/components/views/add-memory" +import { usePathname, useRouter } from "next/navigation" +import { useFeatureFlagEnabled } from "posthog-js/react" import { useEffect, useState } from "react" export default function NavigationLayout({ @@ -11,6 +13,16 @@ export default function NavigationLayout({ children: React.ReactNode }) { const [showAddMemoryView, setShowAddMemoryView] = useState(false) + const pathname = usePathname() + const router = useRouter() + const flagEnabled = useFeatureFlagEnabled("nova-alpha-access") + + useEffect(() => { + if (flagEnabled && !pathname.includes("/new")) { + router.replace("/new") + } + }, [flagEnabled, router, pathname]) + useEffect(() => { const handleKeydown = (event: KeyboardEvent) => { const target = event.target as HTMLElement diff --git a/apps/web/app/api/og/route.ts b/apps/web/app/api/og/route.ts index 97f024a5..4c61ebe5 100644 --- a/apps/web/app/api/og/route.ts +++ b/apps/web/app/api/og/route.ts @@ -37,6 +37,70 @@ function isPrivateHost(hostname: string): boolean { return privateIpPatterns.some((pattern) => pattern.test(hostname)) } +// File extensions that are not HTML and can't be scraped for OG data +const NON_HTML_EXTENSIONS = [ + ".pdf", + ".doc", + ".docx", + ".xls", + ".xlsx", + ".ppt", + ".pptx", + ".zip", + ".rar", + ".7z", + ".tar", + ".gz", + ".mp3", + ".mp4", + ".avi", + ".mov", + ".wmv", + ".flv", + ".webm", + ".wav", + ".ogg", + ".jpg", + ".jpeg", + ".png", + ".gif", + ".webp", + ".svg", + ".ico", + ".bmp", + ".tiff", + ".exe", + ".dmg", + ".iso", + ".bin", +] + +function isNonHtmlUrl(url: string): boolean { + try { + const urlObj = new URL(url) + const pathname = urlObj.pathname.toLowerCase() + return NON_HTML_EXTENSIONS.some((ext) => pathname.endsWith(ext)) + } catch { + return false + } +} + +function extractImageUrl(image: unknown): string | undefined { + if (!image) return undefined + + if (typeof image === "string") { + return image + } + + if (Array.isArray(image) && image.length > 0) { + const first = image[0] + if (first && typeof first === "object" && "url" in first) { + return String(first.url) + } + } + return "" +} + function extractMetaTag(html: string, patterns: RegExp[]): string { for (const pattern of patterns) { const match = html.match(pattern) @@ -101,6 +165,19 @@ export async function GET(request: Request) { ) } + // Skip OG scraping for non-HTML files (PDFs, images, etc.) + if (isNonHtmlUrl(trimmedUrl)) { + return Response.json( + { title: "", description: "" }, + { + headers: { + "Cache-Control": + "public, s-maxage=3600, stale-while-revalidate=86400", + }, + }, + ) + } + const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 8000) diff --git a/apps/web/app/new/page.tsx b/apps/web/app/new/page.tsx index afb077b3..31fab182 100644 --- a/apps/web/app/new/page.tsx +++ b/apps/web/app/new/page.tsx @@ -9,12 +9,17 @@ import { AddDocumentModal } from "@/components/new/add-document" import { MCPModal } from "@/components/new/mcp-modal" import { DocumentModal } from "@/components/new/document-modal" import { DocumentsCommandPalette } from "@/components/new/documents-command-palette" +import { FullscreenNoteModal } from "@/components/new/fullscreen-note-modal" +import type { HighlightItem } from "@/components/new/highlights-card" import { HotkeysProvider } from "react-hotkeys-hook" import { useHotkeys } from "react-hotkeys-hook" import { AnimatePresence } from "motion/react" import { useIsMobile } from "@hooks/use-mobile" import { useProject } from "@/stores" +import { useQuickNoteDraftReset } from "@/stores/quick-note-draft" import { analytics } from "@/lib/analytics" +import { useDocumentMutations } from "@/hooks/use-document-mutations" +import { useQuery } from "@tanstack/react-query" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" @@ -31,6 +36,56 @@ export default function NewPage() { useState<DocumentWithMemories | null>(null) const [isDocumentModalOpen, setIsDocumentModalOpen] = useState(false) + const [isFullScreenNoteOpen, setIsFullScreenNoteOpen] = useState(false) + const [fullscreenInitialContent, setFullscreenInitialContent] = useState("") + const [queuedChatSeed, setQueuedChatSeed] = useState<string | null>(null) + const [searchPrefill, setSearchPrefill] = useState("") + + const resetDraft = useQuickNoteDraftReset(selectedProject) + + const { noteMutation } = useDocumentMutations({ + onClose: () => { + resetDraft() + setIsFullScreenNoteOpen(false) + }, + }) + + // Fetch space highlights (highlights + suggested questions) + type SpaceHighlightsResponse = { + highlights: HighlightItem[] + questions: string[] + generatedAt: string + } + const { data: highlightsData, isLoading: isLoadingHighlights } = + useQuery<SpaceHighlightsResponse>({ + queryKey: ["space-highlights", selectedProject], + queryFn: async (): Promise<SpaceHighlightsResponse> => { + const response = await fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/space-highlights`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ + spaceId: selectedProject || "sm_project_default", + highlightsCount: 3, + questionsCount: 4, + includeHighlights: true, + includeQuestions: true, + }), + }, + ) + + if (!response.ok) { + throw new Error("Failed to fetch space highlights") + } + + return response.json() + }, + staleTime: 4 * 60 * 60 * 1000, // 4 hours (matches backend cache) + refetchOnWindowFocus: false, + }) + useHotkeys("c", () => { analytics.addDocumentModalOpened() setIsAddDocumentOpen(true) @@ -46,6 +101,42 @@ export default function NewPage() { setIsDocumentModalOpen(true) }, []) + const handleQuickNoteSave = useCallback( + (content: string) => { + if (content.trim()) { + noteMutation.mutate({ content, project: selectedProject }) + } + }, + [selectedProject, noteMutation], + ) + + const handleFullScreenSave = useCallback( + (content: string) => { + if (content.trim()) { + noteMutation.mutate({ content, project: selectedProject }) + } + }, + [selectedProject, noteMutation], + ) + + const handleMaximize = useCallback( + (content: string) => { + setFullscreenInitialContent(content) + setIsFullScreenNoteOpen(true) + }, + [], + ) + + const handleHighlightsChat = useCallback((seed: string) => { + setQueuedChatSeed(seed) + setIsChatOpen(true) + }, []) + + const handleHighlightsShowRelated = useCallback((query: string) => { + setSearchPrefill(query) + setIsSearchOpen(true) + }, []) + return ( <HotkeysProvider> <div className="bg-black min-h-screen"> @@ -69,10 +160,21 @@ export default function NewPage() { key={`main-container-${isChatOpen}`} className="z-10 flex flex-col md:flex-row relative" > - <div className="flex-1 p-4 md:p-6 md:pr-0"> + <div className="flex-1 p-4 md:p-6 md:pr-0 pt-2!"> <MemoriesGrid isChatOpen={isChatOpen} onOpenDocument={handleOpenDocument} + quickNoteProps={{ + onSave: handleQuickNoteSave, + onMaximize: handleMaximize, + isSaving: noteMutation.isPending, + }} + highlightsProps={{ + items: highlightsData?.highlights || [], + onChat: handleHighlightsChat, + onShowRelated: handleHighlightsShowRelated, + isLoading: isLoadingHighlights, + }} /> </div> <div className="hidden md:block md:sticky md:top-0 md:h-screen"> @@ -80,13 +182,22 @@ export default function NewPage() { <ChatSidebar isChatOpen={isChatOpen} setIsChatOpen={setIsChatOpen} + queuedMessage={queuedChatSeed} + onConsumeQueuedMessage={() => setQueuedChatSeed(null)} + emptyStateSuggestions={highlightsData?.questions} /> </AnimatePresence> </div> </main> {isMobile && ( - <ChatSidebar isChatOpen={isChatOpen} setIsChatOpen={setIsChatOpen} /> + <ChatSidebar + isChatOpen={isChatOpen} + setIsChatOpen={setIsChatOpen} + queuedMessage={queuedChatSeed} + onConsumeQueuedMessage={() => setQueuedChatSeed(null)} + emptyStateSuggestions={highlightsData?.questions} + /> )} <AddDocumentModal @@ -99,15 +210,26 @@ export default function NewPage() { /> <DocumentsCommandPalette open={isSearchOpen} - onOpenChange={setIsSearchOpen} + onOpenChange={(open) => { + setIsSearchOpen(open) + if (!open) setSearchPrefill("") + }} projectId={selectedProject} onOpenDocument={handleOpenDocument} + initialSearch={searchPrefill} /> <DocumentModal document={selectedDocument} isOpen={isDocumentModalOpen} onClose={() => setIsDocumentModalOpen(false)} /> + <FullscreenNoteModal + isOpen={isFullScreenNoteOpen} + onClose={() => setIsFullScreenNoteOpen(false)} + initialContent={fullscreenInitialContent} + onSave={handleFullScreenSave} + isSaving={noteMutation.isPending} + /> </div> </HotkeysProvider> ) |