File size: 6,994 Bytes
55b1be9
4123c19
6cbeb06
4ca65f6
217ceea
f91846a
4123c19
d244aaf
 
6fa6ba2
b6cee01
754b9f8
d390efa
 
 
 
 
98dc21e
0919e0a
98dc21e
 
0919e0a
98dc21e
4123c19
1ce0901
 
98dc21e
4123c19
 
 
98dc21e
55b1be9
b59435f
98dc21e
4123c19
55b1be9
 
 
df5bf2a
55b1be9
 
 
 
4123c19
55b1be9
6482098
a7c9894
2cf7795
4123c19
6482098
4123c19
 
 
 
 
 
0a52d39
3a2e91a
4123c19
34287b9
 
 
3a2e91a
4123c19
34287b9
0385c62
4123c19
8ff7523
4123c19
 
 
 
883cea6
b92f1fc
8b31299
fbc391f
e4477cb
ee25523
ab5a246
daa20a4
d09dee4
 
ab5a246
d09dee4
 
 
83355d0
 
 
 
 
 
 
 
 
 
 
 
 
78521f1
83355d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ab5a246
 
 
 
 
0919e0a
 
 
 
 
 
ab5a246
 
 
 
 
 
 
e868d4f
ab5a246
e4477cb
 
 
 
 
 
 
0919e0a
 
 
 
 
 
e868d4f
 
 
 
 
 
 
ab5a246
e4477cb
 
 
 
 
 
 
 
 
 
 
d390efa
b92f1fc
c0ca9ac
4123c19
3bf6983
4123c19
98dc21e
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
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, Request,  Header, BackgroundTasks, HTTPException, status
from fastapi.staticfiles import StaticFiles
from google import genai
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageSendMessage, AudioMessage, ImageMessage
import json, os
import io
import PIL.Image
from Image_text_generation import Image_text_Generator
from Uploading_images_file import get_image_url, store_user_message, analyze_with_gemini, get_previous_message

#==========================
#  API 金鑰
#==========================

# 設定 Google AI API 金鑰
client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))

# 設定生成文字的參數
generation_config = genai.types.GenerateContentConfig(max_output_tokens=256, temperature=0.5, top_p=0.5, top_k=16)

# 設定 Line Bot 的 API 金鑰和秘密金鑰
line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"])
line_handler = WebhookHandler(os.environ["CHANNEL_SECRET"])

# 設定是否正在與使用者交談
working_status = os.getenv("DEFALUT_TALKING", default = "true").lower() == "true"

# 建立 FastAPI 應用程式
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")

# 設定 CORS,允許跨域請求
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 處理根路徑請求
@app.get("/")
def root():
    return {"title": "Line Bot"}

# 處理 Line Webhook 請求
@app.post("/webhook")
async def webhook(
    request: Request,
    background_tasks: BackgroundTasks,
    x_line_signature=Header(None),
):
    # 取得請求內容
    body = await request.body()
    try:
        # 將處理 Line 事件的任務加入背景工作
        background_tasks.add_task(
            line_handler.handle, body.decode("utf-8"), x_line_signature
        )
    except InvalidSignatureError:
        # 處理無效的簽章錯誤
        raise HTTPException(status_code=400, detail="Invalid signature")
    return "ok"
    

#==========================
# 主程式(圖片與文字)
#==========================
# 建立 chat_sessions 字典
chat_sessions = {}
@line_handler.add(MessageEvent, message=(ImageMessage, TextMessage))
def handle_image_message(event):
    user_id = event.source.user_id
    user_text = event.message.text if event.message.type == "text" else None
    previous_message = get_previous_message(user_id)

    if event.message.type != "text" and event.message.type != "image":
        line_bot_api.reply_message(event.reply_token, TextSendMessage(text="請輸入文字或圖片~"))
        return
    elif user_text == "再見":
        line_bot_api.reply_message(event.reply_token, TextSendMessage(text="Bye!"))
        return
        
    # ========
    # 生成圖片
    # ========
    elif user_text and user_text.startswith("生成圖片"):
        prompt = user_text.replace("生成圖片", "").strip()

         # 先立即回覆避免token過期
        line_bot_api.reply_message(event.reply_token, TextSendMessage(text="圖片生成中~ 請稍候.....✨"))
        image_generator = Image_text_Generator(user_id)
        # 生成圖片
        image_binary = image_generator.generate_image_with_gemini(prompt)

        if image_binary:
            image_url = os.getenv("HF_SPACE")+image_generator.upload_image_to_tmp(image_binary) ### os.getenv("HF_SPACE") => https://alanchen1115-linebot.hf.space/

            if image_url:
                # 使用 push message 發送圖片,避免 reply token 超時
                line_bot_api.push_message(
                    event.source.user_id,
                    [
                        TextSendMessage(text="✨ 這是我為你生成的圖片喔~"),
                        ImageSendMessage(original_content_url=image_url, preview_image_url=image_url)
                    ]
                )
            else:
                line_bot_api.push_message(
                    event.source.user_id,
                    TextSendMessage(text="⚠️ 圖片上傳失敗,請稍後再試~")
                )
        else:
            line_bot_api.push_message(
                event.source.user_id,
                TextSendMessage(text="⚠️ 圖片生成失敗,請稍後再試~")
            )
        return
        
    # ========
    # 純文字
    # ========
    elif event.message.type == "text" and previous_message["type"] != "image":
        try: 
            user_id = event.source.user_id
            chat = chat_sessions.get(user_id) or client.chats.create(model="gemini-2.0-flash", config=generation_config)
            chat_sessions[user_id] = chat
            # 取得使用者輸入的文字
            user_input = event.message.text
            response = chat.send_message(user_input)
            if (response.text != None):
                out = response.text
            else:
                out = "Gemini沒答案!請換個說法!"
        except:
            # 處理錯誤
            out = "Gemini執行出錯!請換個說法!" 
        
    elif previous_message and previous_message["type"] == "image" and event.message.type == "text":
        image_path = previous_message["content"]
        user_text = event.message.text
        store_user_message(user_id, "text", user_text)
        try:
            if not os.path.exists(image_path):
                raise FileNotFoundError(f"圖片路徑無效:{image_path}")
            previous_img = PIL.Image.open(image_path)
            user_id = event.source.user_id
            chat = chat_sessions.get(user_id) or client.chats.create(model="gemini-2.0-flash", config=generation_config)
            chat_sessions[user_id] = chat
            # 取得使用者輸入的文字
            user_input = event.message.text
            response = chat.send_message([previous_img, user_input])
            if (response.text != None):
                out = response.text
            else:
                out = "Gemini沒答案!請換個說法!"
        except:
            # 處理錯誤
            out = "Gemini執行出錯!請換個說法!" 

    # ========
    # 上傳圖片
    # ========
    elif event.message.type == "image":
        image_path = get_image_url(event.message.id)
        if image_path:
            store_user_message(user_id, "image", image_path)
            line_bot_api.reply_message(event.reply_token, TextSendMessage(text="圖片已接收成功囉,幫我輸入你想詢問的問題喔~"))
        else:
            line_bot_api.reply_message(event.reply_token, TextSendMessage(text="沒有接收到圖片~"))
        return

    line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))


if __name__ == "__main__":
    # 啟動 FastAPI 應用程式
    uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)