Files
gart/admin/api/models.py
T
João HenriqueandClaude Opus 5 6090e229e9 refactor: models_api.py vira ponto de entrada sobre admin/api/
A ponte JSON do app tinha 1.395 linhas e 37 comandos de oito assuntos
diferentes num arquivo só. Agora models_api.py guarda apenas a referência
dos comandos, a tabela de despacho e o main(); cada assunto virou um módulo
em admin/api/ (models, project, editing, zoom, subtitles, transcription,
voice, review), com a base comum em shared.py.

Nada muda para o app: ele continua chamando admin/models_api.py por caminho,
e os 37 comandos respondem igual — verificado rodando a ponte de verdade.

Duas coisas que a divisão obrigou a arrumar:

- A saída passa por `shared.emit` chamada pelo módulo, não pelo nome
  importado. Isso preserva a propriedade de que trocar `emit` num lugar só
  captura a saída de todos os comandos — que era acidental quando tudo
  morava no mesmo arquivo, e vira intencional agora.
- `_CANCEL` e o lock eram globais compartilhados. O registro de downloads
  foi para models.py, junto de quem o usa, com lock próprio: o antigo
  protegia ao mesmo tempo o dicionário e a escrita em stdout, duas coisas
  sem relação.

Também: admin/test_models_api.py estava fora de `testpaths` e nunca rodava.
Movido para code/tests/ e ligado ao gate — 1441 → 1454 testes
(ver Engine/docs/05_EXPERIENCIAS.md #24).

Lint zerado, 1454 testes passando.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 21:46:47 -04:00

146 lines
4.3 KiB
Python

"""Catálogo de modelos: listar, baixar, escolher, apagar.
Extraído de models_api.py — a tabela de comandos segue lá.
"""
from __future__ import annotations
import shutil
import subprocess
import sys
import threading
from pathlib import Path
# code/ is the package root for fcpxml and server modules.
_CODE_DIR = str(Path(__file__).resolve().parent.parent / "code")
if _CODE_DIR not in sys.path:
sys.path.insert(0, _CODE_DIR)
from fcpxml.diarize import (
diarization_capability,
)
from fcpxml.model_manager import (
download_model,
get_models_dir,
is_model_downloaded,
list_installed_models,
load_catalog,
load_hf_token,
load_num_speakers,
load_selected_model,
load_transcript_language,
model_cache_dir,
save_models_dir,
save_selected_model,
save_transcript_language,
)
from . import shared
from .shared import (
RECOMMENDED,
)
# Downloads em andamento, para o comando `cancel` conseguir interrompê-los.
# Mora aqui, e não no shared, porque só `download` e `cancel` o tocam — e o
# lock é próprio: ele protege este dicionário, não a saída em stdout.
_CANCEL: dict[str, threading.Event] = {}
_CANCEL_LOCK = threading.Lock()
def cmd_catalog() -> None:
catalog = load_catalog()
installed = list_installed_models()
diar_ok, diar_msg = diarization_capability(load_hf_token())
shared.emit(
{
"models": catalog,
"installed": installed,
"selected": load_selected_model(),
"language": load_transcript_language(),
"models_dir": str(get_models_dir()),
"installed_count": len(installed),
"recommended": list(RECOMMENDED),
"diarization": diar_ok,
"diarization_message": diar_msg,
"hf_token_set": bool(load_hf_token()),
"num_speakers": load_num_speakers(),
}
)
def cmd_download(args: dict) -> int:
model = str(args.get("model", ""))
if model not in _model_names():
shared.emit({"type": "error", "message": f"Modelo desconhecido: {model}"})
return 1
ev = threading.Event()
with _CANCEL_LOCK:
_CANCEL[model] = ev
try:
download_model(model, progress_cb=lambda f: shared.emit({"type": "progress", "fraction": f}), cancel_event=ev)
installed = is_model_downloaded(model)
shared.emit({"type": "done", "installed": installed})
if installed:
save_selected_model(model)
return 0 if installed else 1
except Exception as exc:
shared.emit({"type": "error", "message": str(exc)})
return 1
finally:
with _CANCEL_LOCK:
_CANCEL.pop(model, None)
def cmd_cancel(args: dict) -> None:
model = str(args.get("model", ""))
ev = _CANCEL.get(model)
if ev is not None:
ev.set()
shared.emit({"ok": True})
def cmd_select(args: dict) -> None:
model = str(args.get("model", ""))
if not is_model_downloaded(model):
shared.emit({"ok": False, "error": "Modelo não está instalado."})
return
save_selected_model(model)
shared.emit({"ok": True, "selected": load_selected_model()})
def cmd_set_language(args: dict) -> int:
"""Persist the transcription language (the default for every transcription)."""
lang = str(args.get("language", "auto"))
try:
saved = save_transcript_language(lang)
except ValueError as exc:
shared.emit({"ok": False, "error": str(exc)})
return 1
shared.emit({"ok": True, "language": saved})
return 0
def cmd_delete(args: dict) -> None:
model = str(args.get("model", ""))
try:
shutil.rmtree(model_cache_dir(model), ignore_errors=True)
except Exception:
pass
shared.emit({"ok": True})
def cmd_open_finder(args: dict) -> None:
target = str(args.get("path") or model_cache_dir(str(args.get("model", ""))))
try:
subprocess.Popen(["open", target])
except OSError:
pass
shared.emit({"ok": True})
def cmd_set_models_dir(args: dict) -> int:
try:
d = save_models_dir(str(args.get("dir", "")))
shared.emit({"ok": True, "models_dir": d})
return 0
except ValueError as exc:
shared.emit({"ok": False, "error": str(exc)})
return 1
def _model_names() -> list[str]:
return [m["internal_name"] for m in load_catalog()]