import gradio as gr from transformers import AutoModelForCausalLM, AutoTokenizer from sentence_transformers import SentenceTransformer, util import torch import torch.nn.functional as F import unicodedata import json import re import random from nutrition import UserProfile, build_basic_plan, gerar_plano_diario # Carregar o JSON with open("exercicios.json", "r", encoding="utf-8") as f: exercicios_db = json.load(f) # ------------------------- # Config # ------------------------- EMBEDDING_MODEL = "rufimelo/bert-large-portuguese-cased-sts" LLM_MODEL = "tiiuae/Falcon3-1B-Instruct" THRESHOLD = 0.50 # score mínimo para aceitar como fitness KEYWORD_WEIGHT = 0.15 # peso por conceito identificado MAX_KEYWORD_BONUS = 0.60 # limite do bônus por conceitos KW_SIM_THRESHOLD = 0.45 # similaridade para considerar conceito detectado MUSCLE_SIM_THRESHOLD = 0.75 # similaridade para considerar músculo detectado via embedding # ------------------------- # Normalização # ------------------------- def normalize_text(text: str) -> str: if text is None: return "" text = unicodedata.normalize("NFD", text) text = "".join(ch for ch in text if unicodedata.category(ch) != "Mn") return text.lower().strip() # ------------------------- # Carregamento de modelos # ------------------------- print("Carregando embedder:", EMBEDDING_MODEL) embedder = SentenceTransformer(EMBEDDING_MODEL) print("Carregando LLM:", LLM_MODEL) tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL, use_fast=True) model = AutoModelForCausalLM.from_pretrained( LLM_MODEL, torch_dtype=torch.float32, device_map=None # 👈 evita tentar usar offload para "disk" ).to("cpu") # ------------------------- # Domínio fitness (frases representativas) # ------------------------- fitness_domains = [ "exercícios de musculação", "treino de academia", "programa de treino para ganhar força e massa muscular", "condicionamento físico, resistência, explosividade e velocidade", # treino por grupo (inclui panturrilha) "exercícios para pernas, glúteos e panturrilhas", "exercícios para costas e bíceps", "exercícios para peito e tríceps", "treino de abdômen e core", "treino de ombros e trapézio", "treino de antebraços", "treino de panturrilhas", "treino de corpo inteiro", "treino funcional para atletas", # nutrição "dieta para ganhar massa muscular", "dieta para emagrecimento", "alimentação pré e pós treino", "suplementação para hipertrofia", "suplementação para recuperação muscular", "planejamento alimentar para atletas", # recuperação "recuperação e descanso muscular", "sono e desempenho esportivo", "alongamento e aquecimento antes do treino", # saúde e prevenção "prevenção de lesões articulares e tendíneas", "treino adaptado para lesão no joelho", "treino adaptado para lesão no ombro", "treino adaptado para lesão na lombar", "treino adaptado para lesão no quadril", "treino adaptado para lesão no tornozelo", "fisioterapia e reabilitação esportiva" ] # ------------------------- # Conceitos (keywords agrupadas) # ------------------------- concept_keywords = { "treino": ["treino", "treinar", "treinos", "workout", "malhar", "musculacao", "musculação", "gym"], "hipertrofia": ["hipertrofia", "ganhar massa", "massa muscular"], "força": ["forca", "força", "ganho de força", "explosividade"], "resistência": ["resistencia", "resistência", "condicionamento", "cardio"], "dieta": ["dieta", "alimentacao", "alimentação", "plano alimentar", "nutrição", "nutricao","emagrecer", "perder peso", "cutting", "secar"], "suplementos": ["suplemento", "suplementos", "creatina", "whey", "proteina", "proteína", "bcaa", "pre treino", "pré treino", "pos treino", "pós treino"], "recuperação": ["recuperacao", "recuperação", "descanso", "sono", "alongamento", "aquecimento"], "lesões": ["lesao", "lesão", "lesoes", "lesões", "joelho", "ombro", "lombar", "coluna", "tendinite", "fisioterapia", "reabilitação", "reabilitacao"], "estratégias": ["divisao de treino", "divisão de treino", "periodizacao", "periodização", "circuito", "hiit", "fullbody", "corpo inteiro"], "cardio": ["corrida", "correr", "bicicleta", "bike", "esteira", "natação", "natacao"] } # ------------------------- # Grupos musculares (keywords) # ------------------------- muscle_keywords = { # 🔹 Pernas e subgrupos "pernas": ["perna", "pernas", "inferiores", "lower body", "treino inferior"], "quadriceps": ["quadriceps", "quads", "coxa da frente", "frontal da coxa"], "posterior_de_coxa": ["posterior", "posterior de coxa", "isquiotibiais", "hamstrings"], "gluteo": ["gluteo", "glúteos", "bumbum", "gluteus"], "panturrilhas": ["panturrilha", "panturrilhas", "batata da perna", "gastrocnêmio", "soleo"], # 🔹 Costas e subgrupos "costas": ["costas", "dorsal", "latissimo", "lats", "dorso", "costa"], "lombar": ["lombar", "parte baixa das costas", "erectores", "eretores da espinha"], "trapezio": ["trapezio", "trapézio", "pescoço largo"], # 🔹 Peito "peito": ["peito", "peitoral", "chest"], # 🔹 Braços (grupo e subgrupos) "bracos": ["braco", "braço", "bracos", "braços", "arm", "arms", "treino de bracos", "treino de braços"], "biceps": ["biceps", "bíceps"], "triceps": ["triceps", "tríceps"], "antebraco": ["antebraco", "antebraço", "antebracos", "antebraços", "forearm"], # 🔹 Ombros "ombro": ["ombro", "ombros", "deltoide", "deltoides", "shoulder"], # 🔹 Abdômen e core "abdomen": ["abdomen", "abdominal", "reto abdominal", "abs"], "oblíquos": ["oblíquos", "obliquo", "oblíquo"], "core": ["core", "centro do corpo", "estabilizadores"], # 🔹 Superiores "superiores": ["superior", "superiores", "upper body", "treino superior"], "puxar": ["puxar", "puxada", "puxadas", "pull"], "empurrar": ["empurrar", "empurrada", "empurradas", "push"], } # Expansão de grupos compostos group_hierarchy = { "pernas": ["quadriceps", "posterior_de_coxa", "gluteo", "panturrilhas"], "costas": ["lombar", "trapezio", "dorsal"], "superiores": ["peito", "dorsal", "trapezio", "biceps", "triceps", "ombro", "antebraco"], "bracos": ["biceps", "triceps", "antebraco"], "puxar": ["biceps", "costas", "lombar", "trapézio", "antebraço"], "empurrar": ["triceps", "peito", "ombro", "deltoides"] } # ------------------------- # Lesões (keywords) # ------------------------- lesao_context_keywords = [ "dor", "dói","doi", "doe", "machucado", "lesão", "lesoes", "lesões", "rompido", "lesionado" "inflamado", "inflamação", "luxação", "ruptura", "tendinite", "entorse", "condromalacia", "bursite", "hernia", "hérnia" ] lesao_keywords = { "joelho": ["joelho", "ligamento cruzado", "lca", "menisco", "condromalacia"], "ombro": ["ombro", "manguito rotador", "luxação de ombro", "tendinite no ombro"], "lombar": ["lombar", "coluna", "hernia de disco", "hérnia de disco", "ciática"], "quadril": ["quadril", "artrose no quadril", "bursite no quadril"], "tornozelo": ["tornozelo", "entorse de tornozelo", "lesão no tornozelo"], "cotovelo": ["cotovelo", "epicondilite", "tennis elbow", "golfista"], "punho": ["punho", "síndrome do túnel do carpo"], } def detectar_lesoes(texto: str) -> list[str]: texto = texto.lower() # 1️⃣ Verifica se existe algum contexto de lesão if not any(k in texto for k in lesao_context_keywords): return [] # 2️⃣ Só então procura as articulações/problemas detectadas = [] for lesao, termos in lesao_keywords.items(): for termo in termos: if termo in texto: detectadas.append(lesao) break return detectadas def is_safe_for_lesoes(exercicio, lesoes: list[str]) -> bool: """ Retorna False se o exercício tiver intensidade 'alta' em alguma articulação lesionada. """ if not lesoes: return True # sem lesão, tudo liberado for lesao in lesoes: if lesao in exercicio.get("intensidade_articulacao", {}): intensidade = exercicio["intensidade_articulacao"][lesao] if intensidade == "alta": return False return True def escolher_variacao(ex, lesoes): """ Se não houver lesão, retorna variação aleatória. Se houver, tenta priorizar variações de menor impacto/custo. """ variacoes = ex["variacoes"] if not lesoes: return random.choice(variacoes) # 🎯 Priorizando custo menor (proxy para menor impacto articular) variacoes_ordenadas = sorted(variacoes, key=lambda v: v["custo"]) return variacoes_ordenadas[0] # ------------------------- # Pré-calcular embeddings (normalize) # ------------------------- fitness_embeddings = embedder.encode([normalize_text(s) for s in fitness_domains], convert_to_tensor=True) fitness_embeddings = F.normalize(fitness_embeddings, p=2, dim=1) # concept embeddings: média das palavras do conceito concept_embeddings = {} for concept, words in concept_keywords.items(): emb = embedder.encode([normalize_text(w) for w in words], convert_to_tensor=True) emb = F.normalize(emb, p=2, dim=1) concept_embeddings[concept] = torch.mean(emb, dim=0, keepdim=True) # muscle embeddings: média das palavras do músculo muscle_embeddings = {} muscle_keywords_norm = {} for muscle, words in muscle_keywords.items(): words_norm = [normalize_text(w) for w in words] muscle_keywords_norm[muscle] = words_norm emb = embedder.encode(words_norm, convert_to_tensor=True) emb = F.normalize(emb, p=2, dim=1) muscle_embeddings[muscle] = torch.mean(emb, dim=0, keepdim=True) # ------------------------- # Helpers: detectar conceitos e músculos # ------------------------- def detectar_conceitos(prompt_emb, prompt_norm): matches = [] for concept, c_emb in concept_embeddings.items(): sim = float(util.cos_sim(prompt_emb, c_emb).item()) if sim >= KW_SIM_THRESHOLD: matches.append((concept, sim)) # fallback por regex if any(k in prompt_norm for k in ["emagrecer", "perder peso", "cutting", "bulking", "secar", "ganhar massa", "ganhara peso"]): matches.append(("dieta", 1.0)) return matches def detectar_musculos(texto: str) -> list[str]: texto = texto.lower() detectados = set() # 1️⃣ Identificar palavras-chave for musculo, termos in muscle_keywords.items(): for termo in termos: if termo in texto: detectados.add(musculo) # 2️⃣ Expansão genérica de todos os grupos compostos expansoes = set() for grupo, subgrupos in group_hierarchy.items(): if grupo in detectados: detectados.remove(grupo) expansoes.update(subgrupos) detectados.update(expansoes) # 3️⃣ Hierarquia reversa: se um grupo e seus subgrupos aparecerem, priorizar os subgrupos for grupo, subgrupos in group_hierarchy.items(): if grupo in detectados and any(s in detectados for s in subgrupos): detectados.remove(grupo) return list(detectados) # ------------------------- # Objetivos (keywords) # ------------------------- objetivo_keywords = { "hipertrofia": ["hipertrofia", "massa", "crescimento muscular", "ganhar tamanho", "volume"], "forca": ["força", "forca", "powerlifting", "power", "pesado", "ganhar força"], "condicionamento": ["resistência", "resistencia", "condicionamento", "endurance", "cardio", "alta repetiçao", "repetições altas"], "explosividade": ["explosivo", "explosividade", "pliometria", "saltar", "sprints", "potência", "potencia"] } def detectar_objetivos(texto: str) -> list[str]: texto = texto.lower() objetivos_detectados = [] for objetivo, termos in objetivo_keywords.items(): for termo in termos: if termo in texto: objetivos_detectados.append(objetivo) break if not objetivos_detectados: return ["hipertrofia"] # padrão return objetivos_detectados texto = texto.lower() for objetivo, termos in objetivo_keywords.items(): for termo in termos: if termo in texto: return objetivo return "hipertrofia" # padrão se nada for detectado def detectar_intencao(prompt_norm: str, musculos_detectados: list[str]): """ Retorna: ("split", dias) -> se detectar pedido de divisão semanal ("isolado", musculos) -> se detectar treino de músculos específicos """ # 🔹 Detectar split (nº de dias por semana) padrao_split = re.search(r"(\d+)\s*(x|vezes|dias)\s*(por\s*semana)?", prompt_norm) if padrao_split: dias = int(padrao_split.group(1)) return "split", dias # 🔹 Detectar treino isolado (músculos) if musculos_detectados: return "isolado", musculos_detectados # 🔹 Default → treino full body isolado return "isolado", ["peito", "costas", "ombro", "braços", "pernas", "core"] def montar_treino(musculos_alvo, budget=45, objetivos=["hipertrofia"], lesoes=[]): treino = [] custo_total = 0 usados = set() musculos_cobertos = set() # 🔹 Pré-filtrar exercícios seguros exercicios_validos = [ex for ex in exercicios_db if is_safe_for_lesoes(ex, lesoes)] # 🔹 Se "explosividade" NÃO está nos objetivos, remove exercícios pliométricos if "explosividade" not in objetivos: exercicios_validos = [ex for ex in exercicios_validos if not ex.get("pliometrico", False)] # 1️⃣ Faixas de repetições por objetivo faixas_reps = { "hipertrofia": (6, 15), "forca": (2, 5), "condicionamento": (15, 50), "explosividade": (5, 12) } def escolher_reps(objetivo): faixa = faixas_reps.get(objetivo, (8, 12)) return random.randint(*faixa) def add_exercicio(ex, variacao, series, objetivo_escolhido): nonlocal custo_total custo_ex = variacao["custo"] * series reps = escolher_reps(objetivo_escolhido) if ex["nome"] in usados: return False if custo_total + custo_ex <= budget: descricao_final = variacao["descricao"] if objetivo_escolhido == "explosividade" and not ex.get("pliometrico", False): if ex.get("equipamento") == "peso_livre": descricao_final += " (executar com carga moderada e máxima velocidade)" else: return False treino.append({ "nome": ex["nome"], "descricao": descricao_final, "series": series, "reps": reps, "custo_total": custo_ex, "custo_unit": variacao["custo"], "video": variacao["video"], "objetivo": objetivo_escolhido, "musculos": ex["musculos"] }) custo_total += custo_ex usados.add(ex["nome"]) musculos_cobertos.update(ex["musculos"]) return True return False # 2️⃣ Multiarticulado principal candidatos_multi = [] for ex in exercicios_validos: if any(m in ex["musculos"] for m in musculos_alvo): for v in ex["variacoes"]: if v["custo"] == 5: cobertura = len(set(ex["musculos"]) & set(musculos_alvo)) candidatos_multi.append((ex, v, cobertura)) if candidatos_multi: candidatos_multi.sort(key=lambda x: x[2], reverse=True) melhor_cobertura = candidatos_multi[0][2] top = [c for c in candidatos_multi if c[2] == melhor_cobertura] ex, variacao, _ = random.choice(top) obj_escolhido = random.choice(objetivos) add_exercicio(ex, variacao, series=4, objetivo_escolhido=obj_escolhido) # 3️⃣ Garantir pelo menos 1 exercício por músculo for alvo in musculos_alvo: if alvo not in musculos_cobertos: candidatos = [] for ex in exercicios_validos: if alvo in ex["musculos"] and ex["nome"] not in usados: v = escolher_variacao(ex, lesoes) candidatos.append((ex, v)) if candidatos: candidatos.sort(key=lambda x: x[1]["custo"]) top_custo = candidatos[0][1]["custo"] top = [c for c in candidatos if c[1]["custo"] == top_custo] ex, variacao = random.choice(top) obj_escolhido = random.choice(objetivos) add_exercicio(ex, variacao, series=3, objetivo_escolhido=obj_escolhido) # 3.5️⃣ Distribuir resto do budget de forma equilibrada entre músculos mapa = {m: 0 for m in musculos_alvo} for ex in treino: for m in ex["musculos"]: if m in mapa: mapa[m] += 1 while custo_total < budget: # Ordena músculos pelo número atual de exercícios musculos_ordenados = sorted(mapa.items(), key=lambda x: x[1]) adicionou = False for alvo, _ in musculos_ordenados: candidatos = [] for ex in exercicios_validos: if alvo in ex["musculos"] and ex["nome"] not in usados: v = escolher_variacao(ex, lesoes) if v and v["custo"] <= 4: # evitar só exercícios caros candidatos.append((ex, v)) if candidatos: # Pega o mais barato viável candidatos.sort(key=lambda x: x[1]["custo"]) ex, variacao = candidatos[0] obj_escolhido = random.choice(objetivos) if add_exercicio(ex, variacao, series=3, objetivo_escolhido=obj_escolhido): mapa[alvo] += 1 adicionou = True break # vai para o próximo loop if not adicionou: break # não dá para adicionar mais nada # 🔹 Ordem de prioridade dos músculos ordem_musculos = { "quadriceps": 1, "posterior_de_coxa": 2, "gluteo": 3, "panturrilhas": 4, "core": 5, "peito": 6, "ombro": 7, "triceps": 8, "dorsal": 9, "trapezio": 10, "biceps": 11, "antebracos": 12, "deltoide_frontal": 13, "deltoide_lateral": 14, "deltoide_posterior": 15, "romboides": 16, "lombar": 17 } # 🔹 Ordenar treino: treino.sort( key=lambda x: ( -x["custo_total"], # 1️⃣ Primeiro custo (maior primeiro) min([ordem_musculos.get(m, 99) for m in x["musculos"]]) # 2️⃣ Depois prioridade do músculo ) ) return treino, custo_total # 🔹 Carregar splits.json uma vez with open("splits.json", "r", encoding="utf-8") as f: splits_por_dias = json.load(f) with open("splits_mulher.json", "r", encoding="utf-8") as f: splits_por_dias_mulher = json.load(f) def gerar_split(sexo="homem", dias = 5, budget=45, objetivos=["hipertrofia"], lesoes=[]): """ Gera um plano semanal de treino baseado no número de dias escolhido. - dias: número de dias de treino por semana (1 a 6) - budget: "tempo/esforço" máximo (compatível com montar_treino) - objetivos: lista de objetivos (ex: ["hipertrofia", "forca"]) - lesoes: lista de articulações com lesões (ex: ["joelho"]) """ dias_str = str(dias) # as chaves do JSON são strings if dias_str not in splits_por_dias: raise ValueError(f"Não há split configurado para {dias} dias/semana.") # 🔹 Escolher aleatoriamente um split entre os disponíveis para esse número de dias if(sexo == "homem"): split_escolhido = random.choice(splits_por_dias[dias_str]) else: split_escolhido = random.choice(splits_por_dias_mulher[dias_str]) treino_semana = { "split_nome": split_escolhido["nome"], "dias": {} } # 🔹 Montar treino para cada dia do split for i, musculos_dia in enumerate(split_escolhido["dias"], start=1): treino, custo = montar_treino( musculos_dia, budget=budget, objetivos=objetivos, lesoes=lesoes ) treino_semana["dias"][f"Dia {i}"] = { "musculos_alvo": musculos_dia, "treino": treino, "custo_total": custo } return treino_semana def gerar_plano(idade, sexo, peso, altura, atividade, objetivo, intensidade, n_refeicoes=5): try: user = UserProfile( idade=int(idade), sexo=sexo, peso_kg=float(peso), altura_cm=float(altura), atividade=atividade, ) plano = build_basic_plan(user, objetivo=objetivo, intensidade=intensidade) # Gerar plano diário de refeições plano_diario = gerar_plano_diario(plano, n_refeicoes=n_refeicoes) resumo = ( f"📊 **Plano Nutricional**\n\n" f"- Calorias alvo: {plano.calorias_alvo} kcal\n" f"- Proteína: {plano.proteina_g} g\n" f"- Carboidratos: {plano.carboidratos_g} g\n" f"- Gorduras: {plano.gorduras_g} g\n\n" f"ℹ️ {plano.nota}\n" ) return resumo, plano_diario except Exception as e: return f"Erro: {str(e)}", None import re def extrair_dados_usuario(prompt_norm: str): dados = {} # ----------------------------- # Peso (em kg) # ----------------------------- peso_match = re.search(r"(\d{2,3}(?:[.,]\d{1,2})?)\s*(kg|quilo|quilos)", prompt_norm) if peso_match: dados["peso"] = float(peso_match.group(1).replace(",", ".")) # ----------------------------- # Altura (cm ou metros) # ----------------------------- altura_m_match = re.search(r"(\d(?:[.,]\d{1,2})?)\s*(m|metro|metros)", prompt_norm) if altura_m_match: dados["altura"] = float(altura_m_match.group(1).replace(",", ".")) * 100 else: altura_cm_match = re.search(r"(\d{2,3})\s*(cm|centimetros|centímetros)", prompt_norm) if altura_cm_match: dados["altura"] = float(altura_cm_match.group(1)) # ----------------------------- # Idade # ----------------------------- idade_match = re.search(r"(\d{1,2})\s*(anos|idade|ano)", prompt_norm) if idade_match: dados["idade"] = int(idade_match.group(1)) # ----------------------------- # Sexo / Gênero # ----------------------------- if re.search(r"\b(homem|masculino|rapaz|menino)\b", prompt_norm): dados["sexo"] = "homem" elif re.search(r"\b(mulher|feminino|garota|menina)\b", prompt_norm): dados["sexo"] = "mulher" # ----------------------------- # Objetivo (bulking/cutting) # ----------------------------- if any(word in prompt_norm for word in ["ganhar massa", "ganhar peso", "bulking", "hipertrofia", "aumentar massa", "crescer"]): dados["objetivo"] = "bulking" elif any(word in prompt_norm for word in ["perder peso", "emagrecer", "cutting", "definir", "secar", "perder gordura"]): dados["objetivo"] = "cutting" else: dados["objetivo"] = "manutenção" # ----------------------------- # Nível de atividade # ----------------------------- atividade_map = { "sedentário": [r"sedent[áa]rio", r"inativo", r"parado"], "leve": [r"leve", r"pouco ativo", r"atividade leve", r"caminhadas ocasionais", r"1\s*(vez|x)", r"uma vez"], "moderado": [r"moderado", r"regular", r"atividade moderada", r"2\s*(vezes|x)", r"duas vezes", r"3\s*(vezes|x)", r"tr[eê]s vezes"], "ativo": [r"alto", r"intenso", r"treino pesado", r"4\s*(vezes|x)", r"quatro vezes", r"5\s*(vezes|x)", r"cinco vezes"], "muito_ativo": [r"muito alto", r"atleta", r"competidor", r"6\s*(vezes|x)", r"seis vezes", r"7\s*(vezes|x)", r"sete vezes", r"di[áa]rio"], } for nivel, padroes in atividade_map.items(): for z1 in padroes: if re.search(z1, prompt_norm): dados["atividade"] = nivel break if "atividade" in dados: break return dados def coletar_ou_gerar_plano(prompt_norm: str): dados = extrair_dados_usuario(prompt_norm) campos_obrigatorios = ["idade", "sexo", "peso", "altura", "atividade", "objetivo"] faltando = [c for c in campos_obrigatorios if c not in dados] if faltando: return { "status": "incompleto", "mensagem": f"Preciso que você me diga também: {', '.join(faltando)}." } # Se tudo ok → gerar plano return gerar_plano( idade=dados["idade"], sexo=dados["sexo"], peso=dados["peso"], altura=dados["altura"], atividade=dados["atividade"], objetivo=dados["objetivo"], intensidade="moderada" ) def formatar_resposta_humana(resposta_final: dict) -> str: """ Usa o Falcon para transformar os dados técnicos em uma resposta natural. """ system_prompt = ( "Você é um personal trainer e nutricionista virtual. " "Explique o resultado abaixo em português, de forma simples, motivadora " "e prática, como se estivesse conversando com o aluno.\n\n" ) dados_json = json.dumps(resposta_final, ensure_ascii=False, indent=2) entrada = system_prompt + dados_json inputs = tokenizer(entrada, return_tensors="pt", truncation=True).to("cpu") output = model.generate(**inputs, max_new_tokens=400) resposta = tokenizer.decode(output[0], skip_special_tokens=True) return resposta.strip() # ------------------------- # Função principal # ------------------------- def responder(prompt: str): try: prompt_text = prompt or "" prompt_norm = normalize_text(prompt_text) # 1️⃣ Extrair dados do usuário primeiro dados_usuario = extrair_dados_usuario(prompt_norm) campos_obrigatorios = ["idade", "sexo", "peso", "altura", "atividade", "objetivo"] faltando = [c for c in campos_obrigatorios if c not in dados_usuario] if faltando: return f"Preciso que você me diga também: {', '.join(faltando)}." # 2️⃣ Só depois segue para embeddings e intenções prompt_emb = embedder.encode(prompt_norm, convert_to_tensor=True) if prompt_emb.dim() == 1: prompt_emb = prompt_emb.unsqueeze(0) prompt_emb = F.normalize(prompt_emb, p=2, dim=1) sims = util.cos_sim(prompt_emb, fitness_embeddings)[0] max_fitness = float(torch.max(sims).item()) concept_matches = detectar_conceitos(prompt_emb, prompt_norm) keyword_bonus = min(len(concept_matches) * KEYWORD_WEIGHT, MAX_KEYWORD_BONUS) score = max_fitness + keyword_bonus conceitos_detectados = [c for c, _ in concept_matches] intenções = { "treino": any(c in conceitos_detectados for c in ["treino", "hipertrofia", "força", "resistência", "estratégias", "cardio"]), "nutricao": any(c in conceitos_detectados for c in ["dieta", "suplementos", "ganhar peso", "perder peso", "cutting", "bulking", "ganhar massa", "definir"]), "recuperacao": any(c in conceitos_detectados for c in ["recuperação", "lesões"]), } resposta_final = {} print(dados_usuario["sexo"]) # 🚀 TREINO if intenções["treino"]: musculos_alvo = detectar_musculos(prompt_norm) lesoes = detectar_lesoes(prompt_norm) objetivos = detectar_objetivos(prompt_norm) tipo, dados = detectar_intencao(prompt_norm, musculos_alvo) if tipo == "split": dias = dados try: treino_semana = gerar_split(dias=dias, budget=50, objetivos=objetivos, lesoes=lesoes) resposta_final["treino"] = treino_semana except ValueError: resposta_final["treino"] = f"Não tenho splits configurados para {dias} dias/semana." elif tipo == "isolado": musculos = dados treino, custo = montar_treino(musculos, budget=50, objetivos=objetivos, lesoes=lesoes) resposta_final["treino"] = { "split_nome": "Treino isolado", "musculos_alvo": musculos, "custo_total": custo, "treino": treino } # 🚀 NUTRIÇÃO if intenções["nutricao"]: resumo, plano = gerar_plano( idade=dados_usuario["idade"], sexo=dados_usuario["sexo"], peso=dados_usuario["peso"], altura=dados_usuario["altura"], atividade=dados_usuario["atividade"], objetivo=dados_usuario["objetivo"], intensidade="moderada" ) resposta_final["nutricao"] = {"resumo": resumo, "plano": plano} # 🚀 RECUPERAÇÃO if intenções["recuperacao"]: resposta_final["recuperacao"] = { "dica": "Lembre-se de alongar e priorizar o sono para melhor recuperação." } if not resposta_final: return "Não consegui identificar se você quer um treino, nutrição ou recuperação." return resposta_final except Exception as e: import traceback print("❌ Erro na função responder:") traceback.print_exc() return f"Ocorreu um erro: {str(e)}" # ------------------------- # Interface Gradio # ------------------------- demo = gr.Interface( fn=responder, inputs=gr.Textbox(lines=3, label="Pergunta"), outputs=gr.Textbox(label="Resposta"), title="Personal Trainer AI (com detecção de músculos)" ) if __name__ == "__main__": demo.queue().launch()