Spaces:
Sleeping
Sleeping
| import os | |
| import tempfile | |
| from pathlib import Path | |
| from typing import Any | |
| from urllib.parse import urlparse, unquote | |
| from urllib.request import Request, urlopen | |
| import gradio as gr | |
| from parsers import pdf_to_json, image_to_json, xlsx_to_json | |
| IMAGE_EXTS = {"png", "jpg", "jpeg", "webp", "bmp", "tiff", "gif"} | |
| XLSX_EXTS = {"xlsx", "xlsm"} | |
| def _get_path(file_obj: Any) -> str: | |
| if file_obj is None: | |
| return None | |
| if isinstance(file_obj, str): | |
| return file_obj | |
| # gr.File may pass a dict or a file-like object | |
| if hasattr(file_obj, "name"): | |
| return getattr(file_obj, "name") | |
| if isinstance(file_obj, dict): | |
| return file_obj.get("name") or file_obj.get("tmp_path") or file_obj.get("tempfile") | |
| return None | |
| def _dispatch(path: str) -> dict: | |
| """Route a local file path to the matching parser based on its extension.""" | |
| suffix = Path(path).suffix.lower().lstrip(".") | |
| if suffix == "pdf": | |
| return pdf_to_json(path) | |
| if suffix in IMAGE_EXTS: | |
| return image_to_json(path) | |
| if suffix in XLSX_EXTS: | |
| return xlsx_to_json(path) | |
| return {"error": f"unsupported file type: .{suffix}"} | |
| def _download_to_temp(url: str) -> str: | |
| """Fetch a URL to a temp file, preserving the extension for dispatch.""" | |
| req = Request(url, headers={"User-Agent": "file-to-json"}) | |
| with urlopen(req, timeout=30) as resp: | |
| data = resp.read() | |
| suffix = Path(unquote(urlparse(url).path)).suffix | |
| fd, tmp = tempfile.mkstemp(suffix=suffix) | |
| with os.fdopen(fd, "wb") as f: | |
| f.write(data) | |
| return tmp | |
| def parse_file_url(file_url: str) -> dict: | |
| """Parse a PDF, image, or Excel file into JSON. | |
| Args: | |
| file_url: An http(s) URL pointing to a .pdf, image, or .xlsx/.xlsm file. | |
| Returns: | |
| A JSON object containing the file's parsed contents. | |
| """ | |
| if not file_url: | |
| return {"error": "no file_url provided"} | |
| try: | |
| path = _download_to_temp(file_url) | |
| except Exception as e: | |
| return {"error": f"could not fetch url: {e}"} | |
| try: | |
| return _dispatch(path) | |
| except Exception as e: | |
| return {"error": str(e)} | |
| finally: | |
| try: | |
| os.remove(path) | |
| except OSError: | |
| pass | |
| def process_file(file_obj: Any) -> dict: | |
| """Web-UI handler: parse a file uploaded through the Gradio interface.""" | |
| path = _get_path(file_obj) | |
| if not path: | |
| return {"error": "no file provided"} | |
| try: | |
| return _dispatch(path) | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def main(): | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# MCP: PDF, Image & Excel → JSON\nUpload a PDF, image, or .xlsx file to parse into JSON.") | |
| file_in = gr.File(label="Upload PDF, Image, or Excel (.xlsx)") | |
| out = gr.JSON(label="Parsed JSON") | |
| # Web-UI only: hidden from the API/MCP so no file-upload tool is exposed. | |
| file_in.change(fn=process_file, inputs=file_in, outputs=out, api_name=False) | |
| # The MCP tool: takes a URL string (no file-input upload instructions). | |
| gr.api(parse_file_url, api_name="parse_file_url") | |
| demo.launch(mcp_server=True) | |
| if __name__ == '__main__': | |
| main() | |