{
  "id": "w0iWHqwJVcIyX6cA",
  "meta": {
    "instanceId": "5895d8ca92a7edd8f068d104b226176ebc0c945a1ed97c3c1dd3b247833ae825",
    "templateCredsSetupCompleted": true
  },
  "name": "01.Firecrawl Web Search Chatbot",
  "tags": [],
  "nodes": [
    {
      "id": "41ff48ba-2409-4697-a84c-c0ac49f292f0",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -640,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 944,
        "height": 304,
        "content": "## Internet Search Service with Firecrawl\nBackend search service responsible for processing search requests.\nReceives search queries from the chat interface, executes the web search via Firecrawl, structures the results, and returns the response to the calling interface flow."
      },
      "typeVersion": 1
    },
    {
      "id": "1c55b030-a9f4-459b-8759-fc42720972f3",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -640,
        -448
      ],
      "parameters": {
        "color": 7,
        "width": 1424,
        "height": 304,
        "content": "## Internet Search Chat Interface\nHandles user-facing chat interactions for the Internet Search feature.\nReceives chat messages, forwards the search query to the backend Firecrawl service, formats the returned data, and sends the final response back to the user."
      },
      "typeVersion": 1
    },
    {
      "id": "5d29f451-e073-48f7-b4e5-7a70c4115676",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        336,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 304,
        "content": "## Firecrawl Account Credits Monitor\nMonitors Firecrawl account credit usage.\nAllows manual triggering to check the remaining available credits, returning the usage summary and terminating the monitor flow."
      },
      "typeVersion": 1
    },
    {
      "id": "9e41f6a8-0189-4815-8cb0-0c9e6a011644",
      "name": "Receive search query",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -576,
        16
      ],
      "webhookId": "125888cf-8570-4874-b0b8-0d8554f682f8",
      "parameters": {
        "path": "620a78d5-00a6-4a05-9587-837a8d23ef7c",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "d1c5d503-3643-416d-989d-553e20b3fcee",
      "name": "Search the web (Firecrawl)",
      "type": "@mendable/n8n-nodes-firecrawl.firecrawl",
      "position": [
        -352,
        16
      ],
      "parameters": {
        "query": "={{ $json.body.consultaPesquisa }}",
        "operation": "search",
        "requestOptions": {}
      },
      "credentials": {
        "firecrawlApi": {
          "id": "credential-id",
          "name": "firecrawlApi Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "94f362a0-ec97-4005-8680-a74dd04a0df9",
      "name": "Answer search query",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -128,
        16
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.4
    },
    {
      "id": "55f74c19-a743-488b-83c2-3effc8426332",
      "name": "Terminate service flow",
      "type": "n8n-nodes-base.noOp",
      "position": [
        96,
        16
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "cecca7b5-e368-48e3-9322-2c45e7eb3960",
      "name": "Receive chat message",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "position": [
        -576,
        -320
      ],
      "webhookId": "c9e1d15b-a969-4082-b39f-9bc3ab2752ca",
      "parameters": {
        "public": true,
        "options": {
          "title": "Interface de Chat para Busca na Internet",
          "subtitle": "Alysson Neves (https://n8n.io/creators/alysson)",
          "customCss": ":root {\n  /* Paleta de cores principal */\n  --chat--color-primary: #e74266;\n  --chat--color-primary-shade-50: #db4061;\n  --chat--color-primary-shade-100: #cf3c5c;\n  --chat--color-secondary: #20b69e;\n  --chat--color-secondary-shade-50: #1ca08a;\n  --chat--color-white: #ffffff;\n  --chat--color-light: #f2f4f8;\n  --chat--color-light-shade-50: #e6e9f1;\n  --chat--color-light-shade-100: #c2c5cc;\n  --chat--color-medium: #d2d4d9;\n  --chat--color-dark: #101330;\n  --chat--color-disabled: #d2d4d9;\n  --chat--color-typing: #404040;\n\n  /* Layout base */\n  --chat--spacing: 1rem;\n  --chat--border-radius: 0.25rem;\n  --chat--transition-duration: 0.15s;\n  --chat--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;\n\n  /* Dimensões da janela do chat */\n  --chat--window--width: 400px;\n  --chat--window--height: 600px;\n  --chat--window--bottom: var(--chat--spacing);\n  --chat--window--right: var(--chat--spacing);\n  --chat--window--z-index: 9999;\n  --chat--window--border: 1px solid var(--chat--color-light-shade-50);\n  --chat--window--border-radius: var(--chat--border-radius);\n  --chat--window--margin-bottom: var(--chat--spacing);\n\n  /* Estilos do cabeçalho */\n  --chat--header-height: auto;\n  --chat--header--padding: var(--chat--spacing);\n\n  /*\n    Fundo do cabeçalho:\n    - Imagem alinhada à direita, centralizada verticalmente\n    - Altura da imagem = 100% da altura do cabeçalho\n  */\n  --chat--header--background: url(\"https://user-images.githubusercontent.com/10284570/173569848-c624317f-42b1-45a6-ab09-f0ea3c247648.png\")\n    right center / auto 100% no-repeat\n    var(--chat--color-dark);\n\n  --chat--header--color: var(--chat--color-light);\n  --chat--header--border-top: none;\n  --chat--header--border-bottom: none;\n  --chat--header--border-left: none;\n  --chat--header--border-right: none;\n  --chat--heading--font-size: 1.2rem;/*2em;*/\n  --chat--subtitle--font-size: inherit;\n  --chat--subtitle--line-height: 1.8;\n\n  /* Estilos das mensagens */\n  --chat--message--font-size: 1rem;\n  --chat--message--padding: var(--chat--spacing);\n  --chat--message--border-radius: var(--chat--border-radius);\n  --chat--message-line-height: 1.5;\n  --chat--message--margin-bottom: calc(var(--chat--spacing) * 1);\n  --chat--message--bot--background: var(--chat--color-white);\n  --chat--message--bot--color: var(--chat--color-dark);\n  --chat--message--bot--border: none;\n  --chat--message--user--background: var(--chat--color-secondary);\n  --chat--message--user--color: var(--chat--color-white);\n  --chat--message--user--border: none;\n  --chat--message--pre--background: rgba(0, 0, 0, 0.05);\n  --chat--messages-list--padding: var(--chat--spacing);\n\n  /* Botão de toggle do chat */\n  --chat--toggle--size: 64px;\n  --chat--toggle--width: var(--chat--toggle--size);\n  --chat--toggle--height: var(--chat--toggle--size);\n  --chat--toggle--border-radius: 50%;\n  --chat--toggle--background: var(--chat--color-primary);\n  --chat--toggle--hover--background: var(--chat--color-primary-shade-50);\n  --chat--toggle--active--background: var(--chat--color-primary-shade-100);\n  --chat--toggle--color: var(--chat--color-white);\n\n  /* Área de input */\n  --chat--textarea--height: 50px;\n  --chat--textarea--max-height: 30rem;\n  --chat--input--font-size: inherit;\n  --chat--input--border: 0;\n  --chat--input--border-radius: 0;\n  --chat--input--padding: 0.8rem;\n  --chat--input--background: var(--chat--color-white);\n  --chat--input--text-color: initial;\n  --chat--input--line-height: 1.5;\n  --chat--input--placeholder--font-size: var(--chat--input--font-size);\n  --chat--input--border-active: 0;\n  --chat--input--left--panel--width: 2rem;\n\n  /* Botões gerais */\n  --chat--button--color: var(--chat--color-light);\n  --chat--button--background: var(--chat--color-primary);\n  --chat--button--padding: calc(var(--chat--spacing) * 1 / 2) var(--chat--spacing);\n  --chat--button--border-radius: var(--chat--border-radius);\n  --chat--button--hover--color: var(--chat--color-light);\n  --chat--button--hover--background: var(--chat--color-primary-shade-50);\n  --chat--close--button--color-hover: var(--chat--color-primary);\n\n  /* Botões de envio e anexos */\n  --chat--input--send--button--background: var(--chat--color-white);\n  --chat--input--send--button--color: var(--chat--color-secondary);\n  --chat--input--send--button--background-hover: var(--chat--color-primary-shade-50);\n  --chat--input--send--button--color-hover: var(--chat--color-secondary-shade-50);\n  --chat--input--file--button--background: var(--chat--color-white);\n  --chat--input--file--button--color: var(--chat--color-secondary);\n  --chat--input--file--button--background-hover: var(--chat--input--file--button--background);\n  --chat--input--file--button--color-hover: var(--chat--color-secondary-shade-50);\n  --chat--files-spacing: 0.25rem;\n\n  /* Corpo e rodapé do chat */\n  --chat--body--background: var(--chat--color-light);\n  --chat--footer--background: var(--chat--color-light);\n  --chat--footer--color: var(--chat--color-dark);\n}\n\n/* Limita a largura das mensagens no balão */\n.chat-message {\n  max-width: 50%;\n}\n\n/* Fonte do cabeçalho (título + subtítulo) em Arial */\n.chat-header,\n.chat-header * {\n  font-family: Arial, Helvetica, sans-serif !important;\n}\n",
          "responseMode": "responseNodes",
          "inputPlaceholder": "Digite aqui..."
        },
        "initialMessages": "🤖 Olá! Como posso ajudar você hoje?"
      },
      "typeVersion": 1.4
    },
    {
      "id": "1a5c7d26-cf8d-4dac-98bd-390b67b8881a",
      "name": "Query search server (HTTP)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -128,
        -320
      ],
      "parameters": {
        "url": "={{ $json.webhookUrl }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "consultaPesquisa",
              "value": "={{ $('Receive chat message').item.json.chatInput }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "e383074e-3c9b-4140-86c2-d468c1feba07",
      "name": "Reply to the user in the chat",
      "type": "@n8n/n8n-nodes-langchain.chat",
      "position": [
        320,
        -320
      ],
      "parameters": {
        "message": "={{ $json.output }}",
        "options": {},
        "waitUserReply": false
      },
      "typeVersion": 1
    },
    {
      "id": "a0499bf1-b38b-449b-a315-96df8c6ddb5b",
      "name": "Terminate interface flow",
      "type": "n8n-nodes-base.noOp",
      "position": [
        544,
        -320
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "cecacc25-6dc9-45e7-8d7a-60b80c0c28ba",
      "name": "Format search response (Python)",
      "type": "n8n-nodes-base.code",
      "position": [
        96,
        -320
      ],
      "parameters": {
        "language": "python",
        "pythonCode": "import json\n\n# Retrieve the JSON payload from the previous node\ndata = items[0].get(\"json\", {})\n\n# If the payload is a JSON string, parse it into a Python object\nif isinstance(data, str):\n    data = json.loads(data)\n\n# If the payload is a list, use the first element as the main object\nif isinstance(data, list) and data:\n    data = data[0]\n\n# Navigate to data.web, which is expected to be a list of web items\nweb_items = data.get(\"data\", {}).get(\"web\", [])\n\nblocks = []\n\nfor item in web_items:\n    position = str(item.get(\"position\", \"\"))\n    url = item.get(\"url\", \"\")\n    title = item.get(\"title\", \"\")\n    description = item.get(\"description\", \"\")\n\n    # Build a clickable Markdown link using the URL, if present\n    markdown_link = f\"[{url}]({url})\" if url else \"\"\n\n    # Build the heading line:\n    # - Use \"####\" to create a smaller heading\n    # - Escape the closing parenthesis to avoid Markdown list interpretation\n    if position and title:\n        title_line = f\"#### {position}\\\\) {title}\"\n    elif position:\n        title_line = f\"#### {position}\\\\)\"\n    elif title:\n        title_line = f\"#### {title}\"\n    else:\n        title_line = \"\"\n\n    # Each block has the format:\n    # 1) Heading (title_line)\n    # 2) Description\n    # 3) Clickable link\n    block_lines = [title_line, description, markdown_link]\n\n    # Join non-empty lines with a blank line between them\n    block = \"\\n\\n\".join(line for line in block_lines if line)\n    blocks.append(block)\n\n# Use a horizontal separator between result blocks\noutput_text = \"\\n\\n-----\\n\\n\".join(blocks).strip()\n\nreturn [{\n    \"json\": {\n        \"output\": output_text\n    }\n}]\n"
      },
      "typeVersion": 2
    },
    {
      "id": "eb1d665a-21cf-47e0-b9ae-08e9a7beae24",
      "name": "Define constants",
      "type": "n8n-nodes-base.set",
      "position": [
        -352,
        -320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ad1474cc-d06c-45ea-9779-2e4ac47da5f9",
              "name": "webhookUrl",
              "type": "string",
              "value": "https://n8n.dudarobotics.cloud/webhook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b52569fa-a18b-4b2c-8fe9-e32267a3a684",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1168,
        -448
      ],
      "parameters": {
        "width": 496,
        "height": 640,
        "content": "## Internet Search Chat with Firecrawl\n\n### How it works\n1. A user sends a query via the chat widget and the Chat Trigger captures the message.\n2. The chat flow posts the query to the backend webhook (HTTP Request) which forwards it to the search service.\n3. The webhook calls Firecrawl to run the web search and returns raw results.\n4. A formatter converts the raw results into concise Markdown blocks and separators.\n5. The chat node sends the formatted search summary back to the user.\n6. Optional: an admin can manually trigger a credits check to review Firecrawl usage.\n\n### Setup\n- [ ] Add Firecrawl API credentials in n8n.\n- [ ] Update the webhook URL in the \"Define constants\" node to your n8n instance URL.\n- [ ] Configure and enable the Chat Trigger (make it public and set initial messages).\n- [ ] Ensure the webhook node path matches the constant and is reachable from the chat node.\n- [ ] Test the chat by sending a sample query and verify the formatted search results.\n- [ ] (Optional) Run the manual \"Check credits\" trigger to monitor Firecrawl account usage.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "96fb707f-a004-406b-a188-2b31d8760075",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        400,
        16
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "21efbd3f-d8bb-4b64-a7cb-b808e6697de9",
      "name": "Verify Firecrawl Account Credit Balance",
      "type": "@mendable/n8n-nodes-firecrawl.firecrawl",
      "position": [
        624,
        16
      ],
      "parameters": {
        "operation": "teamCreditUsage",
        "requestOptions": {}
      },
      "credentials": {
        "firecrawlApi": {
          "id": "credential-id",
          "name": "firecrawlApi Credential"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7c0d7a57-cb7b-4bd5-be98-fc22207d44b5",
      "name": "Terminate Monitor Flow",
      "type": "n8n-nodes-base.noOp",
      "position": [
        848,
        16
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": true,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d49505fb-d0ea-4702-b028-3ba99c13ba54",
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Verify Firecrawl Account Credit Balance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Define constants": {
      "main": [
        [
          {
            "node": "Query search server (HTTP)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Answer search query": {
      "main": [
        [
          {
            "node": "Terminate service flow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive chat message": {
      "main": [
        [
          {
            "node": "Define constants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive search query": {
      "main": [
        [
          {
            "node": "Search the web (Firecrawl)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query search server (HTTP)": {
      "main": [
        [
          {
            "node": "Format search response (Python)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search the web (Firecrawl)": {
      "main": [
        [
          {
            "node": "Answer search query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply to the user in the chat": {
      "main": [
        [
          {
            "node": "Terminate interface flow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format search response (Python)": {
      "main": [
        [
          {
            "node": "Reply to the user in the chat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Firecrawl Account Credit Balance": {
      "main": [
        [
          {
            "node": "Terminate Monitor Flow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}