Travel Itinerary Generator
Plan your perfect trip and estimate the budget.
1. Trip Details
2. Quick Generate
Let Gemini suggest a 3-day itinerary based on your destination.
Generating detailed suggestions...
3. Add Custom Activity
Destination: Tokyo, Japan
Dates: N/A
Estimated Total Cost: $0.00
No activities planned yet. Use the Planner tab to add activities.
'; return; } // 3. Render Daily Cards Object.keys(groupedByDay).sort((a, b) => a - b).forEach(day => { const dayNum = parseInt(day); const activities = groupedByDay[day]; const dayCard = document.createElement('div'); dayCard.className = 'tig-daily-card bg-white p-4 mb-4 rounded-lg shadow-md border-l-4 border-blue-400'; let activityListHtml = ''; // Sort activities by time activities.sort((a, b) => { const timeA = a.time || '23:59'; const timeB = b.time || '23:59'; return timeA.localeCompare(timeB); }); activities.forEach(activity => { const color = { 'Meal': 'text-orange-500', 'Sightseeing': 'text-purple-500', 'Activity': 'text-green-500', 'Transport': 'text-blue-500' }[activity.type] || 'text-gray-500'; activityListHtml += `
${activity.time || 'N/A'}
${escapeHtml(activity.description)}
(${activity.type})
$${Number(activity.cost).toFixed(2)}
$${Number(activity.cost).toFixed(2)}
Day ${dayNum}
${activityListHtml}
`;
logContainer.appendChild(dayCard);
});
}
// --- Interaction Functions ---
function tigAddActivity() {
const day = parseInt(document.getElementById('tig-in-day').value);
const time = document.getElementById('tig-in-time').value;
const cost = parseFloat(document.getElementById('tig-in-cost').value);
const type = document.getElementById('tig-in-type').value;
const desc = document.getElementById('tig-in-desc').value;
if (isNaN(day) || day < 1 || !desc || isNaN(cost)) {
alert("Please ensure Day Number (>=1), Description, and valid Cost are entered.");
return;
}
const newActivity = {
id: Date.now(),
day: day,
time: time,
type: type,
description: desc,
cost: cost,
notes: '' // Keeping notes for future expansion
};
tripData.itinerary.push(newActivity);
// Clear inputs (keep day number)
document.getElementById('tig-in-time').value = '';
document.getElementById('tig-in-cost').value = '0.00';
document.getElementById('tig-in-desc').value = '';
tigRenderItinerary();
tigSwitchTab('view');
}
window.tigDeleteActivity = function(id) {
if(confirm("Are you sure you want to remove this activity?")) {
tripData.itinerary = tripData.itinerary.filter(a => a.id !== id);
tigRenderItinerary();
}
}
window.tigSwitchTab = function(tabName) {
document.getElementById('tab-content-planner').classList.toggle('hidden', tabName !== 'planner');
document.getElementById('tab-content-view').classList.toggle('hidden', tabName !== 'view');
document.getElementById('tab-planner').classList.toggle('active', tabName === 'planner');
document.getElementById('tab-view').classList.toggle('active', tabName === 'view');
// Always re-render when switching (to catch detail updates)
tigRenderItinerary();
}
// --- Gemini API Generator ---
// Function to simulate asynchronous API call with exponential backoff
async function fetchWithRetry(url, options, maxRetries = 5) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response;
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
async function tigGenerateItinerary() {
const destination = document.getElementById('tig-destination').value;
const startDay = new Date(document.getElementById('tig-start-date').value);
const endDay = new Date(document.getElementById('tig-end-date').value);
const duration = tigCalculateDuration();
const days = Math.min(duration, 3); // Limit generation to 3 days for complexity/cost reasons
if (!destination || duration < 1) {
alert("Please enter a valid Destination and Start/End Dates.");
return;
}
const loadingMsg = document.getElementById('tig-loading-msg');
const generateBtn = document.getElementById('tig-generate-btn');
loadingMsg.classList.remove('hidden');
generateBtn.disabled = true;
try {
const systemPrompt = "You are a world-class, creative travel planner. Generate a fun and realistic 3-day travel itinerary for the specified destination. Include a unique activity, meal suggestion, and transportation method for each time slot (Morning, Afternoon, Evening). The response MUST be a JSON array matching the provided schema.";
const userQuery = `Generate a ${days}-day itinerary for ${destination}. Ensure the itinerary is focused on general tourism and local experiences.`;
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`;
const payload = {
contents: [{ parts: [{ text: userQuery }] }],
systemInstruction: { parts: [{ text: systemPrompt }] },
generationConfig: {
responseMimeType: "application/json",
responseSchema: {
type: "ARRAY",
items: {
type: "OBJECT",
properties: {
day: { type: "NUMBER", description: "The day number, starting from 1." },
activities: {
type: "ARRAY",
items: {
type: "OBJECT",
properties: {
time: { type: "STRING", description: "The time of day (e.g., Morning, 9:00 AM, Lunch)." },
activity: { type: "STRING", description: "A detailed description of the activity/meal." },
costEstimate: { type: "NUMBER", description: "Estimated cost in USD." },
activityType: { type: "STRING", description: "Type: Sightseeing, Meal, Transport, or Activity." }
},
required: ["time", "activity", "costEstimate", "activityType"]
}
}
}
}
}
}
};
const response = await fetchWithRetry(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await response.json();
const jsonText = result.candidates?.[0]?.content?.parts?.[0]?.text;
if (jsonText) {
const generatedItinerary = JSON.parse(jsonText);
// Clear existing plan
tripData.itinerary = [];
// Process and merge generated data
let currentId = Date.now();
generatedItinerary.forEach(dayPlan => {
dayPlan.activities.forEach(activity => {
const newActivity = {
id: currentId++,
day: dayPlan.day,
time: activity.time.split('-')[0].trim(), // Simplify time string
type: activity.activityType || 'Activity',
description: activity.activity,
cost: activity.costEstimate || 0,
notes: ''
};
tripData.itinerary.push(newActivity);
});
});
alert(`Successfully generated a ${generatedItinerary.length}-day itinerary for ${destination}!`);
tigRenderItinerary();
tigSwitchTab('view');
} else {
console.error("Gemini response structure error:", result);
alert("Failed to generate itinerary. Please try again or check console for details.");
}
} catch (error) {
console.error("API Error:", error);
alert("An error occurred during itinerary generation. Please check your inputs.");
} finally {
loadingMsg.classList.add('hidden');
generateBtn.disabled = false;
}
}
// --- PDF Download ---
window.tigDownloadPDF = function() {
const element = document.getElementById('tig-print-area');
const destination = document.getElementById('tig-destination').value || 'Travel_Plan';
document.body.classList.add('tig-generating-pdf');
const opt = {
margin: [0.5, 0.5],
filename: `${destination.replace(/\s+/g, '_')}_Itinerary.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
};
html2pdf().set(opt).from(element).save().then(() => {
document.body.classList.remove('tig-generating-pdf');
});
}
// Utility: HTML Escape
function escapeHtml(text) {
if (!text) return '';
return text.replace(/[&<>"']/g, function(m) {
switch (m) {
case '&': return '&';
case '<': return '<';
case '>': return '>';
case '"': return '"';
case "'": return ''';
default: return m;
}
});
}
