DeepLearning101 commited on
Commit
5921281
·
verified ·
1 Parent(s): f39b90a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +155 -41
app.py CHANGED
@@ -26,13 +26,22 @@ def get_key(c):
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,25 +51,35 @@ def load_data():
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,17 +101,19 @@ def format_df(source_list, saved_list):
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,13 +122,16 @@ def search_companies(query, current_saved):
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,24 +139,28 @@ def load_more(query, current_results, current_saved):
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,40 +181,73 @@ def select_company(evt: gr.SelectData, search_results, saved_data, view_mode):
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,25 +268,40 @@ def chat_response(history, message, selected_comp):
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()
@@ -234,6 +310,7 @@ def init_on_load():
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)
@@ -292,7 +369,8 @@ with gr.Blocks(title="Com.404 公司去那兒?", theme=gr.themes.Soft()) as de
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)
@@ -317,28 +395,64 @@ with gr.Blocks(title="Com.404 公司去那兒?", theme=gr.themes.Soft()) as de
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()
 
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
+ print("雲端同步完成。")
41
+ except Exception as e:
42
+ print(f"雲端同步略過 (初次啟動或無權限): {e}")
43
 
44
+ # 2. 讀取檔案
45
  if os.path.exists(SAVE_FILE):
46
  try:
47
  with open(SAVE_FILE, 'r', encoding='utf-8') as f:
 
51
  return data
52
 
53
  def save_data(data):
54
+ # 1. 存本地
55
  try:
56
  with open(SAVE_FILE, 'w', encoding='utf-8') as f:
57
  json.dump(data, f, ensure_ascii=False, indent=2)
58
  except Exception as e:
59
+ print(f"Save Error: {e}")
60
  return
61
 
62
+ # 2. 上傳雲端
63
  if HF_TOKEN and DATASET_REPO_ID:
64
  try:
65
  api = HfApi(token=HF_TOKEN)
66
+ api.upload_file(
67
+ path_or_fileobj=SAVE_FILE,
68
+ path_in_repo=SAVE_FILE,
69
+ repo_id=DATASET_REPO_ID,
70
+ repo_type="dataset",
71
+ commit_message="Sync company data"
72
+ )
73
  except Exception as e:
74
+ print(f"Upload Error: {e}")
75
 
76
  def format_df(source_list, saved_list):
77
  if not source_list:
78
  return pd.DataFrame(columns=["狀態", "公司名稱", "產業類別", "標籤"])
79
 
80
+ if saved_list is None:
81
+ saved_list = []
82
+
83
  saved_map = {get_key(c): c for c in saved_list}
84
 
85
  data = []
 
101
  return pd.DataFrame(data, columns=["狀態", "公司名稱", "產業類別", "標籤"])
102
 
103
  def get_tags_text(comp):
104
+ if not comp or not comp.get('tags'):
105
+ return "目前標籤: (無)"
106
  return "🏷️ " + ", ".join([f"`{t}`" for t in comp['tags']])
107
 
108
  def get_tags_choices(comp):
109
  if not comp: return []
110
  return comp.get('tags', [])
111
 
112
+ # --- Event Handlers ---
113
 
114
  def search_companies(query, current_saved):
115
  if not query: return gr.update(), current_saved, gr.update()
116
+
117
  try:
118
  results = gemini_service.search_companies(query)
119
  return format_df(results, current_saved), results, gr.update(visible=True)
 
122
 
123
  def load_more(query, current_results, current_saved):
124
  if not query: return gr.update(), current_results
125
+
126
  current_names = [c['name'] for c in current_results]
127
  try:
128
  new_results = gemini_service.search_companies(query, exclude_names=current_names)
129
+
130
  existing_keys = set(get_key(c) for c in current_results)
131
  for c in new_results:
132
  if get_key(c) not in existing_keys:
133
  current_results.append(c)
134
+
135
  return format_df(current_results, current_saved), current_results
136
  except Exception as e:
137
  raise gr.Error(f"載入失敗: {e}")
 
139
  def select_company(evt: gr.SelectData, search_results, saved_data, view_mode):
140
  if not evt: return [gr.update()] * 8
141
  index = evt.index[0]
142
+
143
  target_list = saved_data if view_mode == "追蹤清單" else search_results
144
+ if not target_list or index >= len(target_list):
145
+ return gr.update(), gr.update(), gr.update(), None, None, gr.update(), gr.update(), gr.update()
146
 
147
  comp = target_list[index]
148
+
149
  key = get_key(comp)
150
  saved_comp = next((c for c in saved_data if get_key(c) == key), None)
151
  current_comp = saved_comp if saved_comp else comp
152
 
153
  details_md = ""
154
 
155
+ # Check Cache
156
  if current_comp.get('details') and len(current_comp.get('details')) > 10:
157
  details_md = current_comp['details']
158
+ if not saved_comp:
159
  saved_data.insert(0, current_comp)
160
  save_data(saved_data)
161
  else:
162
+ # Call API
163
+ gr.Info(f"正在調�� {current_comp['name']} (查詢統編、PTT評價)...")
164
  try:
165
  res = gemini_service.get_company_details(current_comp)
166
  current_comp['details'] = res['text']
 
181
  details_md += f"- [{s['title']}]({s['uri']})\n"
182
 
183
  return (
184
+ gr.update(visible=True),
185
+ details_md,
186
+ [],
187
+ current_comp,
188
+ saved_data,
189
+ get_tags_text(current_comp),
190
+ gr.update(choices=get_tags_choices(current_comp), value=None),
191
+ gr.update(visible=True)
192
  )
193
 
194
  def add_tag(new_tag, selected_comp, saved_data, view_mode, search_results):
195
+ if not selected_comp or not new_tag:
196
+ return gr.update(), gr.update(), gr.update(), saved_data, gr.update()
197
+
198
  if 'tags' not in selected_comp: selected_comp['tags'] = []
199
 
200
  if new_tag not in selected_comp['tags']:
201
  selected_comp['tags'].append(new_tag)
202
+
203
  key = get_key(selected_comp)
204
  found = False
205
  for i, c in enumerate(saved_data):
206
  if get_key(c) == key:
207
  saved_data[i] = selected_comp
208
+ found = True
209
+ break
210
+ if not found:
211
+ saved_data.insert(0, selected_comp)
212
+
213
  save_data(saved_data)
214
  gr.Info(f"已新增標籤: {new_tag}")
215
 
216
  target_list = saved_data if view_mode == "追蹤清單" else search_results
217
+ new_df = format_df(target_list, saved_data)
218
 
219
+ return (
220
+ gr.update(value=""),
221
+ get_tags_text(selected_comp),
222
+ gr.update(choices=selected_comp['tags']),
223
+ saved_data,
224
+ new_df
225
+ )
226
+
227
+ def remove_tag(tag_to_remove, selected_comp, saved_data, view_mode, search_results):
228
+ if not selected_comp or not tag_to_remove:
229
+ return gr.update(), gr.update(), saved_data, gr.update()
230
+
231
+ if 'tags' in selected_comp and tag_to_remove in selected_comp['tags']:
232
+ selected_comp['tags'].remove(tag_to_remove)
233
+
234
  key = get_key(selected_comp)
235
  for i, c in enumerate(saved_data):
236
+ if get_key(c) == key:
237
+ saved_data[i] = selected_comp
238
+ break
239
  save_data(saved_data)
240
+ gr.Info(f"已移除標籤: {tag_to_remove}")
241
+
242
  target_list = saved_data if view_mode == "追蹤清單" else search_results
243
+ new_df = format_df(target_list, saved_data)
244
+
245
+ return (
246
+ get_tags_text(selected_comp),
247
+ gr.update(choices=selected_comp['tags'], value=None),
248
+ saved_data,
249
+ new_df
250
+ )
251
 
252
  def chat_response(history, message, selected_comp):
253
  if not selected_comp: return history, ""
 
268
 
269
  def update_status(status, selected_comp, saved_data, view_mode, search_results):
270
  if not selected_comp: return gr.update(), saved_data
271
+
272
  selected_comp['status'] = status if selected_comp.get('status') != status else None
273
+
274
  key = get_key(selected_comp)
275
  for i, c in enumerate(saved_data):
276
+ if get_key(c) == key:
277
+ saved_data[i] = selected_comp
278
+ break
279
  save_data(saved_data)
280
+
281
  target_list = saved_data if view_mode == "追蹤清單" else search_results
282
  return format_df(target_list, saved_data), saved_data
283
 
284
  def remove_comp(selected_comp, saved_data, view_mode, search_results):
285
  if not selected_comp: return gr.update(), gr.update(value=None), saved_data, gr.update(visible=False)
286
+
287
  key = get_key(selected_comp)
288
  new_saved = [c for c in saved_data if get_key(c) != key]
289
  save_data(new_saved)
290
+
291
  target_list = new_saved if view_mode == "追蹤清單" else search_results
292
+
293
+ return (
294
+ gr.Info("已移除"),
295
+ format_df(target_list, new_saved),
296
+ new_saved,
297
+ gr.update(visible=False)
298
+ )
299
 
300
  def toggle_view(mode, search_res, saved_data):
301
+ if mode == "搜尋結果":
302
+ return format_df(search_res, saved_data), gr.update(visible=True)
303
+ else:
304
+ return format_df(saved_data, saved_data), gr.update(visible=False)
305
 
306
  def init_on_load():
307
  data = load_data()
 
310
  # --- UI Layout ---
311
 
312
  with gr.Blocks(title="Com.404 公司去那兒?", theme=gr.themes.Soft()) as demo:
313
+
314
  saved_state = gr.State([])
315
  search_res_state = gr.State([])
316
  selected_comp_state = gr.State(None)
 
369
  # Chat Section
370
  with gr.Column(elem_classes="chat-section"):
371
  gr.Markdown("### 🤖 商業顧問 (已閱讀下方報告)")
372
+ # 🌟 修正點:移除 type="messages",恢復預設值
373
+ chatbot = gr.Chatbot(height=250)
374
  with gr.Row():
375
  msg = gr.Textbox(label="提問", placeholder="例如:這間公司適合新鮮人嗎?有勞資糾紛嗎?", scale=4)
376
  send_btn = gr.Button("送出", scale=1)
 
395
  btn_remove = gr.Button("🗑️ 移除", variant="stop")
396
 
397
  # --- Wiring ---
398
+
399
  demo.load(init_on_load, inputs=None, outputs=[saved_state, comp_df])
400
 
401
+ search_btn.click(
402
+ search_professors,
403
+ inputs=[search_input, saved_state],
404
+ outputs=[comp_df, search_res_state, load_more_btn]
405
+ ).then(
406
+ lambda: gr.update(value="搜尋結果"), outputs=[view_radio]
407
+ )
408
+
409
+ load_more_btn.click(
410
+ load_more,
411
+ inputs=[search_input, search_res_state, saved_state],
412
+ outputs=[comp_df, search_res_state]
413
+ )
414
+
415
+ view_radio.change(
416
+ toggle_view,
417
+ inputs=[view_radio, search_res_state, saved_state],
418
+ outputs=[comp_df, load_more_btn]
419
+ )
420
 
421
  comp_df.select(
422
+ select_professor_from_df,
423
  inputs=[search_res_state, saved_state, view_radio],
424
+ outputs=[
425
+ details_col, detail_md, chatbot, selected_comp_state, saved_state,
426
+ tags_display, tag_dropdown, tags_row
427
+ ]
428
  )
429
 
430
  send_btn.click(chat_response, inputs=[chatbot, msg, selected_comp_state], outputs=[chatbot, msg])
431
  msg.submit(chat_response, inputs=[chatbot, msg, selected_comp_state], outputs=[chatbot, msg])
432
 
433
+ tag_add_btn.click(
434
+ add_tag,
435
+ inputs=[tag_input, selected_comp_state, saved_state, view_radio, search_res_state],
436
+ outputs=[tag_input, tags_display, tag_dropdown, saved_state, comp_df]
437
+ )
438
+ tag_del_btn.click(
439
+ remove_tag,
440
+ inputs=[tag_dropdown, selected_comp_state, saved_state, view_radio, search_res_state],
441
+ outputs=[tags_display, tag_dropdown, saved_state, comp_df]
442
+ )
443
 
444
  for btn, status in [(btn_good, 'good'), (btn_risk, 'risk'), (btn_pending, 'pending')]:
445
+ btn.click(
446
+ update_status,
447
+ inputs=[gr.State(status), selected_comp_state, saved_state, view_radio, search_res_state],
448
+ outputs=[comp_df, saved_state]
449
+ )
450
 
451
+ btn_remove.click(
452
+ remove_prof,
453
+ inputs=[selected_comp_state, saved_state, view_radio, search_res_state],
454
+ outputs=[gr.State(None), comp_df, saved_state, details_col]
455
+ )
456
 
457
  if __name__ == "__main__":
458
  demo.launch()