diff --git a/backend/routers/prompts.py b/backend/routers/prompts.py index da50aef..7198f89 100644 --- a/backend/routers/prompts.py +++ b/backend/routers/prompts.py @@ -922,3 +922,132 @@ def update_unified_prompt(prompt_id: str, p: UnifiedPromptUpdate, session: dict ) return {"ok": True} + + +@router.get("/export-all") +def export_all_prompts(session: dict = Depends(require_admin)): + """ + Export all prompts as JSON array. + Admin only. Used for backup and dev→prod sync. + """ + from datetime import datetime + with get_db() as conn: + cur = get_cursor(conn) + cur.execute("SELECT * FROM ai_prompts ORDER BY sort_order, slug") + prompts = [r2d(row) for row in cur.fetchall()] + + # Convert to export format (clean up DB-specific fields) + export_data = [] + for p in prompts: + export_item = { + 'slug': p['slug'], + 'name': p['name'], + 'display_name': p.get('display_name'), + 'description': p.get('description'), + 'type': p.get('type', 'pipeline'), + 'category': p.get('category', 'ganzheitlich'), + 'template': p.get('template'), + 'stages': p.get('stages'), + 'output_format': p.get('output_format', 'text'), + 'output_schema': p.get('output_schema'), + 'active': p.get('active', True), + 'sort_order': p.get('sort_order', 0) + } + export_data.append(export_item) + + return { + 'export_date': datetime.now().isoformat(), + 'count': len(export_data), + 'prompts': export_data + } + + +@router.post("/import") +def import_prompts( + data: dict, + overwrite: bool = False, + session: dict = Depends(require_admin) +): + """ + Import prompts from JSON export. + + Args: + data: Export data from /export-all endpoint + overwrite: If true, update existing prompts. If false, skip duplicates. + + Returns: + Summary of import results (created, updated, skipped) + """ + if 'prompts' not in data: + raise HTTPException(400, "Invalid import data: missing 'prompts' key") + + prompts = data['prompts'] + created = 0 + updated = 0 + skipped = 0 + errors = [] + + with get_db() as conn: + cur = get_cursor(conn) + + for p in prompts: + slug = p.get('slug') + if not slug: + errors.append('Prompt without slug skipped') + continue + + # Check if exists + cur.execute("SELECT id FROM ai_prompts WHERE slug=%s", (slug,)) + existing = cur.fetchone() + + if existing and not overwrite: + skipped += 1 + continue + + # Prepare stages JSON if present + stages_json = None + if p.get('stages'): + stages_json = json.dumps(p['stages']) if isinstance(p['stages'], list) else p['stages'] + + if existing: + # Update existing + cur.execute(""" + UPDATE ai_prompts SET + name=%s, display_name=%s, description=%s, type=%s, + category=%s, template=%s, stages=%s, output_format=%s, + output_schema=%s, active=%s, sort_order=%s, + updated=CURRENT_TIMESTAMP + WHERE slug=%s + """, ( + p.get('name'), p.get('display_name'), p.get('description'), + p.get('type', 'pipeline'), p.get('category', 'ganzheitlich'), + p.get('template'), stages_json, p.get('output_format', 'text'), + p.get('output_schema'), p.get('active', True), + p.get('sort_order', 0), slug + )) + updated += 1 + else: + # Create new + cur.execute(""" + INSERT INTO ai_prompts ( + slug, name, display_name, description, type, category, + template, stages, output_format, output_schema, + active, sort_order, created, updated + ) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP) + """, ( + slug, p.get('name'), p.get('display_name'), p.get('description'), + p.get('type', 'pipeline'), p.get('category', 'ganzheitlich'), + p.get('template'), stages_json, p.get('output_format', 'text'), + p.get('output_schema'), p.get('active', True), p.get('sort_order', 0) + )) + created += 1 + + conn.commit() + + return { + 'success': True, + 'created': created, + 'updated': updated, + 'skipped': skipped, + 'errors': errors if errors else None + } diff --git a/frontend/src/pages/AdminPromptsPage.jsx b/frontend/src/pages/AdminPromptsPage.jsx index 79a2ae1..2defd60 100644 --- a/frontend/src/pages/AdminPromptsPage.jsx +++ b/frontend/src/pages/AdminPromptsPage.jsx @@ -17,6 +17,8 @@ export default function AdminPromptsPage() { const [error, setError] = useState(null) const [editingPrompt, setEditingPrompt] = useState(null) const [showNewPrompt, setShowNewPrompt] = useState(false) + const [importing, setImporting] = useState(false) + const [importResult, setImportResult] = useState(null) const categories = [ { id: 'all', label: 'Alle Kategorien' }, @@ -167,6 +169,53 @@ export default function AdminPromptsPage() { return 'var(--text3)' } + const handleExportAll = async () => { + try { + const data = await api.exportAllPrompts() + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `all-prompts-${new Date().toISOString().split('T')[0]}.json` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } catch (e) { + setError('Export-Fehler: ' + e.message) + } + } + + const handleImport = async (event) => { + const file = event.target.files[0] + if (!file) return + + setImporting(true) + setError(null) + setImportResult(null) + + try { + const text = await file.text() + const data = JSON.parse(text) + + // Ask user about overwrite + const overwrite = confirm( + 'Bestehende Prompts überschreiben?\n\n' + + 'JA = Existierende Prompts aktualisieren\n' + + 'NEIN = Nur neue Prompts erstellen, Duplikate überspringen' + ) + + const result = await api.importPrompts(data, overwrite) + setImportResult(result) + await loadPrompts() + } catch (e) { + setError('Import-Fehler: ' + e.message) + } finally { + setImporting(false) + event.target.value = '' // Reset file input + } + } + return (
KI-Prompts ({filteredPrompts.length}) - +
+ + + +
{error && ( @@ -203,6 +271,30 @@ export default function AdminPromptsPage() { )} + {importResult && ( +
+
+ ✅ Import erfolgreich +
+
+ {importResult.created} erstellt · {importResult.updated} aktualisiert · {importResult.skipped} übersprungen +
+ +
+ )} + {/* Filters */}
req('/prompts/unified', json(d)), updateUnifiedPrompt: (id,d) => req(`/prompts/unified/${id}`, jput(d)), + + // Batch Import/Export + exportAllPrompts: () => req('/prompts/export-all'), + importPrompts: (data, overwrite=false) => { + const params = new URLSearchParams() + if (overwrite) params.append('overwrite', 'true') + return req(`/prompts/import?${params}`, json(data)) + }, }