{
  "name": "19-telegram-research-agent-02-research-processor",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "id": "68ffd3bd-0007-464b-8488-419614799530",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -2352,
        3344
      ]
    },
    {
      "parameters": {},
      "id": "d50501cb-2b71-4bf1-b0ad-724f917e4e4c",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -2352,
        3568
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $json.SHEET_ID }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $json.SHEET_NAME }}",
          "mode": "name"
        },
        "options": {}
      },
      "id": "d3c4693c-178b-4de8-ac4b-3953d035a801",
      "name": "Sheets: Get All Rows",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -1792,
        3472
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// L\u1ecdc c\u00e1c y\u00eau c\u1ea7u c\u00f3 status=queue v\u00e0 l\u1ea5y y\u00eau c\u1ea7u \u0111\u1ea7u ti\u00ean (oldest first)\nconst config = $('Set: Config Vars1').first().json;\nconst allItems = $input.all();\n\nconst queueItems = allItems\n  .filter(item => item.json.Status === 'queue')\n  .sort((a, b) => {\n    const dateA = new Date(a.json.Created_At || 0);\n    const dateB = new Date(b.json.Created_At || 0);\n    return dateA - dateB; // oldest first\n  });\n\nif (queueItems.length === 0) {\n  return [{ json: { has_pending: false, config } }];\n}\n\nconst first = queueItems[0].json;\nreturn [{\n  json: {\n    has_pending: true,\n    request_id: first.Request_ID,\n    title: first.Title,\n    raw_request: first.Raw_Request,\n    research_type: first.Research_Type || 'other',\n    chat_id: first.Chat_ID,\n    user_id: first.User_ID,\n    created_at: first.Created_At,\n    // Pass config\n    SHEET_ID: config.SHEET_ID,\n    SHEET_NAME: config.SHEET_NAME,\n    DRIVE_ROOT_FOLDER_ID: config.DRIVE_ROOT_FOLDER_ID,\n    RESEARCH_SOURCES: config.RESEARCH_SOURCES,\n    MAX_SEARCH_RESULTS: config.MAX_SEARCH_RESULTS,\n    TOP_URLS_TO_SCRAPE: config.TOP_URLS_TO_SCRAPE,\n    REPORT_LANGUAGE: config.REPORT_LANGUAGE\n  }\n}];"
      },
      "id": "4d4d3824-76d1-4b4a-ae02-bb5dbb4f7a03",
      "name": "Code: Filter Queue",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1504,
        3472
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "check-pending",
              "leftValue": "={{ $json.has_pending }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2d026c1d-745e-4abd-aa24-43f4e67cefce",
      "name": "IF: Has Pending?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -1232,
        3472
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.SHEET_ID }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $json.SHEET_NAME }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Request_ID": "={{ $json.request_id }}",
            "Status": "processing",
            "Updated_At": "={{ new Date().toISOString() }}"
          },
          "matchingColumns": [
            "Request_ID"
          ],
          "schema": [
            {
              "id": "Request_ID",
              "displayName": "Request_ID",
              "required": false,
              "defaultMatch": true,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Updated_At",
              "displayName": "Updated_At",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "fecf9d07-31e8-4f56-b0e8-580b3cb233de",
      "name": "Sheets: Update \u2192 Processing",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        -944,
        3344
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.raw_request }}",
        "options": {
          "systemMessage": "=B\u1ea1n l\u00e0 chuy\u00ean gia research th\u1ecb tr\u01b0\u1eddng v\u00e0 ph\u00e2n t\u00edch d\u1eef li\u1ec7u h\u00e0ng \u0111\u1ea7u.\n\nNHI\u1ec6M V\u1ee4: Nghi\u00ean c\u1ee9u to\u00e0n di\u1ec7n v\u1ec1 ch\u1ee7 \u0111\u1ec1 sau:\nTITLE: {{ $json.title }}\nTYPE: {{ $json.research_type }}\n\nNGU\u1ed2N \u01afU TI\u00caN: {{ $json.RESEARCH_SOURCES }}\n\nQUY TR\u00ccNH B\u1eaeT BU\u1ed8C:\n1. T\u1ea1o 3-4 keyword t\u00ecm ki\u1ebfm ph\u00f9 h\u1ee3p v\u1edbi ch\u1ee7 \u0111\u1ec1 (ti\u1ebfng Vi\u1ec7t v\u00e0 ti\u1ebfng Anh n\u1ebfu c\u1ea7n), \u01b0u ti\u00ean domain t\u1eeb NGU\u1ed2N \u01afU TI\u00caN\n2. D\u00f9ng c\u00f4ng c\u1ee5 \"/search in Firecrawl\" v\u1edbi t\u1eebng keyword \u0111\u1ec3 l\u1ea5y danh s\u00e1ch URLs li\u00ean quan\n3. T\u1eeb k\u1ebft qu\u1ea3 search, ch\u1ecdn 4-5 URLs c\u00f3 n\u1ed9i dung c\u1ee5 th\u1ec3 v\u00e0 uy t\u00edn nh\u1ea5t v\u1ec1 ch\u1ee7 \u0111\u1ec1\n4. D\u00f9ng c\u00f4ng c\u1ee5 \"/scrape in Firecrawl2\" \u0111\u1ec3 \u0111\u1ecdc n\u1ed9i dung \u0111\u1ea7y \u0111\u1ee7 t\u1eebng URL \u0111\u00f3\n5. Thu th\u1eadp s\u1ed1 li\u1ec7u c\u1ee5 th\u1ec3, th\u1ed1ng k\u00ea, d\u1eef li\u1ec7u \u0111\u1ecbnh l\u01b0\u1ee3ng t\u1eeb c\u00e1c b\u00e0i vi\u1ebft\n\nY\u00caU C\u1ea6U:\n- \u01afu ti\u00ean t\u00ecm: s\u1ed1 li\u1ec7u th\u1ecb tr\u01b0\u1eddng, xu h\u01b0\u1edbng, \u0111\u1ed1i th\u1ee7, c\u01a1 h\u1ed9i, r\u1ee7i ro\n- M\u1ed7i URL scrape, gi\u1eef l\u1ea1i ph\u1ea7n n\u1ed9i dung quan tr\u1ecdng nh\u1ea5t\n- T\u1ed5ng h\u1ee3p v\u00e0 tr\u1ea3 v\u1ec1 k\u1ebft qu\u1ea3 research ho\u00e0n ch\u1ec9nh b\u1eb1ng ti\u1ebfng Vi\u1ec7t bao g\u1ed3m:\n  * C\u00e1c ph\u00e1t hi\u1ec7n ch\u00ednh v\u1edbi d\u1eabn ch\u1ee9ng t\u1eeb ngu\u1ed3n\n  * S\u1ed1 li\u1ec7u, th\u1ed1ng k\u00ea quan tr\u1ecdng\n  * Danh s\u00e1ch URLs \u0111\u00e3 s\u1eed d\u1ee5ng",
          "maxIterations": 15
        }
      },
      "id": "4d74712d-50e7-45e1-bc45-f08c53d0455b",
      "name": "AI Agent: Research",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        -368,
        3344
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "modelName": "models/gemini-3-flash-preview",
        "options": {
          "maxOutputTokens": 8192,
          "temperature": 0.3
        }
      },
      "id": "34b3d9a3-072d-4ee5-99d5-5746769fc388",
      "name": "Gemini Flash (Agent)",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        -496,
        3648
      ],
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=D\u1ef1a tr\u00ean k\u1ebft qu\u1ea3 research sau \u0111\u00e2y, vi\u1ebft m\u1ed9t B\u00c1O C\u00c1O CHUY\u00caN S\u00c2U HO\u00c0N CH\u1ec8NH b\u1eb1ng ti\u1ebfng Vi\u1ec7t.\n\n--- D\u1eee LI\u1ec6U RESEARCH ---\n{{ $json.output }}\n--- K\u1ebeT TH\u00daC D\u1eee LI\u1ec6U ---\n\nTH\u00d4NG TIN B\u00c1O C\u00c1O:\n- Ti\u00eau \u0111\u1ec1: {{ $node['Code: Filter Queue'].json.title }}\n- Lo\u1ea1i nghi\u00ean c\u1ee9u: {{ $node['Code: Filter Queue'].json.research_type }}\n- Ng\u00e0y: {{ new Date().toLocaleDateString('vi-VN') }}\n\nVi\u1ebft b\u00e1o c\u00e1o theo \u0111\u00fang format Markdown sau:\n\n# {{ $node['Code: Filter Queue'].json.title }}\n\n**Ng\u00e0y b\u00e1o c\u00e1o:** [ng\u00e0y h\u00f4m nay] | **Lo\u1ea1i:** [research_type] | **Tr\u1ea1ng th\u00e1i:** Ho\u00e0n th\u00e0nh\n\n---\n\n## T\u00f3m T\u1eaft \u0110i\u1ec1u H\u00e0nh\n[3-5 bullet points v\u1edbi c\u00e1c ph\u00e1t hi\u1ec7n quan tr\u1ecdng nh\u1ea5t]\n\n## B\u1ed1i C\u1ea3nh & T\u1ed5ng Quan\n[Gi\u1edbi thi\u1ec7u v\u1ec1 ch\u1ee7 \u0111\u1ec1, t\u1ea1i sao quan tr\u1ecdng]\n\n## Ph\u00e2n T\u00edch Chi Ti\u1ebft\n[N\u1ed9i dung ph\u00e2n t\u00edch s\u00e2u v\u1edbi d\u1eabn ch\u1ee9ng v\u00e0 s\u1ed1 li\u1ec7u]\n\n## D\u1eef Li\u1ec7u & S\u1ed1 Li\u1ec7u Quan Tr\u1ecdng\n[B\u1ea3ng ho\u1eb7c danh s\u00e1ch c\u00e1c s\u1ed1 li\u1ec7u, th\u1ed1ng k\u00ea c\u1ee5 th\u1ec3]\n\n## Xu H\u01b0\u1edbng & C\u01a1 H\u1ed9i\n[C\u00e1c xu h\u01b0\u1edbng \u0111ang n\u1ed5i l\u00ean v\u00e0 c\u01a1 h\u1ed9i ti\u1ec1m n\u0103ng]\n\n## R\u1ee7i Ro & Th\u00e1ch Th\u1ee9c\n[C\u00e1c r\u1ee7i ro c\u1ea7n l\u01b0u \u00fd]\n\n## K\u1ebft Lu\u1eadn & Khuy\u1ebfn Ngh\u1ecb\n[3-5 khuy\u1ebfn ngh\u1ecb h\u00e0nh \u0111\u1ed9ng c\u1ee5 th\u1ec3]\n\n## Ngu\u1ed3n Tham Kh\u1ea3o\n[Danh s\u00e1ch c\u00e1c URLs \u0111\u00e3 s\u1eed d\u1ee5ng]\n\nY\u00caU C\u1ea6U:\n- Ng\u00f4n ng\u1eef ti\u1ebfng Vi\u1ec7t chu\u1ea9n, chuy\u00ean nghi\u1ec7p\n- T\u1ed5ng \u0111\u1ed9 d\u00e0i 2000-3500 t\u1eeb\n- Tr\u00edch d\u1eabn s\u1ed1 li\u1ec7u v\u1edbi ngu\u1ed3n r\u00f5 r\u00e0ng\n- S\u1eed d\u1ee5ng format Markdown chu\u1ea9n (heading ##, bold **text**, bullet -, b\u1ea3ng |---|)"
      },
      "id": "9461c57c-e0f8-4648-8041-e110d707be69",
      "name": "Gemini: Write Report",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        -16,
        3088
      ]
    },
    {
      "parameters": {
        "modelName": "models/gemini-3-flash-preview",
        "options": {
          "maxOutputTokens": 8192,
          "temperature": 0.4
        }
      },
      "id": "fcec3fc3-5936-4856-8987-4fe36238a0e9",
      "name": "Gemini Flash (Report)",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        -16,
        3264
      ],
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Chu\u1ea9n b\u1ecb n\u1ed9i dung file b\u00e1o c\u00e1o v\u00e0 t\u00ean folder\nconst reportMarkdown = $input.first().json.text || '';\nconst req = $('Code: Rebuild Context').first().json;\n\n// T\u1ea1o t\u00ean folder: RequestID - Title - YYYY-MM-DD\nconst dateStr = new Date().toISOString().split('T')[0]; // YYYY-MM-DD\nconst safeTitle = req.title\n  .replace(/[/\\\\:*?\"<>|]/g, '-') // k\u00fd t\u1ef1 kh\u00f4ng h\u1ee3p l\u1ec7 cho t\u00ean folder\n  .substring(0, 60);\nconst folderName = `${req.request_id} - ${safeTitle} - ${dateStr}`;\n\n// Encode report sang base64 \u0111\u1ec3 upload Drive\nconst reportBuffer = Buffer.from(reportMarkdown, 'utf-8');\nconst reportBase64 = reportBuffer.toString('base64');\n\n// Tr\u00edch xu\u1ea5t overview (200 k\u00fd t\u1ef1 \u0111\u1ea7u c\u1ee7a n\u1ed9i dung sau header)\nconst lines = reportMarkdown.split('\\n').filter(l => l.trim());\nlet overview = '';\nfor (const line of lines) {\n  if (!line.startsWith('#') && !line.startsWith('---') && !line.startsWith('**')) {\n    overview += line.replace(/[*_#]/g, '').trim() + ' ';\n    if (overview.length > 200) break;\n  }\n}\noverview = overview.trim().substring(0, 250);\n\nreturn [{\n  json: {\n    folder_name: folderName,\n    report_filename: `report-${req.request_id}.md`,\n    report_markdown: reportMarkdown,\n    overview: overview || req.title,\n    request_id: req.request_id,\n    chat_id: req.chat_id,\n    title: req.title,\n    DRIVE_ROOT_FOLDER_ID: req.DRIVE_ROOT_FOLDER_ID,\n    SHEET_ID: req.SHEET_ID,\n    SHEET_NAME: req.SHEET_NAME\n  },\n  binary: {\n    reportFile: {\n      data: reportBase64,\n      mimeType: 'text/markdown',\n      fileName: `report-${req.request_id}.md`,\n      fileExtension: 'md'\n    }\n  }\n}];"
      },
      "id": "29d8b57d-c409-4fec-93c4-eba5a7d7942e",
      "name": "Code: Prepare Files",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        256,
        3088
      ]
    },
    {
      "parameters": {
        "jsCode": "// L\u01b0u folder ID v\u00e0 chu\u1ea9n b\u1ecb d\u1eef li\u1ec7u cho upload\nconst folderResponse = $input.first().json;\nconst prevData = $('Code: Prepare Files').first().json;\nconst prevBinary = $('Code: Prepare Files').first().binary;\n\nconst folderId = folderResponse.id || '';\nconst folderLink = folderId ? `https://drive.google.com/drive/folders/${folderId}` : '';\n\nreturn [{\n  json: {\n    ...prevData,\n    folder_id: folderId,\n    folder_link: folderLink\n  },\n  binary: prevBinary\n}];"
      },
      "id": "4bd39986-a8ac-4bed-9f65-32237bd75516",
      "name": "Code: Save Folder ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        592,
        3088
      ]
    },
    {
      "parameters": {
        "inputDataFieldName": "reportFile",
        "name": "={{ $json.report_filename }}",
        "driveId": {
          "__rl": true,
          "value": "My Drive",
          "mode": "list",
          "cachedResultName": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $json.folder_id }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "49dbab36-7159-4c95-b0ac-ba29ddfd6cf5",
      "name": "Drive: Upload Report",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        768,
        3088
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// Build final data sau khi upload\nconst uploadResp = $input.first().json;\nconst prev = $('Code: Save Folder ID').first().json;\n\nconst fileId = uploadResp.id || '';\nconst fileLink = uploadResp.webViewLink || (fileId ? `https://drive.google.com/file/d/${fileId}/view` : '');\n\nreturn [{\n  json: {\n    request_id: prev.request_id,\n    title: prev.title,\n    overview: prev.overview,\n    chat_id: prev.chat_id,\n    folder_link: prev.folder_link,\n    folder_id: prev.folder_id,\n    file_link: fileLink,\n    file_id: fileId,\n    SHEET_ID: prev.SHEET_ID,\n    SHEET_NAME: prev.SHEET_NAME\n  }\n}];"
      },
      "id": "839d9690-84ba-4182-9087-aec7be90072c",
      "name": "Code: Build Final Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        3072
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.SHEET_ID }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $json.SHEET_NAME }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Request_ID": "={{ $json.request_id }}",
            "Status": "done",
            "Overview": "={{ $json.overview }}",
            "Drive_Folder_Link": "={{ $json.folder_link }}",
            "Report_Link": "={{ $json.file_link }}",
            "Updated_At": "={{ new Date().toISOString() }}"
          },
          "matchingColumns": [
            "Request_ID"
          ],
          "schema": [
            {
              "id": "Request_ID",
              "displayName": "Request_ID",
              "required": false,
              "defaultMatch": true,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Overview",
              "displayName": "Overview",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Drive_Folder_Link",
              "displayName": "Drive_Folder_Link",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Report_Link",
              "displayName": "Report_Link",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Updated_At",
              "displayName": "Updated_At",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "53d3819e-2aac-452f-849b-0988b47e5c7f",
      "name": "Sheets: Update \u2192 Done",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1168,
        3072
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $node['Code: Build Final Data'].json.chat_id }}",
        "text": "=\ud83d\udcca *B\u00e1o c\u00e1o Research Ho\u00e0n Th\u00e0nh!*\n\n\ud83d\udccc *Ch\u1ee7 \u0111\u1ec1:* {{ $node['Code: Build Final Data'].json.title }}\n\ud83c\udd94 *ID:* `{{ $node['Code: Build Final Data'].json.request_id }}`\n\n\ud83d\udcdd *T\u00f3m t\u1eaft:*\n{{ $node['Code: Build Final Data'].json.overview }}\n\n\ud83d\udcc1 *Folder Drive:* [Xem t\u1ea1i \u0111\u00e2y]({{ $node['Code: Build Final Data'].json.folder_link }})\n\ud83d\udcc4 *File B\u00e1o C\u00e1o:* [T\u1ea3i v\u1ec1]({{ $node['Code: Build Final Data'].json.file_link }})\n\n\u2705 Nghi\u00ean c\u1ee9u ho\u00e0n t\u1ea5t! M\u1eddi b\u1ea1n xem chi ti\u1ebft b\u00e1o c\u00e1o.",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "075b56e7-6e40-474d-b3b5-4b9b6b3a842c",
      "name": "Telegram: Notify Done",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1376,
        3072
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Kh\u00f4ng c\u00f3 y\u00eau c\u1ea7u \u0111ang ch\u1edd \u2192 d\u1eebng l\u1ea1i\nconsole.log('[Research Processor] Kh\u00f4ng c\u00f3 y\u00eau c\u1ea7u n\u00e0o trong queue.');\nreturn [];"
      },
      "id": "345dac17-3130-4508-b9a5-1ce2c83c01b4",
      "name": "No Op: No Pending",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -944,
        3568
      ]
    },
    {
      "parameters": {
        "jsCode": "// X\u1eed l\u00fd l\u1ed7i - l\u1ea5y data t\u1eeb $input (ch\u1ee9a full context t\u1eeb Code: Rebuild Context)\n// KH\u00d4NG d\u00f9ng $node[] reference v\u00ec error branch c\u00f3 th\u1ec3 kh\u00f4ng truy c\u1eadp \u0111\u01b0\u1ee3c\nconst failedItem = $input.first();\nconst data = failedItem.json || {};\n\n// L\u1ea5y th\u00f4ng tin l\u1ed7i t\u1eeb field error (n8n th\u00eam v\u00e0o khi continueErrorOutput)\nconst errorObj = data.error || {};\nconst errorMessage = typeof errorObj === 'string'\n  ? errorObj\n  : (errorObj.message || errorObj.description || JSON.stringify(errorObj) || 'Unknown error');\n\nreturn [{\n  json: {\n    request_id: data.request_id || 'UNKNOWN',\n    title: data.title || 'Unknown request',\n    chat_id: data.chat_id || '',\n    error_message: String(errorMessage).substring(0, 500),\n    SHEET_ID: data.SHEET_ID || '',\n    SHEET_NAME: data.SHEET_NAME || ''\n  }\n}];"
      },
      "id": "4f4b1d71-a997-4269-aaaf-c7a338e82f7c",
      "name": "Code: Handle Error",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        3360
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.SHEET_ID }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $json.SHEET_NAME }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Request_ID": "={{ $json.request_id }}",
            "Status": "error",
            "Overview": "=L\u1ed7i x\u1eed l\u00fd: {{ $json.error_message.substring(0, 200) }}",
            "Updated_At": "={{ new Date().toISOString() }}"
          },
          "matchingColumns": [
            "Request_ID"
          ],
          "schema": [
            {
              "id": "Request_ID",
              "displayName": "Request_ID",
              "required": false,
              "defaultMatch": true,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Overview",
              "displayName": "Overview",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "Updated_At",
              "displayName": "Updated_At",
              "required": false,
              "defaultMatch": false,
              "canBeUsedToMatch": true,
              "display": true,
              "type": "string",
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "9233a352-43e1-43e8-b530-4024fd3be55f",
      "name": "Sheets: Update \u2192 Error",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1184,
        3360
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chat_id }}",
        "text": "=\u274c *C\u00f3 l\u1ed7i khi x\u1eed l\u00fd y\u00eau c\u1ea7u!*\n\n\ud83c\udd94 *ID:* `{{ $json.request_id }}`\n\ud83d\udccc *Ch\u1ee7 \u0111\u1ec1:* {{ $json.title }}\n\n\u26a0\ufe0f Vui l\u00f2ng ki\u1ec3m tra l\u1ea1i sau ho\u1eb7c th\u1eed l\u1ea1i y\u00eau c\u1ea7u.\n\n_Chi ti\u1ebft l\u1ed7i \u0111\u00e3 \u0111\u01b0\u1ee3c ghi nh\u1eadn trong h\u1ec7 th\u1ed1ng._",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "5aee0456-f2e2-4c60-a10c-9b71b85c0e83",
      "name": "Telegram: Notify Error",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1376,
        3360
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Sheets Update node ch\u1ec9 pass qua {Request_ID, Status, Updated_At} \u2192 c\u1ea7n restore full context\n// D\u00f9ng $() accessor (n8n v1+ recommended) \u0111\u1ec3 l\u1ea5y data t\u1eeb Code: Filter Queue \u0111\u00e3 ch\u1ea1y tr\u01b0\u1edbc\nlet queue;\ntry {\n  // C\u00e1ch 1: n8n v1+ item accessor - recommended\n  queue = $('Code: Filter Queue').first().json;\n} catch (e1) {\n  try {\n    // C\u00e1ch 2: node reference accessor - fallback\n    queue = $('Code: Filter Queue').first().json;\n  } catch (e2) {\n    throw new Error('Cannot restore context. Err1: ' + e1.message + ' | Err2: ' + e2.message);\n  }\n}\n\nif (!queue || !queue.request_id) {\n  throw new Error('Context empty after restore - check Code: Filter Queue output');\n}\n\nreturn [{ json: queue }];"
      },
      "id": "acf568ad-3468-4c7b-998c-d0e905bddba6",
      "name": "Code: Rebuild Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -672,
        3344
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "c01",
              "name": "SHEET_ID",
              "value": "YOUR_SHEET_ID",
              "type": "string"
            },
            {
              "id": "c02",
              "name": "SHEET_NAME",
              "value": "Research Queue",
              "type": "string"
            },
            {
              "id": "c03",
              "name": "DRIVE_ROOT_FOLDER_ID",
              "value": "YOUR_DRIVE_ROOT_FOLDER_ID",
              "type": "string"
            },
            {
              "id": "c05",
              "name": "RESEARCH_SOURCES",
              "value": "vnexpress.net,cafef.vn,tuoitre.vn,thanhnien.vn,mof.gov.vn,gso.gov.vn,statista.com,reuters.com,bloomberg.com,techcrunch.com,mckinsey.com,deloitte.com",
              "type": "string"
            },
            {
              "id": "c06",
              "name": "MAX_SEARCH_RESULTS",
              "value": "8",
              "type": "string"
            },
            {
              "id": "c07",
              "name": "TOP_URLS_TO_SCRAPE",
              "value": "5",
              "type": "string"
            },
            {
              "id": "c08",
              "name": "REPORT_LANGUAGE",
              "value": "Vietnamese",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "44d2ad08-c14e-48b0-9dbf-40f773267f04",
      "name": "Set: Config Vars1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -2064,
        3472
      ],
      "notes": "\u2699\ufe0f C\u1ea4U H\u00ccNH CH\u00cdNH: \u0110i\u1ec1n SHEET_ID, DRIVE_ROOT_FOLDER_ID v\u00e0 danh s\u00e1ch ngu\u1ed3n \u01b0u ti\u00ean RESEARCH_SOURCES t\u1ea1i \u0111\u00e2y. Firecrawl API Key c\u1ea5u h\u00ecnh trong Credentials c\u1ee7a node Firecrawl."
    },
    {
      "parameters": {
        "operation": "scrape",
        "url": "={{ $fromAI('url', 'URL \u0111\u1ea7y \u0111\u1ee7 c\u1ee7a trang c\u1ea7n \u0111\u1ecdc n\u1ed9i dung, v\u00ed d\u1ee5: https://vnexpress.net/bai-viet-123') }}",
        "requestOptions": {}
      },
      "type": "@mendable/n8n-nodes-firecrawl.firecrawlTool",
      "typeVersion": 1,
      "position": [
        -144,
        3648
      ],
      "id": "d8de1969-a98b-40d7-baba-5e81b974bbbc",
      "name": "/scrape in Firecrawl2",
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "MapSearch",
        "operation": "search",
        "query": "={{ $fromAI('query', 'T\u1eeb kh\u00f3a t\u00ecm ki\u1ebfm th\u00f4ng tin, v\u00ed d\u1ee5: th\u1ecb tr\u01b0\u1eddng xe \u0111i\u1ec7n Vi\u1ec7t Nam 2025') }}",
        "requestOptions": {}
      },
      "type": "@mendable/n8n-nodes-firecrawl.firecrawlTool",
      "typeVersion": 1,
      "position": [
        -304,
        3648
      ],
      "id": "8f4d62ed-5921-4abb-acda-2da91b7803d9",
      "name": "/search in Firecrawl",
      "credentials": {
        "firecrawlApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "folder",
        "name": "={{ $json.folder_name }}",
        "driveId": {
          "__rl": true,
          "value": "={{ $json.DRIVE_ROOT_FOLDER_ID }}",
          "mode": "id"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $json.DRIVE_ROOT_FOLDER_ID }}",
          "mode": "id"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        432,
        3088
      ],
      "id": "38da1467-a362-40fc-859b-9e09f671c090",
      "name": "Create folder",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Set: Config Vars1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Set: Config Vars1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets: Get All Rows": {
      "main": [
        [
          {
            "node": "Code: Filter Queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Filter Queue": {
      "main": [
        [
          {
            "node": "IF: Has Pending?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Has Pending?": {
      "main": [
        [
          {
            "node": "Sheets: Update \u2192 Processing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Op: No Pending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets: Update \u2192 Processing": {
      "main": [
        [
          {
            "node": "Code: Rebuild Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Flash (Agent)": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent: Research",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent: Research": {
      "main": [
        [
          {
            "node": "Gemini: Write Report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code: Handle Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Flash (Report)": {
      "ai_languageModel": [
        [
          {
            "node": "Gemini: Write Report",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Gemini: Write Report": {
      "main": [
        [
          {
            "node": "Code: Prepare Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Prepare Files": {
      "main": [
        [
          {
            "node": "Create folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Save Folder ID": {
      "main": [
        [
          {
            "node": "Drive: Upload Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Drive: Upload Report": {
      "main": [
        [
          {
            "node": "Code: Build Final Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code: Handle Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build Final Data": {
      "main": [
        [
          {
            "node": "Sheets: Update \u2192 Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets: Update \u2192 Done": {
      "main": [
        [
          {
            "node": "Telegram: Notify Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Handle Error": {
      "main": [
        [
          {
            "node": "Sheets: Update \u2192 Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets: Update \u2192 Error": {
      "main": [
        [
          {
            "node": "Telegram: Notify Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Rebuild Context": {
      "main": [
        [
          {
            "node": "AI Agent: Research",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Config Vars1": {
      "main": [
        [
          {
            "node": "Sheets: Get All Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "/scrape in Firecrawl2": {
      "ai_tool": [
        [
          {
            "node": "AI Agent: Research",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "/search in Firecrawl": {
      "ai_tool": [
        [
          {
            "node": "AI Agent: Research",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Create folder": {
      "main": [
        [
          {
            "node": "Code: Save Folder ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "versionId": "a429f0ce-3922-438c-b892-f5d5e29e3dfd",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "evvOm6EdqHzoPPYU",
  "tags": [
    {
      "updatedAt": "2026-05-29T07:46:11.087Z",
      "createdAt": "2026-05-29T07:46:11.087Z",
      "id": "pHmdbe3WVnvAQV7N",
      "name": "research-agent"
    }
  ]
}