"""Sentence completion for the compose window (app/completion.py).""" from __future__ import annotations import datetime as dt from typing import Annotated from fastapi import APIRouter, Depends from pydantic import BaseModel, Field from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app import completion from app.db import get_session from app.identity.deps import CurrentUser, require_csrf from app.models import CompletionIndexState from app.queue import enqueue_job router = APIRouter(prefix="/completion", tags=["compose"], dependencies=[Depends(require_csrf)]) DbSession = Annotated[AsyncSession, Depends(get_session)] class CompletionIn(BaseModel): # The text before the cursor (its last line is all that matters). A POST, # not a query string: what the user is typing stays out of access logs. before: str = Field(max_length=2000) class CompletionOut(BaseModel): suggestion: str | None @router.post("", response_model=CompletionOut) async def suggest(body: CompletionIn, user: CurrentUser, db: DbSession) -> CompletionOut: before = body.before[-300:] built = await db.scalar( select(CompletionIndexState.built_at).where(CompletionIndexState.user_id == user.id) ) if built is None or built < dt.datetime.now(dt.UTC) - completion.REBUILD_AFTER: # In the background; until it is built, the starter phrases still answer. await enqueue_job("build_index_job", str(user.id), _job_id=f"complete:{user.id}") personal = await completion.personal_phrases(db, user.id, completion.keys_for(before)) return CompletionOut(suggestion=completion.complete(personal, before))