AlanRex commited on
Commit
1ef45d6
·
verified ·
1 Parent(s): fe349c2

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +126 -8
main.py CHANGED
@@ -1,8 +1,14 @@
1
- import json, os, glob, pathlib, time, re
 
 
 
 
 
 
2
  from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException
3
  from fastapi.middleware.cors import CORSMiddleware
4
  from fastapi.responses import HTMLResponse, JSONResponse
5
- import google.generativeai as genai
6
  from linebot import LineBotApi, WebhookHandler
7
  from linebot.exceptions import InvalidSignatureError
8
  from linebot.models import MessageEvent, TextMessage, TextSendMessage
@@ -111,10 +117,11 @@ def create_calendar_event(booking_data, booking_id):
111
  # ============== Gemini AI 設定 ==============
112
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
113
  ai_enabled = False
 
114
 
115
  if GOOGLE_API_KEY:
116
  try:
117
- genai.configure(api_key=GOOGLE_API_KEY)
118
  ai_enabled = True
119
  logger.info("✅ Gemini AI 已啟用")
120
  except Exception as e:
@@ -339,7 +346,74 @@ def parse_people(people_str):
339
  return None, "❌ 人數必須在 1-10 人之間"
340
  return None, "❌ 請輸入有效的人數\n例如:2人、3"
341
 
342
- def validate_room(room_str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  room_normalized = room_str.strip().upper()
344
  for room in AVAILABLE_ROOMS:
345
  if room.upper() in room_normalized or room_normalized in room.upper():
@@ -422,6 +496,7 @@ def handle_booking_flow(user_id, user_message):
422
  # 解析並驗證時間
423
  start_time, end_time, display_time, error, err_msg = parse_time_range(user_message)
424
  if error:
 
425
  return err_msg
426
 
427
  # 儲存標準格式
@@ -443,7 +518,21 @@ def handle_booking_flow(user_id, user_message):
443
  elif current_state == BookingState.ASKING_ROOM:
444
  room, error = validate_room(user_message)
445
  if error:
 
446
  return error
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  booking["room"] = room
448
  booking["state"] = BookingState.CONFIRMING
449
 
@@ -455,7 +544,8 @@ def handle_booking_flow(user_id, user_message):
455
  f"👥 {booking['people']}人\n"
456
  f"🎹 {booking['room']}\n"
457
  f"{'='*20}\n\n"
458
- f"輸入「確認」送出"
 
459
  )
460
 
461
  elif current_state == BookingState.CONFIRMING:
@@ -532,6 +622,32 @@ def get_bookings():
532
  return {"total": 0, "bookings": [], "error": "讀取失敗"}
533
  return {"total": 0, "bookings": []}
534
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
  @app.get("/bookings/latest", response_class=JSONResponse)
536
  def get_latest_booking():
537
  """最新預約"""
@@ -673,12 +789,14 @@ def handle_message(event):
673
  return
674
 
675
  # AI 問答
676
- if ai_enabled:
677
  try:
678
  prompt = f"參考資料:{pdf_content}\n\n問題:{user_message}\n\n簡潔回答。" if pdf_content else user_message
679
 
680
- model = genai.GenerativeModel('gemini-2.5-flash')
681
- response = model.generate_content(prompt)
 
 
682
 
683
  out = response.text if response and response.text else "無法回答"
684
  except Exception as e:
 
1
+ def validate_room(room_str):
2
+ """驗證琴房選擇"""
3
+ room_normalized = room_str.strip().upper()
4
+ for room in AVAILABLE_ROOMS:
5
+ if room.upper() in room_normalized or room_normalized in room.upper():
6
+ return room, None
7
+ return None, f"❌ 請選擇:{', '.join(AVAILABLE_ROOMS)}"import json, os, glob, pathlib, time, re
8
  from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException
9
  from fastapi.middleware.cors import CORSMiddleware
10
  from fastapi.responses import HTMLResponse, JSONResponse
11
+ from google import genai
12
  from linebot import LineBotApi, WebhookHandler
13
  from linebot.exceptions import InvalidSignatureError
14
  from linebot.models import MessageEvent, TextMessage, TextSendMessage
 
117
  # ============== Gemini AI 設定 ==============
118
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
119
  ai_enabled = False
120
+ genai_client = None
121
 
122
  if GOOGLE_API_KEY:
123
  try:
124
+ genai_client = genai.Client(api_key=GOOGLE_API_KEY)
125
  ai_enabled = True
126
  logger.info("✅ Gemini AI 已啟用")
127
  except Exception as e:
 
346
  return None, "❌ 人數必須在 1-10 人之間"
347
  return None, "❌ 請輸入有效的人數\n例如:2人、3"
348
 
349
+ def check_booking_conflict(date_str, start_time, end_time, room):
350
+ """檢查預約時段是否有衝突"""
351
+ bookings_file = pathlib.Path("bookings/bookings.json")
352
+
353
+ if not bookings_file.exists():
354
+ return False, None
355
+
356
+ try:
357
+ with open(bookings_file, 'r', encoding='utf-8') as f:
358
+ bookings = json.load(f)
359
+ except:
360
+ return False, None
361
+
362
+ # 將時間轉換為分鐘數便於比較
363
+ def time_to_minutes(time_str):
364
+ h, m = map(int, time_str.split(':'))
365
+ return h * 60 + m
366
+
367
+ new_start = time_to_minutes(start_time)
368
+ new_end = time_to_minutes(end_time)
369
+
370
+ conflicts = []
371
+
372
+ for booking in bookings:
373
+ # 只檢查相同日期
374
+ if booking.get('date') != date_str:
375
+ continue
376
+
377
+ # 只檢查相同琴房或任意琴房
378
+ booking_room = booking.get('room', '')
379
+ if room != '任意' and booking_room != '任意':
380
+ if booking_room != room:
381
+ continue
382
+
383
+ # 獲取已預約的時段
384
+ existing_start = booking.get('start_time')
385
+ existing_end = booking.get('end_time')
386
+
387
+ if not existing_start or not existing_end:
388
+ # 如果沒有 start_time/end_time,嘗試從 time 欄位解析
389
+ time_str = booking.get('time', '')
390
+ start, end, _, _, _ = parse_time_range(time_str)
391
+ if start and end:
392
+ existing_start = start
393
+ existing_end = end
394
+ else:
395
+ continue
396
+
397
+ exist_start = time_to_minutes(existing_start)
398
+ exist_end = time_to_minutes(existing_end)
399
+
400
+ # 檢查是否有時段重疊
401
+ # 重疊條件:新預約開始時間 < 既有預約結束時間 AND 新預約結束時間 > 既有預約開始時間
402
+ if new_start < exist_end and new_end > exist_start:
403
+ conflicts.append({
404
+ 'booking_id': booking.get('booking_id'),
405
+ 'time': booking.get('time'),
406
+ 'room': booking_room
407
+ })
408
+
409
+ if conflicts:
410
+ conflict_msg = f"❌ 預約衝突!\n\n該時段已有預約:\n"
411
+ for c in conflicts:
412
+ conflict_msg += f"• {c['room']} - {c['time']}\n"
413
+ conflict_msg += f"\n請選擇其他時段或琴房"
414
+ return True, conflict_msg
415
+
416
+ return False, None
417
  room_normalized = room_str.strip().upper()
418
  for room in AVAILABLE_ROOMS:
419
  if room.upper() in room_normalized or room_normalized in room.upper():
 
496
  # 解析並驗證時間
497
  start_time, end_time, display_time, error, err_msg = parse_time_range(user_message)
498
  if error:
499
+ # 格式錯誤,不進入下一步,保持在當前狀態
500
  return err_msg
501
 
502
  # 儲存標準格式
 
518
  elif current_state == BookingState.ASKING_ROOM:
519
  room, error = validate_room(user_message)
520
  if error:
521
+ # 格式錯誤,保持在當前狀態
522
  return error
523
+
524
+ # 檢查時段是否有衝突
525
+ has_conflict, conflict_msg = check_booking_conflict(
526
+ booking["date"],
527
+ booking["start_time"],
528
+ booking["end_time"],
529
+ room
530
+ )
531
+
532
+ if has_conflict:
533
+ # 有衝突,保持在當前狀態,讓使用者重新選擇琴房
534
+ return conflict_msg
535
+
536
  booking["room"] = room
537
  booking["state"] = BookingState.CONFIRMING
538
 
 
544
  f"👥 {booking['people']}人\n"
545
  f"🎹 {booking['room']}\n"
546
  f"{'='*20}\n\n"
547
+ f"輸入「確認」送出\n"
548
+ f"輸入「取消」重新填寫"
549
  )
550
 
551
  elif current_state == BookingState.CONFIRMING:
 
622
  return {"total": 0, "bookings": [], "error": "讀取失敗"}
623
  return {"total": 0, "bookings": []}
624
 
625
+ @app.get("/bookings/check")
626
+ def check_availability(date: str = None, room: str = None):
627
+ """檢查特定日期或琴房的預約狀況"""
628
+ bookings_file = pathlib.Path("bookings/bookings.json")
629
+ if not bookings_file.exists():
630
+ return {"message": "無預約資料"}
631
+
632
+ with open(bookings_file, 'r', encoding='utf-8') as f:
633
+ try:
634
+ all_bookings = json.load(f)
635
+ except:
636
+ return {"error": "讀取失敗"}
637
+
638
+ # 篩選預約
639
+ filtered = all_bookings
640
+ if date:
641
+ filtered = [b for b in filtered if b.get('date') == date]
642
+ if room:
643
+ filtered = [b for b in filtered if b.get('room') == room]
644
+
645
+ return {
646
+ "date": date,
647
+ "room": room,
648
+ "count": len(filtered),
649
+ "bookings": filtered
650
+ }
651
  @app.get("/bookings/latest", response_class=JSONResponse)
652
  def get_latest_booking():
653
  """最新預約"""
 
789
  return
790
 
791
  # AI 問答
792
+ if ai_enabled and genai_client:
793
  try:
794
  prompt = f"參考資料:{pdf_content}\n\n問題:{user_message}\n\n簡潔回答。" if pdf_content else user_message
795
 
796
+ response = genai_client.models.generate_content(
797
+ model='gemini-2.5-flash',
798
+ contents=prompt
799
+ )
800
 
801
  out = response.text if response and response.text else "無法回答"
802
  except Exception as e: