e833b923c8
接入 Playwright Worker、会话 WebSocket 与全局浏览器面板(固定于 App Bar 下); 含验证码 stealth、设置项默认音与 URL 安全校验;附带 worker 部署与设计文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
"""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
|