Add app_settings migration, settings UI, and OpenAI-compatible httpx LLM client with mocked tests. Preserve API keys on blank form submissions, require a fresh key when base_url changes, and keep AI search settings untouched for step 3. Update docs/design LLM integration and step 3 AI search notes, including prompt contract and extra-hints planning.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""Settings read/write helpers for the ``app_settings`` KV table.
|
||||
|
||||
Provides a typed ``LLMConfig`` dataclass and two helpers:
|
||||
|
||||
- ``get_app_settings(db) -> LLMConfig`` — reads KV rows (or returns defaults).
|
||||
- ``save_app_settings(db, ...) -> None`` — writes KV rows; API key left blank
|
||||
means "keep the old value".
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import AppSetting
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMConfig:
|
||||
"""All settings consumed by the LLM client and settings UI."""
|
||||
|
||||
enabled: bool = False
|
||||
base_url: str = "https://api.openai.com/v1"
|
||||
model: str = ""
|
||||
api_key: str = ""
|
||||
ai_search_enabled: bool = False
|
||||
|
||||
|
||||
def _get_value(rows: dict[str, str], key: str, default: str) -> str:
|
||||
return rows.get(key, default)
|
||||
|
||||
|
||||
def _get_bool(rows: dict[str, str], key: str, default: bool) -> bool:
|
||||
return rows.get(key, str(default).lower()) == "true"
|
||||
|
||||
|
||||
def get_app_settings(db: Session) -> LLMConfig:
|
||||
"""Read all settings from ``app_settings`` and return an ``LLMConfig``."""
|
||||
rows: dict[str, str] = {}
|
||||
for row in db.query(AppSetting).all():
|
||||
if row.value is not None:
|
||||
rows[row.key] = row.value
|
||||
|
||||
return LLMConfig(
|
||||
enabled=_get_bool(rows, "llm_enabled", False),
|
||||
base_url=_get_value(rows, "llm_base_url", "https://api.openai.com/v1"),
|
||||
model=_get_value(rows, "llm_model", ""),
|
||||
api_key=_get_value(rows, "llm_api_key", ""),
|
||||
ai_search_enabled=_get_bool(rows, "ai_search_enabled", False),
|
||||
)
|
||||
|
||||
|
||||
def save_app_settings(
|
||||
db: Session,
|
||||
*,
|
||||
enabled: bool | None = None,
|
||||
base_url: str | None = None,
|
||||
model: str | None = None,
|
||||
api_key: str | None = None,
|
||||
ai_search_enabled: bool | None = None,
|
||||
) -> None:
|
||||
"""Write settings to ``app_settings``.
|
||||
|
||||
If ``api_key`` is ``None`` (form field left blank), the existing key is
|
||||
preserved. All other fields are written as-is.
|
||||
"""
|
||||
updates: dict[str, str | None] = {}
|
||||
|
||||
if enabled is not None:
|
||||
updates["llm_enabled"] = str(enabled).lower()
|
||||
if base_url is not None:
|
||||
updates["llm_base_url"] = base_url
|
||||
if model is not None:
|
||||
updates["llm_model"] = model
|
||||
if api_key is not None:
|
||||
updates["llm_api_key"] = api_key
|
||||
if ai_search_enabled is not None:
|
||||
updates["ai_search_enabled"] = str(ai_search_enabled).lower()
|
||||
|
||||
for key, value in updates.items():
|
||||
existing = db.get(AppSetting, key)
|
||||
if existing is not None:
|
||||
existing.value = value
|
||||
else:
|
||||
db.add(AppSetting(key=key, value=value))
|
||||
|
||||
db.commit()
|
||||
Reference in New Issue
Block a user