diff --git a/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py b/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py index 45fdddb9d..a683b1c17 100644 --- a/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py +++ b/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py @@ -262,7 +262,11 @@ def _normalize_connectors( valid_set -= _LIVE_SEARCH_CONNECTORS if not connectors_to_search: - base = list(available_connectors) if available_connectors else list(_ALL_CONNECTORS) + base = ( + list(available_connectors) + if available_connectors + else list(_ALL_CONNECTORS) + ) return [c for c in base if c not in _LIVE_SEARCH_CONNECTORS] normalized: list[str] = [] @@ -291,7 +295,11 @@ def _normalize_connectors( # Fallback to all available if nothing matched if not out: - base = list(available_connectors) if available_connectors else list(_ALL_CONNECTORS) + base = ( + list(available_connectors) + if available_connectors + else list(_ALL_CONNECTORS) + ) return [c for c in base if c not in _LIVE_SEARCH_CONNECTORS] return out diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index 541dcc34d..6f2e36b08 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -68,12 +68,12 @@ from .podcast import create_generate_podcast_tool from .report import create_generate_report_tool from .scrape_webpage import create_scrape_webpage_tool from .search_surfsense_docs import create_search_surfsense_docs_tool -from .web_search import create_web_search_tool from .shared_memory import ( create_recall_shared_memory_tool, create_save_shared_memory_tool, ) from .user_memory import create_recall_memory_tool, create_save_memory_tool +from .web_search import create_web_search_tool # ============================================================================= # Tool Definition diff --git a/surfsense_backend/app/agents/new_chat/tools/web_search.py b/surfsense_backend/app/agents/new_chat/tools/web_search.py index 987fb9c80..c67db541c 100644 --- a/surfsense_backend/app/agents/new_chat/tools/web_search.py +++ b/surfsense_backend/app/agents/new_chat/tools/web_search.py @@ -72,20 +72,22 @@ def _format_web_results( continue metadata_json = json.dumps(metadata, ensure_ascii=False) - doc_xml = "\n".join([ - "", - "", - f" {source}", - f" <![CDATA[{title}]]>", - f" ", - f" ", - "", - "", - f" ", - "", - "", - "", - ]) + doc_xml = "\n".join( + [ + "", + "", + f" {source}", + f" <![CDATA[{title}]]>", + f" ", + f" ", + "", + "", + f" ", + "", + "", + "", + ] + ) if total_chars + len(doc_xml) > max_chars: parts.append("") @@ -152,9 +154,7 @@ def create_web_search_tool( ] engine_names = ["SearXNG (platform default)"] - engine_names.extend( - _CONNECTOR_LABELS.get(c, c) for c in active_live_connectors - ) + engine_names.extend(_CONNECTOR_LABELS.get(c, c) for c in active_live_connectors) engines_summary = ", ".join(engine_names) description = ( @@ -179,10 +179,12 @@ def create_web_search_tool( tasks: list[asyncio.Task[list[dict[str, Any]]]] = [] if web_search_service.is_available(): + async def _searxng() -> list[dict[str, Any]]: async with semaphore: _result_obj, docs = await web_search_service.search( - query=query, top_k=clamped_top_k, + query=query, + top_k=clamped_top_k, ) return docs diff --git a/surfsense_backend/app/retriever/chunks_hybrid_search.py b/surfsense_backend/app/retriever/chunks_hybrid_search.py index 2a0bc1a4a..d8b009655 100644 --- a/surfsense_backend/app/retriever/chunks_hybrid_search.py +++ b/surfsense_backend/app/retriever/chunks_hybrid_search.py @@ -428,4 +428,4 @@ class ChucksHybridSearchRetriever: search_space_id, document_type, ) - return final_docs \ No newline at end of file + return final_docs diff --git a/surfsense_backend/app/services/connector_service.py b/surfsense_backend/app/services/connector_service.py index cb12ffd70..1dd2ac0c2 100644 --- a/surfsense_backend/app/services/connector_service.py +++ b/surfsense_backend/app/services/connector_service.py @@ -2,7 +2,6 @@ import asyncio import time from datetime import datetime from typing import Any -from urllib.parse import urljoin import httpx from linkup import LinkupClient diff --git a/surfsense_backend/app/services/llm_service.py b/surfsense_backend/app/services/llm_service.py index e11abd886..59f52a4eb 100644 --- a/surfsense_backend/app/services/llm_service.py +++ b/surfsense_backend/app/services/llm_service.py @@ -278,7 +278,7 @@ async def get_search_space_llm_instance( "ALIBABA_QWEN": "openai", "MOONSHOT": "openai", "ZHIPU": "openai", - "MINIMAX": "openai", + "MINIMAX": "openai", } provider_prefix = provider_map.get( global_config["provider"], global_config["provider"].lower() diff --git a/surfsense_backend/app/services/web_search_service.py b/surfsense_backend/app/services/web_search_service.py index 6cf0f4566..a5c776323 100644 --- a/surfsense_backend/app/services/web_search_service.py +++ b/surfsense_backend/app/services/web_search_service.py @@ -8,6 +8,7 @@ latency overhead. from __future__ import annotations +import contextlib import hashlib import json import logging @@ -112,10 +113,8 @@ def _cache_get(key: str) -> dict | None: def _cache_set(key: str, value: dict) -> None: - try: + with contextlib.suppress(redis.RedisError): _get_redis().setex(key, _CACHE_TTL_SECONDS, json.dumps(value)) - except redis.RedisError: - pass # --------------------------------------------------------------------------- @@ -208,7 +207,9 @@ async def search( try: async with httpx.AsyncClient(timeout=15.0, verify=False) as client: response = await client.get( - searx_endpoint, params=params, headers=headers, + searx_endpoint, + params=params, + headers=headers, ) response.raise_for_status() data = response.json() @@ -217,7 +218,10 @@ async def search( last_error = exc if attempt == 0 and ( isinstance(exc, httpx.TimeoutException) - or (isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code >= 500) + or ( + isinstance(exc, httpx.HTTPStatusError) + and exc.response.status_code >= 500 + ) ): continue break @@ -246,29 +250,33 @@ async def search( source_id = 200_000 + idx description = result.get("content") or result.get("snippet") or "" - sources_list.append({ - "id": source_id, - "title": result.get("title", "Web Search Result"), - "description": description, - "url": result.get("url", ""), - }) - - documents.append({ - "chunk_id": source_id, - "content": description or result.get("content", ""), - "score": result.get("score", 0.0), - "document": { + sources_list.append( + { "id": source_id, "title": result.get("title", "Web Search Result"), - "document_type": "SEARXNG_API", - "metadata": { - "url": result.get("url", ""), - "engines": result.get("engines", []), - "category": result.get("category"), - "source": "SEARXNG_API", + "description": description, + "url": result.get("url", ""), + } + ) + + documents.append( + { + "chunk_id": source_id, + "content": description or result.get("content", ""), + "score": result.get("score", 0.0), + "document": { + "id": source_id, + "title": result.get("title", "Web Search Result"), + "document_type": "SEARXNG_API", + "metadata": { + "url": result.get("url", ""), + "engines": result.get("engines", []), + "category": result.get("category"), + "source": "SEARXNG_API", + }, }, - }, - }) + } + ) result_object: dict[str, Any] = { "id": 11, diff --git a/surfsense_backend/app/tasks/connector_indexers/base.py b/surfsense_backend/app/tasks/connector_indexers/base.py index b6ce2f2f9..ffc8ab72e 100644 --- a/surfsense_backend/app/tasks/connector_indexers/base.py +++ b/surfsense_backend/app/tasks/connector_indexers/base.py @@ -51,9 +51,7 @@ async def safe_set_chunks( from app.db import Chunk if document.id is not None: - await session.execute( - delete(Chunk).where(Chunk.document_id == document.id) - ) + await session.execute(delete(Chunk).where(Chunk.document_id == document.id)) for chunk in chunks: chunk.document_id = document.id diff --git a/surfsense_backend/app/tasks/document_processors/base.py b/surfsense_backend/app/tasks/document_processors/base.py index 580126d48..fc538d7cf 100644 --- a/surfsense_backend/app/tasks/document_processors/base.py +++ b/surfsense_backend/app/tasks/document_processors/base.py @@ -37,9 +37,7 @@ async def safe_set_chunks( from app.db import Chunk if document.id is not None: - await session.execute( - delete(Chunk).where(Chunk.document_id == document.id) - ) + await session.execute(delete(Chunk).where(Chunk.document_id == document.id)) for chunk in chunks: chunk.document_id = document.id diff --git a/surfsense_backend/app/tasks/surfsense_docs_indexer.py b/surfsense_backend/app/tasks/surfsense_docs_indexer.py index 7d449c0ab..db88c8700 100644 --- a/surfsense_backend/app/tasks/surfsense_docs_indexer.py +++ b/surfsense_backend/app/tasks/surfsense_docs_indexer.py @@ -9,11 +9,9 @@ import re from datetime import UTC, datetime from pathlib import Path -from sqlalchemy import select +from sqlalchemy import delete as sa_delete, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload - -from sqlalchemy import delete as sa_delete from sqlalchemy.orm.attributes import set_committed_value from app.config import config @@ -39,6 +37,7 @@ async def _safe_set_docs_chunks( set_committed_value(document, "chunks", chunks) session.add_all(chunks) + # Path to docs relative to project root DOCS_DIR = ( Path(__file__).resolve().parent.parent.parent.parent diff --git a/surfsense_web/app/dashboard/[search_space_id]/onboard/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/onboard/page.tsx index ad523027f..b188d7c8f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/onboard/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/onboard/page.tsx @@ -261,9 +261,7 @@ export default function OnboardPage() { You can add more configurations and customize settings anytime in{" "} + - + diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx index 38d3c325c..223a6e3e7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx @@ -27,58 +27,24 @@ export function ApiKeyContent() { return (
- - - {t("api_key_warning_title")} - - {t("api_key_warning_description")} - - + + + {t("api_key_warning_title")} + + {t("api_key_warning_description")} + + -
+

{t("your_api_key")}

- {isLoading ? ( -
- ) : apiKey ? ( -
-
-

- {apiKey} -

-
- - - - - - {copied ? t("copied") : t("copy")} - - -
- ) : ( -

{t("no_api_key")}

- )} -
- -
-

{t("usage_title")}

-

{t("usage_description")}

+ {isLoading ? ( +
+ ) : apiKey ? (
-
-								Authorization: Bearer {apiKey || "YOUR_API_KEY"}
-							
+

+ {apiKey} +

@@ -86,21 +52,55 @@ export function ApiKeyContent() { - {copiedUsage ? t("copied") : t("copy")} + {copied ? t("copied") : t("copy")}
+ ) : ( +

{t("no_api_key")}

+ )} +
+ +
+

{t("usage_title")}

+

{t("usage_description")}

+
+
+
+							Authorization: Bearer {apiKey || "YOUR_API_KEY"}
+						
+
+ + + + + + {copiedUsage ? t("copied") : t("copy")} + +
+
); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx index 0711add2d..3a3bc65fb 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx @@ -72,42 +72,42 @@ export function ProfileContent() { return (
- {isUserLoading ? ( -
- -
- ) : ( -
-
-
-
- - -
+ {isUserLoading ? ( +
+ +
+ ) : ( + +
+
+
+ + +
-
- - setDisplayName(e.target.value)} - /> -

{t("profile_display_name_hint")}

-
+
+ + setDisplayName(e.target.value)} + /> +

{t("profile_display_name_hint")}

+
-
- - -
+
+ +
+
-
+
- - )} -
+ + )} +
); } diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 6c42a3ffc..4f4bf5cea 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -419,19 +419,19 @@ export const ConnectorIndicator = forwardRef + size="sm" + variant="outline" + onClick={() => { + handleOpenChange(false); + setSearchSpaceSettingsDialog({ + open: true, + initialTab: "models", + }); + }} + > + + Go to Settings + )} diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx index 15c29c6d0..c34890dff 100644 --- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx +++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx @@ -159,19 +159,19 @@ const DocumentUploadPopupContent: FC<{ : "You need to configure a Document Summary LLM before uploading files. This LLM is used to process and summarize your uploaded documents."}

+ size="sm" + variant="outline" + onClick={() => { + onOpenChange(false); + setSearchSpaceSettingsDialog({ + open: true, + initialTab: "models", + }); + }} + > + + Go to Settings + ) : ( diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 5e4b1bb46..cb28d668c 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -233,7 +233,9 @@ const ThreadWelcome: FC = () => {
{/* Greeting positioned above the composer */}
-

{greeting}

+

+ {greeting} +

{/* Composer - top edge fixed, expands downward only */}
@@ -283,7 +285,11 @@ const ConnectToolsBanner: FC = () => { Connect your tools {BANNER_CONNECTORS.map(({ type }, i) => ( - + {getConnectorIcon(type, "size-3.5")} @@ -601,7 +607,10 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false ); const filteredEnabledCount = useMemo(() => { if (!filteredTools) return 0; - return filteredTools.length - disabledTools.filter((d) => filteredTools.some((t) => t.name === d)).length; + return ( + filteredTools.length - + disabledTools.filter((d) => filteredTools.some((t) => t.name === d)).length + ); }, [filteredTools, disabledTools]); useEffect(() => { @@ -775,8 +784,15 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false )} > diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index cb3b6c41f..3cdbe5b95 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -286,9 +286,7 @@ export function LayoutShell({ ); } diff --git a/surfsense_web/components/settings/roles-manager.tsx b/surfsense_web/components/settings/roles-manager.tsx index 36d31d4c8..23b9aa4b6 100644 --- a/surfsense_web/components/settings/roles-manager.tsx +++ b/surfsense_web/components/settings/roles-manager.tsx @@ -682,11 +682,11 @@ function PermissionsEditor({ return (
- - +
-
- +
+ - )} + {onCancel && ( + + )}