Spaces:
Runtime error
Runtime error
Update main.py
Browse files
main.py
CHANGED
|
@@ -9,10 +9,15 @@ import requests
|
|
| 9 |
import base64
|
| 10 |
from collections import defaultdict
|
| 11 |
import uvicorn
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
# 檢查環境變數
|
| 14 |
-
if not all(k in os.environ for k in ["CHANNEL_ACCESS_TOKEN", "CHANNEL_SECRET", "GOOGLE_API_KEY", "
|
| 15 |
-
|
|
|
|
| 16 |
|
| 17 |
# Line Bot API 設定
|
| 18 |
line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"])
|
|
@@ -32,7 +37,7 @@ user_states = defaultdict(lambda: {
|
|
| 32 |
MAX_IMAGES_PER_TYPE = 3
|
| 33 |
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent"
|
| 34 |
GOOGLE_API_KEY = os.environ["GOOGLE_API_KEY"]
|
| 35 |
-
|
| 36 |
|
| 37 |
app = FastAPI()
|
| 38 |
|
|
@@ -62,25 +67,33 @@ async def webhook(
|
|
| 62 |
return "ok"
|
| 63 |
|
| 64 |
def get_base64_image(message_id: str):
|
|
|
|
| 65 |
message_content = line_bot_api.get_message_content(message_id)
|
| 66 |
image_bytes = message_content.content
|
| 67 |
return base64.b64encode(image_bytes).decode('utf-8')
|
| 68 |
|
| 69 |
-
def
|
| 70 |
"""
|
| 71 |
-
將 Base64 編碼的圖片上傳到
|
| 72 |
"""
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
try:
|
| 76 |
-
response = requests.post(
|
| 77 |
response.raise_for_status()
|
| 78 |
-
|
|
|
|
|
|
|
| 79 |
except requests.exceptions.RequestException as e:
|
| 80 |
-
|
| 81 |
return None
|
| 82 |
|
| 83 |
def get_gemini_response(prompt: str, images: list):
|
|
|
|
| 84 |
payload = {
|
| 85 |
"contents": [
|
| 86 |
{
|
|
@@ -93,17 +106,28 @@ def get_gemini_response(prompt: str, images: list):
|
|
| 93 |
]
|
| 94 |
}
|
| 95 |
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
def get_virtual_tryon_image(user_photo, upper_body_image, lower_body_image):
|
| 103 |
"""
|
| 104 |
模擬虛擬試穿 API,返回一個試穿後的圖片 URL。
|
| 105 |
在實際應用中,這裡會呼叫一個運行 IDM-VTON 的獨立服務。
|
| 106 |
"""
|
|
|
|
| 107 |
# 這裡的邏輯需要您自行實現,可以是呼叫一個公開的 API,
|
| 108 |
# 或者將圖片傳輸到一個運行 IDM-VTON 的伺服器。
|
| 109 |
# 由於沒有實際的 IDM-VTON 服務,我們使用佔位符圖片來展示功能。
|
|
@@ -115,12 +139,14 @@ def handle_text_message(event):
|
|
| 115 |
text = event.message.text.lower()
|
| 116 |
reply_token = event.reply_token
|
| 117 |
|
|
|
|
|
|
|
| 118 |
# 新增 重置 功能
|
| 119 |
-
if text
|
| 120 |
user_states[user_id] = defaultdict(lambda: {"upper_body_images": [], "lower_body_images": [], "current_mode": None, "is_ready_for_outfit": False, "is_ready_for_photo": False, "user_info": {}, "personal_photo": None})[user_id]
|
| 121 |
line_bot_api.reply_message(
|
| 122 |
reply_token,
|
| 123 |
-
TextSendMessage(text="
|
| 124 |
)
|
| 125 |
return
|
| 126 |
|
|
@@ -150,6 +176,7 @@ def handle_text_message(event):
|
|
| 150 |
)
|
| 151 |
return
|
| 152 |
except ValueError:
|
|
|
|
| 153 |
line_bot_api.reply_message(
|
| 154 |
reply_token,
|
| 155 |
TextSendMessage(text="請依照格式輸入:身高,三圍胸圍,腰圍,臀圍,場合。例如:165,85,65,90,約會")
|
|
@@ -184,8 +211,11 @@ def handle_image_message(event):
|
|
| 184 |
user_id = event.source.user_id
|
| 185 |
reply_token = event.reply_token
|
| 186 |
|
|
|
|
|
|
|
| 187 |
# 檢查是否已準備好處理圖片
|
| 188 |
if not user_states[user_id]["is_ready_for_outfit"]:
|
|
|
|
| 189 |
line_bot_api.reply_message(
|
| 190 |
reply_token,
|
| 191 |
TextSendMessage(text="請先輸入個人資訊,格式為:身高,胸圍,腰圍,臀圍,場合。例如:165,85,65,90,約會")
|
|
@@ -197,8 +227,8 @@ def handle_image_message(event):
|
|
| 197 |
try:
|
| 198 |
image_id = event.message.id
|
| 199 |
base64_img = get_base64_image(image_id)
|
| 200 |
-
# 將個人照片上傳到
|
| 201 |
-
photo_url =
|
| 202 |
user_states[user_id]["personal_photo"] = photo_url
|
| 203 |
user_states[user_id]["is_ready_for_photo"] = True
|
| 204 |
|
|
@@ -250,6 +280,7 @@ def handle_image_message(event):
|
|
| 250 |
return
|
| 251 |
|
| 252 |
except Exception as e:
|
|
|
|
| 253 |
line_bot_api.reply_message(
|
| 254 |
reply_token,
|
| 255 |
TextSendMessage(text=f"圖片處理失敗,請稍後再試。錯誤:{e}")
|
|
@@ -259,6 +290,7 @@ def handle_image_message(event):
|
|
| 259 |
# 處理衣物圖片上傳
|
| 260 |
mode = user_states[user_id]["current_mode"]
|
| 261 |
if not mode:
|
|
|
|
| 262 |
line_bot_api.reply_message(
|
| 263 |
reply_token,
|
| 264 |
TextSendMessage(text="請先傳送「上衣」或「褲子」來選擇要上傳的衣服類型。")
|
|
@@ -268,12 +300,13 @@ def handle_image_message(event):
|
|
| 268 |
try:
|
| 269 |
image_id = event.message.id
|
| 270 |
base64_img = get_base64_image(image_id)
|
| 271 |
-
# 上傳圖片到
|
| 272 |
-
image_url =
|
| 273 |
if not image_url:
|
|
|
|
| 274 |
line_bot_api.reply_message(
|
| 275 |
reply_token,
|
| 276 |
-
TextSendMessage(text="
|
| 277 |
)
|
| 278 |
return
|
| 279 |
|
|
@@ -281,6 +314,7 @@ def handle_image_message(event):
|
|
| 281 |
if len(user_states[user_id]["upper_body_images"]) < MAX_IMAGES_PER_TYPE:
|
| 282 |
user_states[user_id]["upper_body_images"].append(image_url)
|
| 283 |
else:
|
|
|
|
| 284 |
line_bot_api.reply_message(
|
| 285 |
reply_token,
|
| 286 |
TextSendMessage(text="上衣數量已滿,請傳送「褲子」來上傳褲子圖片。")
|
|
@@ -290,6 +324,7 @@ def handle_image_message(event):
|
|
| 290 |
if len(user_states[user_id]["lower_body_images"]) < MAX_IMAGES_PER_TYPE:
|
| 291 |
user_states[user_id]["lower_body_images"].append(image_url)
|
| 292 |
else:
|
|
|
|
| 293 |
line_bot_api.reply_message(
|
| 294 |
reply_token,
|
| 295 |
TextSendMessage(text="褲子數量已滿,請傳送「上衣」來上傳上衣圖片。")
|
|
@@ -305,12 +340,14 @@ def handle_image_message(event):
|
|
| 305 |
TextSendMessage(text=f"已接收。上衣: {upper_count}/{MAX_IMAGES_PER_TYPE},褲子/裙子: {lower_count}/{MAX_IMAGES_PER_TYPE}。")
|
| 306 |
)
|
| 307 |
else:
|
|
|
|
| 308 |
line_bot_api.reply_message(
|
| 309 |
reply_token,
|
| 310 |
TextSendMessage(text="已收到所有衣物圖片!接下來,請上傳一張您個人的全身照片,以便進行虛擬試穿。")
|
| 311 |
)
|
| 312 |
|
| 313 |
except Exception as e:
|
|
|
|
| 314 |
line_bot_api.reply_message(
|
| 315 |
reply_token,
|
| 316 |
TextSendMessage(text=f"圖片處理失敗,請稍後再試。錯誤:{e}")
|
|
|
|
| 9 |
import base64
|
| 10 |
from collections import defaultdict
|
| 11 |
import uvicorn
|
| 12 |
+
import logging
|
| 13 |
+
|
| 14 |
+
# 設置日誌記錄
|
| 15 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 16 |
|
| 17 |
# 檢查環境變數
|
| 18 |
+
if not all(k in os.environ for k in ["CHANNEL_ACCESS_TOKEN", "CHANNEL_SECRET", "GOOGLE_API_KEY", "IMGBB_API_KEY"]):
|
| 19 |
+
logging.error("Missing environment variables. Please set CHANNEL_ACCESS_TOKEN, CHANNEL_SECRET, GOOGLE_API_KEY, and IMGBB_API_KEY.")
|
| 20 |
+
raise ValueError("Missing environment variables. Please set CHANNEL_ACCESS_TOKEN, CHANNEL_SECRET, GOOGLE_API_KEY, and IMGBB_API_KEY.")
|
| 21 |
|
| 22 |
# Line Bot API 設定
|
| 23 |
line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"])
|
|
|
|
| 37 |
MAX_IMAGES_PER_TYPE = 3
|
| 38 |
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent"
|
| 39 |
GOOGLE_API_KEY = os.environ["GOOGLE_API_KEY"]
|
| 40 |
+
IMGBB_API_KEY = os.environ["IMGBB_API_KEY"]
|
| 41 |
|
| 42 |
app = FastAPI()
|
| 43 |
|
|
|
|
| 67 |
return "ok"
|
| 68 |
|
| 69 |
def get_base64_image(message_id: str):
|
| 70 |
+
logging.info(f"Fetching image content for message ID: {message_id}")
|
| 71 |
message_content = line_bot_api.get_message_content(message_id)
|
| 72 |
image_bytes = message_content.content
|
| 73 |
return base64.b64encode(image_bytes).decode('utf-8')
|
| 74 |
|
| 75 |
+
def upload_to_imgbb(image_base64: str):
|
| 76 |
"""
|
| 77 |
+
將 Base64 編碼的圖片上傳到 Imgbb。
|
| 78 |
"""
|
| 79 |
+
logging.info("Attempting to upload image to Imgbb.")
|
| 80 |
+
url = "https://api.imgbb.com/1/upload"
|
| 81 |
+
payload = {
|
| 82 |
+
"key": IMGBB_API_KEY,
|
| 83 |
+
"image": image_base64
|
| 84 |
+
}
|
| 85 |
try:
|
| 86 |
+
response = requests.post(url, data=payload)
|
| 87 |
response.raise_for_status()
|
| 88 |
+
imgbb_link = response.json()["data"]["url"]
|
| 89 |
+
logging.info(f"Image successfully uploaded to Imgbb: {imgbb_link}")
|
| 90 |
+
return imgbb_link
|
| 91 |
except requests.exceptions.RequestException as e:
|
| 92 |
+
logging.error(f"Error uploading to Imgbb: {e}")
|
| 93 |
return None
|
| 94 |
|
| 95 |
def get_gemini_response(prompt: str, images: list):
|
| 96 |
+
logging.info("Sending request to Gemini API.")
|
| 97 |
payload = {
|
| 98 |
"contents": [
|
| 99 |
{
|
|
|
|
| 106 |
]
|
| 107 |
}
|
| 108 |
|
| 109 |
+
try:
|
| 110 |
+
response = requests.post(f"{GEMINI_API_URL}?key={GOOGLE_API_KEY}", json=payload, timeout=60) # Increased timeout
|
| 111 |
+
response.raise_for_status()
|
| 112 |
+
response_data = response.json()
|
| 113 |
+
if 'candidates' in response_data and response_data['candidates']:
|
| 114 |
+
gemini_text = response_data['candidates'][0]['content']['parts'][0]['text']
|
| 115 |
+
logging.info("Successfully received response from Gemini.")
|
| 116 |
+
return gemini_text
|
| 117 |
+
else:
|
| 118 |
+
logging.error(f"Gemini API response has no candidates: {response.text}")
|
| 119 |
+
return "Gemini API 暫時無法提供服務,請稍後再試。"
|
| 120 |
+
except requests.exceptions.RequestException as e:
|
| 121 |
+
logging.error(f"Gemini API request failed: {e}")
|
| 122 |
+
return f"Gemini API 請求失敗:{e}"
|
| 123 |
+
|
| 124 |
|
| 125 |
def get_virtual_tryon_image(user_photo, upper_body_image, lower_body_image):
|
| 126 |
"""
|
| 127 |
模擬虛擬試穿 API,返回一個試穿後的圖片 URL。
|
| 128 |
在實際應用中,這裡會呼叫一個運行 IDM-VTON 的獨立服務。
|
| 129 |
"""
|
| 130 |
+
logging.info("Simulating virtual try-on and returning a placeholder image.")
|
| 131 |
# 這裡的邏輯需要您自行實現,可以是呼叫一個公開的 API,
|
| 132 |
# 或者將圖片傳輸到一個運行 IDM-VTON 的伺服器。
|
| 133 |
# 由於沒有實際的 IDM-VTON 服務,我們使用佔位符圖片來展示功能。
|
|
|
|
| 139 |
text = event.message.text.lower()
|
| 140 |
reply_token = event.reply_token
|
| 141 |
|
| 142 |
+
logging.info(f"Received text message from user {user_id}: '{text}'")
|
| 143 |
+
|
| 144 |
# 新增 重置 功能
|
| 145 |
+
if text in ["重置", "重來", "重新��始", "再一次"]:
|
| 146 |
user_states[user_id] = defaultdict(lambda: {"upper_body_images": [], "lower_body_images": [], "current_mode": None, "is_ready_for_outfit": False, "is_ready_for_photo": False, "user_info": {}, "personal_photo": None})[user_id]
|
| 147 |
line_bot_api.reply_message(
|
| 148 |
reply_token,
|
| 149 |
+
TextSendMessage(text="狀態已重置。請重新輸入個人資訊,格式為:身高,胸圍,腰圍,臀圍,場合。")
|
| 150 |
)
|
| 151 |
return
|
| 152 |
|
|
|
|
| 176 |
)
|
| 177 |
return
|
| 178 |
except ValueError:
|
| 179 |
+
logging.error(f"Invalid user info format: '{text}'")
|
| 180 |
line_bot_api.reply_message(
|
| 181 |
reply_token,
|
| 182 |
TextSendMessage(text="請依照格式輸入:身高,三圍胸圍,腰圍,臀圍,場合。例如:165,85,65,90,約會")
|
|
|
|
| 211 |
user_id = event.source.user_id
|
| 212 |
reply_token = event.reply_token
|
| 213 |
|
| 214 |
+
logging.info(f"Received image message from user {user_id}")
|
| 215 |
+
|
| 216 |
# 檢查是否已準備好處理圖片
|
| 217 |
if not user_states[user_id]["is_ready_for_outfit"]:
|
| 218 |
+
logging.warning("User is not in outfit collection mode.")
|
| 219 |
line_bot_api.reply_message(
|
| 220 |
reply_token,
|
| 221 |
TextSendMessage(text="請先輸入個人資訊,格式為:身高,胸圍,腰圍,臀圍,場合。例如:165,85,65,90,約會")
|
|
|
|
| 227 |
try:
|
| 228 |
image_id = event.message.id
|
| 229 |
base64_img = get_base64_image(image_id)
|
| 230 |
+
# 將個人照片上傳到 Imgbb,並儲存 URL
|
| 231 |
+
photo_url = upload_to_imgbb(base64_img)
|
| 232 |
user_states[user_id]["personal_photo"] = photo_url
|
| 233 |
user_states[user_id]["is_ready_for_photo"] = True
|
| 234 |
|
|
|
|
| 280 |
return
|
| 281 |
|
| 282 |
except Exception as e:
|
| 283 |
+
logging.error(f"Error processing personal photo: {e}")
|
| 284 |
line_bot_api.reply_message(
|
| 285 |
reply_token,
|
| 286 |
TextSendMessage(text=f"圖片處理失敗,請稍後再試。錯誤:{e}")
|
|
|
|
| 290 |
# 處理衣物圖片上傳
|
| 291 |
mode = user_states[user_id]["current_mode"]
|
| 292 |
if not mode:
|
| 293 |
+
logging.warning("User is not in a defined upload mode.")
|
| 294 |
line_bot_api.reply_message(
|
| 295 |
reply_token,
|
| 296 |
TextSendMessage(text="請先傳送「上衣」或「褲子」來選擇要上傳的衣服類型。")
|
|
|
|
| 300 |
try:
|
| 301 |
image_id = event.message.id
|
| 302 |
base64_img = get_base64_image(image_id)
|
| 303 |
+
# 上傳圖片到 Imgbb,並儲存 URL
|
| 304 |
+
image_url = upload_to_imgbb(base64_img)
|
| 305 |
if not image_url:
|
| 306 |
+
logging.error("Failed to upload image to Imgbb.")
|
| 307 |
line_bot_api.reply_message(
|
| 308 |
reply_token,
|
| 309 |
+
TextSendMessage(text="圖片上傳失敗,請稍後再試。")
|
| 310 |
)
|
| 311 |
return
|
| 312 |
|
|
|
|
| 314 |
if len(user_states[user_id]["upper_body_images"]) < MAX_IMAGES_PER_TYPE:
|
| 315 |
user_states[user_id]["upper_body_images"].append(image_url)
|
| 316 |
else:
|
| 317 |
+
logging.info("Upper body image count is full.")
|
| 318 |
line_bot_api.reply_message(
|
| 319 |
reply_token,
|
| 320 |
TextSendMessage(text="上衣數量已滿,請傳送「褲子」來上傳褲子圖片。")
|
|
|
|
| 324 |
if len(user_states[user_id]["lower_body_images"]) < MAX_IMAGES_PER_TYPE:
|
| 325 |
user_states[user_id]["lower_body_images"].append(image_url)
|
| 326 |
else:
|
| 327 |
+
logging.info("Lower body image count is full.")
|
| 328 |
line_bot_api.reply_message(
|
| 329 |
reply_token,
|
| 330 |
TextSendMessage(text="褲子數量已滿,請傳送「上衣」來上傳上衣圖片。")
|
|
|
|
| 340 |
TextSendMessage(text=f"已接收。上衣: {upper_count}/{MAX_IMAGES_PER_TYPE},褲子/裙子: {lower_count}/{MAX_IMAGES_PER_TYPE}。")
|
| 341 |
)
|
| 342 |
else:
|
| 343 |
+
logging.info("All outfit images received. Prompting for personal photo.")
|
| 344 |
line_bot_api.reply_message(
|
| 345 |
reply_token,
|
| 346 |
TextSendMessage(text="已收到所有衣物圖片!接下來,請上傳一張您個人的全身照片,以便進行虛擬試穿。")
|
| 347 |
)
|
| 348 |
|
| 349 |
except Exception as e:
|
| 350 |
+
logging.error(f"Error processing outfit image: {e}")
|
| 351 |
line_bot_api.reply_message(
|
| 352 |
reply_token,
|
| 353 |
TextSendMessage(text=f"圖片處理失敗,請稍後再試。錯誤:{e}")
|