|
|
|
|
|
import inspect |
|
import re |
|
|
|
|
|
|
|
|
|
_tool_registry = {} |
|
|
|
def tool(name: str, control_components): |
|
""" |
|
Decorator to register a tool function and its UI builder. |
|
|
|
Args: |
|
name (str): The display name of the tool in the UI dropdown. |
|
control_components (callable): A function that builds the Gradio UI |
|
components (input, output, button) for this tool. |
|
This function should return a tuple of |
|
(ui_group, input_components, output_components, button_component). |
|
The ui_group should be a gr.Group or similar container |
|
that holds all the tool's UI and whose visibility can be toggled. |
|
""" |
|
def decorator(func): |
|
|
|
if func.__name__ in _tool_registry: |
|
print(f"Warning: Tool '{func.__name__}' is already registered. Overwriting.") |
|
|
|
_tool_registry[func.__name__] = { |
|
"name": name, |
|
"func": func, |
|
"ui_builder": control_components |
|
} |
|
print(f"Registered tool: {name} (Internal name: {func.__name__})") |
|
|
|
return func |
|
return decorator |
|
|
|
def get_tool_registry(): |
|
"""Returns the dictionary of registered tools.""" |
|
return _tool_registry |
|
|
|
def format_docstring_as_markdown(docstring: str | None) -> str: |
|
""" |
|
Converts a standard Python function docstring into a Markdown-formatted string. |
|
|
|
Handles: |
|
- Formatting the first sentence/paragraph as a heading (##). |
|
- Bolding standard sections like "Args:", "Returns:", "Raises:". |
|
- Formatting items under Args/Returns as list items (-) with inline code (` `) |
|
for the parameter/return type part. |
|
- Preserving blank lines for paragraph breaks. |
|
- Converting single newlines within paragraphs/list items to Markdown line breaks |
|
(two spaces + newline) to ensure correct rendering. |
|
|
|
Args: |
|
docstring (str | None): The raw docstring string from a function's __doc__. |
|
|
|
Returns: |
|
str: The Markdown-formatted string suitable for gr.Markdown. |
|
Returns a default message if the input docstring is None or empty. |
|
""" |
|
if not docstring: |
|
return "" |
|
|
|
|
|
|
|
lines = inspect.cleandoc(docstring).splitlines() |
|
|
|
formatted_lines = [] |
|
in_params_section = False |
|
summary_done = False |
|
|
|
|
|
for i, line in enumerate(lines): |
|
stripped_line = line.strip() |
|
|
|
if not stripped_line: |
|
|
|
formatted_lines.append("") |
|
in_params_section = False |
|
continue |
|
|
|
if not summary_done: |
|
|
|
formatted_lines.append(f"## {stripped_line}") |
|
summary_done = True |
|
continue |
|
|
|
|
|
|
|
|
|
section_match = re.match(r"^(Args|Arguments|Params|Parameters|Returns|Return|Raises|Raise|Example|Examples|Note|Notes|Warning|Warnings|Todo|Todos):?\s*", stripped_line, re.IGNORECASE) |
|
|
|
if section_match: |
|
section_header = section_match.group(0).strip() |
|
section_name = section_match.group(1) |
|
|
|
|
|
header_text_bold = f"**{section_header.rstrip(':')}**:" |
|
formatted_lines.append(header_text_bold) |
|
|
|
|
|
if section_name.lower() in ['args', 'arguments', 'params', 'parameters', 'returns', 'return', 'raises', 'raise']: |
|
in_params_section = True |
|
else: |
|
in_params_section = False |
|
|
|
elif in_params_section and ':' in line: |
|
|
|
|
|
parts = line.split(':', 1) |
|
name_type_part = parts[0].strip() |
|
description_part = parts[1].strip() if len(parts) > 1 else "" |
|
|
|
|
|
|
|
formatted_line = f"- `{name_type_part}` : {description_part}" |
|
formatted_lines.append(formatted_line) |
|
|
|
elif in_params_section and ':' not in line and stripped_line: |
|
|
|
|
|
|
|
|
|
formatted_lines.append(line) |
|
|
|
else: |
|
|
|
|
|
formatted_lines.append(line) |
|
|
|
|
|
|
|
joined_docstring = "\n".join(formatted_lines) |
|
|
|
|
|
|
|
|
|
|
|
processed_docstring = re.sub(r'(?<!\n)\n(?!\n)', ' \n', joined_docstring) |
|
|
|
|
|
|
|
|
|
|
|
processed_docstring = processed_docstring.strip() |
|
|
|
|
|
return processed_docstring |