import React, { useState, useEffect } from 'react' import { Brain, Trash2, ChevronDown, ChevronUp } from 'lucide-react' import { api } from '../utils/api' import { useAuth } from '../context/AuthContext' import Markdown from '../utils/Markdown' import UsageBadge from '../components/UsageBadge' import dayjs from 'dayjs' import 'dayjs/locale/de' dayjs.locale('de') // Legacy fallback labels (display_name takes precedence) const SLUG_LABELS = { pipeline: '🔬 Mehrstufige Gesamtanalyse' } function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) { const [open, setOpen] = useState(defaultOpen) // Parse metadata early to determine showOnlyValues const metadataRaw = ins.metadata ? (typeof ins.metadata === 'string' ? JSON.parse(ins.metadata) : ins.metadata) : null const isBasePrompt = metadataRaw?.prompt_type === 'base' const isJsonOutput = ins.content && (ins.content.trim().startsWith('{') || ins.content.trim().startsWith('[')) const placeholdersRaw = metadataRaw?.placeholders || {} const showOnlyValues = isBasePrompt && isJsonOutput && Object.keys(placeholdersRaw).length > 0 const [showValues, setShowValues] = useState(showOnlyValues) // Auto-expand for base prompts with JSON const [expertMode, setExpertMode] = useState(false) // Show empty/technical placeholders // Find matching prompt to get display_name const prompt = prompts.find(p => p.slug === ins.scope) const displayName = prompt?.display_name || SLUG_LABELS[ins.scope] || ins.scope // Use already-parsed metadata const metadata = metadataRaw const allPlaceholders = placeholdersRaw // Filter placeholders: In normal mode, hide empty values and raw stage outputs const placeholders = expertMode ? allPlaceholders : Object.fromEntries( Object.entries(allPlaceholders).filter(([key, data]) => { // Hide raw stage outputs (JSON) in normal mode if (data.is_stage_raw) return false // Hide empty values const val = data.value || '' return val.trim() !== '' && val !== 'nicht verfügbar' && val !== '[Nicht verfügbar]' }) ) const placeholderCount = Object.keys(placeholders).length const hiddenCount = Object.keys(allPlaceholders).length - placeholderCount // Group placeholders by category const groupedPlaceholders = Object.entries(placeholders).reduce((acc, [key, data]) => { const category = data.category || 'Sonstiges' if (!acc[category]) acc[category] = [] acc[category].push([key, data]) return acc }, {}) // Sort categories: Regular categories first, then Stage outputs, then Rohdaten const sortedCategories = Object.keys(groupedPlaceholders).sort((a, b) => { const aIsStage = a.startsWith('Stage') const bIsStage = b.startsWith('Stage') const aIsRohdaten = a.includes('Rohdaten') const bIsRohdaten = b.includes('Rohdaten') // Rohdaten last if (aIsRohdaten && !bIsRohdaten) return 1 if (!aIsRohdaten && bIsRohdaten) return -1 // Stage outputs after regular categories if (!aIsStage && bIsStage) return -1 if (aIsStage && !bIsStage) return 1 // Otherwise alphabetical return a.localeCompare(b) }) return (
setOpen(o=>!o)}>
{displayName}
{dayjs(ins.created).format('DD. MMMM YYYY, HH:mm')}
{open ? : }
{open && ( <> {/* For base prompts with JSON: Only show value table */} {showOnlyValues && (
ℹ️ Basis-Prompt Rohdaten (JSON-Struktur für technische Nutzung)
Technische Daten anzeigen
                  {ins.content}
                
)} {/* For other prompts: Show full content */} {!showOnlyValues && } {/* Value Table */} {placeholderCount > 0 && (
setShowValues(!showValues)} style={{ cursor: 'pointer', fontSize: 12, color: 'var(--text2)', fontWeight: 600, display: 'flex', alignItems: 'center', gap: 6 }} > {showValues ? : } 📊 Verwendete Werte ({placeholderCount}) {hiddenCount > 0 && !expertMode && ( (+{hiddenCount} ausgeblendet) )}
{showValues && Object.keys(allPlaceholders).length > 0 && ( )}
{showValues && (
{sortedCategories.map(category => ( {/* Category Header */} {/* Category Values */} {groupedPlaceholders[category].map(([key, data]) => { const isExtracted = data.is_extracted const isStageRaw = data.is_stage_raw return ( ) })} ))}
Platzhalter Wert Beschreibung
{category}
{isExtracted && '↳ '} {isStageRaw && '🔬 '} {key} {data.value} {data.description || '—'}
)}
)} )}
) } export default function Analysis() { const { canUseAI } = useAuth() const [prompts, setPrompts] = useState([]) const [allInsights, setAllInsights] = useState([]) const [loading, setLoading] = useState(null) const [error, setError] = useState(null) const [tab, setTab] = useState('run') const [newResult, setNewResult] = useState(null) const [aiUsage, setAiUsage] = useState(null) const loadAll = async () => { const [p, i] = await Promise.all([ api.listPrompts(), api.listInsights() ]) setPrompts(Array.isArray(p)?p:[]) setAllInsights(Array.isArray(i)?i:[]) } useEffect(()=>{ loadAll() // Load feature usage for badges api.getFeatureUsage().then(features => { const aiFeature = features.find(f => f.feature_id === 'ai_calls') setAiUsage(aiFeature) }).catch(err => console.error('Failed to load usage:', err)) },[]) const runPrompt = async (slug) => { setLoading(slug); setError(null); setNewResult(null) try { // Use new unified executor with save=true const result = await api.executeUnifiedPrompt(slug, null, null, false, true) // Transform result to match old format for InsightCard let content = '' if (result.type === 'pipeline') { // For pipeline, extract final output const finalOutput = result.output || {} if (typeof finalOutput === 'object' && Object.keys(finalOutput).length === 1) { content = Object.values(finalOutput)[0] } else { content = JSON.stringify(finalOutput, null, 2) } } else { // For base prompts, use output directly content = typeof result.output === 'string' ? result.output : JSON.stringify(result.output, null, 2) } // Build metadata from debug info (same logic as backend) let metadata = null if (result.debug && result.debug.resolved_placeholders) { const placeholders = {} const resolved = result.debug.resolved_placeholders // For pipeline, collect from all stages if (result.type === 'pipeline' && result.debug.stages) { for (const stage of result.debug.stages) { for (const promptDebug of (stage.prompts || [])) { const stageResolved = promptDebug.resolved_placeholders || promptDebug.ref_debug?.resolved_placeholders || {} for (const [key, value] of Object.entries(stageResolved)) { if (!placeholders[key]) { placeholders[key] = { value, description: '' } } } } } } else { // For base prompts for (const [key, value] of Object.entries(resolved)) { placeholders[key] = { value, description: '' } } } if (Object.keys(placeholders).length > 0) { metadata = { prompt_type: result.type, placeholders } } } setNewResult({ scope: slug, content, metadata }) await loadAll() setTab('run') } catch(e) { setError('Fehler: ' + e.message) } finally { setLoading(null) } } const deleteInsight = async (id) => { if (!confirm('Analyse löschen?')) return try { await api.deleteInsight(id) if (newResult?.id === id) setNewResult(null) await loadAll() } catch (e) { setError('Löschen fehlgeschlagen: ' + e.message) } } // Group insights by scope for history view const grouped = {} allInsights.forEach(ins => { const key = ins.scope || 'sonstige' grouped[key] = grouped[key] || [] grouped[key].push(ins) }) // Show only active pipeline-type prompts const pipelinePrompts = prompts.filter(p => p.active && p.type === 'pipeline') return (

KI-Analyse

{error && (
{error.includes('nicht aktiviert') || error.includes('Limit') ? <>🔒 KI-Zugang eingeschränkt
Dein Profil hat keinen Zugang zu KI-Analysen oder das Tageslimit wurde erreicht. Bitte den Admin kontaktieren. : error}
)} {/* ── Analysen starten ── */} {tab==='run' && (
{/* Fresh result shown immediately */} {newResult && (
âś… Neue Analyse erstellt:
)} {!canUseAI && (
đź”’ KI-Analysen nicht freigeschaltet
Dein Profil hat keinen Zugang zu KI-Analysen. Bitte den Admin bitten, KI für dein Profil zu aktivieren (Einstellungen → Admin → Profil bearbeiten).
)} {canUseAI && pipelinePrompts.length > 0 && (

Wähle eine mehrstufige KI-Analyse:

)} {pipelinePrompts.map(p => { const existing = allInsights.find(i=>i.scope===p.slug) return (
{p.display_name || SLUG_LABELS[p.slug] || p.name} {aiUsage && }
{p.description && (
{p.description}
)} {existing && (
Letzte Analyse: {dayjs(existing.created).format('DD.MM.YYYY, HH:mm')}
)}
{/* Show existing result collapsed */} {existing && newResult?.id !== existing.id && (
)}
) })} {canUseAI && pipelinePrompts.length === 0 && (

Keine aktiven Pipeline-Prompts verfĂĽgbar.

Erstelle Pipeline-Prompts im Admin-Bereich (Einstellungen → Admin → KI-Prompts).

)}
)} {/* ── Verlauf gruppiert ── */} {tab==='history' && (
{allInsights.length===0 ?

Noch keine Analysen

: Object.entries(grouped).map(([scope, ins]) => (
{prompts.find(p => p.slug === scope)?.display_name || SLUG_LABELS[scope] || scope} ({ins.length})
{ins.map(i => )}
)) }
)}
) }