From b23e361791103d8be907d8a4e8d66fadc5b2f5ab Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 25 Mar 2026 10:01:49 +0100 Subject: [PATCH] feat: Pipeline-System Frontend - Admin UI (Issue #28, Phase 2 Part 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementiert Admin-UI für Pipeline-Konfigurationen: - Pipeline-Config Dialog mit Module-Auswahl - Stage-Konfiguration (Stage 1/2/3 Prompts) - Admin-UI: Zwei Tabs (Prompts + Pipeline-Configs) - CRUD-Operationen für Pipeline-Configs - API-Integration: Pipeline-Config Endpoints **Frontend:** - components/PipelineConfigModal.jsx (neu): Dialog für Pipeline-Konfiguration - Module-Auswahl mit Zeiträumen (7 Module) - Stage 1: Multi-Select für parallele Prompts - Stage 2: Synthese-Prompt Auswahl - Stage 3: Optional (Goals) - Validierung (mind. 1 Modul, mind. 1 Stage-1-Prompt, Stage-2 erforderlich) - pages/AdminPromptsPage.jsx (erweitert): Tab-Navigation - Tab 1: Prompts (bestehend) - Tab 2: Pipeline-Konfigurationen (neu) - Liste aller Configs mit Status (Aktiv, Standard) - Aktionen: Bearbeiten, Löschen, Als Standard setzen - Icons: Star, Edit, Trash2 - utils/api.js (erweitert): - listPipelineConfigs, createPipelineConfig, updatePipelineConfig - deletePipelineConfig, setDefaultPipelineConfig - executePipeline, resetPromptToDefault **Nächste Schritte:** - Pipeline-Auswahl in AnalysisPage (User-Seite) - Mobile-Responsive Design Issue #28 Progress: Frontend 2/3 (67%) | Design 0/3 | Testing 0/1 Co-Authored-By: Claude Opus 4.6 --- .../src/components/PipelineConfigModal.jsx | 386 ++++++++++++++++++ frontend/src/pages/AdminPromptsPage.jsx | 214 +++++++++- frontend/src/utils/api.js | 11 + 3 files changed, 605 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/PipelineConfigModal.jsx 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 */} +
+
+ + setName(e.target.value)} + placeholder="z.B. Alltags-Check" + style={{width:'100%', textAlign:'left'}} + /> +
+ +
+ +