{
  "id": "LfzcEYPmQ3RRkrSB",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "crisp",
  "tags": [],
  "nodes": [
    {
      "id": "8e5d273c-6e7b-4478-83b1-9ee5c2647749",
      "name": "crisp",
      "type": "n8n-nodes-base.webhook",
      "position": [
        640,
        736
      ],
      "parameters": {
        "path": "b16f5841-783c-47de-a4ae-b11a81be200c",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "c7617483-2442-4349-ac9b-1279baf0bed9",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1616,
        864
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f651678d-6155-40e4-abe5-c9a97f9563fa",
      "name": "get",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        1088,
        640
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "session_id",
              "keyValue": "={{ $json.body.data.session_id }}"
            }
          ]
        },
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "UTnFJYqs2RBUfKbl",
          "cachedResultUrl": "/projects/x7mWBmZLdHejTdq6/datatables/UTnFJYqs2RBUfKbl",
          "cachedResultName": "crisp"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "89620a25-f766-4f8b-933e-b8a070fca336",
      "name": "closed",
      "type": "n8n-nodes-base.if",
      "position": [
        864,
        736
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.body.data.content.namespace }}",
              "rightValue": "state:resolved"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f6ee5bc3-953a-4f2a-a4be-b62e06bdf907",
      "name": "insert",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        1088,
        832
      ],
      "parameters": {
        "columns": {
          "value": {
            "text": "={{ $json.body.data.content }}",
            "type": "={{ $json.body.event }}",
            "session_id": "={{ $json.body.data.session_id }}"
          },
          "schema": [
            {
              "id": "session_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "session_id",
              "defaultMatch": false
            },
            {
              "id": "text",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "text",
              "defaultMatch": false
            },
            {
              "id": "type",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "type",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "UTnFJYqs2RBUfKbl",
          "cachedResultUrl": "/projects/x7mWBmZLdHejTdq6/datatables/UTnFJYqs2RBUfKbl",
          "cachedResultName": "crisp"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b8cc6245-5337-4846-ba98-4bd685595f46",
      "name": "format",
      "type": "n8n-nodes-base.code",
      "position": [
        1312,
        640
      ],
      "parameters": {
        "jsCode": "// Get all messages from the data table\nconst messages = $input.all();\n\n// Sort messages by their id to maintain chronological order\nconst sortedMessages = messages.sort((a, b) => a.json.id - b.json.id);\n\n// Separate user messages (questions) and operator messages (answers)\nconst userMessages = [];\nconst operatorMessages = [];\n\nfor (const message of sortedMessages) {\n  const messageData = message.json;\n  \n  // Check type field for 'message:send' (user questions)\n  if (messageData.type === 'message:send') {\n    userMessages.push({\n      content: messageData.text || '',\n      session_id: messageData.session_id,\n      id: messageData.id\n    });\n  } \n  // Check type field for 'message:received' (operator answers)\n  else if (messageData.type === 'message:received') {\n    operatorMessages.push({\n      content: messageData.text || '',\n      session_id: messageData.session_id,\n      id: messageData.id\n    });\n  }\n}\n\n// Format into Q&A pairs matching user messages with operator responses in chronological order\nconst qaPairs = [];\nconst maxPairs = Math.max(userMessages.length, operatorMessages.length);\n\nfor (let i = 0; i < maxPairs; i++) {\n  const question = userMessages[i]?.content || '';\n  const answer = operatorMessages[i]?.content || '';\n  \n  if (question || answer) {\n    qaPairs.push({\n      question: question,\n      answer: answer\n    });\n  }\n}\n\n// Create a formatted text version for AI processing\nlet formattedConversation = 'Conversation Q&A:\\n\\n';\n\nfor (let i = 0; i < qaPairs.length; i++) {\n  formattedConversation += `Q${i + 1}: ${qaPairs[i].question}\\n`;\n  formattedConversation += `A${i + 1}: ${qaPairs[i].answer}\\n\\n`;\n}\n\n// Return the formatted data\nreturn [\n  {\n    json: {\n      qaPairs: qaPairs,\n      formattedConversation: formattedConversation,\n      totalQuestions: userMessages.length,\n      totalAnswers: operatorMessages.length,\n      conversationSummary: `${qaPairs.length} Q&A pairs extracted from conversation`\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "1f63d5e0-4f3a-462d-b6aa-f9a6bdeb0571",
      "name": "Gen Help",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1536,
        640
      ],
      "parameters": {
        "text": "=Based on the following customer support conversation Q&A pairs, create a comprehensive help documentation article. Extract the key problem, solution, and any important details. Format it as a clear, reusable help doc entry.\n\nIMPORTANT: Remove all personal identifying information (PII) from the help documentation including:\n- Email addresses\n- Names of people or organizations\n- Organization codes or IDs\n- Passwords or credentials\n- Phone numbers\n- Account numbers\n- Any other sensitive personal data\n\nReplace PII with generic placeholders like [USER_EMAIL], [ORGANIZATION_NAME], [ACCOUNT_ID], etc.\n\nConversation: {{ $json.formattedConversation }}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "70bd923d-664b-44ac-abda-d5f0844487d2",
      "name": "store-doc",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        1888,
        640
      ],
      "parameters": {
        "columns": {
          "value": {
            "doc": "={{ $json.text }}",
            "publish": false
          },
          "schema": [
            {
              "id": "doc",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "doc",
              "defaultMatch": false
            },
            {
              "id": "publish",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "publish",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "cMiRhzsbTqFjiXXW",
          "cachedResultUrl": "/projects/x7mWBmZLdHejTdq6/datatables/cMiRhzsbTqFjiXXW",
          "cachedResultName": "crisphelp"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1463b4c3-69c6-4ed8-8c40-57c9fbf76c7c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        368
      ],
      "parameters": {
        "width": 1072,
        "height": 176,
        "content": "## Crisp chat \u2192 Helpdoc generator\n\n### How it works\n1. Receive Crisp chat events via a webhook and trigger when a session is marked resolved.\n2. Save the chat messages to a raw chats datatable to capture the full session.\n3. When the chat is finished, format messages into chronological Q&A pairs and a short summary.\n4. Call the language model to convert the Q&A into a clear help article and automatically remove or replace all PII with placeholders.\n5. Store the sanitized help article in a helpdocs datatable for publishing, emailing, or further review.\n\n### Setup\n- [ ] Add the workflow webhook URL to Crisp (Workspace settings \u2192 Advanced \u2192 Webhooks).\n- [ ] Connect your OpenAI account / API key in workflow credentials.\n- [ ] Create or select a datatable for raw chats and set its ID in the workflow.\n- [ ] Create or select a datatable for generated helpdocs and set its ID in the workflow.\n- [ ] Verify the trigger condition matches your \"resolved\" chat state.\n- [ ] Run a closed-chat test to confirm a helpdoc is generated and saved."
      },
      "typeVersion": 1
    },
    {
      "id": "eb65cade-fed9-443b-89bd-805b4e485e3d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        640
      ],
      "parameters": {
        "content": "### Check if resolved"
      },
      "typeVersion": 1
    },
    {
      "id": "9c653b23-4870-43e1-8dbb-84ea7249451d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        800
      ],
      "parameters": {
        "content": "### Add chat to table"
      },
      "typeVersion": 1
    },
    {
      "id": "129ad5e4-3ff0-4a8f-acc3-bf4731a788c8",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        560
      ],
      "parameters": {
        "width": 384,
        "height": 208,
        "content": "### Chat finished, format the chats into a Q+A "
      },
      "typeVersion": 1
    },
    {
      "id": "dac54ef9-abe8-4f03-a080-17ff67fe4130",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        560
      ],
      "parameters": {
        "width": 528,
        "height": 208,
        "content": "### Create the Helpdoc and store it.\n#### You could add an email node or Google docs node after here too."
      },
      "typeVersion": 1
    },
    {
      "id": "768593a2-0ffd-4ac4-b69d-46e1bbab3e17",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        640
      ],
      "parameters": {
        "height": 224,
        "content": "### Use this in Crisp/Settings/Workspace settings/Advanced"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "2938d0ae-fc32-42b3-a9ff-4025b98d5061",
  "connections": {
    "get": {
      "main": [
        [
          {
            "node": "format",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "crisp": {
      "main": [
        [
          {
            "node": "closed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "closed": {
      "main": [
        [
          {
            "node": "get",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "insert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "format": {
      "main": [
        [
          {
            "node": "Gen Help",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gen Help": {
      "main": [
        [
          {
            "node": "store-doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Gen Help",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}