adddrett commited on
Commit
585bcee
·
1 Parent(s): 30ccf1a
Files changed (1) hide show
  1. app.py +157 -150
app.py CHANGED
@@ -1,216 +1,223 @@
1
  """
2
- 图表问答数据集审核系统 - Gradio 5.x 应用
3
  """
4
  import gradio as gr
5
- from data_manager import DataManager, data_manager
6
- from typing import Dict, List, Optional, Tuple, Any
7
  import json
8
- import os
9
  import base64
 
10
 
11
- # ============== 全局状态 ==============
12
 
13
  class AppState:
14
  def __init__(self):
15
- self.current_source: str = ""
16
- self.current_chart_type: str = ""
17
- self.current_chart_id: 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
39
- path['chart_id'] == chart_id and
40
- path['model'] == model):
41
  self.current_index = i
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
48
  path = self.all_paths[new_index]
49
- self.current_source = path['source']
50
- self.current_chart_type = path['chart_type']
51
- self.current_chart_id = path['chart_id']
52
- self.current_model = path['model']
53
- return True
54
- return False
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)
90
- html_content = chart_data.get('html_content', '')
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, show_api=False)
 
 
1
  """
2
+ 图表问答数据集审核系统 - Gradio 5.x 稳定版
3
  """
4
  import gradio as gr
5
+ from data_manager import data_manager # 假设你已经实例化了 data_manager
 
6
  import json
 
7
  import base64
8
+ from typing import Dict, List, Optional
9
 
10
+ # ============== 全局状态管理 ==============
11
 
12
  class AppState:
13
  def __init__(self):
14
+ self.current_source = ""
15
+ self.current_chart_type = ""
16
+ self.current_chart_id = ""
17
+ self.current_model = ""
18
+ self.all_paths = []
19
+ self.current_index = -1
20
  self.refresh_paths()
21
 
22
  def refresh_paths(self):
23
  self.all_paths = data_manager.get_all_chart_paths()
24
 
25
+ def set_position(self, source, chart_type, chart_id, model):
 
 
 
 
 
26
  self.current_source = source
27
  self.current_chart_type = chart_type
28
  self.current_chart_id = chart_id
29
  self.current_model = model
30
  for i, path in enumerate(self.all_paths):
31
+ if (path['source'] == source and path['chart_type'] == chart_type and
32
+ path['chart_id'] == chart_id and path['model'] == model):
 
 
33
  self.current_index = i
34
  break
35
 
36
+ def navigate(self, direction):
37
  new_index = self.current_index + direction
38
  if 0 <= new_index < len(self.all_paths):
39
  self.current_index = new_index
40
  path = self.all_paths[new_index]
41
+ return path['source'], path['chart_type'], path['chart_id'], path['model']
42
+ return None
 
 
 
 
43
 
44
  state = AppState()
45
 
46
+ # ============== 辅助逻辑 ==============
47
+
48
+ def create_embedded_html(html_content: str) -> str:
49
+ if not html_content:
50
+ return '<div style="height:600px;display:flex;align-items:center;justify-content:center;background:#eee;">请选择图表进行加载</div>'
51
+ # 使用 Base64 嵌入以避免跨域和字符编码问题
52
+ html_base64 = base64.b64encode(html_content.encode('utf-8')).decode('utf-8')
53
+ return f'<iframe src="data:text/html;base64,{html_base64}" style="width:100%;height:700px;border:none;background:white;"></iframe>'
54
 
55
+ # ============== UI 事件处理 ==============
56
+
57
+ def on_source_change(source):
58
  structure = data_manager.get_dataset_structure()
59
+ types = list(structure.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
60
+ return gr.update(choices=types, value=types[0] if types else None)
61
 
62
+ def on_type_change(source, chart_type):
 
 
63
  charts = data_manager.get_chart_list(source, chart_type)
64
  structure = data_manager.get_dataset_structure()
65
+ models = structure.get('sources', {}).get(source, {}).get('chart_types', {}).get(chart_type, {}).get('models', [])
 
66
  return (
67
+ gr.update(choices=charts, value=charts[0] if charts else None),
68
+ gr.update(choices=models, value=models[0] if models else None)
69
  )
70
 
71
+ def load_data(source, chart_type, chart_id, model):
 
 
 
 
 
 
72
  if not all([source, chart_type, chart_id, model]):
73
+ return ["", "", "[]", "等待选择...", "0/0", "{}", gr.update(choices=[]), "未选择"]
74
 
75
  state.set_position(source, chart_type, chart_id, model)
76
  chart_data = data_manager.get_chart_data(source, chart_type, chart_id)
 
 
 
 
 
 
77
  qa_list = data_manager.get_qa_list(source, chart_type, model, chart_id)
78
+ reviews = {r['qa_id']: r for r in data_manager.get_reviews_by_chart(chart_id, model)}
 
79
  stats = data_manager.get_review_stats()
80
+
81
+ # 构造返回值
82
+ html_frame = create_embedded_html(chart_data.get('html_content', ''))
83
+ label_md = "\n".join([f"- **{k}**: {v}" for k, v in chart_data.get('label_info', {}).items()])
84
+ qa_json = json.dumps([{"id": q.id, "question": q.question, "answer": q.answer} for q in qa_list])
85
+ stats_msg = f"✅{stats['correct']} | ❌{stats['incorrect']} | 总{stats['total']}"
86
+ progress_msg = f"{state.current_index + 1} / {len(state.all_paths)}"
87
+ qa_choices = [f"Q{i+1}: {q.question[:30]}..." for i, q in enumerate(qa_list)]
88
 
89
  return [
90
+ html_frame, label_md, qa_json, stats_msg, progress_msg,
91
+ json.dumps(reviews), gr.update(choices=qa_choices, value=qa_choices[0] if qa_choices else None),
92
+ f"{source}/{chart_type}/{chart_id}"
 
 
93
  ]
94
 
95
+ # ============== 界面构建 ==============
 
 
 
 
96
 
97
  def create_ui():
98
+ with gr.Blocks(title="图表 QA 审核系统", theme=gr.themes.Default()) as app:
99
+ # 隐藏状态存储
100
+ qa_store = gr.State("[]")
101
+ review_store = gr.State("{}")
 
 
102
 
103
+ gr.Markdown("# 📊 图表问答数据集审核")
104
 
 
105
  with gr.Row():
106
+ with gr.Column(scale=3):
107
+ with gr.Row():
108
+ source_drop = gr.Dropdown(label="数据源", choices=[], allow_custom_value=True)
109
+ type_drop = gr.Dropdown(label="类型", choices=[], allow_custom_value=True)
110
+ id_drop = gr.Dropdown(label="图表 ID", choices=[], allow_custom_value=True)
111
+ model_drop = gr.Dropdown(label="型", choices=[], allow_custom_value=True)
112
+
113
+ html_view = gr.HTML()
114
+ path_display = gr.Text(label="当前路径", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ with gr.Column(scale=1):
117
+ stats_view = gr.Textbox(label="审核统计", interactive=False)
118
+ progress_view = gr.Textbox(label="总进度", interactive=False)
119
 
120
+ with gr.Accordion("图表元数据", open=False):
121
+ meta_view = gr.Markdown()
 
 
122
 
123
+ gr.Markdown("### 审核区")
124
+ qa_select = gr.Radio(label="选择题目", choices=[])
125
+ curr_qa_id = gr.Text(visible=False)
126
+ q_text = gr.Textbox(label="问题", interactive=False, lines=2)
127
+ a_text = gr.Textbox(label="答案", interactive=False)
128
+
129
+ status_radio = gr.Radio(
130
+ label="判定",
131
+ choices=[("正确", "correct"), ("错误", "incorrect"), ("改后通过", "modified")],
132
+ value="correct"
133
+ )
134
+ issue_type = gr.Dropdown(label="错误类型", choices=["无", "答案错误", "问题模糊", "图表读取失败"])
135
+ mod_q = gr.Textbox(label="修改问题(选填)")
136
+ mod_a = gr.Textbox(label="修改答案(选填)")
137
  comment = gr.Textbox(label="备注")
138
+
139
+ save_btn = gr.Button("💾 保存审核", variant="primary")
140
+ with gr.Row():
141
+ prev_btn = gr.Button("⬅️ 上一个")
142
+ next_btn = gr.Button("➡️ 下一个")
143
+
144
+ export_btn = gr.Button("📦 导出 JSON")
145
+ download_file = gr.File(label="下载地址", visible=False)
 
 
 
 
 
 
 
146
 
147
+ # --- 交互绑定 ---
148
+
149
+ # 初始化
150
+ def init_ui():
151
+ structure = data_manager.get_dataset_structure()
152
+ sources = list(structure.get('sources', {}).keys())
153
+ return gr.update(choices=sources, value=sources[0] if sources else None)
154
+
155
+ app.load(init_ui, outputs=[source_drop])
156
+
157
+ # 联动逻辑
158
+ source_drop.change(on_source_change, inputs=[source_drop], outputs=[type_drop])
159
+ type_drop.change(on_type_change, inputs=[source_drop, type_drop], outputs=[id_drop, model_drop])
160
+
161
+ # 加载数据
162
+ load_inputs = [source_drop, type_drop, id_drop, model_drop]
163
+ load_outputs = [html_view, meta_view, qa_store, stats_view, progress_view, review_store, qa_select, path_display]
164
+ id_drop.change(load_data, inputs=load_inputs, outputs=load_outputs)
165
+ model_drop.change(load_data, inputs=load_inputs, outputs=load_outputs)
166
+
167
+ # 题目切换
168
+ def switch_qa(sel, qa_json, rev_json):
169
+ if not sel or qa_json == "[]": return [""] * 8
170
+ qas = json.loads(qa_json)
171
  revs = json.loads(rev_json)
172
  idx = int(sel.split(":")[0][1:]) - 1
173
+ curr = qas[idx]
174
+ r = revs.get(curr['id'], {})
175
+ return [
176
+ curr['id'], curr['question'], curr['answer'],
177
+ r.get('status', 'correct'), r.get('issue_type', '无'),
178
+ r.get('modified_question', ''), r.get('modified_answer', ''),
179
+ r.get('comment', '')
180
+ ]
181
+
182
+ qa_select.change(
183
+ switch_qa,
184
+ inputs=[qa_select, qa_store, review_store],
185
+ outputs=[curr_qa_id, q_text, a_text, status_radio, issue_type, mod_q, mod_a, comment]
186
+ )
187
 
 
 
188
  # 导航
189
+ def nav(direction):
190
+ res = state.navigate(direction)
191
+ if res:
192
+ return [gr.update(value=v) for v in res]
193
+ return [gr.update() for _ in range(4)]
194
+
195
+ prev_btn.click(lambda: nav(-1), outputs=load_inputs)
196
+ next_btn.click(lambda: nav(1), outputs=load_inputs)
197
+
198
+ # 保存
199
+ def save_handler(q_id, c_id, src, tp, mdl, o_q, o_a, stat, m_q, m_a, it, cmt):
200
+ data_manager.save_review({
201
+ "qa_id": q_id, "chart_id": c_id, "source": src, "chart_type": tp, "model": mdl,
202
+ "original_question": o_q, "original_answer": o_a, "status": stat,
203
+ "modified_question": m_q, "modified_answer": m_a, "issue_type": it, "comment": cmt
204
+ })
205
+ return "已保存"
206
 
207
  save_btn.click(
208
+ save_handler,
209
+ inputs=[curr_qa_id, id_drop, source_drop, type_drop, model_drop, q_text, a_text, status_radio, mod_q, mod_a, issue_type, comment],
210
+ outputs=[gr.Textbox(visible=False)] # 仅触发
211
+ )
212
+
213
+ export_btn.click(
214
+ fn=lambda: gr.update(value=data_manager.export_reviews(), visible=True),
215
+ outputs=[download_file]
216
+ )
 
 
217
 
218
+ return app # 必须要 return app
219
 
220
  if __name__ == "__main__":
221
+ demo = create_ui()
222
+ # show_api=False 是解决那个 TypeError 的关键
223
+ demo.launch(server_name="0.0.0.0", server_port=7860, show_api=False)