Version 9b #1
|
|
@ -179,6 +179,10 @@ export default function Analysis() {
|
||||||
|
|
||||||
const activePrompts = prompts.filter(p=>p.active && !p.slug.startsWith('pipeline_'))
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title">KI-Analyse</h1>
|
<h1 className="page-title">KI-Analyse</h1>
|
||||||
|
|
@ -221,36 +225,38 @@ export default function Analysis() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Pipeline button */}
|
{/* Pipeline button - only if all sub-prompts are active */}
|
||||||
<div className="card" style={{marginBottom:16,borderColor:'var(--accent)',borderWidth:2}}>
|
{pipelineAvailable && (
|
||||||
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
|
<div className="card" style={{marginBottom:16,borderColor:'var(--accent)',borderWidth:2}}>
|
||||||
<div style={{flex:1}}>
|
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
|
||||||
<div style={{fontWeight:700,fontSize:15,color:'var(--accent)'}}>🔬 Mehrstufige Gesamtanalyse</div>
|
<div style={{flex:1}}>
|
||||||
<div style={{fontSize:12,color:'var(--text2)',marginTop:3,lineHeight:1.5}}>
|
<div style={{fontWeight:700,fontSize:15,color:'var(--accent)'}}>🔬 Mehrstufige Gesamtanalyse</div>
|
||||||
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
|
<div style={{fontSize:12,color:'var(--text2)',marginTop:3,lineHeight:1.5}}>
|
||||||
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
|
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
|
||||||
</div>
|
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
|
||||||
{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>
|
||||||
)}
|
{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>
|
</div>
|
||||||
<button className="btn btn-primary" style={{flexShrink:0,minWidth:100}}
|
{pipelineLoading && (
|
||||||
onClick={runPipeline} disabled={!!loading||pipelineLoading}>
|
<div style={{marginTop:10,padding:'8px 12px',background:'var(--accent-light)',
|
||||||
{pipelineLoading
|
borderRadius:8,fontSize:12,color:'var(--accent-dark)'}}>
|
||||||
? <><div className="spinner" style={{width:13,height:13}}/> Läuft…</>
|
⚡ Stufe 1: 3 parallele Analyse-Calls… dann Synthese… dann Zielabgleich
|
||||||
: <><Brain size={13}/> Starten</>}
|
</div>
|
||||||
</button>
|
)}
|
||||||
{!canUseAI && <div style={{fontSize:11,color:'#D85A30',marginTop:4}}>🔒 KI nicht freigeschaltet</div>}
|
|
||||||
</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 && (
|
{!canUseAI && (
|
||||||
<div style={{padding:'14px 16px',background:'#FCEBEB',borderRadius:10,
|
<div style={{padding:'14px 16px',background:'#FCEBEB',borderRadius:10,
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ function PeriodSelector({ value, onChange }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Body Section (Weight + Composition combined) ──────────────────────────────
|
// ── 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 [period, setPeriod] = useState(90)
|
||||||
const sex = profile?.sex||'m'
|
const sex = profile?.sex||'m'
|
||||||
const height = profile?.height||178
|
const height = profile?.height||178
|
||||||
|
|
@ -394,14 +394,14 @@ function BodySection({ weights, calipers, circs, profile, insights, onRequest, l
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<InsightBox insights={insights} slugs={['pipeline','koerper','gesundheit','ziele']}
|
<InsightBox insights={insights} slugs={filterActiveSlugs(['pipeline','koerper','gesundheit','ziele'])}
|
||||||
onRequest={onRequest} loading={loadingSlug}/>
|
onRequest={onRequest} loading={loadingSlug}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Nutrition Section ─────────────────────────────────────────────────────────
|
// ── Nutrition Section ─────────────────────────────────────────────────────────
|
||||||
function NutritionSection({ nutrition, weights, profile, insights, onRequest, loadingSlug }) {
|
function NutritionSection({ nutrition, weights, profile, insights, onRequest, loadingSlug, filterActiveSlugs }) {
|
||||||
const [period, setPeriod] = useState(30)
|
const [period, setPeriod] = useState(30)
|
||||||
if (!nutrition?.length) return (
|
if (!nutrition?.length) return (
|
||||||
<EmptySection text="Noch keine Ernährungsdaten." to="/nutrition" toLabel="FDDB importieren"/>
|
<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>
|
<div style={{fontSize:12,fontWeight:600,color:'var(--text3)',marginBottom:8}}>BEWERTUNG</div>
|
||||||
{macroRules.map((item,i)=><RuleCard key={i} item={item}/>)}
|
{macroRules.map((item,i)=><RuleCard key={i} item={item}/>)}
|
||||||
</div>
|
</div>
|
||||||
<InsightBox insights={insights} slugs={['ernaehrung']} onRequest={onRequest} loading={loadingSlug}/>
|
<InsightBox insights={insights} slugs={filterActiveSlugs(['ernaehrung'])} onRequest={onRequest} loading={loadingSlug}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Activity Section ──────────────────────────────────────────────────────────
|
// ── Activity Section ──────────────────────────────────────────────────────────
|
||||||
function ActivitySection({ activities, insights, onRequest, loadingSlug }) {
|
function ActivitySection({ activities, insights, onRequest, loadingSlug, filterActiveSlugs }) {
|
||||||
const [period, setPeriod] = useState(30)
|
const [period, setPeriod] = useState(30)
|
||||||
if (!activities?.length) return (
|
if (!activities?.length) return (
|
||||||
<EmptySection text="Noch keine Aktivitätsdaten." to="/activity" toLabel="Aktivität erfassen"/>
|
<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>
|
<div style={{fontSize:12,fontWeight:600,color:'var(--text3)',marginBottom:8}}>BEWERTUNG</div>
|
||||||
{actRules.map((item,i)=><RuleCard key={i} item={item}/>)}
|
{actRules.map((item,i)=><RuleCard key={i} item={item}/>)}
|
||||||
</div>
|
</div>
|
||||||
<InsightBox insights={insights} slugs={['aktivitaet']} onRequest={onRequest} loading={loadingSlug}/>
|
<InsightBox insights={insights} slugs={filterActiveSlugs(['aktivitaet'])} onRequest={onRequest} loading={loadingSlug}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Correlation Section ───────────────────────────────────────────────────────
|
// ── 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)
|
const filtered = (corrData||[]).filter(d=>d.kcal&&d.weight)
|
||||||
if (filtered.length < 5) return (
|
if (filtered.length < 5) return (
|
||||||
<EmptySection text="Für Korrelationen werden Gewichts- und Ernährungsdaten benötigt (mind. 5 gemeinsame Tage)."/>
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<InsightBox insights={insights} slugs={['gesamt','ziele']} onRequest={onRequest} loading={loadingSlug}/>
|
<InsightBox insights={insights} slugs={filterActiveSlugs(['gesamt','ziele'])} onRequest={onRequest} loading={loadingSlug}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -903,6 +903,7 @@ export default function History() {
|
||||||
const [activities, setActivities] = useState([])
|
const [activities, setActivities] = useState([])
|
||||||
const [corrData, setCorrData] = useState([])
|
const [corrData, setCorrData] = useState([])
|
||||||
const [insights, setInsights] = useState([])
|
const [insights, setInsights] = useState([])
|
||||||
|
const [prompts, setPrompts] = useState([])
|
||||||
const [profile, setProfile] = useState(null)
|
const [profile, setProfile] = useState(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [loadingSlug,setLoadingSlug]= useState(null)
|
const [loadingSlug,setLoadingSlug]= useState(null)
|
||||||
|
|
@ -911,10 +912,12 @@ export default function History() {
|
||||||
api.listWeight(365), api.listCaliper(), api.listCirc(),
|
api.listWeight(365), api.listCaliper(), api.listCirc(),
|
||||||
api.listNutrition(90), api.listActivity(200),
|
api.listNutrition(90), api.listActivity(200),
|
||||||
api.nutritionCorrelations(), api.latestInsights(), api.getProfile(),
|
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)
|
setWeights(w); setCalipers(ca); setCircs(ci)
|
||||||
setNutrition(n); setActivities(a); setCorrData(corr)
|
setNutrition(n); setActivities(a); setCorrData(corr)
|
||||||
setInsights(Array.isArray(ins)?ins:[]); setProfile(p)
|
setInsights(Array.isArray(ins)?ins:[]); setProfile(p)
|
||||||
|
setPrompts(Array.isArray(pr)?pr:[])
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -923,17 +926,23 @@ export default function History() {
|
||||||
const requestInsight = async (slug) => {
|
const requestInsight = async (slug) => {
|
||||||
setLoadingSlug(slug)
|
setLoadingSlug(slug)
|
||||||
try {
|
try {
|
||||||
const pid=localStorage.getItem('bodytrack_active_profile')||''
|
const result = await api.runInsight(slug)
|
||||||
const r=await api.runInsight(slug)
|
// result is already JSON, not a Response object
|
||||||
if(!r.ok) throw new Error(await r.text())
|
const ins = await api.latestInsights()
|
||||||
const ins=await api.latestInsights()
|
|
||||||
setInsights(Array.isArray(ins)?ins:[])
|
setInsights(Array.isArray(ins)?ins:[])
|
||||||
} catch(e){ alert('KI-Fehler: '+e.message) }
|
} catch(e){
|
||||||
|
alert('KI-Fehler: '+e.message)
|
||||||
|
}
|
||||||
finally{ setLoadingSlug(null) }
|
finally{ setLoadingSlug(null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if(loading) return <div className="empty-state"><div className="spinner"/></div>
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user