chore: import upstream snapshot with attribution
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
from gradio_client.client import Client
|
||||
from gradio_client.data_classes import FileData
|
||||
from gradio_client.utils import __version__, file, handle_file
|
||||
|
||||
__all__ = [
|
||||
"Client",
|
||||
"file",
|
||||
"handle_file",
|
||||
"FileData",
|
||||
"__version__",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
|
||||
class FileData(TypedDict):
|
||||
name: str | None # filename
|
||||
data: str | None # base64 encoded data
|
||||
size: NotRequired[int | None] # size in bytes
|
||||
is_file: NotRequired[
|
||||
bool
|
||||
] # whether the data corresponds to a file or base64 encoded data
|
||||
orig_name: NotRequired[str] # original filename
|
||||
mime_type: NotRequired[str]
|
||||
is_stream: NotRequired[bool]
|
||||
|
||||
|
||||
class ParameterInfo(TypedDict):
|
||||
label: str
|
||||
parameter_name: str
|
||||
parameter_has_default: NotRequired[bool]
|
||||
parameter_default: NotRequired[Any]
|
||||
type: dict
|
||||
python_type: dict
|
||||
component: str
|
||||
example_input: Any
|
||||
@@ -0,0 +1,371 @@
|
||||
"""Contains methods that generate documentation for Gradio functions and classes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import inspect
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from functools import lru_cache
|
||||
|
||||
classes_to_document = defaultdict(list)
|
||||
classes_inherit_documentation = {}
|
||||
|
||||
|
||||
def set_documentation_group(m): # noqa: ARG001
|
||||
"""A no-op for backwards compatibility of custom components published prior to 4.16.0"""
|
||||
pass
|
||||
|
||||
|
||||
def extract_instance_attr_doc(cls, attr):
|
||||
code = inspect.getsource(cls.__init__)
|
||||
lines = [line.strip() for line in code.split("\n")]
|
||||
i = None
|
||||
for i, line in enumerate(lines): # noqa: B007
|
||||
if line.startswith("self." + attr + ":") or line.startswith(
|
||||
"self." + attr + " ="
|
||||
):
|
||||
break
|
||||
if i is None:
|
||||
raise NameError(f"Could not find {attr} in {cls.__name__}")
|
||||
start_line = lines.index('"""', i)
|
||||
end_line = lines.index('"""', start_line + 1)
|
||||
for j in range(i + 1, start_line):
|
||||
if lines[j].startswith("self."):
|
||||
raise ValueError(
|
||||
f"Found another attribute before docstring for {attr} in {cls.__name__}: "
|
||||
+ lines[j]
|
||||
+ "\n start:"
|
||||
+ lines[i]
|
||||
)
|
||||
doc_string = " ".join(lines[start_line + 1 : end_line])
|
||||
return doc_string
|
||||
|
||||
|
||||
_module_prefixes = [
|
||||
("gradio._simple_templates", "component"),
|
||||
("gradio.server", "block"),
|
||||
("gradio.block", "block"),
|
||||
("gradio.chat", "chatinterface"),
|
||||
("gradio.component", "component"),
|
||||
("gradio.events", "helpers"),
|
||||
("gradio.data_classes", "helpers"),
|
||||
("gradio.exceptions", "helpers"),
|
||||
("gradio.external", "helpers"),
|
||||
("gradio.flag", "flagging"),
|
||||
("gradio.helpers", "helpers"),
|
||||
("gradio.interface", "interface"),
|
||||
("gradio.layout", "layout"),
|
||||
("gradio.route", "routes"),
|
||||
("gradio.theme", "themes"),
|
||||
("gradio_client.", "py-client"),
|
||||
("gradio.utils", "helpers"),
|
||||
("gradio.renderable", "renderable"),
|
||||
("gradio.validators", "validators"),
|
||||
("gradio.caching", "helpers"),
|
||||
]
|
||||
|
||||
|
||||
@lru_cache(maxsize=10)
|
||||
def _get_module_documentation_group(modname) -> str:
|
||||
for prefix, group in _module_prefixes:
|
||||
if modname.startswith(prefix):
|
||||
return group
|
||||
raise ValueError(f"No known documentation group for module {modname!r}")
|
||||
|
||||
|
||||
def document(*fns, inherit=False, documentation_group=None):
|
||||
"""
|
||||
Defines the @document decorator which adds classes or functions to the Gradio
|
||||
documentation at www.gradio.app/docs.
|
||||
|
||||
Usage examples:
|
||||
- Put @document() above a class to document the class and its constructor.
|
||||
- Put @document("fn1", "fn2") above a class to also document methods fn1 and fn2.
|
||||
- Put @document("*fn3") with an asterisk above a class to document the instance attribute methods f3.
|
||||
"""
|
||||
_documentation_group = documentation_group
|
||||
|
||||
def inner_doc(cls):
|
||||
functions = list(fns)
|
||||
if hasattr(cls, "EVENTS"):
|
||||
functions += cls.EVENTS
|
||||
if inherit:
|
||||
classes_inherit_documentation[cls] = None
|
||||
|
||||
documentation_group = _documentation_group # avoid `nonlocal` reassignment
|
||||
if _documentation_group is None:
|
||||
try:
|
||||
modname = inspect.getmodule(cls).__name__ # type: ignore
|
||||
if modname.startswith("gradio.") or modname.startswith(
|
||||
"gradio_client."
|
||||
):
|
||||
documentation_group = _get_module_documentation_group(modname)
|
||||
else:
|
||||
# Then this is likely a custom Gradio component that we do not include in the documentation
|
||||
pass
|
||||
except Exception as exc:
|
||||
warnings.warn(f"Could not get documentation group for {cls}: {exc}")
|
||||
classes_to_document[documentation_group].append((cls, functions))
|
||||
return cls
|
||||
|
||||
return inner_doc
|
||||
|
||||
|
||||
def document_fn(fn: Callable, cls) -> tuple[str, list[dict], dict, str | None]:
|
||||
"""
|
||||
Generates documentation for any function.
|
||||
Parameters:
|
||||
fn: Function to document
|
||||
Returns:
|
||||
description: General description of fn
|
||||
parameters: A list of dicts for each parameter, storing data for the parameter name, annotation and doc
|
||||
return: A dict storing data for the returned annotation and doc
|
||||
example: Code for an example use of the fn
|
||||
"""
|
||||
doc_str = inspect.getdoc(fn) or ""
|
||||
doc_lines = doc_str.split("\n")
|
||||
signature = inspect.signature(fn)
|
||||
description, parameters, returns, examples = [], {}, [], []
|
||||
mode = "description"
|
||||
current_parameter = None
|
||||
base_indent = None
|
||||
for line in doc_lines:
|
||||
line = line.rstrip()
|
||||
if line == "Parameters:":
|
||||
mode = "parameter"
|
||||
base_indent = None
|
||||
elif line.startswith("Example:"):
|
||||
mode = "example"
|
||||
base_indent = None
|
||||
if "(" in line and ")" in line:
|
||||
c = line.split("(")[1].split(")")[0]
|
||||
if c != cls.__name__:
|
||||
mode = "ignore"
|
||||
elif line == "Returns:":
|
||||
mode = "return"
|
||||
base_indent = None
|
||||
else:
|
||||
if mode == "description":
|
||||
description.append(line if line.strip() else "<br>")
|
||||
continue
|
||||
if not (line.startswith(" ") or line.strip() == ""):
|
||||
print(line)
|
||||
if not (line.startswith(" ") or line.strip() == ""):
|
||||
raise SyntaxError(
|
||||
f"Documentation format for {fn.__name__} has format error in line: {line}"
|
||||
)
|
||||
line = line[4:]
|
||||
if mode == "parameter":
|
||||
if ": " in line and not line.startswith(" "):
|
||||
colon_index = line.index(": ")
|
||||
if colon_index < -1:
|
||||
raise SyntaxError(
|
||||
f"Documentation format for {fn.__name__} has format error in line: {line}"
|
||||
)
|
||||
current_parameter = line[:colon_index]
|
||||
parameter_doc = line[colon_index + 2 :]
|
||||
parameters[current_parameter] = parameter_doc
|
||||
base_indent = None
|
||||
elif current_parameter and line.strip():
|
||||
if base_indent is None:
|
||||
base_indent = len(line) - len(line.lstrip())
|
||||
if base_indent > 0 and line.startswith(" " * base_indent):
|
||||
line = line[base_indent:]
|
||||
parameters[current_parameter] += "\n" + line
|
||||
elif mode == "return":
|
||||
returns.append(line)
|
||||
elif mode == "example":
|
||||
examples.append(line)
|
||||
description_doc = " ".join(description)
|
||||
parameter_docs = []
|
||||
for param_name, param in signature.parameters.items():
|
||||
if param_name.startswith("_"):
|
||||
continue
|
||||
if param_name == "self":
|
||||
continue
|
||||
if param_name in ["kwargs", "args"] and param_name not in parameters:
|
||||
continue
|
||||
parameter_doc = {
|
||||
"name": param_name,
|
||||
"annotation": param.annotation,
|
||||
"doc": parameters.get(param_name),
|
||||
}
|
||||
if param_name in parameters:
|
||||
del parameters[param_name]
|
||||
if param.default != inspect.Parameter.empty:
|
||||
default = param.default
|
||||
if isinstance(default, str):
|
||||
default = '"' + default + '"'
|
||||
if default.__class__.__module__ != "builtins":
|
||||
default = f"{default.__class__.__name__}()"
|
||||
parameter_doc["default"] = default
|
||||
elif parameter_doc["doc"] is not None:
|
||||
if "kwargs" in parameter_doc["doc"]:
|
||||
parameter_doc["kwargs"] = True
|
||||
if "args" in parameter_doc["doc"]:
|
||||
parameter_doc["args"] = True
|
||||
parameter_docs.append(parameter_doc)
|
||||
if parameters:
|
||||
raise ValueError(
|
||||
f"Documentation format for {fn.__name__} documents "
|
||||
f"nonexistent parameters: {', '.join(parameters.keys())}. "
|
||||
f"Valid parameters: {', '.join(signature.parameters.keys())}"
|
||||
)
|
||||
if len(returns) == 0:
|
||||
return_docs = {}
|
||||
else:
|
||||
return_doc_text = "\n".join(returns)
|
||||
return_docs = {
|
||||
"annotation": signature.return_annotation,
|
||||
"doc": return_doc_text,
|
||||
}
|
||||
examples_doc = "\n".join(examples) if len(examples) > 0 else None
|
||||
return description_doc, parameter_docs, return_docs, examples_doc
|
||||
|
||||
|
||||
def document_cls(cls):
|
||||
doc_str = inspect.getdoc(cls)
|
||||
if doc_str is None:
|
||||
return "", {}, ""
|
||||
tags = {}
|
||||
description_lines = []
|
||||
mode = "description"
|
||||
for line in doc_str.split("\n"):
|
||||
line = line.rstrip()
|
||||
if line.endswith(":") and " " not in line:
|
||||
mode = line[:-1].lower()
|
||||
tags[mode] = []
|
||||
elif line.split(" ")[0].endswith(":") and not line.startswith(" "):
|
||||
tag = line[: line.index(":")].lower()
|
||||
value = line[line.index(":") + 2 :]
|
||||
tags[tag] = value
|
||||
elif mode == "description":
|
||||
description_lines.append(line if line.strip() else "<br>")
|
||||
else:
|
||||
if not (line.startswith(" ") or not line.strip()):
|
||||
raise SyntaxError(
|
||||
f"Documentation format for {cls.__name__} has format error in line: {line}"
|
||||
)
|
||||
tags[mode].append(line[4:])
|
||||
if "example" in tags:
|
||||
example = "\n".join(tags["example"])
|
||||
del tags["example"]
|
||||
else:
|
||||
example = None
|
||||
for key, val in tags.items():
|
||||
if isinstance(val, list):
|
||||
tags[key] = "<br>".join(val)
|
||||
description = " ".join(description_lines).replace("\n", "<br>")
|
||||
return description, tags, example
|
||||
|
||||
|
||||
def generate_documentation():
|
||||
documentation = {}
|
||||
for mode, class_list in classes_to_document.items():
|
||||
documentation[mode] = []
|
||||
for cls, fns in class_list:
|
||||
fn_to_document = (
|
||||
cls
|
||||
if inspect.isfunction(cls) or dataclasses.is_dataclass(cls)
|
||||
else cls.__init__
|
||||
)
|
||||
_, parameter_doc, return_doc, _ = document_fn(fn_to_document, cls)
|
||||
if (
|
||||
hasattr(cls, "preprocess")
|
||||
and callable(cls.preprocess) # type: ignore
|
||||
and hasattr(cls, "postprocess")
|
||||
and callable(cls.postprocess) # type: ignore
|
||||
):
|
||||
preprocess_doc = document_fn(cls.preprocess, cls) # type: ignore
|
||||
postprocess_doc = document_fn(cls.postprocess, cls) # type: ignore
|
||||
preprocess_doc, postprocess_doc = (
|
||||
{
|
||||
"parameter_doc": preprocess_doc[1],
|
||||
"return_doc": preprocess_doc[2],
|
||||
},
|
||||
{
|
||||
"parameter_doc": postprocess_doc[1],
|
||||
"return_doc": postprocess_doc[2],
|
||||
},
|
||||
)
|
||||
cls_description, cls_tags, cls_example = document_cls(cls)
|
||||
cls_documentation = {
|
||||
"class": cls,
|
||||
"name": cls.__name__,
|
||||
"description": cls_description,
|
||||
"tags": cls_tags,
|
||||
"parameters": parameter_doc,
|
||||
"returns": return_doc,
|
||||
"example": cls_example,
|
||||
"fns": [],
|
||||
}
|
||||
if (
|
||||
hasattr(cls, "preprocess")
|
||||
and callable(cls.preprocess) # type: ignore
|
||||
and hasattr(cls, "postprocess")
|
||||
and callable(cls.postprocess) # type: ignore
|
||||
):
|
||||
cls_documentation["preprocess"] = preprocess_doc # type: ignore
|
||||
cls_documentation["postprocess"] = postprocess_doc # type: ignore
|
||||
for fn_name in fns:
|
||||
instance_attribute_fn = fn_name.startswith("*")
|
||||
if instance_attribute_fn:
|
||||
fn_name = fn_name[1:]
|
||||
# Instance attribute fns are classes
|
||||
# whose __call__ method determines their behavior
|
||||
fn = getattr(cls(), fn_name).__call__
|
||||
else:
|
||||
fn = getattr(cls, fn_name)
|
||||
if not callable(fn):
|
||||
description_doc = str(fn)
|
||||
parameter_docs = {}
|
||||
return_docs = {}
|
||||
examples_doc = ""
|
||||
override_signature = f"gr.{cls.__name__}.{fn_name}"
|
||||
else:
|
||||
(
|
||||
description_doc,
|
||||
parameter_docs,
|
||||
return_docs,
|
||||
examples_doc,
|
||||
) = document_fn(fn, cls)
|
||||
if fn_name in getattr(cls, "EVENTS", []):
|
||||
parameter_docs = parameter_docs[1:]
|
||||
override_signature = None
|
||||
if instance_attribute_fn:
|
||||
description_doc = extract_instance_attr_doc(cls, fn_name)
|
||||
cls_documentation["fns"].append(
|
||||
{
|
||||
"fn": fn,
|
||||
"name": fn_name,
|
||||
"description": description_doc,
|
||||
"tags": {},
|
||||
"parameters": parameter_docs,
|
||||
"returns": return_docs,
|
||||
"example": examples_doc,
|
||||
"override_signature": override_signature,
|
||||
}
|
||||
)
|
||||
documentation[mode].append(cls_documentation)
|
||||
if cls in classes_inherit_documentation:
|
||||
classes_inherit_documentation[cls] = cls_documentation["fns"]
|
||||
for mode, class_list in classes_to_document.items():
|
||||
for i, (cls, _) in enumerate(class_list):
|
||||
for super_class, fns in classes_inherit_documentation.items():
|
||||
if (
|
||||
inspect.isclass(cls)
|
||||
and issubclass(cls, super_class)
|
||||
and cls != super_class
|
||||
):
|
||||
for inherited_fn in fns:
|
||||
inherited_fn = dict(inherited_fn)
|
||||
try:
|
||||
inherited_fn["description"] = extract_instance_attr_doc(
|
||||
cls, inherited_fn["name"]
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
documentation[mode][i]["fns"].append(inherited_fn)
|
||||
return documentation
|
||||
@@ -0,0 +1,43 @@
|
||||
class SerializationSetupError(ValueError):
|
||||
"""Raised when a serializers cannot be set up correctly."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AuthenticationError(ValueError):
|
||||
"""Raised when the client is unable to authenticate itself to a Gradio app due to invalid or missing credentials."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(ValueError):
|
||||
"""Raised when the data that is passed into the Gradio app fails developer-defined validation."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AppError(ValueError):
|
||||
"""Raised when the upstream Gradio app throws an error because of the value submitted by the client."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Error raised.",
|
||||
duration: float | None = 10,
|
||||
visible: bool = True,
|
||||
title: str = "Error",
|
||||
print_exception: bool = True,
|
||||
):
|
||||
"""
|
||||
Parameters:
|
||||
message: The error message to be displayed to the user. Can be HTML, which will be rendered in the modal.
|
||||
duration: The duration in seconds to display the error message. If None or 0, the error message will be displayed until the user closes it.
|
||||
visible: Whether the error message should be displayed in the UI.
|
||||
title: The title to be displayed to the user at the top of the error modal.
|
||||
print_exception: Whether to print traceback of the error to the console when the error is raised.
|
||||
"""
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.duration = duration
|
||||
self.visible = visible
|
||||
self.print_exception = print_exception
|
||||
super().__init__(self.message)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "gradio_client",
|
||||
"version": "2.5.0",
|
||||
"description": "",
|
||||
"python": "true",
|
||||
"main_changeset": true,
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
"""Centralized code snippet generation for Gradio API endpoints. Generates Python, JavaScript, and cURL code snippets from API info dicts."""
|
||||
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
BLOB_COMPONENTS = {
|
||||
"Audio",
|
||||
"DownloadButton",
|
||||
"File",
|
||||
"Image",
|
||||
"ImageSlider",
|
||||
"Model3D",
|
||||
"UploadButton",
|
||||
"Video",
|
||||
}
|
||||
|
||||
|
||||
def _is_file_data(obj: Any) -> bool:
|
||||
return (
|
||||
isinstance(obj, dict)
|
||||
and "url" in obj
|
||||
and obj.get("url")
|
||||
and "meta" in obj
|
||||
and isinstance(obj.get("meta"), dict)
|
||||
and obj["meta"].get("_type") == "gradio.FileData"
|
||||
)
|
||||
|
||||
|
||||
def _has_file_data(obj: Any) -> bool:
|
||||
if isinstance(obj, dict):
|
||||
if _is_file_data(obj):
|
||||
return True
|
||||
return any(_has_file_data(v) for v in obj.values())
|
||||
if isinstance(obj, (list, tuple)):
|
||||
return any(_has_file_data(item) for item in obj)
|
||||
return False
|
||||
|
||||
|
||||
def _replace_file_data_py(obj: Any) -> Any:
|
||||
if isinstance(obj, dict) and _is_file_data(obj):
|
||||
return f"handle_file('{obj['url']}')"
|
||||
if isinstance(obj, (list, tuple)):
|
||||
return [_replace_file_data_py(item) for item in obj]
|
||||
if isinstance(obj, dict):
|
||||
return {k: _replace_file_data_py(v) for k, v in obj.items()}
|
||||
return obj
|
||||
|
||||
|
||||
def _simplify_file_data(obj: Any) -> Any:
|
||||
if isinstance(obj, dict) and _is_file_data(obj):
|
||||
return {"path": obj["url"], "meta": {"_type": "gradio.FileData"}}
|
||||
if isinstance(obj, (list, tuple)):
|
||||
return [_simplify_file_data(item) for item in obj]
|
||||
if isinstance(obj, dict):
|
||||
return {k: _simplify_file_data(v) for k, v in obj.items()}
|
||||
return obj
|
||||
|
||||
|
||||
_UNQUOTED = "UNQUOTED_GRADIO_"
|
||||
|
||||
|
||||
def _stringify_py(obj: Any) -> str:
|
||||
def _prepare(o: Any) -> Any:
|
||||
if o is None:
|
||||
return f"{_UNQUOTED}None"
|
||||
if isinstance(o, bool):
|
||||
return f"{_UNQUOTED}True" if o else f"{_UNQUOTED}False"
|
||||
if isinstance(o, str) and o.startswith("handle_file(") and o.endswith(")"):
|
||||
return f"{_UNQUOTED}{o}"
|
||||
if isinstance(o, (list, tuple)):
|
||||
return [_prepare(item) for item in o]
|
||||
if isinstance(o, dict):
|
||||
return {k: _prepare(v) for k, v in o.items()}
|
||||
return o
|
||||
|
||||
prepared = _prepare(obj)
|
||||
result = json.dumps(prepared, default=str)
|
||||
result = re.sub(
|
||||
rf'"{_UNQUOTED}(handle_file\([^)]*\))"',
|
||||
r"\1",
|
||||
result,
|
||||
)
|
||||
result = result.replace(f'"{_UNQUOTED}None"', "None")
|
||||
result = result.replace(f'"{_UNQUOTED}True"', "True")
|
||||
result = result.replace(f'"{_UNQUOTED}False"', "False")
|
||||
return result
|
||||
|
||||
|
||||
def _represent_value(value: Any, python_type: str | None, lang: str) -> str:
|
||||
if python_type is None:
|
||||
return "None" if lang == "py" else "null"
|
||||
if value is None:
|
||||
return "None" if lang == "py" else "null"
|
||||
if python_type in ("string", "str"):
|
||||
return f'"{value}"'
|
||||
if python_type == "number":
|
||||
return str(value)
|
||||
if python_type in ("boolean", "bool"):
|
||||
if lang == "py":
|
||||
return "True" if value else "False"
|
||||
return str(value).lower() if isinstance(value, bool) else str(value)
|
||||
if python_type == "List[str]":
|
||||
return json.dumps(value)
|
||||
if python_type.startswith("Literal['"):
|
||||
return f'"{value}"'
|
||||
|
||||
if isinstance(value, str):
|
||||
if value == "":
|
||||
return "None" if lang == "py" else "null"
|
||||
return value
|
||||
|
||||
value = copy.deepcopy(value)
|
||||
if lang == "bash":
|
||||
value = _simplify_file_data(value)
|
||||
if lang == "py":
|
||||
value = _replace_file_data_py(value)
|
||||
return _stringify_py(value)
|
||||
|
||||
|
||||
def _get_param_value(param: dict) -> Any:
|
||||
if param.get("parameter_has_default"):
|
||||
return param.get("parameter_default")
|
||||
return param.get("example_input")
|
||||
|
||||
|
||||
def generate_python_snippet(
|
||||
api_name: str,
|
||||
params: list[dict],
|
||||
src: str,
|
||||
) -> str:
|
||||
has_file = any(_has_file_data(p.get("example_input")) for p in params)
|
||||
imports = "from gradio_client import Client"
|
||||
if has_file:
|
||||
imports += ", handle_file"
|
||||
|
||||
lines = [imports, ""]
|
||||
lines.append(f'client = Client("{src}")')
|
||||
|
||||
predict_args = []
|
||||
for p in params:
|
||||
name = p.get("parameter_name") or p.get("label", "input")
|
||||
value = _get_param_value(p)
|
||||
ptype = p.get("python_type", {}).get("type")
|
||||
formatted = _represent_value(value, ptype, "py")
|
||||
predict_args.append(f"\t{name}={formatted},")
|
||||
|
||||
lines.append("result = client.predict(")
|
||||
lines.extend(predict_args)
|
||||
lines.append(f'\tapi_name="{api_name}",')
|
||||
lines.append(")")
|
||||
lines.append("print(result)")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_js_snippet(
|
||||
api_name: str,
|
||||
params: list[dict],
|
||||
src: str,
|
||||
) -> str:
|
||||
blob_params = [p for p in params if p.get("component") in BLOB_COMPONENTS]
|
||||
|
||||
lines = ['import { Client } from "@gradio/client";', ""]
|
||||
|
||||
for i, bp in enumerate(blob_params):
|
||||
example = bp.get("example_input", {})
|
||||
url = example.get("url", "") if isinstance(example, dict) else ""
|
||||
component = bp.get("component", "")
|
||||
lines.append(f'const response_{i} = await fetch("{url}");')
|
||||
lines.append(f"const example{component} = await response_{i}.blob();")
|
||||
|
||||
if blob_params:
|
||||
lines.append("")
|
||||
|
||||
lines.append(f'const client = await Client.connect("{src}");')
|
||||
|
||||
blob_component_names = {bp.get("component") for bp in blob_params}
|
||||
|
||||
predict_args = []
|
||||
for p in params:
|
||||
name = p.get("parameter_name") or p.get("label", "input")
|
||||
component = p.get("component", "")
|
||||
if component in blob_component_names:
|
||||
predict_args.append(f"\t\t{name}: example{component},")
|
||||
else:
|
||||
value = _get_param_value(p)
|
||||
ptype = p.get("python_type", {}).get("type")
|
||||
formatted = _represent_value(value, ptype, "js")
|
||||
predict_args.append(f"\t\t{name}: {formatted},")
|
||||
|
||||
lines.append(f'const result = await client.predict("{api_name}", {{')
|
||||
lines.extend(predict_args)
|
||||
lines.append("});")
|
||||
lines.append("")
|
||||
lines.append("console.log(result.data);")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_bash_snippet(
|
||||
api_name: str,
|
||||
params: list[dict],
|
||||
root: str,
|
||||
api_prefix: str = "/",
|
||||
) -> str:
|
||||
normalised_root = root.rstrip("/")
|
||||
normalised_prefix = api_prefix if api_prefix else "/"
|
||||
endpoint_name = api_name.lstrip("/")
|
||||
|
||||
has_file = any(_has_file_data(p.get("example_input")) for p in params)
|
||||
upload_url = f"{normalised_root}{normalised_prefix}upload"
|
||||
|
||||
lines: list[str] = []
|
||||
|
||||
file_param_names: list[str] = []
|
||||
if has_file:
|
||||
for p in params:
|
||||
if _has_file_data(p.get("example_input")):
|
||||
name = p.get("parameter_name") or p.get("label", "input")
|
||||
file_param_names.append(name)
|
||||
lines.append(
|
||||
f"FILE_PATH=$(curl -s -X POST {upload_url}"
|
||||
" -F 'files=@/path/to/your/file'"
|
||||
" | tr -d '[]\" ')"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
data_dict = {}
|
||||
for p in params:
|
||||
name = p.get("parameter_name") or p.get("label", "input")
|
||||
if name in file_param_names:
|
||||
data_dict[name] = "FILE_PATH_PLACEHOLDER"
|
||||
else:
|
||||
value = _get_param_value(p)
|
||||
ptype = p.get("python_type", {}).get("type")
|
||||
formatted = _represent_value(value, ptype, "bash")
|
||||
data_dict[name] = formatted
|
||||
|
||||
data_entries = ", ".join(f'"{k}": {v}' for k, v in data_dict.items())
|
||||
data_str = "{" + data_entries + "}"
|
||||
for _ in file_param_names:
|
||||
replacement = '{"path": "\'$FILE_PATH\'", "meta": {"_type": "gradio.FileData"}}'
|
||||
data_str = data_str.replace("FILE_PATH_PLACEHOLDER", replacement)
|
||||
|
||||
base_url = f"{normalised_root}{normalised_prefix}call/v2/{endpoint_name}"
|
||||
get_url = f"{normalised_root}{normalised_prefix}call/{endpoint_name}"
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
f'curl -X POST {base_url} -s -H "Content-Type: application/json" \\',
|
||||
f" -d '{data_str}' \\",
|
||||
" | awk -F'\"' '{ print $4}' \\",
|
||||
f" | read EVENT_ID; curl -N {get_url}/$EVENT_ID",
|
||||
]
|
||||
)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_code_snippets(
|
||||
api_name: str,
|
||||
endpoint_info: dict,
|
||||
root: str,
|
||||
space_id: str | None = None,
|
||||
api_prefix: str = "/",
|
||||
) -> dict[str, str]:
|
||||
params = endpoint_info.get("parameters", [])
|
||||
src = space_id or root
|
||||
|
||||
return {
|
||||
"python": generate_python_snippet(api_name, params, src),
|
||||
"javascript": generate_js_snippet(api_name, params, src),
|
||||
"bash": generate_bash_snippet(api_name, params, root, api_prefix),
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import asyncio
|
||||
import os
|
||||
import threading
|
||||
from threading import Event
|
||||
|
||||
import discord
|
||||
import gradio as gr
|
||||
from discord import Permissions
|
||||
from discord.ext import commands
|
||||
from discord.utils import oauth_url
|
||||
|
||||
import gradio_client as grc
|
||||
from gradio_client.utils import QueueError
|
||||
|
||||
event = Event()
|
||||
|
||||
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
||||
|
||||
|
||||
async def wait(job):
|
||||
while not job.done():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
|
||||
def get_client(session: str | None = None) -> grc.Client:
|
||||
client = grc.Client("<<app-src>>", token=os.getenv("HF_TOKEN"))
|
||||
if session:
|
||||
client.session_hash = session
|
||||
return client
|
||||
|
||||
|
||||
def truncate_response(response: str) -> str:
|
||||
ending = "...\nTruncating response to 2000 characters due to discord api limits."
|
||||
if len(response) > 2000:
|
||||
return response[: 2000 - len(ending)] + ending
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
bot = commands.Bot(command_prefix="/", intents=intents)
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
|
||||
synced = await bot.tree.sync()
|
||||
print(f"Synced commands: {', '.join([s.name for s in synced])}.")
|
||||
event.set()
|
||||
print("------")
|
||||
|
||||
|
||||
thread_to_client = {}
|
||||
thread_to_user = {}
|
||||
|
||||
|
||||
@bot.hybrid_command(
|
||||
name="<<command-name>>",
|
||||
description="Enter some text to chat with the bot! Like this: /<<command-name>> Hello, how are you?",
|
||||
)
|
||||
async def chat(ctx, prompt: str):
|
||||
if ctx.author.id == bot.user.id:
|
||||
return
|
||||
try:
|
||||
message = await ctx.send("Creating thread...")
|
||||
|
||||
thread = await message.create_thread(name=prompt)
|
||||
loop = asyncio.get_running_loop()
|
||||
client = await loop.run_in_executor(None, get_client, None)
|
||||
job = client.submit(prompt, api_name="/<<api-name>>")
|
||||
await wait(job)
|
||||
|
||||
try:
|
||||
job.result()
|
||||
response = job.outputs()[-1]
|
||||
await thread.send(truncate_response(response))
|
||||
thread_to_client[thread.id] = client
|
||||
thread_to_user[thread.id] = ctx.author.id
|
||||
except QueueError:
|
||||
await thread.send(
|
||||
"The gradio space powering this bot is really busy! Please try again later!"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"{e}")
|
||||
|
||||
|
||||
async def continue_chat(message):
|
||||
"""Continues a given conversation based on chathistory"""
|
||||
try:
|
||||
client = thread_to_client[message.channel.id]
|
||||
prompt = message.content
|
||||
job = client.submit(prompt, api_name="/<<api-name>>")
|
||||
await wait(job)
|
||||
try:
|
||||
job.result()
|
||||
response = job.outputs()[-1]
|
||||
await message.reply(truncate_response(response))
|
||||
except QueueError:
|
||||
await message.reply(
|
||||
"The gradio space powering this bot is really busy! Please try again later!"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_message(message):
|
||||
"""Continue the chat"""
|
||||
try:
|
||||
if not message.author.bot:
|
||||
if message.channel.id in thread_to_user:
|
||||
if thread_to_user[message.channel.id] == message.author.id:
|
||||
await continue_chat(message)
|
||||
else:
|
||||
await bot.process_commands(message)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
|
||||
# running in thread
|
||||
def run_bot():
|
||||
if not DISCORD_TOKEN:
|
||||
print("DISCORD_TOKEN NOT SET")
|
||||
event.set()
|
||||
else:
|
||||
bot.run(DISCORD_TOKEN)
|
||||
|
||||
|
||||
threading.Thread(target=run_bot).start()
|
||||
|
||||
event.wait()
|
||||
|
||||
if not DISCORD_TOKEN:
|
||||
welcome_message = """
|
||||
|
||||
## You have not specified a DISCORD_TOKEN, which means you have not created a bot account. Please follow these steps:
|
||||
|
||||
### 1. Go to https://discord.com/developers/applications and click 'New Application'
|
||||
|
||||
### 2. Give your bot a name 🤖
|
||||
|
||||

|
||||
|
||||
## 3. In Settings > Bot, click the 'Reset Token' button to get a new token. Write it down and keep it safe 🔐
|
||||
|
||||

|
||||
|
||||
## 4. Optionally make the bot public if you want anyone to be able to add it to their servers
|
||||
|
||||
## 5. Scroll down and enable 'Message Content Intent' under 'Priviledged Gateway Intents'
|
||||
|
||||

|
||||
|
||||
## 6. Save your changes!
|
||||
|
||||
## 7. The token from step 3 is the DISCORD_TOKEN. Rerun the deploy_discord command, e.g client.deploy_discord(discord_bot_token=DISCORD_TOKEN, ...), or add the token as a space secret manually.
|
||||
"""
|
||||
else:
|
||||
permissions = Permissions(326417525824)
|
||||
url = oauth_url(bot.user.id, permissions=permissions)
|
||||
welcome_message = f"""
|
||||
## Add this bot to your server by clicking this link:
|
||||
|
||||
{url}
|
||||
|
||||
## How to use it?
|
||||
|
||||
The bot can be triggered via `/<<command-name>>` followed by your text prompt.
|
||||
|
||||
This will create a thread with the bot's response to your text prompt.
|
||||
You can reply in the thread (without `/<<command-name>>`) to continue the conversation.
|
||||
In the thread, the bot will only reply to the original author of the command.
|
||||
|
||||
⚠️ Note ⚠️: Please make sure this bot's command does have the same name as another command in your server.
|
||||
|
||||
⚠️ Note ⚠️: Bot commands do not work in DMs with the bot as of now.
|
||||
"""
|
||||
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
gr.Markdown(
|
||||
f"""
|
||||
# Discord bot of <<app-src>>
|
||||
{welcome_message}
|
||||
"""
|
||||
)
|
||||
|
||||
demo.launch()
|
||||
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"SimpleSerializable": {
|
||||
"type": {},
|
||||
"description": "any valid value"
|
||||
},
|
||||
"StringSerializable": {
|
||||
"type": "string"
|
||||
},
|
||||
"ListStringSerializable": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"BooleanSerializable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"NumberSerializable": {
|
||||
"type": "number"
|
||||
},
|
||||
"ImgSerializable": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of an image"
|
||||
},
|
||||
"FileSerializable": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filepath on your computer (or URL) of file"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filepath on your computer (or URL) of file"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"SingleFileSerializable": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filepath on your computer (or URL) of file"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"MultipleFileSerializable": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filepath on your computer (or URL) of file"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"JSONSerializable": {
|
||||
"type": {},
|
||||
"description": "any valid json"
|
||||
},
|
||||
"GallerySerializable": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"maxSize": 2,
|
||||
"minSize": 2,
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "description": "name of file" },
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "base64 representation of file"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "size of image in bytes"
|
||||
},
|
||||
"is_file": {
|
||||
"type": "boolean",
|
||||
"description": "true if the file has been uploaded to the server"
|
||||
},
|
||||
"orig_name": {
|
||||
"type": "string",
|
||||
"description": "original name of the file"
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"]
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{ "type": "string", "description": "caption of image" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user