[
  {
    "updatedAt": "2026-10-21T21:47:10.347Z",
    "createdAt": "2026-10-21T08:19:14.923Z",
    "id": "l9kx8WlAcxkZ2x9c",
    "name": "Chatbot API - Command Code",
    "description": null,
    "active": true,
    "isArchived": false,
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "chat",
          "responseMode": "responseNode",
          "options": {
            "allowedOrigins": "*"
          },
          "authentication": "headerAuth"
        },
        "id": "aa000000-0000-4000-8000-000000000001",
        "name": "Webhook",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          -460,
          0
        ],
        "webhookId": "aa000000-0000-4000-8000-0000000000f1",
        "credentials": {
          "httpHeaderAuth": {
            "id": "mVCkEw5sWtGqwfiz",
            "name": "Webhook Auth - Chatbot"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// Susun payload untuk Command Code Provider API (format OpenAI Chat Completions)\nconst input = $input.first().json;\nconst body = input.body ?? {};\nconst query = input.query ?? {};\n\nconst sessionId = String(body.sessionId ?? query.sessionId ?? 'default');\nconst userMessage = String(body.message ?? query.message ?? '').trim();\n\nif (!userMessage) {\n  throw new Error('Field \"message\" wajib diisi.');\n}\n\nconst model = body.model ?? $env.CHAT_MODEL ?? 'deepseek/deepseek-v4-flash';\nconst systemPrompt = body.system ?? $env.SYSTEM_PROMPT ?? 'Kamu adalah asisten AI yang membantu.';\n\n// Memori percakapan per-session. Persisten hanya pada eksekusi produksi\n// (webhook production), tidak tersimpan saat klik 'Test workflow'.\nconst staticData = $getWorkflowStaticData('global');\nstaticData.memory = staticData.memory || {};\nconst stored = staticData.memory[sessionId] || [];\n\nconst history = Array.isArray(body.history)\n  ? body.history\n  : stored.slice(-10);\n\nconst messages = [\n  { role: 'system', content: systemPrompt },\n  ...history,\n  { role: 'user', content: userMessage },\n];\n\nreturn [{ json: { sessionId, model, messages, userMessage } }];"
        },
        "id": "aa000000-0000-4000-8000-000000000002",
        "name": "Build Request",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -240,
          0
        ]
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $env.CMD_API_BASE + '/chat/completions' }}",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ model: $json.model, messages: $json.messages, stream: false }) }}",
          "options": {
            "timeout": 120000,
            "response": {
              "response": {
                "neverError": false
              }
            }
          }
        },
        "id": "aa000000-0000-4000-8000-000000000003",
        "name": "Command Code API",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          0,
          0
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 3000,
        "onError": "continueRegularOutput",
        "credentials": {
          "httpHeaderAuth": {
            "id": "4pZCab9psqQKX2GY",
            "name": "Command Code API"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// Ambil jawaban model, simpan ke memori, siapkan respons HTTP.\nconst staticData = $getWorkflowStaticData('global');\nstaticData.memory = staticData.memory || {};\n\nconst req = $('Build Request').item.json;\nconst res = $input.first().json;\n\nconst upstreamError = res.error;\nconst reply = upstreamError\n  ? `[error] ${upstreamError.message ?? JSON.stringify(upstreamError)}`\n  : (res.choices?.[0]?.message?.content ?? '').trim();\n\nif (!upstreamError && reply) {\n  const history = staticData.memory[req.sessionId] || [];\n  history.push({ role: 'user', content: req.userMessage });\n  history.push({ role: 'assistant', content: reply });\n  staticData.memory[req.sessionId] = history.slice(-20);\n}\n\nreturn [{\n  json: {\n    sessionId: req.sessionId,\n    model: req.model,\n    reply,\n    error: upstreamError ? true : false,\n    usage: res.usage ?? null,\n  },\n}];"
        },
        "id": "aa000000-0000-4000-8000-000000000004",
        "name": "Extract Reply",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          220,
          0
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify({ sessionId: $json.sessionId, reply: $json.reply, error: $json.error, usage: $json.usage }) }}",
          "options": {
            "responseCode": "={{ $json.error ? 502 : 200 }}"
          }
        },
        "id": "aa000000-0000-4000-8000-000000000005",
        "name": "Respond to Webhook",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          440,
          0
        ]
      }
    ],
    "connections": {
      "Webhook": {
        "main": [
          [
            {
              "node": "Build Request",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Build Request": {
        "main": [
          [
            {
              "node": "Command Code API",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Command Code API": {
        "main": [
          [
            {
              "node": "Extract Reply",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Extract Reply": {
        "main": [
          [
            {
              "node": "Respond to Webhook",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "settings": {
      "executionOrder": "v1",
      "saveManualExecutions": true,
      "callerPolicy": "workflowsFromSameOwner"
    },
    "staticData": {
      "global": {
        "memory": {
          "fin-1": [
            {
              "role": "user",
              "content": "Jawab satu kata: hidup"
            },
            {
              "role": "assistant",
              "content": "Hidup"
            },
            {
              "role": "user",
              "content": "Aku tadi minta jawab apa?"
            },
            {
              "role": "assistant",
              "content": "Kamu minta aku menjawab satu kata: hidup."
            }
          ],
          "pub-1": [
            {
              "role": "user",
              "content": "Jawab satu kata: publik"
            },
            {
              "role": "assistant",
              "content": "publik"
            }
          ],
          "default": [
            {
              "role": "user",
              "content": "hello"
            },
            {
              "role": "assistant",
              "content": "Halo! Ada yang bisa saya bantu?"
            }
          ],
          "cron-cek": [
            {
              "role": "user",
              "content": "Jawab satu kata: hidup"
            },
            {
              "role": "assistant",
              "content": "Hidup"
            }
          ],
          "pw-reset": [
            {
              "role": "user",
              "content": "Jawab satu kata: hidup"
            },
            {
              "role": "assistant",
              "content": "hidup"
            }
          ]
        }
      }
    },
    "meta": null,
    "nodeGroups": [],
    "pinData": {},
    "versionId": "d422bce9-f2b6-4bce-997f-553296818fd1",
    "activeVersionId": "d422bce9-f2b6-4bce-997f-553296818fd1",
    "versionCounter": 5,
    "triggerCount": 1,
    "sourceWorkflowId": null,
    "tags": [],
    "shared": [
      {
        "updatedAt": "2026-10-21T08:19:14.923Z",
        "createdAt": "2026-10-21T08:19:14.923Z",
        "role": "workflow:owner",
        "workflowId": "l9kx8WlAcxkZ2x9c",
        "projectId": "O5dS2R3vFvvVOZZK",
        "project": {
          "updatedAt": "2026-10-23T06:20:42.590Z",
          "createdAt": "2026-10-21T04:53:49.401Z",
          "id": "O5dS2R3vFvvVOZZK",
          "name": "Mohammad Khoiruman <arifkhoiruman2@gmail.com>",
          "type": "personal",
          "icon": null,
          "description": null,
          "customTelemetryTags": [],
          "creatorId": "77323fa2-5a74-405e-999c-961acc6ca1ba"
        }
      }
    ],
    "versionMetadata": {
      "name": null,
      "description": null
    }
  },
  {
    "updatedAt": "2026-10-23T06:59:02.982Z",
    "createdAt": "2026-10-23T06:52:11.168Z",
    "id": "WKCVyMQBetXh8FFk",
    "name": "ImpactView — Sheet Sync Preview",
    "description": null,
    "active": true,
    "isArchived": false,
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "sheet-preview",
          "responseMode": "responseNode",
          "authentication": "headerAuth",
          "options": {}
        },
        "id": "sheet-webhook",
        "name": "Webhook",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          -620,
          0
        ],
        "webhookId": "ee000000-0000-4000-8000-0000000000f1",
        "credentials": {
          "httpHeaderAuth": {
            "id": "nvkabokeng6itHSl",
            "name": "ImpactView n8n Webhook"
          }
        }
      },
      {
        "parameters": {
          "url": "https://docs.google.com/spreadsheets/d/1kKPyGWZZKS-5TtCSXBtQwX_GwxnPybUlp8FmWgEjcvM/gviz/tq?tqx=out:csv&sheet=NANO",
          "options": {
            "timeout": 60000,
            "response": {
              "response": {
                "responseFormat": "text"
              }
            }
          }
        },
        "id": "sheet-nano",
        "name": "Sheet NANO",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -380,
          -180
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 3000,
        "notesInFlow": true,
        "notes": "CSV publik tab NANO"
      },
      {
        "parameters": {
          "url": "https://docs.google.com/spreadsheets/d/1kKPyGWZZKS-5TtCSXBtQwX_GwxnPybUlp8FmWgEjcvM/gviz/tq?tqx=out:csv&sheet=MICRO",
          "options": {
            "timeout": 60000,
            "response": {
              "response": {
                "responseFormat": "text"
              }
            }
          }
        },
        "id": "sheet-micro",
        "name": "Sheet MICRO",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -380,
          -20
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 3000,
        "notesInFlow": true,
        "notes": "CSV publik tab MICRO"
      },
      {
        "parameters": {
          "url": "https://docs.google.com/spreadsheets/d/1kKPyGWZZKS-5TtCSXBtQwX_GwxnPybUlp8FmWgEjcvM/gviz/tq?tqx=out:csv&sheet=MACRO",
          "options": {
            "timeout": 60000,
            "response": {
              "response": {
                "responseFormat": "text"
              }
            }
          }
        },
        "id": "sheet-macro",
        "name": "Sheet MACRO",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -380,
          140
        ],
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 3000,
        "notesInFlow": true,
        "notes": "CSV publik tab MACRO"
      },
      {
        "parameters": {
          "url": "=https://impactview.mdnetwork.web.id/api/v1/n8n/events/{{ $('Webhook').first().json.body.event_id }}/kol-index",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "options": {
            "timeout": 60000
          }
        },
        "id": "kol-index",
        "name": "KOL Index",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          -140,
          300
        ],
        "credentials": {
          "httpHeaderAuth": {
            "id": "6jYfhBUaEAyf7Q6l",
            "name": "ImpactView API"
          }
        },
        "retryOnFail": true,
        "maxTries": 3,
        "waitBetweenTries": 3000
      },
      {
        "parameters": {
          "jsCode": "\n// ============================================================================\n// Normalisasi + diff antara Google Sheet dan data KOL di ImpactView.\n// Tidak menulis apa pun ke database.\n// ============================================================================\n\nconst eventId = Number($('Webhook').first().json.body?.event_id ?? 0);\nif (!eventId) throw new Error('event_id wajib dikirim di body.');\n\nconst csvNano = $('Sheet NANO').first().json.data ?? '';\nconst csvMicro = $('Sheet MICRO').first().json.data ?? '';\nconst csvMacro = $('Sheet MACRO').first().json.data ?? '';\nconst index = $('KOL Index').first().json ?? {};\n\nconst peringatan = [];\n\n// ---------------------------------------------------------------- util\nfunction parseCsv(text) {\n  const rows = [];\n  let row = [], field = '', inQ = false;\n  for (let i = 0; i < text.length; i++) {\n    const c = text[i];\n    if (inQ) {\n      if (c === '\"') {\n        if (text[i + 1] === '\"') { field += '\"'; i++; }\n        else inQ = false;\n      } else field += c;\n    } else if (c === '\"') inQ = true;\n    else if (c === ',') { row.push(field); field = ''; }\n    else if (c === '\\n') { row.push(field); rows.push(row); row = []; field = ''; }\n    else if (c !== '\\r') field += c;\n  }\n  if (field.length || row.length) { row.push(field); rows.push(row); }\n  return rows.filter((r) => r.some((x) => String(x).trim() !== ''));\n}\n\nconst pick = (obj, ...names) => {\n  for (const n of names) {\n    const k = Object.keys(obj).find((x) => x.trim().toLowerCase() === n);\n    if (k !== undefined && String(obj[k]).trim() !== '') return String(obj[k]).trim();\n  }\n  return '';\n};\n\nfunction normPlatform(v) {\n  const s = String(v || '').trim().toLowerCase();\n  if (s.startsWith('insta')) return 'instagram';\n  if (s.startsWith('tik')) return 'tiktok';\n  if (s.startsWith('youtu')) return 'youtube';\n  if (s === 'x' || s === 'twitter') return 'x';\n  if (s.startsWith('face')) return 'facebook';\n  return s || null;\n}\n\nfunction extractUsername(raw, platform) {\n  let s = String(raw || '').trim();\n  if (!s) return '';\n  s = s.replace(/^Https?/i, 'https');\n  // rapikan double slash SETELAH domain: \"instagram.com//user\" -> \"instagram.com/user\"\n  s = s.replace('://', '\\u0000').replace(/\\/{2,}/g, '/').replace('\\u0000', '://');\n  const m = s.match(/(?:instagram\\.com|tiktok\\.com)\\/@?([A-Za-z0-9_.]+)/i);\n  if (m) return m[1].toLowerCase();\n  return s.replace(/^@/, '').replace(/\\/+$/, '').split('?')[0].trim().toLowerCase();\n}\n\nfunction parseFollowers(v) {\n  let s = String(v == null ? '' : v).trim().toLowerCase().replace(/\\s/g, '');\n  if (!s) return null;\n\n  // \"1,535\" -> \"1535\"\n  s = s.replace(/,/g, '');\n\n  let mult = 1;\n  if (s.endsWith('k')) { mult = 1000; s = s.slice(0, -1); }\n  else if (s.endsWith('m')) { mult = 1000000; s = s.slice(0, -1); }\n\n  // Tanpa suffix, titik = pemisah ribuan gaya Indonesia: \"1.535\" -> 1535.\n  // Dengan suffix, titik tetap desimal: \"2.1m\" -> 2.1 * 1e6.\n  if (mult === 1 && /^\\d{1,3}(\\.\\d{3})+$/.test(s)) {\n    s = s.replace(/\\./g, '');\n  }\n\n  const n = parseFloat(s);\n  if (isNaN(n)) return { ambigu: true, mentah: v };\n  return Math.round(n * mult);\n}\n\n// Kunci postingan: tahan terhadap perubahan parameter tracking (?stkn, utm_*)\nfunction postKey(url) {\n  const u = String(url || '').trim();\n  if (!u) return null;\n  let m = u.match(/instagram\\.com\\/(reel|reels|p|tv)\\/([A-Za-z0-9_-]+)/i);\n  if (m) return 'ig:' + m[2];\n  m = u.match(/tiktok\\.com\\/.*?\\/video\\/(\\d+)/i);\n  if (m) return 'tt:' + m[1];\n  m = u.match(/vt\\.tiktok\\.com\\/([A-Za-z0-9]+)/i);\n  if (m) return 'tt_short:' + m[1];\n  return 'url:' + u.split('?')[0].replace(/\\/+$/, '').toLowerCase();\n}\n\nfunction canonicalPost(url) {\n  const u = String(url || '').trim();\n  const m = u.match(/instagram\\.com\\/(reel|reels|p|tv)\\/([A-Za-z0-9_-]+)/i);\n  if (m) {\n    const type = m[1].toLowerCase() === 'reels' ? 'reel' : m[1].toLowerCase();\n    return `https://www.instagram.com/${type}/${m[2]}/`;\n  }\n  return u.split('?')[0].replace(/\\/+$/, '');\n}\n\nfunction parseDate(v) {\n  const s = String(v || '').trim();\n  let m = s.match(/^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})$/);\n  if (m) return `${m[3]}-${m[2].padStart(2, '0')}-${m[1].padStart(2, '0')}`;\n  m = s.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n  return m ? s : null;\n}\n\n// ---------------------------------------------------------------- baca sheet\nconst SHEETS = [\n  { name: 'NANO', csv: csvNano },\n  { name: 'MICRO', csv: csvMicro },\n  { name: 'MACRO', csv: csvMacro },\n];\n\nconst sheetRows = [];\n\nfor (const sh of SHEETS) {\n  const rows = parseCsv(sh.csv);\n  if (rows.length < 2) continue;\n  const header = rows[0].map((h) => String(h).trim());\n  for (let i = 1; i < rows.length; i++) {\n    const obj = {};\n    header.forEach((h, idx) => { if (h) obj[h] = rows[i][idx] ?? ''; });\n\n    const platform = normPlatform(pick(obj, 'platform'));\n    if (!platform) continue;\n\n    const linkPlatform = pick(obj, 'link platform');\n    const username = extractUsername(linkPlatform, platform);\n    if (!username) continue;\n\n    // Tier: kolom \"Column 15\" kalau ada, kalau tidak dari nama tab\n    let tier = pick(obj, 'column 15').toLowerCase();\n    if (!['mega', 'macro', 'micro', 'nano'].includes(tier)) tier = sh.name.toLowerCase();\n\n    const linkUpload = pick(obj, 'link upload', 'link upload (instagram)');\n    const key = postKey(linkUpload);\n    const followers = parseFollowers(pick(obj, 'follower'));\n\n    const baris = i + 1;\n    if (followers && followers.ambigu) {\n      peringatan.push({ tab: sh.name, baris, masalah: `FOLLOWER tidak bisa dibaca: \"${followers.mentah}\"` });\n    }\n    if (linkUpload && key && key.startsWith('ig:') && !/instagram\\.com\\/(reel|p|tv)/i.test(linkUpload)) {\n      peringatan.push({ tab: sh.name, baris, masalah: 'LINK UPLOAD tidak dikenali bentuknya' });\n    }\n    if (linkUpload && extractUsername(linkUpload) === username && !key) {\n      peringatan.push({ tab: sh.name, baris, masalah: 'LINK UPLOAD menunjuk profil, bukan postingan' });\n    }\n\n    sheetRows.push({\n      tab: sh.name,\n      baris,\n      nama: pick(obj, 'nama kol'),\n      platform,\n      username,\n      tier,\n      followers: followers && !followers.ambigu ? followers : null,\n      link_upload: linkUpload || null,\n      post_key: key,\n      tanggal_upload: parseDate(pick(obj, 'tanggal upload')),\n    });\n  }\n}\n\n// ---------------------------------------------------------------- index DB\nconst dbPlatforms = (index.platforms || []).map((p) => ({\n  ...p,\n  key: `${p.platform}|${String(p.username || '').toLowerCase()}`,\n  postKeys: (p.deliverables || []).map((d) => postKey(d.live_post_url)).filter(Boolean),\n}));\n\nconst dbByKey = new Map(dbPlatforms.map((p) => [p.key, p]));\nconst sheetByKey = new Map();\nfor (const r of sheetRows) {\n  const k = `${r.platform}|${r.username}`;\n  if (!sheetByKey.has(k)) sheetByKey.set(k, []);\n  sheetByKey.get(k).push(r);\n}\n\n// ---------------------------------------------------------------- diff\nconst kol_baru = [];\nconst kol_takedown = [];\nconst link_baru = [];\nconst link_berubah = [];\nconst data_berubah = [];\nconst cocok = [];\n\nfor (const [key, rows] of sheetByKey) {\n  const db = dbByKey.get(key);\n\n  if (!db) {\n    const r = rows[0];\n    kol_baru.push({\n      platform: r.platform, username: r.username, nama: r.nama, tier: r.tier,\n      followers: r.followers, link_upload: r.link_upload,\n      jumlah_baris: rows.length, tab: r.tab,\n    });\n    continue;\n  }\n\n  cocok.push(key);\n\n  const dbKeys = new Set(db.postKeys);\n  const seen = new Set();\n\n  for (const r of rows) {\n    if (!r.post_key) continue;\n    if (seen.has(r.post_key)) continue;\n    seen.add(r.post_key);\n\n    if (!dbKeys.has(r.post_key)) {\n      link_baru.push({\n        kol_id: db.kol_id, event_kol_id: db.event_kol_id, kol_platform_id: db.kol_platform_id,\n        platform: r.platform, username: r.username, nama: r.nama,\n        link: canonicalPost(r.link_upload), post_key: r.post_key,\n        tanggal_upload: r.tanggal_upload, tab: r.tab, baris: r.baris,\n      });\n    } else {\n      const d = (db.deliverables || []).find((x) => postKey(x.live_post_url) === r.post_key);\n      if (d && d.live_post_url && canonicalPost(d.live_post_url) !== canonicalPost(r.link_upload)) {\n        link_berubah.push({\n          deliverable_id: d.id, username: r.username, platform: r.platform,\n          dari: d.live_post_url, ke: canonicalPost(r.link_upload),\n        });\n      }\n    }\n  }\n\n  const r = rows[0];\n  if (r.followers != null && db.followers != null && Number(r.followers) !== Number(db.followers)) {\n    data_berubah.push({\n      kol_platform_id: db.kol_platform_id, username: r.username, platform: r.platform,\n      field: 'followers', dari: db.followers, ke: r.followers,\n    });\n  }\n  if (r.tier && db.tier && r.tier !== db.tier) {\n    data_berubah.push({\n      kol_platform_id: db.kol_platform_id, username: r.username, platform: r.platform,\n      field: 'tier', dari: db.tier, ke: r.tier,\n    });\n  }\n}\n\nfor (const p of dbPlatforms) {\n  if (!sheetByKey.has(p.key)) {\n    kol_takedown.push({\n      kol_id: p.kol_id, event_kol_id: p.event_kol_id, kol_platform_id: p.kol_platform_id,\n      platform: p.platform, username: p.username, nama: p.nama, tier: p.tier,\n      jumlah_deliverable: (p.deliverables || []).length,\n    });\n  }\n}\n\n\n// Pasangkan kol_baru & kol_takedown yang nama KOL-nya sama pada platform yang\n// sama: itu hampir pasti username yang berubah, bukan dua masalah terpisah.\nconst ganti_username = [];\nconst normNama = (x) => String(x || '').toLowerCase().replace(/[^a-z0-9]/g, '');\n\nfor (let i = kol_baru.length - 1; i >= 0; i--) {\n  const b = kol_baru[i];\n  if (!normNama(b.nama)) continue;\n  const j = kol_takedown.findIndex(\n    (t) => t.platform === b.platform && normNama(t.nama) === normNama(b.nama),\n  );\n  if (j < 0) continue;\n  const t = kol_takedown[j];\n  ganti_username.push({\n    nama: b.nama,\n    platform: b.platform,\n    username_lama: t.username,\n    username_baru: b.username,\n    kol_id: t.kol_id,\n    kol_platform_id: t.kol_platform_id,\n    event_kol_id: t.event_kol_id,\n    tier: b.tier,\n    followers: b.followers,\n    link_upload: b.link_upload,\n  });\n  kol_baru.splice(i, 1);\n  kol_takedown.splice(j, 1);\n}\n\n// ---------------------------------------------------------------- keluaran\nreturn [{\n  json: {\n    event_id: eventId,\n    event_name: index.event?.name ?? null,\n    generated_at: new Date().toISOString(),\n    sumber: { spreadsheet_id: '1kKPyGWZZKS-5TtCSXBtQwX_GwxnPybUlp8FmWgEjcvM', tabs: SHEETS.map((s) => s.name) },\n    summary: {\n      baris_sheet: sheetRows.length,\n      platform_db: dbPlatforms.length,\n      cocok: cocok.length,\n      kol_baru: kol_baru.length,\n      kol_takedown: kol_takedown.length,\n      ganti_username: ganti_username.length,\n      link_baru: link_baru.length,\n      link_berubah: link_berubah.length,\n      data_berubah: data_berubah.length,\n      peringatan: peringatan.length,\n    },\n    kol_baru,\n    kol_takedown,\n    ganti_username,\n    link_baru,\n    link_berubah,\n    data_berubah,\n    peringatan: peringatan.slice(0, 50),\n  },\n}];\n",
          "mode": "runOnceForAllItems"
        },
        "id": "diff",
        "name": "Normalisasi & Diff",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          100,
          0
        ],
        "notesInFlow": true,
        "notes": "Tidak menulis ke DB. Hanya menghasilkan JSON preview."
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify($json) }}",
          "options": {
            "responseCode": 200
          }
        },
        "id": "respond",
        "name": "Respond to Webhook",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          340,
          0
        ]
      }
    ],
    "connections": {
      "Webhook": {
        "main": [
          [
            {
              "node": "Sheet NANO",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Sheet NANO": {
        "main": [
          [
            {
              "node": "Sheet MICRO",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Sheet MICRO": {
        "main": [
          [
            {
              "node": "Sheet MACRO",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Sheet MACRO": {
        "main": [
          [
            {
              "node": "KOL Index",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "KOL Index": {
        "main": [
          [
            {
              "node": "Normalisasi & Diff",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Normalisasi & Diff": {
        "main": [
          [
            {
              "node": "Respond to Webhook",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "settings": {
      "executionOrder": "v1",
      "saveManualExecutions": true,
      "callerPolicy": "workflowsFromSameOwner",
      "timezone": "Asia/Jakarta"
    },
    "staticData": null,
    "meta": null,
    "nodeGroups": [],
    "pinData": {},
    "versionId": "6a963439-acab-4a73-872f-4fc9121c4c4b",
    "activeVersionId": "6a963439-acab-4a73-872f-4fc9121c4c4b",
    "versionCounter": 3,
    "triggerCount": 1,
    "sourceWorkflowId": null,
    "tags": [],
    "shared": [
      {
        "updatedAt": "2026-10-23T06:52:11.168Z",
        "createdAt": "2026-10-23T06:52:11.168Z",
        "role": "workflow:owner",
        "workflowId": "WKCVyMQBetXh8FFk",
        "projectId": "O5dS2R3vFvvVOZZK",
        "project": {
          "updatedAt": "2026-10-23T06:20:42.590Z",
          "createdAt": "2026-10-21T04:53:49.401Z",
          "id": "O5dS2R3vFvvVOZZK",
          "name": "Mohammad Khoiruman <arifkhoiruman2@gmail.com>",
          "type": "personal",
          "icon": null,
          "description": null,
          "customTelemetryTags": [],
          "creatorId": "77323fa2-5a74-405e-999c-961acc6ca1ba"
        }
      }
    ],
    "versionMetadata": {
      "name": null,
      "description": null
    }
  },
  {
    "updatedAt": "2026-10-21T21:45:11.027Z",
    "createdAt": "2026-10-21T21:29:38.313Z",
    "id": "RBeftVKCMM3sApGD",
    "name": "Agent Dashboard - Command Code",
    "description": null,
    "active": true,
    "isArchived": false,
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "agent",
          "responseMode": "responseNode",
          "options": {
            "allowedOrigins": "*"
          },
          "authentication": "headerAuth"
        },
        "id": "cc000000-0000-4000-8000-000000000001",
        "name": "Webhook",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          -620,
          0
        ],
        "webhookId": "cc000000-0000-4000-8000-0000000000f1",
        "credentials": {
          "httpHeaderAuth": {
            "id": "BmNYa8ZUOmDjn7aA",
            "name": "Webhook Auth - Agent"
          }
        }
      },
      {
        "parameters": {
          "promptType": "define",
          "text": "={{ $json.body.message }}",
          "options": {
            "systemMessage": "=Kamu adalah asisten yang membantu mengelola dashboard.\n\nAturan:\n- Selalu pakai tool yang tersedia untuk mengambil data. Jangan pernah mengarang angka atau isi data.\n- Kamu hanya punya akses BACA. Jangan mengaku sudah mengubah atau menghapus data.\n- Kalau data tidak bisa diambil lewat tool yang ada, katakan terus terang dan sebutkan tool apa yang kamu butuhkan.\n- Jawab ringkas dan jelas dalam Bahasa Indonesia."
          }
        },
        "id": "cc000000-0000-4000-8000-000000000002",
        "name": "AI Agent",
        "type": "@n8n/n8n-nodes-langchain.agent",
        "typeVersion": 3.1,
        "position": [
          -380,
          0
        ]
      },
      {
        "parameters": {
          "model": {
            "__rl": true,
            "mode": "id",
            "value": "deepseek/deepseek-v4-flash"
          },
          "responsesApiEnabled": false,
          "options": {}
        },
        "id": "cc000000-0000-4000-8000-000000000003",
        "name": "Command Code Chat Model",
        "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
        "typeVersion": 1.3,
        "position": [
          -560,
          240
        ],
        "credentials": {
          "openAiApi": {
            "id": "O15RAXfWFILGTy8N",
            "name": "Command Code (Chat Model)"
          }
        }
      },
      {
        "parameters": {
          "sessionIdType": "customKey",
          "sessionKey": "={{ $('Webhook').first().json.body.sessionId || 'default' }}",
          "contextWindowLength": 10
        },
        "id": "cc000000-0000-4000-8000-000000000004",
        "name": "Simple Memory",
        "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
        "typeVersion": 1.4,
        "position": [
          -340,
          240
        ]
      },
      {
        "parameters": {
          "name": "daftar_model_ai",
          "description": "Ambil daftar model AI yang tersedia beserta jumlahnya. Panggil tool ini saat pengguna bertanya soal model AI apa saja yang tersedia. GANTI isi code ini dengan panggilan ke API dashboard kamu.",
          "language": "javaScript",
          "jsCode": "// GANTI url di bawah dengan endpoint API dashboard kamu.\nconst res = await this.helpers.httpRequest({\n  method: 'GET',\n  url: 'https://api.commandcode.ai/provider/v1/models',\n  json: true,\n});\n\nconst items = Array.isArray(res.data) ? res.data : [];\nconst contoh = items.slice(0, 5).map((m) => m.id);\n\nreturn JSON.stringify({ jumlah_model: items.length, contoh });"
        },
        "id": "cc000000-0000-4000-8000-000000000005",
        "name": "Tool: Daftar Model AI",
        "type": "@n8n/n8n-nodes-langchain.toolCode",
        "typeVersion": 1.3,
        "position": [
          -120,
          240
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify({ sessionId: $('Webhook').first().json.body.sessionId || 'default', reply: $json.output, error: false }) }}",
          "options": {}
        },
        "id": "cc000000-0000-4000-8000-000000000006",
        "name": "Respond to Webhook",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          -100,
          0
        ]
      }
    ],
    "connections": {
      "Webhook": {
        "main": [
          [
            {
              "node": "AI Agent",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Command Code Chat Model": {
        "ai_languageModel": [
          [
            {
              "node": "AI Agent",
              "type": "ai_languageModel",
              "index": 0
            }
          ]
        ]
      },
      "Simple Memory": {
        "ai_memory": [
          [
            {
              "node": "AI Agent",
              "type": "ai_memory",
              "index": 0
            }
          ]
        ]
      },
      "Tool: Daftar Model AI": {
        "ai_tool": [
          [
            {
              "node": "AI Agent",
              "type": "ai_tool",
              "index": 0
            }
          ]
        ]
      },
      "AI Agent": {
        "main": [
          [
            {
              "node": "Respond to Webhook",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "settings": {
      "executionOrder": "v1",
      "saveManualExecutions": true,
      "callerPolicy": "workflowsFromSameOwner"
    },
    "staticData": null,
    "meta": null,
    "nodeGroups": [],
    "pinData": {},
    "versionId": "e88f6f18-747a-4346-a099-6a15934f9a40",
    "activeVersionId": "e88f6f18-747a-4346-a099-6a15934f9a40",
    "versionCounter": 5,
    "triggerCount": 1,
    "sourceWorkflowId": null,
    "tags": [],
    "shared": [
      {
        "updatedAt": "2026-10-21T21:29:38.313Z",
        "createdAt": "2026-10-21T21:29:38.313Z",
        "role": "workflow:owner",
        "workflowId": "RBeftVKCMM3sApGD",
        "projectId": "O5dS2R3vFvvVOZZK",
        "project": {
          "updatedAt": "2026-10-23T06:20:42.590Z",
          "createdAt": "2026-10-21T04:53:49.401Z",
          "id": "O5dS2R3vFvvVOZZK",
          "name": "Mohammad Khoiruman <arifkhoiruman2@gmail.com>",
          "type": "personal",
          "icon": null,
          "description": null,
          "customTelemetryTags": [],
          "creatorId": "77323fa2-5a74-405e-999c-961acc6ca1ba"
        }
      }
    ],
    "versionMetadata": {
      "name": null,
      "description": null
    }
  }
]