Spaces:
Runtime error
Runtime error
Update main.py
Browse files
main.py
CHANGED
|
@@ -39,6 +39,7 @@ user_states = defaultdict(lambda: {
|
|
| 39 |
|
| 40 |
MAX_IMAGES_PER_TYPE = 3
|
| 41 |
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent"
|
|
|
|
| 42 |
GOOGLE_API_KEY = os.environ["GOOGLE_API_KEY"]
|
| 43 |
IMGBB_API_KEY = os.environ["IMGBB_API_KEY"]
|
| 44 |
|
|
@@ -97,7 +98,19 @@ def upload_to_imgbb(image_base64: str):
|
|
| 97 |
logging.error(f"Imgbb API Response: {response.status_code} - {response.text}")
|
| 98 |
return None
|
| 99 |
|
| 100 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
logging.info("Sending request to Gemini API.")
|
| 102 |
payload = {
|
| 103 |
"contents": [
|
|
@@ -112,7 +125,7 @@ def get_gemini_response(prompt: str, images: list):
|
|
| 112 |
}
|
| 113 |
|
| 114 |
try:
|
| 115 |
-
response = requests.post(f"{
|
| 116 |
response.raise_for_status()
|
| 117 |
response_data = response.json()
|
| 118 |
if 'candidates' in response_data and response_data['candidates']:
|
|
@@ -127,15 +140,57 @@ def get_gemini_response(prompt: str, images: list):
|
|
| 127 |
return f"Gemini API 請求失敗:{e}"
|
| 128 |
|
| 129 |
|
| 130 |
-
def get_virtual_tryon_image(
|
| 131 |
"""
|
| 132 |
-
|
| 133 |
"""
|
| 134 |
-
logging.info("
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
@line_handler.add(MessageEvent, message=TextMessage)
|
| 141 |
def handle_text_message(event):
|
|
@@ -196,7 +251,7 @@ def handle_text_message(event):
|
|
| 196 |
TextSendMessage(text=f"請上傳三件上衣圖片,您已上傳 {len(user_states[user_id]['upper_body_images'])}/{MAX_IMAGES_PER_TYPE} 張。")
|
| 197 |
)
|
| 198 |
return
|
| 199 |
-
elif text
|
| 200 |
user_states[user_id]["current_mode"] = "lower"
|
| 201 |
line_bot_api.reply_message(
|
| 202 |
reply_token,
|
|
@@ -262,7 +317,7 @@ def handle_image_message(event):
|
|
| 262 |
[user_states[user_id]["personal_photo_base64"]]
|
| 263 |
)
|
| 264 |
|
| 265 |
-
response_text = get_gemini_response(prompt, all_images_base64)
|
| 266 |
|
| 267 |
# 從 Gemini 建議中找出最佳搭配的衣物圖片(這裡需要更複雜的邏輯,我們使用佔位符)
|
| 268 |
# 在實際應用中,您可以設計一個提示,讓 Gemini 返回最佳搭配的圖片索引
|
|
@@ -273,7 +328,7 @@ def handle_image_message(event):
|
|
| 273 |
best_lower_body_image = user_states[user_id]["lower_body_images"][best_lower_index]
|
| 274 |
|
| 275 |
# 呼叫虛擬試穿 API,並取得結果圖片 URL
|
| 276 |
-
virtual_tryon_url = get_virtual_tryon_image(user_states[user_id]["
|
| 277 |
|
| 278 |
# 發送 Gemini 的文字建議
|
| 279 |
line_bot_api.push_message(
|
|
@@ -282,10 +337,11 @@ def handle_image_message(event):
|
|
| 282 |
)
|
| 283 |
|
| 284 |
# 發送虛擬試穿照片
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
|
|
|
| 289 |
|
| 290 |
# 重置狀態以便下一次使用
|
| 291 |
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, "personal_photo_base64": None})[user_id]
|
|
|
|
| 39 |
|
| 40 |
MAX_IMAGES_PER_TYPE = 3
|
| 41 |
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent"
|
| 42 |
+
IMAGIN_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent"
|
| 43 |
GOOGLE_API_KEY = os.environ["GOOGLE_API_KEY"]
|
| 44 |
IMGBB_API_KEY = os.environ["IMGBB_API_KEY"]
|
| 45 |
|
|
|
|
| 98 |
logging.error(f"Imgbb API Response: {response.status_code} - {response.text}")
|
| 99 |
return None
|
| 100 |
|
| 101 |
+
def get_base64_from_url(image_url: str):
|
| 102 |
+
"""
|
| 103 |
+
從 URL 下載圖片並轉換為 Base64 編碼。
|
| 104 |
+
"""
|
| 105 |
+
try:
|
| 106 |
+
response = requests.get(image_url)
|
| 107 |
+
response.raise_for_status()
|
| 108 |
+
return base64.b64encode(response.content).decode('utf-8')
|
| 109 |
+
except requests.exceptions.RequestException as e:
|
| 110 |
+
logging.error(f"Error fetching image from URL: {image_url}, error: {e}")
|
| 111 |
+
return None
|
| 112 |
+
|
| 113 |
+
def get_gemini_response(prompt: str, images: list, api_url: str):
|
| 114 |
logging.info("Sending request to Gemini API.")
|
| 115 |
payload = {
|
| 116 |
"contents": [
|
|
|
|
| 125 |
}
|
| 126 |
|
| 127 |
try:
|
| 128 |
+
response = requests.post(f"{api_url}?key={GOOGLE_API_KEY}", json=payload, timeout=120) # Increased timeout for image generation
|
| 129 |
response.raise_for_status()
|
| 130 |
response_data = response.json()
|
| 131 |
if 'candidates' in response_data and response_data['candidates']:
|
|
|
|
| 140 |
return f"Gemini API 請求失敗:{e}"
|
| 141 |
|
| 142 |
|
| 143 |
+
def get_virtual_tryon_image(user_photo_base64: str, upper_body_image_base64: str, lower_body_image_base64: str):
|
| 144 |
"""
|
| 145 |
+
使用 gemini-2.5-flash-image-preview (nanobanana) 模擬虛擬試穿。
|
| 146 |
"""
|
| 147 |
+
logging.info("Attempting virtual try-on with gemini-2.5-flash-image-preview.")
|
| 148 |
+
|
| 149 |
+
prompt = "請將提供的上衣和褲子,虛擬試穿到第一張人像照片上。結果必須看起來真實且自然,衣服的紋理和細節必須保留。"
|
| 150 |
+
|
| 151 |
+
all_images_base64 = [user_photo_base64, upper_body_image_base64, lower_body_image_base64]
|
| 152 |
+
|
| 153 |
+
payload = {
|
| 154 |
+
"contents": [
|
| 155 |
+
{
|
| 156 |
+
"parts": [
|
| 157 |
+
{"text": prompt}
|
| 158 |
+
] + [
|
| 159 |
+
{"inlineData": {"mimeType": "image/jpeg", "data": img_base64}} for img_base64 in all_images_base64
|
| 160 |
+
]
|
| 161 |
+
}
|
| 162 |
+
],
|
| 163 |
+
"generationConfig": {
|
| 164 |
+
"responseModalities": ['IMAGE']
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
try:
|
| 169 |
+
response = requests.post(f"{IMAGIN_API_URL}?key={GOOGLE_API_KEY}", json=payload, timeout=120)
|
| 170 |
+
response.raise_for_status()
|
| 171 |
+
response_data = response.json()
|
| 172 |
+
if 'candidates' in response_data and response_data['candidates']:
|
| 173 |
+
# 從回應中提取 Base64 編碼的圖片
|
| 174 |
+
image_part = response_data['candidates'][0]['content']['parts'][0]
|
| 175 |
+
if image_part and 'inlineData' in image_part:
|
| 176 |
+
generated_image_base64 = image_part['inlineData']['data']
|
| 177 |
+
# 將生成的 Base64 圖片上傳到 Imgbb
|
| 178 |
+
virtual_tryon_url = upload_to_imgbb(generated_image_base64)
|
| 179 |
+
if virtual_tryon_url:
|
| 180 |
+
logging.info(f"Successfully generated and uploaded virtual try-on image: {virtual_tryon_url}")
|
| 181 |
+
return virtual_tryon_url
|
| 182 |
+
else:
|
| 183 |
+
logging.error("Failed to upload generated image to Imgbb.")
|
| 184 |
+
return None
|
| 185 |
+
else:
|
| 186 |
+
logging.error("Gemini image response format is invalid.")
|
| 187 |
+
return None
|
| 188 |
+
else:
|
| 189 |
+
logging.error(f"Gemini image generation response has no candidates: {response.text}")
|
| 190 |
+
return None
|
| 191 |
+
except requests.exceptions.RequestException as e:
|
| 192 |
+
logging.error(f"Gemini image generation API request failed: {e}")
|
| 193 |
+
return None
|
| 194 |
|
| 195 |
@line_handler.add(MessageEvent, message=TextMessage)
|
| 196 |
def handle_text_message(event):
|
|
|
|
| 251 |
TextSendMessage(text=f"請上傳三件上衣圖片,您已上傳 {len(user_states[user_id]['upper_body_images'])}/{MAX_IMAGES_PER_TYPE} 張。")
|
| 252 |
)
|
| 253 |
return
|
| 254 |
+
elif text in ["褲子", "裙子"]:
|
| 255 |
user_states[user_id]["current_mode"] = "lower"
|
| 256 |
line_bot_api.reply_message(
|
| 257 |
reply_token,
|
|
|
|
| 317 |
[user_states[user_id]["personal_photo_base64"]]
|
| 318 |
)
|
| 319 |
|
| 320 |
+
response_text = get_gemini_response(prompt, all_images_base64, GEMINI_API_URL)
|
| 321 |
|
| 322 |
# 從 Gemini 建議中找出最佳搭配的衣物圖片(這裡需要更複雜的邏輯,我們使用佔位符)
|
| 323 |
# 在實際應用中,您可以設計一個提示,讓 Gemini 返回最佳搭配的圖片索引
|
|
|
|
| 328 |
best_lower_body_image = user_states[user_id]["lower_body_images"][best_lower_index]
|
| 329 |
|
| 330 |
# 呼叫虛擬試穿 API,並取得結果圖片 URL
|
| 331 |
+
virtual_tryon_url = get_virtual_tryon_image(user_states[user_id]["personal_photo_base64"], get_base64_from_url(best_upper_body_image), get_base64_from_url(best_lower_body_image))
|
| 332 |
|
| 333 |
# 發送 Gemini 的文字建議
|
| 334 |
line_bot_api.push_message(
|
|
|
|
| 337 |
)
|
| 338 |
|
| 339 |
# 發送虛擬試穿照片
|
| 340 |
+
if virtual_tryon_url:
|
| 341 |
+
line_bot_api.push_message(
|
| 342 |
+
user_id,
|
| 343 |
+
ImageSendMessage(original_content_url=virtual_tryon_url, preview_image_url=virtual_tryon_url)
|
| 344 |
+
)
|
| 345 |
|
| 346 |
# 重置狀態以便下一次使用
|
| 347 |
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, "personal_photo_base64": None})[user_id]
|