8
n8n 中文网amn8n.com

Qwen3-VL-8B-Thinking旅行规划器

高级

这是一个Personal Productivity, Multimodal AI领域的自动化工作流,包含 18 个节点。主要使用 Set, Code, Gmail, Slack, Webhook 等节点。 基于Skyscanner、Booking.com和Gmail的AI优化旅行行程生成器

前置要求
  • Google 账号和 Gmail API 凭证
  • Slack Bot Token 或 Webhook URL
  • HTTP Webhook 端点(n8n 会自动生成)
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "Yi6ZHj09hQ6aoCt2",
  "meta": {
    "instanceId": "b91e510ebae4127f953fd2f5f8d40d58ca1e71c746d4500c12ae86aad04c1502",
    "templateCredsSetupCompleted": true
  },
  "name": "Qwen3-VL-8B-Thinking旅行规划器:搜索-->比较-->邮件发送最佳行程",
  "tags": [],
  "nodes": [
    {
      "id": "f3a23f51-4039-4a08-bad5-d9e1f475e8af",
      "name": "Webhook - 旅行请求",
      "type": "n8n-nodes-base.webhook",
      "position": [
        2832,
        -256
      ],
      "webhookId": "1fa59b2c-c301-41bf-8f01-6feb10be6aa8",
      "parameters": {
        "path": "travel-search",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "65e7017e-5242-4875-bf24-661fc68eb771",
      "name": "提取请求数据",
      "type": "n8n-nodes-base.set",
      "position": [
        2992,
        -256
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "destination",
              "type": "string",
              "value": "={{ $json.body.destination || 'Shanghai' }}"
            },
            {
              "id": "a2",
              "name": "departureCity",
              "type": "string",
              "value": "={{ $json.body.departureCity }}"
            },
            {
              "id": "a3",
              "name": "checkInDate",
              "type": "string",
              "value": "={{ $json.body.checkInDate }}"
            },
            {
              "id": "a4",
              "name": "checkOutDate",
              "type": "string",
              "value": "={{ $json.body.checkOutDate }}"
            },
            {
              "id": "a5",
              "name": "travelers",
              "type": "number",
              "value": "={{ $json.body.travelers || 1 }}"
            },
            {
              "id": "a6",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body.email }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "51891077-df8a-45db-b575-0585f52aa1db",
      "name": "搜索航班 - Skyscanner",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -640
      ],
      "parameters": {
        "url": "https://skyscanner-api.p.rapidapi.com/v3/flights/live/search/create",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "={{ {\"market\":\"US\",\"locale\":\"en-US\",\"currency\":\"USD\",\"queryLegs\":[{\"originPlace\":{\"queryPlace\":{\"iata\":$json.departureCity}},\"destinationPlace\":{\"queryPlace\":{\"iata\":$json.destination}},\"date\":{\"year\":$json.checkInDate.split('-')[0],\"month\":$json.checkInDate.split('-')[1],\"day\":$json.checkInDate.split('-')[2]}}],\"adults\":$json.travelers,\"cabinClass\":\"CABIN_CLASS_ECONOMY\"} }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "X-RapidAPI-Key",
              "value": "={{ $credentials.rapidApiKey }}"
            },
            {
              "name": "X-RapidAPI-Host",
              "value": "skyscanner-api.p.rapidapi.com"
            }
          ]
        },
        "nodeCredentialType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "953e3d0b-528d-41ba-b7ba-4fc7363c703f",
      "name": "搜索酒店 - Booking.com",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -448
      ],
      "parameters": {
        "url": "https://booking-com.p.rapidapi.com/v1/hotels/search",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "dest_type",
              "value": "city"
            },
            {
              "name": "dest_id",
              "value": "={{ $('Extract Request Data').item.json.destination }}"
            },
            {
              "name": "checkin_date",
              "value": "={{ $('Extract Request Data').item.json.checkInDate }}"
            },
            {
              "name": "checkout_date",
              "value": "={{ $('Extract Request Data').item.json.checkOutDate }}"
            },
            {
              "name": "adults_number",
              "value": "={{ $('Extract Request Data').item.json.travelers }}"
            },
            {
              "name": "order_by",
              "value": "price"
            },
            {
              "name": "units",
              "value": "metric"
            },
            {
              "name": "room_number",
              "value": "1"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "X-RapidAPI-Key",
              "value": "={{ $credentials.rapidApiKey }}"
            },
            {
              "name": "X-RapidAPI-Host",
              "value": "booking-com.p.rapidapi.com"
            }
          ]
        },
        "nodeCredentialType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "133388c8-6fe6-42cb-94b7-f76bf0cb4b2e",
      "name": "通过Gmail发送邮件",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4480,
        64
      ],
      "webhookId": "10b4febd-7ca2-41c6-866c-979cc9592dbb",
      "parameters": {
        "sendTo": "={{ $('Webhook - Travel Request').item.json.body.email }}",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "🤖 AI-Optimized Travel Itinerary: {{ $('Extract Request Data').item.json.destination }} Trip"
      },
      "typeVersion": 2.1
    },
    {
      "id": "5fcf6271-c0fd-430b-858e-4a462e98b648",
      "name": "响应Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        4784,
        64
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ { \"success\": true, \"message\": \"AI-optimized travel itinerary sent successfully!\", \"itinerariesCount\": $('AI Score & Recommendations').all().length, \"email\": $('Extract Request Data').item.json.email, \"bestDeal\": { \"price\": $('AI Score & Recommendations').first().json.totalPrice, \"aiScore\": $('AI Score & Recommendations').first().json.aiScore } } }}"
      },
      "typeVersion": 1
    },
    {
      "id": "2aedf5b8-536b-44bc-9b6d-3b80daa2c6c0",
      "name": "AI代理 - 行程优化器",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3808,
        -496
      ],
      "parameters": {
        "options": {
          "systemMessage": "You are a travel expert. Analyze the provided flight and hotel combinations and provide a detailed recommendation score (0-100) for each itinerary based on:\n1. Total price value\n2. Flight convenience (stops, duration, departure times)\n3. Hotel quality (rating, reviews, location)\n4. Overall trip experience\n\nFor each itinerary, return:\n- score (0-100)\n- reasoning (2-3 sentences)\n- highlights (bullet points)\n- warnings (if any)\n\nFormat as JSON array."
        }
      },
      "typeVersion": 1
    },
    {
      "id": "759f481f-598a-4bff-a3d1-3cb3310ecb88",
      "name": "搜索替代航班 - Kiwi",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -256
      ],
      "parameters": {
        "url": "https://api.tequila.kiwi.com/v2/search",
        "options": {},
        "authentication": "predefinedCredentialType"
      },
      "typeVersion": 1
    },
    {
      "id": "63c46681-a628-4bd8-aab0-a226ab57e703",
      "name": "获取天气预报",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        -64
      ],
      "parameters": {
        "url": "https://api.openweathermap.org/data/2.5/forecast",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "15404c5f-d821-4455-abf6-a08db26baee3",
      "name": "搜索本地活动 - Viator",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3216,
        128
      ],
      "parameters": {
        "url": "https://viator-api.p.rapidapi.com/search",
        "options": {},
        "authentication": "predefinedCredentialType"
      },
      "typeVersion": 1
    },
    {
      "id": "27b5a21e-02a6-43e2-b1b9-a7bd5074dd29",
      "name": "合并所有数据源",
      "type": "n8n-nodes-base.code",
      "position": [
        3440,
        -256
      ],
      "parameters": {
        "jsCode": "const skyscannerFlights = $input.item(0).json;\nconst kiwiFlights = $input.item(1).json;\nconst bookingHotels = $input.item(2).json;\nconst weather = $input.item(3).json;\nconst activities = $input.item(4).json;\nconst requestData = $('Extract Request Data').first().json;\n\nconst allFlightData = {\n  skyscanner: skyscannerFlights,\n  kiwi: kiwiFlights,\n  weather: weather,\n  activities: activities\n};\n\nconst hotelData = bookingHotels;\n\nreturn [{\n  json: {\n    flights: allFlightData,\n    hotels: hotelData,\n    request: requestData\n  }\n}];"
      },
      "typeVersion": 1
    },
    {
      "id": "d460fa9d-9991-4f29-befc-caff18a9cf38",
      "name": "增强型行程构建器",
      "type": "n8n-nodes-base.code",
      "position": [
        3616,
        -256
      ],
      "parameters": {
        "jsCode": "const mergedData = $input.first().json;\nconst requestData = mergedData.request;\n\n// Parse Skyscanner flights\nconst skyscannerFlights = [];\nif (mergedData.flights.skyscanner.itineraries?.results) {\n  mergedData.flights.skyscanner.itineraries.results.slice(0, 8).forEach((flight, i) => {\n    skyscannerFlights.push({\n      id: `sky_${i}`,\n      source: 'Skyscanner',\n      airline: flight.legs[0]?.carriers?.marketing[0]?.name || 'Unknown',\n      departureTime: flight.legs[0]?.departure || '',\n      arrivalTime: flight.legs[0]?.arrival || '',\n      duration: flight.legs[0]?.duration || 0,\n      stops: flight.legs[0]?.stopCount || 0,\n      price: flight.pricing?.price?.amount || 0,\n      currency: flight.pricing?.price?.currency || 'USD',\n      bookingLink: flight.deeplink || '#'\n    });\n  });\n}\n\n// Parse Kiwi flights\nconst kiwiFlights = [];\nif (mergedData.flights.kiwi.data) {\n  mergedData.flights.kiwi.data.slice(0, 8).forEach((flight, i) => {\n    kiwiFlights.push({\n      id: `kiwi_${i}`,\n      source: 'Kiwi.com',\n      airline: flight.airlines?.[0] || 'Unknown',\n      departureTime: new Date(flight.local_departure).toISOString(),\n      arrivalTime: new Date(flight.local_arrival).toISOString(),\n      duration: flight.duration?.total || 0,\n      stops: (flight.route?.length || 1) - 1,\n      price: flight.price || 0,\n      currency: flight.currency || 'USD',\n      bookingLink: flight.deep_link || '#'\n    });\n  });\n}\n\nconst allFlights = [...skyscannerFlights, ...kiwiFlights].sort((a,b) => a.price - b.price);\n\n// Parse hotels\nconst hotels = [];\nif (mergedData.hotels.result) {\n  mergedData.hotels.result.slice(0, 10).forEach((hotel, i) => {\n    hotels.push({\n      id: `hotel_${i}`,\n      name: hotel.hotel_name || 'Unknown',\n      address: hotel.address || '',\n      rating: hotel.review_score || 0,\n      reviewCount: hotel.review_nr || 0,\n      price: hotel.min_total_price || 0,\n      pricePerNight: hotel.price_breakdown?.gross_price || 0,\n      currency: hotel.currency_code || 'USD',\n      amenities: hotel.unit_configuration_label || '',\n      distanceToCenter: hotel.distance || '',\n      bookingLink: hotel.url || '#'\n    });\n  });\n}\n\n// Weather summary\nlet weatherSummary = 'No weather data';\nif (mergedData.flights.weather?.list) {\n  const temps = mergedData.flights.weather.list.slice(0, 8).map(w => w.main.temp);\n  const avgTemp = (temps.reduce((a,b)=>a+b,0)/temps.length).toFixed(1);\n  const conditions = mergedData.flights.weather.list[0]?.weather[0]?.description || 'N/A';\n  weatherSummary = `${avgTemp}°C, ${conditions}`;\n}\n\n// Activities\nconst activities = [];\nif (mergedData.flights.activities?.data) {\n  mergedData.flights.activities.data.slice(0, 10).forEach((act, i) => {\n    activities.push({\n      id: `act_${i}`,\n      name: act.title || 'Activity',\n      description: act.description || '',\n      rating: act.rating || 0,\n      reviewCount: act.reviewCount || 0,\n      price: act.price?.amount || 0,\n      currency: act.price?.currency || 'USD',\n      duration: act.duration || '',\n      bookingLink: act.productUrl || '#'\n    });\n  });\n}\n\n// Create enhanced itineraries (top 8)\nconst itineraries = [];\nfor (let f = 0; f < Math.min(4, allFlights.length); f++) {\n  for (let h = 0; h < Math.min(2, hotels.length); h++) {\n    if (itineraries.length >= 8) break;\n    \n    const flight = allFlights[f];\n    const hotel = hotels[h];\n    const selectedActivities = activities.slice(0, 3);\n    \n    const totalPrice = flight.price + hotel.price + selectedActivities.reduce((sum, act) => sum + act.price, 0);\n    \n    itineraries.push({\n      id: `itinerary_${itineraries.length + 1}`,\n      flight: flight,\n      hotel: hotel,\n      activities: selectedActivities,\n      weather: weatherSummary,\n      totalPrice: totalPrice,\n      currency: flight.currency,\n      destination: requestData.destination,\n      departureCity: requestData.departureCity,\n      checkInDate: requestData.checkInDate,\n      checkOutDate: requestData.checkOutDate,\n      travelers: requestData.travelers\n    });\n  }\n  if (itineraries.length >= 8) break;\n}\n\nitineraries.sort((a, b) => a.totalPrice - b.totalPrice);\n\nreturn itineraries.slice(0, 8).map(it => ({ json: it }));"
      },
      "typeVersion": 1
    },
    {
      "id": "33047f44-9726-4f2f-a029-c7e71c8aceb8",
      "name": "AI评分与推荐",
      "type": "n8n-nodes-base.code",
      "position": [
        4128,
        64
      ],
      "parameters": {
        "jsCode": "const itineraries = $input.all().map(item => item.json);\nconst aiAnalysis = $('AI Agent - Itinerary Optimizer').first().json;\n\nlet aiScores = [];\ntry {\n  aiScores = typeof aiAnalysis.output === 'string' ? JSON.parse(aiAnalysis.output) : aiAnalysis.output;\n} catch (e) {\n  aiScores = itineraries.map((_, i) => ({\n    score: 85 - i * 5,\n    reasoning: 'AI analysis pending',\n    highlights: ['Competitive pricing', 'Good availability'],\n    warnings: []\n  }));\n}\n\nconst enrichedItineraries = itineraries.map((itinerary, index) => {\n  const aiData = aiScores[index] || { score: 75, reasoning: 'Standard option', highlights: [], warnings: [] };\n  return {\n    ...itinerary,\n    aiScore: aiData.score || 75,\n    aiReasoning: aiData.reasoning || 'Good option',\n    aiHighlights: aiData.highlights || [],\n    aiWarnings: aiData.warnings || []\n  };\n});\n\nenrichedItineraries.sort((a, b) => b.aiScore - a.aiScore);\n\nreturn enrichedItineraries.map(it => ({ json: it }));"
      },
      "typeVersion": 1
    },
    {
      "id": "015b10c8-ad0a-4f34-8855-e1366cde7658",
      "name": "生成高级HTML邮件",
      "type": "n8n-nodes-base.code",
      "position": [
        4288,
        64
      ],
      "parameters": {
        "jsCode": "const itineraries = $input.all().map(item => item.json);\n\nif (itineraries.length === 0) {\n  return [{ json: { html: '<html><body><h2>No Results</h2></body></html>' } }];\n}\n\nconst first = itineraries[0];\nlet html = `\n<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style>\n  body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #1a1a1a; max-width: 1000px; margin: 0 auto; padding: 20px; background: #f8f9fa; }\n  .container { background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }\n  .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px 30px; text-align: center; }\n  .header h1 { margin: 0; font-size: 32px; font-weight: 700; }\n  .summary { background: #f0f4ff; padding: 25px 30px; border-left: 5px solid #667eea; margin: 20px; }\n  .summary strong { color: #667eea; }\n  .itinerary { border: 2px solid #e0e0e0; margin: 20px; border-radius: 10px; overflow: hidden; transition: transform 0.2s; }\n  .itinerary:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.15); }\n  .best-deal { border-color: #ffd700; background: linear-gradient(to right, #fff9e6, #ffffff); }\n  .best-deal .itinerary-header { background: linear-gradient(135deg, #ffd700, #ffed4e); }\n  .itinerary-header { background: #667eea; color: white; padding: 20px; display: flex; justify-content: space-between; align-items: center; }\n  .ai-score { background: white; color: #667eea; padding: 8px 20px; border-radius: 20px; font-weight: bold; font-size: 18px; }\n  .section { padding: 25px; }\n  .section-title { color: #667eea; font-size: 20px; font-weight: 600; margin-bottom: 15px; border-bottom: 2px solid #e0e0e0; padding-bottom: 8px; }\n  .detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 15px 0; }\n  .detail-item { padding: 12px; background: #f8f9fa; border-radius: 6px; }\n  .detail-label { color: #666; font-size: 13px; margin-bottom: 5px; }\n  .detail-value { color: #1a1a1a; font-weight: 600; font-size: 15px; }\n  .activities-list { list-style: none; padding: 0; }\n  .activities-list li { padding: 12px; margin: 8px 0; background: #f0f4ff; border-left: 4px solid #667eea; border-radius: 4px; }\n  .ai-insights { background: linear-gradient(135deg, #e0f7fa, #f0f4ff); padding: 20px; border-radius: 8px; margin: 15px 0; }\n  .highlight { color: #2e7d32; }\n  .warning { color: #d32f2f; }\n  .price-box { text-align: center; background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 25px; border-radius: 8px; margin: 20px 0; }\n  .price { font-size: 42px; font-weight: 700; }\n  .button { display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; text-decoration: none; border-radius: 25px; margin: 8px; font-weight: 600; transition: all 0.3s; }\n  .button:hover { transform: scale(1.05); box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); }\n  .comparison-table { width: 100%; border-collapse: collapse; margin: 20px 0; }\n  .comparison-table th { background: #667eea; color: white; padding: 15px; text-align: left; font-weight: 600; }\n  .comparison-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }\n  .comparison-table tr:hover { background: #f8f9fa; }\n  .badge { display: inline-block; padding: 5px 12px; background: #ffd700; color: #1a1a1a; border-radius: 15px; font-size: 12px; font-weight: 700; margin-left: 10px; }\n  .weather-box { background: linear-gradient(135deg, #4facfe, #00f2fe); color: white; padding: 15px; border-radius: 8px; text-align: center; margin: 15px 0; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n  <div class=\"header\">\n    <h1>✈️ Your AI-Optimized Travel Itineraries</h1>\n    <p style=\"margin: 10px 0 0 0; font-size: 18px; opacity: 0.9;\">Powered by Advanced AI Analysis</p>\n  </div>\n\n  <div class=\"summary\">\n    <strong>📍 Route:</strong> ${first.departureCity} → ${first.destination}<br>\n    <strong>📅 Dates:</strong> ${first.checkInDate} to ${first.checkOutDate}<br>\n    <strong>👥 Travelers:</strong> ${first.travelers}<br>\n    <strong>🌤️ Weather:</strong> ${first.weather}\n  </div>\n\n  <div style=\"padding: 30px;\">\n    <h2 style=\"color: #667eea; font-size: 28px; margin-bottom: 20px;\">🏆 Top ${itineraries.length} AI-Ranked Itineraries</h2>\n`;\n\nitineraries.forEach((it, index) => {\n  const isBest = index === 0;\n  html += `\n    <div class=\"itinerary ${isBest ? 'best-deal' : ''}\">\n      <div class=\"itinerary-header\" ${isBest ? 'style=\"background: linear-gradient(135deg, #ffd700, #ffed4e); color: #1a1a1a;\"' : ''}>\n        <div>\n          <h3 style=\"margin: 0; font-size: 24px;\">Option ${index + 1} ${isBest ? '<span class=\"badge\">BEST VALUE</span>' : ''}</h3>\n          <p style=\"margin: 5px 0 0 0; opacity: 0.8;\">AI-Optimized Package</p>\n        </div>\n        <div class=\"ai-score\">🤖 ${it.aiScore}/100</div>\n      </div>\n\n      <div class=\"section\">\n        <div class=\"ai-insights\">\n          <strong style=\"color: #667eea; font-size: 16px;\">🧠 AI Analysis:</strong>\n          <p style=\"margin: 10px 0;\">${it.aiReasoning}</p>\n          ${it.aiHighlights.length > 0 ? `<div class=\"highlight\">✓ ${it.aiHighlights.join(' • ')}</div>` : ''}\n          ${it.aiWarnings.length > 0 ? `<div class=\"warning\">⚠ ${it.aiWarnings.join(' • ')}</div>` : ''}\n        </div>\n\n        <div class=\"section-title\">✈️ Flight Details</div>\n        <div class=\"detail-grid\">\n          <div class=\"detail-item\"><div class=\"detail-label\">Source</div><div class=\"detail-value\">${it.flight.source}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Airline</div><div class=\"detail-value\">${it.flight.airline}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Departure</div><div class=\"detail-value\">${it.flight.departureTime}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Arrival</div><div class=\"detail-value\">${it.flight.arrivalTime}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Stops</div><div class=\"detail-value\">${it.flight.stops === 0 ? 'Non-stop ⭐' : it.flight.stops + ' stop(s)'}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Flight Price</div><div class=\"detail-value\">${it.currency} $${it.flight.price.toFixed(2)}</div></div>\n        </div>\n\n        <div class=\"section-title\">🏨 Hotel Details</div>\n        <div class=\"detail-grid\">\n          <div class=\"detail-item\"><div class=\"detail-label\">Hotel Name</div><div class=\"detail-value\">${it.hotel.name}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Rating</div><div class=\"detail-value\">⭐ ${it.hotel.rating}/10 (${it.hotel.reviewCount} reviews)</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Location</div><div class=\"detail-value\">${it.hotel.distanceToCenter || 'City Center'}</div></div>\n          <div class=\"detail-item\"><div class=\"detail-label\">Hotel Price</div><div class=\"detail-value\">${it.currency} $${it.hotel.price.toFixed(2)}</div></div>\n        </div>\n\n        ${it.activities.length > 0 ? `\n        <div class=\"section-title\">🎯 Recommended Activities</div>\n        <ul class=\"activities-list\">\n          ${it.activities.map(act => `\n            <li>\n              <strong>${act.name}</strong> ${act.rating > 0 ? `(${act.rating}⭐)` : ''}<br>\n              <span style=\"color: #666; font-size: 13px;\">${act.description.substring(0, 100)}...</span><br>\n              <strong style=\"color: #667eea;\">${act.currency} $${act.price.toFixed(2)}</strong> • ${act.duration}\n            </li>\n          `).join('')}\n        </ul>\n        ` : ''}\n\n        <div class=\"weather-box\">\n          <strong>🌤️ Weather Forecast:</strong> ${it.weather}\n        </div>\n\n        <div class=\"price-box\">\n          <div style=\"font-size: 16px; margin-bottom: 5px;\">TOTAL PACKAGE PRICE</div>\n          <div class=\"price\">${it.currency} $${it.totalPrice.toFixed(2)}</div>\n          <div style=\"margin-top: 20px;\">\n            <a href=\"${it.flight.bookingLink}\" class=\"button\">Book Flight</a>\n            <a href=\"${it.hotel.bookingLink}\" class=\"button\">Book Hotel</a>\n          </div>\n        </div>\n      </div>\n    </div>\n  `;\n});\n\nhtml += `\n    <div style=\"margin-top: 40px;\">\n      <h2 style=\"color: #667eea; font-size: 24px;\">📊 Price Comparison Matrix</h2>\n      <table class=\"comparison-table\">\n        <tr>\n          <th>Option</th>\n          <th>AI Score</th>\n          <th>Source</th>\n          <th>Flight</th>\n          <th>Hotel</th>\n          <th>Activities</th>\n          <th>Total</th>\n        </tr>\n`;\n\nitineraries.forEach((it, i) => {\n  const actPrice = it.activities.reduce((sum, a) => sum + a.price, 0);\n  html += `\n    <tr ${i === 0 ? 'style=\"background: #fff9e6; font-weight: bold;\"' : ''}>\n      <td>Option ${i + 1} ${i === 0 ? '<span class=\"badge\">BEST</span>' : ''}</td>\n      <td>${it.aiScore}/100</td>\n      <td>${it.flight.source}</td>\n      <td>$${it.flight.price.toFixed(2)}</td>\n      <td>$${it.hotel.price.toFixed(2)}</td>\n      <td>$${actPrice.toFixed(2)}</td>\n      <td style=\"color: #667eea; font-size: 18px; font-weight: bold;\">$${it.totalPrice.toFixed(2)}</td>\n    </tr>\n  `;\n});\n\nhtml += `\n      </table>\n    </div>\n\n    <div style=\"margin-top: 40px; padding: 25px; background: linear-gradient(135deg, #f0f4ff, #e0f7fa); border-radius: 10px;\">\n      <h3 style=\"color: #667eea; margin-top: 0;\">💡 AI Travel Tips</h3>\n      <ul style=\"line-height: 1.8;\">\n        <li>Book flights and hotels separately for maximum flexibility</li>\n        <li>Weather data is forecast-based; pack accordingly</li>\n        <li>Activities can be booked on-site or in advance</li>\n        <li>Prices are dynamic and subject to change</li>\n        <li>Consider travel insurance for peace of mind</li>\n      </ul>\n    </div>\n  </div>\n</div>\n\n<div style=\"text-align: center; margin-top: 30px; padding: 20px; color: #666; font-size: 13px;\">\n  <p>🤖 This itinerary was generated by AI-powered n8n workflow automation<br>\n  Prices and availability are subject to change • Book early for best rates</p>\n</div>\n</body>\n</html>\n`;\n\nreturn [{ json: { html, itineraries } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "0551345f-ca44-4adc-8f49-5ddf522f8a74",
      "name": "发送Slack通知",
      "type": "n8n-nodes-base.slack",
      "position": [
        4656,
        256
      ],
      "parameters": {
        "text": "🎉 New travel itinerary sent!\\n✈️ Route: {{ $('Extract Request Data').item.json.departureCity }} → {{ $('Extract Request Data').item.json.destination }}\\n📧 Email: {{ $('Webhook - Travel Request').item.json.body.email }}\\n🏆 Best Deal: ${{ $('AI Score & Recommendations').first().json.totalPrice.toFixed(2) }}\\n🤖 AI Score: {{ $('AI Score & Recommendations').first().json.aiScore }}/100",
        "channel": "#travel-bookings",
        "attachments": [],
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "82bb48ce-ad62-43cc-bb7e-69cf5f55e4c0",
      "name": "OpenRouter 聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        3792,
        -320
      ],
      "parameters": {
        "model": "qwen/qwen3-vl-8b-thinking",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "id": "fKnn6LL7cRFqNHDX",
          "name": "OpenRouter account2"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e66d7eb9-383f-4ec3-9e76-c33dcfabffdf",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2288,
        -608
      ],
      "parameters": {
        "width": 512,
        "height": 768,
        "content": "## 简介"
      },
      "typeVersion": 1
    },
    {
      "id": "70771bd2-ff0c-46a1-86ae-08d5698b5c56",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4144,
        -544
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 576,
        "content": "## 先决条件"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "973f16a5-77bb-4015-a5c5-a32efacdddd7",
  "connections": {
    "Generate HTML Email": {
      "main": [
        [
          {
            "node": "Send Email via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Request Data": {
      "main": [
        [
          {
            "node": "Search Flights - Skyscanner",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Hotels - Booking.com",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Alternative Flights - Kiwi",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Weather Forecast",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search Local Activities - Viator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Weather Forecast": {
      "main": [
        [
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email via Gmail": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Itinerary Optimizer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Merge All Data Sources": {
      "main": [
        [
          {
            "node": "Enhanced Itinerary Builder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Slack Notification": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Travel Request": {
      "main": [
        [
          {
            "node": "Extract Request Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Score & Recommendations": {
      "main": [
        [
          {
            "node": "Generate Premium HTML Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine & Rank Itineraries": {
      "main": [
        [
          {
            "node": "Generate HTML Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enhanced Itinerary Builder": {
      "main": [
        [
          {
            "node": "AI Agent - Itinerary Optimizer",
            "type": "main",
            "index": 0
          },
          {
            "node": "AI Score & Recommendations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Premium HTML Email": {
      "main": [
        [
          {
            "node": "Send Email via Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Flights - Skyscanner": {
      "main": [
        [
          {
            "node": "Combine & Rank Itineraries",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Hotels - Booking.com": {
      "main": [
        [
          {
            "node": "Combine & Rank Itineraries",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Itinerary Optimizer": {
      "main": [
        [
          {
            "node": "AI Score & Recommendations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Local Activities - Viator": {
      "main": [
        [
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Alternative Flights - Kiwi": {
      "main": [
        [
          {
            "node": "Merge All Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 个人效率, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量18
分类2
节点类型10
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Cheng Siong Chin

Cheng Siong Chin

@cschin

Prof. Cheng Siong CHIN serves as Chair Professor in Intelligent Systems Modelling and Simulation in Newcastle University, Singapore. His academic credentials include an M.Sc. in Advanced Control and Systems Engineering from The University of Manchester and a Ph.D. in Robotics from Nanyang Technological University.

外部链接
在 n8n.io 查看

分享此工作流