mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
feat: no login experience and prem tokens
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
This commit is contained in:
parent
87452bb315
commit
ff4e0f9b62
68 changed files with 5914 additions and 121 deletions
24
surfsense_web/app/(home)/free/[model_slug]/layout.tsx
Normal file
24
surfsense_web/app/(home)/free/[model_slug]/layout.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { use } from "react";
|
||||
import { FreeLayoutDataProvider } from "@/components/layout/providers/FreeLayoutDataProvider";
|
||||
import { AnonymousModeProvider } from "@/contexts/anonymous-mode";
|
||||
import { LoginGateProvider } from "@/contexts/login-gate";
|
||||
|
||||
interface FreeModelLayoutProps {
|
||||
children: ReactNode;
|
||||
params: Promise<{ model_slug: string }>;
|
||||
}
|
||||
|
||||
export default function FreeModelLayout({ children, params }: FreeModelLayoutProps) {
|
||||
const { model_slug } = use(params);
|
||||
|
||||
return (
|
||||
<AnonymousModeProvider initialModelSlug={model_slug}>
|
||||
<LoginGateProvider>
|
||||
<FreeLayoutDataProvider>{children}</FreeLayoutDataProvider>
|
||||
</LoginGateProvider>
|
||||
</AnonymousModeProvider>
|
||||
);
|
||||
}
|
||||
254
surfsense_web/app/(home)/free/[model_slug]/page.tsx
Normal file
254
surfsense_web/app/(home)/free/[model_slug]/page.tsx
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
import { SquareArrowOutUpRight } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { FreeChatPage } from "@/components/free-chat/free-chat-page";
|
||||
import { BreadcrumbNav } from "@/components/seo/breadcrumb-nav";
|
||||
import { FAQJsonLd, JsonLd } from "@/components/seo/json-ld";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import type { AnonModel } from "@/contracts/types/anonymous-chat.types";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ model_slug: string }>;
|
||||
}
|
||||
|
||||
async function getModel(slug: string): Promise<AnonModel | null> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${BACKEND_URL}/api/v1/public/anon-chat/models/${encodeURIComponent(slug)}`,
|
||||
{ next: { revalidate: 300 } }
|
||||
);
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllModels(): Promise<AnonModel[]> {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
next: { revalidate: 300 },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function buildSeoTitle(model: AnonModel): string {
|
||||
if (model.seo_title) return model.seo_title;
|
||||
return `${model.name} Free Online Without Login | No Sign-Up AI Chat | SurfSense`;
|
||||
}
|
||||
|
||||
function buildSeoDescription(model: AnonModel): string {
|
||||
if (model.seo_description) return model.seo_description;
|
||||
return `Use ${model.name} free online without login. No sign-up required. Chat with ${model.name} by ${model.provider} instantly on SurfSense, the open source ChatGPT alternative with no login.`;
|
||||
}
|
||||
|
||||
function buildModelFaq(model: AnonModel) {
|
||||
return [
|
||||
{
|
||||
question: `Can I use ${model.name} without login?`,
|
||||
answer: `Yes. ${model.name} is available on SurfSense without login. No account creation, no email, no password. Just open the page and start chatting with ${model.name} for free.`,
|
||||
},
|
||||
{
|
||||
question: `Is ${model.name} really free on SurfSense?`,
|
||||
answer: `Yes! You can use ${model.name} completely free without login or sign-up. SurfSense gives you 1 million free tokens to use across any model, including ${model.name}.`,
|
||||
},
|
||||
{
|
||||
question: `How do I use ${model.name} with no login?`,
|
||||
answer: `Just start typing in the chat box above. ${model.name} will respond instantly. No login wall, no sign-up form, no verification. Your conversations are not stored in any database.`,
|
||||
},
|
||||
{
|
||||
question: `What can I do with ${model.name} on SurfSense?`,
|
||||
answer: `You can ask questions, get explanations, write content, brainstorm ideas, debug code, and more. ${model.name} is a powerful AI assistant available for free without login on SurfSense.`,
|
||||
},
|
||||
{
|
||||
question: `How is SurfSense different from using ${model.name} directly?`,
|
||||
answer: `SurfSense gives you free access without login to ${model.name} and many other AI models in one place. Create a free account to unlock document Q&A, team collaboration, and integrations with Slack, Google Drive, Notion, and more.`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const { model_slug } = await params;
|
||||
const model = await getModel(model_slug);
|
||||
if (!model) return { title: "Model Not Found | SurfSense" };
|
||||
|
||||
const title = buildSeoTitle(model);
|
||||
const description = buildSeoDescription(model);
|
||||
const canonicalUrl = `https://surfsense.com/free/${model.seo_slug}`;
|
||||
const modelNameLower = model.name.toLowerCase();
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: { canonical: canonicalUrl },
|
||||
keywords: [
|
||||
`${modelNameLower} free`,
|
||||
`free ${modelNameLower}`,
|
||||
`${modelNameLower} online`,
|
||||
`${modelNameLower} online free`,
|
||||
`${modelNameLower} without login`,
|
||||
`${modelNameLower} no login`,
|
||||
`${modelNameLower} no sign up`,
|
||||
`${modelNameLower} login free`,
|
||||
`${modelNameLower} free without login`,
|
||||
`${modelNameLower} free no login`,
|
||||
`${modelNameLower} chat free`,
|
||||
`${modelNameLower} free online`,
|
||||
`use ${modelNameLower} for free`,
|
||||
`use ${modelNameLower} without login`,
|
||||
`${modelNameLower} alternative`,
|
||||
`${modelNameLower} alternative free`,
|
||||
"chatgpt no login",
|
||||
"chatgpt without login",
|
||||
"free ai chat no login",
|
||||
"ai chat without login",
|
||||
"free ai apps",
|
||||
],
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: canonicalUrl,
|
||||
siteName: "SurfSense",
|
||||
type: "website",
|
||||
images: [
|
||||
{
|
||||
url: "/og-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: `${model.name} Free Chat on SurfSense`,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const models = await getAllModels();
|
||||
return models.filter((m) => m.seo_slug).map((m) => ({ model_slug: m.seo_slug! }));
|
||||
}
|
||||
|
||||
export default async function FreeModelPage({ params }: PageProps) {
|
||||
const { model_slug } = await params;
|
||||
const [model, allModels] = await Promise.all([getModel(model_slug), getAllModels()]);
|
||||
if (!model) notFound();
|
||||
|
||||
const description = buildSeoDescription(model);
|
||||
const faqItems = buildModelFaq(model);
|
||||
|
||||
const relatedModels = allModels
|
||||
.filter((m) => m.seo_slug && m.seo_slug !== model.seo_slug)
|
||||
.slice(0, 4);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Invisible SEO metadata */}
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
name: `${model.name} Free Chat Without Login - SurfSense`,
|
||||
description,
|
||||
url: `https://surfsense.com/free/${model.seo_slug}`,
|
||||
applicationCategory: "ChatApplication",
|
||||
operatingSystem: "Web",
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
price: "0",
|
||||
priceCurrency: "USD",
|
||||
description: `Free access to ${model.name} AI chat without login`,
|
||||
},
|
||||
provider: {
|
||||
"@type": "Organization",
|
||||
name: "SurfSense",
|
||||
url: "https://surfsense.com",
|
||||
},
|
||||
isPartOf: {
|
||||
"@type": "WebSite",
|
||||
name: "SurfSense",
|
||||
url: "https://surfsense.com",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FAQJsonLd questions={faqItems} />
|
||||
|
||||
{/* Chat fills the entire viewport area inside LayoutShell */}
|
||||
<div className="h-full">
|
||||
<FreeChatPage />
|
||||
</div>
|
||||
|
||||
{/* SEO content: in DOM for crawlers, clipped by parent overflow-hidden */}
|
||||
<div className="border-t bg-background">
|
||||
<article className="container mx-auto px-4 py-10 max-w-3xl">
|
||||
<BreadcrumbNav
|
||||
items={[
|
||||
{ name: "Home", href: "/" },
|
||||
{ name: "Free AI Chat", href: "/free" },
|
||||
{ name: model.name, href: `/free/${model.seo_slug}` },
|
||||
]}
|
||||
/>
|
||||
|
||||
<header className="mt-6 mb-6">
|
||||
<h1 className="text-2xl font-bold mb-2">Chat with {model.name} Free, No Login</h1>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Use <strong>{model.name}</strong> free online without login or sign-up. No account, no
|
||||
email, no password needed. Powered by SurfSense.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<Separator className="my-8" />
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold mb-4">
|
||||
{model.name} Free: Frequently Asked Questions
|
||||
</h2>
|
||||
<dl className="flex flex-col gap-3">
|
||||
{faqItems.map((item) => (
|
||||
<div key={item.question} className="rounded-lg border bg-card p-4">
|
||||
<dt className="font-medium text-sm">{item.question}</dt>
|
||||
<dd className="mt-1.5 text-sm text-muted-foreground leading-relaxed">
|
||||
{item.answer}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{relatedModels.length > 0 && (
|
||||
<>
|
||||
<Separator className="my-8" />
|
||||
<nav aria-label="Other free AI models">
|
||||
<h2 className="text-xl font-bold mb-4">Try Other Free AI Models</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{relatedModels.map((m) => (
|
||||
<Button key={m.id} variant="outline" size="sm" asChild>
|
||||
<Link href={`/free/${m.seo_slug}`}>
|
||||
{m.name}
|
||||
<SquareArrowOutUpRight className="size-3" />
|
||||
</Link>
|
||||
</Button>
|
||||
))}
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/free">View All Models</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
387
surfsense_web/app/(home)/free/page.tsx
Normal file
387
surfsense_web/app/(home)/free/page.tsx
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
import { SquareArrowOutUpRight } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { BreadcrumbNav } from "@/components/seo/breadcrumb-nav";
|
||||
import { FAQJsonLd, JsonLd } from "@/components/seo/json-ld";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import type { AnonModel } from "@/contracts/types/anonymous-chat.types";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "ChatGPT Free Online Without Login | Chat GPT No Login, Claude AI Free | SurfSense",
|
||||
description:
|
||||
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more for free. No sign-up required. Open source NotebookLM alternative with free AI chat and document Q&A.",
|
||||
keywords: [
|
||||
"chatgpt free",
|
||||
"chat gpt free",
|
||||
"free chatgpt",
|
||||
"free chat gpt",
|
||||
"chatgpt online",
|
||||
"chat gpt online",
|
||||
"online chatgpt",
|
||||
"chatgpt free online",
|
||||
"chatgpt online free",
|
||||
"chat gpt free online",
|
||||
"chatgpt no login",
|
||||
"chatgpt without login",
|
||||
"chat gpt login free",
|
||||
"chat gpt login",
|
||||
"free chatgpt without login",
|
||||
"free chatgpt no login",
|
||||
"ai chat no login",
|
||||
"ai chat without login",
|
||||
"claude ai without login",
|
||||
"claude no login",
|
||||
"chatgpt for free",
|
||||
"gpt chat free",
|
||||
"claude ai free",
|
||||
"claude free",
|
||||
"free claude ai",
|
||||
"free claude",
|
||||
"chatgpt alternative free",
|
||||
"free chatgpt alternative",
|
||||
"chatgpt free alternative",
|
||||
"free alternative to chatgpt",
|
||||
"alternative to chatgpt free",
|
||||
"ai like chatgpt",
|
||||
"sites like chatgpt",
|
||||
"free ai chatbot like chatgpt",
|
||||
"free ai chatbots like chatgpt",
|
||||
"apps like chatgpt for free",
|
||||
"best free alternative to chatgpt",
|
||||
"free ai apps",
|
||||
"ai with no restrictions",
|
||||
"notebooklm alternative",
|
||||
],
|
||||
alternates: {
|
||||
canonical: "https://surfsense.com/free",
|
||||
},
|
||||
openGraph: {
|
||||
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense",
|
||||
description:
|
||||
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and 100+ AI models. Open source NotebookLM alternative.",
|
||||
url: "https://surfsense.com/free",
|
||||
siteName: "SurfSense",
|
||||
type: "website",
|
||||
images: [
|
||||
{
|
||||
url: "/og-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "SurfSense - ChatGPT Free Online, Claude AI Free, No Login Required",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense",
|
||||
description:
|
||||
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more. No sign-up needed.",
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
};
|
||||
|
||||
async function getModels(): Promise<AnonModel[]> {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
next: { revalidate: 300 },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
return res.json();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const FAQ_ITEMS = [
|
||||
{
|
||||
question: "Can I use ChatGPT without login?",
|
||||
answer:
|
||||
"Yes. SurfSense lets you use ChatGPT without login or any sign-up. Just pick a model and start chatting. No email, no password, no account needed. You get 1 million free tokens to use across ChatGPT, Claude AI, Gemini, and other models.",
|
||||
},
|
||||
{
|
||||
question: "Is ChatGPT really free on SurfSense?",
|
||||
answer:
|
||||
"Yes. SurfSense gives you free access to ChatGPT (GPT-4), Claude AI, Gemini, and other models without login. You get 1 million free tokens across any model with no sign-up required.",
|
||||
},
|
||||
{
|
||||
question: "How do I use ChatGPT no login?",
|
||||
answer:
|
||||
"Go to any model page on SurfSense and start typing your message. There is no login wall, no account creation, and no verification step. ChatGPT no login works instantly in your browser.",
|
||||
},
|
||||
{
|
||||
question: "What AI models can I use for free without login?",
|
||||
answer:
|
||||
"SurfSense offers free access without login to models from OpenAI (GPT-4, GPT-4 Turbo), Anthropic (Claude 3, Claude free), Google (Gemini), DeepSeek, Mistral, Llama, and more. All available as a free ChatGPT alternative online with no login required.",
|
||||
},
|
||||
{
|
||||
question: "What happens after I use 1 million free tokens?",
|
||||
answer:
|
||||
"After your free tokens, create a free SurfSense account to unlock 5 million more. Premium model tokens can be purchased at $1 per million tokens. Non-premium models remain unlimited for registered users.",
|
||||
},
|
||||
{
|
||||
question: "Is Claude AI available without login?",
|
||||
answer:
|
||||
"Yes. You can use Claude AI free without login on SurfSense. Both Claude 3 and other Anthropic models are available with no sign-up, alongside ChatGPT and Gemini.",
|
||||
},
|
||||
{
|
||||
question: "How is SurfSense different from ChatGPT?",
|
||||
answer:
|
||||
"SurfSense is an open source NotebookLM alternative that gives you access to multiple AI models in one place without login. Unlike ChatGPT alone, SurfSense includes document Q&A with citations, integrations with Slack, Google Drive, Notion, and Confluence, plus team collaboration features.",
|
||||
},
|
||||
{
|
||||
question: "Is SurfSense a free ChatGPT alternative?",
|
||||
answer:
|
||||
"Yes. SurfSense is a free, open source alternative to ChatGPT that works without login. It gives you access to Claude AI free, Gemini, and other AI models alongside document Q&A with citations, team collaboration, and 30+ integrations.",
|
||||
},
|
||||
{
|
||||
question: "Is my data private when using free AI chat without login?",
|
||||
answer:
|
||||
"Anonymous chat sessions are not stored in any database. No account means no personal data collected. SurfSense is open source, so you can self-host for complete data control and privacy.",
|
||||
},
|
||||
];
|
||||
|
||||
export default async function FreeHubPage() {
|
||||
const models = await getModels();
|
||||
const seoModels = models.filter((m) => m.seo_slug);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pt-20">
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
name: "ChatGPT Free Online Without Login - SurfSense",
|
||||
description:
|
||||
"Use ChatGPT, Claude AI, Gemini and more AI models free online without login or sign-up. Open source NotebookLM alternative with no login required.",
|
||||
url: "https://surfsense.com/free",
|
||||
isPartOf: { "@type": "WebSite", name: "SurfSense", url: "https://surfsense.com" },
|
||||
mainEntity: {
|
||||
"@type": "ItemList",
|
||||
numberOfItems: seoModels.length,
|
||||
itemListElement: seoModels.map((m, i) => ({
|
||||
"@type": "ListItem",
|
||||
position: i + 1,
|
||||
name: m.name,
|
||||
url: `https://surfsense.com/free/${m.seo_slug}`,
|
||||
})),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FAQJsonLd questions={FAQ_ITEMS} />
|
||||
|
||||
<article className="container mx-auto px-4 pb-20">
|
||||
<BreadcrumbNav
|
||||
items={[
|
||||
{ name: "Home", href: "/" },
|
||||
{ name: "Free AI Chat", href: "/free" },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="mt-8 text-center max-w-3xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-bold tracking-tight">
|
||||
ChatGPT Free Online Without Login
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-muted-foreground max-w-2xl mx-auto leading-relaxed">
|
||||
Use <strong>ChatGPT</strong>, <strong>Claude AI</strong>, <strong>Gemini</strong>, and
|
||||
other AI models free online without login. No sign-up, no email, no password. Pick a
|
||||
model and start chatting instantly.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-3 mt-6">
|
||||
<Badge variant="secondary" className="px-3 py-1.5 text-sm">
|
||||
No login required
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="px-3 py-1.5 text-sm">
|
||||
1M free tokens
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="px-3 py-1.5 text-sm">
|
||||
{seoModels.length} AI models
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="px-3 py-1.5 text-sm">
|
||||
Open source
|
||||
</Badge>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Separator className="my-12 max-w-4xl mx-auto" />
|
||||
|
||||
{/* Model Table */}
|
||||
{seoModels.length > 0 ? (
|
||||
<section
|
||||
className="max-w-4xl mx-auto"
|
||||
aria-label="Free AI models available without login"
|
||||
>
|
||||
<h2 className="text-2xl font-bold mb-2">Free AI Models Available Without Login</h2>
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
All models below work without login or sign-up. Click any model to start a free AI
|
||||
chat instantly.
|
||||
</p>
|
||||
|
||||
<div className="overflow-hidden rounded-lg border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[45%]">Model</TableHead>
|
||||
<TableHead>Provider</TableHead>
|
||||
<TableHead>Tier</TableHead>
|
||||
<TableHead className="text-right w-[100px]" />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{seoModels.map((model) => (
|
||||
<TableRow key={model.id}>
|
||||
<TableCell>
|
||||
<Link
|
||||
href={`/free/${model.seo_slug}`}
|
||||
className="group flex flex-col gap-0.5"
|
||||
>
|
||||
<span className="font-medium group-hover:underline">{model.name}</span>
|
||||
{model.description && (
|
||||
<span className="text-xs text-muted-foreground line-clamp-1">
|
||||
{model.description}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{model.provider}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{model.is_premium ? (
|
||||
<Badge className="bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300 border-0">
|
||||
Premium
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">Free</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href={`/free/${model.seo_slug}`}>
|
||||
Chat
|
||||
<SquareArrowOutUpRight className="size-3" />
|
||||
</Link>
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section className="mt-12 text-center max-w-4xl mx-auto">
|
||||
<p className="text-muted-foreground">
|
||||
No models are currently available. Please check back later.
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<Separator className="my-12 max-w-4xl mx-auto" />
|
||||
|
||||
{/* Why SurfSense */}
|
||||
<section className="max-w-4xl mx-auto">
|
||||
<h2 className="text-2xl font-bold mb-6">
|
||||
Why Use SurfSense as Your Free ChatGPT Alternative
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="rounded-lg border bg-card p-5">
|
||||
<h3 className="font-semibold mb-1.5">Multiple AI Models in One Place</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Access ChatGPT, Claude AI free, Gemini, DeepSeek, and more. Works like sites like
|
||||
ChatGPT but with all AI models available, not just GPT. A true free AI chatbot like
|
||||
ChatGPT and beyond.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border bg-card p-5">
|
||||
<h3 className="font-semibold mb-1.5">No Login, No Sign-Up Required</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Start using ChatGPT free online immediately. No email, no password, no verification.
|
||||
Get ChatGPT no login access and Claude AI free access from one platform. AI with no
|
||||
restrictions on which model you can use.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border bg-card p-5">
|
||||
<h3 className="font-semibold mb-1.5">Open Source NotebookLM Alternative</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
SurfSense is a free, open source NotebookLM alternative with document Q&A and
|
||||
citations, integrations with Slack, Google Drive, Notion, and Confluence, plus team
|
||||
collaboration and self-hosting support.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Separator className="my-12 max-w-4xl mx-auto" />
|
||||
|
||||
{/* CTA */}
|
||||
<section className="max-w-3xl mx-auto text-center">
|
||||
<h2 className="text-2xl font-bold mb-3">Want More Features?</h2>
|
||||
<p className="text-muted-foreground mb-6 leading-relaxed">
|
||||
Create a free SurfSense account to unlock 5 million tokens, document uploads with
|
||||
citations, team collaboration, and integrations with Slack, Google Drive, Notion, and
|
||||
30+ more tools.
|
||||
</p>
|
||||
<Button size="lg" asChild>
|
||||
<Link href="/register">Create Free Account</Link>
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<Separator className="my-12 max-w-4xl mx-auto" />
|
||||
|
||||
{/* FAQ */}
|
||||
<section className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-bold text-center mb-8">Frequently Asked Questions</h2>
|
||||
<dl className="flex flex-col gap-4">
|
||||
{FAQ_ITEMS.map((item) => (
|
||||
<div key={item.question} className="rounded-lg border bg-card p-5">
|
||||
<dt className="font-medium text-sm">{item.question}</dt>
|
||||
<dd className="mt-2 text-sm text-muted-foreground leading-relaxed">
|
||||
{item.answer}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{/* Internal links */}
|
||||
<nav aria-label="Related pages" className="mt-16 max-w-3xl mx-auto">
|
||||
<h2 className="text-lg font-semibold mb-3">Explore SurfSense</h2>
|
||||
<ul className="flex flex-wrap gap-2">
|
||||
<li>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/pricing">Pricing</Link>
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/docs">Documentation</Link>
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/blog">Blog</Link>
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href="/register">Sign Up Free</Link>
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</article>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,6 +7,11 @@ import { Navbar } from "@/components/homepage/navbar";
|
|||
export default function HomePageLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const isAuthPage = pathname === "/login" || pathname === "/register";
|
||||
const isFreeModelChat = /^\/free\/[^/]+$/.test(pathname);
|
||||
|
||||
if (isFreeModelChat) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-linear-to-b from-gray-50 to-gray-100 text-gray-900 dark:from-black dark:to-gray-900 dark:text-white overflow-x-hidden">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import { useState } from "react";
|
||||
import { BuyPagesContent } from "@/components/settings/buy-pages-content";
|
||||
import { BuyTokensContent } from "@/components/settings/buy-tokens-content";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TABS = [
|
||||
{ id: "pages", label: "Pages" },
|
||||
{ id: "tokens", label: "Premium Tokens" },
|
||||
] as const;
|
||||
|
||||
type TabId = (typeof TABS)[number]["id"];
|
||||
|
||||
export default function BuyMorePage() {
|
||||
const [activeTab, setActiveTab] = useState<TabId>("pages");
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[calc(100vh-64px)] select-none items-center justify-center px-4 py-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="w-full max-w-md space-y-6"
|
||||
>
|
||||
<div className="flex items-center justify-center rounded-lg border bg-muted/30 p-1">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={cn(
|
||||
"flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
||||
activeTab === tab.id
|
||||
? "bg-background text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{activeTab === "pages" ? <BuyPagesContent /> : <BuyTokensContent />}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,19 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import { BuyPagesContent } from "@/components/settings/buy-pages-content";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function BuyPagesPage() {
|
||||
return (
|
||||
<div className="flex min-h-[calc(100vh-64px)] select-none items-center justify-center px-4 py-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="w-full max-w-md space-y-6"
|
||||
>
|
||||
<BuyPagesContent />
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const searchSpaceId = params?.search_space_id ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
router.replace(`/dashboard/${searchSpaceId}/buy-more`);
|
||||
}, [router, searchSpaceId]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function BuyTokensPage() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const searchSpaceId = params?.search_space_id ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
router.replace(`/dashboard/${searchSpaceId}/buy-more`);
|
||||
}, [router, searchSpaceId]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -23,16 +23,14 @@ export default function PurchaseCancelPage() {
|
|||
<CardHeader className="text-center">
|
||||
<CircleSlash2 className="mx-auto h-10 w-10 text-muted-foreground" />
|
||||
<CardTitle className="text-2xl">Checkout canceled</CardTitle>
|
||||
<CardDescription>
|
||||
No charge was made and your current pages are unchanged.
|
||||
</CardDescription>
|
||||
<CardDescription>No charge was made and your account is unchanged.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-center text-sm text-muted-foreground">
|
||||
You can return to the pricing options and try again whenever you're ready.
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col gap-2 sm:flex-row">
|
||||
<Button asChild className="w-full">
|
||||
<Link href={`/dashboard/${searchSpaceId}/more-pages`}>Back to Buy Pages</Link>
|
||||
<Link href={`/dashboard/${searchSpaceId}/buy-more`}>Back to Pricing</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<Link href={`/dashboard/${searchSpaceId}/new-chat`}>Back to Dashboard</Link>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default function PurchaseSuccessPage() {
|
|||
|
||||
useEffect(() => {
|
||||
void queryClient.invalidateQueries({ queryKey: USER_QUERY_KEY });
|
||||
void queryClient.invalidateQueries({ queryKey: ["token-status"] });
|
||||
}, [queryClient]);
|
||||
|
||||
return (
|
||||
|
|
@ -31,13 +32,11 @@ export default function PurchaseSuccessPage() {
|
|||
<CardHeader className="text-center">
|
||||
<CheckCircle2 className="mx-auto h-10 w-10 text-emerald-500" />
|
||||
<CardTitle className="text-2xl">Purchase complete</CardTitle>
|
||||
<CardDescription>
|
||||
Your additional pages are being applied to your account now.
|
||||
</CardDescription>
|
||||
<CardDescription>Your purchase is being applied to your account now.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your sidebar usage meter should refresh automatically in a moment.
|
||||
Your usage meters should refresh automatically in a moment.
|
||||
</p>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col gap-2">
|
||||
|
|
@ -45,7 +44,7 @@ export default function PurchaseSuccessPage() {
|
|||
<Link href={`/dashboard/${searchSpaceId}/new-chat`}>Back to Dashboard</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<Link href={`/dashboard/${searchSpaceId}/more-pages`}>Buy More Pages</Link>
|
||||
<Link href={`/dashboard/${searchSpaceId}/buy-more`}>Buy More</Link>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -14,14 +14,31 @@ const changelogSource = loader({
|
|||
});
|
||||
|
||||
const BASE_URL = "https://surfsense.com";
|
||||
const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
async function getFreeModelSlugs(): Promise<string[]> {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
next: { revalidate: 3600 },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const models = await res.json();
|
||||
return models
|
||||
.filter((m: { seo_slug?: string }) => m.seo_slug)
|
||||
.map((m: { seo_slug: string }) => m.seo_slug);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const now = new Date();
|
||||
now.setMinutes(0, 0, 0);
|
||||
const lastModified = now;
|
||||
|
||||
const staticPages: MetadataRoute.Sitemap = [
|
||||
{ url: `${BASE_URL}/`, lastModified, changeFrequency: "daily", priority: 1 },
|
||||
{ url: `${BASE_URL}/free`, lastModified, changeFrequency: "daily", priority: 0.95 },
|
||||
{ url: `${BASE_URL}/pricing`, lastModified, changeFrequency: "weekly", priority: 0.9 },
|
||||
{ url: `${BASE_URL}/contact`, lastModified, changeFrequency: "monthly", priority: 0.7 },
|
||||
{ url: `${BASE_URL}/blog`, lastModified, changeFrequency: "daily", priority: 0.9 },
|
||||
|
|
@ -34,6 +51,14 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
|||
{ url: `${BASE_URL}/register`, lastModified, changeFrequency: "monthly", priority: 0.5 },
|
||||
];
|
||||
|
||||
const slugs = await getFreeModelSlugs();
|
||||
const freeModelPages: MetadataRoute.Sitemap = slugs.map((slug) => ({
|
||||
url: `${BASE_URL}/free/${slug}`,
|
||||
lastModified,
|
||||
changeFrequency: "daily" as const,
|
||||
priority: 0.9,
|
||||
}));
|
||||
|
||||
const docsPages: MetadataRoute.Sitemap = docsSource.getPages().map((page) => ({
|
||||
url: `${BASE_URL}${page.url}`,
|
||||
lastModified,
|
||||
|
|
@ -55,5 +80,5 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
|||
priority: 0.5,
|
||||
}));
|
||||
|
||||
return [...staticPages, ...docsPages, ...blogPages, ...changelogPages];
|
||||
return [...staticPages, ...freeModelPages, ...docsPages, ...blogPages, ...changelogPages];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue