All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 36s
Test Suite / playwright-tests (push) Successful in 1m16s
- Added `exercise_instruction_rewrite` functionality to enhance AI-generated instructions, incorporating fields for goal, execution, preparation, and trainer notes. - Updated `ExerciseFormAiPromptContext` to include new fields and methods for instruction handling. - Enhanced the `run_exercise_form_ai_suggestion` function to support instruction rewriting and validation. - Modified API endpoints and frontend components to integrate instruction features, including a new button for AI instruction revision. - Incremented application version to 0.8.163 and updated changelog to reflect these changes, including migration details and new functionality.
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
"""
|
|
Mustache-aehnliche Platzhalter {{schluessel}} fuer KI-Templates aus ai_prompts.
|
|
|
|
Kein Vereinsbezug — reine Textersetzung; Aufrufe aus exercise_ai und Admin-Vorschau.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from typing import Dict, List, Mapping
|
|
|
|
_PLACEHOLDER_RE = re.compile(r"\{\{\s*([a-zA-Z0-9_]+)\s*\}\}")
|
|
|
|
|
|
def _placeholder_pattern_for_key(key: str) -> re.Pattern[str]:
|
|
return re.compile(r"\{\{\s*" + re.escape(str(key).strip()) + r"\s*\}\}")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MustacheRenderResult:
|
|
"""Ergebnis von render_mustache_template."""
|
|
|
|
text: str
|
|
keys_in_template: List[str]
|
|
keys_substituted: List[str]
|
|
keys_missing_variables: List[str]
|
|
placeholders_remaining: List[str]
|
|
|
|
|
|
def extract_mustache_keys(template: str) -> List[str]:
|
|
"""Platzhalter-Namen in Vorkommensreihenfolge, ohne erstes Duplikat."""
|
|
seen: set[str] = set()
|
|
ordered: List[str] = []
|
|
for m in _PLACEHOLDER_RE.finditer(template or ""):
|
|
k = str(m.group(1) or "").strip()
|
|
if not k or k in seen:
|
|
continue
|
|
seen.add(k)
|
|
ordered.append(k)
|
|
return ordered
|
|
|
|
|
|
def render_mustache_template(template: str, variables: Mapping[str, str]) -> MustacheRenderResult:
|
|
"""
|
|
Ersetzt {{keys}} durch die passenden Strings.
|
|
Variablen, die fuer einen im Template genutzten Key fehlen, werden als Leerstring ersetzt.
|
|
|
|
Rueckgabe-liste keys_missing_variables: Keys, die im Template vorkommen, aber nicht als Map-Keys
|
|
uebergeben wurden (oder None-Wert entsprachen Leerung).
|
|
"""
|
|
tpl_in = template or ""
|
|
vars_norm: Dict[str, str] = {}
|
|
for k, v in variables.items():
|
|
vars_norm[str(k)] = "" if v is None else str(v)
|
|
keys_in = extract_mustache_keys(tpl_in)
|
|
missing_known: List[str] = []
|
|
out = tpl_in
|
|
substituted: List[str] = []
|
|
|
|
for key in keys_in:
|
|
pat = _placeholder_pattern_for_key(key)
|
|
repl = vars_norm.get(key)
|
|
if key not in vars_norm:
|
|
missing_known.append(key)
|
|
repl = ""
|
|
substituted.append(key)
|
|
out = pat.sub(repl, out)
|
|
|
|
still = extract_mustache_keys(out)
|
|
|
|
return MustacheRenderResult(
|
|
text=out,
|
|
keys_in_template=keys_in,
|
|
keys_substituted=substituted,
|
|
keys_missing_variables=missing_known,
|
|
placeholders_remaining=still,
|
|
)
|
|
|
|
|
|
def exercise_placeholder_catalog() -> dict:
|
|
"""
|
|
Statischer Platzhalter-Katalog fuer Uebungs-KI-Templates — deckt aktuelle Seeds ab.
|
|
(Erweiterung andere Kontexte: matrix/import folgen separat.)
|
|
"""
|
|
defs = [
|
|
{
|
|
"key": "exercise_title",
|
|
"placeholder": "{{exercise_title}}",
|
|
"description": "Titel der Uebung (oder Platzhalter, wenn leer).",
|
|
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
|
|
},
|
|
{
|
|
"key": "exercise_focus_area",
|
|
"placeholder": "{{exercise_focus_area}}",
|
|
"description": "Fokuskontext (Text-Hinweis aus Formular, optional).",
|
|
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
|
|
},
|
|
{
|
|
"key": "exercise_goal",
|
|
"placeholder": "{{exercise_goal}}",
|
|
"description": "Ziel aus dem Formular, als Plaintext ohne HTML-Zeichen.",
|
|
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
|
|
},
|
|
{
|
|
"key": "exercise_execution",
|
|
"placeholder": "{{exercise_execution}}",
|
|
"description": "Durchfuehrung als Plaintext ohne HTML-Zeichen.",
|
|
"used_by_slugs": ["exercise_summary", "exercise_skill_suggestions", "exercise_instruction_rewrite"],
|
|
},
|
|
{
|
|
"key": "exercise_preparation",
|
|
"placeholder": "{{exercise_preparation}}",
|
|
"description": "Vorbereitung/Aufbau als Plaintext ohne HTML.",
|
|
"used_by_slugs": ["exercise_instruction_rewrite"],
|
|
},
|
|
{
|
|
"key": "exercise_trainer_notes",
|
|
"placeholder": "{{exercise_trainer_notes}}",
|
|
"description": "Trainer-Hinweise als Plaintext ohne HTML.",
|
|
"used_by_slugs": ["exercise_instruction_rewrite"],
|
|
},
|
|
{
|
|
"key": "skills_catalog",
|
|
"placeholder": "{{skills_catalog}}",
|
|
"description": (
|
|
"Gewichtete, kontextbezogene Liste aus dem Skill-Katalog (retrieval_profiles). "
|
|
"Nur fuer exercise_skill_suggestions."
|
|
),
|
|
"used_by_slugs": ["exercise_skill_suggestions"],
|
|
},
|
|
]
|
|
return {"context": "exercise", "placeholders": defs}
|
|
|
|
|
|
__all__ = [
|
|
"MustacheRenderResult",
|
|
"exercise_placeholder_catalog",
|
|
"extract_mustache_keys",
|
|
"render_mustache_template",
|
|
]
|