{
  "id": "rarpWe7vgiY8AHmp",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Analyzing uploaded Liveblocks comments attachments with AI",
  "tags": [],
  "nodes": [
    {
      "id": "ebf2043f-c524-4f1f-a112-bcc3ca70ce2e",
      "name": "Liveblocks Trigger",
      "type": "CUSTOM.liveblocksTrigger",
      "position": [
        -64,
        -112
      ],
      "parameters": {
        "events": [
          "commentCreated"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "liveblocksWebhookSigningSecretApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9204a3e9-87a9-43bb-80cf-0cda40e8ebc0",
      "name": "Get a comment",
      "type": "CUSTOM.liveblocks",
      "position": [
        176,
        -112
      ],
      "parameters": {
        "roomId": "={{ $json.event.data.roomId }}",
        "resource": "comment",
        "threadId": "={{ $json.event.data.threadId }}",
        "commentId": "={{ $json.event.data.commentId }}",
        "operation": "getComment"
      },
      "credentials": {
        "liveblocksApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "40d19409-c683-49cd-8ae9-8bef1eac988b",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        368,
        -112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "93da4049-4fd2-4b5b-8126-a9c0c86e376e",
              "operator": {
                "type": "array",
                "operation": "contains",
                "rightType": "any"
              },
              "leftValue": "={{ $json.mentionedUserIds }}",
              "rightValue": "__AI_AGENT"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ba245bc1-35ed-4873-817c-dcc9f86c2ba4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -128,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 230,
        "height": 272,
        "content": "## Comment created\nWhen a comment is created in a room."
      },
      "typeVersion": 1
    },
    {
      "id": "40e4f97f-4c97-4a24-8a87-2e2b933fed8e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 272,
        "content": "## Check if AI mentioned and attachment uploaded\nCheck if a comment has an attachment, and if it mentions the AI agent. Their ID is `\"__AI_AGENT\"` in this demo."
      },
      "typeVersion": 1
    },
    {
      "id": "f3124ad9-f874-49f0-9e7f-62fd0f6eea0c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 272,
        "content": "## Fetch attachment IDs\nIf there are attachments, loop through every attachment and fetch its URL."
      },
      "typeVersion": 1
    },
    {
      "id": "44ad1915-f785-40b5-a289-d99bc163a50a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2496,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 272,
        "content": "## Add response to thread\nThe response is then added as a comment in the same thread. The user ID is set to `\"__AI_AGENT\"`."
      },
      "typeVersion": 1
    },
    {
      "id": "fcebab59-f7a8-407a-b963-0e0c400d7cd0",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -736,
        -224
      ],
      "parameters": {
        "width": 534,
        "height": 992,
        "content": "## Analyzing uploaded Liveblocks comments attachments with AI\n\n### How it works\n \nThis example uses [Liveblocks Comments](https://liveblocks.io/comments), collaborative commenting components for React. When an AI assistant is mentioned in a thread  (e.g. \"@AI Assistant\"), it will automatically leave a response. Additionally, it will analyze any PDf or image attachments in the comments, and use them to help it respond.\n\nUsing [webhooks](https://liveblocks.io/docs/platform/webhooks), this workflow is triggered when a comment is created in a thread. If the agent's ID (`\"__AI_AGENT\"`) it will create a response. If a PDF or image file is uploaded, these will be analyzed by Anthropic and used as context. This response is then added, and users will see it appear in their apps in real time.\n\n### Setup\nThis workflow requires a Comments app installed and webhooks set up in the Liveblocks dashboard. You can try it with a demo application:\n- [ ] Download the [Next.js comments example](https://liveblocks.io/examples/comments/nextjs-comments), and run it with a secret key.\n- [ ] Find `database.ts` inside the example and uncomment the AI assistant user.\n- [ ] Insert the secret key from the project into n8n nodes: \"Get a comment\", \"Get a thread\", \"Create a comment\".\n- [ ] Go to the [Liveblocks dashboard](https://liveblocks.io/dashboard), open your project and go to \"Webhooks\". Create a new webhook in your project using a placeholder URL, and selecting \"commentCreated\" events.\n- [ ] Copy your webhook secret from this page and paste it into the \"Liveblocks Trigger\" node.\n- [ ] Expose the webhook URL from the trigger, for example with `localtunnel` or `ngrok`. Copy the production URL from the \"Liveblocks Trigger\" and replace `localhost:5678` with the new URL.\n- [ ] Your workflow is now set up! Tag @AI Assistant in the application and add attachments to trigger it.\n\n#### Localtunnel\nThe easiest way to expose your webhook URL:\n`npx localtunnel --port 5678 --subdomain your-name-here`\n\nThis creates a URL like:\n`https://honest-months-fix.loca.lt`\n\nThe URL you need for the dashboard looks like this:\n`https://honest-months-fix.loca.lt/webhook/9cc66974-aaaf-4720-b557-1267105ca78b/webhook`\n"
      },
      "typeVersion": 1
    },
    {
      "id": "51bf57e0-fb1f-4dab-8f20-ee2dafe4683a",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        -112
      ],
      "parameters": {
        "jsCode": "return $input.first().json.attachments.map((item) => ({ id: item.id }));"
      },
      "typeVersion": 2
    },
    {
      "id": "292b839a-a63b-4965-9177-b17382434760",
      "name": "Get attachment1",
      "type": "CUSTOM.liveblocks",
      "position": [
        1024,
        -112
      ],
      "parameters": {
        "roomId": "={{ $('Get a comment').item.json.roomId }}",
        "resource": "attachment",
        "attachmentId": "={{ $json.id }}"
      },
      "credentials": {
        "liveblocksApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "57affdfc-dbf7-455e-a089-55085186541d",
      "name": "Analyze document1",
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "position": [
        1488,
        -112
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-6",
          "cachedResultName": "claude-sonnet-4-6"
        },
        "options": {},
        "resource": "document",
        "documentUrls": "={{ $json.url }}"
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "799b8b8d-9829-4d82-a447-e44d8ace841e",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        1232,
        -112
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "4de5fd1b-488d-48e6-be52-e60470484186",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.mimeType }}",
                    "rightValue": "application/pdf"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "9f419375-66a9-402d-82a2-fe61f82c2af0",
                    "operator": {
                      "type": "string",
                      "operation": "startsWith"
                    },
                    "leftValue": "={{ $json.mimeType }}",
                    "rightValue": "image/"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "861b0731-4a5d-486b-befa-fc4cfb151525",
      "name": "Analyze image",
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "position": [
        1488,
        48
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-6",
          "cachedResultName": "claude-sonnet-4-6"
        },
        "options": {},
        "resource": "image",
        "imageUrls": "={{ $('Get attachment1').item.json.url }}"
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d4779db5-6db4-4373-8380-e85a14cb0365",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1792,
        -112
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "b6d0ea12-82ab-4e3f-8bfc-d39ef99c6882",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        1488,
        224
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d56dee2e-0089-42d4-b657-fb7657dc1074",
              "name": "mimeType",
              "type": "string",
              "value": "={{ $json.mimeType }}"
            },
            {
              "id": "7cdec271-69f5-4146-909d-fe49799fe164",
              "name": "name",
              "type": "string",
              "value": "={{ $json.name }}"
            },
            {
              "id": "82b7ff0e-4bc7-43f2-bb6c-91075b016ab8",
              "name": "message",
              "type": "string",
              "value": "Cannot analyze files of this type"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0544e919-f532-4bb9-b2a9-32e498faf93a",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2208,
        -112
      ],
      "parameters": {
        "text": "=You're responding to a comment left by a users. There are some file attachments attached to the comment. You've just done an analysis of the images, this is your analysis:\n\n```\n{{ JSON.stringify($json.result, null, 2) }}\n```\n\nRespond to the users prompt using the file analysis reply. This is what the user asked you:\n\n\"\"\"\n{{ $('Get a comment').item.json.bodyPlain }}\n''''",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "6229e5ba-5754-4a4a-8fd6-9d0e6d78e829",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        2208,
        80
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-5-20250929",
          "cachedResultName": "Claude Sonnet 4.5"
        },
        "options": {}
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "0d14b116-f71e-46c0-9663-f4d3434fd65e",
      "name": "Code in JavaScript1",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        -112
      ],
      "parameters": {
        "jsCode": "return { result: $input.all() };"
      },
      "typeVersion": 2
    },
    {
      "id": "60dc9f02-8f1f-4d97-8876-45c39ed2214c",
      "name": "Create a comment1",
      "type": "CUSTOM.liveblocks",
      "position": [
        2576,
        -112
      ],
      "parameters": {
        "roomId": "={{ $('Get a comment').first().json.roomId }}",
        "resource": "comment",
        "threadId": "={{ $('Get a comment').first().json.threadId }}",
        "createComment_userId": "__AI_AGENT",
        "createComment_bodyMode": "plainText",
        "createComment_bodyText": "={{ $json.output }}"
      },
      "credentials": {
        "liveblocksApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b65aff57-08a0-48fe-9b44-4540a28c867c",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 608,
        "content": "## Analyze attachments\nIf Anthropic can analyze the attachment (i.e PDF or image), feed it through an analyzer. Otherwise, say you can't analyze the file type."
      },
      "typeVersion": 1
    },
    {
      "id": "f39b4b1a-6c91-49e4-ae1d-e0903333ae7a",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Merge the analyses\nMerge the analyses back into a single list"
      },
      "typeVersion": 1
    },
    {
      "id": "cc55ab1d-0121-4dac-940c-6540d6daf0dd",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 464,
        "content": "## Create AI response\nAsk AI to write a response to the original comment, with its analysis context"
      },
      "typeVersion": 1
    },
    {
      "id": "59b3ffe4-4e79-4984-86b3-2e137b3a3192",
      "name": "If1",
      "type": "n8n-nodes-base.if",
      "position": [
        592,
        -112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3a4b15ca-e141-4096-8081-bbe359f59733",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.attachments }}",
              "rightValue": "__AI_AGENT"
            },
            {
              "id": "93da4049-4fd2-4b5b-8126-a9c0c86e376e",
              "operator": {
                "type": "array",
                "operation": "contains",
                "rightType": "any"
              },
              "leftValue": "={{ $json.mentionedUserIds }}",
              "rightValue": "__AI_AGENT"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "89a79d5b-a318-4097-b846-b20bda0e2938",
      "name": "AI Agent1",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        672,
        208
      ],
      "parameters": {
        "text": "=Respond to the users message: {{ $('Get a comment').item.json.bodyPlain }}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "6de37a66-cdab-4611-aa19-1ab05e4a0d6d",
      "name": "Anthropic Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        672,
        400
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-5-20250929",
          "cachedResultName": "Claude Sonnet 4.5"
        },
        "options": {}
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "2db5795e-9526-464a-aedb-12e272f5f074",
      "name": "Create a comment",
      "type": "CUSTOM.liveblocks",
      "position": [
        1008,
        208
      ],
      "parameters": {
        "roomId": "={{ $('Get a comment').first().json.roomId }}",
        "resource": "comment",
        "threadId": "={{ $('Get a comment').first().json.threadId }}",
        "createComment_userId": "__AI_AGENT",
        "createComment_bodyMode": "plainText",
        "createComment_bodyText": "={{ $json.output }}"
      },
      "credentials": {
        "liveblocksApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "fa2f17d3-deae-4e25-bd0a-5a9abcc1e2ca",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 448,
        "content": "## Ignore attachments and respond\nIf there are no attachments, AI generates a response to the comment, and it's added to the thread. The user ID is set to `\"__AI_AGENT\"`."
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "6a66c646-72fa-4e6d-a6d6-6d3041d20012",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "If1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If1": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI Agent1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch": {
      "main": [
        [
          {
            "node": "Analyze document1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Analyze image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Create a comment1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent1": {
      "main": [
        [
          {
            "node": "Create a comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Analyze image": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Get a comment": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get attachment1": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze document1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Get attachment1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Liveblocks Trigger": {
      "main": [
        [
          {
            "node": "Get a comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript1": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}