572 lines
No EOL
20 KiB
JSON
572 lines
No EOL
20 KiB
JSON
{
|
||
"name": "FoodSnap - Coach AI (WhatsApp)",
|
||
"nodes": [
|
||
{
|
||
"parameters": {
|
||
"httpMethod": "POST",
|
||
"path": "wa/coach-inbound",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.webhook",
|
||
"typeVersion": 2.1,
|
||
"position": [
|
||
-500,
|
||
-940
|
||
],
|
||
"id": "webhook-coach",
|
||
"name": "Webhook (Whatsapp)"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "const body = $json.body ?? $json;\nconst data = body.data ?? {};\n\nconst remoteJid = data?.key?.remoteJid?.includes('@s.whatsapp.net') ? data.key.remoteJid : data?.key?.remoteJidAlt || '';\nconst number = remoteJid.replace(/\\D/g, '');\nconst message_id = data?.key?.id || '';\nconst text = data?.message?.conversation || data?.message?.extendedTextMessage?.text || '';\n\n// Check for image\nconst imageMessage = data?.message?.imageMessage || data?.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage || null;\n\nreturn [{\n number,\n remoteJid,\n message_id,\n text,\n hasImage: !!imageMessage,\n imageMessage,\n username: data?.pushName || 'Atleta'\n}];"
|
||
},
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
-280,
|
||
-940
|
||
],
|
||
"id": "normalize-inbound",
|
||
"name": "Normalizar Dados"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "executeQuery",
|
||
"query": "select * from check_access_by_whatsapp('{{ $json.number }}')",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgres",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
-60,
|
||
-940
|
||
],
|
||
"id": "validate-user",
|
||
"name": "Validar Usuario (RPC)",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "2JDD2OJz4cAsWb0J",
|
||
"name": "foodsnap_supabase"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 3
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "check-user",
|
||
"leftValue": "={{ $json.exists }}",
|
||
"rightValue": "",
|
||
"operator": {
|
||
"type": "boolean",
|
||
"operation": "true",
|
||
"singleValue": true
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.3,
|
||
"position": [
|
||
160,
|
||
-940
|
||
],
|
||
"id": "if-exists",
|
||
"name": "Usuario Existe?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 3
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "check-process",
|
||
"leftValue": "={{ $json.can_process }}",
|
||
"rightValue": "",
|
||
"operator": {
|
||
"type": "boolean",
|
||
"operation": "true",
|
||
"singleValue": true
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.3,
|
||
"position": [
|
||
400,
|
||
-1040
|
||
],
|
||
"id": "if-quota",
|
||
"name": "Tem Quota/Plano?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 3
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "check-image",
|
||
"leftValue": "={{ $node[\"Normalizar Dados\"].json.hasImage }}",
|
||
"rightValue": "",
|
||
"operator": {
|
||
"type": "boolean",
|
||
"operation": "true",
|
||
"singleValue": true
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.3,
|
||
"position": [
|
||
640,
|
||
-940
|
||
],
|
||
"id": "if-image",
|
||
"name": "Tem Imagem?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "messages-api",
|
||
"instanceName": "FoodSnap",
|
||
"remoteJid": "={{ $('Normalizar Dados').item.json.number }}",
|
||
"messageText": "💪 *Coach AI*: Olá! Envie uma foto do seu corpo (preferencialmente de frente, roupa de treino) para eu fazer uma análise rápida do seu biótipo e sugestão de treino.",
|
||
"options_message": {}
|
||
},
|
||
"type": "n8n-nodes-evolution-api.evolutionApi",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
860,
|
||
-840
|
||
],
|
||
"id": "msg-intro",
|
||
"name": "Msg Intro",
|
||
"credentials": {
|
||
"evolutionApi": {
|
||
"id": "nGWBERcZoQWgdxgk",
|
||
"name": "FoodSnap"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "messages-api",
|
||
"instanceName": "FoodSnap",
|
||
"remoteJid": "={{ $('Normalizar Dados').item.json.number }}",
|
||
"messageText": "🧐 Analisando seu físico... Um momento!",
|
||
"options_message": {}
|
||
},
|
||
"type": "n8n-nodes-evolution-api.evolutionApi",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
860,
|
||
-1040
|
||
],
|
||
"id": "msg-ack",
|
||
"name": "Msg Ack",
|
||
"credentials": {
|
||
"evolutionApi": {
|
||
"id": "nGWBERcZoQWgdxgk",
|
||
"name": "FoodSnap"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "chat-api",
|
||
"operation": "get-media-base64",
|
||
"instanceName": "FoodSnap",
|
||
"messageId": "={{ $('Normalizar Dados').item.json.message_id }}"
|
||
},
|
||
"type": "n8n-nodes-evolution-api.evolutionApi",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
1080,
|
||
-1040
|
||
],
|
||
"id": "get-image",
|
||
"name": "Baixar Imagem",
|
||
"credentials": {
|
||
"evolutionApi": {
|
||
"id": "nGWBERcZoQWgdxgk",
|
||
"name": "FoodSnap"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "toBinary",
|
||
"sourceProperty": "data.base64",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.convertToFile",
|
||
"typeVersion": 1.1,
|
||
"position": [
|
||
1300,
|
||
-1040
|
||
],
|
||
"id": "convert-binary",
|
||
"name": "Converter Binario"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "image",
|
||
"operation": "analyze",
|
||
"modelId": {
|
||
"__rl": true,
|
||
"value": "models/gemini-pro-vision",
|
||
"mode": "list",
|
||
"cachedResultName": "models/gemini-pro-vision"
|
||
},
|
||
"text": "=Você é um Treinador Físico de Elite e Nutricionista Esportivo.\nAnalise a imagem fornecida (foto de corpo inteiro/físico).\n\n1. Verifique se é uma foto de corpo humano válida para análise fitness. Se não, retorne \"valid_body\": false.\n2. Se for válida, estime:\n - Biótipo Predominante (Ectomorfo, Mesomorfo, Endomorfo)\n - Percentual de Gordura (BF% aproximado)\n - Nível de Massa Muscular (Baixo, Médio, Alto)\n3. Sugira:\n - Objetivo Principal (Cutting, Bulking, Manutenção)\n - Divisão de Treino Recomendada (ex: ABC, ABCDE, Upper/Lower)\n - Foco Dietético (ex: Carb Cycling, Alto Carbo, Keto)\n - Dica de Ouro (uma frase curta de impacto)\n\nResponda APENAS em JSON estrito (sem markdown):\n{\n \"valid_body\": true,\n \"biotype\": \"...\",\n \"estimated_body_fat\": 15,\n \"muscle_mass\": \"Médio\",\n \"goal\": \"Bulking\",\n \"workout\": \"...\",\n \"diet\": \"...\",\n \"tip\": \"...\"\n}",
|
||
"inputType": "binary",
|
||
"simplify": false,
|
||
"options": {}
|
||
},
|
||
"type": "@n8n/n8n-nodes-langchain.googleGemini",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
1520,
|
||
-1100
|
||
],
|
||
"id": "analyze-gemini",
|
||
"name": "Gemini Coach Analysis",
|
||
"credentials": {
|
||
"googlePalmApi": {
|
||
"id": "T2uIVBcjJ9h8BFCC",
|
||
"name": "Backup APIKEY"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "const raw = $json?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\nconst clean = raw.replace(/```json/g, '').replace(/```/g, '').trim();\nlet data = {};\ntry { \n data = JSON.parse(clean); \n} catch(e) {\n data = { valid_body: false, error: 'Failed to parse AI' };\n}\n\nreturn [{ ...data }];"
|
||
},
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [
|
||
1740,
|
||
-1100
|
||
],
|
||
"id": "parse-response",
|
||
"name": "Parse AI JSON"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict",
|
||
"version": 3
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "check-validity",
|
||
"leftValue": "={{ $json.valid_body }}",
|
||
"rightValue": "",
|
||
"operator": {
|
||
"type": "boolean",
|
||
"operation": "true",
|
||
"singleValue": true
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2.3,
|
||
"position": [
|
||
1960,
|
||
-1100
|
||
],
|
||
"id": "if-valid-body",
|
||
"name": "Corpo Valido?"
|
||
},
|
||
{
|
||
"parameters": {
|
||
"operation": "executeQuery",
|
||
"query": "insert into public.coach_analyses\n(\n user_id,\n source,\n image_url,\n ai_raw_response,\n ai_structured,\n biotype,\n estimated_body_fat,\n goal_suggestion,\n muscle_mass_level,\n used_free_quota\n)\nvalues\n(\n cast('{{ $(\"Validar Usuario & Quota\").item.json.user_id }}' as uuid),\n 'whatsapp',\n null,\n '{{ JSON.stringify($json) }}',\n '{{ JSON.stringify($json) }}',\n '{{ $json.biotype }}',\n cast({{ $json.estimated_body_fat || 0 }} as numeric),\n '{{ $json.goal }}',\n '{{ $json.muscle_mass }}',\n CASE\n WHEN {{ $(\"Validar Usuario & Quota\").item.json.plan_active }} = true THEN false\n ELSE true\n END\n)\nreturning id as analysis_id;",
|
||
"options": {}
|
||
},
|
||
"type": "n8n-nodes-base.postgres",
|
||
"typeVersion": 2.6,
|
||
"position": [
|
||
2200,
|
||
-1180
|
||
],
|
||
"id": "save-db",
|
||
"name": "Salvar DB",
|
||
"credentials": {
|
||
"postgres": {
|
||
"id": "2JDD2OJz4cAsWb0J",
|
||
"name": "foodsnap_supabase"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "messages-api",
|
||
"instanceName": "FoodSnap",
|
||
"remoteJid": "={{ $('Normalizar Dados').item.json.number }}",
|
||
"messageText": "=⚡ *Análise Coach AI*\n\n🧬 *Biótipo*: {{$json.biotype}}\n⚖️ *Gordura (BF)*: ~{{$json.estimated_body_fat}}%\n💪 *Massa Muscular*: {{$json.muscle_mass}}\n\n🎯 *Objetivo Sugerido*: {{$json.goal}}\n🏋️ *Treino*: {{$json.workout}}\n🥗 *Dieta*: {{$json.diet}}\n\n💡 *Dica*: {{$json.tip}}\n\n_Para ver o plano completo, acesse o App!_",
|
||
"options_message": {}
|
||
},
|
||
"type": "n8n-nodes-evolution-api.evolutionApi",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
2420,
|
||
-1180
|
||
],
|
||
"id": "reply-success",
|
||
"name": "Responder Resultado",
|
||
"credentials": {
|
||
"evolutionApi": {
|
||
"id": "nGWBERcZoQWgdxgk",
|
||
"name": "FoodSnap"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "messages-api",
|
||
"instanceName": "FoodSnap",
|
||
"remoteJid": "={{ $('Normalizar Dados').item.json.number }}",
|
||
"messageText": "⚠️ Não consegui identificar um físico claro nesta foto. Tente enviar uma foto de corpo inteiro ou tronco, com boa iluminação.",
|
||
"options_message": {}
|
||
},
|
||
"type": "n8n-nodes-evolution-api.evolutionApi",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
2200,
|
||
-980
|
||
],
|
||
"id": "reply-invalid",
|
||
"name": "Responder Invalido",
|
||
"credentials": {
|
||
"evolutionApi": {
|
||
"id": "nGWBERcZoQWgdxgk",
|
||
"name": "FoodSnap"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"resource": "messages-api",
|
||
"instanceName": "FoodSnap",
|
||
"remoteJid": "={{ $('Normalizar Dados').item.json.number }}",
|
||
"messageText": "🚫 *Limite do Coach Atingido*\n\nVocê já usou suas 3 análises de Coach gratuitas. Assine o plano PRO para avaliações ilimitadas! 🚀",
|
||
"options_message": {}
|
||
},
|
||
"type": "n8n-nodes-evolution-api.evolutionApi",
|
||
"typeVersion": 1,
|
||
"position": [
|
||
640,
|
||
-1140
|
||
],
|
||
"id": "reply-limit",
|
||
"name": "Msg Limite",
|
||
"credentials": {
|
||
"evolutionApi": {
|
||
"id": "nGWBERcZoQWgdxgk",
|
||
"name": "FoodSnap"
|
||
}
|
||
}
|
||
}
|
||
],
|
||
"connections": {
|
||
"Webhook (Whatsapp)": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Normalizar Dados",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Normalizar Dados": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Validar Usuario & Quota",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Validar Usuario & Quota": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Usuario Existe?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Usuario Existe?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Tem Quota/Plano?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Tem Quota/Plano?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Tem Imagem?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "Msg Limite",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Tem Imagem?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Msg Ack",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "Msg Intro",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Msg Ack": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Baixar Imagem",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Baixar Imagem": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Converter Binario",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Converter Binario": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Gemini Coach Analysis",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Gemini Coach Analysis": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Parse AI JSON",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Parse AI JSON": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Corpo Valido?",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Corpo Valido?": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Salvar DB",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
],
|
||
[
|
||
{
|
||
"node": "Responder Invalido",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Salvar DB": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Responder Resultado",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
}
|
||
}
|
||
} |