Spaces:
Sleeping
Sleeping
首次上傳 LINE Bot
Browse files
gemini.py
CHANGED
|
@@ -3,7 +3,7 @@ import logging
|
|
| 3 |
import os
|
| 4 |
import tempfile
|
| 5 |
import uuid
|
| 6 |
-
from io import BytesIO
|
| 7 |
|
| 8 |
import markdown
|
| 9 |
from bs4 import BeautifulSoup
|
|
@@ -44,7 +44,11 @@ google_search_tool = Tool(
|
|
| 44 |
chat = client.chats.create(
|
| 45 |
model="gemini-2.0-flash",
|
| 46 |
config=GenerateContentConfig(
|
| 47 |
-
system_instruction=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
tools=[google_search_tool],
|
| 49 |
response_modalities=["TEXT"],
|
| 50 |
)
|
|
@@ -220,63 +224,115 @@ def handle_image_message(event):
|
|
| 220 |
|
| 221 |
# === 處理影片訊息 ===
|
| 222 |
|
| 223 |
-
@handler.add(MessageEvent, message=
|
| 224 |
-
def
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
-
# 儲存影片到本地
|
| 231 |
-
if video_data is None:
|
| 232 |
-
err_msg = "抱歉,無法取得影片內容。"
|
| 233 |
-
app.logger.error(err_msg)
|
| 234 |
with ApiClient(configuration) as api_client:
|
| 235 |
line_bot_api = MessagingApi(api_client)
|
| 236 |
line_bot_api.reply_message(
|
| 237 |
ReplyMessageRequest(
|
| 238 |
reply_token=event.reply_token,
|
| 239 |
-
messages=[TextMessage(text=
|
| 240 |
)
|
| 241 |
)
|
| 242 |
-
return
|
| 243 |
-
|
| 244 |
-
with tempfile.NamedTemporaryFile(
|
| 245 |
-
dir=static_tmp_path, suffix=".mp4", delete=False
|
| 246 |
-
) as tf:
|
| 247 |
-
tf.write(video_data)
|
| 248 |
-
filename = os.path.basename(tf.name)
|
| 249 |
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
response = client.models.generate_content(
|
| 258 |
-
model="gemini-2.5-flash-preview-05-20",
|
| 259 |
-
config=types.GenerateContentConfig(
|
| 260 |
-
system_instruction="你是一個專業的影片解說員,請用繁體中文簡要說明這段影片的內容。",
|
| 261 |
-
response_modalities=["TEXT"],
|
| 262 |
-
tools=[google_search_tool],
|
| 263 |
-
),
|
| 264 |
-
contents=[video_bytes, "用繁體中文描述這段影片"],
|
| 265 |
-
)
|
| 266 |
-
description = response.text
|
| 267 |
-
except Exception as e:
|
| 268 |
-
app.logger.error(f"Gemini API error (video): {e}")
|
| 269 |
-
description = "抱歉,無法解釋這段影片內容。"
|
| 270 |
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
reply_token=event.reply_token,
|
| 277 |
-
messages=[
|
| 278 |
-
TextMessage(text=f"影片連結:{video_url}"),
|
| 279 |
-
TextMessage(text=description),
|
| 280 |
-
],
|
| 281 |
)
|
| 282 |
-
)
|
|
|
|
| 3 |
import os
|
| 4 |
import tempfile
|
| 5 |
import uuid
|
| 6 |
+
from io import BytesIO
|
| 7 |
|
| 8 |
import markdown
|
| 9 |
from bs4 import BeautifulSoup
|
|
|
|
| 44 |
chat = client.chats.create(
|
| 45 |
model="gemini-2.0-flash",
|
| 46 |
config=GenerateContentConfig(
|
| 47 |
+
system_instruction=(
|
| 48 |
+
"你是一位專業健身教練與營養顧問,擁有多年重量訓練與健身飲食指導經驗。"
|
| 49 |
+
"請使用繁體中文,針對使用者的健身問題提供專業建議,包含動作教學、訓練計畫、飲食建議與常見錯誤修正等。"
|
| 50 |
+
),
|
| 51 |
+
|
| 52 |
tools=[google_search_tool],
|
| 53 |
response_modalities=["TEXT"],
|
| 54 |
)
|
|
|
|
| 224 |
|
| 225 |
# === 處理影片訊息 ===
|
| 226 |
|
| 227 |
+
@handler.add(MessageEvent, message=TextMessageContent)
|
| 228 |
+
def handle_text_message(event):
|
| 229 |
+
user_input = event.message.text.strip()
|
| 230 |
+
|
| 231 |
+
# === 使用 Gemini 生成圖片(AI xxx) ===
|
| 232 |
+
if user_input.startswith("AI "):
|
| 233 |
+
prompt = user_input[3:].strip()
|
| 234 |
+
try:
|
| 235 |
+
response = client.models.generate_content(
|
| 236 |
+
model="gemini-2.0-flash-exp-image-generation",
|
| 237 |
+
contents=prompt,
|
| 238 |
+
config=types.GenerateContentConfig(
|
| 239 |
+
response_modalities=["TEXT", "IMAGE"]
|
| 240 |
+
),
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
for part in response.candidates[0].content.parts:
|
| 244 |
+
if part.inline_data is not None:
|
| 245 |
+
image = Image.open(BytesIO(part.inline_data.data))
|
| 246 |
+
filename = f"{uuid.uuid4().hex}.png"
|
| 247 |
+
image_path = os.path.join(static_tmp_path, filename)
|
| 248 |
+
image.save(image_path)
|
| 249 |
+
|
| 250 |
+
image_url = f"https://{base_url}/images/{filename}"
|
| 251 |
+
app.logger.info(f"Image URL: {image_url}")
|
| 252 |
+
|
| 253 |
+
with ApiClient(configuration) as api_client:
|
| 254 |
+
line_bot_api = MessagingApi(api_client)
|
| 255 |
+
line_bot_api.reply_message(
|
| 256 |
+
ReplyMessageRequest(
|
| 257 |
+
reply_token=event.reply_token,
|
| 258 |
+
messages=[
|
| 259 |
+
ImageMessage(
|
| 260 |
+
original_content_url=image_url,
|
| 261 |
+
preview_image_url=image_url,
|
| 262 |
+
)
|
| 263 |
+
],
|
| 264 |
+
)
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
except Exception as e:
|
| 268 |
+
app.logger.error(f"Gemini API error: {e}")
|
| 269 |
+
with ApiClient(configuration) as api_client:
|
| 270 |
+
line_bot_api = MessagingApi(api_client)
|
| 271 |
+
line_bot_api.reply_message(
|
| 272 |
+
ReplyMessageRequest(
|
| 273 |
+
reply_token=event.reply_token,
|
| 274 |
+
messages=[TextMessage(text="抱歉,生成圖片時發生錯誤。")],
|
| 275 |
+
)
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
# === 處理「菜單 xxx」功能 ===
|
| 279 |
+
elif user_input.startswith("菜單 "):
|
| 280 |
+
muscle_group = user_input[3:].strip()
|
| 281 |
+
if not muscle_group:
|
| 282 |
+
with ApiClient(configuration) as api_client:
|
| 283 |
+
line_bot_api = MessagingApi(api_client)
|
| 284 |
+
line_bot_api.reply_message(
|
| 285 |
+
ReplyMessageRequest(
|
| 286 |
+
reply_token=event.reply_token,
|
| 287 |
+
messages=[TextMessage(text="請輸入要安排的部位,例如:菜單 胸肌、菜單 臀部")]
|
| 288 |
+
)
|
| 289 |
+
)
|
| 290 |
+
return
|
| 291 |
+
|
| 292 |
+
# 同義詞簡化
|
| 293 |
+
synonym_map = {
|
| 294 |
+
"胸": "胸肌",
|
| 295 |
+
"腿": "腿部",
|
| 296 |
+
"肩": "肩膀",
|
| 297 |
+
"背": "背肌",
|
| 298 |
+
"手": "手臂",
|
| 299 |
+
"手臂": "手臂",
|
| 300 |
+
"核心": "核心肌群",
|
| 301 |
+
"臀": "臀部",
|
| 302 |
+
"臀部": "臀部",
|
| 303 |
+
"全身": "全身初學者",
|
| 304 |
+
"初學者": "全身初學者",
|
| 305 |
+
}
|
| 306 |
+
muscle_group = synonym_map.get(muscle_group, muscle_group)
|
| 307 |
+
|
| 308 |
+
# 發送訓練菜單請求
|
| 309 |
+
prompt = (
|
| 310 |
+
f"請依據「{muscle_group}」提供一份健身訓練菜單。"
|
| 311 |
+
"每份菜單包含 3~5 個動作,建議組數與次數,以及適當的休息時間。"
|
| 312 |
+
"請以繁體中文簡潔列出,使用條列方式排版,適合 LINE 顯示格式。"
|
| 313 |
+
)
|
| 314 |
+
response = query(prompt)
|
| 315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
with ApiClient(configuration) as api_client:
|
| 317 |
line_bot_api = MessagingApi(api_client)
|
| 318 |
line_bot_api.reply_message(
|
| 319 |
ReplyMessageRequest(
|
| 320 |
reply_token=event.reply_token,
|
| 321 |
+
messages=[TextMessage(text=response)],
|
| 322 |
)
|
| 323 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
+
# === 處理一般文字訊息 ===
|
| 326 |
+
else:
|
| 327 |
+
with ApiClient(configuration) as api_client:
|
| 328 |
+
line_bot_api = MessagingApi(api_client)
|
| 329 |
+
response = query(event.message.text)
|
| 330 |
+
html_msg = markdown.markdown(response)
|
| 331 |
+
soup = BeautifulSoup(html_msg, "html.parser")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
+
line_bot_api.reply_message_with_http_info(
|
| 334 |
+
ReplyMessageRequest(
|
| 335 |
+
reply_token=event.reply_token,
|
| 336 |
+
messages=[TextMessage(text=soup.get_text())],
|
| 337 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
)
|
|
|