| |
| """ |
| Gradio Web UI for GDL -> IR(v0.95) pipeline |
| |
| 功能: |
| - 上传 GDL 文本文件(.txt/.gdl)与可选 Schema(默认自动发现 poker_gdl_ir.schema.v0.95.zh.json) |
| - 运行 parse -> normalize -> map_to_v095 -> validate -> report 全流程 |
| - 在线预览 IR(JSON)、Issues(JSON)、Report(Markdown) |
| - 支持将结果作为文件下载 |
| """ |
| from __future__ import annotations |
|
|
| import io |
| import json |
| import pathlib |
| from typing import Tuple, Any |
| import tempfile |
|
|
| import gradio as gr |
|
|
| from gdl_parser_v2 import parse_gdl |
| from normalizer_v2 import normalize_ir |
| from mapper_v2 import map_to_v095 |
| from validator_v2 import validate_with_schema |
| from gap_detector_v2 import make_report |
|
|
|
|
| def _auto_discover_schema(cli_arg: str | None) -> pathlib.Path: |
| candidates = [] |
| if cli_arg: |
| candidates.append(cli_arg) |
| here = pathlib.Path(__file__).resolve().parent |
| candidates.append(str(here / "poker_gdl_ir.schema.v0.95.zh.json")) |
| candidates.append("poker_gdl_ir.schema.v0.95.zh.json") |
| candidates.append("/mnt/data/poker_gdl_ir.schema.v0.95.zh.json") |
| for c in candidates: |
| try: |
| p = pathlib.Path(c) |
| if p.exists(): |
| return p |
| except Exception: |
| pass |
| raise FileNotFoundError("未找到 Schema。请上传或将 poker_gdl_ir.schema.v0.95.zh.json 放在程序目录/当前目录。") |
|
|
|
|
| def _read_as_bytes(maybe_file: Any) -> bytes: |
| |
| if maybe_file is None: |
| raise gr.Error("未提供文件") |
| if isinstance(maybe_file, (bytes, bytearray)): |
| return bytes(maybe_file) |
| if isinstance(maybe_file, str): |
| return pathlib.Path(maybe_file).read_bytes() |
| if hasattr(maybe_file, "read") and callable(getattr(maybe_file, "read")): |
| return maybe_file.read() |
| if isinstance(maybe_file, dict): |
| data = maybe_file.get("data") |
| if isinstance(data, (bytes, bytearray)): |
| return bytes(data) |
| raise gr.Error("无法读取上传的文件内容") |
|
|
|
|
| def run_pipeline(gdl_file: Any, schema_file: Any | None) -> Tuple[str, str, str, bytes, bytes, bytes]: |
| if gdl_file is None: |
| raise gr.Error("请先上传 GDL 文件") |
|
|
| |
| gdl_bytes = _read_as_bytes(gdl_file) |
| try: |
| gdl_txt = gdl_bytes.decode("utf-8") |
| except Exception: |
| |
| for enc in ("utf-8-sig", "gbk", "latin-1"): |
| try: |
| gdl_txt = gdl_bytes.decode(enc) |
| break |
| except Exception: |
| continue |
| else: |
| raise gr.Error("无法解码 GDL 文本,请确认编码为 UTF-8") |
|
|
| |
| gdl_ast = parse_gdl(gdl_txt) |
| nz = normalize_ir(gdl_ast, gdl_txt) |
| ir = map_to_v095(nz, gdl_txt) |
|
|
| |
| if schema_file is not None: |
| try: |
| schema_bytes = _read_as_bytes(schema_file) |
| tmp = tempfile.NamedTemporaryFile(suffix=".json", delete=False) |
| tmp.write(schema_bytes) |
| tmp.flush() |
| tmp.close() |
| schema_path = pathlib.Path(tmp.name) |
| except Exception: |
| raise gr.Error("读取 Schema 失败,请确认文件格式正确") |
| else: |
| try: |
| schema_path = _auto_discover_schema(None) |
| except FileNotFoundError as e: |
| raise gr.Error(str(e)) |
|
|
| |
| ret = validate_with_schema(ir, str(schema_path)) |
| if isinstance(ret, tuple) and len(ret) == 3: |
| ok, issues, schema_msg = ret |
| elif isinstance(ret, tuple) and len(ret) == 2: |
| ok, issues = ret |
| schema_msg = f"Schema: {'OK' if ok else 'FAIL'}; issues={len(issues)}" |
| else: |
| ok = bool(ret) |
| issues = [] |
| schema_msg = f"Schema: {'OK' if ok else 'FAIL'}" |
|
|
| report_md = make_report(ir) |
|
|
| ir_json_str = json.dumps(ir, ensure_ascii=False, indent=2) |
| issues_json_str = json.dumps(issues, ensure_ascii=False, indent=2) |
|
|
| |
| ir_bytes = ir_json_str.encode("utf-8") |
| issues_bytes = issues_json_str.encode("utf-8") |
| report_bytes = report_md.encode("utf-8") |
|
|
| return ( |
| ir_json_str, |
| issues_json_str, |
| report_md, |
| ir_bytes, |
| issues_bytes, |
| report_bytes, |
| ) |
|
|
|
|
| def _save_temp(name_hint: str, data: bytes) -> str: |
| suffix = pathlib.Path(name_hint).suffix or "" |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix) |
| tmp.write(data) |
| tmp.flush() |
| tmp.close() |
| return tmp.name |
|
|
|
|
| with gr.Blocks(title="GDL → IR(v0.95) Web UI") as demo: |
| gr.Markdown(""" |
| **GDL → IR(v0.95) 一键转换与校验** |
| - 上传 GDL 文本文件(.txt/.gdl) |
| - 可选上传 Schema(若不上传,将自动发现同目录中的 `poker_gdl_ir.schema.v0.95.zh.json`) |
| - 生成:IR(JSON)、Issues(JSON)、自检报告(Markdown) |
| """ |
| ) |
|
|
| with gr.Row(): |
| gdl_in = gr.File(label="GDL 文件 (.txt/.gdl)", file_types=[".txt", ".gdl"], type="binary") |
| schema_in = gr.File(label="Schema 文件 (可选)", file_types=[".json"], type="binary") |
|
|
| run_btn = gr.Button("运行转换与校验") |
|
|
| with gr.Tab("IR 预览"): |
| ir_json = gr.Code(label="IR (v0.95)", language="json") |
| ir_dl = gr.File(label="下载 IR JSON", interactive=False) |
|
|
| with gr.Tab("Issues 预览"): |
| issues_json = gr.Code(label="Schema 校验问题", language="json") |
| issues_dl = gr.File(label="下载 Issues JSON", interactive=False) |
|
|
| with gr.Tab("自检报告"): |
| report_md = gr.Markdown() |
| report_dl = gr.File(label="下载 自检报告.md", interactive=False) |
|
|
| def _on_click(gdl_file, schema_file): |
| ir_str, issues_str, report_str, ir_b, issues_b, report_b = run_pipeline(gdl_file, schema_file) |
| ir_path = _save_temp("ir.v0.95.json", ir_b) |
| issues_path = _save_temp("issues.json", issues_b) |
| report_path = _save_temp("selfcheck.md", report_b) |
| return ir_str, ir_path, issues_str, issues_path, report_str, report_path |
|
|
| run_btn.click( |
| _on_click, |
| inputs=[gdl_in, schema_in], |
| outputs=[ir_json, ir_dl, issues_json, issues_dl, report_md, report_dl] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=True) |
|
|
|
|
|
|