AlexanderStaniel commited on
Commit
2d6e8d9
·
verified ·
1 Parent(s): 34e9bb0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -395
app.py CHANGED
@@ -1,412 +1,224 @@
1
- ✨🚀 The ULTIMATE Step-by-Step V1.0 Build Guide for Your AI Travel Bot (Granular for Beginners)
2
-
3
- Welcome, future coder! 🎉 This guide contains every single step and command you need—no external references required. Follow along on Windows 11, and you'll have a working AI Travel Bot by the end.
4
-
5
- 📋 Prerequisites (Install Everything First)
6
-
7
- Before we begin, install all tools below. Each is required for the project.
8
-
9
- Git
10
-
11
- Open your browser to https://git-scm.com/downloads
12
-
13
- Click Download for Windows.
14
-
15
- Run the downloaded installer (Git-*.exe).
16
-
17
- In each dialog: click Next, accept defaults, then Finish.
18
-
19
- Verify by opening Command Prompt and running:
20
-
21
-
22
-
23
- git --version
24
- ```
25
- You should see git version 2.x.x.
26
-
27
- Python 3.10+
28
-
29
- Go to https://python.org/downloads
30
-
31
- Click Download Python 3.10.x.
32
-
33
- Run the installer.
34
-
35
- Important: Check Add Python to PATH.
36
-
37
- Click Install Now.
38
-
39
- Verify:
40
-
41
-
42
-
43
- python --version
44
- ```
45
- You should see Python 3.10.x.
46
-
47
- Node.js (LTS)
48
-
49
- Visit https://nodejs.org/en/download/
50
-
51
- Download Windows Installer (.msi) for LTS.
52
-
53
- Run the installer: Next → Next → Install → Finish.
54
-
55
- Verify:
56
-
57
-
58
-
59
- node --version
60
- npm --version
61
- ```
62
- You should see versions (e.g., v18.x.x, 8.x.x).
63
-
64
- Hugging Face Account
65
-
66
- Open https://huggingface.co/ and click Sign up.
67
-
68
- Register with your email or GitHub.
69
-
70
- Confirm your email and log in.
71
-
72
- 1️⃣ Create Your Online Home: Hugging Face Space
73
-
74
- In your browser, go to https://huggingface.co/spaces.
75
-
76
- Click Create new Space (blue button top right).
77
-
78
- Fill in:
79
-
80
- Space name: WanderlustAI-v1
81
-
82
- SDK: select Gradio
83
-
84
- Visibility: choose Public for now
85
-
86
- Click Create Space.
87
-
88
- Wait until you see the file explorer panel on the left. That’s your project directory.
89
-
90
- 2️⃣ Set Up Your Project Files (The Skeleton)
91
-
92
- In the Hugging Face Space file explorer, create these files one at a time:
93
-
94
- app.py
95
-
96
- Click Add file → Create file.
97
-
98
- Enter app.py → click Create.
99
-
100
- README.md
101
-
102
- Add file → README.md → Create.
103
-
104
- .env.example
105
-
106
- Add file → .env.example → Create.
107
-
108
- requirements.txt
109
-
110
- Add file → requirements.txt → Create.
111
-
112
- packages.txt
113
-
114
- Add file → packages.txt → Create.
115
-
116
- Confirm all five files appear in the list.
117
-
118
- 3️⃣ Document the Essentials (Fill In Templates)
119
-
120
- 3.1 README.md
121
-
122
- Click README.md → pencil icon to edit.
123
-
124
- Paste:
125
-
126
- # AI Travel Bot
127
- This project builds an AI-powered travel assistant using ChatGPT and web scraping.
128
-
129
- ## How to Use
130
- 1. Set up secrets in `.env`.
131
- 2. Run `python app.py` locally or deploy on Hugging Face.
132
-
133
- Click Commit changes.
134
-
135
- 3.2 .env.example
136
-
137
- Open .env.example → edit.
138
-
139
- Paste:
140
-
141
- OPENAI_API_KEY=YOUR_OPENAI_KEY_HERE
142
- GOOGLE_API_KEY=YOUR_GOOGLE_KEY_HERE
143
-
144
- Commit changes.
145
-
146
- 4️⃣ List Your Tools (Dependencies)
147
-
148
- 4.1 requirements.txt
149
-
150
- Open requirements.txt → edit.
151
-
152
- Paste exactly:
153
-
154
- gradio
155
- langchain
156
- langchain-openai
157
- langchain-google-genai
158
- langchain-community
159
- playwright
160
- scrapy
161
- beautifulsoup4
162
- lxml
163
- requests
164
- python-dotenv
165
- cachetools
166
- tenacity
167
-
168
- Commit changes.
169
-
170
- 4.2 packages.txt
171
-
172
- Open packages.txt → edit.
173
-
174
- Paste:
175
-
176
- chromium-driver
177
-
178
- Commit changes.
179
-
180
- 5️⃣ Secure Your Secrets (Add Real Keys)
181
-
182
- In the Space UI, click Settings (top menu).
183
-
184
- Scroll to Repository secrets → click New secret.
185
-
186
- Add:
187
-
188
- Name: OPENAI_API_KEY
189
-
190
- Value: paste your actual key
191
-
192
- Click Add secret.
193
-
194
- Repeat for:
195
-
196
- GOOGLE_API_KEY
197
-
198
- 🔒 Secrets are hidden; your code will read them via os.environ.
199
-
200
- 6️⃣ Build the Brain: Full app.py Code (One Paste)
201
-
202
- Open app.py → click pencil.
203
-
204
- Delete any existing text.
205
-
206
- Copy & paste the ENTIRE code below in one go:
207
-
208
- # 1. IMPORTS & SETUP
209
  import asyncio
 
210
  import datetime
211
- import logging
212
- import os
213
- import gradio as gr
214
- from cachetools import TTLCache
215
- from langchain_core.output_parsers import JsonOutputParser
216
  from langchain_core.prompts import ChatPromptTemplate
 
217
  from langchain_core.pydantic_v1 import BaseModel, Field
218
  from langchain_openai import ChatOpenAI
219
-
220
- # Configure logging
221
- logging.basicConfig(
222
- level=logging.INFO,
223
- format='%(asctime)s - %(levelname)s - %(message)s'
224
- )
225
-
226
- # Simple in-memory cache
227
- ttl_cache = TTLCache(maxsize=100, ttl=3600)
228
-
229
- # 2. DATA MODEL
 
 
 
 
 
230
  class TravelRequest(BaseModel):
231
- origin_city: str = Field(description="Starting city or airport")
232
- destination_city: str = Field(description="Destination city or airport")
233
- start_date: str = Field(description="Trip start date (YYYY-MM-DD)")
234
- end_date: str = Field(description="Trip end date (YYYY-MM-DD)")
235
- budget: int = Field(description="Total budget in USD")
236
- interests: list[str] = Field(description="List of interests like 'hiking', 'museums'")
237
-
238
- # 3. PARSE USER INPUT
239
- async def _extract_user_request(question: str) -> dict:
240
- llm = ChatOpenAI(model="gpt-4o", temperature=0)
241
- parser = JsonOutputParser(pydantic_object=TravelRequest)
242
- prompt = ChatPromptTemplate.from_messages([
243
- ("system", "You are a travel assistant. Extract details into JSON."),
244
- ("human", "{format_instructions}\n{request}")
245
- ]).partial(format_instructions=parser.get_format_instructions())
246
- date_str = datetime.date.today().isoformat()
247
- logging.info(f"Extracting: {question}")
248
- return await (prompt | llm | parser).ainvoke({
249
- "request": question,
250
- "current_date": date_str
251
- })
252
-
253
- # 4. SEARCH HELPERS
254
- async def _cached_search(func, details):
255
- key = (func.__name__, str(details))
256
- if key in ttl_cache:
257
- logging.info(f"Cache hit for {func.__name__}")
258
- return ttl_cache[key]
259
- logging.info(f"Cache miss for {func.__name__}")
260
- res = await func(details)
261
- ttl_cache[key] = res
262
- return res
263
-
264
- async def _search_skyscanner(details: dict) -> list:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  try:
266
- logging.info(f"Skyscanner: {details['destination_city']}")
267
- await asyncio.sleep(1)
268
- return [{
269
- "source": "Skyscanner",
270
- "type": "flight",
271
- "details": f"Round-trip to {details['destination_city']}",
272
- "price": 800,
273
- "link": "https://skyscanner.com"
274
- }]
 
 
 
 
 
 
275
  except Exception as e:
276
- logging.error(e)
277
- return []
278
 
279
- async def _search_expedia(details: dict) -> list:
280
- try:
281
- logging.info(f"Expedia: {details['destination_city']}")
282
- await asyncio.sleep(1)
283
- return [{
284
- "source": "Expedia",
285
- "type": "hotel",
286
- "details": f"Hotel in {details['destination_city']}",
287
- "price": 1200,
288
- "link": "https://expedia.com"
289
- }]
290
- except Exception as e:
291
- logging.error(e)
292
- return []
293
 
294
- async def _search_getyourguide(details: dict) -> list:
295
- try:
296
- logging.info(f"GetYourGuide: {details['interests']}")
297
- await asyncio.sleep(1)
298
- return [{
299
- "source": "GetYourGuide",
300
- "type": "activity",
301
- "details": f"Tour: {details['interests']}",
302
- "price": 150,
303
- "link": "https://getyourguide.com"
304
- }]
305
- except Exception as e:
306
- logging.error(e)
307
- return []
308
-
309
- # 5. GATHER ALL RESULTS
310
- async def _gather_travel_data(req: dict) -> list:
311
  tasks = [
312
- _cached_search(_search_skyscanner, req),
313
- _cached_search(_search_expedia, req),
314
- _cached_search(_search_getyourguide, req)
 
 
 
315
  ]
 
 
316
  results = await asyncio.gather(*tasks, return_exceptions=True)
317
- combined = []
318
- for r in results:
319
- if isinstance(r, Exception):
320
- logging.error(r)
321
- else:
322
- combined.extend(r)
323
- return combined
324
 
325
- # 6. FORMAT OUTPUT
326
- def _format_response(req: dict, data: list) -> str:
327
- dest = req['destination_city']
328
- text = f"## Travel Plan for {dest}\n"
329
- flights = [i for i in data if i['type']=='flight']
330
- hotels = [i for i in data if i['type']=='hotel']
331
- acts = [i for i in data if i['type']=='activity']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  if flights:
333
- text += "\n### Flights:\n"
334
- for f in flights:
335
- text += f"- {f['details']} (${f['price']}) [Book]({f['link']})\n"
 
 
 
 
 
 
 
336
  if hotels:
337
- text += "\n### Hotels:\n"
338
- for h in hotels:
339
- text += f"- {h['details']} (${h['price']}) [Book]({h['link']})\n"
340
- if acts:
341
- text += "\n### Activities:\n"
342
- for a in acts:
343
- text += f"- {a['details']} (${a['price']}) [Book]({a['link']})\n"
344
- return text
345
-
346
- # 7. MAIN ORCHESTRATOR & UI
347
- async def ask_bot(question: str) -> str:
348
- if not question:
349
- return "Tell me your trip!"
350
- try:
351
- req = await _extract_user_request(question)
352
- data = await _gather_travel_data(req)
353
- return _format_response(req, data)
354
- except Exception as e:
355
- logging.error(e)
356
- return "Error. Try again."
357
-
358
- iface = gr.Interface(
359
- fn=ask_bot,
360
- inputs=gr.Textbox(lines=4, label="Your dream trip?"),
361
- outputs=gr.Markdown(),
362
- title="AI Travel Bot"
363
- )
364
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  if __name__ == "__main__":
366
- iface.launch(server_name="0.0.0.0", server_port=7860)
367
-
368
- Click Commit changes to save and trigger build.
369
-
370
- 7️⃣ Local Smoke Test (Catch Errors Early)
371
-
372
- On Windows Command Prompt:
373
-
374
- git clone https://huggingface.co/spaces/YOUR_USERNAME/WanderlustAI-v1
375
- cd WanderlustAI-v1
376
- copy .env.example .env
377
- # Open .env in Notepad and paste real keys
378
- pip install -r requirements.txt
379
- python app.py
380
-
381
- A browser window opens with your bot.
382
-
383
- Type e.g. "Cheap flight from NYC to Paris" → Submit.
384
-
385
- Check console: you should see INFO logs like "Extracting:" and "Skyscanner:".
386
-
387
- If it runs without errors, move to deployment.
388
-
389
- 8️⃣ Deploy to Hugging Face
390
-
391
- git add .
392
- git commit -m "V1 ready"
393
- git push origin main
394
-
395
- In your Space on the web, click App tab.
396
-
397
- Wait ~60 seconds for build.
398
-
399
- Test live UI again.
400
-
401
- 🎯 Roadmap to V2.0
402
-
403
- Split code into modules (extract.py, search.py, format.py).
404
-
405
- Write tests with pytest & vcrpy.
406
-
407
- Add retry/backoff examples using tenacity.
408
-
409
- Automate CI/CD with GitHub Actions.
410
-
411
- Congratulations! You've got every step—install, code, test, deploy—all in one place. Now build your AI Travel Bot! 🌍✨
412
-
 
1
+ import gradio as gr
2
+ import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import asyncio
4
+ import json
5
  import datetime
6
+ from dotenv import load_dotenv
7
+
8
+ # --- Langchain Imports ---
 
 
9
  from langchain_core.prompts import ChatPromptTemplate
10
+ from langchain_core.output_parsers import JsonOutputParser
11
  from langchain_core.pydantic_v1 import BaseModel, Field
12
  from langchain_openai import ChatOpenAI
13
+ from langchain_google_genai import ChatGoogleGenerativeAI
14
+ from langchain_core.messages import SystemMessage, HumanMessage
15
+
16
+ # Load environment variables from .env file (for local testing)
17
+ # On Hugging Face Spaces, secrets are automatically available as env vars.
18
+ load_dotenv()
19
+
20
+ # --- 1. API KEY CHECK ---
21
+ # This will make the app fail early if the key isn't set
22
+ try:
23
+ openai_api_key = os.environ["OPENAI_API_KEY"]
24
+ except KeyError:
25
+ raise EnvironmentError("Missing OPENAI_API_KEY. Please set it in a .env file locally, or in Hugging Face Space secrets.")
26
+
27
+ # --- 2. PYDANTIC DATA STRUCTURE DEFINITION ---
28
+ # Defines the JSON structure we want the LLM to output
29
  class TravelRequest(BaseModel):
30
+ departure_city: str = Field(description="The city or airport of departure. Infer if not specified.")
31
+ destination_city: str = Field(description="The city or airport of the travel destination.")
32
+ departure_date: str = Field(description="The departure date or general time frame (e.g., 'August 2025').")
33
+ return_date: str = Field(description="The return date or general time frame.")
34
+ duration_days_approx: str = Field(description="Approximate duration of the trip in days.")
35
+ budget_preference: str = Field(description="User's budget preference (e.g., 'cheap', 'luxury').")
36
+ flight_preferences: str = Field(description="Specific flight preferences (e.g., 'direct flights', 'few stops').")
37
+ accommodation_type: str = Field(description="Preferred accommodation (e.g., 'hotel', 'hostel', 'resort').")
38
+ number_of_travelers: int = Field(description="Number of adults traveling. Default to 1 if not specified.")
39
+ activity_interests: str = Field(description="Specific interests for activities (e.g., 'museums', 'hiking').")
40
+
41
+ # --- 3. CONCEPTUAL ASYNC SEARCH FUNCTIONS ---
42
+ # You will implement the real logic for each of these. For now, they simulate a delay.
43
+ async def search_skyscanner(details):
44
+ # Simulate network delay and return mock data for a flight
45
+ print(f"Searching Skyscanner for: {details['destination_city']}")
46
+ await asyncio.sleep(2)
47
+ return [{"source": "Skyscanner", "type": "flight", "details": "Flight to Paris", "price": 850.00, "link": "https://www.skyscanner.com"}]
48
+
49
+ async def search_expedia(details):
50
+ # Simulate network delay and return mock data for a hotel
51
+ print(f"Searching Expedia for: {details['destination_city']}")
52
+ await asyncio.sleep(1.5)
53
+ return [{"source": "Expedia", "type": "hotel", "details": "Hotel in Paris", "price": 150.00, "link": "https://www.expedia.com"}]
54
+
55
+ async def scrape_Google_Flights(details):
56
+ # Simulate network delay and return mock data for a scraped flight
57
+ print(f"Scraping Google Flights for: {details['destination_city']}")
58
+ await asyncio.sleep(3)
59
+ return [{"source": "Google Flights (Scraped)", "type": "flight", "details": "Cheaper Flight to Paris", "price": 814.00, "link": "https://www.google.com/flights"}]
60
+
61
+ async def search_get_your_guide(details):
62
+ # Simulate network delay and return mock data for an activity
63
+ print(f"Searching GetYourGuide for: {details['activity_interests']}")
64
+ await asyncio.sleep(1)
65
+ return [{"source": "GetYourGuide", "type": "activity", "details": "Eiffel Tower Tour", "price": 50.00, "link": "https://www.getyourguide.com"}]
66
+
67
+ async def search_llm_redundancy(details, llm, llm_name):
68
+ # Query another LLM for additional, general travel ideas
69
+ print(f"Querying {llm_name} for additional ideas...")
70
+ prompt = f"""As a helpful travel assistant, provide some brief travel suggestions for a trip based on these details: {details}.
71
+ Focus on general tips, hidden gems, or activity ideas. Do not suggest specific prices or flights."""
72
+ messages = [SystemMessage(content=prompt), HumanMessage(content="What are your suggestions?")]
73
+ response = await llm.ainvoke(messages)
74
+ return {"source": llm_name, "type": "insight", "details": response.content}
75
+
76
+ # --- 4. MAIN BOT LOGIC ---
77
+ async def ask_bot(question):
78
+ """
79
+ This is the main ASYNCHRONOUS function for the bot.
80
+ It orchestrates the extraction, search, and output formatting.
81
+ """
82
+ # Part 1: Extract structured data from user request using an LLM
83
  try:
84
+ # Initialize the LLM for extraction (GPT-4o for precise JSON output)
85
+ llm_extractor = ChatOpenAI(model="gpt-4o", temperature=0)
86
+ # Define the parser for the Pydantic data model
87
+ parser = JsonOutputParser(pydantic_object=TravelRequest)
88
+ # Create the prompt template for extraction, including format instructions
89
+ extraction_prompt = ChatPromptTemplate.from_messages([
90
+ ("system", "You are a helpful travel assistant. Your goal is to accurately extract travel details from a user's natural language request and output them as a JSON object, following the specified schema. Be precise and infer intelligently. The current date is {current_date}."),
91
+ ("human", "Extract travel details from this request:\n\n{format_instructions}\n\nUser request: {request}"),
92
+ ]).partial(format_instructions=parser.get_format_instructions())
93
+ # Create the LangChain expression language chain
94
+ extraction_chain = extraction_prompt | llm_extractor | parser
95
+ # Get the current date to help the LLM with date parsing
96
+ current_date_str = datetime.date.today().strftime("%Y-%m-%d")
97
+ # Invoke the chain to get the structured request
98
+ structured_request = await extraction_chain.ainvoke({"request": question, "current_date": current_date_str})
99
  except Exception as e:
100
+ # Handle errors during the extraction phase
101
+ return f"Error extracting request details: {e}. Please try rephrasing your request."
102
 
103
+ # Part 2: Gather all search tasks to run concurrently
104
+ # Initialize LLM instances for redundancy search (e.g., creative suggestions)
105
+ llm_openai_creative = ChatOpenAI(model="gpt-4o", temperature=0.7)
106
+ # To enable Gemini redundancy, uncomment the line below and ensure GOOGLE_API_KEY is set in your .env
107
+ # llm_gemini_search = ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0.7)
 
 
 
 
 
 
 
 
 
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  tasks = [
110
+ search_skyscanner(structured_request), # Flight search
111
+ search_expedia(structured_request), # Hotel search
112
+ scrape_Google_Flights(structured_request), # Another flight source (simulated scraping)
113
+ search_get_your_guide(structured_request), # Activities search
114
+ search_llm_redundancy(structured_request, llm_openai_creative, "ChatGPT (Creative)"), # AI insights
115
+ # search_llm_redundancy(structured_request, llm_gemini_search, "Gemini"), # Uncomment for Gemini insights
116
  ]
117
+
118
+ # Part 3: Run all tasks concurrently and collect results
119
  results = await asyncio.gather(*tasks, return_exceptions=True)
 
 
 
 
 
 
 
120
 
121
+ # Part 4: Normalize, filter, and rank results
122
+ all_results = []
123
+ for res in results:
124
+ if isinstance(res, Exception):
125
+ # Log any exceptions from the concurrent tasks
126
+ print(f"A search task failed: {res}")
127
+ elif res:
128
+ # Extend the combined results list if the task was successful
129
+ all_results.extend(res)
130
+
131
+ # Separate and sort results by type (e.g., flights by price)
132
+ flights = sorted([r for r in all_results if r['type'] == 'flight'], key=lambda x: x['price'])
133
+ hotels = sorted([r for r in all_results if r['type'] == 'hotel'], key=lambda x: x['price'])
134
+ activities = [r for r in all_results if r['type'] == 'activity']
135
+ insights = [r for r in all_results if r['type'] == 'insight']
136
+
137
+ # Part 5: Generate the final formatted output in Markdown
138
+ dest_city = structured_request['destination_city']
139
+ output_text = f"## ✨ Your Personalized Travel Plan to {dest_city}! ✨\n\n"
140
+
141
+ # Format Flights section
142
+ output_text += f"### ✈️ Top Flights\n"
143
  if flights:
144
+ for flight in flights[:3]: # Display top 3 flights
145
+ output_text += f"* **{flight['details']}** from **{flight['source']}**\n"
146
+ output_text += f" * Price: **C${flight['price']:,.2f}**\n"
147
+ output_text += f" * 🔗 [**View Deal**]({flight['link']})\n"
148
+ else:
149
+ output_text += "_No flights found matching your criteria. Try adjusting the dates._\n"
150
+ output_text += "\n"
151
+
152
+ # Format Hotels section
153
+ output_text += f"### 🏨 Top Accommodations\n"
154
  if hotels:
155
+ for hotel in hotels[:2]: # Display top 2 hotels
156
+ output_text += f"* **{hotel['details']}** from **{hotel['source']}**\n"
157
+ output_text += f" * Price: **C${hotel['price']:,.2f}** / night\n"
158
+ output_text += f" * 🔗 [**View Deal**]({hotel['link']})\n"
159
+ else:
160
+ output_text += "_No accommodations found matching your criteria._\n"
161
+ output_text += "\n"
162
+
163
+ # Format Activities section
164
+ output_text += f"### 🎉 Fun Activities\n"
165
+ if activities:
166
+ for activity in activities[:2]: # Display top 2 activities
167
+ output_text += f"* **{activity['details']}** from **{activity['source']}**\n"
168
+ output_text += f" * Price: **C${activity['price']:,.2f}**\n"
169
+ output_text += f" * 🔗 [**View Activity**]({activity['link']})\n"
170
+ else:
171
+ output_text += "_No specific activities found, but there's always something to explore!_\n"
172
+ output_text += "\n"
173
+
174
+ # Format AI Insights section (from redundant LLMs)
175
+ output_text += "### 💡 Additional AI Insights (for Redundancy)\n"
176
+ if insights:
177
+ for insight in insights:
178
+ output_text += f"* **{insight['source']} says:** {insight['details']}\n"
179
+ else:
180
+ output_text += "_No additional AI insights available._\n"
181
+ output_text += "\n"
182
+
183
+ # Add a disclaimer
184
+ output_text += "> **Disclaimer:** Prices and availability change rapidly. Click the links for the most up-to-date information. Happy travels! 🚀"
185
+
186
+ return output_text
187
+
188
+ # --- 5. DEFINE THE GRADIO INTERFACE ---
189
+ # Using gr.Blocks for a more customizable layout compared to gr.Interface
190
+ with gr.Blocks(theme=gr.themes.Soft()) as iface:
191
+ # Main title for the application
192
+ gr.Markdown(
193
+ """
194
+ # ✨ The Ultimate Global Travel Planner Bot ✨
195
+ Your smart AI assistant for finding the best flights, hotels, and activities worldwide!
196
+ """
197
+ )
198
+ with gr.Row():
199
+ # User input text box with more lines and a guiding placeholder
200
+ user_input = gr.Textbox(lines=5, label="✈️ Tell me about your dream trip!", placeholder="e.g., I wanna go to Paris from Halifax, maybe around August for a week or so. Cheap as possible, no crazy layovers!")
201
+ with gr.Row():
202
+ # Submit button to trigger the bot's logic
203
+ submit_button = gr.Button("Find My Trip!", variant="primary")
204
+ with gr.Row():
205
+ # Area to display the Markdown formatted output
206
+ output_display = gr.Markdown(label="🌟 Your Travel Plan:")
207
+
208
+ # Examples to help users understand how to phrase their requests
209
+ gr.Examples(
210
+ examples=[
211
+ ["I need a cheap flight from Toronto to London around July for 5 days."],
212
+ ["Looking for a beach resort in Mexico for 2 people in December, not too expensive."],
213
+ ["Find me some cool things to do in Tokyo in spring."]
214
+ ],
215
+ inputs=user_input # Connect examples to the user input box
216
+ )
217
+
218
+ # Connect the submit button's click event to the ask_bot function
219
+ submit_button.click(fn=ask_bot, inputs=user_input, outputs=output_display)
220
+
221
+ # --- 6. LAUNCH THE APP ---
222
+ # This block ensures the Gradio app runs when the script is executed
223
  if __name__ == "__main__":
224
+ iface.launch()