demo_gradio.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. """
  2. Layout Inference Web Application with Gradio
  3. A Gradio-based layout inference tool that supports image uploads and multiple backend inference engines.
  4. It adopts a reference-style interface design while preserving the original inference logic.
  5. """
  6. import gradio as gr
  7. import json
  8. import os
  9. import io
  10. import tempfile
  11. import base64
  12. import zipfile
  13. import uuid
  14. import re
  15. from pathlib import Path
  16. from PIL import Image
  17. import requests
  18. import shutil # Import shutil for cleanup
  19. # Local tool imports
  20. from dots_ocr.utils import dict_promptmode_to_prompt
  21. from dots_ocr.utils.consts import MIN_PIXELS, MAX_PIXELS
  22. from dots_ocr.utils.demo_utils.display import read_image
  23. from dots_ocr.utils.doc_utils import load_images_from_pdf
  24. # Add DotsOCRParser import
  25. from dots_ocr.parser import DotsOCRParser
  26. # ==================== Configuration ====================
  27. DEFAULT_CONFIG = {
  28. 'ip': "127.0.0.1",
  29. 'port_vllm': 8000,
  30. 'min_pixels': MIN_PIXELS,
  31. 'max_pixels': MAX_PIXELS,
  32. 'test_images_dir': "./assets/showcase_origin",
  33. }
  34. # ==================== Global Variables ====================
  35. # Store current configuration
  36. current_config = DEFAULT_CONFIG.copy()
  37. # Create DotsOCRParser instance
  38. dots_parser = DotsOCRParser(
  39. ip=DEFAULT_CONFIG['ip'],
  40. port=DEFAULT_CONFIG['port_vllm'],
  41. dpi=200,
  42. min_pixels=DEFAULT_CONFIG['min_pixels'],
  43. max_pixels=DEFAULT_CONFIG['max_pixels']
  44. )
  45. def get_initial_session_state():
  46. return {
  47. 'processing_results': {
  48. 'original_image': None,
  49. 'processed_image': None,
  50. 'layout_result': None,
  51. 'markdown_content': None,
  52. 'cells_data': None,
  53. 'temp_dir': None,
  54. 'session_id': None,
  55. 'result_paths': None,
  56. 'pdf_results': None
  57. },
  58. 'pdf_cache': {
  59. "images": [],
  60. "current_page": 0,
  61. "total_pages": 0,
  62. "file_type": None,
  63. "is_parsed": False,
  64. "results": []
  65. }
  66. }
  67. def read_image_v2(img):
  68. """Reads an image, supports URLs and local paths"""
  69. if isinstance(img, str) and img.startswith(("http://", "https://")):
  70. with requests.get(img, stream=True) as response:
  71. response.raise_for_status()
  72. img = Image.open(io.BytesIO(response.content))
  73. elif isinstance(img, str):
  74. img, _, _ = read_image(img, use_native=True)
  75. elif isinstance(img, Image.Image):
  76. pass
  77. else:
  78. raise ValueError(f"Invalid image type: {type(img)}")
  79. return img
  80. def load_file_for_preview(file_path, session_state):
  81. """Loads a file for preview, supports PDF and image files"""
  82. pdf_cache = session_state['pdf_cache']
  83. if not file_path or not os.path.exists(file_path):
  84. return None, "<div id='page_info_box'>0 / 0</div>", session_state
  85. file_ext = os.path.splitext(file_path)[1].lower()
  86. try:
  87. if file_ext == '.pdf':
  88. pages = load_images_from_pdf(file_path)
  89. pdf_cache["file_type"] = "pdf"
  90. elif file_ext in ['.jpg', '.jpeg', '.png']:
  91. image = Image.open(file_path)
  92. pages = [image]
  93. pdf_cache["file_type"] = "image"
  94. else:
  95. return None, "<div id='page_info_box'>Unsupported file format</div>", session_state
  96. except Exception as e:
  97. return None, f"<div id='page_info_box'>PDF loading failed: {str(e)}</div>", session_state
  98. pdf_cache["images"] = pages
  99. pdf_cache["current_page"] = 0
  100. pdf_cache["total_pages"] = len(pages)
  101. pdf_cache["is_parsed"] = False
  102. pdf_cache["results"] = []
  103. return pages[0], f"<div id='page_info_box'>1 / {len(pages)}</div>", session_state
  104. def turn_page(direction, session_state):
  105. """Page turning function"""
  106. pdf_cache = session_state['pdf_cache']
  107. if not pdf_cache["images"]:
  108. return None, "<div id='page_info_box'>0 / 0</div>", "", session_state
  109. if direction == "prev":
  110. pdf_cache["current_page"] = max(0, pdf_cache["current_page"] - 1)
  111. elif direction == "next":
  112. pdf_cache["current_page"] = min(pdf_cache["total_pages"] - 1, pdf_cache["current_page"] + 1)
  113. index = pdf_cache["current_page"]
  114. current_image = pdf_cache["images"][index] # Use the original image by default
  115. page_info = f"<div id='page_info_box'>{index + 1} / {pdf_cache['total_pages']}</div>"
  116. current_json = ""
  117. if pdf_cache["is_parsed"] and index < len(pdf_cache["results"]):
  118. result = pdf_cache["results"][index]
  119. if 'cells_data' in result and result['cells_data']:
  120. try:
  121. current_json = json.dumps(result['cells_data'], ensure_ascii=False, indent=2)
  122. except:
  123. current_json = str(result.get('cells_data', ''))
  124. if 'layout_image' in result and result['layout_image']:
  125. current_image = result['layout_image']
  126. return current_image, page_info, current_json, session_state
  127. def get_test_images():
  128. """Gets the list of test images"""
  129. test_images = []
  130. test_dir = current_config['test_images_dir']
  131. if os.path.exists(test_dir):
  132. test_images = [os.path.join(test_dir, name) for name in os.listdir(test_dir)
  133. if name.lower().endswith(('.png', '.jpg', '.jpeg', '.pdf'))]
  134. return test_images
  135. def create_temp_session_dir():
  136. """Creates a unique temporary directory for each processing request"""
  137. session_id = uuid.uuid4().hex[:8]
  138. temp_dir = os.path.join(tempfile.gettempdir(), f"dots_ocr_demo_{session_id}")
  139. os.makedirs(temp_dir, exist_ok=True)
  140. return temp_dir, session_id
  141. def parse_image_with_high_level_api(parser, image, prompt_mode, fitz_preprocess=False):
  142. """
  143. Processes using the high-level API parse_image from DotsOCRParser
  144. """
  145. # Create a temporary session directory
  146. temp_dir, session_id = create_temp_session_dir()
  147. try:
  148. # Save the PIL Image as a temporary file
  149. temp_image_path = os.path.join(temp_dir, f"input_{session_id}.png")
  150. image.save(temp_image_path, "PNG")
  151. # Use the high-level API parse_image
  152. filename = f"demo_{session_id}"
  153. results = parser.parse_image(
  154. input_path=image,
  155. filename=filename,
  156. prompt_mode=prompt_mode,
  157. save_dir=temp_dir,
  158. fitz_preprocess=fitz_preprocess
  159. )
  160. # Parse the results
  161. if not results:
  162. raise ValueError("No results returned from parser")
  163. result = results[0] # parse_image returns a list with a single result
  164. layout_image = None
  165. if 'layout_image_path' in result and os.path.exists(result['layout_image_path']):
  166. layout_image = Image.open(result['layout_image_path'])
  167. cells_data = None
  168. if 'layout_info_path' in result and os.path.exists(result['layout_info_path']):
  169. with open(result['layout_info_path'], 'r', encoding='utf-8') as f:
  170. cells_data = json.load(f)
  171. md_content = None
  172. if 'md_content_path' in result and os.path.exists(result['md_content_path']):
  173. with open(result['md_content_path'], 'r', encoding='utf-8') as f:
  174. md_content = f.read()
  175. return {
  176. 'layout_image': layout_image,
  177. 'cells_data': cells_data,
  178. 'md_content': md_content,
  179. 'filtered': result.get('filtered', False),
  180. 'temp_dir': temp_dir,
  181. 'session_id': session_id,
  182. 'result_paths': result,
  183. 'input_width': result.get('input_width', 0),
  184. 'input_height': result.get('input_height', 0),
  185. }
  186. except Exception as e:
  187. if os.path.exists(temp_dir):
  188. shutil.rmtree(temp_dir, ignore_errors=True)
  189. raise e
  190. def parse_pdf_with_high_level_api(parser, pdf_path, prompt_mode):
  191. """
  192. Processes using the high-level API parse_pdf from DotsOCRParser
  193. """
  194. # Create a temporary session directory
  195. temp_dir, session_id = create_temp_session_dir()
  196. try:
  197. # Use the high-level API parse_pdf
  198. filename = f"demo_{session_id}"
  199. results = parser.parse_pdf(
  200. input_path=pdf_path,
  201. filename=filename,
  202. prompt_mode=prompt_mode,
  203. save_dir=temp_dir
  204. )
  205. # Parse the results
  206. if not results:
  207. raise ValueError("No results returned from parser")
  208. # Handle multi-page results
  209. parsed_results = []
  210. all_md_content = []
  211. all_cells_data = []
  212. for i, result in enumerate(results):
  213. page_result = {
  214. 'page_no': result.get('page_no', i),
  215. 'layout_image': None,
  216. 'cells_data': None,
  217. 'md_content': None,
  218. 'filtered': False
  219. }
  220. # Read the layout image
  221. if 'layout_image_path' in result and os.path.exists(result['layout_image_path']):
  222. page_result['layout_image'] = Image.open(result['layout_image_path'])
  223. # Read the JSON data
  224. if 'layout_info_path' in result and os.path.exists(result['layout_info_path']):
  225. with open(result['layout_info_path'], 'r', encoding='utf-8') as f:
  226. page_result['cells_data'] = json.load(f)
  227. all_cells_data.extend(page_result['cells_data'])
  228. # Read the Markdown content
  229. if 'md_content_path' in result and os.path.exists(result['md_content_path']):
  230. with open(result['md_content_path'], 'r', encoding='utf-8') as f:
  231. page_content = f.read()
  232. page_result['md_content'] = page_content
  233. all_md_content.append(page_content)
  234. page_result['filtered'] = result.get('filtered', False)
  235. parsed_results.append(page_result)
  236. combined_md = "\n\n---\n\n".join(all_md_content) if all_md_content else ""
  237. return {
  238. 'parsed_results': parsed_results,
  239. 'combined_md_content': combined_md,
  240. 'combined_cells_data': all_cells_data,
  241. 'temp_dir': temp_dir,
  242. 'session_id': session_id,
  243. 'total_pages': len(results)
  244. }
  245. except Exception as e:
  246. if os.path.exists(temp_dir):
  247. shutil.rmtree(temp_dir, ignore_errors=True)
  248. raise e
  249. # ==================== Core Processing Function ====================
  250. def process_image_inference(session_state, test_image_input, file_input,
  251. prompt_mode, server_ip, server_port, min_pixels, max_pixels,
  252. fitz_preprocess=False
  253. ):
  254. """Core function to handle image/PDF inference"""
  255. # Use session_state instead of global variables
  256. processing_results = session_state['processing_results']
  257. pdf_cache = session_state['pdf_cache']
  258. if processing_results.get('temp_dir') and os.path.exists(processing_results['temp_dir']):
  259. try:
  260. shutil.rmtree(processing_results['temp_dir'], ignore_errors=True)
  261. except Exception as e:
  262. print(f"Failed to clean up previous temporary directory: {e}")
  263. # Reset processing results for the current session
  264. session_state['processing_results'] = get_initial_session_state()['processing_results']
  265. processing_results = session_state['processing_results']
  266. current_config.update({
  267. 'ip': server_ip,
  268. 'port_vllm': server_port,
  269. 'min_pixels': min_pixels,
  270. 'max_pixels': max_pixels
  271. })
  272. # Update parser configuration
  273. dots_parser.ip = server_ip
  274. dots_parser.port = server_port
  275. dots_parser.min_pixels = min_pixels
  276. dots_parser.max_pixels = max_pixels
  277. input_file_path = file_input if file_input else test_image_input
  278. if not input_file_path:
  279. return None, "Please upload image/PDF file or select test image", "", "", gr.update(value=None), None, "", session_state
  280. file_ext = os.path.splitext(input_file_path)[1].lower()
  281. try:
  282. if file_ext == '.pdf':
  283. # MINIMAL CHANGE: The `process_pdf_file` function is now inlined and uses session_state.
  284. preview_image, page_info, session_state = load_file_for_preview(input_file_path, session_state)
  285. pdf_result = parse_pdf_with_high_level_api(dots_parser, input_file_path, prompt_mode)
  286. session_state['pdf_cache']["is_parsed"] = True
  287. session_state['pdf_cache']["results"] = pdf_result['parsed_results']
  288. processing_results.update({
  289. 'markdown_content': pdf_result['combined_md_content'],
  290. 'cells_data': pdf_result['combined_cells_data'],
  291. 'temp_dir': pdf_result['temp_dir'],
  292. 'session_id': pdf_result['session_id'],
  293. 'pdf_results': pdf_result['parsed_results']
  294. })
  295. total_elements = len(pdf_result['combined_cells_data'])
  296. info_text = f"**PDF Information:**\n- Total Pages: {pdf_result['total_pages']}\n- Server: {current_config['ip']}:{current_config['port_vllm']}\n- Total Detected Elements: {total_elements}\n- Session ID: {pdf_result['session_id']}"
  297. current_page_layout_image = preview_image
  298. current_page_json = ""
  299. if session_state['pdf_cache']["results"]:
  300. first_result = session_state['pdf_cache']["results"][0]
  301. if 'layout_image' in first_result and first_result['layout_image']:
  302. current_page_layout_image = first_result['layout_image']
  303. if first_result.get('cells_data'):
  304. try:
  305. current_page_json = json.dumps(first_result['cells_data'], ensure_ascii=False, indent=2)
  306. except:
  307. current_page_json = str(first_result['cells_data'])
  308. download_zip_path = None
  309. if pdf_result['temp_dir']:
  310. download_zip_path = os.path.join(pdf_result['temp_dir'], f"layout_results_{pdf_result['session_id']}.zip")
  311. with zipfile.ZipFile(download_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
  312. for root, _, files in os.walk(pdf_result['temp_dir']):
  313. for file in files:
  314. if not file.endswith('.zip'): zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), pdf_result['temp_dir']))
  315. return (
  316. current_page_layout_image, info_text, pdf_result['combined_md_content'] or "No markdown content generated",
  317. pdf_result['combined_md_content'] or "No markdown content generated",
  318. gr.update(value=download_zip_path, visible=bool(download_zip_path)), page_info, current_page_json, session_state
  319. )
  320. else: # Image processing
  321. image = read_image_v2(input_file_path)
  322. session_state['pdf_cache'] = get_initial_session_state()['pdf_cache']
  323. original_image = image
  324. parse_result = parse_image_with_high_level_api(dots_parser, image, prompt_mode, fitz_preprocess)
  325. if parse_result['filtered']:
  326. info_text = f"**Image Information:**\n- Original Size: {original_image.width} x {original_image.height}\n- Processing: JSON parsing failed, using cleaned text output\n- Server: {current_config['ip']}:{current_config['port_vllm']}\n- Session ID: {parse_result['session_id']}"
  327. processing_results.update({
  328. 'original_image': original_image, 'markdown_content': parse_result['md_content'],
  329. 'temp_dir': parse_result['temp_dir'], 'session_id': parse_result['session_id'],
  330. 'result_paths': parse_result['result_paths']
  331. })
  332. return original_image, info_text, parse_result['md_content'], parse_result['md_content'], gr.update(visible=False), None, "", session_state
  333. md_content_raw = parse_result['md_content'] or "No markdown content generated"
  334. processing_results.update({
  335. 'original_image': original_image, 'layout_result': parse_result['layout_image'],
  336. 'markdown_content': parse_result['md_content'], 'cells_data': parse_result['cells_data'],
  337. 'temp_dir': parse_result['temp_dir'], 'session_id': parse_result['session_id'],
  338. 'result_paths': parse_result['result_paths']
  339. })
  340. num_elements = len(parse_result['cells_data']) if parse_result['cells_data'] else 0
  341. info_text = f"**Image Information:**\n- Original Size: {original_image.width} x {original_image.height}\n- Model Input Size: {parse_result['input_width']} x {parse_result['input_height']}\n- Server: {current_config['ip']}:{current_config['port_vllm']}\n- Detected {num_elements} layout elements\n- Session ID: {parse_result['session_id']}"
  342. current_json = json.dumps(parse_result['cells_data'], ensure_ascii=False, indent=2) if parse_result['cells_data'] else ""
  343. download_zip_path = None
  344. if parse_result['temp_dir']:
  345. download_zip_path = os.path.join(parse_result['temp_dir'], f"layout_results_{parse_result['session_id']}.zip")
  346. with zipfile.ZipFile(download_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
  347. for root, _, files in os.walk(parse_result['temp_dir']):
  348. for file in files:
  349. if not file.endswith('.zip'): zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), parse_result['temp_dir']))
  350. return (
  351. parse_result['layout_image'], info_text, parse_result['md_content'] or "No markdown content generated",
  352. md_content_raw, gr.update(value=download_zip_path, visible=bool(download_zip_path)),
  353. None, current_json, session_state
  354. )
  355. except Exception as e:
  356. import traceback
  357. traceback.print_exc()
  358. return None, f"Error during processing: {e}", "", "", gr.update(value=None), None, "", session_state
  359. # MINIMAL CHANGE: Functions now take `session_state` as an argument.
  360. def clear_all_data(session_state):
  361. """Clears all data"""
  362. processing_results = session_state['processing_results']
  363. if processing_results.get('temp_dir') and os.path.exists(processing_results['temp_dir']):
  364. try:
  365. shutil.rmtree(processing_results['temp_dir'], ignore_errors=True)
  366. except Exception as e:
  367. print(f"Failed to clean up temporary directory: {e}")
  368. # Reset the session state by returning a new initial state
  369. new_session_state = get_initial_session_state()
  370. return (
  371. None, # Clear file input
  372. "", # Clear test image selection
  373. None, # Clear result image
  374. "Waiting for processing results...", # Reset info display
  375. "## Waiting for processing results...", # Reset Markdown display
  376. "🕐 Waiting for parsing result...", # Clear raw Markdown text
  377. gr.update(visible=False), # Hide download button
  378. "<div id='page_info_box'>0 / 0</div>", # Reset page info
  379. "🕐 Waiting for parsing result...", # Clear current page JSON
  380. new_session_state
  381. )
  382. def update_prompt_display(prompt_mode):
  383. """Updates the prompt display content"""
  384. return dict_promptmode_to_prompt[prompt_mode]
  385. # ==================== Gradio Interface ====================
  386. def create_gradio_interface():
  387. """Creates the Gradio interface"""
  388. # CSS styles, matching the reference style
  389. css = """
  390. #parse_button {
  391. background: #FF576D !important; /* !important 确保覆盖主题默认样式 */
  392. border-color: #FF576D !important;
  393. }
  394. /* 鼠标悬停时的颜色 */
  395. #parse_button:hover {
  396. background: #F72C49 !important;
  397. border-color: #F72C49 !important;
  398. }
  399. #page_info_html {
  400. display: flex;
  401. align-items: center;
  402. justify-content: center;
  403. height: 100%;
  404. margin: 0 12px;
  405. }
  406. #page_info_box {
  407. padding: 8px 20px;
  408. font-size: 16px;
  409. border: 1px solid #bbb;
  410. border-radius: 8px;
  411. background-color: #f8f8f8;
  412. text-align: center;
  413. min-width: 80px;
  414. box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  415. }
  416. #markdown_output {
  417. min-height: 800px;
  418. overflow: auto;
  419. }
  420. footer {
  421. visibility: hidden;
  422. }
  423. #info_box {
  424. padding: 10px;
  425. background-color: #f8f9fa;
  426. border-radius: 8px;
  427. border: 1px solid #dee2e6;
  428. margin: 10px 0;
  429. font-size: 14px;
  430. }
  431. #result_image {
  432. border-radius: 8px;
  433. }
  434. #markdown_tabs {
  435. height: 100%;
  436. }
  437. """
  438. with gr.Blocks(theme="ocean", css=css, title='dots.ocr') as demo:
  439. session_state = gr.State(value=get_initial_session_state())
  440. # Title
  441. gr.HTML("""
  442. <div style="display: flex; align-items: center; justify-content: center; margin-bottom: 20px;">
  443. <h1 style="margin: 0; font-size: 2em;">🔍 dots.ocr</h1>
  444. </div>
  445. <div style="text-align: center; margin-bottom: 10px;">
  446. <em>Supports image/PDF layout analysis and structured output</em>
  447. </div>
  448. """)
  449. with gr.Row():
  450. # Left side: Input and Configuration
  451. with gr.Column(scale=1, elem_id="left-panel"):
  452. gr.Markdown("### 📥 Upload & Select")
  453. file_input = gr.File(
  454. label="Upload PDF/Image",
  455. type="filepath",
  456. file_types=[".pdf", ".jpg", ".jpeg", ".png"],
  457. )
  458. test_images = get_test_images()
  459. test_image_input = gr.Dropdown(
  460. label="Or Select an Example",
  461. choices=[""] + test_images,
  462. value="",
  463. )
  464. gr.Markdown("### ⚙️ Prompt & Actions")
  465. prompt_mode = gr.Dropdown(
  466. label="Select Prompt",
  467. choices=["prompt_layout_all_en", "prompt_layout_only_en", "prompt_ocr"],
  468. value="prompt_layout_all_en",
  469. )
  470. # Display current prompt content
  471. prompt_display = gr.Textbox(
  472. label="Current Prompt Content",
  473. value=dict_promptmode_to_prompt[list(dict_promptmode_to_prompt.keys())[0]],
  474. lines=4,
  475. max_lines=8,
  476. interactive=False,
  477. show_copy_button=True
  478. )
  479. with gr.Row():
  480. process_btn = gr.Button("🔍 Parse", variant="primary", scale=2, elem_id="parse_button")
  481. clear_btn = gr.Button("🗑️ Clear", variant="secondary", scale=1)
  482. with gr.Accordion("🛠️ Advanced Configuration", open=False):
  483. fitz_preprocess = gr.Checkbox(
  484. label="Enable fitz_preprocess for images",
  485. value=True,
  486. info="Processes image via a PDF-like pipeline (image->pdf->200dpi image). Recommended if your image DPI is low."
  487. )
  488. with gr.Row():
  489. server_ip = gr.Textbox(label="Server IP", value=DEFAULT_CONFIG['ip'])
  490. server_port = gr.Number(label="Port", value=DEFAULT_CONFIG['port_vllm'], precision=0)
  491. with gr.Row():
  492. min_pixels = gr.Number(label="Min Pixels", value=DEFAULT_CONFIG['min_pixels'], precision=0)
  493. max_pixels = gr.Number(label="Max Pixels", value=DEFAULT_CONFIG['max_pixels'], precision=0)
  494. # Right side: Result Display
  495. with gr.Column(scale=6, variant="compact"):
  496. with gr.Row():
  497. # Result Image
  498. with gr.Column(scale=3):
  499. gr.Markdown("### 👁️ File Preview")
  500. result_image = gr.Image(
  501. label="Layout Preview",
  502. visible=True,
  503. height=800,
  504. show_label=False
  505. )
  506. # Page navigation (shown during PDF preview)
  507. with gr.Row():
  508. prev_btn = gr.Button("⬅ Previous", size="sm")
  509. page_info = gr.HTML(
  510. value="<div id='page_info_box'>0 / 0</div>",
  511. elem_id="page_info_html"
  512. )
  513. next_btn = gr.Button("Next ➡", size="sm")
  514. # Info Display
  515. info_display = gr.Markdown(
  516. "Waiting for processing results...",
  517. elem_id="info_box"
  518. )
  519. # Markdown Result
  520. with gr.Column(scale=3):
  521. gr.Markdown("### ✔️ Result Display")
  522. with gr.Tabs(elem_id="markdown_tabs"):
  523. with gr.TabItem("Markdown Render Preview"):
  524. md_output = gr.Markdown(
  525. "## Please click the parse button to parse or select for single-task recognition...",
  526. max_height=600,
  527. latex_delimiters=[
  528. {"left": "$$", "right": "$$", "display": True},
  529. {"left": "$", "right": "$", "display": False}
  530. ],
  531. show_copy_button=False,
  532. elem_id="markdown_output"
  533. )
  534. with gr.TabItem("Markdown Raw Text"):
  535. md_raw_output = gr.Textbox(
  536. value="🕐 Waiting for parsing result...",
  537. label="Markdown Raw Text",
  538. max_lines=100,
  539. lines=38,
  540. show_copy_button=True,
  541. elem_id="markdown_output",
  542. show_label=False
  543. )
  544. with gr.TabItem("Current Page JSON"):
  545. current_page_json = gr.Textbox(
  546. value="🕐 Waiting for parsing result...",
  547. label="Current Page JSON",
  548. max_lines=100,
  549. lines=38,
  550. show_copy_button=True,
  551. elem_id="markdown_output",
  552. show_label=False
  553. )
  554. # Download Button
  555. with gr.Row():
  556. download_btn = gr.DownloadButton(
  557. "⬇️ Download Results",
  558. visible=False
  559. )
  560. # When the prompt mode changes, update the display content
  561. prompt_mode.change(
  562. fn=update_prompt_display,
  563. inputs=prompt_mode,
  564. outputs=prompt_display,
  565. )
  566. # Show preview on file upload
  567. file_input.upload(
  568. # fn=lambda file_data, state: load_file_for_preview(file_data, state),
  569. fn=load_file_for_preview,
  570. inputs=[file_input, session_state],
  571. outputs=[result_image, page_info, session_state]
  572. )
  573. # Also handle test image selection
  574. test_image_input.change(
  575. # fn=lambda path, state: load_file_for_preview(path, state),
  576. fn=load_file_for_preview,
  577. inputs=[test_image_input, session_state],
  578. outputs=[result_image, page_info, session_state]
  579. )
  580. prev_btn.click(
  581. fn=lambda s: turn_page("prev", s),
  582. inputs=[session_state],
  583. outputs=[result_image, page_info, current_page_json, session_state]
  584. )
  585. next_btn.click(
  586. fn=lambda s: turn_page("next", s),
  587. inputs=[session_state],
  588. outputs=[result_image, page_info, current_page_json, session_state]
  589. )
  590. process_btn.click(
  591. fn=process_image_inference,
  592. inputs=[
  593. session_state, test_image_input, file_input,
  594. prompt_mode, server_ip, server_port, min_pixels, max_pixels,
  595. fitz_preprocess
  596. ],
  597. outputs=[
  598. result_image, info_display, md_output, md_raw_output,
  599. download_btn, page_info, current_page_json, session_state
  600. ]
  601. )
  602. clear_btn.click(
  603. fn=clear_all_data,
  604. inputs=[session_state],
  605. outputs=[
  606. file_input, test_image_input,
  607. result_image, info_display, md_output, md_raw_output,
  608. download_btn, page_info, current_page_json, session_state
  609. ]
  610. )
  611. return demo
  612. # ==================== Main Program ====================
  613. if __name__ == "__main__":
  614. import sys
  615. port = int(sys.argv[1])
  616. demo = create_gradio_interface()
  617. demo.queue().launch(
  618. server_name="0.0.0.0",
  619. server_port=port,
  620. debug=True
  621. )