PhilipL commited on
Commit
0b1a66c
·
verified ·
1 Parent(s): fac38aa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -115
app.py CHANGED
@@ -28,7 +28,8 @@ HF_TOKEN = os.getenv("HF_TOKEN")
28
  # --- 1. 資料讀取 (主要資料庫 - 使用 restaurants.csv) ---
29
  df = pd.DataFrame()
30
  try:
31
- df = pd.read_csv('restaurants.csv', engine='python')
 
32
  df.columns = df.columns.str.strip() # 清理欄位空白
33
 
34
  def classify_mood(row):
@@ -54,7 +55,8 @@ try:
54
 
55
  print("✅ 成功讀取 restaurants.csv 並建立 Mood_Tags")
56
  except Exception as e:
57
- print(f"⚠️ 讀取 restaurants.csv 失敗: {e}")
 
58
 
59
  # --- 2. 資料讀取 (Prompt 資料庫) ---
60
  df_prompts = pd.DataFrame()
@@ -84,51 +86,12 @@ if not df.empty and 'RAG_Content' in df.columns:
84
  print(f"⚠️ RAG 初始化失敗: {e}")
85
 
86
  # ==========================================
87
- # 1. 小測驗設定
88
- # ==========================================
89
- custom_css = """
90
- .vertical-radio fieldset {
91
- display: flex !important;
92
- flex-direction: column !important;
93
- align-items: flex-start !important;
94
- gap: 12px !important;
95
- }
96
- .vertical-radio label {
97
- width: 100% !important;
98
- margin: 0 !important;
99
- display: flex !important;
100
- }
101
- """
102
- # 若無圖片自動生成空白圖避免報錯
103
- img_path_1 = "images/image_0.png"
104
- img_path_2 = "images/image_1.png"
105
- if not os.path.exists("images"): os.makedirs("images")
106
- try:
107
- if not os.path.exists(img_path_1): Image.new('RGB', (400, 300), color='gray').save(img_path_1)
108
- if not os.path.exists(img_path_2): Image.new('RGB', (400, 300), color='lightblue').save(img_path_2)
109
- except: pass
110
-
111
- q1_options_map = {
112
- "不​要​再​問白癡​問題​了​我​只​想​吃飯​": "現實主義者 (只想吃飯)",
113
- "去​過隱​居​生活": "隱士 (嚮往平靜)",
114
- "加入​禁衛軍,​保衛​這個​世界": "守護者 (充滿正義感)",
115
- "神​羅​天​征,毀滅這個​世界​": "破壞神 (心情可能很差或很中二)"
116
- }
117
-
118
- q2_options_map = {
119
- "不​要​再​問白癡​問題​了​我​只​想​吃飯​": "無魔法 (飢餓度MAX)",
120
- "麵​包形狀​的​魔法​炸彈": "爆炸魔法 (喜歡刺激/重口味)",
121
- "毒氣​的​生化​魔法": "毒氣魔法 (可能想吃臭豆腐或特殊風味)",
122
- "領域​展開:​無量​空處​": "領域展開 (思緒混亂或想放空)"
123
- }
124
-
125
- # ==========================================
126
- # 2. 核心功能函式 (餐廳推薦邏輯)
127
  # ==========================================
128
 
129
  def get_restaurant_data(mood_score_str, food_choice):
130
  if df.empty: return None, True
131
- # 定義分數與 Mood_Tags 的對應關係
132
  score_map = {
133
  "1 (心情差)": ["傷心/疲憊", "生氣/發洩"],
134
  "2 (不太好)": ["傷心/疲憊", "生氣/發洩"],
@@ -157,23 +120,21 @@ def get_restaurant_data(mood_score_str, food_choice):
157
  if candidates.empty: return df.sample(1).iloc[0], True
158
  return candidates.sample(1).iloc[0], False
159
 
160
- def generate_content_with_groq(restaurant_name, restaurant_detail, user_diary, mood_score, quiz_result):
 
161
  if not GROQ_API_KEY: return "⚠️ 請設定 GROQ_API_KEY (系統變數)"
162
  client = OpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1")
163
- system_prompt = "你是一個幽默、懂吃且善解人意的 AI 朋友。請根據使用者的日記、心情以及「中二病測驗結果」來推薦餐廳。"
164
  user_msg = f"""
165
  【使用者狀態】
166
  心情分數:{mood_score}
167
  心情日記:{user_diary}
168
- 【前測小遊戲結果】
169
- 隱藏人設:{quiz_result.get('q1', '未知')}
170
- 魔法:{quiz_result.get('q2', '未知')}
171
  【推薦餐廳】
172
  名稱:{restaurant_name}
173
  資料:{restaurant_detail}
174
  請完成任務:
175
  【回應內容】:用繁體中文寫一段溫暖有趣的回覆。
176
- - 結合「日記」與「前測人設」給予回應。
177
  - 推薦上述餐廳,說明為什麼這家店適合他。
178
  """
179
  try:
@@ -193,7 +154,8 @@ def generate_image_huggingface(prompt):
193
  except: return None
194
 
195
  # --- 主邏輯 Agent ---
196
- def mood_agent_logic(score_input, food_input, diary_input, quiz_state, debug_mode):
 
197
  # 確保 score_input 有值
198
  if not score_input:
199
  yield "⚠️ 請先選擇心情分數,或使用上方相機偵測!", None, ""
@@ -230,8 +192,8 @@ def mood_agent_logic(score_input, food_input, diary_input, quiz_state, debug_mod
230
  prompt_source = "✅ CSV 檔案"
231
  except: pass
232
 
233
- # 3. 呼叫 LLM
234
- ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, quiz_state)
235
 
236
  # 4. 組合回應
237
  debug_text = ""
@@ -246,7 +208,7 @@ def mood_agent_logic(score_input, food_input, diary_input, quiz_state, debug_mod
246
  yield final_response, image_output, map_html
247
 
248
  # ==========================================
249
- # 3. [新增] 情緒辨識整合模組
250
  # ==========================================
251
 
252
  # 定義中文字典 (繪圖用)
@@ -255,11 +217,10 @@ emotion_text_obj = {
255
  'happy': '開心', 'sad': '難過', 'surprise': '驚訝', 'neutral': '正常'
256
  }
257
 
258
- # 繪圖函式
259
  def putText(img, x, y, text, size=50, color=(255, 255, 255)):
260
  try:
261
  fontpath = 'NotoSansTC-VariableFont_wght.ttf'
262
- # 確保字型存在,若無則回傳原圖
263
  if not os.path.exists(fontpath): return img
264
  font = ImageFont.truetype(fontpath, size)
265
  imgPil = Image.fromarray(img)
@@ -270,13 +231,13 @@ def putText(img, x, y, text, size=50, color=(255, 255, 255)):
270
  except:
271
  return img
272
 
273
- # ★ 關鍵功能:偵測情緒並回傳「分數選項」
274
  def detect_emotion_and_map(frame):
275
  if frame is None:
276
  return frame, None
277
 
278
- detected_emotion = "neutral" # 預設
279
- mapped_score = "3 (普通)" # 預設
280
 
281
  try:
282
  # 1. 辨識情緒
@@ -288,11 +249,6 @@ def detect_emotion_and_map(frame):
288
  frame = putText(frame, 20, 40, detected_emotion)
289
 
290
  # 3. 橋樑:將情緒轉換為餐廳系統的分數
291
- # happy -> 5
292
- # surprise -> 4
293
- # neutral -> 3
294
- # sad, fear -> 2
295
- # angry, disgust -> 1
296
  if detected_emotion == 'happy':
297
  mapped_score = "5 (超棒)"
298
  elif detected_emotion == 'surprise':
@@ -311,63 +267,27 @@ def detect_emotion_and_map(frame):
311
  return frame, mapped_score
312
 
313
  # ==========================================
314
- # 4. 頁面切換與 UI 邏輯
315
- # ==========================================
316
-
317
- def handle_q1_change(selected_label, current_state):
318
- if not selected_label: return current_state, gr.Button(interactive=False)
319
- current_state["q1"] = q1_options_map.get(selected_label, selected_label)
320
- return current_state, gr.Button(interactive=True, variant="primary")
321
-
322
- def handle_q2_change(selected_label, current_state):
323
- if not selected_label: return current_state, gr.Button(interactive=False)
324
- current_state["q2"] = q2_options_map.get(selected_label, selected_label)
325
- return current_state, gr.Button(interactive=True, variant="primary", value="完成測驗 (前往點餐) ➔")
326
-
327
- def go_to_page_2(): return gr.update(visible=False), gr.update(visible=True)
328
- def go_to_main_app(): return gr.update(visible=False), gr.update(visible=True)
329
-
330
- # ==========================================
331
- # 5. Gradio 介面建構
332
  # ==========================================
333
 
334
  with gr.Blocks(title="AI 心情食堂") as demo:
335
- gr.HTML(f"<style>{custom_css}</style>")
336
- quiz_state = gr.State(value={})
337
-
338
- # Page 1
339
- with gr.Column(visible=True) as page_1_col:
340
- gr.Markdown("### 🔮 第一題:直覺測試")
341
- gr.Image(value=img_path_1, type="filepath", label="請觀察圖片", height=300)
342
- gr.Markdown("**問題:請觀察上方圖片,如果是你,你會怎麼做?**")
343
- radio_q1 = gr.Radio(choices=list(q1_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
344
- btn_q1_next = gr.Button("下一頁 ➔", interactive=False)
345
-
346
- # Page 2
347
- with gr.Column(visible=False) as page_2_col:
348
- gr.Markdown("### 🔮 第二題:魔法適性")
349
- gr.Image(value=img_path_2, type="filepath", label="請觀察圖片", height=300)
350
- gr.Markdown("**問題:身為魔導士的你,會選擇哪一個法術?**")
351
- radio_q2 = gr.Radio(choices=list(q2_options_map.keys()), label="請選擇", elem_classes="vertical-radio")
352
- btn_q2_finish = gr.Button("完成測驗 ➔", interactive=False)
353
-
354
- # Page 3 (Main)
355
- with gr.Column(visible=False) as main_app_col:
356
  gr.Markdown(f"## 🍱 AI 心情食堂導航")
357
- gr.Markdown("測驗結束!讓 AI 看看你的臉色,幫你決定吃什麼!")
358
 
359
  with gr.Row():
360
  # 左側:輸入區
361
  with gr.Column(scale=1):
362
 
363
- # ★ 新增:情緒偵測區塊
364
  gr.Markdown("### 📸 步驟 1:心情偵測 (選用)")
365
  with gr.Group():
366
  webcam_input = gr.Image(sources=["webcam"], label="看著鏡頭", streaming=True)
367
  detect_btn = gr.Button("📸 偵測心情並自動填入 👇", variant="secondary")
368
 
369
  gr.Markdown("### 📝 步驟 2:確認與補充")
370
- # score_input 會被 detect_btn 更新
371
  score_input = gr.Radio(
372
  ["1 (心情差)", "2 (不太好)", "3 (普通)", "4 (不錯)", "5 (超棒)"],
373
  label="1. 心情分數 (可手動修改)",
@@ -377,30 +297,29 @@ with gr.Blocks(title="AI 心情食堂") as demo:
377
  diary_input = gr.Textbox(lines=3, label="3. 心情日記 (選填)", placeholder="例如:今天被老闆罵了,想吃點好料的...")
378
 
379
  debug_mode_btn = gr.Checkbox(label="🔧 顯示 Prompt 除錯資訊", value=False)
380
- submit_btn = gr.Button("🚀 送出給 Agent", variant="primary")
 
381
 
382
- # 右側:輸出區
383
  with gr.Column(scale=1):
384
  agent_output = gr.Markdown(label="AI 回應")
385
  image_output = gr.Image(label="AI 推薦美食圖", type="pil", width=400)
386
  map_output = gr.HTML(label="地圖導航")
387
 
388
- # Events
389
- radio_q1.change(fn=handle_q1_change, inputs=[radio_q1, quiz_state], outputs=[quiz_state, btn_q1_next])
390
- btn_q1_next.click(fn=go_to_page_2, outputs=[page_1_col, page_2_col])
391
- radio_q2.change(fn=handle_q2_change, inputs=[radio_q2, quiz_state], outputs=[quiz_state, btn_q2_finish])
392
- btn_q2_finish.click(fn=go_to_main_app, outputs=[page_2_col, main_app_col])
393
-
394
- # ★ 新增事件:點擊偵測按鈕 -> 執行 DeepFace -> 更新 score_input
395
  detect_btn.click(
396
  fn=detect_emotion_and_map,
397
  inputs=[webcam_input],
398
  outputs=[webcam_input, score_input] # 同時更新圖片(畫字)和分數(Radio)
399
  )
400
 
 
 
401
  submit_btn.click(
402
  fn=mood_agent_logic,
403
- inputs=[score_input, food_input, diary_input, quiz_state, debug_mode_btn],
404
  outputs=[agent_output, image_output, map_output]
405
  )
406
 
 
28
  # --- 1. 資料讀取 (主要資料庫 - 使用 restaurants.csv) ---
29
  df = pd.DataFrame()
30
  try:
31
+ # 確保你有 restaurants.csv 在專案根目錄
32
+ df = pd.read_csv('restaurants.csv', engine='python')
33
  df.columns = df.columns.str.strip() # 清理欄位空白
34
 
35
  def classify_mood(row):
 
55
 
56
  print("✅ 成功讀取 restaurants.csv 並建立 Mood_Tags")
57
  except Exception as e:
58
+ # 如果找不到檔案或讀取失敗,這裡會提醒
59
+ print(f"⚠️ 讀取 restaurants.csv 失敗: {e}")
60
 
61
  # --- 2. 資料讀取 (Prompt 資料庫) ---
62
  df_prompts = pd.DataFrame()
 
86
  print(f"⚠️ RAG 初始化失敗: {e}")
87
 
88
  # ==========================================
89
+ # 1. 核心功能函式 (餐廳推薦邏輯)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  # ==========================================
91
 
92
  def get_restaurant_data(mood_score_str, food_choice):
93
  if df.empty: return None, True
94
+ # 定義分數與 Mood_Tags 的對應關係 (與舊版維持一致)
95
  score_map = {
96
  "1 (心情差)": ["傷心/疲憊", "生氣/發洩"],
97
  "2 (不太好)": ["傷心/疲憊", "生氣/發洩"],
 
120
  if candidates.empty: return df.sample(1).iloc[0], True
121
  return candidates.sample(1).iloc[0], False
122
 
123
+ # 注意:因為移除了測驗,這裡我們用 '無測驗' 作為預設結果
124
+ def generate_content_with_groq(restaurant_name, restaurant_detail, user_diary, mood_score):
125
  if not GROQ_API_KEY: return "⚠️ 請設定 GROQ_API_KEY (系統變數)"
126
  client = OpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1")
127
+ system_prompt = "你是一個幽默、懂吃且善解人意的 AI 朋友。請根據使用者的日記和心情來推薦餐廳。"
128
  user_msg = f"""
129
  【使用者狀態】
130
  心情分數:{mood_score}
131
  心情日記:{user_diary}
 
 
 
132
  【推薦餐廳】
133
  名稱:{restaurant_name}
134
  資料:{restaurant_detail}
135
  請完成任務:
136
  【回應內容】:用繁體中文寫一段溫暖有趣的回覆。
137
+ - 結合「日記」與「心情」給予回應。
138
  - 推薦上述餐廳,說明為什麼這家店適合他。
139
  """
140
  try:
 
154
  except: return None
155
 
156
  # --- 主邏輯 Agent ---
157
+ # 移除了 quiz_state 參數
158
+ def mood_agent_logic(score_input, food_input, diary_input, debug_mode):
159
  # 確保 score_input 有值
160
  if not score_input:
161
  yield "⚠️ 請先選擇心情分數,或使用上方相機偵測!", None, ""
 
192
  prompt_source = "✅ CSV 檔案"
193
  except: pass
194
 
195
+ # 3. 呼叫 LLM (移除了 quiz_state)
196
+ ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input)
197
 
198
  # 4. 組合回應
199
  debug_text = ""
 
208
  yield final_response, image_output, map_html
209
 
210
  # ==========================================
211
+ # 2. 情緒辨識整合模組 (維持不變)
212
  # ==========================================
213
 
214
  # 定義中文字典 (繪圖用)
 
217
  'happy': '開心', 'sad': '難過', 'surprise': '驚訝', 'neutral': '正常'
218
  }
219
 
220
+ # 繪圖函式 (維持不變)
221
  def putText(img, x, y, text, size=50, color=(255, 255, 255)):
222
  try:
223
  fontpath = 'NotoSansTC-VariableFont_wght.ttf'
 
224
  if not os.path.exists(fontpath): return img
225
  font = ImageFont.truetype(fontpath, size)
226
  imgPil = Image.fromarray(img)
 
231
  except:
232
  return img
233
 
234
+ # 關鍵功能:偵測情緒並回傳「分數選項」 (維持不變)
235
  def detect_emotion_and_map(frame):
236
  if frame is None:
237
  return frame, None
238
 
239
+ detected_emotion = "neutral"
240
+ mapped_score = "3 (普通)"
241
 
242
  try:
243
  # 1. 辨識情緒
 
249
  frame = putText(frame, 20, 40, detected_emotion)
250
 
251
  # 3. 橋樑:將情緒轉換為餐廳系統的分數
 
 
 
 
 
252
  if detected_emotion == 'happy':
253
  mapped_score = "5 (超棒)"
254
  elif detected_emotion == 'surprise':
 
267
  return frame, mapped_score
268
 
269
  # ==========================================
270
+ # 3. Gradio 介面建構 (移除前兩頁)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  # ==========================================
272
 
273
  with gr.Blocks(title="AI 心情食堂") as demo:
274
+
275
+ # 只有一個主應用程式 Column,並設定為預設顯示
276
+ with gr.Column(visible=True) as main_app_col:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  gr.Markdown(f"## 🍱 AI 心情食堂導航")
278
+ gr.Markdown("請看著鏡頭,讓 AI 幫你判斷今天的心情分數!")
279
 
280
  with gr.Row():
281
  # 左側:輸入區
282
  with gr.Column(scale=1):
283
 
284
+ # 情緒偵測區塊 (維持不變)
285
  gr.Markdown("### 📸 步驟 1:心情偵測 (選用)")
286
  with gr.Group():
287
  webcam_input = gr.Image(sources=["webcam"], label="看著鏡頭", streaming=True)
288
  detect_btn = gr.Button("📸 偵測心情並自動填入 👇", variant="secondary")
289
 
290
  gr.Markdown("### 📝 步驟 2:確認與補充")
 
291
  score_input = gr.Radio(
292
  ["1 (心情差)", "2 (不太好)", "3 (普通)", "4 (不錯)", "5 (超棒)"],
293
  label="1. 心情分數 (可手動修改)",
 
297
  diary_input = gr.Textbox(lines=3, label="3. 心情日記 (選填)", placeholder="例如:今天被老闆罵了,想吃點好料的...")
298
 
299
  debug_mode_btn = gr.Checkbox(label="🔧 顯示 Prompt 除錯資訊", value=False)
300
+ # 移除了 quiz_state
301
+ submit_btn = gr.Button("🚀 送出給 Agent", variant="primary")
302
 
303
+ # 右側:輸出區 (維持不變)
304
  with gr.Column(scale=1):
305
  agent_output = gr.Markdown(label="AI 回應")
306
  image_output = gr.Image(label="AI 推薦美食圖", type="pil", width=400)
307
  map_output = gr.HTML(label="地圖導航")
308
 
309
+ # Events (只保留情緒偵測和提交)
310
+
311
+ # 點擊偵測按鈕 -> 執行 DeepFace -> 更新 score_input
 
 
 
 
312
  detect_btn.click(
313
  fn=detect_emotion_and_map,
314
  inputs=[webcam_input],
315
  outputs=[webcam_input, score_input] # 同時更新圖片(畫字)和分數(Radio)
316
  )
317
 
318
+ # 提交按鈕
319
+ # 移除了 quiz_state 的輸入
320
  submit_btn.click(
321
  fn=mood_agent_logic,
322
+ inputs=[score_input, food_input, diary_input, debug_mode_btn],
323
  outputs=[agent_output, image_output, map_output]
324
  )
325