Mac-Worker: - profiles/meeting.yaml — extrahiert die bisherigen Prompts + DOCX-Layout - core/profiles.py — YAML-Loader mit Cache und Fallback - ollama_client.summarize/make_protocol nehmen profile_name - docx_export: generischer Renderer aus profile.docx (meta + sections mit Typ text/list/tasks/transcript) - /api/profiles listet verfügbare Profile - pyyaml als Dependency LXC: - Job.profile + User.default_profile (Migration: ALTER TABLE) - /api/profiles proxy mit 60s-Cache und Fallback - Upload-Form akzeptiert profile (Server-Default: user.default_profile) - Pipeline gibt Profile bei summarize/protocol/docx an Mac weiter - PATCH /api/auth/me — User kann Standard-Profil ändern Frontend: - Profile-Dropdown im Upload (nur sichtbar wenn ≥2 Profile) - Settings-Karte "Mein Standard-Profil" (nur wenn ≥2 Profile) - Job-Zeile zeigt grünes Profile-Tag (nur wenn relevant) Neue Profile = neue YAML-Datei in mac-worker/profiles/ — kein Code-Deploy nötig, der Mac-Worker liest sie beim Start ein.
85 lines
2.4 KiB
Python
85 lines
2.4 KiB
Python
"""Lädt Verarbeitungs-Profile aus mac-worker/profiles/*.yaml.
|
|
|
|
Ein Profil definiert:
|
|
- Prompts für Summary und Protocol
|
|
- DOCX-Layout (Meta-Zeilen + Sektionen mit Typ)
|
|
|
|
Sind keine Profile vorhanden, wirft get() FileNotFoundError beim ersten Zugriff.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
log = logging.getLogger("mac-worker.profiles")
|
|
|
|
PROFILES_DIR = Path(__file__).resolve().parent.parent.parent / "profiles"
|
|
|
|
DEFAULT_PROFILE_NAME = "meeting"
|
|
|
|
|
|
@dataclass
|
|
class Profile:
|
|
name: str
|
|
display_name: str
|
|
language: str = "de"
|
|
summary_prompt: str = ""
|
|
protocol_prompt: str = ""
|
|
docx: dict = field(default_factory=dict)
|
|
|
|
|
|
_cache: dict[str, Profile] = {}
|
|
|
|
|
|
def _load() -> dict[str, Profile]:
|
|
if _cache:
|
|
return _cache
|
|
if not PROFILES_DIR.exists():
|
|
log.warning("Profile-Verzeichnis %s existiert nicht", PROFILES_DIR)
|
|
return _cache
|
|
for f in sorted(PROFILES_DIR.glob("*.yaml")):
|
|
try:
|
|
data = yaml.safe_load(f.read_text(encoding="utf-8")) or {}
|
|
p = Profile(
|
|
name=data["name"],
|
|
display_name=data.get("display_name", data["name"]),
|
|
language=data.get("language", "de"),
|
|
summary_prompt=data.get("summary_prompt", ""),
|
|
protocol_prompt=data.get("protocol_prompt", ""),
|
|
docx=data.get("docx") or {},
|
|
)
|
|
_cache[p.name] = p
|
|
log.info("Profil geladen: %s (%s)", p.name, p.display_name)
|
|
except Exception as e: # noqa: BLE001
|
|
log.error("Profil %s konnte nicht geladen werden: %s", f.name, e)
|
|
return _cache
|
|
|
|
|
|
def list_all() -> list[dict]:
|
|
"""Zur Auslieferung über /api/profiles."""
|
|
return [
|
|
{"name": p.name, "display_name": p.display_name, "language": p.language}
|
|
for p in _load().values()
|
|
]
|
|
|
|
|
|
def get(name: str | None) -> Profile:
|
|
profiles = _load()
|
|
if not profiles:
|
|
raise FileNotFoundError(
|
|
f"Keine Profile gefunden in {PROFILES_DIR} — bitte mind. eine YAML-Datei anlegen."
|
|
)
|
|
if name and name in profiles:
|
|
return profiles[name]
|
|
if DEFAULT_PROFILE_NAME in profiles:
|
|
return profiles[DEFAULT_PROFILE_NAME]
|
|
return next(iter(profiles.values()))
|
|
|
|
|
|
def reload() -> None:
|
|
"""Cache leeren (z. B. für Tests oder Hot-Reload via SIGHUP)."""
|
|
_cache.clear()
|