{"id":"Yu2P4qvlbmpPT3mg","meta":{"instanceId":"b35269c8495db354f1459fb10ec8f343e34cd6c71fb24a004bb668ccda72b4e6","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],"webhookId":"09b95814-ef46-410f-adfb-7ef2683d499d","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éponds à 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":"## 🧠 Mini Workflow — 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":{"id":"Eyt0iPqZCLeZxqlC","name":"OpenAi account"}},"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":{"id":"sqvLt8TzAknSm5NM","name":"n8n account 2"}},"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":"## 📊 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],"webhookId":"176f23d4-71b3-41e0-9364-43bea6be01d3","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çus\nreturn items.map(item => {\n  // Récupération 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ût global\n  const globalCost = (completionTokens * completionTokensPrice) + (promptTokens * promptTokensPrice);\n\n  // Retourner l'item avec globalCost ajouté\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ût 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èle\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énération 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>🌙</span>\n      <div class=\"toggle-switch\"></div>\n      <span>☀️</span>\n    </div>\n  </div>\n\n  <div class=\"section-title\"><i class=\"fas fa-wallet\"></i> Statistiques Clés</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)} €</div>\n      <div class=\"kpi-label\">Coût total</div>\n    </div>\n    <div class=\"kpi-card\">\n      <div class=\"kpi-value kpi-negative\">${avgCostPerConversation} €</div>\n      <div class=\"kpi-label\">Coût 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é\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és 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":"## 🤖 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":"## ⚙️ 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 “chat message” 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,"pinData":{"Edit Fields1":[{"json":{"name":"claude-4.5-sonnet","promptTokensPrice":0.000003,"completionTokensPrice":0.000015}},{"json":{"name":"claude-4.5-sonnet-extended-context","promptTokensPrice":0.000006,"completionTokensPrice":0.0000225}},{"json":{"name":"gpt-5","promptTokensPrice":0.00000125,"completionTokensPrice":0.00001}},{"json":{"name":"gpt-5-mini","promptTokensPrice":2.5e-7,"completionTokensPrice":0.000002}},{"json":{"name":"gpt-5-nano","promptTokensPrice":5e-8,"completionTokensPrice":4e-7}},{"json":{"name":"Gemini-2.5-Pro","promptTokensPrice":0.00000125,"completionTokensPrice":0.00000125}},{"json":{"name":"Gemini-1.5-Flash","promptTokensPrice":7.5e-7,"completionTokensPrice":0.000003}},{"json":{"name":"gpt-4o","promptTokensPrice":0.0000025,"completionTokensPrice":0.00001}},{"json":{"name":"gpt-4o-mini","promptTokensPrice":1.5e-7,"completionTokensPrice":6e-7}},{"json":{"name":"gpt-4.1-mini","promptTokensPrice":4e-7,"completionTokensPrice":0.0000016}},{"json":{"name":"gpt-4.1-nano","promptTokensPrice":1e-7,"completionTokensPrice":4e-7}},{"json":{"name":"o3-mini","promptTokensPrice":0.0000011,"completionTokensPrice":0.0000044}},{"json":{"name":"Gemini-2.0-Flash-Lite","promptTokensPrice":7.5e-8,"completionTokensPrice":3e-7}},{"json":{"name":"DeepSeek-V3","promptTokensPrice":2.7e-7,"completionTokensPrice":0.0000011}},{"json":{"name":"Claude-3.7-Sonnet","promptTokensPrice":0.000003,"completionTokensPrice":0.000015}},{"json":{"name":"gpt-oss-20b","promptTokensPrice":3e-8,"completionTokensPrice":1.4e-7}}]},"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}]]}}}