"""Embedded browser UI preferences API.""" from __future__ import annotations from fastapi import APIRouter, Depends from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from server.api.auth_jwt import get_current_admin from server.api.dependencies import get_db from server.domain.models import Admin from server.infrastructure.database.admin_ui_preference_repo import AdminUiPreferenceRepositoryImpl from server.utils.browser_ui_state import ( BROWSER_UI_CONTEXT, merge_browser_state, parse_browser_state, record_visit, ) router = APIRouter(prefix="/api/browser", tags=["browser"]) class BrowserVisitRecord(BaseModel): url: str title: str | None = None class BrowserTabState(BaseModel): id: str url: str title: str | None = None stack: list[str] | None = None stack_index: int | None = None class BrowserPanelRect(BaseModel): x: float y: float w: float h: float class BrowserStateUpdate(BaseModel): url_history: list[str] | None = None visits: list[dict] | None = None tabs: list[BrowserTabState] | None = None active_tab_id: str | None = None minimized: bool | None = None rect: BrowserPanelRect | None = None minimized_rect: BrowserPanelRect | None = None record_url: str | None = Field(default=None, description="Append one visit + history entry") record_title: str | None = None @router.get("/state") async def get_browser_state( admin: Admin = Depends(get_current_admin), db: AsyncSession = Depends(get_db), ): repo = AdminUiPreferenceRepositoryImpl(db) raw = await repo.get(admin.id, BROWSER_UI_CONTEXT) return parse_browser_state(raw) @router.put("/state") async def put_browser_state( payload: BrowserStateUpdate, admin: Admin = Depends(get_current_admin), db: AsyncSession = Depends(get_db), ): repo = AdminUiPreferenceRepositoryImpl(db) current = parse_browser_state(await repo.get(admin.id, BROWSER_UI_CONTEXT)) incoming = payload.model_dump(exclude_none=True) if payload.tabs is not None: incoming["tabs"] = [t.model_dump(exclude_none=True) for t in payload.tabs] if payload.rect is not None: incoming["rect"] = payload.rect.model_dump() if payload.minimized_rect is not None: incoming["minimized_rect"] = payload.minimized_rect.model_dump() merged = merge_browser_state(current, incoming) if payload.record_url: merged = record_visit(merged, payload.record_url, payload.record_title) await repo.upsert(admin.id, BROWSER_UI_CONTEXT, merged) return merged