"use client"; import { ChevronDown, Clock, Download, Monitor, Sparkles } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import Link from "next/link"; import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import Balancer from "react-wrap-balancer"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from "@/components/ui/empty"; import { ExpandedMediaOverlay, useExpandedMedia } from "@/components/ui/expanded-gif-overlay"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { GITHUB_RELEASES_URL, getAssetLabel, usePrimaryDownload, } from "@/lib/desktop-download-utils"; 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 ); type HeroUseCase = { id: string; title: string; description: string; src: string | null; comingSoon?: boolean; }; type HeroCategory = { id: string; label: string; desktopOnly?: boolean; useCases: HeroUseCase[]; }; const HERO_TUTORIAL = "/homepage/hero_tutorial"; const HERO_REALTIME = "/homepage/hero_realtime"; const CATEGORIES: HeroCategory[] = [ { id: "desktop", label: "Desktop App", desktopOnly: true, useCases: [ { id: "general", title: "General Assist", description: "Launch SurfSense instantly from any application with a global shortcut.", src: `${HERO_TUTORIAL}/general_assist.mp4`, }, { id: "quick", title: "Quick Assist", description: "Select text anywhere, then ask AI to explain, rewrite, or act on it.", src: `${HERO_TUTORIAL}/quick_assist.mp4`, }, { id: "screenshot", title: "Screenshot Assist", description: "Capture any region of your screen and ask AI about what’s in it.", src: `${HERO_TUTORIAL}/screenshot_assist.mp4`, }, { id: "watch-folder", title: "Watch Local Folder", description: "Auto-sync a local folder to your knowledge base. Great for Obsidian vaults.", src: `${HERO_TUTORIAL}/folder_watch.mp4`, }, ], }, { id: "deliverables", label: "Deliverable Studio", useCases: [ { id: "report", title: "AI Report Generator", description: "Generate cited research reports from your documents, then export to PDF or Markdown.", src: `${HERO_TUTORIAL}/ReportGenGif_compressed.mp4`, }, { id: "podcast", title: "AI Podcast Generator", description: "Turn any document or folder into a two-host AI podcast in under 20 seconds.", src: `${HERO_TUTORIAL}/PodcastGenGif.mp4`, }, { id: "presentation", title: "AI Presentation & Video Maker", description: "Create editable slide decks and narrated video overviews from your sources.", src: `${HERO_TUTORIAL}/video_gen_surf.mp4`, }, { id: "image", title: "AI Image Generator", description: "Generate high-quality images straight from your chats and documents.", src: `${HERO_TUTORIAL}/ImageGenGif.mp4`, }, { id: "resume", title: "AI Resume Builder", description: "Draft and format an ATS-ready resume as a polished PDF.", src: null, comingSoon: true, }, ], }, { id: "automations", label: "Automations", useCases: [ { id: "schedule", title: "Scheduled AI Workflows", description: "Run an agent on a schedule: daily briefs, weekly digests, recurring reports.", src: null, comingSoon: true, }, { id: "event", title: "Event-Triggered Automations", description: "Fire an agent the moment a document lands in a folder, then post the result to your tools.", src: null, comingSoon: true, }, { id: "chat-built", title: "Chat-Built Automations", description: "Describe an automation in plain English and SurfSense builds it for you.", src: null, comingSoon: true, }, ], }, { id: "search-chat", label: "Search & Chat", useCases: [ { id: "chat-docs", title: "Chat With Your PDFs & Docs", description: "Ask questions across all your files and get answers with inline citations.", src: `${HERO_TUTORIAL}/BQnaGif_compressed.mp4`, }, { id: "search", title: "AI Search With Citations", description: "Hybrid semantic and keyword search across your entire knowledge base.", src: `${HERO_TUTORIAL}/BSNCGif.mp4`, }, { id: "collab", title: "Collaborative AI Chat", description: "Work on AI conversations with your team in real time.", src: `${HERO_REALTIME}/RealTimeChatGif.mp4`, }, { id: "comments", title: "Comments & Mentions", description: "Comment and tag teammates on any AI message.", src: `${HERO_REALTIME}/RealTimeCommentsFlow.mp4`, }, ], }, { id: "connectors", label: "Connectors & Integrations", useCases: [ { id: "connect", title: "Connect & Sync Your Tools", description: "Sync Notion, Slack, Google Drive, Gmail, GitHub, Linear and 25+ sources into one searchable corpus.", src: `${HERO_TUTORIAL}/ConnectorFlowGif.mp4`, }, { id: "upload", title: "Chat With Uploaded Files", description: "Drop in PDFs, Office docs, images and audio. Instantly searchable.", src: `${HERO_TUTORIAL}/DocUploadGif.mp4`, }, { id: "write-back", title: "Connector Write-Back", description: "Let the agent post results back to Notion, Slack, Linear and Drive.", src: null, comingSoon: true, }, { id: "obsidian", title: "Obsidian & Knowledge Base Sync", description: "Keep your Obsidian vault and personal knowledge base in sync.", src: null, comingSoon: true, }, ], }, ]; export function HeroSection() { return (

NotebookLM for Teams

A free, open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.

); } function GetStartedButton() { const isGoogleAuth = AUTH_TYPE === "GOOGLE"; const [isRedirecting, setIsRedirecting] = useState(false); const handleGoogleLogin = () => { if (isRedirecting) return; setIsRedirecting(true); trackLoginAttempt("google"); window.location.href = `${BACKEND_URL}/auth/google/authorize-redirect`; }; if (isGoogleAuth) { return ( ); } return ( ); } function DownloadButton() { const { os, primary, alternatives } = usePrimaryDownload(); const fallbackUrl = GITHUB_RELEASES_URL; if (!primary) { return ( ); } return (
{alternatives.map((asset) => ( {getAssetLabel(asset.name)} ))} All downloads
); } const TabVideo = memo(function TabVideo({ src, title, reduceMotion, }: { src: string; title: string; reduceMotion: boolean; }) { const videoRef = useRef(null); const [hasLoaded, setHasLoaded] = useState(false); useEffect(() => { setHasLoaded(false); const video = videoRef.current; if (!video) return; video.currentTime = 0; // Respect reduced-motion: show the first frame and expose controls instead of autoplaying. if (!reduceMotion) { video.play().catch(() => {}); } }, [reduceMotion]); const handleCanPlay = useCallback(() => { setHasLoaded(true); }, []); return (
); }); const UseCasePlaceholder = ({ title }: { title: string }) => ( Demo coming soon {`A walkthrough of ${title} is on the way.`} ); const DesktopBadge = () => ( Desktop app only ); const UseCasePane = memo(function UseCasePane({ useCase, reduceMotion, }: { useCase: HeroUseCase; reduceMotion: boolean; }) { const { expanded, open, close } = useExpandedMedia(); const hasVideo = !useCase.comingSoon && Boolean(useCase.src); const media = hasVideo ? ( ) : (
); const card = (

{useCase.title}

{useCase.description}

{media}
); return ( <> {reduceMotion ? ( card ) : ( {card} )} {expanded && hasVideo && ( )} ); }); const CategoryPanel = memo(function CategoryPanel({ category, reduceMotion, }: { category: HeroCategory; reduceMotion: boolean; }) { return (
{category.desktopOnly && (
)} {category.useCases.map((useCase) => ( {useCase.title} ))}
{category.useCases.map((useCase) => ( ))}
); }); const BrowserWindow = () => { const [activeCategory, setActiveCategory] = useState(CATEGORIES[0].id); const reduceMotion = useReducedMotion() ?? false; return (
{CATEGORIES.map((category, index) => ( {category.label} {category.desktopOnly && } {index !== CATEGORIES.length - 1 && ( )} ))}
{CATEGORIES.map((category) => ( ))}
); };