adddrett commited on
Commit
4c7b3d5
·
1 Parent(s): 1c91312
Files changed (1) hide show
  1. app.py +85 -292
app.py CHANGED
@@ -1,6 +1,5 @@
1
  """
2
  图表问答数据集审核系统 - Gradio 5.x 应用
3
- 用于人工审核每个图表对应的问题和答案是否合理正确
4
  """
5
  import gradio as gr
6
  from data_manager import DataManager, data_manager
@@ -12,7 +11,6 @@ import base64
12
  # ============== 全局状态 ==============
13
 
14
  class AppState:
15
- """应用状态管理"""
16
  def __init__(self):
17
  self.current_source: str = ""
18
  self.current_chart_type: str = ""
@@ -20,28 +18,21 @@ class AppState:
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
@@ -51,7 +42,6 @@ class AppState:
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
@@ -65,90 +55,35 @@ class AppState:
65
 
66
  state = AppState()
67
 
68
-
69
  # ============== UI 更新函数 ==============
70
 
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 = []
78
-
79
- if state.current_source:
80
- source_data = structure['sources'].get(state.current_source, {})
81
- chart_types = list(source_data.get('chart_types', {}).keys())
82
-
83
- if state.current_chart_type:
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, {})
104
  models = ct_data.get('models', [])
105
-
106
  return (
107
  gr.Dropdown(choices=charts, value=charts[0] if charts else None),
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>
120
- <div style="font-size:12px;margin-top:16px;color:#888;">请检查数据集目录中是否存在该图表的 HTML 文件</div>
121
- </div>
122
- """
123
-
124
- # 使用 base64 编码 HTML 内容,避免引号转义问题
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)
@@ -156,268 +91,126 @@ def load_chart_data(source: str, chart_type: str, chart_id: str, model: str):
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', '-')} |
166
- | **类型** | {label_info.get('Type', '-')} |
167
- | **来源** | {label_info.get('Source', '-')} |
168
- | **主题** | {label_info.get('Topic', '-')} |
169
- | **描述** | {label_info.get('Describe', '-')} |
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):
202
- path = state.get_current_path()
203
- if path:
204
- return (
205
- gr.Dropdown(value=path['source']),
206
- gr.Dropdown(value=path['chart_type']),
207
- gr.Dropdown(value=path['chart_id']),
208
- gr.Dropdown(value=path['model'])
209
- )
210
- return [gr.Dropdown(), gr.Dropdown(), gr.Dropdown(), gr.Dropdown()]
211
-
212
- def navigate_next():
213
- """导航到下一个图表"""
214
- if state.navigate(1):
215
- path = state.get_current_path()
216
- if path:
217
- return (
218
- gr.Dropdown(value=path['source']),
219
- gr.Dropdown(value=path['chart_type']),
220
- gr.Dropdown(value=path['chart_id']),
221
- gr.Dropdown(value=path['model'])
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;
273
- padding: 8px;
274
- background: #f5f5f5;
275
- border-radius: 4px;
276
- margin-top: 10px;
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(
420
- server_name="0.0.0.0",
421
- server_port=7860,
422
- share=True
423
- )
 
1
  """
2
  图表问答数据集审核系统 - Gradio 5.x 应用
 
3
  """
4
  import gradio as gr
5
  from data_manager import DataManager, data_manager
 
11
  # ============== 全局状态 ==============
12
 
13
  class AppState:
 
14
  def __init__(self):
15
  self.current_source: str = ""
16
  self.current_chart_type: str = ""
 
18
  self.current_model: str = ""
19
  self.all_paths: List[Dict] = []
20
  self.current_index: int = -1
 
 
21
  self.refresh_paths()
22
 
23
  def refresh_paths(self):
 
24
  self.all_paths = data_manager.get_all_chart_paths()
25
 
26
  def get_current_path(self) -> Optional[Dict]:
 
27
  if 0 <= self.current_index < len(self.all_paths):
28
  return self.all_paths[self.current_index]
29
  return None
30
 
31
  def set_position(self, source: str, chart_type: str, chart_id: str, model: str):
 
32
  self.current_source = source
33
  self.current_chart_type = chart_type
34
  self.current_chart_id = chart_id
35
  self.current_model = model
 
 
36
  for i, path in enumerate(self.all_paths):
37
  if (path['source'] == source and
38
  path['chart_type'] == chart_type and
 
42
  break
43
 
44
  def navigate(self, direction: int) -> bool:
 
45
  new_index = self.current_index + direction
46
  if 0 <= new_index < len(self.all_paths):
47
  self.current_index = new_index
 
55
 
56
  state = AppState()
57
 
 
58
  # ============== UI 更新函数 ==============
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  def update_chart_type_dropdown(source: str):
 
61
  state.current_source = source
62
  structure = data_manager.get_dataset_structure()
63
  chart_types = list(structure.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
64
  return gr.Dropdown(choices=chart_types, value=chart_types[0] if chart_types else None)
65
 
66
  def update_chart_dropdown(source: str, chart_type: str):
 
67
  state.current_source = source
68
  state.current_chart_type = chart_type
69
  charts = data_manager.get_chart_list(source, chart_type)
70
  structure = data_manager.get_dataset_structure()
71
  ct_data = structure.get('sources', {}).get(source, {}).get('chart_types', {}).get(chart_type, {})
72
  models = ct_data.get('models', [])
 
73
  return (
74
  gr.Dropdown(choices=charts, value=charts[0] if charts else None),
75
  gr.Dropdown(choices=models, value=models[0] if models else None)
76
  )
77
 
78
  def create_embedded_html(html_content: str, chart_id: str = "") -> str:
 
79
  if not html_content:
80
+ return f'<div style="display:flex;min-height:750px;align-items:center;justify-content:center;background:#fafafa;border:2px dashed #ddd;">暂无图表内容 (ID: {chart_id})</div>'
81
+ html_base64 = base64.b64encode(html_content.encode('utf-8')).decode('utf-8')
82
+ return f'<iframe src="data:text/html;base64,{html_base64}" style="width:100%;height:750px;border:1px solid #e0e0e0;background:#fff;" sandbox="allow-scripts allow-same-origin"></iframe>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  def load_chart_data(source: str, chart_type: str, chart_id: str, model: str):
 
85
  if not all([source, chart_type, chart_id, model]):
86
+ return [create_embedded_html(""), "### 请选择路径", "[]", "等待加载...", "请选择图表", "{}", gr.Radio(choices=[]), ""]
 
 
 
 
 
 
 
 
 
87
 
88
  state.set_position(source, chart_type, chart_id, model)
89
  chart_data = data_manager.get_chart_data(source, chart_type, chart_id)
 
91
  label_info = chart_data.get('label_info', {})
92
 
93
  embedded_html = create_embedded_html(html_content, chart_id)
94
+ label_text = f"| 属性 | 值 |\n|---|---|\n" + "\n".join([f"| **{k}** | {v} |" for k, v in label_info.items()])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  qa_list = data_manager.get_qa_list(source, chart_type, model, chart_id)
97
+ existing_reviews = {r['qa_id']: r for r in data_manager.get_reviews_by_chart(chart_id, model)}
 
 
 
 
 
98
 
99
  stats = data_manager.get_review_stats()
100
+ status_msg = f"已审核: {stats['total']} | ✅正确: {stats['correct']} | ❌错误: {stats['incorrect']}"
101
+ progress_msg = f"进度: {state.current_index + 1} / {len(state.all_paths)}"
102
+ qa_choices = [f"Q{i+1}: {qa.question[:40]}..." for i, qa in enumerate(qa_list)]
103
 
104
  return [
105
+ embedded_html, label_text,
 
106
  json.dumps([{"id": qa.id, "question": qa.question, "answer": qa.answer} for qa in qa_list]),
107
+ status_msg, progress_msg, json.dumps(existing_reviews),
 
 
108
  gr.Radio(choices=qa_choices, value=qa_choices[0] if qa_choices else None),
109
+ f"Path: {source}/{chart_type}/{chart_id}"
110
  ]
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  def export_reviews_handler():
 
 
113
  output_path = data_manager.export_reviews("./reviews_export.json")
 
114
  return gr.update(value=output_path, visible=True)
115
 
 
116
  # ============== 创建 Gradio 界面 ==============
117
 
118
  def create_ui():
119
+ custom_css = ".chart-container { min-height: 750px; } .nav-row { background: #f8f9fa; padding: 10px; border-radius: 8px; }"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  with gr.Blocks(title="图表问答数据集审核系统", theme=gr.themes.Soft(), css=custom_css) as app:
122
+ # 内部状态
123
  qa_data_json = gr.State(value="[]")
124
  current_reviews_json = gr.State(value="{}")
125
 
 
126
  gr.Markdown("# 📊 图表问答数据集审核系统")
127
 
128
+ # 顶部导航
129
  with gr.Row():
130
+ status_text = gr.Textbox(label="统计", interactive=False, scale=2)
131
+ progress_text = gr.Textbox(label="进度", interactive=False, scale=1)
132
 
133
+ with gr.Row(elem_classes=["nav-row"]):
134
+ source_drop = gr.Dropdown(label="数据来源", choices=[], allow_custom_value=True)
135
+ type_drop = gr.Dropdown(label="图表类型", choices=[], allow_custom_value=True)
136
+ id_drop = gr.Dropdown(label="图表 ID", choices=[], allow_custom_value=True)
137
+ model_drop = gr.Dropdown(label="模型", choices=[], allow_custom_value=True)
138
+ reviewer = gr.Textbox(label="审核人", value="admin")
139
 
140
  with gr.Row():
141
+ prev_btn = gr.Button("⬅️ 上一个")
142
+ next_btn = gr.Button("➡️ 下一个")
143
+ export_btn = gr.Button("📦 导出记录", variant="secondary")
144
+ download_file = gr.File(label="下载导出的 JSON", visible=False)
 
 
145
 
146
+ # 主体:左右排
147
  with gr.Row():
148
+ with gr.Column(scale=6): # 左侧图表
149
+ html_display = gr.HTML(elem_classes=["chart-container"])
150
+ debug_info = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ with gr.Column(scale=4): # 右侧审核
153
+ with gr.Accordion("📝 元数据", open=False):
154
+ label_display = gr.Markdown()
155
 
156
+ qa_selector = gr.Radio(label="选择问答对")
157
+ current_qa_id = gr.Textbox(visible=False)
158
+ orig_q = gr.Textbox(label="原始问题", interactive=False, lines=2)
159
+ orig_a = gr.Textbox(label="原始答案", interactive=False)
160
 
161
  gr.Markdown("---")
162
+ status_radio = gr.Radio(label="判定结果", choices=[("✅ 正确", "correct"), ("❌ 错误", "incorrect"), ("✏️ 需修改", "needs_modification")], value="correct")
163
+ issue_type = gr.Dropdown(label="错误类型", choices=["答案错误", "问题模糊", "图文不符", "其他"])
164
+ mod_q = gr.Textbox(label="修改问题", lines=2)
165
+ mod_a = gr.Textbox(label="修改答案")
166
+ comment = gr.Textbox(label="备注")
167
+ save_btn = gr.Button("💾 保存当前审核", variant="primary")
168
+ save_msg = gr.Textbox(label="系统提示", visible=False)
169
+
170
+ # --- 事件绑定 ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  def init_dataset():
172
+ s = list(data_manager.get_dataset_structure().get('sources', {}).keys())
173
+ return gr.update(choices=s, value=s[0] if s else None)
 
174
 
175
+ app.load(fn=init_dataset, outputs=[source_drop])
176
+ source_drop.change(fn=update_chart_type_dropdown, inputs=[source_drop], outputs=[type_drop])
177
+ type_drop.change(fn=update_chart_dropdown, inputs=[source_drop, type_drop], outputs=[id_drop, model_drop])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
+ load_args = dict(fn=load_chart_data, inputs=[source_drop, type_drop, id_drop, model_drop], outputs=[html_display, label_display, qa_data_json, status_text, progress_text, current_reviews_json, qa_selector, debug_info])
180
+ id_drop.change(**load_args)
181
+ model_drop.change(**load_args)
182
+
183
+ def on_qa_select(sel, qa_json, rev_json):
184
+ if not sel: return [""] * 8
185
+ qa_list = json.loads(qa_json)
186
+ revs = json.loads(rev_json)
187
+ idx = int(sel.split(":")[0][1:]) - 1
188
+ qa = qa_list[idx]
189
+ r = revs.get(qa['id'], {})
190
+ return [qa['id'], qa['question'], qa['answer'], r.get('status', 'correct'), r.get('issue_type', ''), r.get('modified_question', ''), r.get('modified_answer', ''), r.get('comment', '')]
191
+
192
+ qa_selector.change(fn=on_qa_select, inputs=[qa_selector, qa_data_json, current_reviews_json], outputs=[current_qa_id, orig_q, orig_a, status_radio, issue_type, mod_q, mod_a, comment])
193
 
194
+ # 导航
195
+ nav_out = [source_drop, type_drop, id_drop, model_drop]
196
+ prev_btn.click(fn=lambda: (state.navigate(-1), *[gr.update(value=v) for v in [state.current_source, state.current_chart_type, state.current_chart_id, state.current_model]])[1:], outputs=nav_out)
197
+ next_btn.click(fn=lambda: (state.navigate(1), *[gr.update(value=v) for v in [state.current_source, state.current_chart_type, state.current_chart_id, state.current_model]])[1:], outputs=nav_out)
198
+
199
  save_btn.click(
200
+ fn=lambda *args: (data_manager.save_review({
201
+ "qa_id": args[0], "chart_id": args[1], "source": args[2], "chart_type": args[3], "model": args[4],
202
+ "original_question": args[5], "original_answer": args[6], "status": args[7],
203
+ "modified_question": args[8], "modified_answer": args[9], "issue_type": args[10],
204
+ "comment": args[11], "reviewer": args[12]
205
+ }), "✅ 已保存记录")[1],
206
+ inputs=[current_qa_id, id_drop, source_drop, type_drop, model_drop, orig_q, orig_a, status_radio, mod_q, mod_a, issue_type, comment, reviewer],
207
+ outputs=[save_msg]
208
+ ).then(fn=lambda: gr.update(visible=True), outputs=[save_msg])
209
+
210
+ export_btn.click(fn=export_reviews_handler, outputs=[download_file])
211
+
212
+ return app # <--- 关键点:确保在这里 return
213
 
 
214
  if __name__ == "__main__":
215
  app = create_ui()
216
+ app.launch(server_name="0.0.0.0", server_port=7860)