AlanRex commited on
Commit
8cc90ad
·
verified ·
1 Parent(s): e9e4b9f

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +344 -68
main.py CHANGED
@@ -1,9 +1,119 @@
1
- # 在主程式中加入以下功能
2
-
3
- import re
 
 
 
 
 
 
4
  from datetime import datetime, timedelta
5
 
6
- # ========== 日期解析與驗證 ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  def parse_date(date_str):
8
  """解析並驗證日期"""
9
  today = datetime.now()
@@ -33,68 +143,56 @@ def parse_date(date_str):
33
 
34
  # 檢查是否為過去的日期
35
  if date.date() < today.date():
36
- return None, "日期不能是過去的時間,請重新輸入。"
37
 
38
  return date.strftime("%Y-%m-%d"), None
39
  except ValueError:
40
  pass
41
 
42
- return None, "日期格式不正確,請使用如:明天、12/26、2025-12-30"
43
 
44
- # ========== 時間驗證 ==========
45
  def validate_time(time_str):
46
  """驗證時段格式"""
47
- # 簡單驗證是否包含時間相關字詞
48
- time_keywords = ["點", "時", ":", "-", "到", "至"]
49
  if any(kw in time_str for kw in time_keywords):
50
  return True, None
51
- return False, "時段格式不正確,請使用如:09:00-11:00 或 下午2點到4點"
52
 
53
- # ========== 人數驗證 ==========
54
  def parse_people(people_str):
55
  """解析並驗證人數"""
56
- # 提取數字
57
  numbers = re.findall(r'\d+', people_str)
58
  if numbers:
59
  count = int(numbers[0])
60
- if 1 <= count <= 10: # 假設最多10人
61
  return count, None
62
  else:
63
- return None, "人數必須在 1-10 人之間,請重新輸入。"
64
- return None, "請輸入有效的人數,例如:2人、3"
65
-
66
- # ========== 琴房驗證 ==========
67
- AVAILABLE_ROOMS = ["A琴房", "B琴房", "C琴房", "D琴房", "任意"]
68
 
69
  def validate_room(room_str):
70
  """驗證琴房選擇"""
71
- # 標準化輸入
72
  room_normalized = room_str.strip().upper()
73
 
74
- # 檢查是否在可用琴房列表中
75
  for room in AVAILABLE_ROOMS:
76
  if room.upper() in room_normalized or room_normalized in room.upper():
77
  return room, None
78
 
79
- return None, f"請選擇有效的琴房:{', '.join(AVAILABLE_ROOMS)}"
80
 
81
- # ========== 將預約資料存入檔案 ==========
82
  def save_booking_to_file(user_id, booking_data):
83
  """將預約資料儲存到 JSON 檔案"""
84
- import json
85
- from pathlib import Path
86
-
87
- # 建立 bookings 資料夾
88
- bookings_dir = Path("bookings")
89
  bookings_dir.mkdir(exist_ok=True)
90
 
91
- # 預約檔案路徑
92
  booking_file = bookings_dir / "bookings.json"
93
 
94
  # 讀取現有預約
95
  if booking_file.exists():
96
  with open(booking_file, 'r', encoding='utf-8') as f:
97
- bookings = json.load(f)
 
 
 
98
  else:
99
  bookings = []
100
 
@@ -107,7 +205,7 @@ def save_booking_to_file(user_id, booking_data):
107
  "people": booking_data["people"],
108
  "room": booking_data["room"],
109
  "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
110
- "status": "pending" # pending, confirmed, cancelled
111
  }
112
 
113
  bookings.append(booking_record)
@@ -116,63 +214,82 @@ def save_booking_to_file(user_id, booking_data):
116
  with open(booking_file, 'w', encoding='utf-8') as f:
117
  json.dump(bookings, f, ensure_ascii=False, indent=2)
118
 
 
119
  return booking_record["booking_id"]
120
 
121
- # ========== 修改後的 handle_booking_flow 函數 ==========
122
- def handle_booking_flow_enhanced(user_id, user_message):
123
- """處理預約流程(加強版)"""
124
  booking = get_user_booking(user_id)
125
  current_state = booking["state"]
126
 
127
- # 超時重置
128
  if time.time() - booking["last_update"] > 600:
129
  reset_booking(user_id)
130
  booking = get_user_booking(user_id)
131
  current_state = booking["state"]
132
 
 
133
  booking["last_update"] = time.time()
134
 
135
  # 取消預約
136
- if user_message in ["取消", "取消預約", "重來"]:
137
  reset_booking(user_id)
138
- return "已取消預約流程。如需重新預約,請輸入「預約」。"
139
 
140
- # 開始預約
141
  if current_state == BookingState.IDLE:
142
  if is_booking_keyword(user_message):
143
  booking["state"] = BookingState.ASKING_DATE
144
- return "好的!請問您想要預約哪一天呢?\n例如:明天、12/26、2025/12/30\n\n(輸入「取消」可取消預約)"
 
 
 
 
 
145
  else:
146
- return None
147
 
148
- # 詢問日期(加入驗證)
149
  elif current_state == BookingState.ASKING_DATE:
150
  parsed_date, error = parse_date(user_message)
151
  if error:
152
  return error
153
  booking["date"] = parsed_date
154
  booking["state"] = BookingState.ASKING_TIME
155
- return f"好的,預約日期:{parsed_date}\n\n請問您想要預約哪個時段呢?\n例如:09:00-11:00、下午2點到4點"
 
 
 
 
156
 
157
- # 詢問時段(加入驗證)
158
  elif current_state == BookingState.ASKING_TIME:
159
  is_valid, error = validate_time(user_message)
160
  if not is_valid:
161
  return error
162
  booking["time"] = user_message
163
  booking["state"] = BookingState.ASKING_PEOPLE
164
- return f"好的,時段:{user_message}\n\n請問有幾位使用呢?\n例如:1人、2人"
 
 
 
 
165
 
166
- # 詢問人數(加入驗證)
167
  elif current_state == BookingState.ASKING_PEOPLE:
168
  people_count, error = parse_people(user_message)
169
  if error:
170
  return error
171
  booking["people"] = people_count
172
  booking["state"] = BookingState.ASKING_ROOM
173
- return f"好的,人數:{people_count}人\n\n請問您想使用哪個琴房呢?\n可選擇:{', '.join(AVAILABLE_ROOMS)}"
 
 
 
 
 
174
 
175
- # 詢問琴房(加入驗證)
176
  elif current_state == BookingState.ASKING_ROOM:
177
  room, error = validate_room(user_message)
178
  if error:
@@ -181,36 +298,195 @@ def handle_booking_flow_enhanced(user_id, user_message):
181
  booking["state"] = BookingState.CONFIRMING
182
 
183
  summary = (
184
- f"📋 請確認您的預約資訊:\n\n"
 
185
  f"📅 日期:{booking['date']}\n"
186
  f"⏰ 時段:{booking['time']}\n"
187
  f"👥 人數:{booking['people']}人\n"
188
- f"🎹 琴房:{booking['room']}\n\n"
189
- f"請輸入「確認」送出預約,或輸入「取消」重新填寫。"
 
 
190
  )
191
  return summary
192
 
193
- # 確認預約(儲存資料)
194
  elif current_state == BookingState.CONFIRMING:
195
- if user_message in ["確認", "確定", "送出", "ok", "OK"]:
196
  # 儲存預約資料
197
- booking_id = save_booking_to_file(user_id, booking)
198
-
199
- summary = (
200
- f"✅ 預約成功!\n\n"
201
- f"📅 日期:{booking['date']}\n"
202
- f"⏰ 時段:{booking['time']}\n"
203
- f"👥 人數:{booking['people']}人\n"
204
- f"🎹 琴房:{booking['room']}\n"
205
- f"📝 預約編號:{booking_id}\n\n"
206
- f"我們已收到您的預約,服務專員將盡快與您聯繫確認。\n"
207
- f"如有任何問題,歡迎隨時詢問!"
208
- )
209
-
210
- logger.info(f"新預約已儲存:{booking_id}")
211
- reset_booking(user_id)
212
- return summary
 
 
 
 
 
 
 
 
213
  else:
214
  return "請輸入「確認」來完成預約,或輸入「取消」重新開始。"
215
 
216
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json, os, glob, pathlib, time, re
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status
4
+ from google import genai
5
+ from linebot import LineBotApi, WebhookHandler
6
+ from linebot.exceptions import InvalidSignatureError
7
+ from linebot.models import MessageEvent, TextMessage, TextSendMessage
8
+ import PyPDF2
9
+ import logging
10
  from datetime import datetime, timedelta
11
 
12
+ # ============== 設定日誌 ==============
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # ============== API 金鑰檢查 ==============
17
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
18
+ if not GOOGLE_API_KEY:
19
+ raise ValueError("請在環境變數中設定 GOOGLE_API_KEY")
20
+
21
+ client = genai.Client(api_key=GOOGLE_API_KEY)
22
+
23
+ # ============== 讀取 PDF 內容 ==============
24
+ files = glob.glob('docs/*.pdf')
25
+ pdf_content = ''
26
+
27
+ if not files:
28
+ logger.warning("警告:docs/ 資料夾中找不到 PDF 檔案")
29
+ else:
30
+ logger.info(f"找到 {len(files)} 個 PDF 檔案")
31
+ for filename in files:
32
+ try:
33
+ with open(filename, 'rb') as pdf_file:
34
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
35
+ for i in range(len(pdf_reader.pages)):
36
+ page = pdf_reader.pages[i]
37
+ pdf_content += page.extract_text()
38
+ logger.info(f"成功讀取: {filename}")
39
+ except Exception as e:
40
+ logger.error(f"讀取 {filename} 失敗: {str(e)}")
41
+
42
+ # 限制 PDF 內容長度
43
+ MAX_CONTENT_LENGTH = 30000
44
+ if len(pdf_content) > MAX_CONTENT_LENGTH:
45
+ pdf_content = pdf_content[:MAX_CONTENT_LENGTH]
46
+
47
+ logger.info(f"PDF 內容總長度: {len(pdf_content)} 字元")
48
+
49
+ # ============== Gemini 設定 ==============
50
+ system_instruction = (
51
+ "你是問答助手,使用以下參考資料回答問題。\n"
52
+ "規則:\n"
53
+ "1. 只用提供的資料回答\n"
54
+ "2. 不知道就說「請聯繫服務專員」\n"
55
+ "3. 答案簡潔,必要時用列表\n\n"
56
+ f"參考資料:\n{pdf_content}"
57
+ )
58
+
59
+ thinking_config = genai.types.ThinkingConfig(thinking_budget=0)
60
+ generation_config = genai.types.GenerateContentConfig(
61
+ max_output_tokens=1000,
62
+ temperature=0.1,
63
+ top_p=0.2,
64
+ thinking_config=thinking_config,
65
+ system_instruction=system_instruction
66
+ )
67
+
68
+ # ============== LINE Bot 設定 ==============
69
+ line_bot_api = LineBotApi(os.getenv("CHANNEL_ACCESS_TOKEN"))
70
+ line_handler = WebhookHandler(os.getenv("CHANNEL_SECRET"))
71
+ working_status = os.getenv("DEFALUT_TALKING", default="true").lower() == "true"
72
+
73
+ # ============== 請求速率控制 ==============
74
+ last_request_time = {}
75
+ REQUEST_COOLDOWN = 2
76
+
77
+ # ============== 預約系統 ==============
78
+ user_booking_state = {}
79
+
80
+ class BookingState:
81
+ """預約狀態管理"""
82
+ IDLE = "idle"
83
+ ASKING_DATE = "asking_date"
84
+ ASKING_TIME = "asking_time"
85
+ ASKING_PEOPLE = "asking_people"
86
+ ASKING_ROOM = "asking_room"
87
+ CONFIRMING = "confirming"
88
+
89
+ AVAILABLE_ROOMS = ["A琴房", "B琴房", "C琴房", "D琴房", "任意"]
90
+
91
+ def init_user_booking(user_id):
92
+ """初始化使用者的預約資料"""
93
+ user_booking_state[user_id] = {
94
+ "state": BookingState.IDLE,
95
+ "date": None,
96
+ "time": None,
97
+ "people": None,
98
+ "room": None,
99
+ "last_update": time.time()
100
+ }
101
+
102
+ def get_user_booking(user_id):
103
+ """取得使用者的預約狀態"""
104
+ if user_id not in user_booking_state:
105
+ init_user_booking(user_id)
106
+ return user_booking_state[user_id]
107
+
108
+ def reset_booking(user_id):
109
+ """重置使用者的預約狀態"""
110
+ init_user_booking(user_id)
111
+
112
+ def is_booking_keyword(text):
113
+ """判斷是否為預約相關關鍵字"""
114
+ keywords = ["預約", "預定", "訂位", "訂房", "訂琴房", "借琴房", "租琴房", "我要預約", "想預約"]
115
+ return any(keyword in text for keyword in keywords)
116
+
117
  def parse_date(date_str):
118
  """解析並驗證日期"""
119
  today = datetime.now()
 
143
 
144
  # 檢查是否為過去的日期
145
  if date.date() < today.date():
146
+ return None, "❌ 日期不能是過去的時間,請重新輸入。\n例如:明天、12/26、2025-12-30"
147
 
148
  return date.strftime("%Y-%m-%d"), None
149
  except ValueError:
150
  pass
151
 
152
+ return None, "❌ 日期格式不正確,請重新輸入。\n例如:明天、12/26、2025-12-30"
153
 
 
154
  def validate_time(time_str):
155
  """驗證時段格式"""
156
+ time_keywords = ["點", "時", ":", "-", "到", "至", "~"]
 
157
  if any(kw in time_str for kw in time_keywords):
158
  return True, None
159
+ return False, "❌ 時段格式不正確,請重新輸入。\n例如:09:00-11:00、下午2點到4點"
160
 
 
161
  def parse_people(people_str):
162
  """解析並驗證人數"""
 
163
  numbers = re.findall(r'\d+', people_str)
164
  if numbers:
165
  count = int(numbers[0])
166
+ if 1 <= count <= 10:
167
  return count, None
168
  else:
169
+ return None, "人數必須在 1-10 人之間,請重新輸入。"
170
+ return None, "❌ 請輸入有效的人數。\n例如:2人、3、5位"
 
 
 
171
 
172
  def validate_room(room_str):
173
  """驗證琴房選擇"""
 
174
  room_normalized = room_str.strip().upper()
175
 
 
176
  for room in AVAILABLE_ROOMS:
177
  if room.upper() in room_normalized or room_normalized in room.upper():
178
  return room, None
179
 
180
+ return None, f"請選擇有效的琴房:{', '.join(AVAILABLE_ROOMS)}"
181
 
 
182
  def save_booking_to_file(user_id, booking_data):
183
  """將預約資料儲存到 JSON 檔案"""
184
+ bookings_dir = pathlib.Path("bookings")
 
 
 
 
185
  bookings_dir.mkdir(exist_ok=True)
186
 
 
187
  booking_file = bookings_dir / "bookings.json"
188
 
189
  # 讀取現有預約
190
  if booking_file.exists():
191
  with open(booking_file, 'r', encoding='utf-8') as f:
192
+ try:
193
+ bookings = json.load(f)
194
+ except json.JSONDecodeError:
195
+ bookings = []
196
  else:
197
  bookings = []
198
 
 
205
  "people": booking_data["people"],
206
  "room": booking_data["room"],
207
  "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
208
+ "status": "pending"
209
  }
210
 
211
  bookings.append(booking_record)
 
214
  with open(booking_file, 'w', encoding='utf-8') as f:
215
  json.dump(bookings, f, ensure_ascii=False, indent=2)
216
 
217
+ logger.info(f"預約已儲存: {booking_record['booking_id']}")
218
  return booking_record["booking_id"]
219
 
220
+ def handle_booking_flow(user_id, user_message):
221
+ """處理預約流程"""
 
222
  booking = get_user_booking(user_id)
223
  current_state = booking["state"]
224
 
225
+ # 檢查是否超時(10分鐘無互動則重置)
226
  if time.time() - booking["last_update"] > 600:
227
  reset_booking(user_id)
228
  booking = get_user_booking(user_id)
229
  current_state = booking["state"]
230
 
231
+ # 更新最後互動時間
232
  booking["last_update"] = time.time()
233
 
234
  # 取消預約
235
+ if user_message in ["取消", "取消預約", "重來", "重新開始"]:
236
  reset_booking(user_id)
237
+ return "✅ 已取消預約流程。\n\n如需重新預約,請輸入「預約」。"
238
 
239
+ # 開始預約流程
240
  if current_state == BookingState.IDLE:
241
  if is_booking_keyword(user_message):
242
  booking["state"] = BookingState.ASKING_DATE
243
+ return (
244
+ "🎹 歡迎使用琴房預約系統!\n\n"
245
+ "📅 請問您想要預約哪一天呢?\n"
246
+ "例如:明天、12/26、2025/12/30\n\n"
247
+ "💡 隨時輸入「取消」可取消預約"
248
+ )
249
  else:
250
+ return None # 不是預約,交給 AI 處理
251
 
252
+ # 詢問日期
253
  elif current_state == BookingState.ASKING_DATE:
254
  parsed_date, error = parse_date(user_message)
255
  if error:
256
  return error
257
  booking["date"] = parsed_date
258
  booking["state"] = BookingState.ASKING_TIME
259
+ return (
260
+ f"✅ 預約日期:{parsed_date}\n\n"
261
+ f"⏰ 請問您想要預約哪個時段呢?\n"
262
+ f"例如:09:00-11:00、下午2點到4點、14:00-16:00"
263
+ )
264
 
265
+ # 詢問時段
266
  elif current_state == BookingState.ASKING_TIME:
267
  is_valid, error = validate_time(user_message)
268
  if not is_valid:
269
  return error
270
  booking["time"] = user_message
271
  booking["state"] = BookingState.ASKING_PEOPLE
272
+ return (
273
+ f"✅ 預約時段:{user_message}\n\n"
274
+ f"👥 請問有幾位使用呢?\n"
275
+ f"例如:1人、2人、3位"
276
+ )
277
 
278
+ # 詢問人數
279
  elif current_state == BookingState.ASKING_PEOPLE:
280
  people_count, error = parse_people(user_message)
281
  if error:
282
  return error
283
  booking["people"] = people_count
284
  booking["state"] = BookingState.ASKING_ROOM
285
+ return (
286
+ f"✅ 使用人數:{people_count}人\n\n"
287
+ f"🎹 請問您想使用哪個琴房呢?\n"
288
+ f"可選擇:{', '.join(AVAILABLE_ROOMS)}\n\n"
289
+ f"💡 如不指定可輸入「任意」"
290
+ )
291
 
292
+ # 詢問琴房
293
  elif current_state == BookingState.ASKING_ROOM:
294
  room, error = validate_room(user_message)
295
  if error:
 
298
  booking["state"] = BookingState.CONFIRMING
299
 
300
  summary = (
301
+ f"📋 請確認您的預約資訊:\n"
302
+ f"{'='*25}\n"
303
  f"📅 日期:{booking['date']}\n"
304
  f"⏰ 時段:{booking['time']}\n"
305
  f"👥 人數:{booking['people']}人\n"
306
+ f"🎹 琴房:{booking['room']}\n"
307
+ f"{'='*25}\n\n"
308
+ f"✅ 請輸入「確認」送出預約\n"
309
+ f"❌ 輸入「取消」重新填寫"
310
  )
311
  return summary
312
 
313
+ # 確認預約
314
  elif current_state == BookingState.CONFIRMING:
315
+ if user_message in ["確認", "確定", "送出", "ok", "OK", "沒錯", "正確"]:
316
  # 儲存預約資料
317
+ try:
318
+ booking_id = save_booking_to_file(user_id, booking)
319
+
320
+ summary = (
321
+ f"🎉 預約成功!\n"
322
+ f"{'='*25}\n"
323
+ f"📅 日期:{booking['date']}\n"
324
+ f" 時段:{booking['time']}\n"
325
+ f"👥 人數:{booking['people']}人\n"
326
+ f"🎹 琴房:{booking['room']}\n"
327
+ f"📝 預約編號:{booking_id}\n"
328
+ f"{'='*25}\n\n"
329
+ f"✅ 我們已收到您的預約!\n"
330
+ f"服務專員將盡快與您聯繫確認。\n\n"
331
+ f"如有任何問題,歡迎隨時詢問!"
332
+ )
333
+
334
+ # 重置狀態
335
+ reset_booking(user_id)
336
+ return summary
337
+ except Exception as e:
338
+ logger.error(f"儲存預約失敗: {str(e)}")
339
+ reset_booking(user_id)
340
+ return "❌ 預約儲存失敗,請稍後再試或聯繫服務專員。"
341
  else:
342
  return "請輸入「確認」來完成預約,或輸入「取消」重新開始。"
343
 
344
+ return None
345
+
346
+ # ============== FastAPI 應用 ==============
347
+ app = FastAPI()
348
+
349
+ app.add_middleware(
350
+ CORSMiddleware,
351
+ allow_origins=["*"],
352
+ allow_credentials=True,
353
+ allow_methods=["*"],
354
+ allow_headers=["*"],
355
+ )
356
+
357
+ @app.get("/")
358
+ def root():
359
+ return {
360
+ "title": "Line Bot with Booking System",
361
+ "status": "running",
362
+ "version": "2.0",
363
+ "pdf_loaded": len(pdf_content) > 0,
364
+ "pdf_length": len(pdf_content),
365
+ "active_bookings": len(user_booking_state),
366
+ "features": ["AI Q&A", "Booking System", "PDF Knowledge Base"]
367
+ }
368
+
369
+ @app.get("/health")
370
+ def health_check():
371
+ """健康檢查端點"""
372
+ return {
373
+ "status": "healthy",
374
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
375
+ "pdf_loaded": len(pdf_content) > 0,
376
+ "api_configured": bool(GOOGLE_API_KEY)
377
+ }
378
+
379
+ @app.get("/bookings")
380
+ def get_bookings():
381
+ """查看所有預約(管理用)"""
382
+ bookings_file = pathlib.Path("bookings/bookings.json")
383
+ if bookings_file.exists():
384
+ with open(bookings_file, 'r', encoding='utf-8') as f:
385
+ bookings = json.load(f)
386
+ return {"total": len(bookings), "bookings": bookings}
387
+ return {"total": 0, "bookings": []}
388
+
389
+ @app.post("/webhook")
390
+ async def webhook(
391
+ request: Request,
392
+ background_tasks: BackgroundTasks,
393
+ x_line_signature=Header(None),
394
+ ):
395
+ body = await request.body()
396
+ try:
397
+ background_tasks.add_task(
398
+ line_handler.handle, body.decode("utf-8"), x_line_signature
399
+ )
400
+ except InvalidSignatureError:
401
+ logger.error("無效的簽章")
402
+ raise HTTPException(status_code=400, detail="Invalid signature")
403
+ return "ok"
404
+
405
+ @line_handler.add(MessageEvent, message=TextMessage)
406
+ def handle_message(event):
407
+ global working_status
408
+
409
+ if event.type != "message" or event.message.type != "text":
410
+ return
411
+
412
+ user_message = event.message.text.strip()
413
+ user_id = event.source.user_id
414
+
415
+ logger.info(f"收到訊息 - 使用者: {user_id[:8]}..., 內容: {user_message[:50]}")
416
+
417
+ # 特殊指令
418
+ if user_message == "再見":
419
+ line_bot_api.reply_message(
420
+ event.reply_token,
421
+ TextSendMessage(text="👋 Bye! 期待下次為您服務!")
422
+ )
423
+ return
424
+
425
+ # 速率限制
426
+ current_time = time.time()
427
+ if user_id in last_request_time:
428
+ elapsed = current_time - last_request_time[user_id]
429
+ if elapsed < REQUEST_COOLDOWN:
430
+ line_bot_api.reply_message(
431
+ event.reply_token,
432
+ TextSendMessage(text=f"⏰ 請稍等 {REQUEST_COOLDOWN - int(elapsed)} 秒後再提問")
433
+ )
434
+ return
435
+
436
+ last_request_time[user_id] = current_time
437
+
438
+ # ============== 優先處理預約流程 ==============
439
+ booking_response = handle_booking_flow(user_id, user_message)
440
+
441
+ if booking_response:
442
+ # 預約流程有回應,直接返回
443
+ line_bot_api.reply_message(
444
+ event.reply_token,
445
+ TextSendMessage(text=booking_response)
446
+ )
447
+ return
448
+
449
+ # ============== 一般問答(使用 Gemini) ==============
450
+ if working_status:
451
+ try:
452
+ logger.info(f"呼叫 Gemini API - 使用者提問: {user_message[:50]}")
453
+
454
+ response = client.models.generate_content(
455
+ model="gemini-1.5-flash",
456
+ contents=user_message,
457
+ config=generation_config
458
+ )
459
+
460
+ if response and response.text:
461
+ out = response.text
462
+ logger.info(f"AI 回應成功 - 長度: {len(out)} 字元")
463
+ else:
464
+ out = "抱歉,我無法回答這個問題。請聯繫服務專員。"
465
+ logger.warning("Gemini 回應為空")
466
+
467
+ except Exception as e:
468
+ error_msg = str(e)
469
+ logger.error(f"Gemini API 錯誤: {error_msg}")
470
+
471
+ if "quota" in error_msg.lower() or "limit" in error_msg.lower() or "resource_exhausted" in error_msg.lower():
472
+ out = "⚠️ 系統忙碌中,請稍後再試或聯繫服務專員。"
473
+ elif "429" in error_msg:
474
+ out = "⚠️ 請求過於頻繁,請稍後再試。"
475
+ elif "key" in error_msg.lower() or "auth" in error_msg.lower():
476
+ out = "⚠️ 系統設定錯誤,請聯繫服務專員。"
477
+ else:
478
+ out = "⚠️ 系統暫時無法處理,請聯繫服務專員。"
479
+
480
+ line_bot_api.reply_message(
481
+ event.reply_token,
482
+ TextSendMessage(text=out)
483
+ )
484
+
485
+ if __name__ == "__main__":
486
+ import uvicorn
487
+ logger.info("="*50)
488
+ logger.info("啟動琴房預約 LINE Bot")
489
+ logger.info(f"PDF 內容長度: {len(pdf_content)} 字元")
490
+ logger.info(f"可用琴房: {', '.join(AVAILABLE_ROOMS)}")
491
+ logger.info("="*50)
492
+ uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)