Update app.py
Browse files
app.py
CHANGED
|
@@ -10,8 +10,11 @@ from langchain_community.document_loaders import DataFrameLoader
|
|
| 10 |
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 11 |
from langchain_huggingface import HuggingFaceEmbeddings
|
| 12 |
from langchain_community.vectorstores import FAISS
|
| 13 |
-
from PIL import Image
|
| 14 |
import warnings
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# 忽略 pandas 的一些警告
|
| 17 |
warnings.filterwarnings("ignore")
|
|
@@ -25,7 +28,6 @@ HF_TOKEN = os.getenv("HF_TOKEN")
|
|
| 25 |
# --- 1. 資料讀取 (主要資料庫 - 使用 restaurants.csv) ---
|
| 26 |
df = pd.DataFrame()
|
| 27 |
try:
|
| 28 |
-
# 修改:讀取 restaurants.csv
|
| 29 |
df = pd.read_csv('restaurants.csv', engine='python')
|
| 30 |
df.columns = df.columns.str.strip() # 清理欄位空白
|
| 31 |
|
|
@@ -54,26 +56,15 @@ try:
|
|
| 54 |
except Exception as e:
|
| 55 |
print(f"⚠️ 讀取 restaurants.csv 失敗: {e}")
|
| 56 |
|
| 57 |
-
# --- 2. 資料讀取 (Prompt 資料庫
|
| 58 |
df_prompts = pd.DataFrame()
|
| 59 |
-
# 修改:您的 CSV 中 Prompt 欄位名稱為 Visual_prompt
|
| 60 |
PROMPT_COL_NAME = 'Visual_prompt'
|
| 61 |
|
| 62 |
try:
|
| 63 |
-
# 直接使用已讀取的 df 來建立 Prompt 索引
|
| 64 |
if not df.empty:
|
| 65 |
df_prompts = df.copy()
|
| 66 |
-
|
| 67 |
if 'Name' in df_prompts.columns:
|
| 68 |
df_prompts.set_index('Name', inplace=True)
|
| 69 |
-
else:
|
| 70 |
-
print(f"⚠️ Prompts 處理警告: 找不到 'Name' 欄位")
|
| 71 |
-
|
| 72 |
-
if PROMPT_COL_NAME not in df_prompts.columns:
|
| 73 |
-
print(f"⚠️ Prompts 處理警告: 找不到 '{PROMPT_COL_NAME}' 欄位")
|
| 74 |
-
else:
|
| 75 |
-
print("⚠️ 主資料庫為空,無法建立 Prompt 索引")
|
| 76 |
-
|
| 77 |
except Exception as e:
|
| 78 |
print(f"⚠️ 處理 Prompt 資料發生錯誤: {e}")
|
| 79 |
|
|
@@ -108,7 +99,7 @@ custom_css = """
|
|
| 108 |
display: flex !important;
|
| 109 |
}
|
| 110 |
"""
|
| 111 |
-
|
| 112 |
img_path_1 = "images/image_0.png"
|
| 113 |
img_path_2 = "images/image_1.png"
|
| 114 |
if not os.path.exists("images"): os.makedirs("images")
|
|
@@ -132,11 +123,12 @@ q2_options_map = {
|
|
| 132 |
}
|
| 133 |
|
| 134 |
# ==========================================
|
| 135 |
-
# 2. 核心功能函式
|
| 136 |
# ==========================================
|
| 137 |
|
| 138 |
def get_restaurant_data(mood_score_str, food_choice):
|
| 139 |
if df.empty: return None, True
|
|
|
|
| 140 |
score_map = {
|
| 141 |
"1 (心情差)": ["傷心/疲憊", "生氣/發洩"],
|
| 142 |
"2 (不太好)": ["傷心/疲憊", "生氣/發洩"],
|
|
@@ -200,15 +192,19 @@ def generate_image_huggingface(prompt):
|
|
| 200 |
return hf_client.text_to_image(prompt=prompt, model="stabilityai/stable-diffusion-xl-base-1.0")
|
| 201 |
except: return None
|
| 202 |
|
| 203 |
-
# --- 主邏輯 Agent
|
| 204 |
def mood_agent_logic(score_input, food_input, diary_input, quiz_state, debug_mode):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
restaurant, is_random = get_restaurant_data(score_input, food_input)
|
| 206 |
if restaurant is None:
|
| 207 |
yield "資料庫讀取錯誤或為空", None, ""
|
| 208 |
return
|
| 209 |
|
| 210 |
name = str(restaurant['Name'])
|
| 211 |
-
address = str(restaurant.get('Address', '未知地址'))
|
| 212 |
url = str(restaurant.get('URL', f'https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(name)}'))
|
| 213 |
note = "(隨機推薦)" if is_random else ""
|
| 214 |
|
|
@@ -220,56 +216,102 @@ def mood_agent_logic(score_input, food_input, diary_input, quiz_state, debug_mod
|
|
| 220 |
if docs: rag_info = "\n".join([d.page_content for d in docs])
|
| 221 |
except: pass
|
| 222 |
|
| 223 |
-
# 2. 準備圖片 Prompt
|
| 224 |
-
img_prompt = f"Delicious food from {name}, cinematic lighting, 8k, photorealistic"
|
| 225 |
-
prompt_source = "⚠️ 預設生成
|
| 226 |
|
| 227 |
if not df_prompts.empty and PROMPT_COL_NAME in df_prompts.columns:
|
| 228 |
if name in df_prompts.index:
|
| 229 |
try:
|
| 230 |
csv_prompt = df_prompts.loc[name, PROMPT_COL_NAME]
|
| 231 |
if isinstance(csv_prompt, pd.Series): csv_prompt = csv_prompt.iloc[0]
|
| 232 |
-
|
| 233 |
if pd.notna(csv_prompt) and str(csv_prompt).strip() != "":
|
| 234 |
img_prompt = str(csv_prompt)
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
else:
|
| 238 |
-
prompt_source = "⚠️ CSV 有此餐廳但 Prompt 空白"
|
| 239 |
-
except Exception as e:
|
| 240 |
-
prompt_source = f"❌ 讀取錯誤: {e}"
|
| 241 |
-
else:
|
| 242 |
-
prompt_source = "ℹ️ CSV 中無此餐廳"
|
| 243 |
|
| 244 |
# 3. 呼叫 LLM
|
| 245 |
ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, quiz_state)
|
| 246 |
|
| 247 |
-
# 4. 組合回應
|
| 248 |
debug_text = ""
|
| 249 |
if debug_mode:
|
| 250 |
-
debug_text = f""
|
| 251 |
-
\n\n---
|
| 252 |
-
**🛠️ Prompt 除錯模式**
|
| 253 |
-
* **餐廳名稱**: `{name}`
|
| 254 |
-
* **Prompt 來源**: {prompt_source}
|
| 255 |
-
* **實際使用 Prompt**:
|
| 256 |
-
```
|
| 257 |
-
{img_prompt}
|
| 258 |
-
```
|
| 259 |
-
"""
|
| 260 |
|
| 261 |
final_response = f"### 🍽️ 推薦:{name} {note}\n\n{ai_text}{debug_text}"
|
| 262 |
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>'
|
| 263 |
|
| 264 |
-
# 5. 先回傳文字
|
| 265 |
yield final_response, None, map_html
|
| 266 |
-
|
| 267 |
-
# 6. 生成圖片
|
| 268 |
image_output = generate_image_huggingface(img_prompt)
|
| 269 |
yield final_response, image_output, map_html
|
| 270 |
|
| 271 |
# ==========================================
|
| 272 |
-
# 3.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
# ==========================================
|
| 274 |
|
| 275 |
def handle_q1_change(selected_label, current_state):
|
|
@@ -286,7 +328,7 @@ def go_to_page_2(): return gr.update(visible=False), gr.update(visible=True)
|
|
| 286 |
def go_to_main_app(): return gr.update(visible=False), gr.update(visible=True)
|
| 287 |
|
| 288 |
# ==========================================
|
| 289 |
-
#
|
| 290 |
# ==========================================
|
| 291 |
|
| 292 |
with gr.Blocks(title="AI 心情食堂") as demo:
|
|
@@ -312,19 +354,32 @@ with gr.Blocks(title="AI 心情食堂") as demo:
|
|
| 312 |
# Page 3 (Main)
|
| 313 |
with gr.Column(visible=False) as main_app_col:
|
| 314 |
gr.Markdown(f"## 🍱 AI 心情食堂導航")
|
| 315 |
-
gr.Markdown("測驗結束!
|
| 316 |
|
| 317 |
with gr.Row():
|
|
|
|
| 318 |
with gr.Column(scale=1):
|
| 319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
food_input = gr.Radio(["吃飯", "吃麵", "隨便"], label="2. 想吃什麼", value="隨便")
|
| 321 |
-
diary_input = gr.Textbox(lines=
|
| 322 |
|
| 323 |
-
# [新增] 除錯模式開關
|
| 324 |
debug_mode_btn = gr.Checkbox(label="🔧 顯示 Prompt 除錯資訊", value=False)
|
| 325 |
-
|
| 326 |
-
submit_btn = gr.Button("送出給 Agent", variant="primary")
|
| 327 |
|
|
|
|
| 328 |
with gr.Column(scale=1):
|
| 329 |
agent_output = gr.Markdown(label="AI 回應")
|
| 330 |
image_output = gr.Image(label="AI 推薦美食圖", type="pil", width=400)
|
|
@@ -336,7 +391,13 @@ with gr.Blocks(title="AI 心情食堂") as demo:
|
|
| 336 |
radio_q2.change(fn=handle_q2_change, inputs=[radio_q2, quiz_state], outputs=[quiz_state, btn_q2_finish])
|
| 337 |
btn_q2_finish.click(fn=go_to_main_app, outputs=[page_2_col, main_app_col])
|
| 338 |
|
| 339 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
submit_btn.click(
|
| 341 |
fn=mood_agent_logic,
|
| 342 |
inputs=[score_input, food_input, diary_input, quiz_state, debug_mode_btn],
|
|
|
|
| 10 |
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 11 |
from langchain_huggingface import HuggingFaceEmbeddings
|
| 12 |
from langchain_community.vectorstores import FAISS
|
| 13 |
+
from PIL import Image, ImageFont, ImageDraw
|
| 14 |
import warnings
|
| 15 |
+
import cv2
|
| 16 |
+
import numpy as np
|
| 17 |
+
from deepface import DeepFace
|
| 18 |
|
| 19 |
# 忽略 pandas 的一些警告
|
| 20 |
warnings.filterwarnings("ignore")
|
|
|
|
| 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 |
|
|
|
|
| 56 |
except Exception as e:
|
| 57 |
print(f"⚠️ 讀取 restaurants.csv 失敗: {e}")
|
| 58 |
|
| 59 |
+
# --- 2. 資料讀取 (Prompt 資料庫) ---
|
| 60 |
df_prompts = pd.DataFrame()
|
|
|
|
| 61 |
PROMPT_COL_NAME = 'Visual_prompt'
|
| 62 |
|
| 63 |
try:
|
|
|
|
| 64 |
if not df.empty:
|
| 65 |
df_prompts = df.copy()
|
|
|
|
| 66 |
if 'Name' in df_prompts.columns:
|
| 67 |
df_prompts.set_index('Name', inplace=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
except Exception as e:
|
| 69 |
print(f"⚠️ 處理 Prompt 資料發生錯誤: {e}")
|
| 70 |
|
|
|
|
| 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")
|
|
|
|
| 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 (不太好)": ["傷心/疲憊", "生氣/發洩"],
|
|
|
|
| 192 |
return hf_client.text_to_image(prompt=prompt, model="stabilityai/stable-diffusion-xl-base-1.0")
|
| 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, ""
|
| 200 |
+
return
|
| 201 |
+
|
| 202 |
restaurant, is_random = get_restaurant_data(score_input, food_input)
|
| 203 |
if restaurant is None:
|
| 204 |
yield "資料庫讀取錯誤或為空", None, ""
|
| 205 |
return
|
| 206 |
|
| 207 |
name = str(restaurant['Name'])
|
|
|
|
| 208 |
url = str(restaurant.get('URL', f'https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(name)}'))
|
| 209 |
note = "(隨機推薦)" if is_random else ""
|
| 210 |
|
|
|
|
| 216 |
if docs: rag_info = "\n".join([d.page_content for d in docs])
|
| 217 |
except: pass
|
| 218 |
|
| 219 |
+
# 2. 準備圖片 Prompt
|
| 220 |
+
img_prompt = f"Delicious food from {name}, cinematic lighting, 8k, photorealistic"
|
| 221 |
+
prompt_source = "⚠️ 預設生成"
|
| 222 |
|
| 223 |
if not df_prompts.empty and PROMPT_COL_NAME in df_prompts.columns:
|
| 224 |
if name in df_prompts.index:
|
| 225 |
try:
|
| 226 |
csv_prompt = df_prompts.loc[name, PROMPT_COL_NAME]
|
| 227 |
if isinstance(csv_prompt, pd.Series): csv_prompt = csv_prompt.iloc[0]
|
|
|
|
| 228 |
if pd.notna(csv_prompt) and str(csv_prompt).strip() != "":
|
| 229 |
img_prompt = str(csv_prompt)
|
| 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 = ""
|
| 238 |
if debug_mode:
|
| 239 |
+
debug_text = f"\n\n---\n**🛠️ Prompt 來源**: {prompt_source}\n**Prompt**: `{img_prompt}`"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
final_response = f"### 🍽️ 推薦:{name} {note}\n\n{ai_text}{debug_text}"
|
| 242 |
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>'
|
| 243 |
|
|
|
|
| 244 |
yield final_response, None, map_html
|
|
|
|
|
|
|
| 245 |
image_output = generate_image_huggingface(img_prompt)
|
| 246 |
yield final_response, image_output, map_html
|
| 247 |
|
| 248 |
# ==========================================
|
| 249 |
+
# 3. [新增] 情緒辨識整合模組
|
| 250 |
+
# ==========================================
|
| 251 |
+
|
| 252 |
+
# 定義中文字典 (繪圖用)
|
| 253 |
+
emotion_text_obj = {
|
| 254 |
+
'angry': '生氣', 'disgust': '噁心', 'fear': '害怕',
|
| 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)
|
| 266 |
+
draw = ImageDraw.Draw(imgPil)
|
| 267 |
+
displayText = emotion_text_obj.get(text, text)
|
| 268 |
+
draw.text((x, y), displayText, fill=color, font=font)
|
| 269 |
+
return np.array(imgPil)
|
| 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. 辨識情緒
|
| 283 |
+
analyze = DeepFace.analyze(frame, actions=['emotion'], enforce_detection=False)
|
| 284 |
+
if isinstance(analyze, list): analyze = analyze[0]
|
| 285 |
+
detected_emotion = analyze['dominant_emotion']
|
| 286 |
+
|
| 287 |
+
# 2. 畫在圖片上
|
| 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':
|
| 299 |
+
mapped_score = "4 (不錯)"
|
| 300 |
+
elif detected_emotion == 'neutral':
|
| 301 |
+
mapped_score = "3 (普通)"
|
| 302 |
+
elif detected_emotion in ['sad', 'fear']:
|
| 303 |
+
mapped_score = "2 (不太好)"
|
| 304 |
+
elif detected_emotion in ['angry', 'disgust']:
|
| 305 |
+
mapped_score = "1 (心情差)"
|
| 306 |
+
|
| 307 |
+
except Exception as e:
|
| 308 |
+
print(f"DeepFace Error: {e}")
|
| 309 |
+
pass
|
| 310 |
+
|
| 311 |
+
return frame, mapped_score
|
| 312 |
+
|
| 313 |
+
# ==========================================
|
| 314 |
+
# 4. 頁面切換與 UI 邏輯
|
| 315 |
# ==========================================
|
| 316 |
|
| 317 |
def handle_q1_change(selected_label, current_state):
|
|
|
|
| 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:
|
|
|
|
| 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. 心情分數 (可手動修改)",
|
| 374 |
+
value="3 (普通)"
|
| 375 |
+
)
|
| 376 |
food_input = gr.Radio(["吃飯", "吃麵", "隨便"], label="2. 想吃什麼", value="隨便")
|
| 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)
|
|
|
|
| 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],
|