adddrett commited on
Commit
1c91312
·
1 Parent(s): fe162f8
Files changed (2) hide show
  1. app.py +112 -372
  2. test.py +683 -0
app.py CHANGED
@@ -51,15 +51,7 @@ class AppState:
51
  break
52
 
53
  def navigate(self, direction: int) -> bool:
54
- """
55
- 导航到上一个或下一个图表
56
-
57
- Args:
58
- direction: 1 表示下一个,-1 表示上一个
59
-
60
- Returns:
61
- 是否成功导航
62
- """
63
  new_index = self.current_index + direction
64
  if 0 <= new_index < len(self.all_paths):
65
  self.current_index = new_index
@@ -79,9 +71,7 @@ state = AppState()
79
  def get_dataset_choices() -> Tuple[List[str], List[str], List[str], List[str]]:
80
  """获取数据集的选择项"""
81
  structure = data_manager.get_dataset_structure()
82
-
83
  sources = list(structure.get('sources', {}).keys())
84
-
85
  chart_types = []
86
  charts = []
87
  models = []
@@ -94,25 +84,20 @@ def get_dataset_choices() -> Tuple[List[str], List[str], List[str], List[str]]:
94
  charts = data_manager.get_chart_list(state.current_source, state.current_chart_type)
95
  ct_data = source_data.get('chart_types', {}).get(state.current_chart_type, {})
96
  models = ct_data.get('models', [])
97
-
98
  return sources, chart_types, charts, models
99
 
100
-
101
  def update_chart_type_dropdown(source: str):
102
  """更新图表类型下拉框"""
103
  state.current_source = source
104
  structure = data_manager.get_dataset_structure()
105
-
106
  chart_types = list(structure.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
107
-
108
  return gr.Dropdown(choices=chart_types, value=chart_types[0] if chart_types else None)
109
 
110
-
111
  def update_chart_dropdown(source: str, chart_type: str):
112
  """更新图表和模型下拉框"""
113
  state.current_source = source
114
  state.current_chart_type = chart_type
115
-
116
  charts = data_manager.get_chart_list(source, chart_type)
117
  structure = data_manager.get_dataset_structure()
118
  ct_data = structure.get('sources', {}).get(source, {}).get('chart_types', {}).get(chart_type, {})
@@ -123,17 +108,12 @@ def update_chart_dropdown(source: str, chart_type: str):
123
  gr.Dropdown(choices=models, value=models[0] if models else None)
124
  )
125
 
126
-
127
  def create_embedded_html(html_content: str, chart_id: str = "") -> str:
128
- """
129
- 创建嵌入式的 HTML 显示
130
-
131
- 使用 data URI 方式嵌入 HTML 内容到 iframe 中
132
- """
133
  if not html_content:
134
  return f"""
135
  <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;
136
- min-height:400px;color:#999;border:2px dashed #ddd;border-radius:12px;background:#fafafa;">
137
  <div style="font-size:48px;margin-bottom:16px;">📭</div>
138
  <div style="font-size:18px;font-weight:500;">暂无图表内容</div>
139
  <div style="font-size:14px;margin-top:8px;">图表 ID: {chart_id or '未知'}</div>
@@ -145,57 +125,41 @@ def create_embedded_html(html_content: str, chart_id: str = "") -> str:
145
  html_bytes = html_content.encode('utf-8')
146
  html_base64 = base64.b64encode(html_bytes).decode('utf-8')
147
 
148
- # 使用 data URI
149
  iframe_html = f"""
150
  <iframe
151
  src="data:text/html;base64,{html_base64}"
152
- style="width:100%;height:500px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;"
153
  sandbox="allow-scripts allow-same-origin"
154
  loading="lazy"
155
  ></iframe>
156
  """
157
-
158
  return iframe_html
159
 
160
-
161
  def load_chart_data(source: str, chart_type: str, chart_id: str, model: str):
162
- """
163
- 加载图表数据并返回所有 UI 更新
164
-
165
- Returns:
166
- 包含所有 UI 组件更新值的元组
167
- """
168
  if not all([source, chart_type, chart_id, model]):
169
  return [
170
  create_embedded_html(""), # html_display
171
- "### 请在左侧选择图表", # label_info
172
- "[]", # qa_data (JSON string)
173
  "等待加载数据...", # status_text
174
- "请在左侧选择图表", # progress_text
175
- "{}", # current_qa_reviews (JSON string)
176
  gr.Radio(choices=[], value=None), # qa_selector
177
  "" # debug_info
178
  ]
179
 
180
- # 更新状态
181
  state.set_position(source, chart_type, chart_id, model)
182
-
183
- # 获取图表数据
184
  chart_data = data_manager.get_chart_data(source, chart_type, chart_id)
185
  html_content = chart_data.get('html_content', '')
186
  label_info = chart_data.get('label_info', {})
187
 
188
- # 创建嵌入式 HTML
189
  embedded_html = create_embedded_html(html_content, chart_id)
190
-
191
- # 调试信息
192
  debug_info = f"📁 {source}/{chart_type}/{chart_id} | HTML: {len(html_content)} 字符"
193
 
194
- # 格式化标签信息
195
  if label_info:
196
  label_text = f"""
197
- ### 图表信息
198
-
199
  | 属性 | 值 |
200
  |------|-----|
201
  | **编号** | {label_info.get('Number', '-')} |
@@ -206,38 +170,32 @@ def load_chart_data(source: str, chart_type: str, chart_id: str, model: str):
206
  | **链接** | [查看原图]({label_info.get('Weblink', '#')}) |
207
  """
208
  else:
209
- label_text = "### ⚠️ 暂无标签信息"
210
 
211
- # 获取 QA 列表
212
  qa_list = data_manager.get_qa_list(source, chart_type, model, chart_id)
213
 
214
- # 获取已有的审核记录
215
  existing_reviews = {}
216
  for review in data_manager.get_reviews_by_chart(chart_id, model):
217
  existing_reviews[review['qa_id']] = review
 
 
218
 
219
- # 更新进度文本
220
- progress_text = f"当前: {state.current_index + 1} / {len(state.all_paths)} 个图表"
221
-
222
- # 状态文本
223
  stats = data_manager.get_review_stats()
224
- status_text = f"已审核: {stats['total']} | ✅正确: {stats['correct']} | ❌错误: {stats['incorrect']} | ✏️需修改: {stats['needs_modification']}"
225
 
226
- # QA 选择器选项
227
  qa_choices = [f"Q{i+1}: {qa.question[:50]}..." for i, qa in enumerate(qa_list)] if qa_list else []
228
 
229
  return [
230
- embedded_html, # html_display
231
- label_text, # label_info
232
- json.dumps([{"id": qa.id, "question": qa.question, "answer": qa.answer} for qa in qa_list]), # qa_data (JSON string)
233
- status_text, # status_text
234
- progress_text, # progress_text
235
- json.dumps(existing_reviews), # current_qa_reviews (JSON string)
236
- gr.Radio(choices=qa_choices, value=qa_choices[0] if qa_choices else None), # qa_selector
237
- debug_info # debug_info
238
  ]
239
 
240
-
241
  def navigate_prev():
242
  """导航到上一个图表"""
243
  if state.navigate(-1):
@@ -251,7 +209,6 @@ def navigate_prev():
251
  )
252
  return [gr.Dropdown(), gr.Dropdown(), gr.Dropdown(), gr.Dropdown()]
253
 
254
-
255
  def navigate_next():
256
  """导航到下一个图表"""
257
  if state.navigate(1):
@@ -265,73 +222,51 @@ def navigate_next():
265
  )
266
  return [gr.Dropdown(), gr.Dropdown(), gr.Dropdown(), gr.Dropdown()]
267
 
268
-
269
  def save_review_handler(
270
- qa_id: str,
271
- chart_id: str,
272
- source: str,
273
- chart_type: str,
274
- model: str,
275
- original_question: str,
276
- original_answer: str,
277
- status: str,
278
- modified_question: str,
279
- modified_answer: str,
280
- issue_type: str,
281
- comment: str,
282
- reviewer: str
283
  ) -> str:
284
  """保存审核记录"""
285
  if not qa_id:
286
  return "❌ 请先选择一个问答对"
287
-
288
  review_data = {
289
- "qa_id": qa_id,
290
- "chart_id": chart_id,
291
- "source": source,
292
- "chart_type": chart_type,
293
- "model": model,
294
- "original_question": original_question,
295
- "original_answer": original_answer,
296
- "status": status,
297
- "modified_question": modified_question,
298
- "modified_answer": modified_answer,
299
- "issue_type": issue_type,
300
- "comment": comment,
301
- "reviewer": reviewer
302
  }
303
 
304
- result = data_manager.save_review(review_data)
305
-
306
- # 返回更新后的统计
307
  stats = data_manager.get_review_stats()
308
- return f"✅ 已保存! 总计: {stats['total']} | ✅正确: {stats['correct']} | ❌错误: {stats['incorrect']} | ✏️需修改: {stats['needs_modification']}"
309
-
310
 
311
  def export_reviews_handler():
312
- """导出审核记录"""
 
313
  output_path = data_manager.export_reviews("./reviews_export.json")
314
- return f"✅ 审核记录已导出至: {output_path}"
 
315
 
316
 
317
  # ============== 创建 Gradio 界面 ==============
318
 
319
  def create_ui():
320
- """创建 Gradio 界面"""
321
-
322
- # 自定义 CSS
323
  custom_css = """
324
  .chart-container {
325
- min-height: 520px;
326
  }
327
-
328
  .control-panel {
329
  background: #f8f9fa;
330
  padding: 15px;
331
  border-radius: 8px;
332
  margin-bottom: 10px;
333
  }
334
-
335
  .debug-panel {
336
  font-size: 12px;
337
  color: #666;
@@ -342,338 +277,143 @@ def create_ui():
342
  }
343
  """
344
 
345
- with gr.Blocks(
346
- title="图表问答数据集审核系统",
347
- theme=gr.themes.Soft(),
348
- css=custom_css
349
- ) as app:
350
 
351
- # 隐藏的状态存储(使用 JSON 字符串)
352
  qa_data_json = gr.State(value="[]")
353
  current_reviews_json = gr.State(value="{}")
354
 
355
- # ==================== 标题 ====================
356
- gr.Markdown("""
357
- # 📊 图表问答数据集审核系统
358
 
359
- 审核每个图表对应的问题和答案是否合理正确。使用 ← → 按钮切换图表。
360
- """)
361
-
362
- # ==================== 顶部状态栏 ====================
363
  with gr.Row():
364
- status_text = gr.Textbox(
365
- label="审核统计",
366
- value="等待加载数据...",
367
- interactive=False,
368
- show_label=False,
369
- scale=2
370
- )
371
- progress_text = gr.Textbox(
372
- label="进度",
373
- value="请在左侧选择图表",
374
- interactive=False,
375
- show_label=False,
376
- scale=1
377
- )
378
-
379
- # ==================== 主内容区 ====================
380
  with gr.Row():
381
- # ===== 左侧:导航控制 =====
382
- with gr.Column(scale=1, min_width=250):
383
- gr.Markdown("### 📁 数据选择")
384
-
385
- source_dropdown = gr.Dropdown(
386
- label="数据来源 (Source)",
387
- choices=[],
388
- interactive=True
389
- )
390
-
391
- chart_type_dropdown = gr.Dropdown(
392
- label="图表类型 (Chart Type)",
393
- choices=[],
394
- interactive=True
395
- )
396
-
397
- chart_dropdown = gr.Dropdown(
398
- label="图表 ID",
399
- choices=[],
400
- interactive=True
401
- )
402
-
403
- model_dropdown = gr.Dropdown(
404
- label="模型 (Model)",
405
- choices=[],
406
- interactive=True
407
- )
408
-
409
- gr.Markdown("---")
410
-
411
- # 导航按钮
412
- with gr.Row():
413
- prev_btn = gr.Button("⬅️ 上一个")
414
- next_btn = gr.Button("➡️ 下一个")
415
-
416
- # 导出按钮
417
- export_btn = gr.Button("📥 导出审核记录", variant="secondary")
418
- export_result = gr.Textbox(label="", visible=False)
419
-
420
- # 审核人设置
421
- reviewer_input = gr.Textbox(
422
- label="审核人",
423
- value="default",
424
- interactive=True
425
- )
426
-
427
- # 调试信息
428
- debug_info = gr.Textbox(
429
- label="调试信息",
430
- value="",
431
- interactive=False,
432
- show_label=False,
433
- elem_classes=["debug-panel"]
434
- )
435
 
436
- # ===== 中间:图表展 =====
437
- with gr.Column(scale=2, min_width=400):
 
 
 
 
 
438
  gr.Markdown("### 📈 图表展示")
439
-
440
- # HTML 图表展示(使用 iframe)
441
  html_display = gr.HTML(
442
- value="<div style='text-align:center;padding:50px;color:#999;'>请选择图表</div>",
443
  elem_classes=["chart-container"]
444
  )
445
-
446
- # ===== 右侧:标签信息和 QA 审核 =====
447
- with gr.Column(scale=2, min_width=400):
448
- # 标签信息
449
- gr.Markdown("### 📝 图表标签")
450
- label_display = gr.Markdown(
451
- value="暂无信息",
452
- elem_classes=["control-panel"]
453
- )
454
 
455
- # QA 审核区
456
  gr.Markdown("### ❓ 问答审核")
457
 
458
- # 当前选中的 QA 信息(隐藏)
459
  current_qa_id = gr.Textbox(visible=False, value="")
460
 
461
- # QA 显示
462
- qa_question_display = gr.Textbox(
463
- label="问题",
464
- interactive=False,
465
- lines=2,
466
- value=""
467
- )
468
- qa_answer_display = gr.Textbox(
469
- label="答案",
470
- interactive=False,
471
- lines=1,
472
- value=""
473
- )
474
 
475
- # QA 选择器
476
- qa_selector = gr.Radio(
477
- label="选择要审核的问答对",
478
- choices=[],
479
- interactive=True
480
- )
481
 
482
  gr.Markdown("---")
483
- gr.Markdown("#### 审核操作")
484
 
485
- # 审核状态选择
486
  status_radio = gr.Radio(
487
- label="审核状态",
488
- choices=[
489
- ("✅ 正确", "correct"),
490
- ("❌ 错误", "incorrect"),
491
- ("✏️ 需修改", "needs_modification"),
492
- ("⏳ 待定", "pending")
493
- ],
494
- value="pending",
495
- interactive=True
496
  )
497
 
498
- # 问题类型
499
  issue_type_dropdown = gr.Dropdown(
500
- label="问题类型",
501
- choices=[
502
- "问题歧义",
503
- "答案错误",
504
- "图表不清晰",
505
- "问题不合理",
506
- "答案格式错误",
507
- "其他"
508
- ],
509
- interactive=True,
510
- value=""
511
  )
512
 
513
- # 修改后的问题和答案
514
- modified_question = gr.Textbox(
515
- label="修改后的问题",
516
- placeholder="如需修改问题,请在此输入...",
517
- lines=2,
518
- interactive=True,
519
- value=""
520
- )
521
-
522
- modified_answer = gr.Textbox(
523
- label="修改后的答案",
524
- placeholder="如需修改答案,请在此输入...",
525
- lines=1,
526
- interactive=True,
527
- value=""
528
- )
529
 
530
- # 评论
531
- comment_textbox = gr.Textbox(
532
- label="评论/备注",
533
- placeholder="请输入审核意见或备注...",
534
- lines=2,
535
- interactive=True,
536
- value=""
537
- )
538
-
539
- # 保存按钮
540
- save_btn = gr.Button("💾 保存审核结果", variant="primary")
541
  save_result = gr.Textbox(label="", visible=False)
542
-
543
  # ==================== 事件绑定 ====================
544
 
545
- # 初始化数据集选择
546
  def init_dataset():
547
  structure = data_manager.get_dataset_structure()
548
  sources = list(structure.get('sources', {}).keys())
549
  return gr.Dropdown(choices=sources, value=sources[0] if sources else None)
 
 
550
 
551
- app.load(
552
- fn=init_dataset,
553
- outputs=[source_dropdown]
554
- )
555
-
556
- # Source 变化 -> 更新 Chart Type
557
- source_dropdown.change(
558
- fn=update_chart_type_dropdown,
559
- inputs=[source_dropdown],
560
- outputs=[chart_type_dropdown]
561
- )
562
-
563
- # Chart Type 变化 -> 更新 Chart 和 Model
564
- chart_type_dropdown.change(
565
- fn=update_chart_dropdown,
566
- inputs=[source_dropdown, chart_type_dropdown],
567
- outputs=[chart_dropdown, model_dropdown]
568
- )
569
-
570
- # 选择图表 -> 加载数据
571
- model_dropdown.change(
572
- fn=load_chart_data,
573
- inputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown],
574
- outputs=[
575
- html_display, label_display, qa_data_json, status_text, progress_text,
576
- current_reviews_json, qa_selector, debug_info
577
- ]
578
- )
579
 
580
- chart_dropdown.change(
581
- fn=load_chart_data,
582
- inputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown],
583
- outputs=[
584
- html_display, label_display, qa_data_json, status_text, progress_text,
585
- current_reviews_json, qa_selector, debug_info
586
- ]
587
- )
588
 
589
- # QA 选择器变化 -> 更新审核面板
590
  def on_qa_selected(qa_index_str, qa_json, reviews_json):
591
  if not qa_index_str or not qa_json:
592
  return ["", "", "", gr.Radio(value="pending"), "", "", "", ""]
593
-
594
  try:
595
  qa_list = json.loads(qa_json)
596
  reviews = json.loads(reviews_json)
597
-
598
- # 解析索引
599
  index = int(qa_index_str.split(":")[0].replace("Q", "")) - 1
600
  qa = qa_list[index]
601
-
602
- # 检查是否有现有审核
603
  review = reviews.get(qa['id'], {})
604
-
605
  return [
606
- qa['id'], # current_qa_id
607
- qa['question'], # qa_question_display
608
- qa['answer'], # qa_answer_display
609
- gr.Radio(value=review.get('status', 'pending')), # status_radio
610
- review.get('issue_type', ''), # issue_type_dropdown
611
- review.get('modified_question', ''), # modified_question
612
- review.get('modified_answer', ''), # modified_answer
613
- review.get('comment', '') # comment_textbox
614
  ]
615
  except Exception as e:
616
  print(f"Error in on_qa_selected: {e}")
617
  return ["", "", "", gr.Radio(value="pending"), "", "", "", ""]
618
-
619
  qa_selector.change(
620
  fn=on_qa_selected,
621
  inputs=[qa_selector, qa_data_json, current_reviews_json],
622
- outputs=[
623
- current_qa_id, qa_question_display, qa_answer_display,
624
- status_radio, issue_type_dropdown, modified_question, modified_answer, comment_textbox
625
- ]
626
- )
627
-
628
- # 导航按钮
629
- prev_btn.click(
630
- fn=navigate_prev,
631
- outputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown]
632
  )
633
 
634
- next_btn.click(
635
- fn=navigate_next,
636
- outputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown]
637
- )
638
 
639
- # 保存审核
640
  save_btn.click(
641
  fn=save_review_handler,
642
- inputs=[
643
- current_qa_id,
644
- chart_dropdown,
645
- source_dropdown,
646
- chart_type_dropdown,
647
- model_dropdown,
648
- qa_question_display,
649
- qa_answer_display,
650
- status_radio,
651
- modified_question,
652
- modified_answer,
653
- issue_type_dropdown,
654
- comment_textbox,
655
- reviewer_input
656
- ],
657
- outputs=[save_result]
658
- ).then(
659
- fn=lambda: gr.Textbox(visible=True),
660
  outputs=[save_result]
661
- )
662
 
663
- # 导出
664
  export_btn.click(
665
  fn=export_reviews_handler,
666
- outputs=[export_result]
667
- ).then(
668
- fn=lambda: gr.Textbox(visible=True),
669
- outputs=[export_result]
670
  )
671
-
672
- return app
673
-
674
 
675
  # ============== 主入口 ==============
676
-
677
  if __name__ == "__main__":
678
  app = create_ui()
679
  app.launch(
 
51
  break
52
 
53
  def navigate(self, direction: int) -> bool:
54
+ """导航到上一个或下一个图表"""
 
 
 
 
 
 
 
 
55
  new_index = self.current_index + direction
56
  if 0 <= new_index < len(self.all_paths):
57
  self.current_index = new_index
 
71
  def get_dataset_choices() -> Tuple[List[str], List[str], List[str], List[str]]:
72
  """获取数据集的选择项"""
73
  structure = data_manager.get_dataset_structure()
 
74
  sources = list(structure.get('sources', {}).keys())
 
75
  chart_types = []
76
  charts = []
77
  models = []
 
84
  charts = data_manager.get_chart_list(state.current_source, state.current_chart_type)
85
  ct_data = source_data.get('chart_types', {}).get(state.current_chart_type, {})
86
  models = ct_data.get('models', [])
87
+
88
  return sources, chart_types, charts, models
89
 
 
90
  def update_chart_type_dropdown(source: str):
91
  """更新图表类型下拉框"""
92
  state.current_source = source
93
  structure = data_manager.get_dataset_structure()
 
94
  chart_types = list(structure.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
 
95
  return gr.Dropdown(choices=chart_types, value=chart_types[0] if chart_types else None)
96
 
 
97
  def update_chart_dropdown(source: str, chart_type: str):
98
  """更新图表和模型下拉框"""
99
  state.current_source = source
100
  state.current_chart_type = chart_type
 
101
  charts = data_manager.get_chart_list(source, chart_type)
102
  structure = data_manager.get_dataset_structure()
103
  ct_data = structure.get('sources', {}).get(source, {}).get('chart_types', {}).get(chart_type, {})
 
108
  gr.Dropdown(choices=models, value=models[0] if models else None)
109
  )
110
 
 
111
  def create_embedded_html(html_content: str, chart_id: str = "") -> str:
112
+ """创建嵌入式的 HTML 显示"""
 
 
 
 
113
  if not html_content:
114
  return f"""
115
  <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;
116
+ min-height:750px;color:#999;border:2px dashed #ddd;border-radius:12px;background:#fafafa;">
117
  <div style="font-size:48px;margin-bottom:16px;">📭</div>
118
  <div style="font-size:18px;font-weight:500;">暂无图表内容</div>
119
  <div style="font-size:14px;margin-top:8px;">图表 ID: {chart_id or '未知'}</div>
 
125
  html_bytes = html_content.encode('utf-8')
126
  html_base64 = base64.b64encode(html_bytes).decode('utf-8')
127
 
128
+ # 增加高度为 750px
129
  iframe_html = f"""
130
  <iframe
131
  src="data:text/html;base64,{html_base64}"
132
+ style="width:100%;height:750px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;"
133
  sandbox="allow-scripts allow-same-origin"
134
  loading="lazy"
135
  ></iframe>
136
  """
 
137
  return iframe_html
138
 
 
139
  def load_chart_data(source: str, chart_type: str, chart_id: str, model: str):
140
+ """加载图表数据并返回所有 UI 更新"""
 
 
 
 
 
141
  if not all([source, chart_type, chart_id, model]):
142
  return [
143
  create_embedded_html(""), # html_display
144
+ "### ⚠️ 请在上方选择完整数据路径", # label_info
145
+ "[]", # qa_data
146
  "等待加载数据...", # status_text
147
+ "请在上方选择图表", # progress_text
148
+ "{}", # current_qa_reviews
149
  gr.Radio(choices=[], value=None), # qa_selector
150
  "" # debug_info
151
  ]
152
 
 
153
  state.set_position(source, chart_type, chart_id, model)
 
 
154
  chart_data = data_manager.get_chart_data(source, chart_type, chart_id)
155
  html_content = chart_data.get('html_content', '')
156
  label_info = chart_data.get('label_info', {})
157
 
 
158
  embedded_html = create_embedded_html(html_content, chart_id)
 
 
159
  debug_info = f"📁 {source}/{chart_type}/{chart_id} | HTML: {len(html_content)} 字符"
160
 
 
161
  if label_info:
162
  label_text = f"""
 
 
163
  | 属性 | 值 |
164
  |------|-----|
165
  | **编号** | {label_info.get('Number', '-')} |
 
170
  | **链接** | [查看原图]({label_info.get('Weblink', '#')}) |
171
  """
172
  else:
173
+ label_text = "⚠️ 暂无标签信息"
174
 
 
175
  qa_list = data_manager.get_qa_list(source, chart_type, model, chart_id)
176
 
 
177
  existing_reviews = {}
178
  for review in data_manager.get_reviews_by_chart(chart_id, model):
179
  existing_reviews[review['qa_id']] = review
180
+
181
+ progress_text = f"进度: 当前第 {state.current_index + 1} 个 / 共 {len(state.all_paths)} 个"
182
 
 
 
 
 
183
  stats = data_manager.get_review_stats()
184
+ status_text = f"总览: 已审核 {stats['total']} | ✅正确: {stats['correct']} | ❌错误: {stats['incorrect']} | ✏️需修改: {stats['needs_modification']}"
185
 
 
186
  qa_choices = [f"Q{i+1}: {qa.question[:50]}..." for i, qa in enumerate(qa_list)] if qa_list else []
187
 
188
  return [
189
+ embedded_html,
190
+ label_text,
191
+ json.dumps([{"id": qa.id, "question": qa.question, "answer": qa.answer} for qa in qa_list]),
192
+ status_text,
193
+ progress_text,
194
+ json.dumps(existing_reviews),
195
+ gr.Radio(choices=qa_choices, value=qa_choices[0] if qa_choices else None),
196
+ debug_info
197
  ]
198
 
 
199
  def navigate_prev():
200
  """导航到上一个图表"""
201
  if state.navigate(-1):
 
209
  )
210
  return [gr.Dropdown(), gr.Dropdown(), gr.Dropdown(), gr.Dropdown()]
211
 
 
212
  def navigate_next():
213
  """导航到下一个图表"""
214
  if state.navigate(1):
 
222
  )
223
  return [gr.Dropdown(), gr.Dropdown(), gr.Dropdown(), gr.Dropdown()]
224
 
 
225
  def save_review_handler(
226
+ qa_id: str, chart_id: str, source: str, chart_type: str, model: str,
227
+ original_question: str, original_answer: str, status: str,
228
+ modified_question: str, modified_answer: str, issue_type: str,
229
+ comment: str, reviewer: str
 
 
 
 
 
 
 
 
 
230
  ) -> str:
231
  """保存审核记录"""
232
  if not qa_id:
233
  return "❌ 请先选择一个问答对"
234
+
235
  review_data = {
236
+ "qa_id": qa_id, "chart_id": chart_id, "source": source,
237
+ "chart_type": chart_type, "model": model,
238
+ "original_question": original_question, "original_answer": original_answer,
239
+ "status": status, "modified_question": modified_question,
240
+ "modified_answer": modified_answer, "issue_type": issue_type,
241
+ "comment": comment, "reviewer": reviewer
 
 
 
 
 
 
 
242
  }
243
 
244
+ data_manager.save_review(review_data)
 
 
245
  stats = data_manager.get_review_stats()
246
+ return f"✅ 已保存! 已审核: {stats['total']} | ✅正确: {stats['correct']} | ❌错误: {stats['incorrect']} | ✏️需修改: {stats['needs_modification']}"
 
247
 
248
  def export_reviews_handler():
249
+ """导出审核记录并返回文件供下载"""
250
+ # 将文件保存到当前目录
251
  output_path = data_manager.export_reviews("./reviews_export.json")
252
+ # 更新 gr.File 组件使其显示并提供下载
253
+ return gr.update(value=output_path, visible=True)
254
 
255
 
256
  # ============== 创建 Gradio 界面 ==============
257
 
258
  def create_ui():
259
+ # 调整了最小高度为 750px 适配放大后的 iframe
 
 
260
  custom_css = """
261
  .chart-container {
262
+ min-height: 750px;
263
  }
 
264
  .control-panel {
265
  background: #f8f9fa;
266
  padding: 15px;
267
  border-radius: 8px;
268
  margin-bottom: 10px;
269
  }
 
270
  .debug-panel {
271
  font-size: 12px;
272
  color: #666;
 
277
  }
278
  """
279
 
280
+ with gr.Blocks(title="图表问答数据集审核系统", theme=gr.themes.Soft(), css=custom_css) as app:
 
 
 
 
281
 
 
282
  qa_data_json = gr.State(value="[]")
283
  current_reviews_json = gr.State(value="{}")
284
 
285
+ # ==================== 顶部标题与数据导航区 ====================
286
+ gr.Markdown("# 📊 图表问答数据集审核系统")
 
287
 
 
 
 
 
288
  with gr.Row():
289
+ status_text = gr.Textbox(value="等待加载数据...", interactive=False, show_label=False, scale=2)
290
+ progress_text = gr.Textbox(value="请在下方选择图表", interactive=False, show_label=False, scale=1)
291
+
292
+ with gr.Row(elem_classes=["control-panel"]):
293
+ source_dropdown = gr.Dropdown(label="数据来源 (Source)", choices=[], interactive=True, scale=1)
294
+ chart_type_dropdown = gr.Dropdown(label="图表类型 (Chart Type)", choices=[], interactive=True, scale=1)
295
+ chart_dropdown = gr.Dropdown(label="图表 ID", choices=[], interactive=True, scale=1)
296
+ model_dropdown = gr.Dropdown(label="模型 (Model)", choices=[], interactive=True, scale=1)
297
+ reviewer_input = gr.Textbox(label="审核人", value="default", interactive=True, scale=1)
298
+
 
 
 
 
 
 
299
  with gr.Row():
300
+ prev_btn = gr.Button("⬅️ 上一个图表", variant="secondary")
301
+ next_btn = gr.Button("➡️ 下一个图表", variant="primary")
302
+ export_btn = gr.Button("📦 生成并导出审核记录")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
+ # 下载文件的容器,默认隐藏,生成后显
305
+ download_file = gr.File(label="点击下方链接下载", visible=False)
306
+
307
+ # ==================== 主内容区 (左右并排) ====================
308
+ with gr.Row():
309
+ # ===== 左侧:图表展示 (宽度占比 60%) =====
310
+ with gr.Column(scale=6, min_width=500):
311
  gr.Markdown("### 📈 图表展示")
 
 
312
  html_display = gr.HTML(
313
+ value="<div style='text-align:center;padding:50px;color:#999;'>请在上方选择完整路径以加载图表</div>",
314
  elem_classes=["chart-container"]
315
  )
316
+ debug_info = gr.Textbox(label="调试信息", value="", interactive=False, show_label=False, elem_classes=["debug-panel"])
317
+
318
+ # ===== 右侧:标签信息和 QA 审核 (宽度占比 40%) =====
319
+ with gr.Column(scale=4, min_width=400):
320
+ # 将元数据折叠,给下方的问答留出更多空间
321
+ with gr.Accordion("📝 图表标签信息 (Metadata)", open=False):
322
+ label_display = gr.Markdown(value="暂无信息")
 
 
323
 
 
324
  gr.Markdown("### ❓ 问答审核")
325
 
 
326
  current_qa_id = gr.Textbox(visible=False, value="")
327
 
328
+ qa_selector = gr.Radio(label="1. 选择要审核的问答对", choices=[], interactive=True)
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
+ qa_question_display = gr.Textbox(label="原始问题", interactive=False, lines=2, value="")
331
+ qa_answer_display = gr.Textbox(label="原始答案", interactive=False, lines=2, value="")
 
 
 
 
332
 
333
  gr.Markdown("---")
334
+ gr.Markdown("#### 2. 录入审核结果")
335
 
 
336
  status_radio = gr.Radio(
337
+ label="判定结果",
338
+ choices=[("✅ 正确", "correct"), ("❌ 错误", "incorrect"), ("✏️ 需修改", "needs_modification"), ("⏳ 待定", "pending")],
339
+ value="pending", interactive=True
 
 
 
 
 
 
340
  )
341
 
 
342
  issue_type_dropdown = gr.Dropdown(
343
+ label="问题错误类型 (若有)",
344
+ choices=["问题歧义", "答案错误", "图表不清晰", "问题不合理", "答案格式错误", "其他"],
345
+ interactive=True, value=""
 
 
 
 
 
 
 
 
346
  )
347
 
348
+ modified_question = gr.Textbox(label="修改后的问题", placeholder="如需修改问题,请在此输入...", lines=2, interactive=True, value="")
349
+ modified_answer = gr.Textbox(label="修改后的答案", placeholder="如需修改答案,请在此输入...", lines=2, interactive=True, value="")
350
+ comment_textbox = gr.Textbox(label="评论/备注", placeholder="请输入审核意见或备注...", lines=2, interactive=True, value="")
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
+ save_btn = gr.Button("💾 保存当前 QA 审核结果", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
353
  save_result = gr.Textbox(label="", visible=False)
354
+
355
  # ==================== 事件绑定 ====================
356
 
 
357
  def init_dataset():
358
  structure = data_manager.get_dataset_structure()
359
  sources = list(structure.get('sources', {}).keys())
360
  return gr.Dropdown(choices=sources, value=sources[0] if sources else None)
361
+
362
+ app.load(fn=init_dataset, outputs=[source_dropdown])
363
 
364
+ source_dropdown.change(fn=update_chart_type_dropdown, inputs=[source_dropdown], outputs=[chart_type_dropdown])
365
+ chart_type_dropdown.change(fn=update_chart_dropdown, inputs=[source_dropdown, chart_type_dropdown], outputs=[chart_dropdown, model_dropdown])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
+ # 联动加载数据
368
+ load_inputs = [source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown]
369
+ load_outputs = [html_display, label_display, qa_data_json, status_text, progress_text, current_reviews_json, qa_selector, debug_info]
370
+ model_dropdown.change(fn=load_chart_data, inputs=load_inputs, outputs=load_outputs)
371
+ chart_dropdown.change(fn=load_chart_data, inputs=load_inputs, outputs=load_outputs)
 
 
 
372
 
373
+ # QA 选择器
374
  def on_qa_selected(qa_index_str, qa_json, reviews_json):
375
  if not qa_index_str or not qa_json:
376
  return ["", "", "", gr.Radio(value="pending"), "", "", "", ""]
 
377
  try:
378
  qa_list = json.loads(qa_json)
379
  reviews = json.loads(reviews_json)
 
 
380
  index = int(qa_index_str.split(":")[0].replace("Q", "")) - 1
381
  qa = qa_list[index]
 
 
382
  review = reviews.get(qa['id'], {})
 
383
  return [
384
+ qa['id'], qa['question'], qa['answer'],
385
+ gr.Radio(value=review.get('status', 'pending')),
386
+ review.get('issue_type', ''),
387
+ review.get('modified_question', ''),
388
+ review.get('modified_answer', ''),
389
+ review.get('comment', '')
 
 
390
  ]
391
  except Exception as e:
392
  print(f"Error in on_qa_selected: {e}")
393
  return ["", "", "", gr.Radio(value="pending"), "", "", "", ""]
394
+
395
  qa_selector.change(
396
  fn=on_qa_selected,
397
  inputs=[qa_selector, qa_data_json, current_reviews_json],
398
+ outputs=[current_qa_id, qa_question_display, qa_answer_display, status_radio, issue_type_dropdown, modified_question, modified_answer, comment_textbox]
 
 
 
 
 
 
 
 
 
399
  )
400
 
401
+ prev_btn.click(fn=navigate_prev, outputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown])
402
+ next_btn.click(fn=navigate_next, outputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown])
 
 
403
 
 
404
  save_btn.click(
405
  fn=save_review_handler,
406
+ inputs=[current_qa_id, chart_dropdown, source_dropdown, chart_type_dropdown, model_dropdown, qa_question_display, qa_answer_display, status_radio, modified_question, modified_answer, issue_type_dropdown, comment_textbox, reviewer_input],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  outputs=[save_result]
408
+ ).then(fn=lambda: gr.Textbox(visible=True), outputs=[save_result])
409
 
410
+ # 导出并显示下载文件组件
411
  export_btn.click(
412
  fn=export_reviews_handler,
413
+ outputs=[download_file]
 
 
 
414
  )
 
 
 
415
 
416
  # ============== 主入口 ==============
 
417
  if __name__ == "__main__":
418
  app = create_ui()
419
  app.launch(
test.py ADDED
@@ -0,0 +1,683 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 图表问答数据集审核系统 - Gradio 5.x 应用
3
+ 用于人工审核每个图表对应的问题和答案是否合理正确
4
+ """
5
+ import gradio as gr
6
+ from data_manager import DataManager, data_manager
7
+ from typing import Dict, List, Optional, Tuple, Any
8
+ import json
9
+ import os
10
+ import base64
11
+
12
+ # ============== 全局状态 ==============
13
+
14
+ class AppState:
15
+ """应用状态管理"""
16
+ def __init__(self):
17
+ self.current_source: str = ""
18
+ self.current_chart_type: str = ""
19
+ self.current_chart_id: str = ""
20
+ self.current_model: str = ""
21
+ self.all_paths: List[Dict] = []
22
+ self.current_index: int = -1
23
+
24
+ # 初始化时获取所有路径
25
+ self.refresh_paths()
26
+
27
+ def refresh_paths(self):
28
+ """刷新所有图表路径"""
29
+ self.all_paths = data_manager.get_all_chart_paths()
30
+
31
+ def get_current_path(self) -> Optional[Dict]:
32
+ """获取当前路径信息"""
33
+ if 0 <= self.current_index < len(self.all_paths):
34
+ return self.all_paths[self.current_index]
35
+ return None
36
+
37
+ def set_position(self, source: str, chart_type: str, chart_id: str, model: str):
38
+ """设置当前位置"""
39
+ self.current_source = source
40
+ self.current_chart_type = chart_type
41
+ self.current_chart_id = chart_id
42
+ self.current_model = model
43
+
44
+ # 更新索引
45
+ for i, path in enumerate(self.all_paths):
46
+ if (path['source'] == source and
47
+ path['chart_type'] == chart_type and
48
+ path['chart_id'] == chart_id and
49
+ path['model'] == model):
50
+ self.current_index = i
51
+ break
52
+
53
+ def navigate(self, direction: int) -> bool:
54
+ """
55
+ 导航到上一个或下一个图表
56
+
57
+ Args:
58
+ direction: 1 表示下一个,-1 表示上一个
59
+
60
+ Returns:
61
+ 是否成功导航
62
+ """
63
+ new_index = self.current_index + direction
64
+ if 0 <= new_index < len(self.all_paths):
65
+ self.current_index = new_index
66
+ path = self.all_paths[new_index]
67
+ self.current_source = path['source']
68
+ self.current_chart_type = path['chart_type']
69
+ self.current_chart_id = path['chart_id']
70
+ self.current_model = path['model']
71
+ return True
72
+ return False
73
+
74
+ state = AppState()
75
+
76
+
77
+ # ============== UI 更新函数 ==============
78
+
79
+ def get_dataset_choices() -> Tuple[List[str], List[str], List[str], List[str]]:
80
+ """获取数据集的选择项"""
81
+ structure = data_manager.get_dataset_structure()
82
+
83
+ sources = list(structure.get('sources', {}).keys())
84
+
85
+ chart_types = []
86
+ charts = []
87
+ models = []
88
+
89
+ if state.current_source:
90
+ source_data = structure['sources'].get(state.current_source, {})
91
+ chart_types = list(source_data.get('chart_types', {}).keys())
92
+
93
+ if state.current_chart_type:
94
+ charts = data_manager.get_chart_list(state.current_source, state.current_chart_type)
95
+ ct_data = source_data.get('chart_types', {}).get(state.current_chart_type, {})
96
+ models = ct_data.get('models', [])
97
+
98
+ return sources, chart_types, charts, models
99
+
100
+
101
+ def update_chart_type_dropdown(source: str):
102
+ """更新图表类型下拉框"""
103
+ state.current_source = source
104
+ structure = data_manager.get_dataset_structure()
105
+
106
+ chart_types = list(structure.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
107
+
108
+ return gr.Dropdown(choices=chart_types, value=chart_types[0] if chart_types else None)
109
+
110
+
111
+ def update_chart_dropdown(source: str, chart_type: str):
112
+ """更新图表和模型下拉框"""
113
+ state.current_source = source
114
+ state.current_chart_type = chart_type
115
+
116
+ charts = data_manager.get_chart_list(source, chart_type)
117
+ structure = data_manager.get_dataset_structure()
118
+ ct_data = structure.get('sources', {}).get(source, {}).get('chart_types', {}).get(chart_type, {})
119
+ models = ct_data.get('models', [])
120
+
121
+ return (
122
+ gr.Dropdown(choices=charts, value=charts[0] if charts else None),
123
+ gr.Dropdown(choices=models, value=models[0] if models else None)
124
+ )
125
+
126
+
127
+ def create_embedded_html(html_content: str, chart_id: str = "") -> str:
128
+ """
129
+ 创建嵌入式的 HTML 显示
130
+
131
+ 使用 data URI 方式嵌入 HTML 内容到 iframe 中
132
+ """
133
+ if not html_content:
134
+ return f"""
135
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;
136
+ min-height:400px;color:#999;border:2px dashed #ddd;border-radius:12px;background:#fafafa;">
137
+ <div style="font-size:48px;margin-bottom:16px;">📭</div>
138
+ <div style="font-size:18px;font-weight:500;">暂无图表内容</div>
139
+ <div style="font-size:14px;margin-top:8px;">图表 ID: {chart_id or '未知'}</div>
140
+ <div style="font-size:12px;margin-top:16px;color:#888;">请检查数据集目录中是否存在该图表的 HTML 文件</div>
141
+ </div>
142
+ """
143
+
144
+ # 使用 base64 编码 HTML 内容,避免引号转义问题
145
+ html_bytes = html_content.encode('utf-8')
146
+ html_base64 = base64.b64encode(html_bytes).decode('utf-8')
147
+
148
+ # 使用 data URI
149
+ iframe_html = f"""
150
+ <iframe
151
+ src="data:text/html;base64,{html_base64}"
152
+ style="width:100%;height:500px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;"
153
+ sandbox="allow-scripts allow-same-origin"
154
+ loading="lazy"
155
+ ></iframe>
156
+ """
157
+
158
+ return iframe_html
159
+
160
+
161
+ def load_chart_data(source: str, chart_type: str, chart_id: str, model: str):
162
+ """
163
+ 加载图表数据并返回所有 UI 更新
164
+
165
+ Returns:
166
+ 包含所有 UI 组件更新值的元组
167
+ """
168
+ if not all([source, chart_type, chart_id, model]):
169
+ return [
170
+ create_embedded_html(""), # html_display
171
+ "### 请在左侧选择图表", # label_info
172
+ "[]", # qa_data (JSON string)
173
+ "等待加载数据...", # status_text
174
+ "请在左侧选择图表", # progress_text
175
+ "{}", # current_qa_reviews (JSON string)
176
+ gr.Radio(choices=[], value=None), # qa_selector
177
+ "" # debug_info
178
+ ]
179
+
180
+ # 更新状态
181
+ state.set_position(source, chart_type, chart_id, model)
182
+
183
+ # 获取图表数据
184
+ chart_data = data_manager.get_chart_data(source, chart_type, chart_id)
185
+ html_content = chart_data.get('html_content', '')
186
+ label_info = chart_data.get('label_info', {})
187
+
188
+ # 创建嵌入式 HTML
189
+ embedded_html = create_embedded_html(html_content, chart_id)
190
+
191
+ # 调试信息
192
+ debug_info = f"📁 {source}/{chart_type}/{chart_id} | HTML: {len(html_content)} 字符"
193
+
194
+ # 格式化标签信息
195
+ if label_info:
196
+ label_text = f"""
197
+ ### 图表信息
198
+
199
+ | 属性 | 值 |
200
+ |------|-----|
201
+ | **编号** | {label_info.get('Number', '-')} |
202
+ | **类型** | {label_info.get('Type', '-')} |
203
+ | **来源** | {label_info.get('Source', '-')} |
204
+ | **主题** | {label_info.get('Topic', '-')} |
205
+ | **描述** | {label_info.get('Describe', '-')} |
206
+ | **链接** | [查看原图]({label_info.get('Weblink', '#')}) |
207
+ """
208
+ else:
209
+ label_text = "### ⚠️ 暂无标签信息"
210
+
211
+ # 获取 QA 列表
212
+ qa_list = data_manager.get_qa_list(source, chart_type, model, chart_id)
213
+
214
+ # 获取已有的审核记录
215
+ existing_reviews = {}
216
+ for review in data_manager.get_reviews_by_chart(chart_id, model):
217
+ existing_reviews[review['qa_id']] = review
218
+
219
+ # 更新进度文本
220
+ progress_text = f"当前: {state.current_index + 1} / {len(state.all_paths)} 个图表"
221
+
222
+ # 状态文本
223
+ stats = data_manager.get_review_stats()
224
+ status_text = f"已审核: {stats['total']} | ✅正确: {stats['correct']} | ❌错误: {stats['incorrect']} | ✏️需修改: {stats['needs_modification']}"
225
+
226
+ # QA 选择器选项
227
+ qa_choices = [f"Q{i+1}: {qa.question[:50]}..." for i, qa in enumerate(qa_list)] if qa_list else []
228
+
229
+ return [
230
+ embedded_html, # html_display
231
+ label_text, # label_info
232
+ json.dumps([{"id": qa.id, "question": qa.question, "answer": qa.answer} for qa in qa_list]), # qa_data (JSON string)
233
+ status_text, # status_text
234
+ progress_text, # progress_text
235
+ json.dumps(existing_reviews), # current_qa_reviews (JSON string)
236
+ gr.Radio(choices=qa_choices, value=qa_choices[0] if qa_choices else None), # qa_selector
237
+ debug_info # debug_info
238
+ ]
239
+
240
+
241
+ def navigate_prev():
242
+ """导航到上一个图表"""
243
+ if state.navigate(-1):
244
+ path = state.get_current_path()
245
+ if path:
246
+ return (
247
+ gr.Dropdown(value=path['source']),
248
+ gr.Dropdown(value=path['chart_type']),
249
+ gr.Dropdown(value=path['chart_id']),
250
+ gr.Dropdown(value=path['model'])
251
+ )
252
+ return [gr.Dropdown(), gr.Dropdown(), gr.Dropdown(), gr.Dropdown()]
253
+
254
+
255
+ def navigate_next():
256
+ """导航到下一个图表"""
257
+ if state.navigate(1):
258
+ path = state.get_current_path()
259
+ if path:
260
+ return (
261
+ gr.Dropdown(value=path['source']),
262
+ gr.Dropdown(value=path['chart_type']),
263
+ gr.Dropdown(value=path['chart_id']),
264
+ gr.Dropdown(value=path['model'])
265
+ )
266
+ return [gr.Dropdown(), gr.Dropdown(), gr.Dropdown(), gr.Dropdown()]
267
+
268
+
269
+ def save_review_handler(
270
+ qa_id: str,
271
+ chart_id: str,
272
+ source: str,
273
+ chart_type: str,
274
+ model: str,
275
+ original_question: str,
276
+ original_answer: str,
277
+ status: str,
278
+ modified_question: str,
279
+ modified_answer: str,
280
+ issue_type: str,
281
+ comment: str,
282
+ reviewer: str
283
+ ) -> str:
284
+ """保存审核记录"""
285
+ if not qa_id:
286
+ return "❌ 请先选择一个问答对"
287
+
288
+ review_data = {
289
+ "qa_id": qa_id,
290
+ "chart_id": chart_id,
291
+ "source": source,
292
+ "chart_type": chart_type,
293
+ "model": model,
294
+ "original_question": original_question,
295
+ "original_answer": original_answer,
296
+ "status": status,
297
+ "modified_question": modified_question,
298
+ "modified_answer": modified_answer,
299
+ "issue_type": issue_type,
300
+ "comment": comment,
301
+ "reviewer": reviewer
302
+ }
303
+
304
+ result = data_manager.save_review(review_data)
305
+
306
+ # 返回更新后的统计
307
+ stats = data_manager.get_review_stats()
308
+ return f"✅ 已保存! 总计: {stats['total']} | ✅正确: {stats['correct']} | ❌错误: {stats['incorrect']} | ✏️需修改: {stats['needs_modification']}"
309
+
310
+
311
+ def export_reviews_handler():
312
+ """导出审核记录"""
313
+ output_path = data_manager.export_reviews("./reviews_export.json")
314
+ return f"✅ 审核记录已导出至: {output_path}"
315
+
316
+
317
+ # ============== 创建 Gradio 界面 ==============
318
+
319
+ def create_ui():
320
+ """创建 Gradio 界面"""
321
+
322
+ # 自定义 CSS
323
+ custom_css = """
324
+ .chart-container {
325
+ min-height: 520px;
326
+ }
327
+
328
+ .control-panel {
329
+ background: #f8f9fa;
330
+ padding: 15px;
331
+ border-radius: 8px;
332
+ margin-bottom: 10px;
333
+ }
334
+
335
+ .debug-panel {
336
+ font-size: 12px;
337
+ color: #666;
338
+ padding: 8px;
339
+ background: #f5f5f5;
340
+ border-radius: 4px;
341
+ margin-top: 10px;
342
+ }
343
+ """
344
+
345
+ with gr.Blocks(
346
+ title="图表问答数据集审核系统",
347
+ theme=gr.themes.Soft(),
348
+ css=custom_css
349
+ ) as app:
350
+
351
+ # 隐藏的状态存储(使用 JSON 字符串)
352
+ qa_data_json = gr.State(value="[]")
353
+ current_reviews_json = gr.State(value="{}")
354
+
355
+ # ==================== 标题栏 ====================
356
+ gr.Markdown("""
357
+ # 📊 图表问答数据集审核系统
358
+
359
+ 审核每个图表对应的问题和答案是否合理正确。使用 ← → 按钮切换图表。
360
+ """)
361
+
362
+ # ==================== 顶部状态栏 ====================
363
+ with gr.Row():
364
+ status_text = gr.Textbox(
365
+ label="审核统计",
366
+ value="等待加载数据...",
367
+ interactive=False,
368
+ show_label=False,
369
+ scale=2
370
+ )
371
+ progress_text = gr.Textbox(
372
+ label="进度",
373
+ value="请在左侧选择图表",
374
+ interactive=False,
375
+ show_label=False,
376
+ scale=1
377
+ )
378
+
379
+ # ==================== 主内容区 ====================
380
+ with gr.Row():
381
+ # ===== 左侧:导航控制 =====
382
+ with gr.Column(scale=1, min_width=250):
383
+ gr.Markdown("### 📁 数据选择")
384
+
385
+ source_dropdown = gr.Dropdown(
386
+ label="数据来源 (Source)",
387
+ choices=[],
388
+ interactive=True
389
+ )
390
+
391
+ chart_type_dropdown = gr.Dropdown(
392
+ label="图表类型 (Chart Type)",
393
+ choices=[],
394
+ interactive=True
395
+ )
396
+
397
+ chart_dropdown = gr.Dropdown(
398
+ label="图表 ID",
399
+ choices=[],
400
+ interactive=True
401
+ )
402
+
403
+ model_dropdown = gr.Dropdown(
404
+ label="模型 (Model)",
405
+ choices=[],
406
+ interactive=True
407
+ )
408
+
409
+ gr.Markdown("---")
410
+
411
+ # 导航按钮
412
+ with gr.Row():
413
+ prev_btn = gr.Button("⬅️ 上一个")
414
+ next_btn = gr.Button("➡️ 下一个")
415
+
416
+ # 导出按钮
417
+ export_btn = gr.Button("📥 导出审核记录", variant="secondary")
418
+ export_result = gr.Textbox(label="", visible=False)
419
+
420
+ # 审核人设置
421
+ reviewer_input = gr.Textbox(
422
+ label="审核人",
423
+ value="default",
424
+ interactive=True
425
+ )
426
+
427
+ # 调试信息
428
+ debug_info = gr.Textbox(
429
+ label="调试信息",
430
+ value="",
431
+ interactive=False,
432
+ show_label=False,
433
+ elem_classes=["debug-panel"]
434
+ )
435
+
436
+ # ===== 中间:图表展示 =====
437
+ with gr.Column(scale=2, min_width=400):
438
+ gr.Markdown("### 📈 图表展示")
439
+
440
+ # HTML 图表展示(使用 iframe)
441
+ html_display = gr.HTML(
442
+ value="<div style='text-align:center;padding:50px;color:#999;'>请选择图表</div>",
443
+ elem_classes=["chart-container"]
444
+ )
445
+
446
+ # ===== 右侧:标签信息和 QA 审核 =====
447
+ with gr.Column(scale=2, min_width=400):
448
+ # 标签信息
449
+ gr.Markdown("### 📝 图表标签")
450
+ label_display = gr.Markdown(
451
+ value="暂无信息",
452
+ elem_classes=["control-panel"]
453
+ )
454
+
455
+ # QA 审核区
456
+ gr.Markdown("### ❓ 问答审核")
457
+
458
+ # 当前选中的 QA 信息(隐藏)
459
+ current_qa_id = gr.Textbox(visible=False, value="")
460
+
461
+ # QA 显示
462
+ qa_question_display = gr.Textbox(
463
+ label="问题",
464
+ interactive=False,
465
+ lines=2,
466
+ value=""
467
+ )
468
+ qa_answer_display = gr.Textbox(
469
+ label="答案",
470
+ interactive=False,
471
+ lines=1,
472
+ value=""
473
+ )
474
+
475
+ # QA 选择器
476
+ qa_selector = gr.Radio(
477
+ label="选择要审核的问答对",
478
+ choices=[],
479
+ interactive=True
480
+ )
481
+
482
+ gr.Markdown("---")
483
+ gr.Markdown("#### 审核操作")
484
+
485
+ # 审核状态选择
486
+ status_radio = gr.Radio(
487
+ label="审核状态",
488
+ choices=[
489
+ ("✅ 正确", "correct"),
490
+ ("❌ 错误", "incorrect"),
491
+ ("✏️ 需修改", "needs_modification"),
492
+ ("⏳ 待定", "pending")
493
+ ],
494
+ value="pending",
495
+ interactive=True
496
+ )
497
+
498
+ # 问题类型
499
+ issue_type_dropdown = gr.Dropdown(
500
+ label="问题类型",
501
+ choices=[
502
+ "问题歧义",
503
+ "答案错误",
504
+ "图表不清晰",
505
+ "问题不合理",
506
+ "答案格式错误",
507
+ "其他"
508
+ ],
509
+ interactive=True,
510
+ value=""
511
+ )
512
+
513
+ # 修改后的问题和答案
514
+ modified_question = gr.Textbox(
515
+ label="修改后的问题",
516
+ placeholder="如需修改问题,请在此输入...",
517
+ lines=2,
518
+ interactive=True,
519
+ value=""
520
+ )
521
+
522
+ modified_answer = gr.Textbox(
523
+ label="修改后的答案",
524
+ placeholder="如需修改答案,请在此输入...",
525
+ lines=1,
526
+ interactive=True,
527
+ value=""
528
+ )
529
+
530
+ # 评论
531
+ comment_textbox = gr.Textbox(
532
+ label="评论/备注",
533
+ placeholder="请输入审核意见或备注...",
534
+ lines=2,
535
+ interactive=True,
536
+ value=""
537
+ )
538
+
539
+ # 保存按钮
540
+ save_btn = gr.Button("💾 保存审核结果", variant="primary")
541
+ save_result = gr.Textbox(label="", visible=False)
542
+
543
+ # ==================== 事件绑定 ====================
544
+
545
+ # 初始化数据集选择
546
+ def init_dataset():
547
+ structure = data_manager.get_dataset_structure()
548
+ sources = list(structure.get('sources', {}).keys())
549
+ return gr.Dropdown(choices=sources, value=sources[0] if sources else None)
550
+
551
+ app.load(
552
+ fn=init_dataset,
553
+ outputs=[source_dropdown]
554
+ )
555
+
556
+ # Source 变化 -> 更新 Chart Type
557
+ source_dropdown.change(
558
+ fn=update_chart_type_dropdown,
559
+ inputs=[source_dropdown],
560
+ outputs=[chart_type_dropdown]
561
+ )
562
+
563
+ # Chart Type 变化 -> 更新 Chart 和 Model
564
+ chart_type_dropdown.change(
565
+ fn=update_chart_dropdown,
566
+ inputs=[source_dropdown, chart_type_dropdown],
567
+ outputs=[chart_dropdown, model_dropdown]
568
+ )
569
+
570
+ # 选择图表 -> 加载数据
571
+ model_dropdown.change(
572
+ fn=load_chart_data,
573
+ inputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown],
574
+ outputs=[
575
+ html_display, label_display, qa_data_json, status_text, progress_text,
576
+ current_reviews_json, qa_selector, debug_info
577
+ ]
578
+ )
579
+
580
+ chart_dropdown.change(
581
+ fn=load_chart_data,
582
+ inputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown],
583
+ outputs=[
584
+ html_display, label_display, qa_data_json, status_text, progress_text,
585
+ current_reviews_json, qa_selector, debug_info
586
+ ]
587
+ )
588
+
589
+ # QA 选择器变化 -> 更新审核面板
590
+ def on_qa_selected(qa_index_str, qa_json, reviews_json):
591
+ if not qa_index_str or not qa_json:
592
+ return ["", "", "", gr.Radio(value="pending"), "", "", "", ""]
593
+
594
+ try:
595
+ qa_list = json.loads(qa_json)
596
+ reviews = json.loads(reviews_json)
597
+
598
+ # 解析索引
599
+ index = int(qa_index_str.split(":")[0].replace("Q", "")) - 1
600
+ qa = qa_list[index]
601
+
602
+ # 检查是否有现有审核
603
+ review = reviews.get(qa['id'], {})
604
+
605
+ return [
606
+ qa['id'], # current_qa_id
607
+ qa['question'], # qa_question_display
608
+ qa['answer'], # qa_answer_display
609
+ gr.Radio(value=review.get('status', 'pending')), # status_radio
610
+ review.get('issue_type', ''), # issue_type_dropdown
611
+ review.get('modified_question', ''), # modified_question
612
+ review.get('modified_answer', ''), # modified_answer
613
+ review.get('comment', '') # comment_textbox
614
+ ]
615
+ except Exception as e:
616
+ print(f"Error in on_qa_selected: {e}")
617
+ return ["", "", "", gr.Radio(value="pending"), "", "", "", ""]
618
+
619
+ qa_selector.change(
620
+ fn=on_qa_selected,
621
+ inputs=[qa_selector, qa_data_json, current_reviews_json],
622
+ outputs=[
623
+ current_qa_id, qa_question_display, qa_answer_display,
624
+ status_radio, issue_type_dropdown, modified_question, modified_answer, comment_textbox
625
+ ]
626
+ )
627
+
628
+ # 导航按钮
629
+ prev_btn.click(
630
+ fn=navigate_prev,
631
+ outputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown]
632
+ )
633
+
634
+ next_btn.click(
635
+ fn=navigate_next,
636
+ outputs=[source_dropdown, chart_type_dropdown, chart_dropdown, model_dropdown]
637
+ )
638
+
639
+ # 保存审核
640
+ save_btn.click(
641
+ fn=save_review_handler,
642
+ inputs=[
643
+ current_qa_id,
644
+ chart_dropdown,
645
+ source_dropdown,
646
+ chart_type_dropdown,
647
+ model_dropdown,
648
+ qa_question_display,
649
+ qa_answer_display,
650
+ status_radio,
651
+ modified_question,
652
+ modified_answer,
653
+ issue_type_dropdown,
654
+ comment_textbox,
655
+ reviewer_input
656
+ ],
657
+ outputs=[save_result]
658
+ ).then(
659
+ fn=lambda: gr.Textbox(visible=True),
660
+ outputs=[save_result]
661
+ )
662
+
663
+ # 导出
664
+ export_btn.click(
665
+ fn=export_reviews_handler,
666
+ outputs=[export_result]
667
+ ).then(
668
+ fn=lambda: gr.Textbox(visible=True),
669
+ outputs=[export_result]
670
+ )
671
+
672
+ return app
673
+
674
+
675
+ # ============== 主入口 ==============
676
+
677
+ if __name__ == "__main__":
678
+ app = create_ui()
679
+ app.launch(
680
+ server_name="0.0.0.0",
681
+ server_port=7860,
682
+ share=True
683
+ )