import { useState, useEffect } from 'react'
import { Brain, Pencil, Trash2, ChevronDown, ChevronUp, Check, X } 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')
const SLUG_LABELS = {
gesamt: '🔍 Gesamtanalyse',
koerper: '🫧 Körperkomposition',
ernaehrung: '🍽️ Ernährung',
aktivitaet: '🏋️ Aktivität',
gesundheit: '❤️ Gesundheitsindikatoren',
ziele: '🎯 Zielfortschritt',
pipeline: '🔬 Mehrstufige Gesamtanalyse',
pipeline_body: '🔬 Pipeline: Körper-Analyse (JSON)',
pipeline_nutrition: '🔬 Pipeline: Ernährungs-Analyse (JSON)',
pipeline_activity: '🔬 Pipeline: Aktivitäts-Analyse (JSON)',
pipeline_synthesis: '🔬 Pipeline: Synthese',
pipeline_goals: '🔬 Pipeline: Zielabgleich',
}
function InsightCard({ ins, onDelete, defaultOpen=false }) {
const [open, setOpen] = useState(defaultOpen)
return (
setOpen(o=>!o)}>
{ins.display_name || SLUG_LABELS[ins.scope] || ins.scope}
{dayjs(ins.created).format('DD. MMMM YYYY, HH:mm')}
{e.stopPropagation();onDelete(ins.id)}}>
{open ?
:
}
{open &&
}
)
}
function PromptEditor({ prompt, onSave, onCancel }) {
const [template, setTemplate] = useState(prompt.template)
const [name, setName] = useState(prompt.name)
const [desc, setDesc] = useState(prompt.description||'')
const VARS = ['{{name}}','{{geschlecht}}','{{height}}','{{goal_weight}}','{{goal_bf_pct}}',
'{{weight_trend}}','{{weight_aktuell}}','{{kf_aktuell}}','{{caliper_summary}}',
'{{circ_summary}}','{{nutrition_summary}}','{{nutrition_detail}}',
'{{protein_ziel_low}}','{{protein_ziel_high}}','{{activity_summary}}',
'{{activity_kcal_summary}}','{{activity_detail}}',
'{{sleep_summary}}','{{sleep_detail}}','{{sleep_avg_duration}}','{{sleep_avg_quality}}',
'{{rest_days_summary}}','{{rest_days_count}}','{{rest_days_types}}',
'{{vitals_summary}}','{{vitals_detail}}','{{vitals_avg_hr}}','{{vitals_avg_hrv}}',
'{{vitals_avg_bp}}','{{vitals_vo2_max}}','{{bp_summary}}']
return (
)
}
export default function Analysis() {
const { canUseAI, isAdmin } = useAuth()
const [prompts, setPrompts] = useState([])
const [allInsights, setAllInsights] = useState([])
const [loading, setLoading] = useState(null)
const [error, setError] = useState(null)
const [editing, setEditing] = useState(null)
const [tab, setTab] = useState('run')
const [newResult, setNewResult] = useState(null)
const [pipelineLoading, setPipelineLoading] = useState(false)
const [aiUsage, setAiUsage] = useState(null) // Phase 3: Usage badge
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 runPipeline = async () => {
setPipelineLoading(true); setError(null); setNewResult(null)
try {
const result = await api.insightPipeline()
setNewResult(result)
await loadAll()
setTab('run')
} catch(e) {
setError('Pipeline-Fehler: ' + e.message)
} finally { setPipelineLoading(false) }
}
const runPrompt = async (slug) => {
setLoading(slug); setError(null); setNewResult(null)
try {
const result = await api.runInsight(slug)
setNewResult(result) // show immediately
await loadAll() // refresh lists
setTab('run') // stay on run tab to see result
} catch(e) {
setError('Fehler: ' + e.message)
} finally { setLoading(null) }
}
const savePrompt = async (promptId, data) => {
const token = localStorage.getItem('bodytrack_token')||''
await fetch(`/api/prompts/${promptId}`, {
method:'PUT',
headers:{'Content-Type':'application/json', 'X-Auth-Token': token},
body:JSON.stringify(data)
})
setEditing(null); await loadAll()
}
const deleteInsight = async (id) => {
if (!confirm('Analyse löschen?')) return
const pid = localStorage.getItem('bodytrack_active_profile')||''
await fetch(`/api/insights/${id}`, {
method:'DELETE', headers: pid ? {'X-Profile-Id':pid} : {}
})
if (newResult?.id === id) setNewResult(null)
await loadAll()
}
// 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)
})
const activePrompts = prompts.filter(p=>p.active && !p.slug.startsWith('pipeline_') && p.slug !== 'pipeline')
// Pipeline is available if the "pipeline" prompt is active
const pipelinePrompt = prompts.find(p=>p.slug==='pipeline')
const pipelineAvailable = pipelinePrompt?.active ?? true // Default to true if not found (backwards compatibility)
return (
KI-Analyse
setTab('run')}>Analysen starten
setTab('history')}>
Verlauf
{allInsights.length>0 && {allInsights.length} }
{isAdmin && setTab('prompts')}>Prompts }
{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:
)}
{/* Pipeline button - only if all sub-prompts are active */}
{pipelineAvailable && (
🔬 Mehrstufige Gesamtanalyse
{aiUsage && }
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
{allInsights.find(i=>i.scope==='pipeline') && (
Letzte Analyse: {dayjs(allInsights.find(i=>i.scope==='pipeline').created).format('DD.MM.YYYY, HH:mm')}
)}
{pipelineLoading
? <> Läuft…>
: (aiUsage && !aiUsage.allowed) ? 'đź”’ Limit'
: <> Starten>}
{!canUseAI && đź”’ KI nicht freigeschaltet
}
{pipelineLoading && (
⚡ Stufe 1: 3 parallele Analyse-Calls… dann Synthese… dann Zielabgleich
)}
)}
{!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 &&
Oder wähle eine Einzelanalyse:
}
{activePrompts.map(p => {
// Show latest existing insight for this prompt
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 Auswertung: {dayjs(existing.created).format('DD.MM.YYYY, HH:mm')}
)}
runPrompt(p.slug)}
disabled={!!loading||!canUseAI||(aiUsage && !aiUsage.allowed)}
>
{loading===p.slug
? <> Läuft…>
: (aiUsage && !aiUsage.allowed) ? 'đź”’ Limit'
: <> {existing?'Neu erstellen':'Starten'}>}
{/* Show existing result collapsed */}
{existing && newResult?.id !== existing.id && (
)}
)
})}
{activePrompts.length===0 && (
Keine aktiven Prompts. Aktiviere im Tab "Prompts".
)}
)}
{/* ── Verlauf gruppiert ── */}
{tab==='history' && (
{allInsights.length===0
?
Noch keine Analysen
: Object.entries(grouped).map(([scope, ins]) => (
{SLUG_LABELS[scope]||scope} ({ins.length})
{ins.map(i =>
)}
))
}
)}
{/* ── Prompts ── */}
{tab==='prompts' && (
Passe Prompts an. Variablen wie{' '}
{'{{name}}'}{' '}
werden automatisch mit deinen Daten befĂĽllt.
{editing ? (
savePrompt(editing.id,data)}
onCancel={()=>setEditing(null)}/>
) : (() => {
const singlePrompts = prompts.filter(p=>!p.slug.startsWith('pipeline_'))
const pipelinePrompts = prompts.filter(p=>p.slug.startsWith('pipeline_'))
const jsonSlugs = ['pipeline_body','pipeline_nutrition','pipeline_activity']
return (
<>
{/* Single prompts */}
Einzelanalysen
{singlePrompts.map(p=>(
{p.display_name || SLUG_LABELS[p.slug] || p.name}
{!p.active && ⏸ Deaktiviert }
{p.description &&
{p.description}
}
{
const token = localStorage.getItem('bodytrack_token')||''
fetch(`/api/prompts/${p.id}`,{
method:'PUT',
headers:{'Content-Type':'application/json','X-Auth-Token':token},
body:JSON.stringify({active:!p.active})
}).then(loadAll)
}}>
{p.active?'Deaktivieren':'Aktivieren'}
setEditing(p)}>
{p.template.slice(0,200)}…
))}
{/* Pipeline prompts */}
Mehrstufige Pipeline
{(() => {
const pipelinePrompt = prompts.find(p=>p.slug==='pipeline')
return pipelinePrompt && (
{
const token = localStorage.getItem('bodytrack_token')||''
fetch(`/api/prompts/${pipelinePrompt.id}`,{
method:'PUT',
headers:{'Content-Type':'application/json','X-Auth-Token':token},
body:JSON.stringify({active:!pipelinePrompt.active})
}).then(loadAll)
}}>
{pipelinePrompt.active ? 'Gesamte Pipeline deaktivieren' : 'Gesamte Pipeline aktivieren'}
)
})()}
{(() => {
const pipelinePrompt = prompts.find(p=>p.slug==='pipeline')
const isPipelineActive = pipelinePrompt?.active ?? true
return (
{isPipelineActive ? (
<>⚠️ Hinweis: Pipeline-Stage-1-Prompts müssen valides JSON zurückgeben.
Halte das JSON-Format im Prompt erhalten. Stage 2 + 3 können frei angepasst werden.>
) : (
<>⏸ Pipeline deaktiviert: Die mehrstufige Gesamtanalyse ist aktuell nicht verfügbar.
Aktiviere sie mit dem Schalter oben, um sie auf der Analyse-Seite zu nutzen.>
)}
)
})()}
{pipelinePrompts.map(p=>{
const isJson = jsonSlugs.includes(p.slug)
return (
{p.name}
{isJson && JSON-Output }
{!p.active && ⏸ Deaktiviert }
{p.description &&
{p.description}
}
setEditing(p)}>
{p.template.slice(0,300)}…
)
})}
>
)
})()}
)}
)
}