"""Public demo (read-only) entry for HF Spaces — Reconciliation Workbench.
This is the PUBLIC demo build. It ships ONLY the frontend, two pre-computed sample
payloads and the demo video. The deterministic reconciliation engine and the LLM
extraction pipeline are deliberately NOT included; live PDF upload and on-the-fly
recompute are disabled (see the demo video). The two bundled samples render fully
from cached results, so the PDF ↔ live-sheet ↔ finding linkage all works.
"""
from __future__ import annotations
import json
from pathlib import Path
from urllib.parse import quote
import gradio as gr
from wbdemo import enrich
_HERE = Path(__file__).resolve().parent
_WB = _HERE / "wbdemo"
_ASSETS = _WB / "assets"
_PDFJS = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174"
# pdf.js (UMD) 注入 head:挂 window.pdfjsLib + window.__pdfReady 就绪 Promise(与全功能版一致)。
_HEAD = f"""
"""
_SAMPLES = {
"sanmu": {
"title": "Sanmu Group 2024 Annual Report (Consolidated)",
"payload": "sanmu.json", "pdf": "sanmu.pdf",
},
"prospectus": {
"title": "A Prospectus (Consolidated)",
"payload": "prospectus.json", "pdf": None,
},
}
_DEFAULT = "sanmu"
def _read(name: str) -> str:
return (_ASSETS / name).read_text(encoding="utf-8")
def _file_url(abspath: str | None) -> str | None:
if not abspath:
return None
return "/gradio_api/file=" + quote(abspath, safe="/:")
def _load_payload(fn: str) -> dict:
p = json.loads((_WB / "payloads" / fn).read_text(encoding="utf-8"))
return enrich.attach_review_cells(p) # 纯函数:挂 manual_review→cell_ids,供三联动
def _focus_table_id(payload: dict) -> str | None:
for t in payload.get("tables", []):
if t.get("kind") == "BS":
return t.get("table_id")
return None
def _initial_value() -> dict:
samples: dict = {}
for k, s in _SAMPLES.items():
payload = _load_payload(s["payload"])
pdf_abs = str(_WB / s["pdf"]) if s["pdf"] else None
samples[k] = {
"sample_key": k, "title": s["title"], "payload": payload,
"pdf_url": _file_url(pdf_abs), "focus_table_id": _focus_table_id(payload),
}
return {"samples": samples, "default": _DEFAULT}
# 公开演示为只读:上传/人工修正重算/导出均需确定性引擎(未随本演示发布),统一桩成友好提示。
_DISABLED = {
"ok": False,
"error": ("This public demo is read-only. Live PDF upload, human correction + recompute, "
"and Excel export run the deterministic engine, which isn't shipped in this public "
"demo — watch the demo video for them. Full interactive version available on request."),
}
def submit_override(req: dict) -> dict:
return dict(_DISABLED)
def process_pdf(req: dict) -> dict:
return dict(_DISABLED)
def export_xlsx(req: dict) -> dict:
return dict(_DISABLED)
def build_demo() -> gr.Blocks:
pdf_files = [str(_WB / s["pdf"]) for s in _SAMPLES.values() if s["pdf"]]
if pdf_files:
gr.set_static_paths(paths=pdf_files)
init = _initial_value()
with gr.Blocks(title="Reconciliation Workbench") as demo:
gr.HTML(
value=init,
html_template=_read("island.html"),
css_template=_read("island.css"),
js_on_load=_read("island.js"),
head=_HEAD,
apply_default_css=False,
min_height=640,
server_functions=[submit_override, process_pdf, export_xlsx],
)
return demo
demo = build_demo()
if __name__ == "__main__":
# HF Spaces(Gradio SDK)期望端口 7860——用裸 launch() 让 gradio 自动适配 Spaces 环境
# (与全功能版一致,实证可达 RUNNING)。本地调试用 GRADIO_SERVER_PORT 环境变量覆盖。
demo.launch()