fix(whatsapp): update Coach mode to 1 photo, fix google login infinite loading, improve welcome messages
This commit is contained in:
parent
f7753cfeb1
commit
6640162d4d
2 changed files with 43 additions and 85 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue