/** * KI-gestützte Schnellanlage einer Übung (Planung / ExercisePickerModal). */ import { stripHtmlToText } from './htmlUtils' import { normalizeSkillLevelSlug } from '../constants/skillLevels' import { EXERCISE_SKILL_INTENSITY_DEFAULT, normalizeExerciseSkillIntensity, } from '../constants/exerciseSkillIntensity' function escapeHtmlText(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') } /** Plaintext → Absätze für RichTextEditor / API. */ export function aiPlainTextToMinimalHtml(text) { const raw = String(text || '').trim() if (!raw) return '' const parts = raw.split(/\n+/).map((p) => p.trim()).filter(Boolean) const paras = parts.length ? parts : [raw] return paras.map((p) => `
${escapeHtmlText(p)}
`).join('') } export function normalizeAiSkillRowFromApi(sug) { const sid = Number(sug?.skill_id) if (!Number.isFinite(sid) || sid < 1) return null return { skill_id: sid, intensity: normalizeExerciseSkillIntensity(sug.intensity) || EXERCISE_SKILL_INTENSITY_DEFAULT, required_level: normalizeSkillLevelSlug(sug.required_level) || 'grundlagen', target_level: normalizeSkillLevelSlug(sug.target_level) || normalizeSkillLevelSlug(sug.required_level) || 'grundlagen', is_primary: !!sug.is_primary, ai_suggested: true, } } /** * Baut createExercise-Payload aus suggestExerciseAi-Antwort. * @throws {Error} wenn Ziel/Durchführung nach KI leer wären */ export function buildQuickCreateExercisePayload({ title, focusAreaId, sketchPlain, apiRes }) { const sketchHtml = aiPlainTextToMinimalHtml(sketchPlain) const fields = apiRes?.instructions?.fields || {} let goal = (fields.goal || '').trim() || sketchHtml let execution = (fields.execution || '').trim() const prep = (fields.preparation || '').trim() || null const trainerNotes = (fields.trainer_notes || '').trim() || null if (!stripHtmlToText(goal).trim() && !stripHtmlToText(execution).trim()) { throw new Error('KI lieferte keine verwertbare Anleitung (Ziel/Durchführung leer).') } if (!stripHtmlToText(goal).trim()) goal = execution if (!stripHtmlToText(execution).trim()) execution = goal let summary = null if (apiRes?.summary?.text) { const sumHtml = aiPlainTextToMinimalHtml(apiRes.summary.text) if (sumHtml) summary = sumHtml } const skills = [] if (Array.isArray(apiRes?.skills)) { for (const sug of apiRes.skills) { const row = normalizeAiSkillRowFromApi(sug) if (row) skills.push(row) } } const fid = Number(focusAreaId) if (!Number.isFinite(fid) || fid < 1) { throw new Error('Fokusbereich ist erforderlich.') } return { title: (title || '').trim(), summary, goal, execution, preparation: prep, trainer_notes: trainerNotes, visibility: 'private', status: 'draft', equipment: [], focus_areas_multi: [{ focus_area_id: fid, is_primary: true }], training_styles_multi: [], training_types_multi: [], target_groups_multi: [], age_groups: [], skills, club_id: null, } }