{
  "name": "Quiz Generator Workflow V1",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "generate-quiz",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "eaf76a79-572b-4bd8-a1ba-4fd43f7e42fa",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "notes": "User g\u1eedi POST request v\u1edbi body: { \"folderUrl\": \"https://drive.google.com/drive/folders/FOLDER_ID\", \"quizTitle\": \"T\u00ean Quiz\", \"questionsPerFile\": 3 }"
    },
    {
      "parameters": {
        "jsCode": "// Parse Google Drive folder URL/ID t\u1eeb input\nconst body = $input.first().json.body || $input.first().json;\n\nlet folderUrl = body.folderUrl || body.folder_url || '';\nlet folderIdMatch = folderUrl.match(/folders\\/([a-zA-Z0-9_-]+)/);\nlet folderId = folderIdMatch ? folderIdMatch[1] : folderUrl.trim();\n\nif (!folderId) {\n  throw new Error('Vui l\u00f2ng cung c\u1ea5p folderUrl ho\u1eb7c folderId h\u1ee3p l\u1ec7 t\u1eeb Google Drive');\n}\n\nconst quizTitle = body.quizTitle || body.quiz_title || 'Quiz t\u1eeb t\u00e0i li\u1ec7u Markdown';\nconst questionsPerFile = parseInt(body.questionsPerFile || body.questions_per_file || '3');\nconst description = body.description || 'B\u1ed9 c\u00e2u h\u1ecfi \u0111\u01b0\u1ee3c t\u1ea1o t\u1ef1 \u0111\u1ed9ng t\u1eeb t\u00e0i li\u1ec7u';\n\nconsole.log(`Folder ID: ${folderId}`);\nconsole.log(`Quiz Title: ${quizTitle}`);\nconsole.log(`Questions per file: ${questionsPerFile}`);\n\nreturn [{\n  json: {\n    folderId,\n    folderUrl,\n    quizTitle,\n    questionsPerFile,\n    description\n  }\n}];"
      },
      "id": "fa565bea-4ecd-414f-a68c-2e7ffdb4cc87",
      "name": "Parse Input & Extract Folder ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        256,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// L\u1ecdc ch\u1ec9 l\u1ea5y c\u00e1c file Markdown (.md)\nconst allFiles = $input.all();\nconst folderId = $('Parse Input & Extract Folder ID').first().json.folderId;\nconst quizTitle = $('Parse Input & Extract Folder ID').first().json.quizTitle;\nconst questionsPerFile = $('Parse Input & Extract Folder ID').first().json.questionsPerFile;\nconst description = $('Parse Input & Extract Folder ID').first().json.description;\n\nconsole.log(`Total files found: ${allFiles.length}`);\n\nconst markdownFiles = allFiles.filter(item => {\n  const name = item.json.name || '';\n  const mimeType = item.json.mimeType || '';\n  // L\u1ea5y .md files v\u00e0 text/plain (Google Drive c\u00f3 th\u1ec3 l\u01b0u .md d\u01b0\u1edbi d\u1ea1ng text/plain)\n  return name.endsWith('.md') || \n         name.endsWith('.markdown') ||\n         mimeType === 'text/markdown' ||\n         (mimeType === 'text/plain' && (name.endsWith('.md') || name.endsWith('.markdown')));\n});\n\nconsole.log(`Markdown files found: ${markdownFiles.length}`);\n\nif (markdownFiles.length === 0) {\n  throw new Error(`Kh\u00f4ng t\u00ecm th\u1ea5y file Markdown (.md) n\u00e0o trong folder: ${folderId}`);\n}\n\n// Truy\u1ec1n metadata cho c\u00e1c b\u01b0\u1edbc ti\u1ebfp theo\nreturn markdownFiles.map(item => ({\n  json: {\n    fileId: item.json.id,\n    fileName: item.json.name,\n    mimeType: item.json.mimeType,\n    modifiedTime: item.json.modifiedTime,\n    quizTitle,\n    questionsPerFile,\n    description,\n    totalFiles: markdownFiles.length\n  }\n}));"
      },
      "id": "c154582a-e638-4acf-99c3-5b6d998d8878",
      "name": "Filter Markdown Files",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        752,
        0
      ]
    },
    {
      "parameters": {
        "operation": "download",
        "fileId": {
          "__rl": true,
          "value": "={{ $json.fileId }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "ba292c6f-698e-47e6-a6fe-6fcb73691cf6",
      "name": "Download Markdown File Content",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1008,
        0
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Convert binary data to text v\u00e0 chu\u1ea9n b\u1ecb cho AI\nconst item = $input.first();\nconst binaryData = item.binary?.data;\nconst jsonData = item.json;\n\nlet markdownContent = '';\n\nif (binaryData) {\n  // Decode base64 binary content\n  const base64Content = binaryData.data;\n  markdownContent = Buffer.from(base64Content, 'base64').toString('utf-8');\n} else if (jsonData.data) {\n  markdownContent = jsonData.data;\n}\n\n// Gi\u1edbi h\u1ea1n content \u0111\u1ec3 tr\u00e1nh v\u01b0\u1ee3t token limit c\u1ee7a AI\nconst maxLength = 8000;\nif (markdownContent.length > maxLength) {\n  markdownContent = markdownContent.substring(0, maxLength) + '\\n... [n\u1ed9i dung b\u1ecb c\u1eaft b\u1edbt]';\n}\n\nconsole.log(`File: ${jsonData.fileName}, Content length: ${markdownContent.length}`);\n\nreturn [{\n  json: {\n    ...jsonData,\n    markdownContent,\n    contentLength: markdownContent.length\n  }\n}];"
      },
      "id": "0fb1559d-75c3-4d1f-b0a9-cd7bce6b3123",
      "name": "Extract Text Content",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1264,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-pro-preview:generateContent",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googlePalmApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ (() => {\n  const systemPrompt = 'B\u1ea1n l\u00e0 chuy\u00ean gia t\u1ea1o c\u00e2u h\u1ecfi quiz gi\u00e1o d\u1ee5c. Nhi\u1ec7m v\u1ee5 c\u1ee7a b\u1ea1n l\u00e0 \u0111\u1ecdc t\u00e0i li\u1ec7u Markdown v\u00e0 t\u1ea1o ra c\u00e1c c\u00e2u h\u1ecfi tr\u1eafc nghi\u1ec7m ch\u1ea5t l\u01b0\u1ee3ng cao.\\n\\nY\u00eau c\u1ea7u:\\n- T\u1ea1o \u0111\u00fang s\u1ed1 c\u00e2u h\u1ecfi \u0111\u01b0\u1ee3c y\u00eau c\u1ea7u\\n- M\u1ed7i c\u00e2u h\u1ecfi ph\u1ea3i c\u00f3 4 l\u1ef1a ch\u1ecdn (A, B, C, D)\\n- Ch\u1ec9 c\u00f3 1 \u0111\u00e1p \u00e1n \u0111\u00fang\\n- C\u00e2u h\u1ecfi ph\u1ea3i b\u00e1m s\u00e1t n\u1ed9i dung t\u00e0i li\u1ec7u\\n- C\u00e2u h\u1ecfi vi\u1ebft b\u1eb1ng ti\u1ebfng Vi\u1ec7t (n\u1ebfu t\u00e0i li\u1ec7u b\u1eb1ng ti\u1ebfng Vi\u1ec7t) ho\u1eb7c c\u00f9ng ng\u00f4n ng\u1eef v\u1edbi t\u00e0i li\u1ec7u\\n- \u0110\u1ed9 kh\u00f3 \u0111a d\u1ea1ng: 40% d\u1ec5, 40% trung b\u00ecnh, 20% kh\u00f3\\n\\nTR\u1ea2 L\u1edcI PH\u1ea2I L\u00c0 JSON THU\u1ea6N T\u00daY (kh\u00f4ng c\u00f3 markdown code block), theo \u0111\u1ecbnh d\u1ea1ng:\\n{\\n  \"questions\": [\\n    {\\n      \"question\": \"N\u1ed9i dung c\u00e2u h\u1ecfi?\",\\n      \"options\": {\\n        \"A\": \"L\u1ef1a ch\u1ecdn A\",\\n        \"B\": \"L\u1ef1a ch\u1ecdn B\",\\n        \"C\": \"L\u1ef1a ch\u1ecdn C\",\\n        \"D\": \"L\u1ef1a ch\u1ecdn D\"\\n      },\\n      \"correctAnswer\": \"A\",\\n      \"explanation\": \"Gi\u1ea3i th\u00edch t\u1ea1i sao \u0111\u00e1p \u00e1n n\u00e0y \u0111\u00fang\"\\n    }\\n  ]\\n}';\n  const userPrompt = `T\u1ea1o ${$json.questionsPerFile} c\u00e2u h\u1ecfi tr\u1eafc nghi\u1ec7m t\u1eeb t\u00e0i li\u1ec7u sau:\\n\\n**T\u00ean file:** ${$json.fileName}\\n\\n**N\u1ed9i dung:**\\n${$json.markdownContent}`;\n  return JSON.stringify({\n    systemInstruction: {\n      parts: [{ text: systemPrompt }]\n    },\n    contents: [{\n      role: 'user',\n      parts: [{ text: userPrompt }]\n    }],\n    generationConfig: {\n      temperature: 0.7,\n      maxOutputTokens: 2000,\n      responseMimeType: 'application/json'\n    }\n  });\n})() }}",
        "options": {
          "response": {
            "response": {}
          }
        }
      },
      "id": "a097b0d9-4b55-4238-a86a-1c9fe3c2d562",
      "name": "AI Generate Quiz Questions",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        0
      ],
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Parse Gemini API response v\u00e0 format c\u00e2u h\u1ecfi\nconst item = $input.first();\n\nconst candidates = item.json.candidates || [];\nconst aiText = candidates[0]?.content?.parts?.[0]?.text || '';\nconst finishReason = candidates[0]?.finishReason || 'UNKNOWN';\n\nconst fileName = $('Extract Text Content').first().json.fileName;\nconst quizTitle = $('Extract Text Content').first().json.quizTitle;\nconst description = $('Extract Text Content').first().json.description;\n\nif (!aiText) {\n  throw new Error(`Gemini kh\u00f4ng tr\u1ea3 v\u1ec1 n\u1ed9i dung cho file ${fileName}. Finish reason: ${finishReason}`);\n}\n\n// N\u1ebfu b\u1ecb d\u1eebng ngang do h\u1ebft token ho\u1eb7c l\u00fd do an to\u00e0n\nif (finishReason !== 'STOP' && finishReason !== 'UNKNOWN') {\n  throw new Error(`Gemini b\u1ecb ng\u1eaft ngang do [${finishReason}] khi x\u1eed l\u00fd file ${fileName}`);\n}\n\nlet parsedData;\ntry {\n  let cleanResponse = aiText.replace(/^```json\\s*/m, '').replace(/```\\s*$/m, '').trim();\n  parsedData = JSON.parse(cleanResponse);\n} catch (e) {\n  console.error('Parse error:', e.message);\n  throw new Error(`Kh\u00f4ng th\u1ec3 parse Gemini response cho file ${fileName}: ${e.message}\\nN\u1ed9i dung b\u1ecb l\u1ed7i:\\n${aiText.substring(0, 500)}...`);\n}\n\nlet questions = [];\nif (Array.isArray(parsedData)) {\n  questions = parsedData; // Tr\u01b0\u1eddng h\u1ee3p AI tr\u1ea3 th\u1eb3ng v\u1ec1 Array\n} else if (parsedData.questions && Array.isArray(parsedData.questions)) {\n  questions = parsedData.questions; // Tr\u01b0\u1eddng h\u1ee3p chu\u1ea9n\n} else if (parsedData.quiz && Array.isArray(parsedData.quiz)) {\n  questions = parsedData.quiz; \n} else {\n  // T\u00ecm m\u1ea3ng b\u1ea5t k\u1ef3 trong object\n  for (const key in parsedData) {\n    if (Array.isArray(parsedData[key])) {\n      questions = parsedData[key];\n      break;\n    }\n  }\n}\n\nif (questions.length === 0) {\n  throw new Error(`Gemini tr\u1ea3 v\u1ec1 JSON nh\u01b0ng kh\u00f4ng t\u00ecm th\u1ea5y danh s\u00e1ch c\u00e2u h\u1ecfi. N\u1ed9i dung th\u1ef1c t\u1ebf: ${JSON.stringify(parsedData)}`);\n}\n\nreturn [{\n  json: {\n    fileName,\n    quizTitle,\n    description,\n    questions,\n    questionsCount: questions.length\n  }\n}];\n"
      },
      "id": "4da48e68-609f-4c85-8137-ac35466a10bd",
      "name": "Parse AI Quiz Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        0
      ]
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {
          "includeBinaries": false
        }
      },
      "id": "1d127d44-596c-42e4-9ce6-f1fe320da50a",
      "name": "Aggregate All Quiz Questions",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        2000,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// T\u1ed5ng h\u1ee3p t\u1ea5t c\u1ea3 c\u00e2u h\u1ecfi t\u1eeb nhi\u1ec1u file (H\u1ed7 tr\u1ee3 m\u1ecdi \u0111\u1ecbnh d\u1ea1ng \u0111\u1ea7u v\u00e0o)\nconst items = $input.all();\n\nlet allQuestions = [];\nlet quizTitle = 'Quiz T\u1ef1 \u0110\u1ed9ng';\nlet description = '';\nlet processedFiles = 0;\n\nfor (const item of items) {\n  // T\u00ecm d\u1eef li\u1ec7u th\u1ef1c s\u1ef1 (x\u1eed l\u00fd c\u1ea3 khi d\u00f9ng Aggregate node v\u00e0 kh\u00f4ng d\u00f9ng)\n  let dataToProcess = [];\n  if (item.json.allQuizData && Array.isArray(item.json.allQuizData)) {\n    dataToProcess = item.json.allQuizData;\n  } else if (item.json.data && Array.isArray(item.json.data)) {\n    dataToProcess = item.json.data;\n  } else {\n    dataToProcess = [item.json];\n  }\n\n  // Duy\u1ec7t qua t\u1eebng file \u0111\u00e3 parse\n  for (const fileData of dataToProcess) {\n    if (fileData.quizTitle) quizTitle = fileData.quizTitle;\n    if (fileData.description) description = fileData.description;\n    \n    if (fileData.questions && Array.isArray(fileData.questions)) {\n      processedFiles++;\n      // Th\u00eam source file v\u00e0o m\u1ed7i c\u00e2u h\u1ecfi\n      const questionsWithSource = fileData.questions.map(q => ({\n        ...q,\n        sourceFile: fileData.fileName || 'Unknown File'\n      }));\n      allQuestions = allQuestions.concat(questionsWithSource);\n    }\n  }\n}\n\nconsole.log(`Total questions aggregated: ${allQuestions.length}`);\n\n// B\u1eaft l\u1ed7i chi ti\u1ebft n\u1ebfu v\u1eabn b\u1eb1ng 0\nif (allQuestions.length === 0) {\n  throw new Error(`Kh\u00f4ng c\u00f3 c\u00e2u h\u1ecfi n\u00e0o \u0111\u01b0\u1ee3c t\u1ea1o. D\u1eef li\u1ec7u nh\u1eadn \u0111\u01b0\u1ee3c: ${JSON.stringify(items[0].json).substring(0, 500)}`);\n}\n\nreturn [{\n  json: {\n    quizTitle,\n    description: description || `B\u1ed9 c\u00e2u h\u1ecfi g\u1ed3m ${allQuestions.length} c\u00e2u \u0111\u01b0\u1ee3c t\u1ea1o t\u1eeb ${processedFiles} t\u00e0i li\u1ec7u Markdown`,\n    questions: allQuestions,\n    totalQuestions: allQuestions.length,\n    totalFiles: processedFiles\n  }\n}];\n"
      },
      "id": "d8ad7cd2-9e76-49a9-a4ec-da149e59f18f",
      "name": "Merge & Prepare Quiz Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2256,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://forms.googleapis.com/v1/forms",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleDriveOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"info\": {\n    \"title\": \"{{ $json.quizTitle }}\"\n  }\n}\n",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        }
      },
      "id": "defeb102-4135-458a-96da-499cdf27a801",
      "name": "Create Google Form",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2512,
        0
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Extract Form ID v\u00e0 chu\u1ea9n b\u1ecb \u0111\u1ec3 add questions\nconst createResponse = $input.first().json.body || $input.first().json;\nconst quizData = $('Merge & Prepare Quiz Data').first().json;\n\nconst formId = createResponse.formId;\nconst formUrl = createResponse.responderUri;\n\nconsole.log(`Created form ID: ${formId}`);\nconsole.log(`Form URL: ${formUrl}`);\n\nif (!formId) {\n  throw new Error('Kh\u00f4ng th\u1ec3 t\u1ea1o Google Form: ' + JSON.stringify(createResponse));\n}\n\nreturn [{\n  json: {\n    formId,\n    formUrl,\n    editUrl: `https://docs.google.com/forms/d/${formId}/edit`,\n    quizTitle: quizData.quizTitle,\n    questions: quizData.questions,\n    totalQuestions: quizData.totalQuestions,\n    totalFiles: quizData.totalFiles\n  }\n}];"
      },
      "id": "9c87fb12-b154-4ab8-9928-d18d326c4e2a",
      "name": "Extract Form ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2752,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://forms.googleapis.com/v1/forms/{{ $json.formId }}:batchUpdate",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleDriveOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ (() => {\n  const formId = $json.formId;\n  const questions = $json.questions;\n  \n  const requests = [];\n  \n  // 1. C\u1eadp nh\u1eadt Description cho Form\n  if ($json.description) {\n    requests.push({\n      \"updateFormInfo\": {\n        \"info\": {\n          \"description\": $json.description\n        },\n        \"updateMask\": \"description\"\n      }\n    });\n  }\n  \n  // 2. Chuy\u1ec3n Form th\u00e0nh d\u1ea1ng Quiz (Ch\u1ea5m \u0111i\u1ec3m)\n  requests.push({\n    \"updateSettings\": {\n      \"settings\": {\n        \"quizSettings\": {\n          \"isQuiz\": true\n        }\n      },\n      \"updateMask\": \"quizSettings.isQuiz\"\n    }\n  });\n  \n  // 3. Th\u00eam t\u1eebng c\u00e2u h\u1ecfi v\u00e0o Form\n  questions.forEach((q, index) => {\n    const options = Object.entries(q.options || {}).map(([key, value]) => ({\n      \"value\": `${key}. ${value}`\n    }));\n    \n    const correctKey = q.correctAnswer;\n    const correctOptionValue = `${correctKey}. ${(q.options || {})[correctKey]}`;\n    \n    requests.push({\n      \"createItem\": {\n        \"item\": {\n          \"title\": q.question,\n          \"description\": q.explanation ? `\ud83d\udca1 Gi\u1ea3i th\u00edch: ${q.explanation}` : '',\n          \"questionItem\": {\n            \"question\": {\n              \"required\": true,\n              \"grading\": {\n                \"pointValue\": 1,\n                \"correctAnswers\": {\n                  \"answers\": [{ \"value\": correctOptionValue }]\n                },\n                \"whenRight\": { \"text\": \"\u2705 Ch\u00ednh x\u00e1c!\" },\n                \"whenWrong\": { \"text\": `\u274c \u0110\u00e1p \u00e1n \u0111\u00fang l\u00e0: ${correctOptionValue}` }\n              },\n              \"choiceQuestion\": {\n                \"type\": \"RADIO\",\n                \"options\": options,\n                \"shuffle\": false\n              }\n            }\n          }\n        },\n        \"location\": { \"index\": index }\n      }\n    });\n  });\n  \n  return JSON.stringify({ requests });\n})() }}\n\n",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        }
      },
      "id": "7aa47221-d920-474a-a4ba-6c6e183a4558",
      "name": "Add Quiz Questions to Form",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3008,
        0
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Build final response\nconst formData = $('Extract Form ID').first().json;\nconst addResponse = $input.first().json;\n\nreturn [{\n  json: {\n    success: true,\n    message: `\u2705 \u0110\u00e3 t\u1ea1o th\u00e0nh c\u00f4ng b\u1ed9 quiz v\u1edbi ${formData.totalQuestions} c\u00e2u h\u1ecfi t\u1eeb ${formData.totalFiles} file Markdown`,\n    result: {\n      formId: formData.formId,\n      formTitle: formData.quizTitle,\n      formUrl: formData.formUrl,\n      editUrl: formData.editUrl,\n      totalQuestions: formData.totalQuestions,\n      totalFilesProcessed: formData.totalFiles\n    }\n  }\n}];"
      },
      "id": "cf2fac80-5d99-418d-9a48-5bc4a5379cb7",
      "name": "Build Success Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3264,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "4a71212c-9d27-4617-9bb7-0a31d31820f6",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        3504,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// X\u1eed l\u00fd l\u1ed7i v\u00e0 tr\u1ea3 v\u1ec1 th\u00f4ng tin l\u1ed7i\nconst error = $input.first().json;\nconsole.error('Workflow error:', JSON.stringify(error));\n\nreturn [{\n  json: {\n    success: false,\n    message: '\u274c C\u00f3 l\u1ed7i x\u1ea3y ra trong qu\u00e1 tr\u00ecnh t\u1ea1o quiz',\n    error: {\n      message: error.message || 'Unknown error',\n      node: error.node || 'Unknown node',\n      details: error.description || ''\n    },\n    troubleshooting: [\n      'Ki\u1ec3m tra Google Drive credentials \u0111\u00e3 \u0111\u01b0\u1ee3c c\u1ea5p quy\u1ec1n ch\u01b0a',\n      'Ki\u1ec3m tra folder URL c\u00f3 \u0111\u00fang \u0111\u1ecbnh d\u1ea1ng kh\u00f4ng',\n      'Ki\u1ec3m tra folder c\u00f3 ch\u1ee9a file .md kh\u00f4ng',\n      'Ki\u1ec3m tra Google Gemini API key c\u00f2n quota kh\u00f4ng (AI Studio / Vertex AI)',\n      'Ki\u1ec3m tra Google Forms API \u0111\u00e3 \u0111\u01b0\u1ee3c b\u1eadt trong Cloud Console ch\u01b0a',\n      'Ki\u1ec3m tra Gemini model name c\u00f3 \u0111\u00fang kh\u00f4ng: gemini-2.5-pro-preview'\n    ]\n  }\n}];"
      },
      "id": "bf5b3b0b-6b1d-40fe-923a-35d999103602",
      "name": "Error Handler",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3264,
        208
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {
          "responseCode": 500,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "id": "9893af27-a3e0-47f0-bba6-b0e346003e63",
      "name": "Respond Error to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        3504,
        208
      ]
    },
    {
      "parameters": {
        "resource": "fileFolder",
        "returnAll": true,
        "filter": {
          "folderId": {
            "__rl": true,
            "value": "={{ $json.folderId }}",
            "mode": "id"
          }
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        464,
        0
      ],
      "id": "f1fdb219-9f2f-49c0-8bab-0344b6611371",
      "name": "Search files and folders",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Parse Input & Extract Folder ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Input & Extract Folder ID": {
      "main": [
        [
          {
            "node": "Search files and folders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Markdown Files": {
      "main": [
        [
          {
            "node": "Download Markdown File Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Markdown File Content": {
      "main": [
        [
          {
            "node": "Extract Text Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Text Content": {
      "main": [
        [
          {
            "node": "AI Generate Quiz Questions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Generate Quiz Questions": {
      "main": [
        [
          {
            "node": "Parse AI Quiz Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Quiz Response": {
      "main": [
        [
          {
            "node": "Aggregate All Quiz Questions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate All Quiz Questions": {
      "main": [
        [
          {
            "node": "Merge & Prepare Quiz Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge & Prepare Quiz Data": {
      "main": [
        [
          {
            "node": "Create Google Form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Google Form": {
      "main": [
        [
          {
            "node": "Extract Form ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Form ID": {
      "main": [
        [
          {
            "node": "Add Quiz Questions to Form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Quiz Questions to Form": {
      "main": [
        [
          {
            "node": "Build Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Success Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Handler": {
      "main": [
        [
          {
            "node": "Respond Error to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search files and folders": {
      "main": [
        [
          {
            "node": "Filter Markdown Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "2d715825-9712-448c-bbbf-1d2fb76f8233",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "B5uztSfsuBAHUNlq",
  "tags": []
}