root commited on
Commit
d18ae45
·
1 Parent(s): f9aef00
Files changed (1) hide show
  1. app.py +92 -218
app.py CHANGED
@@ -10,7 +10,7 @@ import gradio as gr
10
  # =========================
11
  # Config
12
  # =========================
13
- DEFAULT_API_URL =os.environ.get("API_URL")
14
  TOKEN = os.environ.get("TOKEN")
15
  LOGO_IMAGE_PATH = './assets/logo.jpg'
16
  GOOGLE_FONTS_URL = "<link href='https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap' rel='stylesheet'>"
@@ -23,7 +23,7 @@ LATEX_DELIMS = [
23
  AUTH_HEADER = {"Authorization": f"bearer {TOKEN}"}
24
  JSON_HEADERS = {**AUTH_HEADER, "Content-Type": "application/json"}
25
  # =========================
26
- # Base64 and Example Loading Logic (From New Script)
27
  # =========================
28
  def image_to_base64_data_url(filepath: str) -> str:
29
  """Reads a local image file and encodes it into a Base64 Data URL."""
@@ -53,7 +53,7 @@ targeted_recognition_examples = _get_examples_from_dir(TARGETED_EXAMPLES_DIR)
53
  complex_document_examples = _get_examples_from_dir(COMPLEX_EXAMPLES_DIR)
54
 
55
  # =========================
56
- # UI Helpers (From New Script)
57
  # =========================
58
  def render_uploaded_image_div(file_path: str) -> str:
59
  data_url = image_to_base64_data_url(file_path)
@@ -78,7 +78,7 @@ def _on_gallery_select(example_paths: List[str], evt: gr.SelectData):
78
  return None
79
 
80
  # =========================
81
- # API Call Logic (From New Script - More feature complete)
82
  # =========================
83
  def _file_to_b64_image_only(file_path: str) -> Tuple[str, int]:
84
  if not file_path: raise ValueError("Please upload an image first.")
@@ -104,12 +104,13 @@ def _call_api(api_url: str, file_path: str, use_layout_detection: bool,
104
  payload["useChartRecognition"] = True
105
 
106
  try:
107
- # 关键改动:带上 headers
108
  start_time = time.time()
109
  resp = requests.post(api_url, json=payload, headers=JSON_HEADERS, timeout=600)
110
  end_time = time.time()
111
- duration = end_time - start_time # 新增: 计算耗时
112
- print(f"Received API response in {duration:.2f} seconds.") # 新增: 打印耗时
 
113
  resp.raise_for_status()
114
  data = resp.json()
115
  except requests.exceptions.RequestException as e:
@@ -123,104 +124,85 @@ def _call_api(api_url: str, file_path: str, use_layout_detection: bool,
123
 
124
 
125
  # =========================
126
- # Core Logic for Handling Image URLs (From Old "Xinghe" Script)
127
  # =========================
128
- def url_to_base64_data_url(url: str) -> str:
129
- """Downloads an image from a URL and formats it as a Base64 Data URL for Markdown."""
130
- try:
131
- response = requests.get(url, timeout=600)
132
- response.raise_for_status()
133
- mime_type = response.headers.get('Content-Type', 'image/jpeg')
134
- if not mime_type.startswith('image/'):
135
- print(f"Warning: URL did not return an image content type. Got: {mime_type}")
136
- mime_type = 'image/jpeg'
137
- image_bytes = response.content
138
- encoded_string = base64.b64encode(image_bytes).decode('utf-8')
139
- return f"data:{mime_type};base64,{encoded_string}"
140
- except requests.exceptions.RequestException as e:
141
- print(f"Error fetching markdown image from URL {url}: {e}")
142
- return url # Fallback to original URL on error
143
- except Exception as e:
144
- print(f"An unexpected error occurred while processing markdown URL {url}: {e}")
145
- return url
146
-
147
- def replace_image_urls_with_data_urls(md_text: str, md_images_map: Dict[str, str]) -> str:
148
- """Replaces image placeholder paths in Markdown with Base64 Data URLs fetched from external URLs."""
149
- if not md_images_map:
150
- return md_text
151
- for placeholder_path, image_url in md_images_map.items():
152
- print(f"Processing markdown image for '{placeholder_path}' from URL: {image_url}")
153
- data_url = url_to_base64_data_url(image_url)
154
- md_text = md_text.replace(f'src="{placeholder_path}"', f'src="{data_url}"') \
155
- .replace(f']({placeholder_path})', f']({data_url})')
156
- return md_text
157
-
158
- def url_to_pil_image(url: str) -> Optional[Image.Image]:
159
- """Downloads an image from a URL and returns it as a PIL Image object for the Gradio Image component."""
160
- if not url or not url.startswith(('http://', 'https://')):
161
- print(f"Warning: Invalid URL provided for visualization image: {url}")
162
- return None
163
- try:
164
- response = requests.get(url, timeout=600)
165
- response.raise_for_status()
166
- image_bytes = response.content
167
- pil_image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
168
- return pil_image
169
- except requests.exceptions.RequestException as e:
170
- print(f"Error fetching visualization image from URL {url}: {e}")
171
- return None
172
- except Exception as e:
173
- print(f"Error processing visualization image from URL {url}: {e}")
174
- return None
175
 
176
- # =========================
177
- # API Response Processing (From Old "Xinghe" Script - Handles URLs)
178
- # =========================
179
- def _process_api_response_page(result: Dict[str, Any]) -> Tuple[str, Optional[Image.Image], str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  """
181
- Processes the API response which contains URLs for images.
182
- 1. Converts markdown image URLs to inline Base64 Data URLs.
183
- 2. Downloads the visualization image URL into a PIL Image object.
184
  """
185
  layout_results = (result or {}).get("layoutParsingResults", [])
186
  if not layout_results:
187
- return "No content was recognized.", None, ""
188
 
189
  page0 = layout_results[0] or {}
190
 
191
- # Step 1: Process Markdown content using URL-to-Base64 logic
192
  md_data = page0.get("markdown") or {}
193
  md_text = md_data.get("text", "") or ""
194
- md_images_map = md_data.get("images", {}) # This map contains URLs: {"placeholder.jpg": "http://..."}
195
  if md_images_map:
196
- md_text = replace_image_urls_with_data_urls(md_text, md_images_map)
197
-
198
- # Step 2: Process Visualization images by downloading from URLs
199
- vis_images: List[Image.Image] = []
200
- out_imgs = page0.get("outputImages") or {} # This dict contains URLs: {"0": "http://...", "1": "http://..."}
201
- for _, img_url in sorted(out_imgs.items()):
202
- pil_image = url_to_pil_image(img_url)
203
- if pil_image:
204
- vis_images.append(pil_image)
205
- else:
206
- print(f"Warning: Failed to load visualization image from URL: {img_url}")
207
-
208
- # Logic to select the final visualization image
209
- output_image: Optional[Image.Image] = None
210
- if len(vis_images) >= 2:
211
- output_image = vis_images[1]
212
- elif vis_images:
213
- output_image = vis_images[0]
 
 
 
 
 
 
 
214
 
215
- return md_text or "(Empty result)", output_image, md_text
216
 
217
  # =========================
218
- # Handlers (From New Script - More feature complete)
219
  # =========================
220
- def handle_complex_doc(file_path: str, use_chart_recognition: bool) -> Tuple[str, Optional[Image.Image], str]:
221
  if not file_path: raise gr.Error("Please upload an image first.")
222
  data = _call_api(DEFAULT_API_URL, file_path, use_layout_detection=True, prompt_label=None, use_chart_recognition=use_chart_recognition)
223
  result = data.get("result", {})
 
224
  return _process_api_response_page(result)
225
 
226
  def handle_targeted_recognition(file_path: str, prompt_choice: str) -> Tuple[str, str]:
@@ -233,158 +215,51 @@ def handle_targeted_recognition(file_path: str, prompt_choice: str) -> Tuple[str
233
  return md_preview, md_raw
234
 
235
  # =========================
236
- # CSS & UI (From New Script)
237
  # =========================
238
  custom_css = """
239
  /* 全局字体 */
240
  body, .gradio-container, .gradio-container * {
241
  font-family: "Noto Sans SC", "Microsoft YaHei", "PingFang SC", sans-serif !important;
242
  }
243
-
244
- /* 头部 */
245
- .app-header {
246
- text-align: center;
247
- max-width: 900px;
248
- margin: 0 auto 8px !important;
249
- }
250
-
251
- /* 全局容器与标签页 */
252
- .gradio-container {
253
- padding: 4px 0 !important;
254
- }
255
- .gradio-container [data-testid="tabs"],
256
- .gradio-container .tabs {
257
- margin-top: 0 !important;
258
- }
259
- .gradio-container [data-testid="tabitem"],
260
- .gradio-container .tabitem {
261
- padding-top: 4px !important;
262
- }
263
-
264
- /* 快速链接 */
265
- .quick-links {
266
- text-align: center;
267
- padding: 8px 0;
268
- border: 1px solid #e5e7eb;
269
- border-radius: 8px;
270
- margin: 8px auto;
271
- max-width: 900px;
272
- }
273
- .quick-links a {
274
- margin: 0 12px;
275
- font-size: 14px;
276
- font-weight: 600;
277
- color: #3b82f6;
278
- text-decoration: none;
279
- }
280
- .quick-links a:hover {
281
- text-decoration: underline;
282
- }
283
-
284
- /* 按钮区域(识别类型选择) */
285
- .prompt-grid {
286
- display: flex;
287
- flex-wrap: wrap;
288
- gap: 8px;
289
- margin-top: 6px;
290
- }
291
- .prompt-grid button {
292
- height: 40px !important;
293
- padding: 0 12px !important;
294
- border-radius: 8px !important;
295
- font-weight: 600 !important;
296
- font-size: 13px !important;
297
- letter-spacing: 0.2px;
298
- }
299
-
300
- /* 图片预览与可视化 (vh -> px) */
301
- #image_preview_vl, #image_preview_doc {
302
- height: 400px !important; /* 原为 60vh */
303
- overflow: auto;
304
- }
305
-
306
- #image_preview_vl img,
307
- #image_preview_doc img,
308
- #vis_image_doc img {
309
- width: 100% !important;
310
- height: auto !important;
311
- object-fit: contain !important;
312
- display: block;
313
- }
314
-
315
- /* Markdown 预览区 (vh -> px) */
316
- #md_preview_vl, #md_preview_doc {
317
- max-height: 540px; /* 原为 60vh */
318
- min-height: 180px;
319
- overflow: auto;
320
- scrollbar-gutter: stable both-edges;
321
- }
322
- #md_preview_vl .prose,
323
- #md_preview_doc .prose {
324
- line-height: 1.7 !important;
325
- }
326
- #md_preview_vl .prose img,
327
- #md_preview_doc .prose img {
328
- display: block;
329
- margin: 0 auto;
330
- max-width: 100%;
331
- height: auto;
332
- }
333
- /* Notice banner */
334
- .notice {
335
- margin: 8px auto 0;
336
- max-width: 900px;
337
- padding: 10px 12px;
338
- border: 1px solid #e5e7eb;
339
- border-radius: 8px;
340
- background: #f8fafc;
341
- font-size: 14px;
342
- line-height: 1.6;
343
- }
344
  .notice strong { font-weight: 700; }
345
  .notice a { color: #3b82f6; text-decoration: none; }
346
  .notice a:hover { text-decoration: underline; }
347
-
348
  """
349
 
350
-
351
  with gr.Blocks(head=GOOGLE_FONTS_URL, css=custom_css, theme=gr.themes.Soft()) as demo:
352
  logo_data_url = image_to_base64_data_url(LOGO_IMAGE_PATH) if os.path.exists(LOGO_IMAGE_PATH) else ""
353
- gr.HTML(f"""
354
- <div class="app-header">
355
- <img src="{logo_data_url}" alt="App Logo" style="max-height:10%; width: auto; margin: 10px auto; display: block;">
356
- </div>
357
- """)
358
- gr.HTML("""
359
- <div class="notice">
360
- <strong>Heads up:</strong> The Hugging Face demo can be slow at times.
361
- For a faster experience, please try
362
- <a href="https://modelscope.cn/studios/PaddlePaddle/PaddleOCR-VL_Online_Demo/summary" target="_blank" rel="noopener noreferrer">ModelScope</a>
363
- or
364
- <a href="https://aistudio.baidu.com/application/detail/98365" target="_blank" rel="noopener noreferrer">Baidu AI Studio</a>.
365
- </div>
366
- """)
367
- gr.HTML("""
368
- <div class="quick-links">
369
- <a href="https://github.com/PaddlePaddle/PaddleOCR" target="_blank">GitHub</a> |
370
- <a href="https://huggingface.co/spaces/PaddlePaddle/PaddleOCR-VL_Online_Demo" target="_blank">Technical Report</a> |
371
- <a href="https://huggingface.co/spaces/PaddlePaddle/PaddleOCR-VL_Online_Demo" target="_blank">Model</a>
372
- </div>
373
- """)
374
  with gr.Tabs():
375
  with gr.Tab("Document Parsing"):
376
  with gr.Row():
377
  with gr.Column(scale=5):
378
  file_doc = gr.File(label="Upload Image", file_count="single", type="filepath", file_types=["image"])
379
  preview_doc_html = gr.HTML(value="", elem_id="image_preview_doc", visible=False)
380
-
381
  gr.Markdown("_( Use this mode for recognizing full-page documents with structured layouts, such as reports, papers, or magazines.)_")
382
  gr.Markdown("💡 *To recognize a single, pre-cropped element (e.g., a table or formula), switch to the 'Element-level Recognition' tab for better results.*")
383
-
384
  with gr.Row(variant="panel"):
385
  chart_parsing_switch = gr.Checkbox(label="Enable chart parsing", value=False, scale=1)
386
  btn_parse = gr.Button("Parse Document", variant="primary", scale=2)
387
-
388
  if complex_document_examples:
389
  complex_paths = [e[0] for e in complex_document_examples]
390
  complex_state = gr.State(complex_paths)
@@ -397,7 +272,8 @@ with gr.Blocks(head=GOOGLE_FONTS_URL, css=custom_css, theme=gr.themes.Soft()) as
397
  with gr.Tab("Markdown Preview"):
398
  md_preview_doc = gr.Markdown("Please upload an image and click 'Parse Document'.", latex_delimiters=LATEX_DELIMS, elem_id="md_preview_doc")
399
  with gr.Tab("Visualization"):
400
- vis_image_doc = gr.Image(label="Detection Visualization", interactive=False, elem_id="vis_image_doc")
 
401
  with gr.Tab("Markdown Source"):
402
  md_raw_doc = gr.Code(label="Markdown Source Code", language="markdown")
403
 
@@ -409,7 +285,6 @@ with gr.Blocks(head=GOOGLE_FONTS_URL, css=custom_css, theme=gr.themes.Soft()) as
409
  with gr.Column(scale=5):
410
  file_vl = gr.File(label="Upload Image", file_count="single", type="filepath", file_types=["image"])
411
  preview_vl_html = gr.HTML(value="", elem_id="image_preview_vl", visible=False)
412
-
413
  gr.Markdown("_(Best for images with a **simple, single-column layout** (e.g., pure text), or for a **pre-cropped single element** like a table, formula, or chart.)_")
414
  gr.Markdown("Choose a recognition type:")
415
  with gr.Row(elem_classes=["prompt-grid"]):
@@ -418,7 +293,6 @@ with gr.Blocks(head=GOOGLE_FONTS_URL, css=custom_css, theme=gr.themes.Soft()) as
418
  with gr.Row(elem_classes=["prompt-grid"]):
419
  btn_table = gr.Button("Table Recognition", variant="secondary")
420
  btn_chart = gr.Button("Chart Recognition", variant="secondary")
421
-
422
  if targeted_recognition_examples:
423
  targeted_paths = [e[0] for e in targeted_recognition_examples]
424
  targeted_state = gr.State(targeted_paths)
@@ -441,4 +315,4 @@ with gr.Blocks(head=GOOGLE_FONTS_URL, css=custom_css, theme=gr.themes.Soft()) as
441
 
442
  if __name__ == "__main__":
443
  port = int(os.getenv("PORT", "7860"))
444
- demo.queue(max_size=6).launch(server_name="0.0.0.0", server_port=port,share=False)
 
10
  # =========================
11
  # Config
12
  # =========================
13
+ DEFAULT_API_URL = os.environ.get("API_URL")
14
  TOKEN = os.environ.get("TOKEN")
15
  LOGO_IMAGE_PATH = './assets/logo.jpg'
16
  GOOGLE_FONTS_URL = "<link href='https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap' rel='stylesheet'>"
 
23
  AUTH_HEADER = {"Authorization": f"bearer {TOKEN}"}
24
  JSON_HEADERS = {**AUTH_HEADER, "Content-Type": "application/json"}
25
  # =========================
26
+ # Base64 and Example Loading Logic
27
  # =========================
28
  def image_to_base64_data_url(filepath: str) -> str:
29
  """Reads a local image file and encodes it into a Base64 Data URL."""
 
53
  complex_document_examples = _get_examples_from_dir(COMPLEX_EXAMPLES_DIR)
54
 
55
  # =========================
56
+ # UI Helpers
57
  # =========================
58
  def render_uploaded_image_div(file_path: str) -> str:
59
  data_url = image_to_base64_data_url(file_path)
 
78
  return None
79
 
80
  # =========================
81
+ # API Call Logic
82
  # =========================
83
  def _file_to_b64_image_only(file_path: str) -> Tuple[str, int]:
84
  if not file_path: raise ValueError("Please upload an image first.")
 
104
  payload["useChartRecognition"] = True
105
 
106
  try:
107
+ print(f"Sending API request to {api_url}...")
108
  start_time = time.time()
109
  resp = requests.post(api_url, json=payload, headers=JSON_HEADERS, timeout=600)
110
  end_time = time.time()
111
+ duration = end_time - start_time
112
+ print(f"Received API response in {duration:.2f} seconds.")
113
+
114
  resp.raise_for_status()
115
  data = resp.json()
116
  except requests.exceptions.RequestException as e:
 
124
 
125
 
126
  # =========================
127
+ # API Response Processing
128
  # =========================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ # 【改动点】: 这个函数现在不再需要,因为我们不再将URL下载为PIL Image对象。
131
+ # def url_to_pil_image(url: str) -> Optional[Image.Image]:
132
+ # """Downloads an image from a URL and returns it as a PIL Image object for the Gradio Image component."""
133
+ # if not url or not url.startswith(('http://', 'https://')):
134
+ # print(f"Warning: Invalid URL provided for visualization image: {url}")
135
+ # return None
136
+ # try:
137
+ # start_time = time.time()
138
+ # response = requests.get(url, timeout=600)
139
+ # end_time = time.time()
140
+ # print(f"Fetched visualization image from {url} in {end_time - start_time:.2f} seconds.")
141
+ #
142
+ # response.raise_for_status()
143
+ # image_bytes = response.content
144
+ # pil_image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
145
+ # return pil_image
146
+ # except requests.exceptions.RequestException as e:
147
+ # print(f"Error fetching visualization image from URL {url}: {e}")
148
+ # return None
149
+ # except Exception as e:
150
+ # print(f"Error processing visualization image from URL {url}: {e}")
151
+ # return None
152
+
153
+ def _process_api_response_page(result: Dict[str, Any]) -> Tuple[str, str, str]:
154
  """
155
+ Processes the API response.
156
+ 1. Replaces markdown image placeholders with their direct URLs.
157
+ 2. Constructs an HTML <img> tag string for the visualization image URL.
158
  """
159
  layout_results = (result or {}).get("layoutParsingResults", [])
160
  if not layout_results:
161
+ return "No content was recognized.", "<p>No visualization available.</p>", ""
162
 
163
  page0 = layout_results[0] or {}
164
 
165
+ # Step 1: Process Markdown content (unchanged from previous optimization)
166
  md_data = page0.get("markdown") or {}
167
  md_text = md_data.get("text", "") or ""
168
+ md_images_map = md_data.get("images", {})
169
  if md_images_map:
170
+ for placeholder_path, image_url in md_images_map.items():
171
+ md_text = md_text.replace(f'src="{placeholder_path}"', f'src="{image_url}"') \
172
+ .replace(f']({placeholder_path})', f']({image_url})')
173
+
174
+ # 【核心改动点】 Step 2: Process Visualization images by creating an HTML string
175
+ output_html = "<p style='text-align:center; color:#888;'>No visualization image available.</p>"
176
+ out_imgs = page0.get("outputImages") or {}
177
+
178
+ # Get all image URLs and sort them
179
+ sorted_urls = [img_url for _, img_url in sorted(out_imgs.items()) if img_url]
180
+
181
+ # Logic to select the final visualization image URL
182
+ output_image_url: Optional[str] = None
183
+ if len(sorted_urls) >= 2:
184
+ output_image_url = sorted_urls[1]
185
+ elif sorted_urls:
186
+ output_image_url = sorted_urls[0]
187
+
188
+ # If a URL was found, create the <img> tag
189
+ if output_image_url:
190
+ print(f"Found visualization image URL: {output_image_url}")
191
+ # The CSS will style this `img` tag because of the `#vis_image_doc img` selector
192
+ output_html = f'<img src="{output_image_url}" alt="Detection Visualization">'
193
+ else:
194
+ print("Warning: No visualization image URL found in the API response.")
195
 
196
+ return md_text or "(Empty result)", output_html, md_text
197
 
198
  # =========================
199
+ # Handlers
200
  # =========================
201
+ def handle_complex_doc(file_path: str, use_chart_recognition: bool) -> Tuple[str, str, str]:
202
  if not file_path: raise gr.Error("Please upload an image first.")
203
  data = _call_api(DEFAULT_API_URL, file_path, use_layout_detection=True, prompt_label=None, use_chart_recognition=use_chart_recognition)
204
  result = data.get("result", {})
205
+ # Note the return types now align with the new function signature
206
  return _process_api_response_page(result)
207
 
208
  def handle_targeted_recognition(file_path: str, prompt_choice: str) -> Tuple[str, str]:
 
215
  return md_preview, md_raw
216
 
217
  # =========================
218
+ # CSS & UI
219
  # =========================
220
  custom_css = """
221
  /* 全局字体 */
222
  body, .gradio-container, .gradio-container * {
223
  font-family: "Noto Sans SC", "Microsoft YaHei", "PingFang SC", sans-serif !important;
224
  }
225
+ /* ... (rest of the CSS is unchanged) ... */
226
+ .app-header { text-align: center; max-width: 900px; margin: 0 auto 8px !important; }
227
+ .gradio-container { padding: 4px 0 !important; }
228
+ .gradio-container [data-testid="tabs"], .gradio-container .tabs { margin-top: 0 !important; }
229
+ .gradio-container [data-testid="tabitem"], .gradio-container .tabitem { padding-top: 4px !important; }
230
+ .quick-links { text-align: center; padding: 8px 0; border: 1px solid #e5e7eb; border-radius: 8px; margin: 8px auto; max-width: 900px; }
231
+ .quick-links a { margin: 0 12px; font-size: 14px; font-weight: 600; color: #3b82f6; text-decoration: none; }
232
+ .quick-links a:hover { text-decoration: underline; }
233
+ .prompt-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
234
+ .prompt-grid button { height: 40px !important; padding: 0 12px !important; border-radius: 8px !important; font-weight: 600 !important; font-size: 13px !important; letter-spacing: 0.2px; }
235
+ #image_preview_vl, #image_preview_doc { height: 400px !important; overflow: auto; }
236
+ #image_preview_vl img, #image_preview_doc img, #vis_image_doc img { width: 100% !important; height: auto !important; object-fit: contain !important; display: block; }
237
+ #md_preview_vl, #md_preview_doc { max-height: 540px; min-height: 180px; overflow: auto; scrollbar-gutter: stable both-edges; }
238
+ #md_preview_vl .prose, #md_preview_doc .prose { line-height: 1.7 !important; }
239
+ #md_preview_vl .prose img, #md_preview_doc .prose img { display: block; margin: 0 auto; max-width: 100%; height: auto; }
240
+ .notice { margin: 8px auto 0; max-width: 900px; padding: 10px 12px; border: 1px solid #e5e7eb; border-radius: 8px; background: #f8fafc; font-size: 14px; line-height: 1.6; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  .notice strong { font-weight: 700; }
242
  .notice a { color: #3b82f6; text-decoration: none; }
243
  .notice a:hover { text-decoration: underline; }
 
244
  """
245
 
 
246
  with gr.Blocks(head=GOOGLE_FONTS_URL, css=custom_css, theme=gr.themes.Soft()) as demo:
247
  logo_data_url = image_to_base64_data_url(LOGO_IMAGE_PATH) if os.path.exists(LOGO_IMAGE_PATH) else ""
248
+ gr.HTML(f"""<div class="app-header"><img src="{logo_data_url}" alt="App Logo" style="max-height:10%; width: auto; margin: 10px auto; display: block;"></div>""")
249
+ gr.HTML("""<div class="notice"><strong>Heads up:</strong> The Hugging Face demo can be slow at times. For a faster experience, please try <a href="https://aistudio.baidu.com/application/detail/98365" target="_blank" rel="noopener noreferrer">Baidu AI Studio</a> or <a href="https://modelscope.cn/studios/PaddlePaddle/PaddleOCR-VL_Online_Demo/summary" target="_blank" rel="noopener noreferrer">ModelScope</a>.</div>""")
250
+ gr.HTML("""<div class="quick-links"><a href="https://github.com/PaddlePaddle/PaddleOCR" target="_blank">GitHub</a> | <a href="https://huggingface.co/spaces/PaddlePaddle/PaddleOCR-VL_Online_Demo" target="_blank">Technical Report</a> | <a href="https://huggingface.co/spaces/PaddlePaddle/PaddleOCR-VL_Online_Demo" target="_blank">Model</a></div>""")
251
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  with gr.Tabs():
253
  with gr.Tab("Document Parsing"):
254
  with gr.Row():
255
  with gr.Column(scale=5):
256
  file_doc = gr.File(label="Upload Image", file_count="single", type="filepath", file_types=["image"])
257
  preview_doc_html = gr.HTML(value="", elem_id="image_preview_doc", visible=False)
 
258
  gr.Markdown("_( Use this mode for recognizing full-page documents with structured layouts, such as reports, papers, or magazines.)_")
259
  gr.Markdown("💡 *To recognize a single, pre-cropped element (e.g., a table or formula), switch to the 'Element-level Recognition' tab for better results.*")
 
260
  with gr.Row(variant="panel"):
261
  chart_parsing_switch = gr.Checkbox(label="Enable chart parsing", value=False, scale=1)
262
  btn_parse = gr.Button("Parse Document", variant="primary", scale=2)
 
263
  if complex_document_examples:
264
  complex_paths = [e[0] for e in complex_document_examples]
265
  complex_state = gr.State(complex_paths)
 
272
  with gr.Tab("Markdown Preview"):
273
  md_preview_doc = gr.Markdown("Please upload an image and click 'Parse Document'.", latex_delimiters=LATEX_DELIMS, elem_id="md_preview_doc")
274
  with gr.Tab("Visualization"):
275
+ # 【核心改动点】: gr.Image 替换为 gr.HTML
276
+ vis_image_doc = gr.HTML(label="Detection Visualization", elem_id="vis_image_doc")
277
  with gr.Tab("Markdown Source"):
278
  md_raw_doc = gr.Code(label="Markdown Source Code", language="markdown")
279
 
 
285
  with gr.Column(scale=5):
286
  file_vl = gr.File(label="Upload Image", file_count="single", type="filepath", file_types=["image"])
287
  preview_vl_html = gr.HTML(value="", elem_id="image_preview_vl", visible=False)
 
288
  gr.Markdown("_(Best for images with a **simple, single-column layout** (e.g., pure text), or for a **pre-cropped single element** like a table, formula, or chart.)_")
289
  gr.Markdown("Choose a recognition type:")
290
  with gr.Row(elem_classes=["prompt-grid"]):
 
293
  with gr.Row(elem_classes=["prompt-grid"]):
294
  btn_table = gr.Button("Table Recognition", variant="secondary")
295
  btn_chart = gr.Button("Chart Recognition", variant="secondary")
 
296
  if targeted_recognition_examples:
297
  targeted_paths = [e[0] for e in targeted_recognition_examples]
298
  targeted_state = gr.State(targeted_paths)
 
315
 
316
  if __name__ == "__main__":
317
  port = int(os.getenv("PORT", "7860"))
318
+ demo.queue(max_size=6).launch(server_name="0.0.0.0", server_port=port,share=False)