PhilipL commited on
Commit
6abbb4d
·
verified ·
1 Parent(s): a1e515e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -129
app.py CHANGED
@@ -10,10 +10,6 @@ from langchain_huggingface import HuggingFaceEmbeddings
10
  from langchain_community.vectorstores import FAISS
11
  from PIL import Image
12
  from dotenv import load_dotenv
13
- import warnings
14
-
15
- # 忽略警告
16
- warnings.filterwarnings("ignore")
17
  load_dotenv() # 自動尋找並載入 .env 檔案中的變數
18
 
19
  # ==========================================
@@ -38,33 +34,24 @@ def init_rag_system():
38
 
39
  print("⏳ 正在初始化系統...")
40
 
41
- # --- A. 讀取 CSV ---
42
  try:
43
- # 嘗試讀取 restaurants.csv (請確保檔案存在)
44
- if os.path.exists('restaurants.csv'):
45
- global_df = pd.read_csv('restaurants.csv')
46
- global_df['RAG_Content'] = global_df['RAG_Content'].fillna("")
47
- global_df['Category'] = global_df['Category'].fillna("其他")
48
- else:
49
- print("⚠️ 找不到 restaurants.csv,將使用空的 DataFrame")
50
- global_df = pd.DataFrame(columns=['Name', 'Category', 'Address', 'URL', 'Visual_prompt', 'RAG_Content'])
51
-
52
- # 嘗試讀取 mood_food_guide.csv (若無則建立預設)
53
- if os.path.exists('mood_food_guide.csv'):
54
- global_mood_df = pd.read_csv('mood_food_guide.csv')
55
- else:
56
- global_mood_df = pd.DataFrame(columns=['分數', '推薦料理類別', '原因'])
57
-
58
  print(f"✅ CSV 資料讀取成功 (餐廳: {len(global_df)} 筆)")
59
  except Exception as e:
60
  print(f"❌ CSV 讀取失敗: {e}")
61
  return
62
 
63
- # --- B. 讀取 FAISS 索引 ---
64
  if os.path.exists("faiss_index"):
65
  try:
66
  print("⏳ 正在載入 FAISS 向量資料庫...")
67
  embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
 
68
  vectorstore = FAISS.load_local(
69
  "faiss_index",
70
  embeddings,
@@ -74,10 +61,9 @@ def init_rag_system():
74
  print("✅ FAISS 資料庫載入完成!")
75
  except Exception as e:
76
  print(f"❌ FAISS 載入失敗: {e}")
77
- global_retriever = None
78
  else:
79
- print("⚠️ 警告:找不到 'faiss_index' 資料夾,RAG 功能將受限。")
80
- global_retriever = None
81
 
82
  rag_initialized = True
83
 
@@ -91,22 +77,20 @@ custom_css = """
91
  .vertical-radio label { width: 100% !important; margin: 0 !important; display: flex !important; }
92
  """
93
 
94
- # 設定圖片路徑
95
- img_paths = {
96
- "q1": "images/image_0.png",
97
- "q2": "images/image_1.png",
98
- "q3": "images/image_3.png",
99
- "q4": "images/image_4.png",
100
- "q5": "images/image_5.png"
101
- }
102
 
103
  if not os.path.exists("images"): os.makedirs("images")
104
- # 自動產生缺失的圖片(僅作為佔位符)
105
- for key, path in img_paths.items():
106
- if not os.path.exists(path):
107
- Image.new('RGB', (400, 300), color='#E0E0E0').save(path)
108
 
109
- # --- 選項設定 (您可以修改這裡的選項內容) ---
110
  q1_options_map = {
111
  "不​要​再​問白癡​問題​了​我​只​想​吃飯​": "現實主義者 (只想吃飯)",
112
  "去​過隱​居​生活": "隱士 (嚮往平靜)",
@@ -114,6 +98,7 @@ q1_options_map = {
114
  "神​羅​天​征,毀滅這個​世界​": "破壞神 (心情可能很差或很中二)"
115
  }
116
 
 
117
  q2_options_map = {
118
  "不​要​再​問白癡​問題​了​我​只​想​吃飯​": "無魔法 (飢餓度MAX)",
119
  "麵​包形狀​的​魔法​炸彈": "爆炸魔法 (喜歡刺激/重口味)",
@@ -121,28 +106,29 @@ q2_options_map = {
121
  "領域​展開:​無量​空處​": "領域展開 (思緒混亂或想放空)"
122
  }
123
 
124
- # [新增] Q3 選項 - 旅遊
125
  q3_options_map = {
126
- "日本 (秋葉原/古都巡禮)": "哈日族 (喜歡精緻美食)",
127
- "歐洲 (充滿歷史與浪漫)": "浪漫派 (喜歡氣氛佳)",
128
- "熱帶海島 (放空度假)": "享樂主義 (喜歡海鮮或冷飲)",
129
- "南極 (挑戰極限)": "冒險家 (喜歡新奇食物)"
 
130
  }
131
 
132
- # [新增] Q4 選項 - 社交
133
  q4_options_map = {
134
- "那個看起來最受歡迎的核心人物": "外向者 (喜歡熱鬧聚餐)",
135
- "角落安靜滑手機的人": "內向者 (喜歡安靜獨食)",
136
- "正在專心吃東西的人": "吃貨 (食物比人重要)",
137
- "看起來跟我一樣尷尬的人": "隨和者 (好相處)"
138
  }
139
 
140
- # [新增] Q5 選項 - 追劇
141
  q5_options_map = {
142
- "絕命毒師 (Breaking Bad)": "重口味/劇情控",
143
- "六人行 (Friends)": "輕鬆美式/早午餐",
144
- "鬼滅之刃": "熱血動漫迷",
145
- "甄嬛傳": "宮廷/精緻料理"
146
  }
147
 
148
  # ==========================================
@@ -158,20 +144,17 @@ def get_restaurant_data(mood_score_str, food_choice):
158
  except:
159
  score = 3
160
 
161
- # 嘗試從 mood_food_guide 獲取建議,若無則預設
162
- rec_categories = ""
163
- mood_reason = "隨意探索"
164
-
165
- if global_mood_df is not None and not global_mood_df.empty:
166
- mood_info = global_mood_df[global_mood_df['分數'] == score]
167
- if not mood_info.empty:
168
- rec_categories = mood_info.iloc[0]['推薦料理類別']
169
- mood_reason = mood_info.iloc[0]['原因']
170
 
171
  candidates = global_df.copy()
172
 
173
  if rec_categories:
174
- # 簡單篩選類別
175
  candidates = candidates[candidates['Category'].apply(lambda x: str(x) in str(rec_categories) or str(rec_categories) in str(x))]
176
 
177
  food_keyword = "飯" if food_choice == "吃飯" else "麵" if food_choice == "吃麵" else ""
@@ -195,21 +178,18 @@ def generate_content_with_groq(restaurant_name, restaurant_detail, user_diary, m
195
 
196
  client = OpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1")
197
 
198
- system_prompt = "你是一個幽默、懂吃且善解人意的 AI 朋友。請根據使用者的日記、心情以及「五題心理測驗結果」來推薦餐廳。"
199
 
200
- # 組合測驗結果字串
201
- quiz_summary = f"""
202
- 1. 直覺/人設: {quiz_result.get('q1', '未知')}
203
- 2. 魔法/口味: {quiz_result.get('q2', '未知')}
204
- 3. 旅遊/嚮往: {quiz_result.get('q3', '未知')}
205
- 4. 社交/個性: {quiz_result.get('q4', '未知')}
206
- 5. 追劇/喜好: {quiz_result.get('q5', '未知')}
207
- """
208
-
209
  user_msg = f"""
210
  【狀態】心情分數:{mood_score},日記:{user_diary}
211
- 【心理測驗全結果】
212
- {quiz_summary}
 
 
 
 
 
213
 
214
  【心情美食指南建議】
215
  因為分數是 {mood_score},建議吃這類食物的原因是:「{mood_guide_reason}」。
@@ -220,9 +200,9 @@ def generate_content_with_groq(restaurant_name, restaurant_detail, user_diary, m
220
 
221
  任務:
222
  請用繁體中文寫一段溫暖有趣的回覆:
223
- 1. 綜合分析他的測驗結果(例如:提到他想去哪裡玩、或是想看什麼劇的關聯性)。
224
- 2. 結合日記內容與心情給予安慰或鼓勵。
225
- 3. 推薦上述餐廳,說明為什麼這家店適合「現在的他」。
226
  (只需要回覆文字內容)
227
  """
228
  try:
@@ -251,20 +231,18 @@ def mood_agent_logic(score_input, food_input, diary_input, quiz_state):
251
  yield "資料庫讀取錯誤", None, ""
252
  return
253
 
254
- name = str(restaurant['Name'])
255
- address = str(restaurant.get('Address', '未知地址'))
256
- url = str(restaurant.get('URL', f'https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(name)}'))
257
  img_prompt = restaurant.get('Visual_prompt')
258
  note = "(隨機推薦)" if is_random else ""
259
 
260
  # RAG 檢索
261
  rag_info = str(restaurant.get('RAG_Content', ''))
262
  if global_retriever:
263
- try:
264
- docs = global_retriever.invoke(name)
265
- if docs:
266
- rag_info = "\n".join([d.page_content for d in docs])
267
- except: pass
268
 
269
  ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, quiz_state, mood_reason)
270
 
@@ -278,32 +256,32 @@ def mood_agent_logic(score_input, food_input, diary_input, quiz_state):
278
  # ==========================================
279
  # 4. 介面互動邏輯
280
  # ==========================================
281
-
282
  def handle_q1_change(selected_label, current_state):
283
  if not selected_label: return current_state, gr.Button(interactive=False)
284
- current_state["q1"] = q1_options_map.get(selected_label, selected_label)
285
  return current_state, gr.Button(interactive=True, variant="primary")
286
 
287
  def handle_q2_change(selected_label, current_state):
288
  if not selected_label: return current_state, gr.Button(interactive=False)
289
- current_state["q2"] = q2_options_map.get(selected_label, selected_label)
290
- return current_state, gr.Button(interactive=True, variant="primary")
291
 
292
- # [新增] Q3, Q4, Q5 的處理函式
293
  def handle_q3_change(selected_label, current_state):
294
  if not selected_label: return current_state, gr.Button(interactive=False)
295
- current_state["q3"] = q3_options_map.get(selected_label, selected_label)
296
  return current_state, gr.Button(interactive=True, variant="primary")
297
 
 
298
  def handle_q4_change(selected_label, current_state):
299
  if not selected_label: return current_state, gr.Button(interactive=False)
300
- current_state["q4"] = q4_options_map.get(selected_label, selected_label)
301
  return current_state, gr.Button(interactive=True, variant="primary")
302
 
 
303
  def handle_q5_change(selected_label, current_state):
304
  if not selected_label: return current_state, gr.Button(interactive=False)
305
- current_state["q5"] = q5_options_map.get(selected_label, selected_label)
306
- # 最後一題,按鈕文字改為完成
307
  return current_state, gr.Button(interactive=True, variant="primary", value="完成測驗 (前往點餐) ➔")
308
 
309
  # ==========================================
@@ -313,13 +291,12 @@ with gr.Blocks(title="AI 心情食堂") as demo:
313
  gr.HTML(f"<style>{custom_css}</style>")
314
  quiz_state = gr.State(value={})
315
 
316
- # 使用 Tabs 隱藏導覽列,透過按鈕切換
317
  with gr.Tabs(elem_id="hidden_tabs") as tabs:
318
  # Tab 0: Q1
319
  with gr.TabItem("Q1", id=0):
320
  with gr.Column():
321
- gr.Markdown("### 🔮 Q1:直覺測試")
322
- gr.Image(value=img_paths["q1"], type="filepath", label="請觀察圖片", height=300)
323
  gr.Markdown("**問題:請觀察上方圖片,如果是你,你會怎麼做?**")
324
  radio_q1 = gr.Radio(choices=list(q1_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
325
  btn_q1_next = gr.Button("下一頁 ➔", interactive=False)
@@ -327,44 +304,43 @@ with gr.Blocks(title="AI 心情食堂") as demo:
327
  # Tab 1: Q2
328
  with gr.TabItem("Q2", id=1):
329
  with gr.Column():
330
- gr.Markdown("### 🔮 Q2:魔法適性")
331
- gr.Image(value=img_paths["q2"], type="filepath", label="請觀察圖片", height=300)
332
  gr.Markdown("**問題:身為魔導士的你,會選擇哪一個法術?**")
333
  radio_q2 = gr.Radio(choices=list(q2_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
334
- btn_q2_next = gr.Button("下一頁 ➔", interactive=False)
335
 
336
- # Tab 2: Q3 (新���)
337
  with gr.TabItem("Q3", id=2):
338
  with gr.Column():
339
- gr.Markdown("### ✈️ Q3:旅遊嚮往")
340
- gr.Image(value=img_paths["q3"], type="filepath", label="機場圖片", height=300)
341
- gr.Markdown("**問題:不考慮其他因素,假你在桃機你最想去哪裡玩?**")
342
- radio_q3 = gr.Radio(choices=list(q3_options_map.keys()), label="請選擇目的地", elem_classes="vertical-radio")
343
  btn_q3_next = gr.Button("下一頁 ➔", interactive=False)
344
 
345
- # Tab 3: Q4 (新增)
346
  with gr.TabItem("Q4", id=3):
347
  with gr.Column():
348
- gr.Markdown("### 🤝 Q4:社交直覺")
349
- gr.Image(value=img_paths["q4"], type="filepath", label="聚會圖片", height=300)
350
- gr.Markdown("**問題:你是小大一,班上還不熟,現在你正參加你們班上的認識彼此的活動。下列哪一個人是你會選擇搭話的人?**")
351
- radio_q4 = gr.Radio(choices=list(q4_options_map.keys()), label="請選擇對象", elem_classes="vertical-radio")
352
  btn_q4_next = gr.Button("下一頁 ➔", interactive=False)
353
 
354
- # Tab 4: Q5 (新增)
355
  with gr.TabItem("Q5", id=4):
356
  with gr.Column():
357
- gr.Markdown("### 📺 Q5:追劇時光")
358
- gr.Image(value=img_paths["q5"], type="filepath", label="劇照/電視圖片", height=300)
359
- gr.Markdown("**問題:假設失憶了忘記以下所列的劇的劇情,而你現在閒了下來剛好想看劇,你會想看哪一部?**")
360
- radio_q5 = gr.Radio(choices=list(q5_options_map.keys()), label="請選擇劇集", elem_classes="vertical-radio")
361
  btn_q5_finish = gr.Button("完成測驗 ➔", interactive=False)
362
 
363
- # Tab 5: Main (ID 改為 5)
364
  with gr.TabItem("Main", id=5):
365
  with gr.Column():
366
  gr.Markdown(f"## 🍱 呷飽沒??!")
367
- gr.Markdown("測驗完成!AI 已根據你的選擇分析出最適合你的用餐氛圍。")
368
  with gr.Row():
369
  with gr.Column(scale=1):
370
  score_input = gr.Radio(["1 (心情差)", "2 (不太好)", "3 (普通)", "4 (不錯)", "5 (超棒)"], label="1. 心情分數", value="3 (普通)")
@@ -376,28 +352,27 @@ with gr.Blocks(title="AI 心情食堂") as demo:
376
  image_output = gr.Image(label="AI 推薦美食圖", type="pil", width=400)
377
  map_output = gr.HTML(label="地圖導航")
378
 
379
- # --- Event Binding ---
380
- # Q1 -> Q2
381
  radio_q1.change(fn=handle_q1_change, inputs=[radio_q1, quiz_state], outputs=[quiz_state, btn_q1_next])
382
- btn_q1_next.click(fn=lambda: gr.Tabs(selected=1), outputs=tabs)
383
 
384
- # Q2 -> Q3
385
  radio_q2.change(fn=handle_q2_change, inputs=[radio_q2, quiz_state], outputs=[quiz_state, btn_q2_next])
386
- btn_q2_next.click(fn=lambda: gr.Tabs(selected=2), outputs=tabs)
387
 
388
- # Q3 -> Q4
389
  radio_q3.change(fn=handle_q3_change, inputs=[radio_q3, quiz_state], outputs=[quiz_state, btn_q3_next])
390
- btn_q3_next.click(fn=lambda: gr.Tabs(selected=3), outputs=tabs)
391
 
392
- # Q4 -> Q5
393
  radio_q4.change(fn=handle_q4_change, inputs=[radio_q4, quiz_state], outputs=[quiz_state, btn_q4_next])
394
- btn_q4_next.click(fn=lambda: gr.Tabs(selected=4), outputs=tabs)
395
 
396
- # Q5 -> Main
397
  radio_q5.change(fn=handle_q5_change, inputs=[radio_q5, quiz_state], outputs=[quiz_state, btn_q5_finish])
398
- btn_q5_finish.click(fn=lambda: gr.Tabs(selected=5), outputs=tabs)
399
 
400
- # Main Agent Submit
401
  submit_btn.click(
402
  fn=mood_agent_logic,
403
  inputs=[score_input, food_input, diary_input, quiz_state],
 
10
  from langchain_community.vectorstores import FAISS
11
  from PIL import Image
12
  from dotenv import load_dotenv
 
 
 
 
13
  load_dotenv() # 自動尋找並載入 .env 檔案中的變數
14
 
15
  # ==========================================
 
34
 
35
  print("⏳ 正在初始化系統...")
36
 
37
+ # --- A. 讀取 CSV (用於篩選與對照) ---
38
  try:
39
+ global_df = pd.read_csv('restaurants.csv')
40
+ global_df['RAG_Content'] = global_df['RAG_Content'].fillna("")
41
+ global_df['Category'] = global_df['Category'].fillna("其他")
42
+
43
+ global_mood_df = pd.read_csv('mood_food_guide.csv')
 
 
 
 
 
 
 
 
 
 
44
  print(f"✅ CSV 資料讀取成功 (餐廳: {len(global_df)} 筆)")
45
  except Exception as e:
46
  print(f"❌ CSV 讀取失敗: {e}")
47
  return
48
 
49
+ # --- B. 讀取預先建立好的 FAISS 索引 ---
50
  if os.path.exists("faiss_index"):
51
  try:
52
  print("⏳ 正在載入 FAISS 向量資料庫...")
53
  embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
54
+
55
  vectorstore = FAISS.load_local(
56
  "faiss_index",
57
  embeddings,
 
61
  print("✅ FAISS 資料庫載入完成!")
62
  except Exception as e:
63
  print(f"❌ FAISS 載入失敗: {e}")
 
64
  else:
65
+ print("⚠️ 警告:找不到 'faiss_index' 資料夾,RAG 功能將無法使用。")
66
+ print("請先執行 build_index.py 並上傳資料夾。")
67
 
68
  rag_initialized = True
69
 
 
77
  .vertical-radio label { width: 100% !important; margin: 0 !important; display: flex !important; }
78
  """
79
 
80
+ # 設定圖片路徑 (包含新增的 Q3, Q4, Q5)
81
+ img_path_1 = "images/image_0.png"
82
+ img_path_2 = "images/image_1.png"
83
+ img_path_3 = "images/image_2.png" # Q3
84
+ img_path_4 = "images/image_3.png" # Q4
85
+ img_path_5 = "images/image_4.png" # Q5
 
 
86
 
87
  if not os.path.exists("images"): os.makedirs("images")
88
+ # 建立預設圖片以防報錯
89
+ for p in [img_path_1, img_path_2, img_path_3, img_path_4, img_path_5]:
90
+ if not os.path.exists(p):
91
+ Image.new('RGB', (400, 300), color='lightgray').save(p)
92
 
93
+ # --- Q1 選項 ---
94
  q1_options_map = {
95
  "不​要​再​問白癡​問題​了​我​只​想​吃飯​": "現實主義者 (只想吃飯)",
96
  "去​過隱​居​生活": "隱士 (嚮往平靜)",
 
98
  "神​羅​天​征,毀滅這個​世界​": "破壞神 (心情可能很差或很中二)"
99
  }
100
 
101
+ # --- Q2 選項 ---
102
  q2_options_map = {
103
  "不​要​再​問白癡​問題​了​我​只​想​吃飯​": "無魔法 (飢餓度MAX)",
104
  "麵​包形狀​的​魔法​炸彈": "爆炸魔法 (喜歡刺激/重口味)",
 
106
  "領域​展開:​無量​空處​": "領域展開 (思緒混亂或想放空)"
107
  }
108
 
109
+ # --- [新增] Q3 選項 ---
110
  q3_options_map = {
111
+ "不​要​在​問白癡​問題​了​我​只​想​吃飯​": "現實主義�� (只想吃飯)",
112
+ "歐洲": "嚮往歐洲 (浪漫/西式)",
113
+ "日本": "嚮往日本 (精緻/日式)",
114
+ "泰國": "嚮往泰國 (熱情/酸辣)",
115
+ "轉身搭機捷回家": "戀家 (只想回家)"
116
  }
117
 
118
+ # --- [新增] Q4 選項 ---
119
  q4_options_map = {
120
+ "轉頭​回家​睡覺​": "獨行俠 (想睡覺)",
121
+ "看​起來​很​會玩​的​帥潮​": "外向 (找帥潮)",
122
+ "感覺​是​動漫宅​的​同好​": "御宅族 (找同好)",
123
+ "有​興趣​的​異性​": "大膽 (找異性)"
124
  }
125
 
126
+ # --- [新增] Q5 選項 ---
127
  q5_options_map = {
128
+ "甚麼​都​不​想​​": "放空 (什麼都不想)",
129
+ "來​自​星星​的​你​": "韓劇迷 (浪漫愛情)",
130
+ "進擊​的​巨人​": "動漫迷 (熱血戰鬥)",
131
+ "洛基": "美劇迷 (懸疑燒腦)"
132
  }
133
 
134
  # ==========================================
 
144
  except:
145
  score = 3
146
 
147
+ mood_info = global_mood_df[global_mood_df['分數'] == score]
148
+ if not mood_info.empty:
149
+ rec_categories = mood_info.iloc[0]['推薦料理類別']
150
+ mood_reason = mood_info.iloc[0]['原因']
151
+ else:
152
+ rec_categories = ""
153
+ mood_reason = "隨意探索"
 
 
154
 
155
  candidates = global_df.copy()
156
 
157
  if rec_categories:
 
158
  candidates = candidates[candidates['Category'].apply(lambda x: str(x) in str(rec_categories) or str(rec_categories) in str(x))]
159
 
160
  food_keyword = "飯" if food_choice == "吃飯" else "麵" if food_choice == "吃麵" else ""
 
178
 
179
  client = OpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1")
180
 
181
+ system_prompt = "你是一個幽默、懂吃且善解人意的 AI 朋友。請根據使用者的日記、心情以及「心情美食指南」來推薦餐廳。"
182
 
183
+ # [修改] 將新增的測驗結果加入 Prompt
 
 
 
 
 
 
 
 
184
  user_msg = f"""
185
  【狀態】心情分數:{mood_score},日記:{user_diary}
186
+
187
+ 【五題測驗結果】
188
+ 1. 人設直覺:{quiz_result.get('q1', '未知')}
189
+ 2. 魔法適性:{quiz_result.get('q2', '未知')}
190
+ 3. 旅遊偏好:{quiz_result.get('q3', '未知')}
191
+ 4. 社交選擇:{quiz_result.get('q4', '未知')}
192
+ 5. 追劇偏好:{quiz_result.get('q5', '未知')}
193
 
194
  【心情美食指南建議】
195
  因為分數是 {mood_score},建議吃這類食物的原因是:「{mood_guide_reason}」。
 
200
 
201
  任務:
202
  請用繁體中文寫一段溫暖有趣的回覆:
203
+ 1. 綜合回應他的日記與上述 5 個測驗結果(例如如果他選日本,可以提到這家店有沒有日式氛圍;如果選動漫宅,可以用一點動漫梗)。
204
+ 2. 引用「心情美食指南」的原因。
205
+ 3. 介紹這家餐廳的特色。
206
  (只需要回覆文字內容)
207
  """
208
  try:
 
231
  yield "資料庫讀取錯誤", None, ""
232
  return
233
 
234
+ name = restaurant['Name']
235
+ address = restaurant['Address']
236
+ url = restaurant['URL']
237
  img_prompt = restaurant.get('Visual_prompt')
238
  note = "(隨機推薦)" if is_random else ""
239
 
240
  # RAG 檢索
241
  rag_info = str(restaurant.get('RAG_Content', ''))
242
  if global_retriever:
243
+ docs = global_retriever.invoke(name)
244
+ if docs:
245
+ rag_info = "\n".join([d.page_content for d in docs])
 
 
246
 
247
  ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, quiz_state, mood_reason)
248
 
 
256
  # ==========================================
257
  # 4. 介面互動邏輯
258
  # ==========================================
 
259
  def handle_q1_change(selected_label, current_state):
260
  if not selected_label: return current_state, gr.Button(interactive=False)
261
+ current_state["q1"] = q1_options_map[selected_label]
262
  return current_state, gr.Button(interactive=True, variant="primary")
263
 
264
  def handle_q2_change(selected_label, current_state):
265
  if not selected_label: return current_state, gr.Button(interactive=False)
266
+ current_state["q2"] = q2_options_map[selected_label]
267
+ return current_state, gr.Button(interactive=True, variant="primary") # 修改:這裡不再是完成,而是下一頁
268
 
269
+ # [新增] Q3 Handler
270
  def handle_q3_change(selected_label, current_state):
271
  if not selected_label: return current_state, gr.Button(interactive=False)
272
+ current_state["q3"] = q3_options_map[selected_label]
273
  return current_state, gr.Button(interactive=True, variant="primary")
274
 
275
+ # [新增] Q4 Handler
276
  def handle_q4_change(selected_label, current_state):
277
  if not selected_label: return current_state, gr.Button(interactive=False)
278
+ current_state["q4"] = q4_options_map[selected_label]
279
  return current_state, gr.Button(interactive=True, variant="primary")
280
 
281
+ # [新增] Q5 Handler (最後一題)
282
  def handle_q5_change(selected_label, current_state):
283
  if not selected_label: return current_state, gr.Button(interactive=False)
284
+ current_state["q5"] = q5_options_map[selected_label]
 
285
  return current_state, gr.Button(interactive=True, variant="primary", value="完成測驗 (前往點餐) ➔")
286
 
287
  # ==========================================
 
291
  gr.HTML(f"<style>{custom_css}</style>")
292
  quiz_state = gr.State(value={})
293
 
 
294
  with gr.Tabs(elem_id="hidden_tabs") as tabs:
295
  # Tab 0: Q1
296
  with gr.TabItem("Q1", id=0):
297
  with gr.Column():
298
+ gr.Markdown("### 🔮 第一題:直覺測試")
299
+ gr.Image(value=img_path_1, type="filepath", label="請觀察圖片", height=300)
300
  gr.Markdown("**問題:請觀察上方圖片,如果是你,你會怎麼做?**")
301
  radio_q1 = gr.Radio(choices=list(q1_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
302
  btn_q1_next = gr.Button("下一頁 ➔", interactive=False)
 
304
  # Tab 1: Q2
305
  with gr.TabItem("Q2", id=1):
306
  with gr.Column():
307
+ gr.Markdown("### 🔮 第二題:魔法適性")
308
+ gr.Image(value=img_path_2, type="filepath", label="請觀察圖片", height=300)
309
  gr.Markdown("**問題:身為魔導士的你,會選擇哪一個法術?**")
310
  radio_q2 = gr.Radio(choices=list(q2_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
311
+ btn_q2_next = gr.Button("下一頁 ➔", interactive=False) # 修改按鈕名稱
312
 
313
+ # [新增] Tab 2: Q3
314
  with gr.TabItem("Q3", id=2):
315
  with gr.Column():
316
+ gr.Markdown("### 🔮 第三題:旅遊直覺")
317
+ gr.Image(value=img_path_3, type="filepath", label="請觀察圖片", height=300)
318
+ gr.Markdown("**問題三:不考慮其他因素,假你在桃機你最想去哪裡玩?**")
319
+ radio_q3 = gr.Radio(choices=list(q3_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
320
  btn_q3_next = gr.Button("下一頁 ➔", interactive=False)
321
 
322
+ # [新增] Tab 3: Q4
323
  with gr.TabItem("Q4", id=3):
324
  with gr.Column():
325
+ gr.Markdown("### 🔮 第四題:社交場合")
326
+ gr.Image(value=img_path_4, type="filepath", label="請觀察圖片", height=300)
327
+ gr.Markdown("**問題四:你是小大一,班上還不熟,現在你正參加你們班上的認識彼此的活動,下列哪一個人是你會選擇搭話的人?**")
328
+ radio_q4 = gr.Radio(choices=list(q4_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
329
  btn_q4_next = gr.Button("下一頁 ➔", interactive=False)
330
 
331
+ # [新增] Tab 4: Q5
332
  with gr.TabItem("Q5", id=4):
333
  with gr.Column():
334
+ gr.Markdown("### 🔮 第五題:追劇時光")
335
+ gr.Image(value=img_path_5, type="filepath", label="請觀察圖片", height=300)
336
+ gr.Markdown("**問題五:假設失憶了忘記以下所列的劇的劇情,而你現在閒了下來剛好想看劇,你會想看哪一部?**")
337
+ radio_q5 = gr.Radio(choices=list(q5_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
338
  btn_q5_finish = gr.Button("完成測驗 ➔", interactive=False)
339
 
340
+ # Tab 5: Main
341
  with gr.TabItem("Main", id=5):
342
  with gr.Column():
343
  gr.Markdown(f"## 🍱 呷飽沒??!")
 
344
  with gr.Row():
345
  with gr.Column(scale=1):
346
  score_input = gr.Radio(["1 (心情差)", "2 (不太好)", "3 (普通)", "4 (不錯)", "5 (超棒)"], label="1. 心情分數", value="3 (普通)")
 
352
  image_output = gr.Image(label="AI 推薦美食圖", type="pil", width=400)
353
  map_output = gr.HTML(label="地圖導航")
354
 
355
+ # 事件綁定 (Events)
356
+ # Q1
357
  radio_q1.change(fn=handle_q1_change, inputs=[radio_q1, quiz_state], outputs=[quiz_state, btn_q1_next])
358
+ btn_q1_next.click(fn=lambda: gr.Tabs(selected=1), outputs=tabs) # 跳到 Q2
359
 
360
+ # Q2
361
  radio_q2.change(fn=handle_q2_change, inputs=[radio_q2, quiz_state], outputs=[quiz_state, btn_q2_next])
362
+ btn_q2_next.click(fn=lambda: gr.Tabs(selected=2), outputs=tabs) # 跳到 Q3
363
 
364
+ # [新增] Q3
365
  radio_q3.change(fn=handle_q3_change, inputs=[radio_q3, quiz_state], outputs=[quiz_state, btn_q3_next])
366
+ btn_q3_next.click(fn=lambda: gr.Tabs(selected=3), outputs=tabs) # 跳到 Q4
367
 
368
+ # [新增] Q4
369
  radio_q4.change(fn=handle_q4_change, inputs=[radio_q4, quiz_state], outputs=[quiz_state, btn_q4_next])
370
+ btn_q4_next.click(fn=lambda: gr.Tabs(selected=4), outputs=tabs) # 跳到 Q5
371
 
372
+ # [新增] Q5
373
  radio_q5.change(fn=handle_q5_change, inputs=[radio_q5, quiz_state], outputs=[quiz_state, btn_q5_finish])
374
+ btn_q5_finish.click(fn=lambda: gr.Tabs(selected=5), outputs=tabs) # 跳到 Main
375
 
 
376
  submit_btn.click(
377
  fn=mood_agent_logic,
378
  inputs=[score_input, food_input, diary_input, quiz_state],