"""What the API documents for agents: /gradio_api/info, which the Space's agents.md points to. - texture_size: Gradio's clients render every enum as strings (python_type Literal['1024', '2048'], "2048" in the code snippets), so the documented choices are the strings "1024" and "2048". The numbers 1024 and 2048 are accepted too. - Endpoint descriptions: parameters, timing, auth, and where the GLB is in the result. - Code snippets: the bash one is generated with the public host (Gradio uses the host of the first /info request, which on Spaces is often http://localhost:7860), sends the HF token, and keeps EVENT_ID in bash (Gradio's `| read EVENT_ID` runs in a subshell there, so it is empty). """ import os import re import gradio as gr import gradio.routes TEXTURE_SIZES = ("1024", "2048") class TextureSize(gr.Radio): """The texture size radio (create it with choices TEXTURE_SIZES); passes the size to the function as an int. No __init__ of its own: Gradio builds a component's config from its constructor's parameters.""" is_template = True # rendered by the frontend as a gr.Radio def preprocess(self, payload): if payload is None: payload = self.value if isinstance(payload, (int, float)) and not isinstance(payload, bool) and float(payload).is_integer(): payload = str(int(payload)) elif isinstance(payload, str): payload = payload.strip() if payload not in TEXTURE_SIZES: raise gr.Error(f'texture_size must be "1024" or "2048" (got {payload!r}).') return int(super().preprocess(payload)) def api_info(self): return { **super().api_info(), "description": 'texture resolution in pixels: "1024" or "2048" (the numbers 1024 and 2048 work too)', } # name: (steps, faces, texture_size, end-to-end time). Times measured on the Space (2026-10-16) for # a rocking chair, a bonsai and an espresso machine; detailed objects take the longer times. PRESETS = { "Fast": (8, 100_000, "1024", "1-3 min"), "Balanced": (12, 300_000, "2048", "1.5-4 min"), "Quality": (20, 300_000, "2048", "1.5-5.5 min"), } _COMMON = ( "faces: 100000-500000, default 300000. " 'texture_size: "1024" or "2048", default "2048". ' "steps: 8-20, default 12 (20 is the paper setting, slower). " "Presets, each verified end to end: " + "; ".join( f'{name.lower()}: steps {steps}, faces {faces}, texture_size "{texture}" ({time})' for name, (steps, faces, texture, time) in PRESETS.items() ) + ". Balanced is the default; detailed objects take the longer times. " "Runs use the caller's ZeroGPU quota, so send your " "Hugging Face token (Authorization: Bearer $HF_TOKEN). " "Poll GET /gradio_api/call/{endpoint}/{event_id}: it streams `generating` updates for the web page, then " "`event: complete` with data = [progress HTML, 3D viewer state, GLB file, seed used]. " "Download the model from data[2].url. A failure ends the stream with `event: error` and a message." ) TEXT_API = ( "Text to a textured 3D model (GLB): Krea 2 draws the object, Qwen-Image-Edit-2511 generates its " "right, back and left views, and UniForge3D builds the model from all four. " 'subject (required): the object, e.g. "a red vintage rotary telephone". ' "seed: integer, used only when randomize is false. randomize: default true. " + _COMMON ) IMAGE_API = ( "One image of a single object to a textured 3D model (GLB): the background is removed " "automatically, Qwen-Image-Edit-2511 generates the right, back and left views, and UniForge3D " "builds the model from all four. " "image (required): a photo or render of the object seen from the front (PNG, JPEG or WebP). " "Upload it with POST /gradio_api/upload -F files=@object.jpg and pass " '{"path": "", "meta": {"_type": "gradio.FileData"}}, or pass a public image URL ' 'as {"path": "https://...", "meta": {"_type": "gradio.FileData"}}. ' "seed: integer, default 42, used only when randomize is false. randomize: default false. " + _COMMON ) AUTH = '-H "Authorization: Bearer $HF_TOKEN"' _BASH_CALL = re.compile( r'curl -X POST (?P\S+) -s -H "Content-Type: application/json" \\\n' r"(?P -d '.*') \\\n" r" \| awk -F'\"' '\{ print \$4\}' \\\n" r" \| read EVENT_ID; curl -N (?P\S+)$" ) def _fix_bash(snippet): match = _BASH_CALL.search(snippet) if not match: # a Gradio version with another template: keep it return snippet call = ( f'EVENT_ID=$(curl -s -X POST {match["post"]} -H "Content-Type: application/json" {AUTH} \\\n' f'{match["data"]} \\\n' " | awk -F'\"' '{ print $4}')\n" f'curl -sS -N {AUTH} {match["get"]}' ) snippet = snippet[: match.start()] + call return snippet.replace("FILE_PATH=$(curl -s -X POST ", f"FILE_PATH=$(curl -s {AUTH} -X POST ", 1) def document_only(blocks, endpoints): """Keep the API docs to `endpoints`. While gr.Examples caches, Gradio registers a public endpoint named after the example function, and /info is computed once, on the first request, so whether such endpoints were documented depended on timing.""" get_api_info = blocks.get_api_info def api_info(all_endpoints=False): info = get_api_info(all_endpoints) if not all_endpoints: info["named_endpoints"] = {k: v for k, v in info["named_endpoints"].items() if k in endpoints} info["unnamed_endpoints"] = {} return info blocks.get_api_info = api_info _generate_code_snippets = gradio.routes.generate_code_snippets def _code_snippets(api_name, endpoint_info, root, space_id=None, api_prefix="/"): host = os.environ.get("SPACE_HOST") if host: root = f"https://{host}" snippets = _generate_code_snippets(api_name, endpoint_info, root, space_id=space_id, api_prefix=api_prefix) snippets["bash"] = _fix_bash(snippets["bash"]) return snippets gradio.routes.generate_code_snippets = _code_snippets