try: ip fix for cludflare

- Introduced AI File Sorting functionality to automatically organize documents into a smart folder hierarchy based on source, date, and topic.
- Updated README.md to include the new feature.
- Enhanced homepage components with new illustrations and descriptions for AI File Sorting.
- Refactored rate limiting logic to extract real client IPs more accurately.
This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-04-16 02:13:52 -07:00
parent 99995c67b2
commit 2cb30c604d
6 changed files with 143 additions and 12 deletions

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. - **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. - **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. - **Real-Time Multiplayer Support** - Work easily with your team members in a shared notebook.
- **AI File Sorting** - Automatically organize your documents into a smart folder hierarchy using AI-powered categorization by source, date, and topic.
- **Desktop App** - Get AI assistance in any application with Quick Assist, General Assist, Extreme Assist, and local folder sync. - **Desktop App** - Get AI assistance in any application with Quick Assist, General Assist, Extreme Assist, and local folder sync.
...and more to come. ...and more to come.
@ -199,6 +200,7 @@ All features operate against your chosen search space, so your answers are alway
| **Video Generation** | Cinematic Video Overviews via Veo 3 (Ultra only) | Available (NotebookLM is better here, actively improving) | | **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 | | **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) | | **Podcast Generation** | Audio Overviews with customizable hosts and languages | Available with multiple TTS providers (NotebookLM is better here, actively improving) |
| **AI File Sorting** | No | LLM-powered auto-categorization into source, date, category, and subcategory folders |
| **Desktop App** | No | Native app with General Assist, Quick Assist, Extreme Assist, and local folder sync | | **Desktop App** | No | Native app with General Assist, Quick Assist, Extreme Assist, and local folder sync |
| **Browser Extension** | No | Cross-browser extension to save any webpage, including auth-protected pages | | **Browser Extension** | No | Cross-browser extension to save any webpage, including auth-protected pages |

View file

@ -15,7 +15,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from slowapi.errors import RateLimitExceeded from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware from slowapi.middleware import SlowAPIMiddleware
from slowapi.util import get_remote_address from slowapi.util import get_remote_address # noqa: F401 — kept for reference
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request as StarletteRequest from starlette.requests import Request as StarletteRequest
@ -35,7 +35,7 @@ from app.config import (
) )
from app.db import User, create_db_and_tables, get_async_session from app.db import User, create_db_and_tables, get_async_session
from app.exceptions import GENERIC_5XX_MESSAGE, ISSUES_URL, SurfSenseError from app.exceptions import GENERIC_5XX_MESSAGE, ISSUES_URL, SurfSenseError
from app.rate_limiter import limiter from app.rate_limiter import get_real_client_ip, limiter
from app.routes import router as crud_router from app.routes import router as crud_router
from app.routes.auth_routes import router as auth_router from app.routes.auth_routes import router as auth_router
from app.schemas import UserCreate, UserRead, UserUpdate from app.schemas import UserCreate, UserRead, UserUpdate
@ -290,7 +290,7 @@ def _check_rate_limit(
Uses atomic INCR + EXPIRE to avoid race conditions. Uses atomic INCR + EXPIRE to avoid race conditions.
Falls back to in-memory sliding window if Redis is unavailable. Falls back to in-memory sliding window if Redis is unavailable.
""" """
client_ip = get_remote_address(request) client_ip = get_real_client_ip(request)
key = f"surfsense:auth_rate_limit:{scope}:{client_ip}" key = f"surfsense:auth_rate_limit:{scope}:{client_ip}"
try: try:

View file

@ -1,13 +1,33 @@
"""Shared SlowAPI limiter instance used by app.py and route modules.""" """Shared SlowAPI limiter instance used by app.py and route modules."""
from __future__ import annotations
from limits.storage import MemoryStorage from limits.storage import MemoryStorage
from slowapi import Limiter from slowapi import Limiter
from slowapi.util import get_remote_address from starlette.requests import Request
from app.config import config from app.config import config
def get_real_client_ip(request: Request) -> str:
"""Extract the real client IP behind Cloudflare / reverse proxies.
Priority: CF-Connecting-IP > X-Real-IP > X-Forwarded-For (first entry) > socket peer.
"""
cf_ip = request.headers.get("cf-connecting-ip")
if cf_ip:
return cf_ip.strip()
real_ip = request.headers.get("x-real-ip")
if real_ip:
return real_ip.strip()
forwarded = request.headers.get("x-forwarded-for")
if forwarded:
return forwarded.split(",")[0].strip()
return request.client.host if request.client else "127.0.0.1"
limiter = Limiter( limiter = Limiter(
key_func=get_remote_address, key_func=get_real_client_ip,
storage_uri=config.REDIS_APP_URL, storage_uri=config.REDIS_APP_URL,
default_limits=["1024/minute"], default_limits=["1024/minute"],
in_memory_fallback_enabled=True, in_memory_fallback_enabled=True,

View file

@ -51,12 +51,17 @@ def _get_or_create_session_id(request: Request, response: Response) -> str:
def _get_client_ip(request: Request) -> str: def _get_client_ip(request: Request) -> str:
"""Extract the real client IP, preferring Cloudflare's header."""
cf_ip = request.headers.get("cf-connecting-ip")
if cf_ip:
return cf_ip.strip()
real_ip = request.headers.get("x-real-ip")
if real_ip:
return real_ip.strip()
forwarded = request.headers.get("x-forwarded-for") forwarded = request.headers.get("x-forwarded-for")
return ( if forwarded:
forwarded.split(",")[0].strip() return forwarded.split(",")[0].strip()
if forwarded return request.client.host if request.client else "unknown"
else (request.client.host if request.client else "unknown")
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -1,4 +1,10 @@
import { IconMessage, IconMicrophone, IconSearch, IconUsers } from "@tabler/icons-react"; import {
IconBinaryTree,
IconMessage,
IconMicrophone,
IconSearch,
IconUsers,
} from "@tabler/icons-react";
import Image from "next/image"; import Image from "next/image";
import React from "react"; import React from "react";
import { BentoGrid, BentoGridItem } from "@/components/ui/bento-grid"; import { BentoGrid, BentoGridItem } from "@/components/ui/bento-grid";
@ -414,6 +420,91 @@ const AudioCommentIllustration = () => (
</div> </div>
); );
const AiSortIllustration = () => (
<div className="relative flex w-full h-full min-h-[6rem] items-center justify-center overflow-hidden rounded-xl bg-gradient-to-br from-emerald-50 via-teal-50 to-cyan-50 dark:from-emerald-950/20 dark:via-teal-950/20 dark:to-cyan-950/20 p-4">
<svg viewBox="0 0 400 200" className="w-full h-full" xmlns="http://www.w3.org/2000/svg">
<title>AI File Sorting illustration showing automatic folder organization</title>
{/* Scattered documents on the left */}
<g opacity="0.5">
<rect x="20" y="40" width="35" height="45" rx="4" className="fill-neutral-200 dark:fill-neutral-700" transform="rotate(-8 37 62)" />
<rect x="50" y="80" width="35" height="45" rx="4" className="fill-neutral-200 dark:fill-neutral-700" transform="rotate(5 67 102)" />
<rect x="15" y="110" width="35" height="45" rx="4" className="fill-neutral-200 dark:fill-neutral-700" transform="rotate(-3 32 132)" />
</g>
{/* AI sparkle / magic in the center */}
<g transform="translate(140, 90)">
<path d="M 0,-18 L 4,-6 L 16,-4 L 6,4 L 8,16 L 0,10 L -8,16 L -6,4 L -16,-4 L -4,-6 Z" className="fill-emerald-500 dark:fill-emerald-400" opacity="0.85">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="10s" repeatCount="indefinite" />
</path>
<circle cx="0" cy="0" r="3" className="fill-white dark:fill-emerald-200">
<animate attributeName="opacity" values="0.5;1;0.5" dur="2s" repeatCount="indefinite" />
</circle>
</g>
{/* Animated sorting arrows */}
<g className="stroke-emerald-500 dark:stroke-emerald-400" strokeWidth="2" fill="none" opacity="0.6">
<path d="M 100 70 Q 140 60, 180 50" strokeDasharray="4,4">
<animate attributeName="stroke-dashoffset" from="8" to="0" dur="1s" repeatCount="indefinite" />
</path>
<path d="M 100 100 Q 140 100, 180 100" strokeDasharray="4,4">
<animate attributeName="stroke-dashoffset" from="8" to="0" dur="1s" repeatCount="indefinite" />
</path>
<path d="M 100 130 Q 140 140, 180 150" strokeDasharray="4,4">
<animate attributeName="stroke-dashoffset" from="8" to="0" dur="1s" repeatCount="indefinite" />
</path>
</g>
{/* Organized folder tree on the right */}
{/* Root folder */}
<g>
<rect x="220" y="30" width="160" height="28" rx="6" className="fill-white dark:fill-neutral-800" opacity="0.9" />
<rect x="228" y="36" width="16" height="14" rx="3" className="fill-emerald-500 dark:fill-emerald-400" />
<line x1="252" y1="43" x2="330" y2="43" className="stroke-neutral-400 dark:stroke-neutral-500" strokeWidth="2.5" strokeLinecap="round" />
</g>
{/* Subfolder 1 */}
<g>
<line x1="240" y1="58" x2="240" y2="76" className="stroke-neutral-300 dark:stroke-neutral-600" strokeWidth="1.5" />
<line x1="240" y1="76" x2="250" y2="76" className="stroke-neutral-300 dark:stroke-neutral-600" strokeWidth="1.5" />
<rect x="250" y="64" width="130" height="24" rx="5" className="fill-white dark:fill-neutral-800" opacity="0.85" />
<rect x="257" y="70" width="12" height="11" rx="2" className="fill-teal-400 dark:fill-teal-500" />
<line x1="276" y1="76" x2="340" y2="76" className="stroke-neutral-400 dark:stroke-neutral-500" strokeWidth="2" strokeLinecap="round" />
</g>
{/* Subfolder 2 */}
<g>
<line x1="240" y1="76" x2="240" y2="108" className="stroke-neutral-300 dark:stroke-neutral-600" strokeWidth="1.5" />
<line x1="240" y1="108" x2="250" y2="108" className="stroke-neutral-300 dark:stroke-neutral-600" strokeWidth="1.5" />
<rect x="250" y="96" width="130" height="24" rx="5" className="fill-white dark:fill-neutral-800" opacity="0.85" />
<rect x="257" y="102" width="12" height="11" rx="2" className="fill-cyan-400 dark:fill-cyan-500" />
<line x1="276" y1="108" x2="350" y2="108" className="stroke-neutral-400 dark:stroke-neutral-500" strokeWidth="2" strokeLinecap="round" />
</g>
{/* Subfolder 3 */}
<g>
<line x1="240" y1="108" x2="240" y2="140" className="stroke-neutral-300 dark:stroke-neutral-600" strokeWidth="1.5" />
<line x1="240" y1="140" x2="250" y2="140" className="stroke-neutral-300 dark:stroke-neutral-600" strokeWidth="1.5" />
<rect x="250" y="128" width="130" height="24" rx="5" className="fill-white dark:fill-neutral-800" opacity="0.85" />
<rect x="257" y="134" width="12" height="11" rx="2" className="fill-emerald-400 dark:fill-emerald-500" />
<line x1="276" y1="140" x2="325" y2="140" className="stroke-neutral-400 dark:stroke-neutral-500" strokeWidth="2" strokeLinecap="round" />
</g>
{/* Sparkle accents */}
<g className="opacity-60">
<circle cx="170" cy="45" r="2" className="fill-emerald-400">
<animate attributeName="opacity" values="0;1;0" dur="2s" repeatCount="indefinite" />
</circle>
<circle cx="190" cy="155" r="1.5" className="fill-teal-400">
<animate attributeName="opacity" values="0;1;0" dur="2.5s" begin="0.8s" repeatCount="indefinite" />
</circle>
<circle cx="155" cy="120" r="1.5" className="fill-cyan-400">
<animate attributeName="opacity" values="0;1;0" dur="3s" begin="0.4s" repeatCount="indefinite" />
</circle>
</g>
</svg>
</div>
);
const items = [ const items = [
{ {
title: "Find, Ask, Act", title: "Find, Ask, Act",
@ -431,6 +522,14 @@ const items = [
className: "md:col-span-1", className: "md:col-span-1",
icon: <IconUsers className="h-4 w-4 text-neutral-500" />, icon: <IconUsers className="h-4 w-4 text-neutral-500" />,
}, },
{
title: "AI File Sorting",
description:
"Automatically organize documents into a smart folder hierarchy using AI-powered categorization by source, date, and topic.",
header: <AiSortIllustration />,
className: "md:col-span-1",
icon: <IconBinaryTree className="h-4 w-4 text-neutral-500" />,
},
{ {
title: "Collaborate Beyond Text", title: "Collaborate Beyond Text",
description: description:
@ -443,7 +542,7 @@ const items = [
title: "Context Where It Counts", title: "Context Where It Counts",
description: "Add comments directly to your chats and docs for clear, in-the-moment feedback.", description: "Add comments directly to your chats and docs for clear, in-the-moment feedback.",
header: <AnnotationIllustration />, header: <AnnotationIllustration />,
className: "md:col-span-2", className: "md:col-span-1",
icon: <IconMessage className="h-4 w-4 text-neutral-500" />, icon: <IconMessage className="h-4 w-4 text-neutral-500" />,
}, },
]; ];

View file

@ -348,6 +348,11 @@ const comparisonRows: {
notebookLm: false, notebookLm: false,
surfSense: true, surfSense: true,
}, },
{
feature: "AI File Sorting",
notebookLm: false,
surfSense: true,
},
]; ];
function ComparisonStrip() { function ComparisonStrip() {