mirror of
https://github.com/lfnovo/open-notebook.git
synced 2026-08-19 05:53:51 +00:00
* feat(sources): add Crawl4AI URL engine and honor persisted engine settings (#432) - Add "crawl4ai" as a selectable URL processing engine (domain Literal, settings API validation, SettingsForm select, and label across all 14 locales; urlHelp updated in en-US to describe the new fallback chain). - The source graph now loads the persisted ContentSettings and passes the document/URL engine choices to ContentCoreConfig. Previously it built a hard-coded ContentSettings with "auto" engines, so a user's selection in Settings never took effect. Falls back to defaults if settings can't load. - Crawl4AI Docker mode is driven by content-core's native CRAWL4AI_API_URL env var (documented separately under #1105). Part of #939. * fix(432): bundle Crawl4AI runtime + address review - Bundle the Crawl4AI runtime so its local, no-API-key mode works out of the box: depend on content-core[crawl4ai] and install the Chromium browser via playwright in the Docker runtime-base (both image variants). Footprint is modest (no torch/transformers/CUDA); image grows ~300 MB from Chromium + system libs. - Preserve the server-side traceback when persisted content settings fail to load (logger.opt(exception=True)) instead of only the message. - Reset the ContentSettings singleton between domain tests (clear_instance) so a non-default value can't leak into neighboring tests. Addresses review on #432. * i18n(432): translate urlHelp Crawl4AI description across all 13 non-en locales The Crawl4AI engine label was already localized; this brings the URL-engine 'help me choose' text in line with en-US in every locale — describing Crawl4AI (local JS rendering, no API key) and its place in the auto fallback chain (Firecrawl -> Jina -> Crawl4AI -> simple).
97 lines
3.8 KiB
Python
97 lines
3.8 KiB
Python
from fastapi import APIRouter, HTTPException
|
|
from loguru import logger
|
|
|
|
from api.models import SettingsResponse, SettingsUpdate
|
|
from open_notebook.domain.content_settings import ContentSettings
|
|
from open_notebook.exceptions import (
|
|
InvalidInputError,
|
|
OpenNotebookError,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/settings", response_model=SettingsResponse)
|
|
async def get_settings():
|
|
"""Get all application settings."""
|
|
try:
|
|
settings: ContentSettings = await ContentSettings.get_instance() # type: ignore[assignment]
|
|
|
|
return SettingsResponse(
|
|
default_content_processing_engine_doc=settings.default_content_processing_engine_doc,
|
|
default_content_processing_engine_url=settings.default_content_processing_engine_url,
|
|
default_embedding_option=settings.default_embedding_option,
|
|
auto_delete_files=settings.auto_delete_files,
|
|
youtube_preferred_languages=settings.youtube_preferred_languages,
|
|
)
|
|
except HTTPException:
|
|
raise
|
|
except OpenNotebookError:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error fetching settings: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500, detail="Error fetching settings"
|
|
)
|
|
|
|
|
|
@router.put("/settings", response_model=SettingsResponse)
|
|
async def update_settings(settings_update: SettingsUpdate):
|
|
"""Update application settings."""
|
|
try:
|
|
settings: ContentSettings = await ContentSettings.get_instance() # type: ignore[assignment]
|
|
|
|
# Update only provided fields
|
|
if settings_update.default_content_processing_engine_doc is not None:
|
|
# Cast to proper literal type
|
|
from typing import Literal, cast
|
|
|
|
settings.default_content_processing_engine_doc = cast(
|
|
Literal["auto", "docling", "simple"],
|
|
settings_update.default_content_processing_engine_doc,
|
|
)
|
|
if settings_update.default_content_processing_engine_url is not None:
|
|
from typing import Literal, cast
|
|
|
|
settings.default_content_processing_engine_url = cast(
|
|
Literal["auto", "firecrawl", "jina", "crawl4ai", "simple"],
|
|
settings_update.default_content_processing_engine_url,
|
|
)
|
|
if settings_update.default_embedding_option is not None:
|
|
from typing import Literal, cast
|
|
|
|
settings.default_embedding_option = cast(
|
|
Literal["ask", "always", "never"],
|
|
settings_update.default_embedding_option,
|
|
)
|
|
if settings_update.auto_delete_files is not None:
|
|
from typing import Literal, cast
|
|
|
|
settings.auto_delete_files = cast(
|
|
Literal["yes", "no"], settings_update.auto_delete_files
|
|
)
|
|
if settings_update.youtube_preferred_languages is not None:
|
|
settings.youtube_preferred_languages = (
|
|
settings_update.youtube_preferred_languages
|
|
)
|
|
|
|
await settings.update()
|
|
|
|
return SettingsResponse(
|
|
default_content_processing_engine_doc=settings.default_content_processing_engine_doc,
|
|
default_content_processing_engine_url=settings.default_content_processing_engine_url,
|
|
default_embedding_option=settings.default_embedding_option,
|
|
auto_delete_files=settings.auto_delete_files,
|
|
youtube_preferred_languages=settings.youtube_preferred_languages,
|
|
)
|
|
except HTTPException:
|
|
raise
|
|
except InvalidInputError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except OpenNotebookError:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error updating settings: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500, detail="Error updating settings"
|
|
)
|