{
  "id": "w0iWHqwJVcIyX6cA",
  "meta": {
    "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
      ],
      "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": {
          "name": "<your 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
      ],
      "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\u00f5es 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\u00e7alho */\n  --chat--header-height: auto;\n  --chat--header--padding: var(--chat--spacing);\n\n  /*\n    Fundo do cabe\u00e7alho:\n    - Imagem alinhada \u00e0 direita, centralizada verticalmente\n    - Altura da imagem = 100% da altura do cabe\u00e7alho\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\u00e3o 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  /* \u00c1rea 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\u00f5es 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\u00f5es 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\u00e9 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\u00e3o */\n.chat-message {\n  max-width: 50%;\n}\n\n/* Fonte do cabe\u00e7alho (t\u00edtulo + subt\u00edtulo) em Arial */\n.chat-header,\n.chat-header * {\n  font-family: Arial, Helvetica, sans-serif !important;\n}\n",
          "responseMode": "responseNodes",
          "inputPlaceholder": "Digite aqui..."
        },
        "initialMessages": "\ud83e\udd16 Ol\u00e1! Como posso ajudar voc\u00ea 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": {
          "name": "<your 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,
  "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
          }
        ]
      ]
    }
  }
}