{
  "name": "LINE AI Sales Bot",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "line-sales-bot",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-1",
      "name": "LINE Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        1080,
        1400
      ]
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst body = input.body || input;\nconst events = body.events || [];\nconst event = events[0] || null;\nif (!event || !event.replyToken || (event.type !== 'message' && event.type !== 'postback')) return [{ json: { replyable: false } }];\nconst message = event.message || {};\nconst text = event.type === 'message' && message.type === 'text' ? String(message.text || '').trim() : '';\nreturn [{ json: { replyable: true, replyToken: event.replyToken, lineUserId: event.source?.userId || 'unknown', chatInput: text || 'Show me the help menu' } }];"
      },
      "id": "code-parse",
      "name": "Parse LINE Event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        1400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.replyable }}",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "rightValue": true
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-1",
      "name": "Is Replyable LINE Event?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1560,
        1400
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.chatInput }}",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "Classify the customer message into exactly one action: product_search, order_track, support_ticket, or hang_to_human. Detect language as Eng or Th. Return only the structured result."
            }
          ]
        },
        "batching": {}
      },
      "id": "llm-chain",
      "name": "Classify Customer Message",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        1820,
        1300
      ]
    },
    {
      "parameters": {
        "model": "qwen3:8b",
        "options": {
          "temperature": 0,
          "keepAlive": "30m"
        }
      },
      "id": "model-1",
      "name": "Ollama Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        2020,
        1600
      ]
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"action\": { \"type\": \"string\", \"enum\": [\"product_search\", \"order_track\", \"support_ticket\", \"hang_to_human\"] },\n    \"language\": { \"type\": \"string\", \"enum\": [\"Eng\", \"Th\"] }\n  },\n  \"required\": [\"action\", \"language\"],\n  \"additionalProperties\": false\n}"
      },
      "id": "parser-1",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        2200,
        1600
      ]
    },
    {
      "parameters": {
        "jsCode": "const line = $('Parse LINE Event').first().json;\nconst result = $input.first().json.output || {};\nreturn [{ json: Object.assign({}, line, result) }];"
      },
      "id": "combine-1",
      "name": "Combine Context + Classification",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2060,
        1300
      ]
    },
    {
      "parameters": {
        "mode": "expression",
        "output": "={{ ['product_search','order_track','support_ticket','hang_to_human'].indexOf($json.action) }}",
        "looseTypeValidation": true
      },
      "id": "switch-1",
      "name": "Route by Action",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        2300,
        1300
      ]
    },
    {
      "parameters": {
        "jsCode": "const th = $json.language === 'Th';\nreturn [{ json: { ...$json, replyText: th ? '\u0e44\u0e14\u0e49\u0e40\u0e25\u0e22\u0e04\u0e23\u0e31\u0e1a \u0e2a\u0e48\u0e07\u0e0a\u0e37\u0e48\u0e2d\u0e2a\u0e34\u0e19\u0e04\u0e49\u0e32 \u0e23\u0e38\u0e48\u0e19 \u0e2b\u0e23\u0e37\u0e2d\u0e2a\u0e34\u0e48\u0e07\u0e17\u0e35\u0e48\u0e01\u0e33\u0e25\u0e31\u0e07\u0e21\u0e2d\u0e07\u0e2b\u0e32\u0e21\u0e32\u0e44\u0e14\u0e49\u0e40\u0e25\u0e22' : 'Please send the product name, model, or what you are looking for.' } }];"
      },
      "id": "reply-1",
      "name": "Product Search Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2580,
        1060
      ]
    },
    {
      "parameters": {
        "jsCode": "const th = $json.language === 'Th';\nreturn [{ json: { ...$json, replyText: th ? '\u0e01\u0e23\u0e38\u0e13\u0e32\u0e2a\u0e48\u0e07\u0e40\u0e25\u0e02\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e0b\u0e37\u0e49\u0e2d\u0e2b\u0e23\u0e37\u0e2d\u0e2b\u0e21\u0e32\u0e22\u0e40\u0e25\u0e02\u0e15\u0e34\u0e14\u0e15\u0e32\u0e21\u0e1e\u0e31\u0e2a\u0e14\u0e38' : 'Please send your order number or tracking number.' } }];"
      },
      "id": "reply-2",
      "name": "Order Tracking Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2580,
        1220
      ]
    },
    {
      "parameters": {
        "jsCode": "const th = $json.language === 'Th';\nreturn [{ json: { ...$json, replyText: th ? '\u0e22\u0e34\u0e19\u0e14\u0e35\u0e0a\u0e48\u0e27\u0e22\u0e04\u0e23\u0e31\u0e1a \u0e01\u0e23\u0e38\u0e13\u0e32\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22\u0e1b\u0e31\u0e0d\u0e2b\u0e32\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e15\u0e34\u0e21' : 'I am happy to help. Please describe your issue in more detail.' } }];"
      },
      "id": "reply-3",
      "name": "Support Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2580,
        1380
      ]
    },
    {
      "parameters": {
        "jsCode": "const th = $json.language === 'Th';\nreturn [{ json: { ...$json, replyText: th ? '\u0e17\u0e35\u0e21\u0e07\u0e32\u0e19\u0e08\u0e30\u0e40\u0e02\u0e49\u0e32\u0e21\u0e32\u0e14\u0e39\u0e41\u0e25\u0e15\u0e48\u0e2d \u0e01\u0e23\u0e38\u0e13\u0e32\u0e1d\u0e32\u0e01\u0e23\u0e32\u0e22\u0e25\u0e30\u0e40\u0e2d\u0e35\u0e22\u0e14\u0e44\u0e27\u0e49\u0e44\u0e14\u0e49\u0e40\u0e25\u0e22' : 'A human agent will assist you shortly. Please leave the details here.' } }];"
      },
      "id": "reply-4",
      "name": "Human Handoff Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2580,
        1540
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.line.me/v2/bot/message/reply",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_LINE_CHANNEL_ACCESS_TOKEN"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { replyToken: $json.replyToken, messages: [{ type: 'text', text: $json.replyText }] } }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "http-reply",
      "name": "Send LINE Reply",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        2860,
        1300
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": {
          "ok": true
        },
        "options": {
          "responseCode": 200
        }
      },
      "id": "respond-1",
      "name": "Respond to LINE",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        3120,
        1300
      ]
    }
  ],
  "connections": {
    "LINE Webhook": {
      "main": [
        [
          {
            "node": "Parse LINE Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse LINE Event": {
      "main": [
        [
          {
            "node": "Is Replyable LINE Event?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Replyable LINE Event?": {
      "main": [
        [
          {
            "node": "Classify Customer Message",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Classify Customer Message": {
      "main": [
        [
          {
            "node": "Combine Context + Classification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Classify Customer Message",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Classify Customer Message",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Combine Context + Classification": {
      "main": [
        [
          {
            "node": "Route by Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Action": {
      "main": [
        [
          {
            "node": "Product Search Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Order Tracking Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Support Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Human Handoff Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Product Search Reply": {
      "main": [
        [
          {
            "node": "Send LINE Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Order Tracking Reply": {
      "main": [
        [
          {
            "node": "Send LINE Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Support Reply": {
      "main": [
        [
          {
            "node": "Send LINE Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Human Handoff Reply": {
      "main": [
        [
          {
            "node": "Send LINE Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send LINE Reply": {
      "main": [
        [
          {
            "node": "Respond to LINE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}