Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -430,93 +430,73 @@ def render_parse_panel(parse, completed, show_vat=True):
|
|
| 430 |
|
| 431 |
def decide_fiscal(text, issuer_status, parse, explicit_doc_choice,
|
| 432 |
clarify_vat_basis=None, clarify_client_tax_id=None):
|
| 433 |
-
"""
|
| 434 |
-
Returns (issuer_dict, final_parse, questions_dict) where:
|
| 435 |
-
- issuer_dict: ready for core.complete()
|
| 436 |
-
- final_parse: enriched parse ready for core.complete()
|
| 437 |
-
- questions_dict: {field: question_text} for any remaining mandatory gaps
|
| 438 |
-
"""
|
| 439 |
-
questions = {}
|
| 440 |
today = _today()
|
| 441 |
|
| 442 |
-
# ββ 1. Issuer ββββββββββββ
|
| 443 |
if not issuer_status:
|
| 444 |
-
|
|
|
|
|
|
|
| 445 |
exempt = (issuer_status == "Χ€ΧΧΧ¨")
|
| 446 |
issuer = {**DEMO_ISSUER_BASE,
|
| 447 |
"status": "exempt_dealer" if exempt else "authorized_dealer",
|
| 448 |
"is_company": False}
|
| 449 |
|
| 450 |
-
# ββ 2. Mandatory content
|
| 451 |
if not parse.get("client_name", "").strip():
|
| 452 |
questions["client_name"] = "Χ©Χ ΧΧΧ§ΧΧ / ΧΧΧ©ΧΧ?"
|
| 453 |
if not parse.get("items"):
|
| 454 |
questions["items"] = "ΧΧ ΧΧ©ΧΧ¨ΧΧͺ/ΧΧΧΧ¦Χ¨ ΧΧΧΧΧΧ Χ‘ΧΧΧ? (ΧΧΧ©Χ: ΧΧΧ’ΧΧ₯ Χ’Χ‘Χ§Χ βͺ500)"
|
| 455 |
|
| 456 |
-
# ββ 3. Date
|
| 457 |
-
detected_date = _detect_date_from_text(text) or today.isoformat()
|
| 458 |
parse = dict(parse)
|
| 459 |
-
parse.setdefault("date",
|
| 460 |
|
| 461 |
-
# ββ 4. Doc type
|
| 462 |
-
is_gross
|
| 463 |
-
is_net
|
| 464 |
has_vat_cue = is_gross or is_net
|
| 465 |
|
| 466 |
if exempt:
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
amount_basis = "net" # irrelevant (vat_rate = 0)
|
| 470 |
elif explicit_doc_choice and explicit_doc_choice != "ΧΧΧΧΧ ΧΧΧΧΧΧΧ":
|
| 471 |
-
doc_type = {
|
| 472 |
-
|
| 473 |
-
"ΧΧ©ΧΧΧ ΧΧͺ ΧΧ‘": "tax_invoice",
|
| 474 |
-
"ΧΧ©ΧΧΧ ΧΧͺ ΧΧ‘ ΧΧ§ΧΧΧ": "tax_invoice_receipt",
|
| 475 |
-
}.get(explicit_doc_choice, "receipt")
|
| 476 |
-
# amount basis for tax-invoice types
|
| 477 |
if doc_type != "receipt":
|
| 478 |
-
if is_gross:
|
| 479 |
-
|
| 480 |
-
elif
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
|
|
|
| 485 |
else:
|
| 486 |
amount_basis = "net"
|
| 487 |
else:
|
| 488 |
amount_basis = "net"
|
| 489 |
else:
|
| 490 |
-
# Auto
|
| 491 |
if has_vat_cue:
|
| 492 |
-
doc_type
|
| 493 |
-
amount_basis = "gross" if is_gross else "net"
|
| 494 |
else:
|
| 495 |
-
|
| 496 |
-
doc_type = "receipt"
|
| 497 |
-
amount_basis = "net"
|
| 498 |
-
|
| 499 |
-
# ββ 5. Payment received vs. demanded βββββββββββββββββββββββββββββββββββββ
|
| 500 |
-
# ΧΧ©ΧΧΧ ΧΧͺ ΧΧ‘ (no Χ§ΧΧΧ) = money not yet received β explicit choice only
|
| 501 |
-
# Auto never produces pure tax_invoice (always /Χ§ΧΧΧ if VAT)
|
| 502 |
|
| 503 |
-
# ββ
|
| 504 |
if doc_type in ("tax_invoice", "tax_invoice_receipt") and not exempt:
|
| 505 |
-
subtotal
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
if client_biz and subtotal >= threshold and not parse.get("client_tax_id"):
|
| 512 |
-
if clarify_client_tax_id:
|
| 513 |
parse["client_tax_id"] = clarify_client_tax_id.strip()
|
| 514 |
-
|
| 515 |
-
questions["client_tax_id"] = (
|
| 516 |
-
f"Χ.Χ€. ΧΧΧ§ΧΧ β Χ ΧΧ¨Χ© ΧΧΧ‘Χ€Χ¨ ΧΧ§Χ¦ΧΧ (Χ’Χ‘Χ§Χ β₯ βͺ{threshold:,})")
|
| 517 |
|
| 518 |
-
parse
|
| 519 |
-
parse["amount_basis"] = amount_basis
|
| 520 |
parse.setdefault("payment_method", "bank_transfer")
|
| 521 |
parse.setdefault("currency", "ILS")
|
| 522 |
parse.setdefault("client_tax_id", None)
|
|
@@ -552,16 +532,19 @@ def _build_clarification_message(questions: dict, note_text: str) -> str:
|
|
| 552 |
|
| 553 |
def generate(note, issuer_radio, doc_choice,
|
| 554 |
clarify_vat, clarify_tax_id):
|
| 555 |
-
"""
|
| 556 |
-
Single-shot, stateless pipeline. Returns:
|
| 557 |
-
(status_md, clarify_html, parse_html, doc_html, recs_html,
|
| 558 |
-
clarify_visible)
|
| 559 |
-
"""
|
| 560 |
note = (note or "").strip()
|
| 561 |
if not note:
|
| 562 |
return ("βοΈ ΧΧͺΧΧ Χ€ΧͺΧ§ ΧΧΧ Χ‘Χ ΧΧΧΧ₯ ΧΧ€Χ§ ΧΧ‘ΧΧ.",
|
| 563 |
"", "", "", "", False)
|
| 564 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
_lazy_init()
|
| 566 |
parse = model_parse(note)
|
| 567 |
|
|
@@ -570,10 +553,7 @@ def generate(note, issuer_radio, doc_choice,
|
|
| 570 |
"'Χ§ΧΧΧΧͺΧ 500βͺ ΧΧΧ©Χ Χ’Χ ΧΧΧ’ΧΧ₯'.",
|
| 571 |
"", "", "", "", False)
|
| 572 |
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
# apply clarification answers from the note text itself
|
| 576 |
-
cvat = clarify_vat if clarify_vat != "ΧΧ Χ¦ΧΧΧ" else None
|
| 577 |
ctax = clarify_tax_id.strip() if clarify_tax_id and clarify_tax_id.strip() else None
|
| 578 |
|
| 579 |
issuer, final, questions = decide_fiscal(
|
|
@@ -581,14 +561,13 @@ def generate(note, issuer_radio, doc_choice,
|
|
| 581 |
|
| 582 |
if questions:
|
| 583 |
msg = _build_clarification_message(questions, note)
|
| 584 |
-
return (msg, "", "", "", "", True)
|
| 585 |
|
| 586 |
try:
|
| 587 |
completed = core.complete(issuer, final, _STATE["rng"])
|
| 588 |
except Exception as e:
|
| 589 |
return (f"β οΈ Χ©ΧΧΧΧ: {e}", "", "", "", "", False)
|
| 590 |
|
| 591 |
-
# override VAT display for receipt (no breakdown)
|
| 592 |
show_vat = (final["doc_type"] != "receipt")
|
| 593 |
|
| 594 |
return ("β
ΧΧΧ‘ΧΧ ΧΧΧ€Χ§ ΧΧΧ¦ΧΧΧ.",
|
|
@@ -598,59 +577,6 @@ def generate(note, issuer_radio, doc_choice,
|
|
| 598 |
render_recommendations(recommend(note)),
|
| 599 |
False)
|
| 600 |
|
| 601 |
-
"""'Behind the scenes' panel: what the MODEL extracted vs what the
|
| 602 |
-
DETERMINISTIC engine computed. Demonstrates the model/rules separation β
|
| 603 |
-
the model predicts only the extraction; all arithmetic is rule-based."""
|
| 604 |
-
items = parse.get("items", [])
|
| 605 |
-
item_rows = "".join(
|
| 606 |
-
f'<div style="display:flex;justify-content:space-between;font-size:12px;'
|
| 607 |
-
f'color:#334155;padding:3px 0;"><span>{it.get("description","")}</span>'
|
| 608 |
-
f'<span style="color:#64748b;">{it.get("quantity",1)} Γ βͺ{it.get("unit_price",0):,.0f}</span></div>'
|
| 609 |
-
for it in items)
|
| 610 |
-
|
| 611 |
-
chips = (
|
| 612 |
-
f'<span style="background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;'
|
| 613 |
-
f'border-radius:999px;padding:2px 9px;font-size:11px;">ΧΧ§ΧΧ: {parse.get("client_name","β")}</span>'
|
| 614 |
-
f'<span style="background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;'
|
| 615 |
-
f'border-radius:999px;padding:2px 9px;font-size:11px;">'
|
| 616 |
-
f'{"Χ’Χ‘Χ§" if parse.get("client_is_business") else "Χ€Χ¨ΧΧ"}</span>'
|
| 617 |
-
f'<span style="background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;'
|
| 618 |
-
f'border-radius:999px;padding:2px 9px;font-size:11px;">{len(items)} Χ€Χ¨ΧΧΧΧ</span>')
|
| 619 |
-
|
| 620 |
-
alloc_line = ""
|
| 621 |
-
if completed.get("allocation_required"):
|
| 622 |
-
alloc_line = ('<div style="font-size:12px;color:#334155;padding:3px 0;">'
|
| 623 |
-
'ΧΧ‘Χ€Χ¨ ΧΧ§Χ¦ΧΧ <span style="color:#0d9488;">β Χ ΧΧ¨Χ©</span> '
|
| 624 |
-
'(ΧͺΧ ΧΧ: ΧΧ©ΧΧΧ ΧΧͺ Β· Χ’Χ‘Χ§ Β· β₯ Χ‘Χ£)</div>')
|
| 625 |
-
|
| 626 |
-
return f"""
|
| 627 |
-
<div dir="rtl" style="font-family:'Segoe UI',Arial,sans-serif;max-width:660px;
|
| 628 |
-
margin:14px auto 0;display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
| 629 |
-
|
| 630 |
-
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;">
|
| 631 |
-
<div style="font-size:11px;font-weight:700;color:#0d9488;letter-spacing:.05em;
|
| 632 |
-
text-transform:uppercase;margin-bottom:10px;">π§ ΧΧΧΧΧ ΧΧΧΧ₯</div>
|
| 633 |
-
<div style="display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px;">{chips}</div>
|
| 634 |
-
{item_rows}
|
| 635 |
-
<div style="font-size:10px;color:#94a3b8;margin-top:10px;border-top:1px dashed #e2e8f0;
|
| 636 |
-
padding-top:8px;">gemma-2-2b + LoRA Β· ΧΧΧΧ Χ¨Χ§ <code>parse</code></div>
|
| 637 |
-
</div>
|
| 638 |
-
|
| 639 |
-
<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:14px 16px;">
|
| 640 |
-
<div style="font-size:11px;font-weight:700;color:#7c3aed;letter-spacing:.05em;
|
| 641 |
-
text-transform:uppercase;margin-bottom:10px;">βοΈ ΧΧΧ ΧΧ’ ΧΧΧ©Χ</div>
|
| 642 |
-
<div style="font-size:12px;color:#334155;padding:3px 0;">ΧΧ’"Χ {int(round(completed.get("vat_rate",0)*100))}% Β·
|
| 643 |
-
βͺ{completed.get("vat_amount",0):,.2f}</div>
|
| 644 |
-
<div style="font-size:12px;color:#334155;padding:3px 0;">Χ‘Χ"Χ βͺ{completed.get("total",0):,.2f}</div>
|
| 645 |
-
<div style="font-size:12px;color:#334155;padding:3px 0;">ΧΧ‘Χ³ ΧΧ‘ΧΧ {completed.get("serial_number","")}</div>
|
| 646 |
-
{alloc_line}
|
| 647 |
-
<div style="font-size:10px;color:#94a3b8;margin-top:10px;border-top:1px dashed #e2e8f0;
|
| 648 |
-
padding-top:8px;">ΧΧΧ¨ΧΧΧ ΧΧ‘ΧΧ Β· ΧΧ€Χ‘ ΧΧΧΧΧͺ ΧΧΧ©ΧΧΧ</div>
|
| 649 |
-
</div>
|
| 650 |
-
</div>"""
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 655 |
# 7. UI β Gradio 6.17 compatible, ChatGPT/Claude style
|
| 656 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 430 |
|
| 431 |
def decide_fiscal(text, issuer_status, parse, explicit_doc_choice,
|
| 432 |
clarify_vat_basis=None, clarify_client_tax_id=None):
|
| 433 |
+
"""Returns (issuer_dict, final_parse, questions_dict)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
today = _today()
|
| 435 |
|
| 436 |
+
# ββ 1. Issuer required first β no model run needed if missing ββββββββββββ
|
| 437 |
if not issuer_status:
|
| 438 |
+
return None, None, {"issuer_status": "ΧΧΧ¨ Χ‘ΧΧΧΧ‘ ΧΧ Χ€ΧΧ§ ΧΧΧ’ΧΧ (Χ’ΧΧ‘Χ§ Χ€ΧΧΧ¨ / Χ’ΧΧ‘Χ§ ΧΧΧ¨Χ©Χ)."}
|
| 439 |
+
|
| 440 |
+
questions = {}
|
| 441 |
exempt = (issuer_status == "Χ€ΧΧΧ¨")
|
| 442 |
issuer = {**DEMO_ISSUER_BASE,
|
| 443 |
"status": "exempt_dealer" if exempt else "authorized_dealer",
|
| 444 |
"is_company": False}
|
| 445 |
|
| 446 |
+
# ββ 2. Mandatory content ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 447 |
if not parse.get("client_name", "").strip():
|
| 448 |
questions["client_name"] = "Χ©Χ ΧΧΧ§ΧΧ / ΧΧΧ©ΧΧ?"
|
| 449 |
if not parse.get("items"):
|
| 450 |
questions["items"] = "ΧΧ ΧΧ©ΧΧ¨ΧΧͺ/ΧΧΧΧ¦Χ¨ ΧΧΧΧΧΧ Χ‘ΧΧΧ? (ΧΧΧ©Χ: ΧΧΧ’ΧΧ₯ Χ’Χ‘Χ§Χ βͺ500)"
|
| 451 |
|
| 452 |
+
# ββ 3. Date βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 453 |
parse = dict(parse)
|
| 454 |
+
parse.setdefault("date", _detect_date_from_text(text) or today.isoformat())
|
| 455 |
|
| 456 |
+
# ββ 4. Doc type + VAT basis βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 457 |
+
is_gross = _text_has(text, _GROSS_PATTERNS) or clarify_vat_basis == "ΧΧΧΧ ΧΧ’Χ΄Χ"
|
| 458 |
+
is_net = _text_has(text, _NET_PATTERNS) or clarify_vat_basis == "ΧΧ€Χ Χ ΧΧ’Χ΄Χ"
|
| 459 |
has_vat_cue = is_gross or is_net
|
| 460 |
|
| 461 |
if exempt:
|
| 462 |
+
doc_type, amount_basis = "receipt", "net"
|
| 463 |
+
|
|
|
|
| 464 |
elif explicit_doc_choice and explicit_doc_choice != "ΧΧΧΧΧ ΧΧΧΧΧΧΧ":
|
| 465 |
+
doc_type = {"Χ§ΧΧΧ": "receipt", "ΧΧ©ΧΧΧ ΧΧͺ ΧΧ‘": "tax_invoice",
|
| 466 |
+
"ΧΧ©ΧΧΧ ΧΧͺ ΧΧ‘ ΧΧ§ΧΧΧ": "tax_invoice_receipt"}.get(explicit_doc_choice, "receipt")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
if doc_type != "receipt":
|
| 468 |
+
if is_gross:
|
| 469 |
+
amount_basis = "gross"
|
| 470 |
+
elif is_net:
|
| 471 |
+
amount_basis = "net"
|
| 472 |
+
elif clarify_vat_basis in (None, "ΧΧ Χ¦ΧΧΧ"):
|
| 473 |
+
total = sum(i.get("unit_price",0)*i.get("quantity",1) for i in parse.get("items",[]))
|
| 474 |
+
questions["vat_basis"] = f"ΧΧ‘ΧΧΧ (βͺ{total:,.0f}) β ΧΧΧΧ ΧΧ’Χ΄Χ ΧΧ ΧΧ€Χ Χ ΧΧ’Χ΄Χ?"
|
| 475 |
+
amount_basis = "net"
|
| 476 |
else:
|
| 477 |
amount_basis = "net"
|
| 478 |
else:
|
| 479 |
amount_basis = "net"
|
| 480 |
else:
|
| 481 |
+
# Auto: VAT cue β tax_invoice_receipt; no cue β receipt (no VAT)
|
| 482 |
if has_vat_cue:
|
| 483 |
+
doc_type, amount_basis = "tax_invoice_receipt", "gross" if is_gross else "net"
|
|
|
|
| 484 |
else:
|
| 485 |
+
doc_type, amount_basis = "receipt", "net"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
|
| 487 |
+
# ββ 5. Allocation: Χ.Χ€. only for ΧΧ’"Χ/ΧΧΧ¨Χ clients + large amounts βββββββ
|
| 488 |
if doc_type in ("tax_invoice", "tax_invoice_receipt") and not exempt:
|
| 489 |
+
subtotal = sum(it.get("unit_price",0)*it.get("quantity",1) for it in parse.get("items",[]))
|
| 490 |
+
threshold = core.allocation_threshold_for_date(_dt.date.fromisoformat(parse["date"]))
|
| 491 |
+
name = parse.get("client_name", "")
|
| 492 |
+
is_corp = any(kw in name for kw in ['ΧΧ’"Χ', "ΧΧ’Χ", "ΧΧΧ¨Χ", "ΧΧ’'Χ", "ΧΧ’.Χ"])
|
| 493 |
+
if is_corp and subtotal >= threshold:
|
| 494 |
+
if clarify_client_tax_id and clarify_client_tax_id.strip():
|
|
|
|
|
|
|
| 495 |
parse["client_tax_id"] = clarify_client_tax_id.strip()
|
| 496 |
+
elif not parse.get("client_tax_id"):
|
| 497 |
+
questions["client_tax_id"] = f"Χ.Χ€. ΧΧΧ§ΧΧ (Χ ΧΧ¨Χ© ΧΧΧ§Χ¦ΧΧ β Χ’Χ‘Χ§Χ β₯ βͺ{threshold:,})"
|
|
|
|
| 498 |
|
| 499 |
+
parse.update({"doc_type": doc_type, "amount_basis": amount_basis})
|
|
|
|
| 500 |
parse.setdefault("payment_method", "bank_transfer")
|
| 501 |
parse.setdefault("currency", "ILS")
|
| 502 |
parse.setdefault("client_tax_id", None)
|
|
|
|
| 532 |
|
| 533 |
def generate(note, issuer_radio, doc_choice,
|
| 534 |
clarify_vat, clarify_tax_id):
|
| 535 |
+
"""Single-shot, stateless pipeline."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
note = (note or "").strip()
|
| 537 |
if not note:
|
| 538 |
return ("βοΈ ΧΧͺΧΧ Χ€ΧͺΧ§ ΧΧΧ Χ‘Χ ΧΧΧΧ₯ ΧΧ€Χ§ ΧΧ‘ΧΧ.",
|
| 539 |
"", "", "", "", False)
|
| 540 |
|
| 541 |
+
issuer_status = issuer_radio if issuer_radio in ("Χ€ΧΧΧ¨", "ΧΧΧ¨Χ©Χ") else None
|
| 542 |
+
|
| 543 |
+
# ββ Check issuer BEFORE running the model (saves 90s on CPU) βββββββββββββ
|
| 544 |
+
if not issuer_status:
|
| 545 |
+
return ("π€ ΧΧΧ¨ ΧͺΧΧΧΧ: **Χ’ΧΧ‘Χ§ Χ€ΧΧΧ¨** ΧΧ **Χ’ΧΧ‘Χ§ ΧΧΧ¨Χ©Χ** ΧΧΧ’ΧΧ.",
|
| 546 |
+
"", "", "", "", False)
|
| 547 |
+
|
| 548 |
_lazy_init()
|
| 549 |
parse = model_parse(note)
|
| 550 |
|
|
|
|
| 553 |
"'Χ§ΧΧΧΧͺΧ 500βͺ ΧΧΧ©Χ Χ’Χ ΧΧΧ’ΧΧ₯'.",
|
| 554 |
"", "", "", "", False)
|
| 555 |
|
| 556 |
+
cvat = clarify_vat if clarify_vat not in (None, "ΧΧ Χ¦ΧΧΧ") else None
|
|
|
|
|
|
|
|
|
|
| 557 |
ctax = clarify_tax_id.strip() if clarify_tax_id and clarify_tax_id.strip() else None
|
| 558 |
|
| 559 |
issuer, final, questions = decide_fiscal(
|
|
|
|
| 561 |
|
| 562 |
if questions:
|
| 563 |
msg = _build_clarification_message(questions, note)
|
| 564 |
+
return (msg, "", "", "", "", True)
|
| 565 |
|
| 566 |
try:
|
| 567 |
completed = core.complete(issuer, final, _STATE["rng"])
|
| 568 |
except Exception as e:
|
| 569 |
return (f"β οΈ Χ©ΧΧΧΧ: {e}", "", "", "", "", False)
|
| 570 |
|
|
|
|
| 571 |
show_vat = (final["doc_type"] != "receipt")
|
| 572 |
|
| 573 |
return ("β
ΧΧΧ‘ΧΧ ΧΧΧ€Χ§ ΧΧΧ¦ΧΧΧ.",
|
|
|
|
| 577 |
render_recommendations(recommend(note)),
|
| 578 |
False)
|
| 579 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 581 |
# 7. UI β Gradio 6.17 compatible, ChatGPT/Claude style
|
| 582 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|