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 Travel Planner: Search-->Compare-->Email Best Itineraries",
"tags": [],
"nodes": [
{
"id": "f3a23f51-4039-4a08-bad5-d9e1f475e8af",
"name": "Webhook - Travel Request",
"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": "Extract Request Data",
"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": "Search Flights - 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": "Search Hotels - 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": "Send Email via 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": "Respond to 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 Agent - Itinerary Optimizer",
"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": "Search Alternative Flights - 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": "Get Weather Forecast",
"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": "Search Local Activities - 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": "Merge All Data Sources",
"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": "Enhanced Itinerary Builder",
"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 Score & Recommendations",
"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": "Generate Premium HTML Email",
"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": "Send Slack Notification",
"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 Chat Model",
"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": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2288,
-608
],
"parameters": {
"width": 512,
"height": 768,
"content": "## Introduction\nAutomates travel planning by aggregating flights, hotels, activities, and weather via APIs, then uses AI to generate professional itineraries delivered through Gmail and Slack.\n\n## How It Works\nWebhook receives requests, searches APIs (Skyscanner, Booking.com, Kiwi, Viator, weather), merges data, AI builds itineraries, scores options, generates HTML emails, delivers via Gmail/Slack.\n\n## Workflow Template\nWebhook → Extract → Parallel Searches (Flights/Hotels/Activities/Weather) → Merge → Build Itinerary → AI Processing → Score → Generate HTML → Gmail → Slack → Response\n\n## Workflow Steps\n1. Trigger & Extract: Receives destination, dates, preferences, extracts parameters.\n2. Data Gathering: Parallel APIs fetch flights, hotels, activities, weather, merges responses.\n3. AI Processing: Analyzes data, creates itinerary, ranks recommendations.\n4. Delivery: Generates HTML email, sends via Gmail/Slack, confirms completion.\n\n## Setup Instructions\n1. API Configuration: Add keys for Skyscanner, Booking.com, Kiwi, Viator, OpenWeatherMap, OpenRouter.\n2. Communication: Connect Gmail OAuth2, Slack webhook.\n3. Customization: Adjust endpoints, AI prompts, HTML template, scoring criteria.\n\n\n"
},
"typeVersion": 1
},
{
"id": "70771bd2-ff0c-46a1-86ae-08d5698b5c56",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
4144,
-544
],
"parameters": {
"color": 5,
"width": 352,
"height": 576,
"content": "## Prerequisites\n- API keys: Skyscanner, Booking.com, Kiwi, Viator, OpenWeatherMap, OpenRouter\n- Gmail account\n- Slack workspace\n- n8n instance\n## Use Cases\n- Corporate travel planning\n- Vacation itinerary generation\n- Group trip coordination\n\n## Customization\n- Add sources (Airbnb, TripAdvisor)\n- Filter by budget preferences\n- Add PDF generation\n- Customize Slack format\n\n## Benefits\n- Saves 3-5 hours per trip\n- Real-time pricing aggregation\n- AI-powered personalization\n- Automated multi-channel delivery"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "973f16a5-77bb-4015-a5c5-a32efacdddd7",
"connections": {
"Generate HTML Email": {
"main": [
[
{
"node": "133388c8-6fe6-42cb-94b7-f76bf0cb4b2e",
"type": "main",
"index": 0
}
]
]
},
"65e7017e-5242-4875-bf24-661fc68eb771": {
"main": [
[
{
"node": "51891077-df8a-45db-b575-0585f52aa1db",
"type": "main",
"index": 0
},
{
"node": "953e3d0b-528d-41ba-b7ba-4fc7363c703f",
"type": "main",
"index": 0
},
{
"node": "759f481f-598a-4bff-a3d1-3cb3310ecb88",
"type": "main",
"index": 0
},
{
"node": "63c46681-a628-4bd8-aab0-a226ab57e703",
"type": "main",
"index": 0
},
{
"node": "15404c5f-d821-4455-abf6-a08db26baee3",
"type": "main",
"index": 0
}
]
]
},
"63c46681-a628-4bd8-aab0-a226ab57e703": {
"main": [
[
{
"node": "27b5a21e-02a6-43e2-b1b9-a7bd5074dd29",
"type": "main",
"index": 0
}
]
]
},
"133388c8-6fe6-42cb-94b7-f76bf0cb4b2e": {
"main": [
[
{
"node": "5fcf6271-c0fd-430b-858e-4a462e98b648",
"type": "main",
"index": 0
},
{
"node": "0551345f-ca44-4adc-8f49-5ddf522f8a74",
"type": "main",
"index": 0
}
]
]
},
"82bb48ce-ad62-43cc-bb7e-69cf5f55e4c0": {
"ai_languageModel": [
[
{
"node": "2aedf5b8-536b-44bc-9b6d-3b80daa2c6c0",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"27b5a21e-02a6-43e2-b1b9-a7bd5074dd29": {
"main": [
[
{
"node": "d460fa9d-9991-4f29-befc-caff18a9cf38",
"type": "main",
"index": 0
}
]
]
},
"0551345f-ca44-4adc-8f49-5ddf522f8a74": {
"main": [
[
{
"node": "5fcf6271-c0fd-430b-858e-4a462e98b648",
"type": "main",
"index": 0
}
]
]
},
"f3a23f51-4039-4a08-bad5-d9e1f475e8af": {
"main": [
[
{
"node": "65e7017e-5242-4875-bf24-661fc68eb771",
"type": "main",
"index": 0
}
]
]
},
"33047f44-9726-4f2f-a029-c7e71c8aceb8": {
"main": [
[
{
"node": "015b10c8-ad0a-4f34-8855-e1366cde7658",
"type": "main",
"index": 0
}
]
]
},
"Combine & Rank Itineraries": {
"main": [
[
{
"node": "Generate HTML Email",
"type": "main",
"index": 0
}
]
]
},
"d460fa9d-9991-4f29-befc-caff18a9cf38": {
"main": [
[
{
"node": "2aedf5b8-536b-44bc-9b6d-3b80daa2c6c0",
"type": "main",
"index": 0
},
{
"node": "33047f44-9726-4f2f-a029-c7e71c8aceb8",
"type": "main",
"index": 0
}
]
]
},
"015b10c8-ad0a-4f34-8855-e1366cde7658": {
"main": [
[
{
"node": "133388c8-6fe6-42cb-94b7-f76bf0cb4b2e",
"type": "main",
"index": 0
}
]
]
},
"51891077-df8a-45db-b575-0585f52aa1db": {
"main": [
[
{
"node": "Combine & Rank Itineraries",
"type": "main",
"index": 0
},
{
"node": "27b5a21e-02a6-43e2-b1b9-a7bd5074dd29",
"type": "main",
"index": 0
}
]
]
},
"953e3d0b-528d-41ba-b7ba-4fc7363c703f": {
"main": [
[
{
"node": "Combine & Rank Itineraries",
"type": "main",
"index": 0
},
{
"node": "27b5a21e-02a6-43e2-b1b9-a7bd5074dd29",
"type": "main",
"index": 0
}
]
]
},
"2aedf5b8-536b-44bc-9b6d-3b80daa2c6c0": {
"main": [
[
{
"node": "33047f44-9726-4f2f-a029-c7e71c8aceb8",
"type": "main",
"index": 0
}
]
]
},
"15404c5f-d821-4455-abf6-a08db26baee3": {
"main": [
[
{
"node": "27b5a21e-02a6-43e2-b1b9-a7bd5074dd29",
"type": "main",
"index": 0
}
]
]
},
"759f481f-598a-4bff-a3d1-3cb3310ecb88": {
"main": [
[
{
"node": "27b5a21e-02a6-43e2-b1b9-a7bd5074dd29",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 个人效率, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
来自多个招聘网站的求职自动化
使用 5 个招聘平台和 AI 简历生成器自动化求职与申请
If
Set
Code
+
If
Set
Code
34 节点Gerald Denor
个人效率
AI-Deepseek-R1t 会议差旅审批与费用授权申请
通过Deepseek AI、Gmail和Google Sheets自动化会议差旅审批
If
Set
Code
+
If
Set
Code
24 节点Cheng Siong Chin
文档提取
AI驱动的同行评审作业系统,带自动评分标准生成
使用GPT-4-nano、Slack和邮件通知自动化同行评审分配
Set
Code
Slack
+
Set
Code
Slack
22 节点Cheng Siong Chin
文档提取
GPT实时航班优惠分析器:自动抓取、评估并发布到WordPress
使用GPT、Google航班和WordPress的航班优惠分析器,含天气数据
Set
Html
Slack
+
Set
Html
Slack
19 节点Cheng Siong Chin
内容创作
AI驱动的Grok-3健康预警系统(含家属通知功能)
基于Grok-3 AI分析的健康监测系统,含家属/医生邮件警报
If
Set
Merge
+
If
Set
Merge
17 节点Cheng Siong Chin
个人效率
竞争对手内容差距分析器:自动化网站主题映射
使用Gemini AI、Apify和Google Sheets分析竞争对手内容差距
If
Set
Code
+
If
Set
Code
30 节点Mychel Garzon
杂项
工作流信息
难度等级
高级
节点数量18
分类2
节点类型10
作者
Cheng Siong Chin
@cschinProf. 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 查看 →
分享此工作流