79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
"""Per-user UI preferences (account-bound, follow the user across devices).
|
|
|
|
``hide_admin_prompt_prefix`` hides the admin-configured prompt-prefix switch.
|
|
``followup_suggestions_enabled`` opts in to recommended questions after answers.
|
|
Kept as a small key-value (JSON) store so new toggles do not each need a schema
|
|
change.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.gateway.api_read_cache import get_cache_version, get_cached_json, set_cached_json
|
|
from deerflow.runtime.user_context import get_effective_user_id
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/user-preferences", tags=["user-preferences"])
|
|
|
|
|
|
class UserPreferencesResponse(BaseModel):
|
|
hide_admin_prompt_prefix: bool = False
|
|
followup_suggestions_enabled: bool = False
|
|
|
|
|
|
class UserPreferencesUpdateRequest(BaseModel):
|
|
hide_admin_prompt_prefix: bool | None = Field(default=None)
|
|
followup_suggestions_enabled: bool | None = Field(default=None)
|
|
|
|
|
|
def _current_user_id(request: Request) -> str:
|
|
user = getattr(request.state, "user", None)
|
|
if user is not None:
|
|
return str(user.id)
|
|
return get_effective_user_id()
|
|
|
|
|
|
def _get_store(request: Request):
|
|
store = getattr(request.app.state, "user_preferences_store", None)
|
|
if store is None:
|
|
raise HTTPException(status_code=503, detail="User preferences store not available")
|
|
return store
|
|
|
|
|
|
def _to_response(prefs: dict) -> UserPreferencesResponse:
|
|
return UserPreferencesResponse(
|
|
hide_admin_prompt_prefix=bool(prefs.get("hide_admin_prompt_prefix", False)),
|
|
followup_suggestions_enabled=bool(prefs.get("followup_suggestions_enabled", False)),
|
|
)
|
|
|
|
|
|
@router.get("", response_model=UserPreferencesResponse)
|
|
async def get_user_preferences(request: Request) -> UserPreferencesResponse:
|
|
user_id = _current_user_id(request)
|
|
config = getattr(request.app.state, "config", None)
|
|
cache_key = f"user:{user_id}"
|
|
version = await get_cache_version(config, "user_preferences")
|
|
cached = await get_cached_json(config, "user_preferences", cache_key, version=version)
|
|
if cached is not None:
|
|
return UserPreferencesResponse.model_validate(cached)
|
|
store = _get_store(request)
|
|
prefs = await store.get_for_user(user_id)
|
|
response = _to_response(prefs)
|
|
await set_cached_json(config, "user_preferences", cache_key, response, expected_version=version)
|
|
return response
|
|
|
|
|
|
@router.put("", response_model=UserPreferencesResponse)
|
|
async def update_user_preferences(request: Request, body: UserPreferencesUpdateRequest) -> UserPreferencesResponse:
|
|
store = _get_store(request)
|
|
user_id = _current_user_id(request)
|
|
patch = body.model_dump(exclude_unset=True, exclude_none=True)
|
|
if not patch:
|
|
return _to_response(await store.get_for_user(user_id))
|
|
prefs = await store.merge_for_user(user_id, patch)
|
|
return _to_response(prefs)
|