Rene0119 commited on
Commit
0bc5aa3
·
1 Parent(s): e49e5d9

Restructure

Browse files
Files changed (1) hide show
  1. app.py +132 -25
app.py CHANGED
@@ -1,17 +1,24 @@
 
1
  import sqlite3
2
  import requests
3
- import os
4
- from flask import Flask, request, abort
 
 
 
 
 
5
  from linebot.v3.webhook import WebhookParser, WebhookHandler
6
- from linebot.v3.webhooks import MessageEvent, TextMessageContent
7
- from linebot.v3.messaging import MessagingApi, Configuration, ApiClient
8
  from linebot.v3.messaging.models import (
9
  TextMessage, ReplyMessageRequest,
10
  FlexMessage, FlexBubble, FlexBox, FlexText, FlexButton, URIAction,
11
- QuickReply, QuickReplyItem, LocationAction
12
  )
13
  from linebot.v3.exceptions import InvalidSignatureError
14
 
 
15
  import io
16
  import json
17
  from dotenv import load_dotenv
@@ -27,8 +34,15 @@ load_dotenv()
27
  app = Flask(__name__)
28
 
29
  # 從環境變數讀取 LINE Bot 設定
30
- LINE_CHANNEL_SECRET = os.getenv("LINE_CHANNEL_SECRET", "YOUR_FALLBACK_SECRET") # 建議提供一個預設值或錯誤處理
31
- LINE_CHANNEL_ACCESS_TOKEN = os.getenv("LINE_CHANNEL_ACCESS_TOKEN", "YOUR_FALLBACK_TOKEN")
 
 
 
 
 
 
 
32
 
33
  # --- 資料庫路徑設定 ---
34
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -93,14 +107,24 @@ if not DOWNLOAD_SUCCESS:
93
  print("CRITICAL: Database download failed or was skipped. The application might not function as expected if the database is required at startup.")
94
  # --- END 應用程式啟動時執行資料庫下載 ---
95
 
96
- print(f"LINE_CHANNEL_SECRET: {LINE_CHANNEL_SECRET}")
97
- print(f"LINE_CHANNEL_ACCESS_TOKEN: {LINE_CHANNEL_ACCESS_TOKEN}")
98
- #print(f"GOOGLE_MAP_API_KEY: {GOOGLE_MAP_API_KEY}")
99
-
100
  configuration = Configuration(access_token=LINE_CHANNEL_ACCESS_TOKEN)
101
  parser = WebhookParser(LINE_CHANNEL_SECRET)
102
  handler = WebhookHandler(LINE_CHANNEL_SECRET)
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  @app.route("/callback", methods=["POST"])
105
  def callback():
106
 
@@ -122,14 +146,32 @@ def callback():
122
 
123
  with ApiClient(configuration) as api_client:
124
  messaging_api = MessagingApi(api_client)
 
125
 
126
  for event in events:
127
  if event.type == "message":
 
128
  if event.message.type == "text":
129
  user_input = event.message.text.strip()
130
  print("📨 收到訊息:", user_input)
131
 
132
- if user_input == "查詢藥品":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  reply_text = "請輸入藥品名稱,例如:口服感冒藥"
134
  reply_request = ReplyMessageRequest(
135
  reply_token=event.reply_token,
@@ -137,6 +179,7 @@ def callback():
137
  )
138
  messaging_api.reply_message(reply_message_request=reply_request)
139
 
 
140
  elif "查詢藥局" in user_input:
141
  quick_reply = QuickReply(
142
  items=[QuickReplyItem(action=LocationAction(label="傳送我的位置"))]
@@ -147,8 +190,8 @@ def callback():
147
  )
148
  messaging_api.reply_message(reply_message_request=reply_request)
149
 
 
150
  else:
151
- # 直接當作藥品名稱查詢
152
  medicine_name = user_input.lower()
153
  try:
154
  conn = sqlite3.connect(DB_PATH)
@@ -159,20 +202,44 @@ def callback():
159
  WHERE LOWER(中文品名) LIKE ?
160
  LIMIT 3
161
  """
162
- cursor.execute(query, ('%' + medicine_name + '%',))
 
163
  rows = cursor.fetchall()
164
  conn.close()
165
-
166
- if not rows:
167
- reply_text = f"找不到與「{medicine_name}」相關的藥品。"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  else:
169
- reply_text = ""
170
- for row in rows:
171
- reply_text += (
172
- f"🔹 中文品名:{row[0]}\n"
173
- f"📌 英文品名:{row[1]}\n"
174
- f"📄 適應症:{row[2]}\n\n"
175
- )
 
 
 
 
 
 
 
176
  except Exception as e:
177
  reply_text = f"⚠️ 查詢資料時發生錯誤:{str(e)}"
178
 
@@ -182,6 +249,7 @@ def callback():
182
  )
183
  messaging_api.reply_message(reply_message_request=reply_request)
184
 
 
185
  elif event.message.type == "location":
186
  user_lat = event.message.latitude
187
  user_lng = event.message.longitude
@@ -254,8 +322,47 @@ def callback():
254
  )
255
  messaging_api.reply_message(reply_message_request=reply_request)
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  return "OK"
258
 
259
  if __name__ == "__main__":
260
  port = int(os.environ.get("PORT", 7860)) # 讀取環境變數 PORT,預設為 7860
261
- app.run(host="0.0.0.0", port=port, debug=False)
 
1
+ import os
2
  import sqlite3
3
  import requests
4
+ import tempfile
5
+ import logging
6
+ from io import BytesIO
7
+
8
+ from flask import Flask, request, abort, send_from_directory
9
+ from PIL import Image
10
+
11
  from linebot.v3.webhook import WebhookParser, WebhookHandler
12
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent, ImageMessageContent
13
+ from linebot.v3.messaging import MessagingApi, Configuration, ApiClient, MessagingApiBlob
14
  from linebot.v3.messaging.models import (
15
  TextMessage, ReplyMessageRequest,
16
  FlexMessage, FlexBubble, FlexBox, FlexText, FlexButton, URIAction,
17
+ QuickReply, QuickReplyItem, LocationAction, ImageMessage
18
  )
19
  from linebot.v3.exceptions import InvalidSignatureError
20
 
21
+ import google.generativeai as genai
22
  import io
23
  import json
24
  from dotenv import load_dotenv
 
34
  app = Flask(__name__)
35
 
36
  # 從環境變數讀取 LINE Bot 設定
37
+ LINE_CHANNEL_SECRET = os.environ.get("LINE_CHANNEL_SECRET")
38
+ LINE_CHANNEL_ACCESS_TOKEN = os.environ.get("LINE_CHANNEL_ACCESS_TOKEN")
39
+
40
+ if not LINE_CHANNEL_SECRET or not LINE_CHANNEL_ACCESS_TOKEN:
41
+ raise RuntimeError("Missing essential environment variables")
42
+
43
+ print(f"LINE_CHANNEL_SECRET: {LINE_CHANNEL_SECRET}")
44
+ print(f"LINE_CHANNEL_ACCESS_TOKEN: {LINE_CHANNEL_ACCESS_TOKEN}")
45
+ #print(f"GOOGLE_MAP_API_KEY: {GOOGLE_MAP_API_KEY}")
46
 
47
  # --- 資料庫路徑設定 ---
48
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 
107
  print("CRITICAL: Database download failed or was skipped. The application might not function as expected if the database is required at startup.")
108
  # --- END 應用程式啟動時執行資料庫下載 ---
109
 
 
 
 
 
110
  configuration = Configuration(access_token=LINE_CHANNEL_ACCESS_TOKEN)
111
  parser = WebhookParser(LINE_CHANNEL_SECRET)
112
  handler = WebhookHandler(LINE_CHANNEL_SECRET)
113
 
114
+ genai.configure(api_key=GOOGLE_API_KEY)
115
+ chat = genai.GenerativeModel(model_name="gemini-1.5-flash")
116
+
117
+ logging.basicConfig(level=logging.INFO)
118
+ app.logger.setLevel(logging.INFO)
119
+
120
+ @app.route("/images/<filename>")
121
+ def serve_image(filename):
122
+ return send_from_directory(static_tmp_path, filename)
123
+
124
+ @app.route("/")
125
+ def home():
126
+ return {"message": "Line Webhook Server"}
127
+
128
  @app.route("/callback", methods=["POST"])
129
  def callback():
130
 
 
146
 
147
  with ApiClient(configuration) as api_client:
148
  messaging_api = MessagingApi(api_client)
149
+ blob_api = MessagingApiBlob(api_client)
150
 
151
  for event in events:
152
  if event.type == "message":
153
+ # 文字訊息
154
  if event.message.type == "text":
155
  user_input = event.message.text.strip()
156
  print("📨 收到訊息:", user_input)
157
 
158
+ # AI 問答
159
+ if user_input.startswith("AI "):
160
+ prompt = "你是一個中文的AI助手,請用繁體中文回答。\n" + user_input[3:].strip()
161
+ try:
162
+ response = chat.generate_content(prompt)
163
+ reply_text = response.text
164
+ except Exception as e:
165
+ reply_text = f"AI 回答失敗:{e}"
166
+
167
+ reply_request = ReplyMessageRequest(
168
+ reply_token=event.reply_token,
169
+ messages=[TextMessage(text=reply_text)]
170
+ )
171
+ messaging_api.reply_message(reply_message_request=reply_request)
172
+
173
+ # 查詢藥品
174
+ elif user_input == "查詢藥品":
175
  reply_text = "請輸入藥品名稱,例如:口服感冒藥"
176
  reply_request = ReplyMessageRequest(
177
  reply_token=event.reply_token,
 
179
  )
180
  messaging_api.reply_message(reply_message_request=reply_request)
181
 
182
+ # 查詢藥局
183
  elif "查詢藥局" in user_input:
184
  quick_reply = QuickReply(
185
  items=[QuickReplyItem(action=LocationAction(label="傳送我的位置"))]
 
190
  )
191
  messaging_api.reply_message(reply_message_request=reply_request)
192
 
193
+ # 其他:查詢藥品資料庫,副作用永遠由AI產生
194
  else:
 
195
  medicine_name = user_input.lower()
196
  try:
197
  conn = sqlite3.connect(DB_PATH)
 
202
  WHERE LOWER(中文品名) LIKE ?
203
  LIMIT 3
204
  """
205
+ like_param = f'%{medicine_name}%'
206
+ cursor.execute(query, (like_param,))
207
  rows = cursor.fetchall()
208
  conn.close()
209
+
210
+ if rows:
211
+ zh_name, en_name, indication = rows[0]
212
+ # 副作用由 AI 產生
213
+ prompt = (
214
+ f"請用簡短條列式,僅列出副作用,針對藥品「{zh_name}」(英文名:{en_name}),"
215
+ "請用繁體中文回答,若無法判斷請推測。"
216
+ )
217
+ try:
218
+ ai_resp = chat.generate_content(prompt)
219
+ side_effects = ai_resp.text.strip()
220
+ except Exception as e:
221
+ side_effects = f"AI 回答失敗:{e}"
222
+ reply_text = (
223
+ f"🔹 中文品名:{zh_name}\n"
224
+ f"📌 英文品名:{en_name}\n"
225
+ f"📄 適應症:{indication}\n"
226
+ f"⚠️ 副作用:{side_effects}"
227
+ )
228
  else:
229
+ # 全部請AI生成
230
+ prompt = (
231
+ f"請用以下格式,幫我介紹藥品「{medicine_name}」,若無法查到請盡量推測:\n"
232
+ "🔹 中文品名:\n"
233
+ "📌 英文品名:\n"
234
+ "📄 適應症:\n"
235
+ "⚠️ 副作用:"
236
+ )
237
+ try:
238
+ ai_resp = chat.generate_content(prompt)
239
+ reply_text = ai_resp.text
240
+ except Exception as e:
241
+ reply_text = f"AI 回答失敗:{e}"
242
+
243
  except Exception as e:
244
  reply_text = f"⚠️ 查詢資料時發生錯誤:{str(e)}"
245
 
 
249
  )
250
  messaging_api.reply_message(reply_message_request=reply_request)
251
 
252
+ # 處理位置訊息(查詢附近藥局)
253
  elif event.message.type == "location":
254
  user_lat = event.message.latitude
255
  user_lng = event.message.longitude
 
322
  )
323
  messaging_api.reply_message(reply_message_request=reply_request)
324
 
325
+ # 圖片訊息:用 Gemini AI 以藥品格式解釋圖片
326
+ elif event.message.type == "image":
327
+ try:
328
+ content = blob_api.get_message_content(message_id=event.message.id)
329
+ with tempfile.NamedTemporaryFile(dir=static_tmp_path, suffix=".jpg", delete=False) as tf:
330
+ tf.write(content)
331
+ filename = os.path.basename(tf.name)
332
+ image_url = f"https://{base_url}/images/{filename}"
333
+ image = Image.open(tf.name)
334
+
335
+ # Gemini 圖片說明(指定格式,四欄都AI產生)
336
+ prompt = (
337
+ "請根據這張圖片判斷藥品資訊,並用以下格式回答,若無法判斷請盡量推測:\n"
338
+ "🔹 中文品名:\n"
339
+ "📌 英文品名:\n"
340
+ "📄 適應症:\n"
341
+ "⚠️ 副作用:"
342
+ )
343
+ response = chat.generate_content([image, prompt])
344
+ description = response.text
345
+
346
+ reply_request = ReplyMessageRequest(
347
+ reply_token=event.reply_token,
348
+ messages=[
349
+ ImageMessage(
350
+ original_content_url=image_url,
351
+ preview_image_url=image_url
352
+ ),
353
+ TextMessage(text=description)
354
+ ]
355
+ )
356
+ messaging_api.reply_message(reply_message_request=reply_request)
357
+ except Exception as e:
358
+ reply_request = ReplyMessageRequest(
359
+ reply_token=event.reply_token,
360
+ messages=[TextMessage(text=f"圖片處理失敗:{e}")]
361
+ )
362
+ messaging_api.reply_message(reply_message_request=reply_request)
363
+
364
  return "OK"
365
 
366
  if __name__ == "__main__":
367
  port = int(os.environ.get("PORT", 7860)) # 讀取環境變數 PORT,預設為 7860
368
+ app.run(host="0.0.0.0", port=port, debug=False)