"use client"; import { ChevronDown, Download, Monitor } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import Balancer from "react-wrap-balancer"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { ExpandedMediaOverlay, useExpandedMedia } from "@/components/ui/expanded-gif-overlay"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { AUTH_TYPE, BACKEND_URL } from "@/lib/env-config"; import { trackLoginAttempt } from "@/lib/posthog/events"; import { cn } from "@/lib/utils"; const GoogleLogo = ({ className }: { className?: string }) => ( Google logo ); const TAB_ITEMS = [ { title: "General Assist", description: "Launch SurfSense instantly from any application.", src: "/homepage/hero_tutorial/general_assist.mp4", featured: true, }, { title: "Quick Assist", description: "Select text anywhere, then ask AI to explain, rewrite, or act on it.", src: "/homepage/hero_tutorial/quick_assist.mp4", featured: true, }, { title: "Extreme Assist", description: "Get inline writing suggestions powered by your knowledge base as you type in any app.", src: "/homepage/hero_tutorial/extreme_assist.mp4", featured: true, }, { title: "Watch Local Folder", description: "Watch a local folder and automatically sync file changes to your knowledge base. Works great with Obsidian vaults.", src: "/homepage/hero_tutorial/folder_watch.mp4", featured: true, }, // { // title: "Connect & Sync", // description: // "Connect data sources like Notion, Drive and Gmail. Automatically sync to keep them updated.", // src: "/homepage/hero_tutorial/ConnectorFlowGif.mp4", // featured: true, // }, // { // title: "Upload Documents", // description: "Upload documents directly, from images to massive PDFs.", // src: "/homepage/hero_tutorial/DocUploadGif.mp4", // featured: true, // }, { title: "Video & Presentations", description: "Create short videos and editable presentations with AI-generated visuals and narration from your sources.", src: "/homepage/hero_tutorial/video_gen_surf.mp4", featured: false, }, { title: "Search & Citation", description: "Ask questions and get cited responses from your knowledge base.", src: "/homepage/hero_tutorial/BSNCGif.mp4", featured: false, }, { title: "Document Q&A", description: "Mention specific documents in chat for targeted answers.", src: "/homepage/hero_tutorial/BQnaGif_compressed.mp4", featured: false, }, { title: "Reports", description: "Generate reports from your sources in many formats.", src: "/homepage/hero_tutorial/ReportGenGif_compressed.mp4", featured: false, }, { title: "Podcasts", description: "Turn anything into a podcast in under 20 seconds.", src: "/homepage/hero_tutorial/PodcastGenGif.mp4", featured: false, }, { title: "Image Generation", description: "Generate high-quality images easily from your conversations.", src: "/homepage/hero_tutorial/ImageGenGif.mp4", featured: false, }, { title: "Collaborative Chat", description: "Collaborate on AI-powered conversations in realtime with your team.", src: "/homepage/hero_realtime/RealTimeChatGif.mp4", featured: false, }, { title: "Comments", description: "Add comments and tag teammates on any message.", src: "/homepage/hero_realtime/RealTimeCommentsFlow.mp4", featured: false, }, ] as const; export function HeroSection() { return (

NotebookLM for Teams

An open source, privacy focused alternative to NotebookLM for teams with no data limits.

); } function GetStartedButton() { const isGoogleAuth = AUTH_TYPE === "GOOGLE"; const handleGoogleLogin = () => { trackLoginAttempt("google"); window.location.href = `${BACKEND_URL}/auth/google/authorize-redirect`; }; if (isGoogleAuth) { return ( ); } return ( Get Started ); } type OSInfo = { os: "macOS" | "Windows" | "Linux"; arch: "arm64" | "x64"; }; function useUserOS(): OSInfo { const [info, setInfo] = useState({ os: "macOS", arch: "arm64" }); useEffect(() => { const ua = navigator.userAgent; let os: OSInfo["os"] = "macOS"; let arch: OSInfo["arch"] = "x64"; if (/Windows/i.test(ua)) { os = "Windows"; arch = "x64"; } else if (/Linux/i.test(ua)) { os = "Linux"; arch = "x64"; } else { os = "macOS"; arch = /Mac/.test(ua) && !/Intel/.test(ua) ? "arm64" : "arm64"; } const uaData = (navigator as Navigator & { userAgentData?: { architecture?: string } }) .userAgentData; if (uaData?.architecture === "arm") arch = "arm64"; else if (uaData?.architecture === "x86") arch = "x64"; setInfo({ os, arch }); }, []); return info; } interface ReleaseAsset { name: string; url: string; } function useLatestRelease() { const [assets, setAssets] = useState([]); useEffect(() => { const controller = new AbortController(); fetch("https://api.github.com/repos/MODSetter/SurfSense/releases/latest", { signal: controller.signal, }) .then((r) => r.json()) .then((data) => { if (data?.assets) { setAssets( data.assets .filter((a: { name: string }) => /\.(exe|dmg|AppImage|deb)$/.test(a.name)) .map((a: { name: string; browser_download_url: string }) => ({ name: a.name, url: a.browser_download_url, })) ); } }) .catch(() => {}); return () => controller.abort(); }, []); return assets; } const ASSET_LABELS: Record = { ".exe": "Windows (exe)", "-arm64.dmg": "macOS Apple Silicon (dmg)", "-x64.dmg": "macOS Intel (dmg)", "-arm64.zip": "macOS Apple Silicon (zip)", "-x64.zip": "macOS Intel (zip)", ".AppImage": "Linux (AppImage)", ".deb": "Linux (deb)", }; function getAssetLabel(name: string): string { for (const [suffix, label] of Object.entries(ASSET_LABELS)) { if (name.endsWith(suffix)) return label; } return name; } function DownloadButton() { const { os, arch } = useUserOS(); const assets = useLatestRelease(); const { primary, alternatives } = useMemo(() => { if (assets.length === 0) return { primary: null, alternatives: [] }; const matchers: Record boolean> = { Windows: (n) => n.endsWith(".exe"), macOS: (n) => n.endsWith(`-${arch}.dmg`), Linux: (n) => n.endsWith(".AppImage"), }; const match = matchers[os]; const primary = assets.find((a) => match(a.name)) ?? null; const alternatives = assets.filter((a) => a !== primary); return { primary, alternatives }; }, [assets, os, arch]); const fallbackUrl = GITHUB_RELEASES_URL; if (!primary) { return ( Download for {os} ); } return (
Download for {os} {alternatives.map((asset) => ( {getAssetLabel(asset.name)} ))} All downloads
); } const BrowserWindow = () => { const [selectedIndex, setSelectedIndex] = useState(0); const selectedItem = TAB_ITEMS[selectedIndex]; const { expanded, open, close } = useExpandedMedia(); return ( <>
{TAB_ITEMS.map((item, index) => ( {index !== TAB_ITEMS.length - 1 && (
)} ))}

{selectedItem.title}

{selectedItem.description}

{expanded && ( )} ); }; const TabVideo = memo(function TabVideo({ src }: { src: string }) { const videoRef = useRef(null); const [hasLoaded, setHasLoaded] = useState(false); useEffect(() => { setHasLoaded(false); const video = videoRef.current; if (!video) return; video.currentTime = 0; video.play().catch(() => {}); }, [src]); const handleCanPlay = useCallback(() => { setHasLoaded(true); }, []); return (