Laramie2 commited on
Commit
8c35fec
·
verified ·
1 Parent(s): 4e378e7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -116
app.py CHANGED
@@ -42,42 +42,17 @@ def get_user_dirs(session_id):
42
  # --- 🎨 Custom Purple Theme Definition ---
43
  # ==========================================
44
  colors.purple = colors.Color(
45
- name="purple",
46
- c50="#FAF5FF",
47
- c100="#F3E8FF",
48
- c200="#E9D5FF",
49
- c300="#DAB2FF",
50
- c400="#C084FC",
51
- c500="#A855F7",
52
- c600="#9333EA",
53
- c700="#7E22CE",
54
- c800="#6B21A8",
55
- c900="#581C87",
56
- c950="#3B0764",
57
  )
58
 
59
  class PurpleTheme(Soft):
60
- def __init__(
61
- self,
62
- *,
63
- primary_hue: colors.Color | str = colors.gray,
64
- secondary_hue: colors.Color | str = colors.purple,
65
- neutral_hue: colors.Color | str = colors.slate,
66
- text_size: sizes.Size | str = sizes.text_lg,
67
- font: fonts.Font | str | Iterable[fonts.Font | str] = (
68
- fonts.GoogleFont("Outfit"), "Arial", "sans-serif",
69
- ),
70
- font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
71
- fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace",
72
- ),
73
- ):
74
  super().__init__(
75
- primary_hue=primary_hue,
76
- secondary_hue=secondary_hue,
77
- neutral_hue=neutral_hue,
78
- text_size=text_size,
79
- font=font,
80
- font_mono=font_mono,
81
  )
82
  super().set(
83
  background_fill_primary="*primary_50",
@@ -85,30 +60,39 @@ class PurpleTheme(Soft):
85
  body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
86
  body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",
87
  button_primary_text_color="white",
88
- button_primary_text_color_hover="white",
89
  button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",
90
  button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",
91
- button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_700)",
92
- button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_600)",
93
  button_secondary_text_color="black",
94
- button_secondary_text_color_hover="white",
95
  button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)",
96
- button_secondary_background_fill_hover="linear-gradient(90deg, *primary_400, *primary_400)",
97
- button_secondary_background_fill_dark="linear-gradient(90deg, *primary_500, *primary_600)",
98
- button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_500, *primary_500)",
99
  slider_color="*secondary_500",
100
- slider_color_dark="*secondary_600",
101
- block_title_text_weight="600",
102
  block_border_width="3px",
103
  block_shadow="*shadow_drop_lg",
104
  button_primary_shadow="*shadow_drop_lg",
105
- button_large_padding="11px",
106
- color_accent_soft="*primary_100",
107
- block_label_background_fill="*primary_200",
108
  )
109
 
110
  purple_theme = PurpleTheme()
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  # ==========================================
113
  # --- ⚙️ Backend Logic & Functions ---
114
  # ==========================================
@@ -159,51 +143,44 @@ def get_debug_info(session_id):
159
  return html
160
 
161
  def save_api_settings(api_key, api_base_url, session_id):
162
- """不再写入全局 yaml,而是保存在 Gradio State 并在运行时通过环境变量注入"""
163
  if not api_key:
164
  return "❌ Key cannot be empty", get_debug_info(session_id), False, "", ""
165
-
166
  success_msg = "✅ Key saved securely in session memory"
167
  if api_base_url: success_msg += ", Base URL updated"
168
-
169
  return success_msg, get_debug_info(session_id), True, api_key, api_base_url
170
 
171
  def save_pdf(file, session_id):
172
- if file is None: return "❌ Please select a PDF first", get_debug_info(session_id), False
 
173
  try:
174
  papers_dir, _, _ = get_user_dirs(session_id)
175
-
176
- # 清理旧文件
177
  for f in os.listdir(papers_dir):
178
  file_to_del = os.path.join(papers_dir, f)
179
  if os.path.isfile(file_to_del): os.remove(file_to_del)
180
 
181
  file_path = os.path.join(papers_dir, os.path.basename(file.name))
182
  shutil.copy(file.name, file_path)
183
- return f"✅ Saved: {os.path.basename(file.name)}", get_debug_info(session_id), True
184
  except Exception as e:
185
- return f"❌ Error: {str(e)}", get_debug_info(session_id), False
186
 
187
  def clear_pdf(session_id):
188
  try:
189
  user_base = os.path.join(SESSIONS_BASE_DIR, session_id)
190
  if os.path.exists(user_base):
191
- shutil.rmtree(user_base) # 直接删除该用户的整个工作区
192
 
193
  disable_btn = gr.update(interactive=False)
194
- return f"🗑️ Workspace cleared", get_debug_info(session_id), False, disable_btn, disable_btn, disable_btn, disable_btn
195
  except Exception as e:
196
- return f"❌ Error deleting: {str(e)}", get_debug_info(session_id), False, gr.update(), gr.update(), gr.update(), gr.update()
197
 
198
  def build_user_env(api_key, api_base_url, papers_dir, output_dir):
199
- """构建用户专属的子进程环境变量"""
200
  env = os.environ.copy()
201
  env["MINERU_FORMULA_ENABLE"] = "false"
202
  env["MINERU_TABLE_ENABLE"] = "false"
203
  env["MINERU_DEVICE_MODE"] = "cpu"
204
  env["MINERU_VIRTUAL_VRAM_SIZE"] = "8"
205
-
206
- # 注入专属 API 配置和路径
207
  if api_key: env["GEMINI_API_KEY"] = api_key
208
  if api_base_url: env["GEMINI_API_BASE_URL"] = api_base_url
209
  env["USER_PAPERS_DIR"] = papers_dir
@@ -216,69 +193,73 @@ def run_mineru_parsing_and_dag_gen(session_id, api_key, api_base_url, progress=g
216
  papers_dir, output_dir, _ = get_user_dirs(session_id)
217
 
218
  if not os.path.exists(papers_dir) or not any(f.endswith('.pdf') for f in os.listdir(papers_dir)):
219
- yield "❌ No PDF file found", get_debug_info(session_id), "No execution logs.", no_change, no_change, no_change, no_change
220
  return
221
 
222
  full_log = ""
223
  try:
224
  env = build_user_env(api_key, api_base_url, papers_dir, output_dir)
225
-
226
  command_mineru = ["mineru", "-p", papers_dir, "-o", output_dir]
227
  full_log += f"--- Mineru Executing (Session: {session_id[:8]}) ---\n"
228
 
 
229
  progress(0.1, desc="启动 Mineru 解析...")
230
- yield "⏳ Executing Mineru parsing...", get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
231
 
232
  process_mineru = subprocess.Popen(
233
  command_mineru, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
234
  )
235
 
 
236
  progress(0.3, desc="Mineru 正在解析 PDF...")
237
  for line in iter(process_mineru.stdout.readline, ''):
238
  full_log += line
239
- yield "⏳ Executing Mineru parsing...", get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
240
  process_mineru.stdout.close()
241
  returncode_mineru = process_mineru.wait()
242
 
243
  if returncode_mineru != 0:
244
  progress(1.0, desc="Mineru 解析失败")
245
- yield f"❌ Mineru parsing failed (Exit Code: {returncode_mineru})", get_debug_info(session_id), full_log, disable_btn, disable_btn, disable_btn, disable_btn
246
  return
247
 
248
  command_dag = [sys.executable, "gen_dag.py"]
249
  full_log += "\n--- DAG Gen Executing ---\n"
 
 
250
  progress(0.6, desc="执行 DAG 生成...")
251
- yield "⏳ Executing DAG generation...", get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
252
 
253
  process_dag = subprocess.Popen(
254
  command_dag, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
255
  )
256
 
 
257
  progress(0.8, desc="构建图结构中...")
258
  for line in iter(process_dag.stdout.readline, ''):
259
  full_log += line
260
- yield "⏳ Executing DAG generation...", get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
261
  process_dag.stdout.close()
262
  returncode_dag = process_dag.wait()
263
 
264
  if returncode_dag == 0:
265
  progress(1.0, desc="解析与构建完成!")
266
  enable_btn = gr.update(interactive=True)
267
- yield "✅ PDF parsing & DAG generation fully completed", get_debug_info(session_id), full_log, enable_btn, enable_btn, enable_btn, enable_btn
268
  else:
269
  progress(1.0, desc="DAG 生成失败")
270
- yield f"❌ DAG generation failed", get_debug_info(session_id), full_log, disable_btn, disable_btn, disable_btn, disable_btn
271
 
272
  except Exception as e:
273
  progress(1.0, desc="发生异常")
274
  error_log = full_log + f"\n[Global Exception]:\n{str(e)}"
275
- yield "❌ Execution Exception", get_debug_info(session_id), error_log, disable_btn, disable_btn, disable_btn, disable_btn
276
 
277
  def run_final_generation(task_type, session_id, api_key, api_base_url, progress=gr.Progress()):
278
  papers_dir, output_dir, zip_path = get_user_dirs(session_id)
279
 
280
  if not os.path.exists(output_dir):
281
- yield "❌ Please run parsing first", get_debug_info(session_id), "No output folder found.", gr.update(visible=False)
282
  return
283
 
284
  scripts_to_run = []
@@ -289,7 +270,7 @@ def run_final_generation(task_type, session_id, api_key, api_base_url, progress=
289
 
290
  full_log = f"🚀 Starting {len(scripts_to_run)} tasks for session {session_id[:8]}...\n"
291
  progress(0.1, desc=f"启动 {task_type.upper()} 生成任务...")
292
- yield f"⏳ Starting {task_type.upper()} generation...", get_debug_info(session_id), full_log, gr.update(visible=False)
293
 
294
  q = queue.Queue()
295
  processes = []
@@ -317,7 +298,7 @@ def run_final_generation(task_type, session_id, api_key, api_base_url, progress=
317
  try:
318
  script_name, line = q.get(timeout=0.1)
319
  full_log += f"[{script_name}] {line}"
320
- yield f"⏳ Generating {task_type.upper()}...", get_debug_info(session_id), full_log, gr.update(visible=False)
321
  except queue.Empty:
322
  active_processes = sum(1 for _, p in processes if p.poll() is None)
323
 
@@ -325,24 +306,24 @@ def run_final_generation(task_type, session_id, api_key, api_base_url, progress=
325
 
326
  if not success:
327
  progress(1.0, desc="生成失败")
328
- yield f"❌ Tasks failed, check logs", get_debug_info(session_id), full_log, gr.update(visible=False)
329
  return
330
 
331
  full_log += "\n📦 Zipping output directory...\n"
332
  progress(0.9, desc="打包压缩结果...")
333
- yield f"⏳ Zipping outputs...", get_debug_info(session_id), full_log, gr.update(visible=False)
334
 
335
  zip_base_name = zip_path.replace(".zip", "")
336
  shutil.make_archive(zip_base_name, 'zip', output_dir)
337
 
338
  full_log += "✅ All tasks completed successfully.\n"
339
  progress(1.0, desc="全部完成!")
340
- yield f"✅ {task_type.upper()} successfully generated", get_debug_info(session_id), full_log, gr.update(value=zip_path, visible=True)
341
 
342
  except Exception as e:
343
  progress(1.0, desc="发生全局异常")
344
  error_log = full_log + f"\n[Global Exception]:\n{str(e)}"
345
- yield "❌ Global exception", get_debug_info(session_id), error_log, gr.update(visible=False)
346
 
347
  # ==========================================
348
  # --- 🚀 UI Configuration & Advanced CSS ---
@@ -402,8 +383,42 @@ body, .gradio-container {
402
  border-color: rgba(168, 85, 247, 0.3) !important;
403
  }
404
 
405
- .gradio-group > div,
406
- .gradio-group > .form { border-radius: 12px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
  #pdf-upload-box {
409
  border: 2px dashed rgba(192, 132, 252, 0.6) !important;
@@ -493,7 +508,6 @@ body, .gradio-container {
493
  }
494
  .action-btn:active { transform: translateY(2px) scale(0.98) !important; box-shadow: 0 2px 10px rgba(147, 51, 234, 0.2) !important; }
495
 
496
- /* ======== CSS FOR DISABLED BUTTONS ======== */
497
  .primary-action-btn:disabled, .action-btn:disabled {
498
  background: #e5e7eb !important;
499
  color: #9ca3af !important;
@@ -515,13 +529,11 @@ body, .gradio-container {
515
  ::-webkit-scrollbar-track { background: rgba(168, 85, 247, 0.05); border-radius: 4px; }
516
  ::-webkit-scrollbar-thumb { background: linear-gradient(135deg, #A855F7, #C084FC); border-radius: 4px; }
517
  ::-webkit-scrollbar-thumb:hover { background: linear-gradient(135deg, #9333EA, #A855F7); }
518
-
519
  details > summary { transition: color 0.2s ease; }
520
  details > summary:hover { color: #E9D5FF !important; }
521
  """
522
 
523
  with gr.Blocks(theme=purple_theme, css=custom_css) as demo:
524
- # 核心:为每个连接的用户生成唯一的 Session ID 和存放凭证的状态
525
  session_id_state = gr.State("")
526
  user_api_key_state = gr.State("")
527
  user_api_base_state = gr.State("")
@@ -534,7 +546,6 @@ with gr.Blocks(theme=purple_theme, css=custom_css) as demo:
534
  gr.Markdown("One-click parsing of academic PDFs, DAG structuring, and multi-modal asset generation.", elem_id="subtitle")
535
 
536
  with gr.Row():
537
- # ================= LEFT COLUMN: SETTINGS & ACTIONS =================
538
  with gr.Column(scale=1):
539
  # 1. API Configuration
540
  with gr.Group(elem_classes="gradio-group"):
@@ -543,69 +554,54 @@ with gr.Blocks(theme=purple_theme, css=custom_css) as demo:
543
  key_input = gr.Textbox(label="API Key", type="password", placeholder="sk-...", scale=1)
544
  api_base_url_input = gr.Textbox(label="Base URL (Optional)", placeholder="https://api.example.com", scale=1)
545
  key_btn = gr.Button("💾 Save API Configuration")
546
- api_status = gr.Textbox(
547
- show_label=False, placeholder="Waiting for API configuration...", lines=1, interactive=False, elem_classes="status-text"
548
- )
549
 
550
  # 2. Document Parsing
551
  with gr.Group(elem_classes="gradio-group"):
552
  gr.Markdown("### 📄 2. Document Parsing")
553
- pdf_input = gr.File(
554
- label="Upload Document",
555
- file_types=[".pdf"],
556
- elem_id="pdf-upload-box"
557
- )
558
  parse_btn = gr.Button("🚀 Start Mineru & DAG Extraction", elem_classes="primary-action-btn", interactive=False)
559
- parse_status = gr.Textbox(
560
- show_label=False, placeholder="Waiting for document upload...", lines=1, interactive=False, elem_classes="status-text"
561
- )
562
 
563
  # 3. Asset Generation
564
  with gr.Group(elem_classes="gradio-group"):
565
  gr.Markdown("### 🎯 3. Asset Generation")
566
- gr.Markdown("Generate final formats based on DAG structure:")
567
  with gr.Row(elem_classes="action-row"):
568
  gen_ppt_btn = gr.Button("📊 Gen PPT", elem_classes="action-btn", interactive=False)
569
  gen_poster_btn = gr.Button("🖼️ Gen Poster", elem_classes="action-btn", interactive=False)
570
  gen_pr_btn = gr.Button("📰 Gen PR", elem_classes="action-btn", interactive=False)
571
  gen_all_btn = gr.Button("✨ Generate All Assets (ALL)", elem_classes="primary-action-btn", interactive=False)
 
 
 
572
 
573
- # ================= RIGHT COLUMN: OUTPUTS & LOGS =================
574
  with gr.Column(scale=1):
575
  # 4. Results & Downloads
576
  with gr.Group(elem_classes="gradio-group"):
577
  gr.Markdown("### 📦 Generation Results & Download")
578
- gen_status = gr.Textbox(
579
- show_label=False, placeholder="No generation task currently...", lines=2, interactive=False, elem_classes="status-text"
580
- )
581
  download_file = gr.File(label="📥 Get Final Zip Archive", interactive=False, visible=False)
582
 
583
- # 5. Debugging & Terminal
584
  with gr.Group(elem_classes="gradio-group"):
585
  gr.Markdown("### 🛠️ Developer Monitoring (Debug Only)")
586
  with gr.Tabs():
587
  with gr.Tab("📜 Terminal Stream"):
588
- cmd_logs = gr.Textbox(
589
- label="Stdout / Stderr", placeholder="Waiting for task to start...", lines=14, interactive=False, elem_classes="log-box"
590
- )
591
  with gr.Tab("🔍 System Snapshot"):
592
  refresh_btn = gr.Button("🔄 Refresh Directory Tree")
593
  debug_view = gr.HTML()
594
 
595
- # ================= LOGIC BINDINGS =================
596
 
597
- # [新增] 专门的页面初始化函数:当用户打开网页时,生成 UUID 并返回给 State
598
  def init_app_for_user():
599
  new_session_id = str(uuid.uuid4())
600
- # 获取 debug info 的同时,顺便把属于该用户的空文件夹也创建好
601
  debug_html = get_debug_info(new_session_id)
602
  return new_session_id, debug_html
603
 
604
- # 在页面加载的瞬间,生成专属 ID 存入状态,并渲染空的监控视图
605
  demo.load(fn=init_app_for_user, inputs=None, outputs=[session_id_state, debug_view])
606
 
607
- # ================= EVENT BINDINGS =================
608
-
609
  key_btn.click(
610
  fn=save_api_settings,
611
  inputs=[key_input, api_base_url_input, session_id_state],
@@ -615,13 +611,13 @@ with gr.Blocks(theme=purple_theme, css=custom_css) as demo:
615
  pdf_input.upload(
616
  fn=save_pdf,
617
  inputs=[pdf_input, session_id_state],
618
- outputs=[parse_status, debug_view, pdf_ready_state]
619
  )
620
 
621
  pdf_input.clear(
622
  fn=clear_pdf,
623
  inputs=[session_id_state],
624
- outputs=[parse_status, debug_view, pdf_ready_state, gen_ppt_btn, gen_poster_btn, gen_pr_btn, gen_all_btn]
625
  )
626
 
627
  def check_parse_btn_ready(api_ready, pdf_ready):
@@ -633,7 +629,7 @@ with gr.Blocks(theme=purple_theme, css=custom_css) as demo:
633
  parse_btn.click(
634
  fn=run_mineru_parsing_and_dag_gen,
635
  inputs=[session_id_state, user_api_key_state, user_api_base_state],
636
- outputs=[parse_status, debug_view, cmd_logs, gen_ppt_btn, gen_poster_btn, gen_pr_btn, gen_all_btn]
637
  )
638
 
639
  def trigger_gen_ppt(sid, ak, ab, progress=gr.Progress()): yield from run_final_generation("ppt", sid, ak, ab, progress)
@@ -641,13 +637,12 @@ with gr.Blocks(theme=purple_theme, css=custom_css) as demo:
641
  def trigger_gen_pr(sid, ak, ab, progress=gr.Progress()): yield from run_final_generation("pr", sid, ak, ab, progress)
642
  def trigger_gen_all(sid, ak, ab, progress=gr.Progress()): yield from run_final_generation("all", sid, ak, ab, progress)
643
 
644
- # 绑定需要传递所有专属状态
645
- gen_ppt_btn.click(fn=trigger_gen_ppt, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_status, debug_view, cmd_logs, download_file])
646
- gen_poster_btn.click(fn=trigger_gen_poster, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_status, debug_view, cmd_logs, download_file])
647
- gen_pr_btn.click(fn=trigger_gen_pr, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_status, debug_view, cmd_logs, download_file])
648
- gen_all_btn.click(fn=trigger_gen_all, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_status, debug_view, cmd_logs, download_file])
649
 
650
  refresh_btn.click(fn=get_debug_info, inputs=[session_id_state], outputs=debug_view)
651
 
652
  if __name__ == "__main__":
653
- demo.launch()
 
42
  # --- 🎨 Custom Purple Theme Definition ---
43
  # ==========================================
44
  colors.purple = colors.Color(
45
+ name="purple", c50="#FAF5FF", c100="#F3E8FF", c200="#E9D5FF",
46
+ c300="#DAB2FF", c400="#C084FC", c500="#A855F7", c600="#9333EA",
47
+ c700="#7E22CE", c800="#6B21A8", c900="#581C87", c950="#3B0764",
 
 
 
 
 
 
 
 
 
48
  )
49
 
50
  class PurpleTheme(Soft):
51
+ def __init__(self, **kwargs):
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  super().__init__(
53
+ primary_hue=colors.gray, secondary_hue=colors.purple, neutral_hue=colors.slate,
54
+ font=(fonts.GoogleFont("Outfit"), "Arial", "sans-serif"),
55
+ font_mono=(fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace"),
 
 
 
56
  )
57
  super().set(
58
  background_fill_primary="*primary_50",
 
60
  body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
61
  body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",
62
  button_primary_text_color="white",
 
63
  button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",
64
  button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",
 
 
65
  button_secondary_text_color="black",
 
66
  button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)",
 
 
 
67
  slider_color="*secondary_500",
 
 
68
  block_border_width="3px",
69
  block_shadow="*shadow_drop_lg",
70
  button_primary_shadow="*shadow_drop_lg",
 
 
 
71
  )
72
 
73
  purple_theme = PurpleTheme()
74
 
75
+ # ==========================================
76
+ # --- 🚀 HTML Progress Bar Components ---
77
+ # ==========================================
78
+ def empty_progress_html(text="Waiting for action..."):
79
+ return f"""
80
+ <div class="custom-progress-container" style="background-color: transparent; border: 2px dashed rgba(168, 85, 247, 0.4);">
81
+ <div class="custom-progress-text" style="color: #A855F7;">{text}</div>
82
+ </div>
83
+ """
84
+
85
+ def create_progress_html(percent, text, status="active"):
86
+ """
87
+ status: "active" (紫色滚动条纹), "success" (绿色), "error" (红色)
88
+ """
89
+ return f"""
90
+ <div class="custom-progress-container">
91
+ <div class="custom-progress-bar {status}" style="width: {percent}%;"></div>
92
+ <div class="custom-progress-text">{text} ({percent}%)</div>
93
+ </div>
94
+ """
95
+
96
  # ==========================================
97
  # --- ⚙️ Backend Logic & Functions ---
98
  # ==========================================
 
143
  return html
144
 
145
  def save_api_settings(api_key, api_base_url, session_id):
 
146
  if not api_key:
147
  return "❌ Key cannot be empty", get_debug_info(session_id), False, "", ""
 
148
  success_msg = "✅ Key saved securely in session memory"
149
  if api_base_url: success_msg += ", Base URL updated"
 
150
  return success_msg, get_debug_info(session_id), True, api_key, api_base_url
151
 
152
  def save_pdf(file, session_id):
153
+ if file is None:
154
+ return empty_progress_html("❌ Please select a PDF first"), get_debug_info(session_id), False
155
  try:
156
  papers_dir, _, _ = get_user_dirs(session_id)
 
 
157
  for f in os.listdir(papers_dir):
158
  file_to_del = os.path.join(papers_dir, f)
159
  if os.path.isfile(file_to_del): os.remove(file_to_del)
160
 
161
  file_path = os.path.join(papers_dir, os.path.basename(file.name))
162
  shutil.copy(file.name, file_path)
163
+ return create_progress_html(100, f"✅ PDF Uploaded: {os.path.basename(file.name)}", "success"), get_debug_info(session_id), True
164
  except Exception as e:
165
+ return create_progress_html(0, f"❌ Error: {str(e)}", "error"), get_debug_info(session_id), False
166
 
167
  def clear_pdf(session_id):
168
  try:
169
  user_base = os.path.join(SESSIONS_BASE_DIR, session_id)
170
  if os.path.exists(user_base):
171
+ shutil.rmtree(user_base)
172
 
173
  disable_btn = gr.update(interactive=False)
174
+ return empty_progress_html("🗑️ Workspace cleared"), empty_progress_html("No generation task currently..."), get_debug_info(session_id), False, disable_btn, disable_btn, disable_btn, disable_btn
175
  except Exception as e:
176
+ return create_progress_html(0, f"❌ Clear Error: {str(e)}", "error"), gr.update(), get_debug_info(session_id), False, gr.update(), gr.update(), gr.update(), gr.update()
177
 
178
  def build_user_env(api_key, api_base_url, papers_dir, output_dir):
 
179
  env = os.environ.copy()
180
  env["MINERU_FORMULA_ENABLE"] = "false"
181
  env["MINERU_TABLE_ENABLE"] = "false"
182
  env["MINERU_DEVICE_MODE"] = "cpu"
183
  env["MINERU_VIRTUAL_VRAM_SIZE"] = "8"
 
 
184
  if api_key: env["GEMINI_API_KEY"] = api_key
185
  if api_base_url: env["GEMINI_API_BASE_URL"] = api_base_url
186
  env["USER_PAPERS_DIR"] = papers_dir
 
193
  papers_dir, output_dir, _ = get_user_dirs(session_id)
194
 
195
  if not os.path.exists(papers_dir) or not any(f.endswith('.pdf') for f in os.listdir(papers_dir)):
196
+ yield create_progress_html(0, "❌ No PDF file found", "error"), get_debug_info(session_id), "No execution logs.", no_change, no_change, no_change, no_change
197
  return
198
 
199
  full_log = ""
200
  try:
201
  env = build_user_env(api_key, api_base_url, papers_dir, output_dir)
 
202
  command_mineru = ["mineru", "-p", papers_dir, "-o", output_dir]
203
  full_log += f"--- Mineru Executing (Session: {session_id[:8]}) ---\n"
204
 
205
+ # 10%
206
  progress(0.1, desc="启动 Mineru 解析...")
207
+ yield create_progress_html(10, "⏳ Starting Mineru parsing...", "active"), get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
208
 
209
  process_mineru = subprocess.Popen(
210
  command_mineru, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
211
  )
212
 
213
+ # 30%
214
  progress(0.3, desc="Mineru 正在解析 PDF...")
215
  for line in iter(process_mineru.stdout.readline, ''):
216
  full_log += line
217
+ yield create_progress_html(30, "⏳ Mineru parsing PDF...", "active"), get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
218
  process_mineru.stdout.close()
219
  returncode_mineru = process_mineru.wait()
220
 
221
  if returncode_mineru != 0:
222
  progress(1.0, desc="Mineru 解析失败")
223
+ yield create_progress_html(30, f"❌ Mineru failed (Code {returncode_mineru})", "error"), get_debug_info(session_id), full_log, disable_btn, disable_btn, disable_btn, disable_btn
224
  return
225
 
226
  command_dag = [sys.executable, "gen_dag.py"]
227
  full_log += "\n--- DAG Gen Executing ---\n"
228
+
229
+ # 60%
230
  progress(0.6, desc="执行 DAG 生成...")
231
+ yield create_progress_html(60, "⏳ Executing DAG generation...", "active"), get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
232
 
233
  process_dag = subprocess.Popen(
234
  command_dag, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
235
  )
236
 
237
+ # 80%
238
  progress(0.8, desc="构建图结构中...")
239
  for line in iter(process_dag.stdout.readline, ''):
240
  full_log += line
241
+ yield create_progress_html(80, "⏳ Building Graph...", "active"), get_debug_info(session_id), full_log, no_change, no_change, no_change, no_change
242
  process_dag.stdout.close()
243
  returncode_dag = process_dag.wait()
244
 
245
  if returncode_dag == 0:
246
  progress(1.0, desc="解析与构建完成!")
247
  enable_btn = gr.update(interactive=True)
248
+ yield create_progress_html(100, "✅ Fully completed", "success"), get_debug_info(session_id), full_log, enable_btn, enable_btn, enable_btn, enable_btn
249
  else:
250
  progress(1.0, desc="DAG 生成失败")
251
+ yield create_progress_html(80, "❌ DAG generation failed", "error"), get_debug_info(session_id), full_log, disable_btn, disable_btn, disable_btn, disable_btn
252
 
253
  except Exception as e:
254
  progress(1.0, desc="发生异常")
255
  error_log = full_log + f"\n[Global Exception]:\n{str(e)}"
256
+ yield create_progress_html(0, "❌ Execution Exception", "error"), get_debug_info(session_id), error_log, disable_btn, disable_btn, disable_btn, disable_btn
257
 
258
  def run_final_generation(task_type, session_id, api_key, api_base_url, progress=gr.Progress()):
259
  papers_dir, output_dir, zip_path = get_user_dirs(session_id)
260
 
261
  if not os.path.exists(output_dir):
262
+ yield create_progress_html(0, "❌ Please run parsing first", "error"), get_debug_info(session_id), "No output folder found.", gr.update(visible=False)
263
  return
264
 
265
  scripts_to_run = []
 
270
 
271
  full_log = f"🚀 Starting {len(scripts_to_run)} tasks for session {session_id[:8]}...\n"
272
  progress(0.1, desc=f"启动 {task_type.upper()} 生成任务...")
273
+ yield create_progress_html(10, f"⏳ Starting {task_type.upper()}...", "active"), get_debug_info(session_id), full_log, gr.update(visible=False)
274
 
275
  q = queue.Queue()
276
  processes = []
 
298
  try:
299
  script_name, line = q.get(timeout=0.1)
300
  full_log += f"[{script_name}] {line}"
301
+ yield create_progress_html(50, f"⏳ Generating {task_type.upper()}...", "active"), get_debug_info(session_id), full_log, gr.update(visible=False)
302
  except queue.Empty:
303
  active_processes = sum(1 for _, p in processes if p.poll() is None)
304
 
 
306
 
307
  if not success:
308
  progress(1.0, desc="生成失败")
309
+ yield create_progress_html(50, "❌ Tasks failed", "error"), get_debug_info(session_id), full_log, gr.update(visible=False)
310
  return
311
 
312
  full_log += "\n📦 Zipping output directory...\n"
313
  progress(0.9, desc="打包压缩结果...")
314
+ yield create_progress_html(90, "⏳ Zipping outputs...", "active"), get_debug_info(session_id), full_log, gr.update(visible=False)
315
 
316
  zip_base_name = zip_path.replace(".zip", "")
317
  shutil.make_archive(zip_base_name, 'zip', output_dir)
318
 
319
  full_log += "✅ All tasks completed successfully.\n"
320
  progress(1.0, desc="全部完成!")
321
+ yield create_progress_html(100, f"✅ {task_type.upper()} Generated", "success"), get_debug_info(session_id), full_log, gr.update(value=zip_path, visible=True)
322
 
323
  except Exception as e:
324
  progress(1.0, desc="发生全局异常")
325
  error_log = full_log + f"\n[Global Exception]:\n{str(e)}"
326
+ yield create_progress_html(0, "❌ Global exception", "error"), get_debug_info(session_id), error_log, gr.update(visible=False)
327
 
328
  # ==========================================
329
  # --- 🚀 UI Configuration & Advanced CSS ---
 
383
  border-color: rgba(168, 85, 247, 0.3) !important;
384
  }
385
 
386
+ /* ================= 进度条自定义 CSS ================= */
387
+ @keyframes progress-bar-stripes {
388
+ from { background-position: 1rem 0; }
389
+ to { background-position: 0 0; }
390
+ }
391
+ .custom-progress-container {
392
+ width: 100%;
393
+ background-color: rgba(233, 213, 255, 0.3);
394
+ border-radius: 12px;
395
+ overflow: hidden;
396
+ position: relative;
397
+ height: 40px;
398
+ border: 1px solid rgba(168, 85, 247, 0.3);
399
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.05);
400
+ }
401
+ .custom-progress-bar {
402
+ height: 100%;
403
+ border-radius: 12px;
404
+ transition: width 0.4s ease;
405
+ }
406
+ .custom-progress-bar.active {
407
+ background-color: #A855F7;
408
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
409
+ background-size: 1rem 1rem;
410
+ animation: progress-bar-stripes 1s linear infinite;
411
+ box-shadow: 0 0 10px rgba(168, 85, 247, 0.5);
412
+ }
413
+ .custom-progress-bar.success { background-image: none; background-color: #10B981; box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);}
414
+ .custom-progress-bar.error { background-image: none; background-color: #EF4444; box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);}
415
+ .custom-progress-text {
416
+ position: absolute; top: 0; left: 0; width: 100%; height: 100%;
417
+ display: flex; align-items: center; justify-content: center;
418
+ font-weight: 600; color: #581C87; font-size: 14px;
419
+ text-shadow: 0 0 4px rgba(255,255,255,0.8);
420
+ }
421
+ .dark .custom-progress-text { color: #E9D5FF; text-shadow: 0 0 4px rgba(0,0,0,0.8); }
422
 
423
  #pdf-upload-box {
424
  border: 2px dashed rgba(192, 132, 252, 0.6) !important;
 
508
  }
509
  .action-btn:active { transform: translateY(2px) scale(0.98) !important; box-shadow: 0 2px 10px rgba(147, 51, 234, 0.2) !important; }
510
 
 
511
  .primary-action-btn:disabled, .action-btn:disabled {
512
  background: #e5e7eb !important;
513
  color: #9ca3af !important;
 
529
  ::-webkit-scrollbar-track { background: rgba(168, 85, 247, 0.05); border-radius: 4px; }
530
  ::-webkit-scrollbar-thumb { background: linear-gradient(135deg, #A855F7, #C084FC); border-radius: 4px; }
531
  ::-webkit-scrollbar-thumb:hover { background: linear-gradient(135deg, #9333EA, #A855F7); }
 
532
  details > summary { transition: color 0.2s ease; }
533
  details > summary:hover { color: #E9D5FF !important; }
534
  """
535
 
536
  with gr.Blocks(theme=purple_theme, css=custom_css) as demo:
 
537
  session_id_state = gr.State("")
538
  user_api_key_state = gr.State("")
539
  user_api_base_state = gr.State("")
 
546
  gr.Markdown("One-click parsing of academic PDFs, DAG structuring, and multi-modal asset generation.", elem_id="subtitle")
547
 
548
  with gr.Row():
 
549
  with gr.Column(scale=1):
550
  # 1. API Configuration
551
  with gr.Group(elem_classes="gradio-group"):
 
554
  key_input = gr.Textbox(label="API Key", type="password", placeholder="sk-...", scale=1)
555
  api_base_url_input = gr.Textbox(label="Base URL (Optional)", placeholder="https://api.example.com", scale=1)
556
  key_btn = gr.Button("💾 Save API Configuration")
557
+ api_status = gr.Textbox(show_label=False, interactive=False, elem_classes="status-text")
 
 
558
 
559
  # 2. Document Parsing
560
  with gr.Group(elem_classes="gradio-group"):
561
  gr.Markdown("### 📄 2. Document Parsing")
562
+ pdf_input = gr.File(label="Upload Document", file_types=[".pdf"], elem_id="pdf-upload-box")
 
 
 
 
563
  parse_btn = gr.Button("🚀 Start Mineru & DAG Extraction", elem_classes="primary-action-btn", interactive=False)
564
+
565
+ # 进度条替换原本的文本框
566
+ parse_progress = gr.HTML(value=empty_progress_html("Waiting for document upload..."))
567
 
568
  # 3. Asset Generation
569
  with gr.Group(elem_classes="gradio-group"):
570
  gr.Markdown("### 🎯 3. Asset Generation")
 
571
  with gr.Row(elem_classes="action-row"):
572
  gen_ppt_btn = gr.Button("📊 Gen PPT", elem_classes="action-btn", interactive=False)
573
  gen_poster_btn = gr.Button("🖼️ Gen Poster", elem_classes="action-btn", interactive=False)
574
  gen_pr_btn = gr.Button("📰 Gen PR", elem_classes="action-btn", interactive=False)
575
  gen_all_btn = gr.Button("✨ Generate All Assets (ALL)", elem_classes="primary-action-btn", interactive=False)
576
+
577
+ # 新增进度的展示条
578
+ gen_progress = gr.HTML(value=empty_progress_html("No generation task currently..."))
579
 
 
580
  with gr.Column(scale=1):
581
  # 4. Results & Downloads
582
  with gr.Group(elem_classes="gradio-group"):
583
  gr.Markdown("### 📦 Generation Results & Download")
 
 
 
584
  download_file = gr.File(label="📥 Get Final Zip Archive", interactive=False, visible=False)
585
 
586
+ # 5. Debugging
587
  with gr.Group(elem_classes="gradio-group"):
588
  gr.Markdown("### 🛠️ Developer Monitoring (Debug Only)")
589
  with gr.Tabs():
590
  with gr.Tab("📜 Terminal Stream"):
591
+ cmd_logs = gr.Textbox(show_label=False, lines=14, interactive=False, elem_classes="log-box")
 
 
592
  with gr.Tab("🔍 System Snapshot"):
593
  refresh_btn = gr.Button("🔄 Refresh Directory Tree")
594
  debug_view = gr.HTML()
595
 
596
+ # ================= LOGIC BINDINGS =================
597
 
 
598
  def init_app_for_user():
599
  new_session_id = str(uuid.uuid4())
 
600
  debug_html = get_debug_info(new_session_id)
601
  return new_session_id, debug_html
602
 
 
603
  demo.load(fn=init_app_for_user, inputs=None, outputs=[session_id_state, debug_view])
604
 
 
 
605
  key_btn.click(
606
  fn=save_api_settings,
607
  inputs=[key_input, api_base_url_input, session_id_state],
 
611
  pdf_input.upload(
612
  fn=save_pdf,
613
  inputs=[pdf_input, session_id_state],
614
+ outputs=[parse_progress, debug_view, pdf_ready_state]
615
  )
616
 
617
  pdf_input.clear(
618
  fn=clear_pdf,
619
  inputs=[session_id_state],
620
+ outputs=[parse_progress, gen_progress, debug_view, pdf_ready_state, gen_ppt_btn, gen_poster_btn, gen_pr_btn, gen_all_btn]
621
  )
622
 
623
  def check_parse_btn_ready(api_ready, pdf_ready):
 
629
  parse_btn.click(
630
  fn=run_mineru_parsing_and_dag_gen,
631
  inputs=[session_id_state, user_api_key_state, user_api_base_state],
632
+ outputs=[parse_progress, debug_view, cmd_logs, gen_ppt_btn, gen_poster_btn, gen_pr_btn, gen_all_btn]
633
  )
634
 
635
  def trigger_gen_ppt(sid, ak, ab, progress=gr.Progress()): yield from run_final_generation("ppt", sid, ak, ab, progress)
 
637
  def trigger_gen_pr(sid, ak, ab, progress=gr.Progress()): yield from run_final_generation("pr", sid, ak, ab, progress)
638
  def trigger_gen_all(sid, ak, ab, progress=gr.Progress()): yield from run_final_generation("all", sid, ak, ab, progress)
639
 
640
+ gen_ppt_btn.click(fn=trigger_gen_ppt, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_progress, debug_view, cmd_logs, download_file])
641
+ gen_poster_btn.click(fn=trigger_gen_poster, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_progress, debug_view, cmd_logs, download_file])
642
+ gen_pr_btn.click(fn=trigger_gen_pr, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_progress, debug_view, cmd_logs, download_file])
643
+ gen_all_btn.click(fn=trigger_gen_all, inputs=[session_id_state, user_api_key_state, user_api_base_state], outputs=[gen_progress, debug_view, cmd_logs, download_file])
 
644
 
645
  refresh_btn.click(fn=get_debug_info, inputs=[session_id_state], outputs=debug_view)
646
 
647
  if __name__ == "__main__":
648
+ demo.queue(default_concurrency_limit=5).launch()