adddrett commited on
Commit
fb1b4d2
·
1 Parent(s): 4a22058
Files changed (1) hide show
  1. app.py +50 -33
app.py CHANGED
@@ -8,10 +8,10 @@ class ReviewState:
8
  def __init__(self):
9
  self.all_paths = []
10
  self.current_idx = -1
11
-
12
  def sync_paths(self):
13
  self.all_paths = data_manager.get_all_chart_paths()
14
-
15
  def get_nav_target(self, direction):
16
  new_idx = self.current_idx + direction
17
  if 0 <= new_idx < len(self.all_paths):
@@ -32,7 +32,6 @@ def to_html_frame(html_content):
32
  def handle_source_change(source):
33
  struct = data_manager.get_dataset_structure()
34
  types = list(struct.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
35
- # 默认选中第一个
36
  return gr.update(choices=types, value=types[0] if types else None)
37
 
38
  def handle_type_change(source, c_type):
@@ -46,29 +45,25 @@ def handle_type_change(source, c_type):
46
 
47
  def handle_load(source, c_type, c_id, model):
48
  if not all([source, c_type, c_id, model]):
49
- # 必须返回 8 个 update 对象以匹配 outputs
50
  return [gr.update()] * 8
51
-
52
- # 获取数据
53
  chart_data = data_manager.get_chart_data(source, c_type, c_id)
54
  qa_list = data_manager.get_qa_list(source, c_type, model, c_id)
55
  stats = data_manager.get_review_stats()
56
-
57
- # 同步索引位置
58
  nav_state.sync_paths()
59
  for i, p in enumerate(nav_state.all_paths):
60
  if p['chart_id'] == c_id and p['model'] == model:
61
  nav_state.current_idx = i
62
  break
63
 
64
- # 准备 UI 数据
65
  html_code = to_html_frame(chart_data.get('html_content', ''))
66
  meta_md = "\n".join([f"- **{k}**: {v}" for k, v in chart_data.get('label_info', {}).items()])
67
  qa_json = json.dumps([{"id": q.id, "q": q.question, "a": q.answer} for q in qa_list])
68
  stats_str = f"✅{stats['correct']} | ❌{stats['incorrect']} | 总{stats['total']}"
69
  prog_str = f"{nav_state.current_idx + 1} / {len(nav_state.all_paths)}"
70
  radio_choices = [f"Q{i+1}: {q.question[:20]}..." for i, q in enumerate(qa_list)]
71
-
72
  return [
73
  html_code,
74
  meta_md,
@@ -77,7 +72,7 @@ def handle_load(source, c_type, c_id, model):
77
  prog_str,
78
  f"{source}/{c_type}/{c_id}",
79
  gr.update(choices=radio_choices, value=radio_choices[0] if radio_choices else None),
80
- json.dumps({}) # 占位 review_store
81
  ]
82
 
83
  def handle_qa_select(selection, qa_json):
@@ -94,7 +89,6 @@ def handle_qa_select(selection, qa_json):
94
  # ============== UI 布局 ==============
95
  def create_ui():
96
  with gr.Blocks(title="审核系统 V2", theme=gr.themes.Soft()) as demo:
97
- # 显式初始化 State,避免布尔值陷阱
98
  qa_store = gr.State(value="[]")
99
  review_store = gr.State(value="{}")
100
 
@@ -118,7 +112,7 @@ def create_ui():
118
 
119
  with gr.Accordion("元数据解析", open=False):
120
  meta_md = gr.Markdown()
121
-
122
  gr.Markdown("---")
123
  qa_radio = gr.Radio(label="题目列表", choices=[])
124
 
@@ -126,39 +120,43 @@ def create_ui():
126
  curr_qid = gr.Text(visible=False)
127
  q_disp = gr.Text(label="问题内容", lines=2)
128
  a_disp = gr.Text(label="标准答案")
129
-
130
  status_opt = gr.Radio(
131
  label="审核结论",
132
- choices=[("确", "correct"), ("错误", "incorrect"), ("优化", "modified")],
 
133
  value="correct"
134
  )
135
  err_type = gr.Dropdown(label="错误分类", choices=["无", "事实错误", "逻辑错误", "图表无法读取"])
136
-
137
  comment = gr.Text(label="审核备注")
 
 
 
138
  save_btn = gr.Button("💾 提交单条审核", variant="primary")
139
 
140
  with gr.Row():
141
  prev_btn = gr.Button("⬅️ 上一个图表")
142
  next_btn = gr.Button("➡️ 下一个图表")
143
 
 
 
 
 
 
 
144
  # --- 事件绑定 ---
145
-
146
- # 初始化第一级
147
  demo.load(
148
  fn=lambda: gr.update(choices=list(data_manager.get_dataset_structure().get('sources', {}).keys())),
149
  outputs=[src_dd]
150
  )
151
 
152
- # 联动逻辑
153
  src_dd.change(handle_source_change, inputs=[src_dd], outputs=[typ_dd])
154
  typ_dd.change(handle_type_change, inputs=[src_dd, typ_dd], outputs=[id_dd, mdl_dd])
155
-
156
- # 加载数据 (严格匹配 8 个 output)
157
  load_event_outputs = [chart_view, meta_md, qa_store, stats_txt, prog_txt, path_info, qa_radio, review_store]
158
  id_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_event_outputs)
159
  mdl_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_event_outputs)
160
 
161
- # 题目切换
162
  qa_radio.change(
163
  handle_qa_select,
164
  inputs=[qa_radio, qa_store],
@@ -173,36 +171,55 @@ def create_ui():
173
  gr.update(value=target['source']),
174
  gr.update(value=target['chart_type']),
175
  gr.update(value=target['chart_id']),
176
- gr.update(value=target['model'])
 
177
  ]
178
- return [gr.update()] * 4
 
 
 
179
 
180
- prev_btn.click(lambda: navigate(-1), outputs=[src_dd, typ_dd, id_dd, mdl_dd])
181
- next_btn.click(lambda: navigate(1), outputs=[src_dd, typ_dd, id_dd, mdl_dd])
182
-
183
- # 保存逻辑
184
  def quick_save(qid, cid, src, status, cmt):
185
- if not qid: return "无效操作"
 
 
186
  data_manager.save_review({
187
  "qa_id": qid, "chart_id": cid, "source": src,
188
  "status": status, "comment": cmt
189
  })
190
- return "已保存"
 
 
 
 
 
191
 
192
  save_btn.click(
193
  quick_save,
194
  inputs=[curr_qid, id_dd, src_dd, status_opt, comment],
195
- outputs=[gr.Text(visible=False)]
 
 
 
 
 
 
 
 
 
 
 
 
196
  )
197
 
198
  return demo
199
 
200
  if __name__ == "__main__":
201
  app = create_ui()
202
- # 强制禁用所有 API 预览功能
203
  app.launch(
204
  server_name="0.0.0.0",
205
  server_port=7860,
206
  show_api=False,
207
  max_threads=10
208
- )
 
8
  def __init__(self):
9
  self.all_paths = []
10
  self.current_idx = -1
11
+
12
  def sync_paths(self):
13
  self.all_paths = data_manager.get_all_chart_paths()
14
+
15
  def get_nav_target(self, direction):
16
  new_idx = self.current_idx + direction
17
  if 0 <= new_idx < len(self.all_paths):
 
32
  def handle_source_change(source):
33
  struct = data_manager.get_dataset_structure()
34
  types = list(struct.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
 
35
  return gr.update(choices=types, value=types[0] if types else None)
36
 
37
  def handle_type_change(source, c_type):
 
45
 
46
  def handle_load(source, c_type, c_id, model):
47
  if not all([source, c_type, c_id, model]):
 
48
  return [gr.update()] * 8
49
+
 
50
  chart_data = data_manager.get_chart_data(source, c_type, c_id)
51
  qa_list = data_manager.get_qa_list(source, c_type, model, c_id)
52
  stats = data_manager.get_review_stats()
53
+
 
54
  nav_state.sync_paths()
55
  for i, p in enumerate(nav_state.all_paths):
56
  if p['chart_id'] == c_id and p['model'] == model:
57
  nav_state.current_idx = i
58
  break
59
 
 
60
  html_code = to_html_frame(chart_data.get('html_content', ''))
61
  meta_md = "\n".join([f"- **{k}**: {v}" for k, v in chart_data.get('label_info', {}).items()])
62
  qa_json = json.dumps([{"id": q.id, "q": q.question, "a": q.answer} for q in qa_list])
63
  stats_str = f"✅{stats['correct']} | ❌{stats['incorrect']} | 总{stats['total']}"
64
  prog_str = f"{nav_state.current_idx + 1} / {len(nav_state.all_paths)}"
65
  radio_choices = [f"Q{i+1}: {q.question[:20]}..." for i, q in enumerate(qa_list)]
66
+
67
  return [
68
  html_code,
69
  meta_md,
 
72
  prog_str,
73
  f"{source}/{c_type}/{c_id}",
74
  gr.update(choices=radio_choices, value=radio_choices[0] if radio_choices else None),
75
+ json.dumps({})
76
  ]
77
 
78
  def handle_qa_select(selection, qa_json):
 
89
  # ============== UI 布局 ==============
90
  def create_ui():
91
  with gr.Blocks(title="审核系统 V2", theme=gr.themes.Soft()) as demo:
 
92
  qa_store = gr.State(value="[]")
93
  review_store = gr.State(value="{}")
94
 
 
112
 
113
  with gr.Accordion("元数据解析", open=False):
114
  meta_md = gr.Markdown()
115
+
116
  gr.Markdown("---")
117
  qa_radio = gr.Radio(label="题目列表", choices=[])
118
 
 
120
  curr_qid = gr.Text(visible=False)
121
  q_disp = gr.Text(label="问题内容", lines=2)
122
  a_disp = gr.Text(label="标准答案")
 
123
  status_opt = gr.Radio(
124
  label="审核结论",
125
+ # 【修】将 "modified" 改为 "needs_modification" 匹配 DataManager 内部逻辑
126
+ choices=[("正确", "correct"), ("错误", "incorrect"), ("优化", "needs_modification")],
127
  value="correct"
128
  )
129
  err_type = gr.Dropdown(label="错误分类", choices=["无", "事实错误", "逻辑错误", "图表无法读取"])
130
+
131
  comment = gr.Text(label="审核备注")
132
+
133
+ # 【新增】状态提示框,用于反馈保存成功与否
134
+ save_status = gr.Text(label="操作反馈", interactive=False)
135
  save_btn = gr.Button("💾 提交单条审核", variant="primary")
136
 
137
  with gr.Row():
138
  prev_btn = gr.Button("⬅️ 上一个图表")
139
  next_btn = gr.Button("➡️ 下一个图表")
140
 
141
+ gr.Markdown("---")
142
+ # 【新增】下载区
143
+ with gr.Row():
144
+ export_btn = gr.Button("📥 导出并下载审核记录", variant="secondary")
145
+ download_file = gr.File(label="下载文件", visible=False)
146
+
147
  # --- 事件绑定 ---
 
 
148
  demo.load(
149
  fn=lambda: gr.update(choices=list(data_manager.get_dataset_structure().get('sources', {}).keys())),
150
  outputs=[src_dd]
151
  )
152
 
 
153
  src_dd.change(handle_source_change, inputs=[src_dd], outputs=[typ_dd])
154
  typ_dd.change(handle_type_change, inputs=[src_dd, typ_dd], outputs=[id_dd, mdl_dd])
155
+
 
156
  load_event_outputs = [chart_view, meta_md, qa_store, stats_txt, prog_txt, path_info, qa_radio, review_store]
157
  id_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_event_outputs)
158
  mdl_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_event_outputs)
159
 
 
160
  qa_radio.change(
161
  handle_qa_select,
162
  inputs=[qa_radio, qa_store],
 
171
  gr.update(value=target['source']),
172
  gr.update(value=target['chart_type']),
173
  gr.update(value=target['chart_id']),
174
+ gr.update(value=target['model']),
175
+ gr.update(value="") # 切换时清空状态
176
  ]
177
+ return [gr.update()] * 4 + [gr.update()]
178
+
179
+ prev_btn.click(lambda: navigate(-1), outputs=[src_dd, typ_dd, id_dd, mdl_dd, save_status])
180
+ next_btn.click(lambda: navigate(1), outputs=[src_dd, typ_dd, id_dd, mdl_dd, save_status])
181
 
182
+ # 【修复与增强】保存逻辑
 
 
 
183
  def quick_save(qid, cid, src, status, cmt):
184
+ if not qid:
185
+ return "⚠️ 无效操作:未选择题目", gr.update()
186
+
187
  data_manager.save_review({
188
  "qa_id": qid, "chart_id": cid, "source": src,
189
  "status": status, "comment": cmt
190
  })
191
+
192
+ # 【增强】保存后立刻刷新全局统计信息
193
+ stats = data_manager.get_review_stats()
194
+ stats_str = f"✅{stats['correct']} | ❌{stats['incorrect']} | 总{stats['total']}"
195
+
196
+ return f"✅ 保存成功 (ID: {qid})", stats_str
197
 
198
  save_btn.click(
199
  quick_save,
200
  inputs=[curr_qid, id_dd, src_dd, status_opt, comment],
201
+ # 【修复】将结果输出到已经实例化的组件上
202
+ outputs=[save_status, stats_txt]
203
+ )
204
+
205
+ # 【新增】导出下载逻辑
206
+ def handle_export():
207
+ file_path = data_manager.export_reviews()
208
+ return gr.update(value=file_path, visible=True)
209
+
210
+ export_btn.click(
211
+ handle_export,
212
+ inputs=[],
213
+ outputs=[download_file]
214
  )
215
 
216
  return demo
217
 
218
  if __name__ == "__main__":
219
  app = create_ui()
 
220
  app.launch(
221
  server_name="0.0.0.0",
222
  server_port=7860,
223
  show_api=False,
224
  max_threads=10
225
+ )