{
  "name": "PixelSensei(ZH)",
  "nodes": [
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $('User Input').item.json.topic }}",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "={{ $('Extract Character Config').item.json.planningPrompt }}\n\nDo NOT refuse the topic. Your job is to explain it regardless of the domain.\n\n**Language Rule**: Detect the language of the user's input and respond in the SAME language. If the user writes in Chinese, respond in Chinese. If the user writes in English, respond in English."
        }
      },
      "id": "7f222b40-90e1-4162-bc87-6eba458bfe69",
      "name": "Planning Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        -43280,
        -21408
      ],
      "retryOnFail": true,
      "maxTries": 5
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"topic\": {\n      \"type\": \"string\",\n      \"description\": \"The technical concept to explain\"\n    },\n    \"complexity\": {\n      \"type\": \"string\",\n      \"description\": \"Complexity level: simple, medium, complex, or systematic\"\n    },\n    \"totalPages\": {\n      \"type\": \"number\",\n      \"description\": \"Total number of comic pages to generate\"\n    },\n    \"artStyle\": {\n      \"type\": \"string\",\n      \"description\": \"Art style: manga, minimal, cyberpunk, or sketch\"\n    },\n    \"coreAnalogy\": {\n      \"type\": \"string\",\n      \"description\": \"Main analogy or metaphor to explain the concept\"\n    },\n    \"pages\": {\n      \"type\": \"array\",\n      \"description\": \"Array of page objects with flexible dialogue and technical content structure\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"pageNumber\": {\n            \"type\": \"number\",\n            \"description\": \"Page number in sequence\"\n          },\n          \"title\": {\n            \"type\": \"string\",\n            \"description\": \"Page title\"\n          },\n          \"scene\": {\n            \"type\": \"string\",\n            \"description\": \"Scene description and setting\"\n          },\n          \"dialogue\": {\n            \"type\": \"array\",\n            \"description\": \"Array of dialogue objects with character and text\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"character\": {\n                  \"type\": \"string\",\n                  \"description\": \"Character name (e.g., Rick Sanchez, Morty Smith)\"\n                },\n                \"text\": {\n                  \"type\": \"string\",\n                  \"description\": \"Dialogue text for this character\"\n                }\n              },\n              \"required\": [\"character\", \"text\"]\n            }\n          },\n          \"visualElements\": {\n            \"type\": \"string\",\n            \"description\": \"Visual elements and actions in the panel\"\n          },\n          \"technicalSection\": {\n            \"type\": \"object\",\n            \"description\": \"Technical content section with title and detailed content\",\n            \"properties\": {\n              \"title\": {\n                \"type\": \"string\",\n                \"description\": \"Title of the technical section\"\n              },\n              \"content\": {\n                \"type\": \"string\",\n                \"description\": \"Detailed technical explanation or diagram description\"\n              }\n            },\n            \"required\": [\"title\", \"content\"]\n          },\n          \"learningPoint\": {\n            \"type\": \"object\",\n            \"description\": \"Key learning takeaway from this page\",\n            \"properties\": {\n              \"title\": {\n                \"type\": \"string\",\n                \"description\": \"Title of the learning point\"\n              },\n              \"content\": {\n                \"type\": \"string\",\n                \"description\": \"Detailed learning point explanation\"\n              }\n            },\n            \"required\": [\"title\", \"content\"]\n          }\n        },\n        \"required\": [\"pageNumber\", \"title\", \"scene\", \"dialogue\", \"visualElements\", \"learningPoint\"]\n      }\n    }\n  },\n  \"required\": [\"topic\", \"complexity\", \"totalPages\", \"artStyle\", \"coreAnalogy\", \"pages\"]\n}",
        "autoFix": true
      },
      "id": "71ec8bb8-4df4-4869-99f0-01c3e606ac1f",
      "name": "Comic Plan Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        -43104,
        -21136
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "topic",
              "value": "={{ $json.output.topic }}",
              "type": "string"
            },
            {
              "id": "id-2",
              "name": "complexity",
              "value": "={{ $json.output.complexity }}",
              "type": "string"
            },
            {
              "id": "id-3",
              "name": "totalPages",
              "value": "={{ $json.output.totalPages }}",
              "type": "number"
            },
            {
              "id": "id-4",
              "name": "artStyle",
              "value": "={{ $json.output.artStyle }}",
              "type": "string"
            },
            {
              "id": "id-5",
              "name": "coreAnalogy",
              "value": "={{ $json.output.coreAnalogy }}",
              "type": "string"
            },
            {
              "id": "id-6",
              "name": "pages",
              "value": "={{ JSON.stringify($json.output.pages) }}",
              "type": "array"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "d320f205-55d0-4f42-8c0e-c6a724c70d6e",
      "name": "Extract Plan Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -42912,
        -21408
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const planData = $input.item.json;\n// \u83b7\u53d6\u5b9e\u9645\u7684\u9875\u9762\u6570\u7ec4\nconst pages = Array.isArray(planData.pages) ? planData.pages : [];\n\n// \u2b50 \u5173\u952e\u70b9\uff1a\u76f4\u63a5\u8ba1\u7b97\u5b9e\u9645\u751f\u6210\u7684\u9875\u6570\uff08\u6bd4\u5982 12\uff09\n// \u4e0d\u8981\u5b8c\u5168\u4fe1\u4efb planData.totalPages\uff0c\u4ee5\u6570\u7ec4\u5b9e\u9645\u957f\u5ea6\u4e3a\u51c6\nconst actualTotalCount = pages.length;\n\nreturn {\n  output: pages\n    .filter(page => page && typeof page === 'object' && !Array.isArray(page))\n    .map(page => ({\n      json: {\n        ...page,\n        // \u628a\u7236\u7ea7\u7684\u901a\u7528\u5c5e\u6027\u5e26\u4e0b\u6765\n        topic: planData.topic,\n        artStyle: planData.artStyle,\n        \n        // \u2b50 \u6ce8\u5165\u603b\u9875\u6570\uff01\u8fd9\u6837\u6bcf\u4e00\u9875\u90fd\u77e5\u9053\u603b\u6570\u662f\u591a\u5c11\n        totalPages: actualTotalCount \n      }\n    }))\n};"
      },
      "id": "b4eac772-9642-46a1-bd93-47a461fa6dfa",
      "name": "Split Pages into Items",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -42688,
        -21408
      ]
    },
    {
      "parameters": {
        "fieldToSplitOut": "output",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        -42416,
        -21408
      ],
      "id": "63779125-2a45-4213-a4cc-6cd05e536407",
      "name": "Split Out"
    },
    {
      "parameters": {
        "content": "#### \u914d\u7f6e Gemini Header Auth \ud83d\udc47",
        "height": 80,
        "width": 246,
        "color": 3
      },
      "id": "23666ad9-d342-4b92-8f4d-4c754b2b7fc0",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -42240,
        12480
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "jsCode": "// HTML Assembler - Combine all pages into main HTML template\nconst inputItems = $input.all();\nconst firstItem = inputItems[0];\nconst allPages = firstItem.json.data || [];\n\n// Add check for empty pages data\nif (allPages.length === 0) {\n  console.error('HTML Assembler - No pages data found');\n  return [{\n    json: {\n      error: 'No pages data available'\n    }\n  }];\n}\n\nconsole.log('HTML Assembler - Data structure:', {\n  totalInputItems: inputItems.length,\n  hasDataArray: !!firstItem.json.data,\n  dataArrayLength: firstItem.json.data?.length\n});\n\n// Debug: Log first page data\nconsole.log('HTML Assembler - First page data:', JSON.stringify({\n  mentorName: allPages[0]?.mentorName,\n  learnerName: allPages[0]?.learnerName,\n  mainTitle: allPages[0]?.mainTitle,\n  hasDialogueHTML: !!allPages[0]?.dialogueHTML,\n  hasPageHTML: !!allPages[0]?.pageHTML\n}));\n\n// Get comic metadata from first page\nconst comicData = allPages[0];\nconsole.log(\"#####\")\nconsole.log(allPages)\nconsole.log(\"#####\")\n// Collect all page HTML - Fixed to properly extract pageHTML from data array\nconst allPagesHTML = allPages\n  .map(page => {\n    const pageObj = page;\n    return pageObj.pageHTML || '';\n  })\n  .filter(html => html.trim() !== '')\n  .join('\\n\\n');\n\n// Read main HTML template\nconst mainTemplate = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>{{TITLE}} - Tech Comic</title>\n        <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);\n            color: #e2e8f0;\n            line-height: 1.6;\n            min-height: 100vh;\n            padding: 20px;\n        }\n\n        .container {\n            max-width: 1200px;\n            margin: 0 auto;\n        }\n\n        header {\n            text-align: center;\n            margin-bottom: 40px;\n            padding: 30px;\n            background: rgba(30, 41, 59, 0.8);\n            border-radius: 20px;\n            border: 1px solid rgba(148, 163, 184, 0.2);\n            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);\n        }\n\n        h1 {\n            font-size: 2.8rem;\n            background: linear-gradient(90deg, #60a5fa, #a78bfa);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            margin-bottom: 15px;\n            text-shadow: 0 2px 10px rgba(96, 165, 250, 0.3);\n        }\n\n        .subtitle {\n            font-size: 1.2rem;\n            color: #94a3b8;\n            margin-bottom: 20px;\n        }\n\n        .stats {\n            display: flex;\n            justify-content: center;\n            gap: 30px;\n            margin-top: 20px;\n            flex-wrap: wrap;\n        }\n\n        .stat-item {\n            background: rgba(51, 65, 85, 0.6);\n            padding: 15px 25px;\n            border-radius: 12px;\n            border: 1px solid rgba(148, 163, 184, 0.2);\n        }\n\n        .stat-label {\n            font-size: 0.9rem;\n            color: #94a3b8;\n            margin-bottom: 5px;\n        }\n\n        .stat-value {\n            font-size: 1.4rem;\n            font-weight: bold;\n            color: #60a5fa;\n        }\n\n        .intro-box {\n            background: rgba(96, 165, 250, 0.1);\n            border: 1px solid rgba(96, 165, 250, 0.3);\n            border-radius: 15px;\n            padding: 25px;\n            margin-bottom: 40px;\n            text-align: center;\n        }\n\n        .intro-box h3 {\n            color: #60a5fa;\n            margin-bottom: 10px;\n            font-size: 1.4em;\n        }\n\n        .intro-box p {\n            color: #94a3b8;\n            font-size: 1.1em;\n            line-height: 1.6;\n        }\n\n        .page {\n            margin: 40px 0;\n            padding: 30px;\n            border-radius: 15px;\n            background: rgba(255,255,255,0.02);\n            border-left: 5px solid #60a5fa;\n            box-shadow: 0 5px 20px rgba(0,0,0,0.2);\n            transition: all 0.3s ease;\n        }\n\n        .page:hover {\n            transform: translateX(5px);\n            box-shadow: 0 8px 30px rgba(96, 165, 250, 0.2);\n        }\n\n        .page:nth-child(even) {\n            border-left-color: #a78bfa;\n        }\n\n        .page-header {\n            display: flex;\n            align-items: center;\n            gap: 20px;\n            margin-bottom: 25px;\n            justify-content: space-between;\n        }\n\n        .page-number {\n            background: linear-gradient(45deg, #60a5fa, #3b82f6);\n            color: white;\n            width: 50px;\n            height: 50px;\n            border-radius: 50%;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 1.4em;\n            font-weight: bold;\n            box-shadow: 0 4px 15px rgba(96, 165, 250, 0.4);\n            flex-shrink: 0;\n        }\n\n        .page:nth-child(even) .page-number {\n            background: linear-gradient(45deg, #a78bfa, #9333ea);\n            box-shadow: 0 4px 15px rgba(167, 139, 250, 0.4);\n        }\n\n        .page-title {\n            color: #60a5fa;\n            font-size: 1.6em;\n            font-weight: bold;\n        }\n\n        .page:nth-child(even) .page-title {\n            color: #a78bfa;\n        }\n\n        .page-content {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 30px;\n            align-items: flex-start;\n        }\n\n        .page-image {\n            flex: 1;\n            min-width: 300px;\n            border-radius: 12px;\n            overflow: hidden;\n            box-shadow: 0 10px 30px rgba(0,0,0,0.3);\n            border: 2px solid rgba(255,255,255,0.1);\n            transition: all 0.3s ease;\n        }\n\n        .page-image:hover {\n            transform: scale(1.02);\n            box-shadow: 0 15px 40px rgba(96, 165, 250, 0.3);\n        }\n\n        .page-image img {\n            width: 100%;\n            height: auto;\n            display: block;\n        }\n\n        .page-image-container {\n            flex: 1;\n            min-width: 310px;\n            padding: 20px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background: rgba(15, 23, 42, 0.7);\n        }\n\n        .page-info {\n            flex: 1;\n            padding: 20px;\n            background: rgba(15, 23, 42, 0.9);\n        }\n\n        .page-desc {\n            font-size: 1rem;\n            color: #94a3b8;\n            margin-bottom: 20px;\n            line-height: 1.5;\n        }\n\n        .dialogue-section {\n            margin-bottom: 25px;\n        }\n\n        .section-title {\n            font-size: 1rem;\n            color: #60a5fa;\n            margin-bottom: 15px;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n        }\n\n        .dialogue-item {\n            margin-bottom: 15px;\n            padding: 12px 15px;\n            background: rgba(51, 65, 85, 0.6);\n            border-radius: 10px;\n            border-left: 4px solid;\n        }\n\n        /* Rick - Blue theme */\n        .dialogue-item.role-learner {\n            border-left-color: #60a5fa;\n        }\n\n        /* Morty - Yellow theme */\n        .dialogue-item.role-mentor {\n            border-left-color: #fbbf24;\n        }\n\n        .character {\n            font-weight: bold;\n            margin-bottom: 5px;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .character.role-learner {\n            color: #60a5fa;\n        }\n\n        .character.role-mentor {\n            color: #fbbf24;\n        }\n\n        .character-emoji {\n            font-size: 1.2rem;\n        }\n\n        .dialogue-text {\n            color: #e2e8f0;\n            font-size: 0.95rem;\n        }\n\n        .tech-section {\n            margin-top: 20px;\n            margin-bottom: 20px;\n        }\n\n        .tech-box {\n            background: rgba(51, 65, 85, 0.6);\n            border-radius: 10px;\n            padding: 15px;\n        }\n\n        .tech-label {\n            font-size: 0.9rem;\n            color: #94a3b8;\n            margin-bottom: 8px;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n        }\n\n        .tech-content {\n            color: #a78bfa;\n            font-family: 'Courier New', monospace;\n            font-size: 0.9rem;\n            background: rgba(15, 23, 42, 0.6);\n            padding: 10px;\n            border-radius: 8px;\n            border: 1px solid rgba(167, 139, 250, 0.3);\n        }\n\n        .learning-section {\n            margin-top: 20px;\n            background: rgba(34, 197, 94, 0.1);\n            border-left: 4px solid #22c55e;\n            border-radius: 10px;\n            padding: 15px;\n        }\n\n        .learning-content {\n            color: #e2e8f0;\n            font-size: 0.95rem;\n            line-height: 1.6;\n            margin-top: 10px;\n        }\n\n        footer {\n            text-align: center;\n            margin-top: 50px;\n            padding-top: 30px;\n            border-top: 1px solid rgba(255,255,255,0.1);\n            color: #666;\n        }\n\n        .footer-icons {\n            display: flex;\n            justify-content: center;\n            gap: 15px;\n            margin-bottom: 15px;\n            font-size: 1.5em;\n        }\n        .footer-honest {\n          background: linear-gradient(180deg, rgba(15, 23, 42, 0.8) 0%, rgba(2, 6, 23, 0.95) 100%);\n          border-top: 1px solid rgba(148, 163, 184, 0.15);\n          padding: 50px 20px 30px;\n          margin-top: 80px;\n        }\n        \n        .footer-container {\n          max-width: 900px;\n          margin: 0 auto;\n          display: flex;\n          flex-direction: column;\n          gap: 35px;\n        }\n        \n        /* \u54c1\u724c\u533a\u57df */\n        .footer-brand {\n          text-align: center;\n        }\n        \n        .brand-header {\n          display: inline-flex;\n          align-items: center;\n          gap: 12px;\n          margin-bottom: 12px;\n        }\n        \n        .brand-icon {\n          font-size: 2rem;\n        }\n        \n        .brand-name {\n          font-size: 1.4rem;\n          font-weight: 600;\n          background: linear-gradient(90deg, #60a5fa, #a78bfa);\n          -webkit-background-clip: text;\n          -webkit-text-fill-color: transparent;\n        }\n        \n        .brand-tagline {\n          color: #94a3b8;\n          font-size: 1rem;\n          line-height: 1.6;\n          max-width: 650px;\n          margin: 0 auto;\n        }\n        \n        /* CTA \u6309\u94ae\u533a\u57df */\n        .footer-actions {\n          display: flex;\n          justify-content: center;\n          gap: 15px;\n          flex-wrap: wrap;\n        }\n        \n        .btn-primary {\n          display: inline-flex;\n          align-items: center;\n          gap: 8px;\n          background: linear-gradient(135deg, #60a5fa, #3b82f6);\n          color: white;\n          padding: 14px 28px;\n          border-radius: 10px;\n          text-decoration: none;\n          font-weight: 600;\n          font-size: 0.95rem;\n          transition: all 0.3s ease;\n          box-shadow: 0 4px 15px rgba(96, 165, 250, 0.3);\n        }\n        \n        .btn-primary:hover {\n          transform: translateY(-2px);\n          box-shadow: 0 6px 20px rgba(96, 165, 250, 0.4);\n        }\n        \n        .btn-secondary {\n          display: inline-flex;\n          align-items: center;\n          color: #94a3b8;\n          padding: 14px 28px;\n          border: 1px solid rgba(148, 163, 184, 0.3);\n          border-radius: 10px;\n          text-decoration: none;\n          font-weight: 500;\n          font-size: 0.95rem;\n          transition: all 0.3s ease;\n        }\n        \n        .btn-secondary:hover {\n          border-color: #60a5fa;\n          color: #60a5fa;\n          background: rgba(96, 165, 250, 0.05);\n        }\n        \n        /* \u6cd5\u5f8b\u4fe1\u606f\u533a\u57df */\n        .footer-legal {\n          text-align: center;\n          padding-top: 30px;\n          border-top: 1px solid rgba(148, 163, 184, 0.1);\n        }\n        \n        .legal-text {\n          display: flex;\n          flex-direction: column;\n          gap: 10px;\n        }\n        \n        .legal-text p {\n          color: #64748b;\n          font-size: 0.875rem;\n        }\n        \n        .legal-text a {\n          color: #60a5fa;\n          text-decoration: none;\n          transition: color 0.2s;\n        }\n        \n        .legal-text a:hover {\n          color: #93c5fd;\n          text-decoration: underline;\n        }\n        \n        .disclaimer {\n          font-size: 0.8rem !important;\n          color: #475569 !important;\n          font-style: italic;\n        }\n        \n        @media (max-width: 640px) {\n          .footer-actions {\n            flex-direction: column;\n            width: 100%;\n          }\n          \n          .btn-primary,\n          .btn-secondary {\n            width: 100%;\n            justify-content: center;\n          }\n          \n          .brand-tagline {\n            font-size: 0.9rem;\n          }\n        }\n        @media (max-width: 768px) {\n            .page-content {\n                flex-direction: column;\n            }\n\n            .page-image, .page-text {\n                min-width: 100%;\n            }\n\n            h1 {\n                font-size: 2em;\n            }\n\n            .page-title {\n                font-size: 1.3em;\n            }\n\n            .meta-info {\n                flex-direction: column;\n                align-items: center;\n            }\n        }\n    </style>\n\n    <!-- \u4e3b\u9898\u5207\u6362\u5668\u6837\u5f0f -->\n    <style>\n        /* \u4e3b\u9898\u5207\u6362\u5668\u6837\u5f0f */\n        .theme-switcher {\n          position: fixed;\n          top: 20px;\n          right: 20px;\n          background: rgba(30, 41, 59, 0.95);\n          border-radius: 15px;\n          padding: 10px;\n          box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);\n          backdrop-filter: blur(10px);\n          z-index: 1000;\n          border: 1px solid rgba(148, 163, 184, 0.2);\n        }\n\n        .switcher-label {\n          color: #94a3b8;\n          font-size: 0.85rem;\n          margin-bottom: 10px;\n          text-align: center;\n          font-weight: 600;\n          text-transform: uppercase;\n          letter-spacing: 1px;\n        }\n\n        .theme-options {\n          display: flex;\n          flex-direction: column;\n          gap: 8px;\n        }\n\n        .theme-btn {\n          display: flex;\n          align-items: center;\n          gap: 10px;\n          padding: 10px 10px;\n          background: rgba(51, 65, 85, 0.6);\n          border: 2px solid transparent;\n          border-radius: 10px;\n          cursor: pointer;\n          transition: all 0.3s ease;\n          min-width: 140px;\n        }\n\n        .theme-btn:hover {\n          background: rgba(51, 65, 85, 0.9);\n          border-color: rgba(96, 165, 250, 0.5);\n          transform: translateX(-3px);\n        }\n\n        .theme-btn.active {\n          border-color: #60a5fa;\n          background: rgba(96, 165, 250, 0.15);\n          box-shadow: 0 0 15px rgba(96, 165, 250, 0.3);\n        }\n\n        .theme-preview {\n          width: 30px;\n          height: 30px;\n          border-radius: 8px;\n          display: block;\n          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n        }\n\n        .theme-name {\n          color: #e2e8f0;\n          font-size: 0.9rem;\n          font-weight: 500;\n        }\n\n        /* \u79fb\u52a8\u7aef\u4f18\u5316 */\n        @media (max-width: 768px) {\n          .theme-switcher {\n            top: auto;\n            bottom: 20px;\n            right: 20px;\n            left: 20px;\n          }\n          \n          .theme-options {\n            flex-direction: row;\n            flex-wrap: wrap;\n          }\n          \n          .theme-btn {\n            flex: 1;\n            min-width: auto;\n          }\n          \n          .theme-name {\n            display: none;\n          }\n        }\n    </style>\n    \n    <!-- \u52a8\u6001\u4e3b\u9898\u5bb9\u5668 -->\n    <style id=\"dynamic-theme\"></style>\n\n    <script>\n    // \u4e3b\u9898\u5207\u6362\u903b\u8f91\n    const themeStyles = {\n      default: \\`/* \u5f53\u524d\u7684\u9ed8\u8ba4 CSS */\\`,\n      cyberpunk: \\`{{CYBERPUNK_CSS}}\\`,\n      minimal: \\`{{MINIMAL_CSS}}\\`,\n    };\n\n    // \u7b49\u5f85 DOM \u52a0\u8f7d\u5b8c\u6210\n    document.addEventListener('DOMContentLoaded', function() {\n      // \u521d\u59cb\u5316\u4e3b\u9898\n      const savedTheme = localStorage.getItem('preferredTheme') || 'default';\n      applyTheme(savedTheme);\n      \n      // \u66f4\u65b0\u6309\u94ae\u72b6\u6001\n      updateActiveButton(savedTheme);\n      \n      // \u7ed1\u5b9a\u6309\u94ae\u4e8b\u4ef6\n      document.querySelectorAll('.theme-btn').forEach(btn => {\n        btn.addEventListener('click', function() {\n          const theme = this.dataset.theme;\n          applyTheme(theme);\n          updateActiveButton(theme);\n        });\n      });\n    });\n\n    // \u521d\u59cb\u5316\n    let currentTheme = localStorage.getItem('preferredTheme') || 'default';\n    applyTheme(currentTheme);\n\n    // \u7ed1\u5b9a\u6309\u94ae\u4e8b\u4ef6\n    document.querySelectorAll('.theme-btn').forEach(btn => {\n      btn.addEventListener('click', () => {\n        const theme = btn.dataset.theme;\n        applyTheme(theme);\n        \n        // \u66f4\u65b0\u6309\u94ae\u72b6\u6001\n        document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active'));\n        btn.classList.add('active');\n      });\n    });\n\n    // \u5e94\u7528\u4e3b\u9898\n    function applyTheme(themeName) {\n      const styleTag = document.getElementById('dynamic-theme') || createStyleTag();\n      styleTag.textContent = themeStyles[themeName];\n      localStorage.setItem('preferredTheme', themeName);\n      \n      // \u6dfb\u52a0\u5207\u6362\u52a8\u753b - \u4fee\u590d\u540e\u7684\u7248\u672c\n      if (document.body) {\n        document.body.style.transition = 'opacity 0.15s ease';\n        document.body.style.opacity = '0.95';\n        setTimeout(() => {\n          document.body.style.opacity = '1';\n        }, 150);\n      }\n    }\n\n    function createStyleTag() {\n      const style = document.createElement('style');\n      style.id = 'dynamic-theme';\n      document.head.appendChild(style);\n      return style;\n    }\n    // \u66f4\u65b0\u6309\u94ae\u6fc0\u6d3b\u72b6\u6001\n    function updateActiveButton(themeName) {\n      document.querySelectorAll('.theme-btn').forEach(btn => {\n        if (btn.dataset.theme === themeName) {\n          btn.classList.add('active');\n        } else {\n          btn.classList.remove('active');\n        }\n      });\n    }\n    </script>\n</head>\n<body>\n    <div class=\"container\">\n        <header>\n            <h1>{{TITLE}}</h1>\n            <p class=\"subtitle\">{{SUBTITLE}}</p>\n            <div class=\"stats\">\n                <div class=\"stat-item\">\n                    <div class=\"stat-label\">Characters</div>\n                    <div class=\"stat-value\">{{CHARACTERS}}</div>\n                </div>\n                <div class=\"stat-item\">\n                    <div class=\"stat-label\">Art Style</div>\n                    <div class=\"stat-value\">{{STYLE}}</div>\n                </div>\n                <div class=\"stat-item\">\n                    <div class=\"stat-label\">Complexity</div>\n                    <div class=\"stat-value\">{{COMPLEXITY}}</div>\n                </div>\n                <div class=\"stat-item\">\n                    <div class=\"stat-label\">Chapters</div>\n                    <div class=\"stat-value\">{{PAGE_COUNT}} Pages</div>\n                </div>\n            </div>\n        </header>\n\n        <div class=\"theme-switcher\">\n          <div class=\"switcher-label\">\ud83c\udfa8 Theme</div>\n          <div class=\"theme-options\">\n            <button class=\"theme-btn active\" data-theme=\"default\">\n              <span class=\"theme-preview\" style=\"background: linear-gradient(45deg, #60a5fa, #a78bfa)\"></span>\n              <span class=\"theme-name\">Default</span>\n            </button>\n            <button class=\"theme-btn\" data-theme=\"cyberpunk\">\n              <span class=\"theme-preview\" style=\"background: linear-gradient(45deg, #00f0ff, #ff006e)\"></span>\n              <span class=\"theme-name\">Cyberpunk</span>\n            </button>\n            <button class=\"theme-btn\" data-theme=\"minimal\">\n              <span class=\"theme-preview\" style=\"background: linear-gradient(45deg, #3b82f6, #8b5cf6)\"></span>\n              <span class=\"theme-name\">Minimal</span>\n            </button>\n          </div>\n        </div>\n        \n        <div class=\"intro-box\">\n            <h3>\ud83d\udcd6 {{ANALOGY_TITLE}}</h3>\n            <p>{{ANALOGY_DESC}}</p>\n        </div>\n\n        <!-- ===== Page Content Area ===== -->\n        {{PAGES}}\n        <!-- ===== Page Content End ===== -->\n\n        <footer class=\"footer-honest\">\n          <div class=\"footer-container\">\n            <!-- \u54c1\u724c + \u4ef7\u503c\u4e3b\u5f20 -->\n            <div class=\"footer-brand\">\n              <div class=\"brand-header\">\n                <span class=\"brand-icon\">\ud83c\udfa8</span>\n                <span class=\"brand-name\">Tech Comic Generator</span>\n              </div>\n              <p class=\"brand-tagline\">\n                AI-powered educational comics. Learn complex tech concepts through visual storytelling.\n              </p>\n            </div>\n            \n            <!-- \u6838\u5fc3 CTA -->\n            <div class=\"footer-actions\">\n              <a href=\"https://mulerun.com/@LunarAITalk/pixelsensei\" class=\"btn-primary\">\n                <span>Create Your Own Comic</span>\n                <span class=\"btn-icon\">\u2192</span>\n              </a>\n            </div>\n            \n            <!-- \u6cd5\u5f8b + \u5f52\u5c5e -->\n            <div class=\"footer-legal\">\n              <div class=\"legal-text\">\n                <p>\n                  \u00a9 2025 Tech Comic Generator \u2022 \n                </p>\n                <p class=\"disclaimer\">\n                  \u26a1 Generated with AI \u2022 Characters are inspired by popular franchises for educational purposes\n                </p>\n              </div>\n            </div>\n          </div>\n        </footer>\n    </div>\n</body>\n</html>`;\n\n// Generate dynamic subtitle\nconst mentorName = allPages[0]?.mentorName || 'Mentor';\nconst learnerName = allPages[0]?.learnerName || 'Learner';\nconst topicName = allPages[0]?.mainTitle || allPages[0]?.topic || 'Tech Topic';\n\n// Generate subtitle, e.g., \"Rick and Morty teach you n8n\"\nconst subtitle = `${mentorName} and ${learnerName} teach you ${topicName}`;\n\n// \u751f\u6210\u89d2\u8272\u5bf9\u663e\u793a\u540d\u79f0: \"Rick & Morty\" \u6216 \"Optimus & Bumblebee\"\nconst characterDisplay = `${(mentorName || \"Mentor\").split(\" \")[0]} & ${(learnerName || \"Learner\").split(\" \")[0]}`;\n\n\n// Generate appropriate analogy title\nconst analogyTitle = comicData.coreAnalogy ? 'Core Analogy' : 'Learning Guide';\n// \u751f\u6210\u6bd4\u55bb\u63cf\u8ff0\nconst analogyDesc = comicData.coreAnalogy || \n  `Learn ${topicName} through conversations between ${mentorName} and ${learnerName}`;\n\nconst themeCSS = $('theme css').first().json\n\n\n// Replace placeholders in template\nconst finalHTML = mainTemplate\n  .replaceAll('{{TITLE}}', comicData.mainTitle || 'Tech Comic')\n  .replace('{{SUBTITLE}}', subtitle)\n  .replace('{{CYBERPUNK_CSS}}', themeCSS.cyberpunkCSS || '')\n  .replace('{{MINIMAL_CSS}}', themeCSS.minimalCSS || '')\n  .replace('{{CHARACTERS}}', characterDisplay || '')\n  .replace('{{COMPLEXITY}}', comicData.complexity || 'medium')\n  .replace('{{PAGE_COUNT}}', allPages.length.toString())\n  .replace('{{STYLE}}', comicData.artStyle || 'manga')\n  .replace('{{ANALOGY_TITLE}}', analogyTitle)\n  .replace('{{ANALOGY_DESC}}', analogyDesc)\n  .replace('{{PAGES}}', allPagesHTML);\n\nconsole.log('HTML Assembler - Generated final HTML with', allPages.length, 'pages');\n\nreturn [{\n  json: {\n    finalHTML: finalHTML,\n    pageCount: allPages.length,\n    topic: comicData.topic,\n    artStyle: comicData.artStyle,\n    complexity: comicData.complexity\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -41296,
        -20240
      ],
      "id": "ad7a3ca3-207b-45fa-b8d8-9e6a31e699ba",
      "name": "HTML Assembler"
    },
    {
      "parameters": {
        "jsCode": "// Character Prompts Library - Define character pairs with their prompts and visual details\n// This library provides reusable character configurations for different comic styles\n\nconst characterPairs = {\n  \"rick_morty\": {\n    name: \"Rick and Morty\",\n    mentor: {\n      name: \"Rick Sanchez\",\n      fullName: \"Rick Sanchez\",\n      emoji: \"\ud83d\udd2c\",\n      personality: \"Genius scientist, cynical, impatient but secretly caring\",\n      speech: \"Uses scientific jargon, burps mid-sentence, says 'Morty' a lot\",\n      teachingStyle: \"Explains through wild sci-fi analogies and interdimensional examples\",\n      visualDetails: {\n        hair: \"Spiky blue hair standing up\",\n        clothing: \"White lab coat with stains over gray-blue shirt\",\n        features: \"Unibrow, wrinkled face, bags under eyes\",\n        accessories: \"Flask or sci-fi gadget in hand\",\n        expression: \"Cynical expression or manic grin\",\n        build: \"Lanky build, hunched posture\",\n        skinTone: \"pale peachy/pink\"\n      }\n    },\n    learner: {\n      name: \"Morty Smith\",\n      fullName: \"Morty Smith\",\n      emoji: \"\ud83d\ude30\",\n      personality: \"Anxious teenager, eager to learn but easily confused\",\n      speech: \"Stutters ('Aw geez', 'I-I don't know Rick'), asks clarifying questions\",\n      learningStyle: \"Needs simple explanations, makes relatable mistakes\",\n      visualDetails: {\n        hair: \"Brown messy hair\",\n        clothing: \"Bright yellow t-shirt with blue jeans\",\n        features: \"Wide nervous eyes, round face\",\n        expression: \"Anxious or confused expression\",\n        build: \"Shorter than Rick, average teenage build\",\n        gestures: \"Often gesturing nervously with hands\",\n        skinTone: \"peachy/tan\"\n      }\n    },\n    emojiMap: {\n      \"Rick Sanchez\": \"\ud83d\udd2c\",\n      \"Morty Smith\": \"\ud83d\ude30\"\n    },\n    planningPrompt: `You are Rick Sanchez, the genius scientist from Rick and Morty, tasked with creating educational comics.\nYour role: Analyze the provided topic and create a detailed comic structure plan featuring Rick (mentor) and Morty (learner).\n\n## Character Profiles\n**Rick Sanchez (Mentor)**\n- Genius scientist, cynical, impatient but secretly caring\n- Speech: Uses scientific jargon, burps mid-sentence, says \"Morty\" a lot\n- Teaching style: Explains through wild sci-fi analogies and interdimensional examples\n- Appearance: Spiky blue hair, white lab coat over gray-blue shirt, unibrow, flask in hand\n\n**Morty Smith (Learner)**\n- Anxious teenager, eager to learn but easily confused\n- Speech: Stutters (\"Aw geez\", \"I-I don't know Rick\"), asks clarifying questions\n- Learning style: Needs simple explanations, makes relatable mistakes\n- Appearance: Brown messy hair, bright yellow t-shirt with blue jeans, wide nervous eyes\n\n## Your Task\n1. **Analyze Complexity**: Count core concepts and determine page count\n   - Simple (1-2 concepts): 3-4 pages\n   - Medium (3-5 concepts): 5-8 pages\n   - Complex (6-10 concepts): 9-15 pages\n   - Systematic (10+ concepts): 15-25 pages\n\n2. **Create Comic Structure**: For each page, provide:\n   - Page number and title\n   - Scene description (location in Rick's garage, lab, or interdimensional setting)\n   - Dialogue array: Each page MUST have 2 dialogue entries showing a conversation between BOTH characters. Each dialogue entry must have:\n     * character: \"Rick Sanchez\" or \"Morty Smith\" (use full names)\n     * text: What they say - keep concise, suitable for a single comic panel\n   - Visual elements (what's shown in the panel)\n   - Technical section object:\n     * title: Title of the concept/topic\n     * content: Detailed explanation of the concept or mechanism\n   - Learning point object:\n     * title: Key learning objective\n     * content: What the learner should understand from this page\n\n3. **Dialogue Guidelines - CRITICAL: Story-Driven Learning**:\n\n**Overall Narrative Arc (Across ALL Pages):**\n\nThe complete comic should follow this journey:\n- **Act 1 (Pages 1-2)**: Establish the PROBLEM\n  - Morty encounters a real challenge or confusion about the topic\n  - Show the pain point and its impact\n  - Create emotional investment in finding the solution\n\n- **Act 2 (Pages 3-5)**: Investigate ROOT CAUSE  \n  - Rick guides Morty to analyze the underlying issue\n  - Use sci-fi analogies to reveal hidden patterns\n  - Build understanding step-by-step\n\n- **Act 3 (Pages 6-8)**: Implement SOLUTION\n  - Introduce the solution/concept progressively\n  - Explain core concepts through dialogue\n  - Show how each piece fits together\n\n- **Act 4 (Final 1-2 pages)**: AHA MOMENT & Mastery\n  - Morty demonstrates complete understanding\n  - Success moment with visible results\n  - Reinforce key principles for retention\n\n**Individual Page Structure:**\n\nEach page serves ONE specific purpose in the story:\n- Advances the narrative thread from previous page\n- Introduces ONE new concept or insight\n- Ends with a hook that leads to the next page\n- Maintains conversational flow between Morty and Rick\n\n**Page-to-Page Continuity (MANDATORY):**\n- Page N ends with a question/observation \u2192 Page N+1 answers it\n- Reference what was learned in previous pages\n- Build knowledge cumulatively (don't repeat basics)\n- Use \"So that means...\", \"Wait, earlier you said...\", \"Now I understand why...\"\n\n**Dialogue Format (Each Page):**\n- 2 dialogue exchanges between Morty and Rick\n- Natural conversation, not lecture format\n- Morty's questions/observations drive the story forward\n- Rick's responses reveal insights using sci-fi analogies\n\n**Character Roles:**\n\n**Morty (Drives the Story):**\n- Page 1: Expresses the problem/frustration\n- Middle pages: Asks probing questions, makes deductions\n- Final page: Demonstrates mastery, celebrates success\n- Voice: Anxious, curious, uses \"Aw geez!\" and \"Holy crap!\" for realizations\n\n**Rick (Guides Discovery):**\n- Page 1: Acknowledges problem, proposes investigation\n- Middle pages: Reveals insights through interdimensional analogies\n- Final page: Confirms understanding, reinforces principles  \n- Voice: Cynical but helpful, uses \"Listen Morty...\", \"It's simple, Morty...\"\n\n**Forbidden Patterns:**\n- \u274c Each page solving a different problem (breaks continuity)\n- \u274c Repeating information from earlier pages\n- \u274c Page starts without connecting to previous page\n- \u274c Morty asking \"What is X?\" if X was already explained\n- \u274c Rick giving long monologues without Morty interaction\n\n**Validation Checklist:**\n- [ ] Does page 1 establish a CONCRETE problem?\n- [ ] Does each page reference or build upon previous pages?\n- [ ] Is there a clear progression from confusion \u2192 understanding?\n- [ ] Does the final page show Morty successfully applying the knowledge?\n- [ ] Would a reader want to turn to the next page to learn more?\n- [ ] Are BOTH characters present in dialogue on EVERY page?\n\n4. **Include Learning Moments**: At least one page must show:\n   - Morty misunderstanding the concept\n   - Rick correcting him with a clearer (but still cynical) explanation\n   - Visual comparison of wrong vs. right understanding\n\n5. **Use Sci-Fi Analogies**: Relate the topic to:\n   - Interdimensional portals, parallel universes\n   - Alien technology, sci-fi gadgets\n   - Rick's inventions and experiments\n   - Portal gun mechanics, dimension-hopping\n\n6. **Art Style Options**: Choose based on user preference or default to manga\n   - manga: Japanese comic style with dynamic panels\n   - minimal: Clean lines, simple backgrounds\n   - cyberpunk: Neon colors, futuristic tech aesthetic\n   - sketch: Hand-drawn, rough artistic style\n\n7. **Character Consistency - CRITICAL**:\n   - Rick ALWAYS wears: white lab coat over gray-blue shirt\n   - Morty ALWAYS wears: bright yellow t-shirt with blue jeans\n   - Do NOT change clothing colors or styles between pages\n\n## Output Structure\nFor each page, return:\n- pageNumber (number)\n- title (string)\n- scene (string)\n- dialogue (array of objects with character and text fields - MUST include both \"Rick Sanchez\" and \"Morty Smith\")\n- visualElements (string)\n- technicalSection (object with title and content)\n- learningPoint (object with title and content)\n- topic (string)\n- artStyle (string)\n\nReturn the complete comic plan in the structured format.\n\n**VALIDATION: Before returning output, ensure EVERY page has dialogue from BOTH Morty Smith AND Rick Sanchez.**`,\n    imagePromptGuidelines: `You are an expert comic image prompt generator specializing in Rick and Morty style educational comics.\nYour task: Transform comic page data into detailed image generation prompts that capture the Rick and Morty aesthetic while clearly explaining the concepts.\n\n## Art Style Guidelines\n\n**manga**: Japanese comic style with dynamic action lines, speed effects, dramatic angles, **FULL COLOR with vibrant tones** (NOT black and white), expressive character reactions, speech bubbles with bold text. Color palette: bright yellows, blues, greens, and lab equipment grays. Rich shading and highlights.\n\n**minimal**: Clean vector art, simple geometric shapes, limited color palette (3-4 colors), clear white backgrounds, focus on essential elements only, simple speech bubbles with sans-serif text\n\n**cyberpunk**: Neon colors (cyan, magenta, yellow), dark backgrounds, holographic interfaces, glitch effects, futuristic tech aesthetic, grid patterns, glowing speech bubbles\n\n**sketch**: Hand-drawn pencil style, visible sketch lines, crosshatching for shading, rough artistic texture, paper texture background, hand-drawn speech bubbles. Can be colored or grayscale based on request.\n\n## Character Visual Details - CRITICAL CONSISTENCY\n\n**Rick Sanchez** (MUST be consistent across ALL pages):\n- Spiky blue hair standing up\n- **White lab coat over gray-blue shirt** (ALWAYS - do not change)\n- Unibrow, wrinkled face, bags under eyes\n- Flask or sci-fi gadget in hand\n- Cynical expression or manic grin\n- Lanky build, hunched posture\n- **Skin tone: pale peachy/pink**\n\n**Morty Smith** (MUST be consistent across ALL pages):\n- Brown messy hair\n- **Bright yellow t-shirt with blue jeans** (ALWAYS - do not change)\n- Wide nervous eyes, round face\n- Anxious or confused expression\n- Shorter than Rick, average teenage build\n- Often gesturing nervously\n- **Skin tone: peachy/tan**\n\n## Speech Bubble Requirements - CRITICAL\n\n**MUST include visible speech bubbles with COMPLETE dialogue text from BOTH characters:**\n- Each character's speech bubble should contain their FULL dialogue text\n- Position speech bubbles clearly near each character\n- Use quotation marks or bubble tails to indicate who is speaking\n- Format: Rick's bubble: \"[Rick's complete dialogue]\" - Morty's bubble: \"[Morty's complete dialogue]\"\n- Bubbles should be readable and not overlap with diagrams\n- Support both English and Chinese dialogue\n\n## Concept/Diagram Integration\n\nFor each concept, include:\n- Clear visual representation of the concept or diagram\n- Labels and arrows showing flow or relationships\n- Color coding to distinguish different components\n- Integration with the scene (holographic display, whiteboard, floating interface)\n- Rick pointing at or interacting with the diagram\n- Ensure diagram doesn't obscure speech bubbles\n\n## Prompt Structure\n\nGenerate prompts in this format:\n\n\"[Art style] comic panel: Rick and Morty in [scene location]. Rick (spiky blue hair, white lab coat over gray-blue shirt) [action]. Morty (brown hair, bright yellow t-shirt, blue jeans) [action]. [Diagram/Visual description]. Speech bubbles visible - Rick's bubble: '[COMPLETE Rick dialogue]' - Morty's bubble: '[COMPLETE Morty dialogue]'. [Mood/lighting]. **Full color, vibrant tones**. High quality, detailed.\"\n\n## Critical Requirements for Output\n\nYour generated prompt MUST:\n1. Include \"[Character]'s bubble: '[COMPLETE dialogue text]'\" for BOTH Rick and Morty\n2. **ALWAYS specify Rick's clothing: \"white lab coat over gray-blue shirt\"**\n3. **ALWAYS specify Morty's clothing: \"bright yellow t-shirt with blue jeans\"**\n4. Place dialogue specifications clearly in the prompt\n5. Ensure dialogue text is from the input page data\n6. **ALWAYS specify \"Full color, vibrant tones\" for manga style**\n7. Never deviate from the character clothing descriptions\n\n**VALIDATION**: Before returning, verify:\n- Rick is wearing \"white lab coat over gray-blue shirt\"\n- Morty is wearing \"bright yellow t-shirt with blue jeans\"\n- Both characters have complete dialogue bubbles\n- \"Full color\" specification is present\n\nReturn ONLY the prompt text, no additional explanation.`,\n    settings: [\"Rick's garage\", \"interdimensional lab\", \"sci-fi laboratory\", \"portal room\"],\n    analogyStyle: \"Interdimensional portals, parallel universes, alien technology, sci-fi gadgets\"\n  },\n  \n  \"optimus_bumblebee\": {\n    name: \"Optimus Prime and Bumblebee\",\n    mentor: {\n      name: \"Optimus Prime\",\n      fullName: \"Optimus Prime\",\n      emoji: \"\ud83e\udd16\",\n      personality: \"Wise leader, noble, patient, values honor and courage\",\n      speech: \"Speaks with gravitas and wisdom, uses 'Freedom is the right of all sentient beings' philosophy\",\n      teachingStyle: \"Explains through transformation principles, modular systems, and Cybertronian technology\",\n      visualDetails: {\n        hair: \"Metallic helmet with iconic red and blue design\",\n        clothing: \"Red and blue armor plating with silver accents\",\n        features: \"Blue optics (glowing eyes), battle mask, noble facial structure\",\n        accessories: \"Ion blaster, transformation matrix glowing in chest\",\n        expression: \"Wise and determined, or contemplative\",\n        build: \"Tall, powerful robotic frame with broad shoulders\",\n        skinTone: \"Metallic red and blue with silver chrome details\"\n      }\n    },\n    learner: {\n      name: \"Bumblebee\",\n      fullName: \"Bumblebee\",\n      emoji: \"\ud83d\ude97\",\n      personality: \"Young eager scout, brave, enthusiastic, loyal\",\n      speech: \"Energetic and enthusiastic, uses radio clips and beeps sometimes, calls Optimus 'sir'\",\n      learningStyle: \"Quick learner, learns through action, asks practical questions\",\n      visualDetails: {\n        hair: \"Yellow helmet with black stripe design\",\n        clothing: \"Yellow and black armor plating\",\n        features: \"Bright blue optics, expressive face, smaller frame\",\n        accessories: \"Plasma cannon, scanner device, speed boosters\",\n        expression: \"Excited, curious, or concentrating intensely\",\n        build: \"Smaller, more agile robotic frame than Optimus\",\n        gestures: \"Dynamic action poses, often ready to transform\",\n        skinTone: \"Bright yellow with black stripes and silver details\"\n      }\n    },\n    emojiMap: {\n      \"Optimus Prime\": \"\ud83e\udd16\",\n      \"Bumblebee\": \"\ud83d\ude97\"\n    },\n    planningPrompt: `You are Optimus Prime, the noble leader of the Autobots, tasked with creating educational comics.\nYour role: Analyze the provided topic and create a detailed comic structure plan featuring Optimus Prime (mentor) and Bumblebee (learner).\n\n## Character Profiles\n**Optimus Prime (Mentor)**\n- Wise leader, noble, patient, values honor and courage\n- Speech: Speaks with gravitas and wisdom, uses transformation and unity metaphors\n- Teaching style: Explains through modular systems, transformation principles, Cybertronian technology\n- Appearance: Red and blue armor with silver accents, blue glowing optics, battle mask, transformation matrix in chest\n\n**Bumblebee (Learner)**\n- Young eager scout, brave, enthusiastic, loyal\n- Speech: Energetic and enthusiastic, uses beeps and radio clips, calls Optimus 'sir'\n- Learning style: Quick learner, learns through action, asks practical questions\n- Appearance: Yellow and black armor, bright blue optics, smaller agile frame, scanner device\n\n## Your Task\n1. **Analyze Complexity**: Count core concepts and determine page count\n   - Simple (1-2 concepts): 3-4 pages\n   - Medium (3-5 concepts): 5-8 pages\n   - Complex (6-10 concepts): 9-15 pages\n   - Systematic (10+ concepts): 15-25 pages\n\n2. **Create Comic Structure**: For each page, provide:\n   - Page number and title\n   - Scene description (location in Autobot base, Cybertron, battlefield, or Earth garage)\n   - Dialogue array: Each page MUST have 2 dialogue entries showing a conversation between BOTH characters. Each dialogue entry must have:\n     * character: \"Optimus Prime\" or \"Bumblebee\" (use full names)\n     * text: What they say - keep concise, suitable for a single comic panel\n   - Visual elements (what's shown in the panel)\n   - Technical section object:\n     * title: Title of the concept/topic\n     * content: Detailed explanation of the concept or mechanism\n   - Learning point object:\n     * title: Key learning objective\n     * content: What the learner should understand from this page\n\n3. **Dialogue Guidelines - CRITICAL: Story-Driven Learning**:\n\n**Overall Narrative Arc (Across ALL Pages):**\n\nThe complete comic should follow this journey:\n- **Act 1 (Pages 1-2)**: Establish the PROBLEM\n  - Bumblebee encounters a real challenge or confusion about the topic\n  - Show the pain point and its impact\n  - Create emotional investment in finding the solution\n\n- **Act 2 (Pages 3-5)**: Investigate ROOT CAUSE  \n  - Optimus guides Bumblebee to analyze the underlying issue\n  - Use Transformer analogies to reveal hidden patterns\n  - Build understanding step-by-step\n\n- **Act 3 (Pages 6-8)**: Implement SOLUTION\n  - Introduce the solution/concept progressively\n  - Explain core concepts through dialogue\n  - Show how each piece fits together\n\n- **Act 4 (Final 1-2 pages)**: AHA MOMENT & Mastery\n  - Bumblebee demonstrates complete understanding\n  - Success moment with visible results\n  - Reinforce key principles for retention\n\n**Individual Page Structure:**\n\nEach page serves ONE specific purpose in the story:\n- Advances the narrative thread from previous page\n- Introduces ONE new concept or insight\n- Ends with a hook that leads to the next page\n- Maintains conversational flow between Bumblebee and Optimus\n\n**Page-to-Page Continuity (MANDATORY):**\n- Page N ends with a question/observation \u2192 Page N+1 answers it\n- Reference what was learned in previous pages\n- Build knowledge cumulatively (don't repeat basics)\n- Use \"So that means...\", \"Wait, earlier you said...\", \"Now I understand why...\"\n\n**Dialogue Format (Each Page):**\n- 2 dialogue exchanges between Bumblebee and Optimus\n- Natural conversation, not lecture format\n- Bumblebee's questions/observations drive the story forward\n- Optimus's responses reveal insights using Transformer analogies\n\n**Character Roles:**\n\n**Bumblebee (Drives the Story):**\n- Page 1: Expresses the problem/frustration\n- Middle pages: Asks probing questions, makes deductions\n- Final page: Demonstrates mastery, celebrates success\n- Voice: Enthusiastic, brave, uses beeps and excited exclamations\n\n**Optimus (Guides Discovery):**\n- Page 1: Acknowledges problem, proposes investigation\n- Middle pages: Reveals insights through transformation analogies\n- Final page: Confirms understanding, reinforces principles  \n- Voice: Wise, patient, uses \"Young scout...\", \"Observe, Bumblebee...\"\n\n**Forbidden Patterns:**\n- \u274c Each page solving a different problem (breaks continuity)\n- \u274c Repeating information from earlier pages\n- \u274c Page starts without connecting to previous page\n- \u274c Bumblebee asking \"What is X?\" if X was already explained\n- \u274c Optimus giving long monologues without Bumblebee interaction\n\n**Validation Checklist:**\n- [ ] Does page 1 establish a CONCRETE problem?\n- [ ] Does each page reference or build upon previous pages?\n- [ ] Is there a clear progression from confusion \u2192 understanding?\n- [ ] Does the final page show Bumblebee successfully applying the knowledge?\n- [ ] Would a reader want to turn to the next page to learn more?\n- [ ] Are BOTH characters present in dialogue on EVERY page?\n\n4. **Include Learning Moments**: At least one page must show:\n   - Bumblebee making an eager but incomplete observation\n   - Optimus guiding him to the complete understanding with wisdom\n   - Visual comparison of basic vs. advanced understanding\n\n5. **Use Transformer Analogies**: Relate the topic to:\n   - Transformation sequences (changing states/modes)\n   - Modular robot components (microservices)\n   - Energon flow and power distribution (data pipelines)\n   - Autobot communication networks (APIs)\n   - Cybertronian databases (storage systems)\n\n6. **Art Style Options**: Choose based on user preference or default to manga\n   - manga: Japanese comic style with dynamic panels\n   - minimal: Clean lines, simple backgrounds\n   - cyberpunk: Neon colors, futuristic tech aesthetic\n   - sketch: Hand-drawn, rough artistic style\n\n7. **Character Consistency - CRITICAL**:\n   - Optimus ALWAYS has: red and blue armor with silver accents, blue glowing optics\n   - Bumblebee ALWAYS has: yellow and black armor, blue glowing optics\n   - Do NOT change armor colors or design between pages\n\n## Output Structure\nFor each page, return:\n- pageNumber (number)\n- title (string)\n- scene (string)\n- dialogue (array of objects with character and text fields - MUST include both \"Optimus Prime\" and \"Bumblebee\")\n- visualElements (string)\n- technicalSection (object with title and content)\n- learningPoint (object with title and content)\n- topic (string)\n- artStyle (string)\n\nReturn the complete comic plan in the structured format.\n\n**VALIDATION: Before returning output, ensure EVERY page has dialogue from BOTH Bumblebee AND Optimus Prime.**`,\n    imagePromptGuidelines: `You are an expert comic image prompt generator specializing in Transformers-style educational comics.\nYour task: Transform comic page data into detailed image generation prompts that capture the Transformers aesthetic while clearly explaining the concepts.\n\n## Art Style Guidelines\n\n**manga**: Japanese comic style with dynamic action lines, speed effects, dramatic angles, **FULL COLOR with vibrant tones**, expressive character reactions, speech bubbles with bold text. Color palette: reds, blues, yellows, blacks, and metallic chrome. Rich shading and highlights with metallic sheen effects.\n\n**minimal**: Clean vector art, simple geometric shapes, limited color palette (3-4 colors), clear white backgrounds, focus on essential elements only, simple speech bubbles with sans-serif text\n\n**cyberpunk**: Neon colors (cyan, magenta, yellow), dark backgrounds, holographic interfaces, glitch effects, futuristic tech aesthetic, grid patterns, glowing speech bubbles, energon glow effects\n\n**sketch**: Hand-drawn pencil style, visible sketch lines, crosshatching for shading, rough artistic texture, paper texture background, hand-drawn speech bubbles with mechanical sketch details.\n\n## Character Visual Details - CRITICAL CONSISTENCY\n\n**Optimus Prime** (MUST be consistent across ALL pages):\n- Iconic helmet with red and blue metallic design\n- **Red and blue armor plating with silver chrome accents** (ALWAYS - do not change)\n- Blue glowing optics (eyes)\n- Battle mask or noble face\n- Transformation matrix glowing in chest (blue/orange light)\n- Ion blaster or energon axe as weapon\n- Tall, powerful robotic frame with broad shoulders\n- **Color scheme: Red, blue, and silver metallic** (NEVER change colors)\n\n**Bumblebee** (MUST be consistent across ALL pages):\n- Yellow helmet with black stripe design\n- **Yellow and black armor plating** (ALWAYS - do not change)\n- Bright blue glowing optics (eyes)\n- Expressive face, smaller than Optimus\n- Plasma cannon on arm\n- Scanner device and speed boosters\n- Agile, athletic robotic frame\n- **Color scheme: Yellow with black stripes and silver details** (NEVER change colors)\n\n## Speech Bubble Requirements - CRITICAL\n\n**MUST include visible speech bubbles with COMPLETE dialogue text from BOTH characters:**\n- Each character's speech bubble should contain their FULL dialogue text\n- Position speech bubbles clearly near each character\n- Format: Optimus's bubble: \"[Optimus's complete dialogue]\" - Bumblebee's bubble: \"[Bumblebee's complete dialogue]\"\n- Bubbles should be readable and not overlap with diagrams\n- Can include robotic/mechanical font styling\n- Support both English and Chinese dialogue\n\n## Concept/Diagram Integration\n\nFor each concept, include:\n- Clear visual representation (holographic displays, energon flow charts, Cybertronian schematics)\n- Labels and arrows showing flow\n- Color coding with Autobot blue and energon orange highlights\n- Integration with scene (holographic projections, command center screens)\n- Optimus pointing at or gesturing to diagram\n- Transformation sequence diagrams when relevant\n- Ensure diagram doesn't obscure speech bubbles\n\n## Prompt Structure\n\nGenerate prompts in this format:\n\n\"[Art style] comic panel: Optimus Prime and Bumblebee in [scene location]. Optimus (red and blue armor with silver accents, blue glowing optics) [action]. Bumblebee (yellow and black armor, blue glowing optics) [action]. [Diagram/Visual description]. Speech bubbles visible - Optimus's bubble: '[COMPLETE Optimus dialogue]' - Bumblebee's bubble: '[COMPLETE Bumblebee dialogue]'. [Mood/lighting]. **Full color, metallic sheen, vibrant robot aesthetic**. High quality, detailed.\"\n\n## Critical Requirements for Output\n\nYour generated prompt MUST:\n1. Include \"[Character]'s bubble: '[COMPLETE dialogue text]'\" for BOTH Optimus and Bumblebee\n2. **ALWAYS specify Optimus's colors: \"red and blue armor with silver accents\"**\n3. **ALWAYS specify Bumblebee's colors: \"yellow and black armor\"**\n4. **ALWAYS mention \"blue glowing optics\" for both**\n5. Place dialogue specifications clearly in the prompt\n6. **ALWAYS specify \"Full color, metallic sheen\" for manga style**\n7. Include transformation/energon effects where appropriate\n8. Never deviate from the armor color schemes\n\n**VALIDATION**: Before returning, verify:\n- Optimus has \"red and blue armor with silver accents\"\n- Bumblebee has \"yellow and black armor\"\n- Both have \"blue glowing optics\"\n- Both characters have complete dialogue bubbles\n- \"Full color, metallic\" specification is present\n\nReturn ONLY the prompt text, no additional explanation.`,\n    settings: [\"Autobot base command center\", \"Cybertron data archives\", \"Earth garage hideout\", \"Battlefield with holographic displays\"],\n    analogyStyle: \"Transformation sequences, modular robot systems, energon flow, Autobot communication networks, Cybertronian technology\"\n  },\n  \n  \"batman_robin\": {\n    name: \"Batman and Robin\",\n    mentor: {\n      name: \"Batman\",\n      fullName: \"Batman\",\n      emoji: \"\ud83e\udd87\",\n      personality: \"Brilliant detective, strategic, disciplined, protective mentor\",\n      speech: \"Deep analytical voice, strategic thinking, uses detective terminology\",\n      teachingStyle: \"Explains through logic, deduction, evidence analysis, and tactical thinking\",\n      visualDetails: {\n        hair: \"Black cowl covering head with pointed bat ears\",\n        clothing: \"Dark gray suit with black cape, yellow utility belt, bat symbol on chest\",\n        features: \"White eye lenses in cowl, strong jawline\",\n        accessories: \"Utility belt with gadgets, grappling gun, batarangs\",\n        expression: \"Intense focus, slight smirk, or stern determination\",\n        build: \"Muscular, athletic build, imposing presence\",\n        skinTone: \"Not visible (covered by suit)\"\n      }\n    },\n    learner: {\n      name: \"Robin\",\n      fullName: \"Robin\",\n      emoji: \"\ud83c\udfad\",\n      personality: \"Energetic young hero, eager to learn, acrobatic, loyal\",\n      speech: \"Enthusiastic, asks insightful questions, uses exclamations like 'Holy...'\",\n      learningStyle: \"Learns through action and observation, connects concepts quickly\",\n      visualDetails: {\n        hair: \"Black domino mask showing dark hair\",\n        clothing: \"Red vest with green sleeves and pants, yellow cape, 'R' symbol on chest\",\n        features: \"Young face with domino mask, bright determined eyes\",\n        accessories: \"Utility belt, bo staff, grappling line\",\n        expression: \"Excited, concentrating, or ready for action\",\n        build: \"Lean, athletic acrobat build, shorter than Batman\",\n        gestures: \"Dynamic acrobatic poses, energetic movements\",\n        skinTone: \"Fair/peachy (visible on lower face and neck)\"\n      }\n    },\n    emojiMap: {\n      \"Batman\": \"\ud83e\udd87\",\n      \"Robin\": \"\ud83c\udfad\"\n    },\n    planningPrompt: `You are Batman, the World's Greatest Detective, tasked with creating educational comics.\nYour role: Analyze the provided topic and create a detailed comic structure plan featuring Batman (mentor) and Robin (learner).\n\n## Character Profiles\n**Batman (Mentor)**\n- Brilliant detective, strategic, disciplined, protective mentor\n- Speech: Deep analytical voice, strategic thinking, uses detective terminology\n- Teaching style: Explains through logic, deduction, evidence analysis, Batcomputer systems\n- Appearance: Dark gray suit with black cape and cowl, bat ears, yellow utility belt, white eye lenses\n\n**Robin (Learner)**\n- Energetic young hero, eager to learn, acrobatic, loyal sidekick\n- Speech: Enthusiastic, asks insightful questions, uses exclamations, calls Batman 'Batman' or 'sir'\n- Learning style: Learns through action and observation, connects concepts quickly\n- Appearance: Red vest, green sleeves and pants, yellow cape, 'R' symbol, domino mask, bo staff\n\n## Your Task\n1. **Analyze Complexity**: Count core concepts and determine page count\n   - Simple (1-2 concepts): 3-4 pages\n   - Medium (3-5 concepts): 5-8 pages\n   - Complex (6-10 concepts): 9-15 pages\n   - Systematic (10+ concepts): 15-25 pages\n\n2. **Create Comic Structure**: For each page, provide:\n   - Page number and title\n   - Scene description (location in Batcave, Gotham rooftop, Wayne Manor computer room, or crime scene)\n   - Dialogue array: Each page MUST have 2 dialogue entries showing a conversation between BOTH characters. Each dialogue entry must have:\n     * character: \"Batman\" or \"Robin\" (use these names)\n     * text: What they say - keep concise, suitable for a single comic panel\n   - Visual elements (what's shown in the panel)\n   - Technical section object:\n     * title: Title of the concept/topic\n     * content: Detailed explanation of the concept or mechanism\n   - Learning point object:\n     * title: Key learning objective\n     * content: What the learner should understand from this page\n\n3. **Dialogue Guidelines - CRITICAL: Story-Driven Learning**:\n\n**Overall Narrative Arc (Across ALL Pages):**\n\nThe complete comic should follow this journey:\n- **Act 1 (Pages 1-2)**: Establish the PROBLEM\n  - Robin encounters a real challenge or confusion about the topic\n  - Show the pain point and its impact\n  - Create emotional investment in finding the solution\n\n- **Act 2 (Pages 3-5)**: Investigate ROOT CAUSE  \n  - Batman guides Robin to analyze the underlying issue\n  - Use detective analogies to reveal hidden patterns\n  - Build understanding step-by-step\n\n- **Act 3 (Pages 6-8)**: Implement SOLUTION\n  - Introduce the solution/concept progressively\n  - Explain core concepts through dialogue\n  - Show how each piece fits together\n\n- **Act 4 (Final 1-2 pages)**: AHA MOMENT & Mastery\n  - Robin demonstrates complete understanding\n  - Success moment with visible results\n  - Reinforce key principles for retention\n\n**Individual Page Structure:**\n\nEach page serves ONE specific purpose in the story:\n- Advances the narrative thread from previous page\n- Introduces ONE new concept or insight\n- Ends with a hook that leads to the next page\n- Maintains conversational flow between Robin and Batman\n\n**Page-to-Page Continuity (MANDATORY):**\n- Page N ends with a question/observation \u2192 Page N+1 answers it\n- Reference what was learned in previous pages\n- Build knowledge cumulatively (don't repeat basics)\n- Use \"So that means...\", \"Wait, earlier you said...\", \"Now I understand why...\"\n\n**Dialogue Format (Each Page):**\n- 2 dialogue exchanges between Robin and Batman\n- Natural conversation, not lecture format\n- Robin's questions/observations drive the story forward\n- Batman's responses reveal insights using detective analogies\n\n**Character Roles:**\n\n**Robin (Drives the Story):**\n- Page 1: Expresses the problem/frustration\n- Middle pages: Asks probing questions, makes deductions\n- Final page: Demonstrates mastery, celebrates success\n- Voice: Enthusiastic, curious, uses \"Holy [X]!\" for realizations\n\n**Batman (Guides Discovery):**\n- Page 1: Acknowledges problem, proposes investigation\n- Middle pages: Reveals insights through detective analogies\n- Final page: Confirms understanding, reinforces principles  \n- Voice: Analytical, uses \"Observe...\", \"The evidence shows...\"\n\n**Forbidden Patterns:**\n- \u274c Each page solving a different problem (breaks continuity)\n- \u274c Repeating information from earlier pages\n- \u274c Page starts without connecting to previous page\n- \u274c Robin asking \"What is X?\" if X was already explained\n- \u274c Batman giving long monologues without Robin interaction\n\n**Validation Checklist:**\n- [ ] Does page 1 establish a CONCRETE problem?\n- [ ] Does each page reference or build upon previous pages?\n- [ ] Is there a clear progression from confusion \u2192 understanding?\n- [ ] Does the final page show Robin successfully applying the knowledge?\n- [ ] Would a reader want to turn to the next page to learn more?\n- [ ] Are BOTH characters present in dialogue on EVERY page?\n\n4. **Include Learning Moments**: At least one page must show:\n   - Robin making an enthusiastic but incomplete deduction\n   - Batman guiding him to complete understanding through logic\n   - Visual comparison of surface clues vs. deeper patterns\n\n5. **Use Detective/Tech Analogies**: Relate the topic to:\n   - Crime scene investigation and evidence tracking (debugging)\n   - Batcomputer database systems (data storage)\n   - Bat-signal network (communication protocols)\n   - Utility belt gadgets (modular tools/APIs)\n   - Gotham City surveillance (monitoring systems)\n   - Case file organization (data structures)\n\n6. **Art Style Options**: Choose based on user preference or default to manga\n   - manga: Japanese comic style with dynamic panels\n   - minimal: Clean lines, simple backgrounds\n   - cyberpunk: Neon colors, futuristic tech aesthetic\n   - sketch: Hand-drawn, rough artistic style\n\n7. **Character Consistency - CRITICAL**:\n   - Batman ALWAYS wears: dark gray suit, black cape and cowl, yellow utility belt\n   - Robin ALWAYS wears: red vest, green sleeves/pants, yellow cape, 'R' symbol\n   - Do NOT change costume colors or design between pages\n\n## Output Structure\nFor each page, return:\n- pageNumber (number)\n- title (string)\n- scene (string)\n- dialogue (array of objects with character and text fields - MUST include both \"Batman\" and \"Robin\")\n- visualElements (string)\n- technicalSection (object with title and content)\n- learningPoint (object with title and content)\n- topic (string)\n- artStyle (string)\n\nReturn the complete comic plan in the structured format.\n\n**VALIDATION: Before returning output, ensure EVERY page has dialogue from BOTH Robin AND Batman.**`,\n    imagePromptGuidelines: `You are an expert comic image prompt generator specializing in Batman and Robin style educational comics.\nYour task: Transform comic page data into detailed image generation prompts that capture the Dark Knight detective aesthetic while clearly explaining the concepts.\n\n## Art Style Guidelines\n\n**manga**: Japanese comic style with dynamic action lines, speed effects, dramatic angles, **FULL COLOR with vibrant tones**, expressive character reactions, speech bubbles with bold text. Color palette: dark grays, blacks, blues for Gotham atmosphere, with red, green, and yellow hero colors. Rich noir-style shading and highlights.\n\n**minimal**: Clean vector art, simple geometric shapes, limited color palette (3-4 colors), clear white backgrounds, focus on essential elements only, simple speech bubbles with sans-serif text\n\n**cyberpunk**: Neon colors (cyan, magenta, yellow), dark backgrounds, holographic interfaces, glitch effects, futuristic tech aesthetic, grid patterns, glowing speech bubbles, Bat-tech glow effects\n\n**sketch**: Hand-drawn pencil style, visible sketch lines, crosshatching for shading, rough artistic texture, paper texture background, hand-drawn speech bubbles with detective noir style.\n\n## Character Visual Details - CRITICAL CONSISTENCY\n\n**Batman** (MUST be consistent across ALL pages):\n- Black cowl with pointed bat ears covering entire head\n- **Dark gray bodysuit with black cape, black bat symbol on chest, yellow utility belt** (ALWAYS - do not change)\n- White eye lenses in cowl (no visible eyes)\n- Strong defined jawline (only visible part of face)\n- Cape flowing dramatically\n- Utility belt with pouches and gadgets\n- Grappling gun, batarangs as accessories\n- Muscular, imposing athletic build\n- **Color scheme: Dark gray suit, black cape/cowl/symbol, yellow belt** (NEVER change)\n\n**Robin** (MUST be consistent across ALL pages):\n- Black domino mask showing dark hair and eyes\n- **Red vest with 'R' symbol, green sleeves and green pants, yellow cape** (ALWAYS - do not change)\n- Young face with determined expression (lower face visible)\n- Bo staff or grappling line as accessories\n- Utility belt (smaller than Batman's)\n- Lean acrobatic athletic build, shorter than Batman\n- Dynamic acrobatic poses\n- **Color scheme: Red vest, green sleeves/pants, yellow cape, black mask** (NEVER change)\n\n## Speech Bubble Requirements - CRITICAL\n\n**MUST include visible speech bubbles with COMPLETE dialogue text from BOTH characters:**\n- Each character's speech bubble should contain their FULL dialogue text\n- Position speech bubbles clearly near each character\n- Format: Batman's bubble: \"[Batman's complete dialogue]\" - Robin's bubble: \"[Robin's complete dialogue]\"\n- Bubbles should be readable and not overlap with diagrams\n- Can include noir/detective comic styling\n- Support both English and Chinese dialogue\n\n## Concept/Diagram Integration\n\nFor each concept, include:\n- Clear visual representation (Batcomputer displays, holographic projections, evidence boards)\n- Labels and arrows showing flow and logical connections\n- Color coding with Bat-tech blue and amber highlights\n- Integration with scene (Batcave computer screens, holographic interfaces)\n- Batman pointing at or analyzing diagram\n- Crime scene/detective aesthetic when relevant\n- Ensure diagram doesn't obscure speech bubbles\n\n## Prompt Structure\n\nGenerate prompts in this format:\n\n\"[Art style] comic panel: Batman and Robin in [scene location]. Batman (dark gray suit, black cape and cowl with bat ears, yellow utility belt) [action]. Robin (red vest with 'R' symbol, green sleeves and pants, yellow cape, black domino mask) [action]. [Diagram/Visual description]. Speech bubbles visible - Batman's bubble: '[COMPLETE Batman dialogue]' - Robin's bubble: '[COMPLETE Robin dialogue]'. [Mood/lighting]. **Full color, noir atmosphere, dynamic comic book style**. High quality, detailed.\"\n\n## Critical Requirements for Output\n\nYour generated prompt MUST:\n1. Include \"[Character]'s bubble: '[COMPLETE dialogue text]'\" for BOTH Batman and Robin\n2. **ALWAYS specify Batman's costume: \"dark gray suit, black cape and cowl, yellow utility belt\"**\n3. **ALWAYS specify Robin's costume: \"red vest with 'R' symbol, green sleeves and pants, yellow cape\"**\n4. **ALWAYS mention Batman's \"white eye lenses\" and Robin's \"black domino mask\"**\n5. Place dialogue specifications clearly in the prompt\n6. **ALWAYS specify \"Full color, noir atmosphere\" for manga style**\n7. Include Bat-tech/detective elements where appropriate\n8. Never deviate from the costume color schemes\n\n**VALIDATION**: Before returning, verify:\n- Batman has \"dark gray suit, black cape/cowl, yellow utility belt\"\n- Robin has \"red vest, green sleeves/pants, yellow cape\"\n- Both characters have complete dialogue bubbles\n- \"Full color, noir\" specification is present\n- Bat-symbol or detective elements mentioned\n\nReturn ONLY the prompt text, no additional explanation.`,\n    settings: [\"Batcave with Batcomputer\", \"Gotham City rooftops\", \"Wayne Manor study\", \"Crime scene investigation\", \"Bat-tech workshop\"],\n    analogyStyle: \"Detective investigation, evidence analysis, Batcomputer systems, crime scene debugging, case file organization, Bat-signal networks\"\n  }\n};\n\n// Return the character library\nreturn [{\n  json: {\n    characterPairs: characterPairs,\n    availablePairs: Object.keys(characterPairs),\n    defaultPair: \"rick_morty\"\n  }\n}];"
      },
      "id": "d8df38bf-0bbd-4a47-9c32-bd54b8c5b652",
      "name": "Character Prompts Library",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -44368,
        -21232
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "bfa1de8f-1f93-452b-835b-e854b2db74ed",
              "name": "topic",
              "value": "={{ $('Form Data Mapper').item.json.topic }}",
              "type": "string"
            },
            {
              "id": "id-2",
              "name": "characterPair",
              "value": "={{ $('Form Data Mapper').item.json.characterPair }}",
              "type": "string"
            },
            {
              "id": "95077900-68a5-4d0c-ab9a-f4bf882494e2",
              "name": "artStyle",
              "value": "={{ $('Form Data Mapper').item.json.artStyle }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -44128,
        -21232
      ],
      "id": "08081119-e490-4c5c-bd67-a936ebfbfec4",
      "name": "User Input"
    },
    {
      "parameters": {
        "content": "# 1\ufe0f\u20e3  \u8f93\u5165\u4e0e\u914d\u7f6e\u6a21\u5757 (Input & Configuration)\n\nUser input processing and character configuration. \nThis section handles user preferences (topic, character pair, art style) \nand loads character-specific prompts from the library.",
        "height": 656,
        "width": 1040,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -44784,
        -21568
      ],
      "typeVersion": 1,
      "id": "f04c3204-67e3-46c9-ae97-f8de896ab2f7",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "# 2\ufe0f\u20e3  \u5185\u5bb9\u89c4\u5212\u6a21\u5757 (Content Planning)\n\nAI-powered comic structure planning. Analyzes topic complexity, \ngenerates page structure with dialogues, technical concepts, \nand learning points for each page.",
        "height": 656,
        "width": 1472,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -43664,
        -21568
      ],
      "id": "703c89a4-7fa8-4721-90b4-0198492c7fc6",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "# 5\ufe0f\u20e3  HTML \u751f\u6210\u6a21\u5757 (HTML Generation)\n\nAssembles individual pages into HTML format. \nGenerates dialogue sections, combines with images, \nand creates the final comic webpage.",
        "height": 800,
        "width": 896
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -41840,
        -20832
      ],
      "id": "7ca353fb-1111-49c6-b788-ae696f60c073",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "0d5f8a8a-fbc4-42dc-b483-b75fe9795839",
              "name": "planningPrompt",
              "value": "={{ $('Character Prompts Library').item.json.characterPairs[$json.characterPair].planningPrompt }}",
              "type": "string"
            },
            {
              "id": "c6d73745-f638-42c8-81f2-120730585108",
              "name": "imagePromptTemplate",
              "value": "={{ $('Character Prompts Library').item.json.characterPairs[$json.characterPair].imagePromptGuidelines }}",
              "type": "string"
            },
            {
              "id": "2b3fa52e-25a0-4d6c-84d1-702b2027700f",
              "name": "mentorName",
              "value": "={{ $('Character Prompts Library').item.json.characterPairs[$json.characterPair].mentor.fullName }}",
              "type": "string"
            },
            {
              "id": "9adb600d-547a-4e44-b349-ab68c2216d72",
              "name": "learnerName",
              "value": "={{ $('Character Prompts Library').item.json.characterPairs[$json.characterPair].learner.fullName }}",
              "type": "string"
            },
            {
              "id": "e6530227-ed78-4413-9f84-48e1d5cdd733",
              "name": "characterPairName",
              "value": "={{ $('Character Prompts Library').item.json.characterPairs[$json.characterPair].name }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -43888,
        -21232
      ],
      "id": "1af29013-67a5-4a28-83f7-b4779dc89db5",
      "name": "Extract Character Config"
    },
    {
      "parameters": {
        "jsCode": "// \u5c06HTML\u5b57\u7b26\u4e32\u8f6c\u6362\u4e3abinary\u6570\u636e\nconst htmlContent = $input.first().json.finalHTML;\nconst rawFileName = $('Page Collector').first().json.data[0].mainTitle;\n\n// \u6e05\u7406\u6587\u4ef6\u540d:\u53bb\u9664\u7279\u6b8a\u5b57\u7b26,\u66ff\u6362\u7a7a\u683c\nconst cleanFileName = rawFileName\n  .replace(/[<>:\"/\\\\|?*]/g, '')  // \u53bb\u9664Windows\u6587\u4ef6\u7cfb\u7edf\u4e0d\u5141\u8bb8\u7684\u5b57\u7b26\n  .replace(/\\s+/g, '_')           // \u5c06\u7a7a\u683c\u66ff\u6362\u4e3a\u4e0b\u5212\u7ebf\n  .replace(/[^\\x00-\\x7F]/g, '')   // \u53ef\u9009:\u53bb\u9664\u975eASCII\u5b57\u7b26(\u5982\u679c\u9700\u8981\u7684\u8bdd)\n  .trim()                          // \u53bb\u9664\u9996\u5c3e\u7a7a\u683c\n  .substring(0, 200);              // \u9650\u5236\u6587\u4ef6\u540d\u957f\u5ea6,\u907f\u514d\u8fc7\u957f\n\n// \u5982\u679c\u6e05\u7406\u540e\u6587\u4ef6\u540d\u4e3a\u7a7a,\u4f7f\u7528\u9ed8\u8ba4\u540d\u79f0\nconst finalFileName = cleanFileName || 'untitled';\n\nreturn [{\n  binary: {\n    data: {\n      data: Buffer.from(htmlContent, 'utf8').toString('base64'),\n      mimeType: 'text/html',\n      fileName: finalFileName,\n      fileExtension: 'html'\n    }\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -41120,
        -20240
      ],
      "id": "30d2d23e-8829-4e07-9894-e50f46e2dcb0",
      "name": "HTML Manager Binary"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1173326c-df2b-4fba-9022-7e98f0e4eaa4",
              "name": "cyberpunkCSS",
              "value": "<style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: 'Courier New', 'Roboto Mono', monospace;\n            background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);\n            color: #e0e7ff;\n            line-height: 1.6;\n            min-height: 100vh;\n            padding: 20px;\n        }\n\n        .container {\n            max-width: 1200px;\n            margin: 0 auto;\n        }\n\n        header {\n            text-align: center;\n            margin-bottom: 40px;\n            padding: 30px;\n            background: rgba(26, 31, 58, 0.95);\n            border-radius: 20px;\n            border: 2px solid #00f0ff;\n            box-shadow: 0 0 30px rgba(0, 240, 255, 0.3), 0 10px 30px rgba(0, 0, 0, 0.5);\n        }\n\n        h1 {\n            font-size: 3rem;\n            background: linear-gradient(90deg, #00f0ff, #ff006e, #00f0ff);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            margin-bottom: 15px;\n            text-shadow: 0 0 20px rgba(0, 240, 255, 0.8);\n            letter-spacing: 3px;\n            font-weight: 900;\n        }\n\n        .subtitle {\n            font-size: 1.1rem;\n            color: #00f0ff;\n            margin-bottom: 20px;\n            text-transform: uppercase;\n            letter-spacing: 2px;\n        }\n\n        .stats {\n            display: flex;\n            justify-content: center;\n            gap: 30px;\n            margin-top: 20px;\n            flex-wrap: wrap;\n        }\n\n        .stat-item {\n            background: rgba(0, 240, 255, 0.05);\n            padding: 15px 25px;\n            border-radius: 12px;\n            border: 1px solid #00f0ff;\n            box-shadow: 0 0 15px rgba(0, 240, 255, 0.3);\n        }\n\n        .stat-label {\n            font-size: 0.9rem;\n            color: #8b9dc3;\n            margin-bottom: 5px;\n        }\n\n        .stat-value {\n            font-size: 1.4rem;\n            font-weight: bold;\n            color: #ff006e;\n            text-shadow: 0 0 10px rgba(255, 0, 110, 0.8);\n        }\n\n        .intro-box {\n            background: rgba(0, 240, 255, 0.08);\n            border: 1px solid rgba(0, 240, 255, 0.4);\n            border-radius: 15px;\n            padding: 25px;\n            margin-bottom: 40px;\n            text-align: center;\n            box-shadow: 0 0 20px rgba(0, 240, 255, 0.2);\n        }\n\n        .intro-box h3 {\n            color: #00f0ff;\n            margin-bottom: 10px;\n            font-size: 1.4em;\n            text-shadow: 0 0 15px rgba(0, 240, 255, 0.6);\n        }\n\n        .intro-box p {\n            color: #8b9dc3;\n            font-size: 1.1em;\n            line-height: 1.6;\n        }\n\n        .page {\n            margin: 40px 0;\n            padding: 30px;\n            border-radius: 15px;\n            background: rgba(26, 31, 58, 0.8);\n            border-left: 4px solid #00f0ff;\n            box-shadow: 0 5px 30px rgba(0, 240, 255, 0.2);\n            transition: all 0.3s ease;\n            position: relative;\n        }\n\n        .page::before {\n            content: '';\n            position: absolute;\n            left: 0;\n            top: 0;\n            height: 100%;\n            width: 4px;\n            background: linear-gradient(180deg, #00f0ff, #ff006e);\n            box-shadow: 0 0 15px rgba(0, 240, 255, 0.8);\n        }\n\n        .page:hover {\n            transform: translateX(5px);\n            box-shadow: 0 8px 40px rgba(0, 240, 255, 0.4);\n        }\n\n        .page:nth-child(even) {\n            border-left-color: #ff006e;\n        }\n\n        .page:nth-child(even)::before {\n            background: linear-gradient(180deg, #ff006e, #00f0ff);\n            box-shadow: 0 0 15px rgba(255, 0, 110, 0.8);\n        }\n\n        .page-header {\n            display: flex;\n            align-items: center;\n            gap: 20px;\n            margin-bottom: 25px;\n            justify-content: space-between;\n        }\n\n        .page-number {\n            background: linear-gradient(45deg, #00f0ff, #0088ff);\n            color: white;\n            width: 50px;\n            height: 50px;\n            border-radius: 50%;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 1.4em;\n            font-weight: bold;\n            box-shadow: 0 0 25px rgba(0, 240, 255, 0.8);\n            flex-shrink: 0;\n            border: 2px solid #00f0ff;\n        }\n\n        .page:nth-child(even) .page-number {\n            background: linear-gradient(45deg, #ff006e, #d9004f);\n            box-shadow: 0 0 25px rgba(255, 0, 110, 0.8);\n            border-color: #ff006e;\n        }\n\n        .page-title {\n            color: #00f0ff;\n            font-size: 1.6em;\n            font-weight: bold;\n            text-shadow: 0 0 15px rgba(0, 240, 255, 0.6);\n        }\n\n        .page:nth-child(even) .page-title {\n            color: #ff006e;\n            text-shadow: 0 0 15px rgba(255, 0, 110, 0.6);\n        }\n\n        .page-content {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 30px;\n            align-items: flex-start;\n        }\n\n        .page-image {\n            flex: 1;\n            min-width: 300px;\n            border-radius: 12px;\n            overflow: hidden;\n            box-shadow: 0 10px 30px rgba(0, 240, 255, 0.3);\n            border: 2px solid rgba(0, 240, 255, 0.4);\n            transition: all 0.3s ease;\n        }\n\n        .page-image:hover {\n            transform: scale(1.02);\n            box-shadow: 0 15px 40px rgba(0, 240, 255, 0.5);\n        }\n\n        .page-image img {\n            width: 100%;\n            height: auto;\n            display: block;\n        }\n\n        .page-image-container {\n            flex: 1;\n            min-width: 310px;\n            padding: 20px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background: rgba(10, 14, 39, 0.9);\n        }\n\n        .page-info {\n            flex: 1;\n            padding: 20px;\n            background: rgba(10, 14, 39, 0.9);\n        }\n\n        .page-desc {\n            font-size: 1rem;\n            color: #8b9dc3;\n            margin-bottom: 20px;\n            line-height: 1.5;\n        }\n\n        .dialogue-section {\n            margin-bottom: 25px;\n        }\n\n        .section-title {\n            font-size: 1rem;\n            color: #00f0ff;\n            margin-bottom: 15px;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n        }\n\n        .dialogue-item {\n            margin-bottom: 15px;\n            padding: 12px 15px;\n            background: rgba(0, 240, 255, 0.05);\n            border-radius: 10px;\n            border-left: 3px solid #00f0ff;\n            box-shadow: 0 2px 10px rgba(0, 240, 255, 0.2);\n        }\n\n        .dialogue-item.role-learner {\n            border-left-color: #00f0ff;\n        }\n\n        .dialogue-item.role-mentor {\n            border-left-color: #ff006e;\n            background: rgba(255, 0, 110, 0.05);\n            box-shadow: 0 2px 10px rgba(255, 0, 110, 0.2);\n        }\n\n        .character {\n            font-weight: bold;\n            margin-bottom: 5px;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .character.role-learner {\n            color: #00f0ff;\n            text-shadow: 0 0 10px rgba(0, 240, 255, 0.6);\n        }\n\n        .character.role-mentor {\n            color: #ff006e;\n            text-shadow: 0 0 10px rgba(255, 0, 110, 0.6);\n        }\n\n        .character-emoji {\n            font-size: 1.2rem;\n        }\n\n        .dialogue-text {\n            color: #e0e7ff;\n            font-size: 0.95rem;\n        }\n\n        .tech-section {\n            margin-top: 20px;\n            margin-bottom: 20px;\n        }\n\n        .tech-box {\n            background: rgba(0, 0, 0, 0.5);\n            border-radius: 10px;\n            padding: 15px;\n            border: 1px solid rgba(0, 240, 255, 0.3);\n        }\n\n        .tech-label {\n            font-size: 0.9rem;\n            color: #8b9dc3;\n            margin-bottom: 8px;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n        }\n\n        .tech-content {\n            color: #00f0ff;\n            font-family: 'Courier New', monospace;\n            font-size: 0.9rem;\n            background: rgba(0, 0, 0, 0.6);\n            padding: 10px;\n            border-radius: 8px;\n            border: 1px solid rgba(0, 240, 255, 0.4);\n            box-shadow: inset 0 0 20px rgba(0, 240, 255, 0.1);\n        }\n\n        .learning-section {\n            margin-top: 20px;\n            background: rgba(0, 240, 255, 0.08);\n            border-left: 4px solid #00f0ff;\n            border-radius: 10px;\n            padding: 15px;\n        }\n\n        .learning-content {\n            color: #e0e7ff;\n            font-size: 0.95rem;\n            line-height: 1.6;\n            margin-top: 10px;\n        }\n\n        footer {\n            text-align: center;\n            margin-top: 50px;\n            padding-top: 30px;\n            border-top: 1px solid rgba(0, 240, 255, 0.2);\n            color: #8b9dc3;\n        }\n\n        .footer-honest {\n          background: linear-gradient(180deg, rgba(26, 31, 58, 0.8) 0%, rgba(10, 14, 39, 0.95) 100%);\n          border-top: 1px solid rgba(0, 240, 255, 0.3);\n          padding: 50px 20px 30px;\n          margin-top: 80px;\n        }\n        \n        .footer-container {\n          max-width: 900px;\n          margin: 0 auto;\n          display: flex;\n          flex-direction: column;\n          gap: 35px;\n        }\n        \n        .footer-brand {\n          text-align: center;\n        }\n        \n        .brand-header {\n          display: inline-flex;\n          align-items: center;\n          gap: 12px;\n          margin-bottom: 12px;\n        }\n        \n        .brand-icon {\n          font-size: 2rem;\n        }\n        \n        .brand-name {\n          font-size: 1.4rem;\n          font-weight: 600;\n          background: linear-gradient(90deg, #00f0ff, #ff006e);\n          -webkit-background-clip: text;\n          -webkit-text-fill-color: transparent;\n        }\n        \n        .brand-tagline {\n          color: #8b9dc3;\n          font-size: 1rem;\n          line-height: 1.6;\n          max-width: 600px;\n          margin: 0 auto;\n        }\n        \n        .footer-actions {\n          display: flex;\n          justify-content: center;\n          gap: 15px;\n          flex-wrap: wrap;\n        }\n        \n        .btn-primary {\n          display: inline-flex;\n          align-items: center;\n          gap: 8px;\n          background: linear-gradient(135deg, #00f0ff, #0088ff);\n          color: white;\n          padding: 14px 28px;\n          border-radius: 10px;\n          text-decoration: none;\n          font-weight: 600;\n          font-size: 0.95rem;\n          transition: all 0.3s ease;\n          box-shadow: 0 4px 15px rgba(0, 240, 255, 0.5);\n        }\n        \n        .btn-primary:hover {\n          transform: translateY(-2px);\n          box-shadow: 0 6px 20px rgba(0, 240, 255, 0.7);\n        }\n        \n        .btn-secondary {\n          display: inline-flex;\n          align-items: center;\n          color: #00f0ff;\n          padding: 14px 28px;\n          border: 1px solid rgba(0, 240, 255, 0.5);\n          border-radius: 10px;\n          text-decoration: none;\n          font-weight: 500;\n          font-size: 0.95rem;\n          transition: all 0.3s ease;\n        }\n        \n        .btn-secondary:hover {\n          border-color: #00f0ff;\n          color: #00f0ff;\n          background: rgba(0, 240, 255, 0.1);\n          box-shadow: 0 0 15px rgba(0, 240, 255, 0.3);\n        }\n        \n        .footer-legal {\n          text-align: center;\n          padding-top: 30px;\n          border-top: 1px solid rgba(0, 240, 255, 0.2);\n        }\n        \n        .legal-text {\n          display: flex;\n          flex-direction: column;\n          gap: 10px;\n        }\n        \n        .legal-text p {\n          color: #64748b;\n          font-size: 0.875rem;\n        }\n        \n        .legal-text a {\n          color: #00f0ff;\n          text-decoration: none;\n          transition: color 0.2s;\n        }\n        \n        .legal-text a:hover {\n          color: #4dd4ff;\n          text-decoration: underline;\n        }\n        \n        .disclaimer {\n          font-size: 0.8rem !important;\n          color: #475569 !important;\n          font-style: italic;\n        }\n        \n        @media (max-width: 640px) {\n          .footer-actions {\n            flex-direction: column;\n            width: 100%;\n          }\n          \n          .btn-primary,\n          .btn-secondary {\n            width: 100%;\n            justify-content: center;\n          }\n          \n          .brand-tagline {\n            font-size: 0.9rem;\n          }\n        }\n        \n        @media (max-width: 768px) {\n            .page-content {\n                flex-direction: column;\n            }\n\n            .page-image, .page-text {\n                min-width: 100%;\n            }\n\n            h1 {\n                font-size: 2em;\n            }\n\n            .page-title {\n                font-size: 1.3em;\n            }\n        }\n    </style>",
              "type": "string"
            },
            {
              "id": "988df6a7-0e58-4dc6-a759-ca9dd5d3fb57",
              "name": "minimalCSS",
              "value": "<style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            background: #ffffff;\n            background-image: \n                linear-gradient(90deg, rgba(59, 130, 246, 0.03) 1px, transparent 1px),\n                linear-gradient(rgba(59, 130, 246, 0.03) 1px, transparent 1px);\n            background-size: 20px 20px;\n            color: #1e293b;\n            line-height: 1.6;\n            min-height: 100vh;\n            padding: 20px;\n        }\n\n        .container {\n            max-width: 1200px;\n            margin: 0 auto;\n        }\n\n        header {\n            text-align: center;\n            margin-bottom: 40px;\n            padding: 30px;\n            background: #ffffff;\n            border-radius: 20px;\n            border: 2px solid #e2e8f0;\n            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n        }\n\n        h1 {\n            font-size: 2.8rem;\n            background: linear-gradient(90deg, #3b82f6, #8b5cf6);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            margin-bottom: 15px;\n            font-weight: 800;\n        }\n\n        .subtitle {\n            font-size: 1.2rem;\n            color: #64748b;\n            margin-bottom: 20px;\n        }\n\n        .stats {\n            display: flex;\n            justify-content: center;\n            gap: 30px;\n            margin-top: 20px;\n            flex-wrap: wrap;\n        }\n\n        .stat-item {\n            background: #f8fafc;\n            padding: 15px 25px;\n            border-radius: 12px;\n            border: 1px solid #e2e8f0;\n            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);\n        }\n\n        .stat-label {\n            font-size: 0.9rem;\n            color: #64748b;\n            margin-bottom: 5px;\n        }\n\n        .stat-value {\n            font-size: 1.4rem;\n            font-weight: bold;\n            color: #3b82f6;\n        }\n\n        .intro-box {\n            background: #f0f9ff;\n            border: 2px solid #bfdbfe;\n            border-radius: 15px;\n            padding: 25px;\n            margin-bottom: 40px;\n            text-align: center;\n        }\n\n        .intro-box h3 {\n            color: #1e40af;\n            margin-bottom: 10px;\n            font-size: 1.4em;\n        }\n\n        .intro-box p {\n            color: #64748b;\n            font-size: 1.1em;\n            line-height: 1.6;\n        }\n\n        .page {\n            margin: 40px 0;\n            padding: 30px;\n            border-radius: 15px;\n            background: #ffffff;\n            border-left: 4px solid #3b82f6;\n            box-shadow: 0 2px 15px rgba(0, 0, 0, 0.08);\n            transition: all 0.3s ease;\n        }\n\n        .page:hover {\n            transform: translateX(5px);\n            box-shadow: 0 4px 25px rgba(59, 130, 246, 0.15);\n        }\n\n        .page:nth-child(even) {\n            border-left-color: #8b5cf6;\n        }\n\n        .page-header {\n            display: flex;\n            align-items: center;\n            gap: 20px;\n            margin-bottom: 25px;\n            justify-content: space-between;\n        }\n\n        .page-number {\n            background: #3b82f6;\n            color: white;\n            width: 50px;\n            height: 50px;\n            border-radius: 50%;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 1.4em;\n            font-weight: bold;\n            box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);\n            flex-shrink: 0;\n        }\n\n        .page:nth-child(even) .page-number {\n            background: #8b5cf6;\n            box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3);\n        }\n\n        .page-title {\n            color: #1e40af;\n            font-size: 1.6em;\n            font-weight: bold;\n        }\n\n        .page:nth-child(even) .page-title {\n            color: #6d28d9;\n        }\n\n        .page-content {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 30px;\n            align-items: flex-start;\n        }\n\n        .page-image {\n            flex: 1;\n            min-width: 300px;\n            border-radius: 12px;\n            overflow: hidden;\n            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);\n            border: 2px solid #f1f5f9;\n            transition: all 0.3s ease;\n        }\n\n        .page-image:hover {\n            transform: scale(1.02);\n            box-shadow: 0 8px 30px rgba(59, 130, 246, 0.15);\n        }\n\n        .page-image img {\n            width: 100%;\n            height: auto;\n            display: block;\n        }\n\n        .page-image-container {\n            flex: 1;\n            min-width: 310px;\n            padding: 20px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background: #f8fafc;\n        }\n\n        .page-info {\n            flex: 1;\n            padding: 20px;\n            background: #f8fafc;\n            border-radius: 12px;\n        }\n\n        .page-desc {\n            font-size: 1rem;\n            color: #64748b;\n            margin-bottom: 20px;\n            line-height: 1.5;\n        }\n\n        .dialogue-section {\n            margin-bottom: 25px;\n        }\n\n        .section-title {\n            font-size: 1rem;\n            color: #3b82f6;\n            margin-bottom: 15px;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n            font-weight: 600;\n        }\n\n        .dialogue-item {\n            margin-bottom: 15px;\n            padding: 12px 15px;\n            background: #f8fafc;\n            border-radius: 10px;\n            border-left: 3px solid #3b82f6;\n        }\n\n        .dialogue-item.role-learner {\n            border-left-color: #3b82f6;\n            background: #eff6ff;\n        }\n\n        .dialogue-item.role-mentor {\n            border-left-color: #8b5cf6;\n            background: #faf5ff;\n        }\n\n        .character {\n            font-weight: 600;\n            margin-bottom: 5px;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .character.role-learner {\n            color: #1e40af;\n        }\n\n        .character.role-mentor {\n            color: #6d28d9;\n        }\n\n        .character-emoji {\n            font-size: 1.2rem;\n        }\n\n        .dialogue-text {\n            color: #334155;\n            font-size: 0.95rem;\n        }\n\n        .tech-section {\n            margin-top: 20px;\n            margin-bottom: 20px;\n        }\n\n        .tech-box {\n            background: #f1f5f9;\n            border-radius: 10px;\n            padding: 15px;\n        }\n\n        .tech-label {\n            font-size: 0.9rem;\n            color: #64748b;\n            margin-bottom: 8px;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n            font-weight: 600;\n        }\n\n        .tech-content {\n            color: #475569;\n            font-family: 'Courier New', monospace;\n            font-size: 0.9rem;\n            background: #ffffff;\n            padding: 10px;\n            border-radius: 8px;\n            border: 1px solid #cbd5e1;\n        }\n\n        .learning-section {\n            margin-top: 20px;\n            background: #f0fdf4;\n            border-left: 4px solid #22c55e;\n            border-radius: 10px;\n            padding: 15px;\n        }\n\n        .learning-content {\n            color: #334155;\n            font-size: 0.95rem;\n            line-height: 1.6;\n            margin-top: 10px;\n        }\n\n        footer {\n            text-align: center;\n            margin-top: 50px;\n            padding-top: 30px;\n            border-top: 1px solid #e2e8f0;\n            color: #64748b;\n        }\n\n        .footer-honest {\n          background: #f8fafc;\n          border-top: 1px solid #e2e8f0;\n          padding: 50px 20px 30px;\n          margin-top: 80px;\n        }\n        \n        .footer-container {\n          max-width: 900px;\n          margin: 0 auto;\n          display: flex;\n          flex-direction: column;\n          gap: 35px;\n        }\n        \n        .footer-brand {\n          text-align: center;\n        }\n        \n        .brand-header {\n          display: inline-flex;\n          align-items: center;\n          gap: 12px;\n          margin-bottom: 12px;\n        }\n        \n        .brand-icon {\n          font-size: 2rem;\n        }\n        \n        .brand-name {\n          font-size: 1.4rem;\n          font-weight: 600;\n          background: linear-gradient(90deg, #3b82f6, #8b5cf6);\n          -webkit-background-clip: text;\n          -webkit-text-fill-color: transparent;\n        }\n        \n        .brand-tagline {\n          color: #64748b;\n          font-size: 1rem;\n          line-height: 1.6;\n          max-width: 600px;\n          margin: 0 auto;\n        }\n        \n        .footer-actions {\n          display: flex;\n          justify-content: center;\n          gap: 15px;\n          flex-wrap: wrap;\n        }\n        \n        .btn-primary {\n          display: inline-flex;\n          align-items: center;\n          gap: 8px;\n          background: #3b82f6;\n          color: white;\n          padding: 14px 28px;\n          border-radius: 10px;\n          text-decoration: none;\n          font-weight: 600;\n          font-size: 0.95rem;\n          transition: all 0.3s ease;\n          box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);\n        }\n        \n        .btn-primary:hover {\n          transform: translateY(-2px);\n          box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);\n          background: #2563eb;\n        }\n        \n        .btn-secondary {\n          display: inline-flex;\n          align-items: center;\n          color: #3b82f6;\n          padding: 14px 28px;\n          border: 1px solid #bfdbfe;\n          border-radius: 10px;\n          text-decoration: none;\n          font-weight: 500;\n          font-size: 0.95rem;\n          transition: all 0.3s ease;\n        }\n        \n        .btn-secondary:hover {\n          border-color: #3b82f6;\n          color: #1e40af;\n          background: #eff6ff;\n        }\n        \n        .footer-legal {\n          text-align: center;\n          padding-top: 30px;\n          border-top: 1px solid #e2e8f0;\n        }\n        \n        .legal-text {\n          display: flex;\n          flex-direction: column;\n          gap: 10px;\n        }\n        \n        .legal-text p {\n          color: #64748b;\n          font-size: 0.875rem;\n        }\n        \n        .legal-text a {\n          color: #3b82f6;\n          text-decoration: none;\n          transition: color 0.2s;\n        }\n        \n        .legal-text a:hover {\n          color: #1e40af;\n          text-decoration: underline;\n        }\n        \n        .disclaimer {\n          font-size: 0.8rem !important;\n          color: #94a3b8 !important;\n          font-style: italic;\n        }\n        \n        @media (max-width: 640px) {\n          .footer-actions {\n            flex-direction: column;\n            width: 100%;\n          }\n          \n          .btn-primary,\n          .btn-secondary {\n            width: 100%;\n            justify-content: center;\n          }\n          \n          .brand-tagline {\n            font-size: 0.9rem;\n          }\n        }\n        \n        @media (max-width: 768px) {\n            .page-content {\n                flex-direction: column;\n            }\n\n            .page-image, .page-text {\n                min-width: 100%;\n            }\n\n            h1 {\n                font-size: 2em;\n            }\n\n            .page-title {\n                font-size: 1.3em;\n            }\n        }\n    </style>",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -43568,
        -21408
      ],
      "id": "327b1337-2b74-47f5-bba3-cc4c83054f67",
      "name": "theme css"
    },
    {
      "parameters": {
        "formTitle": "AI \u81ea\u52a8\u751f\u6210\u6280\u672f\u79d1\u666e\u6f2b\u753b",
        "formDescription": "\u5c06\u6666\u6da9\u7684\u6280\u672f\u6982\u5ff5\u8f6c\u5316\u4e3a\u751f\u52a8\u7684\u89c6\u89c9\u6545\u4e8b\u3002\u53ea\u9700\u9009\u62e9\u201c\u6559\u5b66\u62cd\u6863\u201d\u548c\u4e3b\u9898\uff0cAI \u5373\u53ef\u81ea\u52a8\u4e3a\u60a8\u751f\u6210\u5b8c\u6574\u7684\u79d1\u666e\u6f2b\u753b\u3002",
        "formFields": {
          "values": [
            {
              "fieldLabel": "\u9009\u62e9\u6559\u5b66\u62cd\u6863",
              "fieldType": "dropdown",
              "defaultValue": "\ud83d\udd2c Rick & Morty",
              "fieldOptions": {
                "values": [
                  {
                    "option": "\ud83d\udd2c Rick & Morty"
                  },
                  {
                    "option": "\ud83e\udd87 Batman & Robin"
                  },
                  {
                    "option": "\ud83e\udd16 Optimus Prime & Bumblebee"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "\u4f60\u60f3\u5b66\u4e60\u4ec0\u4e48\u6280\u672f\u6982\u5ff5\uff1f",
              "defaultValue": " Docker Containers",
              "requiredField": true
            }
          ]
        },
        "responseMode": "lastNode",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.2,
      "position": [
        -44752,
        -21232
      ],
      "id": "4372ea89-da7f-40c5-ad65-271b2e39283a",
      "name": "Form Input"
    },
    {
      "parameters": {
        "jsCode": "// \u65b0\u589e\u4e00\u4e2a Code \u8282\u70b9: \"Form Data Mapper\"\n// \u653e\u5728 Form Input \u548c User Input \u4e4b\u95f4\n\nconst formData = $input.first().json;\n\n// \u89d2\u8272\u6620\u5c04\nconst characterMapping = {\n  '\ud83d\udd2c Rick & Morty': 'rick_morty',\n  '\ud83e\udd87 Batman & Robin': 'batman_robin',\n  '\ud83e\udd16 Optimus Prime & Bumblebee': 'optimus_bumblebee'\n};\n\n// \u590d\u6742\u5ea6\u6620\u5c04(\u5982\u679c\u6709)\nconst complexityMapping = {\n  'Simple': 'simple',\n  'Medium': 'medium',\n  'Complex': 'complex'\n};\n\n// \u53d6\u8868\u5355\u6570\u636e\nconst displayName = formData['\u9009\u62e9\u6559\u5b66\u62cd\u6863'];\nconst topic = formData['\u4f60\u60f3\u5b66\u4e60\u4ec0\u4e48\u6280\u672f\u6982\u5ff5\uff1f'];\nconst complexity = formData['Complexity Level'] || 'medium';\n\n// \u8f6c\u6362\u4e3a\u540e\u7aef\u9700\u8981\u7684\u683c\u5f0f\nconst mappedData = {\n  characterPair: characterMapping[displayName] || 'rick_morty',\n  topic: topic,\n  complexity: complexityMapping[complexity] || 'medium',\n  artStyle: 'manga'\n};\n\nreturn [{\n  json: mappedData\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -44560,
        -21232
      ],
      "id": "d8a29478-b546-42c5-9d31-9e2512fb7fb8",
      "name": "Form Data Mapper"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "openai/gpt-4.1-mini",
          "mode": "list",
          "cachedResultName": "openai/gpt-4.1-mini"
        },
        "responsesApiEnabled": false,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        -43136,
        -21024
      ],
      "id": "9e5340f0-bad0-4ade-8ac5-603d82aa1b70",
      "name": "OpenAI Chat Model2",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"pageNumber\": {\n      \"type\": \"number\",\n      \"description\": \"Page number in the comic sequence\"\n    },\n    \"title\": {\n      \"type\": \"string\",\n      \"description\": \"Title of this comic page\"\n    },\n    \"scene\": {\n      \"type\": \"string\",\n      \"description\": \"Scene description for the comic page setting\"\n    },\n    \"dialogue\": {\n      \"type\": \"array\",\n      \"description\": \"Array of exactly 2 dialogue exchanges between the mentor and learner characters specified in the prompt\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"character\": {\n            \"type\": \"string\",\n            \"description\": \"Character name - must match one of the character names provided in the prompt (mentor or learner)\"\n          },\n          \"text\": {\n            \"type\": \"string\",\n            \"description\": \"The dialogue text\"\n          }\n        },\n        \"required\": [\"character\", \"text\"]\n      },\n      \"minItems\": 2,\n      \"maxItems\": 2\n    },\n    \"visualElements\": {\n      \"type\": \"string\",\n      \"description\": \"Visual elements and character actions to include in the scene\"\n    },\n    \"technicalSection\": {\n      \"type\": \"object\",\n      \"description\": \"Technical concept explanation section\",\n      \"properties\": {\n        \"title\": {\n          \"type\": \"string\",\n          \"description\": \"Title or heading of the technical concept being explained\"\n        },\n        \"content\": {\n          \"type\": \"string\",\n          \"description\": \"Detailed explanation of the technical concept, including diagrams, architecture, and key points\"\n        }\n      },\n      \"required\": [\"title\", \"content\"]\n    },\n    \"learningPoint\": {\n      \"type\": \"object\",\n      \"description\": \"Key learning takeaway from this page\",\n      \"properties\": {\n        \"title\": {\n          \"type\": \"string\",\n          \"description\": \"Title or heading of the main learning point\"\n        },\n        \"content\": {\n          \"type\": \"string\",\n          \"description\": \"Brief summary of what the learner should understand from this page\"\n        }\n      },\n      \"required\": [\"title\", \"content\"]\n    },\n    \"topic\": {\n      \"type\": \"string\",\n      \"description\": \"The overall technical topic being explained\"\n    },\n    \"artStyle\": {\n      \"type\": \"string\",\n      \"description\": \"Art style for the comic\",\n      \"enum\": [\"manga\", \"minimal\", \"cyberpunk\", \"sketch\"]\n    },\n    \"imagePrompt\": {\n      \"type\": \"string\",\n      \"description\": \"Complete detailed image generation prompt for creating the comic page. MUST use the exact character names and visual details provided in the prompt. Include character-specific clothing, features, and dialogue bubbles for BOTH characters.\"\n    }\n  },\n  \"required\": [\n    \"pageNumber\",\n    \"title\",\n    \"scene\",\n    \"dialogue\",\n    \"visualElements\",\n    \"technicalSection\",\n    \"learningPoint\",\n    \"topic\",\n    \"artStyle\",\n    \"imagePrompt\"\n  ]\n}",
        "autoFix": true
      },
      "id": "8ad08105-0da8-4f06-997a-0e019a70f7b4",
      "name": "Prompt Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        -41792,
        -21168
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "exhausted-condition",
              "leftValue": "={{ $json.retryExhausted }}",
              "rightValue": {},
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -42048,
        -20496
      ],
      "id": "3c974782-1690-417f-9ebe-4ea1aa7da4f4",
      "name": "Retries Exhausted?"
    },
    {
      "parameters": {
        "errorMessage": "=Service Temporarily Unavailable\n\nThe AI image generation service is currently experiencing issues and could not generate images after {{ $json.totalRetries }} attempts.\n\nPlease try again later, or consider:\n- Checking your internet connection\n- Adjusting your keyword descriptions\n- Trying at a different time when server load may be lower\n\nWe apologize for the inconvenience."
      },
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        -41792,
        -20688
      ],
      "id": "e341e66e-6ac8-4516-b610-e6100291766a",
      "name": "Max Retries Reached"
    },
    {
      "parameters": {
        "errorMessage": "Content flagged as prohibited. Please modify your keywords to avoid potentially sensitive topics including violence, adult content, weapons, or other restricted themes. Try using more educational, neutral, and academic terminology suitable for your target audience."
      },
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        -42304,
        -20736
      ],
      "id": "b035c23d-0ddb-4291-a432-a5dcaf2e26ca",
      "name": "Prohibited Content End"
    },
    {
      "parameters": {
        "errorMessage": "Image Generation Failed\n\nThe AI could not generate images for your request. This might be due to:\n- Complex or ambiguous descriptions\n- Server capacity issues\n- Content guidelines\n\nPlease try again with different keywords or at a later time."
      },
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        -41632,
        -20240
      ],
      "id": "8d753261-d454-408b-8b3d-5a4906c83204",
      "name": "Image Generation Failed"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4aba0f2f-779a-425f-bbf8-5a7f6dfa0947",
              "name": "prompt",
              "value": "={{ $json.output.imagePrompt }}",
              "type": "string"
            },
            {
              "id": "retry-init",
              "name": "retryCount",
              "value": 0,
              "type": "number"
            },
            {
              "id": "max-retries",
              "name": "maxRetries",
              "value": 3,
              "type": "number"
            },
            {
              "id": "id-4",
              "name": "pageNumber",
              "value": "={{ $json.output.pageNumber }}",
              "type": "number"
            },
            {
              "id": "id-5",
              "name": "title",
              "value": "={{ $json.output.title }}",
              "type": "string"
            },
            {
              "id": "id-8",
              "name": "dialogue",
              "value": "={{ JSON.stringify($json.output.dialogue) }}",
              "type": "array"
            },
            {
              "id": "id-9",
              "name": "technicalSection",
              "value": "={{ $json.output.technicalSection }}",
              "type": "string"
            },
            {
              "id": "id-10",
              "name": "learningPoint",
              "value": "={{ $json.output.learningPoint }}",
              "type": "string"
            },
            {
              "id": "d1fb7765-74a6-4270-9185-09e0e836bdb9",
              "name": "totalPages",
              "value": "={{ $('Split Pages into Items').item.json.output.first().json.totalPages }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -41248,
        -21424
      ],
      "id": "bb92baef-6469-4d2e-bcb0-db6a996f4c78",
      "name": "Data Prep"
    },
    {
      "parameters": {
        "amount": 3
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -42080,
        -20224
      ],
      "id": "608b1572-3e42-4bc4-8b2d-445f1d03e76f",
      "name": "Retry Delay"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Extract Image Data - \u4fee\u590d\u7248 (\u589e\u52a0 totalPages \u7ee7\u627f\u903b\u8f91)\n\nconst inputItem = $input.item;\nconst response = inputItem.json;\nconsole.log('Image Extractor - Processing response');\n\n// \ud83d\udee0\ufe0f \u5de5\u5177\u51fd\u6570: \u5c1d\u8bd5\u89e3\u6790 JSON \u5b57\u7b26\u4e32\nfunction safeParse(data) {\n    if (typeof data === 'string') {\n        try { return JSON.parse(data); } catch (e) { return data; }\n    }\n    return data;\n}\n\n// \ud83d\udfe2 [\u6838\u5fc3\u4fee\u590d] \u667a\u80fd\u94bb\u63a2\u51fd\u6570 (\u5e26\u7ee7\u627f\u80fd\u529b)\nfunction findCoreData(startObject) {\n    let current = startObject;\n    let depth = 0;\n    const maxDepth = 10; \n\n    while (current && depth < maxDepth) {\n        // 1. \ud83c\udfaf \u547d\u4e2d\u76ee\u6807\uff1a\u6570\u636e\u88ab\u5305\u88f9\u5728 'output' \u4e2d\n        if (current.output && (current.output.pageNumber !== undefined || current.output.title)) {\n            console.log(`[Data Finder] Found data inside 'output' at depth ${depth}`);\n            \n            const childData = current.output;\n            \n            // \ud83d\udd25 \u5173\u952e\u4fee\u590d\uff1a\u7ee7\u627f\u903b\u8f91 (Inheritance)\n            // \u5982\u679c\u513f\u5b50(child)\u6ca1\u6709 totalPages\uff0c\u4f46\u7236\u4eb2(current)\u6709\uff0c\u5c31\u7ee7\u627f\u8fc7\u6765\n            if (!childData.totalPages && current.totalPages) {\n                console.log(`[Data Finder] \ud83d\udc74 \u7236\u7c7b\u7ee7\u627f: \u5c06 totalPages (${current.totalPages}) \u4f20\u7ed9\u5b50\u5bf9\u8c61`);\n                childData.totalPages = current.totalPages;\n            }\n            \n            return childData;\n        }\n\n        // 2. \ud83c\udfaf \u547d\u4e2d\u76ee\u6807\uff1a\u5f53\u524d\u5c42\u5c31\u662f\u6570\u636e\n        if (current.pageNumber !== undefined || current.title) {\n             console.log(`[Data Finder] Found flattened data at depth ${depth}`);\n            return current;\n        }\n\n        // 3. \u26cf\ufe0f \u7ee7\u7eed\u6316\u6398\n        if (current.originalFormData) {\n            current = current.originalFormData;\n            depth++;\n        } else {\n            break;\n        }\n    }\n    return null;\n}\n\n// 1. \u83b7\u53d6\u6570\u636e\u6e90 (\u4f18\u5148 Task ID)\nlet taskNodeData = null;\ntry {\n  taskNodeData = $('Extract Task ID').item.json;\n} catch(e) {}\n\n// 2. \u6267\u884c\u67e5\u627e\n// \u8fd9\u91cc\u7684\u987a\u5e8f\u5f88\u91cd\u8981\uff1a\u5148\u627e Task ID\uff0c\u518d\u627e Response\nlet originalPageData = findCoreData(taskNodeData) || findCoreData(response);\n\n// 3. \u4fdd\u5e95\u9ed8\u8ba4\u503c\nif (!originalPageData) {\n  console.error(\"\u274c \u4e25\u91cd\u8b66\u544a\uff1a\u6570\u636e\u4e22\u5931\");\n  originalPageData = { pageNumber: 0, title: \"Unknown Data\", totalPages: 12 };\n}\n\n// --- \u56fe\u50cf\u63d0\u53d6\u90e8\u5206 (\u7cbe\u7b80\u7248\uff0c\u903b\u8f91\u4e0d\u53d8) ---\nconst choice = response.choices?.[0];\nlet imageData = null;\n\nif (choice && choice.message && choice.message.content) {\n    const contentArray = Array.isArray(choice.message.content) ? choice.message.content : [];\n    for (const item of contentArray) {\n        if (item.type === 'image_url' && item.image_url?.url) {\n            imageData = item.image_url.url;\n            if (imageData.startsWith('data:')) {\n                const match = imageData.match(/^data:([^;]+);base64,(.+)$/);\n                if (match) imageData = match[2];\n            }\n            break;\n        }\n    }\n}\n\n// --- \u6700\u7ec8\u8fd4\u56de ---\n\n// \ud83d\udee1\ufe0f \u5b57\u6bb5\u89e3\u6790\u4fdd\u62a4\nconst finalTechnical = safeParse(originalPageData.technicalSection);\nconst finalLearning = safeParse(originalPageData.learningPoint);\nconst finalDialogue = typeof originalPageData.dialogue === 'string' ? safeParse(originalPageData.dialogue) : originalPageData.dialogue;\n\n// \ud83d\udee1\ufe0f \u6700\u7ec8\u786e\u4fdd totalPages \u5b58\u5728\n// \u5982\u679c originalPageData \u91cc\u6709\u5c31\u7528\u5b83\u7684\uff0c\u6ca1\u6709\u5c31\u7528\u9ed8\u8ba4 6\nconst finalTotalPages = originalPageData.totalPages || 6;\n\nreturn {\n  json: {\n    base64Data: imageData,\n    imageUrl: imageData,\n    extractionSuccess: !!imageData,\n    \n    // \u2705 \u663e\u5f0f\u8f93\u51fa totalPages (\u8fd9\u662f\u805a\u5408\u5668\u6d3b\u4e0b\u53bb\u7684\u5173\u952e)\n    totalPages: finalTotalPages,\n    \n    // \u900f\u4f20\u5176\u4ed6\u5b57\u6bb5\n    pageNumber: originalPageData.pageNumber,\n    title: originalPageData.title,\n    dialogue: finalDialogue,\n    technicalSection: finalTechnical,\n    learningPoint: finalLearning,\n    topic: originalPageData.topic,\n    scene: originalPageData.scene || originalPageData.output?.scene,\n    artStyle: originalPageData.artStyle,\n    // \u8c03\u8bd5\u5b57\u6bb5\uff0c\u4f60\u53ef\u4ee5\u770b\u5230\u6700\u7ec8\u7528\u4e86\u591a\u5c11\u9875\n    _debugTotalPagesSource: originalPageData.totalPages ? \"Found in Data\" : \"Default 12\"\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -41632,
        -20416
      ],
      "id": "1905350b-c821-41b8-8586-2200cabb7ac7",
      "name": "Extract Image Data"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "33f73d6e-0438-4c4b-bf97-375980ad8eff",
              "leftValue": "={{ $json.choices[0]?.finish_reason == \"content_filter\"}}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "3ae4f0d0-2467-45aa-90be-620745776742",
              "leftValue": "={{ $json.error?.type == \"content_policy_violation\" || $json.error?.code == \"content_filter\" }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "id": "b831bb8f-e303-4e0b-aee3-71b9d93a6bd9",
              "leftValue": "={{ $json.isProhibitedContent }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -42448,
        -20576
      ],
      "id": "b6850d9a-ca08-4bc2-9b4f-320438f80fa2",
      "name": "Content Filter Check"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Dialogue Generator - \u5355\u9879\u5904\u7406\u7248\u672c (Run Once for Each Item)\n// \u9002\u914d: \u5904\u7406\u5355\u4e2a item\uff0c\u81ea\u52a8\u4fdd\u6301\u6570\u636e\u6d41\u4e0a\u4e0b\u6587\n\nconst item = $input.item.json;\n// \u26a0\ufe0f \u83b7\u53d6\u5168\u5c40\u914d\u7f6e (\u4f7f\u7528 .first() \u56e0\u4e3a\u8fd9\u4e9b\u8282\u70b9\u53ea\u8fd0\u884c\u4e00\u6b21)\nconst editFields = $('Extract Character Config').first().json;\nconst characterLib = $('Character Prompts Library').first().json;\nconst mainTitle = $('Planning Agent').first().json.output.topic;\n\n// \u83b7\u53d6\u89d2\u8272\u914d\u7f6e\nconst mentorName = editFields.mentorName;\nconst learnerName = editFields.learnerName;\n// \u6ce8\u610f\uff1a\u8fd9\u91cc\u4e5f\u8981\u7528 .first() \u83b7\u53d6\u7528\u6237\u8f93\u5165\nconst characterPair = $('User Input').first().json.characterPair || 'rick_morty';\nconst characterEmojis = characterLib.characterPairs[characterPair].emojiMap;\n\n// \u5bf9\u8bdd\u6846\u6a21\u677f\nconst dialogueItemTemplate = `            <div class=\"dialogue-item {{CLASSES}}\">\n                <div class=\"character {{CLASSES}}\">\n                    <span class=\"character-emoji\">{{EMOJI}}</span>\n                    <span>{{NAME}}</span>\n                </div>\n                <div class=\"dialogue-text\">{{CONTENT}}</div>\n            </div>`;\n\n// \u89e3\u6790 dialogue \u6570\u7ec4\n// \u5728\u524d\u9762\u7684 Extract Image Data \u8282\u70b9\u4e2d\uff0c\u6211\u4eec\u5df2\u7ecf\u628a\u539f\u59cb dialogue \u900f\u4f20\u8fc7\u6765\u4e86\n// \u6240\u4ee5\u76f4\u63a5\u4ece item \u4e2d\u8bfb\u53d6\u5373\u53ef\uff0c\u4e0d\u9700\u8981\u518d\u53bb Data Prep \u67e5\u8868\uff0c\u8fd9\u6837\u66f4\u5b89\u5168\nlet dialogueArray = [];\nif (item.dialogue) {\n  try {\n    dialogueArray = typeof item.dialogue === 'string' \n      ? JSON.parse(item.dialogue) \n      : item.dialogue;\n  } catch (e) {\n    console.log(`Failed to parse dialogue for page ${item.pageNumber}:`, e);\n    dialogueArray = [];\n  }\n}\n\n// \u751f\u6210\u5bf9\u8bdd HTML\nconst dialogueItems = dialogueArray\n  .map((dialogue, dialogueIndex) => {\n    const characterName = dialogue.character || `character${dialogueIndex + 1}`;\n    const isMentor = characterName === mentorName;\n    const isLearner = characterName === learnerName;\n    const roleClass = isMentor ? 'role-mentor' : (isLearner ? 'role-learner' : 'role-unknown');\n    const nameClass = 'char-' + characterName.toLowerCase().replace(/\\s+/g, '-');\n    const allClasses = [roleClass, nameClass].join(' ');\n    const emoji = characterEmojis[characterName] || dialogue.emoji || '\ud83d\udcac';\n    \n    return dialogueItemTemplate\n      .replace(/{{CLASSES}}/g, allClasses)\n      .replace('{{EMOJI}}', emoji)\n      .replace('{{NAME}}', characterName)\n      .replace('{{CONTENT}}', dialogue.text || dialogue.dialogue || 'No dialogue');\n  })\n  .join('\\n');\n\nconst dialogueSection = `        <div class=\"dialogue-section\">\n            <div class=\"section-title\">Dialogue</div>\n${dialogueItems}\n        </div>`;\n\n// \u2705 \u76f4\u63a5\u8fd4\u56de\u5355\u4e2a\u5bf9\u8c61\nreturn {\n  json: {\n    ...item, // \u4fdd\u7559\u6240\u6709\u4e0a\u6e38\u4f20\u4e0b\u6765\u7684\u6570\u636e (\u5305\u62ec pageNumber, imageUrl \u7b49)\n    dialogueHTML: dialogueSection,\n    dialogueCount: dialogueArray.length,\n    mainTitle: mainTitle,\n    mentorName: mentorName,\n    learnerName: learnerName\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -41456,
        -20480
      ],
      "id": "c1941245-27c0-4e3e-a30e-660f99e16c4c",
      "name": "Dialogue Generator"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Page Generator - \u5355\u9879\u5904\u7406\u7248\u672c (Run Once for Each Item)\nconst pageData = $input.item.json;\n\nfunction nl2br(text) {\n  if (!text) return '';\n  return String(text).replace(/\\n/g, '<br>');\n}\n\n// \u751f\u6210\u5b89\u5168\u6587\u4ef6\u540d\nlet safeTopic = (pageData.topic || 'technology_comic').toLowerCase();\nsafeTopic = safeTopic.replace(/[^a-z0-9]/g, '_').replace(/_+/g, '_').replace(/^_+|_+$/g, '');\n\nconst imageFileName = `${safeTopic}_page_${pageData.pageNumber}.png`;\nconst pageDesc = pageData.scene || '';\n\n// \u89e3\u6790 technicalSection\nlet technicalSection = pageData.technicalSection;\nif (typeof technicalSection === 'string' && technicalSection.trim()) {\n  try {\n    technicalSection = JSON.parse(technicalSection);\n  } catch (e) {\n    technicalSection = null;\n  }\n}\n\n// \u89e3\u6790 learningPoint\nlet learningPoint = pageData.learningPoint;\nif (typeof learningPoint === 'string' && learningPoint.trim()) {\n  try {\n    learningPoint = JSON.parse(learningPoint);\n  } catch (e) {\n    learningPoint = null;\n  }\n}\n\n// \u6784\u5efa HTML \u7247\u6bb5\nlet technicalSectionHTML = '';\nif (technicalSection?.title && technicalSection?.content) {\n  technicalSectionHTML = `\n                    <div class=\"tech-section\">\n                        <div class=\"tech-box\">\n                            <div class=\"tech-label\">${technicalSection.title}</div>\n                            <div class=\"tech-content\">${nl2br(technicalSection.content)}</div>\n                        </div>\n                    </div>`;\n}\n\nlet learningPointHTML = '';\nif (learningPoint?.title && learningPoint?.content) {\n  learningPointHTML = `\n                    <div class=\"learning-section\">\n                        <div class=\"section-title\">KEY TAKEAWAY</div>\n                        <div class=\"learning-concept\">${learningPoint.title}</div>\n                        <div class=\"learning-content\">${nl2br(learningPoint.content)}</div>\n                    </div>`;\n}\n\n// \u9875\u9762\u6a21\u677f\nconst pageTemplate = `        <div class=\"page\">\n            <div class=\"page-header\">\n                <h2 class=\"page-title\">Page {{PAGE_NUM}}\uff1a{{PAGE_TITLE}}</h2>\n                <span class=\"page-number\">{{PAGE_NUM}}</span>\n            </div>\n            <div class=\"page-content\">\n                <div class=\"page-image-container\">\n                    <img src=\"{{IMAGE_FILE}}\" alt=\"Page {{PAGE_NUM}}\" class=\"page-image\">\n                </div>\n                <div class=\"page-info\">\n                    <div class=\"page-desc\">\n                        {{PAGE_DESC}}\n                    </div>\n{{DIALOGUES}}\n{{TECHNICAL_SECTION}}\n{{LEARNING_POINT}}\n                </div>\n            </div>\n        </div>`;\n\n// \u66ff\u6362\u5360\u4f4d\u7b26\n// \u6ce8\u610f\uff1adialogueHTML \u5df2\u7ecf\u5728\u4e0a\u4e00\u4e2a\u8282\u70b9\u751f\u6210\u597d\u4e86\uff0c\u76f4\u63a5\u7528\nlet pageHTML = pageTemplate\n  .replace(/{{PAGE_NUM}}/g, String(pageData.pageNumber))\n  .replace(/{{PAGE_TITLE}}/g, pageData.title || 'Untitled')\n  .replace('{{IMAGE_FILE}}', pageData.imageUrl || imageFileName)\n  .replace('{{PAGE_DESC}}', nl2br(pageDesc))\n  .replace('{{DIALOGUES}}', pageData.dialogueHTML || '')\n  .replace('{{TECHNICAL_SECTION}}', technicalSectionHTML)\n  .replace('{{LEARNING_POINT}}', learningPointHTML);\n\n// \u2705 \u8fd4\u56de\u5355\u4e2a\u5bf9\u8c61\nreturn {\n  json: {\n    ...pageData,\n    pageHTML: pageHTML,\n    imageFileName: imageFileName,\n    safeTopic: safeTopic\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -41312,
        -20480
      ],
      "id": "da9f4ad6-27da-4d75-9750-161b669ff22d",
      "name": "Page Generator"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.atlascloud.ai/api/v1/model/generateImage",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "google/nano-banana-pro/text-to-image-developer"
            },
            {
              "name": "aspect_ratio",
              "value": "={{ $json.apiPayload.aspect_ratio }}"
            },
            {
              "name": "enable_base64_output",
              "value": "false"
            },
            {
              "name": "enable_sync_mode",
              "value": "false"
            },
            {
              "name": "output_format",
              "value": "png"
            },
            {
              "name": "prompt",
              "value": "={{ JSON.stringify($json.apiPayload.prompt) }}"
            },
            {
              "name": "resolution",
              "value": "2k"
            }
          ]
        },
        "options": {}
      },
      "id": "ce6128a1-efd9-4422-b69e-9deb7d392dd0",
      "name": "Submit Task",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -44528,
        -20416
      ],
      "retryOnFail": true,
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Extract Task ID - \u63d0\u53d6\u4efb\u52a1ID\n// \u4fee\u6539: \u9002\u914d Run Once for Each Item \u6a21\u5f0f\n\nconst response = $input.item.json;  // \u2190 \u6539\u8fd9\u91cc!\nconst taskId = response.data?.id;\nconst originalData = $('Prepare API Request').item.json;  // \u2190 \u6539\u8fd9\u91cc!\n\nreturn {  // \u2190 \u6539\u8fd9\u91cc! \u8fd4\u56de\u5355\u4e2a\u5bf9\u8c61,\u4e0d\u662f\u6570\u7ec4\n  json: {\n    taskId: taskId,\n    apiPayload: originalData.apiPayload,\n    retryCount: originalData.retryCount,\n    maxRetries: originalData.maxRetries,\n    originalFormData: originalData.originalFormData\n  }\n};"
      },
      "id": "acf7c7b0-38fd-41b2-8c6d-9ad3c2ca3a99",
      "name": "Extract Task ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -43984,
        -20576
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "id-1",
              "leftValue": "={{ $json.allCompleted }}",
              "rightValue": "completed",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "e75d898a-14d2-47aa-b24e-4c23012078f7",
      "name": "Check Task Status",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -42992,
        -20560
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const response = $input.item.json;\nconst imageUrl = response.data.outputs?.[0];\nconst originalData = $('Extract Task ID').item.json;\n\nreturn {\n  json: {\n    choices: [{\n      message: {\n        role: 'assistant',\n        content: [{\n          type: 'image_url',\n          image_url: {\n            url: imageUrl\n          }\n        }]\n      },\n      finish_reason: 'stop'\n    }],\n    hasImages: !!imageUrl,\n    retriesUsed: originalData.retryCount || 0\n  }\n};"
      },
      "id": "95442b78-b148-489a-bb3b-bb450b0ca7e2",
      "name": "Extract Image URL",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -42800,
        -20576
      ]
    },
    {
      "parameters": {
        "url": "=https://api.atlascloud.ai/api/v1/model/prediction/{{ $json.taskId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -43600,
        -20576
      ],
      "id": "c0cfe312-2baa-4076-9520-488bfcf1cf85",
      "name": "Get Task Result",
      "retryOnFail": true,
      "alwaysOutputData": true,
      "credentials": {
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "# 3\ufe0f\u20e3 \u6570\u636e\u51c6\u5907\u6a21\u5757 (Data Preparation)\n\nPrepares individual page data for processing. \nExtracts and structures page content, dialogue, \ntechnical sections, and learning points.",
        "height": 656,
        "width": 1168
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -42112,
        -21568
      ],
      "id": "f1049065-3c9e-4976-b187-0f6ee0446775",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "# 4\ufe0f\u20e3 \u56fe\u50cf\u751f\u6210\u6a21\u5757 (Image Generation)\n\nGenerates comic panel images using Nano Banana API. \nIncludes task submission, status polling, retry logic \nfor failed generations, and image extraction.",
        "height": 800,
        "width": 2880,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -44768,
        -20832
      ],
      "id": "0756f9cb-0c58-4ea4-abfc-19136f2105aa",
      "name": "Sticky Note3"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -43808,
        -20576
      ],
      "id": "d5e32878-2c27-4b05-ad06-2884c29025f6",
      "name": "Wait For Generation"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// \u91cd\u8bd5\u68c0\u67e5\u8282\u70b9 - \u68c0\u67e5\u54cd\u5e94\u5e76\u7ba1\u7406\u91cd\u8bd5\u903b\u8f91\nconst inputItem = $input.item;\nconst response = inputItem.json;\nconst originalData = $('Prepare API Request').item.json;\nconst currentRetryCount = originalData.retryCount || 0;\nconst maxRetries = originalData.maxRetries || 3;\n\nconsole.log('Retry Checker - Current retry count:', currentRetryCount);\nconsole.log('Retry Checker - Response received:', !!response);\n\n// \u68c0\u67e5\u662f\u5426\u88ab\u5185\u5bb9\u8fc7\u6ee4\u5668\u963b\u6b62\uff08OpenRouter \u683c\u5f0f\uff09\nconst isProhibitedContent = response.choices?.[0]?.finish_reason === 'content_filter' ||\n                           response.error?.type === 'content_filter' ||\n                           response.error?.code === 'content_policy_violation';\n\nconsole.log('Retry Checker - Prohibited content detected:', isProhibitedContent);\n\n// \u5982\u679c\u662f\u88ab\u7981\u5185\u5bb9\uff0c\u76f4\u63a5\u8fd4\u56de\uff0c\u4e0d\u8fdb\u884c\u91cd\u8bd5\nif (isProhibitedContent) {\n  console.log('Retry Checker - Content prohibited, skipping retry');\n  return {\n    json: {\n      ...response,\n      isProhibitedContent: true,\n      retriesUsed: currentRetryCount\n    }\n  };\n}\n\n// \u68c0\u67e5\u662f\u5426\u6709\u9519\u8bef\uff08HTTP\u9519\u8bef\u6216API\u9519\u8bef\uff09\nconst hasError = inputItem.error || response.error || !response || !response.choices;\n\n// \u68c0\u67e5\u662f\u5426\u6709\u6709\u6548\u7684\u56fe\u7247\u54cd\u5e94\uff08\u68c0\u67e5\u56fe\u7247URL\u800c\u975ebase64\u6570\u636e\uff09\nconst hasValidImages = response.choices?.some(choice => \n  choice.message?.content?.some(contentItem => \n    contentItem.type === 'image_url' && contentItem.image_url?.url && !contentItem.image_url.url.startsWith('data:')\n  )\n) || false;\n\nconsole.log('Retry Checker - Has error:', hasError);\nconsole.log('Retry Checker - Has valid images:', hasValidImages);\n\n// \u5982\u679c\u6709\u9519\u8bef\u6216\u65e0\u6709\u6548\u56fe\u7247\uff0c\u4e14\u8fd8\u53ef\u4ee5\u91cd\u8bd5\nif ((hasError || !hasValidImages) && currentRetryCount < maxRetries) {\n  console.log(`Retry attempt ${currentRetryCount + 1}/${maxRetries}`);\n  \n  return {\n    json: {\n      ...originalData.originalFormData,\n      retryCount: currentRetryCount + 1,\n      maxRetries: maxRetries,\n      needRetry: true,\n      retryReason: hasError ? 'Network/API error' : 'No valid images in response',\n      apiPayload: $('Prepare API Request').item.json.apiPayload\n    }\n  };\n}\n\n// \u5982\u679c\u8fbe\u5230\u6700\u5927\u91cd\u8bd5\u6b21\u6570\nif ((hasError || !hasValidImages) && currentRetryCount >= maxRetries) {\n  return {\n    json: {\n      ...response,\n      retryExhausted: true,\n      totalRetries: currentRetryCount,\n      error: 'Maximum retries reached. Service may be temporarily unavailable.'\n    }\n  };\n}\n\n// \u6210\u529f\u54cd\u5e94\nconsole.log('Retry Checker - Success response');\nreturn {\n  json: {\n    ...response,\n    hasImages: hasValidImages,\n    retriesUsed: currentRetryCount,\n    apiPayload: $('Prepare API Request').item.json.apiPayload\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -42624,
        -20576
      ],
      "id": "419ed03e-6d2b-453e-86a0-b92b4ee9a7d7",
      "name": "Check Retry Limit"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "id-1",
              "leftValue": "={{ $json.data?.status }}",
              "rightValue": "failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "daa0f035-60b2-405d-ae79-29c99cfa493f",
      "name": "Validate Task Success",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -43408,
        -20576
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "retry-condition",
              "leftValue": "={{ $json.needRetry }}",
              "rightValue": {},
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -42288,
        -20496
      ],
      "id": "34504685-47c5-4bc8-973b-00866b577ffc",
      "name": "Should Retry?"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "52d310bd-77db-4cae-b867-dc2b910477c4",
              "leftValue": "={{ $json.hasImages }}",
              "rightValue": {},
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -41824,
        -20400
      ],
      "id": "6235c1f6-19ef-4321-b18b-c968d643b0b7",
      "name": "Validate Image Data"
    },
    {
      "parameters": {
        "jsCode": "const aggregatedItem = $input.first().json; const pagesData = aggregatedItem.data || []; console.log('Sort Pages - Sorting', pagesData.length, 'pages'); const sortedPages = pagesData.sort((a, b) => { const pageNumA = a.pageNumber || 0; const pageNumB = b.pageNumber || 0; return pageNumA - pageNumB; }); console.log('Sort Pages - Sorted page numbers:', sortedPages.map(p => p.pageNumber).join(', ')); return [{ json: { data: sortedPages, pageCount: sortedPages.length } }];"
      },
      "id": "2af0dcdb-c1b0-4ce3-933b-8ab8906e4cf9",
      "name": "Sort Pages by Number",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -41472,
        -20240
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "openai/gpt-4.1-mini",
          "mode": "list",
          "cachedResultName": "openai/gpt-4.1-mini"
        },
        "responsesApiEnabled": false,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        -41888,
        -21024
      ],
      "id": "76112353-ef4c-4f55-8355-ada6876da5fc",
      "name": "OpenAI Chat Model3",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "google/gemini-3-flash-preview",
          "mode": "list",
          "cachedResultName": "google/gemini-3-flash-preview"
        },
        "responsesApiEnabled": false,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        -42064,
        -21168
      ],
      "id": "6e4f8b81-234d-4e07-8648-030c191b33c6",
      "name": "OpenAI Chat Model1",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "anthropic/claude-opus-4.5-20251101",
          "mode": "list",
          "cachedResultName": "anthropic/claude-opus-4.5-20251101"
        },
        "responsesApiEnabled": false,
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        -43424,
        -21184
      ],
      "id": "e98794b5-25e2-4732-8740-d8155ccd30da",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Prepare Retry Data - \u4fee\u590d\u7248\n// \u4fee\u590d: \u9488\u5bf9\u5e76\u884c\u6a21\u5f0f\uff0c\u4f7f\u7528 .item \u83b7\u53d6\u5f53\u524d\u7279\u5b9a\u4efb\u52a1\u7684\u4e0a\u4e0b\u6587\n\n// 1. \u83b7\u53d6\u5f53\u524d\u8282\u70b9\u8f93\u5165\u7684\u5355\u4e2a Item (\u5f53\u524d\u5931\u8d25\u7684\u4efb\u52a1)\nconst inputItem = $input.item;\n\n// 2. \u5173\u952e\u4fee\u590d: \u4f7f\u7528 .item \u83b7\u53d6\u5bf9\u5e94\u7684\u539f\u59cb\u6570\u636e\n// \u8fd9\u6837 n8n \u624d\u77e5\u9053\u6211\u4eec\u8981\u627e\u7684\u662f\u5f53\u524d\u8fd9\u4e2a\u4efb\u52a1\u5728 \"Extract Task ID\" \u65f6\u7684\u72b6\u6001\n// \u4e0d\u8981\u7528 .first()\uff0c\u90a3\u4f1a\u5bfc\u81f4\u6570\u636e\u9519\u4e71\nconst originalDataNode = $('Extract Task ID').item;\n\n// \u5b89\u5168\u68c0\u67e5\uff1a\u9632\u6b62\u4e0a\u6e38\u6570\u636e\u4e22\u5931\nif (!originalDataNode) {\n  throw new Error(\"Critical Error: Lost context from 'Extract Task ID'. Cannot prepare retry.\");\n}\n\nconst originalData = originalDataNode.json;\nconst currentRetryCount = originalData.retryCount || 0;\nconst maxRetries = originalData.maxRetries || 3;\n\nconsole.log(`[Retry Prep] Item Index: ${inputItem.index} | Count: ${currentRetryCount}/${maxRetries}`);\n\n// 3. \u8fd4\u56de\u5355\u4e2a\u5bf9\u8c61 (\u4e0d\u662f\u6570\u7ec4!)\n// \u56e0\u4e3a\u6a21\u5f0f\u662f Run Once for Each Item\nreturn {\n  json: {\n    // \u7ee7\u627f\u6240\u6709\u539f\u59cb\u8868\u5355\u6570\u636e (\u5305\u542b pageNumber, prompt \u7b49)\n    ...originalData.originalFormData,\n    \n    // \u66f4\u65b0\u8ba1\u6570\n    retryCount: currentRetryCount + 1,\n    maxRetries: maxRetries,\n    \n    // \u4fdd\u6301 API \u8bf7\u6c42\u4f53\n    apiPayload: originalData.apiPayload,\n    \n    // \u6807\u8bb0\u9700\u8981\u91cd\u8bd5\n    needRetry: true,\n    retryReason: 'Task generation failed (Status check returned failed)'\n  }\n};"
      },
      "id": "ffb1d34e-a35e-4adb-b62f-ac6bc796e433",
      "name": "Prepare Retry Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -43184,
        -20736
      ]
    },
    {
      "parameters": {},
      "id": "33b60865-819a-43cb-9f61-4ba7d32182b2",
      "name": "Wait Before Retry",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -42992,
        -20784
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "13c21b16-29ce-4f91-b443-ddca3d8decce",
              "leftValue": "={{ $json.code }}",
              "rightValue": 429,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            },
            {
              "id": "a8f5256f-b07f-4c6d-afa1-b10a9a21c984",
              "leftValue": "={{ $json.error.status }}",
              "rightValue": 500,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            },
            {
              "id": "0c64b8dd-bbf2-4459-9fbb-a3ac5a557d56",
              "leftValue": "={{ $json.error.status }}",
              "rightValue": 429,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            },
            {
              "id": "60f953d0-c9bd-434c-a5c2-b08b7db26221",
              "leftValue": "={{ $json.error.code }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -44368,
        -20416
      ],
      "id": "95521a5a-deb1-42a7-90ca-47a42597f86a",
      "name": "If"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -43984,
        -20288
      ],
      "id": "e23fcfa3-90ca-4835-8c20-a92c0ca0c3eb",
      "name": "Wait"
    },
    {
      "parameters": {
        "jsCode": "// Mode: Run Once for All Items\nconst allTasks = $input.all();\n\n// \u7edf\u8ba1\u5404\u79cd\u72b6\u6001\nconst completed = allTasks.filter(t => t.json.data.status === 'completed');\nconst failed = allTasks.filter(t => t.json.data.status === 'failed');\n\n// \u5173\u952e: \u603b\u6570\u51cf\u53bb\u5931\u8d25\u7684,\u53ea\u7edf\u8ba1\u6709\u6548\u4efb\u52a1\nconst validTotal = allTasks.length - failed.length;\n\n// \u5224\u65ad: \u6210\u529f\u6570 = \u6709\u6548\u603b\u6570\nconst allCompleted = completed.length === validTotal && validTotal > 0;\n\nconsole.log(\"=== Task Status ===\");\nconsole.log(`Completed: ${completed.length}`);\nconsole.log(`Failed: ${failed.length}`);\nconsole.log(`Total: ${allTasks.length}`);\nconsole.log(`Valid Total (exclude failed): ${validTotal}`);\nconsole.log(`All Completed: ${allCompleted}`);\nconsole.log(\"==================\");\n\n// \u8fd4\u56de\u6240\u6709\u4efb\u52a1(\u5305\u62ec\u5931\u8d25\u7684,\u4f46\u6807\u8bb0\u4f1a\u5ffd\u7565\u5b83\u4eec)\nreturn allTasks.map(task => ({\n  json: {\n    ...task.json,\n    allCompleted: allCompleted, // \u6210\u529f\u6570 = \u6709\u6548\u603b\u6570\n    completedCount: completed.length,\n    failedCount: failed.length,\n    validTotal: validTotal, // \u6709\u6548\u603b\u6570(\u6392\u9664\u5931\u8d25)\n    totalCount: allTasks.length // \u539f\u59cb\u603b\u6570\n  }\n}));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -43168,
        -20560
      ],
      "id": "527e8c6f-ca5a-480d-b036-496909913a5d",
      "name": "Analyze Batch Status"
    },
    {
      "parameters": {
        "jsCode": "// \ud83d\udfe2 [V4 \u7ec8\u6781\u7248] Stateful Aggregator\n// \u4e13\u4e3a\u4fee\u590d\u540e\u7684\u6570\u636e\u6d41\u8bbe\u8ba1\uff1a\u76f4\u63a5\u8bfb\u53d6 input \u4e2d\u7684 totalPages\n// \u4e0d\u518d\u9700\u8981\u56de\u5934\u67e5\u4e0a\u6e38\u8282\u70b9\uff0c\u903b\u8f91\u6700\u7a33\u3001\u6700\u5feb\n\nconst staticData = $getWorkflowStaticData('global');\nconst execId = $execution.id;\n\n// 1. \u521d\u59cb\u5316\u5f53\u524d\u6267\u884c\u7684\u5b58\u50a8\u6876\nif (!staticData[execId]) {\n  staticData[execId] = {\n    pages: [],\n    totalPages: null // \ud83d\udd12 \u9501\u5b9a\u603b\u9875\u6570\n  };\n}\n\nconst storage = staticData[execId];\nconsole.log(\"storage: \", storage)\n// 2. \u83b7\u53d6\u65b0\u5230\u8fbe\u7684\u6570\u636e items\nconst newItems = $input.all().map(item => item.json);\n\n// 3. \ud83c\udfaf \u6838\u5fc3\u903b\u8f91\uff1a\u9501\u5b9a\u603b\u9875\u6570\n// \u56e0\u4e3a\u4f60\u5728 Data Prep \u91cc\u5df2\u7ecf\u4fee\u597d\u4e86\u6570\u636e\uff0c\u73b0\u5728 input \u91cc\u4e00\u5b9a\u6709 totalPages\nif (!storage.totalPages) {\n  const itemWithTotal = newItems.find(item => item.totalPages);\n  if (itemWithTotal) {\n    storage.totalPages = itemWithTotal.totalPages;\n    console.log(`[Aggregator] \ud83c\udfaf \u9501\u5b9a\u76ee\u6807\u603b\u6570: ${storage.totalPages} (\u6765\u6e90: \u8f93\u5165\u6d41\u900f\u4f20)`);\n  }\n}\n\n// 4. \u5b58\u5165\u5185\u5b58 (\u53bb\u91cd\u4fdd\u62a4)\nfor (const item of newItems) {\n  const exists = storage.pages.find(p => p.pageNumber === item.pageNumber);\n  if (!exists) {\n    storage.pages.push(item);\n  }\n}\n\n// 5. \u83b7\u53d6\u76ee\u6807\u6570 (\u4f18\u5148\u7528\u9501\u5b9a\u7684\uff0c\u4fdd\u5e95\u9ed8\u8ba4 6)\nconst totalExpected = storage.totalPages || 6;\nconst currentCount = storage.pages.length;\n\nconsole.log(`[Aggregator] \ud83d\udcca \u8fdb\u5ea6: ${currentCount} / ${totalExpected}`);\n\n// 6. \u5224\u65ad\u662f\u5426\u96c6\u9f50\nif (currentCount >= totalExpected) {\n  console.log('\u2705 \u5168\u5458\u5230\u9f50\uff01\u6267\u884c\u805a\u5408...');\n  \n  // A. \u83b7\u53d6\u6570\u636e\n  const finalResult = [...storage.pages];\n  \n  // B. \u6392\u5e8f\n  finalResult.sort((a, b) => a.pageNumber - b.pageNumber);\n  \n  // C. \ud83e\uddf9 \u5fc5\u987b\u6e05\u7406\u5185\u5b58\n  delete staticData[execId]; \n  \n  // D. \u8f93\u51fa\u7ed3\u679c\n  return {\n    json: {\n      data: finalResult,\n      pageCount: finalResult.length,\n      status: \"completed_all_pages\"\n    }\n  };\n} else {\n  // E. \u6ca1\u9f50\uff0c\u7ee7\u7eed\u7b49\u5f85\n  return [];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -41136,
        -20480
      ],
      "id": "d7ecb5bd-69bd-4cb0-8b10-afb6520d19ad",
      "name": "Page Collector"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=# Comic Page Generation Request\n\n## Character Information (USE THESE EXACT NAMES)\nCharacter Pair: {{ $('Extract Character Config').item.json.characterPairName }}\nMentor: {{ $('Extract Character Config').item.json.mentorName }} \nLearner: {{ $('Extract Character Config').item.json.learnerName }} \n\n## Page Content\nPage {{ $json.json.pageNumber }}: {{ $json.json.title }}\n\nScene: {{ $json.json.scene }}\nDialogue(MUST use exact character names above):\n{{ $json.json.dialogue.map(d => d.character + \" says: \" + d.text).join(\"\\n\") }}\nVisual elements: {{ $json.json.visualElements }}\ntechnical Section: {{ $json.json.technicalSection.content }}\nlearning Point: {{ $json.json.learningPoint.content }}\nArt style: {{ $json.json.artStyle }}\nTopic: {{ $json.json.topic }}\n\n## CRITICAL INSTRUCTIONS FOR IMAGE PROMPT GENERATION:\n\n1. **Character Names**: Use ONLY \"{{ $('Extract Character Config').item.json.mentorName }}\" and \"{{ $('Extract Character Config').item.json.learnerName }}\" in the image prompt\n2. **Dialogue Bubbles**: Include complete dialogue text for BOTH characters as specified above\n3. **Character Consistency**: Refer to the character visual guidelines for accurate appearance details\n4. **No Substitution**: Do NOT use character names from other character pairs\n\nGenerate a complete image prompt following the imagePromptGuidelines provided in the system prompt.",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "={{ $('Extract Character Config').item.json.imagePromptTemplate }}"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.8,
      "position": [
        -41968,
        -21424
      ],
      "id": "3c72d9e8-f04e-4595-87fb-1a9beafa1002",
      "name": "Generate Image Prompts",
      "retryOnFail": true
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Code\u8282\u70b9 - \u7ec4\u88c5payload\u5e76\u4fdd\u6301\u91cd\u8bd5\u8ba1\u6570\n// \u4fee\u6539: \u9002\u914d Run Once for Each Item \u6a21\u5f0f\n\n// ========================================\n// \u5173\u952e\u6539\u52a8: \u7528 $input.item \u66ff\u4ee3 $input.first()\n// ========================================\nconst currentItem = $input.item.json;  // \u2190 \u6539\u8fd9\u91cc!\nconst retryCount = currentItem.retryCount || 0;\nconst maxRetries = currentItem.maxRetries || 3;\n\nconsole.log('Code - Processing request, retry count:', retryCount);\nconsole.log('Code - Processing page:', currentItem.pageNumber || 'unknown');\n\n// \u68c0\u67e5\u662f\u5426\u5df2\u5b58\u5728 apiPayload\uff08\u6765\u81ea\u91cd\u8bd5\u6d41\u7a0b\uff09\nif (currentItem.apiPayload) {\n  console.log('Code - Using existing apiPayload from retry flow');\n  return {\n    json: {\n      apiPayload: currentItem.apiPayload,\n      retryCount: retryCount,\n      maxRetries: maxRetries,\n      originalFormData: currentItem,\n    }\n  };\n}\n\n// \u5982\u679c\u6ca1\u6709\u73b0\u6709\u7684 apiPayload\uff0c\u5219\u6784\u5efa\u65b0\u7684\nconsole.log('Code - Building new apiPayload');\nconst prompt = currentItem.prompt;\n\n// \u6784\u5efa Google Nano Banana Pro API payload\nconst payload = {\n  prompt: String(prompt || '').trim() || '\u8bf7\u751f\u6210\u56fe\u7247',\n  aspect_ratio: '3:4',\n  resolution: '2K'\n};\n\n// ========================================\n// \u5173\u952e\u6539\u52a8: \u8fd4\u56de\u5355\u4e2a\u5bf9\u8c61,\u4e0d\u662f\u6570\u7ec4!\n// ========================================\nreturn {\n  json: {\n    apiPayload: payload,\n    retryCount: retryCount,\n    maxRetries: maxRetries,\n    originalFormData: currentItem,\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -44736,
        -20416
      ],
      "id": "1f19a32a-3da0-4646-a9f4-e28db46cfd34",
      "name": "Prepare API Request"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// \ud83d\udfe2 [Recover Payload]\n// \u529f\u80fd\uff1a\u4ece \"Prepare API Request\" \u8282\u70b9\u627e\u56de\u539f\u59cb\u6570\u636e\uff0c\u56e0\u4e3a Submit Task \u62a5\u9519\u628a\u6570\u636e\u5f04\u4e22\u4e86\n\n// 1. \u5229\u7528 .item \u673a\u5236\uff0c\u56de\u6eaf\u67e5\u627e\u5f53\u524d\u4efb\u52a1\u5728\u63d0\u4ea4\u524d\u7684\u6570\u636e\n// \u6ce8\u610f\uff1a\u786e\u4fdd\u8fd9\u91cc\u7684\u8282\u70b9\u540d\u79f0\u662f\u4f60 Submit Task \u524d\u9762\u90a3\u4e2a\u8282\u70b9\u7684\u786e\u5207\u540d\u79f0\nconst originalNode = $('Prepare API Request').item;\n\nif (!originalNode) {\n  // \u5982\u679c\u627e\u4e0d\u5230\uff08\u6781\u5c11\u89c1\uff09\uff0c\u8fd4\u56de\u5f53\u524d\u6570\u636e\u9632\u6b62\u62a5\u9519\uff0c\u4f46\u53ef\u80fd\u8fd8\u662f\u4f1a\u53d8\u6210\u9ed8\u8ba4 prompt\n  return { json: { ...$input.item.json } };\n}\n\nconst originalData = originalNode.json;\n\n// 2. \u589e\u52a0\u91cd\u8bd5\u8ba1\u6570 (\u907f\u514d\u65e0\u9650\u6b7b\u5faa\u73af)\n// \u8bfb\u53d6\u5df2\u6709\u8ba1\u6570\uff0c\u5982\u679c\u6ca1\u6709\u5219\u4e3a 0\nconst currentRetries = originalData.retryCount || 0;\n\nconsole.log(`[Network Retry] Recovering data for retry ${currentRetries + 1}`);\n\n// 3. \u8fd4\u56de\u6062\u590d\u540e\u7684\u6570\u636e\nreturn {\n  json: {\n    // \u6062\u590d\u6240\u6709\u539f\u59cb\u5b57\u6bb5 (\u5305\u542b apiPayload, prompt, pageNumber \u7b49)\n    ...originalData,\n    \n    // \u66f4\u65b0\u91cd\u8bd5\u8ba1\u6570\n    retryCount: currentRetries + 1,\n    \n    // (\u53ef\u9009) \u8bb0\u5f55\u4e00\u4e0b\u8fd9\u6b21\u62a5\u9519\u7684\u539f\u56e0\n    lastNetworkError: $input.item.json.error?.message || 'Unknown Network Error'\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -44128,
        -20352
      ],
      "id": "57f6c395-eedc-4222-9a82-99269da83e90",
      "name": "Prepare Network Retry"
    },
    {
      "parameters": {
        "content": "# \ud83c\udfa8 PixelSensei\n\n**\u4f5c\u7528**\uff1aVisual learning comics for any concept with iconic characters.\n\u8ba9 AI \u628a\u67af\u71e5\u7684\u6280\u672f\u6982\u5ff5\u53d8\u6210\u751f\u52a8\u7684\u591a\u89d2\u8272\u6f2b\u753b\u3002\n\n[\ud83d\udd25 \u5168\u7f51\u6700\u5168\u4fdd\u59c6\u7ea7n8n\u81ea\u52a8\u5316\u5b9e\u6218\u8bfe](https://rvfdqgohv5q.feishu.cn/wiki/DkU6we3HGiKgNzkYRHLcOdBMncd?fromScene=spaceOverview)\n\n\n\n\ud83d\udccb **\u57fa\u672c\u4fe1\u606f**\n- **\u5de5\u4f5c\u6d41\u540d\u79f0**\uff1aPixelSensei - AI \u6280\u672f\u6f2b\u753b\u751f\u6210\u5668 \n- **\u7248\u672c**\uff1av1.0\n- **\u6280\u672f\u6808**\uff1a\n  - **Core**\uff1aN8N\n  - **LLM**\uff1aClaude Opus 4.5 (\u5267\u672c\u89c4\u5212)\n  - **Image**\uff1aNano Banana Pro (Atlas Cloud \u5e76\u53d1\u52a0\u901f)\n  - **Frontend**\uff1aHTML/CSS \u52a8\u6001\u6e32\u67d3\n\n- **\u521b\u5efa\u8005**\uff1a\u6797\u6708\u534a\u5b50\n- **\u516c\u4f17\u53f7**\uff1a\u6797\u6708\u534a\u5b50\u7684AI\u7b14\u8bb0\n- **\u5fae\u4fe1**\uff1acloud-native-101\n\n\n\n\ud83d\udd17 **\u8d44\u6e90\u83b7\u53d6**\n- **Atlas Cloud \u4e13\u5c5e\u6ce8\u518c (\u9001$1)**\uff1a\n  https://www.atlascloud.ai?ref=LJDEMT\n\n- **\u5728\u7ebf\u4f53\u9a8c (MuleRun)**\uff1a\n  https://mulerun.com/@LunarAITalk/pixelsensei\n\n\n![PixelSensei](https://r2.cloudnative101.net/assets/images/PixelSensei.webp)",
        "height": 1456,
        "width": 736,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -45552,
        -21568
      ],
      "typeVersion": 1,
      "id": "a2f75684-d160-4216-96c9-660d6f883d62",
      "name": "Sticky Note5"
    }
  ],
  "connections": {
    "Planning Agent": {
      "main": [
        [
          {
            "node": "Extract Plan Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Comic Plan Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Planning Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Extract Plan Data": {
      "main": [
        [
          {
            "node": "Split Pages into Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Pages into Items": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Generate Image Prompts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML Assembler": {
      "main": [
        [
          {
            "node": "HTML Manager Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Character Prompts Library": {
      "main": [
        [
          {
            "node": "User Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "User Input": {
      "main": [
        [
          {
            "node": "Extract Character Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Character Config": {
      "main": [
        [
          {
            "node": "theme css",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "theme css": {
      "main": [
        [
          {
            "node": "Planning Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Form Input": {
      "main": [
        [
          {
            "node": "Form Data Mapper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Form Data Mapper": {
      "main": [
        [
          {
            "node": "Character Prompts Library",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "Comic Plan Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Prompt Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Image Prompts",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Retries Exhausted?": {
      "main": [
        [
          {
            "node": "Max Retries Reached",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Validate Image Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Prep": {
      "main": [
        [
          {
            "node": "Prepare API Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retry Delay": {
      "main": [
        [
          {
            "node": "Prepare API Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Image Data": {
      "main": [
        [
          {
            "node": "Dialogue Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Content Filter Check": {
      "main": [
        [
          {
            "node": "Prohibited Content End",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Should Retry?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dialogue Generator": {
      "main": [
        [
          {
            "node": "Page Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Page Generator": {
      "main": [
        [
          {
            "node": "Page Collector",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit Task": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Task ID": {
      "main": [
        [
          {
            "node": "Wait For Generation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Task Status": {
      "main": [
        [
          {
            "node": "Extract Image URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract Task ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Image URL": {
      "main": [
        [
          {
            "node": "Check Retry Limit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Task Result": {
      "main": [
        [
          {
            "node": "Validate Task Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait For Generation": {
      "main": [
        [
          {
            "node": "Get Task Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Retry Limit": {
      "main": [
        [
          {
            "node": "Content Filter Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Task Success": {
      "main": [
        [
          {
            "node": "Prepare Retry Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Analyze Batch Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Retry?": {
      "main": [
        [
          {
            "node": "Retry Delay",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Retries Exhausted?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Image Data": {
      "main": [
        [
          {
            "node": "Extract Image Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Image Generation Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort Pages by Number": {
      "main": [
        [
          {
            "node": "HTML Assembler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Planning Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model3": {
      "ai_languageModel": [
        [
          {
            "node": "Prompt Output Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Image Prompts",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Retry Data": {
      "main": [
        [
          {
            "node": "Wait Before Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Before Retry": {
      "main": [
        [
          {
            "node": "Prepare API Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Prepare Network Retry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract Task ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "Prepare API Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Batch Status": {
      "main": [
        [
          {
            "node": "Check Task Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Page Collector": {
      "main": [
        [
          {
            "node": "Sort Pages by Number",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Image Prompts": {
      "main": [
        [
          {
            "node": "Data Prep",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare API Request": {
      "main": [
        [
          {
            "node": "Submit Task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Network Retry": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "versionId": "f7182d7c-f7f3-4d30-86ba-a98428babd32",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "NaDdp3ofeX8HnwtK",
  "tags": []
}