{
  "id": "iycv0r7Rs9Nj8oFl",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Convert PDF to MCQ Question Bank in Excel with AI (Gemini)",
  "tags": [],
  "nodes": [
    {
      "id": "2f456a67-94d0-4289-881b-318c8559e34e",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -48,
        0
      ],
      "parameters": {
        "path": "06f418df-1e24-45a9-87dd-7e67e3274633",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "70557ae2-2de7-44f6-a4e5-160c9bb1906d",
      "name": "Extract from File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -48,
        320
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "={{ $json.data }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "484c015d-6206-4ea0-ab07-090ba7945494",
      "name": "Cleaner",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        320
      ],
      "parameters": {
        "jsCode": "const results = [];\n\nfor (const item of items) {\n  try {\n    if (!item.json?.content?.parts?.[0]?.text) return [];\n    \n    const text = item.json.content.parts[0].text;\n\n    // Bersihkan markdown jika ada\n    let clean = text\n      .replace(/```json/g, '')\n      .replace(/```/g, '')\n      .trim();\n\n    // Parse JSON string\n    const parsed = JSON.parse(clean);\n\n    // Push semua question ke results\n    for (const q of parsed) {\n      results.push({ json: q });\n    }\n\n  } catch (err) {\n    // skip kalau error\n    continue;\n  }\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "28e26bf2-0542-4322-ae5c-06034a48cdcc",
      "name": "Message a model",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        448,
        320
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemma-3-1b-it",
          "cachedResultName": "models/gemma-3-1b-it"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are an expert teacher and exam creator.\n\nGenerate multiple choice questions (MCQs) from the content below.\n\nSTRICT RULES:\n- Each question must have exactly 4 options (A, B, C, D)\n- Only ONE correct answer\n- Include explanation\n- No duplicate questions\n- Keep questions clear and concise\n- Max 3 questions per chunk\n\nIMPORTANT:\n- Return ONLY valid JSON\n- Do NOT include markdown\n- Do NOT include explanations outside JSON\n- Do NOT add any text before or after JSON\n\n\nJSON FORMAT:\n[\n  {\n    \"question\": \"...\",\n    \"option_a\": \"...\",\n    \"option_b\": \"...\",\n    \"option_c\": \"...\",\n    \"option_d\": \"...\",\n    \"correct_answer\": \"A\",\n    \"explanation\": \"...\"\n  }\n]\n\nCONTENT:\n{{ $json.chunk }}"
            }
          ]
        }
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f223631f-f07a-4e7d-a345-896353e07951",
      "name": "Validate Parameter & PDF",
      "type": "n8n-nodes-base.code",
      "position": [
        144,
        0
      ],
      "parameters": {
        "jsCode": "const errors = [];\nlet fileSize = $input.first().binary.data.fileSize\nlet size = String(fileSize).toLowerCase();\nlet fileSizeInBytes = parseInt(\n  size.includes('mb')\n    ? parseFloat(size) * 1024 * 1024\n    : size.includes('kb')\n      ? parseFloat(size) * 1024\n      : parseFloat(size)\n);\n\nif(fileSizeInBytes >=5242880){\n  errors.push(\"Files cannot be more than 5MB\");\n}\n\nreturn [{\n  json: {\n    isValid: errors.length === 0,\n    errors: errors,\n    data : $input.first().binary.data,\n    fileName : $input.first().binary.data.fileName.split('.').slice(0, -1).join('.').replaceAll(' ', '-')\n\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f3faab77-7a43-4d9e-9141-3782b64c1cb6",
      "name": "Condition Valid",
      "type": "n8n-nodes-base.if",
      "position": [
        336,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "87665a50-4cd9-4cdd-a009-5eda14975f4f",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.isValid }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "17dbf178-c2e1-4f8b-9fe8-81789ef13bfa",
      "name": "Chunk",
      "type": "n8n-nodes-base.code",
      "position": [
        192,
        320
      ],
      "parameters": {
        "jsCode": "// Ambil text hasil extract PDF\nlet text = $json.text || $json.data || \"\";\n\nconst maxLength = 2000;\nconst chunks = [];\n\nfor (let i = 0; i < text.length; i += maxLength) {\n  chunks.push(text.slice(i, i + maxLength));\n}\n\nreturn chunks.map(chunk => ({ json: { chunk } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "d3ce97a7-050c-449b-a9f6-17cc7a92ff7d",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        576,
        -48
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "{\n  \"status\" : \"success\",\n  \"message\": \"oke\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "545f7d33-00e1-4df6-8bf3-ceb94353c57f",
      "name": "Respond Failed",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        576,
        80
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"failed\",\n  \"message\" : \"{{ $json.errors }}\"\n}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "7b949f4e-24f8-4a32-bb22-5ce98f77de82",
      "name": "Convert to File",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        -48,
        592
      ],
      "parameters": {
        "options": {
          "fileName": "=result-n8n-{{$('Validate Parameter & PDF').first().json.fileName }}.xlsx",
          "headerRow": true,
          "sheetName": "=Sheet 1"
        },
        "operation": "xlsx"
      },
      "typeVersion": 1.1
    },
    {
      "id": "7de37f63-00e9-45f5-aecf-5e75e54f462b",
      "name": "Send a document",
      "type": "n8n-nodes-base.telegram",
      "position": [
        528,
        592
      ],
      "parameters": {
        "chatId": "12345678",
        "operation": "sendDocument",
        "binaryData": true,
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "bcb6ec1a-7919-4a2e-aa95-767d505638b6",
      "name": "Send email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        752,
        592
      ],
      "parameters": {
        "text": "Here attachment result",
        "options": {
          "attachments": "data"
        },
        "subject": "Turn Any PDF into a Structured Question Bank in Excel using AI (MCQ + Answer Key)",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com",
        "emailFormat": "text"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "d234ebf3-1133-4aed-9244-64acd31a76a0",
      "name": "Upload file",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        160,
        592
      ],
      "parameters": {
        "name": "=result-n8n-{{$('Validate Parameter & PDF').first().json.fileName }}.xlsx",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "14CGLMO_IT18kj1otWQZpetwJ_tqGP91d",
          "cachedResultUrl": "https://drive.google.com/drive/folders/14CGLMO_IT18kj1otWQZpetwJ_tqGP91d",
          "cachedResultName": "n8n"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "ddb5d5d2-f9ab-4de4-a846-e7346f6d3b78",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 320,
        "content": "## 1. Input & File Validation\nHandles incoming PDF, validates file size (max 5MB) and ensures file exists before processing."
      },
      "typeVersion": 1
    },
    {
      "id": "d33cdb89-1926-473b-bc30-df2362545e23",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 240,
        "content": "## 2. PDF Text Extraction & Chunking\nExtracts text from PDF and splits it into manageable chunks for AI processing."
      },
      "typeVersion": 1
    },
    {
      "id": "b959faeb-325c-4cfe-99bc-b7aa9f9371ba",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        384,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 240,
        "content": "## 3. AI MCQ Generation (Google Gemini)\nGenerates structured multiple choice questions using AI and cleans the output into valid JSON format."
      },
      "typeVersion": 1
    },
    {
      "id": "9bd03031-f163-46e1-80a3-33a5afe589cc",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        512
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 256,
        "content": "## 4. Output Generation & Storage\nConverts structured data into Excel format and uploads the file to Google Drive."
      },
      "typeVersion": 1
    },
    {
      "id": "e4a91db0-cb49-48ac-a2b4-2bca91ef809e",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        512
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 240,
        "content": "## 5. Delivery & Notification\nSends the generated Excel file to the user via Telegram and Email."
      },
      "typeVersion": 1
    },
    {
      "id": "6308e4f3-6630-400e-b75c-aeaeb8f3d58b",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        -96
      ],
      "parameters": {
        "width": 816,
        "height": 928,
        "content": "# Convert PDF to MCQ Question Bank in Excel with AI (Gemini)\nThis workflow automatically converts any PDF into a structured Multiple Choice Question (MCQ) bank using Google Gemini AI.\n\nIt extracts text from the PDF, processes it into manageable chunks, generates MCQs with answer keys and explanations, and exports the results into an Excel file.\n\nThe final file is uploaded to Google Drive and can be sent directly via Email or Telegram.\n\nThis workflow is ideal for educators, trainers, and content creators who want to automate question generation and save time.\n\n## \u2699\ufe0f How it works\n1. A PDF file is uploaded via webhook.\n2. The workflow validates the file (max 5MB and file existence).\n3. The PDF content is extracted and split into smaller chunks.\n4. Each chunk is processed by Google Gemini to generate MCQs.\n5. The AI response is cleaned and converted into structured JSON.\n6. All questions are compiled and converted into an Excel file.\n7. The file is uploaded to Google Drive.\n8. The result is sent to the user via Email or Telegram.\n\n## \ud83d\udd0c Setup\n1. Import the workflow into your n8n instance.\n2. Configure credentials:\n   - Google Gemini API Key (HTTP Request header)\n   - Google Drive OAuth\n   - Email (SMTP or Gmail)\n   - Telegram Bot Token\n3. Update the Gemini node.\n4. Activate the workflow.\n5. Send a POST request to the webhook with:\n   - file (PDF)\n   - email (optional)\n   - telegram_chat_id (optional)\n6. Run the workflow and receive the generated Excel file."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "ac8c03fa-8e46-4f75-af43-b348016cbe15",
  "connections": {
    "Chunk": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cleaner": {
      "main": [
        [
          {
            "node": "Convert to File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Validate Parameter & PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Condition Valid": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          },
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to File": {
      "main": [
        [
          {
            "node": "Send a document",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Upload file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "Cleaner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Chunk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Parameter & PDF": {
      "main": [
        [
          {
            "node": "Condition Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}