fix(whatsapp): update Coach mode to 1 photo, fix google login infinite loading, improve welcome messages

This commit is contained in:
marciobever 2026-04-22 15:02:40 -03:00
parent f7753cfeb1
commit 6640162d4d
2 changed files with 43 additions and 85 deletions

View file

@ -120,7 +120,10 @@ export const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
if (error) { if (error) {
console.error("UserContext: Falha na sessão inicial", error); console.error("UserContext: Falha na sessão inicial", error);
if (mounted) setLoading(false); if (mounted) setLoading(false);
} else if (!session) { } else if (session) {
// Handled gracefully: forces profile fetch if session exists but events failed
handleSession(session);
} else {
// Se não há sessão e onAuthStateChange não disparou INITIAL_SESSION // Se não há sessão e onAuthStateChange não disparou INITIAL_SESSION
if (mounted) setLoading(false); if (mounted) setLoading(false);
} }

View file

@ -584,6 +584,11 @@ function formatWhatsAppResponse(analysis: any): string {
lines.push(`💡 _DICA:_ ${analysis.tip.text}`); lines.push(`💡 _DICA:_ ${analysis.tip.text}`);
} }
lines.push("");
lines.push("━━━━━━━━━━━━━━━━━━━━━");
lines.push("✨ *Gostou do visual?* Veja este e todos os seus pratos com gráficos interativos no nosso App:");
lines.push("💻 https://foodsnap.com.br/dashboard");
return lines.join("\n"); return lines.join("\n");
} }
@ -718,11 +723,14 @@ async function processMetaMessage(msg: any) {
const userId = user.id; const userId = user.id;
// ── 4. Estado da conversa (Coach state machine) ───────────── // ── 4. Estado da conversa (Coach state machine) ─────────────
let { data: conv } = await supabase let { data: convList } = await supabase
.from("whatsapp_conversations") .from("whatsapp_conversations")
.select("*") .select("*")
.eq("phone_number", senderNumber) .eq("phone_number", senderNumber)
.maybeSingle(); .order("updated_at", { ascending: false })
.limit(1);
let conv = convList && convList.length > 0 ? convList[0] : null;
// ── 3.5 Prevenir Mensagens Duplicadas (Retries da Meta) ────── // ── 3.5 Prevenir Mensagens Duplicadas (Retries da Meta) ──────
if (conv && conv.last_msg_id === messageId) { if (conv && conv.last_msg_id === messageId) {
@ -868,7 +876,7 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
// Mensagem 1: Introdução calorosa // Mensagem 1: Introdução calorosa
await sendWhatsAppMessage( await sendWhatsAppMessage(
remoteJid, remoteJid,
"🤖 *Coach IA ativado!*\n\nVou analisar seu biotipo e montar um protocolo *100% personalizado* de treino e dieta.\n\nO processo é simples: você envia *3 fotos* (frente, lado e costas) e a IA retorna seu plano completo em PDF. ⚡" "🤖 *Coach IA Ativado!*\n\nVou analisar sua composição corporal e montar um protocolo *Titan 100% personalizado* de treino e dieta.\n\nO processo é super simples: você envia apenas *1 foto realista* e a IA processa o resto! ⚡"
); );
// Pequena pausa para não parecer robótico // Pequena pausa para não parecer robótico
@ -877,7 +885,7 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
// Mensagem 2: Instrução da primeira foto // Mensagem 2: Instrução da primeira foto
await sendWhatsAppMessage( await sendWhatsAppMessage(
remoteJid, remoteJid,
"📸 *FOTO 1 de 3 — FRENTE*\n\nEnvie uma foto de frente do seu corpo agora.\n\n✅ Boa iluminação\n✅ Roupa justa ou sem camisa\n✅ Foto do pescoço até os joelhos" "📸 *ENVIE SUA FOTO*\n\nTire uma selfie no espelho ou foto de frente do seu corpo.\n\n✅ Boa iluminação\n✅ Sem camisa (homens) ou de Top/Regata (mulheres)\n✅ Mostrando do pescoço até a cintura/joelhos"
); );
}, 0); }, 0);
return new Response("Coach Started", { status: 200 }); return new Response("Coach Started", { status: 200 });
@ -907,84 +915,20 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
await supabase await supabase
.from("whatsapp_conversations") .from("whatsapp_conversations")
.update({ state: "COACH_SIDE", temp_data: { ...conv!.temp_data, front_image: fileName } }) .update({ state: "COACH_GOAL", temp_data: { ...conv!.temp_data, front_image: fileName } })
.eq("phone_number", senderNumber);
await sendWhatsAppMessage(remoteJid, "✅ Foto de frente recebida!\n\n📸 *FOTO 2 de 3 — LATERAL*\n\nAgora envie uma foto de perfil (lado direito ou esquerdo).\n\n✅ Braço ao longo do corpo\n✅ Mesmo padrão de roupa e iluminação");
}, 0);
return new Response("Coach Front image received", { status: 200 });
}
// COACH_SIDE
if (state === "COACH_SIDE") {
if (!isImage) {
setTimeout(async () => {
await sendWhatsAppMessage(remoteJid, "⚠️ Por favor, envie a foto de *LADO*.");
}, 0);
return new Response("Waiting for image", { status: 200 });
}
// Offload long-running task to background
setTimeout(async () => {
const base64 = await getWhatsAppMedia(messageId);
if (!base64) {
await sendWhatsAppMessage(remoteJid, "⚠️ Não consegui baixar a imagem. Tente enviar novamente.");
return;
}
const fileName = `${userId}_side_${Date.now()}.jpg`;
await supabase.storage
.from("coach-uploads")
.upload(fileName, base64ToUint8Array(base64), { contentType: "image/jpeg" });
await supabase
.from("whatsapp_conversations")
.update({ state: "COACH_BACK", temp_data: { ...conv!.temp_data, side_image: fileName } })
.eq("phone_number", senderNumber);
await sendWhatsAppMessage(remoteJid, "✅ Foto lateral recebida!\n\n📸 *FOTO 3 de 3 — COSTAS*\n\nEnvie agora uma foto de costas.\n\n✅ Postura ereta\n✅ Braços ao lado do corpo\n✅ Mesma roupa e iluminação");
}, 0);
return new Response("Coach Side image received", { status: 200 });
}
// COACH_BACK
if (state === "COACH_BACK") {
if (!isImage) {
setTimeout(async () => {
await sendWhatsAppMessage(remoteJid, "⚠️ Por favor, envie a foto de *COSTAS*.");
}, 0);
return new Response("Waiting for image", { status: 200 });
}
// Offload long-running task to background
setTimeout(async () => {
const base64 = await getWhatsAppMedia(messageId);
if (!base64) {
await sendWhatsAppMessage(remoteJid, "⚠️ Não consegui baixar a imagem. Tente enviar novamente.");
return;
}
const fileName = `${userId}_back_${Date.now()}.jpg`;
await supabase.storage
.from("coach-uploads")
.upload(fileName, base64ToUint8Array(base64), { contentType: "image/jpeg" });
await supabase
.from("whatsapp_conversations")
.update({ state: "COACH_GOAL", temp_data: { ...conv!.temp_data, back_image: fileName } })
.eq("phone_number", senderNumber); .eq("phone_number", senderNumber);
await sendWhatsAppInteractiveButtons( await sendWhatsAppInteractiveButtons(
remoteJid, remoteJid,
"📸 Todas as fotos recebidas!\n\nAgora escolha o seu principal objetivo para o protocolo:", "📸 Imagem processada com sucesso!\n\nAgora para eu calibrar seu treino e dieta, *qual o seu objetivo principal?*",
[ [
{ id: "goal_hipertrofia", title: "💪 Hipertrofia" }, { id: "goal_hipertrofia", title: "💪 Hipertrofia (Massa)" },
{ id: "goal_emagrecer", title: "🔥 Emagrecimento" }, { id: "goal_emagrecer", title: "🔥 Emagrecimento" },
{ id: "goal_definicao", title: "📐 Definição" } { id: "goal_definicao", title: "📐 Definição" }
] ]
); );
}, 0); }, 0);
return new Response("Coach Back image received", { status: 200 }); return new Response("Coach Photo image received", { status: 200 });
} }
// COACH_GOAL // COACH_GOAL
@ -1014,9 +958,9 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
); );
try { try {
const { front_image, side_image, back_image } = conv!.temp_data; const { front_image } = conv!.temp_data;
const images = [front_image, side_image, back_image]; const images = [front_image];
const parts: any[] = [{ text: COACH_SYSTEM_PROMPT }, { text: `Objetivo: ${goal}` }]; const parts: any[] = [{ text: COACH_SYSTEM_PROMPT }, { text: `Objetivo: ${goal}\n(Lembre-se: baseie-se apenas nesta imagem unica enviada pelo usuario.` }];
for (const imgPath of images) { for (const imgPath of images) {
if (imgPath) { if (imgPath) {
@ -1212,20 +1156,20 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
await sendWhatsAppListMenu( await sendWhatsAppListMenu(
remoteJid, remoteJid,
"FoodSnap IA", "FoodSnap IA e Nutrição",
"👋 Olá! Sou sua Inteligência Artificial.\n\n📸 *Para calcular suas calorias, basta me enviar a foto do seu prato agora mesmo!*", "Fala aí! 👋 Sou a FoodSnap, a Inteligência Artificial projetada para revolucionar seu físico.\n\nEscolha uma opção no menu abaixo para navegarmos, ou se preferir, *apenas me mande diretamente a foto do que você está comendo* e eu calculo tudo na hora!",
"Mais Opções 📋", "Menu Principal 👇",
[ [
{ {
title: "Escanear Prato 📸", title: "🍽️ Scanners Diários",
rows: [ rows: [
{ id: "action_help_photo", title: "Dicas de Fotografia", description: "Veja como tirar a foto perfeita" }, { id: "action_help_photo", title: "Dicas de Leitura de Prato", description: "Veja as recomendações pro bot não falhar" },
] ]
}, },
{ {
title: "Evolução PRO 📈", title: "🏋️ Especialistas",
rows: [ rows: [
{ id: "action_coach", title: "Protocólo Coach IA", description: "Gerar plano de treino pelo biotipo" }, { id: "action_coach", title: "Protocolo Coach de Saúde", description: "Gerar plano de treino 100% individual" },
] ]
} }
] ]
@ -1400,9 +1344,20 @@ RETORNE estritamente 3 bullet points recomendando o que o paciente pode adiciona
); );
// Atualizar last_analysis no temp_data para a re-pergunta "O que comer mais?" // Atualizar last_analysis no temp_data para a re-pergunta "O que comer mais?"
// Sem sobrescrever o estado caso o usuário já tenha iniciado o fluxo COACH_FRONT no meio tempo
const { data: currentConv } = await supabase
.from("whatsapp_conversations")
.select("state, temp_data")
.eq("phone_number", senderNumber)
.order("updated_at", { ascending: false })
.limit(1);
const currentState = currentConv?.[0]?.state || "IDLE";
const currentTempData = currentConv?.[0]?.temp_data || {};
await supabase await supabase
.from("whatsapp_conversations") .from("whatsapp_conversations")
.update({ state: "IDLE", temp_data: { last_analysis: rawResponseText } }) .update({ state: currentState, temp_data: { ...currentTempData, last_analysis: rawResponseText } })
.eq("phone_number", senderNumber); .eq("phone_number", senderNumber);
// 6g. Mapear confidence para enum do banco // 6g. Mapear confidence para enum do banco