File size: 5,140 Bytes
887f465
9949813
887f465
096d13f
8f62348
7431696
9949813
 
 
f3ead48
b8e6e12
 
f3ead48
 
9949813
 
 
 
 
 
 
 
 
 
 
f3ead48
 
 
 
 
 
b8e6e12
8f62348
9949813
887f465
 
f3ead48
 
8f62348
 
 
f1dd422
f3ead48
79d4f9b
f3ead48
2b0943a
8da82cb
8f62348
8da82cb
f1dd422
f3ead48
 
 
887f465
bbbbe16
887f465
 
84e80ee
887f465
 
f1dd422
887f465
 
9949813
 
f1dd422
9949813
 
 
887f465
f3ead48
8f62348
887f465
 
 
 
 
 
 
9949813
 
 
 
 
 
 
 
 
 
bbbbe16
9949813
 
 
 
 
f1dd422
 
26174f2
8f62348
 
 
180e4eb
8f62348
 
f1dd422
9949813
f3ead48
7431696
 
84e80ee
9949813
7431696
f1dd422
7431696
096d13f
7431696
 
 
 
8f62348
 
7431696
 
8f62348
9949813
7431696
8f62348
 
 
bbbbe16
8f62348
 
 
 
 
 
 
 
ce94612
8f62348
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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 字

@app.route("/images/<filename>")
def serve_image(filename):
    return send_from_directory(static_tmp_path, filename)

@app.route("/")
def home():
    return {"message": "Line Webhook Server"}

@app.route("/", methods=["POST"])
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"

@handler.add(MessageEvent, message=TextMessageContent)
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)]
            )
        )