AutomationFlowsAI & RAG › AI Model Usage Dashboard: Track Token Metrics and Costs for LLM Workflows

AI Model Usage Dashboard: Track Token Metrics and Costs for LLM Workflows

ByHugo @hugo-misery on n8n.io

This template is designed to collect execution data from your AI workflows and generate an interactive dashboard for easy monitoring. It's compatible with any AI Agent or RAG workflow in n8n. Track messages, tokens used (prompt/completion), session IDs, model names, and compute…

Chat trigger trigger★★★★★ complexityAI-powered30 nodesChat TriggerAgentMemory Buffer WindowOpenAI ChatData Tablen8n
AI & RAG Trigger: Chat trigger Nodes: 30 Complexity: ★★★★★ AI nodes: yes Added:

This workflow corresponds to n8n.io template #9497 — we link there as the canonical source.

This workflow follows the Agent → Chat Trigger recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "Yu2P4qvlbmpPT3mg",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "[Template] - Dashboard Chat",
  "tags": [],
  "nodes": [
    {
      "id": "85c930e9-cb8a-47ad-8082-5ae97c945b4b",
      "name": "When chat message received",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "position": [
        -320,
        1264
      ],
      "parameters": {
        "public": true,
        "options": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "cbd8fb15-5cf0-4993-a5f5-c732496c3efe",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -96,
        1264
      ],
      "parameters": {
        "text": "=R\u00e9ponds \u00e0 ce message : \n{{ $json.chatInput }}\n",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "e0decd9d-dc72-4283-815b-08f79b1b33b9",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        -48,
        1456
      ],
      "parameters": {},
      "typeVersion": 1.3
    },
    {
      "id": "522553ba-e4eb-42d9-ae80-d6de88577792",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        1648
      ],
      "parameters": {
        "color": 3,
        "width": 1968,
        "height": 464,
        "content": "## \ud83e\udde0 Mini Workflow \u2014 Token Tracking\n"
      },
      "typeVersion": 1
    },
    {
      "id": "0f270c23-7f82-49b4-8bbb-4362d12fcd60",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -224,
        1456
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d36b0ff3-8367-4790-ae92-141df8205be1",
      "name": "Get Excution ID",
      "type": "n8n-nodes-base.set",
      "position": [
        272,
        1440
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "3677fb1a-e0b3-4468-bd10-6250ee768329",
              "name": "id",
              "type": "string",
              "value": "={{$execution.id}}"
            },
            {
              "id": "44775e78-8260-4316-a358-39b8b941313e",
              "name": "model",
              "type": "string",
              "value": "={{$('OpenAI Chat Model').params.model}}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6f390583-909a-4ecd-822e-12feb703ff30",
      "name": "Model/Token Info",
      "type": "n8n-nodes-base.set",
      "position": [
        576,
        1872
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "701dd054-2196-489d-8c9c-05d802ddf0d9",
              "name": "model_name",
              "type": "string",
              "value": "={{ $('OpenAI Chat Model').params.model.value }}"
            },
            {
              "id": "4ec60898-644f-46c4-9b14-a306c64c2da9",
              "name": "completionTokens",
              "type": "number",
              "value": "={{ $json.data.resultData.runData['OpenAI Chat Model'][0].data.ai_languageModel[0][0].json.tokenUsage.completionTokens }}"
            },
            {
              "id": "10c28ae4-4618-48c9-9787-63acd3fe66b3",
              "name": "promptTokens",
              "type": "number",
              "value": "={{ $json.data.resultData.runData['OpenAI Chat Model'][0].data.ai_languageModel[0][0].json.tokenUsage.promptTokens }}"
            },
            {
              "id": "41d5534d-dc94-4751-a233-446b039c3a43",
              "name": "totalTokens",
              "type": "number",
              "value": "={{ $json.data.resultData.runData['OpenAI Chat Model'][0].data.ai_languageModel[0][0].json.tokenUsage.totalTokens }}"
            },
            {
              "id": "ec0edb07-dabe-44fd-93df-a901838472c8",
              "name": "executionId",
              "type": "string",
              "value": "={{ $json.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a47a978e-c1aa-4f98-89b4-e25294ff9d63",
      "name": "Insert row2",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        496,
        1440
      ],
      "parameters": {
        "columns": {
          "value": {
            "action": "={{ $('When chat message received').item.json.action }}",
            "output": "={{ $('AI Agent').item.json.output }}",
            "chatInput": "={{ $('When chat message received').item.json.chatInput }}",
            "sessionId": "={{ $('When chat message received').item.json.sessionId }}",
            "executionId": "={{ $json.id }}"
          },
          "schema": [
            {
              "id": "sessionId",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sessionId",
              "defaultMatch": false
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false
            },
            {
              "id": "output",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "output",
              "defaultMatch": false
            },
            {
              "id": "chatInput",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "chatInput",
              "defaultMatch": false
            },
            {
              "id": "completionTokens",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "completionTokens",
              "defaultMatch": false
            },
            {
              "id": "promptTokens",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "promptTokens",
              "defaultMatch": false
            },
            {
              "id": "totalTokens",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "totalTokens",
              "defaultMatch": false
            },
            {
              "id": "globalCost",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "globalCost",
              "defaultMatch": false
            },
            {
              "id": "modelName",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "modelName",
              "defaultMatch": false
            },
            {
              "id": "executionId",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "executionId",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "GyHAqQLTtmZbynYI",
          "cachedResultUrl": "/projects/E58XbkoO8SgET2Sl/datatables/GyHAqQLTtmZbynYI",
          "cachedResultName": "Template - data"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7b0b70b9-5b36-4e36-b811-da21af4dbaae",
      "name": "Get an execution",
      "type": "n8n-nodes-base.n8n",
      "position": [
        352,
        1872
      ],
      "parameters": {
        "options": {
          "activeWorkflows": true
        },
        "resource": "execution",
        "operation": "get",
        "executionId": "={{ $json.executionId }}",
        "requestOptions": {}
      },
      "credentials": {
        "n8nApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "cdd04485-cdbc-4015-85a1-fdd5c359f4f4",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -320,
        1872
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6552176e-15d3-4886-b8b8-f463d0c49e08",
      "name": "Get row(s)",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -96,
        1872
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "modelName",
              "condition": "isEmpty"
            }
          ]
        },
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "GyHAqQLTtmZbynYI",
          "cachedResultUrl": "/projects/E58XbkoO8SgET2Sl/datatables/GyHAqQLTtmZbynYI",
          "cachedResultName": "Template - data"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ca373635-6cc4-44e1-b4f5-d44ed3d487b1",
      "name": "Update row(s)",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        1376,
        1856
      ],
      "parameters": {
        "columns": {
          "value": {
            "modelName": "={{ $json.model_name }}",
            "globalCost": "={{ $json.globalCost }}",
            "totalTokens": "={{ $json.promptTokens }}",
            "promptTokens": "={{ $json.promptTokens }}",
            "completionTokens": "={{ $json.completionTokens }}"
          },
          "schema": [
            {
              "id": "sessionId",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "sessionId",
              "defaultMatch": false
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false
            },
            {
              "id": "output",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "output",
              "defaultMatch": false
            },
            {
              "id": "chatInput",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "chatInput",
              "defaultMatch": false
            },
            {
              "id": "completionTokens",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "completionTokens",
              "defaultMatch": false
            },
            {
              "id": "promptTokens",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "promptTokens",
              "defaultMatch": false
            },
            {
              "id": "totalTokens",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "totalTokens",
              "defaultMatch": false
            },
            {
              "id": "globalCost",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "globalCost",
              "defaultMatch": false
            },
            {
              "id": "modelName",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "modelName",
              "defaultMatch": false
            },
            {
              "id": "executionId",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "executionId",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "executionId",
              "keyValue": "={{ $json.executionId }}"
            }
          ]
        },
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "GyHAqQLTtmZbynYI",
          "cachedResultUrl": "/projects/E58XbkoO8SgET2Sl/datatables/GyHAqQLTtmZbynYI",
          "cachedResultName": "Template - data"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "63788429-3fd4-423d-8b29-6b8d4101720e",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        -128,
        992
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "95f26c22-8da0-4d86-91f5-ee633cc72e98",
              "name": "today",
              "type": "string",
              "value": "={{ $today }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a3372fa9-18a2-4152-891d-2e40faf0b31f",
      "name": "Insert row1",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -304,
        2256
      ],
      "parameters": {
        "columns": {
          "value": {
            "name": "={{ $json.name }}",
            "promptTokensPrice": "={{ $json.promptTokensPrice }}",
            "completionTokensPrice": "={{ $json.completionTokensPrice }}"
          },
          "schema": [
            {
              "id": "name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "name",
              "defaultMatch": false
            },
            {
              "id": "promptTokensPrice",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "promptTokensPrice",
              "defaultMatch": false
            },
            {
              "id": "completionTokensPrice",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "completionTokensPrice",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "5tsC5vulvGwYGS2g",
          "cachedResultUrl": "/projects/E58XbkoO8SgET2Sl/datatables/5tsC5vulvGwYGS2g",
          "cachedResultName": "Model - Price"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a61ae8a0-85a6-4e1f-b3ec-8f4248839816",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        2144
      ],
      "parameters": {
        "color": 5,
        "width": 544,
        "height": 272,
        "content": "## \ud83d\udcca LLM Pricing Table for n8n\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c89ef6c5-8b35-45d3-8e21-b54125323bee",
      "name": "Get row(s)1",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        80,
        992
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "GyHAqQLTtmZbynYI",
          "cachedResultUrl": "/projects/E58XbkoO8SgET2Sl/datatables/GyHAqQLTtmZbynYI",
          "cachedResultName": "Template - data"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9f4069e9-1d31-455c-9d4c-782ff0857ba0",
      "name": "Edit Fields1",
      "type": "n8n-nodes-base.set",
      "position": [
        -80,
        2256
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f52f1004-3bed-448b-9655-b6c61d238f57",
              "name": "",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c3c21917-4e60-4545-b524-2f5bb28789bf",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        128,
        1872
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "451c7483-b793-47d9-869b-695bea8771ab",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        352,
        1680
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "e9c3fa27-22b9-42f5-bf51-e51f78abbe94",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        912
      ],
      "parameters": {
        "color": 6,
        "width": 1152,
        "height": 256,
        "content": "## Generate Dashboard"
      },
      "typeVersion": 1
    },
    {
      "id": "a92b9155-33e0-4d53-8e48-7d6ccd48a433",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -320,
        992
      ],
      "parameters": {
        "path": "176f23d4-71b3-41e0-9364-43bea6be01d3",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "3ce8907e-c978-4769-942a-806cd7182351",
      "name": "Get row(s)3",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        784,
        1744
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "5tsC5vulvGwYGS2g",
          "cachedResultUrl": "/projects/E58XbkoO8SgET2Sl/datatables/5tsC5vulvGwYGS2g",
          "cachedResultName": "Model - Price"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0d493cb2-d367-4eb7-968e-1b05e8e0dee5",
      "name": "Merge1",
      "type": "n8n-nodes-base.merge",
      "position": [
        944,
        1856
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "advanced": true,
        "mergeByFields": {
          "values": [
            {
              "field1": "name",
              "field2": "model_name"
            }
          ]
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "4f7b3e8e-8b1b-4ccc-af0f-2371ca846d95",
      "name": "Code in JavaScript1",
      "type": "n8n-nodes-base.code",
      "position": [
        1152,
        1856
      ],
      "parameters": {
        "jsCode": "// Parcours tous les items re\u00e7us\nreturn items.map(item => {\n  // R\u00e9cup\u00e9ration des valeurs depuis l'item\n  const completionTokens = item.json.completionTokens || 0;\n  const promptTokens = item.json.promptTokens || 0;\n  const completionTokensPrice = item.json.completionTokensPrice || 0;\n  const promptTokensPrice = item.json.promptTokensPrice || 0;\n\n  // Calcul du co\u00fbt global\n  const globalCost = (completionTokens * completionTokensPrice) + (promptTokens * promptTokensPrice);\n\n  // Retourner l'item avec globalCost ajout\u00e9\n  return {\n    json: {\n      ...item.json,\n      globalCost\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a5c21eed-3dcf-4a38-a341-d8bc42aaaf22",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        1200
      ],
      "parameters": {
        "color": 4,
        "width": 1104,
        "height": 416,
        "content": "## Chat Example :\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "07c09e80-88a2-443b-bd8a-59db83d8c0a1",
      "name": "No Operation, do nothing1",
      "type": "n8n-nodes-base.noOp",
      "position": [
        272,
        1264
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "10c02d77-5179-44a5-a219-213921b41377",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        288,
        992
      ],
      "parameters": {
        "jsCode": "// === Calcul des KPI pour les conversations ===\nlet totalConversations = 0;\nlet totalCompletionTokens = 0;\nlet totalPromptTokens = 0;\nlet totalTokens = 0;\nlet totalCost = 0;\nlet sessions = new Set();\nlet modelUsage = {};\nlet conversationsParJour = [];\n\nfor (const item of items) {\n  const data = item.json;\n\n  totalConversations += 1;\n  totalCompletionTokens += data.completionTokens || 0;\n  totalPromptTokens += data.promptTokens || 0;\n  totalTokens += data.totalTokens || 0;\n\n  // Calcul du co\u00fbt global si absent\n  let globalCost = data.globalCost;\n  if (globalCost === null || globalCost === undefined) {\n    const completionTokensPrice = data.completionTokensPrice || 0;\n    const promptTokensPrice = data.promptTokensPrice || 0;\n    globalCost = (data.completionTokens || 0) * completionTokensPrice + (data.promptTokens || 0) * promptTokensPrice;\n  }\n  totalCost += globalCost;\n\n  // Comptage des sessions uniques\n  if (data.sessionId) sessions.add(data.sessionId);\n\n  // Comptage messages par mod\u00e8le\n  const model = data.modelName || \"unknown\";\n  if (!modelUsage[model]) modelUsage[model] = 0;\n  modelUsage[model] += 1;\n\n  // Tableau journalier\n  if (data.createdAt) {\n    const day = data.createdAt.split(\"T\")[0];\n    let dayEntry = conversationsParJour.find(d => d.date === day);\n    if (!dayEntry) {\n      dayEntry = { date: day, count: 0, totalCost: 0, promptTokens: 0, completionTokens: 0 };\n      conversationsParJour.push(dayEntry);\n    }\n    dayEntry.count += 1;\n    dayEntry.totalCost += globalCost;\n    dayEntry.promptTokens += data.promptTokens || 0;\n    dayEntry.completionTokens += data.completionTokens || 0;\n  }\n}\n\n// Tri par date\nconversationsParJour = conversationsParJour.sort((a, b) => a.date.localeCompare(b.date));\n\n// Moyennes\nconst avgTokensPerConversation = totalConversations > 0 ? (totalTokens / totalConversations).toFixed(2) : 0;\nconst avgCostPerConversation = totalConversations > 0 ? (totalCost / totalConversations).toFixed(6) : 0;\n\n// === G\u00e9n\u00e9ration HTML ===\nconst html = `\n<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Dashboard Conversations</title>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js\"></script>\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\">\n<style>\n:root {\n  --radius: 0.65rem;\n  --background: #ffffff;\n  --foreground: #000000;\n  --card: #f8f9fa;\n  --card-foreground: #000000;\n  --primary: #3b82f6;\n  --chart-conversations: #3b82f6;\n  --chart-tokens-prompt: #3b82f6;\n  --chart-tokens-completion: #10b981;\n  --destructive: #ef4444;\n  --muted: #9ca3af;\n  --border: #e5e7eb;\n}\nbody.dark {\n  --background: #0b142c;\n  --foreground: #f1f5f9;\n  --card: #1e293b;\n  --card-foreground: #f1f5f9;\n  --primary: #60a5fa;\n  --chart-conversations: #60a5fa;\n  --chart-tokens-prompt: #60a5fa;\n  --chart-tokens-completion: #34d399;\n  --destructive: #f87171;\n  --muted: #64748b;\n  --border: #334155;\n}\n    * {\n      margin: 0;\n      padding: 0;\n      box-sizing: border-box;\n    }\n\n    body {\n      background: var(--background);\n      color: var(--foreground);\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n      padding: 20px;\n      transition: background-color 0.3s ease, color 0.3s ease;\n    }\n\n    .container {\n      max-width: 1400px;\n      margin: 0 auto;\n    }\n\n    .header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      margin-bottom: 40px;\n    }\n\n    .header h1 {\n      font-size: 2.5rem;\n      font-weight: 700;\n    }\n\n    .section-title-header {\n      font-size: 2.5rem;\n      font-weight: 600;\n      margin-top: 40px;\n      margin-bottom: 20px;\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    }\n\n    .section-title-header i {\n      color: var(--chart-topup);\n      font-size: 2.5rem;\n    }\n\n    .section-title {\n      font-size: 1.5rem;\n      font-weight: 600;\n      margin-top: 40px;\n      margin-bottom: 20px;\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    }\n\n    .section-title i {\n      color: var(--primary);\n      font-size: 1.75rem;\n    }\n\n    .kpi-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n      gap: 1rem;\n      margin-bottom: 30px;\n    }\n\n    .kpi-card {\n      background: var(--card);\n      border-radius: 0.625rem;\n      padding: 20px;\n      border: 1px solid var(--border);\n      transition: background-color 0.3s ease, border-color 0.3s ease;\n      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n    }\n\n    .kpi-value {\n      font-size: 2rem;\n      font-weight: 700;\n      margin-bottom: 8px;\n      color: var(--card-foreground);\n      transition: color 0.3s ease;\n    }\n\n    .kpi-positive {\n      color: var(--chart-investment);\n    }\n\n    .kpi-negative {\n      color: var(--destructive);\n    }\n\n    .kpi-label {\n      font-size: 0.875rem;\n      color: var(--muted-foreground);\n      transition: color 0.3s ease;\n    }\n\n    .charts-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));\n      gap: 20px;\n      margin-bottom: 30px;\n    }\n\n    .chart-container {\n      background: var(--card);\n      border-radius: 0.625rem;\n      padding: 20px;\n      border: 1px solid var(--border);\n      transition: background-color 0.3s ease, border-color 0.3s ease;\n      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n      position: relative;\n      height: 400px;\n    }\n\n    .chart-wrapper {\n      position: relative;\n      height: 100%;\n    }\n\n    .theme-toggle {\n      display: flex;\n      align-items: center;\n      gap: 10px;\n      cursor: pointer;\n      user-select: none;\n    }\n\n    .toggle-switch {\n      width: 48px;\n      height: 24px;\n      background: var(--border);\n      border-radius: 12px;\n      position: relative;\n      cursor: pointer;\n      transition: background-color 0.3s ease;\n    }\n\n    .toggle-switch::after {\n      content: '';\n      position: absolute;\n      width: 20px;\n      height: 20px;\n      background: var(--card);\n      border-radius: 50%;\n      top: 2px;\n      left: 2px;\n      transition: transform 0.3s ease;\n      box-shadow: 0 1px 3px rgba(0,0,0,0.3);\n    }\n\n    body.dark .toggle-switch::after {\n      transform: translateX(24px);\n    }\n\n    @media (max-width: 768px) {\n      .kpi-grid {\n        grid-template-columns: 1fr;\n      }\n      .charts-grid {\n        grid-template-columns: 1fr;\n      }\n      .header {\n        flex-direction: column;\n        gap: 20px;\n      }\n    }\n\n</style>\n</head>\n<body class=\"dark\">\n<div class=\"container\">\n  <div class=\"header\">\n    <h1><i class=\"fas fa-comments\"></i> Dashboard Conversations</h1>\n    <div class=\"theme-toggle\" onclick=\"toggleTheme()\">\n      <span>\ud83c\udf19</span>\n      <div class=\"toggle-switch\"></div>\n      <span>\u2600\ufe0f</span>\n    </div>\n  </div>\n\n  <div class=\"section-title\"><i class=\"fas fa-wallet\"></i> Statistiques Cl\u00e9s</div>\n  <div class=\"kpi-grid\">\n    <div class=\"kpi-card\">\n      <div class=\"kpi-value kpi-positive\">${totalConversations.toLocaleString('fr-FR')}</div>\n      <div class=\"kpi-label\">Total Messages</div>\n    </div>\n    <div class=\"kpi-card\">\n      <div class=\"kpi-value kpi-positive\">${sessions.size.toLocaleString('fr-FR')}</div>\n      <div class=\"kpi-label\">Sessions uniques</div>\n    </div>\n    <div class=\"kpi-card\">\n      <div class=\"kpi-value kpi-positive\">${totalTokens.toLocaleString('fr-FR')}</div>\n      <div class=\"kpi-label\">Total Tokens</div>\n    </div>\n    <div class=\"kpi-card\">\n      <div class=\"kpi-value kpi-positive\">${avgTokensPerConversation}</div>\n      <div class=\"kpi-label\">Tokens moyens par message</div>\n    </div>\n    <div class=\"kpi-card\">\n      <div class=\"kpi-value kpi-negative\">${totalCost.toFixed(6)} \u20ac</div>\n      <div class=\"kpi-label\">Co\u00fbt total</div>\n    </div>\n    <div class=\"kpi-card\">\n      <div class=\"kpi-value kpi-negative\">${avgCostPerConversation} \u20ac</div>\n      <div class=\"kpi-label\">Co\u00fbt moyen par message</div>\n    </div>\n  </div>\n\n  <div class=\"section-title\"><i class=\"fas fa-chart-bar\"></i> Historique Journalier</div>\n  <div class=\"charts-grid\">\n    <div class=\"chart-container\"><div class=\"chart-wrapper\"><canvas id=\"conversationsChart\"></canvas></div></div>\n    <div class=\"chart-container\"><div class=\"chart-wrapper\"><canvas id=\"tokensChart\"></canvas></div></div>\n  </div>\n</div>\n\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js\"></script>\n<script>\nconst conversationsParJour = ${JSON.stringify(conversationsParJour)};\n\nlet charts = {};\n\nfunction getCSSVariable(varName){return getComputedStyle(document.documentElement).getPropertyValue(varName).trim();}\nfunction getThemeColors(){const isDark=document.body.classList.contains('dark'); return {textPrimary:isDark?'#f1f5f9':'#000', textSecondary:isDark?'#94a3b8':'#6b7280', gridColor:isDark?'rgba(148,163,184,0.1)':'rgba(0,0,0,0.1)', borderColor:isDark?'#475569':'#d1d5db', tooltip:isDark?'rgba(15,23,42,0.9)':'rgba(0,0,0,0.9)', chartColor:getCSSVariable('--chart-conversations'), chartPrompt:getCSSVariable('--chart-tokens-prompt'), chartCompletion:getCSSVariable('--chart-tokens-completion')}};\n\nfunction createChartOptions(label){\n  const colors=getThemeColors();\n  return {responsive:true, maintainAspectRatio:false, plugins:{title:{display:true,text:label,color:colors.textPrimary,font:{size:16,weight:'bold'},padding:20},legend:{display:true,position:'top'},tooltip:{backgroundColor:colors.tooltip,titleColor:'#ffffff',bodyColor:'#ffffff',borderColor:colors.borderColor,borderWidth:1,padding:12,displayColors:true}}, scales:{x:{grid:{display:true,color:colors.gridColor,drawBorder:false},ticks:{color:colors.textSecondary,font:{size:11}}},y:{grid:{display:true,color:colors.gridColor,drawBorder:false},ticks:{color:colors.textSecondary,font:{size:11}},beginAtZero:true}}};\n}\n\nfunction initCharts(){\n  const colors=getThemeColors();\n  const ctx=document.getElementById('conversationsChart').getContext('2d');\n  charts.conversations=new Chart(ctx,{type:'bar',data:{labels:conversationsParJour.map(d=>new Date(d.date).toLocaleDateString('fr-FR',{day:'2-digit',month:'2-digit'})),datasets:[{label:'Nombre de messages',data:conversationsParJour.map(d=>d.count),backgroundColor:colors.chartColor,borderColor:colors.chartColor,borderWidth:0,borderRadius:6}]},options:createChartOptions('Messages journaliers')});\n\n  // Graphique tokens empil\u00e9\n  const ctxTokens=document.getElementById('tokensChart').getContext('2d');\n  charts.tokens=new Chart(ctxTokens,{type:'bar',data:{labels:conversationsParJour.map(d=>new Date(d.date).toLocaleDateString('fr-FR',{day:'2-digit',month:'2-digit'})),datasets:[{label:'Prompt Tokens',data:conversationsParJour.map(d=>d.promptTokens),backgroundColor:colors.chartPrompt},{label:'Completion Tokens',data:conversationsParJour.map(d=>d.completionTokens),backgroundColor:colors.chartCompletion}]},options:{...createChartOptions('Tokens utilis\u00e9s par jour'),scales:{x:{stacked:true,grid:{display:true,color:colors.gridColor,drawBorder:false},ticks:{color:colors.textSecondary,font:{size:11}}},y:{stacked:true,grid:{display:true,color:colors.gridColor,drawBorder:false},ticks:{color:colors.textSecondary,font:{size:11}},beginAtZero:true}}}});\n}\n\nfunction updateChartsTheme(){\n  const colors=getThemeColors();\n  if(charts.conversations){\n    charts.conversations.data.datasets[0].backgroundColor=colors.chartColor;\n    charts.conversations.data.datasets[0].borderColor=colors.chartColor;\n    charts.conversations.options.plugins.title.color=colors.textPrimary;\n    charts.conversations.options.scales.x.grid.color=colors.gridColor;\n    charts.conversations.options.scales.x.ticks.color=colors.textSecondary;\n    charts.conversations.options.scales.y.grid.color=colors.gridColor;\n    charts.conversations.options.scales.y.ticks.color=colors.textSecondary;\n    charts.conversations.options.plugins.tooltip.backgroundColor=colors.tooltip;\n    charts.conversations.update();\n  }\n  if(charts.tokens){\n    charts.tokens.data.datasets[0].backgroundColor=colors.chartPrompt;\n    charts.tokens.data.datasets[1].backgroundColor=colors.chartCompletion;\n    charts.tokens.options.plugins.title.color=colors.textPrimary;\n    charts.tokens.options.scales.x.grid.color=colors.gridColor;\n    charts.tokens.options.scales.x.ticks.color=colors.textSecondary;\n    charts.tokens.options.scales.y.grid.color=colors.gridColor;\n    charts.tokens.options.scales.y.ticks.color=colors.textSecondary;\n    charts.tokens.options.plugins.tooltip.backgroundColor=colors.tooltip;\n    charts.tokens.update();\n  }\n}\n\nfunction toggleTheme(){document.body.classList.toggle('dark'); updateChartsTheme();}\ndocument.addEventListener('DOMContentLoaded',initCharts);\n</script>\n</body>\n</html>\n`;\n\n// Retourner le HTML en binaire pour la node n8n\nreturn [{ binary: { data: Buffer.from(html, 'utf8') } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "63aae3c1-6479-477f-a26b-85f3d589a6bc",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        496,
        992
      ],
      "parameters": {
        "options": {},
        "respondWith": "binary"
      },
      "typeVersion": 1.4
    },
    {
      "id": "b2f02fbb-2604-4f60-9449-6a8fa76ee670",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        912
      ],
      "parameters": {
        "width": 496,
        "height": 352,
        "content": "## \ud83e\udd16 n8n AI Workflow Dashboard\n\n### This template helps you collect and visualize data from your AI workflows in a simple and interactive way.\n\n- Track messages, sessions, tokens, and costs for each model.\n- Interactive HTML dashboard with KPIs: messages, sessions, tokens, and costs.\n- Compatible with any AI Agent or RAG workflow in n8n.\n\n### Use this dashboard to monitor AI activity and usage metrics at a glance, and easily identify trends or anomalies in your workflows."
      },
      "typeVersion": 1
    },
    {
      "id": "19d31dbb-e89e-40c6-853d-f898ebbe1122",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        1296
      ],
      "parameters": {
        "width": 496,
        "height": 464,
        "content": "## \u2699\ufe0f Setup & Run\n\n### Follow these steps to get your workflow up and running in n8n:\n\n- Import the JSON workflow into your n8n instance.\n- Create the Model price and Messages tables.\n- Import token cost data for your LLM models (LLM Pricing).\n- Configure the \u201cchat message\u201d node according to your input channels.\n- Once messages are collected, the Token Tracking sub-workflow calculates token usage and costs.\n- Visualize the dashboard using the HTML response returned by the webhook.\n\n### After setup, your workflow will automatically track AI activity, compute costs, and provide a live dashboard to monitor all your KPIs."
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d1513dc9-185a-4183-bdcd-531b250483c3",
  "connections": {
    "Merge1": {
      "main": [
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "No Operation, do nothing1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Excution ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s)": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Get row(s)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s)1": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s)3": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert row1": {
      "main": [
        [
          {
            "node": "Edit Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Update row(s)": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Excution ID": {
      "main": [
        [
          {
            "node": "Insert row2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get an execution",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get an execution": {
      "main": [
        [
          {
            "node": "Model/Token Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Model/Token Info": {
      "main": [
        [
          {
            "node": "Get row(s)3",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get row(s)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript1": {
      "main": [
        [
          {
            "node": "Update row(s)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When chat message received": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This template is designed to collect execution data from your AI workflows and generate an interactive dashboard for easy monitoring. It's compatible with any AI Agent or RAG workflow in n8n. Track messages, tokens used (prompt/completion), session IDs, model names, and compute…

Source: https://n8n.io/workflows/9497/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This project is an automation workflow that generates a personalized resume and cover letter for each job listing. Generates an HTML resume from your data. Hosts it live on GitHub Pages. Converts it t

HTTP Request, Agent, OpenAI Chat +10
AI & RAG

This AI Agent helps you create short links from your original URLs. Each generated short link is automatically stored in a database table for easy management and tracking. Provide a long URL to the Ag

Data Table, Chat Trigger, Agent +4
AI & RAG

HDW Lead Geländewagen. Uses chatTrigger, lmChatOpenAi, memoryBufferWindow, outputParserStructured. Chat trigger; 92 nodes.

Chat Trigger, OpenAI Chat, Memory Buffer Window +5
AI & RAG

router-agent. Uses chatTrigger, agent, lmChatOpenAi, executeWorkflow. Chat trigger; 59 nodes.

Chat Trigger, Agent, OpenAI Chat +1
AI & RAG

This workflow serves as a comprehensive "Workflow Nodes SEO & Documentation Generator". It uses AI to analyze, rename, and document n8n workflows, offering a streamlined way to optimize workflow reada

Form Trigger, n8n, Output Parser Autofixing +11