fix: drei kritische Bugs in KI-Analysen behoben
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:
parent
3f4ef75463
commit
961f905279
|
|
@ -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,36 +225,38 @@ export default function Analysis() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Pipeline button */}
|
||||
<div className="card" style={{marginBottom:16,borderColor:'var(--accent)',borderWidth:2}}>
|
||||
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
|
||||
<div style={{flex:1}}>
|
||||
<div style={{fontWeight:700,fontSize:15,color:'var(--accent)'}}>🔬 Mehrstufige Gesamtanalyse</div>
|
||||
<div style={{fontSize:12,color:'var(--text2)',marginTop:3,lineHeight:1.5}}>
|
||||
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
|
||||
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
|
||||
</div>
|
||||
{allInsights.find(i=>i.scope==='pipeline') && (
|
||||
<div style={{fontSize:11,color:'var(--text3)',marginTop:3}}>
|
||||
Letzte Analyse: {dayjs(allInsights.find(i=>i.scope==='pipeline').created).format('DD.MM.YYYY, HH:mm')}
|
||||
{/* 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}}>
|
||||
<div style={{fontWeight:700,fontSize:15,color:'var(--accent)'}}>🔬 Mehrstufige Gesamtanalyse</div>
|
||||
<div style={{fontSize:12,color:'var(--text2)',marginTop:3,lineHeight:1.5}}>
|
||||
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
|
||||
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
|
||||
</div>
|
||||
)}
|
||||
{allInsights.find(i=>i.scope==='pipeline') && (
|
||||
<div style={{fontSize:11,color:'var(--text3)',marginTop:3}}>
|
||||
Letzte Analyse: {dayjs(allInsights.find(i=>i.scope==='pipeline').created).format('DD.MM.YYYY, HH:mm')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button className="btn btn-primary" style={{flexShrink:0,minWidth:100}}
|
||||
onClick={runPipeline} disabled={!!loading||pipelineLoading}>
|
||||
{pipelineLoading
|
||||
? <><div className="spinner" style={{width:13,height:13}}/> Läuft…</>
|
||||
: <><Brain size={13}/> Starten</>}
|
||||
</button>
|
||||
{!canUseAI && <div style={{fontSize:11,color:'#D85A30',marginTop:4}}>🔒 KI nicht freigeschaltet</div>}
|
||||
</div>
|
||||
<button className="btn btn-primary" style={{flexShrink:0,minWidth:100}}
|
||||
onClick={runPipeline} disabled={!!loading||pipelineLoading}>
|
||||
{pipelineLoading
|
||||
? <><div className="spinner" style={{width:13,height:13}}/> Läuft…</>
|
||||
: <><Brain size={13}/> Starten</>}
|
||||
</button>
|
||||
{!canUseAI && <div style={{fontSize:11,color:'#D85A30',marginTop:4}}>🔒 KI nicht freigeschaltet</div>}
|
||||
{pipelineLoading && (
|
||||
<div style={{marginTop:10,padding:'8px 12px',background:'var(--accent-light)',
|
||||
borderRadius:8,fontSize:12,color:'var(--accent-dark)'}}>
|
||||
⚡ Stufe 1: 3 parallele Analyse-Calls… dann Synthese… dann Zielabgleich
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{pipelineLoading && (
|
||||
<div style={{marginTop:10,padding:'8px 12px',background:'var(--accent-light)',
|
||||
borderRadius:8,fontSize:12,color:'var(--accent-dark)'}}>
|
||||
⚡ Stufe 1: 3 parallele Analyse-Calls… dann Synthese… dann Zielabgleich
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!canUseAI && (
|
||||
<div style={{padding:'14px 16px',background:'#FCEBEB',borderRadius:10,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user