Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -4,10 +4,13 @@ import os
|
|
| 4 |
import pandas as pd
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
from services import GeminiService
|
|
|
|
| 7 |
|
| 8 |
# Load Env
|
| 9 |
load_dotenv()
|
| 10 |
SAVE_FILE = os.getenv("SAVE_FILE_NAME", "saved_professors.json")
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# Init Service
|
| 13 |
try:
|
|
@@ -22,24 +25,55 @@ def get_key(p):
|
|
| 22 |
return f"{p['name']}-{p['university']}"
|
| 23 |
|
| 24 |
def load_data():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
if os.path.exists(SAVE_FILE):
|
| 26 |
try:
|
| 27 |
with open(SAVE_FILE, 'r', encoding='utf-8') as f:
|
| 28 |
-
|
| 29 |
except:
|
| 30 |
-
|
| 31 |
-
return
|
| 32 |
|
| 33 |
def save_data(data):
|
|
|
|
| 34 |
try:
|
| 35 |
with open(SAVE_FILE, 'w', encoding='utf-8') as f:
|
| 36 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 37 |
except Exception as e:
|
| 38 |
print(f"Save Error: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
def format_df(source_list, saved_list):
|
| 41 |
if not source_list:
|
| 42 |
-
# 如果沒有來源資料,回傳空表格
|
| 43 |
return pd.DataFrame(columns=["狀態", "姓名", "大學", "系所", "標籤"])
|
| 44 |
|
| 45 |
if saved_list is None:
|
|
@@ -82,7 +116,6 @@ def search_professors(query, current_saved):
|
|
| 82 |
|
| 83 |
try:
|
| 84 |
results = gemini_service.search_professors(query)
|
| 85 |
-
# 搜尋時,顯示搜尋結果
|
| 86 |
return format_df(results, current_saved), results, gr.update(visible=True)
|
| 87 |
except Exception as e:
|
| 88 |
raise gr.Error(f"搜尋失敗: {e}")
|
|
@@ -119,14 +152,12 @@ def select_professor_from_df(evt: gr.SelectData, search_results, saved_data, vie
|
|
| 119 |
|
| 120 |
details_md = ""
|
| 121 |
|
| 122 |
-
# Check if details are cached
|
| 123 |
if current_prof.get('details') and len(current_prof.get('details')) > 10:
|
| 124 |
details_md = current_prof['details']
|
| 125 |
if not saved_prof:
|
| 126 |
saved_data.insert(0, current_prof)
|
| 127 |
save_data(saved_data)
|
| 128 |
else:
|
| 129 |
-
# Call API
|
| 130 |
gr.Info(f"正在調查 {current_prof['name']}...")
|
| 131 |
try:
|
| 132 |
res = gemini_service.get_professor_details(current_prof)
|
|
@@ -266,25 +297,19 @@ def remove_prof(selected_prof, saved_data, view_mode, search_results):
|
|
| 266 |
|
| 267 |
def toggle_view(mode, search_res, saved_data):
|
| 268 |
if mode == "搜尋結果":
|
| 269 |
-
# 如果是搜尋結果,傳入搜尋結果清單
|
| 270 |
return format_df(search_res, saved_data), gr.update(visible=True)
|
| 271 |
else:
|
| 272 |
-
# 如果是追蹤清單,傳入追蹤清單
|
| 273 |
return format_df(saved_data, saved_data), gr.update(visible=False)
|
| 274 |
|
| 275 |
-
# 新增這個函式,用來在網頁載入時初始化畫面
|
| 276 |
def init_on_load(saved_data):
|
| 277 |
-
# 預設顯示追蹤清單,這樣使用者一重新整理就能看到資料
|
| 278 |
return format_df(saved_data, saved_data)
|
| 279 |
|
| 280 |
# --- UI Layout ---
|
| 281 |
|
| 282 |
with gr.Blocks(title="Prof.404 開箱教授去哪兒?", theme=gr.themes.Soft()) as demo:
|
| 283 |
|
| 284 |
-
# 🌟
|
| 285 |
-
|
| 286 |
-
saved_state = gr.State(load_data)
|
| 287 |
-
|
| 288 |
search_res_state = gr.State([])
|
| 289 |
selected_prof_state = gr.State(None)
|
| 290 |
|
|
@@ -294,7 +319,6 @@ with gr.Blocks(title="Prof.404 開箱教授去哪兒?", theme=gr.themes.Soft()
|
|
| 294 |
search_input = gr.Textbox(label="搜尋研究領域", placeholder="例如: 大型語言模型, 後量子密碼遷移...", scale=4)
|
| 295 |
search_btn = gr.Button("🔍 搜尋", variant="primary", scale=1)
|
| 296 |
|
| 297 |
-
# 🌟 修正點2: 預設選取 "追蹤清單",讓使用者一進來就看到存檔
|
| 298 |
with gr.Row():
|
| 299 |
view_radio = gr.Radio(["搜尋結果", "追蹤清單"], label="顯示模式", value="追蹤清單")
|
| 300 |
|
|
@@ -342,7 +366,7 @@ with gr.Blocks(title="Prof.404 開箱教授去哪兒?", theme=gr.themes.Soft()
|
|
| 342 |
|
| 343 |
# --- Wiring ---
|
| 344 |
|
| 345 |
-
#
|
| 346 |
demo.load(init_on_load, inputs=[saved_state], outputs=[prof_df])
|
| 347 |
|
| 348 |
search_btn.click(
|
|
@@ -350,7 +374,6 @@ with gr.Blocks(title="Prof.404 開箱教授去哪兒?", theme=gr.themes.Soft()
|
|
| 350 |
inputs=[search_input, saved_state],
|
| 351 |
outputs=[prof_df, search_res_state, load_more_btn]
|
| 352 |
).then(
|
| 353 |
-
# 搜尋後自動切換到 "搜尋結果" tab
|
| 354 |
lambda: gr.update(value="搜尋結果"), outputs=[view_radio]
|
| 355 |
)
|
| 356 |
|
|
|
|
| 4 |
import pandas as pd
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
from services import GeminiService
|
| 7 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 8 |
|
| 9 |
# Load Env
|
| 10 |
load_dotenv()
|
| 11 |
SAVE_FILE = os.getenv("SAVE_FILE_NAME", "saved_professors.json")
|
| 12 |
+
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 13 |
+
DATASET_REPO_ID = os.getenv("DATASET_REPO_ID")
|
| 14 |
|
| 15 |
# Init Service
|
| 16 |
try:
|
|
|
|
| 25 |
return f"{p['name']}-{p['university']}"
|
| 26 |
|
| 27 |
def load_data():
|
| 28 |
+
data = []
|
| 29 |
+
# 1. 嘗試從雲端下載
|
| 30 |
+
if HF_TOKEN and DATASET_REPO_ID:
|
| 31 |
+
try:
|
| 32 |
+
print(f"正在同步雲端資料: {DATASET_REPO_ID}...")
|
| 33 |
+
hf_hub_download(
|
| 34 |
+
repo_id=DATASET_REPO_ID,
|
| 35 |
+
filename=SAVE_FILE,
|
| 36 |
+
repo_type="dataset",
|
| 37 |
+
token=HF_TOKEN,
|
| 38 |
+
local_dir="." # 覆蓋本地檔案
|
| 39 |
+
)
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"雲端同步略過 (初次啟動或無權限): {e}")
|
| 42 |
+
|
| 43 |
+
# 2. 讀取檔案
|
| 44 |
if os.path.exists(SAVE_FILE):
|
| 45 |
try:
|
| 46 |
with open(SAVE_FILE, 'r', encoding='utf-8') as f:
|
| 47 |
+
data = json.load(f)
|
| 48 |
except:
|
| 49 |
+
data = []
|
| 50 |
+
return data
|
| 51 |
|
| 52 |
def save_data(data):
|
| 53 |
+
# 1. 存本地
|
| 54 |
try:
|
| 55 |
with open(SAVE_FILE, 'w', encoding='utf-8') as f:
|
| 56 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 57 |
except Exception as e:
|
| 58 |
print(f"Save Error: {e}")
|
| 59 |
+
return
|
| 60 |
+
|
| 61 |
+
# 2. 上傳雲端
|
| 62 |
+
if HF_TOKEN and DATASET_REPO_ID:
|
| 63 |
+
try:
|
| 64 |
+
api = HfApi(token=HF_TOKEN)
|
| 65 |
+
api.upload_file(
|
| 66 |
+
path_or_fileobj=SAVE_FILE,
|
| 67 |
+
path_in_repo=SAVE_FILE,
|
| 68 |
+
repo_id=DATASET_REPO_ID,
|
| 69 |
+
repo_type="dataset",
|
| 70 |
+
commit_message="Sync data from Space"
|
| 71 |
+
)
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"Upload Error: {e}")
|
| 74 |
|
| 75 |
def format_df(source_list, saved_list):
|
| 76 |
if not source_list:
|
|
|
|
| 77 |
return pd.DataFrame(columns=["狀態", "姓名", "大學", "系所", "標籤"])
|
| 78 |
|
| 79 |
if saved_list is None:
|
|
|
|
| 116 |
|
| 117 |
try:
|
| 118 |
results = gemini_service.search_professors(query)
|
|
|
|
| 119 |
return format_df(results, current_saved), results, gr.update(visible=True)
|
| 120 |
except Exception as e:
|
| 121 |
raise gr.Error(f"搜尋失敗: {e}")
|
|
|
|
| 152 |
|
| 153 |
details_md = ""
|
| 154 |
|
|
|
|
| 155 |
if current_prof.get('details') and len(current_prof.get('details')) > 10:
|
| 156 |
details_md = current_prof['details']
|
| 157 |
if not saved_prof:
|
| 158 |
saved_data.insert(0, current_prof)
|
| 159 |
save_data(saved_data)
|
| 160 |
else:
|
|
|
|
| 161 |
gr.Info(f"正在調查 {current_prof['name']}...")
|
| 162 |
try:
|
| 163 |
res = gemini_service.get_professor_details(current_prof)
|
|
|
|
| 297 |
|
| 298 |
def toggle_view(mode, search_res, saved_data):
|
| 299 |
if mode == "搜尋結果":
|
|
|
|
| 300 |
return format_df(search_res, saved_data), gr.update(visible=True)
|
| 301 |
else:
|
|
|
|
| 302 |
return format_df(saved_data, saved_data), gr.update(visible=False)
|
| 303 |
|
|
|
|
| 304 |
def init_on_load(saved_data):
|
|
|
|
| 305 |
return format_df(saved_data, saved_data)
|
| 306 |
|
| 307 |
# --- UI Layout ---
|
| 308 |
|
| 309 |
with gr.Blocks(title="Prof.404 開箱教授去哪兒?", theme=gr.themes.Soft()) as demo:
|
| 310 |
|
| 311 |
+
# 🌟 使用 load_data 函式本身作為 State 初始值 (不加括號),確保每次開啟都會重新讀取
|
| 312 |
+
saved_state = gr.State(load_data)
|
|
|
|
|
|
|
| 313 |
search_res_state = gr.State([])
|
| 314 |
selected_prof_state = gr.State(None)
|
| 315 |
|
|
|
|
| 319 |
search_input = gr.Textbox(label="搜尋研究領域", placeholder="例如: 大型語言模型, 後量子密碼遷移...", scale=4)
|
| 320 |
search_btn = gr.Button("🔍 搜尋", variant="primary", scale=1)
|
| 321 |
|
|
|
|
| 322 |
with gr.Row():
|
| 323 |
view_radio = gr.Radio(["搜尋結果", "追蹤清單"], label="顯示模式", value="追蹤清單")
|
| 324 |
|
|
|
|
| 366 |
|
| 367 |
# --- Wiring ---
|
| 368 |
|
| 369 |
+
# 🌟 啟動時自動載入資料到表格
|
| 370 |
demo.load(init_on_load, inputs=[saved_state], outputs=[prof_df])
|
| 371 |
|
| 372 |
search_btn.click(
|
|
|
|
| 374 |
inputs=[search_input, saved_state],
|
| 375 |
outputs=[prof_df, search_res_state, load_more_btn]
|
| 376 |
).then(
|
|
|
|
| 377 |
lambda: gr.update(value="搜尋結果"), outputs=[view_radio]
|
| 378 |
)
|
| 379 |
|