AlexanderStaniel commited on
Commit
d1dda4b
·
verified ·
1 Parent(s): 2997734

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -19
app.py CHANGED
@@ -3,7 +3,7 @@ import asyncio
3
  import datetime
4
  import logging
5
  import os
6
-
7
  import gradio as gr
8
  from cachetools import TTLCache
9
  from langchain_core.output_parsers import JsonOutputParser
@@ -35,7 +35,7 @@ async def _extract_user_request(question: str) -> dict:
35
  parser = JsonOutputParser(pydantic_object=TravelRequest)
36
  prompt = ChatPromptTemplate.from_messages([
37
  ("system", "You are a travel assistant. Extract details."),
38
- ("human", "{format_instructions}\\n{request}")
39
  ]).partial(format_instructions=parser.get_format_instructions())
40
  date_str = datetime.date.today().isoformat()
41
  logging.info(f"🔍 Extracting: {question}")
@@ -59,7 +59,7 @@ async def _cached_search(func, details):
59
 
60
  # 🌐 5. SCRAPER FUNCTIONS
61
 
62
- async def _search_google_flights(details):
63
  results = []
64
  async with async_playwright() as pw:
65
  browser = await pw.chromium.launch()
@@ -195,6 +195,57 @@ async def _search_skiplagged(details):
195
  await browser.close()
196
  return results
197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  async def _search_secretflying(details):
199
  results = []
200
  async with async_playwright() as pw:
@@ -203,7 +254,7 @@ async def _search_secretflying(details):
203
  url = "https://secretflying.com/"
204
  await page.goto(url)
205
  await page.wait_for_selector(".post-item", timeout=20000)
206
- posts = await page.query_selector_all(".post-item")
207
  for post in posts[:5]:
208
  title_el = await post.query_selector("h2.entry-title a")
209
  p = await title_el.inner_text() if title_el else "Deal"
@@ -270,7 +321,6 @@ async def _search_going(details):
270
 
271
  async def _search_dollarclub(details):
272
  # parse RSS feed
273
- import feedparser
274
  feed = feedparser.parse("https://dollarflightclub.com/feed/")
275
  results = []
276
  for entry in feed.entries[:5]:
@@ -314,11 +364,12 @@ async def _search_edreams(details):
314
  # 🔄 6. GATHER ALL DATA
315
  async def _gather_travel_data(req):
316
  tasks = [
317
- _cached_search(_search_google_flights, req),
318
  _cached_search(_search_kayak, req),
319
  _cached_search(_search_momondo, req),
320
  _cached_search(_search_wego, req),
321
  _cached_search(_search_skiplagged, req),
 
322
  _cached_search(_search_secretflying, req),
323
  _cached_search(_search_thrifty, req),
324
  _cached_search(_search_going, req),
@@ -330,6 +381,8 @@ async def _gather_travel_data(req):
330
  for r in results:
331
  if not isinstance(r, Exception):
332
  combined.extend(r)
 
 
333
  return combined
334
 
335
 
@@ -339,14 +392,18 @@ def _format_response(req, data):
339
  md = f"## 🌏 Travel Plan to **{dest}**\n\n"
340
 
341
  # Flights
342
- md += "### ✈️ Flights & Deals\n"
343
- for item in sorted([i for i in data if i["type"]=="flight"], key=lambda x: x["price"])[:8]:
344
- md += f"- **{item['source']}**: {item['details']} — `${item['price']:.2f}` ([Book]({item['link']}))\n"
345
-
 
 
346
  # Other deals
347
- md += "\n### 💸 Hot Deals & Coupons\n"
348
- for item in [i for i in data if i["type"]=="deal"][:8]:
349
- md += f"- **{item['source']}**: {item['details']} ([Link]({item['link']}))\n"
 
 
350
 
351
  return md
352
 
@@ -355,16 +412,21 @@ def _format_response(req, data):
355
  async def ask_bot(question):
356
  if not question:
357
  return "❓ Tell me where you want to go!"
358
- req = await _extract_user_request(question)
359
- all_data = await _gather_travel_data(req)
360
- return _format_response(req, all_data)
 
 
 
 
361
 
362
  iface = gr.Interface(
363
  fn=ask_bot,
364
- inputs=gr.Textbox(lines=4, label="🌍 Your dream trip?"),
365
  outputs=gr.Markdown(label="🚀 Your Travel Plan"),
366
- title="🛫 High Flyer AI Bot"
 
367
  )
368
 
369
  if __name__ == "__main__":
370
- iface.launch(server_name="0.0.0.0", server_port=7860)
 
3
  import datetime
4
  import logging
5
  import os
6
+ import feedparser
7
  import gradio as gr
8
  from cachetools import TTLCache
9
  from langchain_core.output_parsers import JsonOutputParser
 
35
  parser = JsonOutputParser(pydantic_object=TravelRequest)
36
  prompt = ChatPromptTemplate.from_messages([
37
  ("system", "You are a travel assistant. Extract details."),
38
+ ("human", "{format_instructions}\n{request}")
39
  ]).partial(format_instructions=parser.get_format_instructions())
40
  date_str = datetime.date.today().isoformat()
41
  logging.info(f"🔍 Extracting: {question}")
 
59
 
60
  # 🌐 5. SCRAPER FUNCTIONS
61
 
62
+ async def _search_Google Flights(details):
63
  results = []
64
  async with async_playwright() as pw:
65
  browser = await pw.chromium.launch()
 
195
  await browser.close()
196
  return results
197
 
198
+ # ✨✨ NEW SPY BOT! ✨✨
199
+ async def _search_travala(details):
200
+ results = []
201
+ async with async_playwright() as pw:
202
+ browser = await pw.chromium.launch()
203
+ page = await browser.new_page()
204
+ # Travala focuses on "activities" or "deals" so we'll search the main deals page
205
+ # It's harder to search for specific flights, so we'll grab the top deals
206
+ url = "https://www.travala.com/deals"
207
+ await page.goto(url)
208
+ try:
209
+ await page.wait_for_selector("div[class*='DealCard_container__']", timeout=20000)
210
+ cards = await page.query_selector_all("div[class*='DealCard_container__']")
211
+ for card in cards[:5]: # Get top 5 deals
212
+ title_el = await card.query_selector("p[class*='DealCard_title__']")
213
+ price_el = await card.query_selector("span[class*='DealCard_price__']")
214
+ link_el = await card.query_selector("a")
215
+
216
+ title = await title_el.inner_text() if title_el else "Travala Deal"
217
+ price_text = await price_el.inner_text() if price_el else "0"
218
+ href = await link_el.get_attribute("href") if link_el else url
219
+
220
+ # Clean up the price, which might be like "$50 OFF"
221
+ price = 0.0
222
+ if "OFF" in price_text:
223
+ # It's a discount, so we can't get a final price easily. List as a deal.
224
+ details_text = f"{title} ({price_text})"
225
+ else:
226
+ details_text = title
227
+
228
+ results.append({
229
+ "source": "Travala",
230
+ "type": "deal",
231
+ "details": details_text,
232
+ "price": price, # Price is often not available directly
233
+ "link": f"https://www.travala.com{href}"
234
+ })
235
+ except Exception as e:
236
+ logging.error(f"Travala scraping failed: {e}")
237
+ results.append({
238
+ "source": "Travala",
239
+ "type": "deal",
240
+ "details": "Could not scrape Travala deals.",
241
+ "price": 0,
242
+ "link": url
243
+ })
244
+
245
+ await browser.close()
246
+ return results
247
+
248
+
249
  async def _search_secretflying(details):
250
  results = []
251
  async with async_playwright() as pw:
 
254
  url = "https://secretflying.com/"
255
  await page.goto(url)
256
  await page.wait_for_selector(".post-item", timeout=20000)
257
+ posts = await page.query_selector_all(".post-item")
258
  for post in posts[:5]:
259
  title_el = await post.query_selector("h2.entry-title a")
260
  p = await title_el.inner_text() if title_el else "Deal"
 
321
 
322
  async def _search_dollarclub(details):
323
  # parse RSS feed
 
324
  feed = feedparser.parse("https://dollarflightclub.com/feed/")
325
  results = []
326
  for entry in feed.entries[:5]:
 
364
  # 🔄 6. GATHER ALL DATA
365
  async def _gather_travel_data(req):
366
  tasks = [
367
+ _cached_search(_search_Google Flights, req),
368
  _cached_search(_search_kayak, req),
369
  _cached_search(_search_momondo, req),
370
  _cached_search(_search_wego, req),
371
  _cached_search(_search_skiplagged, req),
372
+ _cached_search(_search_travala, req), # ✨ OUR NEW BOT IS ON THE TEAM!
373
  _cached_search(_search_secretflying, req),
374
  _cached_search(_search_thrifty, req),
375
  _cached_search(_search_going, req),
 
381
  for r in results:
382
  if not isinstance(r, Exception):
383
  combined.extend(r)
384
+ else:
385
+ logging.error(f"A task failed: {r}")
386
  return combined
387
 
388
 
 
392
  md = f"## 🌏 Travel Plan to **{dest}**\n\n"
393
 
394
  # Flights
395
+ flights = sorted([i for i in data if i["type"]=="flight" and i["price"] > 0], key=lambda x: x["price"])
396
+ if flights:
397
+ md += "### ✈️ Flights\n"
398
+ for item in flights[:8]:
399
+ md += f"- **{item['source']}**: {item['details']} — `${item['price']:.2f}` ([Book]({item['link']}))\n"
400
+
401
  # Other deals
402
+ deals = [i for i in data if i["type"]=="deal"]
403
+ if deals:
404
+ md += "\n### 💸 Hot Deals & Coupons\n"
405
+ for item in deals[:8]:
406
+ md += f"- **{item['source']}**: {item['details']} ([Link]({item['link']}))\n"
407
 
408
  return md
409
 
 
412
  async def ask_bot(question):
413
  if not question:
414
  return "❓ Tell me where you want to go!"
415
+ try:
416
+ req = await _extract_user_request(question)
417
+ all_data = await _gather_travel_data(req)
418
+ return _format_response(req, all_data)
419
+ except Exception as e:
420
+ logging.error(f"An error occurred in ask_bot: {e}")
421
+ return "😵 Oh no! Something went wrong. I couldn't get the travel plans. Please try asking in a different way."
422
 
423
  iface = gr.Interface(
424
  fn=ask_bot,
425
+ inputs=gr.Textbox(lines=4, label="🌍 Your dream trip? (e.g., 'I want to fly from New York to Paris for a week in September')"),
426
  outputs=gr.Markdown(label="🚀 Your Travel Plan"),
427
+ title="🛫 High Flyer AI Bot",
428
+ description="Your personal AI travel agent. Find the best flights and deals from over 10 sources in real-time."
429
  )
430
 
431
  if __name__ == "__main__":
432
+ iface.launch(server_name="0.0.0.0", server_port=7860)