OpceanAI commited on
Commit
2b3811b
·
verified ·
1 Parent(s): 8d2fd65

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +71 -23
app.py CHANGED
@@ -54,7 +54,8 @@ def convert(text: str, num_candidates: int = 3, structured_fallback: bool = True
54
 
55
  PASITA samples the document several times, every candidate is post-processed and
56
  scored for number fidelity, content coverage and GFM validity, and the best one is
57
- returned. Inputs detected as HTML or delimited data are converted deterministically.
 
58
 
59
  Args:
60
  text: The document to convert, for example OCR output, pasted HTML, meeting notes or a report.
@@ -93,9 +94,14 @@ def convert(text: str, num_candidates: int = 3, structured_fallback: bool = True
93
  metrics = result["metrics"]
94
  numbers = f"{metrics['numbers_matched']}/{metrics['numbers_in']} NUMBERS"
95
  overlap = f"{metrics['content_overlap']:.0%} CONTENT"
96
- source = "HTML/TABLE CONVERTER" if result.get("strategy", {}).get("mode") == "structured" else "PASITA + RERANK"
 
 
 
 
 
97
  taken = result["seconds"] if result["seconds"] else time.perf_counter() - started
98
- detail = f"{source} / {numbers} / {overlap} / {taken:.1f}S"
99
  return result["markdown"], result["markdown"], _status("DONE", "ready", detail)
100
 
101
 
@@ -103,6 +109,21 @@ def reset() -> tuple[str, str, str, str]:
103
  return EMPTY_MD, "", _status("READY"), _counter("")
104
 
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  HEADER = f"""
107
  <header class="p-head">
108
  <div class="p-brand">
@@ -228,7 +249,7 @@ body, .gradio-container {
228
  -webkit-font-smoothing: antialiased;
229
  }
230
  .gradio-container { max-width: 1240px !important; margin: 0 auto; padding: 0 20px 26px; }
231
- footer { display: none !important; }
232
  :focus-visible { outline: 2px solid var(--cool) !important; outline-offset: 2px; }
233
 
234
  .p-head {
@@ -284,29 +305,33 @@ footer { display: none !important; }
284
  #p-clear { flex: 1; background: transparent !important; color: var(--muted) !important; border: 1px solid var(--hair) !important; }
285
  #p-clear:hover { color: var(--ink) !important; border-color: rgba(255,255,255,0.3) !important; }
286
 
287
- #p-examples table { border-collapse: separate; border-spacing: 6px; }
288
- #p-examples button {
 
289
  background: var(--panel) !important; border: 1px solid var(--hair) !important; border-radius: 999px !important;
290
- color: var(--muted) !important; font-size: 12px !important; min-height: 40px; padding: 8px 14px !important;
291
  text-align: left; transition: background .2s ease, color .2s ease, border-color .2s ease;
292
  }
293
- #p-examples button:hover { border-color: rgba(255,255,255,0.3) !important; color: var(--ink) !important; background: var(--panel-hover) !important; }
294
- #p-examples label { font-family: var(--font-mono) !important; font-size: 0.66rem !important; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted) !important; }
295
 
296
- #p-tabs .tab-nav {
297
  display: inline-flex; gap: 4px; background: var(--panel); border: 1px solid var(--hair);
298
  border-radius: 100px; padding: 3px; margin-bottom: 12px;
299
  }
300
- #p-tabs .tab-nav button {
301
  appearance: none; border: 0 !important; background: transparent !important; border-radius: 100px !important;
302
  padding: 7px 14px !important; font-family: var(--font-mono) !important; font-size: 10.5px !important;
303
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted) !important;
304
  }
305
- #p-tabs .tab-nav button.selected, #p-tabs .tab-nav button[aria-selected="true"] {
 
306
  background: rgba(255,255,255,0.08) !important; color: var(--warm) !important;
307
  }
 
 
308
 
309
  #p-rendered { min-height: 360px; font-size: 0.95rem; line-height: 1.72; color: var(--ink); }
 
310
  #p-rendered h1, #p-rendered h2, #p-rendered h3, #p-rendered h4 { color: #fff; font-weight: 600; line-height: 1.25; margin: 1.25em 0 0.55em; }
311
  #p-rendered h1 { font-size: 1.5em; }
312
  #p-rendered h2 { font-size: 1.28em; }
@@ -330,9 +355,14 @@ footer { display: none !important; }
330
  background: var(--obsidian) !important; border: 1px solid var(--hair-soft) !important;
331
  border-radius: 12px !important; font-family: var(--font-mono) !important; min-height: 360px;
332
  }
 
 
 
 
333
 
334
  #p-settings { background: transparent !important; border: 1px solid var(--hair-soft) !important; border-radius: 12px !important; margin-top: 4px; }
335
- #p-settings summary, #p-settings > .label-wrap { font-family: var(--font-mono) !important; font-size: 0.68rem !important; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted) !important; }
 
336
  #p-settings .p-label { margin-bottom: 4px; }
337
 
338
  .p-foot {
@@ -376,9 +406,9 @@ with gr.Blocks(title="PASITA: plain text to Markdown") as demo:
376
  text_in = gr.Textbox(
377
  show_label=False,
378
  container=False,
379
- placeholder="Paste OCR output, pasted HTML, meeting notes, a report or an article...",
380
  lines=18,
381
- max_lines=36,
382
  elem_id="p-text",
383
  )
384
  with gr.Accordion("SETTINGS", open=False, elem_id="p-settings"):
@@ -397,13 +427,17 @@ with gr.Blocks(title="PASITA: plain text to Markdown") as demo:
397
  clear = gr.Button("CLEAR", elem_id="p-clear")
398
  with gr.Column(scale=1, min_width=320, elem_classes=["p-panel"]):
399
  with gr.Tabs(elem_id="p-tabs"):
400
- with gr.Tab("RENDERED"):
401
- md_out = gr.Markdown(EMPTY_MD, buttons=["copy"], elem_id="p-rendered")
402
- with gr.Tab("SOURCE"):
403
- source = gr.Code(value="", language="markdown", buttons=["copy"], wrap_lines=True, elem_id="p-source")
 
 
 
404
 
405
  gr.Examples(
406
  examples=EXAMPLES,
 
407
  inputs=[text_in],
408
  outputs=[md_out, source, status],
409
  fn=convert,
@@ -415,10 +449,24 @@ with gr.Blocks(title="PASITA: plain text to Markdown") as demo:
415
  )
416
  gr.HTML(FOOTER)
417
 
418
- run.click(convert, inputs=[text_in, candidates, structured], outputs=[md_out, source, status], api_name="convert")
419
- text_in.submit(convert, inputs=[text_in, candidates, structured], outputs=[md_out, source, status], api_name=False)
420
- text_in.change(_counter, inputs=[text_in], outputs=[label_in], api_name=False)
421
- clear.click(reset, inputs=None, outputs=[md_out, source, status, label_in], api_name=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
 
423
  demo.queue()
424
  demo.launch(theme=theme, css=CSS, mcp_server=True)
 
54
 
55
  PASITA samples the document several times, every candidate is post-processed and
56
  scored for number fidelity, content coverage and GFM validity, and the best one is
57
+ returned. Inputs detected as HTML or delimited data are converted deterministically,
58
+ and a run that loses source numbers falls back to a faithful passthrough of the input.
59
 
60
  Args:
61
  text: The document to convert, for example OCR output, pasted HTML, meeting notes or a report.
 
94
  metrics = result["metrics"]
95
  numbers = f"{metrics['numbers_matched']}/{metrics['numbers_in']} NUMBERS"
96
  overlap = f"{metrics['content_overlap']:.0%} CONTENT"
97
+ routes = {
98
+ "model": "PASITA + RERANK",
99
+ "structured": "HTML/TABLE CONVERTER",
100
+ "passthrough": "SOURCE PASSTHROUGH",
101
+ }
102
+ source_label = routes.get(result.get("route"), "PASITA + RERANK")
103
  taken = result["seconds"] if result["seconds"] else time.perf_counter() - started
104
+ detail = f"{source_label} / {numbers} / {overlap} / {taken:.1f}S"
105
  return result["markdown"], result["markdown"], _status("DONE", "ready", detail)
106
 
107
 
 
109
  return EMPTY_MD, "", _status("READY"), _counter("")
110
 
111
 
112
+ BUSY_JS = """(text, num_candidates, structured_fallback, md, source, status) => {
113
+ const n = Number(num_candidates) || 1;
114
+ const label = n > 1 ? 'PASITA ' + n + ' SAMPLES / RERANK' : 'PASITA SAMPLE / RERANK';
115
+ return [md, source,
116
+ '<div class="p-status busy"><span class="p-dot"></span><span class="p-text">CONVERTING</span><span class="p-detail">' + label + '</span></div>'];
117
+ }"""
118
+
119
+ COUNTER_JS = """(text) => {
120
+ const t = (text || '').trim();
121
+ const words = t ? t.split(/\\s+/).length : 0;
122
+ const chars = (text || '').length;
123
+ return '<span class="p-label">PLAIN TEXT</span><span class="p-count">' + chars.toLocaleString() + ' CHARS / ' + words.toLocaleString() + ' WORDS</span>';
124
+ }"""
125
+
126
+
127
  HEADER = f"""
128
  <header class="p-head">
129
  <div class="p-brand">
 
249
  -webkit-font-smoothing: antialiased;
250
  }
251
  .gradio-container { max-width: 1240px !important; margin: 0 auto; padding: 0 20px 26px; }
252
+ footer[aria-label="Gradio footer navigation"] { display: none !important; }
253
  :focus-visible { outline: 2px solid var(--cool) !important; outline-offset: 2px; }
254
 
255
  .p-head {
 
305
  #p-clear { flex: 1; background: transparent !important; color: var(--muted) !important; border: 1px solid var(--hair) !important; }
306
  #p-clear:hover { color: var(--ink) !important; border-color: rgba(255,255,255,0.3) !important; }
307
 
308
+ #p-examples .label { font-family: var(--font-mono) !important; font-size: 0.66rem !important; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted) !important; }
309
+ #p-examples .label svg { display: none; }
310
+ #p-examples .gallery-item {
311
  background: var(--panel) !important; border: 1px solid var(--hair) !important; border-radius: 999px !important;
312
+ color: var(--muted) !important; font-size: 12px !important; min-height: 44px; padding: 8px 14px !important;
313
  text-align: left; transition: background .2s ease, color .2s ease, border-color .2s ease;
314
  }
315
+ #p-examples .gallery-item:hover { border-color: rgba(255,255,255,0.3) !important; color: var(--ink) !important; background: var(--panel-hover) !important; }
 
316
 
317
+ #p-tabs .tab-container[role="tablist"] {
318
  display: inline-flex; gap: 4px; background: var(--panel); border: 1px solid var(--hair);
319
  border-radius: 100px; padding: 3px; margin-bottom: 12px;
320
  }
321
+ #p-tabs .tab-container[role="tablist"] button {
322
  appearance: none; border: 0 !important; background: transparent !important; border-radius: 100px !important;
323
  padding: 7px 14px !important; font-family: var(--font-mono) !important; font-size: 10.5px !important;
324
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted) !important;
325
  }
326
+ #p-tabs .tab-container[role="tablist"] button.selected,
327
+ #p-tabs .tab-container[role="tablist"] button[aria-selected="true"] {
328
  background: rgba(255,255,255,0.08) !important; color: var(--warm) !important;
329
  }
330
+ #p-tabs .tab-container::after,
331
+ #p-tabs .tab-container button.selected::after { content: none !important; }
332
 
333
  #p-rendered { min-height: 360px; font-size: 0.95rem; line-height: 1.72; color: var(--ink); }
334
+ #p-rendered .prose { font-size: 0.95rem; line-height: 1.72; }
335
  #p-rendered h1, #p-rendered h2, #p-rendered h3, #p-rendered h4 { color: #fff; font-weight: 600; line-height: 1.25; margin: 1.25em 0 0.55em; }
336
  #p-rendered h1 { font-size: 1.5em; }
337
  #p-rendered h2 { font-size: 1.28em; }
 
355
  background: var(--obsidian) !important; border: 1px solid var(--hair-soft) !important;
356
  border-radius: 12px !important; font-family: var(--font-mono) !important; min-height: 360px;
357
  }
358
+ #p-source label[data-testid="block-label"] { display: none; }
359
+ #p-source .cm-editor { background: transparent !important; }
360
+ #p-source .cm-content, #p-source .cm-scroller { color: var(--ink) !important; caret-color: var(--warm) !important; }
361
+ #p-source .cm-gutters { background: transparent !important; color: var(--muted-2) !important; border-right: 1px solid var(--hair-soft) !important; }
362
 
363
  #p-settings { background: transparent !important; border: 1px solid var(--hair-soft) !important; border-radius: 12px !important; margin-top: 4px; }
364
+ #p-settings > .label-wrap { font-family: var(--font-mono) !important; font-size: 0.68rem !important; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted) !important; }
365
+ #p-settings > .label-wrap .icon { color: var(--muted-2) !important; }
366
  #p-settings .p-label { margin-bottom: 4px; }
367
 
368
  .p-foot {
 
406
  text_in = gr.Textbox(
407
  show_label=False,
408
  container=False,
409
+ placeholder="Paste OCR output, pasted HTML, meeting notes, a report or an article... (SHIFT + ENTER to convert)",
410
  lines=18,
411
+ max_lines=32,
412
  elem_id="p-text",
413
  )
414
  with gr.Accordion("SETTINGS", open=False, elem_id="p-settings"):
 
427
  clear = gr.Button("CLEAR", elem_id="p-clear")
428
  with gr.Column(scale=1, min_width=320, elem_classes=["p-panel"]):
429
  with gr.Tabs(elem_id="p-tabs"):
430
+ with gr.Tab("RENDERED", elem_id="p-tab-rendered"):
431
+ md_out = gr.Markdown(EMPTY_MD, buttons=["copy"], height=520, elem_id="p-rendered")
432
+ with gr.Tab("SOURCE", elem_id="p-tab-source"):
433
+ source = gr.Code(
434
+ value="", language="markdown", buttons=["copy", "download"],
435
+ wrap_lines=True, lines=24, max_lines=24, elem_id="p-source",
436
+ )
437
 
438
  gr.Examples(
439
  examples=EXAMPLES,
440
+ example_labels=["ES REPORT", "EN MEETING", "HTML", "CSV"],
441
  inputs=[text_in],
442
  outputs=[md_out, source, status],
443
  fn=convert,
 
449
  )
450
  gr.HTML(FOOTER)
451
 
452
+ run.click(
453
+ convert,
454
+ inputs=[text_in, candidates, structured],
455
+ outputs=[md_out, source, status],
456
+ api_name="convert",
457
+ js=BUSY_JS,
458
+ show_progress="minimal",
459
+ )
460
+ text_in.submit(
461
+ convert,
462
+ inputs=[text_in, candidates, structured],
463
+ outputs=[md_out, source, status],
464
+ api_visibility="undocumented",
465
+ js=BUSY_JS,
466
+ show_progress="minimal",
467
+ )
468
+ text_in.change(None, inputs=[text_in], outputs=[label_in], js=COUNTER_JS, api_visibility="undocumented")
469
+ clear.click(reset, inputs=None, outputs=[md_out, source, status, label_in], api_visibility="undocumented")
470
 
471
  demo.queue()
472
  demo.launch(theme=theme, css=CSS, mcp_server=True)