From e06d66bb126d9116961b1284f66470ce7a1e430a Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 2 Dec 2025 15:04:45 +0000 Subject: [PATCH] Auto commit: 2025-12-02T15:04:45.684Z --- api/index.php | 30 +++++++-- assets/js/main.js | 157 ++++++++++++++++++++++++++++++++++++++++++++-- index.php | 32 ++++++++-- 3 files changed, 203 insertions(+), 16 deletions(-) diff --git a/api/index.php b/api/index.php index 6e1e33b..37f644a 100644 --- a/api/index.php +++ b/api/index.php @@ -3,6 +3,8 @@ ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); +session_start(); + header('Content-Type: application/json'); require_once __DIR__ . '/../ai/LocalAIApi.php'; @@ -11,18 +13,30 @@ require_once __DIR__ . '/../includes/pexels.php'; $requestBody = file_get_contents('php://input'); $data = json_decode($requestBody, true); $query = $data['query'] ?? ''; +$persona = $data['persona'] ?? 'travel_agent'; if (empty($query)) { echo json_encode(['error' => 'Query is empty']); exit; } +$conversation = $_SESSION['conversation'] ?? []; +$conversation[] = ['role' => 'user', 'content' => $query]; + +$persona_prompts = [ + 'travel_agent' => 'You are a helpful travel agent specializing in Poland.', + 'historian' => 'You are a historian specializing in Polish history. Your responses should be informative, detailed, and focus on the historical significance of places and events.', + 'foodie' => 'You are a food critic and blogger with a passion for Polish cuisine. Your responses should be enthusiastic, descriptive, and focus on food, restaurants, and culinary experiences.', + 'adventurer' => 'You are an adventure travel guide who loves exploring the wild side of Poland. Your responses should be exciting, energetic, and focus on outdoor activities, hiking, and unique experiences.' +]; + +$system_prompt = $persona_prompts[$persona] . ' The user is looking for: ' . $query . '. Provide a travel suggestion with a title, a short description, a 3-day itinerary, and the latitude and longitude of the location. Your response must be in JSON format, like this: {"title": "...", "description": "...", "itinerary": [{"day": 1, "activities": ["...", "..."]}, {"day": 2, "activities": ["...", "..."]}, {"day": 3, "activities": ["...", "..."]}], "location": {"lat": ..., "lng": ...}}'; + +$messages = array_merge([['role' => 'system', 'content' => $system_prompt]], $conversation); + $resp = LocalAIApi::createResponse( [ - 'input' => [ - ['role' => 'system', 'content' => 'You are a travel agent specializing in Poland. The user is looking for: ' . $query . '. Provide a travel suggestion with a title and a description. Your response must be in JSON format, like this: {"title": "...", "description": "..."}'], - ['role' => 'user', 'content' => $query], - ], + 'input' => $messages ] ); @@ -35,11 +49,17 @@ if (!empty($resp['success'])) { // This is a fallback mechanism $title = "AI Generated Response"; $description = $text; + $itinerary = []; } else { $title = $aiResponse['title'] ?? 'AI Generated Response'; $description = $aiResponse['description'] ?? 'No description available.'; + $itinerary = $aiResponse['itinerary'] ?? []; + $location = $aiResponse['location'] ?? null; } + $conversation[] = ['role' => 'assistant', 'content' => $text]; + $_SESSION['conversation'] = $conversation; + $pexelsUrl = 'https://api.pexels.com/v1/search?query=' . urlencode($title) . '&orientation=landscape&per_page=1&page=1'; $pexelsData = pexels_get($pexelsUrl); @@ -52,6 +72,8 @@ if (!empty($resp['success'])) { echo json_encode([ 'title' => $title, 'description' => $description, + 'itinerary' => $itinerary, + 'location' => $location, 'image' => $imageUrl ]); } else { diff --git a/assets/js/main.js b/assets/js/main.js index e45b5ca..bab2a2b 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,27 +1,119 @@ document.getElementById('suggestion-form').addEventListener('submit', function(e) { e.preventDefault(); const query = document.getElementById('query').value; + const persona = document.getElementById('persona').value; const suggestionCard = document.getElementById('suggestion-card'); const loading = document.getElementById('loading'); + const errorMessage = document.getElementById('error-message'); + const errorText = document.getElementById('error-text'); const suggestionImage = document.getElementById('suggestion-image'); const suggestionTitle = document.getElementById('suggestion-title'); const suggestionText = document.getElementById('suggestion-text'); + const funFact = document.getElementById('fun-fact'); + const historySection = document.getElementById('history-section'); + const historyList = document.getElementById('history-list'); + const itinerarySection = document.getElementById('itinerary-section'); + const itineraryList = document.getElementById('itinerary-list'); + const mapElement = document.getElementById('map'); + + const suggestionHistory = []; + let map; + + const funFacts = [ + "Poland is home to the world's largest castle, Malbork Castle.", + "The name \"Poland\" (Polska) originates from the Polanie tribe, meaning \"people living in open fields.\"", + "Poland has 17 Nobel Prize winners, including four for Peace and five for Literature.", + "The Polish alphabet consists of 32 letters.", + "Marie Curie, the pioneering scientist, was Polish.", + "Poland is the world's biggest exporter of amber.", + "The city of Wrocław has a network of over 300 small bronze statues of dwarves (krasnale).", + "Poland's Białowieża Forest is one of the last and largest remaining parts of the immense primeval forest that once stretched across the European Plain." + ]; + + let funFactInterval; suggestionCard.style.display = 'none'; + errorMessage.style.display = 'none'; loading.style.display = 'block'; + mapElement.style.display = 'none'; + + function showRandomFunFact() { + const randomIndex = Math.floor(Math.random() * funFacts.length); + funFact.textContent = funFacts[randomIndex]; + } + + showRandomFunFact(); + funFactInterval = setInterval(showRandomFunFact, 3000); + + function showError(message) { + errorText.textContent = message; + errorMessage.style.display = 'block'; + loading.style.display = 'none'; + clearInterval(funFactInterval); + } + + function updateHistory() { + historyList.innerHTML = ''; + suggestionHistory.slice(0, 5).forEach(item => { + const a = document.createElement('a'); + a.href = '#'; + a.className = 'list-group-item list-group-item-action'; + a.textContent = item.title; + a.onclick = (e) => { + e.preventDefault(); + suggestionImage.src = item.image; + suggestionTitle.textContent = item.title; + suggestionText.textContent = item.description; + + if (item.itinerary) { + itineraryList.innerHTML = ''; + item.itinerary.forEach(day => { + const li = document.createElement('li'); + li.className = 'list-group-item'; + li.innerHTML = `Day ${day.day}: ${day.activities.join(', ')}`; + itineraryList.appendChild(li); + }); + itinerarySection.style.display = 'block'; + } else { + itinerarySection.style.display = 'none'; + } + + if (item.location && item.location.lat && item.location.lng) { + mapElement.style.display = 'block'; + if (!map) { + map = L.map('map').setView([item.location.lat, item.location.lng], 13); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(map); + } else { + map.setView([item.location.lat, item.location.lng], 13); + } + L.marker([item.location.lat, item.location.lng]).addTo(map) + .bindPopup(item.title) + .openPopup(); + } else { + mapElement.style.display = 'none'; + } + + suggestionCard.style.display = 'block'; + }; + historyList.appendChild(a); + }); + historySection.style.display = suggestionHistory.length > 0 ? 'block' : 'none'; + } fetch('api/index.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query: query }) + body: JSON.stringify({ query: query, persona: persona }) }) .then(response => response.json()) .then(data => { + clearInterval(funFactInterval); if (data.error) { - alert(data.error); - loading.style.display = 'none'; + showError(data.error); return; } @@ -29,12 +121,65 @@ document.getElementById('suggestion-form').addEventListener('submit', function(e suggestionTitle.textContent = data.title; suggestionText.textContent = data.description; + if (data.itinerary) { + itineraryList.innerHTML = ''; + data.itinerary.forEach(day => { + const li = document.createElement('li'); + li.className = 'list-group-item'; + li.innerHTML = `Day ${day.day}: ${day.activities.join(', ')}`; + itineraryList.appendChild(li); + }); + itinerarySection.style.display = 'block'; + } else { + itinerarySection.style.display = 'none'; + } + + if (data.location && data.location.lat && data.location.lng) { + mapElement.style.display = 'block'; + if (!map) { + map = L.map('map').setView([data.location.lat, data.location.lng], 13); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(map); + } else { + map.setView([data.location.lat, data.location.lng], 13); + } + L.marker([data.location.lat, data.location.lng]).addTo(map) + .bindPopup(data.title) + .openPopup(); + } else { + mapElement.style.display = 'none'; + } + + suggestionHistory.unshift(data); + updateHistory(); + loading.style.display = 'none'; suggestionCard.style.display = 'block'; }) .catch(error => { console.error('Error:', error); - loading.style.display = 'none'; - alert('An error occurred while fetching the suggestion.'); + showError('An error occurred while fetching the suggestion.'); }); -}); \ No newline at end of file +}); + +document.getElementById('lucky-button').addEventListener('click', function() { + const queries = [ + "a hidden gem in Warsaw", + "the best pierogi in Krakow", + "a beautiful beach on the Baltic coast", + "a challenging hike in the Tatra Mountains", + "a quirky museum in Gdansk" + ]; + const randomQuery = queries[Math.floor(Math.random() * queries.length)]; + document.getElementById('query').value = randomQuery; + document.getElementById('suggestion-form').dispatchEvent(new Event('submit')); +}); + +// Optional: Add event listener to close the error message +const errorAlert = document.getElementById('error-message'); +if (errorAlert) { + errorAlert.querySelector('.btn-close').addEventListener('click', function () { + errorAlert.style.display = 'none'; + }); +} \ No newline at end of file diff --git a/index.php b/index.php index 6ca28d7..f09f5cb 100644 --- a/index.php +++ b/index.php @@ -1,10 +1,5 @@ - - - - - - AI Travel Agent + @@ -26,11 +21,19 @@
+ +
+
@@ -40,23 +43,40 @@
+
Loading...

Thinking...

+

Suggestion Image

+
+ + + +
+ \ No newline at end of file