eienmojiki commited on
Commit
d5b5f42
·
1 Parent(s): 21bf97f
app.py CHANGED
@@ -1,187 +1,107 @@
 
 
1
  import gradio as gr
2
- import typing as ty
3
- import inspect
4
- import json # For potential JSON output formatting
5
 
6
- # Importing the tool files automatically registers them with the mcp_tool registry
7
- import tools.time_tool
 
 
 
8
 
9
- # Import the mcp_tool registry instance
10
- from utils.mcp_decorator import mcp_tool
11
 
12
- def update_tool_info(selected_api_name: str, *tool_group_visibility_states: gr.State) -> ty.List[gr.update]:
13
- """
14
- Updates the displayed docstring, visibility of tool UI groups, and
15
- visibility of execute/output areas based on the selected tool.
 
 
 
 
 
 
16
 
17
- Args:
18
- selected_api_name: The api_name of the tool selected in the dropdown.
19
- tool_group_visibility_states: The current state of visibility for each tool group component
20
- (passed automatically by Gradio when they are outputs).
 
21
 
22
- Returns:
23
- A list of gr.update objects for the docstring and each tool UI group.
 
 
 
24
  """
25
  updates = []
26
- tool_options = mcp_tool.get_tools_list() # List of (name, api_name)
27
-
28
- # Separate tool groups from other inputs received by the handler
29
- # The first len(tool_options) outputs are the tool UI groups
30
- tool_ui_groups = tool_ui_groups_and_inputs[:len(tool_options)]
31
-
32
- # 1. Update Docstring
33
- docstring_update = gr.Markdown.update(visible=False, value="") # Hide and clear by default
34
- if selected_api_name:
35
- tool_info = mcp_tool.get_tool_info(selected_api_name)
36
- if tool_info and tool_info.get('tool_func') and tool_info['tool_func'].__doc__:
37
- docstring_update = gr.Markdown.update(visible=True, value=f"### Tool Documentation\n---\n{tool_info['tool_func'].__doc__}\n---")
38
-
39
- updates.append(docstring_update)
40
-
41
- # 2. Update Visibility of Tool UI Groups
42
- # We need to return an update for *each* tool UI group defined in the layout.
43
- # The order must match the order they appear in the 'outputs' list of the .change() event.
44
- all_tools_api_names = [api_name for _, api_name in mcp_tool.get_tools_list()]
45
-
46
- # Ensure the order of updates matches the order of tool_uis_list created below
47
- # We'll assume the order from get_tools_list() is consistent.
48
-
49
- for api_name_in_list in all_tools_api_names:
50
- is_selected_tool = (api_name_in_list == selected_api_name)
51
- # Return gr.update(visible=...) for each tool group
52
- updates.append(gr.Group.update(visible=is_selected_tool))
53
-
54
- # The total number of updates returned must match the total number of outputs
55
- # defined in the dropdown.change() call.
56
- # Outputs are [doc_display, *list_of_tool_groups]
57
- # Inputs are [dropdown, *list_of_tool_groups]
58
- # This function signature with *tool_group_visibility_states receiving the
59
- # current state of the output components is how Gradio handles inputs/outputs
60
- # when the outputs themselves are also inputs for their own updates (like visibility).
61
- # However, for simple visibility toggling based *only* on the dropdown value,
62
- # we don't strictly *need* tool_group_visibility_states as input. We can just
63
- # return the updates based on the selected_api_name.
64
- # Let's simplify the function signature and inputs/outputs.
65
- # The function just needs selected_api_name. It will return the docstring update
66
- # and visibility updates for *all* groups.
67
-
68
- # Revised update_tool_info:
69
- # Input: selected_api_name (str)
70
- # Outputs: doc_display (Markdown), Tool_UI_Group_1 (Group), Tool_UI_Group_2 (Group), ...
71
-
72
- return updates # This list contains updates for docstring + all tool groups
73
 
74
- # --- Gradio App Layout ---
75
- with gr.Blocks(title="MCP Server Demo") as demo:
76
- gr.Markdown("# Gradio MCP Server Demo")
77
- gr.Markdown("Select a tool to view its documentation, configure parameters, and execute.")
78
-
79
- # Get defined tools
80
- tool_options = mcp_tool.get_tools_list() # Returns list of (name, api_name)
81
-
82
- if not tool_options:
83
- gr.Warning("No tools defined. Please check the 'tools' directory.")
84
- gr.Markdown("No tools available.")
85
- else:
86
- # Dropdown to select tool. Using api_name as value for easier lookup.
87
- dropdown = gr.Dropdown(
88
- choices=[(name, api_name) for name, api_name in tool_options],
89
- label="Select a Tool",
90
- interactive=True,
91
- value=None # Start with no tool selected
92
- )
93
 
94
- # Markdown component to display tool documentation
95
- # Needs a specific elem_id or be directly referenced as an output
96
- doc_display = gr.Markdown(label="Tool Documentation", visible=False)
97
-
98
- # Button to execute the tool (initially hidden)
99
- execute_button = gr.Button("Execute Tool", visible=False)
100
-
101
- # Area to display the API call signature (initially hidden)
102
- # Using gr.Code for a code-like appearance
103
- api_call_display = gr.Code(
104
- label="Use Via API (Example Call)",
105
- language="json", # Or "yaml" or "text"
106
- interactive=False,
107
- visible=False,
108
- value=""
109
- )
110
 
111
- # Area to display the tool's output (initially hidden)
112
- # Using gr.Textbox for simplicity, could use gr.JSON if outputs are dicts/lists
113
- tool_output_display = gr.Textbox(
114
- label="Tool Output",
115
- interactive=False,
116
- visible=False,
117
- value="",
118
- container=True # Wrap in a container for better spacing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  )
120
 
 
 
 
 
 
 
 
121
 
122
- # Container to hold dynamic UI controls.
123
- tool_uis_container = gr.Column()
124
-
125
- # List to hold the UI groups for each tool and map outputs
126
- tool_ui_groups_list = []
127
- all_input_components_list = []
128
-
129
- # Dynamically create UI components for each tool
130
- with tool_uis_container:
131
- # Process tools in a consistent order (matching mcp_tool.get_all_arg_components_list)
132
- # This ensures the order of components in all_input_components_list is predictable.
133
- for tool_name, api_name in sorted(mcp_tool.tools.items()): # Iterate registry items directly for order
134
- ui_builder = api_name.get('ui_builder')
135
- if ui_builder:
136
- # Call the UI builder function to get the components (should be a gr.Group/Column)
137
- tool_ui_group = ui_builder()
138
- # Ensure the group is initially hidden
139
- tool_ui_group.visible = False
140
- # Add the group to our list for output mapping
141
- tool_ui_groups_list.append(tool_ui_group)
142
-
143
- # Add the individual input components from this tool to the master list
144
- # Ensure order within a tool is consistent (e.g., alphabetical by arg name)
145
- for arg_name in sorted(arg_component_map.keys()):
146
- component = arg_component_map[arg_name]
147
- all_input_components_list.append(component)
148
-
149
- else:
150
- # Handle tools defined without a UI builder
151
- gr.Markdown(f"Tool '{tool_name}' ({api_name}) has no UI controls defined.", visible=False, elem_id=f"no_ui_{api_name}")
152
- # Add a placeholder to maintain output count consistency if necessary,
153
- # or ensure build_ui_control always returns a gr.Group (even empty).
154
- # Let's assume build_ui_control always returns a group/column.
155
- if api_name not in [g.elem_id for g in tool_ui_groups_list if hasattr(g, 'elem_id')]: # Avoid duplicates if builder called earlier
156
- # Create a dummy group if builder was missing or returned None unexpectedly
157
- with gr.Group(visible=False) as dummy_group:
158
- gr.Markdown(f"No UI for {tool_name}")
159
- tool_ui_groups_list.append(dummy_group)
160
-
161
-
162
- # --- Event Handling ---
163
- # When the dropdown selection changes, update the displayed info and controls
164
- dropdown.change(
165
- fn=update_tool_info,
166
- inputs=[dropdown], # Just the selected value from the dropdown
167
- outputs=[doc_display, *tool_ui_groups_list], # Docstring + all tool UI groups for visibility updates
168
- # Set queue=False for smoother UI updates if needed, depends on complexity
169
- # queue=False # might cause issues with visibility updates in complex layouts, test if needed
170
- )
171
-
172
- # Trigger an initial update if a default value is set or after loading
173
- # This ensures the UI is correct when the app first loads if a tool is pre-selected
174
- # or if you want to show the first tool's info by default.
175
- # Let's not trigger initially unless a tool is pre-selected in the dropdown.
176
- # If allow_clear=True and value is None, initial update won't show anything which is fine.
177
 
178
 
179
- # --- Launch the Gradio App as an MCP Server ---
180
  if __name__ == "__main__":
181
- print("Launching Gradio app with MCP server enabled...")
182
- demo.launch(
183
- server_name="0.0.0.0", # Required for Spaces
184
- mcp_server=True, # Enable the MCP server endpoint
185
- # share=True, # Default and recommended for Spaces
186
- )
187
- print("Gradio app launched.")
 
1
+ # app.py
2
+
3
  import gradio as gr
 
 
 
4
 
5
+ # IMPOR T ALL YOUR TOOLS HERE
6
+ # This is essential so the @tool decorators run and populate the registry
7
+ import tools.get_current_time
8
+ import tools.echo_text
9
+ # Import other tools as you create them: import tools.another_tool
10
 
11
+ from utils.tool_manager import get_tool_registry
 
12
 
13
+ # Get the registry after all tool files have been imported
14
+ TOOL_REGISTRY = get_tool_registry()
15
+
16
+ def get_tool_names():
17
+ """Returns a list of tool names for the dropdown."""
18
+ return [tool_info['name'] for tool_info in TOOL_REGISTRY.values()]
19
+
20
+ def get_tool_internal_names():
21
+ """Returns a list of internal function names for dropdown values."""
22
+ return list(TOOL_REGISTRY.keys())
23
 
24
+ def get_tool_ui_groups():
25
+ """Returns a list of the gr.Group components for each tool's UI."""
26
+ # The order must match the order of internal names from get_tool_internal_names
27
+ internal_names_order = get_tool_internal_names()
28
+ return [TOOL_REGISTRY[name]['ui_components']['group'] for name in internal_names_order]
29
 
30
+
31
+ def on_tool_select(selected_tool_internal_name):
32
+ """
33
+ Handles the tool selection dropdown change event.
34
+ Returns a list of gr.update objects to control visibility of tool UIs.
35
  """
36
  updates = []
37
+ # Iterate through all tools in the registry
38
+ for internal_name, tool_info in TOOL_REGISTRY.items():
39
+ ui_group = tool_info['ui_components']['group']
40
+ # Set visible to True only for the selected tool's group
41
+ updates.append(gr.update(visible=(internal_name == selected_tool_internal_name)))
42
+ return updates
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ # --- Gradio App Layout ---
46
+ with gr.Blocks() as app:
47
+ gr.Markdown("# Multi-Tool System")
48
+
49
+ # Tool Selection Dropdown
50
+ tool_dropdown = gr.Dropdown(
51
+ choices=get_tool_names(), # Display names
52
+ value=get_tool_names()[0] if get_tool_names() else None, # Select the first tool by default
53
+ label="Select a Tool",
54
+ interactive=True
55
+ )
 
 
 
 
 
56
 
57
+ # Dictionary to hold references to the UI components for each tool
58
+ tool_uis = {}
59
+
60
+ # Build and store UI components for each registered tool
61
+ # All UIs are built initially, but their containing gr.Group is hidden by the ui_builder
62
+ for internal_name, tool_info in TOOL_REGISTRY.items():
63
+ ui_builder = tool_info['ui_builder']
64
+ tool_func = tool_info['func']
65
+
66
+ # Build the UI components using the tool's ui_builder function
67
+ # The ui_builder MUST return (ui_group, inputs, outputs, button)
68
+ ui_group, inputs, outputs, button = ui_builder()
69
+
70
+ # Store references to the components
71
+ tool_uis[internal_name] = {
72
+ 'group': ui_group,
73
+ 'inputs': inputs,
74
+ 'outputs': outputs,
75
+ 'button': button
76
+ }
77
+
78
+ # Wire up the button click event for this tool
79
+ # Use the actual tool function as the handler
80
+ # Use api_name corresponding to the tool function's name
81
+ button.click(
82
+ fn=tool_func,
83
+ inputs=inputs,
84
+ outputs=outputs,
85
+ api_name=tool_func.__name__ # Required by the user
86
  )
87
 
88
+ # Link the dropdown change event to the function that updates UI visibility
89
+ # The outputs are the gr.Group components of ALL tools
90
+ tool_dropdown.change(
91
+ fn=on_tool_select,
92
+ inputs=tool_dropdown,
93
+ outputs=get_tool_ui_groups() # A list of all gr.Group components
94
+ )
95
 
96
+ # Initial UI setup: Trigger the on_tool_select for the default selected tool
97
+ # This makes the first tool's UI visible on load
98
+ app.load(
99
+ fn=on_tool_select,
100
+ inputs=tool_dropdown, # Pass the initial value of the dropdown
101
+ outputs=get_tool_ui_groups()
102
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
 
105
+ # Run the app
106
  if __name__ == "__main__":
107
+ app.launch()
 
 
 
 
 
 
tools/__pycache__/time_tool.cpython-312.pyc DELETED
Binary file (2.64 kB)
 
tools/echo_text.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # tools/echo_text.py
2
+
3
+ import gradio as gr
4
+ from utils.tool_manager import tool
5
+
6
+ def echo_ui():
7
+ """
8
+ Builds the Gradio UI components for the Echo Text tool.
9
+ Returns a tuple: (ui_group, input_components, output_components, button_component)
10
+ """
11
+ with gr.Group(visible=False) as ui_group: # Initially hidden
12
+ gr.Markdown("## Echo Text")
13
+ input_text = gr.Textbox(label="Enter text to echo", placeholder="Type something...")
14
+ output_text = gr.Textbox(label="Echoed Text", interactive=False)
15
+ run_button = gr.Button("Echo")
16
+ # Return the group, input(s), output(s), and the button
17
+ return ui_group, input_text, output_text, run_button
18
+
19
+ @tool(
20
+ name="Echo Text",
21
+ control_components=echo_ui # Pass the UI builder function
22
+ )
23
+ def echo_function(text: str) -> str:
24
+ """
25
+ Echoes the input text.
26
+
27
+ Args:
28
+ text (str): The input text.
29
+
30
+ Returns:
31
+ str: The input text.
32
+ """
33
+ return text
tools/{time_tool.py → get_current_time.py} RENAMED
@@ -1,15 +1,26 @@
 
 
 
1
  import datetime
2
  import pytz
3
- import typing as ty
4
- import gradio as gr
5
 
6
- # Import the decorator instance
7
- from utils.mcp_decorator import mcp_tool
8
-
9
- # --- Tool Definition ---
10
- @mcp_tool.define(
11
- name="Get current time",
12
- api_name="get_current_time",
 
 
 
 
 
 
 
 
 
13
  )
14
  def get_current_time(timezone: str = "UTC") -> str:
15
  """
@@ -26,6 +37,7 @@ def get_current_time(timezone: str = "UTC") -> str:
26
  str: The current time in the specified timezone, or an error message.
27
  """
28
  try:
 
29
  tz = pytz.timezone(timezone)
30
  now_utc = datetime.datetime.utcnow()
31
  now_in_tz = pytz.utc.localize(now_utc).astimezone(tz)
@@ -33,30 +45,4 @@ def get_current_time(timezone: str = "UTC") -> str:
33
  except pytz.UnknownTimeZoneError:
34
  return f"Error: Unknown timezone '{timezone}'. Please provide a valid IANA timezone name."
35
  except Exception as e:
36
- return f"An unexpected error occurred: {e}"
37
-
38
- # --- UI Control Builder ---
39
- @mcp_tool.build_ui_control(api_name="get_current_time")
40
- def build_time_ui_control() -> ty.Tuple[gr.Group, ty.Dict[str, gr.components.Component]]:
41
- """
42
- Builds Gradio UI components for the Get current time tool.
43
- Returns a tuple: (gr.Group containing controls, dict mapping arg name to component).
44
- """
45
- # Dictionary to hold the mapping from argument name (string) to component instance
46
- arg_components = {}
47
-
48
- with gr.Group(visible=False) as time_tool_group:
49
- gr.Markdown("Configure **Get current time** tool:")
50
- # Create a textbox for the 'timezone' argument
51
- # Store the component instance in the dictionary
52
- timezone_input = gr.Textbox(
53
- label="Timezone",
54
- value="UTC",
55
- placeholder="e.g., America/New_York, Asia/Ho_Chi_Minh",
56
- interactive=True,
57
- # elem_id=f"get_current_time_timezone_input" # Good practice for debugging, not strictly needed by logic
58
- )
59
- arg_components["timezone"] = timezone_input
60
-
61
- # Return the group and the dictionary mapping arg names to components
62
- return time_tool_group, arg_components
 
1
+ # tools/get_current_time.py
2
+
3
+ import gradio as gr
4
  import datetime
5
  import pytz
6
+ from utils.tool_manager import tool
 
7
 
8
+ def time_ui():
9
+ """
10
+ Builds the Gradio UI components for the Get Current Time tool.
11
+ Returns a tuple: (ui_group, input_components, output_components, button_component)
12
+ """
13
+ with gr.Group(visible=False) as ui_group: # Initially hidden
14
+ gr.Markdown("## Get Current Time")
15
+ timezone_input = gr.Textbox(label="Timezone (e.g., UTC, Asia/Ho_Chi_Minh)", value="UTC", placeholder="Enter IANA timezone name")
16
+ output_text = gr.Textbox(label="Current Time", interactive=False)
17
+ run_button = gr.Button("Get Time")
18
+ # Return the group, input(s), output(s), and the button
19
+ return ui_group, timezone_input, output_text, run_button
20
+
21
+ @tool(
22
+ name="Get Current Time",
23
+ control_components=time_ui # Pass the UI builder function
24
  )
25
  def get_current_time(timezone: str = "UTC") -> str:
26
  """
 
37
  str: The current time in the specified timezone, or an error message.
38
  """
39
  try:
40
+ # pytz requires the timezone string to be correct
41
  tz = pytz.timezone(timezone)
42
  now_utc = datetime.datetime.utcnow()
43
  now_in_tz = pytz.utc.localize(now_utc).astimezone(tz)
 
45
  except pytz.UnknownTimeZoneError:
46
  return f"Error: Unknown timezone '{timezone}'. Please provide a valid IANA timezone name."
47
  except Exception as e:
48
+ return f"An unexpected error occurred: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/__pycache__/mcp_decorator.cpython-312.pyc DELETED
Binary file (3.93 kB)
 
utils/mcp_decorator.py DELETED
@@ -1,107 +0,0 @@
1
- import functools
2
- import inspect
3
- import typing as ty
4
- import gradio as gr
5
-
6
- # Define a registry to store tools and their metadata
7
- class MCPToolRegistry:
8
- def __init__(self):
9
- # Stores tool data:
10
- # {'api_name': {
11
- # 'name': str,
12
- # 'tool_func': func,
13
- # 'ui_builder': func,
14
- # 'ui_group': gr.Group, # Store the created group instance
15
- # 'arg_component_map': {str: gr.components.Component} # Map arg name to component instance
16
- # }}
17
- self.tools = {}
18
-
19
- def define(self, name: str, api_name: str):
20
- """Decorator to define an MCP tool."""
21
- def decorator(tool_func: ty.Callable):
22
- if api_name in self.tools:
23
- raise ValueError(f"Tool with api_name '{api_name}' already defined.")
24
-
25
- # Store the tool function and metadata
26
- self.tools[api_name] = {
27
- 'name': name,
28
- 'tool_func': tool_func,
29
- 'ui_builder': None, # Will be filled by @build_ui_control
30
- 'ui_group': None, # Will be filled during UI construction
31
- 'arg_component_map': {}, # Will be filled during UI construction
32
- }
33
-
34
- # Gradio's MCP server needs the function itself to be decorated
35
- # with an attribute carrying the MCP metadata.
36
- setattr(tool_func, "__mcp_tool__", {"name": name, "api_name": api_name})
37
-
38
- return tool_func
39
- return decorator
40
-
41
- def build_ui_control(self, api_name: str):
42
- """
43
- Decorator to associate a UI builder function with a tool.
44
- The decorated function should return a tuple:
45
- (gr.Group or gr.Column component, dict[str, gr.components.Component])
46
- where the dict maps argument names (matching tool_func signature)
47
- to the corresponding input component instance.
48
- """
49
- def decorator(ui_builder_func: ty.Callable[..., ty.Tuple[ty.Union[gr.components.Component, tuple[gr.components.Component, ...]], ty.Dict[str, gr.components.Component]]]):
50
- if api_name not in self.tools:
51
- raise ValueError(f"Tool with api_name '{api_name}' not defined. Define it using @mcp_tool.define first.")
52
-
53
- # Store the UI builder function reference
54
- self.tools[api_name]['ui_builder'] = ui_builder_func
55
-
56
- # Note: The actual ui_group and arg_component_map will be populated
57
- # when app.py calls the ui_builder_func during Blocks setup.
58
-
59
- return ui_builder_func
60
- return decorator
61
-
62
- def get_tools_list(self) -> list[tuple[str, str]]:
63
- """Returns a list of (tool_name, api_name) for all defined tools."""
64
- return [(data['name'], api_name) for api_name, data in self.tools.items()]
65
-
66
- def get_tool_info(self, api_name: str) -> ty.Optional[dict]:
67
- """Returns the full info dict for a given api_name."""
68
- return self.tools.get(api_name)
69
-
70
- def get_tool_ui_builder(self, api_name: str) -> ty.Optional[ty.Callable]:
71
- """Returns the UI builder function for a given api_name."""
72
- info = self.get_tool_info(api_name)
73
- return info.get('ui_builder') if info else None
74
-
75
- def get_tool_function(self, api_name: str) -> ty.Optional[ty.Callable]:
76
- """Returns the tool function for a given api_name."""
77
- info = self.get_tool_info(api_name)
78
- return info.get('tool_func') if info else None
79
-
80
- def set_tool_ui_components(self, api_name: str, ui_group: gr.components.Component, arg_component_map: ty.Dict[str, gr.components.Component]):
81
- """Stores the actual UI group and component map after building."""
82
- if api_name not in self.tools:
83
- print(f"Warning: Tried to set UI components for undefined tool '{api_name}'")
84
- return
85
- self.tools[api_name]['ui_group'] = ui_group
86
- self.tools[api_name]['arg_component_map'] = arg_component_map
87
-
88
- def get_all_arg_components_list(self) -> ty.List[gr.components.Component]:
89
- """Returns a flat list of ALL input components from ALL tools, preserving order."""
90
- all_components = []
91
- # Iterate through tools in a consistent order (e.g., by api_name)
92
- for api_name in sorted(self.tools.keys()):
93
- tool_info = self.tools[api_name]
94
- # Iterate through the arg_component_map in a consistent order (e.g., by arg name)
95
- for arg_name in sorted(tool_info['arg_component_map'].keys()):
96
- component = tool_info['arg_component_map'][arg_name]
97
- all_components.append(component)
98
- return all_components
99
-
100
- def get_arg_component_map(self, api_name: str) -> ty.Optional[ty.Dict[str, gr.components.Component]]:
101
- """Returns the arg_component_map for a specific tool."""
102
- info = self.get_tool_info(api_name)
103
- return info.get('arg_component_map') if info else None
104
-
105
-
106
- # Instantiate the registry. This instance will be used by decorators
107
- mcp_tool = MCPToolRegistry()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/tool_manager.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/tool_manager.py
2
+
3
+ # A dictionary to store registered tools
4
+ # Key: Internal function name (str)
5
+ # Value: Dictionary { 'name': display_name (str), 'func': tool_function (callable), 'ui_builder': ui_builder_function (callable) }
6
+ _tool_registry = {}
7
+
8
+ def tool(name: str, control_components):
9
+ """
10
+ Decorator to register a tool function and its UI builder.
11
+
12
+ Args:
13
+ name (str): The display name of the tool in the UI dropdown.
14
+ control_components (callable): A function that builds the Gradio UI
15
+ components (input, output, button) for this tool.
16
+ This function should return a tuple of
17
+ (ui_group, input_components, output_components, button_component).
18
+ The ui_group should be a gr.Group or similar container
19
+ that holds all the tool's UI and whose visibility can be toggled.
20
+ """
21
+ def decorator(func):
22
+ # Store the function and its metadata in the registry
23
+ if func.__name__ in _tool_registry:
24
+ print(f"Warning: Tool '{func.__name__}' is already registered. Overwriting.")
25
+
26
+ _tool_registry[func.__name__] = {
27
+ "name": name,
28
+ "func": func,
29
+ "ui_builder": control_components
30
+ }
31
+ print(f"Registered tool: {name} (Internal name: {func.__name__})")
32
+ # Return the original function so it can be called
33
+ return func
34
+ return decorator
35
+
36
+ def get_tool_registry():
37
+ """Returns the dictionary of registered tools."""
38
+ return _tool_registry