File size: 6,891 Bytes
9d3e16e
3945713
9d3e16e
60d6fec
 
 
9d3e16e
 
 
60d6fec
 
7305727
 
9d3e16e
 
7305727
71ea8b6
7305727
761d26a
7305727
9d3e16e
98091b2
7305727
d7a9a8c
9d3e16e
 
d2ac32f
d7a9a8c
289b013
7e3f748
289b013
 
 
 
1b05f97
d7a9a8c
60d6fec
0911ff4
27295ef
60d6fec
 
 
 
9d3e16e
 
 
60d6fec
79507aa
60d6fec
 
 
 
 
 
9d3e16e
60d6fec
1b05f97
7e3f748
289b013
1b05f97
289b013
d2ac32f
1b05f97
7305727
9d3e16e
60d6fec
cff9759
 
60d6fec
ac83549
9d3e16e
60d6fec
a69f1b6
 
 
7305727
9d3e16e
60d6fec
7305727
60d6fec
7305727
60d6fec
7305727
 
 
 
60d6fec
7305727
 
60d6fec
7305727
9d3e16e
60d6fec
78cdfd0
60d6fec
1b05f97
9d3e16e
 
 
 
 
b8dbdeb
a55b31d
9d3e16e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d7a9a8c
9d3e16e
 
 
3fc73f7
 
9d3e16e
3fc73f7
 
9d3e16e
3fc73f7
d7a9a8c
9d3e16e
 
 
1b05f97
 
667fd5f
9d3e16e
 
 
 
 
 
 
 
 
58ff4cc
60d6fec
58ff4cc
60d6fec
9d3e16e
911f187
9d3e16e
58ff4cc
 
 
b553789
3945713
 
 
9d3e16e
3945713
 
9d3e16e
58ff4cc
9d3e16e
 
 
 
8dca5c0
58ff4cc
cff9759
fb17c94
9d3e16e
fb17c94
60d6fec
911f187
dfbd6ee
bc129d5
9d3e16e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911f187
dfbd6ee
9d3e16e
3945713
9d3e16e
e4c9f86
 
79507aa
 
 
 
00b6c6d
9d3e16e
911f187
8dca5c0
9d3e16e
e4c9f86
9d3e16e
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# ===東吳大學資料系 2025 年 LINEBOT ===
import base64
import logging
import os
import tempfile

import markdown
from bs4 import BeautifulSoup
from flask import Flask, abort, request, send_from_directory
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 openai import OpenAI

# === 初始化OpenAI模型 ===
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
client = OpenAI(api_key=OPENAI_API_KEY)
text_system_prompt = "你是一個中文的AI助手,請用繁體中文回答"

# === 先建立第一個對話,之後可以延續這個對話 ===
response = client.responses.create(
    model="gpt-4o-mini",
    input=[{"role": "system", "content": text_system_prompt}],
)

message_id = response.id

# === 初始設定 ===
static_tmp_path = tempfile.gettempdir()
os.makedirs(static_tmp_path, exist_ok=True)
base_url = os.getenv("SPACE_HOST")  # e.g., "your-space-name.hf.space"

# === Flask 應用初始化 ===
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)


# === AI Query 包裝 ===
def query(payload, previous_response_id):
    second_response = client.responses.create(
        model="gpt-4o-mini",
        previous_response_id=previous_response_id,
        input=[{"role": "user", "content": f"{payload}"}],
    )
    return second_response


# === 靜態圖檔路由 ===
@app.route("/images/<filename>")
def serve_image(filename):
    return send_from_directory(static_tmp_path, filename)


# === LINE Webhook 接收端點 ===
@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):
    global message_id
    user_input = event.message.text.strip()
    if user_input.startswith("AI "):
        prompt = user_input[3:].strip()
        try:
            response = client.images.generate(
                model="dall-e-3",
                prompt=f"使用下面的文字來畫一幅畫:{prompt}",
                size="1024x1024",
                quality="standard",
                n=1,
            )
            image_url = response.data[0].url
            app.logger.info(image_url)
            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,
                            )
                        ],
                    )
                )
        except Exception as e:
            app.logger.error(f"DALL·E 3 API error: {e}")
            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="抱歉,生成圖像時發生錯誤。")],
                    )
                )
    else:
        with ApiClient(configuration) as api_client:
            line_bot_api = MessagingApi(api_client)
            response = query(event.message.text, previous_response_id=message_id)
            message_id = response.id
            html_msg = markdown.markdown(response.output_text)
            soup = BeautifulSoup(html_msg, "html.parser")

            line_bot_api.reply_message_with_http_info(
                ReplyMessageRequest(
                    reply_token=event.reply_token,
                    messages=[TextMessage(text=soup.get_text())],
                )
            )


# === 處理圖片訊息 ===
@handler.add(MessageEvent, message=ImageMessageContent)
def handle_image_message(event):

    # === 以下是處理圖片回傳部分 === #

    with ApiClient(configuration) as api_client:
        blob_api = MessagingApiBlob(api_client)
        content = blob_api.get_message_content(message_id=event.message.id)
        image_bytes = content

    # Step 2:轉成 base64 字串
    base64_string = base64.b64encode(image_bytes).decode("utf-8")

    # Step 3:組成 OpenAI 的 data URI 格式
    data_uri = f"data:image/png;base64,{base64_string}"
    app.logger.info(f"Data URI: {data_uri}")

    # Step 4:將圖片存到本地端
    with tempfile.NamedTemporaryFile(
        dir=static_tmp_path, suffix=".jpg", delete=False
    ) as tf:
        tf.write(content)
        filename = os.path.basename(tf.name)

    image_url = f"https://{base_url}/images/{filename}"

    app.logger.info(f"Image URL: {image_url}")

    # === 以下是處理解釋圖片部分 === #
    response = client.responses.create(
        model="gpt-4.1-nano",
        input=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "input_text",
                        "text": "describe the image in traditional chinese",
                    },
                    {
                        "type": "input_image",
                        "image_url": data_uri,
                    },
                ],
            }
        ],
    )
    app.logger.info(response.output_text)

    # === 以下是回傳圖片部分 === #

    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
                    ),
                    TextMessage(text=response.output_text),
                ],
            )
        )