Spaces:
Sleeping
Sleeping
import logging | |
import os | |
import tempfile | |
import uuid | |
from io import BytesIO | |
import markdown | |
from bs4 import BeautifulSoup | |
from flask import Flask, abort, request, send_from_directory | |
from google import genai | |
from google.genai import types | |
from google.genai.types import Tool, GenerateContentConfig, GoogleSearch | |
from linebot.v3 import WebhookHandler | |
from linebot.v3.exceptions import InvalidSignatureError | |
from linebot.v3.messaging import ( | |
ApiClient, | |
Configuration, | |
ImageMessage, | |
MessagingApi, | |
MessagingApiBlob, | |
ReplyMessageRequest, | |
TextMessage, | |
) | |
from linebot.v3.webhooks import ( | |
ImageMessageContent, | |
MessageEvent, | |
TextMessageContent, | |
) | |
from PIL import Image | |
from linebot.v3.webhooks import VideoMessageContent | |
# === 初始化 Google Gemini === | |
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") | |
client = genai.Client(api_key=GOOGLE_API_KEY) | |
google_search_tool = Tool( | |
google_search=GoogleSearch() | |
) | |
chat = client.chats.create( | |
model="gemini-2.0-flash", | |
config=GenerateContentConfig( | |
system_instruction=( | |
"你是一位專業健身教練與營養顧問,擁有多年重量訓練與健身飲食指導經驗。" | |
"請使用繁體中文,針對使用者的健身問題提供專業建議,包含動作教學、訓練計畫、飲食建議與常見錯誤修正等。回答請控制在 200 字內。" | |
), | |
tools=[google_search_tool], | |
response_modalities=["TEXT"], | |
) | |
) | |
# === 初始設定 === | |
static_tmp_path = tempfile.gettempdir() | |
os.makedirs(static_tmp_path, exist_ok=True) | |
base_url = os.getenv("SPACE_HOST") | |
app = Flask(__name__) | |
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") | |
app.logger.setLevel(logging.INFO) | |
channel_secret = os.environ.get("YOUR_CHANNEL_SECRET") | |
channel_access_token = os.environ.get("YOUR_CHANNEL_ACCESS_TOKEN") | |
configuration = Configuration(access_token=channel_access_token) | |
handler = WebhookHandler(channel_secret) | |
def query(payload): | |
response = chat.send_message(message=payload) | |
return response.text[:200] # 回覆字數上限 200 字 | |
def serve_image(filename): | |
return send_from_directory(static_tmp_path, filename) | |
def home(): | |
return {"message": "Line Webhook Server"} | |
def callback(): | |
signature = request.headers.get("X-Line-Signature") | |
body = request.get_data(as_text=True) | |
app.logger.info(f"Request body: {body}") | |
try: | |
handler.handle(body, signature) | |
except InvalidSignatureError: | |
app.logger.warning("Invalid signature. Please check channel credentials.") | |
abort(400) | |
return "OK" | |
def handle_text_message(event): | |
user_msg = event.message.text.strip() | |
if user_msg == "飲食紀錄": | |
reply = "點選下方連結開始記錄你的飲食 🍱:\nhttps://docs.google.com/forms/d/e/1FAIpQLScrNcLDvfODtj7V0IEmo_MMBUQG1LA3HfvbNsraM4-mQmJlOA/viewform?usp=dialog" | |
elif user_msg == "訓練紀錄": | |
reply = "點選下方連結記錄你的訓練 💪:\nhttps://docs.google.com/forms/d/e/1FAIpQLSc8crIyxQX-YJeaNzM5x1JUWbQA-qoQPiZB9cbqKuLOq3uQpA/viewform" | |
elif user_msg == "運動補給品": | |
reply = "推薦常見健身補給品:乳清蛋白、BCAA、肌酸與電解質飲。點此選購 👉 https://www.myprotein.tw/" | |
elif user_msg.startswith("AI "): | |
prompt = user_msg[3:].strip() | |
try: | |
response = client.models.generate_content( | |
model="gemini-2.0-flash-exp-image-generation", | |
contents=prompt, | |
config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]), | |
) | |
for part in response.candidates[0].content.parts: | |
if part.inline_data is not None: | |
image = Image.open(BytesIO(part.inline_data.data)) | |
filename = f"{uuid.uuid4().hex}.png" | |
image_path = os.path.join(static_tmp_path, filename) | |
image.save(image_path) | |
image_url = f"https://{base_url}/images/{filename}" | |
with ApiClient(configuration) as api_client: | |
line_bot_api = MessagingApi(api_client) | |
line_bot_api.reply_message( | |
ReplyMessageRequest( | |
reply_token=event.reply_token, | |
messages=[ImageMessage(original_content_url=image_url, preview_image_url=image_url)], | |
) | |
) | |
return | |
except Exception as e: | |
reply = "抱歉,生成圖片時發生錯誤。" | |
else: | |
reply = query(user_msg) | |
with ApiClient(configuration) as api_client: | |
line_bot_api = MessagingApi(api_client) | |
line_bot_api.reply_message( | |
ReplyMessageRequest( | |
reply_token=event.reply_token, | |
messages=[TextMessage(text=reply)] | |
) | |
) | |