import { useCallback, useEffect, useState } from 'react' import { Navigate } from 'react-router-dom' import { Sparkles } from 'lucide-react' import { useAuth } from '../context/AuthContext' import api from '../utils/api' import AdminPageNav from '../components/AdminPageNav' /** * Pflege von ai_prompts (OpenRouter-Vorlagen) — nur Superadmin. */ export default function AdminAiPromptsPage() { const { user } = useAuth() const isSuperadmin = user?.role === 'superadmin' const [prompts, setPrompts] = useState([]) const [catalog, setCatalog] = useState(null) const [selectedId, setSelectedId] = useState(null) const [detail, setDetail] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [error, setError] = useState('') const [draftName, setDraftName] = useState('') const [draftDesc, setDraftDesc] = useState('') const [draftTemplate, setDraftTemplate] = useState('') const [draftActive, setDraftActive] = useState(true) const [pvTitle, setPvTitle] = useState('Testübung') const [pvGoal, setPvGoal] = useState('

Ziel hier

') const [pvExec, setPvExec] = useState('

Ablauf hier

') const [pvHint, setPvHint] = useState('') const [pvFocusId, setPvFocusId] = useState('') const [pvPreview, setPvPreview] = useState(null) const loadList = useCallback(async () => { const [pList, cat] = await Promise.all([ api.listAdminAiPrompts(), api.getAdminAiPromptPlaceholdersCatalog(), ]) setPrompts(Array.isArray(pList) ? pList : []) setCatalog(cat || null) }, []) useEffect(() => { if (!isSuperadmin) return let cancelled = false ;(async () => { setLoading(true) setError('') try { await loadList() } catch (e) { if (!cancelled) setError(e.message || String(e)) } finally { if (!cancelled) setLoading(false) } })() return () => { cancelled = true } }, [isSuperadmin, loadList]) useEffect(() => { if (!isSuperadmin || !selectedId) { setDetail(null) return } let cancelled = false ;(async () => { try { const d = await api.getAdminAiPrompt(selectedId) if (!cancelled) { setDetail(d) setDraftName(d.display_name || '') setDraftDesc(d.description || '') setDraftTemplate(d.template || '') setDraftActive(!!d.active) setPvPreview(null) } } catch (e) { if (!cancelled) setError(e.message || String(e)) } })() return () => { cancelled = true } }, [isSuperadmin, selectedId]) const save = async () => { if (!detail?.id) return setSaving(true) setError('') try { await api.updateAdminAiPrompt(detail.id, { template: draftTemplate, display_name: draftName, description: draftDesc, active: draftActive, }) await loadList() const nd = await api.getAdminAiPrompt(detail.id) setDetail(nd) setPvPreview(null) } catch (e) { setError(e.message || String(e)) } finally { setSaving(false) } } const resetTemplate = async () => { if (!detail?.id || !detail.has_reference_template) return if (!confirm('Template auf gespeicherten Referenztext zurücksetzen?')) return setSaving(true) try { const nd = await api.resetAdminAiPromptTemplate(detail.id) setDetail(nd) setDraftTemplate(nd.template || '') await loadList() } catch (e) { setError(e.message || String(e)) } finally { setSaving(false) } } const runPreview = async () => { if (!detail?.id) return setError('') try { const body = { title: pvTitle, goal: pvGoal, execution: pvExec, focus_hint: pvHint || undefined, } const fid = parseInt(String(pvFocusId).trim(), 10) if (Number.isFinite(fid) && fid >= 1) { body.focus_areas_context = [{ focus_area_id: fid, is_primary: true }] } const r = await api.previewAdminAiPrompt(detail.id, body) setPvPreview(r) } catch (e) { setError(e.message || String(e)) } } if (!isSuperadmin) return if (loading) { return (
) } return (

KI Prompts

Datenbankvorlagen (ai_prompts) für Übungs-KI. Platzhalter im Mustache-Stil werden serverseitig aufgelöst — die Vorschau unten ruft kein externes Modell auf.

{error ?

{error}

: null}
Prompts
    {(prompts || []).map((p) => (
  • ))}
{!selectedId ? (

Prompt links wählen.

) : (
slug: {detail?.slug} · Ausgabe:{' '} {detail?.output_format} · Kategorie: {detail?.category}
setDraftName(e.target.value)} />