- shared/ = portable Android-origin sources vendored from deferred/desktop-server (app/build.gradle.kts srcDir repointed; PlaybackController.kt excluded as Android-only) - backend/ = bundled-lite engine (SQLite + inline queue); .venv symlinked from the old checkout, PYTHONPATH pins THIS backend's code over any editable install - repoRoot() resolves this project dir (env SHONAR_REPO still wins); desktop-dev.sh watches shared/ + backend/ - Verified: :app:compileKotlin + :app:test green (23 tests); engine boots on :8010, self-migrates, /healthz ok
132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
"""Transcription model registry + global default (Stage 1).
|
|
|
|
- GET /models — supported models with display metadata, download and
|
|
availability status, and which is the default.
|
|
- GET /models/default — the current global default model name.
|
|
- PUT /models/default — change the global default (affects future
|
|
recordings only; saved per-recording models are never rewritten).
|
|
- POST /models/{name}/download — fetch a model into the local cache
|
|
(needs internet once; runs synchronously and may take minutes for
|
|
large models).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
|
|
from shonar.api.deps import CurrentUser, SessionDep
|
|
from shonar.services.ai import ProviderConfigError
|
|
from shonar.services.ai.model_registry import (
|
|
SUPPORTED_TRANSCRIPTION_MODELS,
|
|
get_global_default_model,
|
|
is_faster_whisper_installed,
|
|
is_model_downloaded,
|
|
set_global_default_model,
|
|
validate_model_name,
|
|
)
|
|
|
|
router = APIRouter(tags=["models"])
|
|
|
|
|
|
class TranscriptionModelOut(BaseModel):
|
|
name: str
|
|
display_name: str
|
|
description: str
|
|
params: str
|
|
approx_memory: str
|
|
relative_speed: str
|
|
is_default: bool
|
|
downloaded: bool
|
|
available: bool
|
|
|
|
|
|
class ModelsOut(BaseModel):
|
|
default_model: str
|
|
faster_whisper_installed: bool
|
|
models: list[TranscriptionModelOut]
|
|
|
|
|
|
class DefaultModelUpdate(BaseModel):
|
|
model: str = Field(min_length=1, max_length=32)
|
|
|
|
|
|
class DefaultModelOut(BaseModel):
|
|
default_model: str
|
|
|
|
|
|
async def _models_out(session: SessionDep) -> ModelsOut:
|
|
default = await get_global_default_model(session)
|
|
installed = is_faster_whisper_installed()
|
|
return ModelsOut(
|
|
default_model=default,
|
|
faster_whisper_installed=installed,
|
|
models=[
|
|
TranscriptionModelOut(
|
|
name=info.name,
|
|
display_name=info.display_name,
|
|
description=info.description,
|
|
params=info.params,
|
|
approx_memory=info.approx_memory,
|
|
relative_speed=info.relative_speed,
|
|
is_default=info.name == default,
|
|
downloaded=is_model_downloaded(info.name),
|
|
available=installed and is_model_downloaded(info.name),
|
|
)
|
|
for info in SUPPORTED_TRANSCRIPTION_MODELS.values()
|
|
],
|
|
)
|
|
|
|
|
|
@router.get("/models", response_model=ModelsOut)
|
|
async def list_models(user: CurrentUser, session: SessionDep):
|
|
return await _models_out(session)
|
|
|
|
|
|
@router.get("/models/default", response_model=DefaultModelOut)
|
|
async def get_default_model(user: CurrentUser, session: SessionDep):
|
|
return DefaultModelOut(default_model=await get_global_default_model(session))
|
|
|
|
|
|
@router.put("/models/default", response_model=DefaultModelOut)
|
|
async def put_default_model(body: DefaultModelUpdate, user: CurrentUser, session: SessionDep):
|
|
try:
|
|
name = await set_global_default_model(session, body.model)
|
|
except ProviderConfigError as e:
|
|
raise HTTPException(422, str(e)) from None
|
|
return DefaultModelOut(default_model=name)
|
|
|
|
|
|
@router.post("/models/{name}/download", response_model=TranscriptionModelOut)
|
|
async def download_model(name: str, user: CurrentUser, session: SessionDep):
|
|
try:
|
|
clean = validate_model_name(name)
|
|
except ProviderConfigError as e:
|
|
raise HTTPException(422, str(e)) from None
|
|
if not is_faster_whisper_installed():
|
|
raise HTTPException(
|
|
501,
|
|
"faster-whisper is not installed on this server "
|
|
"(pip install shonar-backend[faster-whisper]).",
|
|
)
|
|
from shonar.services.ai.faster_whisper import _load_model
|
|
|
|
try:
|
|
await asyncio.to_thread(_load_model, clean)
|
|
except Exception as e: # noqa: BLE001 — surface download failures plainly
|
|
raise HTTPException(500, f"Model download failed: {e}") from None
|
|
default = await get_global_default_model(session)
|
|
info = SUPPORTED_TRANSCRIPTION_MODELS[clean]
|
|
return TranscriptionModelOut(
|
|
name=info.name,
|
|
display_name=info.display_name,
|
|
description=info.description,
|
|
params=info.params,
|
|
approx_memory=info.approx_memory,
|
|
relative_speed=info.relative_speed,
|
|
is_default=info.name == default,
|
|
downloaded=is_model_downloaded(clean),
|
|
available=is_model_downloaded(clean),
|
|
)
|