feat(model): wählbares LLM-Modell pro User & pro Job

Mac-Worker:
- /api/summarize, /api/protocol, /api/preload akzeptieren optionales
  model-Feld → überschreibt den .env-Default pro Call, kein Restart nötig
- /api/models listet die lokal verfügbaren Ollama-Modelle + Default

LXC:
- User.default_model + Job.model (Migration für SQLite)
- /api/auth/me liefert default_model, PATCH speichert ihn sofort
- /api/mac/models proxiet die Liste an die UI
- Pipeline reicht job.model an Mac-Worker durch; leer = Worker-Default
- create_job & reprocess akzeptieren model-Override
- Snapshot-Meta nutzt job.model (genauer als /health-Snapshot)

UI:
- Header-Dropdown "Modell:" neben "Profil:" — sofort-speichert User-Default
- Jeder Job zeigt sein Modell als Tag (lila); leer bedeutet Worker-Default
- Reprocess öffnet einen Modal-Picker (statt simplem confirm), Auswahl
  aller verfügbaren Modelle inkl. "Worker-Default"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-16 16:48:11 +00:00
co-authored by Claude Opus 4.7
parent dbf7bca6c7
commit c6164d946c
13 changed files with 386 additions and 54 deletions
+8 -4
View File
@@ -97,6 +97,8 @@ async def run_pipeline(job_id: int) -> None:
audio_path = settings.upload_dir / job.filename
title = job.title or audio_path.stem
profile = job.profile or "meeting"
# Leeres `model` → Mac-Worker nutzt seinen .env-Default.
model = (job.model or "").strip() or None
existing_tx_path = _existing(job.transcript_path)
existing_sum_path = _existing(job.summary_path)
existing_pro_path = _existing(job.protocol_path)
@@ -131,16 +133,17 @@ async def run_pipeline(job_id: int) -> None:
_update(job_id, status=JobStatus.SUMMARIZING, progress=70)
else:
_start_step(job_id, JobStatus.SUMMARIZING, 50)
log.info("[job %s] preload model", job_id)
log.info("[job %s] preload model=%s", job_id, model or "(worker default)")
try:
await _with_heartbeat(job_id, client.preload(profile=profile, job_id=job_id))
await _with_heartbeat(job_id, client.preload(profile=profile, model=model, job_id=job_id))
except Exception as e: # noqa: BLE001
log.warning("[job %s] preload failed (non-fatal): %s", job_id, e)
_update(job_id, progress=55)
log.info("[job %s] summarize", job_id)
summary = await _with_heartbeat(
job_id,
client.summarize(transcript_text, title=title, profile=profile, job_id=job_id),
client.summarize(transcript_text, title=title, profile=profile, model=model,
job_id=job_id),
)
summary_path = job_dir / "summary.json"
summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
@@ -156,7 +159,8 @@ async def run_pipeline(job_id: int) -> None:
log.info("[job %s] protocol", job_id)
protocol = await _with_heartbeat(
job_id,
client.protocol(transcript_text, summary, title=title, profile=profile, job_id=job_id),
client.protocol(transcript_text, summary, title=title, profile=profile, model=model,
job_id=job_id),
)
protocol_path = job_dir / "protocol.json"
protocol_path.write_text(json.dumps(protocol, ensure_ascii=False, indent=2), encoding="utf-8")