"""Web search administration: which provider, and how to reach it.""" from __future__ import annotations import logging from fastapi import APIRouter, Form, Request, Response, status from fastapi.responses import RedirectResponse from lembas.api.deps import AdminUser, Db from lembas.services import search as search_service from lembas.services import settings_store from lembas.services.crypto import UNCHANGED_SENTINEL, decrypt, keep_or_replace, mask from lembas.services.search.base import SearchError from lembas.web.templating import render log = logging.getLogger(__name__) router = APIRouter(prefix="/admin/search", tags=["admin-search"]) SAFESEARCH = ("off", "moderate", "strict") @router.get("") async def search_page(request: Request, db: Db, user: AdminUser, saved: bool = False): values = settings_store.search(db) return render( request, "admin/search.html", { "values": values, "providers": search_service.PROVIDERS, # Keyed by provider so the form can show an install hint against # the one that needs it, without the template knowing why. "problems": { p.key: search_service.availability(p.key) for p in search_service.PROVIDERS }, "safesearch_options": SAFESEARCH, "masked": mask(decrypt(values.get("firecrawl_api_key_encrypted") or "")), "unchanged": UNCHANGED_SENTINEL, "saved": saved, }, ) @router.post("") async def save_search( db: Db, user: AdminUser, enabled: bool = Form(False), provider: str = Form("ddgs"), max_results: int = Form(5), region: str = Form("wt-wt"), safesearch: str = Form("moderate"), searxng_base_url: str = Form(""), firecrawl_base_url: str = Form(""), firecrawl_api_key: str = Form(""), timeout: float = Form(20.0), allow_private_fetch: bool = Form(False), ) -> Response: current = settings_store.search(db) known = {p.key for p in search_service.PROVIDERS} settings_store.update( db, { "enabled": enabled, "provider": provider if provider in known else "ddgs", # An upper bound on what any single search may put in the prompt. # Twenty results is already more than a model reads carefully. "max_results": min(max(max_results, 1), 20), "region": region.strip()[:16] or "wt-wt", "safesearch": safesearch if safesearch in SAFESEARCH else "moderate", "searxng_base_url": searxng_base_url.strip().rstrip("/"), "firecrawl_base_url": firecrawl_base_url.strip().rstrip("/") or "https://api.firecrawl.dev", "firecrawl_api_key_encrypted": keep_or_replace( firecrawl_api_key, current.get("firecrawl_api_key_encrypted") or "" ), "timeout": min(max(timeout, 5.0), 120.0), "allow_private_fetch": allow_private_fetch, }, key=settings_store.SEARCH, ) log.info("web search %s by %s", "enabled" if enabled else "disabled", user.email) return RedirectResponse("/admin/search?saved=1", status_code=status.HTTP_303_SEE_OTHER) @router.post("/test") async def test_search(request: Request, db: Db, user: AdminUser, query: str = Form("")): """Run one real search and show what came back. Against the stored settings rather than the unsaved form, so what is tested is what a chat would actually do. """ config = settings_store.search(db) query = query.strip() or "lembas" try: results = await search_service.run(config, query) message, kind = ( f"{search_service.provider(config.get('provider')).label} returned " f"{len(results)} result{'' if len(results) == 1 else 's'}." ), "success" except SearchError as exc: results, message, kind = [], exc.message, "error" return render( request, "admin/_search_result.html", {"results": results, "message": message, "message_kind": kind, "query": query}, )