Upload app.py
Browse files
app.py
CHANGED
|
@@ -25,18 +25,13 @@ HF_TOKEN = os.getenv("HF_TOKEN")
|
|
| 25 |
# --- 1. 資料讀取 (主要資料庫) ---
|
| 26 |
df = pd.DataFrame()
|
| 27 |
try:
|
| 28 |
-
# engine='python' 可以避免 Invalid file descriptor 錯誤
|
| 29 |
df = pd.read_csv('google_maps_rag_data.csv', engine='python')
|
| 30 |
-
|
| 31 |
-
# 清理欄位名稱 (移除前後空白)
|
| 32 |
-
df.columns = df.columns.str.strip()
|
| 33 |
|
| 34 |
def classify_mood(row):
|
| 35 |
-
# 確保轉成字串以免報錯
|
| 36 |
name_str = str(row.get('Name', ''))
|
| 37 |
rag_str = str(row.get('RAG_Content', ''))
|
| 38 |
text = (name_str + " " + rag_str).lower()
|
| 39 |
-
|
| 40 |
tags = []
|
| 41 |
rules = {
|
| 42 |
"開心/慶祝": ["牛排", "steak", "pizza", "炸", "雞排", "甜點", "蛋糕", "cake", "冰", "waffle", "吃到飽", "buffet", "burger", "bistro", "餐酒館"],
|
|
@@ -55,33 +50,27 @@ try:
|
|
| 55 |
df['Mood_Tags'] = df.apply(classify_mood, axis=1)
|
| 56 |
except Exception as e:
|
| 57 |
print(f"⚠️ 讀取 google_maps_rag_data.csv 失敗: {e}")
|
| 58 |
-
print("RAG 功能將受限")
|
| 59 |
|
| 60 |
-
# --- 2. 資料讀取 (Prompt 資料庫)
|
| 61 |
df_prompts = pd.DataFrame()
|
| 62 |
-
PROMPT_COL_NAME = 'Image_Prompt'
|
| 63 |
|
| 64 |
try:
|
| 65 |
-
# 使用 engine='python' 避免 file descriptor 錯誤
|
| 66 |
df_prompts = pd.read_csv('restaurants_prompts_v6.csv', engine='python')
|
| 67 |
-
|
| 68 |
-
# 自動清除欄位名稱的前後空白 (解決 ' Image_Prompt' 抓不到的問題)
|
| 69 |
df_prompts.columns = df_prompts.columns.str.strip()
|
| 70 |
|
| 71 |
-
# 檢查並設定 Index
|
| 72 |
if 'Name' in df_prompts.columns:
|
| 73 |
df_prompts.set_index('Name', inplace=True)
|
| 74 |
else:
|
| 75 |
-
print(f"⚠️ Prompts CSV 警告: 找不到 'Name' 欄位
|
| 76 |
|
| 77 |
-
# 檢查目標欄位是否存在
|
| 78 |
if PROMPT_COL_NAME not in df_prompts.columns:
|
| 79 |
-
print(f"⚠️ Prompts CSV 警告: 找不到 '{PROMPT_COL_NAME}' 欄位
|
| 80 |
|
| 81 |
except FileNotFoundError:
|
| 82 |
-
print("⚠️ 找不到 restaurants_prompts_v6.csv
|
| 83 |
except Exception as e:
|
| 84 |
-
print(f"⚠️ 讀取 Prompt CSV 發生
|
| 85 |
|
| 86 |
|
| 87 |
# --- RAG 建置 ---
|
|
@@ -115,19 +104,14 @@ custom_css = """
|
|
| 115 |
}
|
| 116 |
"""
|
| 117 |
|
| 118 |
-
# 圖片路徑
|
| 119 |
img_path_1 = "images/image_0.png"
|
| 120 |
img_path_2 = "images/image_1.png"
|
| 121 |
-
|
| 122 |
-
# 自動產生替代圖片
|
| 123 |
if not os.path.exists("images"): os.makedirs("images")
|
| 124 |
try:
|
| 125 |
if not os.path.exists(img_path_1): Image.new('RGB', (400, 300), color='gray').save(img_path_1)
|
| 126 |
if not os.path.exists(img_path_2): Image.new('RGB', (400, 300), color='lightblue').save(img_path_2)
|
| 127 |
-
except
|
| 128 |
-
pass # 忽略圖片建立錯誤
|
| 129 |
|
| 130 |
-
# 選項對照表
|
| 131 |
q1_options_map = {
|
| 132 |
"不要再問白癡問題了我只想吃飯": "現實主義者 (只想吃飯)",
|
| 133 |
"去過隱居生活": "隱士 (嚮往平靜)",
|
|
@@ -161,10 +145,7 @@ def get_restaurant_data(mood_score_str, food_choice):
|
|
| 161 |
elif food_choice == "吃麵": food_keyword = "麵"
|
| 162 |
|
| 163 |
candidates = df.copy()
|
| 164 |
-
|
| 165 |
-
# 確保必要欄位存在
|
| 166 |
-
if 'Mood_Tags' not in candidates.columns:
|
| 167 |
-
return df.sample(1).iloc[0], True
|
| 168 |
|
| 169 |
if target_moods:
|
| 170 |
pattern = "|".join(target_moods)
|
|
@@ -181,37 +162,29 @@ def get_restaurant_data(mood_score_str, food_choice):
|
|
| 181 |
|
| 182 |
def generate_content_with_groq(restaurant_name, restaurant_detail, user_diary, mood_score, quiz_result):
|
| 183 |
if not GROQ_API_KEY: return "⚠️ 請設定 GROQ_API_KEY (系統變數)"
|
| 184 |
-
|
| 185 |
client = OpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1")
|
| 186 |
-
|
| 187 |
system_prompt = "你是一個幽默、懂吃且善解人意的 AI 朋友。請根據使用者的日記、心情以及「中二病測驗結果」來推薦餐廳。"
|
| 188 |
-
|
| 189 |
user_msg = f"""
|
| 190 |
【使用者狀態】
|
| 191 |
心情分數:{mood_score}
|
| 192 |
心情日記:{user_diary}
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
餐廳名稱:{restaurant_name}
|
| 200 |
-
詳細資料:{restaurant_detail}
|
| 201 |
-
|
| 202 |
請完成任務:
|
| 203 |
【回應內容】:用繁體中文寫一段溫暖有趣的回覆。
|
| 204 |
- 結合「日記」與「前測人設」給予回應。
|
| 205 |
- 推薦上述餐廳,說明為��麼這家店適合他。
|
| 206 |
-
- 語氣自然,像朋友一樣。
|
| 207 |
"""
|
| 208 |
try:
|
| 209 |
response = client.chat.completions.create(
|
| 210 |
model="llama-3.3-70b-versatile",
|
| 211 |
messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_msg}]
|
| 212 |
)
|
| 213 |
-
|
| 214 |
-
return content
|
| 215 |
except Exception as e:
|
| 216 |
return f"Groq Error: {str(e)}"
|
| 217 |
|
|
@@ -222,8 +195,8 @@ def generate_image_huggingface(prompt):
|
|
| 222 |
return hf_client.text_to_image(prompt=prompt, model="stabilityai/stable-diffusion-xl-base-1.0")
|
| 223 |
except: return None
|
| 224 |
|
| 225 |
-
# --- 主邏輯 Agent ---
|
| 226 |
-
def mood_agent_logic(score_input, food_input, diary_input, quiz_state):
|
| 227 |
restaurant, is_random = get_restaurant_data(score_input, food_input)
|
| 228 |
if restaurant is None:
|
| 229 |
yield "資料庫讀取錯誤或為空", None, ""
|
|
@@ -234,49 +207,58 @@ def mood_agent_logic(score_input, food_input, diary_input, quiz_state):
|
|
| 234 |
url = str(restaurant.get('URL', f'https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(name)}'))
|
| 235 |
note = "(隨機推薦)" if is_random else ""
|
| 236 |
|
| 237 |
-
# 1. 處理 RAG
|
| 238 |
rag_info = str(restaurant.get('RAG_Content', ''))
|
| 239 |
if retriever:
|
| 240 |
try:
|
| 241 |
docs = retriever.invoke(name)
|
| 242 |
if docs: rag_info = "\n".join([d.page_content for d in docs])
|
| 243 |
-
except
|
| 244 |
-
pass
|
| 245 |
|
| 246 |
-
# 2.
|
| 247 |
-
ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, quiz_state)
|
| 248 |
-
|
| 249 |
-
final_response = f"### 🍽️ 推薦:{name} {note}\n\n{ai_text}"
|
| 250 |
-
map_html = f'<div style="text-align:center"><a href="{url}" target="_blank" style="background:#4CAF50;color:white;padding:8px 16px;border-radius:20px;text-decoration:none">🗺️ Google Map 導航</a></div>'
|
| 251 |
-
|
| 252 |
-
# 3. 先回傳文字與地圖
|
| 253 |
-
yield final_response, None, map_html
|
| 254 |
-
|
| 255 |
-
# 4. 查找 CSV 獲取 Prompt 並生成圖片 [安全讀取版]
|
| 256 |
img_prompt = f"Delicious food from {name}, cinematic lighting, 8k, photorealistic" # 預設值
|
| 257 |
-
|
| 258 |
-
|
| 259 |
if not df_prompts.empty and PROMPT_COL_NAME in df_prompts.columns:
|
| 260 |
if name in df_prompts.index:
|
| 261 |
try:
|
| 262 |
csv_prompt = df_prompts.loc[name, PROMPT_COL_NAME]
|
|
|
|
| 263 |
|
| 264 |
-
# 處理重複名稱 (如果是 Series 取第一個)
|
| 265 |
-
if isinstance(csv_prompt, pd.Series):
|
| 266 |
-
csv_prompt = csv_prompt.iloc[0]
|
| 267 |
-
|
| 268 |
-
# 檢查是否為 NaN 或空字串
|
| 269 |
if pd.notna(csv_prompt) and str(csv_prompt).strip() != "":
|
| 270 |
img_prompt = str(csv_prompt)
|
| 271 |
-
|
| 272 |
else:
|
| 273 |
-
|
| 274 |
except Exception as e:
|
| 275 |
-
|
| 276 |
else:
|
| 277 |
-
|
|
|
|
|
|
|
|
|
|
| 278 |
|
| 279 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
image_output = generate_image_huggingface(img_prompt)
|
| 281 |
yield final_response, image_output, map_html
|
| 282 |
|
|
@@ -294,22 +276,18 @@ def handle_q2_change(selected_label, current_state):
|
|
| 294 |
current_state["q2"] = q2_options_map.get(selected_label, selected_label)
|
| 295 |
return current_state, gr.Button(interactive=True, variant="primary", value="完成測驗 (前往點餐) ➔")
|
| 296 |
|
| 297 |
-
def go_to_page_2():
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
def go_to_main_app():
|
| 301 |
-
return gr.update(visible=False), gr.update(visible=True)
|
| 302 |
|
| 303 |
# ==========================================
|
| 304 |
# 4. Gradio 介面建構
|
| 305 |
# ==========================================
|
| 306 |
|
| 307 |
with gr.Blocks(title="AI 心情食堂") as demo:
|
| 308 |
-
|
| 309 |
gr.HTML(f"<style>{custom_css}</style>")
|
| 310 |
quiz_state = gr.State(value={})
|
| 311 |
|
| 312 |
-
#
|
| 313 |
with gr.Column(visible=True) as page_1_col:
|
| 314 |
gr.Markdown("### 🔮 第一題:直覺測試")
|
| 315 |
gr.Image(value=img_path_1, type="filepath", label="請觀察圖片", height=300)
|
|
@@ -317,7 +295,7 @@ with gr.Blocks(title="AI 心情食堂") as demo:
|
|
| 317 |
radio_q1 = gr.Radio(choices=list(q1_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
|
| 318 |
btn_q1_next = gr.Button("下一頁 ➔", interactive=False)
|
| 319 |
|
| 320 |
-
#
|
| 321 |
with gr.Column(visible=False) as page_2_col:
|
| 322 |
gr.Markdown("### 🔮 第二題:魔法適性")
|
| 323 |
gr.Image(value=img_path_2, type="filepath", label="請觀察圖片", height=300)
|
|
@@ -325,7 +303,7 @@ with gr.Blocks(title="AI 心情食堂") as demo:
|
|
| 325 |
radio_q2 = gr.Radio(choices=list(q2_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
|
| 326 |
btn_q2_finish = gr.Button("完成測驗 ➔", interactive=False)
|
| 327 |
|
| 328 |
-
#
|
| 329 |
with gr.Column(visible=False) as main_app_col:
|
| 330 |
gr.Markdown(f"## 🍱 AI 心情食堂導航")
|
| 331 |
gr.Markdown("測驗結束!現在,告訴我你真正想吃什麼吧!")
|
|
@@ -334,7 +312,11 @@ with gr.Blocks(title="AI 心情食堂") as demo:
|
|
| 334 |
with gr.Column(scale=1):
|
| 335 |
score_input = gr.Radio(["1 (心情差)", "2 (不太好)", "3 (普通)", "4 (不錯)", "5 (超棒)"], label="1. 心情分數", value="3 (普通)")
|
| 336 |
food_input = gr.Radio(["吃飯", "吃麵", "隨便"], label="2. 想吃什麼", value="隨便")
|
| 337 |
-
diary_input = gr.Textbox(lines=4, label="3. 心情日記", placeholder="寫下今天發生的事...
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
submit_btn = gr.Button("送出給 Agent", variant="primary")
|
| 339 |
|
| 340 |
with gr.Column(scale=1):
|
|
@@ -342,15 +324,16 @@ with gr.Blocks(title="AI 心情食堂") as demo:
|
|
| 342 |
image_output = gr.Image(label="AI 推薦美食圖", type="pil", width=400)
|
| 343 |
map_output = gr.HTML(label="地圖導航")
|
| 344 |
|
| 345 |
-
#
|
| 346 |
radio_q1.change(fn=handle_q1_change, inputs=[radio_q1, quiz_state], outputs=[quiz_state, btn_q1_next])
|
| 347 |
btn_q1_next.click(fn=go_to_page_2, outputs=[page_1_col, page_2_col])
|
| 348 |
radio_q2.change(fn=handle_q2_change, inputs=[radio_q2, quiz_state], outputs=[quiz_state, btn_q2_finish])
|
| 349 |
btn_q2_finish.click(fn=go_to_main_app, outputs=[page_2_col, main_app_col])
|
| 350 |
|
|
|
|
| 351 |
submit_btn.click(
|
| 352 |
fn=mood_agent_logic,
|
| 353 |
-
inputs=[score_input, food_input, diary_input, quiz_state],
|
| 354 |
outputs=[agent_output, image_output, map_output]
|
| 355 |
)
|
| 356 |
|
|
|
|
| 25 |
# --- 1. 資料讀取 (主要資料庫) ---
|
| 26 |
df = pd.DataFrame()
|
| 27 |
try:
|
|
|
|
| 28 |
df = pd.read_csv('google_maps_rag_data.csv', engine='python')
|
| 29 |
+
df.columns = df.columns.str.strip() # 清理欄位空白
|
|
|
|
|
|
|
| 30 |
|
| 31 |
def classify_mood(row):
|
|
|
|
| 32 |
name_str = str(row.get('Name', ''))
|
| 33 |
rag_str = str(row.get('RAG_Content', ''))
|
| 34 |
text = (name_str + " " + rag_str).lower()
|
|
|
|
| 35 |
tags = []
|
| 36 |
rules = {
|
| 37 |
"開心/慶祝": ["牛排", "steak", "pizza", "炸", "雞排", "甜點", "蛋糕", "cake", "冰", "waffle", "吃到飽", "buffet", "burger", "bistro", "餐酒館"],
|
|
|
|
| 50 |
df['Mood_Tags'] = df.apply(classify_mood, axis=1)
|
| 51 |
except Exception as e:
|
| 52 |
print(f"⚠️ 讀取 google_maps_rag_data.csv 失敗: {e}")
|
|
|
|
| 53 |
|
| 54 |
+
# --- 2. 資料讀取 (Prompt 資料庫) ---
|
| 55 |
df_prompts = pd.DataFrame()
|
| 56 |
+
PROMPT_COL_NAME = 'Image_Prompt'
|
| 57 |
|
| 58 |
try:
|
|
|
|
| 59 |
df_prompts = pd.read_csv('restaurants_prompts_v6.csv', engine='python')
|
|
|
|
|
|
|
| 60 |
df_prompts.columns = df_prompts.columns.str.strip()
|
| 61 |
|
|
|
|
| 62 |
if 'Name' in df_prompts.columns:
|
| 63 |
df_prompts.set_index('Name', inplace=True)
|
| 64 |
else:
|
| 65 |
+
print(f"⚠️ Prompts CSV 警告: 找不到 'Name' 欄位")
|
| 66 |
|
|
|
|
| 67 |
if PROMPT_COL_NAME not in df_prompts.columns:
|
| 68 |
+
print(f"⚠️ Prompts CSV 警告: 找不到 '{PROMPT_COL_NAME}' 欄位")
|
| 69 |
|
| 70 |
except FileNotFoundError:
|
| 71 |
+
print("⚠️ 找不到 restaurants_prompts_v6.csv")
|
| 72 |
except Exception as e:
|
| 73 |
+
print(f"⚠️ 讀取 Prompt CSV 發生錯誤: {e}")
|
| 74 |
|
| 75 |
|
| 76 |
# --- RAG 建置 ---
|
|
|
|
| 104 |
}
|
| 105 |
"""
|
| 106 |
|
|
|
|
| 107 |
img_path_1 = "images/image_0.png"
|
| 108 |
img_path_2 = "images/image_1.png"
|
|
|
|
|
|
|
| 109 |
if not os.path.exists("images"): os.makedirs("images")
|
| 110 |
try:
|
| 111 |
if not os.path.exists(img_path_1): Image.new('RGB', (400, 300), color='gray').save(img_path_1)
|
| 112 |
if not os.path.exists(img_path_2): Image.new('RGB', (400, 300), color='lightblue').save(img_path_2)
|
| 113 |
+
except: pass
|
|
|
|
| 114 |
|
|
|
|
| 115 |
q1_options_map = {
|
| 116 |
"不要再問白癡問題了我只想吃飯": "現實主義者 (只想吃飯)",
|
| 117 |
"去過隱居生活": "隱士 (嚮往平靜)",
|
|
|
|
| 145 |
elif food_choice == "吃麵": food_keyword = "麵"
|
| 146 |
|
| 147 |
candidates = df.copy()
|
| 148 |
+
if 'Mood_Tags' not in candidates.columns: return df.sample(1).iloc[0], True
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
if target_moods:
|
| 151 |
pattern = "|".join(target_moods)
|
|
|
|
| 162 |
|
| 163 |
def generate_content_with_groq(restaurant_name, restaurant_detail, user_diary, mood_score, quiz_result):
|
| 164 |
if not GROQ_API_KEY: return "⚠️ 請設定 GROQ_API_KEY (系統變數)"
|
|
|
|
| 165 |
client = OpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1")
|
|
|
|
| 166 |
system_prompt = "你是一個幽默、懂吃且善解人意的 AI 朋友。請根據使用者的日記、心情以及「中二病測驗結果」來推薦餐廳。"
|
|
|
|
| 167 |
user_msg = f"""
|
| 168 |
【使用者狀態】
|
| 169 |
心情分數:{mood_score}
|
| 170 |
心情日記:{user_diary}
|
| 171 |
+
【前測小遊戲結果】
|
| 172 |
+
隱藏人設:{quiz_result.get('q1', '未知')}
|
| 173 |
+
魔法:{quiz_result.get('q2', '未知')}
|
| 174 |
+
【推薦餐廳】
|
| 175 |
+
名稱:{restaurant_name}
|
| 176 |
+
資料:{restaurant_detail}
|
|
|
|
|
|
|
|
|
|
| 177 |
請完成任務:
|
| 178 |
【回應內容】:用繁體中文寫一段溫暖有趣的回覆。
|
| 179 |
- 結合「日記」與「前測人設」給予回應。
|
| 180 |
- 推薦上述餐廳,說明為��麼這家店適合他。
|
|
|
|
| 181 |
"""
|
| 182 |
try:
|
| 183 |
response = client.chat.completions.create(
|
| 184 |
model="llama-3.3-70b-versatile",
|
| 185 |
messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_msg}]
|
| 186 |
)
|
| 187 |
+
return response.choices[0].message.content
|
|
|
|
| 188 |
except Exception as e:
|
| 189 |
return f"Groq Error: {str(e)}"
|
| 190 |
|
|
|
|
| 195 |
return hf_client.text_to_image(prompt=prompt, model="stabilityai/stable-diffusion-xl-base-1.0")
|
| 196 |
except: return None
|
| 197 |
|
| 198 |
+
# --- 主邏輯 Agent (新增 debug_mode 參數) ---
|
| 199 |
+
def mood_agent_logic(score_input, food_input, diary_input, quiz_state, debug_mode):
|
| 200 |
restaurant, is_random = get_restaurant_data(score_input, food_input)
|
| 201 |
if restaurant is None:
|
| 202 |
yield "資料庫讀取錯誤或為空", None, ""
|
|
|
|
| 207 |
url = str(restaurant.get('URL', f'https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(name)}'))
|
| 208 |
note = "(隨機推薦)" if is_random else ""
|
| 209 |
|
| 210 |
+
# 1. 處理 RAG
|
| 211 |
rag_info = str(restaurant.get('RAG_Content', ''))
|
| 212 |
if retriever:
|
| 213 |
try:
|
| 214 |
docs = retriever.invoke(name)
|
| 215 |
if docs: rag_info = "\n".join([d.page_content for d in docs])
|
| 216 |
+
except: pass
|
|
|
|
| 217 |
|
| 218 |
+
# 2. 準備圖片 Prompt (提前執行以便顯示除錯資訊)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
img_prompt = f"Delicious food from {name}, cinematic lighting, 8k, photorealistic" # 預設值
|
| 220 |
+
prompt_source = "⚠️ 預設生成 (未找到 CSV 對應)"
|
| 221 |
+
|
| 222 |
if not df_prompts.empty and PROMPT_COL_NAME in df_prompts.columns:
|
| 223 |
if name in df_prompts.index:
|
| 224 |
try:
|
| 225 |
csv_prompt = df_prompts.loc[name, PROMPT_COL_NAME]
|
| 226 |
+
if isinstance(csv_prompt, pd.Series): csv_prompt = csv_prompt.iloc[0]
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
if pd.notna(csv_prompt) and str(csv_prompt).strip() != "":
|
| 229 |
img_prompt = str(csv_prompt)
|
| 230 |
+
prompt_source = "✅ CSV 檔案 (restaurants_prompts_v6.csv)"
|
| 231 |
else:
|
| 232 |
+
prompt_source = "⚠️ CSV 有此餐廳但 Prompt 空白"
|
| 233 |
except Exception as e:
|
| 234 |
+
prompt_source = f"❌ 讀取錯誤: {e}"
|
| 235 |
else:
|
| 236 |
+
prompt_source = "ℹ️ CSV 中無此餐廳"
|
| 237 |
+
|
| 238 |
+
# 3. 呼叫 LLM
|
| 239 |
+
ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, quiz_state)
|
| 240 |
|
| 241 |
+
# 4. 組合回應 (包含除錯資訊)
|
| 242 |
+
debug_text = ""
|
| 243 |
+
if debug_mode:
|
| 244 |
+
debug_text = f"""
|
| 245 |
+
\n\n---
|
| 246 |
+
**🛠️ Prompt 除錯模式**
|
| 247 |
+
* **餐廳名稱**: `{name}`
|
| 248 |
+
* **Prompt 來源**: {prompt_source}
|
| 249 |
+
* **實際使用 Prompt**:
|
| 250 |
+
```
|
| 251 |
+
{img_prompt}
|
| 252 |
+
```
|
| 253 |
+
"""
|
| 254 |
+
|
| 255 |
+
final_response = f"### 🍽️ 推薦:{name} {note}\n\n{ai_text}{debug_text}"
|
| 256 |
+
map_html = f'<div style="text-align:center"><a href="{url}" target="_blank" style="background:#4CAF50;color:white;padding:8px 16px;border-radius:20px;text-decoration:none">🗺️ Google Map 導航</a></div>'
|
| 257 |
+
|
| 258 |
+
# 5. 先回傳文字
|
| 259 |
+
yield final_response, None, map_html
|
| 260 |
+
|
| 261 |
+
# 6. 生成圖片
|
| 262 |
image_output = generate_image_huggingface(img_prompt)
|
| 263 |
yield final_response, image_output, map_html
|
| 264 |
|
|
|
|
| 276 |
current_state["q2"] = q2_options_map.get(selected_label, selected_label)
|
| 277 |
return current_state, gr.Button(interactive=True, variant="primary", value="完成測驗 (前往點餐) ➔")
|
| 278 |
|
| 279 |
+
def go_to_page_2(): return gr.update(visible=False), gr.update(visible=True)
|
| 280 |
+
def go_to_main_app(): return gr.update(visible=False), gr.update(visible=True)
|
|
|
|
|
|
|
|
|
|
| 281 |
|
| 282 |
# ==========================================
|
| 283 |
# 4. Gradio 介面建構
|
| 284 |
# ==========================================
|
| 285 |
|
| 286 |
with gr.Blocks(title="AI 心情食堂") as demo:
|
|
|
|
| 287 |
gr.HTML(f"<style>{custom_css}</style>")
|
| 288 |
quiz_state = gr.State(value={})
|
| 289 |
|
| 290 |
+
# Page 1
|
| 291 |
with gr.Column(visible=True) as page_1_col:
|
| 292 |
gr.Markdown("### 🔮 第一題:直覺測試")
|
| 293 |
gr.Image(value=img_path_1, type="filepath", label="請觀察圖片", height=300)
|
|
|
|
| 295 |
radio_q1 = gr.Radio(choices=list(q1_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
|
| 296 |
btn_q1_next = gr.Button("下一頁 ➔", interactive=False)
|
| 297 |
|
| 298 |
+
# Page 2
|
| 299 |
with gr.Column(visible=False) as page_2_col:
|
| 300 |
gr.Markdown("### 🔮 第二題:魔法適性")
|
| 301 |
gr.Image(value=img_path_2, type="filepath", label="請觀察圖片", height=300)
|
|
|
|
| 303 |
radio_q2 = gr.Radio(choices=list(q2_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
|
| 304 |
btn_q2_finish = gr.Button("完成測驗 ➔", interactive=False)
|
| 305 |
|
| 306 |
+
# Page 3 (Main)
|
| 307 |
with gr.Column(visible=False) as main_app_col:
|
| 308 |
gr.Markdown(f"## 🍱 AI 心情食堂導航")
|
| 309 |
gr.Markdown("測驗結束!現在,告訴我你真正想吃什麼吧!")
|
|
|
|
| 312 |
with gr.Column(scale=1):
|
| 313 |
score_input = gr.Radio(["1 (心情差)", "2 (不太好)", "3 (普通)", "4 (不錯)", "5 (超棒)"], label="1. 心情分數", value="3 (普通)")
|
| 314 |
food_input = gr.Radio(["吃飯", "吃麵", "隨便"], label="2. 想吃什麼", value="隨便")
|
| 315 |
+
diary_input = gr.Textbox(lines=4, label="3. 心情日記", placeholder="寫下今天發生的事...")
|
| 316 |
+
|
| 317 |
+
# [新增] 除錯模式開關
|
| 318 |
+
debug_mode_btn = gr.Checkbox(label="🔧 顯示 Prompt 除錯資訊", value=False)
|
| 319 |
+
|
| 320 |
submit_btn = gr.Button("送出給 Agent", variant="primary")
|
| 321 |
|
| 322 |
with gr.Column(scale=1):
|
|
|
|
| 324 |
image_output = gr.Image(label="AI 推薦美食圖", type="pil", width=400)
|
| 325 |
map_output = gr.HTML(label="地圖導航")
|
| 326 |
|
| 327 |
+
# Events
|
| 328 |
radio_q1.change(fn=handle_q1_change, inputs=[radio_q1, quiz_state], outputs=[quiz_state, btn_q1_next])
|
| 329 |
btn_q1_next.click(fn=go_to_page_2, outputs=[page_1_col, page_2_col])
|
| 330 |
radio_q2.change(fn=handle_q2_change, inputs=[radio_q2, quiz_state], outputs=[quiz_state, btn_q2_finish])
|
| 331 |
btn_q2_finish.click(fn=go_to_main_app, outputs=[page_2_col, main_app_col])
|
| 332 |
|
| 333 |
+
# [修改] 加入 debug_mode_btn 到 inputs
|
| 334 |
submit_btn.click(
|
| 335 |
fn=mood_agent_logic,
|
| 336 |
+
inputs=[score_input, food_input, diary_input, quiz_state, debug_mode_btn],
|
| 337 |
outputs=[agent_output, image_output, map_output]
|
| 338 |
)
|
| 339 |
|