diff --git a/CLAUDE.md b/CLAUDE.md
index 164a5a8..2ea8319 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -56,7 +56,7 @@ frontend/src/
└── technical/ # MEMBERSHIP_SYSTEM.md
```
-## Aktuelle Version: v9c (komplett) 🚀 Production seit 21.03.2026
+## Aktuelle Version: v9d + Issue #28 🚀 Development seit 25.03.2026
### Implementiert ✅
- Login (E-Mail + bcrypt), Auth-Middleware alle Endpoints, Rate Limiting
@@ -188,6 +188,53 @@ frontend/src/
📚 Details: `.claude/docs/technical/MEMBERSHIP_SYSTEM.md` · `.claude/docs/architecture/FEATURE_ENFORCEMENT.md`
+### Issue #28: Unified Prompt System ✅ (Development 25.03.2026)
+
+**AI-Prompts Flexibilisierung - Komplett überarbeitet:**
+
+- ✅ **Unified Prompt System (4 Phasen):**
+ - **Phase 1:** DB-Migration - Schema erweitert
+ - `ai_prompts` um `type`, `stages`, `output_format`, `output_schema` erweitert
+ - Alle Prompts zu 1-stage Pipelines migriert
+ - Pipeline-Configs in `ai_prompts` konsolidiert
+ - **Phase 2:** Backend Executor
+ - `prompt_executor.py` - universeller Executor für base + pipeline
+ - Dynamische Placeholder-Auflösung (`{{stage_N_key}}`)
+ - JSON-Output-Validierung
+ - Multi-stage parallele Ausführung
+ - Reference (Basis-Prompts) + Inline (Templates) Support
+ - **Phase 3:** Frontend UI Consolidation
+ - `UnifiedPromptModal` - ein Editor für beide Typen
+ - `AdminPromptsPage` - Tab-Switcher entfernt, Type-Filter hinzugefügt
+ - Stage-Editor mit Add/Remove/Reorder
+ - Mobile-ready Design
+ - **Phase 4:** Cleanup & Docs
+ - Deprecated Komponenten entfernt (PipelineConfigModal, PromptEditModal)
+ - Old endpoints behalten für Backward-Compatibility
+
+**Features:**
+- Unbegrenzte dynamische Stages (keine 3-Stage Limitierung mehr)
+- Mehrere Prompts pro Stage (parallel)
+- Zwei Prompt-Typen: `base` (wiederverwendbar) + `pipeline` (Workflows)
+- Inline-Templates oder Referenzen zu Basis-Prompts
+- JSON-Output erzwingbar pro Prompt
+- Cross-Module Korrelationen möglich
+
+**Migrations:**
+- Migration 020: Unified Prompt System Schema
+
+**Backend Endpoints:**
+- `POST /api/prompts/execute` - Universeller Executor
+- `POST /api/prompts/unified` - Create unified prompt
+- `PUT /api/prompts/unified/{id}` - Update unified prompt
+
+**UI:**
+- Admin → KI-Prompts: Type-Filter (Alle/Basis/Pipeline)
+- Neuer Prompt-Editor mit dynamischem Stage-Builder
+- Inline editing von Stages + Prompts
+
+📚 Details: `.claude/docs/functional/AI_PROMPTS.md`
+
## Feature-Roadmap
> 📋 **Detaillierte Roadmap:** `.claude/docs/ROADMAP.md` (Phasen 0-3, Timeline, Abhängigkeiten)
diff --git a/frontend/src/components/PipelineConfigModal.jsx b/frontend/src/components/PipelineConfigModal.jsx
deleted file mode 100644
index 888b16e..0000000
--- a/frontend/src/components/PipelineConfigModal.jsx
+++ /dev/null
@@ -1,386 +0,0 @@
-import { useState, useEffect } from 'react'
-import { api } from '../utils/api'
-import { X } from 'lucide-react'
-
-const MODULES = [
- { id: 'körper', label: 'Körper', defaultDays: 30 },
- { id: 'ernährung', label: 'Ernährung', defaultDays: 30 },
- { id: 'training', label: 'Training', defaultDays: 14 },
- { id: 'schlaf', label: 'Schlaf', defaultDays: 14 },
- { id: 'vitalwerte', label: 'Vitalwerte', defaultDays: 7 },
- { id: 'mentales', label: 'Mentales', defaultDays: 7 },
- { id: 'ziele', label: 'Ziele', defaultDays: null }, // No timeframe for goals
-]
-
-export default function PipelineConfigModal({ config, onSave, onClose }) {
- const [name, setName] = useState('')
- const [description, setDescription] = useState('')
- const [isDefault, setIsDefault] = useState(false)
- const [active, setActive] = useState(true)
-
- // Modules state: {körper: true, ernährung: false, ...}
- const [modules, setModules] = useState({})
-
- // Timeframes state: {körper: 30, ernährung: 30, ...}
- const [timeframes, setTimeframes] = useState({})
-
- // Stage prompts
- const [stage1Prompts, setStage1Prompts] = useState([])
- const [stage2Prompt, setStage2Prompt] = useState('')
- const [stage3Prompt, setStage3Prompt] = useState('')
-
- // Available prompts (for dropdowns)
- const [availablePrompts, setAvailablePrompts] = useState([])
-
- const [loading, setLoading] = useState(false)
- const [error, setError] = useState(null)
-
- useEffect(() => {
- loadAvailablePrompts()
-
- if (config) {
- // Edit mode
- setName(config.name || '')
- setDescription(config.description || '')
- setIsDefault(config.is_default || false)
- setActive(config.active ?? true)
- setModules(config.modules || {})
- setTimeframes(config.timeframes || {})
- setStage1Prompts(config.stage1_prompts || [])
- setStage2Prompt(config.stage2_prompt || '')
- setStage3Prompt(config.stage3_prompt || '')
- } else {
- // New mode - set defaults
- const defaultModules = {}
- const defaultTimeframes = {}
- MODULES.forEach(m => {
- defaultModules[m.id] = false
- if (m.defaultDays) defaultTimeframes[m.id] = m.defaultDays
- })
- setModules(defaultModules)
- setTimeframes(defaultTimeframes)
- }
- }, [config])
-
- const loadAvailablePrompts = async () => {
- try {
- const prompts = await api.listAdminPrompts()
- setAvailablePrompts(prompts)
- } catch (e) {
- setError('Fehler beim Laden der Prompts: ' + e.message)
- }
- }
-
- const toggleModule = (moduleId) => {
- setModules(prev => ({ ...prev, [moduleId]: !prev[moduleId] }))
- }
-
- const updateTimeframe = (moduleId, days) => {
- setTimeframes(prev => ({ ...prev, [moduleId]: parseInt(days) || 0 }))
- }
-
- const handleSave = async () => {
- // Validation
- if (!name.trim()) {
- setError('Bitte Namen eingeben')
- return
- }
-
- const activeModules = Object.entries(modules).filter(([_, active]) => active).map(([id]) => id)
- if (activeModules.length === 0) {
- setError('Mindestens ein Modul muss aktiviert sein')
- return
- }
-
- if (stage1Prompts.length === 0) {
- setError('Mindestens ein Stage-1-Prompt erforderlich')
- return
- }
-
- if (!stage2Prompt) {
- setError('Stage-2-Prompt (Synthese) erforderlich')
- return
- }
-
- setLoading(true)
- setError(null)
-
- try {
- const data = {
- name,
- description,
- is_default: isDefault,
- active,
- modules,
- timeframes,
- stage1_prompts: stage1Prompts,
- stage2_prompt: stage2Prompt,
- stage3_prompt: stage3Prompt || null
- }
-
- if (config?.id) {
- await api.updatePipelineConfig(config.id, data)
- } else {
- await api.createPipelineConfig(data)
- }
-
- onSave()
- } catch (e) {
- setError(e.message)
- } finally {
- setLoading(false)
- }
- }
-
- const addStage1Prompt = (slug) => {
- if (slug && !stage1Prompts.includes(slug)) {
- setStage1Prompts(prev => [...prev, slug])
- }
- }
-
- const removeStage1Prompt = (slug) => {
- setStage1Prompts(prev => prev.filter(s => s !== slug))
- }
-
- // Filter prompts: only pipeline-type prompts
- const pipelinePrompts = availablePrompts.filter(p =>
- p.slug && p.slug.startsWith('pipeline_') && p.slug !== 'pipeline_synthesis' && p.slug !== 'pipeline_goals'
- )
-
- const synthesisPrompts = availablePrompts.filter(p =>
- p.slug && (p.slug === 'pipeline_synthesis' || p.slug.includes('synthesis'))
- )
-
- const goalsPrompts = availablePrompts.filter(p =>
- p.slug && (p.slug === 'pipeline_goals' || p.slug.includes('goals') || p.slug.includes('ziele'))
- )
-
- return (
-
-
-
-
- {config ? 'Pipeline-Konfiguration bearbeiten' : 'Neue Pipeline-Konfiguration'}
-
-
-
-
- {error && (
-
- {error}
-
- )}
-
- {/* Basic Info */}
-
-
- {/* Module Selection */}
-
-
Module & Zeiträume
-
- {MODULES.map(module => (
-
- ))}
-
-
-
- {/* Stage Configuration */}
-
-
Pipeline-Stufen
-
- {/* Stage 1 */}
-
-
-
- Diese Prompts laufen parallel und liefern JSON-Summaries für Stage 2
-
-
-
-
- {stage1Prompts.length > 0 && (
-
- {stage1Prompts.map(slug => (
-
- {slug}
- removeStage1Prompt(slug)}
- style={{cursor:'pointer', color:'var(--danger)'}}
- />
-
- ))}
-
- )}
-
-
- {/* Stage 2 */}
-
-
-
- Kombiniert alle Stage-1-Ergebnisse zu einer narrativen Analyse
-
-
-
-
- {/* Stage 3 */}
-
-
-
- Optional: Zusätzliche Auswertung basierend auf Synthese
-
-
-
-
-
- {/* Actions */}
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/components/PromptEditModal.jsx b/frontend/src/components/PromptEditModal.jsx
deleted file mode 100644
index b9684da..0000000
--- a/frontend/src/components/PromptEditModal.jsx
+++ /dev/null
@@ -1,564 +0,0 @@
-import { useState, useEffect } from 'react'
-import { api } from '../utils/api'
-import PromptGenerator from './PromptGenerator'
-
-export default function PromptEditModal({ prompt, onSave, onClose }) {
- const [name, setName] = useState('')
- const [slug, setSlug] = useState('')
- const [displayName, setDisplayName] = useState('')
- const [description, setDescription] = useState('')
- const [category, setCategory] = useState('ganzheitlich')
- const [template, setTemplate] = useState('')
- const [active, setActive] = useState(true)
-
- const [preview, setPreview] = useState(null)
- const [unknownPlaceholders, setUnknownPlaceholders] = useState([])
- const [showGenerator, setShowGenerator] = useState(false)
- const [showPlaceholders, setShowPlaceholders] = useState(false)
- const [placeholders, setPlaceholders] = useState({})
- const [placeholderFilter, setPlaceholderFilter] = useState('')
- const [templateRef, setTemplateRef] = useState(null)
- const [optimization, setOptimization] = useState(null)
- const [loading, setLoading] = useState(false)
- const [saving, setSaving] = useState(false)
-
- const categories = [
- { id: 'körper', label: 'Körper' },
- { id: 'ernährung', label: 'Ernährung' },
- { id: 'training', label: 'Training' },
- { id: 'schlaf', label: 'Schlaf' },
- { id: 'vitalwerte', label: 'Vitalwerte' },
- { id: 'ziele', label: 'Ziele' },
- { id: 'ganzheitlich', label: 'Ganzheitlich' }
- ]
-
- useEffect(() => {
- if (prompt) {
- setName(prompt.name || '')
- setSlug(prompt.slug || '')
- setDisplayName(prompt.display_name || '')
- setDescription(prompt.description || '')
- setCategory(prompt.category || 'ganzheitlich')
- setTemplate(prompt.template || '')
- setActive(prompt.active ?? true)
- }
- }, [prompt])
-
- const handlePreview = async () => {
- try {
- setLoading(true)
- const result = await api.previewPrompt(template)
- setPreview(result.resolved)
- setUnknownPlaceholders(result.unknown_placeholders || [])
- } catch (e) {
- alert('Fehler bei Vorschau: ' + e.message)
- } finally {
- setLoading(false)
- }
- }
-
- const handleOptimize = async () => {
- if (!prompt?.id) {
- alert('Prompt muss erst gespeichert werden bevor er optimiert werden kann')
- return
- }
-
- try {
- setLoading(true)
- const result = await api.optimizePrompt(prompt.id)
- setOptimization(result)
- } catch (e) {
- alert('Fehler bei Optimierung: ' + e.message)
- } finally {
- setLoading(false)
- }
- }
-
- const handleApplyOptimized = () => {
- if (optimization?.optimized_prompt) {
- setTemplate(optimization.optimized_prompt)
- setOptimization(null)
- }
- }
-
- const handleSave = async () => {
- if (!name.trim()) {
- alert('Bitte Titel eingeben')
- return
- }
- if (!template.trim()) {
- alert('Bitte Template eingeben')
- return
- }
-
- try {
- setSaving(true)
-
- if (prompt?.id) {
- // Update existing
- await api.updatePrompt(prompt.id, {
- name,
- display_name: displayName || null,
- description,
- category,
- template,
- active
- })
- } else {
- // Create new
- if (!slug.trim()) {
- alert('Bitte Slug eingeben')
- return
- }
- await api.createPrompt({
- name,
- slug,
- display_name: displayName || null,
- description,
- category,
- template,
- active
- })
- }
-
- onSave()
- } catch (e) {
- alert('Fehler beim Speichern: ' + e.message)
- } finally {
- setSaving(false)
- }
- }
-
- const handleGeneratorResult = (generated) => {
- setName(generated.suggested_title)
- setCategory(generated.suggested_category)
- setTemplate(generated.template)
- setShowGenerator(false)
-
- // Auto-generate slug if new prompt
- if (!prompt?.id) {
- const autoSlug = generated.suggested_title
- .toLowerCase()
- .replace(/[^a-z0-9]+/g, '_')
- .replace(/^_+|_+$/g, '')
- setSlug(autoSlug)
- }
- }
-
- const loadPlaceholders = async () => {
- try {
- const data = await api.listPlaceholders()
- setPlaceholders(data)
- setShowPlaceholders(true)
- setPlaceholderFilter('')
- } catch (e) {
- alert('Fehler beim Laden der Platzhalter: ' + e.message)
- }
- }
-
- const insertPlaceholder = (key) => {
- if (!templateRef) {
- // Fallback: append at end
- setTemplate(prev => prev + ` {{${key}}}`)
- } else {
- // Insert at cursor position
- const start = templateRef.selectionStart
- const end = templateRef.selectionEnd
- const text = template
- const before = text.substring(0, start)
- const after = text.substring(end)
- const inserted = `{{${key}}}`
-
- setTemplate(before + inserted + after)
-
- // Set cursor position after inserted placeholder
- setTimeout(() => {
- templateRef.selectionStart = templateRef.selectionEnd = start + inserted.length
- templateRef.focus()
- }, 0)
- }
-
- setShowPlaceholders(false)
- }
-
- return (
-
-
-
- {prompt ? 'Prompt bearbeiten' : 'Neuer Prompt'}
-
-
- {/* Action Buttons */}
-
-
- {prompt?.id && (
-
- )}
-
-
-
- {/* Form Fields */}
-
-
-
- setName(e.target.value)}
- placeholder="z.B. Protein-Analyse"
- style={{width:'100%', textAlign:'left'}}
- />
-
-
- {!prompt?.id && (
-
-
- setSlug(e.target.value)}
- placeholder="z.B. protein_analyse"
- style={{width:'100%', textAlign:'left'}}
- />
-
- )}
-
-
-
-
setDisplayName(e.target.value)}
- placeholder="z.B. 🍽️ Protein-Analyse"
- style={{width:'100%', textAlign:'left'}}
- />
-
- Leer lassen = Titel wird verwendet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setActive(e.target.checked)}
- id="active-checkbox"
- />
-
-
-
-
- {/* Unknown Placeholders Warning */}
- {unknownPlaceholders.length > 0 && (
-
- ⚠️ Unbekannte Platzhalter: {unknownPlaceholders.join(', ')}
-
- )}
-
- {/* Preview */}
- {preview && (
-
-
- Vorschau (mit echten Daten):
-
-
- {preview}
-
-
- )}
-
- {/* Optimization Results */}
- {optimization && (
-
-
- ✨ Optimierungsvorschläge
-
-
-
-
- Score: {optimization.score}/100
-
-
-
-
✅ Stärken:
-
- {optimization.strengths?.map((s, i) => (
- - {s}
- ))}
-
-
-
-
-
⚠️ Verbesserungspotenzial:
-
- {optimization.weaknesses?.map((w, i) => (
- - {w}
- ))}
-
-
-
-
-
Änderungen:
-
{optimization.changes_summary}
-
-
- {/* Side-by-side comparison */}
-
-
-
Original:
-
- {template}
-
-
-
-
Optimiert:
-
- {optimization.optimized_prompt}
-
-
-
-
-
-
-
- )}
-
- {/* Actions */}
-
-
-
-
-
- {/* Generator Modal */}
- {showGenerator && (
-
setShowGenerator(false)}
- />
- )}
-
-
- )
-}