adddrett commited on
Commit
4a22058
·
1 Parent(s): c5b4bef
Files changed (1) hide show
  1. app.py +118 -76
app.py CHANGED
@@ -1,166 +1,208 @@
1
  import gradio as gr
2
  import json
3
  import base64
4
- import os
5
  from data_manager import data_manager
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  # ============== 工具函数 ==============
8
  def to_html_frame(html_content):
9
  if not html_content:
10
  return '<div style="padding:20px;text-align:center;">请选择数据进行加载</div>'
11
- try:
12
- b64_content = base64.b64encode(html_content.encode('utf-8')).decode('utf-8')
13
- return f'<iframe src="data:text/html;base64,{b64_content}" style="width:100%;height:550px;border:2px solid #eee;border-radius:8px;"></iframe>'
14
- except:
15
- return '<div style="color:red;">HTML 渲染失败</div>'
16
-
17
- def get_stats_display():
18
- s = data_manager.get_review_stats()
19
- return f"✅ 正确: {s['correct']} | ❌ 错误: {s['incorrect']} | 🔧 优化: {s.get('modified', 0)} | 总计: {s['total']}"
20
 
21
- # ============== 核心逻辑 ==============
22
  def handle_source_change(source):
23
  struct = data_manager.get_dataset_structure()
24
  types = list(struct.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
25
- return gr.update(choices=types, value=types[0] if types else "")
 
26
 
27
  def handle_type_change(source, c_type):
28
  charts = data_manager.get_chart_list(source, c_type)
29
  struct = data_manager.get_dataset_structure()
30
  models = struct.get('sources', {}).get(source, {}).get('chart_types', {}).get(c_type, {}).get('models', [])
31
  return (
32
- gr.update(choices=charts, value=charts[0] if charts else ""),
33
- gr.update(choices=models, value=models[0] if models else "")
34
  )
35
 
36
  def handle_load(source, c_type, c_id, model):
37
  if not all([source, c_type, c_id, model]):
38
- return [gr.update()] * 7 # 这里的数量必须严格 outputs
 
39
 
 
40
  chart_data = data_manager.get_chart_data(source, c_type, c_id)
41
  qa_list = data_manager.get_qa_list(source, c_type, model, c_id)
 
42
 
43
- # 构造 Radio 选项,添加 ID 标记以便解析
44
- radio_choices = [f"[{i}] {q.question[:30]}..." for i, q in enumerate(qa_list)]
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  return [
47
- to_html_frame(chart_data.get('html_content', '')),
48
- "\n".join([f"- **{k}**: {v}" for k, v in chart_data.get('label_info', {}).items()]) if chart_data.get('label_info') else "无元数据",
49
- json.dumps([{"id": q.id, "q": q.question, "a": q.answer} for q in qa_list], ensure_ascii=False),
50
- get_stats_display(),
51
- f"{source} / {c_type} / {c_id}",
 
52
  gr.update(choices=radio_choices, value=radio_choices[0] if radio_choices else None),
53
- "" # 清空备注栏
54
  ]
55
 
56
  def handle_qa_select(selection, qa_json):
57
  if not selection or not qa_json:
58
- return ["", "", "", ""]
59
  try:
60
  qas = json.loads(qa_json)
61
- # 解析索引,形如 "[0] 标题..."
62
- idx = int(selection.split(']')[0].replace('[', ''))
63
  curr = qas[idx]
64
- return [curr['id'], curr['q'], curr['a'], ""]
65
- except Exception as e:
66
- print(f"解析QA失败: {e}")
67
- return ["", "", "", ""]
68
 
69
  # ============== UI 布局 ==============
70
  def create_ui():
71
- with gr.Blocks(title="ChartQA 审核系统", theme=gr.themes.Default()) as demo:
72
- # 内存状态
73
  qa_store = gr.State(value="[]")
 
74
 
75
- gr.Markdown("# 📑 图表问答数据集审核终端")
76
 
77
  with gr.Row():
78
  with gr.Column(scale=4):
79
  with gr.Row():
80
- src_dd = gr.Dropdown(label="1. 数据源", choices=[])
81
- typ_dd = gr.Dropdown(label="2. 图表类型")
82
- id_dd = gr.Dropdown(label="3. 图表 ID")
83
- mdl_dd = gr.Dropdown(label="4. 评估模型")
84
 
85
- chart_view = gr.HTML()
86
  path_info = gr.Text(label="当前路径", interactive=False)
87
 
88
  with gr.Column(scale=2):
89
  with gr.Group():
90
- stats_txt = gr.Text(label="审核统计", value=get_stats_display(), interactive=False)
91
- export_btn = gr.Button("📤 导出并下载 JSON", variant="secondary")
92
- download_file = gr.File(label="下载链接", visible=False)
93
-
94
- with gr.Accordion("元数据 (Meta)", open=False):
95
  meta_md = gr.Markdown()
96
 
97
  gr.Markdown("---")
98
  qa_radio = gr.Radio(label="题目列表", choices=[])
99
 
100
  with gr.Group():
101
- curr_qid = gr.Text(label="当前 QA ID", interactive=False)
102
- q_disp = gr.Text(label="问题", lines=2)
103
- a_disp = gr.Text(label="答案", lines=2)
 
104
  status_opt = gr.Radio(
105
- label="结论",
106
  choices=[("正确", "correct"), ("错误", "incorrect"), ("优化", "modified")],
107
  value="correct"
108
  )
109
- comment = gr.Text(label="备注")
110
- save_btn = gr.Button("💾 提交审核", variant="primary")
 
 
 
 
 
 
111
 
112
  # --- 事件绑定 ---
113
 
114
- # 1. 初始化
115
  demo.load(
116
  fn=lambda: gr.update(choices=list(data_manager.get_dataset_structure().get('sources', {}).keys())),
117
  outputs=[src_dd]
118
  )
119
 
120
- # 2. 联动
121
  src_dd.change(handle_source_change, inputs=[src_dd], outputs=[typ_dd])
122
  typ_dd.change(handle_type_change, inputs=[src_dd, typ_dd], outputs=[id_dd, mdl_dd])
123
 
124
- # 3. 加载 (确保 outputs 数量为 7)
125
- load_outputs = [chart_view, meta_md, qa_store, stats_txt, path_info, qa_radio, comment]
126
- id_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_outputs)
127
- mdl_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_outputs)
128
 
129
- # 4. 题目选择
130
  qa_radio.change(
131
  handle_qa_select,
132
  inputs=[qa_radio, qa_store],
133
- outputs=[curr_qid, q_disp, a_disp, comment]
134
  )
135
 
136
- # 5. 保存 (增加反馈)
137
- def on_save(qid, cid, src, status, cmt, model, c_type):
138
- if not qid:
139
- return gr.update(value="请先选择题目"), get_stats_display()
140
-
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  data_manager.save_review({
142
  "qa_id": qid, "chart_id": cid, "source": src,
143
- "chart_type": c_type, "model": model,
144
  "status": status, "comment": cmt
145
  })
146
- # 弹出提示
147
- gr.Info(f"已保存记录: {qid}")
148
- return gr.update(value=cmt), get_stats_display()
149
 
150
  save_btn.click(
151
- on_save,
152
- inputs=[curr_qid, id_dd, src_dd, status_opt, comment, mdl_dd, typ_dd],
153
- outputs=[comment, stats_txt]
154
- )
155
-
156
- # 6. 导出
157
- export_btn.click(
158
- fn=lambda: gr.update(value=data_manager.export_reviews(), visible=True),
159
- outputs=[download_file]
160
  )
161
 
162
  return demo
163
 
164
  if __name__ == "__main__":
165
  app = create_ui()
166
- app.launch(server_name="0.0.0.0", server_port=7860, show_api=False)
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import json
3
  import base64
 
4
  from data_manager import data_manager
5
 
6
+ # ============== 状态管理 (封装逻辑) ==============
7
+ class ReviewState:
8
+ def __init__(self):
9
+ self.all_paths = []
10
+ self.current_idx = -1
11
+
12
+ def sync_paths(self):
13
+ self.all_paths = data_manager.get_all_chart_paths()
14
+
15
+ def get_nav_target(self, direction):
16
+ new_idx = self.current_idx + direction
17
+ if 0 <= new_idx < len(self.all_paths):
18
+ self.current_idx = new_idx
19
+ return self.all_paths[new_idx]
20
+ return None
21
+
22
+ nav_state = ReviewState()
23
+
24
  # ============== 工具函数 ==============
25
  def to_html_frame(html_content):
26
  if not html_content:
27
  return '<div style="padding:20px;text-align:center;">请选择数据进行加载</div>'
28
+ b64_content = base64.b64encode(html_content.encode('utf-8')).decode('utf-8')
29
+ return f'<iframe src="data:text/html;base64,{b64_content}" style="width:100%;height:600px;border:none;"></iframe>'
 
 
 
 
 
 
 
30
 
31
+ # ============== 交互逻辑 ==============
32
  def handle_source_change(source):
33
  struct = data_manager.get_dataset_structure()
34
  types = list(struct.get('sources', {}).get(source, {}).get('chart_types', {}).keys())
35
+ # 默认选中第一个
36
+ return gr.update(choices=types, value=types[0] if types else None)
37
 
38
  def handle_type_change(source, c_type):
39
  charts = data_manager.get_chart_list(source, c_type)
40
  struct = data_manager.get_dataset_structure()
41
  models = struct.get('sources', {}).get(source, {}).get('chart_types', {}).get(c_type, {}).get('models', [])
42
  return (
43
+ gr.update(choices=charts, value=charts[0] if charts else None),
44
+ gr.update(choices=models, value=models[0] if models else None)
45
  )
46
 
47
  def handle_load(source, c_type, c_id, model):
48
  if not all([source, c_type, c_id, model]):
49
+ # 必须返回 8 update象以匹配 outputs
50
+ return [gr.update()] * 8
51
 
52
+ # 获取数据
53
  chart_data = data_manager.get_chart_data(source, c_type, c_id)
54
  qa_list = data_manager.get_qa_list(source, c_type, model, c_id)
55
+ stats = data_manager.get_review_stats()
56
 
57
+ # 同步索引位置
58
+ nav_state.sync_paths()
59
+ for i, p in enumerate(nav_state.all_paths):
60
+ if p['chart_id'] == c_id and p['model'] == model:
61
+ nav_state.current_idx = i
62
+ break
63
+
64
+ # 准备 UI 数据
65
+ html_code = to_html_frame(chart_data.get('html_content', ''))
66
+ meta_md = "\n".join([f"- **{k}**: {v}" for k, v in chart_data.get('label_info', {}).items()])
67
+ qa_json = json.dumps([{"id": q.id, "q": q.question, "a": q.answer} for q in qa_list])
68
+ stats_str = f"✅{stats['correct']} | ❌{stats['incorrect']} | 总{stats['total']}"
69
+ prog_str = f"{nav_state.current_idx + 1} / {len(nav_state.all_paths)}"
70
+ radio_choices = [f"Q{i+1}: {q.question[:20]}..." for i, q in enumerate(qa_list)]
71
 
72
  return [
73
+ html_code,
74
+ meta_md,
75
+ qa_json,
76
+ stats_str,
77
+ prog_str,
78
+ f"{source}/{c_type}/{c_id}",
79
  gr.update(choices=radio_choices, value=radio_choices[0] if radio_choices else None),
80
+ json.dumps({}) # 占位 review_store
81
  ]
82
 
83
  def handle_qa_select(selection, qa_json):
84
  if not selection or not qa_json:
85
+ return [""] * 8
86
  try:
87
  qas = json.loads(qa_json)
88
+ idx = int(selection.split(":")[0][1:]) - 1
 
89
  curr = qas[idx]
90
+ return [curr['id'], curr['q'], curr['a'], "correct", "无", "", "", ""]
91
+ except:
92
+ return [""] * 8
 
93
 
94
  # ============== UI 布局 ==============
95
  def create_ui():
96
+ with gr.Blocks(title="审核系统 V2", theme=gr.themes.Soft()) as demo:
97
+ # 显式初始化 State,避免布尔值陷阱
98
  qa_store = gr.State(value="[]")
99
+ review_store = gr.State(value="{}")
100
 
101
+ gr.Markdown("## 📑 图表问答数据集审核终端")
102
 
103
  with gr.Row():
104
  with gr.Column(scale=4):
105
  with gr.Row():
106
+ src_dd = gr.Dropdown(label="数据源", choices=["None"])
107
+ typ_dd = gr.Dropdown(label="图表类型")
108
+ id_dd = gr.Dropdown(label="图表 ID")
109
+ mdl_dd = gr.Dropdown(label="评估模型")
110
 
111
+ chart_view = gr.HTML(value='<div style="height:500px; background:#f0f0f0;"></div>')
112
  path_info = gr.Text(label="当前路径", interactive=False)
113
 
114
  with gr.Column(scale=2):
115
  with gr.Group():
116
+ stats_txt = gr.Text(label="统计信息", interactive=False)
117
+ prog_txt = gr.Text(label="审核进度", interactive=False)
118
+
119
+ with gr.Accordion("元数据解析", open=False):
 
120
  meta_md = gr.Markdown()
121
 
122
  gr.Markdown("---")
123
  qa_radio = gr.Radio(label="题目列表", choices=[])
124
 
125
  with gr.Group():
126
+ curr_qid = gr.Text(visible=False)
127
+ q_disp = gr.Text(label="问题内容", lines=2)
128
+ a_disp = gr.Text(label="标准答案")
129
+
130
  status_opt = gr.Radio(
131
+ label="审核结论",
132
  choices=[("正确", "correct"), ("错误", "incorrect"), ("优化", "modified")],
133
  value="correct"
134
  )
135
+ err_type = gr.Dropdown(label="错误分类", choices=["无", "事实错误", "逻辑错误", "图表无法读取"])
136
+
137
+ comment = gr.Text(label="审核备注")
138
+ save_btn = gr.Button("💾 提交单条审核", variant="primary")
139
+
140
+ with gr.Row():
141
+ prev_btn = gr.Button("⬅️ 上一个图表")
142
+ next_btn = gr.Button("➡️ 下一个图表")
143
 
144
  # --- 事件绑定 ---
145
 
146
+ # 初始化第一级
147
  demo.load(
148
  fn=lambda: gr.update(choices=list(data_manager.get_dataset_structure().get('sources', {}).keys())),
149
  outputs=[src_dd]
150
  )
151
 
152
+ # 联动逻辑
153
  src_dd.change(handle_source_change, inputs=[src_dd], outputs=[typ_dd])
154
  typ_dd.change(handle_type_change, inputs=[src_dd, typ_dd], outputs=[id_dd, mdl_dd])
155
 
156
+ # 加载数据 (严格匹配 8 output)
157
+ load_event_outputs = [chart_view, meta_md, qa_store, stats_txt, prog_txt, path_info, qa_radio, review_store]
158
+ id_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_event_outputs)
159
+ mdl_dd.change(handle_load, inputs=[src_dd, typ_dd, id_dd, mdl_dd], outputs=load_event_outputs)
160
 
161
+ # 题目切换
162
  qa_radio.change(
163
  handle_qa_select,
164
  inputs=[qa_radio, qa_store],
165
+ outputs=[curr_qid, q_disp, a_disp, status_opt, err_type, gr.State(), gr.State(), comment]
166
  )
167
 
168
+ # 导航逻辑
169
+ def navigate(direction):
170
+ target = nav_state.get_nav_target(direction)
171
+ if target:
172
+ return [
173
+ gr.update(value=target['source']),
174
+ gr.update(value=target['chart_type']),
175
+ gr.update(value=target['chart_id']),
176
+ gr.update(value=target['model'])
177
+ ]
178
+ return [gr.update()] * 4
179
+
180
+ prev_btn.click(lambda: navigate(-1), outputs=[src_dd, typ_dd, id_dd, mdl_dd])
181
+ next_btn.click(lambda: navigate(1), outputs=[src_dd, typ_dd, id_dd, mdl_dd])
182
+
183
+ # 保存逻辑
184
+ def quick_save(qid, cid, src, status, cmt):
185
+ if not qid: return "无效操作"
186
  data_manager.save_review({
187
  "qa_id": qid, "chart_id": cid, "source": src,
 
188
  "status": status, "comment": cmt
189
  })
190
+ return "已保存"
 
 
191
 
192
  save_btn.click(
193
+ quick_save,
194
+ inputs=[curr_qid, id_dd, src_dd, status_opt, comment],
195
+ outputs=[gr.Text(visible=False)]
 
 
 
 
 
 
196
  )
197
 
198
  return demo
199
 
200
  if __name__ == "__main__":
201
  app = create_ui()
202
+ # 强制禁用所有 API 预览功能
203
+ app.launch(
204
+ server_name="0.0.0.0",
205
+ server_port=7860,
206
+ show_api=False,
207
+ max_threads=10
208
+ )