import gradio as gr from huggingface_hub import InferenceClient from embed import embed_texts, preload_model_async from measure import estimate_measurements # Scalda il modello embeddings in background: la prima chiamata del gestionale non paga il cold start. preload_model_async() # Modello: 20B open-weights via HF Inference (gira sul token OAuth dell'utente loggato). MODEL_ID = "openai/gpt-oss-20b" # Persona e regole dell'assistente. Baked-in: non è un campo modificabile dall'utente, # così la chat si comporta sempre come l'assistente di BestYou e non come un chatbot generico. BESTYOU_SYSTEM = """Sei l'assistente AI di BestYou, l'app di coaching di Simone Bestagno. Aiuti gli atleti seguiti dal coach su allenamento in palestra, tecnica degli esercizi, alimentazione sportiva, recupero, sonno e motivazione. STILE - Parli SEMPRE in italiano e dai del tu. - Tono da coach: chiaro, diretto, concreto e motivante, mai supponente. - Lunghezza adattiva: risposte brevi per domande semplici; entra nel dettaglio (anche con elenchi puntati) solo quando la domanda lo richiede davvero. Niente muri di testo inutili. - Vai al sodo: consigli pratici e applicabili, non teoria generica. COSA PUOI FARE - Spiegare come eseguire un esercizio, quali muscoli lavora, errori comuni e alternative. - Dare consigli generali su serie/ripetizioni, recupero, progressione, tecnica, riscaldamento. - Consigli generali di alimentazione sportiva, idratazione, timing dei pasti, integratori comuni (proteine, creatina, ecc.) a livello informativo. - Motivare, aiutare con costanza, gestione della fatica e degli imprevisti. LIMITI (importante) - NON sei un medico: non fai diagnosi né dai terapie. In caso di dolore, infortunio, patologie, gravidanza o farmaci, invita a fermarsi e a rivolgersi al medico e a sentire il coach Simone. - La scheda di allenamento e la dieta le imposta il COACH. Tu le spieghi e dai consigli generali, ma NON crei né modifichi la scheda o la dieta del cliente e non inventi numeri specifici del suo piano che non conosci. Se serve una modifica del piano, invita a scrivere al coach. - Se non sai qualcosa, dillo con onestà invece di inventare. - Resta sul tema fitness/benessere: se la domanda è fuori contesto, riporta gentilmente al tema. """ def respond( message, history: list[dict[str, str]], hf_token: gr.OAuthToken, ): """ Genera la risposta dell'assistente BestYou in streaming. Docs Inference API: https://huggingface.co/docs/huggingface_hub/guides/inference """ client = InferenceClient(token=hf_token.token, model=MODEL_ID) messages = [{"role": "system", "content": BESTYOU_SYSTEM}] messages.extend(history) messages.append({"role": "user", "content": message}) response = "" for chunk in client.chat_completion( messages, max_tokens=1024, stream=True, temperature=0.6, top_p=0.95, ): choices = chunk.choices token = "" if len(choices) and choices[0].delta.content: token = choices[0].delta.content response += token yield response chatbot = gr.ChatInterface( respond, title="Assistente BestYou", description="Il tuo coach virtuale per allenamento, alimentazione e recupero. " "Accedi con Hugging Face (pulsante nella barra laterale) per iniziare.", examples=[ "Come si esegue bene lo stacco rumeno?", "Che differenza c'è tra serie a cedimento e a buffer?", "Cosa mangio prima di allenarmi la mattina presto?", "Non ho voglia di allenarmi oggi, come mi motivo?", ], # Senza questo Gradio prova a PRECALCOLARE gli esempi all'avvio chiamando respond(), che # richiede il login OAuth dell'utente -> lo Space crasherebbe in startup. cache_examples=False, ) # Stima misure corporee (vita/petto/fianchi) da una foto check + altezza nota: usata dal gestionale # BestYou (FitRework-KMP) nella schermata "Check". Pipeline CPU-only: segmentazione (rembg) + pose # keypoints (mediapipe) + calibrazione con l'altezza del cliente. Vedi measure.py per i dettagli e i # limiti (è una STIMA approssimativa, non sostituisce il metro). measure_interface = gr.Interface( fn=estimate_measurements, inputs=[ gr.Image(type="pil", label="Foto check (frontale, figura intera)"), gr.Number(label="Altezza cliente (cm)", value=170, minimum=100, maximum=230), gr.Textbox(label="Token", type="password", value=""), ], outputs=gr.JSON(label="Stima misure (approssimativa)"), title="Stima misure da foto", description=( "Stima APPROSSIMATIVA di vita/petto/fianchi da una foto frontale a figura intera + altezza " "nota del cliente. Da usare come valore di PARTENZA da confermare/correggere a mano, non " "come sostituto del metro da sarta." ), ) # Embeddings per il matching semantico del gestionale (esercizi -> catalogo). Endpoint API # (/gradio_api/call/embed_texts); il tab serve solo per prova manuale. embed_interface = gr.Interface( fn=embed_texts, inputs=[ gr.Textbox(label='Testi (array JSON, es. ["lat machine", "panca piana"])'), gr.Textbox(label="Token", type="password", value=""), ], outputs=gr.JSON(label="Vettori normalizzati"), title="Embeddings testi (uso interno)", description="Vettori semantici per il matching esercizi del gestionale BestYou. " "Similarità coseno = prodotto scalare (vettori normalizzati).", ) with gr.Blocks() as demo: with gr.Sidebar(): gr.LoginButton() with gr.Tabs(): with gr.Tab("Chat"): chatbot.render() with gr.Tab("Misure da foto"): measure_interface.render() with gr.Tab("Embeddings"): embed_interface.render() if __name__ == "__main__": demo.launch()