feat: add desktop app section to README files

- Introduced a new section in the README files for the Desktop App, highlighting its features: General Assist, Quick Assist, and Extreme Assist.
- Updated all language-specific README files to include details about the desktop app's capabilities and download instructions.
This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-04-07 16:56:08 -07:00
parent f20540f60c
commit 1a6251eaaa
7 changed files with 522 additions and 0 deletions

View file

@ -41,6 +41,7 @@ NotebookLM es una de las mejores y más útiles plataformas de IA que existen, p
- **Sin Dependencia de Proveedores** - Configura cualquier modelo LLM, de imagen, TTS y STT.
- **25+ Fuentes de Datos Externas** - Agrega tus fuentes desde Google Drive, OneDrive, Dropbox, Notion y muchos otros servicios externos.
- **Soporte Multijugador en Tiempo Real** - Trabaja fácilmente con los miembros de tu equipo en un notebook compartido.
- **Aplicación de Escritorio** - Obtén asistencia de IA en cualquier aplicación con Quick Assist, General Assist y Extreme Assist.
...y más por venir.
@ -130,6 +131,18 @@ El script de instalación configura [Watchtower](https://github.com/nicholas-fed
Para Docker Compose, instalación manual y otras opciones de despliegue, consulta la [documentación](https://www.surfsense.com/docs/).
### Aplicación de Escritorio
SurfSense también ofrece una aplicación de escritorio que lleva la asistencia de IA a cada aplicación en tu computadora. Descárgala desde la [última versión](https://github.com/MODSetter/SurfSense/releases/latest).
La aplicación de escritorio incluye tres potentes funciones:
- **General Assist** — Lanza SurfSense al instante desde cualquier aplicación con un atajo global.
- **Quick Assist** — Selecciona texto en cualquier lugar, luego pide a la IA que lo explique, reescriba o actúe sobre él.
- **Extreme Assist** — Obtén sugerencias de escritura en línea impulsadas por tu base de conocimiento mientras escribes en cualquier aplicación.
Las tres funciones operan contra tu espacio de búsqueda elegido, por lo que tus respuestas siempre están basadas en tus propios datos.
### Cómo Colaborar en Tiempo Real (Beta)
1. Ve a la página de Gestión de Miembros y crea una invitación.
@ -174,6 +187,7 @@ Para Docker Compose, instalación manual y otras opciones de despliegue, consult
| **Generación de Videos** | Resúmenes en video cinemáticos vía Veo 3 (solo Ultra) | Disponible (NotebookLM es mejor aquí, mejorando activamente) |
| **Generación de Presentaciones** | Diapositivas más atractivas pero no editables | Crea presentaciones editables basadas en diapositivas |
| **Generación de Podcasts** | Resúmenes de audio con hosts e idiomas personalizables | Disponible con múltiples proveedores TTS (NotebookLM es mejor aquí, mejorando activamente) |
| **Aplicación de Escritorio** | No | Aplicación nativa con General Assist, Quick Assist y Extreme Assist — asistencia de IA en cualquier aplicación |
| **Extensión de Navegador** | No | Extensión multi-navegador para guardar cualquier página web, incluyendo páginas protegidas por autenticación |
<details>

View file

@ -41,6 +41,7 @@ NotebookLM वहाँ उपलब्ध सबसे अच्छे और
- **कोई विक्रेता लॉक-इन नहीं** - किसी भी LLM, इमेज, TTS और STT मॉडल को कॉन्फ़िगर करें।
- **25+ बाहरी डेटा स्रोत** - Google Drive, OneDrive, Dropbox, Notion और कई अन्य बाहरी सेवाओं से अपने स्रोत जोड़ें।
- **रीयल-टाइम मल्टीप्लेयर सपोर्ट** - एक साझा notebook में अपनी टीम के सदस्यों के साथ आसानी से काम करें।
- **डेस्कटॉप ऐप** - Quick Assist, General Assist और Extreme Assist के साथ किसी भी एप्लिकेशन में AI सहायता प्राप्त करें।
...और भी बहुत कुछ आने वाला है।
@ -130,6 +131,18 @@ irm https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/in
Docker Compose, मैनुअल इंस्टॉलेशन और अन्य डिप्लॉयमेंट विकल्पों के लिए, [डॉक्स](https://www.surfsense.com/docs/) देखें।
### डेस्कटॉप ऐप
SurfSense एक डेस्कटॉप ऐप भी प्रदान करता है जो आपके कंप्यूटर पर हर एप्लिकेशन में AI सहायता लाता है। इसे [नवीनतम रिलीज़](https://github.com/MODSetter/SurfSense/releases/latest) से डाउनलोड करें।
डेस्कटॉप ऐप में तीन शक्तिशाली सुविधाएं शामिल हैं:
- **General Assist** — एक ग्लोबल शॉर्टकट से किसी भी एप्लिकेशन से तुरंत SurfSense लॉन्च करें।
- **Quick Assist** — कहीं भी टेक्स्ट चुनें, फिर AI से समझाने, फिर से लिखने या उस पर कार्रवाई करने को कहें।
- **Extreme Assist** — किसी भी ऐप में टाइप करते समय अपनी नॉलेज बेस से संचालित इनलाइन लेखन सुझाव प्राप्त करें।
तीनों सुविधाएं आपके चुने हुए सर्च स्पेस पर काम करती हैं, ताकि आपके उत्तर हमेशा आपके अपने डेटा पर आधारित हों।
### रीयल-टाइम सहयोग कैसे करें (बीटा)
1. सदस्य प्रबंधन पेज पर जाएं और एक आमंत्रण बनाएं।
@ -174,6 +187,7 @@ Docker Compose, मैनुअल इंस्टॉलेशन और अन
| **वीडियो जनरेशन** | Veo 3 के माध्यम से सिनेमैटिक वीडियो ओवरव्यू (केवल Ultra) | उपलब्ध (NotebookLM यहाँ बेहतर है, सक्रिय रूप से सुधार हो रहा है) |
| **प्रेजेंटेशन जनरेशन** | बेहतर दिखने वाली स्लाइड्स लेकिन संपादन योग्य नहीं | संपादन योग्य, स्लाइड आधारित प्रेजेंटेशन बनाएं |
| **पॉडकास्ट जनरेशन** | कस्टमाइज़ेबल होस्ट और भाषाओं के साथ ऑडियो ओवरव्यू | कई TTS प्रदाताओं के साथ उपलब्ध (NotebookLM यहाँ बेहतर है, सक्रिय रूप से सुधार हो रहा है) |
| **डेस्कटॉप ऐप** | नहीं | General Assist, Quick Assist और Extreme Assist के साथ नेटिव ऐप — किसी भी एप्लिकेशन में AI सहायता |
| **ब्राउज़र एक्सटेंशन** | नहीं | किसी भी वेबपेज को सहेजने के लिए क्रॉस-ब्राउज़र एक्सटेंशन, प्रमाणीकरण सुरक्षित पेज सहित |
<details>

View file

@ -41,6 +41,7 @@ NotebookLM is one of the best and most useful AI platforms out there, but once y
- **No Vendor Lock-in** - Configure any LLM, image, TTS, and STT models to use.
- **25+ External Data Sources** - Add your sources from Google Drive, OneDrive, Dropbox, Notion, and many other external services.
- **Real-Time Multiplayer Support** - Work easily with your team members in a shared notebook.
- **Desktop App** - Get AI assistance in any application with Quick Assist, General Assist, and Extreme Assist.
...and more to come.
@ -131,6 +132,18 @@ The install script sets up [Watchtower](https://github.com/nicholas-fedor/watcht
For Docker Compose, manual installation, and other deployment options, see the [docs](https://www.surfsense.com/docs/).
### Desktop App
SurfSense also ships a desktop app that brings AI assistance to every application on your computer. Download it from the [latest release](https://github.com/MODSetter/SurfSense/releases/latest).
The desktop app includes three powerful features:
- **General Assist** — Launch SurfSense instantly from any application with a global shortcut.
- **Quick Assist** — Select text anywhere, then ask AI to explain, rewrite, or act on it.
- **Extreme Assist** — Get inline writing suggestions powered by your knowledge base as you type in any app.
All three features operate against your chosen search space, so your answers are always grounded in your own data.
### How to Realtime Collaborate (Beta)
1. Go to Manage Members page and create an invite.
@ -175,6 +188,7 @@ For Docker Compose, manual installation, and other deployment options, see the [
| **Video Generation** | Cinematic Video Overviews via Veo 3 (Ultra only) | Available (NotebookLM is better here, actively improving) |
| **Presentation Generation** | Better looking slides but not editable | Create editable, slide-based presentations |
| **Podcast Generation** | Audio Overviews with customizable hosts and languages | Available with multiple TTS providers (NotebookLM is better here, actively improving) |
| **Desktop App** | No | Native app with General Assist, Quick Assist, and Extreme Assist — AI help in any application |
| **Browser Extension** | No | Cross-browser extension to save any webpage, including auth-protected pages |
<details>

View file

@ -41,6 +41,7 @@ O NotebookLM é uma das melhores e mais úteis plataformas de IA disponíveis, m
- **Sem Dependência de Fornecedor** - Configure qualquer modelo LLM, de imagem, TTS e STT.
- **25+ Fontes de Dados Externas** - Adicione suas fontes do Google Drive, OneDrive, Dropbox, Notion e muitos outros serviços externos.
- **Suporte Multiplayer em Tempo Real** - Trabalhe facilmente com os membros da sua equipe em um notebook compartilhado.
- **Aplicativo Desktop** - Obtenha assistência de IA em qualquer aplicativo com Quick Assist, General Assist e Extreme Assist.
...e mais por vir.
@ -130,6 +131,18 @@ O script de instalação configura o [Watchtower](https://github.com/nicholas-fe
Para Docker Compose, instalação manual e outras opções de implantação, consulte a [documentação](https://www.surfsense.com/docs/).
### Aplicativo Desktop
O SurfSense também oferece um aplicativo desktop que traz assistência de IA para cada aplicativo no seu computador. Baixe-o na [última versão](https://github.com/MODSetter/SurfSense/releases/latest).
O aplicativo desktop inclui três recursos poderosos:
- **General Assist** — Abra o SurfSense instantaneamente de qualquer aplicativo com um atalho global.
- **Quick Assist** — Selecione texto em qualquer lugar, depois peça à IA para explicar, reescrever ou agir sobre ele.
- **Extreme Assist** — Receba sugestões de escrita em linha alimentadas pela sua base de conhecimento enquanto digita em qualquer aplicativo.
Os três recursos operam no espaço de busca escolhido, para que suas respostas sejam sempre baseadas nos seus próprios dados.
### Como Colaborar em Tempo Real (Beta)
1. Acesse a página de Gerenciar Membros e crie um convite.
@ -174,6 +187,7 @@ Para Docker Compose, instalação manual e outras opções de implantação, con
| **Geração de Vídeos** | Visões gerais cinemáticas via Veo 3 (apenas Ultra) | Disponível (NotebookLM é melhor aqui, melhorando ativamente) |
| **Geração de Apresentações** | Slides mais bonitos mas não editáveis | Cria apresentações editáveis baseadas em slides |
| **Geração de Podcasts** | Visões gerais em áudio com hosts e idiomas personalizáveis | Disponível com múltiplos provedores TTS (NotebookLM é melhor aqui, melhorando ativamente) |
| **Aplicativo Desktop** | Não | Aplicativo nativo com General Assist, Quick Assist e Extreme Assist — assistência de IA em qualquer aplicativo |
| **Extensão de Navegador** | Não | Extensão multi-navegador para salvar qualquer página web, incluindo páginas protegidas por autenticação |
<details>

View file

@ -41,6 +41,7 @@ NotebookLM 是目前最好、最实用的 AI 平台之一,但当你开始经
- **无供应商锁定** - 配置任何 LLM、图像、TTS 和 STT 模型。
- **25+ 外部数据源** - 从 Google Drive、OneDrive、Dropbox、Notion 和许多其他外部服务添加你的来源。
- **实时多人协作支持** - 在共享笔记本中轻松与团队成员协作。
- **桌面应用** - 通过 Quick Assist、General Assist 和 Extreme Assist 在任何应用程序中获得 AI 助手。
...更多功能即将推出。
@ -130,6 +131,18 @@ irm https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/in
如需 Docker Compose、手动安装及其他部署方式请查看[文档](https://www.surfsense.com/docs/)。
### 桌面应用
SurfSense 还提供桌面应用,将 AI 助手带到您计算机上的每个应用程序中。从[最新版本](https://github.com/MODSetter/SurfSense/releases/latest)下载。
桌面应用包含三个强大功能:
- **General Assist** — 通过全局快捷键从任何应用程序即时启动 SurfSense。
- **Quick Assist** — 在任何位置选中文本,然后让 AI 解释、改写或对其执行操作。
- **Extreme Assist** — 在任何应用中输入时,获得基于您知识库的内联写作建议。
三个功能均基于您选择的搜索空间运行,确保回答始终以您自己的数据为依据。
### 如何实时协作Beta
1. 前往成员管理页面并创建邀请。
@ -174,6 +187,7 @@ irm https://raw.githubusercontent.com/MODSetter/SurfSense/main/docker/scripts/in
| **视频生成** | 通过 Veo 3 的电影级视频概览(仅 Ultra | 可用NotebookLM 在此方面更好,正在积极改进) |
| **演示文稿生成** | 更美观的幻灯片但不可编辑 | 创建可编辑的幻灯片式演示文稿 |
| **播客生成** | 可自定义主持人和语言的音频概览 | 可用,支持多种 TTS 提供商NotebookLM 在此方面更好,正在积极改进) |
| **桌面应用** | 否 | 原生应用,包含 General Assist、Quick Assist 和 Extreme Assist — 在任何应用程序中获得 AI 助手 |
| **浏览器扩展** | 否 | 跨浏览器扩展,保存任何网页,包括需要身份验证的页面 |
<details>

View file

@ -6,6 +6,11 @@ import { useEffect } from "react";
import { HeroSection } from "@/components/homepage/hero-section";
import { getBearerToken } from "@/lib/auth-utils";
const WhySurfSense = dynamic(
() => import("@/components/homepage/why-surfsense").then((m) => ({ default: m.WhySurfSense })),
{ ssr: false }
);
const FeaturesCards = dynamic(
() => import("@/components/homepage/features-card").then((m) => ({ default: m.FeaturesCards })),
{ ssr: false }
@ -40,6 +45,7 @@ export default function HomePage() {
return (
<main className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 text-gray-900 dark:from-black dark:to-gray-900 dark:text-white">
<HeroSection />
<WhySurfSense />
<FeaturesCards />
<FeaturesBentoGrid />
<ExternalIntegrations />

View file

@ -0,0 +1,446 @@
"use client";
import { useRef, useState } from "react";
import { motion, useInView } from "motion/react";
import { IconPointerFilled } from "@tabler/icons-react";
import { Check, X } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
const cards = [
{
title: "Unlimited & Self-Hosted",
description:
"No caps on sources, notebooks, or file sizes. Deploy on your own infra and your data never leaves your control.",
skeleton: <UnlimitedSkeleton />,
},
{
title: "100+ LLMs, Zero Lock-in",
description:
"Swap between 100+ LLMs via OpenAI spec and LiteLLM, or run fully private with vLLM, Ollama, and more.",
skeleton: <LLMFlexibilitySkeleton />,
},
{
title: "Real-Time Multiplayer",
description:
"RBAC with Owner, Admin, Editor, and Viewer roles plus real-time chat and comment threads. Built for teams.",
skeleton: <MultiplayerSkeleton />,
},
];
export function WhySurfSense() {
return (
<section className="px-4 py-10 md:px-8 md:py-24 lg:px-16 lg:py-32">
<div className="mx-auto mb-10 max-w-3xl text-center md:mb-16">
<p className="mb-3 text-sm font-semibold uppercase tracking-widest text-brand">
Why SurfSense
</p>
<h2 className="text-balance text-3xl font-bold tracking-tight text-foreground sm:text-4xl lg:text-5xl">
Everything NotebookLM should have been
</h2>
<p className="mx-auto mt-4 max-w-2xl text-base text-muted-foreground">
Open source. No data limits. No vendor lock-in. Built for teams that
care about privacy and flexibility.
</p>
</div>
<div className="mx-auto grid w-full max-w-6xl grid-cols-1 divide-x-0 divide-y divide-border overflow-hidden rounded-2xl shadow-sm ring-1 ring-border md:grid-cols-3 md:divide-x md:divide-y-0">
{cards.map((card) => (
<FeatureCard key={card.title} {...card} />
))}
</div>
<ComparisonStrip />
</section>
);
}
function UnlimitedSkeleton({ className }: { className?: string }) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-50px" });
const items = [
{ label: "Sources", notebookLm: "50-600", surfSense: "Unlimited", icon: "📄" },
{ label: "Notebooks", notebookLm: "100-500", surfSense: "Unlimited", icon: "📓" },
{ label: "File size", notebookLm: "200 MB", surfSense: "No limit", icon: "📦" },
{ label: "Self-host", notebookLm: "No", surfSense: "Yes", icon: "🏠" },
];
return (
<div
ref={ref}
className={cn("flex h-full flex-col justify-center gap-2.5", className)}
>
{items.map((item, index) => (
<motion.div
key={item.label}
initial={{ opacity: 0, x: -16 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.35, delay: index * 0.15 }}
className="flex items-center gap-2 rounded-lg bg-background px-3 py-2 shadow-sm ring-1 ring-border"
>
<span className="text-sm">{item.icon}</span>
<span className="min-w-[60px] text-xs font-medium text-foreground">
{item.label}
</span>
<div className="ml-auto flex items-center gap-2">
<span className="text-[10px] text-muted-foreground line-through">
{item.notebookLm}
</span>
<motion.div
initial={{ scale: 0.8 }}
animate={isInView ? { scale: 1 } : {}}
transition={{
duration: 0.3,
delay: index * 0.15 + 0.2,
type: "spring",
stiffness: 300,
}}
>
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
{item.surfSense}
</Badge>
</motion.div>
</div>
</motion.div>
))}
</div>
);
}
function LLMFlexibilitySkeleton({ className }: { className?: string }) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-50px" });
const [selected, setSelected] = useState(0);
const models = [
{ name: "GPT-4o", provider: "OpenAI", color: "bg-green-500" },
{ name: "Claude 4", provider: "Anthropic", color: "bg-orange-500" },
{ name: "Gemini 2.5", provider: "Google", color: "bg-blue-500" },
{ name: "Llama 4", provider: "Local", color: "bg-purple-500" },
{ name: "DeepSeek R1", provider: "DeepSeek", color: "bg-cyan-500" },
];
return (
<div
ref={ref}
className={cn(
"flex h-full flex-col items-center justify-center gap-3",
className,
)}
>
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.3 }}
className="flex w-full max-w-[180px] flex-col gap-1.5"
>
{models.map((model, index) => (
<motion.button
key={model.name}
type="button"
onClick={() => setSelected(index)}
initial={{ opacity: 0, x: 12 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.3, delay: 0.1 + index * 0.1 }}
className={cn(
"flex w-full cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-left transition-all",
selected === index
? "bg-background shadow-sm ring-1 ring-border"
: "hover:bg-accent",
)}
>
<div className={cn("size-2 shrink-0 rounded-full", model.color)} />
<div className="min-w-0">
<p className="truncate text-xs font-medium text-foreground">
{model.name}
</p>
<p className="text-[10px] text-muted-foreground">
{model.provider}
</p>
</div>
{selected === index && (
<motion.div
layoutId="model-check"
className="ml-auto"
transition={{ type: "spring", stiffness: 400, damping: 25 }}
>
<Check className="size-3 text-brand" />
</motion.div>
)}
</motion.button>
))}
</motion.div>
</div>
);
}
function MultiplayerSkeleton({ className }: { className?: string }) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-50px" });
const collaborators = [
{
id: 1,
name: "Alice",
role: "Editor",
color: "#3b82f6",
path: [
{ x: 15, y: 10 },
{ x: 80, y: 40 },
{ x: 40, y: 80 },
{ x: 15, y: 10 },
],
},
{
id: 2,
name: "Bob",
role: "Viewer",
color: "#10b981",
path: [
{ x: 115, y: 70 },
{ x: 55, y: 20 },
{ x: 95, y: 50 },
{ x: 115, y: 70 },
],
},
];
const codeLines = [
{ indent: 0, width: "60%", color: "bg-chart-4/60" },
{ indent: 1, width: "75%", color: "bg-muted-foreground/20" },
{ indent: 1, width: "50%", color: "bg-chart-1/60" },
{ indent: 2, width: "80%", color: "bg-muted-foreground/20" },
{ indent: 2, width: "45%", color: "bg-chart-2/60" },
{ indent: 1, width: "30%", color: "bg-muted-foreground/20" },
{ indent: 0, width: "20%", color: "bg-chart-4/60" },
];
return (
<div
ref={ref}
className={cn(
"relative flex h-full items-center justify-center overflow-visible",
className,
)}
>
<motion.div
className="relative w-full max-w-[160px] rounded-lg bg-background p-3 shadow-sm ring-1 ring-border"
initial={{ opacity: 0, y: 10 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.4 }}
>
<div className="mb-2 flex items-center gap-1.5">
<div className="flex gap-1">
<div className="size-1.5 rounded-full bg-red-400" />
<div className="size-1.5 rounded-full bg-yellow-400" />
<div className="size-1.5 rounded-full bg-green-400" />
</div>
<div className="ml-2 h-1.5 w-12 rounded-full bg-muted" />
</div>
{codeLines.map((line, index) => (
<div
key={index}
className="my-1.5 flex items-center"
style={{ paddingLeft: line.indent * 8 }}
>
<div
className={cn("h-1.5 rounded-full", line.color)}
style={{ width: line.width }}
/>
</div>
))}
</motion.div>
{collaborators.map((collaborator, index) => (
<motion.div
key={collaborator.id}
className="absolute"
initial={{ opacity: 0 }}
animate={
isInView
? {
opacity: 1,
x: collaborator.path.map((p) => p.x),
y: collaborator.path.map((p) => p.y),
}
: {}
}
transition={{
opacity: { duration: 0.3, delay: 0.5 + index * 0.2 },
x: {
duration: 6,
delay: 0.5 + index * 0.3,
repeat: Infinity,
ease: "easeInOut",
},
y: {
duration: 6,
delay: 0.5 + index * 0.3,
repeat: Infinity,
ease: "easeInOut",
},
}}
>
<IconPointerFilled
className="size-5 drop-shadow-sm"
style={{ color: collaborator.color }}
/>
<div
className="absolute top-5 left-3 z-50 flex w-max items-center gap-1.5 rounded-full py-1 pr-2.5 pl-1 shadow-sm"
style={{ backgroundColor: collaborator.color }}
>
<div className="flex size-5 items-center justify-center rounded-full bg-white/20 text-[9px] font-bold text-white">
{collaborator.name[0]}
</div>
<span className="shrink-0 text-[10px] font-medium text-white">
{collaborator.name}
</span>
<span className="rounded bg-white/20 px-1 py-px text-[8px] text-white/80">
{collaborator.role}
</span>
</div>
</motion.div>
))}
</div>
);
}
function FeatureCard({
title,
description,
skeleton,
}: {
title: string;
description: string;
skeleton: React.ReactNode;
}) {
return (
<div className="flex h-full flex-col justify-between bg-card p-10 first:rounded-l-2xl last:rounded-r-2xl">
<div className="h-60 w-full overflow-visible rounded-md">{skeleton}</div>
<div className="mt-4">
<h3 className="text-base font-bold tracking-tight text-card-foreground">
{title}
</h3>
<p className="mt-2 text-sm leading-relaxed tracking-tight text-muted-foreground">
{description}
</p>
</div>
</div>
);
}
const comparisonRows: {
feature: string;
notebookLm: string | boolean;
surfSense: string | boolean;
}[] = [
{
feature: "Sources per Notebook",
notebookLm: "50-600",
surfSense: "Unlimited",
},
{
feature: "LLM Support",
notebookLm: "Gemini only",
surfSense: "100+ LLMs",
},
{
feature: "Self-Hostable",
notebookLm: false,
surfSense: true,
},
{
feature: "Open Source",
notebookLm: false,
surfSense: true,
},
{
feature: "External Connectors",
notebookLm: "Limited",
surfSense: "27+",
},
{
feature: "Desktop App",
notebookLm: false,
surfSense: true,
},
{
feature: "Agentic Architecture",
notebookLm: false,
surfSense: true,
},
];
function ComparisonStrip() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: "-80px" });
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: 0.1 }}
className="mx-auto mt-12 w-full max-w-4xl overflow-hidden rounded-2xl bg-card shadow-sm ring-1 ring-border"
>
<div className="grid grid-cols-3 px-4 py-3 sm:px-6">
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
Feature
</span>
<span className="text-center text-xs font-semibold uppercase tracking-wider text-muted-foreground">
NotebookLM
</span>
<span className="text-center text-xs font-semibold uppercase tracking-wider text-muted-foreground">
SurfSense
</span>
</div>
<Separator />
{comparisonRows.map((row, index) => (
<motion.div
key={row.feature}
initial={{ opacity: 0, x: -10 }}
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.3, delay: 0.15 + index * 0.06 }}
>
<div className="grid grid-cols-3 items-center px-4 py-2.5 text-sm sm:px-6">
<span className="font-medium text-card-foreground">
{row.feature}
</span>
<span className="flex justify-center">
{typeof row.notebookLm === "boolean" ? (
row.notebookLm ? (
<Check className="size-4 text-brand" />
) : (
<X className="size-4 text-muted-foreground/40" />
)
) : (
<span className="text-muted-foreground">
{row.notebookLm}
</span>
)}
</span>
<span className="flex justify-center">
{typeof row.surfSense === "boolean" ? (
row.surfSense ? (
<Check className="size-4 text-brand" />
) : (
<X className="size-4 text-muted-foreground/40" />
)
) : (
<Badge variant="secondary">{row.surfSense}</Badge>
)}
</span>
</div>
{index !== comparisonRows.length - 1 && (
<Separator />
)}
</motion.div>
))}
</motion.div>
);
}