hazelhh commited on
Commit
66bc22e
·
verified ·
1 Parent(s): 1239bc0

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +242 -144
main.py CHANGED
@@ -1,11 +1,6 @@
1
- import os
2
- import io
3
- from collections import defaultdict
4
  from fastapi.middleware.cors import CORSMiddleware
5
- from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException
6
  from fastapi.staticfiles import StaticFiles
7
- from google import genai
8
- from google.genai import types
9
  from linebot import LineBotApi, WebhookHandler
10
  from linebot.exceptions import InvalidSignatureError
11
  from linebot.models import (
@@ -15,36 +10,66 @@ from linebot.models import (
15
  ImageSendMessage,
16
  ImageMessage,
17
  )
18
- import PIL.Image
 
 
 
 
 
 
19
  import uvicorn
20
-
21
- # LangChain 相關匯入
22
  from langchain_core.prompts import ChatPromptTemplate
23
  from langchain_core.tools import tool
24
  from langchain_google_genai import ChatGoogleGenerativeAI
25
  from langchain.agents import AgentExecutor, create_tool_calling_agent
26
 
 
 
 
27
 
28
- # ==========================
29
- # 環境設定與工具函式
30
- # ==========================
 
31
 
32
- # 設置 Google AI API 金鑰
33
  google_api = os.environ["GOOGLE_API_KEY"]
 
 
 
 
 
 
 
 
 
 
34
  genai_client = genai.Client(api_key=google_api)
35
 
36
- # 設置 Line Bot 的 API 金鑰和秘密金鑰
37
- line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"])
38
- line_handler = WebhookHandler(os.environ["CHANNEL_SECRET"])
 
 
 
 
 
 
 
 
39
 
40
- # 使用字典模擬用戶訊息歷史存儲
41
- user_message_history = defaultdict(list)
 
42
 
43
  # 建立 FastAPI 應用程式
44
  app = FastAPI()
 
 
45
  app.mount("/static", StaticFiles(directory="static"), name="static")
46
 
47
- # 設定 CORS
48
  app.add_middleware(
49
  CORSMiddleware,
50
  allow_origins=["*"],
@@ -53,100 +78,134 @@ app.add_middleware(
53
  allow_headers=["*"],
54
  )
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  def get_image_url_from_line(message_id):
57
  """
58
  從 Line 訊息 ID 獲取圖片內容並儲存到暫存檔案。
59
  """
60
  try:
61
  message_content = line_bot_api.get_message_content(message_id)
62
- file_path = f"/tmp/{message_id}.png"
63
- with open(file_path, "wb") as f:
64
- for chunk in message_content.iter_content():
65
- f.write(chunk)
66
- print(f"✅ 圖片成功儲存到:{file_path}")
67
- return file_path
68
  except Exception as e:
69
  print(f"❌ 圖片取得失敗:{e}")
70
  return None
71
 
72
- def store_user_message(user_id, message_type, message_content):
73
- """
74
- 儲存用戶的訊息。
75
- """
76
- user_message_history[user_id].append(
77
- {"type": message_type, "content": message_content}
78
- )
79
-
80
- def get_previous_message(user_id):
81
- """
82
- 獲取用戶的上一則訊息。
83
- """
84
- if user_id in user_message_history and len(user_message_history[user_id]) > 0:
85
- return user_message_history[user_id][-1]
86
- return {"type": "text", "content": "No message!"}
87
-
88
-
89
- # ==========================
90
- # LangChain 工具定義
91
- # ==========================
92
-
93
  @tool
94
- def generate_and_upload_image(prompt: str) -> str:
95
  """
96
- 這個工具可以根據文字提示生成圖片,並將其上傳到伺服器。
97
-
98
  Args:
99
- prompt: 用於生成圖片的文字提示。
100
-
 
101
  Returns:
102
  回傳生成圖片的 URL。
103
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  try:
105
- response = genai_client.models.generate_content(
106
- model="gemini-2.0-flash-preview-image-generation",
107
- contents=prompt,
108
- config=types.GenerateContentConfig(response_modalities=['Text', 'Image'])
109
- )
110
-
111
- image_binary = None
112
- for part in response.candidates[0].content.parts:
113
- if part.inline_data is not None:
114
- image_binary = part.inline_data.data
115
- break
116
-
117
- if image_binary:
118
- image = PIL.Image.open(io.BytesIO(image_binary))
119
- # 隨機生成一個檔案名以避免衝突
120
- file_name = f"static/{os.urandom(16).hex()}.png"
121
- image.save(file_name, format="PNG")
122
-
123
- image_url = os.path.join(os.getenv("HF_SPACE"), file_name)
124
- return image_url
125
-
126
- return "圖片生成失敗。"
127
- except Exception as e:
128
  return f"圖片生成與上傳失敗: {e}"
129
 
130
  @tool
131
- def analyze_image_with_text(image_path: str, user_text: str) -> str:
132
  """
133
  這個工具可以根據圖片和文字提示來回答問題。
134
-
135
  Args:
136
- image_path: 圖片在本地端儲存的路徑。
137
  user_text: 針對圖片提出的文字問題。
138
-
139
  Returns:
140
  模型針對圖片和文字提示給出的回應。
141
  """
142
  try:
143
- if not os.path.exists(image_path):
144
  return "圖片路徑無效,無法進行分析。"
145
-
146
- img_user = PIL.Image.open(image_path)
 
 
147
  response = genai_client.models.generate_content(
148
- model="gemini-2.5-flash",
149
- contents=[img_user, user_text]
150
  )
151
  if (response.text != None):
152
  out = response.text
@@ -155,36 +214,10 @@ def analyze_image_with_text(image_path: str, user_text: str) -> str:
155
  except Exception as e:
156
  # 處理錯誤
157
  out = f"Gemini執行出錯: {e}"
158
-
159
  return out
160
 
161
-
162
- # ==========================
163
- # LangChain 代理人設定
164
- # ==========================
165
-
166
- # 結合所有工具
167
- tools = [generate_and_upload_image, analyze_image_with_text]
168
-
169
- # 建立 LLM 模型實例
170
- llm = ChatGoogleGenerativeAI(google_api_key=google_api, model="gemini-2.5-flash", temperature=0.2)
171
-
172
- # 建立提示模板
173
- prompt_template = ChatPromptTemplate([
174
- ("system", "你是一個強大的圖像生成與問答助理,可以根據用戶的請求使用提供的工具。當你執行 generate_and_upload_image 工具\
175
- 成功後會獲得一個 URL,然後你回答的 output 要包含有這個 URL 的完整資訊。如果工具有產生錯誤訊息請解讀並回應。"),
176
- ("user", "{input}"),
177
- ("placeholder", "{agent_scratchpad}"),
178
- ])
179
-
180
- # 建立代理人
181
- agent = create_tool_calling_agent(llm, tools, prompt_template)
182
- agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
183
-
184
- # ==========================
185
- # FastAPI 路由
186
- # ==========================
187
-
188
  @app.get("/")
189
  def root():
190
  return {"title": "Line Bot"}
@@ -210,53 +243,118 @@ def handle_message(event):
210
 
211
  # 處理圖片上傳
212
  if event.message.type == "image":
213
- image_path = get_image_url_from_line(event.message.id)
214
- if image_path:
215
- store_user_message(user_id, "image", image_path)
216
- line_bot_api.reply_message(
217
- event.reply_token, TextSendMessage(text="圖片已接收成功囉,幫我輸入你想詢問的問題喔~")
218
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  else:
220
  line_bot_api.reply_message(
221
  event.reply_token, TextSendMessage(text="沒有接收到圖片~")
222
  )
223
-
224
  # 處理文字訊息
225
  elif event.message.type == "text":
226
- user_text = event.message.text
227
- previous_message = get_previous_message(user_id)
228
- print(previous_message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
- # 根據上一則訊息類型,動態傳遞給代理人
231
- if previous_message["type"] == "image":
232
- image_path = previous_message["content"]
233
- agent_input = {
234
- "input": f"請根據這張圖片回答問題。圖片的路徑是 {image_path},我的問題是:{user_text}"
235
- }
236
- # 清除上一則圖片訊息,避免重複觸發
237
- user_message_history[user_id].pop()
238
- else:
239
- agent_input = {"input": user_text}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  try:
241
  # 運行代理人
242
  response = agent_executor.invoke(agent_input)
243
  out = response["output"]
244
- if 'https' in out:
245
- img_tmp = 'https'+out.split('https')[1]
246
- image_url = img_tmp.split('png')[0]+'png'
247
- line_bot_api.push_message(
248
- event.source.user_id,
249
- [
250
- TextSendMessage(text="✨ 這是我為你生成的圖片喔~"),
251
- ImageSendMessage(original_content_url=image_url, preview_image_url=image_url)
252
- ]
253
- )
254
- else:
255
- line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
256
  except Exception as e:
257
  print(f"代理人執行出錯: {e}")
258
  out = f"代理人執行出錯!錯誤訊息:{e}"
259
  line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
260
 
261
  if __name__ == "__main__":
262
- uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
 
1
+ from fastapi import FastAPI, Request, Header, BackgroundTasks, HTTPException, status
 
 
2
  from fastapi.middleware.cors import CORSMiddleware
 
3
  from fastapi.staticfiles import StaticFiles
 
 
4
  from linebot import LineBotApi, WebhookHandler
5
  from linebot.exceptions import InvalidSignatureError
6
  from linebot.models import (
 
10
  ImageSendMessage,
11
  ImageMessage,
12
  )
13
+ from google import genai
14
+ from google.genai import types
15
+ from PIL import Image
16
+ from collections import defaultdict
17
+ import os
18
+ import io
19
+ import requests
20
  import uvicorn
21
+ import logging
 
22
  from langchain_core.prompts import ChatPromptTemplate
23
  from langchain_core.tools import tool
24
  from langchain_google_genai import ChatGoogleGenerativeAI
25
  from langchain.agents import AgentExecutor, create_tool_calling_agent
26
 
27
+ # ==========================# 環境設定與工具函式# ==========================#
28
+ # 設置日誌記錄,級別為 INFO,格式包含時間、級別和訊息
29
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
30
 
31
+ # 檢查必要的環境變數是否都已設定
32
+ if not all(k in os.environ for k in ["CHANNEL_ACCESS_TOKEN", "CHANNEL_SECRET", "GOOGLE_API_KEY", "IMGBB_API_KEY", "HF_SPACE"]):
33
+ logging.error("Missing environment variables. Please set CHANNEL_ACCESS_TOKEN, CHANNEL_SECRET, GOOGLE_API_KEY, IMGBB_API_KEY, and HF_SPACE.")
34
+ raise ValueError("Missing environment variables. Please set CHANNEL_ACCESS_TOKEN, CHANNEL_SECRET, GOOGLE_API_KEY, IMGBB_API_KEY, and HF_SPACE.")
35
 
36
+ # 獲取環境變數中的金鑰和 URL
37
  google_api = os.environ["GOOGLE_API_KEY"]
38
+ line_channel_access_token = os.environ["CHANNEL_ACCESS_TOKEN"]
39
+ line_channel_secret = os.environ["CHANNEL_SECRET"]
40
+ IMGBB_API_KEY = os.environ["IMGBB_API_KEY"]
41
+ HF_SPACE_URL = os.environ["HF_SPACE"]
42
+
43
+ # Line Bot API 設定
44
+ line_bot_api = LineBotApi(line_channel_access_token)
45
+ line_handler = WebhookHandler(line_channel_secret)
46
+
47
+ # Google AI API 設定
48
  genai_client = genai.Client(api_key=google_api)
49
 
50
+ # 使用者狀態追蹤,用來儲存已上傳的衣物圖片 URL
51
+ user_states = defaultdict(lambda: {
52
+ "upper_body_images": [],
53
+ "lower_body_images": [],
54
+ "current_mode": None,
55
+ "is_ready_for_outfit": False,
56
+ "is_ready_for_photo": False,
57
+ "user_info": {},
58
+ "personal_photo": None,
59
+ "personal_photo_base64": None
60
+ })
61
 
62
+ MAX_IMAGES_PER_TYPE = 3
63
+ GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent"
64
+ IMAGIN_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent"
65
 
66
  # 建立 FastAPI 應用程式
67
  app = FastAPI()
68
+
69
+ # 設定靜態文件服務,用於託管圖片
70
  app.mount("/static", StaticFiles(directory="static"), name="static")
71
 
72
+ # 設定 CORS 跨域請求
73
  app.add_middleware(
74
  CORSMiddleware,
75
  allow_origins=["*"],
 
78
  allow_headers=["*"],
79
  )
80
 
81
+ def get_base64_from_url(image_url: str):
82
+ """從 URL 下載圖片並轉換為 Base64 編碼。"""
83
+ try:
84
+ response = requests.get(image_url)
85
+ response.raise_for_status()
86
+ return base64.b64encode(response.content).decode('utf-8')
87
+ except requests.exceptions.RequestException as e:
88
+ logging.error(f"Error fetching image from URL: {image_url}, error: {e}")
89
+ return None
90
+
91
+ def save_image_locally(image_binary: bytes):
92
+ """
93
+ 將二進位圖片資料儲存到本地,並返回一個可供外部存取的 URL。
94
+ """
95
+ try:
96
+ # 確保 'static' 資料夾存在
97
+ if not os.path.exists("static"):
98
+ os.makedirs("static")
99
+
100
+ image = Image.open(io.BytesIO(image_binary))
101
+ # 隨機生成一個檔案名以避免衝突
102
+ file_name = f"static/{os.urandom(16).hex()}.png"
103
+ image.save(file_name, format="PNG")
104
+
105
+ image_url = os.path.join(HF_SPACE_URL, file_name)
106
+ logging.info(f"Image successfully saved locally: {image_url}")
107
+ return image_url
108
+ except Exception as e:
109
+ logging.error(f"Error saving image locally: {e}")
110
+ return None
111
+
112
  def get_image_url_from_line(message_id):
113
  """
114
  從 Line 訊息 ID 獲取圖片內容並儲存到暫存檔案。
115
  """
116
  try:
117
  message_content = line_bot_api.get_message_content(message_id)
118
+ # 取得二進位圖片資料
119
+ image_binary = message_content.content
120
+ # 使用 save_image_locally 函式儲存圖片並取得 URL
121
+ return save_image_locally(image_binary)
 
 
122
  except Exception as e:
123
  print(f"❌ 圖片取得失敗:{e}")
124
  return None
125
 
126
+ # ==========================# LangChain 工具定義# ==========================#
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  @tool
128
+ def generate_outfit_from_clothes(upper_body_urls: list, lower_body_urls: list) -> str:
129
  """
130
+ 這個工具可以根據提供的上衣和褲子/裙子圖片 URLs,生成一套全新的穿搭圖片。
131
+
132
  Args:
133
+ upper_body_urls: 一組上衣圖片的 URL 列表。
134
+ lower_body_urls: 一組褲子/裙子圖片的 URL 列表。
135
+
136
  Returns:
137
  回傳生成圖片的 URL。
138
  """
139
+ logging.info("Attempting to generate a new outfit image.")
140
+ prompt = "使用提供的上衣和褲子/裙子圖片,生成一套完整且時尚的穿搭圖片。請將衣服呈現在一個有模特兒穿著或是在平面上呈現的完整畫面中。風格應與提供的衣物相符。"
141
+
142
+ all_images_base64 = (
143
+ [get_base64_from_url(url) for url in upper_body_urls] +
144
+ [get_base64_from_url(url) for url in lower_body_urls]
145
+ )
146
+
147
+ payload = {
148
+ "contents": [
149
+ {
150
+ "parts": [
151
+ {"text": prompt}
152
+ ] + [
153
+ {"inlineData": {"mimeType": "image/jpeg", "data": img_base64}} for img_base64 in all_images_base64
154
+ ]
155
+ }
156
+ ],
157
+ "generationConfig": {
158
+ "responseModalities": ['IMAGE']
159
+ }
160
+ }
161
+
162
  try:
163
+ response = requests.post(f"{IMAGIN_API_URL}?key={os.environ['GOOGLE_API_KEY']}", json=payload, timeout=120)
164
+ response.raise_for_status()
165
+ response_data = response.json()
166
+ if 'candidates' in response_data and response_data['candidates']:
167
+ image_part = response_data['candidates'][0]['content']['parts'][0]
168
+ if image_part and 'inlineData' in image_part:
169
+ generated_image_base64 = image_part['inlineData']['data']
170
+ generated_image_url = save_image_locally(base64.b64decode(generated_image_base64))
171
+ if generated_image_url:
172
+ logging.info(f"Successfully generated and saved new outfit image: {generated_image_url}")
173
+ return generated_image_url
174
+ else:
175
+ logging.error("Failed to save generated outfit image locally.")
176
+ return "圖片生成失敗,無法保存到伺服器。"
177
+ else:
178
+ logging.error("Gemini image response format is invalid for outfit generation.")
179
+ return "圖片生成失敗,回應格式不正確。"
180
+ else:
181
+ logging.error(f"Gemini outfit generation response has no candidates: {response.text}")
182
+ return "圖片生成失敗,請稍後再試。"
183
+ except requests.exceptions.RequestException as e:
184
+ logging.error(f"Gemini outfit generation API request failed: {e}")
 
185
  return f"圖片生成與上傳失敗: {e}"
186
 
187
  @tool
188
+ def analyze_image_with_text(image_url: str, user_text: str) -> str:
189
  """
190
  這個工具可以根據圖片和文字提示來回答問題。
191
+
192
  Args:
193
+ image_url: 圖片的 URL。
194
  user_text: 針對圖片提出的文字問題。
195
+
196
  Returns:
197
  模型針對圖片和文字提示給出的回應。
198
  """
199
  try:
200
+ if not image_url:
201
  return "圖片路徑無效,無法進行分析。"
202
+
203
+ image_bytes = requests.get(image_url).content
204
+ img_user = Image.open(io.BytesIO(image_bytes))
205
+
206
  response = genai_client.models.generate_content(
207
+ model="gemini-2.5-flash",
208
+ contents=[img_user, user_text]
209
  )
210
  if (response.text != None):
211
  out = response.text
 
214
  except Exception as e:
215
  # 處理錯誤
216
  out = f"Gemini執行出錯: {e}"
217
+
218
  return out
219
 
220
+ # ==========================# FastAPI 路由# ==========================#
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  @app.get("/")
222
  def root():
223
  return {"title": "Line Bot"}
 
243
 
244
  # 處理圖片上傳
245
  if event.message.type == "image":
246
+ image_url = get_image_url_from_line(event.message.id)
247
+ if image_url:
248
+ mode = user_states[user_id]["current_mode"]
249
+ if not mode:
250
+ line_bot_api.reply_message(
251
+ event.reply_token, TextSendMessage(text="請先輸入「上衣」或「褲子」來選擇要上傳的衣服類型。")
252
+ )
253
+ return
254
+
255
+ if mode == "upper":
256
+ if len(user_states[user_id]["upper_body_images"]) < MAX_IMAGES_PER_TYPE:
257
+ user_states[user_id]["upper_body_images"].append(image_url)
258
+ reply_text = f"已接收上衣。上衣: {len(user_states[user_id]['upper_body_images'])}/{MAX_IMAGES_PER_TYPE},褲子/裙子: {len(user_states[user_id]['lower_body_images'])}/{MAX_IMAGES_PER_TYPE}。"
259
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply_text))
260
+ else:
261
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text="上衣數量已滿。"))
262
+ else: # mode == "lower"
263
+ if len(user_states[user_id]["lower_body_images"]) < MAX_IMAGES_PER_TYPE:
264
+ user_states[user_id]["lower_body_images"].append(image_url)
265
+ reply_text = f"已接收褲子/裙子。上衣: {len(user_states[user_id]['upper_body_images'])}/{MAX_IMAGES_PER_TYPE},褲子/裙子: {len(user_states[user_id]['lower_body_images'])}/{MAX_IMAGES_PER_TYPE}。"
266
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply_text))
267
+ else:
268
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text="褲子/裙子數量已滿。"))
269
+
270
+ # 檢查是否所有圖片都已上傳完畢
271
+ if (len(user_states[user_id]["upper_body_images"]) == MAX_IMAGES_PER_TYPE and
272
+ len(user_states[user_id]["lower_body_images"]) == MAX_IMAGES_PER_TYPE):
273
+ line_bot_api.push_message(
274
+ user_id,
275
+ TextSendMessage(text="所有衣物圖片已收集完畢!\n\n現在您可以輸入「**生成穿搭**」或「**圖片推薦**」來獲得一套新的圖片穿搭。")
276
+ )
277
  else:
278
  line_bot_api.reply_message(
279
  event.reply_token, TextSendMessage(text="沒有接收到圖片~")
280
  )
281
+
282
  # 處理文字訊息
283
  elif event.message.type == "text":
284
+ user_text = event.message.text.lower()
285
+ reply_token = event.reply_token
286
+
287
+ # 處理重置功能
288
+ if user_text in ["重置", "重來", "重新開始", "再一次"]:
289
+ 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]
290
+ line_bot_api.reply_message(
291
+ reply_token, TextSendMessage(text="狀態已重置。請重新輸入個人資訊,格式為:身高,胸圍,腰圍,臀圍,場合。例如:165,85,65,90,約會")
292
+ )
293
+ return
294
+
295
+ # 處理衣物上傳模式切換
296
+ if user_text == "上衣":
297
+ user_states[user_id]["current_mode"] = "upper"
298
+ line_bot_api.reply_message(
299
+ reply_token, TextSendMessage(text=f"請上傳三件上衣圖片,您已上傳 {len(user_states[user_id]['upper_body_images'])}/{MAX_IMAGES_PER_TYPE} 張。")
300
+ )
301
+ return
302
+ elif user_text in ["褲子", "裙子"]:
303
+ user_states[user_id]["current_mode"] = "lower"
304
+ line_bot_api.reply_message(
305
+ reply_token, TextSendMessage(text=f"請上傳三件褲子/裙子圖片,您已上傳 {len(user_states[user_id]['lower_body_images'])}/{MAX_IMAGES_PER_TYPE} 張。")
306
+ )
307
+ return
308
 
309
+ # 處理圖片生成穿搭
310
+ if user_text in ["生成穿搭", "圖片推薦"]:
311
+ if (len(user_states[user_id]["upper_body_images"]) == MAX_IMAGES_PER_TYPE and
312
+ len(user_states[user_id]["lower_body_images"]) == MAX_IMAGES_PER_TYPE):
313
+ try:
314
+ line_bot_api.reply_message(reply_token, TextSendMessage(text="好的,正在為您生成一套新的穿搭圖片,請稍候..."))
315
+
316
+ # 直接呼叫生成工具,並傳入已收集的圖片 URLs
317
+ generated_image_url = generate_outfit_from_clothes(
318
+ user_states[user_id]["upper_body_images"],
319
+ user_states[user_id]["lower_body_images"]
320
+ )
321
+
322
+ if generated_image_url:
323
+ line_bot_api.push_message(
324
+ user_id,
325
+ ImageSendMessage(original_content_url=generated_image_url, preview_image_url=generated_image_url)
326
+ )
327
+ line_bot_api.push_message(
328
+ user_id,
329
+ TextSendMessage(text="這是根據您的衣物生成的圖片推薦。如果想再次使用,請輸入「重置」。")
330
+ )
331
+ else:
332
+ line_bot_api.push_message(user_id, TextSendMessage(text="圖片生成失敗,請稍後再試。"))
333
+
334
+ except Exception as e:
335
+ logging.error(f"Error generating outfit image: {e}")
336
+ line_bot_api.push_message(user_id, TextSendMessage(text=f"圖片生成失敗,請稍後再試。錯誤:{e}"))
337
+
338
+ # 生成後重置狀態以便下一次使用
339
+ 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]
340
+ return
341
+ else:
342
+ line_bot_api.reply_message(
343
+ reply_token, TextSendMessage(text="請先上傳三件上衣和三件褲子/裙子圖片,再輸入「生成穿搭」來獲得圖片推薦。")
344
+ )
345
+ return
346
+
347
+ # 如果都不是特定指令,則交給代理人處理
348
+ agent_input = {"input": user_text}
349
  try:
350
  # 運行代理人
351
  response = agent_executor.invoke(agent_input)
352
  out = response["output"]
353
+ line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
 
 
 
 
 
 
 
 
 
 
 
354
  except Exception as e:
355
  print(f"代理人執行出錯: {e}")
356
  out = f"代理人執行出錯!錯誤訊息:{e}"
357
  line_bot_api.reply_message(event.reply_token, TextSendMessage(text=out))
358
 
359
  if __name__ == "__main__":
360
+ uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)