DeepLearning101 commited on
Commit
4cd5a6a
·
verified ·
1 Parent(s): 9297b21

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -169
app.py CHANGED
@@ -22,27 +22,17 @@ except Exception as e:
22
  # --- Helper Functions ---
23
 
24
  def get_key(c):
25
- # 使用公司名稱當作 Key
26
  return f"{c['name']}"
27
 
28
  def load_data():
29
  data = []
30
- # 1. 嘗試從雲端下載
31
  if HF_TOKEN and DATASET_REPO_ID:
32
  try:
33
- print(f"正在同步雲端資料: {DATASET_REPO_ID}...")
34
- hf_hub_download(
35
- repo_id=DATASET_REPO_ID,
36
- filename=SAVE_FILE,
37
- repo_type="dataset",
38
- token=HF_TOKEN,
39
- local_dir="." # 覆蓋本地檔案
40
- )
41
- print("雲端同步完成。")
42
- except Exception as e:
43
- print(f"雲端同步略過 (初次啟動或無權限): {e}")
44
 
45
- # 2. 讀取檔案
46
  if os.path.exists(SAVE_FILE):
47
  try:
48
  with open(SAVE_FILE, 'r', encoding='utf-8') as f:
@@ -52,35 +42,25 @@ def load_data():
52
  return data
53
 
54
  def save_data(data):
55
- # 1. 存本地
56
  try:
57
  with open(SAVE_FILE, 'w', encoding='utf-8') as f:
58
  json.dump(data, f, ensure_ascii=False, indent=2)
59
  except Exception as e:
60
- print(f"Save Error: {e}")
61
  return
62
 
63
- # 2. 上傳雲端
64
  if HF_TOKEN and DATASET_REPO_ID:
65
  try:
66
  api = HfApi(token=HF_TOKEN)
67
- api.upload_file(
68
- path_or_fileobj=SAVE_FILE,
69
- path_in_repo=SAVE_FILE,
70
- repo_id=DATASET_REPO_ID,
71
- repo_type="dataset",
72
- commit_message="Sync company data"
73
- )
74
  except Exception as e:
75
- print(f"Upload Error: {e}")
76
 
77
  def format_df(source_list, saved_list):
78
  if not source_list:
79
  return pd.DataFrame(columns=["狀態", "公司名稱", "產業類別", "標籤"])
80
 
81
- if saved_list is None:
82
- saved_list = []
83
-
84
  saved_map = {get_key(c): c for c in saved_list}
85
 
86
  data = []
@@ -102,19 +82,17 @@ def format_df(source_list, saved_list):
102
  return pd.DataFrame(data, columns=["狀態", "公司名稱", "產業類別", "標籤"])
103
 
104
  def get_tags_text(comp):
105
- if not comp or not comp.get('tags'):
106
- return "目前標籤: (無)"
107
  return "🏷️ " + ", ".join([f"`{t}`" for t in comp['tags']])
108
 
109
  def get_tags_choices(comp):
110
  if not comp: return []
111
  return comp.get('tags', [])
112
 
113
- # --- Event Handlers ---
114
 
115
  def search_companies(query, current_saved):
116
  if not query: return gr.update(), current_saved, gr.update()
117
-
118
  try:
119
  results = gemini_service.search_companies(query)
120
  return format_df(results, current_saved), results, gr.update(visible=True)
@@ -123,16 +101,13 @@ def search_companies(query, current_saved):
123
 
124
  def load_more(query, current_results, current_saved):
125
  if not query: return gr.update(), current_results
126
-
127
  current_names = [c['name'] for c in current_results]
128
  try:
129
  new_results = gemini_service.search_companies(query, exclude_names=current_names)
130
-
131
  existing_keys = set(get_key(c) for c in current_results)
132
  for c in new_results:
133
  if get_key(c) not in existing_keys:
134
  current_results.append(c)
135
-
136
  return format_df(current_results, current_saved), current_results
137
  except Exception as e:
138
  raise gr.Error(f"載入失敗: {e}")
@@ -140,28 +115,24 @@ def load_more(query, current_results, current_saved):
140
  def select_company(evt: gr.SelectData, search_results, saved_data, view_mode):
141
  if not evt: return [gr.update()] * 8
142
  index = evt.index[0]
143
-
144
  target_list = saved_data if view_mode == "追蹤清單" else search_results
145
- if not target_list or index >= len(target_list):
146
- return gr.update(), gr.update(), gr.update(), None, None, gr.update(), gr.update(), gr.update()
147
 
148
  comp = target_list[index]
149
-
150
  key = get_key(comp)
151
  saved_comp = next((c for c in saved_data if get_key(c) == key), None)
152
  current_comp = saved_comp if saved_comp else comp
153
 
154
  details_md = ""
155
 
156
- # Check Cache
157
  if current_comp.get('details') and len(current_comp.get('details')) > 10:
158
  details_md = current_comp['details']
159
- if not saved_comp:
160
  saved_data.insert(0, current_comp)
161
  save_data(saved_data)
162
  else:
163
- # Call API
164
- gr.Info(f"正在調查 {current_comp['name']} (查詢統編、PTT評價)...")
165
  try:
166
  res = gemini_service.get_company_details(current_comp)
167
  current_comp['details'] = res['text']
@@ -182,73 +153,40 @@ def select_company(evt: gr.SelectData, search_results, saved_data, view_mode):
182
  details_md += f"- [{s['title']}]({s['uri']})\n"
183
 
184
  return (
185
- gr.update(visible=True),
186
- details_md,
187
- [],
188
- current_comp,
189
- saved_data,
190
- get_tags_text(current_comp),
191
- gr.update(choices=get_tags_choices(current_comp), value=None),
192
- gr.update(visible=True)
193
  )
194
 
195
  def add_tag(new_tag, selected_comp, saved_data, view_mode, search_results):
196
- if not selected_comp or not new_tag:
197
- return gr.update(), gr.update(), gr.update(), saved_data, gr.update()
198
-
199
  if 'tags' not in selected_comp: selected_comp['tags'] = []
200
 
201
  if new_tag not in selected_comp['tags']:
202
  selected_comp['tags'].append(new_tag)
203
-
204
  key = get_key(selected_comp)
205
  found = False
206
  for i, c in enumerate(saved_data):
207
  if get_key(c) == key:
208
  saved_data[i] = selected_comp
209
- found = True
210
- break
211
- if not found:
212
- saved_data.insert(0, selected_comp)
213
-
214
  save_data(saved_data)
215
  gr.Info(f"已新增標籤: {new_tag}")
216
 
217
  target_list = saved_data if view_mode == "追蹤清單" else search_results
218
- new_df = format_df(target_list, saved_data)
219
 
220
- return (
221
- gr.update(value=""),
222
- get_tags_text(selected_comp),
223
- gr.update(choices=selected_comp['tags']),
224
- saved_data,
225
- new_df
226
- )
227
-
228
- def remove_tag(tag_to_remove, selected_comp, saved_data, view_mode, search_results):
229
- if not selected_comp or not tag_to_remove:
230
- return gr.update(), gr.update(), saved_data, gr.update()
231
-
232
- if 'tags' in selected_comp and tag_to_remove in selected_comp['tags']:
233
- selected_comp['tags'].remove(tag_to_remove)
234
-
235
  key = get_key(selected_comp)
236
  for i, c in enumerate(saved_data):
237
- if get_key(c) == key:
238
- saved_data[i] = selected_comp
239
- break
240
  save_data(saved_data)
241
- gr.Info(f"已移除標籤: {tag_to_remove}")
242
-
243
  target_list = saved_data if view_mode == "追蹤清單" else search_results
244
- new_df = format_df(target_list, saved_data)
245
-
246
- return (
247
- get_tags_text(selected_comp),
248
- gr.update(choices=selected_comp['tags'], value=None),
249
- saved_data,
250
- new_df
251
- )
252
 
253
  def chat_response(history, message, selected_comp):
254
  if not selected_comp: return history, ""
@@ -269,40 +207,25 @@ def chat_response(history, message, selected_comp):
269
 
270
  def update_status(status, selected_comp, saved_data, view_mode, search_results):
271
  if not selected_comp: return gr.update(), saved_data
272
-
273
  selected_comp['status'] = status if selected_comp.get('status') != status else None
274
-
275
  key = get_key(selected_comp)
276
  for i, c in enumerate(saved_data):
277
- if get_key(c) == key:
278
- saved_data[i] = selected_comp
279
- break
280
  save_data(saved_data)
281
-
282
  target_list = saved_data if view_mode == "追蹤清單" else search_results
283
  return format_df(target_list, saved_data), saved_data
284
 
285
  def remove_comp(selected_comp, saved_data, view_mode, search_results):
286
  if not selected_comp: return gr.update(), gr.update(value=None), saved_data, gr.update(visible=False)
287
-
288
  key = get_key(selected_comp)
289
  new_saved = [c for c in saved_data if get_key(c) != key]
290
  save_data(new_saved)
291
-
292
  target_list = new_saved if view_mode == "追蹤清單" else search_results
293
-
294
- return (
295
- gr.Info("已移除"),
296
- format_df(target_list, new_saved),
297
- new_saved,
298
- gr.update(visible=False)
299
- )
300
 
301
  def toggle_view(mode, search_res, saved_data):
302
- if mode == "搜尋結果":
303
- return format_df(search_res, saved_data), gr.update(visible=True)
304
- else:
305
- return format_df(saved_data, saved_data), gr.update(visible=False)
306
 
307
  def init_on_load():
308
  data = load_data()
@@ -310,23 +233,21 @@ def init_on_load():
310
 
311
  # --- UI Layout ---
312
 
313
- with gr.Blocks(title="Com.404 台企天眼通", theme=gr.themes.Soft()) as demo:
314
-
315
  saved_state = gr.State([])
316
  search_res_state = gr.State([])
317
  selected_comp_state = gr.State(None)
318
 
319
- # 🌟 Com.404 專屬 Header
320
  gr.Markdown("""
321
  <div align="center">
322
 
323
- # 🏢 Com.404 - 台企天眼通 (Company Scout)
324
 
325
  [![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/DeepLearning101/Com.404) &nbsp;
326
  [![GitHub](https://img.shields.io/badge/GitHub-Repo-black)](https://github.com/Deep-Learning-101/prof-404) &nbsp;
327
  [![Powered by](https://img.shields.io/badge/Powered%20by-Gemini%202.0%20Flash-4285F4?logo=google)](https://deepmind.google/technologies/gemini/)
328
 
329
- **查統編、看資本額、搜 PTT/Dcard 評價、掃描勞資糾紛,一鍵完成。**
330
  <span style="font-size: 0.9em; color: gray;">(支援雲端同步!Space 重啟資料不遺失 🔄 | API KEY RPD,建議自行 Fork)</span>
331
 
332
  👉 歡迎 Star [GitHub](https://github.com/Deep-Learning-101/prof-404) ⭐ 覺得不錯 👈
@@ -347,7 +268,7 @@ with gr.Blocks(title="Com.404 台企天眼通", theme=gr.themes.Soft()) as demo:
347
  """)
348
 
349
  with gr.Row():
350
- search_input = gr.Textbox(label="輸入公司名稱或統編", placeholder="例如: 台積電, 2330, 八方雲集...", scale=4)
351
  search_btn = gr.Button("🔍 搜尋", variant="primary", scale=1)
352
 
353
  with gr.Row():
@@ -368,12 +289,12 @@ with gr.Blocks(title="Com.404 台企天眼通", theme=gr.themes.Soft()) as demo:
368
  with gr.Column(scale=2, visible=False) as details_col:
369
  detail_md = gr.Markdown("詳細資料...")
370
 
371
- # 🌟 Chat Section (位於中間)
372
  with gr.Column(elem_classes="chat-section"):
373
- gr.Markdown("### 🤖 商業分析師 (已閱讀下方報告)")
374
  chatbot = gr.Chatbot(height=250, type="messages")
375
  with gr.Row():
376
- msg = gr.Textbox(label="提問", placeholder="例如:這間公司有勞資糾紛嗎?薪資結構如何?", scale=4)
377
  send_btn = gr.Button("送出", scale=1)
378
 
379
  gr.Markdown("---")
@@ -382,9 +303,8 @@ with gr.Blocks(title="Com.404 台企天眼通", theme=gr.themes.Soft()) as demo:
382
  with gr.Column(visible=False) as tags_row:
383
  tags_display = gr.Markdown("目前標籤: (無)")
384
  with gr.Row():
385
- tag_input = gr.Textbox(label="新增標籤", placeholder="例如: 面試過, 薪水高...", scale=3)
386
  tag_add_btn = gr.Button("➕ 新增", scale=1)
387
-
388
  with gr.Accordion("刪除標籤", open=False):
389
  with gr.Row():
390
  tag_dropdown = gr.Dropdown(label="選擇標籤", choices=[], scale=3)
@@ -397,70 +317,28 @@ with gr.Blocks(title="Com.404 台企天眼通", theme=gr.themes.Soft()) as demo:
397
  btn_remove = gr.Button("🗑️ 移除", variant="stop")
398
 
399
  # --- Wiring ---
400
-
401
- # Init
402
  demo.load(init_on_load, inputs=None, outputs=[saved_state, comp_df])
403
 
404
- # Search & Load More
405
- search_btn.click(
406
- search_companies,
407
- inputs=[search_input, saved_state],
408
- outputs=[comp_df, search_res_state, load_more_btn]
409
- ).then(
410
- lambda: gr.update(value="搜尋結果"), outputs=[view_radio]
411
- )
412
-
413
- load_more_btn.click(
414
- load_more,
415
- inputs=[search_input, search_res_state, saved_state],
416
- outputs=[comp_df, search_res_state]
417
- )
418
 
419
- view_radio.change(
420
- toggle_view,
421
- inputs=[view_radio, search_res_state, saved_state],
422
- outputs=[comp_df, load_more_btn]
423
- )
424
-
425
- # Selection
426
  comp_df.select(
427
  select_company,
428
  inputs=[search_res_state, saved_state, view_radio],
429
- outputs=[
430
- details_col, detail_md, chatbot, selected_comp_state, saved_state,
431
- tags_display, tag_dropdown, tags_row
432
- ]
433
  )
434
 
435
- # Chat
436
  send_btn.click(chat_response, inputs=[chatbot, msg, selected_comp_state], outputs=[chatbot, msg])
437
  msg.submit(chat_response, inputs=[chatbot, msg, selected_comp_state], outputs=[chatbot, msg])
438
 
439
- # Tags
440
- tag_add_btn.click(
441
- add_tag,
442
- inputs=[tag_input, selected_comp_state, saved_state, view_radio, search_res_state],
443
- outputs=[tag_input, tags_display, tag_dropdown, saved_state, comp_df]
444
- )
445
- tag_del_btn.click(
446
- remove_tag,
447
- inputs=[tag_dropdown, selected_comp_state, saved_state, view_radio, search_res_state],
448
- outputs=[tags_display, tag_dropdown, saved_state, comp_df]
449
- )
450
 
451
- # Status & Remove
452
  for btn, status in [(btn_good, 'good'), (btn_risk, 'risk'), (btn_pending, 'pending')]:
453
- btn.click(
454
- update_status,
455
- inputs=[gr.State(status), selected_comp_state, saved_state, view_radio, search_res_state],
456
- outputs=[comp_df, saved_state]
457
- )
458
 
459
- btn_remove.click(
460
- remove_comp,
461
- inputs=[selected_comp_state, saved_state, view_radio, search_res_state],
462
- outputs=[gr.State(None), comp_df, saved_state, details_col]
463
- )
464
 
465
  if __name__ == "__main__":
466
  demo.launch()
 
22
  # --- Helper Functions ---
23
 
24
  def get_key(c):
 
25
  return f"{c['name']}"
26
 
27
  def load_data():
28
  data = []
 
29
  if HF_TOKEN and DATASET_REPO_ID:
30
  try:
31
+ print(f"同步雲端資料: {DATASET_REPO_ID}...")
32
+ hf_hub_download(repo_id=DATASET_REPO_ID, filename=SAVE_FILE, repo_type="dataset", token=HF_TOKEN, local_dir=".")
33
+ except Exception:
34
+ pass
 
 
 
 
 
 
 
35
 
 
36
  if os.path.exists(SAVE_FILE):
37
  try:
38
  with open(SAVE_FILE, 'r', encoding='utf-8') as f:
 
42
  return data
43
 
44
  def save_data(data):
 
45
  try:
46
  with open(SAVE_FILE, 'w', encoding='utf-8') as f:
47
  json.dump(data, f, ensure_ascii=False, indent=2)
48
  except Exception as e:
49
+ print(f"Local Save Error: {e}")
50
  return
51
 
 
52
  if HF_TOKEN and DATASET_REPO_ID:
53
  try:
54
  api = HfApi(token=HF_TOKEN)
55
+ api.upload_file(path_or_fileobj=SAVE_FILE, path_in_repo=SAVE_FILE, repo_id=DATASET_REPO_ID, repo_type="dataset", commit_message="Sync company data")
 
 
 
 
 
 
56
  except Exception as e:
57
+ print(f"Cloud Upload Error: {e}")
58
 
59
  def format_df(source_list, saved_list):
60
  if not source_list:
61
  return pd.DataFrame(columns=["狀態", "公司名稱", "產業類別", "標籤"])
62
 
63
+ if saved_list is None: saved_list = []
 
 
64
  saved_map = {get_key(c): c for c in saved_list}
65
 
66
  data = []
 
82
  return pd.DataFrame(data, columns=["狀態", "公司名稱", "產業類別", "標籤"])
83
 
84
  def get_tags_text(comp):
85
+ if not comp or not comp.get('tags'): return "目前標籤: (無)"
 
86
  return "🏷️ " + ", ".join([f"`{t}`" for t in comp['tags']])
87
 
88
  def get_tags_choices(comp):
89
  if not comp: return []
90
  return comp.get('tags', [])
91
 
92
+ # --- Logic Handlers ---
93
 
94
  def search_companies(query, current_saved):
95
  if not query: return gr.update(), current_saved, gr.update()
 
96
  try:
97
  results = gemini_service.search_companies(query)
98
  return format_df(results, current_saved), results, gr.update(visible=True)
 
101
 
102
  def load_more(query, current_results, current_saved):
103
  if not query: return gr.update(), current_results
 
104
  current_names = [c['name'] for c in current_results]
105
  try:
106
  new_results = gemini_service.search_companies(query, exclude_names=current_names)
 
107
  existing_keys = set(get_key(c) for c in current_results)
108
  for c in new_results:
109
  if get_key(c) not in existing_keys:
110
  current_results.append(c)
 
111
  return format_df(current_results, current_saved), current_results
112
  except Exception as e:
113
  raise gr.Error(f"載入失敗: {e}")
 
115
  def select_company(evt: gr.SelectData, search_results, saved_data, view_mode):
116
  if not evt: return [gr.update()] * 8
117
  index = evt.index[0]
 
118
  target_list = saved_data if view_mode == "追蹤清單" else search_results
119
+ if not target_list or index >= len(target_list): return [gr.update()] * 8
 
120
 
121
  comp = target_list[index]
 
122
  key = get_key(comp)
123
  saved_comp = next((c for c in saved_data if get_key(c) == key), None)
124
  current_comp = saved_comp if saved_comp else comp
125
 
126
  details_md = ""
127
 
128
+ # Cache Check
129
  if current_comp.get('details') and len(current_comp.get('details')) > 10:
130
  details_md = current_comp['details']
131
+ if not saved_comp:
132
  saved_data.insert(0, current_comp)
133
  save_data(saved_data)
134
  else:
135
+ gr.Info(f"正在調查 {current_comp['name']} (資本額、PTT評價、風險分析)...")
 
136
  try:
137
  res = gemini_service.get_company_details(current_comp)
138
  current_comp['details'] = res['text']
 
153
  details_md += f"- [{s['title']}]({s['uri']})\n"
154
 
155
  return (
156
+ gr.update(visible=True), details_md, [], current_comp, saved_data,
157
+ get_tags_text(current_comp), gr.update(choices=get_tags_choices(current_comp), value=None), gr.update(visible=True)
 
 
 
 
 
 
158
  )
159
 
160
  def add_tag(new_tag, selected_comp, saved_data, view_mode, search_results):
161
+ if not selected_comp or not new_tag: return gr.update(), gr.update(), gr.update(), saved_data, gr.update()
 
 
162
  if 'tags' not in selected_comp: selected_comp['tags'] = []
163
 
164
  if new_tag not in selected_comp['tags']:
165
  selected_comp['tags'].append(new_tag)
 
166
  key = get_key(selected_comp)
167
  found = False
168
  for i, c in enumerate(saved_data):
169
  if get_key(c) == key:
170
  saved_data[i] = selected_comp
171
+ found = True; break
172
+ if not found: saved_data.insert(0, selected_comp)
 
 
 
173
  save_data(saved_data)
174
  gr.Info(f"已新增標籤: {new_tag}")
175
 
176
  target_list = saved_data if view_mode == "追蹤清單" else search_results
177
+ return gr.update(value=""), get_tags_text(selected_comp), gr.update(choices=selected_comp['tags']), saved_data, format_df(target_list, saved_data)
178
 
179
+ def remove_tag(tag, selected_comp, saved_data, view_mode, search_results):
180
+ if not selected_comp or not tag: return gr.update(), gr.update(), saved_data, gr.update()
181
+ if 'tags' in selected_comp and tag in selected_comp['tags']:
182
+ selected_comp['tags'].remove(tag)
 
 
 
 
 
 
 
 
 
 
 
183
  key = get_key(selected_comp)
184
  for i, c in enumerate(saved_data):
185
+ if get_key(c) == key: saved_data[i] = selected_comp; break
 
 
186
  save_data(saved_data)
187
+ gr.Info(f"已移除: {tag}")
 
188
  target_list = saved_data if view_mode == "追蹤清單" else search_results
189
+ return get_tags_text(selected_comp), gr.update(choices=selected_comp['tags'], value=None), saved_data, format_df(target_list, saved_data)
 
 
 
 
 
 
 
190
 
191
  def chat_response(history, message, selected_comp):
192
  if not selected_comp: return history, ""
 
207
 
208
  def update_status(status, selected_comp, saved_data, view_mode, search_results):
209
  if not selected_comp: return gr.update(), saved_data
 
210
  selected_comp['status'] = status if selected_comp.get('status') != status else None
 
211
  key = get_key(selected_comp)
212
  for i, c in enumerate(saved_data):
213
+ if get_key(c) == key: saved_data[i] = selected_comp; break
 
 
214
  save_data(saved_data)
 
215
  target_list = saved_data if view_mode == "追蹤清單" else search_results
216
  return format_df(target_list, saved_data), saved_data
217
 
218
  def remove_comp(selected_comp, saved_data, view_mode, search_results):
219
  if not selected_comp: return gr.update(), gr.update(value=None), saved_data, gr.update(visible=False)
 
220
  key = get_key(selected_comp)
221
  new_saved = [c for c in saved_data if get_key(c) != key]
222
  save_data(new_saved)
 
223
  target_list = new_saved if view_mode == "追蹤清單" else search_results
224
+ return gr.Info("已移除"), format_df(target_list, new_saved), new_saved, gr.update(visible=False)
 
 
 
 
 
 
225
 
226
  def toggle_view(mode, search_res, saved_data):
227
+ if mode == "搜尋結果": return format_df(search_res, saved_data), gr.update(visible=True)
228
+ else: return format_df(saved_data, saved_data), gr.update(visible=False)
 
 
229
 
230
  def init_on_load():
231
  data = load_data()
 
233
 
234
  # --- UI Layout ---
235
 
236
+ with gr.Blocks(title="Com.404 公司去那兒?", theme=gr.themes.Soft()) as demo:
 
237
  saved_state = gr.State([])
238
  search_res_state = gr.State([])
239
  selected_comp_state = gr.State(None)
240
 
 
241
  gr.Markdown("""
242
  <div align="center">
243
 
244
+ # 🏢 Com.404 - 公司去那兒?
245
 
246
  [![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/DeepLearning101/Com.404) &nbsp;
247
  [![GitHub](https://img.shields.io/badge/GitHub-Repo-black)](https://github.com/Deep-Learning-101/prof-404) &nbsp;
248
  [![Powered by](https://img.shields.io/badge/Powered%20by-Gemini%202.0%20Flash-4285F4?logo=google)](https://deepmind.google/technologies/gemini/)
249
 
250
+ **產業導航、公司徵信、AI 諮詢,拒絕當求職與合作的無頭蒼蠅 🪰**
251
  <span style="font-size: 0.9em; color: gray;">(支援雲端同步!Space 重啟資料不遺失 🔄 | API KEY RPD,建議自行 Fork)</span>
252
 
253
  👉 歡迎 Star [GitHub](https://github.com/Deep-Learning-101/prof-404) ⭐ 覺得不錯 👈
 
268
  """)
269
 
270
  with gr.Row():
271
+ search_input = gr.Textbox(label="探索領域或公司", placeholder="輸入產業領域 (如: 量子計算)、技術關鍵字或公司名稱...", scale=4)
272
  search_btn = gr.Button("🔍 搜尋", variant="primary", scale=1)
273
 
274
  with gr.Row():
 
289
  with gr.Column(scale=2, visible=False) as details_col:
290
  detail_md = gr.Markdown("詳細資料...")
291
 
292
+ # Chat Section
293
  with gr.Column(elem_classes="chat-section"):
294
+ gr.Markdown("### 🤖 商業顧問 (已閱讀下方報告)")
295
  chatbot = gr.Chatbot(height=250, type="messages")
296
  with gr.Row():
297
+ msg = gr.Textbox(label="提問", placeholder="例如:這間公司適合新鮮人嗎?有勞資糾紛嗎?", scale=4)
298
  send_btn = gr.Button("送出", scale=1)
299
 
300
  gr.Markdown("---")
 
303
  with gr.Column(visible=False) as tags_row:
304
  tags_display = gr.Markdown("目前標籤: (無)")
305
  with gr.Row():
306
+ tag_input = gr.Textbox(label="新增標籤", placeholder="例如: 薪水高, 加班多...", scale=3)
307
  tag_add_btn = gr.Button("➕ 新增", scale=1)
 
308
  with gr.Accordion("刪除標籤", open=False):
309
  with gr.Row():
310
  tag_dropdown = gr.Dropdown(label="選擇標籤", choices=[], scale=3)
 
317
  btn_remove = gr.Button("🗑️ 移除", variant="stop")
318
 
319
  # --- Wiring ---
 
 
320
  demo.load(init_on_load, inputs=None, outputs=[saved_state, comp_df])
321
 
322
+ search_btn.click(search_companies, inputs=[search_input, saved_state], outputs=[comp_df, search_res_state, load_more_btn]).then(lambda: gr.update(value="搜尋結果"), outputs=[view_radio])
323
+ load_more_btn.click(load_more, inputs=[search_input, search_res_state, saved_state], outputs=[comp_df, search_res_state])
324
+ view_radio.change(toggle_view, inputs=[view_radio, search_res_state, saved_state], outputs=[comp_df, load_more_btn])
 
 
 
 
 
 
 
 
 
 
 
325
 
 
 
 
 
 
 
 
326
  comp_df.select(
327
  select_company,
328
  inputs=[search_res_state, saved_state, view_radio],
329
+ outputs=[details_col, detail_md, chatbot, selected_comp_state, saved_state, tags_display, tag_dropdown, tags_row]
 
 
 
330
  )
331
 
 
332
  send_btn.click(chat_response, inputs=[chatbot, msg, selected_comp_state], outputs=[chatbot, msg])
333
  msg.submit(chat_response, inputs=[chatbot, msg, selected_comp_state], outputs=[chatbot, msg])
334
 
335
+ tag_add_btn.click(add_tag, inputs=[tag_input, selected_comp_state, saved_state, view_radio, search_res_state], outputs=[tag_input, tags_display, tag_dropdown, saved_state, comp_df])
336
+ tag_del_btn.click(remove_tag, inputs=[tag_dropdown, selected_comp_state, saved_state, view_radio, search_res_state], outputs=[tags_display, tag_dropdown, saved_state, comp_df])
 
 
 
 
 
 
 
 
 
337
 
 
338
  for btn, status in [(btn_good, 'good'), (btn_risk, 'risk'), (btn_pending, 'pending')]:
339
+ btn.click(update_status, inputs=[gr.State(status), selected_comp_state, saved_state, view_radio, search_res_state], outputs=[comp_df, saved_state])
 
 
 
 
340
 
341
+ btn_remove.click(remove_comp, inputs=[selected_comp_state, saved_state, view_radio, search_res_state], outputs=[gr.State(None), comp_df, saved_state, details_col])
 
 
 
 
342
 
343
  if __name__ == "__main__":
344
  demo.launch()