diff --git a/apps/web/app/new/page.tsx b/apps/web/app/new/page.tsx
index 7ddd3630..d03b9dc9 100644
--- a/apps/web/app/new/page.tsx
+++ b/apps/web/app/new/page.tsx
@@ -1,9 +1,27 @@
+"use client"
+
import { Header } from "@/components/new/header"
+import { ChatSidebar } from "@/components/new/chat-sidebar"
+import { AnimatePresence } from "motion/react"
+import { MemoriesGrid } from "@/components/new/memories-grid"
export default function NewPage() {
return (
-
-
+
)
}
\ No newline at end of file
diff --git a/apps/web/components/new/chat-sidebar.tsx b/apps/web/components/new/chat-sidebar.tsx
new file mode 100644
index 00000000..974469fa
--- /dev/null
+++ b/apps/web/components/new/chat-sidebar.tsx
@@ -0,0 +1,204 @@
+"use client"
+
+import { useState } from "react"
+import { motion, AnimatePresence } from "motion/react"
+import NovaOrb from "@/components/nova/nova-orb"
+import { Button } from "@ui/components/button"
+import { HistoryIcon, PanelRightCloseIcon, SendIcon, SquarePenIcon } from "lucide-react"
+import { cn } from "@lib/utils"
+import { dmSansClassName } from "@/utils/fonts"
+
+export function ChatSidebar() {
+ const [message, setMessage] = useState("")
+ const [isChatOpen, setIsChatOpen] = useState(true)
+
+ // Static placeholder messages for UI display
+ const messages = [
+ //{
+ // message: "Sample memory content",
+ // type: "memory" as const,
+ // memories: [
+ // {
+ // url: "https://example.com",
+ // title: "Example Memory",
+ // description: "This is a sample memory description for UI display purposes.",
+ // fullContent: "This is a sample memory description for UI display purposes.",
+ // },
+ // ],
+ //},
+ ]
+
+ const handleSend = () => {
+ setMessage("")
+ }
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault()
+ handleSend()
+ }
+ }
+
+ const toggleChat = () => {
+ setIsChatOpen(!isChatOpen)
+ }
+
+ return (
+
+ {!isChatOpen ? (
+
+
+
+ Chat with Nova
+
+
+ ) : (
+
+
+
Chat Title
+
+
+
+
+
+
+
+
+
+
+ {messages.map((msg, i) => (
+
+ {msg.type === "waiting" ? (
+
+
+ {msg.message}
+
+ ) : (
+ <>
+
+ {i === 0 && (
+
+ )}
+
+
+ {msg.type === "memory" && (
+
+ {msg.memories?.map((memory) => (
+
+ {memory.title && (
+
+ {memory.title}
+
+ )}
+ {memory.url && (
+
+ {memory.url}
+
+ )}
+ {memory.description && (
+
+ {memory.description}
+
+ )}
+
+ ))}
+
+ )}
+ >
+ )}
+
+ ))}
+
+
+
+
+ )}
+
+ )
+}
+
diff --git a/apps/web/components/new/document-cards/file-preview.tsx b/apps/web/components/new/document-cards/file-preview.tsx
new file mode 100644
index 00000000..d8943db4
--- /dev/null
+++ b/apps/web/components/new/document-cards/file-preview.tsx
@@ -0,0 +1,101 @@
+"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 { PDF } from "@ui/assets/icons"
+import { FileText, Image, Video } from "lucide-react"
+
+type DocumentsResponse = z.infer
+type DocumentWithMemories = DocumentsResponse["documents"][0]
+
+function getFileTypeInfo(document: DocumentWithMemories): {
+ icon: React.ReactNode
+ extension: string
+ color?: string
+} {
+ const type = document.type?.toLowerCase()
+ const mimeType = document.metadata?.mimeType as string | undefined
+
+ if (mimeType) {
+ if (mimeType === "application/pdf") {
+ return {
+ icon: ,
+ extension: ".pdf",
+ color: "#DC2626",
+ }
+ }
+ if (mimeType.startsWith("image/")) {
+ const ext = mimeType.split("/")[1] || "jpg"
+ return {
+ icon: ,
+ extension: `.${ext}`,
+ }
+ }
+ if (mimeType.startsWith("video/")) {
+ const ext = mimeType.split("/")[1] || "mp4"
+ return {
+ icon: ,
+ extension: `.${ext}`,
+ }
+ }
+ }
+
+ // Fall back to document type
+ switch (type) {
+ case "pdf":
+ return {
+ icon: ,
+ extension: ".pdf",
+ color: "#DC2626",
+ }
+ case "image":
+ return {
+ icon: ,
+ extension: ".jpg",
+ }
+ case "video":
+ return {
+ icon: ,
+ extension: ".mp4",
+ }
+ default:
+ return {
+ icon: ,
+ extension: ".file",
+ }
+ }
+}
+
+export function FilePreview({ document }: { document: DocumentWithMemories }) {
+ const { icon, extension, color } = getFileTypeInfo(document)
+
+ return (
+
+ {color && (
+
+ )}
+
+ {icon}
+
+ {extension}
+
+
+ {document.content && (
+
+ {document.content}
+
+ )}
+
+ )
+}
+
diff --git a/apps/web/components/new/document-cards/google-docs-preview.tsx b/apps/web/components/new/document-cards/google-docs-preview.tsx
new file mode 100644
index 00000000..843955e3
--- /dev/null
+++ b/apps/web/components/new/document-cards/google-docs-preview.tsx
@@ -0,0 +1,59 @@
+"use client"
+
+import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
+import type { z } from "zod"
+import { dmSansClassName } from "@/utils/fonts"
+import { cn } from "@lib/utils"
+
+type DocumentsResponse = z.infer
+type DocumentWithMemories = DocumentsResponse["documents"][0]
+
+export function GoogleDocsPreview({ document }: { document: DocumentWithMemories }) {
+ return (
+
+
+
+
+ Google Docs
+
+
+ {document.content && (
+
+ {document.content}
+
+ )}
+
+ )
+}
+
diff --git a/apps/web/components/new/document-cards/note-preview.tsx b/apps/web/components/new/document-cards/note-preview.tsx
new file mode 100644
index 00000000..14a77d34
--- /dev/null
+++ b/apps/web/components/new/document-cards/note-preview.tsx
@@ -0,0 +1,97 @@
+"use client"
+
+import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
+import type { z } from "zod"
+import { dmSansClassName } from "@/utils/fonts"
+import { cn } from "@lib/utils"
+
+type DocumentsResponse = z.infer
+type DocumentWithMemories = DocumentsResponse["documents"][0]
+
+function NoteIcon() {
+ return (
+
+ )
+}
+
+export function NotePreview({ document }: { document: DocumentWithMemories }) {
+ return (
+
+
+ {document.content && (
+
+ {document.content}
+
+ )}
+
+ )
+}
+
diff --git a/apps/web/components/new/document-cards/tweet-preview.tsx b/apps/web/components/new/document-cards/tweet-preview.tsx
new file mode 100644
index 00000000..9f0b2728
--- /dev/null
+++ b/apps/web/components/new/document-cards/tweet-preview.tsx
@@ -0,0 +1,39 @@
+"use client"
+
+import { Suspense } from "react"
+import type { Tweet } from "react-tweet/api"
+import {
+ TweetContainer,
+ TweetHeader,
+ TweetBody,
+ TweetMedia,
+ enrichTweet,
+ TweetSkeleton,
+} from "react-tweet"
+import { cn } from "@lib/utils"
+import { dmSansClassName } from "@/utils/fonts"
+
+export function TweetPreview({ data }: { data: Tweet }) {
+ const parsedTweet = typeof data === "string" ? JSON.parse(data) : data
+ const tweet = enrichTweet(parsedTweet)
+
+ return (
+
+ }>
+
+
+
+ {tweet.mediaDetails?.length ? (
+
+ ) : null}
+
+
+
+ )
+}
+
diff --git a/apps/web/components/new/document-cards/website-preview.tsx b/apps/web/components/new/document-cards/website-preview.tsx
new file mode 100644
index 00000000..7740da60
--- /dev/null
+++ b/apps/web/components/new/document-cards/website-preview.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import { useState } from "react"
+import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"
+import type { z } from "zod"
+import { dmSansClassName } from "@/utils/fonts"
+import { cn } from "@lib/utils"
+
+type DocumentsResponse = z.infer
+type DocumentWithMemories = DocumentsResponse["documents"][0]
+
+export function WebsitePreview({ document }: { document: DocumentWithMemories }) {
+ const [imageError, setImageError] = useState(false)
+ const ogImage = (document as DocumentWithMemories & { ogImage?: string }).ogImage
+
+ return (
+
+ {ogImage && !imageError ? (
+
+

setImageError(true)}
+ loading="lazy"
+ />
+
+ ) : (
+
+
+ {document.title || "Untitled Document"}
+
+ {document.content && (
+
+ {document.content}
+
+ )}
+
+ )}
+
+ )
+}
+
diff --git a/apps/web/components/new/header.tsx b/apps/web/components/new/header.tsx
index 15b72812..64444c5a 100644
--- a/apps/web/components/new/header.tsx
+++ b/apps/web/components/new/header.tsx
@@ -4,8 +4,11 @@ import { Logo } from "@ui/assets/Logo"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import { useAuth } from "@lib/auth-context"
import { useEffect, useState } from "react"
-import { ChevronsLeftRight, Plus } from "lucide-react"
+import { ChevronsLeftRight, LayoutGridIcon, Plus, SearchIcon } from "lucide-react"
import { Button } from "@ui/components/button"
+import { cn } from "@lib/utils"
+import { dmSansClassName } from "@/utils/fonts"
+import { Tabs, TabsList, TabsTrigger } from "@ui/components/tabs"
export function Header() {
const { user } = useAuth()
@@ -30,19 +33,34 @@ export function Header() {
)}
-