From 1a4400c923ae390dd5fdd21172bb2b905475e2f2 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Thu, 21 May 2026 13:37:55 -0700 Subject: [PATCH 1/5] refactor(env): streamline BACKEND_URL usage in GoogleLoginButton and DocumentTabContent; update connector-status-config for Composio Google Drive connector maintenance --- surfsense_web/app/(home)/login/GoogleLoginButton.tsx | 5 +---- .../connector-popup/config/connector-status-config.json | 5 +++++ .../components/layout/ui/tabs/DocumentTabContent.tsx | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx index 2e5785f80..7e703a70c 100644 --- a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx +++ b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx @@ -5,7 +5,7 @@ import { Logo } from "@/components/Logo"; import { Button } from "@/components/ui/button"; import { trackLoginAttempt } from "@/lib/posthog/events"; import { AmbientBackground } from "./AmbientBackground"; -<<<<<<< HEAD +import { BACKEND_URL } from "@/lib/env-config"; function GoogleGLogo({ className }: { className?: string }) { return ( @@ -35,9 +35,6 @@ function GoogleGLogo({ className }: { className?: string }) { ); } -======= -import { BACKEND_URL } from "@/lib/env-config"; ->>>>>>> 1127aedb4 (refactor(env): replace inline process.env reads with BACKEND_URL in editor, chat, dashboard and settings) export function GoogleLoginButton() { const t = useTranslations("auth"); const [isRedirecting, setIsRedirecting] = useState(false); diff --git a/surfsense_web/components/assistant-ui/connector-popup/config/connector-status-config.json b/surfsense_web/components/assistant-ui/connector-popup/config/connector-status-config.json index b4e85eab0..466446da9 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/config/connector-status-config.json +++ b/surfsense_web/components/assistant-ui/connector-popup/config/connector-status-config.json @@ -19,6 +19,11 @@ "enabled": false, "status": "maintenance", "statusMessage": "Rework in progress." + }, + "COMPOSIO_GOOGLE_DRIVE_CONNECTOR": { + "enabled": false, + "status": "maintenance", + "statusMessage": "Temporarily unavailable due to an upstream Composio bug (ComposioHQ/composio#3471) that returns malformed presigned URLs for Drive file downloads. Use the native Google Drive connector in the meantime." } }, "globalSettings": { diff --git a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx index 20edc5de3..ef51eee3c 100644 --- a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx +++ b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx @@ -10,7 +10,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils"; -import { BACKEND_URL, BACKEND_URL } from "@/lib/env-config"; +import { BACKEND_URL } from "@/lib/env-config"; const LARGE_DOCUMENT_THRESHOLD = 2 * 1024 * 1024; // 2MB interface DocumentContent { From cacb27e007cfbd45027eb0dad6da4982ed74eabb Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Thu, 21 May 2026 14:41:32 -0700 Subject: [PATCH 2/5] fix: citations in agent responses --- .../system_prompt/prompts/citations/on.md | 45 ++++++++++++++++--- .../knowledge_base/description_readonly.md | 2 +- .../knowledge_base/system_prompt_cloud.md | 37 +++++++++++++++ .../knowledge_base/system_prompt_desktop.md | 4 ++ .../system_prompt_readonly_cloud.md | 39 ++++++++++++++++ .../system_prompt_readonly_desktop.md | 4 ++ 6 files changed, 123 insertions(+), 8 deletions(-) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md index b200f7a9a..e61a0bffb 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md @@ -1,11 +1,42 @@ -Apply chunk citations only when the runtime injects `` / -`` blocks. +Citations reach the answer through two channels. Use whichever applies — and +never invent ids you didn't see. Citation ids are resolved by exact-match +lookup; a wrong id silently breaks the link, so when in doubt, omit. + +### Channel A — chunk blocks injected this turn +When `search_surfsense_docs` or `web_search` returns `` / +`` blocks in this turn: 1. For each factual statement taken from those chunks, add - `[citation:chunk_id]` using the exact id from ``. -2. Multiple chunks → `[citation:id1], [citation:id2]` (comma-separated). -3. Never invent or normalise ids; if unsure, omit. -4. Plain brackets only — no markdown links, no footnote numbering. -5. If no chunk-tagged documents appear this turn, do not fabricate citations. + `[citation:chunk_id]` using the **exact** id from a visible + `` tag. Copy digit-for-digit (or the URL verbatim); + do not retype from memory. +2. `` is the parent doc id, **not** a citation source — + only ids inside `` count. +3. Multiple chunks → `[citation:id1], [citation:id2]` (comma-separated, + each id copied individually). +4. Never invent, normalise, or guess at adjacent ids; if unsure, omit. +5. Plain brackets only — no markdown links, no footnote numbering. + +### Channel B — citations relayed by a `task` specialist +A `task(...)` tool message may contain `[citation:]` markers +the specialist already attached to its prose. The specialist saw the +underlying `` blocks; you didn't. So: + +1. **Preserve those markers verbatim** in your final answer — do not + reformat, renumber, drop, or wrap them in markdown links. When you + paraphrase a specialist sentence, copy the marker character-for- + character; do not regenerate the id from memory (LLMs reliably + corrupt nearby digits). +2. Keep each marker attached to the sentence the specialist attached + it to. +3. Do **not** add new `[citation:…]` markers of your own to a + specialist's prose; if a fact has no marker, the specialist + couldn't tie it to a chunk and neither can you. +4. When a specialist returns JSON, the citation markers live inside + the prose-bearing fields (e.g. a summary or excerpt). Pull them + along with the surrounding sentence when you quote. + +If neither channel surfaces citation markers this turn, do not fabricate +them. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/description_readonly.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/description_readonly.md index d6837ec92..e989e3ee6 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/description_readonly.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/description_readonly.md @@ -2,4 +2,4 @@ Read-only specialist for the user's workspace (documents and folders). Use to fi Pass your full question as one string. The specialist runs in isolation: it cannot see this thread, so include any path hints, filters, or constraints it needs. -The specialist returns plain prose with absolute paths. +The specialist returns plain prose with absolute paths and `[citation:]` markers when claims came from KB-indexed chunks. Preserve those markers verbatim if you forward the answer. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md index 514ec6639..2ae21c271 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md @@ -35,6 +35,43 @@ Map outcomes to your `status`: You construct the structured `evidence` fields from your own knowledge of what you called and what you observed — the tools do not return them. Never report values you did not actually see. +## Chunk citations in your prose + +When `read_file` returns a KB-indexed document under `/documents/`, the response includes `` blocks. Whenever a fact in your `action_summary` or `evidence.content_excerpt` came from a specific chunk, append `[citation:]` to the sentence stating that fact, using the **exact** id from the `` tag. The caller relays these markers to the end user verbatim, and the UI resolves each id by exact match against the database, so a wrong id silently breaks the citation. + +### Where chunk ids live in `read_file` output + +A KB document's XML has three numeric attributes — only **one** is a citation source: + +``` + + + 42 ← NOT a citation. Parent doc id; ignore for citations. + ... + + + ← Index hint; the same id also appears below. + + + + ← This is the citation source. + + + +``` + +### Rules + +- Use the **exact** id from a `` tag whose content you actually quoted or paraphrased. Copy digit-for-digit; do **not** retype from memory. +- Before emitting `[citation:N]`, confirm the literal substring `` (or its index twin `chunk_id="N"`) appears in the tool result you are summarising this turn. If you can't see it, omit the citation. +- Never cite `` — that's the parent doc, not a chunk. +- Never invent, normalise, shorten, or guess at adjacent ids. If unsure between two candidates, omit rather than pick. +- Prefer **fewer accurate citations** over many speculative ones. +- Multiple chunks supporting the same point → comma-separated and copied individually: `[citation:128], [citation:129]`. +- Plain square brackets only — no markdown links, no parentheses, no footnote numbers. +- Tool results without `` (write/edit/move confirmations, `ls` / `glob` / `grep` listings, error strings) carry no chunk id and need none. +- Populate `evidence.chunk_ids` with **only** ids you actually emitted in `[citation:…]` markers — same set, same digits. + ## Examples **Example 1 — happy path write (path discovered from existing convention):** diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md index bfa96ee5b..4e5465aaf 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md @@ -35,6 +35,10 @@ Map outcomes to your `status`: You construct the structured `evidence` fields from your own knowledge of what you called and what you observed — the tools do not return them. `chunk_ids` apply only to `` hits; for local-file operations leave them `null`. Never report values you did not actually see. +## Chunk citations in your prose + +In desktop mode your filesystem tools read local files only, and local-file tool results do **not** carry `` tags. Do not emit `[citation:…]` markers in `action_summary` or `evidence.content_excerpt`, and leave `evidence.chunk_ids` `null` — the absolute path is the only reference for local-file work. + ## Examples **Example 1 — happy path write (path discovered from existing convention):** diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_cloud.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_cloud.md index 3abfcd8b9..c7813e71d 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_cloud.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_cloud.md @@ -27,3 +27,42 @@ Reply in plain prose: - Cite every claim with an absolute path under `/documents/`. - If the workspace does not contain the requested information, say so explicitly. Do not fabricate paths or content. - If the question is genuinely ambiguous after a thorough lookup, list the candidates with their paths and stop. + +## Chunk citations + +When the evidence for a claim came from a `read_file` response that included `` blocks (i.e. a KB-indexed document under `/documents/`), append `[citation:]` to the sentence stating that claim. The caller passes these markers through to the end user verbatim, and the UI resolves each id by exact match against the database, so a wrong id silently breaks the citation. + +### Where chunk ids live in `read_file` output + +A KB document's XML has three numeric attributes — only **one** is a citation source: + +``` + + + 42 ← NOT a citation. Parent doc id; ignore for citations. + ... + + + ← Index hint; the same id also appears below. + + + + ← This is the citation source. + + + +``` + +### Rules + +- Use the **exact** id from a `` tag whose content you actually quoted or paraphrased. Copy digit-for-digit; do **not** retype from memory. +- Before emitting `[citation:N]`, confirm the literal substring `` (or its index twin `chunk_id="N"`) appears in the tool result you are summarising this turn. If you can't see it, omit the citation. +- Never cite `` — that's the parent doc, not a chunk. +- Never invent, normalise, shorten, or guess at adjacent ids. If unsure between two candidates, omit rather than pick. +- Prefer **fewer accurate citations** over many speculative ones. One correct `[citation:128]` is more useful than a string of wrong ids. +- Multiple chunks supporting the same point → comma-separated and copied individually: `[citation:128], [citation:129]`. +- Plain square brackets only — no markdown links, no parentheses, no footnote numbers. +- If a claim came from a tool result that did **not** carry a chunk id (`ls`, `glob`, `grep` listings, error strings, or files without ``), skip the citation. +- The absolute path under `/documents/` is always required; chunk citations are additive, they do not replace the path reference. + +Example: `The Q2 roadmap lists three milestones (/documents/planning/q2-roadmap.md) [citation:128], [citation:129].` diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_desktop.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_desktop.md index 1b3d72b64..2ea711e44 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_desktop.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_readonly_desktop.md @@ -28,3 +28,7 @@ Reply in plain prose: - Cite every claim with an absolute path. - If the workspace does not contain the requested information, say so explicitly. Do not fabricate paths or content. - If the question is genuinely ambiguous after a thorough lookup, list the candidates with their paths and stop. + +## Chunk citations + +In desktop mode your filesystem tools read local files only, and local-file `read_file` responses do **not** carry `` tags. Cite each claim with the absolute local path; do not emit `[citation:…]` markers — your caller has nothing to resolve them against. From 2e589091d85d9ee3fd3169d7980d31846321b95a Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Thu, 21 May 2026 14:44:33 -0700 Subject: [PATCH 3/5] feat: bumped version to 0.0.25 --- VERSION | 2 +- surfsense_backend/pyproject.toml | 2 +- surfsense_backend/uv.lock | 2 +- surfsense_browser_extension/package.json | 2 +- surfsense_desktop/package.json | 2 +- surfsense_web/package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index b056f4120..2678ff8d6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.24 +0.0.25 diff --git a/surfsense_backend/pyproject.toml b/surfsense_backend/pyproject.toml index 26fee1bc3..cd2a6921a 100644 --- a/surfsense_backend/pyproject.toml +++ b/surfsense_backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "surf-new-backend" -version = "0.0.24" +version = "0.0.25" description = "SurfSense Backend" requires-python = ">=3.12" dependencies = [ diff --git a/surfsense_backend/uv.lock b/surfsense_backend/uv.lock index c4e6b5c89..953aebbef 100644 --- a/surfsense_backend/uv.lock +++ b/surfsense_backend/uv.lock @@ -7947,7 +7947,7 @@ wheels = [ [[package]] name = "surf-new-backend" -version = "0.0.24" +version = "0.0.25" source = { editable = "." } dependencies = [ { name = "alembic" }, diff --git a/surfsense_browser_extension/package.json b/surfsense_browser_extension/package.json index 028e653b3..2f17899a8 100644 --- a/surfsense_browser_extension/package.json +++ b/surfsense_browser_extension/package.json @@ -1,7 +1,7 @@ { "name": "surfsense_browser_extension", "displayName": "Surfsense Browser Extension", - "version": "0.0.24", + "version": "0.0.25", "description": "Extension to collect Browsing History for SurfSense.", "author": "https://github.com/MODSetter", "engines": { diff --git a/surfsense_desktop/package.json b/surfsense_desktop/package.json index 68032e9f4..0ad279ece 100644 --- a/surfsense_desktop/package.json +++ b/surfsense_desktop/package.json @@ -1,6 +1,6 @@ { "name": "surfsense-desktop", - "version": "0.0.24", + "version": "0.0.25", "description": "SurfSense Desktop App", "main": "dist/main.js", "scripts": { diff --git a/surfsense_web/package.json b/surfsense_web/package.json index 640d5c207..213adbaad 100644 --- a/surfsense_web/package.json +++ b/surfsense_web/package.json @@ -1,6 +1,6 @@ { "name": "surfsense_web", - "version": "0.0.24", + "version": "0.0.25", "private": true, "packageManager": "pnpm@10.26.0", "description": "SurfSense Frontend", From 2eaf4fbce19ffc1abc3a620aa19453eb549215f6 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Thu, 21 May 2026 21:01:10 -0700 Subject: [PATCH 4/5] feat: added adsense on /free page --- surfsense_web/.env.example | 11 ++- surfsense_web/app/(home)/free/page.tsx | 20 +++++ surfsense_web/components/ads/ad-unit.tsx | 78 +++++++++++++++++++ .../components/ads/adsense-config.ts | 13 ++++ .../components/ads/adsense-script.tsx | 27 +++++++ surfsense_web/public/ads.txt | 1 + 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 surfsense_web/components/ads/ad-unit.tsx create mode 100644 surfsense_web/components/ads/adsense-config.ts create mode 100644 surfsense_web/components/ads/adsense-script.tsx create mode 100644 surfsense_web/public/ads.txt diff --git a/surfsense_web/.env.example b/surfsense_web/.env.example index b121daf0b..5fb9d07d1 100644 --- a/surfsense_web/.env.example +++ b/surfsense_web/.env.example @@ -18,4 +18,13 @@ NEXT_PUBLIC_POSTHOG_KEY= # Cloudflare Turnstile CAPTCHA for anonymous chat abuse prevention # Get your site key from https://dash.cloudflare.com/ -> Turnstile -NEXT_PUBLIC_TURNSTILE_SITE_KEY= \ No newline at end of file +NEXT_PUBLIC_TURNSTILE_SITE_KEY= + +# Google AdSense (optional, only enables ads on the /free hub page). +# Publisher ID from your AdSense dashboard, e.g. ca-pub-XXXXXXXXXXXXXXXX. +# Leave empty to disable ad rendering entirely. +NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID= +# Ad unit slot IDs from AdSense dashboard -> Ads -> By ad unit. +# Leave empty to hide individual slots while keeping the script loaded. +NEXT_PUBLIC_GOOGLE_ADSENSE_SLOT_FREE_HUB_IN_CONTENT= +NEXT_PUBLIC_GOOGLE_ADSENSE_SLOT_FREE_HUB_BEFORE_FAQ= \ No newline at end of file diff --git a/surfsense_web/app/(home)/free/page.tsx b/surfsense_web/app/(home)/free/page.tsx index cd75e7908..4512f3396 100644 --- a/surfsense_web/app/(home)/free/page.tsx +++ b/surfsense_web/app/(home)/free/page.tsx @@ -1,6 +1,9 @@ import { SquareArrowOutUpRight } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; +import { AdUnit } from "@/components/ads/ad-unit"; +import { ADSENSE_SLOTS } from "@/components/ads/adsense-config"; +import { AdSenseScript } from "@/components/ads/adsense-script"; import { BreadcrumbNav } from "@/components/seo/breadcrumb-nav"; import { FAQJsonLd, JsonLd } from "@/components/seo/json-ld"; import { Badge } from "@/components/ui/badge"; @@ -157,6 +160,7 @@ export default async function FreeHubPage() { return (
+ + {/* In-content ad: above the model table */} + + {/* Model Table */} {seoModels.length > 0 ? (
+ {/* In-content ad: after CTA, before FAQ */} + + {/* FAQ */}

Frequently Asked Questions

diff --git a/surfsense_web/components/ads/ad-unit.tsx b/surfsense_web/components/ads/ad-unit.tsx new file mode 100644 index 000000000..5f5860607 --- /dev/null +++ b/surfsense_web/components/ads/ad-unit.tsx @@ -0,0 +1,78 @@ +"use client"; + +import type { CSSProperties } from "react"; +import { useEffect, useRef } from "react"; +import { cn } from "@/lib/utils"; + +const ADSENSE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID; + +declare global { + interface Window { + adsbygoogle?: Record[]; + } +} + +interface AdUnitProps { + /** AdSense ad slot ID from your AdSense dashboard. */ + slot: string; + /** AdSense ad format. Defaults to "auto" for responsive display ads. */ + format?: "auto" | "fluid" | "rectangle" | "vertical" | "horizontal"; + /** Optional layout (e.g. "in-article"). */ + layout?: string; + /** Optional layout key (required for in-feed ads). */ + layoutKey?: string; + /** Full-width responsive on mobile. Defaults to true. */ + responsive?: boolean; + className?: string; + style?: CSSProperties; +} + +/** + * Renders a Google AdSense ad unit. Requires to be mounted + * on the same page. Renders nothing if NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID + * is unset or if `slot` is empty (so missing-slot env vars stay invisible). + */ +export function AdUnit({ + slot, + format = "auto", + layout, + layoutKey, + responsive = true, + className, + style, +}: AdUnitProps) { + const insRef = useRef(null); + + useEffect(() => { + if (!ADSENSE_CLIENT_ID || !slot) return; + const el = insRef.current; + if (!el) return; + // Guard against duplicate pushes (React StrictMode dev double-invoke, + // client-side navigation back to this page, or HMR remounts). AdSense + // sets data-adsbygoogle-status="done" once it has filled a slot. + if (el.getAttribute("data-adsbygoogle-status")) return; + try { + (window.adsbygoogle = window.adsbygoogle || []).push({}); + } catch { + // AdSense throws if pushed before the script has loaded or on + // duplicate pushes. The script processes pending pushes when it + // finishes loading, so we can safely swallow this. + } + }, [slot]); + + if (!ADSENSE_CLIENT_ID || !slot) return null; + + return ( + + ); +} diff --git a/surfsense_web/components/ads/adsense-config.ts b/surfsense_web/components/ads/adsense-config.ts new file mode 100644 index 000000000..f5d22908b --- /dev/null +++ b/surfsense_web/components/ads/adsense-config.ts @@ -0,0 +1,13 @@ +/** + * Centralized AdSense ad slot IDs. + * + * After creating ad units in your AdSense dashboard (Ads → By ad unit), paste + * the numeric slot IDs into the corresponding env vars below. Empty slot IDs + * render nothing (see ), so partial rollout is safe. + */ +export const ADSENSE_SLOTS = { + /** /free hub: between the model table and "Why SurfSense" section. */ + freeHubInContent: process.env.NEXT_PUBLIC_GOOGLE_ADSENSE_SLOT_FREE_HUB_IN_CONTENT ?? "", + /** /free hub: between the CTA and the FAQ section. */ + freeHubBeforeFaq: process.env.NEXT_PUBLIC_GOOGLE_ADSENSE_SLOT_FREE_HUB_BEFORE_FAQ ?? "", +} as const; diff --git a/surfsense_web/components/ads/adsense-script.tsx b/surfsense_web/components/ads/adsense-script.tsx new file mode 100644 index 000000000..e2636b333 --- /dev/null +++ b/surfsense_web/components/ads/adsense-script.tsx @@ -0,0 +1,27 @@ +"use client"; + +import Script from "next/script"; + +const ADSENSE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID; + +/** + * Loads the Google AdSense library (adsbygoogle.js). Mount this once on any + * route that renders instances. Scoped per-route (not in the root + * layout) so the third-party script is not shipped on unrelated pages. + * + * Renders nothing if NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID is unset, so dev and + * preview deployments without the env var stay ad-free. + */ +export function AdSenseScript() { + if (!ADSENSE_CLIENT_ID) return null; + + return ( +