kingame21/BioTech.ai
0
1import flet as ft2import threading3from groq import Groq4 5from state import AppState6from config import (7 CYAN, LIME, TEXT_MAIN, TEXT_GREY, BG_CARD, BG_RESALT, BORDER, GROQ_API_KEY8)9from views.allenamenti import show_scheda, show_feedback10 11 12def show_home(13 page: ft.Page,14 state: AppState,15 content_area: ft.Container,16 *,17 start_checkin_cb=None, # callback: start_checkin(...)18 show_allenamenti_cb=None, # callback: show_allenamenti(...)19):20 ultima = state.storico[-1] if state.storico else None21 n_sett = len(state.storico)22 23 # ---- PRIMA VOLTA ----24 if not ultima:25 content_area.content = ft.Column(26 [27 ft.Container(height=40),28 ft.Icon(ft.Icons.BOLT_ROUNDED, color=CYAN, size=60),29 ft.Text("BIOMECH AI", size=32, weight="bold", color=TEXT_MAIN),30 ft.Text("IL TUO COACH BIOMECCANICO", size=12, color=TEXT_GREY),31 ft.Container(height=40),32 ft.FilledButton(33 "INIZIA IL TUO PERCORSO",34 on_click=(lambda _: start_checkin_cb()) if callable(start_checkin_cb) else None,35 style=ft.ButtonStyle(36 bgcolor=CYAN,37 color="white",38 shape=ft.RoundedRectangleBorder(radius=15),39 ),40 width=280,41 height=55,42 ),43 ],44 horizontal_alignment=ft.CrossAxisAlignment.CENTER,45 )46 page.update()47 return48 49 # ---- DATI SETTIMANA ----50 completati = ultima.get("completati", [])51 giorni_totali = int(ultima["profilo"].get("giorni", 3))52 fatti = sum(completati) if completati else 053 prossimo_idx = next((i for i, c in enumerate(completati) if not c), None)54 55 scheda_text = ultima.get("scheda", "")56 parti = scheda_text.split("### GIORNO ")57 nomi_sessioni = []58 testo_giorni = []59 60 for p in parti[1:]:61 testo_giorni.append("### GIORNO " + p)62 righe = p.strip().split("\n")63 nome = ""64 for r in righe[1:3]:65 r = r.strip().replace("#", "").replace("*", "").strip()66 if r and not r.startswith("["):67 nome = r68 break69 nomi_sessioni.append(nome)70 71 # ---- CARD RECUPERO (come nel tuo originale) ----72 recupero_ref = [5]73 risposta_ai = ft.Text("", size=13, color=TEXT_GREY, italic=True, text_align=ft.TextAlign.CENTER)74 ring_recupero = ft.ProgressRing(visible=False, color=CYAN, width=20, height=20)75 76 def get_label(val: int):77 if val <= 2: return "Distrutto"78 if val <= 4: return "Stanco"79 if val <= 6: return "Nella norma"80 if val <= 8: return "Bene"81 return "Al top!"82 83 slider_label = ft.Text("5 - Nella norma", size=16, weight="bold", color=CYAN)84 85 def on_slider_change(e):86 val = int(e.control.value)87 recupero_ref[0] = val88 slider_label.value = f"{val} - {get_label(val)}"89 risposta_ai.value = ""90 page.update()91 92 def chiedi_ai_recupero(e=None):93 val = recupero_ref[0]94 ring_recupero.visible = True95 risposta_ai.value = ""96 page.update()97 98 def _call():99 try:100 client = Groq(api_key=GROQ_API_KEY)101 obiettivo = ultima["profilo"].get("obiettivo", "")102 prossimo_nome = (103 nomi_sessioni[prossimo_idx]104 if prossimo_idx is not None and prossimo_idx < len(nomi_sessioni)105 else "allenamento"106 )107 msg = (108 f"L'atleta si sente {val}/10 ({get_label(val)}). "109 f"Obiettivo: {obiettivo}. Prossimo workout: {prossimo_nome}. "110 "Consiglio breve (2-3 frasi): allenarsi o riposare?"111 )112 comp = client.chat.completions.create(113 model="llama-3.3-70b-versatile",114 messages=[{"role": "user", "content": msg}],115 temperature=0.4,116 max_tokens=150,117 )118 risposta_ai.value = comp.choices[0].message.content119 except Exception as ex:120 risposta_ai.value = f"Errore: {ex}"121 ring_recupero.visible = False122 page.update()123 124 threading.Thread(target=_call, daemon=True).start()125 126 # ---- BOTTONE “PROSSIMA SESSIONE” ----127 def apri_prossima():128 if prossimo_idx is None:129 return130 testo = (131 testo_giorni[prossimo_idx]132 if prossimo_idx < len(testo_giorni)133 else scheda_text134 )135 show_scheda(136 page,137 state,138 content_area,139 testo,140 prossimo_idx + 1,141 da_home=True,142 idx_giorno=prossimo_idx,143 torna_home=lambda: show_home(144 page, state, content_area,145 start_checkin_cb=start_checkin_cb,146 show_allenamenti_cb=show_allenamenti_cb147 ),148 torna_allenamenti=(lambda: show_allenamenti_cb()) if callable(show_allenamenti_cb) else None,149 apri_feedback=lambda idx, torna_a_home: show_feedback(150 page, state, content_area, idx,151 torna_a_home=torna_a_home,152 torna_home=lambda: show_home(153 page, state, content_area,154 start_checkin_cb=start_checkin_cb,155 show_allenamenti_cb=show_allenamenti_cb156 ),157 torna_allenamenti=(lambda: show_allenamenti_cb()) if callable(show_allenamenti_cb) else None,158 ),159 )160 161 if prossimo_idx is not None:162 nome = nomi_sessioni[prossimo_idx] if prossimo_idx < len(nomi_sessioni) else ""163 btn_prossimo = ft.Container(164 content=ft.Column(165 [166 ft.Text("PROSSIMA SESSIONE", size=10, weight="bold", color="white"),167 ft.Text(f"Giorno {prossimo_idx + 1}", size=22, weight="bold", color="white"),168 ft.Text(nome.upper() if nome else "WORKOUT", size=14, color="white"),169 ],170 spacing=2,171 ),172 padding=25,173 bgcolor=CYAN,174 border_radius=25,175 on_click=lambda _: apri_prossima(),176 ink=True,177 )178 else:179 btn_prossimo = ft.Container(180 content=ft.Row(181 [182 ft.Icon(ft.Icons.CHECK_CIRCLE_OUTLINE, color="white"),183 ft.Text("SETTIMANA COMPLETATA!", size=16, weight="bold", color="white"),184 ],185 alignment=ft.MainAxisAlignment.CENTER,186 ),187 padding=25,188 bgcolor=LIME,189 border_radius=25,190 on_click=(lambda _: start_checkin_cb()) if callable(start_checkin_cb) else None,191 ink=True,192 )193 194 # ---- STATS ----195 perc = fatti / giorni_totali if giorni_totali > 0 else 0196 stelle = ("* " * fatti + "o " * (giorni_totali - fatti)).strip()197 198 card_stats = ft.Container(199 content=ft.Column(200 [201 ft.Row(202 [203 ft.Column(204 [205 ft.Text("SETTIMANA", size=10, color=TEXT_GREY),206 ft.Text(str(n_sett), size=20, weight="bold", color=TEXT_MAIN),207 ],208 expand=1,209 ),210 ft.Column(211 [212 ft.Text("PROGRESSO", size=10, color=TEXT_GREY),213 ft.Text(f"{fatti}/{giorni_totali}", size=20, weight="bold", color=TEXT_MAIN),214 ],215 expand=1,216 horizontal_alignment=ft.CrossAxisAlignment.CENTER,217 ),218 ft.Column(219 [220 ft.Text("PESO", size=10, color=TEXT_GREY),221 ft.Text(f"{ultima['profilo'].get('peso')}kg", size=20, weight="bold", color=TEXT_MAIN),222 ],223 expand=1,224 horizontal_alignment=ft.CrossAxisAlignment.END,225 ),226 ]227 ),228 ft.ProgressBar(value=perc, color=CYAN, bgcolor=BG_RESALT, height=8, border_radius=10),229 ft.Text(stelle, size=18, color=CYAN, text_align=ft.TextAlign.CENTER),230 ],231 spacing=15,232 ),233 padding=20,234 bgcolor=BG_CARD,235 border_radius=20,236 border=ft.Border.all(1, BORDER),237 )238 239 card_recupero = ft.Container(240 content=ft.Column(241 [242 ft.Text("STATUS RECUPERO", size=10, weight="bold", color=TEXT_GREY),243 ft.Row([slider_label, ft.Container(expand=True), ring_recupero]),244 ft.Slider(min=1, max=10, divisions=9, value=5, on_change=on_slider_change, active_color=CYAN),245 ft.FilledButton(246 "Chiedi consiglio all'AI",247 on_click=chiedi_ai_recupero,248 style=ft.ButtonStyle(249 bgcolor=BG_RESALT,250 color=CYAN,251 shape=ft.RoundedRectangleBorder(radius=10),252 ),253 height=40,254 ),255 risposta_ai,256 ],257 spacing=10,258 ),259 padding=20,260 bgcolor=BG_CARD,261 border_radius=20,262 border=ft.Border.all(1, BORDER),263 )264 265 content_area.content = ft.Column(266 [267 ft.Text("DASHBOARD", size=24, weight="bold", color=TEXT_MAIN),268 btn_prossimo,269 card_stats,270 card_recupero,271 ],272 spacing=20,273 scroll=ft.ScrollMode.ADAPTIVE,274 )275 page.update()