diff --git a/frontend/src/components/PipelineConfigModal.jsx b/frontend/src/components/PipelineConfigModal.jsx
new file mode 100644
index 0000000..888b16e
--- /dev/null
+++ b/frontend/src/components/PipelineConfigModal.jsx
@@ -0,0 +1,386 @@
+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/pages/AdminPromptsPage.jsx b/frontend/src/pages/AdminPromptsPage.jsx
index f9dff12..5f528c0 100644
--- a/frontend/src/pages/AdminPromptsPage.jsx
+++ b/frontend/src/pages/AdminPromptsPage.jsx
@@ -1,8 +1,13 @@
import { useState, useEffect } from 'react'
import { api } from '../utils/api'
import PromptEditModal from '../components/PromptEditModal'
+import PipelineConfigModal from '../components/PipelineConfigModal'
+import { Star, Trash2, Edit, Copy } from 'lucide-react'
export default function AdminPromptsPage() {
+ const [activeTab, setActiveTab] = useState('prompts') // 'prompts' | 'pipelines'
+
+ // Prompts state
const [prompts, setPrompts] = useState([])
const [filteredPrompts, setFilteredPrompts] = useState([])
const [category, setCategory] = useState('all')
@@ -11,6 +16,12 @@ export default function AdminPromptsPage() {
const [editingPrompt, setEditingPrompt] = useState(null)
const [showNewPrompt, setShowNewPrompt] = useState(false)
+ // Pipeline configs state
+ const [pipelineConfigs, setPipelineConfigs] = useState([])
+ const [pipelineLoading, setPipelineLoading] = useState(false)
+ const [editingPipeline, setEditingPipeline] = useState(null)
+ const [showNewPipeline, setShowNewPipeline] = useState(false)
+
const categories = [
{ id: 'all', label: 'Alle Kategorien' },
{ id: 'körper', label: 'Körper' },
@@ -24,6 +35,7 @@ export default function AdminPromptsPage() {
useEffect(() => {
loadPrompts()
+ loadPipelineConfigs()
}, [])
useEffect(() => {
@@ -116,6 +128,45 @@ export default function AdminPromptsPage() {
setShowNewPrompt(false)
}
+ // Pipeline Config handlers
+ const loadPipelineConfigs = async () => {
+ try {
+ setPipelineLoading(true)
+ const data = await api.listPipelineConfigs()
+ setPipelineConfigs(data)
+ } catch (e) {
+ setError(e.message)
+ } finally {
+ setPipelineLoading(false)
+ }
+ }
+
+ const handleDeletePipeline = async (config) => {
+ if (!confirm(`Pipeline-Config "${config.name}" wirklich löschen?`)) return
+
+ try {
+ await api.deletePipelineConfig(config.id)
+ await loadPipelineConfigs()
+ } catch (e) {
+ alert('Fehler: ' + e.message)
+ }
+ }
+
+ const handleSetDefaultPipeline = async (config) => {
+ try {
+ await api.setDefaultPipelineConfig(config.id)
+ await loadPipelineConfigs()
+ } catch (e) {
+ alert('Fehler: ' + e.message)
+ }
+ }
+
+ const handleSavePipeline = async () => {
+ await loadPipelineConfigs()
+ setEditingPipeline(null)
+ setShowNewPipeline(false)
+ }
+
if (loading) {
return (
@@ -129,12 +180,38 @@ export default function AdminPromptsPage() {
return (
-
KI-Prompts Verwaltung
+ KI-Prompts & Pipelines
+
+
+ {/* Tab Switcher */}
+
+
+
@@ -144,8 +221,11 @@ export default function AdminPromptsPage() {
)}
- {/* Category Filter */}
-
+ {/* Prompts Tab */}
+ {activeTab === 'prompts' && (
+ <>
+ {/* Category Filter */}
+
@@ -276,8 +356,119 @@ export default function AdminPromptsPage() {
)}
+ >
+ )}
- {/* Edit Modal */}
+ {/* Pipelines Tab */}
+ {activeTab === 'pipelines' && (
+ <>
+ {pipelineLoading ? (
+
+ ) : (
+
+
+
+
+ | Name |
+ Module |
+ Stages |
+ Status |
+ Aktionen |
+
+
+
+ {pipelineConfigs.map(config => {
+ const activeModules = Object.entries(config.modules || {}).filter(([_, active]) => active)
+
+ return (
+
+ |
+ {config.name}
+ {config.is_default && (
+
+ Standard
+
+ )}
+ {config.description && (
+
+ {config.description}
+
+ )}
+ |
+
+
+ {activeModules.map(([name]) => name).join(', ')}
+
+
+ {activeModules.length} Module
+
+ |
+
+
+ S1: {config.stage1_prompts?.length || 0} Prompts
+
+
+ S2: {config.stage2_prompt ? '✓' : '-'} | S3: {config.stage3_prompt ? '✓' : '-'}
+
+ |
+
+
+ {config.active ? 'Aktiv' : 'Inaktiv'}
+
+ |
+
+
+ {!config.is_default && (
+
+ )}
+
+
+
+ |
+
+ )
+ })}
+
+
+
+ {pipelineConfigs.length === 0 && (
+
+ Noch keine Pipeline-Konfigurationen vorhanden
+
+ )}
+
+ )}
+ >
+ )}
+
+ {/* Edit Modals */}
{(editingPrompt || showNewPrompt) && (
)}
+
+ {(editingPipeline || showNewPipeline) && (
+
{
+ setEditingPipeline(null)
+ setShowNewPipeline(false)
+ }}
+ />
+ )}
)
}
diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js
index a9836ac..a3c86ed 100644
--- a/frontend/src/utils/api.js
+++ b/frontend/src/utils/api.js
@@ -294,4 +294,15 @@ export const api = {
generatePrompt: (d) => req('/prompts/generate', json(d)),
optimizePrompt: (id) => req(`/prompts/${id}/optimize`, json({})),
listPlaceholders: () => req('/prompts/placeholders'),
+ resetPromptToDefault: (id) => req(`/prompts/${id}/reset-to-default`, json({})),
+
+ // Pipeline Configs Management (Issue #28 Phase 2)
+ listPipelineConfigs: () => req('/prompts/pipeline-configs'),
+ createPipelineConfig: (d) => req('/prompts/pipeline-configs', json(d)),
+ updatePipelineConfig: (id,d) => req(`/prompts/pipeline-configs/${id}`, jput(d)),
+ deletePipelineConfig: (id) => req(`/prompts/pipeline-configs/${id}`, {method:'DELETE'}),
+ setDefaultPipelineConfig: (id) => req(`/prompts/pipeline-configs/${id}/set-default`, json({})),
+
+ // Pipeline Execution (Issue #28 Phase 2)
+ executePipeline: (configId=null) => req('/insights/pipeline' + (configId ? `?config_id=${configId}` : ''), json({})),
}