Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,7 +8,6 @@ import time
|
|
| 8 |
import requests
|
| 9 |
import urllib.parse
|
| 10 |
import pandas as pd
|
| 11 |
-
import re # 新增 re 模組用於解析地址
|
| 12 |
from langchain_huggingface import HuggingFaceEmbeddings
|
| 13 |
from langchain_community.vectorstores import FAISS
|
| 14 |
from PIL import Image
|
|
@@ -93,86 +92,25 @@ def check_api_key_status(name, key):
|
|
| 93 |
return f"✅ 已設定 ({masked})"
|
| 94 |
|
| 95 |
# ==========================================
|
| 96 |
-
# 2. Google Maps 爬蟲
|
| 97 |
# ==========================================
|
| 98 |
-
def extract_city(address):
|
| 99 |
-
"""
|
| 100 |
-
通用版地址解析:支援中文(台/臺)、英文地址、與郵遞區號判斷
|
| 101 |
-
"""
|
| 102 |
-
if not isinstance(address, str) or not address:
|
| 103 |
-
return "未知地區"
|
| 104 |
-
|
| 105 |
-
# 1. 預處理:轉小寫以利英文比對,將 '臺' 統一轉為 '台' 以利中文比對
|
| 106 |
-
addr_norm = address.lower().replace("臺", "台")
|
| 107 |
-
|
| 108 |
-
# 定義對照表 (關鍵字 -> 標準縣市名稱)
|
| 109 |
-
# 順序很重要:縣市層級要比區層級優先,但這裡我們主要抓大區域
|
| 110 |
-
city_map = {
|
| 111 |
-
"keelung": "基隆市", "基隆": "基隆市",
|
| 112 |
-
"taipei": "台北市", "台北": "台北市",
|
| 113 |
-
"new taipei": "新北市", "新北": "新北市",
|
| 114 |
-
"taoyuan": "桃園市", "桃園": "桃園市",
|
| 115 |
-
"hsinchu city": "新竹市", "新竹市": "新竹市",
|
| 116 |
-
"hsinchu county": "新竹縣", "新竹縣": "新竹縣",
|
| 117 |
-
"hsinchu": "新竹縣市", # 模糊比對放後面
|
| 118 |
-
"miaoli": "苗栗縣", "苗栗": "苗栗縣",
|
| 119 |
-
"taichung": "台中市", "台中": "台中市",
|
| 120 |
-
"changhua": "彰化縣", "彰化": "彰化縣",
|
| 121 |
-
"nantou": "南投縣", "南投": "南投縣",
|
| 122 |
-
"yunlin": "雲林縣", "雲林": "雲林縣",
|
| 123 |
-
"chiayi city": "嘉義市", "嘉義市": "嘉義市",
|
| 124 |
-
"chiayi county": "嘉義縣", "嘉義縣": "嘉義縣",
|
| 125 |
-
"chiayi": "嘉義縣市",
|
| 126 |
-
"tainan": "台南市", "台南": "台南市",
|
| 127 |
-
"kaohsiung": "高雄市", "高雄": "高雄市",
|
| 128 |
-
"pingtung": "屏東縣", "屏東": "屏東縣",
|
| 129 |
-
"yilan": "宜蘭縣", "宜蘭": "宜蘭縣",
|
| 130 |
-
"hualien": "花蓮縣", "花蓮": "花蓮縣",
|
| 131 |
-
"taitung": "台東縣", "台東": "台東縣",
|
| 132 |
-
"penghu": "澎湖縣", "澎湖": "澎湖縣",
|
| 133 |
-
"kinmen": "金門縣", "金門": "金門縣",
|
| 134 |
-
"lienchiang": "連江縣", "連江": "連江縣", "matsu": "連江縣"
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
# 2. 關鍵字比對
|
| 138 |
-
for key, std_city in city_map.items():
|
| 139 |
-
if key in addr_norm:
|
| 140 |
-
return std_city
|
| 141 |
-
|
| 142 |
-
# 3. 補救措施:正規表達式抓取 "3碼郵遞區號" (台灣地址常出現)
|
| 143 |
-
# 例如:106台北市大安區 -> 106 可能是關鍵
|
| 144 |
-
# 這邊簡單實作,若需要更精準可能需要郵遞區號對照表,但通常上述關鍵字已足夠
|
| 145 |
-
|
| 146 |
-
return "其他地區"
|
| 147 |
-
|
| 148 |
def sync_google_maps(url):
|
| 149 |
global global_df, global_retriever, rag_initialized
|
| 150 |
clean_url = url.strip() if url else ""
|
| 151 |
-
|
| 152 |
-
no_update = gr.update()
|
| 153 |
-
empty_df = pd.DataFrame() # 空表格
|
| 154 |
-
|
| 155 |
if not clean_url or "http" not in clean_url.lower():
|
| 156 |
-
|
| 157 |
-
yield "❌ 請輸入有效的 Google Maps 分享連結。", no_update, empty_df
|
| 158 |
return
|
| 159 |
|
| 160 |
try:
|
| 161 |
-
yield "🚀 [1/4] 啟動瀏覽器..."
|
| 162 |
with sync_playwright() as p:
|
| 163 |
-
# 加上 user_agent 模擬正常瀏覽器,減少變成英文版的機率 (雖然我們已經做了英文處理)
|
| 164 |
browser = p.chromium.launch(headless=True)
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
timezone_id='Asia/Taipei'
|
| 168 |
-
)
|
| 169 |
-
page = context.new_page()
|
| 170 |
-
|
| 171 |
-
yield "🌐 [2/4] 連線中...", no_update, empty_df
|
| 172 |
page.goto(clean_url, wait_until="domcontentloaded", timeout=60000)
|
| 173 |
-
yield "⏳ [3/4] 等待列表加載 (約 10 秒)..."
|
| 174 |
time.sleep(10)
|
| 175 |
-
yield "📄 [4/4] 解析餐廳資訊..."
|
| 176 |
|
| 177 |
titles = page.locator('div.fontHeadlineSmall').all_inner_texts()
|
| 178 |
details = page.locator('div.fontBodyMedium').all_inner_texts()
|
|
@@ -181,36 +119,23 @@ def sync_google_maps(url):
|
|
| 181 |
for i, name in enumerate(titles):
|
| 182 |
name = name.strip()
|
| 183 |
if name:
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
city = extract_city(raw_addr)
|
| 187 |
-
|
| 188 |
-
search_query = f"{name} {raw_addr}"
|
| 189 |
restaurant_list.append({
|
| 190 |
"Name": name,
|
| 191 |
-
"
|
| 192 |
-
"Address": raw_addr, # 顯示原始地址以供除錯
|
| 193 |
"Category": "未分類",
|
| 194 |
-
"RAG_Content": f"餐廳:{name},
|
| 195 |
"URL": f"https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(search_query)}"
|
| 196 |
})
|
| 197 |
browser.close()
|
| 198 |
|
| 199 |
if not restaurant_list:
|
| 200 |
-
yield "⚠️ 找不到餐廳。請確認連結格式正確且已公開。"
|
| 201 |
return
|
| 202 |
|
| 203 |
global_df = pd.DataFrame(restaurant_list)
|
| 204 |
|
| 205 |
-
# 準備下拉選單
|
| 206 |
-
unique_cities = sorted(global_df['City'].unique().tolist())
|
| 207 |
-
if "其他地區" in unique_cities:
|
| 208 |
-
unique_cities.remove("其他地區")
|
| 209 |
-
unique_cities.append("其他地區")
|
| 210 |
-
|
| 211 |
-
dropdown_choices = ["全區"] + unique_cities
|
| 212 |
-
|
| 213 |
-
# RAG 初始化 (維持不變)
|
| 214 |
try:
|
| 215 |
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
| 216 |
vectorstore = FAISS.from_texts(global_df['RAG_Content'].tolist(), embeddings, metadatas=global_df.to_dict('records'))
|
|
@@ -219,23 +144,14 @@ def sync_google_maps(url):
|
|
| 219 |
print(f"RAG 初始化警告: {e}")
|
| 220 |
|
| 221 |
rag_initialized = True
|
| 222 |
-
|
| 223 |
-
# 簡化顯示用的 DataFrame (只秀出重點欄位給使用者看)
|
| 224 |
-
display_df = global_df[['Name', 'City', 'Address']].head(20) # 只顯示前20筆避免太長
|
| 225 |
-
|
| 226 |
-
# 回傳:訊息, 下拉選單更新, 表格資料更新
|
| 227 |
-
yield f"✅ 同步成功!共 {len(restaurant_list)} 筆。", gr.update(choices=dropdown_choices, value="全區", visible=True), display_df
|
| 228 |
-
|
| 229 |
except Exception as e:
|
| 230 |
-
yield f"❌ 系統錯誤:{str(e)}"
|
| 231 |
|
| 232 |
# ==========================================
|
| 233 |
# 3. 核心功能 (AI Agent)
|
| 234 |
# ==========================================
|
| 235 |
-
def get_restaurant_data(mood_score_str, food_choice
|
| 236 |
-
"""
|
| 237 |
-
依據心情、食物類別與「地區」篩選餐廳
|
| 238 |
-
"""
|
| 239 |
init_static_data() # 確保心情指南已載入
|
| 240 |
|
| 241 |
if global_df is None or global_df.empty:
|
|
@@ -256,15 +172,6 @@ def get_restaurant_data(mood_score_str, food_choice, location_filter="全區"):
|
|
| 256 |
mood_reason = mood_info.iloc[0]['原因']
|
| 257 |
|
| 258 |
candidates = global_df.copy()
|
| 259 |
-
|
| 260 |
-
# --- 新增:地區篩選邏輯 ---
|
| 261 |
-
if location_filter and location_filter != "全區":
|
| 262 |
-
candidates = candidates[candidates['City'] == location_filter]
|
| 263 |
-
# 如果篩選後沒資料,則退回全部資料並標記 Random (或者回傳空值讓前端顯示警告)
|
| 264 |
-
if candidates.empty:
|
| 265 |
-
return None, True, "", f"在【{location_filter}】找不到餐廳,請嘗試切換地區。"
|
| 266 |
-
# ------------------------
|
| 267 |
-
|
| 268 |
food_keyword = "飯" if food_choice == "吃飯" else "麵" if food_choice == "吃麵" else ""
|
| 269 |
|
| 270 |
is_random = False
|
|
@@ -276,11 +183,7 @@ def get_restaurant_data(mood_score_str, food_choice, location_filter="全區"):
|
|
| 276 |
is_random = True
|
| 277 |
|
| 278 |
if candidates.empty:
|
| 279 |
-
|
| 280 |
-
backup_pool = global_df[global_df['City'] == location_filter] if location_filter != "全區" else global_df
|
| 281 |
-
if backup_pool.empty: backup_pool = global_df
|
| 282 |
-
|
| 283 |
-
result = backup_pool.sample(1).iloc[0]; is_random = True
|
| 284 |
else:
|
| 285 |
result = candidates.sample(1).iloc[0]
|
| 286 |
|
|
@@ -334,18 +237,14 @@ def generate_image_huggingface(prompt):
|
|
| 334 |
# 回傳具體錯誤訊息
|
| 335 |
return None, str(e)
|
| 336 |
|
| 337 |
-
def mood_agent_logic(score_input, food_input,
|
| 338 |
if not rag_initialized:
|
| 339 |
yield "⚠️ 請先同步地圖清單!", None, "", gr.update()
|
| 340 |
return
|
| 341 |
|
| 342 |
-
|
| 343 |
-
restaurant
|
| 344 |
-
|
| 345 |
-
# 錯誤處��:如果 restaurant 是 None,通常是因為該地區沒餐廳
|
| 346 |
-
if restaurant is None:
|
| 347 |
-
error_msg = mood_reason # 這裡借用 mood_reason 回傳錯誤訊息
|
| 348 |
-
yield f"❌ {error_msg}", None, "", gr.update()
|
| 349 |
return
|
| 350 |
|
| 351 |
name = restaurant['Name']; url = restaurant['URL']
|
|
@@ -357,11 +256,8 @@ def mood_agent_logic(score_input, food_input, location_input, diary_input, debug
|
|
| 357 |
ai_text, groq_debug_log = generate_content_with_groq(name, rag_info, diary_input, score_input, mood_reason, debug_mode)
|
| 358 |
|
| 359 |
prefix = ""
|
| 360 |
-
if is_random:
|
| 361 |
-
|
| 362 |
-
prefix = f"> 💡 **溫馨提示**:在 {location_input} 的清單中暫無『{food_input}』,已為您挑選其他類型的美味!\n\n"
|
| 363 |
-
else:
|
| 364 |
-
prefix = f"> 💡 **探索模式**:在 {location_input} 為您精選了一家餐廳!\n\n"
|
| 365 |
|
| 366 |
# 組合完整的除錯資訊,包含 API Key 狀態與錯誤訊息
|
| 367 |
full_debug_log = ""
|
|
@@ -375,7 +271,6 @@ def mood_agent_logic(score_input, food_input, location_input, diary_input, debug
|
|
| 375 |
- **GROQ_API_KEY**: {groq_status}
|
| 376 |
- **HF_TOKEN**: {hf_status}
|
| 377 |
- **RAG 狀態**: {"✅ 已初始化" if rag_initialized else "❌ 未初始化"}
|
| 378 |
-
- **目前篩選地區**: {location_input}
|
| 379 |
"""
|
| 380 |
full_debug_log = api_debug_block + "\n" + groq_debug_log + f"\n\n### 🎨 Image Prompt Debug\n{img_prompt}"
|
| 381 |
|
|
@@ -474,19 +369,6 @@ with gr.Blocks(title="AI 心情食堂", css=css_) as demo:
|
|
| 474 |
with gr.Row():
|
| 475 |
map_url = gr.Textbox(label="Google Maps Saved Lists 連結", placeholder="請貼上清單的分享連結...", scale=3)
|
| 476 |
sync_btn = gr.Button("🔄 同步清單", variant="secondary", scale=1)
|
| 477 |
-
|
| 478 |
-
# [NEW] 新增:資料預覽表格
|
| 479 |
-
# 放在下拉選單上面或下面都可以,這裡放在下面當作檢查用
|
| 480 |
-
with gr.Accordion("📋 點此查看已抓取的餐廳資料 (除錯用)", open=False):
|
| 481 |
-
data_preview = gr.Dataframe(
|
| 482 |
-
headers=["Name", "City", "Address"],
|
| 483 |
-
datatype=["str", "str", "str"],
|
| 484 |
-
label="餐廳資料預覽 (顯示前 20 筆)"
|
| 485 |
-
)
|
| 486 |
-
|
| 487 |
-
# 新增:動態下拉選單
|
| 488 |
-
location_dropdown = gr.Dropdown(label="📍 選擇餐廳地區 (同步後自動更新)", choices=["請先同步清單"], value="請先同步清單", interactive=True)
|
| 489 |
-
|
| 490 |
sync_msg = gr.Markdown("ℹ️ 尚未同步資料庫")
|
| 491 |
gr.Markdown("---")
|
| 492 |
gr.Markdown(f"## 🍱 今天想吃點什麼?")
|
|
@@ -506,22 +388,16 @@ with gr.Blocks(title="AI 心情食堂", css=css_) as demo:
|
|
| 506 |
map_output = gr.HTML(label="地圖導航")
|
| 507 |
|
| 508 |
# 事件
|
| 509 |
-
|
| 510 |
-
sync_btn.click(
|
| 511 |
-
fn=sync_google_maps,
|
| 512 |
-
inputs=[map_url],
|
| 513 |
-
outputs=[sync_msg, location_dropdown, data_preview] # 這裡加上 data_preview
|
| 514 |
-
)
|
| 515 |
-
|
| 516 |
btn_start.click(fn=bridge_start_click, inputs=[st_state], outputs=[result_markdown, cam, btn_stop, btn_start, st_state, btn_go_dining])
|
| 517 |
btn_stop.click(fn=bridge_stop_click, inputs=[st_state], outputs=[result_markdown, cam, btn_stop, btn_start, st_state])
|
| 518 |
cam.stream(fn=bridge_predict_frame, inputs=[cam, st_state], outputs=[cam, result_markdown, st_state, btn_stop, btn_start, score_input, btn_go_dining])
|
| 519 |
btn_go_dining.click(fn=lambda: gr.Tabs(selected=1), inputs=None, outputs=tabs)
|
| 520 |
|
| 521 |
-
#
|
| 522 |
submit_btn.click(
|
| 523 |
fn=mood_agent_logic,
|
| 524 |
-
inputs=[score_input, food_input,
|
| 525 |
outputs=[agent_output, image_output, map_output, debug_output]
|
| 526 |
)
|
| 527 |
|
|
|
|
| 8 |
import requests
|
| 9 |
import urllib.parse
|
| 10 |
import pandas as pd
|
|
|
|
| 11 |
from langchain_huggingface import HuggingFaceEmbeddings
|
| 12 |
from langchain_community.vectorstores import FAISS
|
| 13 |
from PIL import Image
|
|
|
|
| 92 |
return f"✅ 已設定 ({masked})"
|
| 93 |
|
| 94 |
# ==========================================
|
| 95 |
+
# 2. Google Maps 爬蟲功能
|
| 96 |
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
def sync_google_maps(url):
|
| 98 |
global global_df, global_retriever, rag_initialized
|
| 99 |
clean_url = url.strip() if url else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
if not clean_url or "http" not in clean_url.lower():
|
| 101 |
+
yield "❌ 請輸入有效的 Google Maps 分享連結。"
|
|
|
|
| 102 |
return
|
| 103 |
|
| 104 |
try:
|
| 105 |
+
yield "🚀 [1/4] 啟動瀏覽器..."
|
| 106 |
with sync_playwright() as p:
|
|
|
|
| 107 |
browser = p.chromium.launch(headless=True)
|
| 108 |
+
page = browser.new_page()
|
| 109 |
+
yield "🌐 [2/4] 連線中..."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
page.goto(clean_url, wait_until="domcontentloaded", timeout=60000)
|
| 111 |
+
yield "⏳ [3/4] 等待列表加載 (約 10 秒)..."
|
| 112 |
time.sleep(10)
|
| 113 |
+
yield "📄 [4/4] 解析餐廳資訊..."
|
| 114 |
|
| 115 |
titles = page.locator('div.fontHeadlineSmall').all_inner_texts()
|
| 116 |
details = page.locator('div.fontBodyMedium').all_inner_texts()
|
|
|
|
| 119 |
for i, name in enumerate(titles):
|
| 120 |
name = name.strip()
|
| 121 |
if name:
|
| 122 |
+
addr = details[i].strip() if i < len(details) else ""
|
| 123 |
+
search_query = f"{name} {addr}"
|
|
|
|
|
|
|
|
|
|
| 124 |
restaurant_list.append({
|
| 125 |
"Name": name,
|
| 126 |
+
"Address": addr,
|
|
|
|
| 127 |
"Category": "未分類",
|
| 128 |
+
"RAG_Content": f"餐廳:{name},資訊:{addr}",
|
| 129 |
"URL": f"https://www.google.com/maps/search/?api=1&query={urllib.parse.quote(search_query)}"
|
| 130 |
})
|
| 131 |
browser.close()
|
| 132 |
|
| 133 |
if not restaurant_list:
|
| 134 |
+
yield "⚠️ 找不到餐廳。請確認連結格式正確且已公開。"
|
| 135 |
return
|
| 136 |
|
| 137 |
global_df = pd.DataFrame(restaurant_list)
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
try:
|
| 140 |
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
| 141 |
vectorstore = FAISS.from_texts(global_df['RAG_Content'].tolist(), embeddings, metadatas=global_df.to_dict('records'))
|
|
|
|
| 144 |
print(f"RAG 初始化警告: {e}")
|
| 145 |
|
| 146 |
rag_initialized = True
|
| 147 |
+
yield f"✅ 同步成功!已載入 {len(restaurant_list)} 間餐廳。"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
except Exception as e:
|
| 149 |
+
yield f"❌ 系統錯誤:{str(e)}"
|
| 150 |
|
| 151 |
# ==========================================
|
| 152 |
# 3. 核心功能 (AI Agent)
|
| 153 |
# ==========================================
|
| 154 |
+
def get_restaurant_data(mood_score_str, food_choice):
|
|
|
|
|
|
|
|
|
|
| 155 |
init_static_data() # 確保心情指南已載入
|
| 156 |
|
| 157 |
if global_df is None or global_df.empty:
|
|
|
|
| 172 |
mood_reason = mood_info.iloc[0]['原因']
|
| 173 |
|
| 174 |
candidates = global_df.copy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
food_keyword = "飯" if food_choice == "吃飯" else "麵" if food_choice == "吃麵" else ""
|
| 176 |
|
| 177 |
is_random = False
|
|
|
|
| 183 |
is_random = True
|
| 184 |
|
| 185 |
if candidates.empty:
|
| 186 |
+
result = global_df.sample(1).iloc[0]; is_random = True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
else:
|
| 188 |
result = candidates.sample(1).iloc[0]
|
| 189 |
|
|
|
|
| 237 |
# 回傳具體錯誤訊息
|
| 238 |
return None, str(e)
|
| 239 |
|
| 240 |
+
def mood_agent_logic(score_input, food_input, diary_input, debug_mode):
|
| 241 |
if not rag_initialized:
|
| 242 |
yield "⚠️ 請先同步地圖清單!", None, "", gr.update()
|
| 243 |
return
|
| 244 |
|
| 245 |
+
restaurant, is_random, rec_categories, mood_reason = get_restaurant_data(score_input, food_input)
|
| 246 |
+
if restaurant is None:
|
| 247 |
+
yield "資料庫讀取錯誤", None, "", gr.update()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
return
|
| 249 |
|
| 250 |
name = restaurant['Name']; url = restaurant['URL']
|
|
|
|
| 256 |
ai_text, groq_debug_log = generate_content_with_groq(name, rag_info, diary_input, score_input, mood_reason, debug_mode)
|
| 257 |
|
| 258 |
prefix = ""
|
| 259 |
+
if is_random and food_input != "隨便":
|
| 260 |
+
prefix = f"> 💡 **溫馨提示**:清單中暫無『{food_input}』,已從現有名單挑選最適合的店!\n\n"
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
# 組合完整的除錯資訊,包含 API Key 狀態與錯誤訊息
|
| 263 |
full_debug_log = ""
|
|
|
|
| 271 |
- **GROQ_API_KEY**: {groq_status}
|
| 272 |
- **HF_TOKEN**: {hf_status}
|
| 273 |
- **RAG 狀態**: {"✅ 已初始化" if rag_initialized else "❌ 未初始化"}
|
|
|
|
| 274 |
"""
|
| 275 |
full_debug_log = api_debug_block + "\n" + groq_debug_log + f"\n\n### 🎨 Image Prompt Debug\n{img_prompt}"
|
| 276 |
|
|
|
|
| 369 |
with gr.Row():
|
| 370 |
map_url = gr.Textbox(label="Google Maps Saved Lists 連結", placeholder="請貼上清單的分享連結...", scale=3)
|
| 371 |
sync_btn = gr.Button("🔄 同步清單", variant="secondary", scale=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
sync_msg = gr.Markdown("ℹ️ 尚未同步資料庫")
|
| 373 |
gr.Markdown("---")
|
| 374 |
gr.Markdown(f"## 🍱 今天想吃點什麼?")
|
|
|
|
| 388 |
map_output = gr.HTML(label="地圖導航")
|
| 389 |
|
| 390 |
# 事件
|
| 391 |
+
sync_btn.click(fn=sync_google_maps, inputs=[map_url], outputs=[sync_msg])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
btn_start.click(fn=bridge_start_click, inputs=[st_state], outputs=[result_markdown, cam, btn_stop, btn_start, st_state, btn_go_dining])
|
| 393 |
btn_stop.click(fn=bridge_stop_click, inputs=[st_state], outputs=[result_markdown, cam, btn_stop, btn_start, st_state])
|
| 394 |
cam.stream(fn=bridge_predict_frame, inputs=[cam, st_state], outputs=[cam, result_markdown, st_state, btn_stop, btn_start, score_input, btn_go_dining])
|
| 395 |
btn_go_dining.click(fn=lambda: gr.Tabs(selected=1), inputs=None, outputs=tabs)
|
| 396 |
|
| 397 |
+
# debug_mode
|
| 398 |
submit_btn.click(
|
| 399 |
fn=mood_agent_logic,
|
| 400 |
+
inputs=[score_input, food_input, diary_input, debug_mode_btn],
|
| 401 |
outputs=[agent_output, image_output, map_output, debug_output]
|
| 402 |
)
|
| 403 |
|