PORTFOLIO/automation/n8n/portfolio-chat-workflow.json

257 lines
7.5 KiB
JSON

{
"name": "Portfolio Bryan - AI Chatbot",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "portfolio-chat",
"responseMode": "responseNode",
"options": {
"allowedOrigins": "*"
}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
220,
340
],
"id": "webhook-trigger",
"name": "Webhook - Incoming Chat"
},
{
"parameters": {
"jsCode": "// Sanitización y validación de entrada para proteger cuota de API y prevenir abusos\nconst body = $input.item.json.body || {};\nconst rawMessage = typeof body.message === 'string' ? body.message.trim() : '';\nconst sessionId = typeof body.sessionId === 'string' && body.sessionId ? body.sessionId.slice(0, 64) : 'anon-' + Date.now();\n\nif (!rawMessage) {\n return [{\n json: {\n isValid: false,\n errorMessage: 'El mensaje no puede estar vacío.',\n sessionId\n }\n }];\n}\n\n// Limitar a máximo 400 caracteres para cuidar tokens gratuitos\nconst cleanMessage = rawMessage.slice(0, 400).replace(/<[^>]*>?/gm, '');\n\nreturn [{\n json: {\n isValid: true,\n cleanMessage,\n sessionId\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
460,
340
],
"id": "code-sanitizer",
"name": "Sanitize & Validate"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-is-valid",
"leftValue": "={{ $json.isValid }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
680,
340
],
"id": "if-valid-input",
"name": "Is Input Valid?"
},
{
"parameters": {
"promptType": "define",
"text": "={{ $('Sanitize & Validate').item.json.cleanMessage }}",
"options": {
"systemMessage": "=Eres el asistente virtual oficial de Bryan Baño en su portafolio web personal.\n\n### CONTEXTO DE BRYAN BAÑO:\n- Ingeniero en Computación (EPN, Quito - Ecuador).\n- Especialidad: .NET 8/9 Clean Architecture (CQRS MediatR, EF Core MySQL) y Angular Signals (OnPush, Canvas 2D).\n- Filosofía de Software: Ponytail (Simplicidad extrema, YAGNI, cero sobreingeniería, Web Platform nativa, 60 FPS).\n- Proyectos Destacados: Auditoría CACES (.NET), Incident Simulator Cloud Security (Serverless), Portafolio Interactivo Cyber-Zen.\n- Certificaciones: EC-Council Network Defense Essentials (NDE), Cisco Networking Academy.\n- Ubicación: Quito, Ecuador. Disponible para proyectos y contrataciones remotas globales.\n\n### REGLAS DE RESPUESTA:\n1. Responde siempre en el MISMO idioma del usuario (Español o Inglés).\n2. Sé técnico, conciso, profesional y cálido (máximo 2 a 3 párrafos o viñetas breves).\n3. No inventes experiencia que no esté en este contexto.\n4. Si el visitante desea contactar a Bryan o contratarlo, indícale amablemente que use el formulario de contacto del portafolio o su perfil de LinkedIn."
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.7,
"position": [
920,
240
],
"id": "ai-agent-bryan",
"name": "AI Agent - Bryan Assistant"
},
{
"parameters": {
"modelName": "models/gemini-2.0-flash",
"options": {
"temperature": 0.4
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1,
"position": [
880,
480
],
"id": "gemini-model",
"name": "Google Gemini Chat Model",
"credentials": {
"googleGeminiApi": {
"id": "gemini-free-cred",
"name": "Google Gemini Free API"
}
}
},
{
"parameters": {
"sessionKey": "={{ $('Sanitize & Validate').item.json.sessionId }}",
"contextWindowLength": 8
},
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.3,
"position": [
1040,
480
],
"id": "window-memory",
"name": "Window Buffer Memory"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={\n \"reply\": {{ JSON.stringify($json.output) }},\n \"sessionId\": {{ JSON.stringify($('Sanitize & Validate').item.json.sessionId) }}\n}",
"options": {
"responseCode": 200,
"responseHeaders": {
"entries": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
},
{
"name": "Access-Control-Allow-Methods",
"value": "POST, OPTIONS"
},
{
"name": "Access-Control-Allow-Headers",
"value": "Content-Type"
}
]
}
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1220,
240
],
"id": "respond-success",
"name": "Respond - Success"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={\n \"reply\": {{ JSON.stringify($json.errorMessage || 'Mensaje no válido.') }},\n \"sessionId\": {{ JSON.stringify($json.sessionId) }}\n}",
"options": {
"responseCode": 400,
"responseHeaders": {
"entries": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1220,
420
],
"id": "respond-error",
"name": "Respond - Error"
}
],
"connections": {
"Webhook - Incoming Chat": {
"main": [
[
{
"node": "Sanitize & Validate",
"type": "main",
"index": 0
}
]
]
},
"Sanitize & Validate": {
"main": [
[
{
"node": "Is Input Valid?",
"type": "main",
"index": 0
}
]
]
},
"Is Input Valid?": {
"main": [
[
{
"node": "AI Agent - Bryan Assistant",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond - Error",
"type": "main",
"index": 0
}
]
]
},
"AI Agent - Bryan Assistant": {
"main": [
[
{
"node": "Respond - Success",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent - Bryan Assistant",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Window Buffer Memory": {
"ai_memory": [
[
{
"node": "AI Agent - Bryan Assistant",
"type": "ai_memory",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}