Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -78,10 +78,16 @@ def _estimate_duration(text, num_candidates=3, structured_fallback=True, *args,
|
|
| 78 |
return min(30, max(12, 8 + len(text or "") // 200 + 4 * (candidates - 1)))
|
| 79 |
|
| 80 |
|
| 81 |
-
def _run_harness(text: str | None, num_candidates: int | None, structured_fallback: bool | None) -> dict:
|
| 82 |
text, num_candidates, structured_fallback = _normalize(text, num_candidates, structured_fallback)
|
| 83 |
if not text:
|
| 84 |
raise ValueError("Paste a document to convert.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
started = time.perf_counter()
|
| 86 |
result = convert_to_markdown(
|
| 87 |
model,
|
|
@@ -93,6 +99,8 @@ def _run_harness(text: str | None, num_candidates: int | None, structured_fallba
|
|
| 93 |
top_p=0.95,
|
| 94 |
seed=0,
|
| 95 |
use_cache=True,
|
|
|
|
|
|
|
| 96 |
structured_fallback=structured_fallback,
|
| 97 |
skip_model_for_structured=True,
|
| 98 |
)
|
|
@@ -101,7 +109,7 @@ def _run_harness(text: str | None, num_candidates: int | None, structured_fallba
|
|
| 101 |
|
| 102 |
|
| 103 |
@spaces.GPU(duration=_estimate_duration)
|
| 104 |
-
def convert(text: str | None = None, num_candidates: int | None = None, structured_fallback: bool | None = None) -> tuple[str, str, str]:
|
| 105 |
"""Convert a plain-text document into faithful GitHub-Flavored Markdown.
|
| 106 |
|
| 107 |
PASITA samples the document several times, every candidate is post-processed and
|
|
@@ -113,6 +121,7 @@ def convert(text: str | None = None, num_candidates: int | None = None, structur
|
|
| 113 |
text: The document to convert, for example OCR output, pasted HTML, meeting notes or a report.
|
| 114 |
num_candidates: How many PASITA samples to generate and rerank, from 1 to 3.
|
| 115 |
structured_fallback: Use the deterministic converter for HTML and delimited data.
|
|
|
|
| 116 |
Returns:
|
| 117 |
A tuple of the rendered Markdown, the raw Markdown and a status line.
|
| 118 |
"""
|
|
@@ -121,7 +130,7 @@ def convert(text: str | None = None, num_candidates: int | None = None, structur
|
|
| 121 |
return EMPTY_MD, "", _status("READY / PASTE A DOCUMENT TO CONVERT")
|
| 122 |
|
| 123 |
try:
|
| 124 |
-
result = _run_harness(text, num_candidates, structured_fallback)
|
| 125 |
except Exception as exc:
|
| 126 |
message = html.escape(f"{type(exc).__name__}: {exc}")
|
| 127 |
return (
|
|
@@ -145,7 +154,7 @@ def convert(text: str | None = None, num_candidates: int | None = None, structur
|
|
| 145 |
|
| 146 |
|
| 147 |
@spaces.GPU(duration=_estimate_duration)
|
| 148 |
-
def gpu_convert(text: str | None = None, num_candidates: int | None = None, structured_fallback: bool | None = None) -> dict:
|
| 149 |
"""Convert a document and return the full conversion result.
|
| 150 |
|
| 151 |
Same pipeline as the UI conversion. Used by the /v1 OpenAI-style API routes.
|
|
@@ -154,10 +163,11 @@ def gpu_convert(text: str | None = None, num_candidates: int | None = None, stru
|
|
| 154 |
text: The document to convert.
|
| 155 |
num_candidates: How many PASITA samples to generate and rerank, from 1 to 3.
|
| 156 |
structured_fallback: Use the deterministic converter for HTML and delimited data.
|
|
|
|
| 157 |
Returns:
|
| 158 |
The full result dict: markdown, route, metrics, timing and candidate info.
|
| 159 |
"""
|
| 160 |
-
return _run_harness(text, num_candidates, structured_fallback)
|
| 161 |
|
| 162 |
|
| 163 |
COUNTER_JS = """(text) => {
|
|
@@ -190,12 +200,12 @@ def _exception_to_error(exc: Exception) -> JSONResponse:
|
|
| 190 |
return _api_error(message, "server_error", status=500)
|
| 191 |
|
| 192 |
|
| 193 |
-
def _with_request_context(request: fastapi.Request, text: str, candidates: int, fallback: bool) -> dict:
|
| 194 |
if GradioRequest is not None:
|
| 195 |
gr_request = GradioRequest(request=request, session_hash="api")
|
| 196 |
token = LocalContext.request.set(gr_request)
|
| 197 |
try:
|
| 198 |
-
return gpu_convert(text, candidates, fallback)
|
| 199 |
finally:
|
| 200 |
LocalContext.request.reset(token)
|
| 201 |
return gpu_convert(text, candidates, fallback)
|
|
@@ -216,6 +226,7 @@ class ChatCompletionRequest(BaseModel):
|
|
| 216 |
model: str | None = "pasita-v1"
|
| 217 |
messages: list[ChatMessage]
|
| 218 |
stream: bool = False
|
|
|
|
| 219 |
candidates: int = 3
|
| 220 |
structured_fallback: bool = True
|
| 221 |
|
|
@@ -224,11 +235,47 @@ class ConvertRequest(BaseModel):
|
|
| 224 |
text: str
|
| 225 |
candidates: int = 3
|
| 226 |
structured_fallback: bool = True
|
|
|
|
| 227 |
|
| 228 |
|
| 229 |
server = Server(title="PASITA API", version="1.0.0", description=API_DESCRIPTION)
|
| 230 |
|
| 231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
@server.exception_handler(fastapi.exceptions.RequestValidationError)
|
| 233 |
async def validation_error_handler(request: fastapi.Request, exc: fastapi.exceptions.RequestValidationError):
|
| 234 |
issues = "; ".join(f"{'.'.join(str(l) for l in e.get('loc', []))}: {e.get('msg', '')}" for e in exc.errors()[:3])
|
|
@@ -287,7 +334,7 @@ def v1_convert(body: ConvertRequest, request: fastapi.Request) -> dict | JSONRes
|
|
| 287 |
return error
|
| 288 |
started = time.perf_counter()
|
| 289 |
try:
|
| 290 |
-
result = _with_request_context(request, body.text.strip(), int(body.candidates), bool(body.structured_fallback))
|
| 291 |
except Exception as exc:
|
| 292 |
return _exception_to_error(exc)
|
| 293 |
payload = _result_payload(result, body.text.strip(), started)
|
|
@@ -327,7 +374,7 @@ def chat_completions(body: ChatCompletionRequest, request: fastapi.Request) -> d
|
|
| 327 |
|
| 328 |
started = time.perf_counter()
|
| 329 |
try:
|
| 330 |
-
result = _with_request_context(request, text.strip(), int(body.candidates), bool(body.structured_fallback))
|
| 331 |
except Exception as exc:
|
| 332 |
return _exception_to_error(exc)
|
| 333 |
|
|
@@ -475,187 +522,185 @@ Open questions
|
|
| 475 |
],
|
| 476 |
]
|
| 477 |
|
| 478 |
-
CSS = """
|
| 479 |
-
@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
| 480 |
|
| 481 |
:root, .gradio-container {
|
| 482 |
-
--bg:#
|
| 483 |
-
--
|
| 484 |
-
--
|
| 485 |
-
--
|
| 486 |
-
--
|
| 487 |
-
--font-display:"
|
| 488 |
--font-mono:"JetBrains Mono", ui-monospace, monospace;
|
| 489 |
|
| 490 |
-
--body-background-fill:
|
| 491 |
-
--background-fill-primary:
|
| 492 |
-
--background-fill-secondary:
|
| 493 |
--block-background-fill:transparent;
|
| 494 |
--block-border-color:transparent;
|
| 495 |
--block-border-width:0px;
|
| 496 |
-
--block-radius:
|
| 497 |
--border-color-primary:var(--hair);
|
| 498 |
--body-text-color:var(--ink);
|
| 499 |
--body-text-color-subdued:var(--muted);
|
| 500 |
--input-background-fill:transparent;
|
| 501 |
--input-border-color:transparent;
|
| 502 |
-
--input-radius:
|
| 503 |
-
--color-accent:var(--
|
| 504 |
-
--color-accent-soft:rgba(
|
| 505 |
-
--button-primary-background-fill:
|
| 506 |
-
--button-primary-background-fill-hover:#
|
| 507 |
-
--button-primary-text-color:#
|
| 508 |
--button-secondary-background-fill:transparent;
|
| 509 |
--button-secondary-text-color:var(--muted);
|
| 510 |
--button-secondary-border-color:var(--hair);
|
| 511 |
-
--button-large-radius:
|
| 512 |
-
--button-small-radius:
|
|
|
|
|
|
|
| 513 |
--shadow-drop:none; --shadow-drop-lg:none;
|
| 514 |
--font:var(--font-display); --font-mono:var(--font-mono);
|
| 515 |
}
|
| 516 |
|
| 517 |
body, .gradio-container {
|
| 518 |
-
background:
|
| 519 |
-
radial-gradient(120% 80% at 50% 118%, rgba(255,110,40,0.10), rgba(255,110,40,0) 55%),
|
| 520 |
-
radial-gradient(90% 70% at 88% -10%, rgba(80,120,230,0.10), rgba(80,120,230,0) 55%),
|
| 521 |
-
radial-gradient(140% 120% at 50% 50%, #0a0c11 0%, #050608 55%, #030305 100%) !important;
|
| 522 |
-
background-attachment: fixed !important;
|
| 523 |
color: var(--ink);
|
| 524 |
font-family: var(--font-display);
|
|
|
|
| 525 |
-webkit-font-smoothing: antialiased;
|
| 526 |
}
|
| 527 |
-
.gradio-container { max-width:
|
| 528 |
footer[aria-label="Gradio footer navigation"] { display: none !important; }
|
| 529 |
-
:focus-visible { outline: 2px solid var(--
|
| 530 |
|
| 531 |
.p-head {
|
| 532 |
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
| 533 |
-
padding:
|
| 534 |
}
|
| 535 |
.p-brand { display: flex; align-items: center; gap: 10px; }
|
| 536 |
-
.p-glyph { display: inline-flex; width:
|
| 537 |
.p-glyph svg { width: 100%; height: 100%; }
|
| 538 |
-
.p-name { font-weight:
|
| 539 |
-
.p-tag { font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.
|
| 540 |
.p-actions { display: flex; align-items: center; gap: 10px; }
|
| 541 |
.p-btn {
|
| 542 |
-
font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.
|
| 543 |
-
color: var(--ink); text-decoration: none; padding: 0
|
| 544 |
-
display: inline-flex; align-items: center; border-radius:
|
| 545 |
-
background:
|
| 546 |
-
transition: background .
|
| 547 |
}
|
| 548 |
-
.p-btn:hover { background: var(--
|
| 549 |
|
| 550 |
.p-status {
|
| 551 |
display: flex; align-items: center; gap: 9px; margin: -6px 0 16px;
|
| 552 |
-
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.
|
| 553 |
text-transform: uppercase; color: var(--muted); min-height: 20px;
|
|
|
|
| 554 |
}
|
| 555 |
.p-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,0.2); flex-shrink: 0; }
|
| 556 |
-
.p-status.ready .p-dot { background: var(--
|
| 557 |
-
.p-status.busy .p-dot { background:
|
| 558 |
-
.p-status.error { color: var(--
|
| 559 |
-
.p-status.error .p-dot { background: var(--
|
| 560 |
.p-detail { margin-left: auto; color: var(--muted-2); }
|
| 561 |
-
@keyframes p-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.
|
| 562 |
|
| 563 |
-
#p-shell { gap:
|
| 564 |
.p-panel {
|
| 565 |
-
background: var(--panel); border: 1px solid var(--hair); border-radius:
|
| 566 |
padding: 14px 14px 12px; min-width: 0;
|
| 567 |
}
|
| 568 |
-
.p-label { font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.
|
| 569 |
-
.p-count { float: right; font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.
|
| 570 |
|
| 571 |
#p-text, #p-text > div { background: transparent !important; border: 0 !important; box-shadow: none !important; }
|
| 572 |
#p-text textarea {
|
| 573 |
background: transparent !important; border: 0 !important; color: var(--ink) !important;
|
| 574 |
-
font-family: var(--font-display) !important; font-size: 0.
|
| 575 |
min-height: 360px; padding: 12px 2px 0;
|
| 576 |
}
|
| 577 |
#p-text textarea::placeholder { color: var(--muted-2) !important; }
|
| 578 |
|
| 579 |
-
#p-run, #p-clear { font-family: var(--font-mono) !important; font-size: 0.75rem !important; letter-spacing: 0.
|
| 580 |
-
#p-run { flex: 2; }
|
| 581 |
#p-clear { flex: 1; background: transparent !important; color: var(--muted) !important; border: 1px solid var(--hair) !important; }
|
| 582 |
-
#p-clear:hover { color: var(--ink) !important; border-color: rgba(255,255,255,0.
|
| 583 |
|
| 584 |
-
#p-examples .label { font-family: var(--font-mono) !important; font-size: 0.66rem !important; letter-spacing: 0.
|
| 585 |
#p-examples .label svg { display: none; }
|
| 586 |
#p-examples .gallery-item {
|
| 587 |
-
background: var(--panel) !important; border: 1px solid var(--hair) !important; border-radius:
|
| 588 |
color: var(--muted) !important; font-size: 12px !important; min-height: 44px; padding: 8px 14px !important;
|
| 589 |
text-align: left; transition: background .2s ease, color .2s ease, border-color .2s ease;
|
| 590 |
}
|
| 591 |
-
#p-examples .gallery-item:hover { border-color: rgba(255,255,255,0.
|
| 592 |
|
| 593 |
#p-tabs .tab-container[role="tablist"] {
|
| 594 |
-
display: inline-flex; gap:
|
| 595 |
-
border-radius:
|
| 596 |
}
|
| 597 |
#p-tabs .tab-container[role="tablist"] button {
|
| 598 |
-
appearance: none; border: 0 !important; background: transparent !important; border-radius:
|
| 599 |
padding: 7px 14px !important; font-family: var(--font-mono) !important; font-size: 10.5px !important;
|
| 600 |
-
letter-spacing: 0.
|
| 601 |
}
|
| 602 |
#p-tabs .tab-container[role="tablist"] button.selected,
|
| 603 |
#p-tabs .tab-container[role="tablist"] button[aria-selected="true"] {
|
| 604 |
-
background: rgba(255,255,255,0.
|
| 605 |
}
|
| 606 |
#p-tabs .tab-container::after,
|
| 607 |
#p-tabs .tab-container button.selected::after { content: none !important; }
|
| 608 |
|
| 609 |
-
#p-rendered { min-height: 360px; font-size: 0.
|
| 610 |
-
#p-rendered .prose { font-size: 0.
|
| 611 |
-
#p-rendered h1, #p-rendered h2, #p-rendered h3, #p-rendered h4 { color:
|
| 612 |
-
#p-rendered h1 { font-size: 1.
|
| 613 |
-
#p-rendered h2 { font-size: 1.
|
| 614 |
-
#p-rendered h3 { font-size: 1.
|
| 615 |
-
#p-rendered p { margin: 0 0 0.
|
| 616 |
-
#p-rendered ul, #p-rendered ol { margin: 0 0 0.
|
| 617 |
-
#p-rendered li { margin: 0.
|
| 618 |
#p-rendered li::marker { color: var(--muted-2); }
|
| 619 |
-
#p-rendered a { color:
|
| 620 |
-
#p-rendered a:hover { color: #
|
| 621 |
-
#p-rendered blockquote { margin: 0 0 0.
|
| 622 |
-
#p-rendered hr { border: 0; border-top: 1px solid var(--hair); margin: 1.
|
| 623 |
-
#p-rendered code { font-family: var(--font-mono); font-size: 0.
|
| 624 |
-
#p-rendered pre { margin: 0 0 0.
|
| 625 |
#p-rendered pre code { background: none; border: 0; padding: 0; font-size: 0.82em; line-height: 1.6; }
|
| 626 |
-
#p-rendered table { border-collapse: collapse; margin: 0 0 0.
|
| 627 |
-
#p-rendered th, #p-rendered td { border: 1px solid var(--hair); padding: 6px 11px; text-align: left; }
|
| 628 |
-
#p-rendered th { background: var(--panel); color: var(--ink); font-weight: 600; }
|
| 629 |
|
| 630 |
#p-source {
|
| 631 |
background: var(--obsidian) !important; border: 1px solid var(--hair-soft) !important;
|
| 632 |
-
border-radius:
|
| 633 |
}
|
| 634 |
#p-source label[data-testid="block-label"] { display: none; }
|
| 635 |
#p-source .cm-editor { background: transparent !important; }
|
| 636 |
-
#p-source .cm-content, #p-source .cm-scroller { color: var(--ink) !important; caret-color: var(--
|
| 637 |
#p-source .cm-gutters { background: transparent !important; color: var(--muted-2) !important; border-right: 1px solid var(--hair-soft) !important; }
|
| 638 |
|
| 639 |
-
#p-settings { background: transparent !important; border: 1px solid var(--hair-soft) !important; border-radius:
|
| 640 |
-
#p-settings > .label-wrap { font-family: var(--font-mono) !important; font-size: 0.68rem !important; letter-spacing: 0.
|
| 641 |
#p-settings > .label-wrap .icon { color: var(--muted-2) !important; }
|
| 642 |
#p-settings .info-text { font-size: 0.72rem !important; color: var(--muted-2) !important; }
|
| 643 |
|
| 644 |
-
#p-candidates .wrap { display: inline-flex; gap:
|
| 645 |
-
#p-candidates label { border-radius:
|
| 646 |
-
#p-candidates label span { font-family: var(--font-mono) !important; font-size: 11px !important; letter-spacing: 0.
|
| 647 |
-
#p-candidates label.selected { background: rgba(255,255,255,0.
|
| 648 |
-
#p-candidates label.selected span { color: var(--
|
| 649 |
-
#p-candidates input[type="radio"] { accent-color: var(--
|
| 650 |
-
#p-settings .p-label { margin-bottom: 4px; }
|
| 651 |
|
| 652 |
.p-foot {
|
| 653 |
display: flex; flex-wrap: wrap; justify-content: space-between; gap: 14px;
|
| 654 |
margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--hair-soft);
|
| 655 |
-
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.
|
| 656 |
}
|
| 657 |
.p-foot a { color: var(--muted); text-decoration: none; }
|
| 658 |
-
.p-foot a:hover { color: var(--
|
| 659 |
|
| 660 |
@media (max-width: 820px) {
|
| 661 |
.gradio-container { padding: 0 14px 20px; }
|
|
@@ -676,7 +721,7 @@ footer[aria-label="Gradio footer navigation"] { display: none !important; }
|
|
| 676 |
theme = gr.themes.Base(
|
| 677 |
primary_hue=gr.themes.colors.orange,
|
| 678 |
neutral_hue=gr.themes.colors.gray,
|
| 679 |
-
font=[gr.themes.GoogleFont("
|
| 680 |
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "ui-monospace", "monospace"],
|
| 681 |
)
|
| 682 |
|
|
|
|
| 78 |
return min(30, max(12, 8 + len(text or "") // 200 + 4 * (candidates - 1)))
|
| 79 |
|
| 80 |
|
| 81 |
+
def _run_harness(text: str | None, num_candidates: int | None, structured_fallback: bool | None, max_new_cap: int | None = None) -> dict:
|
| 82 |
text, num_candidates, structured_fallback = _normalize(text, num_candidates, structured_fallback)
|
| 83 |
if not text:
|
| 84 |
raise ValueError("Paste a document to convert.")
|
| 85 |
+
hard_cap = 512
|
| 86 |
+
if max_new_cap is not None:
|
| 87 |
+
try:
|
| 88 |
+
hard_cap = max(16, min(512, int(max_new_cap)))
|
| 89 |
+
except (TypeError, ValueError):
|
| 90 |
+
hard_cap = 512
|
| 91 |
started = time.perf_counter()
|
| 92 |
result = convert_to_markdown(
|
| 93 |
model,
|
|
|
|
| 99 |
top_p=0.95,
|
| 100 |
seed=0,
|
| 101 |
use_cache=True,
|
| 102 |
+
max_new_tokens="auto",
|
| 103 |
+
hard_cap=hard_cap,
|
| 104 |
structured_fallback=structured_fallback,
|
| 105 |
skip_model_for_structured=True,
|
| 106 |
)
|
|
|
|
| 109 |
|
| 110 |
|
| 111 |
@spaces.GPU(duration=_estimate_duration)
|
| 112 |
+
def convert(text: str | None = None, num_candidates: int | None = None, structured_fallback: bool | None = None, max_output_tokens: int | None = None) -> tuple[str, str, str]:
|
| 113 |
"""Convert a plain-text document into faithful GitHub-Flavored Markdown.
|
| 114 |
|
| 115 |
PASITA samples the document several times, every candidate is post-processed and
|
|
|
|
| 121 |
text: The document to convert, for example OCR output, pasted HTML, meeting notes or a report.
|
| 122 |
num_candidates: How many PASITA samples to generate and rerank, from 1 to 3.
|
| 123 |
structured_fallback: Use the deterministic converter for HTML and delimited data.
|
| 124 |
+
max_output_tokens: Optional cap on generated tokens. When omitted, the length is chosen automatically.
|
| 125 |
Returns:
|
| 126 |
A tuple of the rendered Markdown, the raw Markdown and a status line.
|
| 127 |
"""
|
|
|
|
| 130 |
return EMPTY_MD, "", _status("READY / PASTE A DOCUMENT TO CONVERT")
|
| 131 |
|
| 132 |
try:
|
| 133 |
+
result = _run_harness(text, num_candidates, structured_fallback, max_output_tokens)
|
| 134 |
except Exception as exc:
|
| 135 |
message = html.escape(f"{type(exc).__name__}: {exc}")
|
| 136 |
return (
|
|
|
|
| 154 |
|
| 155 |
|
| 156 |
@spaces.GPU(duration=_estimate_duration)
|
| 157 |
+
def gpu_convert(text: str | None = None, num_candidates: int | None = None, structured_fallback: bool | None = None, max_output_tokens: int | None = None) -> dict:
|
| 158 |
"""Convert a document and return the full conversion result.
|
| 159 |
|
| 160 |
Same pipeline as the UI conversion. Used by the /v1 OpenAI-style API routes.
|
|
|
|
| 163 |
text: The document to convert.
|
| 164 |
num_candidates: How many PASITA samples to generate and rerank, from 1 to 3.
|
| 165 |
structured_fallback: Use the deterministic converter for HTML and delimited data.
|
| 166 |
+
max_output_tokens: Optional cap on generated tokens. When omitted, the length is chosen automatically.
|
| 167 |
Returns:
|
| 168 |
The full result dict: markdown, route, metrics, timing and candidate info.
|
| 169 |
"""
|
| 170 |
+
return _run_harness(text, num_candidates, structured_fallback, max_output_tokens)
|
| 171 |
|
| 172 |
|
| 173 |
COUNTER_JS = """(text) => {
|
|
|
|
| 200 |
return _api_error(message, "server_error", status=500)
|
| 201 |
|
| 202 |
|
| 203 |
+
def _with_request_context(request: fastapi.Request, text: str, candidates: int, fallback: bool, max_tokens: int | None = None) -> dict:
|
| 204 |
if GradioRequest is not None:
|
| 205 |
gr_request = GradioRequest(request=request, session_hash="api")
|
| 206 |
token = LocalContext.request.set(gr_request)
|
| 207 |
try:
|
| 208 |
+
return gpu_convert(text, candidates, fallback, max_tokens)
|
| 209 |
finally:
|
| 210 |
LocalContext.request.reset(token)
|
| 211 |
return gpu_convert(text, candidates, fallback)
|
|
|
|
| 226 |
model: str | None = "pasita-v1"
|
| 227 |
messages: list[ChatMessage]
|
| 228 |
stream: bool = False
|
| 229 |
+
max_tokens: int | None = None
|
| 230 |
candidates: int = 3
|
| 231 |
structured_fallback: bool = True
|
| 232 |
|
|
|
|
| 235 |
text: str
|
| 236 |
candidates: int = 3
|
| 237 |
structured_fallback: bool = True
|
| 238 |
+
max_tokens: int | None = None
|
| 239 |
|
| 240 |
|
| 241 |
server = Server(title="PASITA API", version="1.0.0", description=API_DESCRIPTION)
|
| 242 |
|
| 243 |
|
| 244 |
+
_V2_CONVERT_PATHS = frozenset({"/gradio_api/call/v2/convert", "/gradio_api/call/v2/convert/"})
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
@server.middleware("http")
|
| 248 |
+
async def _legacy_convert_body(request: fastapi.Request, call_next):
|
| 249 |
+
if request.method == "POST" and request.url.path in _V2_CONVERT_PATHS:
|
| 250 |
+
try:
|
| 251 |
+
body = await request.json()
|
| 252 |
+
except Exception:
|
| 253 |
+
body = None
|
| 254 |
+
if isinstance(body, dict) and "data" in body and "text" not in body:
|
| 255 |
+
data = body.get("data")
|
| 256 |
+
if isinstance(data, list):
|
| 257 |
+
named: dict = {}
|
| 258 |
+
for name, value in zip(("text", "num_candidates", "structured_fallback"), data):
|
| 259 |
+
named[name] = value
|
| 260 |
+
for key in ("session_hash", "event_id", "fn_index", "trigger_id", "batched"):
|
| 261 |
+
if key in body:
|
| 262 |
+
named[key] = body[key]
|
| 263 |
+
raw = json.dumps(named, default=str).encode("utf-8")
|
| 264 |
+
scope = dict(request.scope)
|
| 265 |
+
headers = [(k, v) for k, v in scope.get("headers", []) if k.lower() != b"content-length"]
|
| 266 |
+
headers.append((b"content-length", str(len(raw)).encode("latin-1")))
|
| 267 |
+
scope["headers"] = headers
|
| 268 |
+
messages = [{"type": "http.request", "body": raw, "more_body": False}]
|
| 269 |
+
|
| 270 |
+
async def receive_once():
|
| 271 |
+
if messages:
|
| 272 |
+
return messages.pop(0)
|
| 273 |
+
return {"type": "http.request", "body": b"", "more_body": False}
|
| 274 |
+
|
| 275 |
+
request = fastapi.Request(scope, receive_once)
|
| 276 |
+
return await call_next(request)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
@server.exception_handler(fastapi.exceptions.RequestValidationError)
|
| 280 |
async def validation_error_handler(request: fastapi.Request, exc: fastapi.exceptions.RequestValidationError):
|
| 281 |
issues = "; ".join(f"{'.'.join(str(l) for l in e.get('loc', []))}: {e.get('msg', '')}" for e in exc.errors()[:3])
|
|
|
|
| 334 |
return error
|
| 335 |
started = time.perf_counter()
|
| 336 |
try:
|
| 337 |
+
result = _with_request_context(request, body.text.strip(), int(body.candidates), bool(body.structured_fallback), body.max_tokens)
|
| 338 |
except Exception as exc:
|
| 339 |
return _exception_to_error(exc)
|
| 340 |
payload = _result_payload(result, body.text.strip(), started)
|
|
|
|
| 374 |
|
| 375 |
started = time.perf_counter()
|
| 376 |
try:
|
| 377 |
+
result = _with_request_context(request, text.strip(), int(body.candidates), bool(body.structured_fallback), body.max_tokens)
|
| 378 |
except Exception as exc:
|
| 379 |
return _exception_to_error(exc)
|
| 380 |
|
|
|
|
| 522 |
],
|
| 523 |
]
|
| 524 |
|
| 525 |
+
CSS = """@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
|
|
| 526 |
|
| 527 |
:root, .gradio-container {
|
| 528 |
+
--bg:#0a0a0b; --panel:#101013; --panel-2:#16161a; --obsidian:#0d0d10;
|
| 529 |
+
--ink:#f4f4f5; --muted:#a1a1aa; --muted-2:#8e8e96;
|
| 530 |
+
--gold:#e8b34c; --green:#4cc38a; --red:#f87171;
|
| 531 |
+
--hair:rgba(255,255,255,0.09); --hair-soft:rgba(255,255,255,0.06);
|
| 532 |
+
--panel-hover:rgba(255,255,255,0.05);
|
| 533 |
+
--font-display:"Inter", system-ui, sans-serif;
|
| 534 |
--font-mono:"JetBrains Mono", ui-monospace, monospace;
|
| 535 |
|
| 536 |
+
--body-background-fill:var(--bg);
|
| 537 |
+
--background-fill-primary:var(--panel);
|
| 538 |
+
--background-fill-secondary:var(--panel-2);
|
| 539 |
--block-background-fill:transparent;
|
| 540 |
--block-border-color:transparent;
|
| 541 |
--block-border-width:0px;
|
| 542 |
+
--block-radius:12px;
|
| 543 |
--border-color-primary:var(--hair);
|
| 544 |
--body-text-color:var(--ink);
|
| 545 |
--body-text-color-subdued:var(--muted);
|
| 546 |
--input-background-fill:transparent;
|
| 547 |
--input-border-color:transparent;
|
| 548 |
+
--input-radius:8px;
|
| 549 |
+
--color-accent:var(--gold);
|
| 550 |
+
--color-accent-soft:rgba(232,179,76,0.12);
|
| 551 |
+
--button-primary-background-fill:#fafafa;
|
| 552 |
+
--button-primary-background-fill-hover:#e4e4e7;
|
| 553 |
+
--button-primary-text-color:#0a0a0b;
|
| 554 |
--button-secondary-background-fill:transparent;
|
| 555 |
--button-secondary-text-color:var(--muted);
|
| 556 |
--button-secondary-border-color:var(--hair);
|
| 557 |
+
--button-large-radius:8px;
|
| 558 |
+
--button-small-radius:8px;
|
| 559 |
+
--checkbox-background-color-selected:var(--gold);
|
| 560 |
+
--checkbox-border-color-focus:var(--gold);
|
| 561 |
--shadow-drop:none; --shadow-drop-lg:none;
|
| 562 |
--font:var(--font-display); --font-mono:var(--font-mono);
|
| 563 |
}
|
| 564 |
|
| 565 |
body, .gradio-container {
|
| 566 |
+
background: var(--bg) !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
color: var(--ink);
|
| 568 |
font-family: var(--font-display);
|
| 569 |
+
font-variant-numeric: tabular-nums;
|
| 570 |
-webkit-font-smoothing: antialiased;
|
| 571 |
}
|
| 572 |
+
.gradio-container { max-width: 1200px !important; margin: 0 auto; padding: 0 20px 26px; }
|
| 573 |
footer[aria-label="Gradio footer navigation"] { display: none !important; }
|
| 574 |
+
:focus-visible { outline: 2px solid var(--gold) !important; outline-offset: 2px; }
|
| 575 |
|
| 576 |
.p-head {
|
| 577 |
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
| 578 |
+
padding: 20px 0 14px; border-bottom: 1px solid var(--hair-soft); margin-bottom: 20px;
|
| 579 |
}
|
| 580 |
.p-brand { display: flex; align-items: center; gap: 10px; }
|
| 581 |
+
.p-glyph { display: inline-flex; width: 20px; height: 20px; color: var(--gold); }
|
| 582 |
.p-glyph svg { width: 100%; height: 100%; }
|
| 583 |
+
.p-name { font-weight: 600; letter-spacing: 0.12em; font-size: 0.95rem; }
|
| 584 |
+
.p-tag { font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.12em; color: var(--muted-2); margin-top: 2px; }
|
| 585 |
.p-actions { display: flex; align-items: center; gap: 10px; }
|
| 586 |
.p-btn {
|
| 587 |
+
font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em;
|
| 588 |
+
color: var(--ink); text-decoration: none; padding: 0 16px; min-height: 44px;
|
| 589 |
+
display: inline-flex; align-items: center; border-radius: 8px;
|
| 590 |
+
background: var(--panel); border: 1px solid var(--hair);
|
| 591 |
+
transition: background .2s ease, color .2s ease, border-color .2s ease;
|
| 592 |
}
|
| 593 |
+
.p-btn:hover { background: var(--panel-2); border-color: rgba(255,255,255,0.22); }
|
| 594 |
|
| 595 |
.p-status {
|
| 596 |
display: flex; align-items: center; gap: 9px; margin: -6px 0 16px;
|
| 597 |
+
font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em;
|
| 598 |
text-transform: uppercase; color: var(--muted); min-height: 20px;
|
| 599 |
+
font-variant-numeric: tabular-nums;
|
| 600 |
}
|
| 601 |
.p-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,0.2); flex-shrink: 0; }
|
| 602 |
+
.p-status.ready .p-dot { background: var(--green); }
|
| 603 |
+
.p-status.busy .p-dot { background: var(--gold); animation: p-pulse 1.1s ease-in-out infinite; }
|
| 604 |
+
.p-status.error { color: var(--red); }
|
| 605 |
+
.p-status.error .p-dot { background: var(--red); }
|
| 606 |
.p-detail { margin-left: auto; color: var(--muted-2); }
|
| 607 |
+
@keyframes p-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
|
| 608 |
|
| 609 |
+
#p-shell { gap: 16px; align-items: stretch; }
|
| 610 |
.p-panel {
|
| 611 |
+
background: var(--panel); border: 1px solid var(--hair); border-radius: 12px;
|
| 612 |
padding: 14px 14px 12px; min-width: 0;
|
| 613 |
}
|
| 614 |
+
.p-label { font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); }
|
| 615 |
+
.p-count { float: right; font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.02em; color: var(--muted-2); font-variant-numeric: tabular-nums; }
|
| 616 |
|
| 617 |
#p-text, #p-text > div { background: transparent !important; border: 0 !important; box-shadow: none !important; }
|
| 618 |
#p-text textarea {
|
| 619 |
background: transparent !important; border: 0 !important; color: var(--ink) !important;
|
| 620 |
+
font-family: var(--font-display) !important; font-size: 0.94rem; line-height: 1.65;
|
| 621 |
min-height: 360px; padding: 12px 2px 0;
|
| 622 |
}
|
| 623 |
#p-text textarea::placeholder { color: var(--muted-2) !important; }
|
| 624 |
|
| 625 |
+
#p-run, #p-clear { font-family: var(--font-mono) !important; font-size: 0.75rem !important; letter-spacing: 0.1em; min-height: 46px; }
|
| 626 |
+
#p-run { flex: 2; font-weight: 500; }
|
| 627 |
#p-clear { flex: 1; background: transparent !important; color: var(--muted) !important; border: 1px solid var(--hair) !important; }
|
| 628 |
+
#p-clear:hover { color: var(--ink) !important; border-color: rgba(255,255,255,0.25) !important; }
|
| 629 |
|
| 630 |
+
#p-examples .label { font-family: var(--font-mono) !important; font-size: 0.66rem !important; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted) !important; }
|
| 631 |
#p-examples .label svg { display: none; }
|
| 632 |
#p-examples .gallery-item {
|
| 633 |
+
background: var(--panel-2) !important; border: 1px solid var(--hair) !important; border-radius: 8px !important;
|
| 634 |
color: var(--muted) !important; font-size: 12px !important; min-height: 44px; padding: 8px 14px !important;
|
| 635 |
text-align: left; transition: background .2s ease, color .2s ease, border-color .2s ease;
|
| 636 |
}
|
| 637 |
+
#p-examples .gallery-item:hover { border-color: rgba(255,255,255,0.25) !important; color: var(--ink) !important; }
|
| 638 |
|
| 639 |
#p-tabs .tab-container[role="tablist"] {
|
| 640 |
+
display: inline-flex; gap: 2px; background: var(--panel-2); border: 1px solid var(--hair);
|
| 641 |
+
border-radius: 10px; padding: 3px; margin-bottom: 12px;
|
| 642 |
}
|
| 643 |
#p-tabs .tab-container[role="tablist"] button {
|
| 644 |
+
appearance: none; border: 0 !important; background: transparent !important; border-radius: 7px !important;
|
| 645 |
padding: 7px 14px !important; font-family: var(--font-mono) !important; font-size: 10.5px !important;
|
| 646 |
+
letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted) !important;
|
| 647 |
}
|
| 648 |
#p-tabs .tab-container[role="tablist"] button.selected,
|
| 649 |
#p-tabs .tab-container[role="tablist"] button[aria-selected="true"] {
|
| 650 |
+
background: rgba(255,255,255,0.09) !important; color: var(--gold) !important;
|
| 651 |
}
|
| 652 |
#p-tabs .tab-container::after,
|
| 653 |
#p-tabs .tab-container button.selected::after { content: none !important; }
|
| 654 |
|
| 655 |
+
#p-rendered { min-height: 360px; font-size: 0.94rem; line-height: 1.7; color: var(--ink); }
|
| 656 |
+
#p-rendered .prose { font-size: 0.94rem; line-height: 1.7; }
|
| 657 |
+
#p-rendered h1, #p-rendered h2, #p-rendered h3, #p-rendered h4 { color: var(--ink); font-weight: 600; line-height: 1.3; margin: 1.2em 0 0.5em; letter-spacing: -0.01em; }
|
| 658 |
+
#p-rendered h1 { font-size: 1.45em; }
|
| 659 |
+
#p-rendered h2 { font-size: 1.22em; }
|
| 660 |
+
#p-rendered h3 { font-size: 1.08em; }
|
| 661 |
+
#p-rendered p { margin: 0 0 0.85em; }
|
| 662 |
+
#p-rendered ul, #p-rendered ol { margin: 0 0 0.85em; padding-left: 1.5em; }
|
| 663 |
+
#p-rendered li { margin: 0.22em 0; }
|
| 664 |
#p-rendered li::marker { color: var(--muted-2); }
|
| 665 |
+
#p-rendered a { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }
|
| 666 |
+
#p-rendered a:hover { color: #f2c578; }
|
| 667 |
+
#p-rendered blockquote { margin: 0 0 0.85em; padding: 2px 0 2px 14px; border-left: 2px solid var(--hair); color: var(--muted); }
|
| 668 |
+
#p-rendered hr { border: 0; border-top: 1px solid var(--hair); margin: 1.2em 0; }
|
| 669 |
+
#p-rendered code { font-family: var(--font-mono); font-size: 0.82em; background: var(--panel-2); border: 1px solid var(--hair); border-radius: 6px; padding: 1px 5px; }
|
| 670 |
+
#p-rendered pre { margin: 0 0 0.85em; padding: 14px 16px; background: var(--obsidian); border: 1px solid var(--hair); border-radius: 8px; overflow-x: auto; }
|
| 671 |
#p-rendered pre code { background: none; border: 0; padding: 0; font-size: 0.82em; line-height: 1.6; }
|
| 672 |
+
#p-rendered table { border-collapse: collapse; margin: 0 0 0.85em; font-size: 0.9em; display: block; overflow-x: auto; }
|
| 673 |
+
#p-rendered th, #p-rendered td { border: 1px solid var(--hair); padding: 6px 11px; text-align: left; font-variant-numeric: tabular-nums; }
|
| 674 |
+
#p-rendered th { background: var(--panel-2); color: var(--ink); font-weight: 600; }
|
| 675 |
|
| 676 |
#p-source {
|
| 677 |
background: var(--obsidian) !important; border: 1px solid var(--hair-soft) !important;
|
| 678 |
+
border-radius: 8px !important; font-family: var(--font-mono) !important; min-height: 360px;
|
| 679 |
}
|
| 680 |
#p-source label[data-testid="block-label"] { display: none; }
|
| 681 |
#p-source .cm-editor { background: transparent !important; }
|
| 682 |
+
#p-source .cm-content, #p-source .cm-scroller { color: var(--ink) !important; caret-color: var(--gold) !important; }
|
| 683 |
#p-source .cm-gutters { background: transparent !important; color: var(--muted-2) !important; border-right: 1px solid var(--hair-soft) !important; }
|
| 684 |
|
| 685 |
+
#p-settings { background: transparent !important; border: 1px solid var(--hair-soft) !important; border-radius: 8px !important; margin-top: 4px; }
|
| 686 |
+
#p-settings > .label-wrap { font-family: var(--font-mono) !important; font-size: 0.68rem !important; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted) !important; }
|
| 687 |
#p-settings > .label-wrap .icon { color: var(--muted-2) !important; }
|
| 688 |
#p-settings .info-text { font-size: 0.72rem !important; color: var(--muted-2) !important; }
|
| 689 |
|
| 690 |
+
#p-candidates .wrap { display: inline-flex; gap: 2px; background: var(--panel-2); border: 1px solid var(--hair); border-radius: 10px; padding: 3px; }
|
| 691 |
+
#p-candidates label { border-radius: 7px; padding: 7px 16px; margin: 0 !important; cursor: pointer; transition: background .2s ease, color .2s ease; }
|
| 692 |
+
#p-candidates label span { font-family: var(--font-mono) !important; font-size: 11px !important; letter-spacing: 0.06em; color: var(--muted) !important; font-variant-numeric: tabular-nums; }
|
| 693 |
+
#p-candidates label.selected { background: rgba(255,255,255,0.09) !important; }
|
| 694 |
+
#p-candidates label.selected span { color: var(--gold) !important; }
|
| 695 |
+
#p-candidates input[type="radio"] { accent-color: var(--gold); }
|
|
|
|
| 696 |
|
| 697 |
.p-foot {
|
| 698 |
display: flex; flex-wrap: wrap; justify-content: space-between; gap: 14px;
|
| 699 |
margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--hair-soft);
|
| 700 |
+
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; line-height: 1.7; color: var(--muted-2);
|
| 701 |
}
|
| 702 |
.p-foot a { color: var(--muted); text-decoration: none; }
|
| 703 |
+
.p-foot a:hover { color: var(--gold); }
|
| 704 |
|
| 705 |
@media (max-width: 820px) {
|
| 706 |
.gradio-container { padding: 0 14px 20px; }
|
|
|
|
| 721 |
theme = gr.themes.Base(
|
| 722 |
primary_hue=gr.themes.colors.orange,
|
| 723 |
neutral_hue=gr.themes.colors.gray,
|
| 724 |
+
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
|
| 725 |
font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "ui-monospace", "monospace"],
|
| 726 |
)
|
| 727 |
|