fix: drei kritische Bugs in KI-Analysen behoben
All checks were successful
Deploy Development / deploy (push) Successful in 57s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 13s

PROBLEM 1: Deaktivierte Prompts auf Verlaufsseiten sichtbar
- Deaktivierte Analysen (z.B. "Komposition") wurden auf Verlaufsseiten
  (Körper, Ernährung, etc.) als klickbare Buttons angezeigt
FIX:
- Prompts werden jetzt in History.jsx geladen (api.listPrompts)
- filterActiveSlugs() filtert nur aktive Prompts
- InsightBox zeigt nur Buttons für aktive Analysen

PROBLEM 2: Pipeline konnte nicht deaktiviert werden
- Mehrstufige Gesamtanalyse war immer sichtbar
FIX:
- Pipeline ist nur verfügbar wenn ALLE Sub-Prompts aktiv sind
- Prüft: pipeline_body, pipeline_nutrition, pipeline_activity,
  pipeline_synthesis, pipeline_goals
- Deaktiviere einen Sub-Prompt → Pipeline verschwindet

PROBLEM 3: Fehler "z.text is not a function"
- Nach Analyse-Ausführung auf Verlaufsseiten kam Fehler
- Code behandelte api.runInsight() wie fetch()-Response
FIX:
- api.runInsight() gibt bereits JSON zurück, nicht Response
- Entfernte fehlerhafte if(!r.ok) und await r.text()
- Error-Handling wie in Analysis.jsx (catch e.message)

DATEIEN:
- frontend/src/pages/History.jsx: alle 3 Fixes
- frontend/src/pages/Analysis.jsx: Pipeline-Verfügbarkeit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-19 07:47:59 +01:00
parent 3f4ef75463
commit 961f905279
2 changed files with 57 additions and 42 deletions

View File

@ -179,6 +179,10 @@ export default function Analysis() {
const activePrompts = prompts.filter(p=>p.active && !p.slug.startsWith('pipeline_'))
// Pipeline is available only if ALL pipeline sub-prompts are active
const pipelineSlugs = ['pipeline_body','pipeline_nutrition','pipeline_activity','pipeline_synthesis','pipeline_goals']
const pipelineAvailable = pipelineSlugs.every(slug => prompts.find(p=>p.slug===slug)?.active)
return (
<div>
<h1 className="page-title">KI-Analyse</h1>
@ -221,7 +225,8 @@ export default function Analysis() {
</div>
)}
{/* Pipeline button */}
{/* Pipeline button - only if all sub-prompts are active */}
{pipelineAvailable && (
<div className="card" style={{marginBottom:16,borderColor:'var(--accent)',borderWidth:2}}>
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
<div style={{flex:1}}>
@ -251,6 +256,7 @@ export default function Analysis() {
</div>
)}
</div>
)}
{!canUseAI && (
<div style={{padding:'14px 16px',background:'#FCEBEB',borderRadius:10,

View File

@ -147,7 +147,7 @@ function PeriodSelector({ value, onChange }) {
}
// Body Section (Weight + Composition combined)
function BodySection({ weights, calipers, circs, profile, insights, onRequest, loadingSlug }) {
function BodySection({ weights, calipers, circs, profile, insights, onRequest, loadingSlug, filterActiveSlugs }) {
const [period, setPeriod] = useState(90)
const sex = profile?.sex||'m'
const height = profile?.height||178
@ -394,14 +394,14 @@ function BodySection({ weights, calipers, circs, profile, insights, onRequest, l
</div>
)}
<InsightBox insights={insights} slugs={['pipeline','koerper','gesundheit','ziele']}
<InsightBox insights={insights} slugs={filterActiveSlugs(['pipeline','koerper','gesundheit','ziele'])}
onRequest={onRequest} loading={loadingSlug}/>
</div>
)
}
// Nutrition Section
function NutritionSection({ nutrition, weights, profile, insights, onRequest, loadingSlug }) {
function NutritionSection({ nutrition, weights, profile, insights, onRequest, loadingSlug, filterActiveSlugs }) {
const [period, setPeriod] = useState(30)
if (!nutrition?.length) return (
<EmptySection text="Noch keine Ernährungsdaten." to="/nutrition" toLabel="FDDB importieren"/>
@ -579,13 +579,13 @@ function NutritionSection({ nutrition, weights, profile, insights, onRequest, lo
<div style={{fontSize:12,fontWeight:600,color:'var(--text3)',marginBottom:8}}>BEWERTUNG</div>
{macroRules.map((item,i)=><RuleCard key={i} item={item}/>)}
</div>
<InsightBox insights={insights} slugs={['ernaehrung']} onRequest={onRequest} loading={loadingSlug}/>
<InsightBox insights={insights} slugs={filterActiveSlugs(['ernaehrung'])} onRequest={onRequest} loading={loadingSlug}/>
</div>
)
}
// Activity Section
function ActivitySection({ activities, insights, onRequest, loadingSlug }) {
function ActivitySection({ activities, insights, onRequest, loadingSlug, filterActiveSlugs }) {
const [period, setPeriod] = useState(30)
if (!activities?.length) return (
<EmptySection text="Noch keine Aktivitätsdaten." to="/activity" toLabel="Aktivität erfassen"/>
@ -657,13 +657,13 @@ function ActivitySection({ activities, insights, onRequest, loadingSlug }) {
<div style={{fontSize:12,fontWeight:600,color:'var(--text3)',marginBottom:8}}>BEWERTUNG</div>
{actRules.map((item,i)=><RuleCard key={i} item={item}/>)}
</div>
<InsightBox insights={insights} slugs={['aktivitaet']} onRequest={onRequest} loading={loadingSlug}/>
<InsightBox insights={insights} slugs={filterActiveSlugs(['aktivitaet'])} onRequest={onRequest} loading={loadingSlug}/>
</div>
)
}
// Correlation Section
function CorrelationSection({ corrData, insights, profile, onRequest, loadingSlug }) {
function CorrelationSection({ corrData, insights, profile, onRequest, loadingSlug, filterActiveSlugs }) {
const filtered = (corrData||[]).filter(d=>d.kcal&&d.weight)
if (filtered.length < 5) return (
<EmptySection text="Für Korrelationen werden Gewichts- und Ernährungsdaten benötigt (mind. 5 gemeinsame Tage)."/>
@ -852,7 +852,7 @@ function CorrelationSection({ corrData, insights, profile, onRequest, loadingSlu
</div>
)}
<InsightBox insights={insights} slugs={['gesamt','ziele']} onRequest={onRequest} loading={loadingSlug}/>
<InsightBox insights={insights} slugs={filterActiveSlugs(['gesamt','ziele'])} onRequest={onRequest} loading={loadingSlug}/>
</div>
)
}
@ -903,6 +903,7 @@ export default function History() {
const [activities, setActivities] = useState([])
const [corrData, setCorrData] = useState([])
const [insights, setInsights] = useState([])
const [prompts, setPrompts] = useState([])
const [profile, setProfile] = useState(null)
const [loading, setLoading] = useState(true)
const [loadingSlug,setLoadingSlug]= useState(null)
@ -911,10 +912,12 @@ export default function History() {
api.listWeight(365), api.listCaliper(), api.listCirc(),
api.listNutrition(90), api.listActivity(200),
api.nutritionCorrelations(), api.latestInsights(), api.getProfile(),
]).then(([w,ca,ci,n,a,corr,ins,p])=>{
api.listPrompts(),
]).then(([w,ca,ci,n,a,corr,ins,p,pr])=>{
setWeights(w); setCalipers(ca); setCircs(ci)
setNutrition(n); setActivities(a); setCorrData(corr)
setInsights(Array.isArray(ins)?ins:[]); setProfile(p)
setPrompts(Array.isArray(pr)?pr:[])
setLoading(false)
})
@ -923,17 +926,23 @@ export default function History() {
const requestInsight = async (slug) => {
setLoadingSlug(slug)
try {
const pid=localStorage.getItem('bodytrack_active_profile')||''
const r=await api.runInsight(slug)
if(!r.ok) throw new Error(await r.text())
const ins=await api.latestInsights()
const result = await api.runInsight(slug)
// result is already JSON, not a Response object
const ins = await api.latestInsights()
setInsights(Array.isArray(ins)?ins:[])
} catch(e){ alert('KI-Fehler: '+e.message) }
} catch(e){
alert('KI-Fehler: '+e.message)
}
finally{ setLoadingSlug(null) }
}
if(loading) return <div className="empty-state"><div className="spinner"/></div>
const sp={insights,onRequest:requestInsight,loadingSlug}
// Filter active prompts
const activeSlugs = prompts.filter(p=>p.active).map(p=>p.slug)
const filterActiveSlugs = (slugs) => slugs.filter(s=>activeSlugs.includes(s))
const sp={insights,onRequest:requestInsight,loadingSlug,filterActiveSlugs}
return (
<div>